@lifi/sdk 3.3.0-alpha.3 → 3.3.0-beta.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.
Files changed (156) hide show
  1. package/package.json +3 -7
  2. package/src/_cjs/config.js +1 -0
  3. package/src/_cjs/config.js.map +1 -1
  4. package/src/_cjs/core/EVM/EVMStepExecutor.js +15 -3
  5. package/src/_cjs/core/EVM/EVMStepExecutor.js.map +1 -1
  6. package/src/_cjs/core/EVM/checkAllowance.js +5 -1
  7. package/src/_cjs/core/EVM/checkAllowance.js.map +1 -1
  8. package/src/_cjs/core/EVM/switchChain.js +5 -1
  9. package/src/_cjs/core/EVM/switchChain.js.map +1 -1
  10. package/src/_cjs/core/Solana/SolanaStepExecutor.js +10 -2
  11. package/src/_cjs/core/Solana/SolanaStepExecutor.js.map +1 -1
  12. package/src/_cjs/core/StatusManager.js +2 -1
  13. package/src/_cjs/core/StatusManager.js.map +1 -1
  14. package/src/_cjs/core/UTXO/UTXOStepExecutor.js +39 -17
  15. package/src/_cjs/core/UTXO/UTXOStepExecutor.js.map +1 -1
  16. package/src/_cjs/core/UTXO/getUTXOPublicClient.js +1 -0
  17. package/src/_cjs/core/UTXO/getUTXOPublicClient.js.map +1 -1
  18. package/src/_cjs/core/UTXO/parseUTXOErrors.js +4 -0
  19. package/src/_cjs/core/UTXO/parseUTXOErrors.js.map +1 -1
  20. package/src/_cjs/core/UTXO/utxo-stack/actions/getBlock.js +31 -0
  21. package/src/_cjs/core/UTXO/utxo-stack/actions/getBlock.js.map +1 -0
  22. package/src/_cjs/core/UTXO/utxo-stack/actions/getBlockStats.js +27 -0
  23. package/src/_cjs/core/UTXO/utxo-stack/actions/getBlockStats.js.map +1 -0
  24. package/src/_cjs/core/UTXO/utxo-stack/actions/getUTXOTransaction.js +18 -9
  25. package/src/_cjs/core/UTXO/utxo-stack/actions/getUTXOTransaction.js.map +1 -1
  26. package/src/_cjs/core/UTXO/utxo-stack/actions/waitForTransaction.js +185 -0
  27. package/src/_cjs/core/UTXO/utxo-stack/actions/waitForTransaction.js.map +1 -0
  28. package/src/_cjs/core/UTXO/utxo-stack/actions/watchBlockNumber.js +45 -0
  29. package/src/_cjs/core/UTXO/utxo-stack/actions/watchBlockNumber.js.map +1 -0
  30. package/src/_cjs/core/UTXO/utxo-stack/types/blockStats.js +3 -0
  31. package/src/_cjs/core/UTXO/utxo-stack/types/blockStats.js.map +1 -0
  32. package/src/_cjs/core/UTXO/utxo-stack/utils/cancelTransaction.js +51 -0
  33. package/src/_cjs/core/UTXO/utxo-stack/utils/cancelTransaction.js.map +1 -0
  34. package/src/_cjs/core/UTXO/utxo-stack/utils/modifyFee.js +50 -0
  35. package/src/_cjs/core/UTXO/utxo-stack/utils/modifyFee.js.map +1 -0
  36. package/src/_cjs/core/UTXO/utxo-stack/utils/observe.js +48 -0
  37. package/src/_cjs/core/UTXO/utxo-stack/utils/observe.js.map +1 -0
  38. package/src/_cjs/core/UTXO/utxo-stack/utils/poll.js +28 -0
  39. package/src/_cjs/core/UTXO/utxo-stack/utils/poll.js.map +1 -0
  40. package/src/_cjs/errors/constants.js +1 -0
  41. package/src/_cjs/errors/constants.js.map +1 -1
  42. package/src/_cjs/services/api.js +1 -1
  43. package/src/_cjs/services/balance.js +9 -2
  44. package/src/_cjs/services/balance.js.map +1 -1
  45. package/src/_cjs/version.js +1 -1
  46. package/src/_cjs/version.js.map +1 -1
  47. package/src/_esm/config.js +1 -0
  48. package/src/_esm/config.js.map +1 -1
  49. package/src/_esm/core/EVM/EVMStepExecutor.js +15 -3
  50. package/src/_esm/core/EVM/EVMStepExecutor.js.map +1 -1
  51. package/src/_esm/core/EVM/checkAllowance.js +5 -1
  52. package/src/_esm/core/EVM/checkAllowance.js.map +1 -1
  53. package/src/_esm/core/EVM/switchChain.js +5 -1
  54. package/src/_esm/core/EVM/switchChain.js.map +1 -1
  55. package/src/_esm/core/Solana/SolanaStepExecutor.js +10 -2
  56. package/src/_esm/core/Solana/SolanaStepExecutor.js.map +1 -1
  57. package/src/_esm/core/StatusManager.js +11 -4
  58. package/src/_esm/core/StatusManager.js.map +1 -1
  59. package/src/_esm/core/UTXO/UTXOStepExecutor.js +41 -35
  60. package/src/_esm/core/UTXO/UTXOStepExecutor.js.map +1 -1
  61. package/src/_esm/core/UTXO/getUTXOPublicClient.js +1 -0
  62. package/src/_esm/core/UTXO/getUTXOPublicClient.js.map +1 -1
  63. package/src/_esm/core/UTXO/parseUTXOErrors.js +5 -0
  64. package/src/_esm/core/UTXO/parseUTXOErrors.js.map +1 -1
  65. package/src/_esm/core/UTXO/utxo-stack/actions/getBlock.js +28 -0
  66. package/src/_esm/core/UTXO/utxo-stack/actions/getBlock.js.map +1 -0
  67. package/src/_esm/core/UTXO/utxo-stack/actions/getBlockStats.js +24 -0
  68. package/src/_esm/core/UTXO/utxo-stack/actions/getBlockStats.js.map +1 -0
  69. package/src/_esm/core/UTXO/utxo-stack/actions/getUTXOTransaction.js +18 -10
  70. package/src/_esm/core/UTXO/utxo-stack/actions/getUTXOTransaction.js.map +1 -1
  71. package/src/_esm/core/UTXO/utxo-stack/actions/waitForTransaction.js +230 -0
  72. package/src/_esm/core/UTXO/utxo-stack/actions/waitForTransaction.js.map +1 -0
  73. package/src/_esm/core/UTXO/utxo-stack/actions/watchBlockNumber.js +58 -0
  74. package/src/_esm/core/UTXO/utxo-stack/actions/watchBlockNumber.js.map +1 -0
  75. package/src/_esm/core/UTXO/utxo-stack/types/blockStats.js +2 -0
  76. package/src/_esm/core/UTXO/utxo-stack/types/blockStats.js.map +1 -0
  77. package/src/_esm/core/UTXO/utxo-stack/utils/cancelTransaction.js +55 -0
  78. package/src/_esm/core/UTXO/utxo-stack/utils/cancelTransaction.js.map +1 -0
  79. package/src/_esm/core/UTXO/utxo-stack/utils/modifyFee.js +53 -0
  80. package/src/_esm/core/UTXO/utxo-stack/utils/modifyFee.js.map +1 -0
  81. package/src/_esm/core/UTXO/utxo-stack/utils/observe.js +51 -0
  82. package/src/_esm/core/UTXO/utxo-stack/utils/observe.js.map +1 -0
  83. package/src/_esm/core/UTXO/utxo-stack/utils/poll.js +28 -0
  84. package/src/_esm/core/UTXO/utxo-stack/utils/poll.js.map +1 -0
  85. package/src/_esm/errors/constants.js +1 -0
  86. package/src/_esm/errors/constants.js.map +1 -1
  87. package/src/_esm/services/api.js +1 -1
  88. package/src/_esm/services/balance.js +9 -2
  89. package/src/_esm/services/balance.js.map +1 -1
  90. package/src/_esm/version.js +1 -1
  91. package/src/_esm/version.js.map +1 -1
  92. package/src/_types/config.d.ts.map +1 -1
  93. package/src/_types/core/EVM/EVMStepExecutor.d.ts.map +1 -1
  94. package/src/_types/core/EVM/checkAllowance.d.ts.map +1 -1
  95. package/src/_types/core/Solana/SolanaStepExecutor.d.ts.map +1 -1
  96. package/src/_types/core/StatusManager.d.ts +18 -8
  97. package/src/_types/core/StatusManager.d.ts.map +1 -1
  98. package/src/_types/core/UTXO/UTXOStepExecutor.d.ts.map +1 -1
  99. package/src/_types/core/UTXO/getUTXOPublicClient.d.ts.map +1 -1
  100. package/src/_types/core/UTXO/utxo-stack/actions/getBlock.d.ts +13 -0
  101. package/src/_types/core/UTXO/utxo-stack/actions/getBlock.d.ts.map +1 -0
  102. package/src/_types/core/UTXO/utxo-stack/actions/getBlockStats.d.ts +15 -0
  103. package/src/_types/core/UTXO/utxo-stack/actions/getBlockStats.d.ts.map +1 -0
  104. package/src/_types/core/UTXO/utxo-stack/actions/getUTXOTransaction.d.ts +2 -2
  105. package/src/_types/core/UTXO/utxo-stack/actions/getUTXOTransaction.d.ts.map +1 -1
  106. package/src/_types/core/UTXO/utxo-stack/actions/sendUTXOTransaction.d.ts +1 -1
  107. package/src/_types/core/UTXO/utxo-stack/actions/sendUTXOTransaction.d.ts.map +1 -1
  108. package/src/_types/core/UTXO/utxo-stack/actions/waitForTransaction.d.ts +76 -0
  109. package/src/_types/core/UTXO/utxo-stack/actions/waitForTransaction.d.ts.map +1 -0
  110. package/src/_types/core/UTXO/utxo-stack/actions/watchBlockNumber.d.ts +26 -0
  111. package/src/_types/core/UTXO/utxo-stack/actions/watchBlockNumber.d.ts.map +1 -0
  112. package/src/_types/core/UTXO/utxo-stack/transports/utxo/types.d.ts +16 -0
  113. package/src/_types/core/UTXO/utxo-stack/transports/utxo/types.d.ts.map +1 -1
  114. package/src/_types/core/UTXO/utxo-stack/types/blockStats.d.ts +35 -0
  115. package/src/_types/core/UTXO/utxo-stack/types/blockStats.d.ts.map +1 -0
  116. package/src/_types/core/UTXO/utxo-stack/utils/cancelTransaction.d.ts +3 -0
  117. package/src/_types/core/UTXO/utxo-stack/utils/cancelTransaction.d.ts.map +1 -0
  118. package/src/_types/core/UTXO/utxo-stack/utils/modifyFee.d.ts +3 -0
  119. package/src/_types/core/UTXO/utxo-stack/utils/modifyFee.d.ts.map +1 -0
  120. package/src/_types/core/UTXO/utxo-stack/utils/observe.d.ts +19 -0
  121. package/src/_types/core/UTXO/utxo-stack/utils/observe.d.ts.map +1 -0
  122. package/src/_types/core/UTXO/utxo-stack/utils/poll.d.ts +13 -0
  123. package/src/_types/core/UTXO/utxo-stack/utils/poll.d.ts.map +1 -0
  124. package/src/_types/errors/constants.d.ts +2 -1
  125. package/src/_types/errors/constants.d.ts.map +1 -1
  126. package/src/_types/services/balance.d.ts.map +1 -1
  127. package/src/_types/types/internal.d.ts +1 -0
  128. package/src/_types/types/internal.d.ts.map +1 -1
  129. package/src/_types/version.d.ts +1 -1
  130. package/src/_types/version.d.ts.map +1 -1
  131. package/src/config.ts +1 -0
  132. package/src/core/EVM/EVMStepExecutor.ts +12 -10
  133. package/src/core/EVM/checkAllowance.ts +4 -3
  134. package/src/core/EVM/switchChain.ts +4 -4
  135. package/src/core/Solana/SolanaStepExecutor.ts +8 -7
  136. package/src/core/StatusManager.ts +24 -22
  137. package/src/core/UTXO/UTXOStepExecutor.ts +56 -53
  138. package/src/core/UTXO/getUTXOPublicClient.ts +1 -0
  139. package/src/core/UTXO/parseUTXOErrors.ts +11 -0
  140. package/src/core/UTXO/utxo-stack/actions/getBlock.ts +58 -0
  141. package/src/core/UTXO/utxo-stack/actions/getBlockStats.ts +55 -0
  142. package/src/core/UTXO/utxo-stack/actions/getUTXOTransaction.ts +24 -11
  143. package/src/core/UTXO/utxo-stack/actions/sendUTXOTransaction.ts +1 -1
  144. package/src/core/UTXO/utxo-stack/actions/waitForTransaction.ts +387 -0
  145. package/src/core/UTXO/utxo-stack/actions/watchBlockNumber.ts +105 -0
  146. package/src/core/UTXO/utxo-stack/transports/utxo/types.ts +16 -0
  147. package/src/core/UTXO/utxo-stack/types/blockStats.ts +35 -0
  148. package/src/core/UTXO/utxo-stack/utils/cancelTransaction.ts +75 -0
  149. package/src/core/UTXO/utxo-stack/utils/modifyFee.ts +78 -0
  150. package/src/core/UTXO/utxo-stack/utils/observe.ts +81 -0
  151. package/src/core/UTXO/utxo-stack/utils/poll.ts +46 -0
  152. package/src/errors/constants.ts +1 -0
  153. package/src/services/api.ts +1 -1
  154. package/src/services/balance.ts +11 -2
  155. package/src/types/internal.ts +1 -0
  156. package/src/version.ts +1 -1
@@ -0,0 +1,387 @@
1
+ import { address, Transaction } from 'bitcoinjs-lib'
2
+ import {
3
+ stringify,
4
+ TransactionNotFoundError,
5
+ TransactionReceiptNotFoundError,
6
+ WaitForTransactionReceiptTimeoutError,
7
+ withRetry,
8
+ type Chain,
9
+ type Client,
10
+ type Transport,
11
+ } from 'viem'
12
+ import { getAction } from 'viem/utils'
13
+ import type { UTXOTransaction } from '../types/transaction.js'
14
+ import { observe } from '../utils/observe.js'
15
+ import { getBlock } from './getBlock.js'
16
+ import { getBlockStats } from './getBlockStats.js'
17
+ import { getUTXOTransaction } from './getUTXOTransaction.js'
18
+ import { watchBlockNumber } from './watchBlockNumber.js'
19
+
20
+ export type ReplacementReason = 'cancelled' | 'replaced' | 'repriced'
21
+ export type ReplacementReturnType = {
22
+ reason: ReplacementReason
23
+ replacedTransaction: Transaction
24
+ transaction: UTXOTransaction
25
+ }
26
+
27
+ export type WaitForTransactionReceiptReturnType = UTXOTransaction
28
+
29
+ export type WithRetryParameters = {
30
+ // The delay (in ms) between retries.
31
+ delay?:
32
+ | ((config: { count: number; error: Error }) => number)
33
+ | number
34
+ | undefined
35
+ // The max number of times to retry.
36
+ retryCount?: number | undefined
37
+ }
38
+
39
+ export type WaitForTransactionReceiptParameters = {
40
+ /** The Id of the transaction. */
41
+ txId: string
42
+ /** The hex string of the raw transaction. */
43
+ txHex: string
44
+ /** The sender address of the transaction. */
45
+ senderAddress?: string
46
+ /**
47
+ * The number of confirmations (blocks that have passed) to wait before resolving.
48
+ * @default 1
49
+ */
50
+ confirmations?: number | undefined
51
+ /** Optional callback to emit if the transaction has been replaced. */
52
+ onReplaced?: ((response: ReplacementReturnType) => void) | undefined
53
+ /**
54
+ * Polling frequency (in ms). Defaults to the client's pollingInterval config.
55
+ * @default client.pollingInterval
56
+ */
57
+ pollingInterval?: number | undefined
58
+ /**
59
+ * Number of times to retry if the transaction or block is not found.
60
+ * @default 6 (exponential backoff)
61
+ */
62
+ retryCount?: number
63
+ /**
64
+ * Time to wait (in ms) between retries.
65
+ */
66
+ retryDelay?: ((config: { count: number; error: Error }) => number) | number
67
+ /** Optional timeout (in milliseconds) to wait before stopping polling. */
68
+ timeout?: number | undefined
69
+ }
70
+
71
+ /**
72
+ * Waits for the transaction to be included on a block (one confirmation), and then returns the transaction.
73
+ * - JSON-RPC Methods:
74
+ * - Polls getrawtransaction on each block until it has been processed.
75
+ * - If a transaction has been replaced:
76
+ * - Calls getblock and extracts the transactions
77
+ * - Checks if one of the transactions is a replacement
78
+ * - If so, calls getrawtransaction.
79
+ *
80
+ * The `waitForTransaction` action additionally supports replacement detection (e.g. RBF - transactions replaced-by-fee ).
81
+ *
82
+ * Transactions can be replaced when a user modifies their transaction in their wallet (to speed up or cancel).
83
+ * https://bitcoinops.org/en/topics/replace-by-fee/
84
+ *
85
+ * There are 3 types of Transaction Replacement reasons:
86
+ *
87
+ * - `repriced`: The fee has been modified (e.g. same outputs, different amounts)
88
+ * - `cancelled`: The Transaction has been cancelled (e.g. output is sender address)
89
+ * - `replaced`: The Transaction has been replaced (e.g. different outputs)
90
+ * @param client - Client to use
91
+ * @param parameters - {@link WaitForTransactionReceiptParameters}
92
+ * @returns The UTXO transaction. {@link WaitForTransactionReceiptReturnType}
93
+ */
94
+ export async function waitForTransaction<chain extends Chain | undefined>(
95
+ client: Client<Transport, chain>,
96
+ {
97
+ confirmations = 1,
98
+ txId,
99
+ txHex,
100
+ senderAddress,
101
+ onReplaced,
102
+ pollingInterval = client.pollingInterval,
103
+ retryCount = 10,
104
+ retryDelay = 3_000,
105
+ timeout,
106
+ }: WaitForTransactionReceiptParameters
107
+ ): Promise<WaitForTransactionReceiptReturnType> {
108
+ const observerId = stringify(['waitForTransaction', client.uid, txId])
109
+
110
+ let count = 0
111
+ let transaction: UTXOTransaction | undefined
112
+ let replacedTransaction: Transaction | undefined
113
+ let retrying = false
114
+
115
+ return new Promise((resolve, reject) => {
116
+ if (timeout) {
117
+ setTimeout(
118
+ () =>
119
+ reject(
120
+ new WaitForTransactionReceiptTimeoutError({ hash: txId as never })
121
+ ),
122
+ timeout
123
+ )
124
+ }
125
+
126
+ const _unobserve = observe(
127
+ observerId,
128
+ { onReplaced, resolve, reject },
129
+ (emit) => {
130
+ const _unwatch = getAction(
131
+ client,
132
+ watchBlockNumber,
133
+ 'watchBlockNumber'
134
+ )({
135
+ emitMissed: true,
136
+ emitOnBegin: true,
137
+ pollingInterval,
138
+ async onBlockNumber(blockNumber_) {
139
+ const done = (fn: () => void) => {
140
+ _unwatch()
141
+ fn()
142
+ _unobserve()
143
+ }
144
+
145
+ let blockNumber = blockNumber_
146
+
147
+ if (retrying) {
148
+ return
149
+ }
150
+ if (count > retryCount) {
151
+ done(() =>
152
+ emit.reject(
153
+ new WaitForTransactionReceiptTimeoutError({
154
+ hash: txId as never,
155
+ })
156
+ )
157
+ )
158
+ }
159
+
160
+ try {
161
+ // If we already have a valid receipt, let's check if we have enough
162
+ // confirmations. If we do, then we can resolve.
163
+ if (transaction?.blockhash) {
164
+ const blockStats = await getAction(
165
+ client,
166
+ getBlockStats,
167
+ 'getBlockStats'
168
+ )({
169
+ blockHash: transaction.blockhash,
170
+ stats: ['height'],
171
+ })
172
+ if (
173
+ confirmations > 1 &&
174
+ (!blockStats.height ||
175
+ blockNumber - blockStats.height + 1 < confirmations)
176
+ ) {
177
+ return
178
+ }
179
+ done(() => emit.resolve(transaction!))
180
+ return
181
+ }
182
+
183
+ // Get the transaction to check if it's been replaced.
184
+ // We need to retry as some RPC Providers may be slow to sync
185
+ // up mined transactions.
186
+ retrying = true
187
+ transaction = await withRetry(
188
+ () =>
189
+ getAction(
190
+ client,
191
+ getUTXOTransaction,
192
+ 'getUTXOTransaction'
193
+ // If transaction exists it might be the replaced one with different txId
194
+ )({ txId: transaction?.txid || txId }),
195
+ {
196
+ delay: retryDelay,
197
+ retryCount,
198
+ }
199
+ )
200
+ if (transaction.blockhash) {
201
+ const blockStats = await getAction(
202
+ client,
203
+ getBlockStats,
204
+ 'getBlockStats'
205
+ )({
206
+ blockHash: transaction.blockhash,
207
+ stats: ['height'],
208
+ })
209
+ if (blockStats.height) {
210
+ blockNumber = blockStats.height
211
+ }
212
+ }
213
+ retrying = false
214
+
215
+ // Check if transaction has been processed.
216
+ if (!transaction?.confirmations) {
217
+ throw new TransactionReceiptNotFoundError({
218
+ hash: txId as never,
219
+ })
220
+ }
221
+
222
+ // Check if we have enough confirmations. If not, continue polling.
223
+ if (transaction.confirmations < confirmations) {
224
+ return
225
+ }
226
+
227
+ done(() => emit.resolve(transaction!))
228
+ } catch (err) {
229
+ // If the receipt is not found, the transaction will be pending.
230
+ // We need to check if it has potentially been replaced.
231
+ if (
232
+ err instanceof TransactionNotFoundError ||
233
+ err instanceof TransactionReceiptNotFoundError
234
+ ) {
235
+ try {
236
+ replacedTransaction = Transaction.fromHex(
237
+ transaction?.hex || txHex
238
+ )
239
+
240
+ // Let's retrieve the transactions from the current block.
241
+ // We need to retry as some RPC Providers may be slow to sync
242
+ // up mined blocks.
243
+ retrying = true
244
+ const block = await withRetry(
245
+ () =>
246
+ getAction(
247
+ client,
248
+ getBlock,
249
+ 'getBlock'
250
+ )({
251
+ blockNumber,
252
+ }),
253
+ {
254
+ delay: retryDelay,
255
+ retryCount,
256
+ // shouldRetry: ({ error }) =>
257
+ // error instanceof BlockNotFoundError,
258
+ }
259
+ )
260
+ retrying = false
261
+
262
+ // Create a set of input identifiers for mempool transaction
263
+ const replacedTransactionInputs = new Set<string>()
264
+
265
+ replacedTransaction.ins.forEach((input) => {
266
+ const txid = Array.from(input.hash)
267
+ .reverse()
268
+ .map((byte) => ('00' + byte.toString(16)).slice(-2))
269
+ .join('')
270
+ const vout = input.index
271
+ const inputId = `${txid}:${vout}`
272
+ replacedTransactionInputs.add(inputId)
273
+ })
274
+
275
+ let replacementTransaction: Transaction | undefined
276
+
277
+ for (const tx of block.transactions!) {
278
+ if (tx.isCoinbase()) {
279
+ continue
280
+ }
281
+
282
+ // Check if any input of this transaction matches an input of mempool transaction
283
+ for (const input of tx.ins) {
284
+ const txid = Array.from(input.hash)
285
+ .reverse()
286
+ .map((byte) => ('00' + byte.toString(16)).slice(-2))
287
+ .join('')
288
+ const vout = input.index
289
+ const inputId = `${txid}:${vout}`
290
+ if (replacedTransactionInputs.has(inputId)) {
291
+ replacementTransaction = tx
292
+ break
293
+ }
294
+ }
295
+ if (replacementTransaction) {
296
+ break
297
+ }
298
+ }
299
+
300
+ // If we couldn't find a replacement transaction, continue polling.
301
+ if (!replacementTransaction) {
302
+ return
303
+ }
304
+
305
+ // If we found a replacement transaction, return it's receipt.
306
+ transaction = await getAction(
307
+ client,
308
+ getUTXOTransaction,
309
+ 'getUTXOTransaction'
310
+ )({
311
+ txId: replacementTransaction.getId(),
312
+ })
313
+
314
+ // Check if we have enough confirmations. If not, continue polling.
315
+ if (
316
+ transaction.confirmations &&
317
+ transaction.confirmations < confirmations
318
+ ) {
319
+ return
320
+ }
321
+
322
+ let reason: ReplacementReason = 'replaced'
323
+
324
+ // Function to get output addresses
325
+ function getOutputAddresses(tx: Transaction): string[] {
326
+ const addresses: string[] = []
327
+ for (const output of tx.outs) {
328
+ try {
329
+ const outputAddress = address.fromOutputScript(
330
+ output.script
331
+ )
332
+ addresses.push(outputAddress)
333
+ } catch (e) {
334
+ // Handle non-standard scripts (e.g., OP_RETURN)
335
+ }
336
+ }
337
+ return addresses
338
+ }
339
+
340
+ // Get the recipient addresses from the original transaction
341
+ const originalOutputAddresses =
342
+ getOutputAddresses(replacedTransaction)
343
+
344
+ // Get the recipient addresses from the replacement transaction
345
+ const replacementOutputAddresses = getOutputAddresses(
346
+ replacementTransaction
347
+ )
348
+
349
+ if (
350
+ originalOutputAddresses.length ===
351
+ replacementOutputAddresses.length &&
352
+ originalOutputAddresses.every((address) =>
353
+ replacementOutputAddresses.includes(address)
354
+ )
355
+ ) {
356
+ reason = 'repriced'
357
+ } else if (
358
+ senderAddress &&
359
+ replacementOutputAddresses.length === 1 &&
360
+ replacementOutputAddresses.includes(senderAddress)
361
+ ) {
362
+ reason = 'cancelled'
363
+ }
364
+
365
+ done(() => {
366
+ emit.onReplaced?.({
367
+ reason,
368
+ replacedTransaction: replacedTransaction!,
369
+ transaction: transaction!,
370
+ })
371
+ emit.resolve(transaction!)
372
+ })
373
+ } catch (err_) {
374
+ done(() => emit.reject(err_))
375
+ }
376
+ } else {
377
+ done(() => emit.reject(err))
378
+ }
379
+ } finally {
380
+ count++
381
+ }
382
+ },
383
+ })
384
+ }
385
+ )
386
+ })
387
+ }
@@ -0,0 +1,105 @@
1
+ import { stringify, type Chain, type Client, type Transport } from 'viem'
2
+ import { getAction } from 'viem/utils'
3
+ import { observe } from '../utils/observe.js'
4
+ import { poll } from '../utils/poll.js'
5
+ import { getBlockCount, type GetBlockCountReturnType } from './getBlockCount.js'
6
+
7
+ export type OnBlockNumberParameter = GetBlockCountReturnType
8
+ export type OnBlockNumberFn = (
9
+ blockNumber: OnBlockNumberParameter,
10
+ prevBlockNumber: OnBlockNumberParameter | undefined
11
+ ) => Promise<void>
12
+
13
+ export type WatchBlockNumberParameters = {
14
+ /** The callback to call when a new block number is received. */
15
+ onBlockNumber: OnBlockNumberFn
16
+ /** The callback to call when an error occurred when trying to get for a new block. */
17
+ onError?: ((error: Error) => void) | undefined
18
+ } & {
19
+ /** Whether or not to emit the missed block numbers to the callback. */
20
+ emitMissed?: boolean | undefined
21
+ /** Whether or not to emit the latest block number to the callback when the subscription opens. */
22
+ emitOnBegin?: boolean | undefined
23
+ /** Polling frequency (in ms). Defaults to Client's pollingInterval config. */
24
+ pollingInterval?: number | undefined
25
+ }
26
+
27
+ export type WatchBlockNumberReturnType = () => void
28
+
29
+ /**
30
+ * Watches and returns incoming block numbers.
31
+ * @param client - Client to use
32
+ * @param parameters - {@link WatchBlockNumberParameters}
33
+ * @returns A function that can be invoked to stop watching for new block numbers. {@link WatchBlockNumberReturnType}
34
+ */
35
+ export function watchBlockNumber<
36
+ chain extends Chain | undefined,
37
+ transport extends Transport,
38
+ >(
39
+ client: Client<transport, chain>,
40
+ {
41
+ emitOnBegin = false,
42
+ emitMissed = false,
43
+ onBlockNumber,
44
+ onError,
45
+ pollingInterval = client.pollingInterval,
46
+ }: WatchBlockNumberParameters
47
+ ): WatchBlockNumberReturnType {
48
+ let prevBlockNumber: GetBlockCountReturnType | undefined
49
+
50
+ const observerId = stringify([
51
+ 'watchBlockNumber',
52
+ client.uid,
53
+ emitOnBegin,
54
+ emitMissed,
55
+ pollingInterval,
56
+ ])
57
+
58
+ // TODO (edge cases):
59
+ // 1) Stop iterating block numbers if we are happy with the result of one onBlockNumber execution but there is more in the queue.
60
+ // 2) If we missed some time - user closed the page and came back when the block is already mined.
61
+ // In this case we probably want to save the block when we send the transaction and track the currently checked blocks until we find the relevant one.
62
+ return observe(observerId, { onBlockNumber, onError }, (emit) =>
63
+ poll(
64
+ async () => {
65
+ try {
66
+ const blockNumber = await getAction(
67
+ client,
68
+ getBlockCount,
69
+ 'getBlockCount'
70
+ )({ cacheTime: 0 })
71
+
72
+ if (prevBlockNumber) {
73
+ // If the current block number is the same as the previous,
74
+ // we can skip.
75
+ if (blockNumber === prevBlockNumber) {
76
+ return
77
+ }
78
+
79
+ // If we have missed out on some previous blocks, and the
80
+ // `emitMissed` flag is truthy, let's emit those blocks.
81
+ if (blockNumber - prevBlockNumber > 1 && emitMissed) {
82
+ for (let i = prevBlockNumber + 1; i < blockNumber; i++) {
83
+ await emit.onBlockNumber(i, prevBlockNumber)
84
+ prevBlockNumber = i
85
+ }
86
+ }
87
+ }
88
+
89
+ // If the next block number is greater than the previous,
90
+ // it is not in the past, and we can emit the new block number.
91
+ if (!prevBlockNumber || blockNumber > prevBlockNumber) {
92
+ await emit.onBlockNumber(blockNumber, prevBlockNumber)
93
+ prevBlockNumber = blockNumber
94
+ }
95
+ } catch (err) {
96
+ emit.onError?.(err as Error)
97
+ }
98
+ },
99
+ {
100
+ emitOnBegin,
101
+ interval: pollingInterval,
102
+ }
103
+ )
104
+ )
105
+ }
@@ -1,3 +1,4 @@
1
+ import type { BlockStats, BlockStatsKeys } from '../../types/blockStats.js'
1
2
  import type { UTXOTransaction } from '../../types/transaction.js'
2
3
  import type { HttpRpcClient } from './getHttpRpcClient.js'
3
4
 
@@ -7,6 +8,21 @@ export type UTXOSchema = [
7
8
  Parameters: []
8
9
  ReturnType: number
9
10
  },
11
+ {
12
+ Method: 'getblockhash'
13
+ Parameters: [number]
14
+ ReturnType: string
15
+ },
16
+ {
17
+ Method: 'getblock'
18
+ Parameters: [string, number]
19
+ ReturnType: string
20
+ },
21
+ {
22
+ Method: 'getblockstats'
23
+ Parameters: [string | number, Array<BlockStatsKeys>?]
24
+ ReturnType: BlockStats
25
+ },
10
26
  {
11
27
  Method: 'sendrawtransaction'
12
28
  Parameters: [string, number?]
@@ -0,0 +1,35 @@
1
+ export type BlockStats = Partial<{
2
+ avgfee: number
3
+ avgfeerate: number
4
+ avgtxsize: number
5
+ blockhash: string
6
+ feerate_percentiles: number[]
7
+ height: number
8
+ ins: number
9
+ maxfee: number
10
+ maxfeerate: number
11
+ maxtxsize: number
12
+ medianfee: number
13
+ mediantime: number
14
+ mediantxsize: number
15
+ minfee: number
16
+ minfeerate: number
17
+ mintxsize: number
18
+ outs: number
19
+ subsidy: number
20
+ swtotal_size: number
21
+ swtotal_weight: number
22
+ swtxs: number
23
+ time: number
24
+ total_out: number
25
+ total_size: number
26
+ total_weight: number
27
+ totalfee: number
28
+ txs: number
29
+ utxo_increase: number
30
+ utxo_size_inc: number
31
+ utxo_increase_actual: number
32
+ utxo_size_inc_actual: number
33
+ }>
34
+
35
+ export type BlockStatsKeys = keyof BlockStats
@@ -0,0 +1,75 @@
1
+ import { address, Psbt } from 'bitcoinjs-lib'
2
+
3
+ interface InputData {
4
+ hash: Uint8Array
5
+ index: number
6
+ nonWitnessUtxo?: Uint8Array
7
+ witnessUtxo?: {
8
+ script: Uint8Array
9
+ value: bigint
10
+ }
11
+ redeemScript?: Uint8Array
12
+ witnessScript?: Uint8Array
13
+ }
14
+
15
+ export function cancelTransaction(psbt: Psbt, accountAddress: string): Psbt {
16
+ const newPsbt = new Psbt()
17
+ const inputs = psbt.data.inputs
18
+ const txInputs = psbt.txInputs
19
+
20
+ // Add inputs to the new PSBT
21
+ for (let i = 0; i < inputs.length; i++) {
22
+ const input = inputs[i]
23
+ const txInput = txInputs[i]
24
+ const inputData: InputData = {
25
+ hash: txInput.hash,
26
+ index: txInput.index,
27
+ }
28
+
29
+ // Include UTXO information
30
+ if (input.nonWitnessUtxo) {
31
+ inputData.nonWitnessUtxo = input.nonWitnessUtxo
32
+ } else if (input.witnessUtxo) {
33
+ inputData.witnessUtxo = input.witnessUtxo
34
+ } else {
35
+ throw new Error('Input UTXO information is missing')
36
+ }
37
+
38
+ // Include scripts if necessary
39
+ if (input.redeemScript) {
40
+ inputData.redeemScript = input.redeemScript
41
+ }
42
+ if (input.witnessScript) {
43
+ inputData.witnessScript = input.witnessScript
44
+ }
45
+
46
+ newPsbt.addInput(inputData)
47
+ }
48
+
49
+ // Compute total output amount from the original transaction
50
+ const outputs = psbt.txOutputs
51
+ let totalOutputValue = BigInt(0)
52
+
53
+ for (const output of outputs) {
54
+ totalOutputValue += output.value
55
+ }
56
+
57
+ if (totalOutputValue <= BigInt(0)) {
58
+ throw new Error('Total output value must be greater than zero')
59
+ }
60
+
61
+ // Create the output to send funds back to sender's address
62
+ const outputScript = address.toOutputScript(accountAddress)
63
+ newPsbt.addOutput({
64
+ script: outputScript,
65
+ value: totalOutputValue,
66
+ })
67
+
68
+ // Modify the input sequence number to enable RBF
69
+ newPsbt.txInputs.forEach((_, index) => {
70
+ // Set sequence number to less than 0xfffffffe, e.g., 0xfffffffd
71
+ newPsbt.setInputSequence(index, 0xfffffffd)
72
+ })
73
+
74
+ return newPsbt
75
+ }
@@ -0,0 +1,78 @@
1
+ import { address, Psbt } from 'bitcoinjs-lib'
2
+
3
+ interface InputData {
4
+ hash: Uint8Array
5
+ index: number
6
+ nonWitnessUtxo?: Uint8Array
7
+ witnessUtxo?: {
8
+ script: Uint8Array
9
+ value: bigint
10
+ }
11
+ redeemScript?: Uint8Array
12
+ witnessScript?: Uint8Array
13
+ }
14
+
15
+ export function modifyFee(psbt: Psbt, newFee: bigint, accountAddress: string) {
16
+ const newPsbt = new Psbt()
17
+ const inputs = psbt.data.inputs
18
+ const outputs = psbt.txOutputs
19
+
20
+ // Add inputs to the new PSBT
21
+ for (let i = 0; i < inputs.length; i++) {
22
+ const input = inputs[i]
23
+ const inputData: InputData = {
24
+ hash: psbt.txInputs[i].hash,
25
+ index: psbt.txInputs[i].index,
26
+ }
27
+
28
+ // Include UTXO information
29
+ if (input.nonWitnessUtxo) {
30
+ inputData.nonWitnessUtxo = input.nonWitnessUtxo
31
+ } else if (input.witnessUtxo) {
32
+ inputData.witnessUtxo = input.witnessUtxo
33
+ }
34
+
35
+ // Include scripts if necessary
36
+ if (input.redeemScript) {
37
+ inputData.redeemScript = input.redeemScript
38
+ }
39
+ if (input.witnessScript) {
40
+ inputData.witnessScript = input.witnessScript
41
+ }
42
+
43
+ newPsbt.addInput(inputData)
44
+ }
45
+
46
+ const changeOutputScript = address.toOutputScript(accountAddress)
47
+
48
+ // Add outputs to the new PSBT
49
+ for (const output of outputs) {
50
+ const outputData = {
51
+ script: output.script,
52
+ value: output.value,
53
+ }
54
+
55
+ const scriptsAreEqual =
56
+ output.script.length === changeOutputScript.length &&
57
+ output.script.every((value, index) => value === changeOutputScript[index])
58
+
59
+ if (scriptsAreEqual) {
60
+ outputData.value = output.value - newFee
61
+ if (outputData.value < 0) {
62
+ throw new Error(
63
+ 'Insufficient funds to adjust the fee by the specified amount.'
64
+ )
65
+ }
66
+ }
67
+
68
+ newPsbt.addOutput(outputData)
69
+ }
70
+
71
+ // Modify the input sequence number to enable RBF
72
+ newPsbt.txInputs.forEach((_, index) => {
73
+ // Set sequence number to less than 0xfffffffe, e.g., 0xfffffffd
74
+ newPsbt.setInputSequence(index, 0xfffffffd)
75
+ })
76
+
77
+ return newPsbt
78
+ }