mirror of
https://github.com/Nathanwoodburn/FireWallet.git
synced 2024-12-27 16:28:14 +11:00
474 lines
14 KiB
Plaintext
474 lines
14 KiB
Plaintext
|
#!/usr/bin/env node
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
/**
|
||
|
* Module imports
|
||
|
*/
|
||
|
|
||
|
|
||
|
|
||
|
const Config = require('bcfg');
|
||
|
const { NodeClient, WalletClient } = require('hs-client');
|
||
|
const { hd, MTX, Network } = require('hsd');
|
||
|
const { Rules } = require('hsd/lib/covenants');
|
||
|
const { hashName, types } = Rules;
|
||
|
const { util, HID, LedgerHSD, LedgerChange, LedgerCovenant } = require('..');
|
||
|
const { Device } = HID;
|
||
|
|
||
|
/**
|
||
|
* Global constants
|
||
|
*/
|
||
|
|
||
|
const VALID_CMDS = [
|
||
|
'createwallet',
|
||
|
'createaccount',
|
||
|
'createaddress',
|
||
|
'sendtoaddress',
|
||
|
'getwallets',
|
||
|
'getaccounts',
|
||
|
'getaccount',
|
||
|
'getbalance',
|
||
|
'sendraw'
|
||
|
];
|
||
|
|
||
|
const VERSION = require('../package').version;
|
||
|
|
||
|
async function createWallet(client, config, ledger, args) {
|
||
|
if (args.length !== 1)
|
||
|
throw new Error('Invalid arguments');
|
||
|
|
||
|
const id = args[0];
|
||
|
const index = config.uint('account-index');
|
||
|
const xpub = await ledger.getAccountXPUB(index);
|
||
|
const wallet = await client.createWallet(id, {
|
||
|
watchOnly: true,
|
||
|
accountKey: xpub.xpubkey(config.str('network'))
|
||
|
});
|
||
|
|
||
|
console.log(`Created wallet (id=hsd-ledger, wid=${wallet.wid}).`);
|
||
|
console.log(`Created account (name=default, account-index=${index}').`);
|
||
|
}
|
||
|
|
||
|
async function createAccount(client, config, ledger, args) {
|
||
|
if (args.length !== 2)
|
||
|
throw new Error('Invalid arguments');
|
||
|
|
||
|
const id = config.str('wallet-id');
|
||
|
const name = args[0];
|
||
|
const index = parseInt(args[1], 10);
|
||
|
const xpub = await ledger.getAccountXPUB(index);
|
||
|
await client.createAccount(id, name, {
|
||
|
watchOnly: true,
|
||
|
accountKey: xpub.xpubkey(config.str('network'))
|
||
|
});
|
||
|
|
||
|
console.log(`Created account (name=${name}, account-index=${index}').`);
|
||
|
}
|
||
|
|
||
|
async function createAddress(client, config, ledger, args) {
|
||
|
if (args.length !== 0)
|
||
|
throw new Error('Invalid arguments');
|
||
|
|
||
|
const id = config.str('wallet-id');
|
||
|
const name = config.str('account-name');
|
||
|
const addr = await client.createAddress(id, name);
|
||
|
const acct = await client.getAccount(id, name);
|
||
|
const xpub = hd.PublicKey.fromBase58(acct.accountKey, config.str('network'));
|
||
|
const account = (xpub.childIndex ^ hd.common.HARDENED) >>> 0;
|
||
|
|
||
|
console.log(`Verify address on Ledger device: ${addr.address}`);
|
||
|
|
||
|
await ledger.getAddress(account, addr.branch, addr.index, { confirm: true });
|
||
|
}
|
||
|
|
||
|
async function sendToAddress(wclient, nclient, config, ledger, args) {
|
||
|
if (args.length !== 2)
|
||
|
throw new Error('Invalid arguments');
|
||
|
|
||
|
const network = Network.get(config.str('network'));
|
||
|
const id = config.str('wallet-id');
|
||
|
const acct = config.str('account-name');
|
||
|
const address = args[0];
|
||
|
const amount = parseFloat(args[1]);
|
||
|
|
||
|
await wclient.execute('selectwallet', [id]);
|
||
|
|
||
|
const params = [address, amount, '', '', false, acct];
|
||
|
const json = await wclient.execute('createsendtoaddress', params);
|
||
|
const mtx = MTX.fromJSON(json);
|
||
|
let i, key;
|
||
|
|
||
|
for (i = mtx.outputs.length - 1; i >= 0; i--) {
|
||
|
const output = mtx.outputs[i];
|
||
|
const addr = output.address.toString(network.type);
|
||
|
key = await wclient.getKey(id, addr);
|
||
|
|
||
|
if (key && key.branch)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (!key || !key.branch)
|
||
|
throw new Error('Expected change address.');
|
||
|
|
||
|
const { account, branch, index } = key;
|
||
|
const coinType = network.keyPrefix.coinType;
|
||
|
const options = {
|
||
|
change: new LedgerChange({
|
||
|
path: `m/44'/${coinType}'/${account}'/${branch}/${index}`,
|
||
|
index: i,
|
||
|
version: 0
|
||
|
})
|
||
|
};
|
||
|
|
||
|
util.displayDetails(console, network, mtx, options);
|
||
|
|
||
|
const signed = await ledger.signTransaction(mtx, options);
|
||
|
const rawtx = signed.encode().toString('hex');
|
||
|
const txid = await nclient.execute('sendrawtransaction', [rawtx]);
|
||
|
|
||
|
console.log(`Submitted TXID: ${txid}`);
|
||
|
}
|
||
|
async function sendRaw(wclient, nclient, config, ledger, args) { // Create a function to sign raw transactions
|
||
|
if (args.length !== 2) // Make sure there are two arguments (batch, names)
|
||
|
throw new Error('Invalid arguments'); // Throw an error if there are not two arguments
|
||
|
|
||
|
const network = Network.get(config.str('network')); // Get the network
|
||
|
const id = config.str('wallet-id'); // Get the wallet id
|
||
|
const acct = config.str('account-name'); // Get the account name
|
||
|
const batch = args[0]; // Get the batch file location
|
||
|
const nameslocation = args[1]; // Get the names file location
|
||
|
|
||
|
await wclient.execute('selectwallet', [id]); // Select the wallet
|
||
|
|
||
|
|
||
|
const fs = require('fs'); // Import fs (used to read files)
|
||
|
try {
|
||
|
const data = fs.readFileSync(batch, 'utf8'); // Read the batch file
|
||
|
const json = JSON.parse(data); // Parse the batch file as JSON
|
||
|
const mtx = MTX.fromJSON(json.result); // Create a new MTX from the JSON
|
||
|
|
||
|
const namefile = fs.readFileSync(nameslocation, 'utf8'); // Read the names file
|
||
|
const names = namefile.split(','); // Split the names file into an array
|
||
|
const hashes = {}; // Create an empty object to store the hashes
|
||
|
for (const name of names) { // Loop through the names
|
||
|
const hash = hashName(name); // Hash the name
|
||
|
hashes[hash] = name; // Add the hash to the hashes object to use later
|
||
|
}
|
||
|
|
||
|
|
||
|
let i, key; // Create variables to use later
|
||
|
const options = []; // Create an empty array to store the options
|
||
|
for (i = mtx.outputs.length - 1; i >= 0; i--) { // Loop through the outputs
|
||
|
const output = mtx.outputs[i]; // Get the output
|
||
|
const addr = output.address.toString(network.type); // Get the address
|
||
|
key = await wclient.getKey(id, addr); // Get the key
|
||
|
if (!key) // If there is no key
|
||
|
continue; // Continue to the next output
|
||
|
if (key.branch === 1) { // If the key is a change address
|
||
|
if (options.change) // If there is already a change address
|
||
|
throw new Error('Transaction should only have one change output.'); // Throw an error
|
||
|
const path = `m/44'/${network.keyPrefix.coinType}'/${key.account}'/${key.branch}/${key.index}`; // Create the derivation path
|
||
|
options.change = new LedgerChange({ path: `m/44'/${network.keyPrefix.coinType}'/${key.account}'/${key.branch}/${key.index}`, index: i, version: 0 }); // Add the change address to the options
|
||
|
}
|
||
|
const { account, branch, index } = key; // Get the account, branch, and index from the key
|
||
|
const coinType = network.keyPrefix.coinType; // Get the coin type from the network
|
||
|
switch (output.covenant.type) {
|
||
|
case types.NONE:
|
||
|
case types.OPEN:
|
||
|
case types.BID:
|
||
|
case types.FINALIZE:
|
||
|
break;
|
||
|
case types.REVEAL:
|
||
|
case types.REDEEM:
|
||
|
case types.REGISTER:
|
||
|
case types.UPDATE:
|
||
|
case types.RENEW:
|
||
|
case types.TRANSFER:
|
||
|
case types.REVOKE: { // If the covenant type is any of REVEAL, REDEEM, REGISTER, UPDATE, RENEW, TRANSFER, or REVOKE
|
||
|
if (options.covenants == null) // If there are no covenants
|
||
|
options.covenants = []; // Create an empty array for the covenants
|
||
|
const hash = output.covenant.items[0]; // Get the hash from the covenant
|
||
|
const name = hashes[hash]; // Get the name from the hashes object (is needed for SPV nodes)
|
||
|
if (name == undefined) { // If the name is not found
|
||
|
console.log("Name not found in file"); // Log that the name was not found
|
||
|
console.log(hash); // Log the hash (for debugging)
|
||
|
}
|
||
|
options.covenants.push(new LedgerCovenant({ index: i, name })); // Add the covenant to the options
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
default:
|
||
|
throw new Error('Unrecognized covenant type.');
|
||
|
|
||
|
}
|
||
|
|
||
|
} // end for loop
|
||
|
util.displayDetails(console, network, mtx, options); // Display the details to the log for user verification
|
||
|
const signed = await ledger.signTransaction(mtx, options); // Sign the transaction with the ledger
|
||
|
const rawtx = signed.encode().toString('hex'); // Encode the transaction as hex
|
||
|
const txid = await nclient.execute('sendrawtransaction', [rawtx]); // Send the transaction to the network
|
||
|
console.log(`Submitted TXID: ${txid}`); // Log the TXID to the console to view the transaction on a block explorer
|
||
|
|
||
|
} catch (err) { // Catch any errors
|
||
|
console.error(err); // Log the error to the console
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
async function getWallets(client, args) {
|
||
|
if (args.length)
|
||
|
throw new Error('Too many arguments');
|
||
|
|
||
|
const wallets = await client.getWallets();
|
||
|
|
||
|
console.log(wallets);
|
||
|
}
|
||
|
|
||
|
async function getAccounts(client, config, args) {
|
||
|
if (args.length)
|
||
|
throw new Error('Too many arguments');
|
||
|
|
||
|
const id = config.str('wallet-id');
|
||
|
const accounts = await client.getAccounts(id);
|
||
|
|
||
|
console.log(accounts);
|
||
|
}
|
||
|
|
||
|
async function getAccount(client, config, args) {
|
||
|
if (args.length !== 1)
|
||
|
throw new Error('Invalid arguments');
|
||
|
|
||
|
const id = config.str('wallet-id');
|
||
|
const name = args[0];
|
||
|
const account = await client.getAccount(id, name);
|
||
|
|
||
|
console.log(account);
|
||
|
}
|
||
|
|
||
|
async function getBalance(client, config, args) {
|
||
|
if (args.length !== 0)
|
||
|
throw new Error('Invalid arguments');
|
||
|
|
||
|
const id = config.str('wallet-id');
|
||
|
const name = config.str('account-name');
|
||
|
const balance = await client.getBalance(id, name);
|
||
|
|
||
|
console.log(balance);
|
||
|
}
|
||
|
|
||
|
async function main() {
|
||
|
const device = await Device.requestDevice();
|
||
|
device.set({
|
||
|
timeout: 300000 // 5 minutes.
|
||
|
});
|
||
|
|
||
|
await device.open();
|
||
|
|
||
|
const config = new Config('hsd', {
|
||
|
suffix: 'network',
|
||
|
fallback: 'main',
|
||
|
alias: {
|
||
|
'h': 'help',
|
||
|
'i': 'accountindex',
|
||
|
'a': 'accountname',
|
||
|
'n': 'network',
|
||
|
'p': 'projectsponsor',
|
||
|
'q': 'qrcode',
|
||
|
'k': 'showkey',
|
||
|
'w': 'walletid'
|
||
|
}
|
||
|
});
|
||
|
|
||
|
config.load({
|
||
|
argv: true,
|
||
|
env: true
|
||
|
});
|
||
|
|
||
|
config.inject({
|
||
|
accountIndex: 0,
|
||
|
accountName: 'default',
|
||
|
network: 'main',
|
||
|
walletId: 'hsd-ledger',
|
||
|
token: ''
|
||
|
});
|
||
|
|
||
|
const argv = config.argv;
|
||
|
const cmd = argv.shift();
|
||
|
const args = argv;
|
||
|
const type = config.str('network');
|
||
|
const network = Network.get(type);
|
||
|
const id = config.str('wallet-id');
|
||
|
const token = config.str('token');
|
||
|
|
||
|
if (config.str('help') && argv.length === 0) {
|
||
|
usage();
|
||
|
process.exit(0);
|
||
|
}
|
||
|
|
||
|
if (config.str('version') && argv.length === 0) {
|
||
|
version();
|
||
|
process.exit(0);
|
||
|
}
|
||
|
|
||
|
const ledger = new LedgerHSD({
|
||
|
device: device,
|
||
|
network: type
|
||
|
});
|
||
|
|
||
|
const nclient = new NodeClient({
|
||
|
url: config.str('url') || config.str('node-url'),
|
||
|
apiKey: config.str('api-key') || config.str('node-api-key'),
|
||
|
ssl: config.bool('ssl') || config.str('node-ssl'),
|
||
|
host: config.str('http-host') || config.str('node-http-host'),
|
||
|
port: config.uint('node-http-port') || network.rpcPort
|
||
|
});
|
||
|
|
||
|
const wclient = new WalletClient({
|
||
|
url: config.str('url') || config.str('wallet-url'),
|
||
|
apiKey: config.str('api-key') || config.str('wallet-api-key'),
|
||
|
ssl: config.bool('ssl') || config.bool('wallet-ssl'),
|
||
|
host: config.str('http-host') || config.str('wallet-http-host'),
|
||
|
port: config.uint('wallet-http-port') || network.walletPort,
|
||
|
token
|
||
|
});
|
||
|
|
||
|
await nclient.open();
|
||
|
await wclient.open();
|
||
|
|
||
|
try {
|
||
|
const wallets = await wclient.getWallets();
|
||
|
|
||
|
if (!wallets.includes(id)) {
|
||
|
if (id !== 'hsd-ledger')
|
||
|
throw new Error(`Wallet "${id}" does not exist.`);
|
||
|
|
||
|
console.log('Default hsd-ledger wallet not detected.');
|
||
|
|
||
|
await createWallet(wclient, config, ledger, [id]);
|
||
|
}
|
||
|
|
||
|
switch (cmd) {
|
||
|
case VALID_CMDS[0]:
|
||
|
await createWallet(wclient, config, ledger, args);
|
||
|
break;
|
||
|
|
||
|
case VALID_CMDS[1]:
|
||
|
await createAccount(wclient, config, ledger, args);
|
||
|
break;
|
||
|
|
||
|
case VALID_CMDS[2]:
|
||
|
await createAddress(wclient, config, ledger, args);
|
||
|
break;
|
||
|
|
||
|
case VALID_CMDS[3]:
|
||
|
await sendToAddress(wclient, nclient, config, ledger, args);
|
||
|
break;
|
||
|
|
||
|
case VALID_CMDS[4]:
|
||
|
await getWallets(wclient, args);
|
||
|
break;
|
||
|
|
||
|
case VALID_CMDS[5]:
|
||
|
await getAccounts(wclient, config, args);
|
||
|
break;
|
||
|
|
||
|
case VALID_CMDS[6]:
|
||
|
await getAccount(wclient, config, args);
|
||
|
break;
|
||
|
|
||
|
case VALID_CMDS[7]:
|
||
|
await getBalance(wclient, config, args);
|
||
|
break;
|
||
|
|
||
|
case VALID_CMDS[8]:
|
||
|
await sendRaw(wclient, nclient, config, ledger, args);
|
||
|
break;
|
||
|
default:
|
||
|
usage(new Error('Must provide valid command.'));
|
||
|
process.exit(1);
|
||
|
break;
|
||
|
}
|
||
|
} catch (e) {
|
||
|
throw (e);
|
||
|
} finally {
|
||
|
await wclient.close();
|
||
|
await nclient.close();
|
||
|
await device.close();
|
||
|
}
|
||
|
|
||
|
process.exit(0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Displays application version.
|
||
|
*/
|
||
|
|
||
|
function version() {
|
||
|
console.log(`hsd-ledger v${VERSION}`);
|
||
|
console.log('');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Displays usage or error message.
|
||
|
* @param {String|Error} err - the error message or object
|
||
|
*/
|
||
|
|
||
|
function usage(err) {
|
||
|
if (err) {
|
||
|
console.error(`${err.stack}`);
|
||
|
console.error('');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
console.log('usage:');
|
||
|
console.log(' $ hsd-ledger createwallet <wallet-id>');
|
||
|
console.log(' $ hsd-ledger createaccount <account-name> <account-index>');
|
||
|
console.log(' $ hsd-ledger createaddress');
|
||
|
console.log(' $ hsd-ledger sendtoaddress <address> <amount>');
|
||
|
console.log(' $ hsd-ledger getwallets');
|
||
|
console.log(' $ hsd-ledger getaccounts');
|
||
|
console.log(' $ hsd-ledger getaccount <account-name>');
|
||
|
console.log(' $ hsd-ledger getbalance');
|
||
|
console.log('');
|
||
|
console.log('options:');
|
||
|
console.log(' --help');
|
||
|
console.log(' --version');
|
||
|
console.log(' -n, --network <id> (default "main")');
|
||
|
console.log(' -w, --wallet-id <id> (default "hsd-ledger")');
|
||
|
console.log(' -a, --account-name <name> (default "default")');
|
||
|
console.log(' -i, --account-index <index> (default 0)');
|
||
|
console.log('');
|
||
|
console.log('The following options configure the node and wallet clients:');
|
||
|
console.log(' --ssl');
|
||
|
console.log(' --url <url>');
|
||
|
console.log(' --api-key <api-key>');
|
||
|
console.log(' --host <host> (default "localhost")');
|
||
|
console.log('');
|
||
|
console.log('The following options configure the node client only:');
|
||
|
console.log(' --node-ssl');
|
||
|
console.log(' --node-url <url>');
|
||
|
console.log(' --node-api-key <api-key>');
|
||
|
console.log(' --node-host <host> (default "localhost")');
|
||
|
console.log(' --node-port <port> (default 14037)');
|
||
|
console.log('');
|
||
|
console.log('The following options configure the wallet client only:');
|
||
|
console.log(' --wallet-ssl');
|
||
|
console.log(' --wallet-url <url>');
|
||
|
console.log(' --wallet-api-key <api-key>');
|
||
|
console.log(' --wallet-host <host> (default "localhost")');
|
||
|
console.log(' --wallet-port <port> (default 14039)');
|
||
|
console.log('');
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Execute
|
||
|
*/
|
||
|
|
||
|
main().catch((err) => {
|
||
|
usage(err);
|
||
|
process.exit(1);
|
||
|
});
|