@exodus/bitcoin-api 2.18.4 → 2.20.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 +18 -0
- package/package.json +2 -2
- package/src/fee/can-bump-tx.js +7 -1
- package/src/fee/fee-estimator.js +12 -9
- package/src/fee/fee-utils.js +2 -1
- package/src/fee/get-fee-resolver.js +29 -4
- package/src/fee/utxo-selector.js +27 -10
- package/src/tx-send/index.js +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
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
|
+
## [2.20.0](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@2.19.0...@exodus/bitcoin-api@2.20.0) (2024-07-09)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **BTC:** add changeAddressType to createAsset config ([#2772](https://github.com/ExodusMovement/assets/issues/2772)) ([d8e38dd](https://github.com/ExodusMovement/assets/commit/d8e38dddfbe59b3e8ff9fc2432049bb512a91c22))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## [2.19.0](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@2.18.4...@exodus/bitcoin-api@2.19.0) (2024-07-05)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* **BTC:** add taprootInputWitnessSize for tx size estimation ([#2737](https://github.com/ExodusMovement/assets/issues/2737)) ([13f727a](https://github.com/ExodusMovement/assets/commit/13f727a836f9fc620628abb6a94a49c1a6774ca5))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
6
24
|
## [2.18.4](https://github.com/ExodusMovement/assets/compare/@exodus/bitcoin-api@2.18.3...@exodus/bitcoin-api@2.18.4) (2024-06-20)
|
|
7
25
|
|
|
8
26
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/bitcoin-api",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.20.0",
|
|
4
4
|
"description": "Exodus bitcoin-api",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -66,5 +66,5 @@
|
|
|
66
66
|
"type": "git",
|
|
67
67
|
"url": "git+https://github.com/ExodusMovement/assets.git"
|
|
68
68
|
},
|
|
69
|
-
"gitHead": "
|
|
69
|
+
"gitHead": "0dc141fc6a249ac051a5ca591d46cbc81096a4f3"
|
|
70
70
|
}
|
package/src/fee/can-bump-tx.js
CHANGED
|
@@ -18,6 +18,8 @@ const _canBumpTx = ({
|
|
|
18
18
|
getFeeEstimator,
|
|
19
19
|
allowUnconfirmedRbfEnabledUtxos,
|
|
20
20
|
utxosDescendingOrder,
|
|
21
|
+
taprootInputWitnessSize,
|
|
22
|
+
changeAddressType,
|
|
21
23
|
}) => {
|
|
22
24
|
assert(asset, 'asset must be provided')
|
|
23
25
|
assert(tx, 'tx must be provided')
|
|
@@ -93,6 +95,8 @@ const _canBumpTx = ({
|
|
|
93
95
|
allowUnconfirmedRbfEnabledUtxos,
|
|
94
96
|
unconfirmedTxAncestor,
|
|
95
97
|
utxosDescendingOrder,
|
|
98
|
+
taprootInputWitnessSize,
|
|
99
|
+
changeAddressType,
|
|
96
100
|
})
|
|
97
101
|
if (replaceTx) return { bumpType: BumpType.RBF, bumpFee: fee.sub(replaceTx.feeAmount) }
|
|
98
102
|
}
|
|
@@ -101,12 +105,14 @@ const _canBumpTx = ({
|
|
|
101
105
|
asset,
|
|
102
106
|
usableUtxos,
|
|
103
107
|
feeRate,
|
|
104
|
-
receiveAddress:
|
|
108
|
+
receiveAddress: changeAddressType,
|
|
105
109
|
getFeeEstimator,
|
|
106
110
|
mustSpendUtxos: changeUtxos,
|
|
107
111
|
allowUnconfirmedRbfEnabledUtxos,
|
|
108
112
|
unconfirmedTxAncestor,
|
|
109
113
|
utxosDescendingOrder,
|
|
114
|
+
taprootInputWitnessSize,
|
|
115
|
+
changeAddressType,
|
|
110
116
|
})
|
|
111
117
|
|
|
112
118
|
return fee ? { bumpType: BumpType.CPFP, bumpFee: fee } : { errorMessage: 'insufficient funds' }
|
package/src/fee/fee-estimator.js
CHANGED
|
@@ -48,6 +48,11 @@ const scriptPubKeyLengths = {
|
|
|
48
48
|
[P2TR]: 34,
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// Only the 64 byte Schnorr signature is present for Taproot Key-Path spend
|
|
52
|
+
const signatureLength = 64
|
|
53
|
+
const taprootInputKeyPathWitnessSize =
|
|
54
|
+
varuint.encodingLength(1) + varuint.encodingLength(signatureLength) + signatureLength
|
|
55
|
+
|
|
51
56
|
// bitcoin and bitcoin-like:
|
|
52
57
|
// 10 = version: 4, locktime: 4, inputs and outputs count: 1
|
|
53
58
|
// 148 = txId: 32, vout: 4, count: 1, script: 107 (max), sequence: 4
|
|
@@ -58,7 +63,12 @@ export const getSizeFactory = ({ defaultOutputType, addressApi }) => {
|
|
|
58
63
|
|
|
59
64
|
const scriptClassifier = scriptClassifierFactory({ addressApi })
|
|
60
65
|
|
|
61
|
-
return (
|
|
66
|
+
return (
|
|
67
|
+
asset,
|
|
68
|
+
inputs,
|
|
69
|
+
outputs,
|
|
70
|
+
{ compressed = true, taprootInputWitnessSize = taprootInputKeyPathWitnessSize } = {}
|
|
71
|
+
) => {
|
|
62
72
|
if (inputs instanceof UtxoCollection) {
|
|
63
73
|
inputs = [...inputs].map((utxo) => utxo.script || null)
|
|
64
74
|
}
|
|
@@ -136,14 +146,7 @@ export const getSizeFactory = ({ defaultOutputType, addressApi }) => {
|
|
|
136
146
|
}
|
|
137
147
|
|
|
138
148
|
if ([P2TR].includes(utxoScriptType)) {
|
|
139
|
-
|
|
140
|
-
const signatureLength = 64
|
|
141
|
-
return (
|
|
142
|
-
t +
|
|
143
|
-
varuint.encodingLength(1) +
|
|
144
|
-
varuint.encodingLength(signatureLength) +
|
|
145
|
-
signatureLength
|
|
146
|
-
)
|
|
149
|
+
return t + taprootInputWitnessSize
|
|
147
150
|
}
|
|
148
151
|
|
|
149
152
|
// Non-witness inputs get a placeholder zero byte
|
package/src/fee/fee-utils.js
CHANGED
|
@@ -36,11 +36,12 @@ export default function createDefaultFeeEstimator(getSize) {
|
|
|
36
36
|
inputs = options.inputs,
|
|
37
37
|
outputs = options.outputs,
|
|
38
38
|
unconfirmedTxAncestor = options.unconfirmedTxAncestor,
|
|
39
|
+
taprootInputWitnessSize,
|
|
39
40
|
} = {}) => {
|
|
40
41
|
const extraFee = getExtraFee({ asset, inputs, feePerKB, unconfirmedTxAncestor })
|
|
41
42
|
// Yes, it's suppose to be '1000' and not '1024'
|
|
42
43
|
// https://bitcoin.stackexchange.com/questions/24000/a-fee-is-added-per-kilobyte-of-data-that-means-1000-bytes-or-1024
|
|
43
|
-
const size = getSize(asset, inputs, outputs, options)
|
|
44
|
+
const size = getSize(asset, inputs, outputs, { ...options, taprootInputWitnessSize })
|
|
44
45
|
const feeRaw = Math.ceil((feePerKB.toBaseNumber() * size) / 1000)
|
|
45
46
|
return asset.currency.baseUnit(feeRaw + extraFee)
|
|
46
47
|
}
|
|
@@ -15,13 +15,20 @@ export class GetFeeResolver {
|
|
|
15
15
|
#getFeeEstimator
|
|
16
16
|
#allowUnconfirmedRbfEnabledUtxos
|
|
17
17
|
#utxosDescendingOrder
|
|
18
|
+
#changeAddressType
|
|
18
19
|
|
|
19
|
-
constructor({
|
|
20
|
+
constructor({
|
|
21
|
+
getFeeEstimator,
|
|
22
|
+
allowUnconfirmedRbfEnabledUtxos,
|
|
23
|
+
utxosDescendingOrder,
|
|
24
|
+
changeAddressType,
|
|
25
|
+
}) {
|
|
20
26
|
assert(getFeeEstimator, 'getFeeEstimator must be provided')
|
|
21
27
|
this.#getFeeEstimator = (asset, { feePerKB, ...options }) =>
|
|
22
28
|
getFeeEstimator(asset, feePerKB, options)
|
|
23
29
|
this.#allowUnconfirmedRbfEnabledUtxos = allowUnconfirmedRbfEnabledUtxos
|
|
24
30
|
this.#utxosDescendingOrder = utxosDescendingOrder
|
|
31
|
+
this.#changeAddressType = changeAddressType
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
getFee = ({
|
|
@@ -35,6 +42,7 @@ export class GetFeeResolver {
|
|
|
35
42
|
nft, // sending one nft
|
|
36
43
|
brc20, // sending multiple inscriptions ids (as in sending multiple transfer ordinals)
|
|
37
44
|
receiveAddress,
|
|
45
|
+
taprootInputWitnessSize,
|
|
38
46
|
}) => {
|
|
39
47
|
if (nft) {
|
|
40
48
|
assert(!amount, 'amount must not be provided when nft is provided!!!')
|
|
@@ -60,11 +68,21 @@ export class GetFeeResolver {
|
|
|
60
68
|
customFee,
|
|
61
69
|
isSendAll,
|
|
62
70
|
inscriptionIds,
|
|
71
|
+
taprootInputWitnessSize,
|
|
63
72
|
})
|
|
64
73
|
return { fee: resolvedFee, extraFee }
|
|
65
74
|
}
|
|
66
75
|
|
|
67
|
-
getAvailableBalance = ({
|
|
76
|
+
getAvailableBalance = ({
|
|
77
|
+
asset,
|
|
78
|
+
accountState,
|
|
79
|
+
txSet,
|
|
80
|
+
feeData,
|
|
81
|
+
amount,
|
|
82
|
+
customFee,
|
|
83
|
+
isSendAll,
|
|
84
|
+
taprootInputWitnessSize,
|
|
85
|
+
}) => {
|
|
68
86
|
return this.#getUtxosData({
|
|
69
87
|
asset,
|
|
70
88
|
accountState,
|
|
@@ -73,16 +91,18 @@ export class GetFeeResolver {
|
|
|
73
91
|
customFee,
|
|
74
92
|
isSendAll,
|
|
75
93
|
amount,
|
|
94
|
+
taprootInputWitnessSize,
|
|
76
95
|
}).availableBalance
|
|
77
96
|
}
|
|
78
97
|
|
|
79
|
-
getSpendableBalance = ({ asset, accountState, txSet, feeData }) => {
|
|
98
|
+
getSpendableBalance = ({ asset, accountState, txSet, feeData, taprootInputWitnessSize }) => {
|
|
80
99
|
return this.#getUtxosData({
|
|
81
100
|
asset,
|
|
82
101
|
accountState,
|
|
83
102
|
txSet,
|
|
84
103
|
feeData,
|
|
85
104
|
isSendAll: true,
|
|
105
|
+
taprootInputWitnessSize,
|
|
86
106
|
}).spendableBalance
|
|
87
107
|
}
|
|
88
108
|
|
|
@@ -96,6 +116,7 @@ export class GetFeeResolver {
|
|
|
96
116
|
customFee,
|
|
97
117
|
isSendAll,
|
|
98
118
|
inscriptionIds,
|
|
119
|
+
taprootInputWitnessSize,
|
|
99
120
|
}) => {
|
|
100
121
|
assert(asset, 'asset must be provided')
|
|
101
122
|
assert(feeData, 'feeData must be provided')
|
|
@@ -136,10 +157,12 @@ export class GetFeeResolver {
|
|
|
136
157
|
allowUnconfirmedRbfEnabledUtxos: this.#allowUnconfirmedRbfEnabledUtxos,
|
|
137
158
|
unconfirmedTxAncestor,
|
|
138
159
|
utxosDescendingOrder: this.#utxosDescendingOrder,
|
|
160
|
+
taprootInputWitnessSize,
|
|
161
|
+
changeAddressType: this.#changeAddressType,
|
|
139
162
|
})
|
|
140
163
|
}
|
|
141
164
|
|
|
142
|
-
canBumpTx = ({ asset, tx, txSet, accountState, feeData }) => {
|
|
165
|
+
canBumpTx = ({ asset, tx, txSet, accountState, feeData, taprootInputWitnessSize }) => {
|
|
143
166
|
return canBumpTx({
|
|
144
167
|
asset,
|
|
145
168
|
tx,
|
|
@@ -149,6 +172,8 @@ export class GetFeeResolver {
|
|
|
149
172
|
getFeeEstimator: this.#getFeeEstimator,
|
|
150
173
|
allowUnconfirmedRbfEnabledUtxos: this.#allowUnconfirmedRbfEnabledUtxos,
|
|
151
174
|
utxosDescendingOrder: this.#utxosDescendingOrder,
|
|
175
|
+
taprootInputWitnessSize,
|
|
176
|
+
changeAddressType: this.#changeAddressType,
|
|
152
177
|
})
|
|
153
178
|
}
|
|
154
179
|
}
|
package/src/fee/utxo-selector.js
CHANGED
|
@@ -35,6 +35,8 @@ export const selectUtxos = ({
|
|
|
35
35
|
unconfirmedTxAncestor,
|
|
36
36
|
inscriptionIds, // for each inscription transfer, we need to calculate one more input and one more output
|
|
37
37
|
transferOrdinalsUtxos, // to calculate the size of the input
|
|
38
|
+
taprootInputWitnessSize,
|
|
39
|
+
changeAddressType = 'P2PKH',
|
|
38
40
|
}) => {
|
|
39
41
|
const resolvedReceiveAddresses = getBestReceiveAddresses({
|
|
40
42
|
asset,
|
|
@@ -53,10 +55,6 @@ export const selectUtxos = ({
|
|
|
53
55
|
assert(usableUtxos, 'usableUtxos is required')
|
|
54
56
|
assert(getFeeEstimator, 'getFeeEstimator is required')
|
|
55
57
|
|
|
56
|
-
const changeAddressType = ['bitcoin', 'bitcoinregtest', 'bitcointestnet'].includes(asset.name)
|
|
57
|
-
? 'P2WPKH'
|
|
58
|
-
: 'P2PKH'
|
|
59
|
-
|
|
60
58
|
const feeEstimator = getFeeEstimator(asset, { feePerKB: feeRate, unconfirmedTxAncestor })
|
|
61
59
|
const { currency } = asset
|
|
62
60
|
if (!amount) amount = currency.ZERO
|
|
@@ -100,9 +98,13 @@ export const selectUtxos = ({
|
|
|
100
98
|
|
|
101
99
|
if (isSendAll) {
|
|
102
100
|
additionalUtxos = UtxoCollection.fromArray(confirmedUtxosArray, { currency })
|
|
103
|
-
fee = replaceFeeEstimator({
|
|
101
|
+
fee = replaceFeeEstimator({
|
|
102
|
+
inputs: inputs.union(additionalUtxos),
|
|
103
|
+
outputs,
|
|
104
|
+
taprootInputWitnessSize,
|
|
105
|
+
})
|
|
104
106
|
} else {
|
|
105
|
-
fee = replaceFeeEstimator({ inputs, outputs })
|
|
107
|
+
fee = replaceFeeEstimator({ inputs, outputs, taprootInputWitnessSize })
|
|
106
108
|
additionalUtxos = UtxoCollection.createEmpty({ currency })
|
|
107
109
|
while (replaceTxAmount.add(additionalUtxos.value).lt(amount.add(fee))) {
|
|
108
110
|
if (confirmedUtxosArray.length === 0) {
|
|
@@ -116,6 +118,7 @@ export const selectUtxos = ({
|
|
|
116
118
|
fee = replaceFeeEstimator({
|
|
117
119
|
inputs: inputs.union(additionalUtxos),
|
|
118
120
|
outputs: noChangeOutputs,
|
|
121
|
+
taprootInputWitnessSize,
|
|
119
122
|
})
|
|
120
123
|
}
|
|
121
124
|
|
|
@@ -127,6 +130,7 @@ export const selectUtxos = ({
|
|
|
127
130
|
fee = replaceFeeEstimator({
|
|
128
131
|
inputs: inputs.union(additionalUtxos),
|
|
129
132
|
outputs,
|
|
133
|
+
taprootInputWitnessSize,
|
|
130
134
|
})
|
|
131
135
|
}
|
|
132
136
|
}
|
|
@@ -136,6 +140,7 @@ export const selectUtxos = ({
|
|
|
136
140
|
const chainFee = feeEstimator({
|
|
137
141
|
inputs: changeUtxos.union(additionalUtxos),
|
|
138
142
|
outputs: chainOutputs,
|
|
143
|
+
taprootInputWitnessSize,
|
|
139
144
|
})
|
|
140
145
|
// If batching is same or more expensive than chaining, don't replace
|
|
141
146
|
// Unless we are accelerating a changeless tx, then we must replace because it can't be chained
|
|
@@ -173,7 +178,11 @@ export const selectUtxos = ({
|
|
|
173
178
|
|
|
174
179
|
if (isSendAll) {
|
|
175
180
|
const selectedUtxos = UtxoCollection.fromArray(utxosArray, { currency })
|
|
176
|
-
const fee = feeEstimator({
|
|
181
|
+
const fee = feeEstimator({
|
|
182
|
+
inputs: selectedUtxos,
|
|
183
|
+
outputs: receiveAddresses,
|
|
184
|
+
taprootInputWitnessSize,
|
|
185
|
+
})
|
|
177
186
|
if (selectedUtxos.value.lt(amount.add(fee))) {
|
|
178
187
|
return { fee }
|
|
179
188
|
}
|
|
@@ -208,19 +217,23 @@ export const selectUtxos = ({
|
|
|
208
217
|
? [changeAddressType]
|
|
209
218
|
: [...receiveAddresses, changeAddressType]
|
|
210
219
|
|
|
211
|
-
let fee = feeEstimator({ inputs: selectedUtxos, outputs })
|
|
220
|
+
let fee = feeEstimator({ inputs: selectedUtxos, outputs, taprootInputWitnessSize })
|
|
212
221
|
|
|
213
222
|
while (selectedUtxos.value.lt(amount.add(fee))) {
|
|
214
223
|
// We ran out of UTXOs, give up now
|
|
215
224
|
if (remainingUtxosArray.length === 0) {
|
|
216
225
|
// Try fee with no change
|
|
217
|
-
fee = feeEstimator({
|
|
226
|
+
fee = feeEstimator({
|
|
227
|
+
inputs: selectedUtxos,
|
|
228
|
+
outputs: receiveAddresses,
|
|
229
|
+
taprootInputWitnessSize,
|
|
230
|
+
})
|
|
218
231
|
break
|
|
219
232
|
}
|
|
220
233
|
|
|
221
234
|
// Add a new UTXO and recalculate the fee
|
|
222
235
|
selectedUtxos = selectedUtxos.addUtxo(remainingUtxosArray.shift())
|
|
223
|
-
fee = feeEstimator({ inputs: selectedUtxos, outputs })
|
|
236
|
+
fee = feeEstimator({ inputs: selectedUtxos, outputs, taprootInputWitnessSize })
|
|
224
237
|
}
|
|
225
238
|
|
|
226
239
|
if (selectedUtxos.value.lt(amount.add(fee))) {
|
|
@@ -246,6 +259,8 @@ export const getUtxosData = ({
|
|
|
246
259
|
transferOrdinalsUtxos,
|
|
247
260
|
unconfirmedTxAncestor,
|
|
248
261
|
utxosDescendingOrder,
|
|
262
|
+
taprootInputWitnessSize,
|
|
263
|
+
changeAddressType,
|
|
249
264
|
}) => {
|
|
250
265
|
const { selectedUtxos, replaceTx, fee } = selectUtxos({
|
|
251
266
|
asset,
|
|
@@ -263,6 +278,8 @@ export const getUtxosData = ({
|
|
|
263
278
|
inscriptionIds,
|
|
264
279
|
transferOrdinalsUtxos,
|
|
265
280
|
utxosDescendingOrder,
|
|
281
|
+
taprootInputWitnessSize,
|
|
282
|
+
changeAddressType,
|
|
266
283
|
})
|
|
267
284
|
|
|
268
285
|
const resolvedFee = replaceTx ? fee.sub(replaceTx.feeAmount) : fee
|
package/src/tx-send/index.js
CHANGED
|
@@ -223,6 +223,7 @@ export const getPrepareSendTransaction =
|
|
|
223
223
|
utxosDescendingOrder,
|
|
224
224
|
rbfEnabled: providedRbfEnabled,
|
|
225
225
|
assetClientInterface,
|
|
226
|
+
changeAddressType,
|
|
226
227
|
}) =>
|
|
227
228
|
async ({ asset: maybeToken, walletAccount, address, amount: tokenAmount, options }) => {
|
|
228
229
|
const {
|
|
@@ -236,12 +237,14 @@ export const getPrepareSendTransaction =
|
|
|
236
237
|
isExchange,
|
|
237
238
|
isBip70,
|
|
238
239
|
isRbfAllowed,
|
|
240
|
+
taprootInputWitnessSize,
|
|
239
241
|
} = options
|
|
240
242
|
|
|
241
243
|
const asset = maybeToken.baseAsset
|
|
242
244
|
const assetName = asset.name
|
|
243
245
|
const accountState = await assetClientInterface.getAccountState({ assetName, walletAccount })
|
|
244
246
|
const feeData = await assetClientInterface.getFeeConfig({ assetName })
|
|
247
|
+
feeData.feePerKB = feePerKB ?? feeData.feePerKB
|
|
245
248
|
const insightClient = asset.baseAsset.insightClient
|
|
246
249
|
|
|
247
250
|
const blockHeight = providedBlockHeight || (await getBlockHeight({ assetName, insightClient }))
|
|
@@ -352,6 +355,8 @@ export const getPrepareSendTransaction =
|
|
|
352
355
|
inscriptionIds,
|
|
353
356
|
transferOrdinalsUtxos,
|
|
354
357
|
utxosDescendingOrder,
|
|
358
|
+
taprootInputWitnessSize,
|
|
359
|
+
changeAddressType,
|
|
355
360
|
})
|
|
356
361
|
|
|
357
362
|
if (!selectedUtxos && !replaceTx) throw new Error('Not enough funds.')
|
|
@@ -471,6 +476,7 @@ export const createAndBroadcastTXFactory =
|
|
|
471
476
|
ordinalsEnabled = false,
|
|
472
477
|
utxosDescendingOrder,
|
|
473
478
|
assetClientInterface,
|
|
479
|
+
changeAddressType,
|
|
474
480
|
}) =>
|
|
475
481
|
async ({ asset: maybeToken, walletAccount, address, amount: tokenAmount, options }) => {
|
|
476
482
|
// Prepare transaction
|
|
@@ -498,6 +504,7 @@ export const createAndBroadcastTXFactory =
|
|
|
498
504
|
utxosDescendingOrder,
|
|
499
505
|
rbfEnabled,
|
|
500
506
|
assetClientInterface,
|
|
507
|
+
changeAddressType,
|
|
501
508
|
})({ asset: maybeToken, walletAccount, address, amount: tokenAmount, options })
|
|
502
509
|
const {
|
|
503
510
|
amount,
|