Skip to content

Commit f2b6d6d

Browse files
committed
feat(udt): add udt info querying methods
1 parent 4393803 commit f2b6d6d

File tree

3 files changed

+191
-8
lines changed

3 files changed

+191
-8
lines changed

.changeset/tired-ghosts-greet.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@ckb-ccc/ssri": minor
3+
"@ckb-ccc/udt": minor
4+
"@ckb-ccc/core": patch
5+
---
6+
7+
feat(udt): udt info querying methods
8+

packages/core/src/ckb/transaction.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -965,6 +965,7 @@ export class WitnessArgs extends mol.Entity.Base<
965965
/**
966966
* Convert a bytes to a num.
967967
*
968+
* @deprecated Use `Udt.balanceFrom` from `@ckb-ccc/udt` instead
968969
* @public
969970
*/
970971
export function udtBalanceFrom(dataLike: BytesLike): Num {

packages/udt/src/udt/index.ts

Lines changed: 182 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ export class Udt extends ssri.Trait {
248248
*
249249
* // Manually find cells using the same filter
250250
* for await (const cell of signer.findCells(udt.filter)) {
251-
* console.log(`Found UDT cell with balance: ${ccc.udtBalanceFrom(cell.outputData)}`);
251+
* console.log(`Found UDT cell with balance: ${udt.balanceFrom(cell.outputData, cell.cellOutput)}`);
252252
* }
253253
* ```
254254
*/
@@ -473,6 +473,179 @@ export class Udt extends ssri.Trait {
473473
return ssri.ExecutorResponse.new(undefined);
474474
}
475475

476+
static balanceFrom(outputData: ccc.HexLike): ccc.Num {
477+
const data = ccc.bytesFrom(outputData).slice(0, 16);
478+
return data.length === 0 ? ccc.Zero : ccc.numFromBytes(data);
479+
}
480+
481+
balanceFrom(outputData: ccc.HexLike, _output?: ccc.CellOutputLike): ccc.Num {
482+
return Udt.balanceFrom(outputData);
483+
}
484+
485+
/**
486+
* Calculates comprehensive information about all UDT cells controlled by the signer.
487+
* This method scans through every UDT cell that the signer controls and aggregates
488+
* their balance, capacity, and count information.
489+
*
490+
* ⚠️ **Performance Warning**: This is an expensive operation that scales with the number
491+
* of UDT cells. For addresses with many UDT cells (hundreds or thousands), this method
492+
* can take significant time and resources. Use sparingly and consider caching results.
493+
*
494+
* @param signer - The signer whose UDT cells to scan and analyze
495+
* @param options - Optional configuration for the calculation
496+
* @param options.source - Data source to use: "chain" (default) for on-chain data, "local" for local indexer cache
497+
* @returns A promise resolving to an object containing:
498+
* - balance: Total UDT balance across all cells
499+
* - capacity: Total CKB capacity occupied by all UDT cells
500+
* - count: Number of UDT cells found
501+
*
502+
* @example
503+
* ```typescript
504+
* const udt = new Udt(codeOutPoint, scriptConfig);
505+
*
506+
* // Calculate comprehensive UDT information from chain (default)
507+
* const info = await udt.calculateInfo(signer);
508+
* console.log(`Total UDT balance: ${info.balance}`);
509+
* console.log(`Total capacity used: ${info.capacity} CKB`);
510+
* console.log(`Number of UDT cells: ${info.count}`);
511+
*
512+
* // Use local cache for faster response (may be less up-to-date)
513+
* const localInfo = await udt.calculateInfo(signer, { source: "local" });
514+
* console.log(`Local cached balance: ${localInfo.balance}`);
515+
*
516+
* // Use for wallet balance display
517+
* const balanceInTokens = ccc.fixedPointToString(info.balance, 8); // Assuming 8 decimals
518+
* console.log(`Balance: ${balanceInTokens} tokens in ${info.count} cells`);
519+
* ```
520+
*
521+
* @remarks
522+
* **Performance Considerations:**
523+
* - Execution time is O(n) where n is the number of UDT cells
524+
* - Network requests are made for each cell discovery when using "chain" source
525+
* - "local" source is faster but may not reflect the most recent state
526+
* - Consider implementing client-side caching for frequently accessed data
527+
* - For transaction-specific calculations, use `getInputsInfo()` or `getOutputsInfo()` instead
528+
*
529+
* **Data Source Options:**
530+
* - `"chain"` (default): Queries the blockchain directly for the most up-to-date information
531+
* - `"local"`: Uses local indexer cache, faster but potentially stale data
532+
*
533+
* **Use Cases:**
534+
* - Wallet balance display and portfolio overview
535+
* - UDT cell consolidation planning
536+
* - Comprehensive account analysis
537+
* - Debugging and development tools
538+
*
539+
* **Alternative Methods:**
540+
* - Use `calculateBalance()` if you only need the total balance
541+
* - Use `completeInputsAll()` if you need to collect all cells for a transaction
542+
* - Use transaction-specific methods for partial calculations
543+
*/
544+
async calculateInfo(
545+
signer: ccc.Signer,
546+
options?: { source?: "chain" | "local" | null },
547+
): Promise<{
548+
balance: ccc.Num;
549+
capacity: ccc.Num;
550+
count: number;
551+
}> {
552+
let balance = ccc.Zero;
553+
let capacity = ccc.Zero;
554+
let count = 0;
555+
const isFromLocal = (options?.source ?? "chain") === "local";
556+
557+
for await (const cell of isFromLocal
558+
? signer.findCells(this.filter)
559+
: signer.findCellsOnChain(this.filter)) {
560+
balance += this.balanceFrom(cell.outputData, cell.cellOutput);
561+
capacity += cell.cellOutput.capacity;
562+
count += 1;
563+
}
564+
565+
return {
566+
balance,
567+
capacity,
568+
count,
569+
};
570+
}
571+
572+
/**
573+
* Calculates the total UDT balance across all cells controlled by the signer.
574+
* This method provides a convenient way to get the complete UDT balance without
575+
* needing the additional capacity and count information.
576+
*
577+
* ⚠️ **Performance Warning**: This is an expensive operation that scans all UDT cells.
578+
* For addresses with many UDT cells, this method can be slow and resource-intensive.
579+
* Consider caching results and using sparingly in production applications.
580+
*
581+
* @param signer - The signer whose total UDT balance to calculate
582+
* @param options - Optional configuration for the calculation
583+
* @param options.source - Data source to use: "chain" (default) for on-chain data, "local" for local indexer cache
584+
* @returns A promise resolving to the total UDT balance across all cells
585+
*
586+
* @example
587+
* ```typescript
588+
* const udt = new Udt(codeOutPoint, scriptConfig);
589+
*
590+
* // Get total balance for wallet display (from chain)
591+
* const totalBalance = await udt.calculateBalance(signer);
592+
* console.log(`Total UDT balance: ${totalBalance}`);
593+
*
594+
* // Get balance from local cache for faster response
595+
* const cachedBalance = await udt.calculateBalance(signer, { source: "local" });
596+
* console.log(`Cached UDT balance: ${cachedBalance}`);
597+
*
598+
* // Convert to human-readable format (assuming 8 decimals)
599+
* const decimals = await udt.decimals();
600+
* if (decimals.res !== undefined) {
601+
* const humanReadable = ccc.fixedPointToString(totalBalance, Number(decimals.res));
602+
* console.log(`Balance: ${humanReadable} tokens`);
603+
* }
604+
*
605+
* // Check if user has sufficient balance for a transfer
606+
* const requiredAmount = ccc.fixedPointFrom(100);
607+
* if (totalBalance >= requiredAmount) {
608+
* console.log("Sufficient balance for transfer");
609+
* } else {
610+
* console.log(`Insufficient balance. Need ${requiredAmount - totalBalance} more`);
611+
* }
612+
* ```
613+
*
614+
* @remarks
615+
* **Performance Considerations:**
616+
* - This method internally calls `calculateInfo()` and extracts only the balance
617+
* - Execution time scales linearly with the number of UDT cells
618+
* - Network overhead increases with cell count when using "chain" source
619+
* - "local" source is faster but may not reflect the most recent state
620+
* - Results should be cached when used multiple times
621+
*
622+
* **Data Source Options:**
623+
* - `"chain"` (default): Queries the blockchain directly for the most up-to-date balance
624+
* - `"local"`: Uses local indexer cache, faster but potentially stale data
625+
*
626+
* **When to Use:**
627+
* - Wallet balance display
628+
* - Transfer amount validation
629+
* - Portfolio calculations
630+
* - Simple balance checks
631+
*
632+
* **When NOT to Use:**
633+
* - In transaction loops or frequent operations
634+
* - When you also need capacity or count information (use `calculateInfo()` instead)
635+
* - For transaction input/output analysis (use transaction-specific methods)
636+
*
637+
* **Alternative Methods:**
638+
* - Use `calculateInfo()` if you need additional information beyond balance
639+
* - Use `getInputsBalance()` for transaction input analysis
640+
* - Use `getOutputsBalance()` for transaction output analysis
641+
*/
642+
async calculateBalance(
643+
signer: ccc.Signer,
644+
options?: { source?: "chain" | "local" | null },
645+
): Promise<ccc.Num> {
646+
return (await this.calculateInfo(signer, options)).balance;
647+
}
648+
476649
/**
477650
* Adds the UDT script code as a cell dependency to the transaction.
478651
* This method ensures that the transaction includes the necessary cell dependency
@@ -772,7 +945,7 @@ export class Udt extends ssri.Trait {
772945
}
773946

774947
return [
775-
acc[0] + ccc.udtBalanceFrom(outputData),
948+
acc[0] + this.balanceFrom(outputData, cellOutput),
776949
acc[1] + cellOutput.capacity,
777950
acc[2] + 1,
778951
];
@@ -873,7 +1046,7 @@ export class Udt extends ssri.Trait {
8731046
}
8741047

8751048
return [
876-
acc[0] + ccc.udtBalanceFrom(tx.outputsData[i]),
1049+
acc[0] + this.balanceFrom(tx.outputsData[i], output),
8771050
acc[1] + output.capacity,
8781051
acc[2] + 1,
8791052
];
@@ -994,7 +1167,7 @@ export class Udt extends ssri.Trait {
9941167
* tx,
9951168
* signer,
9961169
* ([balanceAcc, capacityAcc], cell) => {
997-
* const balance = ccc.udtBalanceFrom(cell.outputData);
1170+
* const balance = udt.balanceFrom(cell.outputData, cell.cellOutput);
9981171
* const newBalance = balanceAcc + balance;
9991172
* const newCapacity = capacityAcc + cell.cellOutput.capacity;
10001173
*
@@ -1129,10 +1302,10 @@ export class Udt extends ssri.Trait {
11291302
} = await this.completeInputs(
11301303
tx,
11311304
from,
1132-
([balanceAcc, capacityAcc], { cellOutput: { capacity }, outputData }) => {
1133-
const balance = ccc.udtBalanceFrom(outputData);
1305+
([balanceAcc, capacityAcc], { cellOutput, outputData }) => {
1306+
const balance = this.balanceFrom(outputData, cellOutput);
11341307
const balanceBurned = balanceAcc + balance;
1135-
const capacityBurned = capacityAcc + capacity;
1308+
const capacityBurned = capacityAcc + cellOutput.capacity;
11361309

11371310
// Try to provide enough capacity with UDT cells to avoid extra occupation
11381311
return balanceBurned >= ccc.Zero && capacityBurned >= ccc.Zero
@@ -1354,6 +1527,7 @@ export class Udt extends ssri.Trait {
13541527
) {
13551528
const tx = ccc.Transaction.from(txLike);
13561529
const index = Number(ccc.numFrom(indexLike));
1530+
const output = tx.outputs[index];
13571531
const outputData = ccc.bytesFrom(tx.outputsData[index]);
13581532

13591533
if (!this.isUdt({ cellOutput: tx.outputs[index], outputData })) {
@@ -1366,7 +1540,7 @@ export class Udt extends ssri.Trait {
13661540
(tx, balance, shouldModify) => {
13671541
if (shouldModify) {
13681542
const balanceData = ccc.numLeToBytes(
1369-
ccc.udtBalanceFrom(outputData) + balance,
1543+
this.balanceFrom(outputData, output) + balance,
13701544
16,
13711545
);
13721546

0 commit comments

Comments
 (0)