@teleportdao/bitcoin 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bitcoin-base.d.ts +56 -0
- package/dist/bitcoin-base.d.ts.map +1 -0
- package/dist/bitcoin-base.js +138 -0
- package/dist/bitcoin-base.js.map +1 -0
- package/dist/bitcoin-interface-utils.d.ts +18 -0
- package/dist/bitcoin-interface-utils.d.ts.map +1 -0
- package/dist/bitcoin-interface-utils.js +31 -0
- package/dist/bitcoin-interface-utils.js.map +1 -0
- package/dist/bitcoin-interface.d.ts +154 -0
- package/dist/bitcoin-interface.d.ts.map +1 -0
- package/dist/bitcoin-interface.js +248 -0
- package/dist/bitcoin-interface.js.map +1 -0
- package/dist/bitcoin-utils.d.ts +70 -0
- package/dist/bitcoin-utils.d.ts.map +1 -0
- package/dist/bitcoin-utils.js +388 -0
- package/dist/bitcoin-utils.js.map +1 -0
- package/dist/helper/burn-request-helper.d.ts +7 -0
- package/dist/helper/burn-request-helper.d.ts.map +1 -0
- package/dist/helper/burn-request-helper.js +26 -0
- package/dist/helper/burn-request-helper.js.map +1 -0
- package/dist/helper/teleport-request-helper.d.ts +45 -0
- package/dist/helper/teleport-request-helper.d.ts.map +1 -0
- package/dist/helper/teleport-request-helper.js +141 -0
- package/dist/helper/teleport-request-helper.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/sign/sign-transaction.d.ts +8 -0
- package/dist/sign/sign-transaction.d.ts.map +1 -0
- package/dist/sign/sign-transaction.js +41 -0
- package/dist/sign/sign-transaction.js.map +1 -0
- package/dist/teleport-dao-payments.d.ts +92 -0
- package/dist/teleport-dao-payments.d.ts.map +1 -0
- package/dist/teleport-dao-payments.js +203 -0
- package/dist/teleport-dao-payments.js.map +1 -0
- package/dist/transaction-builder/bitcoin-transaction-builder.d.ts +12 -0
- package/dist/transaction-builder/bitcoin-transaction-builder.d.ts.map +1 -0
- package/dist/transaction-builder/bitcoin-transaction-builder.js +50 -0
- package/dist/transaction-builder/bitcoin-transaction-builder.js.map +1 -0
- package/dist/transaction-builder/transaction-builder-common.d.ts +80 -0
- package/dist/transaction-builder/transaction-builder-common.d.ts.map +1 -0
- package/dist/transaction-builder/transaction-builder-common.js +170 -0
- package/dist/transaction-builder/transaction-builder-common.js.map +1 -0
- package/dist/transaction-builder/transaction-builder.d.ts +19 -0
- package/dist/transaction-builder/transaction-builder.d.ts.map +1 -0
- package/dist/transaction-builder/transaction-builder.js +130 -0
- package/dist/transaction-builder/transaction-builder.js.map +1 -0
- package/dist/utils/networks.d.ts +36 -0
- package/dist/utils/networks.d.ts.map +1 -0
- package/dist/utils/networks.js +30 -0
- package/dist/utils/networks.js.map +1 -0
- package/dist/utils/tools.d.ts +13 -0
- package/dist/utils/tools.d.ts.map +1 -0
- package/dist/utils/tools.js +65 -0
- package/dist/utils/tools.js.map +1 -0
- package/package.json +34 -0
- package/src/bitcoin-base.js +174 -0
- package/src/bitcoin-interface-utils.js +42 -0
- package/src/bitcoin-interface.js +267 -0
- package/src/bitcoin-utils.js +443 -0
- package/src/helper/burn-request-helper.js +27 -0
- package/src/helper/teleport-request-helper.js +162 -0
- package/src/index.js +15 -0
- package/src/sign/sign-transaction.js +36 -0
- package/src/teleport-dao-payments.js +276 -0
- package/src/transaction-builder/bitcoin-transaction-builder.js +37 -0
- package/src/transaction-builder/transaction-builder-common.js +228 -0
- package/src/transaction-builder/transaction-builder.js +135 -0
- package/src/utils/networks.js +31 -0
- package/src/utils/tools.js +72 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
const BitcoinBase = require("./bitcoin-base")
|
|
2
|
+
|
|
3
|
+
class TeleportDaoPayment extends BitcoinBase {
|
|
4
|
+
// payment
|
|
5
|
+
async payBurnRequest(receivers, feeSpeed = "normal") {
|
|
6
|
+
let extendedUtxo = await this.transactionBuilder.getExtendedUtxo({
|
|
7
|
+
address: this.currentAccount,
|
|
8
|
+
addressType: this.currentAccountType,
|
|
9
|
+
publicKey: this.publicKey.toString("hex"),
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
let feeRate = await this.transactionBuilder._getFeeRate(feeSpeed)
|
|
13
|
+
let unsignedTx = await this.transactionBuilder.processUnsignedTransaction({
|
|
14
|
+
extendedUtxo,
|
|
15
|
+
targets: receivers,
|
|
16
|
+
changeAddress: this.currentAccount,
|
|
17
|
+
feeRate,
|
|
18
|
+
fullAmount: false,
|
|
19
|
+
})
|
|
20
|
+
let signedPsbt = await this.signer.signPsbt(unsignedTx, this.privateKey)
|
|
21
|
+
let signedTx = this.signer.finalizePsbts([signedPsbt])
|
|
22
|
+
|
|
23
|
+
console.log(signedTx, signedPsbt)
|
|
24
|
+
let txId = await this.transactionBuilder.sendTx(signedTx)
|
|
25
|
+
console.log(txId)
|
|
26
|
+
return txId
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// send
|
|
30
|
+
async transferBitcoinToEth({
|
|
31
|
+
lockerAddress,
|
|
32
|
+
amount,
|
|
33
|
+
//-----------
|
|
34
|
+
chainId,
|
|
35
|
+
appId,
|
|
36
|
+
recipientAddress, // 20 bytes
|
|
37
|
+
percentageFee, // 2 bytes in satoshi
|
|
38
|
+
speed = 0, // 1 byte
|
|
39
|
+
isExchange = false,
|
|
40
|
+
exchangeTokenAddress = "0x0000000000000000000000000000000000000000", // 20 bytes
|
|
41
|
+
outputAmount = 0, // 28 bytes
|
|
42
|
+
deadline, // 4 bytes
|
|
43
|
+
isFixedToken = false, // 1 byte
|
|
44
|
+
feeSpeed = "normal",
|
|
45
|
+
}) {
|
|
46
|
+
let extendedUtxo = await this.getExtendedUtxo({
|
|
47
|
+
address: this.currentAccount,
|
|
48
|
+
addressType: this.currentAccountType,
|
|
49
|
+
publicKey: this.publicKey.toString("hex"),
|
|
50
|
+
})
|
|
51
|
+
let unsignedTx = await this.getBitcoinToEthUnsignedPsbt({
|
|
52
|
+
changeAddress: this.currentAccount,
|
|
53
|
+
extendedUtxo,
|
|
54
|
+
lockerAddress,
|
|
55
|
+
amount,
|
|
56
|
+
//-----------
|
|
57
|
+
chainId,
|
|
58
|
+
appId,
|
|
59
|
+
recipientAddress,
|
|
60
|
+
percentageFee,
|
|
61
|
+
speed,
|
|
62
|
+
isExchange,
|
|
63
|
+
exchangeTokenAddress,
|
|
64
|
+
outputAmount,
|
|
65
|
+
deadline,
|
|
66
|
+
isFixedToken,
|
|
67
|
+
feeSpeed,
|
|
68
|
+
})
|
|
69
|
+
let signedPsbt = await this.signer.signPsbt(unsignedTx, this.privateKey)
|
|
70
|
+
let txId = await this.sendSignedPsbt(signedPsbt)
|
|
71
|
+
return txId
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// get
|
|
75
|
+
async getBitcoinToEthTargetOutputs({
|
|
76
|
+
lockerAddress,
|
|
77
|
+
amount,
|
|
78
|
+
//-----------
|
|
79
|
+
chainId,
|
|
80
|
+
appId,
|
|
81
|
+
recipientAddress, // 20 bytes
|
|
82
|
+
percentageFee, // 2 bytes in satoshi
|
|
83
|
+
speed = 0, // 1 byte
|
|
84
|
+
isExchange = false,
|
|
85
|
+
exchangeTokenAddress = "0x0000000000000000000000000000000000000000", // 20 bytes
|
|
86
|
+
outputAmount = 0, // 28 bytes
|
|
87
|
+
deadline, // 4 bytes
|
|
88
|
+
isFixedToken = false, // 1 byte
|
|
89
|
+
}) {
|
|
90
|
+
let dataHex = TeleportDaoPayment.getTransferOpReturnData({
|
|
91
|
+
chainId,
|
|
92
|
+
appId,
|
|
93
|
+
recipientAddress,
|
|
94
|
+
percentageFee,
|
|
95
|
+
speed,
|
|
96
|
+
isExchange,
|
|
97
|
+
exchangeTokenAddress,
|
|
98
|
+
outputAmount,
|
|
99
|
+
deadline,
|
|
100
|
+
isFixedToken,
|
|
101
|
+
})
|
|
102
|
+
let opTarget = this.transactionBuilder.getOpReturnTarget(dataHex)
|
|
103
|
+
return [
|
|
104
|
+
{
|
|
105
|
+
address: lockerAddress,
|
|
106
|
+
value: amount,
|
|
107
|
+
},
|
|
108
|
+
opTarget,
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async getBitcoinToEthUnsignedPsbt({
|
|
113
|
+
changeAddress,
|
|
114
|
+
extendedUtxo,
|
|
115
|
+
lockerAddress,
|
|
116
|
+
amount,
|
|
117
|
+
//-----------
|
|
118
|
+
chainId,
|
|
119
|
+
appId,
|
|
120
|
+
recipientAddress, // 20 bytes
|
|
121
|
+
percentageFee, // 2 bytes in satoshi
|
|
122
|
+
speed = 0, // 1 byte
|
|
123
|
+
isExchange = false,
|
|
124
|
+
exchangeTokenAddress = "0x0000000000000000000000000000000000000000", // 20 bytes
|
|
125
|
+
outputAmount = 0, // 28 bytes
|
|
126
|
+
deadline, // 4 bytes
|
|
127
|
+
isFixedToken = false, // 1 byte
|
|
128
|
+
feeSpeed = "normal",
|
|
129
|
+
}) {
|
|
130
|
+
let feeRate = await this.transactionBuilder._getFeeRate(feeSpeed)
|
|
131
|
+
let targets = await this.getBitcoinToEthTargetOutputs({
|
|
132
|
+
lockerAddress,
|
|
133
|
+
amount,
|
|
134
|
+
chainId,
|
|
135
|
+
appId,
|
|
136
|
+
recipientAddress,
|
|
137
|
+
percentageFee,
|
|
138
|
+
speed,
|
|
139
|
+
isExchange,
|
|
140
|
+
exchangeTokenAddress,
|
|
141
|
+
outputAmount,
|
|
142
|
+
deadline,
|
|
143
|
+
isFixedToken,
|
|
144
|
+
})
|
|
145
|
+
let unsignedTx = await this.transactionBuilder.processUnsignedTransaction({
|
|
146
|
+
extendedUtxo,
|
|
147
|
+
targets,
|
|
148
|
+
changeAddress,
|
|
149
|
+
feeRate,
|
|
150
|
+
fullAmount: false,
|
|
151
|
+
})
|
|
152
|
+
return unsignedTx
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// send
|
|
156
|
+
async bitcoinToEthLend({
|
|
157
|
+
lockerAddress,
|
|
158
|
+
amount,
|
|
159
|
+
//-----------
|
|
160
|
+
chainId,
|
|
161
|
+
appId,
|
|
162
|
+
recipientAddress, // 20 bytes
|
|
163
|
+
percentageFee, // 2 bytes in satoshi
|
|
164
|
+
mode = 0, // 1 byte
|
|
165
|
+
isBorrow = false,
|
|
166
|
+
tokenAddress = "0x0000000000000000000000000000000000000000", // 20 bytes
|
|
167
|
+
borrowAmount = 0, // 28 bytes
|
|
168
|
+
}) {
|
|
169
|
+
let dataHex = TeleportDaoPayment.getLendingOpReturnData({
|
|
170
|
+
chainId,
|
|
171
|
+
appId,
|
|
172
|
+
recipientAddress,
|
|
173
|
+
percentageFee,
|
|
174
|
+
mode,
|
|
175
|
+
isBorrow,
|
|
176
|
+
tokenAddress,
|
|
177
|
+
borrowAmount,
|
|
178
|
+
})
|
|
179
|
+
let opTarget = this.transactionBuilder.getOpReturnTarget(dataHex)
|
|
180
|
+
|
|
181
|
+
let extendedUtxo = await this.transactionBuilder.getExtendedUtxo({
|
|
182
|
+
address: this.currentAccount,
|
|
183
|
+
addressType: this.currentAccountType,
|
|
184
|
+
publicKey: this.publicKey.toString("hex"),
|
|
185
|
+
})
|
|
186
|
+
let unsignedTx = await this.transactionBuilder.processUnsignedTransaction({
|
|
187
|
+
extendedUtxo,
|
|
188
|
+
targets: [
|
|
189
|
+
{
|
|
190
|
+
address: lockerAddress,
|
|
191
|
+
value: amount,
|
|
192
|
+
},
|
|
193
|
+
opTarget,
|
|
194
|
+
],
|
|
195
|
+
changeAddress: this.currentAccount,
|
|
196
|
+
feeRate: 1,
|
|
197
|
+
fullAmount: false,
|
|
198
|
+
})
|
|
199
|
+
let signedPsbt = await this.signer.signPsbt(unsignedTx, this.privateKey)
|
|
200
|
+
let signedTx = this.signer.finalizePsbts([signedPsbt])
|
|
201
|
+
console.log(signedTx)
|
|
202
|
+
let txId = await this.transactionBuilder.sendTx(signedTx)
|
|
203
|
+
return txId
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
static getTransferOpReturnData({
|
|
207
|
+
chainId,
|
|
208
|
+
appId,
|
|
209
|
+
recipientAddress, // 20 bytes
|
|
210
|
+
percentageFee, // 2 bytes in satoshi
|
|
211
|
+
speed = 0, // 1 byte
|
|
212
|
+
isExchange = false,
|
|
213
|
+
exchangeTokenAddress = "0x0000000000000000000000000000000000000000", // 20 bytes
|
|
214
|
+
outputAmount = 0, // 28 bytes
|
|
215
|
+
deadline, // 4 bytes
|
|
216
|
+
isFixedToken = false, // 1 byte
|
|
217
|
+
}) {
|
|
218
|
+
let chainIdHex = Number(chainId).toString(16).padStart(2, "0")
|
|
219
|
+
let appIdHex = Number(appId).toString(16).padStart(4, "0")
|
|
220
|
+
let recipientAddressHex = recipientAddress.replace("0x", "").toLowerCase().padStart(40, "0")
|
|
221
|
+
let percentageFeeHex = Number((percentageFee * 100).toFixed(0))
|
|
222
|
+
.toString(16)
|
|
223
|
+
.padStart(4, "0")
|
|
224
|
+
let speedHex = speed ? "01" : "00"
|
|
225
|
+
let dataHex = chainIdHex + appIdHex + recipientAddressHex + percentageFeeHex + speedHex
|
|
226
|
+
if (!isExchange) {
|
|
227
|
+
if (dataHex.length !== 26 * 2) throw new Error("invalid data length")
|
|
228
|
+
return dataHex
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let exchangeTokenAddressHex = exchangeTokenAddress
|
|
232
|
+
.replace("0x", "")
|
|
233
|
+
.toLowerCase()
|
|
234
|
+
.padStart(40, "0")
|
|
235
|
+
let outputAmountHex = Number(outputAmount).toString(16).padStart(56, "0")
|
|
236
|
+
let deadlineHex = Number(deadline).toString(16).padStart(8, "0")
|
|
237
|
+
let isFixedTokenHex = isFixedToken ? "01" : "00"
|
|
238
|
+
|
|
239
|
+
dataHex = dataHex + exchangeTokenAddressHex + outputAmountHex + deadlineHex + isFixedTokenHex
|
|
240
|
+
if (dataHex.length !== 79 * 2) throw new Error("invalid data length")
|
|
241
|
+
return dataHex
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
static getLendingOpReturnData({
|
|
245
|
+
chainId, // 1 byte
|
|
246
|
+
appId, // 1 byte
|
|
247
|
+
recipientAddress, // 20 byte
|
|
248
|
+
percentageFee, // 2 byte
|
|
249
|
+
mode, // 1 byte
|
|
250
|
+
// ------
|
|
251
|
+
isBorrow = false,
|
|
252
|
+
tokenAddress = "0x0000000000000000000000000000000000000000", // 20 bytes
|
|
253
|
+
borrowAmount = 0, // 28 bytes
|
|
254
|
+
}) {
|
|
255
|
+
let chainIdHex = Number(chainId).toString(16).padStart(2, "0")
|
|
256
|
+
let appIdHex = Number(appId).toString(16).padStart(4, "0")
|
|
257
|
+
let recipientAddressHex = recipientAddress.replace("0x", "").toLowerCase().padStart(40, "0")
|
|
258
|
+
let percentageFeeHex = Number((percentageFee * 100).toFixed(0))
|
|
259
|
+
.toString(16)
|
|
260
|
+
.padStart(4, "0")
|
|
261
|
+
let modeHex = Number(mode).toString(16).padStart(2, "0")
|
|
262
|
+
let dataHex = chainIdHex + appIdHex + recipientAddressHex + percentageFeeHex + modeHex
|
|
263
|
+
if (!isBorrow) {
|
|
264
|
+
if (dataHex.length !== 26 * 2) throw new Error("invalid data length")
|
|
265
|
+
return dataHex
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let tokenAddressHex = tokenAddress.replace("0x", "").toLowerCase().padStart(40, "0")
|
|
269
|
+
let borrowAmountHex = Number(borrowAmount).toString(16).padStart(56, "0")
|
|
270
|
+
dataHex = dataHex + tokenAddressHex + borrowAmountHex
|
|
271
|
+
if (dataHex.length !== 74 * 2) throw new Error("invalid data length")
|
|
272
|
+
return dataHex
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
module.exports = TeleportDaoPayment
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const BaseTransactionBuilder = require("./transaction-builder")
|
|
2
|
+
const BitcoinInterface = require("../bitcoin-interface")
|
|
3
|
+
|
|
4
|
+
class BitcoinTransactionBuilder extends BaseTransactionBuilder {
|
|
5
|
+
constructor(connectionInfo, networkName, network) {
|
|
6
|
+
super({
|
|
7
|
+
network,
|
|
8
|
+
testnet: networkName?.includes("_testnet"),
|
|
9
|
+
dustLimit: 1000,
|
|
10
|
+
})
|
|
11
|
+
this.btcInterface = new BitcoinInterface(connectionInfo, networkName)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async _getUtxo(userAddress) {
|
|
15
|
+
let utxos = await this.btcInterface.getAddressesUtxo([userAddress])
|
|
16
|
+
return utxos.map((tx) => ({
|
|
17
|
+
hash: tx.txId,
|
|
18
|
+
value: tx.value,
|
|
19
|
+
index: tx.index,
|
|
20
|
+
}))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async _getFeeRate(speed) {
|
|
24
|
+
return this.btcInterface.getFeeRate(speed)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async _getTransactionHex(transactionId) {
|
|
28
|
+
return this.btcInterface.provider.getRawTransaction(transactionId)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async sendTx(txHex) {
|
|
32
|
+
let txId = await this.btcInterface.provider.sendRawTransaction(txHex)
|
|
33
|
+
return txId
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = BitcoinTransactionBuilder
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/* eslint-disable no-underscore-dangle */
|
|
2
|
+
|
|
3
|
+
const bitcoin = require("bitcoinjs-lib")
|
|
4
|
+
const coinselect = require("coinselect")
|
|
5
|
+
const coinselectSplit = require("coinselect/split")
|
|
6
|
+
const { createAddressObjectByPublicKey } = require("../bitcoin-utils")
|
|
7
|
+
|
|
8
|
+
const TX_EMPTY_SIZE = 4 + 1 + 1 + 4
|
|
9
|
+
const TX_INPUT_BASE = 32 + 4 + 1 + 4
|
|
10
|
+
const TX_INPUT_P2PKH = 107
|
|
11
|
+
const TX_INPUT_P2SH_P2PKH = 50
|
|
12
|
+
const TX_INPUT_P2WPKH = 47
|
|
13
|
+
const TX_OUTPUT_BASE = 8 + 1
|
|
14
|
+
const TX_OUTPUT_P2PKH = 25
|
|
15
|
+
|
|
16
|
+
const componentBytes = {
|
|
17
|
+
bytePerInput: {
|
|
18
|
+
p2pkh: TX_INPUT_BASE + TX_INPUT_P2PKH,
|
|
19
|
+
p2wpkh: TX_INPUT_BASE + TX_INPUT_P2WPKH,
|
|
20
|
+
p2shp2wpkh: TX_INPUT_BASE + TX_INPUT_P2SH_P2PKH,
|
|
21
|
+
},
|
|
22
|
+
baseTxBytes: TX_EMPTY_SIZE,
|
|
23
|
+
bytePerOutput: TX_OUTPUT_BASE + TX_OUTPUT_P2PKH,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class BaseBitcoinLikeTransaction {
|
|
27
|
+
// abstract
|
|
28
|
+
constructor({
|
|
29
|
+
network,
|
|
30
|
+
testnet,
|
|
31
|
+
feeMin = 0,
|
|
32
|
+
dustLimit,
|
|
33
|
+
maximumNumberOfOutputsInTransaction = 50,
|
|
34
|
+
}) {
|
|
35
|
+
this.testnet = testnet
|
|
36
|
+
this.network = network
|
|
37
|
+
this.maximumNumberOfOutputsInTransaction = maximumNumberOfOutputsInTransaction
|
|
38
|
+
this.feeMin = feeMin
|
|
39
|
+
this.dustLimit = dustLimit || 1 * 2 * componentBytes.bytePerInput.p2pkh
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// eslint-disable-next-line no-unused-vars, class-methods-use-this
|
|
43
|
+
createAddressObject({ addressType, publicKey }) {
|
|
44
|
+
return createAddressObjectByPublicKey({ addressType, publicKey }, this.network)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
validateAddress(address) {
|
|
48
|
+
try {
|
|
49
|
+
let isValid = false
|
|
50
|
+
let network = this.network
|
|
51
|
+
let isAddressSegwit = address.startsWith(network.bech32)
|
|
52
|
+
if (isAddressSegwit) {
|
|
53
|
+
bitcoin.address.fromBech32(address)
|
|
54
|
+
isValid = true
|
|
55
|
+
} else {
|
|
56
|
+
let base58Data = bitcoin.address.fromBase58Check(address)
|
|
57
|
+
isValid =
|
|
58
|
+
base58Data.version === Number(network.scriptHash) ||
|
|
59
|
+
base58Data.version === Number(network.pubKeyHash)
|
|
60
|
+
}
|
|
61
|
+
return isValid
|
|
62
|
+
} catch (error) {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// eslint-disable-next-line no-unused-vars, class-methods-use-this
|
|
68
|
+
async _getUtxo(userAddress) {
|
|
69
|
+
// The child has implemented this method.
|
|
70
|
+
throw new Error("Do not call abstract method directly")
|
|
71
|
+
// return utxo
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// eslint-disable-next-line no-unused-vars, class-methods-use-this
|
|
75
|
+
async _getTransactionHex(transactionId) {
|
|
76
|
+
// The child has implemented this method.
|
|
77
|
+
throw new Error("Do not call abstract method directly")
|
|
78
|
+
// return utxo
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// eslint-disable-next-line no-unused-vars, class-methods-use-this
|
|
82
|
+
async convertBaseInputsToInputs(baseInputs) {
|
|
83
|
+
// The child has implemented this method.
|
|
84
|
+
throw new Error("Do not call abstract method directly")
|
|
85
|
+
// return utxo
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// eslint-disable-next-line no-unused-vars, class-methods-use-this
|
|
89
|
+
async createUnsignedTransaction(baseInputs) {
|
|
90
|
+
// The child has implemented this method.
|
|
91
|
+
throw new Error("Do not call abstract method directly")
|
|
92
|
+
// return utxo
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
static helperHandleInputsAndOutputs({
|
|
96
|
+
targets,
|
|
97
|
+
extendedUtxo,
|
|
98
|
+
feeRate,
|
|
99
|
+
changeAddress,
|
|
100
|
+
fullAmount,
|
|
101
|
+
}) {
|
|
102
|
+
let { inputs, outputs, fee } = fullAmount
|
|
103
|
+
? coinselectSplit(extendedUtxo, [{ address: targets[0].address }], Math.round(feeRate))
|
|
104
|
+
: coinselect(extendedUtxo, targets, Math.round(feeRate))
|
|
105
|
+
|
|
106
|
+
if (!inputs || !outputs) {
|
|
107
|
+
throw new Error("not enough balance")
|
|
108
|
+
}
|
|
109
|
+
let changeIndex = outputs.findIndex((x) => !x.address && !x.script && x.value > 0)
|
|
110
|
+
let change
|
|
111
|
+
if (changeIndex >= 0) {
|
|
112
|
+
change = {
|
|
113
|
+
address: changeAddress,
|
|
114
|
+
value: outputs[changeIndex].value,
|
|
115
|
+
}
|
|
116
|
+
outputs.splice(changeIndex, 1)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
inputs,
|
|
121
|
+
fee,
|
|
122
|
+
outputs,
|
|
123
|
+
change,
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
*
|
|
129
|
+
* @param {Object} signerInfo
|
|
130
|
+
* @param {String} signerInfo.address
|
|
131
|
+
* @param {String} signerInfo.addressType
|
|
132
|
+
* @param {String?} signerInfo.publicKey
|
|
133
|
+
* *@param {Array?} signerInfo.publicKeys // todo not used yet- used in multi sig
|
|
134
|
+
* @param {String?} signerInfo.privateKeyId
|
|
135
|
+
* @param {String?} signerInfo.derivationPath
|
|
136
|
+
* @returns
|
|
137
|
+
*/
|
|
138
|
+
async getExtendedUtxo(signerInfo) {
|
|
139
|
+
let utxo = await this._getUtxo(signerInfo.address)
|
|
140
|
+
const extendedUtxo = utxo.map((input) => ({
|
|
141
|
+
...input,
|
|
142
|
+
signerInfo,
|
|
143
|
+
}))
|
|
144
|
+
if (!extendedUtxo || extendedUtxo.length === 0) {
|
|
145
|
+
throw new Error("no utxo found")
|
|
146
|
+
}
|
|
147
|
+
return extendedUtxo
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
*
|
|
152
|
+
* @param {Object[]} extendedUtxo
|
|
153
|
+
* @param {String} extendedUtxo[].hash
|
|
154
|
+
* @param {Number} extendedUtxo[].index
|
|
155
|
+
* @param {Number} extendedUtxo[].value
|
|
156
|
+
* @param {Object} extendedUtxo[].signerInfo
|
|
157
|
+
* @param {Object} extendedUtxo[].signerInfo.address
|
|
158
|
+
* @param {Object} extendedUtxo[].signerInfo.publicKey
|
|
159
|
+
* @param {String} extendedUtxo[].signerInfo.addressType
|
|
160
|
+
* @param {Number} extendedUtxo[].signerInfo.privateKeyId
|
|
161
|
+
*/
|
|
162
|
+
async convertUtxoToInput({ extendedUtxo, targets, changeAddress, fullAmount = false, feeRate }) {
|
|
163
|
+
let {
|
|
164
|
+
inputs: filteredInputs,
|
|
165
|
+
outputs,
|
|
166
|
+
change,
|
|
167
|
+
fee,
|
|
168
|
+
} = BaseBitcoinLikeTransaction.helperHandleInputsAndOutputs({
|
|
169
|
+
targets,
|
|
170
|
+
extendedUtxo,
|
|
171
|
+
fullAmount,
|
|
172
|
+
feeRate,
|
|
173
|
+
changeAddress,
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
let inputs = await this.convertBaseInputsToInputs(filteredInputs)
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
inputs,
|
|
180
|
+
outputs,
|
|
181
|
+
change,
|
|
182
|
+
fee,
|
|
183
|
+
feeRate,
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async processUnsignedTransaction({
|
|
188
|
+
extendedUtxo,
|
|
189
|
+
targets = [],
|
|
190
|
+
changeAddress = undefined,
|
|
191
|
+
fullAmount = false,
|
|
192
|
+
feeRate,
|
|
193
|
+
selfTransaction = false,
|
|
194
|
+
}) {
|
|
195
|
+
if (!selfTransaction && targets.length === 0) throw new Error("no target")
|
|
196
|
+
|
|
197
|
+
const { inputs, outputs, change, fee } = await this.convertUtxoToInput({
|
|
198
|
+
extendedUtxo,
|
|
199
|
+
targets,
|
|
200
|
+
changeAddress,
|
|
201
|
+
fullAmount,
|
|
202
|
+
feeRate,
|
|
203
|
+
})
|
|
204
|
+
let unsignedTransaction = await this.createUnsignedTransaction({
|
|
205
|
+
inputs,
|
|
206
|
+
outputs,
|
|
207
|
+
change,
|
|
208
|
+
fee,
|
|
209
|
+
feeRate,
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
return unsignedTransaction
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
getOpReturnTarget(dataHex) {
|
|
216
|
+
if (!dataHex.length > 0) throw new Error("invalid data in hex")
|
|
217
|
+
const embed = bitcoin.payments.embed({
|
|
218
|
+
data: [Buffer.from(dataHex, "hex")],
|
|
219
|
+
network: this.network,
|
|
220
|
+
})
|
|
221
|
+
return {
|
|
222
|
+
script: embed.output,
|
|
223
|
+
value: 0,
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
module.exports = BaseBitcoinLikeTransaction
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const bitcoin = require("bitcoinjs-lib")
|
|
2
|
+
const BaseBitcoinLikeTransactionBuilderCommon = require("./transaction-builder-common")
|
|
3
|
+
|
|
4
|
+
class BaseBitcoinLikeTransaction extends BaseBitcoinLikeTransactionBuilderCommon {
|
|
5
|
+
async convertBaseInputsToInputs(baseInputs = []) {
|
|
6
|
+
let inputs = baseInputs
|
|
7
|
+
let transactionId = null
|
|
8
|
+
let transactionHex = null
|
|
9
|
+
for (let i in inputs) {
|
|
10
|
+
let { address, publicKey, addressType } = inputs[i].signerInfo
|
|
11
|
+
let addressObject = this.createAddressObject({
|
|
12
|
+
address,
|
|
13
|
+
publicKey: publicKey ? Buffer.from(publicKey, "hex") : null,
|
|
14
|
+
addressType,
|
|
15
|
+
})
|
|
16
|
+
if (addressType === "p2pkh") {
|
|
17
|
+
// add p2pkh data
|
|
18
|
+
if (transactionId === inputs[i].hash) {
|
|
19
|
+
inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
|
|
20
|
+
} else {
|
|
21
|
+
transactionHex = await this._getTransactionHex(inputs[i].hash)
|
|
22
|
+
inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
|
|
23
|
+
transactionId = inputs[i].hash
|
|
24
|
+
}
|
|
25
|
+
} else if (addressType === "p2wpkh") {
|
|
26
|
+
// add p2wpkh data
|
|
27
|
+
inputs[i].witnessUtxo = {
|
|
28
|
+
script: addressObject.output,
|
|
29
|
+
value: inputs[i].value,
|
|
30
|
+
}
|
|
31
|
+
} else if (addressType === "p2sh-p2wpkh") {
|
|
32
|
+
// add p2sh-p2wpkh data
|
|
33
|
+
inputs[i].witnessUtxo = {
|
|
34
|
+
script: addressObject.output,
|
|
35
|
+
value: inputs[i].value,
|
|
36
|
+
}
|
|
37
|
+
inputs[i].redeemScript = addressObject.redeem.output
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return inputs
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
createUnsignedTransaction({
|
|
45
|
+
inputs,
|
|
46
|
+
outputs,
|
|
47
|
+
change,
|
|
48
|
+
fee, // not used in this section - just returned
|
|
49
|
+
feeRate,
|
|
50
|
+
}) {
|
|
51
|
+
const { network } = this
|
|
52
|
+
const newPsbt = new bitcoin.Psbt({ network })
|
|
53
|
+
newPsbt.setMaximumFeeRate = feeRate + feeRate / 100
|
|
54
|
+
// add input
|
|
55
|
+
for (const input of inputs) {
|
|
56
|
+
let { addressType } = input.signerInfo
|
|
57
|
+
switch (addressType) {
|
|
58
|
+
case "p2pkh":
|
|
59
|
+
newPsbt.addInput({
|
|
60
|
+
hash: input.hash,
|
|
61
|
+
index: Number(input.index),
|
|
62
|
+
nonWitnessUtxo: input.nonWitnessUtxo,
|
|
63
|
+
sequence: 0xffffffff - 1,
|
|
64
|
+
})
|
|
65
|
+
break
|
|
66
|
+
case "p2wpkh":
|
|
67
|
+
newPsbt.addInput({
|
|
68
|
+
hash: input.hash,
|
|
69
|
+
index: Number(input.index),
|
|
70
|
+
witnessUtxo: input.witnessUtxo,
|
|
71
|
+
sequence: 0xffffffff - 1,
|
|
72
|
+
})
|
|
73
|
+
break
|
|
74
|
+
case "p2sh-p2wpkh":
|
|
75
|
+
newPsbt.addInput({
|
|
76
|
+
hash: input.hash,
|
|
77
|
+
index: Number(input.index),
|
|
78
|
+
witnessUtxo: input.witnessUtxo,
|
|
79
|
+
redeemScript: input.redeemScript,
|
|
80
|
+
sequence: 0xffffffff - 1,
|
|
81
|
+
})
|
|
82
|
+
break
|
|
83
|
+
default:
|
|
84
|
+
throw new Error("address type is incorrect")
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// add outputs
|
|
89
|
+
for (const target of outputs) {
|
|
90
|
+
newPsbt.addOutput(target)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// add changeAddress
|
|
94
|
+
if (change && Object.keys(change).length !== 0) {
|
|
95
|
+
newPsbt.addOutput({
|
|
96
|
+
address: change.address,
|
|
97
|
+
value: Number(change.value),
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// check created outputs with targets
|
|
102
|
+
for (let i in outputs) {
|
|
103
|
+
if (newPsbt.txOutputs[i].address !== outputs[i].address) {
|
|
104
|
+
throw new Error("error address")
|
|
105
|
+
}
|
|
106
|
+
if (newPsbt.txOutputs[i].value !== outputs[i].value) {
|
|
107
|
+
throw new Error("error value")
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (change && Object.keys(change).length !== 0) {
|
|
111
|
+
if (newPsbt.txOutputs[outputs.length].address !== change.address) {
|
|
112
|
+
throw new Error("error change address")
|
|
113
|
+
}
|
|
114
|
+
if (newPsbt.txOutputs[outputs.length].value !== change.value) {
|
|
115
|
+
throw new Error("error change value")
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const unsignedPsbtBaseText = newPsbt.toBase64()
|
|
120
|
+
return {
|
|
121
|
+
unsignedTransaction: unsignedPsbtBaseText,
|
|
122
|
+
outputs,
|
|
123
|
+
inputs: inputs.map((tx) => ({
|
|
124
|
+
hash: tx.hash,
|
|
125
|
+
value: Number(tx.value),
|
|
126
|
+
index: tx.index,
|
|
127
|
+
signerInfo: tx.signerInfo,
|
|
128
|
+
})),
|
|
129
|
+
fee,
|
|
130
|
+
change,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = BaseBitcoinLikeTransaction
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const bitcoinLib = require("bitcoinjs-lib")
|
|
2
|
+
|
|
3
|
+
const networks = {
|
|
4
|
+
bitcoin: bitcoinLib.networks.bitcoin,
|
|
5
|
+
bitcoin_testnet: bitcoinLib.networks.testnet,
|
|
6
|
+
|
|
7
|
+
litecoin: {
|
|
8
|
+
messagePrefix: "\x19Litecoin Signed Message:\n",
|
|
9
|
+
bech32: "ltc",
|
|
10
|
+
bip32: {
|
|
11
|
+
public: 0x0488b21e,
|
|
12
|
+
private: 0x0488ade4,
|
|
13
|
+
},
|
|
14
|
+
pubKeyHash: 0x30,
|
|
15
|
+
scriptHash: 0x32,
|
|
16
|
+
wif: 0xb0,
|
|
17
|
+
},
|
|
18
|
+
litecoin_testnet: {
|
|
19
|
+
messagePrefix: "\x18Litecoin Signed Message:\n",
|
|
20
|
+
bech32: "tltc",
|
|
21
|
+
bip32: {
|
|
22
|
+
public: 0x043587cf,
|
|
23
|
+
private: 0x04358394,
|
|
24
|
+
},
|
|
25
|
+
pubKeyHash: 0x6f,
|
|
26
|
+
scriptHash: 0x3a, // old 0xc4 -> 2 deprecated
|
|
27
|
+
wif: 0xef,
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = networks
|