@exodus/bitcoin-api 2.3.7 → 2.3.8-ordinals.1
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 +2 -3
- package/src/account-state.js +13 -6
- package/src/address-utils.js +24 -0
- package/src/fee/get-fee-resolver.js +8 -1
- package/src/move-funds.js +12 -1
- package/src/tx-log/bitcoin-monitor-scanner.js +60 -31
- package/src/tx-log/bitcoin-monitor.js +23 -7
- package/src/tx-send/index.js +61 -8
- package/src/utxos-utils.js +34 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/bitcoin-api",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.8-ordinals.1",
|
|
4
4
|
"description": "Exodus bitcoin-api",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -40,6 +40,5 @@
|
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@exodus/bitcoin-meta": "^1.0.1",
|
|
42
42
|
"jest-when": "^3.5.1"
|
|
43
|
-
}
|
|
44
|
-
"gitHead": "7a83d2ff5d672615938fba868779293df74ff51d"
|
|
43
|
+
}
|
|
45
44
|
}
|
package/src/account-state.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { AccountState, UtxoCollection } from '@exodus/models'
|
|
2
2
|
|
|
3
|
-
export function createAccountState({ asset }) {
|
|
3
|
+
export function createAccountState({ asset, ordinalsEnabled = false }) {
|
|
4
|
+
const empty = UtxoCollection.createEmpty({
|
|
5
|
+
currency: asset.currency,
|
|
6
|
+
})
|
|
7
|
+
const defaults = ordinalsEnabled
|
|
8
|
+
? {
|
|
9
|
+
utxos: empty,
|
|
10
|
+
ordinalsUtxos: empty,
|
|
11
|
+
}
|
|
12
|
+
: {
|
|
13
|
+
utxos: empty,
|
|
14
|
+
}
|
|
4
15
|
return class BitcoinAccountState extends AccountState {
|
|
5
|
-
static defaults =
|
|
6
|
-
utxos: UtxoCollection.createEmpty({
|
|
7
|
-
currency: asset.currency,
|
|
8
|
-
}),
|
|
9
|
-
}
|
|
16
|
+
static defaults = defaults
|
|
10
17
|
}
|
|
11
18
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import assert from 'minimalistic-assert'
|
|
2
|
+
|
|
3
|
+
export function isOrdinalAddress(address, ordinalChainIndex) {
|
|
4
|
+
assert(typeof ordinalChainIndex === 'number', `ordinalChainIndex must be a number`)
|
|
5
|
+
return parsePath(address)[0] === ordinalChainIndex
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function isReceiveAddress(address): boolean {
|
|
9
|
+
return parsePath(address)[0] === 0
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function isChangeAddress(address): boolean {
|
|
13
|
+
return parsePath(address)[0] === 1
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function parsePath(address) {
|
|
17
|
+
assert(
|
|
18
|
+
address.meta.path,
|
|
19
|
+
`address parameter ${address} does not have a meta.path. Is it a valid Address object?`
|
|
20
|
+
)
|
|
21
|
+
const path = address.meta.path
|
|
22
|
+
const p1 = path ? path.replace('m/', '').split('/') : ['0', '0']
|
|
23
|
+
return p1.map((i) => parseInt(i, 10))
|
|
24
|
+
}
|
|
@@ -14,7 +14,13 @@ export class GetFeeResolver {
|
|
|
14
14
|
this.#allowUnconfirmedRbfEnabledUtxos = allowUnconfirmedRbfEnabledUtxos
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
getFee = ({ asset, accountState, txSet, feeData, amount, customFee, isSendAll }) => {
|
|
17
|
+
getFee = ({ asset, accountState, txSet, feeData, amount, customFee, isSendAll, nft }) => {
|
|
18
|
+
const inscriptionId = nft?.tokenId
|
|
19
|
+
if (inscriptionId) {
|
|
20
|
+
assert(!amount, 'Only inscriptionId or amount is allowed!!!')
|
|
21
|
+
assert(!isSendAll, 'Only inscriptionId or isSendAll:true is allowed!!!')
|
|
22
|
+
}
|
|
23
|
+
|
|
18
24
|
const { resolvedFee, extraFee } = this.#getUtxosData({
|
|
19
25
|
asset,
|
|
20
26
|
accountState,
|
|
@@ -23,6 +29,7 @@ export class GetFeeResolver {
|
|
|
23
29
|
amount,
|
|
24
30
|
customFee,
|
|
25
31
|
isSendAll,
|
|
32
|
+
inscriptionId,
|
|
26
33
|
})
|
|
27
34
|
return { fee: resolvedFee, extraFee }
|
|
28
35
|
}
|
package/src/move-funds.js
CHANGED
|
@@ -27,6 +27,16 @@ export const getAddressesFromPrivateKeyFactory = ({ purposes, keys, coinInfo })
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
export const renderAddresses = (addresses) => {
|
|
31
|
+
if (!addresses.length) {
|
|
32
|
+
return ''
|
|
33
|
+
}
|
|
34
|
+
if (addresses.length === 1) {
|
|
35
|
+
return `${addresses[0]}`
|
|
36
|
+
}
|
|
37
|
+
return `${addresses.slice(0, -1).join(', ')}, or ${addresses[addresses.length - 1]}`
|
|
38
|
+
}
|
|
39
|
+
|
|
30
40
|
export const moveFundsFactory = ({
|
|
31
41
|
asset,
|
|
32
42
|
insightClient,
|
|
@@ -93,7 +103,8 @@ export const moveFundsFactory = ({
|
|
|
93
103
|
}
|
|
94
104
|
throw new MoveFundsError('balance-zero', {
|
|
95
105
|
...formatProps,
|
|
96
|
-
fromAddress:
|
|
106
|
+
fromAddress: renderAddresses(addresses),
|
|
107
|
+
fromAddresses: addresses.map(String),
|
|
97
108
|
})
|
|
98
109
|
}
|
|
99
110
|
|
|
@@ -1,28 +1,17 @@
|
|
|
1
1
|
/* @flow */
|
|
2
2
|
import { orderTxs } from '../insight-api-client/util'
|
|
3
3
|
import { Address, UtxoCollection } from '@exodus/models'
|
|
4
|
-
import {
|
|
4
|
+
import { compact, isEqual, uniq } from 'lodash'
|
|
5
5
|
import ms from 'ms'
|
|
6
6
|
|
|
7
7
|
import assert from 'minimalistic-assert'
|
|
8
|
-
import {
|
|
8
|
+
import { isChangeAddress, isReceiveAddress } from '../address-utils'
|
|
9
|
+
|
|
10
|
+
import { getOrdinalsUtxos, getUtxos, partitionUtxos } from '../utxos-utils'
|
|
9
11
|
|
|
10
12
|
// Time to check whether to drop a sent tx
|
|
11
13
|
const SENT_TIME_TO_DROP = ms('2m')
|
|
12
14
|
|
|
13
|
-
function isReceiveAddress(addr: Address): boolean {
|
|
14
|
-
return parsePath(addr.meta.path)[0] === 0
|
|
15
|
-
}
|
|
16
|
-
function isChangeAddress(addr: Address): boolean {
|
|
17
|
-
return parsePath(addr.meta.path)[0] === 1
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function parsePath(path) {
|
|
21
|
-
let p1 = path ? path.replace('m/', '').split('/') : ['0', '0']
|
|
22
|
-
p1 = p1.map((i) => parseInt(i, 10))
|
|
23
|
-
return p1
|
|
24
|
-
}
|
|
25
|
-
|
|
26
15
|
export class BitcoinMonitorScanner {
|
|
27
16
|
#asset
|
|
28
17
|
#insightClient
|
|
@@ -30,7 +19,8 @@ export class BitcoinMonitorScanner {
|
|
|
30
19
|
#txFetchLimitResolver
|
|
31
20
|
#shouldExcludeVoutUtxo
|
|
32
21
|
#yieldToUI
|
|
33
|
-
|
|
22
|
+
#ordinalsEnabled
|
|
23
|
+
#ordinalChainIndex
|
|
34
24
|
constructor({
|
|
35
25
|
asset,
|
|
36
26
|
assetClientInterface,
|
|
@@ -38,6 +28,8 @@ export class BitcoinMonitorScanner {
|
|
|
38
28
|
yieldToUI = () => {},
|
|
39
29
|
shouldExcludeVoutUtxo = () => false,
|
|
40
30
|
txFetchLimitResolver = ({ refresh }) => (refresh ? 50 : 10),
|
|
31
|
+
ordinalsEnabled,
|
|
32
|
+
ordinalChainIndex,
|
|
41
33
|
}) {
|
|
42
34
|
assert(asset, 'asset is required!')
|
|
43
35
|
assert(assetClientInterface, 'assetClientInterface is required!')
|
|
@@ -51,6 +43,8 @@ export class BitcoinMonitorScanner {
|
|
|
51
43
|
this.#assetClientInterface = assetClientInterface
|
|
52
44
|
this.#txFetchLimitResolver = txFetchLimitResolver
|
|
53
45
|
this.#shouldExcludeVoutUtxo = shouldExcludeVoutUtxo
|
|
46
|
+
this.#ordinalsEnabled = ordinalsEnabled
|
|
47
|
+
this.#ordinalChainIndex = ordinalChainIndex
|
|
54
48
|
}
|
|
55
49
|
|
|
56
50
|
async rescanBlockchainInsight({ walletAccount, refresh }) {
|
|
@@ -64,7 +58,11 @@ export class BitcoinMonitorScanner {
|
|
|
64
58
|
const accountState = await assetClientInterface.getAccountState({ assetName, walletAccount })
|
|
65
59
|
const currency = asset.currency
|
|
66
60
|
const currentTxs = await assetClientInterface.getTxLog({ assetName, walletAccount })
|
|
67
|
-
|
|
61
|
+
|
|
62
|
+
const regularUtxos = getUtxos({ asset, accountState })
|
|
63
|
+
const ordinalsUtxos = getOrdinalsUtxos({ asset, accountState })
|
|
64
|
+
|
|
65
|
+
const currentUtxos = regularUtxos.union(ordinalsUtxos)
|
|
68
66
|
|
|
69
67
|
const currentTime = new Date().getTime()
|
|
70
68
|
const unconfirmedTxsToCheck = Array.from(currentTxs).reduce((txs, tx) => {
|
|
@@ -221,6 +219,21 @@ export class BitcoinMonitorScanner {
|
|
|
221
219
|
})
|
|
222
220
|
.flat()
|
|
223
221
|
|
|
222
|
+
if (
|
|
223
|
+
fetchCount === 0 &&
|
|
224
|
+
this.#ordinalsEnabled &&
|
|
225
|
+
this.#ordinalChainIndex > 1 &&
|
|
226
|
+
purposes.includes(86)
|
|
227
|
+
) {
|
|
228
|
+
// this is the ordinal address
|
|
229
|
+
chainObjects.push({
|
|
230
|
+
purpose: 86,
|
|
231
|
+
chainIndex: this.#ordinalChainIndex,
|
|
232
|
+
startAddressIndex: 0,
|
|
233
|
+
endAddressIndex: 1,
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
|
|
224
237
|
const addresses = await aggregateAddresses(chainObjects)
|
|
225
238
|
|
|
226
239
|
const txs = await fetchAllTxs(addresses)
|
|
@@ -243,6 +256,11 @@ export class BitcoinMonitorScanner {
|
|
|
243
256
|
const metaAddressIndex = parseInt(pd[2])
|
|
244
257
|
const addressString = String(address)
|
|
245
258
|
const purposeToUpdate = purposeMap[addressString]
|
|
259
|
+
|
|
260
|
+
if (metaChainIndex === this.#ordinalChainIndex && this.#ordinalChainIndex > 1) {
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
|
|
246
264
|
if (!purposeToUpdate) {
|
|
247
265
|
console.warn(`${assetName}: Cannot resolve purpose from address ${addressString}`)
|
|
248
266
|
return
|
|
@@ -310,6 +328,7 @@ export class BitcoinMonitorScanner {
|
|
|
310
328
|
rbfEnabled: txItem.rbf,
|
|
311
329
|
blocksSeen: 0,
|
|
312
330
|
},
|
|
331
|
+
currencies: { [assetName]: currency },
|
|
313
332
|
}
|
|
314
333
|
|
|
315
334
|
let from = []
|
|
@@ -345,13 +364,15 @@ export class BitcoinMonitorScanner {
|
|
|
345
364
|
['bitcoin', 'bitcoinregtest', 'bitcointestnet'].includes(asset.name)
|
|
346
365
|
) {
|
|
347
366
|
txLogItem.data.inputs = UtxoCollection.fromArray(
|
|
348
|
-
txItem.vin
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
367
|
+
txItem.vin
|
|
368
|
+
.filter((vin) => addrMap[vin.addr])
|
|
369
|
+
.map((vin) => ({
|
|
370
|
+
address: addrMap[vin.addr],
|
|
371
|
+
script: asset.address.toScriptPubKey(vin.addr).toString('hex'),
|
|
372
|
+
txId: vin.txid,
|
|
373
|
+
vout: vin.vout,
|
|
374
|
+
value: currency.defaultUnit(vin.value || 0),
|
|
375
|
+
}))
|
|
355
376
|
).toJSON()
|
|
356
377
|
}
|
|
357
378
|
|
|
@@ -501,7 +522,9 @@ export class BitcoinMonitorScanner {
|
|
|
501
522
|
return {
|
|
502
523
|
txsToUpdate: existingTxs,
|
|
503
524
|
txsToAdd: newTxs,
|
|
504
|
-
|
|
525
|
+
...(utxoCol
|
|
526
|
+
? partitionUtxos({ allUtxos: utxoCol, ordinalChainIndex: this.#ordinalChainIndex })
|
|
527
|
+
: {}),
|
|
505
528
|
changedUnusedAddressIndexes,
|
|
506
529
|
}
|
|
507
530
|
}
|
|
@@ -513,12 +536,14 @@ export class BitcoinMonitorScanner {
|
|
|
513
536
|
|
|
514
537
|
const accountState = await aci.getAccountState({ assetName, walletAccount })
|
|
515
538
|
|
|
516
|
-
const
|
|
539
|
+
const storedUtxos = getUtxos({ accountState, asset })
|
|
540
|
+
const storedOrdinalsUtxos = getOrdinalsUtxos({ accountState, asset })
|
|
541
|
+
const allStoredUtxos = storedUtxos.union(storedOrdinalsUtxos)
|
|
517
542
|
|
|
518
543
|
const currentTxs = Array.from(await aci.getTxLog({ assetName, walletAccount }))
|
|
519
544
|
|
|
520
545
|
const unconfirmedTxIds = uniq([
|
|
521
|
-
...
|
|
546
|
+
...storedUtxos
|
|
522
547
|
.toArray()
|
|
523
548
|
.filter((utxos) => !utxos.confirmations)
|
|
524
549
|
.map((utxos) => utxos.txId),
|
|
@@ -548,12 +573,16 @@ export class BitcoinMonitorScanner {
|
|
|
548
573
|
})
|
|
549
574
|
.filter((tx) => Object.keys(tx).length > 1)
|
|
550
575
|
|
|
551
|
-
const txConfirmedUtxos = confirmationsList
|
|
552
|
-
|
|
553
|
-
|
|
576
|
+
const txConfirmedUtxos = allStoredUtxos.updateConfirmations(confirmationsList)
|
|
577
|
+
|
|
578
|
+
const { utxos, ordinalsUtxos } = partitionUtxos({
|
|
579
|
+
allUtxos: txConfirmedUtxos,
|
|
580
|
+
ordinalChainIndex: this.#ordinalChainIndex,
|
|
581
|
+
})
|
|
554
582
|
|
|
555
583
|
return {
|
|
556
|
-
utxos:
|
|
584
|
+
utxos: utxos.equals(storedUtxos) ? null : utxos,
|
|
585
|
+
ordinalsUtxos: ordinalsUtxos.equals(storedOrdinalsUtxos) ? null : ordinalsUtxos,
|
|
557
586
|
txsToUpdate: updatedPropertiesTxs,
|
|
558
587
|
}
|
|
559
588
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from 'minimalistic-assert'
|
|
2
|
-
import { isEmpty, isEqual } from 'lodash'
|
|
2
|
+
import { isEmpty, isEqual, pickBy } from 'lodash'
|
|
3
3
|
|
|
4
4
|
import { BaseMonitor } from '@exodus/asset-lib'
|
|
5
5
|
import InsightWSClient from '../insight-api-client/ws'
|
|
@@ -142,17 +142,21 @@ export class Monitor extends BaseMonitor {
|
|
|
142
142
|
const assetName = asset.name
|
|
143
143
|
const walletAccounts = await aci.getWalletAccounts({ assetName })
|
|
144
144
|
for (const walletAccount of walletAccounts) {
|
|
145
|
-
const { txsToUpdate, utxos } = await this.#scanner.rescanOnNewBlock({
|
|
145
|
+
const { txsToUpdate, utxos, ordinalsUtxos } = await this.#scanner.rescanOnNewBlock({
|
|
146
146
|
walletAccount,
|
|
147
147
|
})
|
|
148
148
|
|
|
149
|
-
if (utxos) {
|
|
149
|
+
if (utxos || ordinalsUtxos) {
|
|
150
150
|
await aci.updateAccountState({
|
|
151
151
|
assetName,
|
|
152
152
|
walletAccount,
|
|
153
|
-
newData:
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
newData: pickBy(
|
|
154
|
+
{
|
|
155
|
+
utxos,
|
|
156
|
+
ordinalsUtxos,
|
|
157
|
+
},
|
|
158
|
+
Boolean
|
|
159
|
+
),
|
|
156
160
|
})
|
|
157
161
|
}
|
|
158
162
|
|
|
@@ -211,6 +215,7 @@ export class Monitor extends BaseMonitor {
|
|
|
211
215
|
txsToAdd,
|
|
212
216
|
txsToUpdate,
|
|
213
217
|
utxos,
|
|
218
|
+
ordinalsUtxos,
|
|
214
219
|
changedUnusedAddressIndexes,
|
|
215
220
|
} = await this.#scanner.rescanBlockchainInsight({
|
|
216
221
|
walletAccount,
|
|
@@ -218,7 +223,18 @@ export class Monitor extends BaseMonitor {
|
|
|
218
223
|
})
|
|
219
224
|
const accountState = await aci.getAccountState({ assetName, walletAccount })
|
|
220
225
|
|
|
221
|
-
if (utxos
|
|
226
|
+
if (utxos || ordinalsUtxos)
|
|
227
|
+
await aci.updateAccountState({
|
|
228
|
+
assetName,
|
|
229
|
+
walletAccount,
|
|
230
|
+
newData: pickBy(
|
|
231
|
+
{
|
|
232
|
+
utxos,
|
|
233
|
+
ordinalsUtxos,
|
|
234
|
+
},
|
|
235
|
+
Boolean
|
|
236
|
+
),
|
|
237
|
+
})
|
|
222
238
|
|
|
223
239
|
if (!isEmpty(changedUnusedAddressIndexes)) {
|
|
224
240
|
// Only for mobile atm, browser and hydra calculates from the latest txLogs
|
package/src/tx-send/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from 'minimalistic-assert'
|
|
2
2
|
// Using this notation so it can be mocked by jest
|
|
3
|
-
import
|
|
3
|
+
import doShuffle from 'lodash/shuffle'
|
|
4
4
|
|
|
5
5
|
import { UtxoCollection, Address } from '@exodus/models'
|
|
6
6
|
import { retry } from '@exodus/simple-retry'
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
createOutput as dogecoinCreateOutput,
|
|
15
15
|
} from './dogecoin'
|
|
16
16
|
import { findUnconfirmedSentRbfTxs } from '../tx-utils'
|
|
17
|
-
import { getUsableUtxos, getUtxos } from '../utxos-utils'
|
|
17
|
+
import { getOrdinalsUtxos, getUsableUtxos, getUtxos } from '../utxos-utils'
|
|
18
18
|
|
|
19
19
|
import * as defaultBitcoinjsLib from '@exodus/bitcoinjs-lib'
|
|
20
20
|
|
|
@@ -130,13 +130,25 @@ export const createAndBroadcastTXFactory = ({
|
|
|
130
130
|
isBip70,
|
|
131
131
|
bumpTxId,
|
|
132
132
|
isRbfAllowed = true,
|
|
133
|
+
nft,
|
|
133
134
|
} = options
|
|
134
135
|
|
|
136
|
+
const inscriptionId = nft?.tokenId
|
|
137
|
+
|
|
138
|
+
const shuffle = (list) => {
|
|
139
|
+
return inscriptionId ? list : doShuffle(list) // don't shuffle when sending ordinal!!!!
|
|
140
|
+
}
|
|
141
|
+
|
|
135
142
|
assert(assetClientInterface, `assetClientInterface must be supplied in sendTx for ${asset.name}`)
|
|
136
143
|
assert(
|
|
137
144
|
address || bumpTxId,
|
|
138
145
|
'should not be called without either a receiving address or to bump a tx'
|
|
139
146
|
)
|
|
147
|
+
if (inscriptionId) {
|
|
148
|
+
assert(!bumpTxId, 'only inscriptionId or bumpTxId must be provided')
|
|
149
|
+
assert(!amount, 'only inscriptionId or amount must be provided')
|
|
150
|
+
assert(address, 'address must be provided when sending an ordinal')
|
|
151
|
+
}
|
|
140
152
|
|
|
141
153
|
const assetName = asset.name
|
|
142
154
|
|
|
@@ -152,6 +164,25 @@ export const createAndBroadcastTXFactory = ({
|
|
|
152
164
|
|
|
153
165
|
const txSet = await assetClientInterface.getTxLog({ assetName, walletAccount })
|
|
154
166
|
const accountState = await assetClientInterface.getAccountState({ assetName, walletAccount })
|
|
167
|
+
|
|
168
|
+
const currentOrdinalsUtxos = getOrdinalsUtxos({ accountState, asset })
|
|
169
|
+
const transferOrdinalsUtxos = inscriptionId
|
|
170
|
+
? currentOrdinalsUtxos.filter((utxo) => utxo.inscriptionId === inscriptionId) /// this could be bulk transfer if multiple inscription ids are provided
|
|
171
|
+
: undefined
|
|
172
|
+
|
|
173
|
+
if (inscriptionId) {
|
|
174
|
+
assert(
|
|
175
|
+
transferOrdinalsUtxos?.size === 1,
|
|
176
|
+
`There should only be one utxo with inscription id ${inscriptionId}. Found: ${transferOrdinalsUtxos?.size ||
|
|
177
|
+
0}`
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
assert(
|
|
181
|
+
transferOrdinalsUtxos.toArray().every((ordinalUtxo) => ordinalUtxo.confirmations > 0),
|
|
182
|
+
`OrdinalUtxo for inscription id ${inscriptionId} has not confirmed yet`
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
155
186
|
const insightClient = asset.baseAsset.insightClient
|
|
156
187
|
const currency = asset.currency
|
|
157
188
|
const feeData = await assetClientInterface.getFeeConfig({ assetName })
|
|
@@ -190,9 +221,10 @@ export const createAndBroadcastTXFactory = ({
|
|
|
190
221
|
}
|
|
191
222
|
}
|
|
192
223
|
|
|
193
|
-
const sendAmount = bumpTxId ? asset.currency.ZERO : amount.toDefault()
|
|
224
|
+
const sendAmount = bumpTxId || transferOrdinalsUtxos ? asset.currency.ZERO : amount.toDefault()
|
|
194
225
|
let receiveAddress = bumpTxId ? (replaceableTxs.length > 0 ? null : 'P2WPKH') : address
|
|
195
226
|
const feeRate = feeData.feePerKB
|
|
227
|
+
const resolvedIsSendAll = (!rbfEnabled && feePerKB) || transferOrdinalsUtxos ? false : isSendAll
|
|
196
228
|
|
|
197
229
|
let { selectedUtxos, fee, replaceTx } = selectUtxos({
|
|
198
230
|
asset,
|
|
@@ -201,7 +233,7 @@ export const createAndBroadcastTXFactory = ({
|
|
|
201
233
|
amount: sendAmount,
|
|
202
234
|
feeRate: customFee || feeRate,
|
|
203
235
|
receiveAddress,
|
|
204
|
-
isSendAll:
|
|
236
|
+
isSendAll: resolvedIsSendAll,
|
|
205
237
|
getFeeEstimator: (asset, { feePerKB }) => getFeeEstimator(asset, feePerKB),
|
|
206
238
|
mustSpendUtxos: utxosToBump,
|
|
207
239
|
allowUnconfirmedRbfEnabledUtxos,
|
|
@@ -217,7 +249,9 @@ export const createAndBroadcastTXFactory = ({
|
|
|
217
249
|
if (bumpTxId && (!selectedUtxos || (replaceTx && replaceTx.txId !== bumpTxId))) {
|
|
218
250
|
throw new Error(`Unable to bump ${bumpTxId}`)
|
|
219
251
|
}
|
|
220
|
-
|
|
252
|
+
if (transferOrdinalsUtxos) {
|
|
253
|
+
selectedUtxos = transferOrdinalsUtxos.union(selectedUtxos)
|
|
254
|
+
}
|
|
221
255
|
if (replaceTx) {
|
|
222
256
|
replaceTx = replaceTx.clone()
|
|
223
257
|
replaceTx = replaceTx.update({ data: { ...replaceTx.data } })
|
|
@@ -241,14 +275,25 @@ export const createAndBroadcastTXFactory = ({
|
|
|
241
275
|
outputs = []
|
|
242
276
|
}
|
|
243
277
|
if (address) {
|
|
244
|
-
|
|
278
|
+
if (transferOrdinalsUtxos) {
|
|
279
|
+
outputs.push(
|
|
280
|
+
...transferOrdinalsUtxos
|
|
281
|
+
.toArray()
|
|
282
|
+
.map((ordinalUtxo) => createOutput(assetName, address, ordinalUtxo.value))
|
|
283
|
+
)
|
|
284
|
+
} else {
|
|
285
|
+
outputs.push(createOutput(assetName, address, sendAmount))
|
|
286
|
+
}
|
|
245
287
|
}
|
|
246
288
|
|
|
247
289
|
const totalAmount = replaceTx
|
|
248
290
|
? replaceTx.data.sent.reduce((total, { amount }) => total.add(amount), sendAmount)
|
|
249
291
|
: sendAmount
|
|
250
292
|
|
|
251
|
-
const change = selectedUtxos.value
|
|
293
|
+
const change = selectedUtxos.value
|
|
294
|
+
.sub(totalAmount)
|
|
295
|
+
.sub(transferOrdinalsUtxos?.value || currency.ZERO)
|
|
296
|
+
.sub(fee)
|
|
252
297
|
const dust = getDustValue(asset)
|
|
253
298
|
let ourAddress = replaceTx?.data?.changeAddress || changeAddress
|
|
254
299
|
if (asset.address.toLegacyAddress) {
|
|
@@ -362,11 +407,17 @@ export const createAndBroadcastTXFactory = ({
|
|
|
362
407
|
remainingUtxos = remainingUtxos.difference(remainingUtxos.getTxIdUtxos(replaceTx.txId))
|
|
363
408
|
}
|
|
364
409
|
|
|
410
|
+
const remainingOrdinalsUtxos = transferOrdinalsUtxos
|
|
411
|
+
? currentOrdinalsUtxos.difference(transferOrdinalsUtxos)
|
|
412
|
+
: undefined
|
|
365
413
|
await assetClientInterface.updateAccountState({
|
|
366
414
|
assetName,
|
|
367
415
|
walletAccount,
|
|
368
416
|
accountState,
|
|
369
|
-
newData: {
|
|
417
|
+
newData: {
|
|
418
|
+
utxos: remainingUtxos,
|
|
419
|
+
ordinalsUtxos: remainingOrdinalsUtxos,
|
|
420
|
+
},
|
|
370
421
|
})
|
|
371
422
|
|
|
372
423
|
// There are two cases of bumping, replacing or chaining a self-send.
|
|
@@ -406,6 +457,7 @@ export const createAndBroadcastTXFactory = ({
|
|
|
406
457
|
blocksSeen: 0,
|
|
407
458
|
inputs: selectedUtxos.toJSON(),
|
|
408
459
|
replacedTxId: replaceTx ? replaceTx.txId : undefined,
|
|
460
|
+
nftId: inscriptionId ? `${assetName}:${inscriptionId}` : undefined, // it allows BE to load the nft info while the nft is in transit
|
|
409
461
|
},
|
|
410
462
|
},
|
|
411
463
|
],
|
|
@@ -442,6 +494,7 @@ function defaultCreateInputs(utxos, rbfEnabled) {
|
|
|
442
494
|
value: parseInt(utxo.value.toBaseString(), 10),
|
|
443
495
|
script: utxo.script,
|
|
444
496
|
sequence: getTxSequence(rbfEnabled),
|
|
497
|
+
inscriptionId: utxo.inscriptionId,
|
|
445
498
|
}))
|
|
446
499
|
}
|
|
447
500
|
|
package/src/utxos-utils.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { UtxoCollection } from '@exodus/models'
|
|
3
3
|
import { findLargeUnconfirmedTxs } from './tx-utils'
|
|
4
4
|
import assert from 'minimalistic-assert'
|
|
5
|
+
import { isOrdinalAddress } from './address-utils'
|
|
6
|
+
// import { isOrdinalAddress } from './address-utils'
|
|
5
7
|
|
|
6
8
|
export function getUtxos({ accountState, asset }) {
|
|
7
9
|
return (
|
|
@@ -12,6 +14,38 @@ export function getUtxos({ accountState, asset }) {
|
|
|
12
14
|
)
|
|
13
15
|
}
|
|
14
16
|
|
|
17
|
+
export function getOrdinalsUtxos({ accountState, asset }) {
|
|
18
|
+
return (
|
|
19
|
+
accountState?.ordinalsUtxos ||
|
|
20
|
+
UtxoCollection.createEmpty({
|
|
21
|
+
currency: asset.currency,
|
|
22
|
+
})
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const ordinalValueUnconfirmedThreshold = 10000
|
|
27
|
+
|
|
28
|
+
function isOrdinalUtxo({ utxo, ordinalChainIndex }) {
|
|
29
|
+
if (utxo.inscriptionId) {
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
const maybeAnOrdinalAddress =
|
|
33
|
+
ordinalChainIndex && isOrdinalAddress(utxo.address, ordinalChainIndex) // if wallet receives utxos to the special address, consider it as ordinal
|
|
34
|
+
if (utxo.confirmations) {
|
|
35
|
+
return maybeAnOrdinalAddress
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return utxo.value.toBaseNumber() <= ordinalValueUnconfirmedThreshold || maybeAnOrdinalAddress // while unconfirmed, put < 10000- sats in the ordinal utxos box just in case
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function partitionUtxos({ allUtxos, ordinalChainIndex }) {
|
|
42
|
+
assert(allUtxos, 'allUtxos is required')
|
|
43
|
+
return {
|
|
44
|
+
utxos: allUtxos.filter((utxo) => !isOrdinalUtxo({ utxo, ordinalChainIndex })),
|
|
45
|
+
ordinalsUtxos: allUtxos.filter((utxo) => isOrdinalUtxo({ utxo, ordinalChainIndex })),
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
15
49
|
export function getConfirmedUtxos({ utxos }) {
|
|
16
50
|
assert(utxos, 'utxos is required')
|
|
17
51
|
return utxos.filter(({ confirmations }) => confirmations > 0)
|