@exodus/bitcoin-api 2.6.8-hiro.1 → 2.6.8
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/package.json +3 -2
- package/src/account-state.js +0 -1
- package/src/tx-log/bitcoin-monitor-scanner.js +6 -36
- package/src/tx-send/index.js +20 -28
- package/src/utxos-utils.js +14 -64
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/bitcoin-api",
|
|
3
|
-
"version": "2.6.8
|
|
3
|
+
"version": "2.6.8",
|
|
4
4
|
"description": "Exodus bitcoin-api",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -42,5 +42,6 @@
|
|
|
42
42
|
"@scure/base": "^1.1.3",
|
|
43
43
|
"@scure/btc-signer": "^1.1.0",
|
|
44
44
|
"jest-when": "^3.5.1"
|
|
45
|
-
}
|
|
45
|
+
},
|
|
46
|
+
"gitHead": "eae322a1c75f9cdaebc919af510619cb59ba4c0e"
|
|
46
47
|
}
|
package/src/account-state.js
CHANGED
|
@@ -67,6 +67,7 @@ export class BitcoinMonitorScanner {
|
|
|
67
67
|
|
|
68
68
|
const storedUtxos = getUtxos({ asset, accountState })
|
|
69
69
|
const storedOrdinalUtxos = getOrdinalsUtxos({ asset, accountState })
|
|
70
|
+
const ordinalAddress = await this.getOrdinalAddress({ walletAccount })
|
|
70
71
|
|
|
71
72
|
const currentStoredUtxos = storedUtxos.union(storedOrdinalUtxos)
|
|
72
73
|
|
|
@@ -89,9 +90,7 @@ export class BitcoinMonitorScanner {
|
|
|
89
90
|
: (txs) =>
|
|
90
91
|
txs.filter((tx) => {
|
|
91
92
|
const txItem = currentTxs.get(tx.txid)
|
|
92
|
-
|
|
93
|
-
const inscriptionsIndexed = txItem?.data?.inscriptionsIndexed
|
|
94
|
-
return confirmed && (!this.#ordinalsEnabled || inscriptionsIndexed)
|
|
93
|
+
return txItem && txItem.confirmed
|
|
95
94
|
}).length >= txs.length
|
|
96
95
|
|
|
97
96
|
const unusedAddressIndexes = await assetClientInterface.getUnusedAddressIndexes({
|
|
@@ -345,15 +344,6 @@ export class BitcoinMonitorScanner {
|
|
|
345
344
|
currencies: { [assetName]: currency },
|
|
346
345
|
}
|
|
347
346
|
|
|
348
|
-
if (this.#ordinalsEnabled) {
|
|
349
|
-
txLogItem.data = {
|
|
350
|
-
...txLogItem.data,
|
|
351
|
-
inscriptionsIndexed: txItem.inscriptionsIndexed,
|
|
352
|
-
sentInscriptions: [],
|
|
353
|
-
receivedInscriptions: [],
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
347
|
let from = []
|
|
358
348
|
|
|
359
349
|
// if txItem.vin has an address that matches ours, means we've spent this tx
|
|
@@ -371,13 +361,8 @@ export class BitcoinMonitorScanner {
|
|
|
371
361
|
txLogItem.coinAmount = txLogItem.coinAmount.sub(currency.defaultUnit(vin.value))
|
|
372
362
|
isSent = true
|
|
373
363
|
txLogItem.data.sent = []
|
|
374
|
-
if (
|
|
375
|
-
txLogItem.data.
|
|
376
|
-
...vin.inscriptions.map((i) => ({
|
|
377
|
-
...i,
|
|
378
|
-
value: currency.defaultUnit(vin.value).toBaseNumber(),
|
|
379
|
-
}))
|
|
380
|
-
)
|
|
364
|
+
if (ordinalAddress && address.toString() === ordinalAddress.toString()) {
|
|
365
|
+
txLogItem.data.isInscriptionSent = true
|
|
381
366
|
}
|
|
382
367
|
|
|
383
368
|
// this is only used to exclude the utxos in the reducer which is why we don't care about the other fields
|
|
@@ -447,13 +432,8 @@ export class BitcoinMonitorScanner {
|
|
|
447
432
|
txLogItem.data.changeAddress = address
|
|
448
433
|
}
|
|
449
434
|
|
|
450
|
-
if (
|
|
451
|
-
txLogItem.data.
|
|
452
|
-
...vout.inscriptions.map((i) => ({
|
|
453
|
-
...i,
|
|
454
|
-
value: currency.defaultUnit(vout.value).toBaseNumber(),
|
|
455
|
-
}))
|
|
456
|
-
)
|
|
435
|
+
if (ordinalAddress && address.toString() === ordinalAddress.toString()) {
|
|
436
|
+
txLogItem.data.isInscriptionReceived = true
|
|
457
437
|
}
|
|
458
438
|
|
|
459
439
|
// it was sent to us...
|
|
@@ -470,11 +450,6 @@ export class BitcoinMonitorScanner {
|
|
|
470
450
|
rbfEnabled: txItem.rbf,
|
|
471
451
|
}
|
|
472
452
|
|
|
473
|
-
if (this.#ordinalsEnabled) {
|
|
474
|
-
output.inscriptionsIndexed = txItem.inscriptionsIndexed
|
|
475
|
-
output.inscriptions = vout.inscriptions || []
|
|
476
|
-
}
|
|
477
|
-
|
|
478
453
|
if (this.#shouldExcludeVoutUtxo({ asset, output, txItem, vout })) {
|
|
479
454
|
return
|
|
480
455
|
}
|
|
@@ -565,13 +540,10 @@ export class BitcoinMonitorScanner {
|
|
|
565
540
|
return !isEqual(chain, originalChain.chain)
|
|
566
541
|
})
|
|
567
542
|
|
|
568
|
-
const ordinalAddress = await this.getOrdinalAddress({ walletAccount })
|
|
569
543
|
const utxosData = utxoCol
|
|
570
544
|
? partitionUtxos({
|
|
571
545
|
allUtxos: utxoCol,
|
|
572
|
-
ordinalsEnabled: this.#ordinalsEnabled,
|
|
573
546
|
ordinalAddress,
|
|
574
|
-
knownBalanceUtxoIds: accountState.knownBalanceUtxoIds,
|
|
575
547
|
})
|
|
576
548
|
: {}
|
|
577
549
|
|
|
@@ -631,9 +603,7 @@ export class BitcoinMonitorScanner {
|
|
|
631
603
|
|
|
632
604
|
const { utxos, ordinalsUtxos } = partitionUtxos({
|
|
633
605
|
allUtxos: txConfirmedUtxos,
|
|
634
|
-
ordinalsEnabled: this.#ordinalsEnabled,
|
|
635
606
|
ordinalAddress: await this.getOrdinalAddress({ walletAccount }),
|
|
636
|
-
knownBalanceUtxoIds: accountState.knownBalanceUtxoIds,
|
|
637
607
|
})
|
|
638
608
|
|
|
639
609
|
return {
|
package/src/tx-send/index.js
CHANGED
|
@@ -14,12 +14,7 @@ import {
|
|
|
14
14
|
createOutput as dogecoinCreateOutput,
|
|
15
15
|
} from './dogecoin'
|
|
16
16
|
import { findUnconfirmedSentRbfTxs } from '../tx-utils'
|
|
17
|
-
import {
|
|
18
|
-
getOrdinalsUtxos,
|
|
19
|
-
getTransferOrdinalsUtxos,
|
|
20
|
-
getUsableUtxos,
|
|
21
|
-
getUtxos,
|
|
22
|
-
} from '../utxos-utils'
|
|
17
|
+
import { getOrdinalsUtxos, getUsableUtxos, getUtxos } from '../utxos-utils'
|
|
23
18
|
|
|
24
19
|
import * as defaultBitcoinjsLib from '@exodus/bitcoinjs-lib'
|
|
25
20
|
|
|
@@ -137,7 +132,6 @@ export const createAndBroadcastTXFactory = ({
|
|
|
137
132
|
getFeeEstimator,
|
|
138
133
|
getSizeAndChangeScript = getSizeAndChangeScriptFactory(), // for decred customizations
|
|
139
134
|
allowUnconfirmedRbfEnabledUtxos,
|
|
140
|
-
ordinalsEnabled = false,
|
|
141
135
|
}) => async (
|
|
142
136
|
{ asset: maybeToken, walletAccount, address, amount: tokenAmount, options },
|
|
143
137
|
{ assetClientInterface }
|
|
@@ -173,11 +167,6 @@ export const createAndBroadcastTXFactory = ({
|
|
|
173
167
|
|
|
174
168
|
const inscriptionIds = nft?.tokenId ? [nft?.tokenId] : brc20 ? brc20.inscriptionIds : undefined
|
|
175
169
|
|
|
176
|
-
assert(
|
|
177
|
-
ordinalsEnabled || !inscriptionIds,
|
|
178
|
-
'inscriptions cannot be sent when ordinalsEnabled=false '
|
|
179
|
-
)
|
|
180
|
-
|
|
181
170
|
const shuffle = (list) => {
|
|
182
171
|
return inscriptionIds ? list : doShuffle(list) // don't shuffle when sending ordinal!!!!
|
|
183
172
|
}
|
|
@@ -203,9 +192,26 @@ export const createAndBroadcastTXFactory = ({
|
|
|
203
192
|
|
|
204
193
|
const currentOrdinalsUtxos = getOrdinalsUtxos({ accountState, asset })
|
|
205
194
|
const transferOrdinalsUtxos = inscriptionIds
|
|
206
|
-
?
|
|
195
|
+
? currentOrdinalsUtxos.filter((utxo) => inscriptionIds.includes(utxo.inscriptionId)) /// this could be bulk transfer if multiple inscription ids are provided
|
|
207
196
|
: undefined
|
|
208
197
|
|
|
198
|
+
if (inscriptionIds) {
|
|
199
|
+
assert(
|
|
200
|
+
transferOrdinalsUtxos?.size === inscriptionIds.length,
|
|
201
|
+
`Expected ordinal utxos ${inscriptionIds.length}. Found: ${transferOrdinalsUtxos?.size || 0}`
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
// const unconfirmedOrdinalUtxos = transferOrdinalsUtxos
|
|
205
|
+
// .toArray()
|
|
206
|
+
// .filter((ordinalUtxo) => !(ordinalUtxo.confirmations > 0))
|
|
207
|
+
// assert(
|
|
208
|
+
// !unconfirmedOrdinalUtxos.length,
|
|
209
|
+
// `OrdinalUtxo with inscription ids ${unconfirmedOrdinalUtxos
|
|
210
|
+
// .map((utxo) => utxo.inscriptionId)
|
|
211
|
+
// .join(', ')} have not confirmed yet`
|
|
212
|
+
// )
|
|
213
|
+
}
|
|
214
|
+
|
|
209
215
|
const insightClient = asset.baseAsset.insightClient
|
|
210
216
|
const currency = asset.currency
|
|
211
217
|
const feeData = await assetClientInterface.getFeeConfig({ assetName })
|
|
@@ -428,8 +434,6 @@ export const createAndBroadcastTXFactory = ({
|
|
|
428
434
|
|
|
429
435
|
const { script, size } = getSizeAndChangeScript({ assetName, tx, rawTx, changeUtxoIndex, txId })
|
|
430
436
|
|
|
431
|
-
// for ordinals, used to allow users spending change utxos even when unconfirmed and ordinals are unknown
|
|
432
|
-
const knownBalanceUtxoIds = accountState.knownBalanceUtxoIds || []
|
|
433
437
|
let remainingUtxos = usableUtxos.difference(selectedUtxos)
|
|
434
438
|
if (changeUtxoIndex !== -1) {
|
|
435
439
|
const address = Address.create(ourAddress.address, ourAddress.meta)
|
|
@@ -442,8 +446,6 @@ export const createAndBroadcastTXFactory = ({
|
|
|
442
446
|
confirmations: 0,
|
|
443
447
|
rbfEnabled,
|
|
444
448
|
}
|
|
445
|
-
|
|
446
|
-
knownBalanceUtxoIds.push(`${changeUtxo.txId}:${changeUtxo.vout}`.toLowerCase())
|
|
447
449
|
remainingUtxos = remainingUtxos.addUtxo(changeUtxo)
|
|
448
450
|
}
|
|
449
451
|
if (replaceTx) {
|
|
@@ -460,7 +462,6 @@ export const createAndBroadcastTXFactory = ({
|
|
|
460
462
|
newData: {
|
|
461
463
|
utxos: remainingUtxos,
|
|
462
464
|
ordinalsUtxos: remainingOrdinalsUtxos,
|
|
463
|
-
knownBalanceUtxoIds,
|
|
464
465
|
},
|
|
465
466
|
})
|
|
466
467
|
|
|
@@ -521,16 +522,7 @@ export const createAndBroadcastTXFactory = ({
|
|
|
521
522
|
inputs: selectedUtxos.toJSON(),
|
|
522
523
|
replacedTxId: replaceTx ? replaceTx.txId : undefined,
|
|
523
524
|
nftId: nft ? `${assetName}:${nft.tokenId}` : undefined, // it allows BE to load the nft info while the nft is in transit
|
|
524
|
-
|
|
525
|
-
sentInscriptions: inscriptionIds
|
|
526
|
-
? inscriptionIds.map((inscriptionId) => {
|
|
527
|
-
return {
|
|
528
|
-
inscriptionId,
|
|
529
|
-
offset: 0,
|
|
530
|
-
value: 0,
|
|
531
|
-
}
|
|
532
|
-
})
|
|
533
|
-
: undefined,
|
|
525
|
+
inscriptionIds: inscriptionIds,
|
|
534
526
|
},
|
|
535
527
|
},
|
|
536
528
|
],
|
package/src/utxos-utils.js
CHANGED
|
@@ -5,46 +5,6 @@ import assert from 'minimalistic-assert'
|
|
|
5
5
|
|
|
6
6
|
const MAX_ORDINAL_VALUE_POSTAGE = 10000
|
|
7
7
|
|
|
8
|
-
export function getTransferOrdinalsUtxos({ inscriptionIds, ordinalsUtxos }) {
|
|
9
|
-
const transferOrdinalsUtxos = ordinalsUtxos.filter((utxo) =>
|
|
10
|
-
utxo.inscriptions?.some((i) => inscriptionIds.includes(i.inscriptionId))
|
|
11
|
-
)
|
|
12
|
-
const unsafeInscriptions = transferOrdinalsUtxos.toArray().flatMap(
|
|
13
|
-
(utxo) =>
|
|
14
|
-
utxo.inscriptions?.filter((i) => {
|
|
15
|
-
const validInscription = isValidInscription({
|
|
16
|
-
value: utxo.value.toBaseNumber(),
|
|
17
|
-
offset: i.offset,
|
|
18
|
-
})
|
|
19
|
-
return validInscription && !inscriptionIds.includes(i.inscriptionId)
|
|
20
|
-
}) || []
|
|
21
|
-
)
|
|
22
|
-
assert(
|
|
23
|
-
!unsafeInscriptions.length,
|
|
24
|
-
`The following inscriptions are unsafe ${unsafeInscriptions.map(
|
|
25
|
-
(i) => i.inscriptionId
|
|
26
|
-
)} when ${inscriptionIds} should be spent`
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
const transferInscriptionIds = transferOrdinalsUtxos
|
|
30
|
-
.toArray()
|
|
31
|
-
.flatMap((utxo) => utxo.inscriptions)
|
|
32
|
-
.filter(({ inscriptionId }) => inscriptionIds.includes(inscriptionId))
|
|
33
|
-
|
|
34
|
-
assert(
|
|
35
|
-
transferInscriptionIds.length === inscriptionIds.length,
|
|
36
|
-
`Expected inscriptions ${inscriptionIds.length}. Found: ${transferInscriptionIds.length}`
|
|
37
|
-
)
|
|
38
|
-
return transferOrdinalsUtxos
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function isValidInscription({ value, offset }) {
|
|
42
|
-
assert(typeof value === 'number', 'value must be a number')
|
|
43
|
-
assert(typeof offset === 'number', 'offset must be a number')
|
|
44
|
-
// value >= 0 in case offset, alternatively convert to string/ln
|
|
45
|
-
return (value >= 0 && value <= MAX_ORDINAL_VALUE_POSTAGE) || offset === 0
|
|
46
|
-
}
|
|
47
|
-
|
|
48
8
|
export function getUtxos({ accountState, asset }) {
|
|
49
9
|
return (
|
|
50
10
|
accountState?.utxos ||
|
|
@@ -63,41 +23,31 @@ export function getOrdinalsUtxos({ accountState, asset }) {
|
|
|
63
23
|
)
|
|
64
24
|
}
|
|
65
25
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
function isOrdinalUtxo({ utxo, ordinalsEnabled, knownBalanceUtxoIds }) {
|
|
73
|
-
if (!ordinalsEnabled) {
|
|
26
|
+
function isOrdinalUtxo({ utxo, ordinalAddress }) {
|
|
27
|
+
if (utxo.inscriptionId) {
|
|
28
|
+
return true
|
|
29
|
+
}
|
|
30
|
+
if (!ordinalAddress) {
|
|
31
|
+
// exclude utxos splitting
|
|
74
32
|
return false
|
|
75
33
|
}
|
|
76
34
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (knownBalanceUtxoIds?.includes(utxoId) && !utxo.inscriptionsIndexed) {
|
|
80
|
-
return false // this allows users see and spend change balance after sending before hiro confirmation
|
|
35
|
+
if (utxo.address.toString() === ordinalAddress.toString()) {
|
|
36
|
+
return true // we assume any utxo to the ordinal address is a ordinal utxos just in case
|
|
81
37
|
}
|
|
82
38
|
|
|
83
|
-
if (
|
|
84
|
-
return
|
|
39
|
+
if (utxo.confirmations) {
|
|
40
|
+
return false
|
|
85
41
|
}
|
|
86
42
|
|
|
87
|
-
|
|
88
|
-
return validInscriptions.length > 0
|
|
43
|
+
return utxo.value.toBaseNumber() <= MAX_ORDINAL_VALUE_POSTAGE // while unconfirmed, put < 10000- sats in the ordinal utxos box just in case
|
|
89
44
|
}
|
|
90
45
|
|
|
91
|
-
export function partitionUtxos({ allUtxos,
|
|
46
|
+
export function partitionUtxos({ allUtxos, ordinalAddress }) {
|
|
92
47
|
assert(allUtxos, 'allUtxos is required')
|
|
93
|
-
// assert(ordinalAddress, 'ordinalAddress is required') // not used atm we may need to tune by ordinalAddress when unconfirmed or rubbish inscriptions
|
|
94
48
|
return {
|
|
95
|
-
utxos: allUtxos.filter(
|
|
96
|
-
|
|
97
|
-
),
|
|
98
|
-
ordinalsUtxos: allUtxos.filter((utxo) =>
|
|
99
|
-
isOrdinalUtxo({ utxo, ordinalsEnabled, knownBalanceUtxoIds })
|
|
100
|
-
),
|
|
49
|
+
utxos: allUtxos.filter((utxo) => !isOrdinalUtxo({ utxo, ordinalAddress })),
|
|
50
|
+
ordinalsUtxos: allUtxos.filter((utxo) => isOrdinalUtxo({ utxo, ordinalAddress })),
|
|
101
51
|
}
|
|
102
52
|
}
|
|
103
53
|
|