课程目标
理解交易的输入、输出、UTXO
实现比特币转账交易
查询交易记录
前言
现在我们的钱包应用程序还差最后一个关键的功能:转账交易,这就是本章的内容。
根据API文档可见,完整的发送交易过程需要经过如下四个步骤:
createTxProposal():创建交易。
publishTxProposal():发布交易。
signTxProposal():签名交易。
broadcastTxProposal():广播交易。
一、创建交易的API文档说明
首先我们需要创建一个交易,API文档说明如下:

第一个参数是opts有很多可选参数,第二个参数是Callback回调。下面解释下opts的重要参数。
opts.outputs: Array,必填:交易的输出,即收款方。数据类型是数组,可见一次交易支持多个转账,数组的元素包含如下:
opts.outputs[].toAddress: String,必填:收款方地址。
opts.outputs[].amount: Number,必填:转账数额。
opts.outputs[].message: String,必填:备注消息。
opts.message: string,必填:本次交易的备注消息,与opts.outputs[].message不同,它是指定的单个转账的备注信息。
opts.fee: string,可选:本次交易的费用,与feePerKb互斥。
opts.feePerKb: string,可选:本次交易每KB的费用,与fee互斥。
opts.changeAddress: string,可选:使用这个地址作为本次交易的地址,该地址必须属于该钱包。
opts.payProUrl: String,可选:忽略。
opts.excludeUnconfirmedUtxos: string,可选:不使用未确认事务的UTXOS作为输入。
opts.customData: Object,可选: 忽略。
opts.inputs: Array,可选:本次交易中使用的输入。
opts.utxosToExclude: Array,可选:忽略。
二、编码实现创建交易
我们先传递必填参数实现创建交易,代码如下:

输出如下:
输出信息有几个关键点:
status:”temporary”,临时状态。
fee:243,手续费为243Satoshi。
feeLevel:”normal”,费用等级是正常。
重点数据如下:
changeAddress:{address:”n1jK5uzvLqJKatSub4dscJ93wsSnvVc6U6″,path:”m/1/1″}
inputs:{address: “n3wRnSdhWJtckzMayhJQstCWunpg91kDMs”,path:”m/1/0″}
outputs:{toAddress: “mm16s7xsf8Wjwxhprc6YzLW9gVncqZNGBR”}
现在这个交易并没有发送出去,要完成这个交易还需进行三个步骤。
三、发送交易的完整过程
前面已经说到了发送交易需要四个过程,createTxProposal –> publishTxProposal –> signTxProposal –> broadcastTxProposal。重点是在第一步创建交易createTxProposal,这里指定本次交易的详细数据,后面的三部只需执行即可。

输出内容太多,下面说下重点数据。
每次交易都会改变地址,现在的输出是
changeAddress:{address:”mi7ZMqMbWfv7p4SHj8iUQXpkuSYbdXe44K”,path:”m/1/2″},
上次是”m/1/1″。
inputs:{address: “n3wRnSdhWJtckzMayhJQstCWunpg91kDMs”,path:”m/1/0″},
与上次没有发送变化,还是”m/1/0″,因为上次交易只是创建了并没有广播。
outputs:{toAddress: “mm16s7xsf8Wjwxhprc6YzLW9gVncqZNGBR”}
执行完每个步骤对交易都会改变它的状态
createTxProposal():status:”broadcasted”。
publishTxProposal():status:”pending”。
signTxProposal():status:”accepted”。
broadcastTxProposal():status:”broadcasted”。
成功执行签名交易后会生成交易id,txid:”57fb33c1199d6e98ae245dc422835d012ed3cab1314e245ca3605de3b44884b4″。
id:”beb003f8-f7d6-4578-a507-c36319a9fc96″。
若几个步骤没有发送错误,则交易成功完成。两个钱包的余额变化情况是
“wallet1”:0.11121494 BTC–>0.10121251 BTC,差值是0.01000243。
收款方“mm16s7xsf8Wjwxhprc6YzLW9gVncqZNGBR”所在钱包是“importWallet1”。
“importWallet1”:0.011 BTC–>0.021 BTC,差值是0.01。
多扣掉的0.01000243-0.01=0.00000243BTC正是本次交易的手续费,为243Satoshi。
四、查询交易记录
现在我们对刚才进行的转账看下是否能成功查询。需要使用getTxHistory()方法,注意,一定要加上参数includeExtendedInfo设置为true,会显示额外的交易详情,如:输入、输出等。

1. 最近一次转账的交易记录
现在该钱包有三次交易记录,我将上面进行转账的交易记录详细截图如下:
我们先来分析上面出现的三个地址。
inputs:{address: “n3wRnSdhWJtckzMayhJQstCWunpg91kDMs”},
这是本次交易的输入,通过getBalance()方法的byAddress字段可以查看钱包的余额由哪些子地址拥有。
outputs[0]:{toAddress: “mm16s7xsf8Wjwxhprc6YzLW9gVncqZNGBR”}
这是输出,是收款方地址。
outputs[1]:{address:”mi7ZMqMbWfv7p4SHj8iUQXpkuSYbdXe44K”,path:”m/1/2″}
我们给一个地址转账为什么会出现两个输出呢?因为会将输入里的余额取出来一部分进行转账,剩余的钱就转移到了这个地址,这个地址是该钱包的另外一个新的子账号地址。所以转账之前在”m/1/0″路径的“n3wR……kDMs”地址中,在本次转账完成后就转移到了”m/1/2″路径的“mi7Z……e44K”地址。
2. 最初接收的交易记录
现在再来看看接收转账的输出,在最初索取BTC测试币的交易就是接收。
可见自己路径“m/0/0”的地址“move……1KQz”作为了输出,用于接收交易。同时可以看到有个状态的字段
action:”send”,代表发送。
action:”received”,代表接收。
3. 第二次交易记录
在这次交易记录中将关联第一次与第三次交易的交易地址。
第一次接收交易的输出:“move……1KQz”。输出即拥有余额的地址。
第二次转出交易的输入:“move……1KQz”,输出(即改变后的地址):“n3wR……kDMs”。
第三次转出交易的输入:“n3wR……kDMs”。
其它类型的转账自己在测试看一下结果。
4. 结论
比特币采用的是 UTXO 模型,并非账户模型,并不直接存在“余额”这个概念,获取余额需要通过遍历整个交易历史获取。
UTXO:是unspend transaction output的简写,指未被花费的交易输出。
场景:假设你过去分别向A、B、C这三个比特币用户购买了BTC,从A手中购买了3.5个BTC,从B手中购买了4.5个BTC,从C手中购买了2个BTC,现在你的比特币钱包里面恰好剩余10个BTC。
问题:这个10个BTC是真正的10个BTC吗?其实不是,这句话可能听起来有点怪。(什么!我钱包里面的BTC不是真正的BTC,你不要吓我……)
解释:前面提到过在比特币的交易系统当中,并不存在账户、余额这些概念,所以,你的钱包里面的10个BTC,并不是说钱包余额为10个BTC。而是说,这10个BTC其实是由你的比特币地址(钱包地址|公钥)锁定了的散落在各个区块和各个交易里面的UTXO的总和。
UTXO 是比特币交易的基本单位,每笔交易都会产生UTXO,一个UTXO可以是一“聪”的任意倍。给某人发送比特币实际上是创造新的UTXO,绑定到那个人的钱包地址,并且能被他用于新的支付。
一般的比特币交易由 交易输入 和 交易输出 两部分组成。A向你支付3.5个BTC这笔交易,实际上产生了一个新的UTXO,这个新的UTXO 等于 3.5个BTC(3.5亿聪),并且锁定到了你的比特币钱包地址上。
假如你要给你女(男)朋友转 1.5 BTC,那么你的钱包会从可用的UTXO中选取一个或多个可用的个体来拼凑出一个大于或等于一笔交易所需的比特币量。比如在这个假设场景里面,你的钱包会选取你和C的交易中的UTXO作为 交易输入,input = 2BTC,这里会生成两个新的交易输出,一个输出(UTXO = 1.5 BTC)会被绑定到你女(男)朋友的钱包地址上,另一个输出(UTXO = 0.5 BTC)会作为找零,重新绑定到你的钱包地址上。
我们需要找到所有未花费的交易输出(UTXO)。Unspent(未花费) 意味着这些交易输出从未被交易输入所指向。
五、完整源码
1. controllers/transaction.js
controllers文件夹下新建transaction.js文件,实现比特币转账交易和查询交易记录功能。







