@@ -5,14 +5,23 @@ import { ZodTypeProvider } from 'fastify-type-provider-zod';
55import { CKBTransaction , Cell , IsomorphicTransaction , Script , XUDTBalance } from './types' ;
66import z from 'zod' ;
77import { 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' ;
1016import { BI } from '@ckb-lumos/lumos' ;
1117import { UTXO } from '../../services/bitcoin/schema' ;
1218import { Transaction as BTCTransaction } from '../bitcoin/types' ;
1319import { TransactionWithStatus } from '../../services/ckb' ;
1420import { computeScriptHash } from '@ckb-lumos/lumos/utils' ;
1521import { 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' ;
1625
1726const addressRoutes : FastifyPluginCallback < Record < never , never > , Server , ZodTypeProvider > = ( fastify , _ , done ) => {
1827 const env : Env = fastify . container . resolve ( 'env' ) ;
@@ -52,6 +61,18 @@ const addressRoutes: FastifyPluginCallback<Record<never, never>, Server, ZodType
5261 return cells ;
5362 }
5463
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+
5576 fastify . get (
5677 '/:btc_address/assets' ,
5778 {
@@ -104,7 +125,12 @@ const addressRoutes: FastifyPluginCallback<Record<never, never>, Server, ZodType
104125 '/:btc_address/balance' ,
105126 {
106127 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+ ` ,
108134 tags : [ 'RGB++' ] ,
109135 params : z . object ( {
110136 btc_address : z . string ( ) ,
@@ -147,13 +173,14 @@ const addressRoutes: FastifyPluginCallback<Record<never, never>, Server, ZodType
147173 throw fastify . httpErrors . badRequest ( 'Unsupported type asset' ) ;
148174 }
149175
150- const utxos = await getUxtos ( btc_address , no_cache ) ;
151176 const xudtBalances : Record < string , XUDTBalance > = { } ;
177+ const utxos = await getUxtos ( btc_address , no_cache ) ;
152178
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 ) ;
157184 Object . keys ( availableXudtBalances ) . forEach ( ( key ) => {
158185 const { amount, ...xudtInfo } = availableXudtBalances [ key ] ;
159186 xudtBalances [ key ] = {
@@ -164,6 +191,7 @@ const addressRoutes: FastifyPluginCallback<Record<never, never>, Server, ZodType
164191 } ;
165192 } ) ;
166193
194+ // Find all unconfirmed RgbppLock XUDT outputs
167195 const pendingUtxos = utxos . filter (
168196 ( utxo ) =>
169197 ! utxo . status . confirmed ||
@@ -172,19 +200,14 @@ const addressRoutes: FastifyPluginCallback<Record<never, never>, Server, ZodType
172200 ) ;
173201 const pendingUtxosGroup = groupBy ( pendingUtxos , ( utxo ) => utxo . txid ) ;
174202 const pendingTxids = Object . keys ( pendingUtxosGroup ) ;
175-
176203 const pendingOutputCellsGroup = await Promise . all (
177204 pendingTxids . map ( async ( txid ) => {
178- const cells = await fastify . transactionProcessor . getPendingOuputCellsByTxid ( txid ) ;
205+ const cells = await fastify . transactionProcessor . getPendingOutputCellsByTxid ( txid ) ;
179206 const lockArgsSet = new Set ( pendingUtxosGroup [ txid ] . map ( ( utxo ) => buildPreLockArgs ( utxo . vout ) ) ) ;
180207 return cells . filter ( ( cell ) => lockArgsSet . has ( cell . cellOutput . lock . args ) ) ;
181208 } ) ,
182209 ) ;
183- let pendingOutputCells = pendingOutputCellsGroup . flat ( ) ;
184- if ( typeScript ) {
185- pendingOutputCells = filterCellsByTypeScript ( pendingOutputCells , typeScript ) ;
186- }
187-
210+ const pendingOutputCells = filterCellsByTypeScript ( pendingOutputCellsGroup . flat ( ) , typeScript ) ;
188211 const pendingXudtBalances = await fastify . rgbppCollector . getRgbppBalanceByCells ( pendingOutputCells ) ;
189212 Object . values ( pendingXudtBalances ) . forEach ( ( { amount, type_hash, ...xudtInfo } ) => {
190213 if ( ! xudtBalances [ type_hash ] ) {
@@ -200,6 +223,50 @@ const addressRoutes: FastifyPluginCallback<Record<never, never>, Server, ZodType
200223 xudtBalances [ type_hash ] . pending_amount = BI . from ( xudtBalances [ type_hash ] . pending_amount )
201224 . add ( BI . from ( amount ) )
202225 . 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+
203270 xudtBalances [ type_hash ] . total_amount = BI . from ( xudtBalances [ type_hash ] . total_amount )
204271 . add ( BI . from ( amount ) )
205272 . toHexString ( ) ;
@@ -322,18 +389,18 @@ const addressRoutes: FastifyPluginCallback<Record<never, never>, Server, ZodType
322389 } as const ;
323390 }
324391
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+
329396 const outputs = isomorphicTx . ckbRawTx ?. outputs || isomorphicTx . ckbTx ?. outputs || [ ] ;
330397
331398 return {
332399 btcTx,
333400 isRgbpp : true ,
334401 isomorphicTx : {
335402 ...isomorphicTx ,
336- inputs,
403+ inputs : inputCellOutputs ,
337404 outputs,
338405 } ,
339406 } as const ;
0 commit comments