|
|
@ -283,3 +283,203 @@ await spend1.fund([coin], { |
|
|
|
}); |
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Using Wallet API |
|
|
|
While it's possible to use `bcoin` for manually constructing transaction with just private keys, it's not |
|
|
|
convenient to handle all logic manually, we even skipped HD wallet parts. So if you have node running |
|
|
|
and you have access to it via HTTP, you can use `bcoin.http.Client` and `bcoin.http.Wallet`. These classes |
|
|
|
provide all API methods described on bcoin and will communicate to running nodes Wallets. |
|
|
|
|
|
|
|
*NOTE: You can check [API Docs][API-DOCS] |
|
|
|
|
|
|
|
### Step 1: Address Creation |
|
|
|
In this step we'll create two new wallets for two cosigners. In this demo, they will exist on same node, |
|
|
|
but it shouldn't matter if these two wallets are on the same node or not. |
|
|
|
|
|
|
|
```js |
|
|
|
'use strict'; |
|
|
|
|
|
|
|
const assert = require('assert'); |
|
|
|
const bcoin = require('../bcoin'); |
|
|
|
const {Client, Wallet} = bcoin.http; |
|
|
|
|
|
|
|
const network = 'regtest'; |
|
|
|
const m = 2; |
|
|
|
const n = 2; |
|
|
|
|
|
|
|
// Wrapper for skipping errors, when you rerun the script |
|
|
|
// It could have been as simple as |
|
|
|
// await client.createWallet(options); |
|
|
|
const createMultisigWallet = async function createMultisigWallet(client, options, skipExists) { |
|
|
|
assert(client instanceof Client, 'client should be bcoin.http.Client'); |
|
|
|
assert(options.id, 'You need to provide id in options'); |
|
|
|
|
|
|
|
const defaultOpts = { |
|
|
|
type: 'multisig', |
|
|
|
m: m, |
|
|
|
n: n |
|
|
|
}; |
|
|
|
|
|
|
|
Object.assign(defaultOpts, options); |
|
|
|
|
|
|
|
let res; |
|
|
|
try { |
|
|
|
res = await client.createWallet(defaultOpts); |
|
|
|
} catch (e) { |
|
|
|
if (skipExists && e.message === 'WDB: Wallet already exists.') { |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
throw e; |
|
|
|
} |
|
|
|
|
|
|
|
return res; |
|
|
|
}; |
|
|
|
|
|
|
|
// Wrapper for skipping errors, when you rerun the script |
|
|
|
// It could have been as simple as |
|
|
|
// await client.addSharedKey(account, xpubkey); |
|
|
|
const addSharedKey = async function addSharedKey(client, account, xpubkey, skipRemoveError) { |
|
|
|
assert(client instanceof Wallet, 'client should be bcoin.http.Wallet'); |
|
|
|
assert(account, 'should provide account'); |
|
|
|
assert(xpubkey, 'should provide xpubkey'); |
|
|
|
|
|
|
|
let res; |
|
|
|
|
|
|
|
try { |
|
|
|
res = await client.addSharedKey(account, xpubkey); |
|
|
|
} catch (e) { |
|
|
|
if (e.message === 'Cannot remove key.') { |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
throw e; |
|
|
|
} |
|
|
|
|
|
|
|
return res; |
|
|
|
}; |
|
|
|
|
|
|
|
(async () => { |
|
|
|
const client = new Client({ network }); |
|
|
|
|
|
|
|
// Let's create wallets if they don't exist |
|
|
|
await createMultisigWallet(client, { id: 'cosigner1' }, true); |
|
|
|
await createMultisigWallet(client, { id: 'cosigner2' }, true); |
|
|
|
|
|
|
|
// Initialize wallet http clients |
|
|
|
// They will be talking to Node's API |
|
|
|
const wallet1 = new Wallet({ id: 'cosigner1', network }); |
|
|
|
const wallet2 = new Wallet({ id: 'cosigner2', network }); |
|
|
|
|
|
|
|
// It wasn't necessary, but you can either create new |
|
|
|
// accounts under wallets and use them |
|
|
|
// or skip accounts when they are 'default' |
|
|
|
const wallet1account = 'default'; |
|
|
|
const wallet2account = 'default'; |
|
|
|
|
|
|
|
// Both wallets need to exchange XPUBKEYs to each other |
|
|
|
// in order to generate receiving and change addresses. |
|
|
|
// Let's take it from the default account. |
|
|
|
const wallet1info = await wallet1.getInfo(); |
|
|
|
const wallet2info = await wallet2.getInfo(); |
|
|
|
|
|
|
|
// Grab the xpubkey from wallet, we need to share them |
|
|
|
const wallet1xpubkey = wallet1info.account.accountKey; |
|
|
|
const wallet2xpubkey = wallet2info.account.accountKey; |
|
|
|
|
|
|
|
// Here we share xpubkeys to each other |
|
|
|
await addSharedKey(wallet1, wallet1account, wallet2xpubkey); |
|
|
|
await addSharedKey(wallet2, wallet2account, wallet1xpubkey); |
|
|
|
|
|
|
|
// Now we can get address from both wallets |
|
|
|
// NOTE: that both wallets should be on the same index |
|
|
|
// (depth) of derivation to geth the same addresses |
|
|
|
// NOTE: Each time you createAddress index(depth) is |
|
|
|
// incremented an new address is generated |
|
|
|
const address1 = await wallet1.createAddress(wallet1account); |
|
|
|
const address2 = await wallet2.createAddress(wallet2account); |
|
|
|
|
|
|
|
// Address for both shouuld be the same |
|
|
|
// Unless they were run separately. (Or by manually triggering API) |
|
|
|
console.log(address1); |
|
|
|
console.log(address2); |
|
|
|
})().catch((e) => { |
|
|
|
console.error(e); |
|
|
|
process.exit(1); |
|
|
|
}); |
|
|
|
``` |
|
|
|
|
|
|
|
You will notice that we grab the `.account.accountKey`, first key is the xpubkey |
|
|
|
and both will be using xpubkey key derivation to come up with new addresses. |
|
|
|
You won't need to share any other public keys, they will derive them for you. |
|
|
|
Depth of the account is the only thing you'll need to keep in mind. |
|
|
|
addSharedKey in wallet/account is used for adding cosigner xpubkeys keys. |
|
|
|
|
|
|
|
### Step 2: Generate Transaction |
|
|
|
We have received transaction |
|
|
|
``` |
|
|
|
Transaction ID: 3c12e1b260354fd2a2848030222c4a66339892f1d63b18752ff80ef4eb0197d2 |
|
|
|
Output index: 0 |
|
|
|
Amount: 100 BTC |
|
|
|
``` |
|
|
|
We are going to send `1 BTC` to `RBg1TLaNuRpH6UTFzogFXhjqubPYZaqWgs` on the regtest network. |
|
|
|
|
|
|
|
But we won't need this information, it will be automatically allocated from coins |
|
|
|
by bcoin node wallet service. |
|
|
|
|
|
|
|
```js |
|
|
|
'use strict'; |
|
|
|
|
|
|
|
const bcoin = require('../bcoin'); |
|
|
|
const {Client, Wallet} = bcoin.http; |
|
|
|
const Amount = bcoin.amount; |
|
|
|
|
|
|
|
const network = 'regtest'; |
|
|
|
const sendTo = 'RBg1TLaNuRpH6UTFzogFXhjqubPYZaqWgs'; |
|
|
|
|
|
|
|
(async () => { |
|
|
|
const client = new Client({ network }); |
|
|
|
const wallet1 = new Wallet({ id: 'cosigner1', network }); |
|
|
|
const wallet2 = new Wallet({ id: 'cosigner2', network }); |
|
|
|
|
|
|
|
// Because we can't sign and spend from account |
|
|
|
// we can't use `spend` and publish directly to network. |
|
|
|
// So we first create the transaction |
|
|
|
const outputs = [{ address: sendTo, value: Amount.fromBTC(1).toValue() }]; |
|
|
|
const options = { |
|
|
|
// rate: 1000, |
|
|
|
outputs: outputs |
|
|
|
}; |
|
|
|
|
|
|
|
// This will automatically find coins and fund the transaction (Sign it), |
|
|
|
// also create changeAddress and calculate fee |
|
|
|
const tx1 = await wallet1.createTX(options); |
|
|
|
|
|
|
|
// Now you can share this raw output |
|
|
|
const raw = tx1.hex; |
|
|
|
|
|
|
|
// Wallet2 will also sign the transaction |
|
|
|
const tx2 = await wallet2.sign(raw); |
|
|
|
|
|
|
|
// Now we can broadcast this transaction to the network |
|
|
|
const broadcast = await client.broadcast(tx2.hex); |
|
|
|
console.log(broadcast); |
|
|
|
})().catch((e) => { |
|
|
|
console.error(e); |
|
|
|
process.exit(1); |
|
|
|
}); |
|
|
|
``` |
|
|
|
|
|
|
|
Here you can see it's much cleaner and easier. |
|
|
|
We still need to manually, using other means, share |
|
|
|
raw transaction data for signing. |
|
|
|
|
|
|
|
`wallet1.createTX(options)` will automatically find the coins |
|
|
|
sent to the multisig wallet, allocate them for spending, |
|
|
|
send remaining - fee to change address and sign it. |
|
|
|
|
|
|
|
`wallet2.sign` will take raw transaction and sign it with according key. |
|
|
|
After that we can just broadcast the transaction to the network. |
|
|
|
|
|
|
|
[API-DOCS]: http://bcoin.io/api-docs/index.html |
|
|
|