Buck Perley
7 years ago
committed by
GitHub
12 changed files with 1391 additions and 48 deletions
@ -0,0 +1,499 @@ |
|||||
|
# Create a Crowdfunding Transaction |
||||
|
```post-author |
||||
|
Buck Perley |
||||
|
``` |
||||
|
|
||||
|
```post-description |
||||
|
Learn how SIGHASH flags work in Bitcoin by building out a custom crowdfunding transaction that lets anyone add their own inputs to a transaction with a fixed output. |
||||
|
``` |
||||
|
|
||||
|
## SIGHASH Flags |
||||
|
In most Bitcoin transactions, when a transaction is signed, the entirety of the data in that transaction is committed to in that input's signature, with all outputs and inputs being included in the unlocking script. This, however, doesn't always have to be the case. The level of commitment is indicated by using `SIGHASH` flags. The most common flag is `SIGHASH_ALL` which has a value of 0x01 (you'll see this represented as a `01` at the end of DER-encoded signatures). To learn more about how the flags work and what other options are available, you should checkout [Chapter 6](https://github.com/bitcoinbook/bitcoinbook/blob/8d01749bcf45f69f36cf23606bbbf3f0bd540db3/ch06.asciidoc) of *Mastering Bitcoin* by Andreas Antonopolous. In that chapter, Andreas posits one novel use of a specialized flag, `ALL|ANYONECANPAY`, and in this guide we'll try and build out a couple of examples implementing this idea using bcoin. |
||||
|
|
||||
|
## How it Works |
||||
|
The `ALL|ANYONECANPAY` flag indicates that a signature is committing all of the outputs and just one input. The suggested use case of this proposed in *Mastering Bitcoin* is a kickstarter-like crowdfunding application. Consider the situation where you have a fundraising goal, say 1BTC. You'd like multiple people to contribute to your goal and you want to prove to them that they won't have to pay *unless* you reach your goal (i.e. the transaction is invalid and won't be accepted by the network if attempted to be sent). This means you'd be committing to one output, the one that sends the funds to the charity or project you'd like to donate to, and only one input, your contribution. This allows multiple users to contribute to the same transaction and that transaction won't be valid until it's fully funded. |
||||
|
|
||||
|
Here's how it's explained in *Mastering Bitcoin*: |
||||
|
> ALL|ANYONECANPAY |
||||
|
This construction can be used to make a "crowdfunding”-style transaction. Someone attempting to raise funds can construct a transaction with a single output. The single output pays the "goal" amount to the fundraiser. Such a transaction is obviously not valid, as it has no inputs. However, others can now amend it by adding an input of their own, as a donation. They sign their own input with ALL|ANYONECANPAY. Unless enough inputs are gathered to reach the value of the output, the transaction is invalid. Each donation is a "pledge," which cannot be collected by the fundraiser until the entire goal amount is raised. |
||||
|
|
||||
|
## The Code |
||||
|
We'll walk through the steps of creating the transaction first without any wallet database or node running. Then we'll do the same thing using bcoin's walletdb to manage the keys to see how it would work in a more realistic application (skip to [Version 2](#version-2-using-the-bcoin-wallet-system)) further in the guide to check it out). At the end, we'll put out some ideas of how these can be built upon for a more robust, production ready application. (If this is something you'd be interested in building, [get in touch](http://bcoin.io/slack-signup.html)!). If you want to see the code, checkout the [repo on github](https://github.com/Bucko13/bitcoin-fundraise). |
||||
|
|
||||
|
If you're not comfortable with key management, coin selection, and how transactions are constructed, checkout the tutorial on [working with transactions](https://github.com/bcoin-org/bcoin/blob/master/docs/Working-with-transactions.md) first. |
||||
|
|
||||
|
### Version 1 - Manual Key Management |
||||
|
#### Step 1: Setup |
||||
|
Let's first start by importing the right tools, setting up some constants, and creating our keychains. (make sure you've installed the latest version of bcoin into your project with `npm install bcoin`). |
||||
|
|
||||
|
Note that we're setting the fundingTarget and amountToFund as constants for simplicity, but they could be set based on user input or some other variable circumstances. |
||||
|
|
||||
|
```javascript |
||||
|
'use strict'; |
||||
|
|
||||
|
const assert = require('assert'); |
||||
|
const bcoin = require('bcoin'); |
||||
|
|
||||
|
const MTX = bcoin.mtx; |
||||
|
const Keyring = bcoin.keyring; |
||||
|
const Outpoint = bcoin.outpoint; |
||||
|
const Script = bcoin.script; |
||||
|
const Coin = bcoin.coin; |
||||
|
const policy = bcoin.protocol.policy |
||||
|
|
||||
|
const fundingTarget = 100000000; // 1 BTC |
||||
|
const amountToFund = 50000000; // .5 BTC |
||||
|
const txRate = 10000; // 10000 satoshis/kb |
||||
|
``` |
||||
|
|
||||
|
Let's derive private hd key for fundee and two funders and create a "keyring" object for each (much of this is borrowed from the *Working with Transactions* guide). |
||||
|
|
||||
|
A keyring object is basically a key manager that is also able to tell you info such as: |
||||
|
- your redeem script |
||||
|
- your scripthash |
||||
|
- your program hash |
||||
|
- your pubkey hash |
||||
|
- your scripthash program hash |
||||
|
|
||||
|
```javascript |
||||
|
// Create an HD master keypair. |
||||
|
const master = bcoin.hd.generate(); |
||||
|
|
||||
|
const fundeeKey = master.derive(0); |
||||
|
const fundeeKeyring = new Keyring(fundeeKey.privateKey); |
||||
|
const fundeeAddress = fundeeKeyring.getAddress(); |
||||
|
|
||||
|
// Derive 2 more private hd keys and keyrings for funders |
||||
|
const funder1Key = master.derive(1); |
||||
|
const funder1Keyring = new Keyring(funder1Key.privateKey); |
||||
|
|
||||
|
const funder2Key = master.derive(2); |
||||
|
const funder2Keyring = new Keyring(funder2Key.privateKey); |
||||
|
|
||||
|
const funders = [funder1Keyring, funder2Keyring]; |
||||
|
``` |
||||
|
|
||||
|
#### Step 2: Fund the Keyrings |
||||
|
This example is working in an isolated environment, so it won't work on the actual network (main, test, or otherwise), but we also don't have to earn or send ourselves coins or wait for confirmation times. That means that we can "spawn" coins for our funder wallets that they can use to spend on the crowdfunding platform. |
||||
|
|
||||
|
Let's create some coinbase transactions to give our keyrings some coins that they can spend. |
||||
|
|
||||
|
```javascript |
||||
|
// create some coinbase transactions to fund our wallets |
||||
|
const coins = {}; |
||||
|
|
||||
|
for(let i=0; i < funders.length; i++) { |
||||
|
const cb = new MTX(); |
||||
|
|
||||
|
// Add a typical coinbase input |
||||
|
cb.addInput({ |
||||
|
prevout: new Outpoint(), |
||||
|
script: new Script() |
||||
|
}); |
||||
|
|
||||
|
cb.addOutput({ |
||||
|
address: funders[i].getAddress(), |
||||
|
value: 500000000 // give the funder 5BTC |
||||
|
}); |
||||
|
|
||||
|
assert(cb.inputs[0].isCoinbase()); |
||||
|
|
||||
|
// Convert the coinbase output to a Coin |
||||
|
// object and add it to the available coins for that keyring. |
||||
|
// In reality you might get these coins from a wallet. |
||||
|
coins[i] = [Coin.fromTX(cb, 0, -1)]; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
The coins object you've created above should look something like this: |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"0": |
||||
|
[ |
||||
|
{ |
||||
|
"type": "pubkeyhash", |
||||
|
"version": 1, |
||||
|
"height": -1, |
||||
|
"value": "5.0", |
||||
|
"script": <Script: OP_DUP OP_HASH160 0x14 0x64cc4e55b2daec25431bd879ef39302a77c1c1ce OP_EQUALVERIFY OP_CHECKSIG>, |
||||
|
"coinbase": true, |
||||
|
"hash": "151e5551cdcec5fff06818fb78ac3d584361276e862b5700110ec8321869d650", |
||||
|
"index": 0, |
||||
|
"address": <Address: type=pubkeyhash version=-1 str=mphvcfcFneRZvyYsmzhy57cSDzFbGrWaRb> |
||||
|
} |
||||
|
], |
||||
|
"1": [...] |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### Step 4: Prepare your Coins |
||||
|
Above, we just funded our funder accounts with a single 5BTC outpoint. This means that the next transaction funded from these accounts can only use that one outpoint (or *coin*) as an input and send the remainder back as change. Remember, in Bitcoin the way you send funds is you fund a transaction with a full UTXO (in this case we only have one worth 5BTC available to our keychains) and then send the change back to yourself as an additional output. Since ALL|ANYONECANPAY transactions mean a fixed output, you can't add new change outputs without other signatures becoming invalid which means we need a coin available equal to the amount we want to contribute to the crowdfund. |
||||
|
|
||||
|
So what we want to do is have each funder create a coin (UTXO) with the value of what they want to donate. |
||||
|
|
||||
|
The first thing we need to do make this work is calculate what the input will be. In our examples we are assuming that the funders cover the fee. Since different keyrings can be using different transaction types of different sizes (p2sh, multisig, etc.), we need a utility to calculate how much the fee should be for that input and add that to the amount to fund with. |
||||
|
|
||||
|
##### Utility functions |
||||
|
We'll need some utility functions to help us out. It's nice to split these out separate from our main operations since we'll actually be reusing some of the functionality. |
||||
|
|
||||
|
Before we build out the tools to calculate fees and split coins, we'll need a utility for composing the inputs for txs that we'll use for our mock transactions in the fee calculator and later for templating our real transaction |
||||
|
|
||||
|
```javascript |
||||
|
const addInput = function addInput(coin, inputIndex, mtx, keyring, hashType) { |
||||
|
const sampleCoin = coin instanceof Coin ? coin : Coin.fromJSON(coin); |
||||
|
if(!hashType) hashType = Script.hashType.ANYONECANPAY | Script.hashType.ALL; |
||||
|
|
||||
|
mtx.addCoin(sampleCoin); |
||||
|
mtx.scriptInput(inputIndex, sampleCoin, keyring); |
||||
|
mtx.signInput(inputIndex, sampleCoin, keyring, hashType); |
||||
|
assert(mtx.isSigned(), 'Input was not signed properly'); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Now let's build the utility for calculating the fee for a single input of unknown type or size |
||||
|
|
||||
|
```javascript |
||||
|
const getFeeForInput = function getFeeForInput(coin, address, keyring, rate) { |
||||
|
const fundingTarget = 100000000; // 1 BTC (arbitrary for purposes of this function) |
||||
|
const testMTX = new MTX(); |
||||
|
|
||||
|
// we're not actually going to use this tx for anything other than calculate what fee should be |
||||
|
addInput(coin, 0, testMTX, keyring); |
||||
|
|
||||
|
return testMTX.getMinFee(null, rate); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Our last utility is an asyncronous function to help us split the coinbases we created in the previous step (we use the `fund` method on the `MTX` primitive to do this, which is asynchronous). |
||||
|
|
||||
|
```javascript |
||||
|
const splitCoinbase = async function splitCoinbase(funderKeyring, coin, targetAmount, txRate) { |
||||
|
// loop through each coinbase coin to split |
||||
|
let coins = []; |
||||
|
|
||||
|
const mtx = new MTX(); |
||||
|
|
||||
|
assert(coin.value > targetAmount, 'coin value is not enough!'); |
||||
|
|
||||
|
// creating a transaction that will have an output equal to what we want to fund |
||||
|
mtx.addOutput({ |
||||
|
address: funderKeyring.getAddress(), |
||||
|
value: targetAmount |
||||
|
}); |
||||
|
|
||||
|
// the fund method will automatically split |
||||
|
// the remaining funds to the change address |
||||
|
// Note that in a real application these splitting transactions will also |
||||
|
// have to be broadcast to the network |
||||
|
await mtx.fund([coin], { |
||||
|
rate: txRate, |
||||
|
// send change back to an address belonging to the funder |
||||
|
changeAddress: funderKeyring.getAddress() |
||||
|
}).then(() => { |
||||
|
// sign the mtx to finalize split |
||||
|
mtx.sign(funderKeyring); |
||||
|
assert(mtx.verify()); |
||||
|
|
||||
|
const tx = mtx.toTX(); |
||||
|
assert(tx.verify(mtx.view)); |
||||
|
|
||||
|
const outputs = tx.outputs; |
||||
|
|
||||
|
// get coins from tx |
||||
|
outputs.forEach((outputs, index) => { |
||||
|
coins.push(Coin.fromTX(tx, index, -1)); |
||||
|
}); |
||||
|
}) |
||||
|
.catch(e => console.log('There was an error: ', e)); |
||||
|
|
||||
|
return coins; |
||||
|
}; |
||||
|
|
||||
|
``` |
||||
|
|
||||
|
Because of the async methods being used, in order to take advantage of the async/await structure, the rest of the code will be enclosed in an async function. |
||||
|
|
||||
|
The first thing we'll do is split the coinbase coins we created earlier using the utility function we just built (we'll also have to calculate the fee and add it to the funding amount in order to get an output of the right value). |
||||
|
|
||||
|
```javascript |
||||
|
const composeCrowdfund = async function composeCrowdfund(coins) { |
||||
|
const funderCoins = {}; |
||||
|
// Loop through each coinbase |
||||
|
for (let index in coins) { |
||||
|
const coinbase = coins[index][0]; |
||||
|
// estimate fee for each coin (assuming their split coins will use same tx type) |
||||
|
const estimatedFee = getFeeForInput(coinbase, fundeeAddress, funders[index], txRate); |
||||
|
const targetPlusFee = amountToFund + estimatedFee; |
||||
|
|
||||
|
// split the coinbase with targetAmount plus estimated fee |
||||
|
const splitCoins = await Utils.splitCoinbase(funders[index], coinbase, targetPlusFee, txRate); |
||||
|
|
||||
|
// add to funderCoins object with returned coins from splitCoinbase being value, |
||||
|
// and index being the key |
||||
|
funderCoins[index] = splitCoins; |
||||
|
} |
||||
|
// ... we'll keep filling out the rest of the code here |
||||
|
}; |
||||
|
``` |
||||
|
|
||||
|
`funderCoins` should return x number of coin arrays, where X is the number of coinbases we created earlier (should be 2) with each array having a coin equal to the amount we want to donate. |
||||
|
|
||||
|
For example... |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"0": |
||||
|
[ |
||||
|
{ |
||||
|
"type": "pubkeyhash", |
||||
|
"version": 1, |
||||
|
"height": -1, |
||||
|
"value": "0.5000157" |
||||
|
"script": <Script: OP_DUP OP_HASH160 0x14 0x62f725e83caf894aa6c3efd29ef28649fc448825 OP_EQUALVERIFY OP_CHECKSIG>, |
||||
|
"coinbase": false, |
||||
|
"hash": "774822d84bd5af02f1b3eacd6215e0a1bcf07cfb6675a000c8a01d2ea34f2a32", |
||||
|
"index": 0, |
||||
|
"address": <Address: type=pubkeyhash version=-1 str=mpYEb17KR7MVhuPZT1GsW3SywZx8ihYube> |
||||
|
}, |
||||
|
... |
||||
|
], |
||||
|
"1": [...] |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### Step 6: Construct the Transaction |
||||
|
Now that we've got our tools and coins ready, we can start to build the transaction! |
||||
|
|
||||
|
```javascript |
||||
|
const composeCrowdfund = async function composeCrowdfund(coins) { |
||||
|
//... |
||||
|
|
||||
|
const fundMe = new MTX(); |
||||
|
|
||||
|
// add an output with the target funding amount |
||||
|
|
||||
|
fundMe.addOutput({ value: fundingTarget, address: fundeeAddress }); |
||||
|
|
||||
|
// fund with first funder |
||||
|
let fundingCoin = funderCoins['0'][0]; |
||||
|
addInput(fundingCoin, 0, fundMe, funder1Keyring); |
||||
|
|
||||
|
// fund with second funder |
||||
|
fundingCoin = funderCoins['1'][0]; |
||||
|
addInput(fundingCoin, 1, fundMe, funder2Keyring); |
||||
|
|
||||
|
// We want to confirm that total value of inputs covers the funding goal |
||||
|
// NOTE: the difference goes to the miner in the form of fees |
||||
|
assert(fundMe.getInputValue() >= fundMe.outputs[0].value, 'Total inputs not enough to fund'); |
||||
|
assert(fundMe.verify(), 'The mtx is malformed'); |
||||
|
|
||||
|
const tx = fundMe.toTX(); |
||||
|
console.log('total input value = ', fundMe.getInputValue()); |
||||
|
console.log('Fee getting sent to miners:', fundMe.getInputValue() - fundingTarget, 'satoshis'); |
||||
|
|
||||
|
assert(tx.verify(fundMe.view), 'there is a problem with your tx'); |
||||
|
|
||||
|
return tx; |
||||
|
}; |
||||
|
|
||||
|
composeCrowdfund(coins).then(myCrowdfundTx => console.log(myCrowdfundTx)); |
||||
|
``` |
||||
|
|
||||
|
`composeCrowdfund` will now return a promise (thanks to async/await) that returns a fully templated transaction that can be transmitted to the network. It should have two inputs and one output with a value of 1. The inputs will have a value equal to the amount to fund plus the cost of the fee. You should also notice the `SIGHASH` flag 0x81 at the end of the input scripts which confirms they are `ALL|ANYONECANPAY` scripts. |
||||
|
|
||||
|
### Version 2: Using the Bcoin Wallet System |
||||
|
Since trying to manage your own keys is pretty tedious, not to mention impractical especially if you want to let other people contribute to your campaign, let's use the bcoin wallet system to take care of that part of the process. For the this example, we're going to interact via the wallet client, but you could also do something similar within a bcoin node (using the wallet and walletdb directly) which would also be more secure. |
||||
|
|
||||
|
Note that this is a little more tedious to test since you need to have funded wallets in order to actually fund your campaign. You can find a bitcoin testnet faucet to get some funds to play with or, as we will do in this example, create your own simnet or regtest network where you mine your own blocks to fund yourself. |
||||
|
|
||||
|
#### Step 1: Setup Our Wallets |
||||
|
We'll skip the setup of constants import modules since we can use the same from the previous example. Since there are a lot of asynchronous operations here now that we're using the client, we'll also put this in an async function and will continue to build out the contents of the function throughout this guide. We'll also be using some of the same [utility functions that we created in the last example](#utility-functions). |
||||
|
|
||||
|
```javascript |
||||
|
const network = 'regtest'; |
||||
|
const composeWalletCrowdfund = async function composeWalletCrowdfund() { |
||||
|
const client = await new bcoin.http.Client({ network }); |
||||
|
|
||||
|
// Step 1: Setup our wallets and funding targets |
||||
|
const fundeeWallet = await new httpWallet({ id: 'fundee', network }); |
||||
|
const fundeeAddress = await fundeeWallet.createAddress('default'); |
||||
|
const funders = { |
||||
|
'funder1': await new httpWallet({ id: 'funder1', network }), |
||||
|
'funder2': await new httpWallet({ id: 'funder2', network }) |
||||
|
}; |
||||
|
//... |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### Step 2: Prepare Your Coins |
||||
|
We have the same issue to deal with here as in step 2 in the previous example: we need to have "exact change" coins available for funding. The process of splitting is a little different with the wallet system though so let's walk through this version. |
||||
|
|
||||
|
Note that the way that we're retrieving the keyring information is pretty insecure as we are sending private keys unencrypted across the network, but we'll leave for the sake of the example. **DON'T USE THIS IN PRODUCTION**. |
||||
|
|
||||
|
```javascript |
||||
|
const composeWalletCrowdfund = async function composeWalletCrowdfund() { |
||||
|
//... |
||||
|
|
||||
|
const fundingCoins = {}; |
||||
|
|
||||
|
// go through each funding wallet to prepare coins |
||||
|
for(let id in funders) { |
||||
|
const funder = funders[id]; |
||||
|
|
||||
|
const coins = await funder.getCoins(); |
||||
|
const funderInfo = await funder.getInfo(); |
||||
|
|
||||
|
// Before we do anything we need to get |
||||
|
// the fee that will be necessary for each funder's input. |
||||
|
const funderKey = await funder.getWIF(coins[0].address); |
||||
|
const funderKeyring = new bcoin.keyring.fromSecret(funderKey.privateKey); |
||||
|
const feeForInput = Utils.getFeeForInput(coins[0], fundeeAddress.address, funderKeyring, rate); |
||||
|
amountToFund += feeForInput; |
||||
|
|
||||
|
// Next, go through available coins |
||||
|
// to find a coin equal to or greater than value to fund |
||||
|
|
||||
|
// We didn't do this before because we knew what coins were available. But if we have one already in our wallets, then we can just use that! |
||||
|
let fundingCoin = {}; |
||||
|
for(let coin of coins) { |
||||
|
if (coin.value === amountToFund) { |
||||
|
// if we already have a coin of the right value we can use that |
||||
|
fundingCoin = coin; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!Object.keys(fundingCoin).length) { |
||||
|
// if we don't have a coin of the right amount to fund with |
||||
|
// we need to create one by sending the funder wallet |
||||
|
// a tx that includes an output of the right amount |
||||
|
|
||||
|
// this is similar to what we did in the manual version |
||||
|
const receiveAddress = await funder.createAddress('default') // send it back to the funder |
||||
|
const tx = await funder.send({ |
||||
|
rate, |
||||
|
outputs: [{ |
||||
|
value: amountToFund, |
||||
|
address: receiveAddress.address |
||||
|
}] |
||||
|
}); |
||||
|
|
||||
|
// get index of ouput for fundingCoin |
||||
|
let coinIndex; |
||||
|
for (let i=0; i < tx.outputs.length; i++) { |
||||
|
if (tx.outputs[i].value === amountToFund) { |
||||
|
coinIndex = i; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
assert(tx.outputs[coinIndex].value === amountToFund, 'value of output at index not correct'); |
||||
|
|
||||
|
// first argument is for the account |
||||
|
// default is being used for all examples |
||||
|
fundingCoin = await funder.getCoin('default', tx.hash, coinIndex); |
||||
|
} |
||||
|
fundingCoins[funder.id] = fundingCoin; |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
We now should have an object available that has the information of the coins we will be using to fund our transaction with mapped to the name of each wallet (so we can retrieve the wallet information later). It should look something like this (note that the values are larger than our funding amount due to the fee): |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"funder1": { |
||||
|
"version": 1, |
||||
|
"height": -1, |
||||
|
"value": 50002370, |
||||
|
"script": "76a914127cb1a40212169c49fe22d13307b18af1fa07ad88ac", |
||||
|
"address": "SNykaBMuTyeUQkK8exZyymWNrnYX5vVPuY", |
||||
|
"coinbase": false, |
||||
|
"hash": "163068016a39e2d9c869bcdb8646dbca93e07824db39217b5c444e7c61d1a82c", |
||||
|
"index": 0 |
||||
|
}, |
||||
|
"funder2": { |
||||
|
"version": 1, |
||||
|
"height": -1, |
||||
|
"value": 50002370, |
||||
|
"script": "76a9146e08c73b355e690ba0b1198d578c4c6c52b3813688ac", |
||||
|
"address": "SXKossD65D7h62fhzGrntERBrPeXUfiC92", |
||||
|
"coinbase": false, |
||||
|
"hash": "11f3180c5069f2692f1ee1463257b21dc217441e792493c8f5ee230c35d97d96", |
||||
|
"index": 0 |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### Step 3: Create our Crowdfund Tx |
||||
|
Now that we have our coins ready we can start to template the transaction! |
||||
|
|
||||
|
Please note again that the way we are funding the transactions is by sending our private keys unencrypted across the network so don't use this in production. |
||||
|
|
||||
|
```javascript |
||||
|
const composeWalletCrowdfund = async function composeWalletCrowdfund() { |
||||
|
//... |
||||
|
const fundMe = new MTX(); |
||||
|
|
||||
|
// Use the maxFee to calculate output value for transaction |
||||
|
fundMe.addOutput({value: fundingTarget, address: fundeeAddress.address }); |
||||
|
|
||||
|
// go through our coins and add each as an input in our transaction |
||||
|
let inputCounter = 0; |
||||
|
for(let funder in fundingCoins) { |
||||
|
const wallet = funders[funder]; |
||||
|
const coinOptions = fundingCoins[funder]; |
||||
|
|
||||
|
const key = await wallet.getWIF(coinOptions.address); |
||||
|
const keyring = new bcoin.keyring.fromSecret(key.privateKey); |
||||
|
|
||||
|
// this is the same utility as we used in our other example |
||||
|
addInput(coinOptions, inputCounter, fundMe, keyring); |
||||
|
assert(fundMe.isSigned(), 'Input has not been signed correctly'); |
||||
|
inputCounter++; |
||||
|
} |
||||
|
|
||||
|
// confirm that the transaction has been properly templated and signed |
||||
|
assert( |
||||
|
fundMe.inputs.length === Object.keys(funders).length, |
||||
|
'Number of inputs in MTX is incorrect' |
||||
|
); |
||||
|
assert(fundMe.verify(), 'MTX is malformed'); |
||||
|
|
||||
|
// make our transaction immutable so we can send it to th enetwork |
||||
|
const tx = fundMe.toTX(); |
||||
|
|
||||
|
assert(tx.verify(fundMe.view), 'TX is malformed. Fix before broadcasting'); |
||||
|
|
||||
|
// check the value of our inputs just to confirm what the fees are |
||||
|
console.log('Total value of inputs: ', fundMe.getInputValue() ); |
||||
|
console.log('Fee to go to miners: ', fundMe.getInputValue() - fundingTarget); |
||||
|
|
||||
|
// Finally, broadcast tx |
||||
|
try { |
||||
|
const broadcastStatus = await client.broadcast(tx); |
||||
|
return tx; |
||||
|
} catch (e) { |
||||
|
console.log('There was a problem: ', e); |
||||
|
} |
||||
|
} |
||||
|
composeWalletCrowdfund() |
||||
|
.then(myCrowdfundTx => console.log('Transaction broadcast: ', myCrowdfundTx)) |
||||
|
.catch(e => console.log('There was a problem: ', e)); |
||||
|
``` |
||||
|
|
||||
|
And there you have it! If you were doing this on testnet, your `fundeeWallet` should now be 1BTC richer. If you're on a simnet or regtest network, you'll have to mine a block with your transactions to get those funds confirmed. Also note that, unless you have exact change coins, there will be 3 transactions that need to be confirmed: one each for the wallets that are splitting coins, and one for the crowdfund transaction. |
||||
|
|
||||
|
## How to Build it Out |
||||
|
These examples are obviously pretty basic, but they should give you an idea of how to use Bitcoin's scripting to build out the foundation for more complex applications. Here are some ideas on how you could build on top of these examples and get closer to a production ready application. |
||||
|
|
||||
|
- More flexible contribution scheme (currently it's just 2 funders that split the amount evenly). E.g. custom number of contributers, custom contribution amount, etc. |
||||
|
- UX to let people interact with the transaction via a browser |
||||
|
- More advanced interface for fee estimation and include platform for large number of funders (for example, since you may be limited to number of funders per tx, you could include interface for multiple transactions for a single campaign. You would also want to include a check to make sure your tx is not bigger than 100kb otherwise it'll get rejected by the network) |
||||
|
- Add a fund matching scheme where someone can say they will match future contributions |
||||
|
- Currently the examples split transactions to make a coin available that equals the target contribution amount. This is expensive since you have to broadcast multiple transactions. An interface to choose to donate from available available coins might help to make this more efficient. |
||||
|
|
||||
|
Make sure to get in touch with us on Twitter or Slack if you build out any of these ideas! |
||||
|
|
||||
|
Again, for a working example of the code (without all the text and explanations), check out [the repo on github](https://github.com/Bucko13/bitcoin-fundraise). |
@ -0,0 +1,811 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<meta name="description" content=""> |
||||
|
<meta name="author" content=""> |
||||
|
|
||||
|
<title>bcoin | Extending Bitcoin into Enterprise & Production</title> |
||||
|
|
||||
|
<!-- Favicons --> |
||||
|
<!-- old |
||||
|
<link rel="shortcut icon" href="../assets/images/bcoin-ico.png">--> |
||||
|
|
||||
|
<!-- generated from http://www.favicon-generator.org/ --> |
||||
|
<link rel="apple-touch-icon" sizes="57x57" href="../assets/images/apple-icon-57x57.png"> |
||||
|
<link rel="apple-touch-icon" sizes="60x60" href="../assets/images/apple-icon-60x60.png"> |
||||
|
<link rel="apple-touch-icon" sizes="72x72" href="../assets/images/apple-icon-72x72.png"> |
||||
|
<link rel="apple-touch-icon" sizes="76x76" href="../assets/images/apple-icon-76x76.png"> |
||||
|
<link rel="apple-touch-icon" sizes="114x114" href="../assets/images/apple-icon-114x114.png"> |
||||
|
<link rel="apple-touch-icon" sizes="120x120" href="../assets/images/apple-icon-120x120.png"> |
||||
|
<link rel="apple-touch-icon" sizes="144x144" href="../assets/images/apple-icon-144x144.png"> |
||||
|
<link rel="apple-touch-icon" sizes="152x152" href="../assets/images/apple-icon-152x152.png"> |
||||
|
<link rel="apple-touch-icon" sizes="180x180" href="../assets/images/apple-icon-180x180.png"> |
||||
|
<link rel="icon" type="image/png" sizes="192x192" href="../assets/images/android-icon-192x192.png"> |
||||
|
<link rel="icon" type="image/png" sizes="32x32" href="../assets/images/favicon-32x32.png"> |
||||
|
<link rel="icon" type="image/png" sizes="96x96" href="../assets/images/favicon-96x96.png"> |
||||
|
<link rel="icon" type="image/png" sizes="16x16" href="../assets/images/favicon-16x16.png"> |
||||
|
<link rel="manifest" href="../assets/images/manifest.json"> |
||||
|
<meta name="msapplication-TileColor" content="#ffffff"> |
||||
|
<meta name="msapplication-TileImage" content="../assets/images/ms-icon-144x144.png"> |
||||
|
<meta name="theme-color" content="#ffffff"> |
||||
|
|
||||
|
<!-- Web Fonts --> |
||||
|
<link href='http://fonts.googleapis.com/css?family=Open+Sans:300,400italic,400,600,700' rel='stylesheet'> |
||||
|
<link href='https://fonts.googleapis.com/css?family=Montserrat:700' rel='stylesheet' type='text/css'> |
||||
|
|
||||
|
<!-- Bootstrap core CSS --> |
||||
|
<link href="../assets/bootstrap/css/bootstrap.min.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- Code Snippet CSS --> |
||||
|
<link href="../assets/css/prism.css" rel="stylesheet"> |
||||
|
|
||||
|
<link href="../assets/css/custom.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- Icon Fonts --> |
||||
|
<link href="../assets/css/font-awesome.min.css" rel="stylesheet"> |
||||
|
<link href="../assets/css/simple-line-icons.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- Plugins --> |
||||
|
<link href="../assets/css/magnific-popup.css" rel="stylesheet"> |
||||
|
<link href="../assets/css/owl.carousel.css" rel="stylesheet"> |
||||
|
<link href="../assets/css/flexslider.css" rel="stylesheet"> |
||||
|
<link href="../assets/css/animate.min.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- Template core CSS --> |
||||
|
<link href="../assets/css/vertical.min.css" rel="stylesheet"> |
||||
|
<link href="../assets/css/template.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- Google Analytics Tracking --> |
||||
|
<script> |
||||
|
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ |
||||
|
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), |
||||
|
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) |
||||
|
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); |
||||
|
ga('create', 'UA-96446060-1', 'auto'); |
||||
|
ga('send', 'pageview'); |
||||
|
</script> |
||||
|
</head> |
||||
|
<body> |
||||
|
|
||||
|
<!-- PRELOADER --> |
||||
|
<div class="page-loader"> |
||||
|
<div class="img-loader">Loading... |
||||
|
<!-- Bcoin logo in SVG --> |
||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" |
||||
|
viewBox="0 0 200 56" style="enable-background:new 0 0 200 56;" xml:space="preserve"> |
||||
|
<g id="XMLID_108_"> |
||||
|
<g id="XMLID_123_"> |
||||
|
<path id="XMLID_124_" d="M8.4,51.8H3.2V4.2h5.2v19.2h0.8c1.2-2,2.8-3.6,4.8-4.7c2-1.1,4.3-1.6,6.7-1.6c2,0,4,0.4,5.8,1.2 |
||||
|
c1.8,0.8,3.4,1.9,4.8,3.3c1.4,1.5,2.5,3.2,3.3,5.4s1.2,4.6,1.2,7.3v1.4c0,2.8-0.4,5.2-1.2,7.4c-0.8,2.1-1.9,3.9-3.3,5.4 |
||||
|
c-1.4,1.5-3,2.6-4.9,3.3s-3.8,1.1-5.9,1.1c-1.1,0-2.2-0.1-3.3-0.4c-1.1-0.3-2.2-0.6-3.2-1.2c-1-0.5-1.9-1.2-2.8-1.9 |
||||
|
c-0.8-0.7-1.6-1.6-2.1-2.7H8.4V51.8z M19.4,47.7c1.6,0,3.1-0.3,4.4-0.9c1.3-0.6,2.5-1.4,3.5-2.4c1-1,1.8-2.3,2.3-3.8 |
||||
|
c0.6-1.5,0.8-3.2,0.8-5v-1.4c0-1.8-0.3-3.5-0.8-4.9c-0.6-1.5-1.3-2.7-2.3-3.8c-1-1.1-2.2-1.9-3.5-2.5c-1.4-0.6-2.8-0.9-4.4-0.9 |
||||
|
c-1.6,0-3,0.3-4.3,0.9c-1.3,0.6-2.5,1.5-3.5,2.6c-1,1.1-1.8,2.4-2.4,3.9c-0.6,1.5-0.9,3.2-0.9,5v0.8c0,1.9,0.3,3.6,0.9,5.1 |
||||
|
c0.6,1.5,1.4,2.8,2.4,3.9s2.2,1.9,3.5,2.5C16.4,47.4,17.9,47.7,19.4,47.7z"/> |
||||
|
</g> |
||||
|
<g id="XMLID_120_"> |
||||
|
<path id="XMLID_121_" d="M76.1,39.8c-0.4,1.9-1,3.6-1.8,5.2c-0.9,1.6-2,3-3.3,4.1s-2.9,2.1-4.7,2.7c-1.8,0.6-3.8,1-5.9,1 |
||||
|
c-2.3,0-4.5-0.4-6.6-1.2c-2.1-0.8-3.9-1.9-5.4-3.4c-1.6-1.5-2.8-3.3-3.7-5.4c-0.9-2.1-1.4-4.6-1.4-7.4v-0.8c0-2.7,0.5-5.2,1.4-7.4 |
||||
|
c0.9-2.2,2.1-4,3.7-5.5c1.6-1.5,3.4-2.7,5.4-3.5c2.1-0.8,4.3-1.2,6.6-1.2c2.1,0,4,0.3,5.8,1c1.8,0.6,3.3,1.5,4.7,2.7 |
||||
|
c1.4,1.2,2.5,2.5,3.3,4.1c0.9,1.6,1.5,3.3,1.8,5.2l-5.2,1.2c-0.1-1.2-0.5-2.3-1-3.4c-0.5-1.1-1.2-2.1-2.1-2.9 |
||||
|
c-0.9-0.8-1.9-1.5-3.2-2c-1.2-0.5-2.7-0.7-4.3-0.7c-1.6,0-3.1,0.3-4.5,0.9c-1.4,0.6-2.6,1.5-3.7,2.6c-1.1,1.1-1.9,2.4-2.5,4 |
||||
|
c-0.6,1.5-0.9,3.2-0.9,5v0.8c0,1.9,0.3,3.6,0.9,5.1c0.6,1.5,1.4,2.8,2.5,3.8c1.1,1,2.3,1.8,3.7,2.4c1.4,0.6,3,0.9,4.6,0.9 |
||||
|
s3.1-0.3,4.3-0.8c1.2-0.5,2.3-1.2,3.1-2c0.9-0.8,1.6-1.8,2.1-2.9c0.5-1.1,0.9-2.2,1-3.4L76.1,39.8z"/> |
||||
|
</g> |
||||
|
<g id="XMLID_116_"> |
||||
|
<path id="XMLID_117_" d="M117.2,35.4c0,2.8-0.5,5.3-1.4,7.5c-0.9,2.2-2.1,4-3.6,5.4c-1.5,1.5-3.3,2.6-5.3,3.4 |
||||
|
c-2,0.8-4.1,1.2-6.3,1.2c-2.2,0-4.3-0.4-6.3-1.2c-2-0.8-3.8-1.9-5.3-3.4c-1.5-1.5-2.7-3.3-3.6-5.4c-0.9-2.2-1.4-4.6-1.4-7.5v-0.8 |
||||
|
c0-2.8,0.5-5.2,1.4-7.4c0.9-2.2,2.1-4,3.7-5.5c1.5-1.5,3.3-2.6,5.3-3.4c2-0.8,4.1-1.2,6.3-1.2c2.2,0,4.3,0.4,6.3,1.2 |
||||
|
c2,0.8,3.8,1.9,5.3,3.4c1.5,1.5,2.8,3.3,3.7,5.5c0.9,2.2,1.4,4.6,1.4,7.4V35.4z M100.6,47.7c1.6,0,3.1-0.3,4.4-0.9 |
||||
|
c1.4-0.6,2.5-1.4,3.6-2.5c1-1.1,1.8-2.4,2.4-3.9c0.6-1.5,0.9-3.2,0.9-5.1v-0.8c0-1.8-0.3-3.5-0.9-5c-0.6-1.5-1.4-2.8-2.4-3.9 |
||||
|
c-1-1.1-2.2-1.9-3.6-2.6c-1.4-0.6-2.8-0.9-4.4-0.9c-1.6,0-3,0.3-4.4,0.9c-1.4,0.6-2.6,1.5-3.6,2.6c-1,1.1-1.8,2.4-2.4,3.9 |
||||
|
c-0.6,1.5-0.9,3.2-0.9,5v0.8c0,1.9,0.3,3.6,0.9,5.1c0.6,1.5,1.4,2.8,2.4,3.9c1,1.1,2.2,1.9,3.6,2.5C97.5,47.5,99,47.7,100.6,47.7z |
||||
|
"/> |
||||
|
</g> |
||||
|
<g id="XMLID_112_"> |
||||
|
<path id="XMLID_113_" d="M127.6,46.9h11.6V23h-10.4v-4.9h15.6v28.9h10.8v4.9h-27.6V46.9z M137.1,8c0-1.3,0.5-2.4,1.4-3.4 |
||||
|
c0.9-0.9,2-1.4,3.3-1.4c1.3,0,2.4,0.5,3.3,1.4c0.9,0.9,1.4,2.1,1.4,3.4c0,1.3-0.5,2.4-1.4,3.4c-0.9,0.9-2,1.4-3.3,1.4 |
||||
|
c-1.3,0-2.4-0.5-3.3-1.4C137.5,10.4,137.1,9.3,137.1,8z"/> |
||||
|
</g> |
||||
|
<g id="XMLID_109_"> |
||||
|
<path id="XMLID_110_" d="M172.8,51.8h-5.2V18.1h5.2v5.7h0.8c2-4.4,5.6-6.7,10.7-6.7c3.8,0,6.9,1.2,9.1,3.6 |
||||
|
c2.3,2.4,3.4,6.1,3.4,10.9v20.2h-5.2V32.8c0-3.5-0.8-6.2-2.3-8c-1.6-1.8-3.7-2.7-6.3-2.7c-3.2,0-5.6,1.1-7.4,3.3s-2.7,5.1-2.7,8.8 |
||||
|
V51.8z"/> |
||||
|
</g> |
||||
|
</g> |
||||
|
</svg> |
||||
|
|
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- END PRELOADER --> |
||||
|
|
||||
|
<!-- HEADER --> |
||||
|
<header class="header js-stick"> |
||||
|
<div class="container"> |
||||
|
<!-- YOUR LOGO HERE --> |
||||
|
<div class="inner-header"> |
||||
|
<a class="inner-brand" href="../index.html"> |
||||
|
<img class="brand-light" src="../assets/images/logo-light.png" width="100" alt=""> |
||||
|
<img class="brand-dark" src="../assets/images/logo-dark.png" width="100" alt=""> |
||||
|
</a> |
||||
|
</div> |
||||
|
|
||||
|
<!-- OPEN MOBILE MENU --> |
||||
|
<div class="main-nav-toggle"> |
||||
|
<div class="nav-icon-toggle" data-toggle="collapse" data-target="#custom-collapse"> |
||||
|
<span class="icon-bar"></span> |
||||
|
<span class="icon-bar"></span> |
||||
|
<span class="icon-bar"></span> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- WIDGETS MENU --> |
||||
|
<div class="inner-header pull-right hide-me"> |
||||
|
<div class="menu-extras clearfix"> |
||||
|
|
||||
|
<!-- SLACK LINK --> |
||||
|
<div class="menu-item"> |
||||
|
<div class=""> |
||||
|
<a href="../slack-signup.html" target="_blank" id="" data-toggle="tooltip" title="" data-placement="bottom" data-original-title="Join us on Slack!"> |
||||
|
<img src="../assets/images/slack_icon.svg" width="18" height="18"/> |
||||
|
<span class=""></span> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- STACK EXCHANGE LINK --> |
||||
|
<div class="menu-item"> |
||||
|
<div class=""> |
||||
|
<a href="https://bitcoin.stackexchange.com/questions/tagged/bcoin" target="_blank" id="" data-toggle="tooltip" title="" data-placement="bottom" data-original-title="Questions! Checkout Stack Exchange."> |
||||
|
<img src="../assets/images/stack-exchange-icon.svg" width="18" height="18"/> |
||||
|
<span class=""></span> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- GITHUB STUFF --> |
||||
|
<div class="menu-item"> |
||||
|
<div class=""> |
||||
|
<a href="https://github.com/bcoin-org/bcoin" target="_blank" id="" data-toggle="tooltip" title="" data-placement="bottom" data-original-title="Visit bcoin on GitHub to see the code!"> |
||||
|
<img src="../assets/images/github_icon.svg" width="18" height="18"/> |
||||
|
<span class=""></span> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="menu-item"> |
||||
|
<div class="ghbuttons"> |
||||
|
<a class="github-button" href="https://github.com/bcoin-org/bcoin" data-icon="octicon-star" data-count-href="/bcoin-org/bcoin/stargazers" data-show-count="true" data-count-aria-label="# stargazers on GitHub" aria-label="Star bcoin-org/bcoin on GitHub">Star</a> |
||||
|
<a class="github-button" href="https://github.com/bcoin-org/bcoin/fork" data-icon="octicon-repo-forked" data-count-href="/bcoin-org/bcoin/network" data-show-count="true" data-count-aria-label="# forks on GitHub" aria-label="Fork bcoin-org/bcoin on GitHub">Fork</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- MAIN MENU --> |
||||
|
<nav id="custom-collapse" class="main-nav collapse clearfix"> |
||||
|
<ul class="inner-nav pull-right"> |
||||
|
|
||||
|
<!-- HOME --> |
||||
|
<li><a href="../index.html">Home</a></li> |
||||
|
<!-- END HOME --> |
||||
|
|
||||
|
<!-- FEATURES --> |
||||
|
<li><a href="../index.html#features">What is Bcoin</a></li> |
||||
|
<!-- END FEATURES --> |
||||
|
|
||||
|
<!-- GUIDES --> |
||||
|
<li><a href="../guides.html">Guides</a></li> |
||||
|
<!-- GUIDES --> |
||||
|
|
||||
|
<!-- API REFERENCE - newer, how to interact once you're setup --> |
||||
|
<li><a href="../api-docs/index.html">API Docs</a></li> |
||||
|
<!-- END API --> |
||||
|
|
||||
|
<!-- FULL DOCS - older, full reference |
||||
|
<li><a href="http://bcoin.io/docs/index.html">Docs</a></li> --> |
||||
|
<!-- END DOCS --> |
||||
|
|
||||
|
<!-- DIVIDER |
||||
|
<li><a> </a></li> |
||||
|
|
||||
|
<li><a href="#">All Demos</a></li>--> |
||||
|
|
||||
|
</ul> |
||||
|
</nav> |
||||
|
|
||||
|
</div> |
||||
|
</header> |
||||
|
<!-- END HEADER --> |
||||
|
|
||||
|
<!-- WRAPPER --> |
||||
|
<div class="wrapper"> |
||||
|
|
||||
|
<!-- PAGE TITLE --> |
||||
|
<section class="module-sm bg-white-dark" data-background="../assets/images/bg-header.jpg"> |
||||
|
<div class="container"> |
||||
|
|
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12 text-center"> |
||||
|
|
||||
|
<h2 class="montserrat text-uppercase m-b-10"><span class="text-highlight-black" style="line-height: 1.5;"> Guides and Videos </span></h2> |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
</section> |
||||
|
<!-- END PAGE TITLE --> |
||||
|
|
||||
|
<!-- GUIDES/TUTORIALS --> |
||||
|
<section class="module" style="padding-top:70px !important;"> |
||||
|
<div class="container"> |
||||
|
|
||||
|
<div class="row"> |
||||
|
|
||||
|
<!-- START SIDEBAR --> |
||||
|
<div class="col-sm-3 sidebar"> |
||||
|
<!-- CATEGORIES WIDGET --> |
||||
|
<div class="widget guide-list"> |
||||
|
<h6 class="montserrat text-uppercase bottom-line">Install</h6> |
||||
|
<ul class="icons-list"> |
||||
|
<!-- INSTALL-LIST --> |
||||
|
<li><a href="install-windows.html">Install on Windows (Video)</a></li> |
||||
|
<li><a href="install-mac.html">Install on Mac OS (Video)</a></li> |
||||
|
<li><a href="install-linux.html">Install on Linux (Video)</a></li> |
||||
|
<!-- /INSTALL-LIST --> |
||||
|
</ul> |
||||
|
<br> |
||||
|
<h6 class="montserrat text-uppercase bottom-line">Guides</h6> |
||||
|
<ul class="icons-list"> |
||||
|
<!-- GUIDES-LIST --> |
||||
|
<li><a href="scripting.html">Intro to Scripting</a></li> |
||||
|
<li><a href="op_return.html">Store Data on the Blockchain</a></li> |
||||
|
<li><a href="multisig-tx.html">Creating Multi Signature Transactions</a></li> |
||||
|
<li><a href="generate-address.html">Generate an Address</a></li> |
||||
|
<li><a href="crowdfund-tx.html">Create a Crowdfunding Transaction</a></li> |
||||
|
<!-- /GUIDES-LIST --> |
||||
|
</ul> |
||||
|
</div> |
||||
|
<!-- END CATEGORIES WIDGET --> |
||||
|
|
||||
|
<!-- TEXT WIDGET --> |
||||
|
<div class="widget"> |
||||
|
<h6 class="montserrat text-uppercase bottom-line">Looking for Docs?</h6> |
||||
|
<p>Checkout our <a href="../api-docs/index.html">API Docs</a> or the <a href="http://bcoin.io/docs/index.html">Full Documentation</a></p> |
||||
|
</div> |
||||
|
<!-- END TEXT WIDGET --> |
||||
|
|
||||
|
<!-- TEXT WIDGET --> |
||||
|
<div class="widget"> |
||||
|
<h6 class="montserrat text-uppercase bottom-line">Get Involved</h6> |
||||
|
<p>If you think you've got what it takes to make your own bcoin guides and tutorials, reach out to us on <a href="../slack-signup.html"> Slack!</a></p> |
||||
|
<p>Want to join the team?<a href="https://angel.co/purse/jobs/90956-bitcoin-protocol-engineer-bcoin"> We’re hiring.</a></p> |
||||
|
</div> |
||||
|
<!-- END TEXT WIDGET --> |
||||
|
</div> |
||||
|
<!-- END SIDEBAR --> |
||||
|
<!-- START OF ARTICLE CONTAINER --> |
||||
|
<div class="col-sm-9 blog-content post-thumbnail"> |
||||
|
<!-- POST IMAGE --> |
||||
|
<article class="post format-image"> |
||||
|
<div class="row"> |
||||
|
<!--<div class="col-sm-5"> |
||||
|
<div class="post-preview"> |
||||
|
<a href="#"><img src="../assets/images/guides/get-started.png" alt=""></a> |
||||
|
</div> |
||||
|
</div>--> |
||||
|
|
||||
|
<!-- after re-enabling the above code, change the col-sm below to col-sm-7 --> |
||||
|
<div class="panel-group"> |
||||
|
<div class="col-sm-12 panel panel-default"> |
||||
|
<div class="post-content" style="color:#000;"> |
||||
|
<!-- START OF GUIDE --> |
||||
|
<h2 class="post-title panel-title" id="create-a-crowdfunding-transaction">Create a Crowdfunding Transaction</h2><ul class="post-meta"><li class="author">By Buck Perley</li></ul><h2 id="sighash-flags">SIGHASH Flags</h2><p>In most Bitcoin transactions, when a transaction is signed, the entirety of the data in that transaction is committed to in that input's signature, with all outputs and inputs being included in the unlocking script. This, however, doesn't always have to be the case. The level of commitment is indicated by using <code>SIGHASH</code> flags. The most common flag is <code>SIGHASH_ALL</code> which has a value of 0x01 (you'll see this represented as a <code>01</code> at the end of DER-encoded signatures). To learn more about how the flags work and what other options are available, you should checkout <a href="https://github.com/bitcoinbook/bitcoinbook/blob/8d01749bcf45f69f36cf23606bbbf3f0bd540db3/ch06.asciidoc">Chapter 6</a> of <em>Mastering Bitcoin</em> by Andreas Antonopolous. In that chapter, Andreas posits one novel use of a specialized flag, <code>ALL|ANYONECANPAY</code>, and in this guide we'll try and build out a couple of examples implementing this idea using bcoin.</p> |
||||
|
<h2 id="how-it-works">How it Works</h2><p>The <code>ALL|ANYONECANPAY</code> flag indicates that a signature is committing all of the outputs and just one input. The suggested use case of this proposed in <em>Mastering Bitcoin</em> is a kickstarter-like crowdfunding application. Consider the situation where you have a fundraising goal, say 1BTC. You'd like multiple people to contribute to your goal and you want to prove to them that they won't have to pay <em>unless</em> you reach your goal (i.e. the transaction is invalid and won't be accepted by the network if attempted to be sent). This means you'd be committing to one output, the one that sends the funds to the charity or project you'd like to donate to, and only one input, your contribution. This allows multiple users to contribute to the same transaction and that transaction won't be valid until it's fully funded.</p> |
||||
|
<p>Here's how it's explained in <em>Mastering Bitcoin</em>:</p> |
||||
|
<blockquote> |
||||
|
<p>ALL|ANYONECANPAY |
||||
|
This construction can be used to make a "crowdfunding”-style transaction. Someone attempting to raise funds can construct a transaction with a single output. The single output pays the "goal" amount to the fundraiser. Such a transaction is obviously not valid, as it has no inputs. However, others can now amend it by adding an input of their own, as a donation. They sign their own input with ALL|ANYONECANPAY. Unless enough inputs are gathered to reach the value of the output, the transaction is invalid. Each donation is a "pledge," which cannot be collected by the fundraiser until the entire goal amount is raised.</p> |
||||
|
</blockquote> |
||||
|
<h2 id="the-code">The Code</h2><p>We'll walk through the steps of creating the transaction first without any wallet database or node running. Then we'll do the same thing using bcoin's walletdb to manage the keys to see how it would work in a more realistic application (skip to <a href="#version-2-using-the-bcoin-wallet-system">Version 2</a>) further in the guide to check it out). At the end, we'll put out some ideas of how these can be built upon for a more robust, production ready application. (If this is something you'd be interested in building, <a href="http://bcoin.io/slack-signup.html">get in touch</a>!). If you want to see the code, checkout the <a href="https://github.com/Bucko13/bitcoin-fundraise">repo on github</a>.</p> |
||||
|
<p>If you're not comfortable with key management, coin selection, and how transactions are constructed, checkout the tutorial on <a href="https://github.com/bcoin-org/bcoin/blob/master/docs/Working-with-transactions.md">working with transactions</a> first.</p> |
||||
|
<h3 id="version-1--manual-key-management">Version 1 - Manual Key Management</h3><h4 id="step-1-setup">Step 1: Setup</h4><p>Let's first start by importing the right tools, setting up some constants, and creating our keychains. (make sure you've installed the latest version of bcoin into your project with <code>npm install bcoin</code>).</p> |
||||
|
<p>Note that we're setting the fundingTarget and amountToFund as constants for simplicity, but they could be set based on user input or some other variable circumstances.</p> |
||||
|
<pre class="snippet line-numbers language-javascript"><code class="line-numbers language-javascript"><span class="token string">'use strict'</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token keyword">const</span> assert <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'assert'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token keyword">const</span> bcoin <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'bcoin'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token keyword">const</span> MTX <span class="token operator">=</span> bcoin<span class="token punctuation">.</span>mtx<span class="token punctuation">;</span> |
||||
|
<span class="token keyword">const</span> Keyring <span class="token operator">=</span> bcoin<span class="token punctuation">.</span>keyring<span class="token punctuation">;</span> |
||||
|
<span class="token keyword">const</span> Outpoint <span class="token operator">=</span> bcoin<span class="token punctuation">.</span>outpoint<span class="token punctuation">;</span> |
||||
|
<span class="token keyword">const</span> Script <span class="token operator">=</span> bcoin<span class="token punctuation">.</span>script<span class="token punctuation">;</span> |
||||
|
<span class="token keyword">const</span> Coin <span class="token operator">=</span> bcoin<span class="token punctuation">.</span>coin<span class="token punctuation">;</span> |
||||
|
<span class="token keyword">const</span> policy <span class="token operator">=</span> bcoin<span class="token punctuation">.</span>protocol<span class="token punctuation">.</span>policy |
||||
|
|
||||
|
<span class="token keyword">const</span> fundingTarget <span class="token operator">=</span> <span class="token number">100000000</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 1 BTC</span> |
||||
|
<span class="token keyword">const</span> amountToFund <span class="token operator">=</span> <span class="token number">50000000</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// .5 BTC</span> |
||||
|
<span class="token keyword">const</span> txRate <span class="token operator">=</span> <span class="token number">10000</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 10000 satoshis/kb</span></code><button class="copy-button"><img src="../assets/images/clippy.svg" alt="Copy to clipboard"></button></pre><p>Let's derive private hd key for fundee and two funders and create a "keyring" object for each (much of this is borrowed from the <em>Working with Transactions</em> guide).</p> |
||||
|
<p>A keyring object is basically a key manager that is also able to tell you info such as:</p> |
||||
|
<ul> |
||||
|
<li>your redeem script</li> |
||||
|
<li>your scripthash</li> |
||||
|
<li>your program hash</li> |
||||
|
<li>your pubkey hash</li> |
||||
|
<li>your scripthash program hash</li> |
||||
|
</ul> |
||||
|
<pre class="snippet line-numbers language-javascript"><code class="line-numbers language-javascript"><span class="token comment" spellcheck="true">// Create an HD master keypair.</span> |
||||
|
<span class="token keyword">const</span> master <span class="token operator">=</span> bcoin<span class="token punctuation">.</span>hd<span class="token punctuation">.</span><span class="token function">generate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token keyword">const</span> fundeeKey <span class="token operator">=</span> master<span class="token punctuation">.</span><span class="token function">derive</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token keyword">const</span> fundeeKeyring <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Keyring</span><span class="token punctuation">(</span>fundeeKey<span class="token punctuation">.</span>privateKey<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token keyword">const</span> fundeeAddress <span class="token operator">=</span> fundeeKeyring<span class="token punctuation">.</span><span class="token function">getAddress</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// Derive 2 more private hd keys and keyrings for funders</span> |
||||
|
<span class="token keyword">const</span> funder1Key <span class="token operator">=</span> master<span class="token punctuation">.</span><span class="token function">derive</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token keyword">const</span> funder1Keyring <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Keyring</span><span class="token punctuation">(</span>funder1Key<span class="token punctuation">.</span>privateKey<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token keyword">const</span> funder2Key <span class="token operator">=</span> master<span class="token punctuation">.</span><span class="token function">derive</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token keyword">const</span> funder2Keyring <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Keyring</span><span class="token punctuation">(</span>funder2Key<span class="token punctuation">.</span>privateKey<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token keyword">const</span> funders <span class="token operator">=</span> <span class="token punctuation">[</span>funder1Keyring<span class="token punctuation">,</span> funder2Keyring<span class="token punctuation">]</span><span class="token punctuation">;</span></code><button class="copy-button"><img src="../assets/images/clippy.svg" alt="Copy to clipboard"></button></pre><h4 id="step-2-fund-the-keyrings">Step 2: Fund the Keyrings</h4><p>This example is working in an isolated environment, so it won't work on the actual network (main, test, or otherwise), but we also don't have to earn or send ourselves coins or wait for confirmation times. That means that we can "spawn" coins for our funder wallets that they can use to spend on the crowdfunding platform.</p> |
||||
|
<p>Let's create some coinbase transactions to give our keyrings some coins that they can spend.</p> |
||||
|
<pre class="snippet line-numbers language-javascript"><code class="line-numbers language-javascript"><span class="token comment" spellcheck="true">// create some coinbase transactions to fund our wallets</span> |
||||
|
<span class="token keyword">const</span> coins <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> funders<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> |
||||
|
<span class="token keyword">const</span> cb <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MTX</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// Add a typical coinbase input</span> |
||||
|
cb<span class="token punctuation">.</span><span class="token function">addInput</span><span class="token punctuation">(</span><span class="token punctuation">{</span> |
||||
|
prevout<span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token class-name">Outpoint</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> |
||||
|
script<span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token class-name">Script</span><span class="token punctuation">(</span><span class="token punctuation">)</span> |
||||
|
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
cb<span class="token punctuation">.</span><span class="token function">addOutput</span><span class="token punctuation">(</span><span class="token punctuation">{</span> |
||||
|
address<span class="token punctuation">:</span> funders<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">getAddress</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> |
||||
|
value<span class="token punctuation">:</span> <span class="token number">500000000</span> <span class="token comment" spellcheck="true">// give the funder 5BTC</span> |
||||
|
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token function">assert</span><span class="token punctuation">(</span>cb<span class="token punctuation">.</span>inputs<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">isCoinbase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// Convert the coinbase output to a Coin</span> |
||||
|
<span class="token comment" spellcheck="true">// object and add it to the available coins for that keyring.</span> |
||||
|
<span class="token comment" spellcheck="true">// In reality you might get these coins from a wallet.</span> |
||||
|
coins<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span>Coin<span class="token punctuation">.</span><span class="token function">fromTX</span><span class="token punctuation">(</span>cb<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span> |
||||
|
<span class="token punctuation">}</span></code><button class="copy-button"><img src="../assets/images/clippy.svg" alt="Copy to clipboard"></button></pre><p>The coins object you've created above should look something like this:</p> |
||||
|
<pre class="snippet line-numbers language-json"><code class="line-numbers language-json"><span class="token punctuation">{</span> |
||||
|
<span class="token property">"0"</span><span class="token operator">:</span> |
||||
|
<span class="token punctuation">[</span> |
||||
|
<span class="token punctuation">{</span> |
||||
|
<span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"pubkeyhash"</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"version"</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"height"</span><span class="token operator">:</span> -<span class="token number">1</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"value"</span><span class="token operator">:</span> <span class="token string">"5.0"</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"script"</span><span class="token operator">:</span> <Script<span class="token operator">:</span> OP_DUP OP_HASH160 <span class="token number">0x14</span> <span class="token number">0x64cc4e55b2daec25431bd879ef39302a77c1c1ce</span> OP_EQUALVERIFY OP_CHECKSIG><span class="token punctuation">,</span> |
||||
|
<span class="token property">"coinbase"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"hash"</span><span class="token operator">:</span> <span class="token string">"151e5551cdcec5fff06818fb78ac3d584361276e862b5700110ec8321869d650"</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"index"</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"address"</span><span class="token operator">:</span> <Address<span class="token operator">:</span> type=pubkeyhash version=-<span class="token number">1</span> str=mphvcfcFneRZvyYsmzhy57cSDzFbGrWaRb> |
||||
|
<span class="token punctuation">}</span> |
||||
|
<span class="token punctuation">]</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"1"</span><span class="token operator">:</span> <span class="token punctuation">[</span>...<span class="token punctuation">]</span> |
||||
|
<span class="token punctuation">}</span></code><button class="copy-button"><img src="../assets/images/clippy.svg" alt="Copy to clipboard"></button></pre><h4 id="step-4-prepare-your-coins">Step 4: Prepare your Coins</h4><p>Above, we just funded our funder accounts with a single 5BTC outpoint. This means that the next transaction funded from these accounts can only use that one outpoint (or <em>coin</em>) as an input and send the remainder back as change. Remember, in Bitcoin the way you send funds is you fund a transaction with a full UTXO (in this case we only have one worth 5BTC available to our keychains) and then send the change back to yourself as an additional output. Since ALL|ANYONECANPAY transactions mean a fixed output, you can't add new change outputs without other signatures becoming invalid which means we need a coin available equal to the amount we want to contribute to the crowdfund.</p> |
||||
|
<p>So what we want to do is have each funder create a coin (UTXO) with the value of what they want to donate.</p> |
||||
|
<p>The first thing we need to do make this work is calculate what the input will be. In our examples we are assuming that the funders cover the fee. Since different keyrings can be using different transaction types of different sizes (p2sh, multisig, etc.), we need a utility to calculate how much the fee should be for that input and add that to the amount to fund with.</p> |
||||
|
<h5 id="utility-functions">Utility functions</h5><p>We'll need some utility functions to help us out. It's nice to split these out separate from our main operations since we'll actually be reusing some of the functionality.</p> |
||||
|
<p>Before we build out the tools to calculate fees and split coins, we'll need a utility for composing the inputs for txs that we'll use for our mock transactions in the fee calculator and later for templating our real transaction</p> |
||||
|
<pre class="snippet line-numbers language-javascript"><code class="line-numbers language-javascript"><span class="token keyword">const</span> addInput <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token function">addInput</span><span class="token punctuation">(</span>coin<span class="token punctuation">,</span> inputIndex<span class="token punctuation">,</span> mtx<span class="token punctuation">,</span> keyring<span class="token punctuation">,</span> hashType<span class="token punctuation">)</span> <span class="token punctuation">{</span> |
||||
|
<span class="token keyword">const</span> sampleCoin <span class="token operator">=</span> coin <span class="token keyword">instanceof</span> <span class="token class-name">Coin</span> <span class="token operator">?</span> coin <span class="token punctuation">:</span> Coin<span class="token punctuation">.</span><span class="token function">fromJSON</span><span class="token punctuation">(</span>coin<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>hashType<span class="token punctuation">)</span> hashType <span class="token operator">=</span> Script<span class="token punctuation">.</span>hashType<span class="token punctuation">.</span>ANYONECANPAY <span class="token operator">|</span> Script<span class="token punctuation">.</span>hashType<span class="token punctuation">.</span>ALL<span class="token punctuation">;</span> |
||||
|
|
||||
|
mtx<span class="token punctuation">.</span><span class="token function">addCoin</span><span class="token punctuation">(</span>sampleCoin<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
mtx<span class="token punctuation">.</span><span class="token function">scriptInput</span><span class="token punctuation">(</span>inputIndex<span class="token punctuation">,</span> sampleCoin<span class="token punctuation">,</span> keyring<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
mtx<span class="token punctuation">.</span><span class="token function">signInput</span><span class="token punctuation">(</span>inputIndex<span class="token punctuation">,</span> sampleCoin<span class="token punctuation">,</span> keyring<span class="token punctuation">,</span> hashType<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token function">assert</span><span class="token punctuation">(</span>mtx<span class="token punctuation">.</span><span class="token function">isSigned</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">'Input was not signed properly'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token punctuation">}</span></code><button class="copy-button"><img src="../assets/images/clippy.svg" alt="Copy to clipboard"></button></pre><p>Now let's build the utility for calculating the fee for a single input of unknown type or size</p> |
||||
|
<pre class="snippet line-numbers language-javascript"><code class="line-numbers language-javascript"><span class="token keyword">const</span> getFeeForInput <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token function">getFeeForInput</span><span class="token punctuation">(</span>coin<span class="token punctuation">,</span> address<span class="token punctuation">,</span> keyring<span class="token punctuation">,</span> rate<span class="token punctuation">)</span> <span class="token punctuation">{</span> |
||||
|
<span class="token keyword">const</span> fundingTarget <span class="token operator">=</span> <span class="token number">100000000</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 1 BTC (arbitrary for purposes of this function)</span> |
||||
|
<span class="token keyword">const</span> testMTX <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MTX</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// we're not actually going to use this tx for anything other than calculate what fee should be</span> |
||||
|
<span class="token function">addInput</span><span class="token punctuation">(</span>coin<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> testMTX<span class="token punctuation">,</span> keyring<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token keyword">return</span> testMTX<span class="token punctuation">.</span><span class="token function">getMinFee</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">,</span> rate<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token punctuation">}</span></code><button class="copy-button"><img src="../assets/images/clippy.svg" alt="Copy to clipboard"></button></pre><p>Our last utility is an asyncronous function to help us split the coinbases we created in the previous step (we use the <code>fund</code> method on the <code>MTX</code> primitive to do this, which is asynchronous).</p> |
||||
|
<pre class="snippet line-numbers language-javascript"><code class="line-numbers language-javascript"><span class="token keyword">const</span> splitCoinbase <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">splitCoinbase</span><span class="token punctuation">(</span>funderKeyring<span class="token punctuation">,</span> coin<span class="token punctuation">,</span> targetAmount<span class="token punctuation">,</span> txRate<span class="token punctuation">)</span> <span class="token punctuation">{</span> |
||||
|
<span class="token comment" spellcheck="true">// loop through each coinbase coin to split</span> |
||||
|
<span class="token keyword">let</span> coins <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token keyword">const</span> mtx <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MTX</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token function">assert</span><span class="token punctuation">(</span>coin<span class="token punctuation">.</span>value <span class="token operator">></span> targetAmount<span class="token punctuation">,</span> <span class="token string">'coin value is not enough!'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// creating a transaction that will have an output equal to what we want to fund</span> |
||||
|
mtx<span class="token punctuation">.</span><span class="token function">addOutput</span><span class="token punctuation">(</span><span class="token punctuation">{</span> |
||||
|
address<span class="token punctuation">:</span> funderKeyring<span class="token punctuation">.</span><span class="token function">getAddress</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> |
||||
|
value<span class="token punctuation">:</span> targetAmount |
||||
|
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// the fund method will automatically split</span> |
||||
|
<span class="token comment" spellcheck="true">// the remaining funds to the change address</span> |
||||
|
<span class="token comment" spellcheck="true">// Note that in a real application these splitting transactions will also</span> |
||||
|
<span class="token comment" spellcheck="true">// have to be broadcast to the network</span> |
||||
|
<span class="token keyword">await</span> mtx<span class="token punctuation">.</span><span class="token function">fund</span><span class="token punctuation">(</span><span class="token punctuation">[</span>coin<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> |
||||
|
rate<span class="token punctuation">:</span> txRate<span class="token punctuation">,</span> |
||||
|
<span class="token comment" spellcheck="true">// send change back to an address belonging to the funder</span> |
||||
|
changeAddress<span class="token punctuation">:</span> funderKeyring<span class="token punctuation">.</span><span class="token function">getAddress</span><span class="token punctuation">(</span><span class="token punctuation">)</span> |
||||
|
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token punctuation">{</span> |
||||
|
<span class="token comment" spellcheck="true">// sign the mtx to finalize split</span> |
||||
|
mtx<span class="token punctuation">.</span><span class="token function">sign</span><span class="token punctuation">(</span>funderKeyring<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token function">assert</span><span class="token punctuation">(</span>mtx<span class="token punctuation">.</span><span class="token function">verify</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token keyword">const</span> tx <span class="token operator">=</span> mtx<span class="token punctuation">.</span><span class="token function">toTX</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token function">assert</span><span class="token punctuation">(</span>tx<span class="token punctuation">.</span><span class="token function">verify</span><span class="token punctuation">(</span>mtx<span class="token punctuation">.</span>view<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token keyword">const</span> outputs <span class="token operator">=</span> tx<span class="token punctuation">.</span>outputs<span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// get coins from tx</span> |
||||
|
outputs<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span>outputs<span class="token punctuation">,</span> index<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token punctuation">{</span> |
||||
|
coins<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>Coin<span class="token punctuation">.</span><span class="token function">fromTX</span><span class="token punctuation">(</span>tx<span class="token punctuation">,</span> index<span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token punctuation">}</span><span class="token punctuation">)</span> |
||||
|
<span class="token punctuation">.</span><span class="token keyword">catch</span><span class="token punctuation">(</span>e <span class="token operator">=</span><span class="token operator">></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'There was an error: '</span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token keyword">return</span> coins<span class="token punctuation">;</span> |
||||
|
<span class="token punctuation">}</span><span class="token punctuation">;</span></code><button class="copy-button"><img src="../assets/images/clippy.svg" alt="Copy to clipboard"></button></pre><p>Because of the async methods being used, in order to take advantage of the async/await structure, the rest of the code will be enclosed in an async function.</p> |
||||
|
<p>The first thing we'll do is split the coinbase coins we created earlier using the utility function we just built (we'll also have to calculate the fee and add it to the funding amount in order to get an output of the right value).</p> |
||||
|
<pre class="snippet line-numbers language-javascript"><code class="line-numbers language-javascript"><span class="token keyword">const</span> composeCrowdfund <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">composeCrowdfund</span><span class="token punctuation">(</span>coins<span class="token punctuation">)</span> <span class="token punctuation">{</span> |
||||
|
<span class="token keyword">const</span> funderCoins <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span> |
||||
|
<span class="token comment" spellcheck="true">// Loop through each coinbase</span> |
||||
|
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> index <span class="token keyword">in</span> coins<span class="token punctuation">)</span> <span class="token punctuation">{</span> |
||||
|
<span class="token keyword">const</span> coinbase <span class="token operator">=</span> coins<span class="token punctuation">[</span>index<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span> |
||||
|
<span class="token comment" spellcheck="true">// estimate fee for each coin (assuming their split coins will use same tx type)</span> |
||||
|
<span class="token keyword">const</span> estimatedFee <span class="token operator">=</span> <span class="token function">getFeeForInput</span><span class="token punctuation">(</span>coinbase<span class="token punctuation">,</span> fundeeAddress<span class="token punctuation">,</span> funders<span class="token punctuation">[</span>index<span class="token punctuation">]</span><span class="token punctuation">,</span> txRate<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token keyword">const</span> targetPlusFee <span class="token operator">=</span> amountToFund <span class="token operator">+</span> estimatedFee<span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// split the coinbase with targetAmount plus estimated fee</span> |
||||
|
<span class="token keyword">const</span> splitCoins <span class="token operator">=</span> <span class="token keyword">await</span> Utils<span class="token punctuation">.</span><span class="token function">splitCoinbase</span><span class="token punctuation">(</span>funders<span class="token punctuation">[</span>index<span class="token punctuation">]</span><span class="token punctuation">,</span> coinbase<span class="token punctuation">,</span> targetPlusFee<span class="token punctuation">,</span> txRate<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// add to funderCoins object with returned coins from splitCoinbase being value,</span> |
||||
|
<span class="token comment" spellcheck="true">// and index being the key</span> |
||||
|
funderCoins<span class="token punctuation">[</span>index<span class="token punctuation">]</span> <span class="token operator">=</span> splitCoins<span class="token punctuation">;</span> |
||||
|
<span class="token punctuation">}</span> |
||||
|
<span class="token comment" spellcheck="true">// ... we'll keep filling out the rest of the code here</span> |
||||
|
<span class="token punctuation">}</span><span class="token punctuation">;</span></code><button class="copy-button"><img src="../assets/images/clippy.svg" alt="Copy to clipboard"></button></pre><p><code>funderCoins</code> should return x number of coin arrays, where X is the number of coinbases we created earlier (should be 2) with each array having a coin equal to the amount we want to donate.</p> |
||||
|
<p>For example...</p> |
||||
|
<pre class="snippet line-numbers language-json"><code class="line-numbers language-json"><span class="token punctuation">{</span> |
||||
|
<span class="token property">"0"</span><span class="token operator">:</span> |
||||
|
<span class="token punctuation">[</span> |
||||
|
<span class="token punctuation">{</span> |
||||
|
<span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"pubkeyhash"</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"version"</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"height"</span><span class="token operator">:</span> -<span class="token number">1</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"value"</span><span class="token operator">:</span> <span class="token string">"0.5000157"</span> |
||||
|
<span class="token property">"script"</span><span class="token operator">:</span> <Script<span class="token operator">:</span> OP_DUP OP_HASH160 <span class="token number">0x14</span> <span class="token number">0x62f725e83caf894aa6c3efd29ef28649fc448825</span> OP_EQUALVERIFY OP_CHECKSIG><span class="token punctuation">,</span> |
||||
|
<span class="token property">"coinbase"</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"hash"</span><span class="token operator">:</span> <span class="token string">"774822d84bd5af02f1b3eacd6215e0a1bcf07cfb6675a000c8a01d2ea34f2a32"</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"index"</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"address"</span><span class="token operator">:</span> <Address<span class="token operator">:</span> type=pubkeyhash version=-<span class="token number">1</span> str=mpYEb17KR7MVhuPZT1GsW3SywZx8ihYube> |
||||
|
<span class="token punctuation">}</span><span class="token punctuation">,</span> |
||||
|
... |
||||
|
<span class="token punctuation">]</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"1"</span><span class="token operator">:</span> <span class="token punctuation">[</span>...<span class="token punctuation">]</span> |
||||
|
<span class="token punctuation">}</span></code><button class="copy-button"><img src="../assets/images/clippy.svg" alt="Copy to clipboard"></button></pre><h4 id="step-6-construct-the-transaction">Step 6: Construct the Transaction</h4><p>Now that we've got our tools and coins ready, we can start to build the transaction!</p> |
||||
|
<pre class="snippet line-numbers language-javascript"><code class="line-numbers language-javascript"><span class="token keyword">const</span> composeCrowdfund <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">composeCrowdfund</span><span class="token punctuation">(</span>coins<span class="token punctuation">)</span> <span class="token punctuation">{</span> |
||||
|
<span class="token comment" spellcheck="true">//...</span> |
||||
|
|
||||
|
<span class="token keyword">const</span> fundMe <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MTX</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// add an output with the target funding amount</span> |
||||
|
|
||||
|
fundMe<span class="token punctuation">.</span><span class="token function">addOutput</span><span class="token punctuation">(</span><span class="token punctuation">{</span> value<span class="token punctuation">:</span> fundingTarget<span class="token punctuation">,</span> address<span class="token punctuation">:</span> fundeeAddress <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// fund with first funder</span> |
||||
|
<span class="token keyword">let</span> fundingCoin <span class="token operator">=</span> funderCoins<span class="token punctuation">[</span><span class="token string">'0'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span> |
||||
|
<span class="token function">addInput</span><span class="token punctuation">(</span>fundingCoin<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> fundMe<span class="token punctuation">,</span> funder1Keyring<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// fund with second funder</span> |
||||
|
fundingCoin <span class="token operator">=</span> funderCoins<span class="token punctuation">[</span><span class="token string">'1'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span> |
||||
|
<span class="token function">addInput</span><span class="token punctuation">(</span>fundingCoin<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> fundMe<span class="token punctuation">,</span> funder2Keyring<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// We want to confirm that total value of inputs covers the funding goal</span> |
||||
|
<span class="token comment" spellcheck="true">// NOTE: the difference goes to the miner in the form of fees</span> |
||||
|
<span class="token function">assert</span><span class="token punctuation">(</span>fundMe<span class="token punctuation">.</span><span class="token function">getInputValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">>=</span> fundMe<span class="token punctuation">.</span>outputs<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>value<span class="token punctuation">,</span> <span class="token string">'Total inputs not enough to fund'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token function">assert</span><span class="token punctuation">(</span>fundMe<span class="token punctuation">.</span><span class="token function">verify</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">'The mtx is malformed'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token keyword">const</span> tx <span class="token operator">=</span> fundMe<span class="token punctuation">.</span><span class="token function">toTX</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'total input value = '</span><span class="token punctuation">,</span> fundMe<span class="token punctuation">.</span><span class="token function">getInputValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Fee getting sent to miners:'</span><span class="token punctuation">,</span> fundMe<span class="token punctuation">.</span><span class="token function">getInputValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> fundingTarget<span class="token punctuation">,</span> <span class="token string">'satoshis'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token function">assert</span><span class="token punctuation">(</span>tx<span class="token punctuation">.</span><span class="token function">verify</span><span class="token punctuation">(</span>fundMe<span class="token punctuation">.</span>view<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">'there is a problem with your tx'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token keyword">return</span> tx<span class="token punctuation">;</span> |
||||
|
<span class="token punctuation">}</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token function">composeCrowdfund</span><span class="token punctuation">(</span>coins<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>myCrowdfundTx <span class="token operator">=</span><span class="token operator">></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>myCrowdfundTx<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><button class="copy-button"><img src="../assets/images/clippy.svg" alt="Copy to clipboard"></button></pre><p><code>composeCrowdfund</code> will now return a promise (thanks to async/await) that returns a fully templated transaction that can be transmitted to the network. It should have two inputs and one output with a value of 1. The inputs will have a value equal to the amount to fund plus the cost of the fee. You should also notice the <code>SIGHASH</code> flag 0x81 at the end of the input scripts which confirms they are <code>ALL|ANYONECANPAY</code> scripts.</p> |
||||
|
<h3 id="version-2-using-the-bcoin-wallet-system">Version 2: Using the Bcoin Wallet System</h3><p>Since trying to manage your own keys is pretty tedious, not to mention impractical especially if you want to let other people contribute to your campaign, let's use the bcoin wallet system to take care of that part of the process. For the this example, we're going to interact via the wallet client, but you could also do something similar within a bcoin node (using the wallet and walletdb directly) which would also be more secure.</p> |
||||
|
<p>Note that this is a little more tedious to test since you need to have funded wallets in order to actually fund your campaign. You can find a bitcoin testnet faucet to get some funds to play with or, as we will do in this example, create your own simnet or regtest network where you mine your own blocks to fund yourself.</p> |
||||
|
<h4 id="step-1-setup-our-wallets">Step 1: Setup Our Wallets</h4><p>We'll skip the setup of constants import modules since we can use the same from the previous example. Since there are a lot of asynchronous operations here now that we're using the client, we'll also put this in an async function and will continue to build out the contents of the function throughout this guide. We'll also be using some of the same <a href="#utility-functions">utility functions that we created in the last example</a>.</p> |
||||
|
<pre class="snippet line-numbers language-javascript"><code class="line-numbers language-javascript"><span class="token keyword">const</span> network <span class="token operator">=</span> <span class="token string">'regtest'</span><span class="token punctuation">;</span> |
||||
|
<span class="token keyword">const</span> composeWalletCrowdfund <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">composeWalletCrowdfund</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> |
||||
|
<span class="token keyword">const</span> client <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">bcoin<span class="token punctuation">.</span>http<span class="token punctuation">.</span>Client</span><span class="token punctuation">(</span><span class="token punctuation">{</span> network <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// Step 1: Setup our wallets and funding targets</span> |
||||
|
<span class="token keyword">const</span> fundeeWallet <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">httpWallet</span><span class="token punctuation">(</span><span class="token punctuation">{</span> id<span class="token punctuation">:</span> <span class="token string">'fundee'</span><span class="token punctuation">,</span> network <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token keyword">const</span> fundeeAddress <span class="token operator">=</span> <span class="token keyword">await</span> fundeeWallet<span class="token punctuation">.</span><span class="token function">createAddress</span><span class="token punctuation">(</span><span class="token string">'default'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token keyword">const</span> funders <span class="token operator">=</span> <span class="token punctuation">{</span> |
||||
|
<span class="token string">'funder1'</span><span class="token punctuation">:</span> <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">httpWallet</span><span class="token punctuation">(</span><span class="token punctuation">{</span> id<span class="token punctuation">:</span> <span class="token string">'funder1'</span><span class="token punctuation">,</span> network <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> |
||||
|
<span class="token string">'funder2'</span><span class="token punctuation">:</span> <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">httpWallet</span><span class="token punctuation">(</span><span class="token punctuation">{</span> id<span class="token punctuation">:</span> <span class="token string">'funder2'</span><span class="token punctuation">,</span> network <span class="token punctuation">}</span><span class="token punctuation">)</span> |
||||
|
<span class="token punctuation">}</span><span class="token punctuation">;</span> |
||||
|
<span class="token comment" spellcheck="true">//...</span> |
||||
|
<span class="token punctuation">}</span></code><button class="copy-button"><img src="../assets/images/clippy.svg" alt="Copy to clipboard"></button></pre><h4 id="step-2-prepare-your-coins">Step 2: Prepare Your Coins</h4><p>We have the same issue to deal with here as in step 2 in the previous example: we need to have "exact change" coins available for funding. The process of splitting is a little different with the wallet system though so let's walk through this version.</p> |
||||
|
<p>Note that the way that we're retrieving the keyring information is pretty insecure as we are sending private keys unencrypted across the network, but we'll leave for the sake of the example. <strong>DON'T USE THIS IN PRODUCTION</strong>.</p> |
||||
|
<pre class="snippet line-numbers language-javascript"><code class="line-numbers language-javascript"><span class="token keyword">const</span> composeWalletCrowdfund <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">composeWalletCrowdfund</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> |
||||
|
<span class="token comment" spellcheck="true">//...</span> |
||||
|
|
||||
|
<span class="token keyword">const</span> fundingCoins <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// go through each funding wallet to prepare coins</span> |
||||
|
<span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> id <span class="token keyword">in</span> funders<span class="token punctuation">)</span> <span class="token punctuation">{</span> |
||||
|
<span class="token keyword">const</span> funder <span class="token operator">=</span> funders<span class="token punctuation">[</span>id<span class="token punctuation">]</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token keyword">const</span> coins <span class="token operator">=</span> <span class="token keyword">await</span> funder<span class="token punctuation">.</span><span class="token function">getCoins</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token keyword">const</span> funderInfo <span class="token operator">=</span> <span class="token keyword">await</span> funder<span class="token punctuation">.</span><span class="token function">getInfo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// Before we do anything we need to get</span> |
||||
|
<span class="token comment" spellcheck="true">// the fee that will be necessary for each funder's input.</span> |
||||
|
<span class="token keyword">const</span> funderKey <span class="token operator">=</span> <span class="token keyword">await</span> funder<span class="token punctuation">.</span><span class="token function">getWIF</span><span class="token punctuation">(</span>coins<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>address<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token keyword">const</span> funderKeyring <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">bcoin<span class="token punctuation">.</span>keyring<span class="token punctuation">.</span>fromSecret</span><span class="token punctuation">(</span>funderKey<span class="token punctuation">.</span>privateKey<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token keyword">const</span> feeForInput <span class="token operator">=</span> Utils<span class="token punctuation">.</span><span class="token function">getFeeForInput</span><span class="token punctuation">(</span>coins<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> fundeeAddress<span class="token punctuation">.</span>address<span class="token punctuation">,</span> funderKeyring<span class="token punctuation">,</span> rate<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
amountToFund <span class="token operator">+</span><span class="token operator">=</span> feeForInput<span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// Next, go through available coins</span> |
||||
|
<span class="token comment" spellcheck="true">// to find a coin equal to or greater than value to fund</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// We didn't do this before because we knew what coins were available. But if we have one already in our wallets, then we can just use that!</span> |
||||
|
<span class="token keyword">let</span> fundingCoin <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span> |
||||
|
<span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> coin <span class="token keyword">of</span> coins<span class="token punctuation">)</span> <span class="token punctuation">{</span> |
||||
|
<span class="token keyword">if</span> <span class="token punctuation">(</span>coin<span class="token punctuation">.</span>value <span class="token operator">===</span> amountToFund<span class="token punctuation">)</span> <span class="token punctuation">{</span> |
||||
|
<span class="token comment" spellcheck="true">// if we already have a coin of the right value we can use that</span> |
||||
|
fundingCoin <span class="token operator">=</span> coin<span class="token punctuation">;</span> |
||||
|
<span class="token keyword">break</span><span class="token punctuation">;</span> |
||||
|
<span class="token punctuation">}</span> |
||||
|
<span class="token punctuation">}</span> |
||||
|
|
||||
|
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>fundingCoin<span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token punctuation">{</span> |
||||
|
<span class="token comment" spellcheck="true">// if we don't have a coin of the right amount to fund with</span> |
||||
|
<span class="token comment" spellcheck="true">// we need to create one by sending the funder wallet</span> |
||||
|
<span class="token comment" spellcheck="true">// a tx that includes an output of the right amount</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// this is similar to what we did in the manual version</span> |
||||
|
<span class="token keyword">const</span> receiveAddress <span class="token operator">=</span> <span class="token keyword">await</span> funder<span class="token punctuation">.</span><span class="token function">createAddress</span><span class="token punctuation">(</span><span class="token string">'default'</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// send it back to the funder</span> |
||||
|
<span class="token keyword">const</span> tx <span class="token operator">=</span> <span class="token keyword">await</span> funder<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token punctuation">{</span> |
||||
|
rate<span class="token punctuation">,</span> |
||||
|
outputs<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">{</span> |
||||
|
value<span class="token punctuation">:</span> amountToFund<span class="token punctuation">,</span> |
||||
|
address<span class="token punctuation">:</span> receiveAddress<span class="token punctuation">.</span>address |
||||
|
<span class="token punctuation">}</span><span class="token punctuation">]</span> |
||||
|
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// get index of ouput for fundingCoin</span> |
||||
|
<span class="token keyword">let</span> coinIndex<span class="token punctuation">;</span> |
||||
|
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> tx<span class="token punctuation">.</span>outputs<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> |
||||
|
<span class="token keyword">if</span> <span class="token punctuation">(</span>tx<span class="token punctuation">.</span>outputs<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>value <span class="token operator">===</span> amountToFund<span class="token punctuation">)</span> <span class="token punctuation">{</span> |
||||
|
coinIndex <span class="token operator">=</span> i<span class="token punctuation">;</span> |
||||
|
<span class="token keyword">break</span><span class="token punctuation">;</span> |
||||
|
<span class="token punctuation">}</span> |
||||
|
<span class="token punctuation">}</span> |
||||
|
|
||||
|
<span class="token function">assert</span><span class="token punctuation">(</span>tx<span class="token punctuation">.</span>outputs<span class="token punctuation">[</span>coinIndex<span class="token punctuation">]</span><span class="token punctuation">.</span>value <span class="token operator">===</span> amountToFund<span class="token punctuation">,</span> <span class="token string">'value of output at index not correct'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// first argument is for the account</span> |
||||
|
<span class="token comment" spellcheck="true">// default is being used for all examples</span> |
||||
|
fundingCoin <span class="token operator">=</span> <span class="token keyword">await</span> funder<span class="token punctuation">.</span><span class="token function">getCoin</span><span class="token punctuation">(</span><span class="token string">'default'</span><span class="token punctuation">,</span> tx<span class="token punctuation">.</span>hash<span class="token punctuation">,</span> coinIndex<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token punctuation">}</span> |
||||
|
fundingCoins<span class="token punctuation">[</span>funder<span class="token punctuation">.</span>id<span class="token punctuation">]</span> <span class="token operator">=</span> fundingCoin<span class="token punctuation">;</span> |
||||
|
<span class="token punctuation">}</span> |
||||
|
<span class="token punctuation">}</span></code><button class="copy-button"><img src="../assets/images/clippy.svg" alt="Copy to clipboard"></button></pre><p>We now should have an object available that has the information of the coins we will be using to fund our transaction with mapped to the name of each wallet (so we can retrieve the wallet information later). It should look something like this (note that the values are larger than our funding amount due to the fee):</p> |
||||
|
<pre class="snippet line-numbers language-json"><code class="line-numbers language-json"><span class="token punctuation">{</span> |
||||
|
<span class="token property">"funder1"</span><span class="token operator">:</span> <span class="token punctuation">{</span> |
||||
|
<span class="token property">"version"</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"height"</span><span class="token operator">:</span> -<span class="token number">1</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"value"</span><span class="token operator">:</span> <span class="token number">50002370</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"script"</span><span class="token operator">:</span> <span class="token string">"76a914127cb1a40212169c49fe22d13307b18af1fa07ad88ac"</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"address"</span><span class="token operator">:</span> <span class="token string">"SNykaBMuTyeUQkK8exZyymWNrnYX5vVPuY"</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"coinbase"</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"hash"</span><span class="token operator">:</span> <span class="token string">"163068016a39e2d9c869bcdb8646dbca93e07824db39217b5c444e7c61d1a82c"</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"index"</span><span class="token operator">:</span> <span class="token number">0</span> |
||||
|
<span class="token punctuation">}</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"funder2"</span><span class="token operator">:</span> <span class="token punctuation">{</span> |
||||
|
<span class="token property">"version"</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"height"</span><span class="token operator">:</span> -<span class="token number">1</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"value"</span><span class="token operator">:</span> <span class="token number">50002370</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"script"</span><span class="token operator">:</span> <span class="token string">"76a9146e08c73b355e690ba0b1198d578c4c6c52b3813688ac"</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"address"</span><span class="token operator">:</span> <span class="token string">"SXKossD65D7h62fhzGrntERBrPeXUfiC92"</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"coinbase"</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"hash"</span><span class="token operator">:</span> <span class="token string">"11f3180c5069f2692f1ee1463257b21dc217441e792493c8f5ee230c35d97d96"</span><span class="token punctuation">,</span> |
||||
|
<span class="token property">"index"</span><span class="token operator">:</span> <span class="token number">0</span> |
||||
|
<span class="token punctuation">}</span> |
||||
|
<span class="token punctuation">}</span></code><button class="copy-button"><img src="../assets/images/clippy.svg" alt="Copy to clipboard"></button></pre><h4 id="step-3-create-our-crowdfund-tx">Step 3: Create our Crowdfund Tx</h4><p>Now that we have our coins ready we can start to template the transaction!</p> |
||||
|
<p>Please note again that the way we are funding the transactions is by sending our private keys unencrypted across the network so don't use this in production.</p> |
||||
|
<pre class="snippet line-numbers language-javascript"><code class="line-numbers language-javascript"><span class="token keyword">const</span> composeWalletCrowdfund <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">composeWalletCrowdfund</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> |
||||
|
<span class="token comment" spellcheck="true">//...</span> |
||||
|
<span class="token keyword">const</span> fundMe <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MTX</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// Use the maxFee to calculate output value for transaction</span> |
||||
|
fundMe<span class="token punctuation">.</span><span class="token function">addOutput</span><span class="token punctuation">(</span><span class="token punctuation">{</span>value<span class="token punctuation">:</span> fundingTarget<span class="token punctuation">,</span> address<span class="token punctuation">:</span> fundeeAddress<span class="token punctuation">.</span>address <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// go through our coins and add each as an input in our transaction</span> |
||||
|
<span class="token keyword">let</span> inputCounter <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> |
||||
|
<span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> funder <span class="token keyword">in</span> fundingCoins<span class="token punctuation">)</span> <span class="token punctuation">{</span> |
||||
|
<span class="token keyword">const</span> wallet <span class="token operator">=</span> funders<span class="token punctuation">[</span>funder<span class="token punctuation">]</span><span class="token punctuation">;</span> |
||||
|
<span class="token keyword">const</span> coinOptions <span class="token operator">=</span> fundingCoins<span class="token punctuation">[</span>funder<span class="token punctuation">]</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token keyword">const</span> key <span class="token operator">=</span> <span class="token keyword">await</span> wallet<span class="token punctuation">.</span><span class="token function">getWIF</span><span class="token punctuation">(</span>coinOptions<span class="token punctuation">.</span>address<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token keyword">const</span> keyring <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">bcoin<span class="token punctuation">.</span>keyring<span class="token punctuation">.</span>fromSecret</span><span class="token punctuation">(</span>key<span class="token punctuation">.</span>privateKey<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// this is the same utility as we used in our other example</span> |
||||
|
<span class="token function">addInput</span><span class="token punctuation">(</span>coinOptions<span class="token punctuation">,</span> inputCounter<span class="token punctuation">,</span> fundMe<span class="token punctuation">,</span> keyring<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token function">assert</span><span class="token punctuation">(</span>fundMe<span class="token punctuation">.</span><span class="token function">isSigned</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">'Input has not been signed correctly'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
inputCounter<span class="token operator">++</span><span class="token punctuation">;</span> |
||||
|
<span class="token punctuation">}</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// confirm that the transaction has been properly templated and signed</span> |
||||
|
<span class="token function">assert</span><span class="token punctuation">(</span> |
||||
|
fundMe<span class="token punctuation">.</span>inputs<span class="token punctuation">.</span>length <span class="token operator">===</span> Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>funders<span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">,</span> |
||||
|
<span class="token string">'Number of inputs in MTX is incorrect'</span> |
||||
|
<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token function">assert</span><span class="token punctuation">(</span>fundMe<span class="token punctuation">.</span><span class="token function">verify</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">'MTX is malformed'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// make our transaction immutable so we can send it to th enetwork</span> |
||||
|
<span class="token keyword">const</span> tx <span class="token operator">=</span> fundMe<span class="token punctuation">.</span><span class="token function">toTX</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token function">assert</span><span class="token punctuation">(</span>tx<span class="token punctuation">.</span><span class="token function">verify</span><span class="token punctuation">(</span>fundMe<span class="token punctuation">.</span>view<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">'TX is malformed. Fix before broadcasting'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// check the value of our inputs just to confirm what the fees are</span> |
||||
|
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Total value of inputs: '</span><span class="token punctuation">,</span> fundMe<span class="token punctuation">.</span><span class="token function">getInputValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Fee to go to miners: '</span><span class="token punctuation">,</span> fundMe<span class="token punctuation">.</span><span class="token function">getInputValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> fundingTarget<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
|
||||
|
<span class="token comment" spellcheck="true">// Finally, broadcast tx</span> |
||||
|
<span class="token keyword">try</span> <span class="token punctuation">{</span> |
||||
|
<span class="token keyword">const</span> broadcastStatus <span class="token operator">=</span> <span class="token keyword">await</span> client<span class="token punctuation">.</span><span class="token function">broadcast</span><span class="token punctuation">(</span>tx<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token keyword">return</span> tx<span class="token punctuation">;</span> |
||||
|
<span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> |
||||
|
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'There was a problem: '</span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span> |
||||
|
<span class="token punctuation">}</span> |
||||
|
<span class="token punctuation">}</span> |
||||
|
<span class="token function">composeWalletCrowdfund</span><span class="token punctuation">(</span><span class="token punctuation">)</span> |
||||
|
<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>myCrowdfundTx <span class="token operator">=</span><span class="token operator">></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Transaction broadcast: '</span><span class="token punctuation">,</span> myCrowdfundTx<span class="token punctuation">)</span><span class="token punctuation">)</span> |
||||
|
<span class="token punctuation">.</span><span class="token keyword">catch</span><span class="token punctuation">(</span>e <span class="token operator">=</span><span class="token operator">></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'There was a problem: '</span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><button class="copy-button"><img src="../assets/images/clippy.svg" alt="Copy to clipboard"></button></pre><p>And there you have it! If you were doing this on testnet, your <code>fundeeWallet</code> should now be 1BTC richer. If you're on a simnet or regtest network, you'll have to mine a block with your transactions to get those funds confirmed. Also note that, unless you have exact change coins, there will be 3 transactions that need to be confirmed: one each for the wallets that are splitting coins, and one for the crowdfund transaction.</p> |
||||
|
<h2 id="how-to-build-it-out">How to Build it Out</h2><p>These examples are obviously pretty basic, but they should give you an idea of how to use Bitcoin's scripting to build out the foundation for more complex applications. Here are some ideas on how you could build on top of these examples and get closer to a production ready application.</p> |
||||
|
<ul> |
||||
|
<li>More flexible contribution scheme (currently it's just 2 funders that split the amount evenly). E.g. custom number of contributers, custom contribution amount, etc.</li> |
||||
|
<li>UX to let people interact with the transaction via a browser</li> |
||||
|
<li>More advanced interface for fee estimation and include platform for large number of funders (for example, since you may be limited to number of funders per tx, you could include interface for multiple transactions for a single campaign. You would also want to include a check to make sure your tx is not bigger than 100kb otherwise it'll get rejected by the network)</li> |
||||
|
<li>Add a fund matching scheme where someone can say they will match future contributions</li> |
||||
|
<li>Currently the examples split transactions to make a coin available that equals the target contribution amount. This is expensive since you have to broadcast multiple transactions. An interface to choose to donate from available available coins might help to make this more efficient.</li> |
||||
|
</ul> |
||||
|
<p>Make sure to get in touch with us on Twitter or Slack if you build out any of these ideas!</p> |
||||
|
<p>Again, for a working example of the code (without all the text and explanations), check out <a href="https://github.com/Bucko13/bitcoin-fundraise">the repo on github</a>.</p> |
||||
|
|
||||
|
|
||||
|
<!-- END OF GUIDE --> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</article> |
||||
|
<!-- END OF ARTICLE CONTAINER --> |
||||
|
|
||||
|
</div> |
||||
|
<!-- END BLOG CONTENT --> |
||||
|
|
||||
|
|
||||
|
|
||||
|
</div><!-- .row --> |
||||
|
|
||||
|
</div> |
||||
|
</section> |
||||
|
<!-- END NEW BLOGS --> |
||||
|
|
||||
|
</div><!-- .row --> |
||||
|
|
||||
|
</div> |
||||
|
</section> |
||||
|
<!-- END BLOGS --> |
||||
|
|
||||
|
|
||||
|
<!-- PARALLAX DOCS CTA --> |
||||
|
<section class="module bg-white-alfa-30 parallax color-white" data-background="../assets/images/bg-header.jpg"> |
||||
|
<div class="container"> |
||||
|
|
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12"> |
||||
|
<div class="text-center"> |
||||
|
<h2 class="montserrat text-uppercase m-b-30">Ready to start building? Read the docs!</h2> |
||||
|
<a href="http://bcoin.io/docs/index.html" target="_blank" class="btn btn-lg btn-purple">Documentation</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div><!-- .row --> |
||||
|
|
||||
|
</div> |
||||
|
</section> |
||||
|
<!-- END PARALLAX DOCS CTA --> |
||||
|
|
||||
|
<!-- FOOTER --> |
||||
|
<footer id="footer" class="footer-minimal"> |
||||
|
<div class="container"> |
||||
|
|
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12"> |
||||
|
<ul class="social-icons social-icons-circle text-center m-b-35"> |
||||
|
<li><a href="https://twitter.com/bcoin"><i class="fa fa-twitter"></i></a></li> |
||||
|
<li><a href="https://github.com/bcoin-org/bcoin"><i class="fa fa-github"></i></a></li> |
||||
|
|
||||
|
|
||||
|
<li><a href="../slack-signup.html" target="_blank"><i class="fa fa-slack"></i></a></li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12 text-center m-b-35"> |
||||
|
<img class="m-b-35 QR-code" src="../assets/images/donation_QR.png"/> |
||||
|
<p class="m-0"><strong>Bcoin Development Donation Address:</strong><br />3Bi9H1hzCHWJoFEjc4xzVzEMywi35dyvsV</p> |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12 text-center"> |
||||
|
<p class="m-0">Copyright <a href="#">bcoin.io, Purse</a>, 2017, All Rights Reserved.</p> |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
</footer> |
||||
|
<!-- END FOOTER --> |
||||
|
|
||||
|
</div> |
||||
|
<!-- /WRAPPER --> |
||||
|
|
||||
|
<!-- JAVASCRIPT FILES --> |
||||
|
|
||||
|
<script src="../assets/js/jquery-2.2.3.min.js"></script> |
||||
|
<script src="http://maps.googleapis.com/maps/api/js?v=3"></script> |
||||
|
<script src="../assets/bootstrap/js/bootstrap.min.js"></script> |
||||
|
<script src="../assets/js/plugins.min.js"></script> |
||||
|
<script src="../assets/js/stickyfill.min.js"></script> |
||||
|
<script src="../assets/js/custom.min.js"></script> |
||||
|
<script src="../assets/js/clipboard.min.js"></script> |
||||
|
<script async defer src="../assets/js/prism.js"></script> |
||||
|
<script> |
||||
|
var copyCode = new Clipboard('.copy-button', { |
||||
|
target: function(trigger) { |
||||
|
return trigger.previousElementSibling; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
copyCode.on('success', function(event) { |
||||
|
event.trigger.classList.add('success'); |
||||
|
event.clearSelection(); |
||||
|
// event.trigger.textContent = 'Copied!'; |
||||
|
window.setTimeout(function() { |
||||
|
event.trigger.classList.remove('success'); |
||||
|
}, 2000); |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<!-- github button js --> |
||||
|
<script async defer src="https://buttons.github.io/buttons.js"></script> |
||||
|
|
||||
|
</body> |
||||
|
</html> |
Loading…
Reference in new issue