From dfccbcb64a8b427e54dc6240fa4a9cf60c32fd52 Mon Sep 17 00:00:00 2001 From: irfan798 Date: Mon, 9 Mar 2026 13:44:58 +0300 Subject: [PATCH 1/2] feat(DetailedAccount): addhexstring to token-id definition --- ur-packages/multi-layer-sync/README.md | 30 +++++++++++-------- .../multi-layer-sync/src/DetailedAccount.ts | 29 ++++++++++-------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/ur-packages/multi-layer-sync/README.md b/ur-packages/multi-layer-sync/README.md index e8e5bfb..280d2a7 100644 --- a/ur-packages/multi-layer-sync/README.md +++ b/ur-packages/multi-layer-sync/README.md @@ -76,6 +76,8 @@ import { DetailedAccount, PortfolioCoin, PortfolioMetadata, Portfolio } from '@n The `DetailedAccount` class represents an account with detailed information, including the HDKey and optional token IDs. +Token IDs for EVM-based chains (ERC20) are encoded using [CBOR tag 263 (hexadecimal string)](https://github.com/toravir/CBOR-Tag-Specs/blob/master/hexString.md), while token IDs for other chains (ERC1155, SPL, ESDT) are encoded as plain text strings (`tstr`). + ```mermaid flowchart TB subgraph DetailedAccount @@ -88,30 +90,34 @@ flowchart TB **CDDL Definition:** ```CDDL account_exp = #6.40303(hdkey) / #6.40308(output-descriptor) +hexstring = #6.263(bytes) -; Accounts are specified using either '#6.40303(hdkey)' or +; Accounts are specified using either '#6.40303(hdkey)' or ; '#6.40308(output-descriptor)'. ; By default, '#6.40303(hdkey)' should be used to share public keys and ; extended public keys. -; '#6.308(output-descriptor)' should be used to share an output descriptor, +; '#6.40308(output-descriptor)' should be used to share an output descriptor, ; e.g. for the different Bitcoin address formats (P2PKH, P2SH-P2WPKH, P2WPKH, P2TR). +; '#6.263(hexstring)' is used to encode token IDs that are hexadecimal strings +; as bytes while preserving their hex semantics. ; Optional 'token-ids' to indicate the synchronization of a list of tokens with ; the associated accounts ; 'token-id' is defined differently depending on the blockchain: -; - ERC20 tokens on EVM chains are identified by their contract addresses -; (e.g. `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`) -; - ERC1155 tokens are identifed with their contract addresses followed by their -; ID with ':' as separator (e.g. `0xfaafdc07907ff5120a76b34b731b278c38d6043c: +; - ERC20 tokens on EVM chains are identified by their contract addresses, +; encoded as '#6.263(hexstring)' (e.g. `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`) +; - ERC1155 tokens are identified with their contract addresses followed by their +; ID with ':' as separator, encoded as tstr since the token ID portion is decimal +; (e.g. `0xfaafdc07907ff5120a76b34b731b278c38d6043c: ; 508851954656174721695133294256171964208`) -; - ESDT tokens on MultiversX are by their name followed by their ID with `-` as -; separator (e.g. `USDC-c76f1f`) -; - SPL tokens on Solana are identified by their contract addresses -; (e.g. `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`) +; - ESDT tokens on MultiversX are by their name followed by their ID with `-` as +; separator, encoded as tstr (e.g. `USDC-c76f1f`) +; - SPL tokens on Solana are identified by their contract addresses, +; encoded as tstr (e.g. `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`) -detailed-account = { +detailed-account = { account: account_exp, - ? token-ids: [+ string / bytes] ; Specify multiple tokens associated to one account + ? token-ids: [+ tstr / hexstring] ; Specify multiple tokens associated to one account } account = 1 diff --git a/ur-packages/multi-layer-sync/src/DetailedAccount.ts b/ur-packages/multi-layer-sync/src/DetailedAccount.ts index 622a667..dd270e7 100644 --- a/ur-packages/multi-layer-sync/src/DetailedAccount.ts +++ b/ur-packages/multi-layer-sync/src/DetailedAccount.ts @@ -19,30 +19,35 @@ export class DetailedAccount extends registryItemFactory({ allowKeysNotInMap: false, CDDL: ` account_exp = #6.40303(hdkey) / #6.40308(output-descriptor) + hexstring = #6.263(bytes) - ; Accounts are specified using either '#6.40303(hdkey)' or + ; Accounts are specified using either '#6.40303(hdkey)' or ; '#6.40308(output-descriptor)'. ; By default, '#6.40303(hdkey)' should be used to share public keys and ; extended public keys. - ; '#6.308(output-descriptor)' should be used to share an output descriptor, + ; '#6.40308(output-descriptor)' should be used to share an output descriptor, ; e.g. for the different Bitcoin address formats (P2PKH, P2SH-P2WPKH, P2WPKH, P2TR). + ; + ; '#6.263(hexstring)' is used to encode token IDs that are hexadecimal strings + ; as bytes while preserving their hex semantics. ; Optional 'token-ids' to indicate the synchronization of a list of tokens with ; the associated accounts ; 'token-id' is defined differently depending on the blockchain: - ; - ERC20 tokens on EVM chains are identified by their contract addresses - ; (e.g. "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") - ; - ERC1155 tokens are identifed with their contract addresses followed by their - ; ID with ':' as separator (e.g. "0xfaafdc07907ff5120a76b34b731b278c38d6043c: + ; - ERC20 tokens on EVM chains are identified by their contract addresses, + ; encoded as '#6.263(hexstring)' (e.g. "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") + ; - ERC1155 tokens are identified with their contract addresses followed by their + ; ID with ':' as separator, encoded as tstr since the token ID portion is decimal + ; (e.g. "0xfaafdc07907ff5120a76b34b731b278c38d6043c: ; 508851954656174721695133294256171964208") - ; - ESDT tokens on MultiversX are by their name followed by their ID with "-" as - ; separator (e.g. "USDC-c76f1f") - ; - SPL tokens on Solana are identified by their contract addresses - ; (e.g. "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") + ; - ESDT tokens on MultiversX are by their name followed by their ID with "-" as + ; separator, encoded as tstr (e.g. "USDC-c76f1f") + ; - SPL tokens on Solana are identified by their contract addresses, + ; encoded as tstr (e.g. "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") - detailed-account = { + detailed-account = { account: account_exp, - ? token-ids: [+ string / bytes] ; Specify multiple tokens associated to one account + ? token-ids: [+ tstr / hexstring] ; Specify multiple tokens associated to one account } account = 1 From d517d93b7d5a303978376201a55cb62a6aa98eb1 Mon Sep 17 00:00:00 2001 From: irfan798 Date: Thu, 23 Apr 2026 15:04:06 +0300 Subject: [PATCH 2/2] refactor(DetailedAccount): align hex-string CDDL + UAI dot form MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename CDDL rule hexstring → hex-string, bytes → bstr across DetailedAccount, HexString, and CoinIdentity (RFC 8610 convention, one name for one concept) - Switch ERC1155/ERC721 example to UAI "contract.tokenId" dot form per NBCR-2024-001, drop the "must be decimal" claim (EIP-1155 token ids are uint256 — rendering is an implementation choice) - Drop ERC20-only framing on HexString promotion (any canonically- hex id is promoted, regardless of chain) - Replace "Shall we really add 0x?" query-comment with a one-line JSDoc explaining the round-trip-parity rule - Collapse HexString's hexadecimal-tag + hex-bytes into a single hex-string = #6.263(bstr) rule - Align CoinIdentity's hex_string → hex-string in CDDL block and JSDoc No wire-format change — tag 263 bytes are byte-for-byte identical, all existing tests pass. Refs LIQ-842 --- ur-packages/coin-identity/README.md | 4 +- ur-packages/coin-identity/src/CoinIdentity.ts | 10 ++-- ur-packages/hex-string/src/HexString.ts | 3 +- ur-packages/hex-string/src/addToRegistry.ts | 2 +- ur-packages/multi-layer-sync/README.md | 32 ++++++------- .../multi-layer-sync/src/DetailedAccount.ts | 48 ++++++++----------- 6 files changed, 46 insertions(+), 53 deletions(-) diff --git a/ur-packages/coin-identity/README.md b/ur-packages/coin-identity/README.md index 64c4add..9e50371 100644 --- a/ur-packages/coin-identity/README.md +++ b/ur-packages/coin-identity/README.md @@ -35,8 +35,8 @@ secp256k1=8 ; SECG secp256k1 curve IESG elliptic_curve = P256 / P384 / P521 / X25519 / X448 / Ed25519 / Ed448 / secp256k1 ; Subtypes specific to some coins (e.g. ChainId for EVM chains) -hex_string = #6.263(bstr) ; byte string is a hexadecimal string no need for decoding -sub_type_exp = uint32 / str / hex_string +hex-string = #6.263(bstr) ; IANA tag 263 — bytes read as a hexadecimal string, no decoding +sub_type_exp = uint32 / str / hex-string coin-identity = { curve: elliptic_curve, diff --git a/ur-packages/coin-identity/src/CoinIdentity.ts b/ur-packages/coin-identity/src/CoinIdentity.ts index 78a30e1..11aea4f 100644 --- a/ur-packages/coin-identity/src/CoinIdentity.ts +++ b/ur-packages/coin-identity/src/CoinIdentity.ts @@ -69,8 +69,8 @@ const CoinIdentityBase: RegistryItemClass = registryItemFactory({ elliptic_curve = P256 / P384 / P521 / X25519 / X448 / Ed25519 / Ed448 / secp256k1 ; Subtypes specific to some coins (e.g. ChainId for EVM chains) - hex_string = #6.263(bstr) ; byte string is a hexadecimal string no need for decoding - sub_type_exp = uint32 / str / hex_string + hex-string = #6.263(bstr) ; IANA tag 263 — bytes read as a hexadecimal string, no decoding + sub_type_exp = uint32 / str / hex-string coin-identity = { curve: elliptic_curve, @@ -102,9 +102,9 @@ const CoinIdentityBase: RegistryItemClass = registryItemFactory({ * * elliptic_curve = P256 / P384 / P521 / X25519 / X448 / Ed25519 / Ed448 / secp256k1 * - * ; Subtypes specific to some coins (e.g. ChainId for EVM chains) - * hex_string = #6.263(bstr) ; byte string is a hexadecimal string no need for decoding - * sub_type_exp = uint32 / str / hex_string + * ; Subtypes specific to some coins (e.g. ChainId for EVM chains) + * hex-string = #6.263(bstr) ; IANA tag 263 — bytes read as a hexadecimal string, no decoding + * sub_type_exp = uint32 / str / hex-string * * coin-identity = { * curve: elliptic_curve, diff --git a/ur-packages/hex-string/src/HexString.ts b/ur-packages/hex-string/src/HexString.ts index c505074..ae67be2 100644 --- a/ur-packages/hex-string/src/HexString.ts +++ b/ur-packages/hex-string/src/HexString.ts @@ -17,8 +17,7 @@ const HexStringBase: RegistryItemClass = registryItemFactory({ tag: 263, URType: 'hex-string', CDDL: ` - hexadecimal-tag = #6.263(hex-bytes) - hex-bytes = bytes + hex-string = #6.263(bstr) `, }) diff --git a/ur-packages/hex-string/src/addToRegistry.ts b/ur-packages/hex-string/src/addToRegistry.ts index 6307682..1dab74d 100644 --- a/ur-packages/hex-string/src/addToRegistry.ts +++ b/ur-packages/hex-string/src/addToRegistry.ts @@ -1,5 +1,5 @@ import { UrRegistry } from '@ngraveio/bc-ur' import { HexString } from './HexString' -// Add hexstring to the global registry +// Add hex-string to the global registry UrRegistry.addItemOnce(HexString); \ No newline at end of file diff --git a/ur-packages/multi-layer-sync/README.md b/ur-packages/multi-layer-sync/README.md index 280d2a7..91e84a9 100644 --- a/ur-packages/multi-layer-sync/README.md +++ b/ur-packages/multi-layer-sync/README.md @@ -76,7 +76,7 @@ import { DetailedAccount, PortfolioCoin, PortfolioMetadata, Portfolio } from '@n The `DetailedAccount` class represents an account with detailed information, including the HDKey and optional token IDs. -Token IDs for EVM-based chains (ERC20) are encoded using [CBOR tag 263 (hexadecimal string)](https://github.com/toravir/CBOR-Tag-Specs/blob/master/hexString.md), while token IDs for other chains (ERC1155, SPL, ESDT) are encoded as plain text strings (`tstr`). +Token IDs that are canonically hexadecimal (e.g. EVM ERC20 contract addresses) are encoded using [CBOR tag 263 (hex-string)](https://github.com/toravir/CBOR-Tag-Specs/blob/master/hexString.md) to halve the on-wire size versus the equivalent `0x…` text. All other identifiers — including compound ids for ERC721/ERC1155 and SPL mint addresses — are encoded as plain text strings (`tstr`), following the UAI `tokenid[.subtype…]` convention from [NBCR-2024-001](https://github.com/ngraveio/Research/blob/main/papers/nbcr-2024-001-unique-asset-id.md). ```mermaid flowchart TB @@ -90,7 +90,7 @@ flowchart TB **CDDL Definition:** ```CDDL account_exp = #6.40303(hdkey) / #6.40308(output-descriptor) -hexstring = #6.263(bytes) +hex-string = #6.263(bstr) ; Accounts are specified using either '#6.40303(hdkey)' or ; '#6.40308(output-descriptor)'. @@ -98,26 +98,26 @@ hexstring = #6.263(bytes) ; extended public keys. ; '#6.40308(output-descriptor)' should be used to share an output descriptor, ; e.g. for the different Bitcoin address formats (P2PKH, P2SH-P2WPKH, P2WPKH, P2TR). -; '#6.263(hexstring)' is used to encode token IDs that are hexadecimal strings -; as bytes while preserving their hex semantics. +; +; 'hex-string' (IANA tag 263) wraps bytes meant to be read as a hexadecimal +; string — the tag preserves hex semantics for display and comparison. ; Optional 'token-ids' to indicate the synchronization of a list of tokens with -; the associated accounts -; 'token-id' is defined differently depending on the blockchain: +; the associated accounts. +; 'token-id' encoding follows the Unique Asset Identifier (UAI) convention — +; see NBCR-2024-001. A token-id is either a scalar identifier or a compound +; "parent.subtype[.subtype...]" form. Current encodings per blockchain: ; - ERC20 tokens on EVM chains are identified by their contract addresses, -; encoded as '#6.263(hexstring)' (e.g. `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`) -; - ERC1155 tokens are identified with their contract addresses followed by their -; ID with ':' as separator, encoded as tstr since the token ID portion is decimal -; (e.g. `0xfaafdc07907ff5120a76b34b731b278c38d6043c: -; 508851954656174721695133294256171964208`) -; - ESDT tokens on MultiversX are by their name followed by their ID with `-` as -; separator, encoded as tstr (e.g. `USDC-c76f1f`) -; - SPL tokens on Solana are identified by their contract addresses, -; encoded as tstr (e.g. `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`) +; encoded as 'hex-string' (e.g. `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`). +; - ERC721 / ERC1155 tokens are identified by contract address plus token id, +; encoded as tstr using the UAI "contract.tokenId" form (e.g. +; `0xfaafdc07907ff5120a76b34b731b278c38d6043c.508851954656174721695133294256171964208`). +; - SPL / Token-2022 on Solana are identified by their mint address, +; encoded as tstr (e.g. `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`). detailed-account = { account: account_exp, - ? token-ids: [+ tstr / hexstring] ; Specify multiple tokens associated to one account + ? token-ids: [+ tstr / hex-string] ; Specify multiple tokens associated to one account } account = 1 diff --git a/ur-packages/multi-layer-sync/src/DetailedAccount.ts b/ur-packages/multi-layer-sync/src/DetailedAccount.ts index dd270e7..2f49ff2 100644 --- a/ur-packages/multi-layer-sync/src/DetailedAccount.ts +++ b/ur-packages/multi-layer-sync/src/DetailedAccount.ts @@ -19,7 +19,7 @@ export class DetailedAccount extends registryItemFactory({ allowKeysNotInMap: false, CDDL: ` account_exp = #6.40303(hdkey) / #6.40308(output-descriptor) - hexstring = #6.263(bytes) + hex-string = #6.263(bstr) ; Accounts are specified using either '#6.40303(hdkey)' or ; '#6.40308(output-descriptor)'. @@ -28,30 +28,29 @@ export class DetailedAccount extends registryItemFactory({ ; '#6.40308(output-descriptor)' should be used to share an output descriptor, ; e.g. for the different Bitcoin address formats (P2PKH, P2SH-P2WPKH, P2WPKH, P2TR). ; - ; '#6.263(hexstring)' is used to encode token IDs that are hexadecimal strings - ; as bytes while preserving their hex semantics. + ; 'hex-string' (IANA tag 263) wraps bytes meant to be read as a hexadecimal + ; string — the tag preserves hex semantics for display and comparison. ; Optional 'token-ids' to indicate the synchronization of a list of tokens with - ; the associated accounts - ; 'token-id' is defined differently depending on the blockchain: + ; the associated accounts. + ; 'token-id' encoding follows the Unique Asset Identifier (UAI) convention — + ; see NBCR-2024-001. A token-id is either a scalar identifier or a compound + ; "parent.subtype[.subtype...]" form. Current encodings per blockchain: ; - ERC20 tokens on EVM chains are identified by their contract addresses, - ; encoded as '#6.263(hexstring)' (e.g. "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") - ; - ERC1155 tokens are identified with their contract addresses followed by their - ; ID with ':' as separator, encoded as tstr since the token ID portion is decimal - ; (e.g. "0xfaafdc07907ff5120a76b34b731b278c38d6043c: - ; 508851954656174721695133294256171964208") - ; - ESDT tokens on MultiversX are by their name followed by their ID with "-" as - ; separator, encoded as tstr (e.g. "USDC-c76f1f") - ; - SPL tokens on Solana are identified by their contract addresses, - ; encoded as tstr (e.g. "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") + ; encoded as 'hex-string' (e.g. "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"). + ; - ERC721 / ERC1155 tokens are identified by contract address plus token id, + ; encoded as tstr using the UAI "contract.tokenId" form (e.g. + ; "0xfaafdc07907ff5120a76b34b731b278c38d6043c.508851954656174721695133294256171964208"). + ; - SPL / Token-2022 on Solana are identified by their mint address, + ; encoded as tstr (e.g. "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"). detailed-account = { account: account_exp, - ? token-ids: [+ tstr / hexstring] ; Specify multiple tokens associated to one account + ? token-ids: [+ tstr / hex-string] ; Specify multiple tokens associated to one account } account = 1 - token-ids = 2 + token-ids = 2 `, }) { data: { @@ -60,23 +59,19 @@ export class DetailedAccount extends registryItemFactory({ } constructor(data: IDetailAccountInput) { - // Token Ids are just hex string for erc20 token like 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 - // For ESDT and SPL types its going to be encoded as string - // We will try to parse the string into bytes with removing 0x part, - // if we cannot we will encode it as utf8 string + // Promote hex-shaped inputs to HexString so they serialise as #6.263(bstr); + // other strings stay tstr. super(data) //@ts-ignore this.data = data let tokenIds = data.tokenIds - // If token ids are provided, convert them to hex strings if (tokenIds !== undefined && Array.isArray(data.tokenIds)) { - // Try to parse them into hex strings + // Try each id as hex; already-HexString passes through, non-hex input + // throws and falls back to tstr. tokenIds = tokenIds.map(tokenId => { - // If its already hexstring, return it - if (tokenId instanceof HexString) return tokenId as HexString - // If its buffer or a string try to parse as HexString + if (tokenId instanceof HexString) return tokenId try { return new HexString(tokenId) } catch (error) { @@ -131,13 +126,12 @@ export class DetailedAccount extends registryItemFactory({ public getOutputDescriptor = () => { return this.data.account instanceof OutputDescriptor ? this.data.account : undefined } + /** Hex-encoded token ids are rendered with a `0x` prefix for round-trip parity. */ public getTokenIds = () => { if (!this.data.tokenIds) return undefined - // Always return string representation of the token ids return this.data.tokenIds.map((tokenId: HexString | string) => { if (tokenId instanceof HexString) { - // Shall we really add 0x to start? return '0x' + tokenId.getData() } return tokenId