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 e8e5bfb..91e84a9 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 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 subgraph DetailedAccount @@ -88,30 +90,34 @@ flowchart TB **CDDL Definition:** ```CDDL account_exp = #6.40303(hdkey) / #6.40308(output-descriptor) +hex-string = #6.263(bstr) -; 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). +; +; '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: -; - 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: -; 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`) - -detailed-account = { +; 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 '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: [+ string / bytes] ; 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 622a667..2f49ff2 100644 --- a/ur-packages/multi-layer-sync/src/DetailedAccount.ts +++ b/ur-packages/multi-layer-sync/src/DetailedAccount.ts @@ -19,34 +19,38 @@ export class DetailedAccount extends registryItemFactory({ allowKeysNotInMap: false, CDDL: ` account_exp = #6.40303(hdkey) / #6.40308(output-descriptor) + hex-string = #6.263(bstr) - ; 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). + ; + ; '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: - ; - 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: - ; 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") - - detailed-account = { + ; 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 '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: [+ string / bytes] ; 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: { @@ -55,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) { @@ -126,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