Skip to content

Conversation

@Hanssen0
Copy link
Member

For full context see #228

@changeset-bot
Copy link

changeset-bot bot commented Aug 16, 2025

🦋 Changeset detected

Latest commit: e8b58dc

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 18 packages
Name Type
@ckb-ccc/udt Minor
@ckb-ccc/core Patch
@ckb-ccc/shell Patch
@ckb-ccc/eip6963 Patch
@ckb-ccc/joy-id Patch
@ckb-ccc/lumos-patches Patch
@ckb-ccc/nip07 Patch
@ckb-ccc/okx Patch
@ckb-ccc/rei Patch
@ckb-ccc/spore Patch
@ckb-ccc/ssri Patch
@ckb-ccc/uni-sat Patch
@ckb-ccc/utxo-global Patch
@ckb-ccc/xverse Patch
@ckb-ccc/ccc Patch
ckb-ccc Patch
@ckb-ccc/connector Patch
@ckb-ccc/connector-react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @Hanssen0, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the UDT (User Defined Token) functionality by introducing comprehensive methods for querying UDT information. It provides utilities to calculate the total balance, capacity, and count of UDT cells associated with a signer, offering both detailed information and a simplified balance-only query. This change improves the usability and efficiency of interacting with UDTs, particularly for wallet displays and account analysis, while also deprecating an older, less specific balance calculation function to promote a more robust and UDT-centric approach.

Highlights

  • New UDT Querying Methods: Introduces calculateInfo and calculateBalance methods within the Udt class to provide comprehensive information (balance, capacity, count) or just the total balance of UDT cells for a given signer. These methods offer flexibility for querying UDT data from either the blockchain or a local indexer cache.
  • Improved Balance Calculation: Adds a dedicated Udt.balanceFrom method for accurately extracting UDT balances from cell output data. This new method provides a more robust and UDT-specific way to interpret balance information, leading to the deprecation of the generic udtBalanceFrom in the @ckb-ccc/core package.
  • Code Refactoring and Consistency: Existing UDT-related functions, such as those for calculating input/output information and completing transactions, have been refactored to utilize the new Udt.balanceFrom method. This ensures consistency across the codebase and leverages the improved UDT-specific balance calculation logic.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces new methods for querying UDT information, such as total balance and cell count, and refactors the UDT balance calculation logic into the @ckb-ccc/udt package. The changes are well-structured and the new methods are thoroughly documented. My feedback focuses on improving the examples in the documentation to use safer methods for handling large numbers, preventing potential precision loss.

Comment on lines 517 to 699
* const balanceInTokens = Number(info.balance) / (10 ** 8); // Assuming 8 decimals
* console.log(`Balance: ${balanceInTokens} tokens in ${info.count} cells`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Converting info.balance to Number can lead to precision loss if the balance is very large (greater than Number.MAX_SAFE_INTEGER). It's safer to use a utility function that handles BigInt arithmetic for formatting, like ccc.fixedPointToString.

Suggested change
* const balanceInTokens = Number(info.balance) / (10 ** 8); // Assuming 8 decimals
* console.log(`Balance: ${balanceInTokens} tokens in ${info.count} cells`);
* const balanceInTokens = ccc.fixedPointToString(info.balance, 8); // Assuming 8 decimals
* console.log(`Balance: ${balanceInTokens} tokens in ${info.count} cells`);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Augment wrote these docs before. You're smarter than her.

@Hanssen0
Copy link
Member Author

@phroi This should allow us to query info about UDT-like cells. One issue left: How should we handle the custom UDT cell while changing?

const balanceData = ccc.numLeToBytes(
ccc.udtBalanceFrom(outputData) + balance,
16,
);

and
const balanceData = ccc.numLeToBytes(balance, 16);

@netlify
Copy link

netlify bot commented Aug 16, 2025

Deploy Preview for apiccc ready!

Name Link
🔨 Latest commit e8b58dc
🔍 Latest deploy log https://app.netlify.com/projects/apiccc/deploys/695fb29c7d28d60008748835
😎 Deploy Preview https://deploy-preview-261--apiccc.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 94 (🟢 up 13 from production)
Accessibility: 100 (no change from production)
Best Practices: 100 (no change from production)
SEO: 94 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Aug 16, 2025

Deploy Preview for appccc ready!

Name Link
🔨 Latest commit e8b58dc
🔍 Latest deploy log https://app.netlify.com/projects/appccc/deploys/695fb29c34c93100093519c9
😎 Deploy Preview https://deploy-preview-261--appccc.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 66 (🔴 down 20 from production)
Accessibility: 89 (🟢 up 1 from production)
Best Practices: 92 (🔴 down 8 from production)
SEO: 100 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Aug 16, 2025

Deploy Preview for docsccc ready!

Name Link
🔨 Latest commit e8b58dc
🔍 Latest deploy log https://app.netlify.com/projects/docsccc/deploys/695fb29c7d837f00081d7601
😎 Deploy Preview https://deploy-preview-261--docsccc.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 71 (🟢 up 8 from production)
Accessibility: 88 (no change from production)
Best Practices: 92 (no change from production)
SEO: 92 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Aug 16, 2025

Deploy Preview for liveccc ready!

Name Link
🔨 Latest commit e8b58dc
🔍 Latest deploy log https://app.netlify.com/projects/liveccc/deploys/695fb29c22e88e0008433962
😎 Deploy Preview https://deploy-preview-261--liveccc.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 26 (🔴 down 9 from production)
Accessibility: 88 (no change from production)
Best Practices: 92 (🔴 down 8 from production)
SEO: 100 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@Hanssen0
Copy link
Member Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces new methods for querying UDT information, such as total balance and capacity. The changes include deprecating the old udtBalanceFrom function in favor of a new Udt.balanceFrom method, and adding calculateInfo and calculateBalance to the Udt class. The refactoring is well-structured and the documentation for the new methods is comprehensive.

I have one suggestion regarding testing for the new functionality to ensure its long-term stability.

@phroi
Copy link
Contributor

phroi commented Aug 16, 2025

This is phase 2 of the UDT master plan:

  1. Try to add more querying methods, like calculating info of a Signer. Meanwhile, we should have methods for parsing a single UDT cell. On this stage, we should consider if these new methods can be overridden to accommodate some unconventional UDT cell requirements.

Back to your comment:

This should allow us to query info about UDT-like cells. One issue left: How should we handle the custom UDT cell while changing?

You meant while chaining, right?

Yeah, I have feelings about those two methods!! 🤣

I don't particularly like static balanceFrom: notice how I can call that method on any cell, irregarding of how it may be a UDT or not. It will also cause confusion if actually is called on cells that seems like UDTs but actually are not (iCKB Receipts)

Additionally, I don't particularly like the non-static balanceFrom:

  • At the very least we need to check the type of the cell against UDT.script
  • Also, for subclasses compatibility reasons, you need to pass the outpoint (if exists) and a ccc.Client parameter (headers of iCKB Receipts).
  • At that point you realize that not all protocols are chainable and that's a fact of life.

For example in the case of iCKB Receipts:

  • iCKB Receipts as output do have a zero iCKB value (cause we don't know the transaction header)
  • iCKB Receipts as inputs have a positive iCKB value (cause we know the transaction header)

Similarly goes for iCKB Deposits:

  • iCKB Deposits as output do have a zero iCKB value (cause we don't know the transaction) header.
  • iCKB Deposits as inputs have a negative iCKB value (cause we know the transaction header)

As outputs (so when depositing), the iCKB Receipt will just track the value of the free CKB of the iCKB Deposit(s). This way the protocol work around the on-chain determinism of L1, which doesn't allow to read the transaction inclusion header at validation time.

As inputs (so when withdrawing), the positive Receipt iCKB value (and iCKB UDTs) and negative Deposit iCKB value cancel each other out.

Are we able to move somewhat closer to infoFrom?

Phroi %36

@phroi
Copy link
Contributor

phroi commented Aug 16, 2025

Just one thing that makes me wonder is filter: correct if we handle only UDT, but we'll be able to extend it later?

I know that it's tightly coupled with tx.completeInputs

Maybe it's not necessary for it to be extendable, cause maybe we don't want to automatically add non-UDT cells

Maybe a second filter or something for other cells types will be needed by subclasses and that's about it

I'm unsure about the scenario here. You mean maybe we will need multiple filters? Or may we need more criteria in the filter?

If it's the first one, will having multiple Udt instances be better?
If it's the second one, the filter is defined by the CKB's PRC. It will be hard to make it extendable.

First case. I'm very much aware of CKB RPC methods and parameters.

Take for example iCKB, a user may own both UDTs and iCKB Receipt cells, which have types that are completely different from each-other, but for example both cells needs to be accounted in the same UDT value for a transaction.

Phroi %11

@Hanssen0
Copy link
Member Author

This should allow us to query info about UDT-like cells. One issue left: How should we handle the custom UDT cell while changing?

You meant while chaining, right?

Nah, I mean while giving the change cell. I thought the change could be used as a verb.
So, for example, we're dealing with an iCKB receipt. How do we construct the change cell so it's a valid cell?

I don't particularly like static balanceFrom: notice how I can call that method on any cell, irregarding of how it may be a UDT or not. It will also cause confusion if actually is called on cells that seems like UDTs but actually are not (iCKB Receipts)

It's for convenience, so devs won't need to make a Udt instance to parse the balance. What about giving it a more detailed name e.g. balanceFromUnsafe or sth?

Additionally, I don't particularly like the non-static balanceFrom:

  • At the very least we need to check the type of the cell against UDT.script

Sure. If it's not an expected UDT cell, let's return 0.

  • Also, for subclasses compatibility reasons, you need to pass the outpoint (if exists) and a ccc.Client parameter (headers of iCKB Receipts).

Of course. Do you think any more info is needed?

  • At that point you realize that not all protocols are chainable and that's a fact of life.

Absolutely. We just want to try our best to make things faster.

Are we able to move somewhat closer to infoFrom?

I'm unsure in what perspective we can make them closer.

  1. I can accept having an infoFrom similar to the current balanceFrom, but returns more detail. I'll still keep the balanceFrom but invoke the infoFrom inside it.
  2. I can accept letting the method accept multiple cells and accumulate their info (need to design the signature so it's still clear with only one cell);
  3. I can accept moving the initialInfo to the last parameter and making it optional;

Is it good enough for you?

So, for example, we're dealing with an iCKB receipt. How do we construct the change cell so it's a valid cell?

This still makes me struggle, and infoFrom doesn't seem to solve it. Looking forward to your suggestions!

@Hanssen0
Copy link
Member Author

Take for example iCKB, a user may own both UDTs and iCKB Receipt cells, which have types that are completely different from each-other, but for example both cells needs to be accounted in the same UDT value for a transaction.

I have two possible solutions:

  1. Making the filter an array. But I would like to make it private until we see cases really need to access the Udt.filters, since I'm not sure if we will still change that in the future.
  2. iCkbUdt can override the completeInputs method.

@Hanssen0
Copy link
Member Author

  • Also, for subclasses compatibility reasons, you need to pass the outpoint (if exists) and a ccc.Client parameter (headers of iCKB Receipts).

Of course. Do you think any more info is needed?

That also means balanceFrom/infoFrom needs to be async, right?

@phroi
Copy link
Contributor

phroi commented Aug 16, 2025

Nah, I mean while giving the change cell. I thought the change could be used as a verb.

You are indeed correct, it is a verb, my bad! I just didn't get the meaning, I'm so sorry!! 🤣

So, for example, we're dealing with an iCKB receipt. How do we construct the change cell so it's a valid cell?

You just add a UDT cell as usual, if for any reason the transaction has a positive burned amount of iCKB.

Change cell will be always an UDT, even in iCKB. Receipt only tracks deposits in output, so in a way is a kind of change cell in itself.

The iCKB validation at L1 script level is:

in_udt_ickb + in_receipts_ickb == out_udt_ickb + in_deposits_ickb

It's for convenience, so devs won't need to make a Udt instance to parse the balance. What about giving it a more detailed name e.g. balanceFromUnsafe or sth?

Something like that, with the appropriate warning in the TypeDoc

Also, for subclasses compatibility reasons, you need to pass the outpoint (if exists) and a ccc.Client parameter (headers of iCKB Receipts).

Of course. Do you think any more info is needed?

Another thought that comes up sometimes is that at time two or more cells cells need to be consumed togheter. That said, usually there is one cell holding the value, while the other controls the logic of accessing it, so should be fine as it is.

That also means balanceFrom/infoFrom needs to be async, right?

Yesss, think for example if these methods are implemented in SSRI.

Are we able to move somewhat closer to #250?

I'm unsure in what perspective we can make them closer.

Just async + client + outpoint should be enough

I have two possible solutions:

  1. Making the filter an array. But I would like to make it private until we see cases really need to access the Udt.filters, since I'm not sure if we will still change that in the future.

Array has an issue: different scripts may have different uses and purposes, as with iCKB Receipt. If it was a named tuple it would make sense for sub-classes, but not for base.

For example, an issue is that consuming each of these cells could be done in a different way, we cannot tell for sure how the Transaction will be modified by consuming a non-UDT cell. iCKB Receipts need header in HeaderDeps, for example.

  1. iCkbUdt can override the completeInputs method.

And all methods using directly UDT.filter, like calculateInfo.

  1. I just ovverride in iCKB UDT the minimal methods like balanceFrom. Dev/user will need to manually add these non-UDT cells, possibly they will not be counted by calculateInfo.

Phroi %38

@Hanssen0
Copy link
Member Author

So, for example, we're dealing with an iCKB receipt. How do we construct the change cell so it's a valid cell?

You just add a UDT cell as usual, if for any reason the transaction has a positive burned amount of iCKB.

Change cell will be always an UDT, even in iCKB. Receipt only tracks deposits in output, so in a way is a kind of change cell in itself.

The iCKB validation at L1 script level is:

in_udt_ickb + in_receipts_ickb == out_udt_ickb + in_deposits_ickb

Glad to hear that we don't need more detailed control currently! This should make things easier.

I have two possible solutions:

  1. Making the filter an array. But I would like to make it private until we see cases really need to access the Udt.filters, since I'm not sure if we will still change that in the future.

Array has an issue: different scripts may have different uses and purposes, as with iCKB Receipt. If it was a named tuple it would make sense for sub-classes, but not for base.

  1. iCkbUdt can override the completeInputs method.

And all methods using directly UDT.filter, like calculateInfo.

Seems like there is no easy solution for this. Perhaps we could create two Udt subclasses, UdtICkb and UdtICkbReceipt, with overridden isUdt methods. This would allow them to recognize others' balances during transaction composition. But when fetching cells on-chain, they only handle their own script.

@phroi
Copy link
Contributor

phroi commented Aug 16, 2025

Seems like there is no easy solution for this. Perhaps we could create two Udt subclasses, UdtICkb and UdtICkbReceipt, with overridden isUdt methods. This would allow them to recognize others' balances during transaction composition. But when fetching cells on-chain, they only handle their own script.

I currently have something along those lines, I need to check the feasibility of Subclassing, but I'm honestly too sleepy now...

If you want to try your hand with a commit, as soon as I wake up I'll check the feasibility!

Wish you a good sleep, night night 🤗

Phroi

@Hanssen0 Hanssen0 force-pushed the feat/udt-query branch 3 times, most recently from 295258e to 25a1301 Compare August 17, 2025 01:01
@Hanssen0
Copy link
Member Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a significant and beneficial refactoring by adding CellAny to represent both on-chain and off-chain cells, which simplifies the core API. It also adds new methods for querying UDT information, improving the library's capabilities. The changes are generally well-implemented and documented. However, I've identified a couple of logical errors in the test files that were introduced during the refactoring. Please address these issues to ensure the tests are correct and provide reliable coverage.

@Hanssen0
Copy link
Member Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a significant and beneficial refactoring by adding CellAny to represent both on-chain and off-chain cells, which helps in reducing code duplication and improving clarity. It also adds new, well-documented UDT info querying methods. The changes are consistent and well-tested. I've provided a few suggestions to simplify some of the new implementations for better readability and maintainability.

@Hanssen0
Copy link
Member Author

#262 should be reviewed first.

await Promise.resolve(change(tx, balanceBurned, false)),
); // Extra capacity introduced by change cell
tx = (
await this.completeInputsByBalance(tx, signer, ccc.Zero, extraCapacity)
Copy link
Contributor

@ashuralyk ashuralyk Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to force extraCapacity to be positive here? because there is an assumption that it always represents the extra capacity offered by the new change cell.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. Let's add a check here to notify devs that there might be issues with the change function.

@Hanssen0 Hanssen0 force-pushed the feat/udt-query branch 2 times, most recently from 3808149 to 18a9d12 Compare January 6, 2026 19:03
@Hanssen0
Copy link
Member Author

Hanssen0 commented Jan 6, 2026

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces valuable methods for querying UDT information, enhancing the library's capabilities. The addition of the UdtInfo class and the refactoring of existing methods for consistency are positive changes. I've identified a couple of opportunities to optimize the performance of the new methods, particularly in how they fetch and process cells, which should improve efficiency for transactions with multiple inputs or outputs.

@Hanssen0
Copy link
Member Author

Hanssen0 commented Jan 6, 2026

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces new methods for querying UDT information and refactors existing code for better consistency and reusability. The addition of the UdtInfo class is a good abstraction. The refactoring to standardize method signatures with client as the first argument and the use of infoFrom improves code quality. The new features are well-tested. I have one suggestion to improve a comment for better clarity in a complex method.

* @param infoLike - The `UdtInfoLike` object to add.
* @returns The current, modified `UdtInfo` instance.
*/
addEq(infoLike: UdtInfoLike) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the Eq in the name addEq?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an equality after an add, just an assignment to self after an add, better names could be:

  • addInPlace
  • addMut
  • addAssign
  • addIn
  • addSet
    ...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addEq means += and it's shorter...!!!

Ok, I accept this suggestion 😭. addEq is not an explanatory name. I will use addAssign.

* @internal
*/
static balanceFromUnsafe(outputData: ccc.HexLike): ccc.Num {
const data = ccc.bytesFrom(outputData).slice(0, 16);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if there are less than 16 bytes but more than 0?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By all means also the 0 case shouldn't happen... but it seemingly does, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/nervosnetwork/ckb-production-scripts/blob/26b0b4f15bb6eeb268b70d7ae006e244b7c06649/c/simple_udt.c#L149

If the data length is less than 16 bytes, it should be an invalid UDT cell.

So I think we should do

    const data = ccc.bytesFrom(outputData).slice(0, 16);
    return data.length < 16 ? ccc.Zero : ccc.numFromBytes(data);

Copy link
Contributor

@phroi phroi Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the current behaviour is reasonable because isUdt one way or another is always called before balanceFromUnsafe, so we already are excluding these previously mentioned edge cases within our code

const isFromLocal = (options?.source ?? "chain") === "local";

return ccc.reduceAsync(
isFromLocal
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't we add a method already in a separate PR doing this switching internally??

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method I refer to is the one that internally switch between getCells and getCellsOnchain, probably was on Signer right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked the code, and this shouldn't have happened yet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I checked too. Still something to keep in mind casue that pattern is pretty common in my code

@phroi
Copy link
Contributor

phroi commented Jan 7, 2026

Once again, I'd like to remind everyone reading that this is phase 2 out of 4 of the CCC UDT master plan:

  1. Try to add more querying methods, like calculating info of a Signer. Meanwhile, we should have methods for parsing a single UDT cell. On this stage, we should consider if these new methods can be overridden to accommodate some unconventional UDT cell requirements.

I reviewed briefly once again, seen a lot of ideas I was working on in my unmerged UDT PR, pretty pleased that you were kind enough to include them back then and now.

When I wake up tomorrow I'll review again, Phroi

P.S.: I like the idea of a branch dedicated to UDT development, so that we don't have to worry about breaking people until the CCC UDT development is more or less completed 👏👏👏

* @public
* @category UDT
*/
export type UdtInfoLike =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see 4 nulls:

  • What's the intended difference in usage undefined and null in this context?
  • Is the additional complexity worth it?

Maybe add a lil comment documenting the usage if we keep it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Later on more nulls, not sure if they are actually needed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an attempt to impove developer-experience by relaxing the type restriction. As we all know, JavaScript has two nullish values, undefined and null, and people have mixed them up for a long time. CCC was only accepting undefined at the beginning until we noticed that ckb-sdk-js uses null as an empty value.

Think of this as the design of TypeLike, both null and undefined are EmptyLike and should be accepted in TypeLike types, and they will be converted to undefined in Type.

return (
(ccc.CellOutput.from(cell.cellOutput).type?.eq(this.script) ?? false) &&
(cell.cellOutput.type?.eq(this.script) ?? false) &&
ccc.bytesFrom(cell.outputData).length >= 16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could improve this recurring pattern for calculating the byte len, how about byteLen(cell.outputData) >= 16

with byteLen defined as:

export function byteLen(data: BytesLike): number {
  if (isHex(data)) {
    return (data.length - 2) / 2;
  }

  return bytesFrom(data).length;
}
```

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be defined in packages/core/src/bytes/index.ts

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to see this, but it's better in a separate PR since it might affect many places.

* signer,
* ([balanceAcc, capacityAcc], cell) => {
* const balance = ccc.udtBalanceFrom(cell.outputData);
* const balance = Udt.balanceFromUnsafe(cell.outputData);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this example where does the isUdt call happen?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! It has been updated.

@phroi
Copy link
Contributor

phroi commented Jan 8, 2026

@Hanssen0 LTGM, I don't have additional comments to this PR.

I'll do that byteLen in a separate PR. Additionally, I'd like to create another PR about the unified getCells, but not sure how/where to add that. Could you provide guidance?

Speaking of PRs, I just noticed that the Epoch PR was merged in releases/next, not in master as I previously targeted. That's why I need to ask a few more questions:

  1. How can I can target an unstable local dev? (For example, new UDT + Epoch + new PRs + new CCC Core changes, useful for preparing the iCKB library and trouble-shooting possible issues, before the actual CCC inclusive of those changes becomes widely available)

  2. Could you explain the general idea of what these CCC branches represent and how they will evolve over time?

Phroi %77

@Hanssen0
Copy link
Member Author

Hanssen0 commented Jan 8, 2026

Speaking of PRs, I just noticed that the Epoch PR was merged in releases/next, not in master as I previously targeted. That's why I need to ask a few more questions:

  1. How can I can target an unstable local dev? (For example, new UDT + Epoch + new PRs + new CCC Core changes, useful for preparing the iCKB library and trouble-shooting possible issues, before the actual CCC inclusive of those changes becomes widely available)
  2. Could you explain the general idea of what these CCC branches represent and how they will evolve over time?

Let me explain my plan (not ready yet):
Master: the main branch, corresponding to the released npm version. All patch changes will be merged to this branch.
Dev: the dev branch. All minor changes will be merged into this branch.
Next: the future branch. All major changes will be merged into this branch.
Releases/*: feature branches. It's useful for unfinished but too big for single-pr things, like UDT.

However, we have already merged some major changes into master a while ago, and I don't want to force-push on it. So I created a release branch for the main branch, releases/next branch for the dev branch, and use master as the future branch.

We will migrate to the first version after publishing the next major version (cleaning the master branch).

My suggestion is to follow the master for everything that has been completed. Considering that UDT is still unfinished, you will need to merge it into master locally before we consider it ready for merging.

@Hanssen0
Copy link
Member Author

Hanssen0 commented Jan 8, 2026

Additionally, I'd like to create another PR about the unified getCells, but not sure how/where to add that. Could you provide guidance?

Let's try to solve this problem by using polymorphism in the Client. We can have a ClientNoCache or pass a parameter to the constructor to turn off caching, or use Client.withCache(cache?: Cache): Client. In this way, we won't have to add a config parameter everywhere or add findCellsOnChain everywhere.

@phroi
Copy link
Contributor

phroi commented Jan 8, 2026

Let me explain my plan (not ready yet):
Master: the main branch, corresponding to the released npm version. All patch changes will be merged to this branch.
Dev: the dev branch. All minor changes will be merged into this branch.
Next: the future branch. All major changes will be merged into this branch.
Releases/*: feature branches. It's useful for unfinished but too big for single-pr things, like UDT.

Gotcha, your WIP plan seems very reasonable!! I'm currently a bit confused due to the transition phase, but I guess that I'll just ask away if any real doubt arise. Glad that you took my Canary feedback to heart! 😁

Let's try to solve this problem by using polymorphism in the Client. We can have a ClientNoCache or pass a parameter to the constructor to turn off caching, or use Client.withCache(cache?: Cache): Client. In this way, we won't have to add a config parameter everywhere or add findCellsOnChain everywhere.

I like the Client.withCache kind of approach, I'll see what I can do in a PR. At that point maybe we can deprecate findCellsOnChain, pointing to the new approach. One thing to note is the handling of empty parameter in the method, cause with the usual Client Constructor, empty means default Cache. I'll think a bit about it and open a PR.

Thank you, Phroi

@Hanssen0
Copy link
Member Author

Hanssen0 commented Jan 9, 2026

One thing to note is the handling of empty parameter in the method, cause with the usual Client Constructor, empty means default Cache.

What about Client.withCacheEnabled(enabled: boolean): Client? In this way, the transition from a non-caching client to a caching client won't require a new cache instance.

@Hanssen0 Hanssen0 merged commit 0ad2a5f into ckb-devrel:releases/udt Jan 9, 2026
17 checks passed
@Hanssen0 Hanssen0 deleted the feat/udt-query branch January 9, 2026 12:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants