@exodus/bitcoin-api 2.5.0-alpha.0 → 2.5.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/bitcoin-api",
3
- "version": "2.5.0-alpha.0",
3
+ "version": "2.5.1",
4
4
  "description": "Exodus bitcoin-api",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -39,7 +39,9 @@
39
39
  },
40
40
  "devDependencies": {
41
41
  "@exodus/bitcoin-meta": "^1.0.1",
42
+ "@scure/base": "^1.1.3",
43
+ "@scure/btc-signer": "^1.1.0",
42
44
  "jest-when": "^3.5.1"
43
45
  },
44
- "gitHead": "04641210f5ac2a4b490cf348b2917b28cf463ba2"
46
+ "gitHead": "562c288cde1c1d7c7ab2c7bc6b1804ccca9b37f2"
45
47
  }
@@ -1,10 +1,5 @@
1
1
  import assert from 'minimalistic-assert'
2
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
3
  export function isReceiveAddress(address): boolean {
9
4
  return parsePath(address)[0] === 0
10
5
  }
@@ -1,5 +1,5 @@
1
1
  import secp256k1 from '@exodus/secp256k1'
2
- import { toXOnly } from '../../tx-sign/taproot'
2
+ import { toXOnly } from '../ecc-utils'
3
3
 
4
4
  /**
5
5
  * Common ecc functions between mobile and desktop. Once mobile accepts @noble/secp256k1, we can unify both
@@ -0,0 +1,3 @@
1
+ export const toXOnly = (publicKey) => {
2
+ return publicKey.slice(1, 33)
3
+ }
@@ -1,5 +1,4 @@
1
1
  import { payments } from 'bitcoinjs-lib'
2
- import assert from 'minimalistic-assert'
3
2
 
4
3
  function isPaymentFactory(payment: any): (script: Buffer, eccLib?: any) => boolean {
5
4
  return (script: Buffer, eccLib?: any): boolean => {
@@ -31,10 +30,9 @@ const types = {
31
30
  NONSTANDARD: 'nonstandard',
32
31
  }
33
32
 
34
- const outputFactory = ({ ecc }) => (script: Buffer) => {
35
- assert(ecc, 'ecc is required')
33
+ const outputFactory = () => (script: Buffer) => {
36
34
  if (isP2WPKH(script)) return types.P2WPKH
37
- if (isP2TR(script, ecc)) return types.P2TR
35
+ if (isP2TR(script)) return types.P2TR
38
36
  if (isP2PKH(script)) return types.P2PKH
39
37
  if (isP2MS(script)) return types.P2MS
40
38
  if (isP2PK(script)) return types.P2PK
@@ -8,14 +8,12 @@ export const createBtcLikeAddress = ({
8
8
  versions,
9
9
  coinInfo,
10
10
  bitcoinjsLib: bitcoinjsLibFork,
11
- ecc,
12
11
  useBip86 = false,
13
12
  validateFunctions = {},
14
13
  extraFunctions = {},
15
14
  }) => {
16
15
  assert(versions, 'versions is required')
17
16
  assert(coinInfo, 'coinInfo is required')
18
- assert(ecc, 'ecc is required')
19
17
 
20
18
  const bs58validateFactory = (version) =>
21
19
  version === undefined
@@ -55,7 +53,7 @@ export const createBtcLikeAddress = ({
55
53
  ((addr) => {
56
54
  try {
57
55
  const network = coinInfo.toBitcoinJS()
58
- bitcoinjsLibFork.payments.p2tr({ address: addr, network }, { eccLib: ecc })
56
+ bitcoinjsLibFork.payments.p2tr({ address: addr, network })
59
57
  return true
60
58
  } catch (e) {
61
59
  return false
@@ -91,7 +89,7 @@ export const createBtcLikeAddress = ({
91
89
 
92
90
  const toScriptPubKey = (string) => {
93
91
  const network = coinInfo.toBitcoinJS()
94
- return bitcoinjsOriginal.address.toOutputScript(string, network, ecc)
92
+ return bitcoinjsOriginal.address.toOutputScript(string, network)
95
93
  }
96
94
 
97
95
  const fromScriptPubKey = (scriptPubKey) => {
@@ -6,7 +6,8 @@ import { identity, pickBy } from 'lodash'
6
6
  import * as defaultBitcoinjsLib from 'bitcoinjs-lib'
7
7
  import secp256k1 from 'secp256k1'
8
8
  import { hash160 } from './hash-utils'
9
- import { toXOnly } from './tx-sign/taproot'
9
+ import { toXOnly } from './bitcoinjs-lib/ecc-utils'
10
+ import { eccFactory } from './bitcoinjs-lib/ecc'
10
11
 
11
12
  export const publicKeyToHashFactory = (p2pkh) => (publicKey) => {
12
13
  const payload = Buffer.concat([Buffer.from([p2pkh]), hash160(publicKey)])
@@ -16,14 +17,13 @@ export const publicKeyToHashFactory = (p2pkh) => (publicKey) => {
16
17
  export const createBtcLikeKeys = ({
17
18
  coinInfo,
18
19
  versions,
19
- ecc,
20
20
  useBip86 = false,
21
21
  bitcoinjsLib = defaultBitcoinjsLib,
22
22
  extraFunctions = {},
23
23
  }) => {
24
24
  assert(coinInfo, 'coinInfo is required')
25
25
  assert(versions, 'versions is required')
26
- assert(ecc, 'ecc is required')
26
+ const ecc = eccFactory()
27
27
  const {
28
28
  encodePrivate: encodePrivateCustom,
29
29
  encodePublic: encodePublicCustom,
@@ -91,10 +91,7 @@ export const createBtcLikeKeys = ({
91
91
  (useBip86
92
92
  ? (publicKey: Buffer): string => {
93
93
  const network = coinInfo.toBitcoinJS()
94
- return bitcoinjsLib.payments.p2tr(
95
- { internalPubkey: toXOnly(publicKey), network },
96
- { eccLib: ecc }
97
- ).address
94
+ return bitcoinjsLib.payments.p2tr({ internalPubkey: toXOnly(publicKey), network }).address
98
95
  }
99
96
  : undefined)
100
97
 
@@ -53,12 +53,11 @@ const scriptPubKeyLengths = {
53
53
  // 10 = version: 4, locktime: 4, inputs and outputs count: 1
54
54
  // 148 = txId: 32, vout: 4, count: 1, script: 107 (max), sequence: 4
55
55
  // 34 = value: 8, count: 1, scriptPubKey: 25 (P2PKH) and 23 (P2SH)
56
- export const getSizeFactory = ({ ecc, defaultOutputType, addressApi }) => {
57
- assert(ecc, 'ecc is required')
56
+ export const getSizeFactory = ({ defaultOutputType, addressApi }) => {
58
57
  assert(defaultOutputType, 'defaultOutputType is required')
59
58
  assert(addressApi, 'addressApi is required')
60
59
 
61
- const scriptClassifier = scriptClassifierFactory({ ecc, addressApi })
60
+ const scriptClassifier = scriptClassifierFactory({ addressApi })
62
61
 
63
62
  return (
64
63
  asset: Object,
@@ -166,8 +165,8 @@ export const getSizeFactory = ({ ecc, defaultOutputType, addressApi }) => {
166
165
  }
167
166
  }
168
167
 
169
- const getFeeEstimatorFactory = ({ ecc, defaultOutputType, addressApi }) => {
170
- const getSize = getSizeFactory({ ecc, defaultOutputType, addressApi })
168
+ const getFeeEstimatorFactory = ({ defaultOutputType, addressApi }) => {
169
+ const getSize = getSizeFactory({ defaultOutputType, addressApi })
171
170
  return createDefaultFeeEstimator(getSize)
172
171
  }
173
172
  export default getFeeEstimatorFactory
@@ -16,11 +16,10 @@ const hashStringIfTooBig = (str) =>
16
16
  .slice(0, maxSize)
17
17
  : str
18
18
 
19
- export const scriptClassifierFactory = ({ addressApi, ecc }) => {
20
- assert(ecc, 'ecc is required')
19
+ export const scriptClassifierFactory = ({ addressApi }) => {
21
20
  assert(addressApi, 'addressApi is required')
22
21
 
23
- const classifyOutput = scriptClassify.outputFactory({ ecc })
22
+ const classifyOutput = scriptClassify.outputFactory()
24
23
 
25
24
  const classifyScriptHex = memoizeLruCache(
26
25
  ({ assetName, script }) => {
@@ -42,6 +41,7 @@ export const scriptClassifierFactory = ({ addressApi, ecc }) => {
42
41
  else if (addressApi.isP2TR && addressApi.isP2TR(address)) return P2TR
43
42
  else if (addressApi.isP2WSH && addressApi.isP2WSH(address)) return P2WSH
44
43
  return classifyScriptHex({
44
+ assetName,
45
45
  classifyOutput,
46
46
  script: addressApi.toScriptPubKey(address).toString('hex'),
47
47
  })
package/src/index.js CHANGED
@@ -16,3 +16,6 @@ export * from './unconfirmed-ancestor-data'
16
16
  export * from './parse-unsigned-tx'
17
17
  export * from './insight-api-client/util'
18
18
  export * from './move-funds'
19
+ export { toAsyncSigner } from './tx-sign/taproot'
20
+ export { toXOnly } from './bitcoinjs-lib/ecc-utils'
21
+ export * from './ordinals-utils'
@@ -0,0 +1,22 @@
1
+ import assert from 'minimalistic-assert'
2
+
3
+ export function getOrdinalAddress({
4
+ asset,
5
+ assetClientInterface,
6
+ walletAccount,
7
+ ordinalChainIndex,
8
+ }) {
9
+ assert(asset, 'asset is required')
10
+ assert(assetClientInterface, 'assetClientInterface is required')
11
+ assert(walletAccount, 'walletAccount is required')
12
+ if (ordinalChainIndex === undefined) {
13
+ return undefined
14
+ }
15
+ return assetClientInterface.getAddress({
16
+ assetName: asset.name,
17
+ walletAccount,
18
+ purpose: 86,
19
+ chainIndex: ordinalChainIndex,
20
+ addressIndex: 0,
21
+ })
22
+ }
@@ -6,6 +6,7 @@ import ms from 'ms'
6
6
  import assert from 'minimalistic-assert'
7
7
  import { isChangeAddress, isReceiveAddress } from '../address-utils'
8
8
  import { getOrdinalsUtxos, getUtxos, partitionUtxos } from '../utxos-utils'
9
+ import { getOrdinalAddress } from '../ordinals-utils'
9
10
 
10
11
  // Time to check whether to drop a sent tx
11
12
  const SENT_TIME_TO_DROP = ms('2m')
@@ -519,7 +520,10 @@ export class BitcoinMonitorScanner {
519
520
  })
520
521
 
521
522
  const utxosData = utxoCol
522
- ? partitionUtxos({ allUtxos: utxoCol, ordinalChainIndex: this.#ordinalChainIndex })
523
+ ? partitionUtxos({
524
+ allUtxos: utxoCol,
525
+ ordinalAddress: await this.getOrdinalAddress({ walletAccount }),
526
+ })
523
527
  : {}
524
528
 
525
529
  return {
@@ -578,7 +582,7 @@ export class BitcoinMonitorScanner {
578
582
 
579
583
  const { utxos, ordinalsUtxos } = partitionUtxos({
580
584
  allUtxos: txConfirmedUtxos,
581
- ordinalChainIndex: this.#ordinalChainIndex,
585
+ ordinalAddress: await this.getOrdinalAddress({ walletAccount }),
582
586
  })
583
587
 
584
588
  return {
@@ -587,4 +591,13 @@ export class BitcoinMonitorScanner {
587
591
  txsToUpdate: updatedPropertiesTxs,
588
592
  }
589
593
  }
594
+
595
+ getOrdinalAddress({ walletAccount }) {
596
+ return getOrdinalAddress({
597
+ asset: this.#asset,
598
+ assetClientInterface: this.#assetClientInterface,
599
+ walletAccount,
600
+ ordinalChainIndex: this.#ordinalChainIndex,
601
+ })
602
+ }
590
603
  }
@@ -201,15 +201,15 @@ export const createAndBroadcastTXFactory = ({
201
201
  `Expected ordinal utxos ${inscriptionIds.length}. Found: ${transferOrdinalsUtxos?.size || 0}`
202
202
  )
203
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
- )
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
213
  }
214
214
 
215
215
  const insightClient = asset.baseAsset.insightClient
@@ -305,6 +305,8 @@ export const createAndBroadcastTXFactory = ({
305
305
  } else {
306
306
  outputs = []
307
307
  }
308
+
309
+ let sendOutput
308
310
  if (address) {
309
311
  if (transferOrdinalsUtxos) {
310
312
  outputs.push(
@@ -313,7 +315,8 @@ export const createAndBroadcastTXFactory = ({
313
315
  .map((ordinalUtxo) => createOutput(assetName, address, ordinalUtxo.value))
314
316
  )
315
317
  } else {
316
- outputs.push(createOutput(assetName, address, sendAmount))
318
+ sendOutput = createOutput(assetName, address, sendAmount)
319
+ outputs.push(sendOutput)
317
320
  }
318
321
  }
319
322
 
@@ -408,17 +411,23 @@ export const createAndBroadcastTXFactory = ({
408
411
  }
409
412
  }
410
413
 
411
- let changeUtxoIndex = -1
412
- if (changeOutput) {
413
- for (let i = 0; i < outputs.length; i++) {
414
- const [address, amount] = outputs[i]
415
- if (changeOutput[0] === address && changeOutput[1] === amount) {
416
- changeUtxoIndex = i
417
- break
414
+ function findUtxoIndex(output) {
415
+ let utxoIndex = -1
416
+ if (output) {
417
+ for (let i = 0; i < outputs.length; i++) {
418
+ const [address, amount] = outputs[i]
419
+ if (output[0] === address && output[1] === amount) {
420
+ utxoIndex = i
421
+ break
422
+ }
418
423
  }
419
424
  }
425
+ return utxoIndex
420
426
  }
421
427
 
428
+ const changeUtxoIndex = findUtxoIndex(changeOutput)
429
+ const sendUtxoIndex = findUtxoIndex(sendOutput)
430
+
422
431
  const { script, size } = getSizeAndChangeScript({ assetName, tx, rawTx, changeUtxoIndex, txId })
423
432
 
424
433
  let remainingUtxos = usableUtxos.difference(selectedUtxos)
@@ -518,7 +527,12 @@ export const createAndBroadcastTXFactory = ({
518
527
  })
519
528
  }
520
529
 
521
- return { txId, replacedTxId: replaceTx?.txId }
530
+ return {
531
+ txId,
532
+ sendUtxoIndex,
533
+ sendAmount: sendAmount.toBaseNumber(),
534
+ replacedTxId: replaceTx?.txId,
535
+ }
522
536
  }
523
537
 
524
538
  export function createInputs(assetName, ...rest) {
@@ -6,7 +6,9 @@ import { getOwnProperty } from '@exodus/basic-utils'
6
6
 
7
7
  import secp256k1 from 'secp256k1'
8
8
 
9
- import { toAsyncSigner, toXOnly, tweakSigner } from './taproot'
9
+ import { toXOnly } from '../bitcoinjs-lib/ecc-utils'
10
+ import { toAsyncSigner, tweakSigner } from './taproot'
11
+ import { eccFactory } from '../bitcoinjs-lib/ecc'
10
12
 
11
13
  let ECPair
12
14
 
@@ -34,28 +36,71 @@ export const serializeTx = ({ tx }) => {
34
36
  }
35
37
  }
36
38
 
37
- export const signTxFactory = ({ assetName, resolvePurpose, keys, coinInfo, network, ecc }) => {
39
+ // Creates a PSBT instance from the passed inputs, outputs etc. The wallet itself provides this data.
40
+ function createPSBT({ inputs, outputs, rawTxs, networkInfo, getKeyAndPurpose, assetName }) {
41
+ // use harcoded max fee rates for specific assets
42
+ // if undefined, will be set to default value by PSBT (2500)
43
+ const maximumFeeRate = _MAXIMUM_FEE_RATES[assetName]
44
+
45
+ const psbt = new Psbt({ maximumFeeRate, network: networkInfo })
46
+
47
+ // Fill tx
48
+ for (const { txId, vout, address, value, script, sequence } of inputs) {
49
+ const { purpose, publicKey } = getKeyAndPurpose(address)
50
+
51
+ const isSegwitAddress = purpose === 84
52
+ const isTaprootAddress = purpose === 86
53
+ const txIn = { hash: txId, index: vout, sequence }
54
+ if (isSegwitAddress || isTaprootAddress) {
55
+ // witness outputs only require the value and the script, not the full transaction
56
+ txIn.witnessUtxo = { value, script: Buffer.from(script, 'hex') }
57
+ if (isTaprootAddress) {
58
+ txIn.tapInternalKey = toXOnly(publicKey)
59
+ }
60
+ } else {
61
+ const rawTx = (rawTxs || []).find((t) => t.txId === txId)
62
+ // non-witness outptus require the full transaction
63
+ assert(!!rawTx, 'Non-witness outputs require the full previous transaction.')
64
+ const rawTxBuffer = Buffer.from(rawTx.rawData, 'hex')
65
+ if (canParseTx(rawTxBuffer)) {
66
+ txIn.nonWitnessUtxo = rawTxBuffer
67
+ } else {
68
+ // temp fix for https://exodusio.slack.com/archives/CP202D90Q/p1671014704829939 until bitcoinjs could parse a mweb tx without failing
69
+ console.warn(`Setting psbt.__CACHE.__UNSAFE_SIGN_NONSEGWIT = true for asset ${assetName}`)
70
+ psbt.__CACHE.__UNSAFE_SIGN_NONSEGWIT = true
71
+ txIn.witnessUtxo = { value, script: Buffer.from(script, 'hex') }
72
+ }
73
+ }
74
+ psbt.addInput(txIn)
75
+ }
76
+
77
+ for (const [address, amount] of outputs) {
78
+ psbt.addOutput({ value: amount, address })
79
+ }
80
+
81
+ return psbt
82
+ }
83
+
84
+ // Creates a PSBT instance from the passed transaction buffer provided by 3rd parties (e.g. dApps).
85
+ function createPSBTFromBuffer({ psbtBuffer, ecc }) {
86
+ const psbt = Psbt.fromBuffer(psbtBuffer, { eccLib: ecc })
87
+
88
+ return psbt
89
+ }
90
+
91
+ export const signTxFactory = ({ assetName, resolvePurpose, keys, coinInfo, network }) => {
38
92
  assert(assetName, 'assetName is required')
39
93
  assert(resolvePurpose, 'resolvePurpose is required')
40
94
  assert(keys, 'keys is required')
41
95
  assert(coinInfo, 'coinInfo is required')
42
- assert(ecc, 'ecc is required')
96
+
43
97
  return async ({ unsignedTx, hdkeys, privateKeysAddressMap }): Object => {
44
98
  assert(unsignedTx, 'unsignedTx is required')
45
99
  assert(hdkeys || privateKeysAddressMap, 'hdkeys or privateKeysAddressMap is required')
46
- const { addressPathsMap, rawTxs } = unsignedTx.txMeta
47
- const { inputs, outputs } = unsignedTx.txData
100
+ const { addressPathsMap } = unsignedTx.txMeta
48
101
  const networkInfo = { ...coinInfo.toBitcoinJS(), messagePrefix: '' }
49
102
 
50
- // use harcoded max fee rates for specific assets
51
- // if undefined, will be set to default value by PSBT (2500)
52
- const maximumFeeRate = _MAXIMUM_FEE_RATES[assetName]
53
-
54
- const psbt = new Psbt({ maximumFeeRate, eccLib: ecc, network: networkInfo })
55
-
56
- if (!['bitcoin', 'bitcoinregtest', 'bitcointestnet'].includes(assetName)) psbt.setVersion(1)
57
-
58
- ECPair = ECPair || ECPairFactory(ecc)
103
+ ECPair = ECPair || ECPairFactory(eccFactory())
59
104
 
60
105
  const getKeyAndPurpose = lodash.memoize((address) => {
61
106
  const purpose = resolvePurpose(address)
@@ -78,44 +123,27 @@ export const signTxFactory = ({ assetName, resolvePurpose, keys, coinInfo, netwo
78
123
  return { key, publicKey, purpose }
79
124
  })
80
125
 
81
- // Fill tx
82
- for (const { txId, vout, address, value, script, sequence } of inputs) {
83
- const { purpose, publicKey } = getKeyAndPurpose(address)
126
+ const isPsbtBufferPassed =
127
+ unsignedTx.txData.psbtBuffer &&
128
+ unsignedTx.txMeta.addressPathsMap &&
129
+ unsignedTx.txMeta.inputsToSign
130
+ const psbt = isPsbtBufferPassed
131
+ ? createPSBTFromBuffer({ psbtBuffer: unsignedTx.txData.psbtBuffer })
132
+ : createPSBT({ ...unsignedTx.txData, ...unsignedTx.txMeta, getKeyAndPurpose, networkInfo })
133
+ const { inputs } = unsignedTx.txData
84
134
 
85
- const isSegwitAddress = purpose === 84
86
- const isTaprootAddress = purpose === 86
87
- const txIn = { hash: txId, index: vout, sequence }
88
- if (isSegwitAddress || isTaprootAddress) {
89
- // witness outputs only require the value and the script, not the full transaction
90
- txIn.witnessUtxo = { value, script: Buffer.from(script, 'hex') }
91
- if (isTaprootAddress) {
92
- txIn.tapInternalKey = toXOnly(publicKey)
93
- }
94
- } else {
95
- const rawTx = (rawTxs || []).find((t) => t.txId === txId)
96
- // non-witness outptus require the full transaction
97
- assert(!!rawTx, 'Non-witness outputs require the full previous transaction.')
98
- const rawTxBuffer = Buffer.from(rawTx.rawData, 'hex')
99
- if (canParseTx(rawTxBuffer)) {
100
- txIn.nonWitnessUtxo = rawTxBuffer
101
- } else {
102
- // temp fix for https://exodusio.slack.com/archives/CP202D90Q/p1671014704829939 until bitcoinjs could parse a mweb tx without failing
103
- console.warn(`Setting psbt.__CACHE.__UNSAFE_SIGN_NONSEGWIT = true for asset ${assetName}`)
104
- psbt.__CACHE.__UNSAFE_SIGN_NONSEGWIT = true
105
- txIn.witnessUtxo = { value, script: Buffer.from(script, 'hex') }
106
- }
107
- }
108
- psbt.addInput(txIn)
109
- }
135
+ if (!['bitcoin', 'bitcoinregtest', 'bitcointestnet'].includes(assetName)) psbt.setVersion(1)
110
136
 
111
- for (const [address, amount] of outputs) {
112
- psbt.addOutput({ value: amount, address })
113
- }
137
+ const inputsToSign = isPsbtBufferPassed ? unsignedTx.txMeta.inputsToSign : inputs
114
138
 
115
139
  // The Taproot SIGHASH flag includes all previous outputs,
116
140
  // so signing is only done AFTER all inputs have been updated
117
- for (let index = 0; index < inputs.length; index++) {
118
- const { address } = inputs[index]
141
+ for (let index = 0; index < psbt.inputCount; index++) {
142
+ const inputInfo = inputsToSign[index]
143
+ // dApps request to sign only specific transaction inputs.
144
+ if (!inputInfo) continue
145
+ const { address, sigHash } = inputInfo
146
+ const sigHashTypes = sigHash ? [sigHash] : undefined
119
147
  const { key, purpose, publicKey } = getKeyAndPurpose(address)
120
148
 
121
149
  if (purpose === 49) {
@@ -130,19 +158,19 @@ export const signTxFactory = ({ assetName, resolvePurpose, keys, coinInfo, netwo
130
158
  })
131
159
  }
132
160
 
133
- if (ecc.signSchnorrAsync) {
134
- // desktop / BE / mobile with bip-schnorr signing
135
- const isTaprootAddress = purpose === 86
136
- const signingKey = isTaprootAddress
137
- ? tweakSigner({ signer: key, ECPair, ecc, network })
138
- : key
139
- await psbt.signInputAsync(index, toAsyncSigner({ keyPair: signingKey, ecc }))
140
- } else {
141
- // mobile signing
142
- psbt.signInput(index, key)
143
- }
161
+ // desktop / BE / mobile with bip-schnorr signing
162
+ const isTaprootAddress = purpose === 86
163
+ const signingKey = isTaprootAddress ? tweakSigner({ signer: key, ECPair, network }) : key
164
+ await psbt.signInputAsync(index, toAsyncSigner({ keyPair: signingKey }), sigHashTypes)
144
165
  }
145
166
 
167
+ // If a dapp authored the TX, it expects a serialized PSBT response.
168
+ // Note: we wouldn't be able to finalise inputs in some cases that's why we serialize before finalizing inputs.
169
+ if (isPsbtBufferPassed) {
170
+ const rawPSBT = psbt.toBuffer()
171
+
172
+ return { plainTx: { rawPSBT } }
173
+ }
146
174
  // Serialize tx
147
175
  psbt.finalizeAllInputs()
148
176
  const tx = psbt.extractTransaction()
@@ -1,11 +1,14 @@
1
1
  import { crypto } from 'bitcoinjs-lib'
2
2
  import assert from 'minimalistic-assert'
3
3
  import { getSchnorrEntropy } from './default-entropy'
4
+ import { toXOnly } from '../bitcoinjs-lib/ecc-utils'
5
+ import { eccFactory } from '../bitcoinjs-lib/ecc'
4
6
 
5
- export function tweakSigner({ signer, ECPair, ecc, tweakHash, network }) {
7
+ const ecc = eccFactory()
8
+
9
+ export function tweakSigner({ signer, ECPair, tweakHash, network }) {
6
10
  assert(signer, 'signer is required')
7
11
  assert(ECPair, 'ECPair is required')
8
- assert(ecc, 'ecc is required')
9
12
  let privateKey: Uint8Array | undefined = signer.privateKey
10
13
  if (!privateKey) {
11
14
  throw new Error('Private key is required for tweaking signer!')
@@ -34,9 +37,8 @@ function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer {
34
37
  /**
35
38
  * Take a sync signer and make it async.
36
39
  */
37
- export function toAsyncSigner({ keyPair, ecc }) {
40
+ export function toAsyncSigner({ keyPair }) {
38
41
  assert(keyPair, 'keyPair is required')
39
- assert(ecc, 'ecc is required')
40
42
  keyPair.sign = async (h) => {
41
43
  const sig = await ecc.signAsync(h, keyPair.privateKey)
42
44
  return Buffer.from(sig)
@@ -48,7 +50,3 @@ export function toAsyncSigner({ keyPair, ecc }) {
48
50
  }
49
51
  return keyPair
50
52
  }
51
-
52
- export const toXOnly = (publicKey) => {
53
- return publicKey.slice(1, 33)
54
- }
@@ -2,7 +2,6 @@
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
5
 
7
6
  const MAX_ORDINAL_VALUE_POSTAGE = 10000
8
7
 
@@ -24,29 +23,31 @@ export function getOrdinalsUtxos({ accountState, asset }) {
24
23
  )
25
24
  }
26
25
 
27
- function isOrdinalUtxo({ utxo, ordinalChainIndex }) {
26
+ function isOrdinalUtxo({ utxo, ordinalAddress }) {
28
27
  if (utxo.inscriptionId) {
29
28
  return true
30
29
  }
31
- if (ordinalChainIndex === undefined || ordinalChainIndex < 0) {
30
+ if (!ordinalAddress) {
32
31
  // exclude utxos splitting
33
32
  return false
34
33
  }
35
34
 
36
- const maybeAnOrdinalAddress =
37
- ordinalChainIndex && isOrdinalAddress(utxo.address, ordinalChainIndex) // if wallet receives utxos to the special address, consider it as ordinal
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
37
+ }
38
+
38
39
  if (utxo.confirmations) {
39
- return maybeAnOrdinalAddress
40
+ return false
40
41
  }
41
42
 
42
- return utxo.value.toBaseNumber() <= MAX_ORDINAL_VALUE_POSTAGE || maybeAnOrdinalAddress // while unconfirmed, put < 10000- sats in the ordinal utxos box just in case
43
+ return utxo.value.toBaseNumber() <= MAX_ORDINAL_VALUE_POSTAGE // while unconfirmed, put < 10000- sats in the ordinal utxos box just in case
43
44
  }
44
45
 
45
- export function partitionUtxos({ allUtxos, ordinalChainIndex }) {
46
+ export function partitionUtxos({ allUtxos, ordinalAddress }) {
46
47
  assert(allUtxos, 'allUtxos is required')
47
48
  return {
48
- utxos: allUtxos.filter((utxo) => !isOrdinalUtxo({ utxo, ordinalChainIndex })),
49
- ordinalsUtxos: allUtxos.filter((utxo) => isOrdinalUtxo({ utxo, ordinalChainIndex })),
49
+ utxos: allUtxos.filter((utxo) => !isOrdinalUtxo({ utxo, ordinalAddress })),
50
+ ordinalsUtxos: allUtxos.filter((utxo) => isOrdinalUtxo({ utxo, ordinalAddress })),
50
51
  }
51
52
  }
52
53