-
Notifications
You must be signed in to change notification settings - Fork 1
/
offlinetransaction.js
112 lines (101 loc) · 3.32 KB
/
offlinetransaction.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#!/usr/bin/env node
/* Builds and signed an offline transaction that can be air gapped then
* transmitted by the p2p network without ever exposing your private keys.
*
* Accepts a destination address and a NYC amount. A raw transaction is
* built using enough unspent transactions to make up the desired amount.
* Change is sent back to the wallet using a change address.
*/
var config = require('config')
, async = require('async')
, _ = require('lodash');
var newyorkcoin = require('node-newyorkcoin')({
host: config.rpchost,
port: config.rpcport,
user: config.rpcuser,
pass: config.rpcpassword
});
if (process.argv.length != 4) {
console.error('Usage:', 'offlinetransaction.js', 'destination_address', 'amount');
process.exit(1);
}
var destination_address = process.argv[2]
, amount = Number(process.argv[3]);
console.log('Sending', amount, 'NYC to', destination_address);
async.waterfall([
function(next) {
/* Get a list of unspent transactions. Note that for this
* approach to work a recent copy of the block chain has to be
* copied on to the airgapped machine (perhaps via USB key). */
newyorkcoin.listUnspent(next);
},
function(unspent, next) {
/* Find enough transactions to make up desired payment amount. */
var sum = 0
, txs = [];
for (var i = 0; i < unspent.length && sum < amount; i++) {
sum += unspent[i].amount;
txs.push(unspent[i]);
}
if (sum < amount) {
next(new Error('Insufficient funds in wallet.'));
} else {
next(null, sum, txs);
}
},
function(sum, txs, next) {
/* Get a change address, assume that the wallet has
* an account with label change. */
newyorkcoin.getAccountaddress('change', function(err, change_address) {
if (err) {
next(err);
} else {
next(null, sum, txs, change_address);
}
});
},
function(sum, txs, change_address, next) {
var change = sum - amount;
/* Inputs are all the transactions required to cumulate
* to at least the desired payment amount. */
var inputs = _.map(txs, function(tx) {
return {
txid: tx.txid,
vout: Number(tx.vout)
};
});
/* Outputs are to the destination address and anything
* remaining is sent back the a wallet address associated
* with the "change" account. */
var outputs = {};
outputs[destination_address] = amount;
if (change > 0) {
outputs[change_address] = change;
}
console.log('Creating raw transaction...');
newyorkcoin.createRawTransaction(inputs, outputs, next);
},
function(unsigned_hex, next) {
console.log('Signing raw transaction...');
newyorkcoin.signRawTransaction(unsigned_hex, next);
},
function(signed, next) {
if (signed.complete) {
console.log('\nYour offline transaction is:');
console.log(signed.hex + '\n');
console.log('Move this across your air gap and send it to the p2p network using the sendRawTransaction newyorkcoind command.\n');
newyorkcoin.decodeRawTransaction(signed.hex, next);
} else {
next(new Error('Incomplete transaction.'));
}
},
function(details, next) {
console.log('Full transaction details:');
console.log(JSON.stringify(details, null, 4));
next();
}
], function(err) {
if (err) {
console.error(err.message);
}
});