@@ -5,14 +5,23 @@ import { ZodTypeProvider } from 'fastify-type-provider-zod';
5
5
import { CKBTransaction , Cell , IsomorphicTransaction , Script , XUDTBalance } from './types' ;
6
6
import z from 'zod' ;
7
7
import { Env } from '../../env' ;
8
- import { buildPreLockArgs , getXudtTypeScript , isScriptEqual , isTypeAssetSupported } from '@rgbpp-sdk/ckb' ;
9
- import { groupBy } from 'lodash' ;
8
+ import {
9
+ isScriptEqual ,
10
+ buildPreLockArgs ,
11
+ getRgbppLockScript ,
12
+ getXudtTypeScript ,
13
+ isTypeAssetSupported ,
14
+ } from '@rgbpp-sdk/ckb' ;
15
+ import { groupBy , uniq } from 'lodash' ;
10
16
import { BI } from '@ckb-lumos/lumos' ;
11
17
import { UTXO } from '../../services/bitcoin/schema' ;
12
18
import { Transaction as BTCTransaction } from '../bitcoin/types' ;
13
19
import { TransactionWithStatus } from '../../services/ckb' ;
14
20
import { computeScriptHash } from '@ckb-lumos/lumos/utils' ;
15
21
import { filterCellsByTypeScript , getTypeScript } from '../../utils/typescript' ;
22
+ import { unpackRgbppLockArgs } from '@rgbpp-sdk/btc/lib/ckb/molecule' ;
23
+ import { TestnetTypeMap } from '../../constants' ;
24
+ import { remove0x } from '@rgbpp-sdk/btc' ;
16
25
17
26
const addressRoutes : FastifyPluginCallback < Record < never , never > , Server , ZodTypeProvider > = ( fastify , _ , done ) => {
18
27
const env : Env = fastify . container . resolve ( 'env' ) ;
@@ -52,6 +61,18 @@ const addressRoutes: FastifyPluginCallback<Record<never, never>, Server, ZodType
52
61
return cells ;
53
62
}
54
63
64
+ /**
65
+ * Filter RgbppLock cells by cells
66
+ */
67
+ function getRgbppLockCellsByCells ( cells : Cell [ ] ) : Cell [ ] {
68
+ const rgbppLockScript = getRgbppLockScript ( env . NETWORK === 'mainnet' , TestnetTypeMap [ env . NETWORK ] ) ;
69
+ return cells . filter (
70
+ ( cell ) =>
71
+ rgbppLockScript . codeHash === cell . cellOutput . lock . codeHash &&
72
+ rgbppLockScript . hashType === cell . cellOutput . lock . hashType ,
73
+ ) ;
74
+ }
75
+
55
76
fastify . get (
56
77
'/:btc_address/assets' ,
57
78
{
@@ -104,7 +125,12 @@ const addressRoutes: FastifyPluginCallback<Record<never, never>, Server, ZodType
104
125
'/:btc_address/balance' ,
105
126
{
106
127
schema : {
107
- description : 'Get RGB++ balance by btc address, support xUDT only for now' ,
128
+ description : `
129
+ Get RGB++ balance by btc address, support xUDT only for now.
130
+
131
+ An address with more than 50 pending BTC transactions is uncommon.
132
+ However, if such a situation arises, it potentially affecting the returned total_amount.
133
+ ` ,
108
134
tags : [ 'RGB++' ] ,
109
135
params : z . object ( {
110
136
btc_address : z . string ( ) ,
@@ -147,13 +173,14 @@ const addressRoutes: FastifyPluginCallback<Record<never, never>, Server, ZodType
147
173
throw fastify . httpErrors . badRequest ( 'Unsupported type asset' ) ;
148
174
}
149
175
150
- const utxos = await getUxtos ( btc_address , no_cache ) ;
151
176
const xudtBalances : Record < string , XUDTBalance > = { } ;
177
+ const utxos = await getUxtos ( btc_address , no_cache ) ;
152
178
153
- let cells = await getRgbppAssetsCells ( btc_address , utxos , no_cache ) ;
154
- cells = typeScript ? filterCellsByTypeScript ( cells , typeScript ) : cells ;
155
-
156
- const availableXudtBalances = await fastify . rgbppCollector . getRgbppBalanceByCells ( cells ) ;
179
+ // Find confirmed RgbppLock XUDT assets
180
+ const confirmedUtxos = utxos . filter ( ( utxo ) => utxo . status . confirmed ) ;
181
+ const confirmedCells = await getRgbppAssetsCells ( btc_address , confirmedUtxos , no_cache ) ;
182
+ const confirmedTargetCells = filterCellsByTypeScript ( confirmedCells , typeScript ) ;
183
+ const availableXudtBalances = await fastify . rgbppCollector . getRgbppBalanceByCells ( confirmedTargetCells ) ;
157
184
Object . keys ( availableXudtBalances ) . forEach ( ( key ) => {
158
185
const { amount, ...xudtInfo } = availableXudtBalances [ key ] ;
159
186
xudtBalances [ key ] = {
@@ -164,6 +191,7 @@ const addressRoutes: FastifyPluginCallback<Record<never, never>, Server, ZodType
164
191
} ;
165
192
} ) ;
166
193
194
+ // Find all unconfirmed RgbppLock XUDT outputs
167
195
const pendingUtxos = utxos . filter (
168
196
( utxo ) =>
169
197
! utxo . status . confirmed ||
@@ -172,19 +200,14 @@ const addressRoutes: FastifyPluginCallback<Record<never, never>, Server, ZodType
172
200
) ;
173
201
const pendingUtxosGroup = groupBy ( pendingUtxos , ( utxo ) => utxo . txid ) ;
174
202
const pendingTxids = Object . keys ( pendingUtxosGroup ) ;
175
-
176
203
const pendingOutputCellsGroup = await Promise . all (
177
204
pendingTxids . map ( async ( txid ) => {
178
- const cells = await fastify . transactionProcessor . getPendingOuputCellsByTxid ( txid ) ;
205
+ const cells = await fastify . transactionProcessor . getPendingOutputCellsByTxid ( txid ) ;
179
206
const lockArgsSet = new Set ( pendingUtxosGroup [ txid ] . map ( ( utxo ) => buildPreLockArgs ( utxo . vout ) ) ) ;
180
207
return cells . filter ( ( cell ) => lockArgsSet . has ( cell . cellOutput . lock . args ) ) ;
181
208
} ) ,
182
209
) ;
183
- let pendingOutputCells = pendingOutputCellsGroup . flat ( ) ;
184
- if ( typeScript ) {
185
- pendingOutputCells = filterCellsByTypeScript ( pendingOutputCells , typeScript ) ;
186
- }
187
-
210
+ const pendingOutputCells = filterCellsByTypeScript ( pendingOutputCellsGroup . flat ( ) , typeScript ) ;
188
211
const pendingXudtBalances = await fastify . rgbppCollector . getRgbppBalanceByCells ( pendingOutputCells ) ;
189
212
Object . values ( pendingXudtBalances ) . forEach ( ( { amount, type_hash, ...xudtInfo } ) => {
190
213
if ( ! xudtBalances [ type_hash ] ) {
@@ -200,6 +223,50 @@ const addressRoutes: FastifyPluginCallback<Record<never, never>, Server, ZodType
200
223
xudtBalances [ type_hash ] . pending_amount = BI . from ( xudtBalances [ type_hash ] . pending_amount )
201
224
. add ( BI . from ( amount ) )
202
225
. toHexString ( ) ;
226
+ } ) ;
227
+
228
+ // Find spent RgbppLock XUDT assets in the inputs of the unconfirmed transactions
229
+ // XXX: the bitcoin.getAddressTxs() API only returns up to 50 mempool transactions
230
+ const latestTxs = await fastify . bitcoin . getAddressTxs ( { address : btc_address } ) ;
231
+ const unconfirmedTxids = latestTxs . filter ( ( tx ) => ! tx . status . confirmed ) . map ( ( tx ) => tx . txid ) ;
232
+ const spendingInputCellsGroup = await Promise . all (
233
+ unconfirmedTxids . map ( async ( txid ) => {
234
+ const inputCells = await fastify . transactionProcessor . getPendingInputCellsByTxid ( txid ) ;
235
+ const inputRgbppCells = getRgbppLockCellsByCells ( filterCellsByTypeScript ( inputCells , typeScript ) ) ;
236
+ const inputCellLockArgs = inputRgbppCells . map ( ( cell ) => unpackRgbppLockArgs ( cell . cellOutput . lock . args ) ) ;
237
+
238
+ const txids = uniq ( inputCellLockArgs . map ( ( args ) => remove0x ( args . btcTxid ) ) ) ;
239
+ const txs = await Promise . all ( txids . map ( ( txid ) => fastify . bitcoin . getTx ( { txid } ) ) ) ;
240
+ const txsMap = txs . reduce (
241
+ ( sum , tx , index ) => {
242
+ const txid = txids [ index ] ;
243
+ sum [ txid ] = tx ?? null ;
244
+ return sum ;
245
+ } ,
246
+ { } as Record < string , BTCTransaction | null > ,
247
+ ) ;
248
+
249
+ return inputRgbppCells . filter ( ( cell , index ) => {
250
+ const lockArgs = inputCellLockArgs [ index ] ;
251
+ const tx = txsMap [ remove0x ( lockArgs . btcTxid ) ] ;
252
+ const utxo = tx ?. vout [ lockArgs . outIndex ] ;
253
+ return utxo ?. scriptpubkey_address === btc_address ;
254
+ } ) ;
255
+ } ) ,
256
+ ) ;
257
+ const spendingInputCells = spendingInputCellsGroup . flat ( ) ;
258
+ const spendingXudtBalances = await fastify . rgbppCollector . getRgbppBalanceByCells ( spendingInputCells ) ;
259
+ Object . values ( spendingXudtBalances ) . forEach ( ( { amount, type_hash, ...xudtInfo } ) => {
260
+ if ( ! xudtBalances [ type_hash ] ) {
261
+ xudtBalances [ type_hash ] = {
262
+ ...xudtInfo ,
263
+ type_hash,
264
+ total_amount : '0x0' ,
265
+ available_amount : '0x0' ,
266
+ pending_amount : '0x0' ,
267
+ } ;
268
+ }
269
+
203
270
xudtBalances [ type_hash ] . total_amount = BI . from ( xudtBalances [ type_hash ] . total_amount )
204
271
. add ( BI . from ( amount ) )
205
272
. toHexString ( ) ;
@@ -322,18 +389,18 @@ const addressRoutes: FastifyPluginCallback<Record<never, never>, Server, ZodType
322
389
} as const ;
323
390
}
324
391
325
- const inputOutpoints = isomorphicTx . ckbRawTx ?. inputs || isomorphicTx . ckbTx ?. inputs || [ ] ;
326
- const inputs = await fastify . ckb . getInputCellsByOutPoint (
327
- inputOutpoints . map ( ( input ) => input . previousOutput ) as CKBComponents . OutPoint [ ] ,
328
- ) ;
392
+ const inputs = isomorphicTx . ckbRawTx ?. inputs || isomorphicTx . ckbTx ?. inputs || [ ] ;
393
+ const inputCells = await fastify . ckb . getInputCellsByOutPoint ( inputs . map ( ( input ) => input . previousOutput ! ) ) ;
394
+ const inputCellOutputs = inputCells . map ( ( cell ) => cell . cellOutput ) ;
395
+
329
396
const outputs = isomorphicTx . ckbRawTx ?. outputs || isomorphicTx . ckbTx ?. outputs || [ ] ;
330
397
331
398
return {
332
399
btcTx,
333
400
isRgbpp : true ,
334
401
isomorphicTx : {
335
402
...isomorphicTx ,
336
- inputs,
403
+ inputs : inputCellOutputs ,
337
404
outputs,
338
405
} ,
339
406
} as const ;
0 commit comments