Pochette is a Bitcoin Wallet for developers, or more accurately a tool for building "single purpose wallets".
It's used extensively at Bitex.la from checking and crediting customer bitcoin deposits to preparing transactions with bip32 paths instead of input addresess, ready to be signed with a Trezor Device
Pochette offers a common interface to full bitcoin nodes like Bitcoin Core Blockchain.info or Toshi and will let you run several instances of each one of them simultaneously always choosing the most recent node to query.
It also provides a Pochette::TransactionBuilder class which receives a list of 'source' addresses and a list of recipients as "address/amount" pairs and uses them to select unspent outputs and build a raw transaction to be signed and broadcasted.
The Pochette::TrezorTransactionBuilder class extends Pochette::TransactionBuilder including transactions, inputs and outputs that are formatted in a way they can be passed directly to a Trezor device for signing. You can even build transactions for multisig addresses to be signed by several trezors.
- Installation and Setup
- Pochette::TransactionBuilder
- Pochette::TrezorTransactionBuilder
- Backend API
- Supported Backends
Add this line to your application's Gemfile:
gem 'pochette'
And then execute:
$ bundle
Or install it yourself as:
$ gem install pochette
You will probably want to setup Pochette with a global default backend, the backend can also be configured separately for each instance of a Pochette::TransactionBuilder
> Pochette.backend = Pochette::Backends::BlockchainInfo.new
> Pochette::TransactionBuilder.backend = Pochette::Backends::BlockchainInfo.new
> Pochette::TrezorTransactionBuilder.backend = Pochette::Backends::BlockchainInfo.new
Pochette can also be configured to use the bitcoin testnet, this will change the default network used by the Bitcoin gem and may alter the way backends work too.
> Pochette.testnet = true
The TransactionBuilder builds transactions from a list of source addresses and a list of recipients, using a configured backend to fetch unspent outputs and related transaction data. Instantiating will perform all the required queries, you'll be left with a TransactionBuilder object that is either valid? or not, and if valid, you can query the results via to_hash.
The TransactionBuilder's initializer receives a single options hash with:
- addresses:
- List of addresses in wallet. We will be spending their unspent outputs.
- outputs:
- List of pairs [recipient_address, amount] This will not be all the final outputs in the transaction, as a 'change' output may be added if needed.
- utxo_blacklist:
- Optional. List of utxos to ignore, a list of pairs [transaction hash, position]
- change_address:
- Optional. Change address to use. Will default to the first source address.
- fee_per_kb:
- Optional. Defaults to 10000 satoshis.
- spend_all:
- Optional. Boolean. Wether to spend all available utxos or just select enough to cover the given outputs.
A hash with
- input_total:
- The sum of all input amounts, in satoshis.
- output_total:
- The sum of all outputs, in satoshis.
- fee:
- fee to pay (input_total - output_total).
- outputs:
- Array of [destination address, amount in satoshis]
- inputs:
- Array of [input address, utxo transaction hash, utxo position, amount, scriptPubKey]
- utxos_to_blacklist:
- Transaction inputs formatted to be used as utxo_blacklist on another TransactionBuilder.
> require 'pochette'
> backend = Pochette::Backends::BlockchainInfo.new
> transaction = Pochette::TransactionBuilder.new({
addresses: ["2NAHscN6XVqUPzBSJHC3fhkeF5SQVxiR9p9"],
outputs: [
["mvtUvWSWCU7knrcMVzjcKJgjL1LdekLK5q", 1_0000_0000],
],
utxo_blacklist: [
["0ded7f014fa3213e9b000bc81b8151bc6f2f926b9afea6e3643c8ad658353c72", 1]
],
change_address: 'mgaTEUM4ZE9kLiK58FcffwHfvxpte5CfvE',
fee_per_kb: 10000,
spend_all: false,
backend: backend
})
> transaction.valid?
=> true
> transaction.as_hash
=> {
input_total: 2_0000_0000,
output_total: 1_9999_0000,
fee: 10000,
outputs: [
["mvtUvWSWCU7knrcMVzjcKJgjL1LdekLK5q", 1_0000_0000],
["mgaTEUM4ZE9kLiK58FcffwHfvxpte5CfvE", 9999_0000],
],
inputs: [
[ "2NAHscN6XVqUPzBSJHC3fhkeF5SQVxiR9p9",
"956b30c3c4335f019dbee60c60d76994319473acac356f774c7858cd5c968e40",
1,
200000000,
'public_key_in_hex_format'
],
],
utxos_to_blacklist: [
["956b30c3c4335f019dbee60c60d76994319473acac356f774c7858cd5c968e40", 1]
],
}
Builds a transaction like TransactionBuilder but includes transaction data and formats inputs and outputs in a way that can be sent directly to your trezor device. If you're using Trezor Connect for signing then you won't need to pass in the transactions.
The TrezorTransactionBuilder's initializer receives a single options hash with:
- bip32_addresses:
-
List of addresses in wallet. We will be spending their unspent outputs.
Each address is represented as a pair, with the public address string
and the BIP32 path as a list of integers, for example:
['public-address-as-string', [44, 1, 3, 11]]
If you're spending from a multisig address then you should provide all the root xpubs ( session.getPublicKey([]) ) from your different trezor devices, the path as a list of integers and the M number (as in M out of N). The actual bitcoin address will be derived from these. The following example is for a multi-sig address where 2 out of 3 trezors can sign. The address is generated from the public key at path [42, 1, 1] in each trezor.
[['xpub661MyMwAqRbcGCmcnz4JtnieVyuvgQFGqZqw3KS1g9khndpF3segkAYbYCKKaQ9Di2ZuWLaZU4Axt7TrKq41aVYx8XTbDbQFzhhDMntKLU5', 'xpub661MyMwAqRbcFwc3Nmz8WmMU9okGmeVSmuprwNHCVsfhy6vMyg6g79octqwNftK4g62TMWmb7UtVpnAWnANzqwtKrCDFe2UaDCv1HoErssE' 'xpub661MyMwAqRbcGkqPSKVkwTMtFZzEpbWXjM4t1Dv1XQbfMxtyLRGupWkp3fcSCDtp6nd1AUrRtq8tnFGTYgkY1pB9muwzaBDnJSMo2rVENhz'], [42,1,1], 2]
- outputs:
- List of pairs [recipient_address, amount] This will not be all the final outputs in the transaction, as a 'change' output may be adted if needed.
- utxo_blacklist:
- Optional. List of utxos to ignore, a list of pairs [transaction hash, position]
- change_address:
- Optional. Change address to use. Will default to the first source address.
- fee_per_kb:
- Optional. Defaults to 10000 satoshis.
- spend_all:
- Optional. Boolean. Wether to spend all available utxos or just select enough to cover the given outputs.
- input_total:
- The sum of all input amounts, in satoshis.
- output_total:
- The sum of all outputs, in satoshis.
- fee:
- fee to pay (input_total - output_total).
- outputs:
- Array of [destination address, amount in satoshis]
- inputs:
- Array of [input address, utxo transaction hash, utxo position, amount]
- utxos_to_blacklist:
- Transaction inputs formatted to be used as utxo_blacklist on another TransactionBuilder.
- transactions:
- Transaction data for each input.
- trezor_inputs:
- List of inputs as hashes with bip32 paths instead of addresses { address_n: [42,1,1], prev_hash: "956b30c3c4335f019dbee60c60d76994319473acac356f774c7858cd5c968e40", prev_index: 1} When spending from a multisig address, the inputs will also include the multisig structure, with nodes and everything your trezor needs to sign. Notice there's a 'signatures' key inside 'multisig', you should sign the transaction as is, then populate the corresponding value in 'signatures' and keep signing until you have all the required signatures. { address_n: [42,1,1], prev_hash: "eeeb30c3c4335f019dbee60c60d76994319473acac356f774c7858cd5c968eee", prev_index: 0, script_type: 'SPENDMULTISIG', multisig: { signatures: ['','',''], m: 2, pubkeys: [ { address_n: [42,1,1], node: { chain_code: 'a6d47170817f78094180f1a7a3a9df7634df75fa9604d71b87e92a5a6bf9d30a', depth: 0, child_num: 0, fingerprint: 0, public_key: '03142b0a6fa6943e7276ddc42582c6b169243d289ff17e7c8101797047eed90c9b', } }, { address_n: [42,1,1], node: { chain_code: '8c9151740446b9e0063ca934df66c5e14121a0b4d8a360748f1b19bfef675460', depth: 0, child_num: 0, fingerprint: 0, public_key: '027565ceb190647ec5c566805ebc5cb6166ae2ee1d4995495f61b9eff371ec0e61', } }, { address_n: [42,1,1], node: { chain_code: 'de5bc5918414df3777ff52ae733bdbc87431485cfd39aea65da6133e183ef68a', depth: 0, child_num: 0, fingerprint: 0, public_key: '028776ff18f0f3808d6d42749a6e2baee5c75c3f7ae07445403a3a5690d580a0af', } } ] }
- trezor_outputs:
- List of outputs as Hashes with: { script_type: 'PAYTOADDRESS', address: '1address-as-string', amount: amount in satoshis }