@exodus/bitcoin-api 4.4.0 → 4.6.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/CHANGELOG.md +30 -0
- package/package.json +2 -2
- package/src/psbt-builder.js +28 -18
- package/src/psbt-parser.js +32 -25
- package/src/psbt-utils.js +23 -14
- package/src/tx-create/create-tx.js +62 -2
- package/src/tx-send/index.js +94 -23
- package/src/tx-send/update-state.js +13 -14
- package/src/tx-sign/common.js +9 -0
- package/src/tx-sign/create-sign-with-wallet.js +6 -2
- package/src/tx-sign/default-create-tx.js +2 -2
- package/src/tx-sign/default-prepare-for-signing.js +26 -19
- package/src/tx-sign/default-sign-hardware.js +2 -2
- package/src/tx-sign/maximum-fee-rates.js +13 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,36 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [4.6.0](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.2.1...@exodus/bitcoin-api@4.6.0) (2025-11-14)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
* feat: add PSBT builder infrastructure (#6822)
|
|
13
|
+
|
|
14
|
+
* feat: add PSBT parser functionality (#6823)
|
|
15
|
+
|
|
16
|
+
* feat: integrate PSBT support and legacy chain index (#6819)
|
|
17
|
+
|
|
18
|
+
* feat: integrate PSBT support and legacy chain index (#6819) (#6829)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## [4.5.0](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.2.1...@exodus/bitcoin-api@4.5.0) (2025-11-12)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Features
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
* feat: add PSBT builder infrastructure (#6822)
|
|
29
|
+
|
|
30
|
+
* feat: add PSBT parser functionality (#6823)
|
|
31
|
+
|
|
32
|
+
* feat: integrate PSBT support and legacy chain index (#6819)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
6
36
|
## [4.4.0](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@4.2.1...@exodus/bitcoin-api@4.4.0) (2025-11-12)
|
|
7
37
|
|
|
8
38
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/bitcoin-api",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.0",
|
|
4
4
|
"description": "Bitcoin transaction and fee monitors, RPC with the blockchain node, other networking code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"type": "git",
|
|
61
61
|
"url": "git+https://github.com/ExodusMovement/assets.git"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "660b300377800da2edc9c250d61736b416db73d1"
|
|
64
64
|
}
|
package/src/psbt-builder.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import { payments
|
|
1
|
+
import { payments } from '@exodus/bitcoinjs'
|
|
2
2
|
import { publicKeyToX } from '@exodus/crypto/secp256k1'
|
|
3
3
|
import assert from 'minimalistic-assert'
|
|
4
4
|
|
|
5
5
|
import { SubType, writePsbtGlobalField, writePsbtOutputField } from './psbt-proprietary-types.js'
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import {
|
|
7
|
+
getAddressType,
|
|
8
|
+
getPurposeXPubs,
|
|
9
|
+
setPsbtVersionIfNotBitcoin,
|
|
10
|
+
validatePurpose,
|
|
11
|
+
} from './psbt-utils.js'
|
|
12
|
+
import { getMaximumFeeRate } from './tx-sign/maximum-fee-rates.js'
|
|
13
|
+
|
|
14
|
+
function canParseTx(rawTxBuffer, Transaction) {
|
|
9
15
|
try {
|
|
10
16
|
Transaction.fromBuffer(rawTxBuffer)
|
|
11
17
|
return true
|
|
@@ -76,7 +82,7 @@ function createPsbtInput({
|
|
|
76
82
|
addressPathsMap,
|
|
77
83
|
purposeXPubs,
|
|
78
84
|
nonWitnessTxs,
|
|
79
|
-
|
|
85
|
+
Transaction,
|
|
80
86
|
}) {
|
|
81
87
|
const psbtInput = {
|
|
82
88
|
hash: input.txId,
|
|
@@ -85,7 +91,7 @@ function createPsbtInput({
|
|
|
85
91
|
}
|
|
86
92
|
|
|
87
93
|
const purpose = asset.address.resolvePurpose(input.address)
|
|
88
|
-
validatePurpose(purpose,
|
|
94
|
+
validatePurpose(purpose, purposeXPubs, `address ${input.address}`)
|
|
89
95
|
|
|
90
96
|
const { isSegwitAddress, isTaprootAddress, isWrappedSegwitAddress } = getAddressType(purpose)
|
|
91
97
|
|
|
@@ -118,7 +124,7 @@ function createPsbtInput({
|
|
|
118
124
|
)
|
|
119
125
|
|
|
120
126
|
const rawTxBuffer = Buffer.from(rawTx.rawData, 'hex')
|
|
121
|
-
if (canParseTx(rawTxBuffer)) {
|
|
127
|
+
if (canParseTx(rawTxBuffer, Transaction)) {
|
|
122
128
|
psbtInput.nonWitnessUtxo = rawTxBuffer
|
|
123
129
|
} else {
|
|
124
130
|
// bitcoinjs can’t parse a handful of edge-case transactions (Litecoin MWEB, odd
|
|
@@ -141,14 +147,7 @@ function createPsbtInput({
|
|
|
141
147
|
return { ...psbtInput, ...derivationData }
|
|
142
148
|
}
|
|
143
149
|
|
|
144
|
-
function createPsbtOutput({
|
|
145
|
-
address,
|
|
146
|
-
amount,
|
|
147
|
-
asset,
|
|
148
|
-
addressPathsMap,
|
|
149
|
-
purposeXPubs,
|
|
150
|
-
allowedPurposes,
|
|
151
|
-
}) {
|
|
150
|
+
function createPsbtOutput({ address, amount, asset, addressPathsMap, purposeXPubs }) {
|
|
152
151
|
const psbtOutput = {
|
|
153
152
|
address,
|
|
154
153
|
value: amount,
|
|
@@ -160,7 +159,7 @@ function createPsbtOutput({
|
|
|
160
159
|
}
|
|
161
160
|
|
|
162
161
|
const purpose = asset.address.resolvePurpose(address)
|
|
163
|
-
validatePurpose(purpose,
|
|
162
|
+
validatePurpose(purpose, purposeXPubs, `output address ${address}`)
|
|
164
163
|
|
|
165
164
|
const { isTaprootAddress, isWrappedSegwitAddress } = getAddressType(purpose)
|
|
166
165
|
|
|
@@ -195,6 +194,8 @@ export async function createPsbtWithMetadata({
|
|
|
195
194
|
addressPathsMap,
|
|
196
195
|
metadata,
|
|
197
196
|
allowedPurposes,
|
|
197
|
+
Psbt,
|
|
198
|
+
Transaction,
|
|
198
199
|
}) {
|
|
199
200
|
assert(inputs, 'inputs is required')
|
|
200
201
|
assert(outputs, 'outputs is required')
|
|
@@ -203,8 +204,16 @@ export async function createPsbtWithMetadata({
|
|
|
203
204
|
assert(walletAccount, 'walletAccount is required')
|
|
204
205
|
assert(addressPathsMap, 'addressPathsMap is required')
|
|
205
206
|
assert(metadata, 'metadata is required')
|
|
207
|
+
assert(Psbt, 'Psbt is required')
|
|
208
|
+
assert(Transaction, 'Transaction is required')
|
|
209
|
+
const psbtOptions = { network: asset.coinInfo.toBitcoinJS() }
|
|
210
|
+
const maximumFeeRate = getMaximumFeeRate(asset.name)
|
|
211
|
+
if (maximumFeeRate !== undefined) {
|
|
212
|
+
psbtOptions.maximumFeeRate = maximumFeeRate
|
|
213
|
+
}
|
|
206
214
|
|
|
207
|
-
const psbt = new Psbt(
|
|
215
|
+
const psbt = new Psbt(psbtOptions)
|
|
216
|
+
setPsbtVersionIfNotBitcoin(psbt, asset.name)
|
|
208
217
|
|
|
209
218
|
const purposeXPubs = await getPurposeXPubs({
|
|
210
219
|
assetClientInterface,
|
|
@@ -223,6 +232,7 @@ export async function createPsbtWithMetadata({
|
|
|
223
232
|
purposeXPubs,
|
|
224
233
|
nonWitnessTxs,
|
|
225
234
|
allowedPurposes,
|
|
235
|
+
Transaction,
|
|
226
236
|
})
|
|
227
237
|
psbt.addInput(psbtInput)
|
|
228
238
|
}
|
|
@@ -244,5 +254,5 @@ export async function createPsbtWithMetadata({
|
|
|
244
254
|
writePsbtOutputField(psbt, sendOutputIndex, SubType.OutputRole, 'primary')
|
|
245
255
|
}
|
|
246
256
|
|
|
247
|
-
return psbt
|
|
257
|
+
return psbt
|
|
248
258
|
}
|
package/src/psbt-parser.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Psbt, Transaction } from '@exodus/bitcoinjs'
|
|
2
1
|
import { publicKeyToX } from '@exodus/crypto/secp256k1'
|
|
3
2
|
import { UtxoCollection } from '@exodus/models'
|
|
4
3
|
import BipPath from 'bip32-path'
|
|
@@ -11,7 +10,7 @@ import { findUnconfirmedSentRbfTxs } from './tx-utils.js'
|
|
|
11
10
|
import { getUnconfirmedTxAncestorMap } from './unconfirmed-ancestor-data.js'
|
|
12
11
|
import { getUsableUtxos, getUtxos } from './utxos-utils.js'
|
|
13
12
|
|
|
14
|
-
function extractInputUtxoData(psbtInput, txInput, index) {
|
|
13
|
+
function extractInputUtxoData(psbtInput, txInput, index, Transaction) {
|
|
15
14
|
if (psbtInput.nonWitnessUtxo) {
|
|
16
15
|
const prevTx = Transaction.fromBuffer(psbtInput.nonWitnessUtxo)
|
|
17
16
|
const prevOutput = prevTx.outs[txInput.index]
|
|
@@ -34,14 +33,14 @@ function extractInputUtxoData(psbtInput, txInput, index) {
|
|
|
34
33
|
throw new Error(`Input ${index} has no witnessUtxo or nonWitnessUtxo`)
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
function parseSingleInput({ txInput, psbtInput, index, asset, purposeXPubs,
|
|
36
|
+
function parseSingleInput({ txInput, psbtInput, index, asset, purposeXPubs, Transaction }) {
|
|
38
37
|
const input = {
|
|
39
38
|
txId: Buffer.from(txInput.hash).reverse().toString('hex'),
|
|
40
39
|
vout: txInput.index,
|
|
41
40
|
sequence: txInput.sequence,
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
const inputUtxoData = extractInputUtxoData(psbtInput, txInput, index)
|
|
43
|
+
const inputUtxoData = extractInputUtxoData(psbtInput, txInput, index, Transaction)
|
|
45
44
|
|
|
46
45
|
input.value = inputUtxoData.value
|
|
47
46
|
input.script = Buffer.from(inputUtxoData.scriptBuffer).toString('hex')
|
|
@@ -53,7 +52,7 @@ function parseSingleInput({ txInput, psbtInput, index, asset, purposeXPubs, allo
|
|
|
53
52
|
|
|
54
53
|
const address = asset.address.fromScriptPubKey(inputUtxoData.scriptBuffer)
|
|
55
54
|
const purpose = asset.address.resolvePurpose(address)
|
|
56
|
-
validatePurpose(purpose,
|
|
55
|
+
validatePurpose(purpose, purposeXPubs, `input ${index}`)
|
|
57
56
|
|
|
58
57
|
const { isTaprootAddress } = getAddressType(purpose)
|
|
59
58
|
|
|
@@ -75,15 +74,7 @@ function parseSingleInput({ txInput, psbtInput, index, asset, purposeXPubs, allo
|
|
|
75
74
|
return input
|
|
76
75
|
}
|
|
77
76
|
|
|
78
|
-
function parseSingleOutput({
|
|
79
|
-
txOutput,
|
|
80
|
-
psbtOutput,
|
|
81
|
-
index,
|
|
82
|
-
asset,
|
|
83
|
-
purposeXPubs,
|
|
84
|
-
psbt,
|
|
85
|
-
allowedPurposes,
|
|
86
|
-
}) {
|
|
77
|
+
function parseSingleOutput({ txOutput, psbtOutput, index, asset, purposeXPubs, psbt }) {
|
|
87
78
|
const address = txOutput.address ?? asset.address.fromScriptPubKey(txOutput.script)
|
|
88
79
|
const output = { amount: txOutput.value }
|
|
89
80
|
|
|
@@ -94,7 +85,7 @@ function parseSingleOutput({
|
|
|
94
85
|
|
|
95
86
|
const purpose = asset.address.resolvePurpose(address)
|
|
96
87
|
try {
|
|
97
|
-
validatePurpose(purpose,
|
|
88
|
+
validatePurpose(purpose, purposeXPubs)
|
|
98
89
|
} catch {
|
|
99
90
|
output.address = { address }
|
|
100
91
|
return output
|
|
@@ -125,9 +116,15 @@ function readGlobalMetadata(psbt) {
|
|
|
125
116
|
}
|
|
126
117
|
|
|
127
118
|
function calculateFee(inputs, outputs) {
|
|
128
|
-
const inputSum = inputs.reduce((sum, input) =>
|
|
129
|
-
|
|
130
|
-
|
|
119
|
+
const inputSum = inputs.reduce((sum, input) => {
|
|
120
|
+
const value = BigInt(input.value || 0)
|
|
121
|
+
return sum + value
|
|
122
|
+
}, BigInt(0))
|
|
123
|
+
const outputSum = outputs.reduce((sum, output) => {
|
|
124
|
+
const amount = BigInt(output.amount || 0)
|
|
125
|
+
return sum + amount
|
|
126
|
+
}, BigInt(0))
|
|
127
|
+
return inputSum - outputSum // Return BigInt directly
|
|
131
128
|
}
|
|
132
129
|
|
|
133
130
|
function getBip32Derivation(hdkey, bip32Derivations, ignoreY) {
|
|
@@ -332,16 +329,20 @@ function extractRawTransactions(parsedInputs) {
|
|
|
332
329
|
}
|
|
333
330
|
|
|
334
331
|
export async function parsePsbt({
|
|
335
|
-
|
|
332
|
+
psbtBuffer,
|
|
336
333
|
asset,
|
|
337
334
|
assetClientInterface,
|
|
338
335
|
walletAccount,
|
|
339
336
|
allowedPurposes,
|
|
337
|
+
Psbt,
|
|
338
|
+
Transaction,
|
|
340
339
|
}) {
|
|
341
|
-
assert(
|
|
340
|
+
assert(psbtBuffer, 'psbtBuffer is required')
|
|
342
341
|
assert(asset, 'asset is required')
|
|
343
342
|
assert(assetClientInterface, 'assetClientInterface is required')
|
|
344
343
|
assert(walletAccount, 'walletAccount is required')
|
|
344
|
+
assert(Psbt, 'Psbt is required')
|
|
345
|
+
assert(Transaction, 'Transaction is required')
|
|
345
346
|
|
|
346
347
|
const purposeXPubs = await getPurposeXPubs({
|
|
347
348
|
assetClientInterface,
|
|
@@ -350,8 +351,7 @@ export async function parsePsbt({
|
|
|
350
351
|
allowedPurposes,
|
|
351
352
|
})
|
|
352
353
|
|
|
353
|
-
|
|
354
|
-
const psbt = Psbt.fromBase64(psbtBase64, { network: asset.coinInfo.toBitcoinJS() })
|
|
354
|
+
const psbt = Psbt.fromBuffer(psbtBuffer, { network: asset.coinInfo.toBitcoinJS() })
|
|
355
355
|
|
|
356
356
|
const inputs = []
|
|
357
357
|
for (let i = 0; i < psbt.inputCount; i++) {
|
|
@@ -362,6 +362,7 @@ export async function parsePsbt({
|
|
|
362
362
|
asset,
|
|
363
363
|
purposeXPubs,
|
|
364
364
|
allowedPurposes,
|
|
365
|
+
Transaction,
|
|
365
366
|
})
|
|
366
367
|
inputs.push(input)
|
|
367
368
|
}
|
|
@@ -392,16 +393,20 @@ export async function parsePsbt({
|
|
|
392
393
|
}
|
|
393
394
|
|
|
394
395
|
export async function extractTransactionContext({
|
|
395
|
-
|
|
396
|
+
psbtBuffer,
|
|
396
397
|
asset,
|
|
397
398
|
assetClientInterface,
|
|
398
399
|
walletAccount,
|
|
399
400
|
allowedPurposes,
|
|
401
|
+
Psbt,
|
|
402
|
+
Transaction,
|
|
400
403
|
}) {
|
|
401
|
-
assert(
|
|
404
|
+
assert(psbtBuffer, 'psbtBuffer is required')
|
|
402
405
|
assert(asset, 'asset is required')
|
|
403
406
|
assert(assetClientInterface, 'assetClientInterface is required')
|
|
404
407
|
assert(walletAccount, 'walletAccount is required')
|
|
408
|
+
assert(Psbt, 'Psbt is required')
|
|
409
|
+
assert(Transaction, 'Transaction is required')
|
|
405
410
|
|
|
406
411
|
const {
|
|
407
412
|
inputs: parsedInputs,
|
|
@@ -409,11 +414,13 @@ export async function extractTransactionContext({
|
|
|
409
414
|
fee: calculatedFee,
|
|
410
415
|
globalMetadata,
|
|
411
416
|
} = await parsePsbt({
|
|
412
|
-
|
|
417
|
+
psbtBuffer,
|
|
413
418
|
asset,
|
|
414
419
|
assetClientInterface,
|
|
415
420
|
walletAccount,
|
|
416
421
|
allowedPurposes,
|
|
422
|
+
Psbt,
|
|
423
|
+
Transaction,
|
|
417
424
|
})
|
|
418
425
|
|
|
419
426
|
const changeOutputData = await getChangeOutputData({
|
package/src/psbt-utils.js
CHANGED
|
@@ -29,26 +29,31 @@ export async function getPurposeXPubs({
|
|
|
29
29
|
const purposeXPubs = Object.create(null)
|
|
30
30
|
|
|
31
31
|
for (const purpose of allowedPurposes) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
32
|
+
try {
|
|
33
|
+
const xpub = await assetClientInterface.getExtendedPublicKey({
|
|
34
|
+
walletAccount,
|
|
35
|
+
assetName: asset.name,
|
|
36
|
+
purpose,
|
|
37
|
+
})
|
|
38
|
+
const hdkey = BIP32.fromXPub(xpub)
|
|
39
|
+
const masterFingerprint = Buffer.alloc(4)
|
|
40
|
+
masterFingerprint.writeUint32BE(hdkey.fingerprint)
|
|
41
|
+
purposeXPubs[purpose] = {
|
|
42
|
+
hdkey,
|
|
43
|
+
masterFingerprint,
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
// ignore any error that happened while we are getting the extended public key to handle cases where the extended public key is not available
|
|
47
|
+
// Eg. Ledger/Trezor doesn't support getting extended public keys for certain purposes
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
return purposeXPubs
|
|
47
52
|
}
|
|
48
53
|
|
|
49
|
-
export function validatePurpose(purpose,
|
|
50
|
-
assert(
|
|
51
|
-
if (!
|
|
54
|
+
export function validatePurpose(purpose, purposeXPubs, context = '') {
|
|
55
|
+
assert(purposeXPubs, 'purposeXPubs is required')
|
|
56
|
+
if (!purposeXPubs[purpose]) {
|
|
52
57
|
throw new Error(`Purpose ${purpose} not found${context ? ' for ' + context : ''}`)
|
|
53
58
|
}
|
|
54
59
|
}
|
|
@@ -126,3 +131,7 @@ export async function withUnsafeNonSegwit({ psbt, fn, unsafe = true }) {
|
|
|
126
131
|
cache.__UNSAFE_SIGN_NONSEGWIT = prevValue
|
|
127
132
|
}
|
|
128
133
|
}
|
|
134
|
+
|
|
135
|
+
export function setPsbtVersionIfNotBitcoin(psbt, assetName) {
|
|
136
|
+
if (!['bitcoin', 'bitcoinregtest', 'bitcointestnet'].includes(assetName)) psbt.setVersion(1)
|
|
137
|
+
}
|
|
@@ -5,6 +5,7 @@ import assert from 'minimalistic-assert'
|
|
|
5
5
|
import { getChangeDustValue } from '../dust.js'
|
|
6
6
|
import { parseCurrency, serializeCurrency } from '../fee/fee-utils.js'
|
|
7
7
|
import { selectUtxos } from '../fee/utxo-selector.js'
|
|
8
|
+
import { createPsbtWithMetadata } from '../psbt-builder.js'
|
|
8
9
|
import { findUnconfirmedSentRbfTxs } from '../tx-utils.js'
|
|
9
10
|
import { getUnconfirmedTxAncestorMap } from '../unconfirmed-ancestor-data.js'
|
|
10
11
|
import { getUsableUtxos, getUtxos } from '../utxos-utils.js'
|
|
@@ -267,10 +268,20 @@ async function createUnsignedTx({
|
|
|
267
268
|
asset,
|
|
268
269
|
selectedUtxos,
|
|
269
270
|
insightClient,
|
|
271
|
+
assetClientInterface,
|
|
272
|
+
walletAccount,
|
|
273
|
+
bumpTxId,
|
|
274
|
+
rbfEnabled,
|
|
275
|
+
txType = 'transfer',
|
|
276
|
+
sendOutputIndex,
|
|
277
|
+
changeOutputIndex,
|
|
278
|
+
allowedPurposes,
|
|
279
|
+
Psbt,
|
|
280
|
+
Transaction,
|
|
270
281
|
}) {
|
|
271
282
|
const nonWitnessTxs = await getNonWitnessTxs(asset, selectedUtxos, insightClient)
|
|
272
283
|
|
|
273
|
-
|
|
284
|
+
const result = {
|
|
274
285
|
txData: {
|
|
275
286
|
inputs,
|
|
276
287
|
outputs,
|
|
@@ -282,6 +293,36 @@ async function createUnsignedTx({
|
|
|
282
293
|
rawTxs: nonWitnessTxs,
|
|
283
294
|
},
|
|
284
295
|
}
|
|
296
|
+
|
|
297
|
+
// Create PSBT for all UTXO coins that support it
|
|
298
|
+
// Note: Some coins (like BCash) may not use PSBT in their signing flow,
|
|
299
|
+
// but creating it here allows for a unified transaction flow
|
|
300
|
+
if (Psbt) {
|
|
301
|
+
const psbt = await createPsbtWithMetadata({
|
|
302
|
+
inputs,
|
|
303
|
+
outputs,
|
|
304
|
+
asset,
|
|
305
|
+
assetClientInterface,
|
|
306
|
+
walletAccount,
|
|
307
|
+
nonWitnessTxs,
|
|
308
|
+
addressPathsMap,
|
|
309
|
+
allowedPurposes,
|
|
310
|
+
metadata: {
|
|
311
|
+
rbfEnabled,
|
|
312
|
+
txType,
|
|
313
|
+
sendOutputIndexes: [sendOutputIndex],
|
|
314
|
+
changeOutputIndex,
|
|
315
|
+
bumpTxId,
|
|
316
|
+
blockHeight,
|
|
317
|
+
},
|
|
318
|
+
Psbt,
|
|
319
|
+
Transaction,
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
result.txData.psbtBuffer = psbt.toBuffer()
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return result
|
|
285
326
|
}
|
|
286
327
|
|
|
287
328
|
const getTxHandler = (type) => {
|
|
@@ -309,6 +350,9 @@ const transferHandler = {
|
|
|
309
350
|
utxosDescendingOrder,
|
|
310
351
|
assetClientInterface,
|
|
311
352
|
changeAddressType,
|
|
353
|
+
allowedPurposes,
|
|
354
|
+
Psbt,
|
|
355
|
+
Transaction,
|
|
312
356
|
}) => {
|
|
313
357
|
const assetName = asset.name
|
|
314
358
|
const insightClient = asset.baseAsset.insightClient
|
|
@@ -437,12 +481,22 @@ const transferHandler = {
|
|
|
437
481
|
asset,
|
|
438
482
|
selectedUtxos,
|
|
439
483
|
insightClient,
|
|
484
|
+
assetClientInterface,
|
|
485
|
+
walletAccount,
|
|
486
|
+
bumpTxId,
|
|
487
|
+
rbfEnabled: context.rbfEnabled,
|
|
488
|
+
txType: 'transfer',
|
|
489
|
+
sendOutputIndex: sendOutput ? outputs.indexOf(sendOutput) : undefined,
|
|
490
|
+
changeOutputIndex: changeOutput ? outputs.indexOf(changeOutput) : undefined,
|
|
491
|
+
allowedPurposes,
|
|
492
|
+
Psbt,
|
|
493
|
+
Transaction,
|
|
440
494
|
})
|
|
441
495
|
|
|
442
496
|
return {
|
|
443
497
|
unsignedTx,
|
|
444
|
-
fee: adjustedFee,
|
|
445
498
|
metadata: {
|
|
499
|
+
fee: adjustedFee,
|
|
446
500
|
amount,
|
|
447
501
|
change: selectedUtxos.value.sub(totalAmount).sub(adjustedFee),
|
|
448
502
|
totalAmount,
|
|
@@ -469,6 +523,9 @@ export const createTxFactory =
|
|
|
469
523
|
utxosDescendingOrder,
|
|
470
524
|
assetClientInterface,
|
|
471
525
|
changeAddressType,
|
|
526
|
+
allowedPurposes,
|
|
527
|
+
Psbt,
|
|
528
|
+
Transaction,
|
|
472
529
|
}) =>
|
|
473
530
|
async ({
|
|
474
531
|
asset,
|
|
@@ -514,5 +571,8 @@ export const createTxFactory =
|
|
|
514
571
|
utxosDescendingOrder,
|
|
515
572
|
assetClientInterface,
|
|
516
573
|
changeAddressType,
|
|
574
|
+
allowedPurposes,
|
|
575
|
+
Psbt,
|
|
576
|
+
Transaction,
|
|
517
577
|
})
|
|
518
578
|
}
|
package/src/tx-send/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as defaultBitcoinjsLib from '@exodus/bitcoinjs'
|
|
2
|
+
import { Psbt as DefaultPsbt, Transaction as DefaultTransaction } from '@exodus/bitcoinjs'
|
|
2
3
|
import assert from 'minimalistic-assert'
|
|
3
4
|
|
|
5
|
+
import { extractTransactionContext } from '../psbt-parser.js'
|
|
4
6
|
import { createTxFactory } from '../tx-create/create-tx.js'
|
|
5
7
|
import { broadcastTransaction } from './broadcast-tx.js'
|
|
6
8
|
import { updateAccountState, updateTransactionLog } from './update-state.js'
|
|
@@ -108,6 +110,9 @@ const getPrepareSendTransaction = async ({
|
|
|
108
110
|
options,
|
|
109
111
|
utxosDescendingOrder,
|
|
110
112
|
walletAccount,
|
|
113
|
+
allowedPurposes,
|
|
114
|
+
Psbt,
|
|
115
|
+
Transaction,
|
|
111
116
|
}) => {
|
|
112
117
|
const createTx = createTxFactory({
|
|
113
118
|
getFeeEstimator,
|
|
@@ -115,6 +120,9 @@ const getPrepareSendTransaction = async ({
|
|
|
115
120
|
utxosDescendingOrder,
|
|
116
121
|
assetClientInterface,
|
|
117
122
|
changeAddressType,
|
|
123
|
+
allowedPurposes,
|
|
124
|
+
Psbt,
|
|
125
|
+
Transaction,
|
|
118
126
|
})
|
|
119
127
|
|
|
120
128
|
// Set default values for options
|
|
@@ -131,6 +139,59 @@ const getPrepareSendTransaction = async ({
|
|
|
131
139
|
})
|
|
132
140
|
}
|
|
133
141
|
|
|
142
|
+
function toTransactionDescriptor({ txContext, psbtBuffer }) {
|
|
143
|
+
const {
|
|
144
|
+
inputs,
|
|
145
|
+
outputs,
|
|
146
|
+
addressPathsMap,
|
|
147
|
+
blockHeight,
|
|
148
|
+
rawTxs,
|
|
149
|
+
fee,
|
|
150
|
+
totalSendAmount,
|
|
151
|
+
changeAmount,
|
|
152
|
+
totalAmount,
|
|
153
|
+
primaryAddresses,
|
|
154
|
+
ourAddress,
|
|
155
|
+
usableUtxos,
|
|
156
|
+
selectedUtxos,
|
|
157
|
+
replaceTx,
|
|
158
|
+
sendOutputs,
|
|
159
|
+
changeOutput,
|
|
160
|
+
rbfEnabled,
|
|
161
|
+
} = txContext
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
unsignedTx: {
|
|
165
|
+
txData: {
|
|
166
|
+
inputs,
|
|
167
|
+
outputs,
|
|
168
|
+
psbtBuffer,
|
|
169
|
+
},
|
|
170
|
+
txMeta: {
|
|
171
|
+
addressPathsMap,
|
|
172
|
+
blockHeight,
|
|
173
|
+
rawTxs,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
metadata: {
|
|
177
|
+
fee,
|
|
178
|
+
amount: totalSendAmount.isZero ? undefined : totalSendAmount,
|
|
179
|
+
sendAmount: totalSendAmount,
|
|
180
|
+
change: changeAmount,
|
|
181
|
+
totalAmount,
|
|
182
|
+
address: primaryAddresses[0]?.address,
|
|
183
|
+
ourAddress,
|
|
184
|
+
usableUtxos,
|
|
185
|
+
selectedUtxos,
|
|
186
|
+
replaceTx,
|
|
187
|
+
sendOutput: sendOutputs[0],
|
|
188
|
+
changeOutput,
|
|
189
|
+
rbfEnabled,
|
|
190
|
+
blockHeight,
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
134
195
|
// not ported from Exodus; but this demos signing / broadcasting
|
|
135
196
|
// NOTE: this will be ripped out in the coming weeks
|
|
136
197
|
export const createAndBroadcastTXFactory =
|
|
@@ -141,15 +202,18 @@ export const createAndBroadcastTXFactory =
|
|
|
141
202
|
utxosDescendingOrder,
|
|
142
203
|
assetClientInterface,
|
|
143
204
|
changeAddressType,
|
|
205
|
+
allowedPurposes,
|
|
206
|
+
Psbt = DefaultPsbt,
|
|
207
|
+
Transaction = DefaultTransaction,
|
|
144
208
|
}) =>
|
|
145
|
-
async ({ asset, walletAccount, address, amount, options }) => {
|
|
209
|
+
async ({ asset, walletAccount, address, amount, ...options }) => {
|
|
146
210
|
// Prepare transaction
|
|
147
211
|
const { bumpTxId } = options
|
|
148
212
|
|
|
149
213
|
const assetName = asset.name
|
|
150
214
|
const accountState = await assetClientInterface.getAccountState({ assetName, walletAccount })
|
|
151
215
|
|
|
152
|
-
|
|
216
|
+
let transactionDescriptor = await getPrepareSendTransaction({
|
|
153
217
|
address,
|
|
154
218
|
allowUnconfirmedRbfEnabledUtxos,
|
|
155
219
|
amount,
|
|
@@ -160,22 +224,35 @@ export const createAndBroadcastTXFactory =
|
|
|
160
224
|
options,
|
|
161
225
|
utxosDescendingOrder,
|
|
162
226
|
walletAccount,
|
|
227
|
+
allowedPurposes,
|
|
228
|
+
Psbt,
|
|
229
|
+
Transaction,
|
|
163
230
|
})
|
|
164
231
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
232
|
+
// If we already created a PSBT for Bitcoin, hydrate the full transaction
|
|
233
|
+
// context from it before signing.
|
|
234
|
+
let unsignedTx, metadata
|
|
235
|
+
if (transactionDescriptor.unsignedTx?.txData?.psbtBuffer) {
|
|
236
|
+
const psbtBuffer = transactionDescriptor.unsignedTx.txData.psbtBuffer
|
|
237
|
+
const txContext = await extractTransactionContext({
|
|
238
|
+
psbtBuffer,
|
|
239
|
+
asset,
|
|
240
|
+
assetClientInterface,
|
|
241
|
+
walletAccount,
|
|
242
|
+
allowedPurposes,
|
|
243
|
+
Psbt,
|
|
244
|
+
Transaction,
|
|
245
|
+
})
|
|
246
|
+
transactionDescriptor = toTransactionDescriptor({ txContext, psbtBuffer })
|
|
247
|
+
unsignedTx = transactionDescriptor.unsignedTx
|
|
248
|
+
metadata = transactionDescriptor.metadata
|
|
249
|
+
} else {
|
|
250
|
+
// Legacy/non-PSBT flows stick with the original descriptor shape.
|
|
251
|
+
unsignedTx = transactionDescriptor.unsignedTx
|
|
252
|
+
metadata = transactionDescriptor.metadata
|
|
253
|
+
}
|
|
177
254
|
|
|
178
|
-
|
|
255
|
+
const { sendAmount, usableUtxos, replaceTx, sendOutput, changeOutput } = metadata
|
|
179
256
|
|
|
180
257
|
// Sign transaction
|
|
181
258
|
const { rawTx, txId, tx } = await signTransaction({
|
|
@@ -192,7 +269,7 @@ export const createAndBroadcastTXFactory =
|
|
|
192
269
|
if (/insight broadcast http error.*missing inputs/i.test(err.message)) {
|
|
193
270
|
err.txInfo = JSON.stringify({
|
|
194
271
|
amount: sendAmount.toDefaultString({ unit: true }),
|
|
195
|
-
fee: ((fee && fee.toDefaultString({ unit: true })) || 0).toString(),
|
|
272
|
+
fee: ((metadata.fee && metadata.fee.toDefaultString({ unit: true })) || 0).toString(),
|
|
196
273
|
allUtxos: usableUtxos.toJSON(),
|
|
197
274
|
})
|
|
198
275
|
}
|
|
@@ -203,7 +280,7 @@ export const createAndBroadcastTXFactory =
|
|
|
203
280
|
function findUtxoIndex(output) {
|
|
204
281
|
let utxoIndex = -1
|
|
205
282
|
if (output) {
|
|
206
|
-
for (const [i, [address, amount]] of outputs.entries()) {
|
|
283
|
+
for (const [i, [address, amount]] of unsignedTx.txData.outputs.entries()) {
|
|
207
284
|
if (output[0] === address && output[1] === amount) {
|
|
208
285
|
utxoIndex = i
|
|
209
286
|
break
|
|
@@ -228,7 +305,6 @@ export const createAndBroadcastTXFactory =
|
|
|
228
305
|
rawTx,
|
|
229
306
|
changeUtxoIndex,
|
|
230
307
|
getSizeAndChangeScript,
|
|
231
|
-
rbfEnabled,
|
|
232
308
|
})
|
|
233
309
|
|
|
234
310
|
await updateTransactionLog({
|
|
@@ -236,14 +312,9 @@ export const createAndBroadcastTXFactory =
|
|
|
236
312
|
assetClientInterface,
|
|
237
313
|
walletAccount,
|
|
238
314
|
txId,
|
|
239
|
-
fee,
|
|
240
315
|
metadata,
|
|
241
|
-
address,
|
|
242
|
-
amount,
|
|
243
316
|
bumpTxId,
|
|
244
317
|
size,
|
|
245
|
-
blockHeight,
|
|
246
|
-
rbfEnabled,
|
|
247
318
|
})
|
|
248
319
|
|
|
249
320
|
return {
|
|
@@ -16,7 +16,6 @@ import { serializeCurrency } from '../fee/fee-utils.js'
|
|
|
16
16
|
* @param {number} params.changeUtxoIndex - Index of change output
|
|
17
17
|
* @param {Object} params.changeOutput - Change output details
|
|
18
18
|
* @param {Object} params.getSizeAndChangeScript - Function to get size and script
|
|
19
|
-
* @param {boolean} params.rbfEnabled - Whether RBF is enabled
|
|
20
19
|
*/
|
|
21
20
|
export async function updateAccountState({
|
|
22
21
|
assetClientInterface,
|
|
@@ -29,9 +28,8 @@ export async function updateAccountState({
|
|
|
29
28
|
rawTx,
|
|
30
29
|
changeUtxoIndex,
|
|
31
30
|
getSizeAndChangeScript,
|
|
32
|
-
rbfEnabled,
|
|
33
31
|
}) {
|
|
34
|
-
const { usableUtxos, selectedUtxos, replaceTx, change, ourAddress } = metadata
|
|
32
|
+
const { usableUtxos, selectedUtxos, replaceTx, change, ourAddress, rbfEnabled } = metadata
|
|
35
33
|
|
|
36
34
|
// Get change script and size
|
|
37
35
|
const { script, size } = getSizeAndChangeScript({
|
|
@@ -88,30 +86,31 @@ export async function updateAccountState({
|
|
|
88
86
|
* @param {Object} params.assetClientInterface - Asset client interface
|
|
89
87
|
* @param {Object} params.walletAccount - Wallet account
|
|
90
88
|
* @param {string} params.txId - Transaction ID
|
|
91
|
-
* @param {Object} params.fee - Transaction fee
|
|
92
89
|
* @param {Object} params.metadata - Transaction metadata
|
|
93
|
-
* @param {string} params.address - Recipient address
|
|
94
|
-
* @param {Object} params.amount - Transaction amount (for regular sends)
|
|
95
90
|
* @param {string} params.bumpTxId - ID of transaction being bumped (if applicable)
|
|
96
91
|
* @param {number} params.size - Transaction size
|
|
97
|
-
* @param {number} params.blockHeight - Block height
|
|
98
|
-
* @param {boolean} params.rbfEnabled - Whether RBF is enabled
|
|
99
92
|
*/
|
|
100
93
|
export async function updateTransactionLog({
|
|
101
94
|
asset,
|
|
102
95
|
assetClientInterface,
|
|
103
96
|
walletAccount,
|
|
104
97
|
txId,
|
|
105
|
-
fee,
|
|
106
98
|
metadata,
|
|
107
|
-
address,
|
|
108
|
-
amount,
|
|
109
99
|
bumpTxId,
|
|
110
100
|
size,
|
|
111
|
-
blockHeight,
|
|
112
|
-
rbfEnabled,
|
|
113
101
|
}) {
|
|
114
|
-
const {
|
|
102
|
+
const {
|
|
103
|
+
totalAmount,
|
|
104
|
+
selectedUtxos,
|
|
105
|
+
replaceTx,
|
|
106
|
+
changeOutput,
|
|
107
|
+
ourAddress,
|
|
108
|
+
fee,
|
|
109
|
+
blockHeight,
|
|
110
|
+
rbfEnabled,
|
|
111
|
+
address,
|
|
112
|
+
amount,
|
|
113
|
+
} = metadata
|
|
115
114
|
const assetName = asset.name
|
|
116
115
|
|
|
117
116
|
// Check if this is a self-send
|
package/src/tx-sign/common.js
CHANGED
|
@@ -27,3 +27,12 @@ export const serializeTx = ({ tx }) => {
|
|
|
27
27
|
})),
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
export function shouldSkipFinalize(unsignedTx) {
|
|
32
|
+
const isExternalPsbt =
|
|
33
|
+
unsignedTx.txData.psbtBuffer &&
|
|
34
|
+
unsignedTx.txMeta.addressPathsMap &&
|
|
35
|
+
unsignedTx.txMeta.inputsToSign
|
|
36
|
+
|
|
37
|
+
return isExternalPsbt || unsignedTx.txMeta.returnPsbt
|
|
38
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { bip371, payments, Transaction } from '@exodus/bitcoinjs'
|
|
2
2
|
import { publicKeyToX } from '@exodus/crypto/secp256k1'
|
|
3
3
|
|
|
4
|
+
import { withUnsafeNonSegwit } from '../psbt-utils.js'
|
|
4
5
|
import { createGetKeyWithMetadata } from './create-get-key-and-purpose.js'
|
|
5
6
|
import { toAsyncBufferSigner, toAsyncSigner } from './taproot.js'
|
|
6
7
|
|
|
@@ -82,9 +83,12 @@ export function createSignWithWallet({
|
|
|
82
83
|
: toAsyncSigner({ privateKey, publicKey, isTaprootKeySpend })
|
|
83
84
|
|
|
84
85
|
// desktop / BE / mobile with bip-schnorr signing
|
|
85
|
-
signingPromises.push(psbt.signInputAsync(index, asyncSigner, allowedSigHashTypes))
|
|
86
|
+
signingPromises.push(() => psbt.signInputAsync(index, asyncSigner, allowedSigHashTypes))
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
await
|
|
89
|
+
await withUnsafeNonSegwit({
|
|
90
|
+
psbt,
|
|
91
|
+
fn: () => Promise.all(signingPromises.map((sign) => sign())),
|
|
92
|
+
})
|
|
89
93
|
}
|
|
90
94
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Psbt as DefaultPsbt, Transaction as DefaultTransaction } from '@exodus/bitcoinjs'
|
|
2
2
|
import assert from 'minimalistic-assert'
|
|
3
3
|
|
|
4
|
-
import { extractTransaction } from './common.js'
|
|
4
|
+
import { extractTransaction, shouldSkipFinalize } from './common.js'
|
|
5
5
|
import { createSignWithWallet } from './create-sign-with-wallet.js'
|
|
6
6
|
import { createPrepareForSigning } from './default-prepare-for-signing.js'
|
|
7
7
|
|
|
@@ -57,7 +57,7 @@ export const signTxFactory = ({
|
|
|
57
57
|
},
|
|
58
58
|
})
|
|
59
59
|
|
|
60
|
-
const skipFinalize =
|
|
60
|
+
const skipFinalize = shouldSkipFinalize(unsignedTx)
|
|
61
61
|
await signWithWallet(psbt, inputsToSign, skipFinalize)
|
|
62
62
|
return extractTransaction({ psbt, skipFinalize })
|
|
63
63
|
}
|
|
@@ -2,11 +2,8 @@ import { Psbt as DefaultPsbt, Transaction as DefaultTransaction } from '@exodus/
|
|
|
2
2
|
import assert from 'minimalistic-assert'
|
|
3
3
|
|
|
4
4
|
import { writePsbtBlockHeight } from '../psbt-proprietary-types.js'
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
qtumignition: 25_000,
|
|
8
|
-
ravencoin: 1_000_000,
|
|
9
|
-
}
|
|
5
|
+
import { setPsbtVersionIfNotBitcoin } from '../psbt-utils.js'
|
|
6
|
+
import { getMaximumFeeRate } from './maximum-fee-rates.js'
|
|
10
7
|
|
|
11
8
|
/**
|
|
12
9
|
* Factory function that create the prepareForSigning function for a bitcoin-like asset.
|
|
@@ -26,17 +23,18 @@ export function createPrepareForSigning({
|
|
|
26
23
|
|
|
27
24
|
return ({ unsignedTx }) => {
|
|
28
25
|
const networkInfo = coinInfo.toBitcoinJS()
|
|
26
|
+
const maximumFeeRate = getMaximumFeeRate(assetName)
|
|
29
27
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
const psbtOptions = { network: networkInfo }
|
|
29
|
+
if (maximumFeeRate !== undefined) {
|
|
30
|
+
psbtOptions.maximumFeeRate = maximumFeeRate
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (unsignedTx.txData.psbtBuffer) {
|
|
36
34
|
return createPsbtFromBuffer({
|
|
37
35
|
Psbt,
|
|
38
36
|
psbtBuffer: unsignedTx.txData.psbtBuffer,
|
|
39
|
-
|
|
37
|
+
psbtOptions,
|
|
40
38
|
})
|
|
41
39
|
}
|
|
42
40
|
|
|
@@ -50,15 +48,17 @@ export function createPrepareForSigning({
|
|
|
50
48
|
Psbt,
|
|
51
49
|
Transaction,
|
|
52
50
|
})
|
|
53
|
-
|
|
51
|
+
setPsbtVersionIfNotBitcoin(psbt, assetName)
|
|
54
52
|
|
|
55
53
|
return psbt
|
|
56
54
|
}
|
|
57
55
|
}
|
|
58
56
|
|
|
59
57
|
// Creates a PSBT instance from the passed transaction buffer provided by 3rd parties (e.g. dApps).
|
|
60
|
-
function createPsbtFromBuffer({ Psbt, psbtBuffer, ecc,
|
|
61
|
-
|
|
58
|
+
function createPsbtFromBuffer({ Psbt, psbtBuffer, ecc, psbtOptions = {} }) {
|
|
59
|
+
const fromBufferOptions = { ...psbtOptions }
|
|
60
|
+
if (ecc) fromBufferOptions.eccLib = ecc
|
|
61
|
+
return Psbt.fromBuffer(psbtBuffer, fromBufferOptions)
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// Creates a PSBT instance from the passed inputs, outputs etc. The wallet itself provides this data.
|
|
@@ -73,11 +73,18 @@ function createPsbtFromTxData({
|
|
|
73
73
|
Transaction,
|
|
74
74
|
blockHeight,
|
|
75
75
|
}) {
|
|
76
|
-
// use
|
|
77
|
-
// if undefined,
|
|
78
|
-
|
|
76
|
+
// use hardcoded max fee rates for specific assets
|
|
77
|
+
// if undefined, the PSBT default (5000 sat/vB) is used. Passing `undefined`
|
|
78
|
+
// directly into the constructor used to clobber the default (buggy behavior),
|
|
79
|
+
// so we now only set the override when we actually have one.
|
|
80
|
+
const maximumFeeRate = getMaximumFeeRate(assetName)
|
|
81
|
+
|
|
82
|
+
const psbtOptions = { network: networkInfo }
|
|
83
|
+
if (maximumFeeRate !== undefined) {
|
|
84
|
+
psbtOptions.maximumFeeRate = maximumFeeRate
|
|
85
|
+
}
|
|
79
86
|
|
|
80
|
-
const psbt = new Psbt(
|
|
87
|
+
const psbt = new Psbt(psbtOptions)
|
|
81
88
|
|
|
82
89
|
// If present, add blockHeight as a proprietary field
|
|
83
90
|
if (blockHeight !== undefined) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from 'minimalistic-assert'
|
|
2
2
|
|
|
3
|
-
import { extractTransaction } from './common.js'
|
|
3
|
+
import { extractTransaction, shouldSkipFinalize } from './common.js'
|
|
4
4
|
import { createPrepareForSigning } from './default-prepare-for-signing.js'
|
|
5
5
|
|
|
6
6
|
export const signHardwareFactory = ({ assetName, resolvePurpose, keys, coinInfo }) => {
|
|
@@ -40,7 +40,7 @@ export const signHardwareFactory = ({ assetName, resolvePurpose, keys, coinInfo
|
|
|
40
40
|
multisigData,
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
-
const skipFinalize =
|
|
43
|
+
const skipFinalize = shouldSkipFinalize(unsignedTx)
|
|
44
44
|
return extractTransaction({ psbt, skipFinalize })
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// We keep hardcoded fee caps here so both signing and PSBT builders reuse the
|
|
2
|
+
// same per-asset overrides (e.g. Dogecoin needs a higher ceiling than the
|
|
3
|
+
// bitcoinjs default). If an asset is missing, the library fallback (5000 sat/vB)
|
|
4
|
+
// will apply instead.
|
|
5
|
+
const MAXIMUM_FEE_RATES = {
|
|
6
|
+
dogecoin: 12_000,
|
|
7
|
+
qtumignition: 25_000,
|
|
8
|
+
ravencoin: 1_000_000,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getMaximumFeeRate(assetName) {
|
|
12
|
+
return MAXIMUM_FEE_RATES[assetName]
|
|
13
|
+
}
|