From 42829dccb243b1160c301886bd53725fbb76bb7f Mon Sep 17 00:00:00 2001 From: roger-gan Date: Mon, 31 Aug 2026 19:12:23 +0800 Subject: [PATCH 1/2] fix(tron): use decimal CAIP-2 identifiers --- .claude/agents/code-reviewer.md | 2 +- .claude/rules/CLAUDE.md | 2 +- .claude/rules/common/conventions.md | 4 +- .claude/rules/networks/tron.md | 10 +- CLAUDE.md | 4 +- README.md | 8 +- examples/typescript/.env-batch.example | 10 +- examples/typescript/.env-exact.example | 6 +- examples/typescript/.env-gasfree.example | 6 +- examples/typescript/.env-multi-scheme.example | 6 +- examples/typescript/.env-upto.example | 10 +- examples/typescript/README.md | 10 +- examples/typescript/clients/mcp/README.md | 2 +- .../typescript/facilitator/basic/README.md | 2 +- .../facilitator/batch-settlement/README.md | 2 +- .../typescript/facilitator/gasfree/README.md | 2 +- .../typescript/facilitator/upto/README.md | 4 +- .../servers/batch-settlement/README.md | 2 +- examples/typescript/servers/express/README.md | 2 +- examples/typescript/servers/mcp/README.md | 2 +- .../typescript/servers/multi-scheme/README.md | 4 +- examples/typescript/servers/upto/README.md | 2 +- specs/CONTRIBUTING.md | 2 +- .../scheme_batch_settlement_tron.md | 6 +- .../scheme_exact_gasfree_tron.md | 8 +- specs/schemes/exact/scheme_exact_tron.md | 15 +-- specs/schemes/upto/scheme_upto_tron.md | 8 +- specs/transports-v2/mcp.md | 6 +- specs/x402-specification-v2.md | 14 ++- typescript/.changeset/tron-decimal-caip2.md | 6 ++ .../src/observability/facilitatorLogging.ts | 2 +- .../observability/resourceServerLogging.ts | 2 +- typescript/packages/core/src/utils/index.ts | 8 ++ .../core/test/unit/utils/utils.test.ts | 11 +++ typescript/packages/http/fetch/src/index.ts | 2 +- .../batch-settlement/refund-network.test.ts | 2 +- typescript/packages/mechanisms/tron/README.md | 18 ++-- .../mechanisms/tron/src/approvalPolicy.ts | 12 ++- .../src/batch-settlement/client/voucher.ts | 2 +- .../batch-settlement/facilitator/scheme.ts | 5 +- .../src/batch-settlement/server/scheme.ts | 2 +- .../src/batch-settlement/server/verify.ts | 4 +- .../packages/mechanisms/tron/src/constants.ts | 51 +++++----- .../tron/src/exact/client/register.ts | 7 +- .../tron/src/exact/facilitator/eip3009.ts | 3 +- .../exact/facilitator/permit2Verification.ts | 3 +- .../tron/src/exact/facilitator/register.ts | 6 +- .../tron/src/exact/server/register.ts | 3 +- .../tron/src/gasfree/client/register.ts | 5 +- .../tron/src/gasfree/client/scheme.ts | 5 +- .../tron/src/gasfree/facilitator/register.ts | 9 +- .../tron/src/gasfree/facilitator/scheme.ts | 5 +- .../tron/src/gasfree/server/register.ts | 5 +- .../packages/mechanisms/tron/src/index.ts | 1 + .../packages/mechanisms/tron/src/network.ts | 96 +++++++++++++++++++ .../resource-sponsoring/memoryCoordinator.ts | 3 +- .../tron/src/resource-sponsoring/policy.ts | 10 +- .../packages/mechanisms/tron/src/rpc.ts | 36 ++++--- .../batch-settlement/authorizerSigner.ts | 4 +- .../src/shared/batch-settlement/constants.ts | 45 +++++---- .../tron/src/shared/batch-settlement/utils.ts | 2 +- .../tron/src/shared/defaultAssets.ts | 2 +- .../shared/extensions/resourceSponsoring.ts | 3 +- .../mechanisms/tron/src/shared/gasfree/api.ts | 3 +- .../tron/src/shared/gasfree/config.ts | 41 ++++---- .../mechanisms/tron/src/shared/tokens.ts | 26 ++--- .../packages/mechanisms/tron/src/signer.ts | 13 +-- .../tron/src/upto/facilitator/permit2.ts | 3 +- .../packages/mechanisms/tron/src/utils.ts | 5 +- .../test/unit/advisory-fee-removal.test.ts | 2 +- .../test/unit/batch-refund-network.test.ts | 6 +- .../unit/batch-settlement/deposit-sig.test.ts | 2 +- .../test/unit/batch-settlement/digest.test.ts | 2 +- .../unit/batch-settlement/lifecycle.test.ts | 2 +- .../batch-settlement/server-hooks.test.ts | 2 +- .../tron/test/unit/client-allowance.test.ts | 2 +- .../tron/test/unit/facilitator-wallet.test.ts | 2 +- .../tron/test/unit/flow-integration.test.ts | 2 +- .../tron/test/unit/gasfree-digest.test.ts | 2 +- .../tron/test/unit/gasfree-flow.test.ts | 4 +- .../mechanisms/tron/test/unit/network.test.ts | 43 +++++++++ .../tron/test/unit/permit2-digest.test.ts | 2 +- .../mechanisms/tron/test/unit/rpc.test.ts | 16 ++-- .../tron/test/unit/selection.test.ts | 2 +- .../test/unit/server.moneyConversion.test.ts | 4 +- .../tron/test/unit/signer-wallet.test.ts | 24 ++++- .../mechanisms/tron/test/unit/tokens.test.ts | 61 ++++++------ ...pproval-resource-sponsoring-client.test.ts | 4 +- ...oval-resource-sponsoring-validator.test.ts | 2 +- .../trc20-resource-sponsoring-runtime.test.ts | 6 +- .../trc20-resource-sponsoring-tronweb.test.ts | 30 +++--- .../tron/test/unit/upto-flow.test.ts | 2 +- 92 files changed, 554 insertions(+), 302 deletions(-) create mode 100644 typescript/.changeset/tron-decimal-caip2.md create mode 100644 typescript/packages/mechanisms/tron/src/network.ts create mode 100644 typescript/packages/mechanisms/tron/test/unit/network.test.ts diff --git a/.claude/agents/code-reviewer.md b/.claude/agents/code-reviewer.md index daab77b8..c751383b 100644 --- a/.claude/agents/code-reviewer.md +++ b/.claude/agents/code-reviewer.md @@ -12,7 +12,7 @@ General code review for x402 changes. Treat like a normal `code-reviewer`, but b 1. **Wire format**: any touch to `PaymentRequired`, `PaymentPayload`, or `PaymentResponse` serialization must match [specs/protocol.md](../../specs/protocol.md) and not break [specs/001-exact-v2-compat/spec.md](../../specs/001-exact-v2-compat/spec.md). 2. **Scheme payload**: check that `payload.authorization` is used (not legacy `extensions.transferAuthorization`) for `exact`. 3. **Pipeline step ordering**: client payment selection is 5 steps in [specs/roles.md](../../specs/roles.md). Policies go at step 5. -4. **Mechanism registration**: exact-match network keys (`tron:0xcd8690dc`) win over wildcards (`tron:*`). Flag changes to registration logic. +4. **Mechanism registration**: exact-match network keys (`tron:3448148188`) win over wildcards (`tron:*`). Flag changes to registration logic. 5. **Lowercase addresses**: all address/selector/topic strings lowercase. Flag any uppercase leak. 6. **Header encoding**: `Base64(UTF-8(JSON.stringify(...)))`, never raw JSON in the header value. diff --git a/.claude/rules/CLAUDE.md b/.claude/rules/CLAUDE.md index 39f83513..3ac7fd9f 100644 --- a/.claude/rules/CLAUDE.md +++ b/.claude/rules/CLAUDE.md @@ -35,7 +35,7 @@ When multiple rule files apply, read all of them before editing. Rules are layer │ └── exact-gasfree.md # TRON GasFree custodial + TIP-712 relayer ├── networks/ │ ├── evm.md # eip155:, contracts, signing -│ └── tron.md # tron:, TIP-712 hex-address rule, GasFree link +│ └── tron.md # tron:, TIP-712 hex-address rule, GasFree link ├── typescript/ │ └── conventions.md # pnpm/turbo, ESM, viem/tronweb, fork+overlay, signer factories └── testing/ diff --git a/.claude/rules/common/conventions.md b/.claude/rules/common/conventions.md index 5e9770e2..628e549a 100644 --- a/.claude/rules/common/conventions.md +++ b/.claude/rules/common/conventions.md @@ -6,7 +6,7 @@ Repository-wide conventions that apply to every component, every scheme. Break t - **Lowercase everything**: addresses, selectors, event topics. - **TRON + EIP/TIP-712**: convert TRON Base58 addresses (`T...`) to **0x-prefixed EVM hex** before passing to any EIP-712 / TIP-712 signer. This applies to `verifyingContract`, `token`, `serviceProvider`, `user`, `receiver`, and any other `address`-typed field, **both in the domain and in the message**. Use `tronAddressToEvm` / `normalizeAddressForSigning` from `mechanisms/tron/src/utils.ts` — do not reimplement. See [docs/solutions.md entry #1](../../../docs/solutions.md). -- **Network identifiers**: CAIP-2 `eip155:` for EVM, `tron:` for TRON (e.g. `tron:0xcd8690dc` for Nile). Detection rule: `tron:` prefix → TRON, `eip155:` prefix → EVM. Prefer the exported constants `TRON_MAINNET` / `TRON_NILE` / `TRON_SHASTA` from `@bankofai/x402-tron` over copying the hex strings. +- **Network identifiers**: CAIP-2 `eip155:` for EVM and `tron:` for TRON (e.g. `tron:3448148188` for Nile). Detection rule: `tron:` prefix → TRON, `eip155:` prefix → EVM. Prefer the exported constants `TRON_MAINNET` / `TRON_NILE` / `TRON_SHASTA` from `@bankofai/x402-tron` over copying identifiers. Deprecated `tron:0x...` values are input aliases only. ## Amounts & fees @@ -36,7 +36,7 @@ Repository-wide conventions that apply to every component, every scheme. Break t ## Mechanism registration -- **Exact match** (e.g. `"tron:0xcd8690dc"`) outranks **wildcard** (e.g. `"tron:*"`). Register more specific first, or accept that exact-match mechanisms win the lookup regardless of insertion order. +- **Exact match** (e.g. `"tron:3448148188"`) outranks **wildcard** (e.g. `"tron:*"`). Register more specific first, or accept that exact-match mechanisms win the lookup regardless of insertion order. - Each scheme class is registered per `(network, scheme)` with `new + register` (no thin wrapper). Do not conflate schemes across mechanisms; register each scheme-network pair explicitly. See [typescript/CLAUDE.md](../../../typescript/CLAUDE.md) "Scheme API surface". ## Policy pipeline diff --git a/.claude/rules/networks/tron.md b/.claude/rules/networks/tron.md index 8057d0fa..72743497 100644 --- a/.claude/rules/networks/tron.md +++ b/.claude/rules/networks/tron.md @@ -2,13 +2,13 @@ Mechanisms: `typescript/packages/mechanisms/tron` (`@bankofai/x402-tron`). **In-house, no upstream** — edit directly. -Identifier: `tron:` per CAIP-2 (hex chain id as the reference, not a human-readable name). Canonical constants: `TRON_MAINNET`, `TRON_NILE`, `TRON_SHASTA` exported from `@bankofai/x402-tron`. +Identifier: `tron:` per CAIP-2. Canonical constants: `TRON_MAINNET`, `TRON_NILE`, `TRON_SHASTA` exported from `@bankofai/x402-tron`. Deprecated `tron:0x...` identifiers are accepted as input aliases but must not be emitted by new code. -| Constant | Identifier | Chain ID (decimal) | Chain ID (hex) | +| Constant | Canonical identifier | TIP-712 chain ID | Deprecated hex alias | |---|---|---|---| -| `TRON_MAINNET` | `tron:0x2b6653dc` | 728126428 | 0x2b6653dc | -| `TRON_SHASTA` | `tron:0x94a9059e` | 2494104990 | 0x94a9059e | -| `TRON_NILE` | `tron:0xcd8690dc` | 3448148188 | 0xcd8690dc | +| `TRON_MAINNET` | `tron:728126428` | 728126428 | 0x2b6653dc | +| `TRON_SHASTA` | `tron:2494104990` | 2494104990 | 0x94a9059e | +| `TRON_NILE` | `tron:3448148188` | 3448148188 | 0xcd8690dc | Chain id helper: `getTronChainId(network)` in `mechanisms/tron/src/utils.ts`. diff --git a/CLAUDE.md b/CLAUDE.md index 32107228..48c6f6ca 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,8 +37,8 @@ Each component has its own guidance with build/test commands and conventions whe - **Addresses, selectors, event topics**: lowercase-normalized everywhere. TRON addresses are converted to **0x-prefixed EVM hex** before any EIP-712/TIP-712 signing — see [docs/solutions.md entry #1](docs/solutions.md). - **Payment ID**: 16 random bytes, encoded as `0x` + 32 hex chars, signed as `bytes16`. - **HTTP header encoding**: `Base64(UTF-8(JSON.stringify(object)))` for `PAYMENT-REQUIRED`, `PAYMENT-SIGNATURE`, `PAYMENT-RESPONSE`. -- **Network identifiers**: CAIP-2 `eip155:` for EVM; `tron:` for TRON (e.g. `tron:0xcd8690dc` for Nile). -- **Mechanism registration**: `tron:0xcd8690dc` (exact match, higher priority) beats `tron:*` (wildcard, lower priority). +- **Network identifiers**: CAIP-2 `eip155:` for EVM; `tron:` for TRON (e.g. `tron:3448148188` for Nile). Deprecated `tron:0x...` values are accepted only as compatibility inputs. +- **Mechanism registration**: `tron:3448148188` (exact match, higher priority) beats `tron:*` (wildcard, lower priority). - **Commit messages**: `(): ` — e.g. `fix(tron): preserve raw_data_hex in tron approvals`. ## Branch workflow diff --git a/README.md b/README.md index c22fd0dc..10a45957 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ const wallet = /* resolve your @bankofai/agent-wallet */; // Permit2 approve that USDT/USDD need. When TRON_GRID_API_KEY is unset, mainnet // RPC falls back to a BankofAI-operated endpoint. const signer = await createClientTronSigner(wallet, { - network: "tron:0xcd8690dc", + network: "tron:3448148188", apiKey: process.env.TRON_GRID_API_KEY, }); @@ -146,9 +146,9 @@ x402 currently supports TRC-20 tokens on the TRON network and BEP-20 tokens on t | Network | ID | Status | Recommended For | |---------|----|--------|-----------------| -| **TRON Nile** | `tron:0xcd8690dc` | Testnet | **Development & Testing** | -| **TRON Shasta** | `tron:0x94a9059e` | Testnet | Alternative Testing | -| **TRON Mainnet** | `tron:0x2b6653dc` | Mainnet | Production | +| **TRON Nile** | `tron:3448148188` | Testnet | **Development & Testing** | +| **TRON Shasta** | `tron:2494104990` | Testnet | Alternative Testing | +| **TRON Mainnet** | `tron:728126428` | Mainnet | Production | | **BSC Testnet** | `eip155:97` | Testnet | **Development & Testing** | | **BSC Mainnet** | `eip155:56` | Mainnet | Production | diff --git a/examples/typescript/.env-batch.example b/examples/typescript/.env-batch.example index 1db62999..566f2673 100644 --- a/examples/typescript/.env-batch.example +++ b/examples/typescript/.env-batch.example @@ -22,12 +22,12 @@ TRON_GRID_API_KEY= # Which chains to exercise, one burst (+refund) per entry, in order. Each entry is # [@] network: CAIP-2 chain id — # EVM: "eip155:97" (BSC testnet), "eip155:56" (BSC mainnet) -# TRON: "tron:0xcd8690dc" (Nile testnet), "tron:0x94a9059e" (Shasta testnet), "tron:0x2b6653dc" (mainnet) +# TRON: "tron:3448148188" (Nile testnet), "tron:2494104990" (Shasta testnet), "tron:728126428" (mainnet) # Shortcuts: "eip155" or "tron" match any network of that family. # token optional (each chain advertises one: EVM=USDC, TRON=USDT) — symbol or # asset address. Use `@` (not `#`) — dotenv treats `#` as a comment. # Unset ⇒ each configured chain once. -PAY_TARGETS=eip155:97,tron:0xcd8690dc +PAY_TARGETS=eip155:97,tron:3448148188 # The resource the client fetches (server route). RESOURCE_URL=http://localhost:4041/weather @@ -48,9 +48,9 @@ REFUND_AFTER_REQUESTS=false # refund the remaining channel balance after the bur # ── SERVER · the resource provider (servers/batch-settlement) ───────────── # Payout addresses (set the chain(s) you want to accept). EVM_ADDRESS=0x... # BSC, eip155:97 (token USDC, permit2) -TRON_ADDRESS=T... # TRON Nile (tron:0xcd8690dc); Shasta: tron:0x94a9059e; mainnet: tron:0x2b6653dc (token USDT, permit2) -# TRON network: tron:0xcd8690dc (Nile, default), tron:0x94a9059e (Shasta), tron:0x2b6653dc (mainnet) -# TRON_NETWORK=tron:0xcd8690dc +TRON_ADDRESS=T... # TRON Nile (tron:3448148188); Shasta: tron:2494104990; mainnet: tron:728126428 (token USDT, permit2) +# TRON network: tron:3448148188 (Nile, default), tron:2494104990 (Shasta), tron:728126428 (mainnet) +# TRON_NETWORK=tron:3448148188 # Where the server binds, and where it reaches the facilitator. SERVER_PORT=4041 diff --git a/examples/typescript/.env-exact.example b/examples/typescript/.env-exact.example index 86b24549..7ec3fc08 100644 --- a/examples/typescript/.env-exact.example +++ b/examples/typescript/.env-exact.example @@ -26,13 +26,13 @@ TRON_GRID_API_KEY= # Which chains/tokens to pay, one payment per entry, in order. # [@] network: CAIP-2 chain id — # EVM: "eip155:97" (BSC testnet), "eip155:56" (BSC mainnet) -# TRON: "tron:0xcd8690dc" (Nile testnet), "tron:0x94a9059e" (Shasta testnet), "tron:0x2b6653dc" (mainnet) +# TRON: "tron:3448148188" (Nile testnet), "tron:2494104990" (Shasta testnet), "tron:728126428" (mainnet) # Shortcuts: "eip155" or "tron" match any network of that family. # token: symbol (DHLU/USDC/USDT on EVM, USDT/USDD on TRON) or an asset address; # omit the token to use that network's first advertised token. # Use `@` (not `#`) — dotenv treats `#` as a comment and would truncate the value. # Unset ⇒ each configured chain once, with its first token. -PAY_TARGETS=eip155:97@DHLU,tron:0xcd8690dc@USDT +PAY_TARGETS=eip155:97@DHLU,tron:3448148188@USDT # The resource the client fetches (server route). RESOURCE_URL=http://localhost:4021/weather @@ -43,7 +43,7 @@ MCP_SERVER_URL=http://localhost:4023 # ── SERVER · the resource provider (servers/express · servers/mcp) ──────── # Payout addresses (set the chain(s) you want to accept). EVM_ADDRESS=0x... # BSC, eip155:97 -TRON_ADDRESS=T... # TRON Nile (tron:0xcd8690dc); Shasta: tron:0x94a9059e; mainnet: tron:0x2b6653dc +TRON_ADDRESS=T... # TRON Nile (tron:3448148188); Shasta: tron:2494104990; mainnet: tron:728126428 # Where the server binds, and where it reaches the facilitator. SERVER_PORT=4021 diff --git a/examples/typescript/.env-gasfree.example b/examples/typescript/.env-gasfree.example index ccfdbabc..82189df8 100644 --- a/examples/typescript/.env-gasfree.example +++ b/examples/typescript/.env-gasfree.example @@ -30,9 +30,9 @@ RESOURCE_URL=http://localhost:4031/weather # ── SERVER · the resource provider (servers/gasfree) ────────────────────── # Payout address (TRON only). -TRON_ADDRESS=T... # TRON Nile (tron:0xcd8690dc); Shasta: tron:0x94a9059e; mainnet: tron:0x2b6653dc -# TRON network: tron:0xcd8690dc (Nile, default), tron:0x94a9059e (Shasta), tron:0x2b6653dc (mainnet) -# TRON_NETWORK=tron:0xcd8690dc +TRON_ADDRESS=T... # TRON Nile (tron:3448148188); Shasta: tron:2494104990; mainnet: tron:728126428 +# TRON network: tron:3448148188 (Nile, default), tron:2494104990 (Shasta), tron:728126428 (mainnet) +# TRON_NETWORK=tron:3448148188 # Where the server binds, and where it reaches the facilitator. SERVER_PORT=4031 diff --git a/examples/typescript/.env-multi-scheme.example b/examples/typescript/.env-multi-scheme.example index 3e774962..fa4a5a01 100644 --- a/examples/typescript/.env-multi-scheme.example +++ b/examples/typescript/.env-multi-scheme.example @@ -10,9 +10,9 @@ # ── Resource-server payout addresses (set the chain(s) you want to accept) ─ EVM_ADDRESS=0x... # BSC, eip155:97 (omit to disable EVM) -TRON_ADDRESS=T... # TRON Nile (tron:0xcd8690dc); Shasta: tron:0x94a9059e; mainnet: tron:0x2b6653dc (omit to disable TRON) -# TRON network: tron:0xcd8690dc (Nile, default), tron:0x94a9059e (Shasta), tron:0x2b6653dc (mainnet) -# TRON_NETWORK=tron:0xcd8690dc +TRON_ADDRESS=T... # TRON Nile (tron:3448148188); Shasta: tron:2494104990; mainnet: tron:728126428 (omit to disable TRON) +# TRON network: tron:3448148188 (Nile, default), tron:2494104990 (Shasta), tron:728126428 (mainnet) +# TRON_NETWORK=tron:3448148188 # ── Facilitator URLs (comma-separated) ──────────────────────────────────── # basic settles `exact` (EVM + TRON permit2); gasfree settles `exact_gasfree`. diff --git a/examples/typescript/.env-upto.example b/examples/typescript/.env-upto.example index ee474b2f..76e4edaa 100644 --- a/examples/typescript/.env-upto.example +++ b/examples/typescript/.env-upto.example @@ -23,12 +23,12 @@ TRON_GRID_API_KEY= # Which chains to exercise, one N-request run per entry, in order. Each entry is # [@] network: CAIP-2 chain id — # EVM: "eip155:97" (BSC testnet), "eip155:56" (BSC mainnet) -# TRON: "tron:0xcd8690dc" (Nile testnet), "tron:0x94a9059e" (Shasta testnet), "tron:0x2b6653dc" (mainnet) +# TRON: "tron:3448148188" (Nile testnet), "tron:2494104990" (Shasta testnet), "tron:728126428" (mainnet) # Shortcuts: "eip155" or "tron" match any network of that family. # token optional (each chain advertises one: EVM=USDC, TRON=USDT) — symbol or # asset address. Use `@` (not `#`) — dotenv treats `#` as a comment. # Unset ⇒ each configured chain once. -PAY_TARGETS=eip155:97,tron:0xcd8690dc +PAY_TARGETS=eip155:97,tron:3448148188 # The resource the client fetches (server route). RESOURCE_URL=http://localhost:4051/generate @@ -44,9 +44,9 @@ NUMBER_OF_REQUESTS=3 # ── SERVER · the resource provider (servers/upto) ───────────────────────── # Payout addresses (set the chain(s) you want to accept). EVM_ADDRESS=0x... # BSC, eip155:97 (token USDC, permit2) -TRON_ADDRESS=T... # TRON Nile (tron:0xcd8690dc); Shasta: tron:0x94a9059e; mainnet: tron:0x2b6653dc (token USDT, permit2) -# TRON network: tron:0xcd8690dc (Nile, default), tron:0x94a9059e (Shasta), tron:0x2b6653dc (mainnet) -# TRON_NETWORK=tron:0xcd8690dc +TRON_ADDRESS=T... # TRON Nile (tron:3448148188); Shasta: tron:2494104990; mainnet: tron:728126428 (token USDT, permit2) +# TRON network: tron:3448148188 (Nile, default), tron:2494104990 (Shasta), tron:728126428 (mainnet) +# TRON_NETWORK=tron:3448148188 # Where the server binds, and where it reaches the facilitator. SERVER_PORT=4051 diff --git a/examples/typescript/README.md b/examples/typescript/README.md index 5023d962..0a4b9574 100644 --- a/examples/typescript/README.md +++ b/examples/typescript/README.md @@ -120,8 +120,8 @@ not contain private keys. Never reuse one wallet for both roles. | Chain | Network | Token | Scheme | One-time approve | User pays gas? | |---|---|---|---|---|---| -| TRON nile | `tron:0xcd8690dc` | USDT (6) | exact **permit2** | yes — client auto-broadcasts | only the approve (TRX) | -| TRON nile | `tron:0xcd8690dc` | USDD (18) | exact **permit2** | yes — client auto-broadcasts | only the approve (TRX) | +| TRON nile | `tron:3448148188` | USDT (6) | exact **permit2** | yes — client auto-broadcasts | only the approve (TRX) | +| TRON nile | `tron:3448148188` | USDD (18) | exact **permit2** | yes — client auto-broadcasts | only the approve (TRX) | | BSC testnet | `eip155:97` | DHLU (6) | exact **eip3009** | none | **none (fully gasless)** | | BSC testnet | `eip155:97` | USDC (18) | exact **permit2 + gas-sponsoring** | yes — client signs, facilitator relays | only the approve (BNB) | | BSC testnet | `eip155:97` | USDT (18) | exact **permit2 + gas-sponsoring** | yes — client signs, facilitator relays | only the approve (BNB) | @@ -151,7 +151,7 @@ sponsorship). ### Mainnet (real funds) -Mainnet networks (`eip155:56`, `tron:0x2b6653dc`) are registered alongside their +Mainnet networks (`eip155:56`, `tron:728126428`) are registered alongside their testnet counterparts in the same tables — no uncommenting needed. The server advertises both networks, and the client selects which one to pay through `PAY_TARGETS`. The example uses one `EVM_ADDRESS` / `TRON_ADDRESS` payout address @@ -164,8 +164,8 @@ All verified on-chain; sources: the official |---|---|---|---|---| | `eip155:56` (BSC) | USDC | `0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d` | 18 | permit2 | | `eip155:56` (BSC) | USDT | `0x55d398326f99059fF775485246999027B3197955` | 18 | permit2 | -| `tron:0x2b6653dc` | USDT | `TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t` | 6 | permit2 | -| `tron:0x2b6653dc` | USDD | `TXDk8mbtRbXeYuMNS83CfKPaYYT8XWv9Hz` | 18 | permit2 | +| `tron:728126428` | USDT | `TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t` | 6 | permit2 | +| `tron:728126428` | USDD | `TXDk8mbtRbXeYuMNS83CfKPaYYT8XWv9Hz` | 18 | permit2 | > BSC mainnet has **no ERC-3009 token** (USDC/USDT are plain BEP-20) — so > mainnet EVM payments all go permit2 + gas-sponsored approve. DHLU (eip3009) is diff --git a/examples/typescript/clients/mcp/README.md b/examples/typescript/clients/mcp/README.md index 4a31b37f..4ef9b2bd 100644 --- a/examples/typescript/clients/mcp/README.md +++ b/examples/typescript/clients/mcp/README.md @@ -2,7 +2,7 @@ Connects to an MCP server over SSE and calls its tools. On a 402, the SDK pays automatically using whichever registered scheme matches the server's offered -network — **BSC testnet** (`eip155:97`) or **TRON Nile** (`tron:0xcd8690dc`). +network — **BSC testnet** (`eip155:97`) or **TRON Nile** (`tron:3448148188`). Both chains are registered in one `createx402MCPClient({ schemes })` call; the payment-selection pipeline filters by the offered network and picks a match. Each diff --git a/examples/typescript/facilitator/basic/README.md b/examples/typescript/facilitator/basic/README.md index 37da78f2..6e54d048 100644 --- a/examples/typescript/facilitator/basic/README.md +++ b/examples/typescript/facilitator/basic/README.md @@ -36,7 +36,7 @@ provider matrix. | Chain | Network | Scheme | |---|---|---| | EVM | `eip155:97` (BSC testnet) | `exact` — DHLU via eip3009, USDC via permit2 + gas-sponsored approve | -| TRON | `tron:0xcd8690dc` | `exact` — USDT/USDD via permit2 (auto-approve) | +| TRON | `tron:3448148188` | `exact` — USDT/USDD via permit2 (auto-approve) | Add another EVM network (e.g. Base Sepolia) by adding one entry to the `EVM_NETWORKS` table in `src/chains/evm.ts`. The ERC-20 approval gas-sponsoring diff --git a/examples/typescript/facilitator/batch-settlement/README.md b/examples/typescript/facilitator/batch-settlement/README.md index 18c44c4c..3ef02c14 100644 --- a/examples/typescript/facilitator/batch-settlement/README.md +++ b/examples/typescript/facilitator/batch-settlement/README.md @@ -41,7 +41,7 @@ server. Endpoints: `POST /verify`, `POST /settle`, `GET /supported`. | Chain | CAIP-2 | Contract | Token (advertised by server) | |---|---|---|---| | BSC testnet | `eip155:97` | deterministic singleton `0x4020…0003` | USDC (Permit2) | -| TRON Nile | `tron:0xcd8690dc` | `TWBwWHZWwH8TzrZnbxit1J645VGYY1K2fA` | USDT (Permit2) | +| TRON Nile | `tron:3448148188` | `TWBwWHZWwH8TzrZnbxit1J645VGYY1K2fA` | USDT (Permit2) | Add a chain by extending `EVM_NETWORKS` in [`src/chains/evm.ts`](src/chains/evm.ts) or adapting [`src/chains/tron.ts`](src/chains/tron.ts). diff --git a/examples/typescript/facilitator/gasfree/README.md b/examples/typescript/facilitator/gasfree/README.md index bffc34e0..19c28f3c 100644 --- a/examples/typescript/facilitator/gasfree/README.md +++ b/examples/typescript/facilitator/gasfree/README.md @@ -4,7 +4,7 @@ Verifies and settles TRON **GasFree** payments. Unlike the `exact` facilitator i does **not** broadcast a raw TRON transaction — it forwards the signed TIP-712 permit to the **GasFree relayer API**, which pays the on-chain energy and submits. -- Scheme: `exact_gasfree` on `tron:0xcd8690dc`. +- Scheme: `exact_gasfree` on `tron:3448148188`. - Port `4032`. - Key custody in `@bankofai/agent-wallet`; the TronWeb instance holds no key. diff --git a/examples/typescript/facilitator/upto/README.md b/examples/typescript/facilitator/upto/README.md index 3fb949a1..44a0edb7 100644 --- a/examples/typescript/facilitator/upto/README.md +++ b/examples/typescript/facilitator/upto/README.md @@ -31,7 +31,7 @@ server and client. ## Notes - The upto Permit2 proxy is a deterministic CREATE2 singleton on EVM (same - address on every chain) and is deployed per-network on TRON (`tron:0xcd8690dc` / - `tron:0x2b6653dc`). + address on every chain) and is deployed per-network on TRON (`tron:3448148188` / + `tron:728126428`). - The settled amount is bounded by the client's Permit2 witness — the facilitator cannot settle more than the authorized maximum. diff --git a/examples/typescript/servers/batch-settlement/README.md b/examples/typescript/servers/batch-settlement/README.md index 3c8301db..8f323442 100644 --- a/examples/typescript/servers/batch-settlement/README.md +++ b/examples/typescript/servers/batch-settlement/README.md @@ -14,7 +14,7 @@ supplies the `receiverAuthorizer` (fetched via `/supported`). - Set the payout address(es) you want to accept: - `EVM_ADDRESS` → BSC (`eip155:97`), token USDC (Permit2) - - `TRON_ADDRESS` → TRON Nile (`tron:0xcd8690dc`), token USDT (Permit2) + - `TRON_ADDRESS` → TRON Nile (`tron:3448148188`), token USDT (Permit2) - A running batch-settlement facilitator (default `FACILITATOR_URL=http://localhost:4042`). ## Run diff --git a/examples/typescript/servers/express/README.md b/examples/typescript/servers/express/README.md index 22de6dc6..6600c41c 100644 --- a/examples/typescript/servers/express/README.md +++ b/examples/typescript/servers/express/README.md @@ -29,7 +29,7 @@ Then pay it with the fetch client (`../../clients/fetch`). | Chain | Network | Tokens advertised | payTo env | |---|---|---|---| | EVM | `eip155:97` (BSC testnet) | DHLU (eip3009), USDC (permit2) | `EVM_ADDRESS` | -| TRON | `tron:0xcd8690dc` | USDT, USDD (permit2) | `TRON_ADDRESS` | +| TRON | `tron:3448148188` | USDT, USDD (permit2) | `TRON_ADDRESS` | Tokens are configured per network in `src/chains/evm.ts` (`EVM_TOKENS`) and `src/chains/tron.ts`. BSC USDC needs a one-time Permit2 approve, advertised via diff --git a/examples/typescript/servers/mcp/README.md b/examples/typescript/servers/mcp/README.md index 2766d584..ab8a5a4c 100644 --- a/examples/typescript/servers/mcp/README.md +++ b/examples/typescript/servers/mcp/README.md @@ -1,7 +1,7 @@ # MCP server — dual-chain (BSC testnet + TRON Nile) An MCP server whose `get_weather` tool is gated behind x402 payment, accepting -**both** BSC testnet (`eip155:97`) and TRON Nile (`tron:0xcd8690dc`) at once. A 402 +**both** BSC testnet (`eip155:97`) and TRON Nile (`tron:3448148188`) at once. A 402 challenge offers every enabled chain; the client pays on whichever it supports. `ping` is free. diff --git a/examples/typescript/servers/multi-scheme/README.md b/examples/typescript/servers/multi-scheme/README.md index fc8df47f..f997b1d6 100644 --- a/examples/typescript/servers/multi-scheme/README.md +++ b/examples/typescript/servers/multi-scheme/README.md @@ -9,7 +9,7 @@ facilitators over HTTP, routing each payment by `(network, scheme)`. ## Why two schemes on one server The resource server keys registered schemes by `(network, scheme)`, so `exact` -and `exact_gasfree` coexist on `tron:0xcd8690dc` without conflict. The client +and `exact_gasfree` coexist on `tron:3448148188` without conflict. The client chooses which scheme to pay by honoring one of the advertised `accepts` entries in the 402 challenge. @@ -42,7 +42,7 @@ Pay it with a fetch/gasfree client pointed at `http://localhost:4061/weather`. | Chain | Network | Schemes advertised | Tokens | payTo env | |---|---|---|---|---| | EVM | `eip155:97` (BSC testnet) | `exact` | DHLU (eip3009), USDC/USDT (permit2) | `EVM_ADDRESS` | -| TRON | `tron:0xcd8690dc` | `exact`, `exact_gasfree` | USDT, USDD (`exact`); USDT (`exact_gasfree`) | `TRON_ADDRESS` | +| TRON | `tron:3448148188` | `exact`, `exact_gasfree` | USDT, USDD (`exact`); USDT (`exact_gasfree`) | `TRON_ADDRESS` | EVM tokens are configured in `src/chains/evm.ts` (`EVM_TOKENS`); TRON schemes in `src/chains/tron.ts`. BSC USDC/USDT need a one-time Permit2 approve, advertised diff --git a/examples/typescript/servers/upto/README.md b/examples/typescript/servers/upto/README.md index aaea2f08..a9cfb9ad 100644 --- a/examples/typescript/servers/upto/README.md +++ b/examples/typescript/servers/upto/README.md @@ -14,7 +14,7 @@ over HTTP. - Set the payout address(es) you want to accept: - `EVM_ADDRESS` → BSC (`eip155:97`), token USDC (Permit2) - - `TRON_ADDRESS` → TRON Nile (`tron:0xcd8690dc`), token USDT (Permit2) + - `TRON_ADDRESS` → TRON Nile (`tron:3448148188`), token USDT (Permit2) - A running upto facilitator (default `FACILITATOR_URL=http://localhost:4052`). ## Run diff --git a/specs/CONTRIBUTING.md b/specs/CONTRIBUTING.md index 56b17daf..e04aff9b 100644 --- a/specs/CONTRIBUTING.md +++ b/specs/CONTRIBUTING.md @@ -74,7 +74,7 @@ Use these templates: - Use **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, and **MAY** in their RFC 2119 sense. - Use `x402Version: 2` as a JSON number, never a string. -- Use CAIP-2 identifiers: `eip155:` for EVM and `tron:` for TRON. +- Use CAIP-2 identifiers: `eip155:` for EVM and `tron:` for TRON. - Express token amounts as base-10 strings in atomic units. - Keep Base58Check TRON addresses on the wire unless a binding explicitly requires 20-byte hex for typed-data construction. diff --git a/specs/schemes/batch-settlement/scheme_batch_settlement_tron.md b/specs/schemes/batch-settlement/scheme_batch_settlement_tron.md index 13f8526f..9a6553fe 100644 --- a/specs/schemes/batch-settlement/scheme_batch_settlement_tron.md +++ b/specs/schemes/batch-settlement/scheme_batch_settlement_tron.md @@ -13,9 +13,9 @@ the protocol boundary and normalized to 20-byte hex for hashing, typed data, and | Network | Channel contract | ERC-3009 collector | Permit2 collector | | --- | --- | --- | --- | -| `tron:0x2b6653dc` | `TW9yNhTySkEHYfjnGQU2u4NAsdb1tW4fbm` | `TTWA7aWMdx4jfcbp8XRAS2JAd2sUhyF9qj` | `TAg5qqp1K9x5KeSTWnRa8LT79B5HUjzSHY` | -| `tron:0xcd8690dc` | `TWBwWHZWwH8TzrZnbxit1J645VGYY1K2fA` | `TJUQ3BQt4YFg8EeevjiUa5LbfSGz5BxzRW` | `TEp6bCqSEKAr99sCiqANC84RtRwx7xGbA4` | -| `tron:0x94a9059e` | `TA3MZHMLsgi8JMU1DL8H4gKp1YjJKATibf` | `TRd1KBfy1iUs6R45oZrtbLUjtcSKzXAvPG` | `TNmfrxbKCHqPUTj9zHVfg4Dq8WNZXPyf1x` | +| `tron:728126428` | `TW9yNhTySkEHYfjnGQU2u4NAsdb1tW4fbm` | `TTWA7aWMdx4jfcbp8XRAS2JAd2sUhyF9qj` | `TAg5qqp1K9x5KeSTWnRa8LT79B5HUjzSHY` | +| `tron:3448148188` | `TWBwWHZWwH8TzrZnbxit1J645VGYY1K2fA` | `TJUQ3BQt4YFg8EeevjiUa5LbfSGz5BxzRW` | `TEp6bCqSEKAr99sCiqANC84RtRwx7xGbA4` | +| `tron:2494104990` | `TA3MZHMLsgi8JMU1DL8H4gKp1YjJKATibf` | `TRd1KBfy1iUs6R45oZrtbLUjtcSKzXAvPG` | `TNmfrxbKCHqPUTj9zHVfg4Dq8WNZXPyf1x` | The channel TIP-712 domain is `{ name: "x402 Batch Settlement", version: "1", chainId, verifyingContract: channelContract }`. diff --git a/specs/schemes/exact-gasfree/scheme_exact_gasfree_tron.md b/specs/schemes/exact-gasfree/scheme_exact_gasfree_tron.md index 54d12eec..84d59a57 100644 --- a/specs/schemes/exact-gasfree/scheme_exact_gasfree_tron.md +++ b/specs/schemes/exact-gasfree/scheme_exact_gasfree_tron.md @@ -10,9 +10,9 @@ fails, and returns the resulting TRON transaction ID. | Network | GasFreeController | Beacon | Default relayer base URL | | --- | --- | --- | --- | -| `tron:0x2b6653dc` | `TFFAMQLZybALaLb4uxHA9RBE7pxhUAjF3U` | `TSP9UW6FQhT76XD2jWA6ipGMx3yGbjDffP` | `https://facilitator.bankofai.io/mainnet` | -| `tron:0xcd8690dc` | `THQGuFzL87ZqhxkgqYEryRAd7gqFqL5rdc` | `TLtCGmaxH3PbuaF6kbybwteZcHptEdgQGC` | `https://facilitator.bankofai.io/nile` | -| `tron:0x94a9059e` | `TQghdCeVDA6CnuNVTUhfaAyPfTetqZWNpm` | `TQ1jvA3nLDMDNbJoMPLzTPoqAg8NvZ5CCW` | `https://facilitator.bankofai.io/shasta` | +| `tron:728126428` | `TFFAMQLZybALaLb4uxHA9RBE7pxhUAjF3U` | `TSP9UW6FQhT76XD2jWA6ipGMx3yGbjDffP` | `https://facilitator.bankofai.io/mainnet` | +| `tron:3448148188` | `THQGuFzL87ZqhxkgqYEryRAd7gqFqL5rdc` | `TLtCGmaxH3PbuaF6kbybwteZcHptEdgQGC` | `https://facilitator.bankofai.io/nile` | +| `tron:2494104990` | `TQghdCeVDA6CnuNVTUhfaAyPfTetqZWNpm` | `TQ1jvA3nLDMDNbJoMPLzTPoqAg8NvZ5CCW` | `https://facilitator.bankofai.io/shasta` | Deployments MAY override the relayer URL, but MUST retain the controller associated with the selected network unless using a separately specified GasFree deployment. @@ -30,7 +30,7 @@ NOT be advertised as `extra.fee`. "x402Version": 2, "accepted": { "scheme": "exact_gasfree", - "network": "tron:0xcd8690dc", + "network": "tron:3448148188", "amount": "1000", "asset": "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf", "payTo": "TReceiverAddress", diff --git a/specs/schemes/exact/scheme_exact_tron.md b/specs/schemes/exact/scheme_exact_tron.md index 2e877de0..bdbd30cf 100644 --- a/specs/schemes/exact/scheme_exact_tron.md +++ b/specs/schemes/exact/scheme_exact_tron.md @@ -17,12 +17,13 @@ prefix. | Network | CAIP-2 ID | Permit2 | Exact proxy | | --- | --- | --- | --- | -| Mainnet | `tron:0x2b6653dc` | `TTJxU3P8rHycAyFY4kVtGNfmnMH4ezcuM9` | `TN49yaJmZMZoEdDCqjB4uPzQLHvYkGw95m` | -| Nile | `tron:0xcd8690dc` | `TYQuuhGbEMxF7nZxUHV3uHJxAVVAegNU9h` | `TFGoaq2KjizijgjtkVxT7yjffW1A5T1j6F` | -| Shasta | `tron:0x94a9059e` | `TJMkP7a3ucTMkvi17p7ChhTCw6zriFX3tg` | `TGZkC38n14f2GpBWPMQLF2BpmcpWW3QNhg` | +| Mainnet | `tron:728126428` | `TTJxU3P8rHycAyFY4kVtGNfmnMH4ezcuM9` | `TN49yaJmZMZoEdDCqjB4uPzQLHvYkGw95m` | +| Nile | `tron:3448148188` | `TYQuuhGbEMxF7nZxUHV3uHJxAVVAegNU9h` | `TFGoaq2KjizijgjtkVxT7yjffW1A5T1j6F` | +| Shasta | `tron:2494104990` | `TJMkP7a3ucTMkvi17p7ChhTCw6zriFX3tg` | `TGZkC38n14f2GpBWPMQLF2BpmcpWW3QNhg` | -The numeric TIP-712 `chainId` is the hexadecimal CAIP-2 reference interpreted as an unsigned -integer. +The numeric TIP-712 `chainId` is the decimal CAIP-2 reference interpreted as an unsigned integer. +Deprecated hexadecimal CAIP-2 aliases may be accepted as inputs during migration, but requirements +and responses use the decimal identifiers above. ## Payment Requirements @@ -45,7 +46,7 @@ tokens do not expose TransferWithAuthorization. "x402Version": 2, "accepted": { "scheme": "exact", - "network": "tron:0xcd8690dc", + "network": "tron:3448148188", "amount": "1000", "asset": "TTokenAddress", "payTo": "TReceiverAddress", @@ -81,7 +82,7 @@ validBefore,bytes32 nonce)`. "x402Version": 2, "accepted": { "scheme": "exact", - "network": "tron:0xcd8690dc", + "network": "tron:3448148188", "amount": "1000", "asset": "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf", "payTo": "TReceiverAddress", diff --git a/specs/schemes/upto/scheme_upto_tron.md b/specs/schemes/upto/scheme_upto_tron.md index b5241b75..96744be4 100644 --- a/specs/schemes/upto/scheme_upto_tron.md +++ b/specs/schemes/upto/scheme_upto_tron.md @@ -13,9 +13,9 @@ The upto proxy deployments are: | Network | `x402UptoPermit2Proxy` | | --- | --- | -| `tron:0x2b6653dc` | `TBLeFPkfDiweBbYmAPqnakaFBPDt9p93sR` | -| `tron:0xcd8690dc` | `TKvcqQ7S2bYyys5ZZNpjj9xGiPhiwzHq1K` | -| `tron:0x94a9059e` | `TMxpieW75DQiA9QaoTB1ifJWeQpuppSB1g` | +| `tron:728126428` | `TBLeFPkfDiweBbYmAPqnakaFBPDt9p93sR` | +| `tron:3448148188` | `TKvcqQ7S2bYyys5ZZNpjj9xGiPhiwzHq1K` | +| `tron:2494104990` | `TMxpieW75DQiA9QaoTB1ifJWeQpuppSB1g` | ## Payment Requirements @@ -33,7 +33,7 @@ the resource server replaces it with the actual amount. The signed maximum remai "x402Version": 2, "accepted": { "scheme": "upto", - "network": "tron:0xcd8690dc", + "network": "tron:3448148188", "amount": "10000", "asset": "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf", "payTo": "TReceiverAddress", diff --git a/specs/transports-v2/mcp.md b/specs/transports-v2/mcp.md index 29a0bc4e..976aa600 100644 --- a/specs/transports-v2/mcp.md +++ b/specs/transports-v2/mcp.md @@ -36,7 +36,7 @@ The BANK OF AI server wrapper returns a normal MCP tool result with `isError: tr "accepts": [ { "scheme": "exact", - "network": "tron:0x2b6653dc", + "network": "tron:728126428", "amount": "1000000", "asset": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", "payTo": "TReceiverAddress", @@ -87,7 +87,7 @@ encoded: }, "accepted": { "scheme": "exact", - "network": "tron:0x2b6653dc", + "network": "tron:728126428", "amount": "1000000", "asset": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", "payTo": "TReceiverAddress", @@ -135,7 +135,7 @@ adds settlement metadata: "x402/payment-response": { "success": true, "transaction": "abc123...", - "network": "tron:0x2b6653dc", + "network": "tron:728126428", "payer": "TPayerAddress", "amount": "1000000" } diff --git a/specs/x402-specification-v2.md b/specs/x402-specification-v2.md index 11757afa..a46e5b1d 100644 --- a/specs/x402-specification-v2.md +++ b/specs/x402-specification-v2.md @@ -453,7 +453,7 @@ Returns the list of payment schemes, networks, and extensions supported by the f { "x402Version": 2, "scheme": "upto", - "network": "tron:0xcd8690dc", + "network": "tron:3448148188", "extra": { "assetTransferMethod": "permit2", "permit2FacilitatorAddress": "TFacilitatorAddress" @@ -462,7 +462,7 @@ Returns the list of payment schemes, networks, and extensions supported by the f { "x402Version": 2, "scheme": "exact_gasfree", - "network": "tron:0xcd8690dc" + "network": "tron:3448148188" } ], "extensions": [], @@ -642,9 +642,13 @@ This repository's supported deployment profile uses: - **`eip155:56`**: BSC mainnet - **`eip155:97`**: BSC testnet -- **`tron:0x2b6653dc`**: TRON mainnet -- **`tron:0xcd8690dc`**: TRON Nile testnet -- **`tron:0x94a9059e`**: TRON Shasta testnet +- **`tron:728126428`**: TRON mainnet +- **`tron:3448148188`**: TRON Nile testnet +- **`tron:2494104990`**: TRON Shasta testnet + +TRON implementations MUST emit these decimal CAIP-2 identifiers. For backwards compatibility, +implementations MAY accept the deprecated hexadecimal aliases `tron:0x2b6653dc`, +`tron:0xcd8690dc`, and `tron:0x94a9059e` as inputs. An implementation MAY register additional EVM networks when the selected scheme's contracts and assets are available. The TRON binding recognizes the three identifiers above unless the SDK is diff --git a/typescript/.changeset/tron-decimal-caip2.md b/typescript/.changeset/tron-decimal-caip2.md new file mode 100644 index 00000000..e2beda29 --- /dev/null +++ b/typescript/.changeset/tron-decimal-caip2.md @@ -0,0 +1,6 @@ +--- +'@bankofai/x402-tron': major +'@bankofai/x402-core': patch +--- + +Use decimal TRON CAIP-2 identifiers as canonical network values while accepting deprecated hexadecimal aliases at protocol and configuration boundaries. diff --git a/typescript/packages/core/src/observability/facilitatorLogging.ts b/typescript/packages/core/src/observability/facilitatorLogging.ts index 18f2ddd0..e9ac61c7 100644 --- a/typescript/packages/core/src/observability/facilitatorLogging.ts +++ b/typescript/packages/core/src/observability/facilitatorLogging.ts @@ -14,7 +14,7 @@ * import { createFacilitator } from "@bankofai/x402-core"; * * const facilitator = createFacilitator(); // logging pre-attached - * facilitator.register("tron:0xcd8690dc", scheme); + * facilitator.register("tron:3448148188", scheme); * ``` */ import { diff --git a/typescript/packages/core/src/observability/resourceServerLogging.ts b/typescript/packages/core/src/observability/resourceServerLogging.ts index 6b0fb573..cd098f2c 100644 --- a/typescript/packages/core/src/observability/resourceServerLogging.ts +++ b/typescript/packages/core/src/observability/resourceServerLogging.ts @@ -25,7 +25,7 @@ * import { createResourceServer } from "@bankofai/x402-core"; * * const server = createResourceServer(facilitatorClient); // logging pre-attached - * server.register("tron:0xcd8690dc", scheme); + * server.register("tron:3448148188", scheme); * ``` */ import { diff --git a/typescript/packages/core/src/utils/index.ts b/typescript/packages/core/src/utils/index.ts index 2c8c216e..08ad3cfd 100644 --- a/typescript/packages/core/src/utils/index.ts +++ b/typescript/packages/core/src/utils/index.ts @@ -120,7 +120,15 @@ const networkPatternToRegExp = (pattern: Network): RegExp => { return new RegExp(`^${source}$`); }; +const normalizeNetworkForMatching = (network: Network): string => { + const tronHexReference = /^tron:0x([0-9a-f]+)$/i.exec(network)?.[1]; + return tronHexReference ? `tron:${BigInt(`0x${tronHexReference}`).toString(10)}` : network; +}; + export const networkMatchesPattern = (pattern: Network, network: Network): boolean => { + if (!pattern.includes("*")) { + return normalizeNetworkForMatching(pattern) === normalizeNetworkForMatching(network); + } return networkPatternToRegExp(pattern).test(network); }; diff --git a/typescript/packages/core/test/unit/utils/utils.test.ts b/typescript/packages/core/test/unit/utils/utils.test.ts index 62f63965..7e80ecef 100644 --- a/typescript/packages/core/test/unit/utils/utils.test.ts +++ b/typescript/packages/core/test/unit/utils/utils.test.ts @@ -29,6 +29,17 @@ describe("Utils", () => { expect(result?.get("intent")).toBe("intentImpl"); }); + it("should match deprecated hexadecimal TRON identifiers to decimal registrations", () => { + const map = new Map>(); + const schemes = new Map(); + schemes.set("exact", "tronImpl"); + map.set("tron:3448148188", schemes); + + const result = findSchemesByNetwork(map, "tron:3448148188" as Network); + + expect(result?.get("exact")).toBe("tronImpl"); + }); + it("should return undefined for network not found", () => { const map = new Map>(); const schemes = new Map(); diff --git a/typescript/packages/http/fetch/src/index.ts b/typescript/packages/http/fetch/src/index.ts index 8111763a..528483bf 100644 --- a/typescript/packages/http/fetch/src/index.ts +++ b/typescript/packages/http/fetch/src/index.ts @@ -24,7 +24,7 @@ import { log } from "@bankofai/x402-core"; * * const client = new x402Client() * .register('eip155:8453', new ExactEvmScheme(evmSigner)) - * .register('tron:0x2b6653dc', new ExactTronScheme(tronSigner)) + * .register('tron:728126428', new ExactTronScheme(tronSigner)) * .register('eip155:1', new ExactEvmScheme(evmSigner), 1); // v1 protocol * * const fetchWithPay = wrapFetchWithPayment(fetch, client); diff --git a/typescript/packages/mechanisms/evm/test/unit/batch-settlement/refund-network.test.ts b/typescript/packages/mechanisms/evm/test/unit/batch-settlement/refund-network.test.ts index 95be4946..0301fcec 100644 --- a/typescript/packages/mechanisms/evm/test/unit/batch-settlement/refund-network.test.ts +++ b/typescript/packages/mechanisms/evm/test/unit/batch-settlement/refund-network.test.ts @@ -29,7 +29,7 @@ const EVM_AUTHORIZER = "0x1111111111111111111111111111111111111111"; function tronAccept(): PaymentRequirements { return { scheme: "batch-settlement", - network: "tron:0xcd8690dc", + network: "tron:3448148188", asset: "TGjgvdTWWrybVLaVeFqSyVqJQWjxqRYbaK", amount: "1000000", payTo: "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf", diff --git a/typescript/packages/mechanisms/tron/README.md b/typescript/packages/mechanisms/tron/README.md index bdc75863..6f70d60f 100644 --- a/typescript/packages/mechanisms/tron/README.md +++ b/typescript/packages/mechanisms/tron/README.md @@ -16,7 +16,11 @@ This package provides the three x402 mechanism roles for TRON, mirroring `@banko - **Facilitator** — processors that verify signatures and submit on-chain settlement - **Server** — resource servers that price requests and build `PaymentRequirements` -It plugs into `@bankofai/x402-core` via the `tron:*` CAIP family — no core changes are required. +It plugs into `@bankofai/x402-core` via the `tron:*` CAIP family. + +TRON network identifiers use decimal CAIP-2 references (`tron:728126428`, `tron:3448148188`, and +`tron:2494104990`). The SDK accepts the older hexadecimal forms as input aliases, while newly +created configuration and protocol output use the decimal identifiers. ### Fee behavior @@ -95,7 +99,7 @@ const clientWallet: ClientTronWallet = { }, }; const clientSigner = await createClientTronSigner(clientWallet, { - network: "tron:0xcd8690dc", + network: "tron:3448148188", }); // Facilitator: signs built settlement transactions for on-chain broadcast. @@ -106,7 +110,7 @@ const facilitatorWallet: FacilitatorTronWallet = { }, }; const facilitatorSigner = await createFacilitatorTronSigner(facilitatorWallet, { - network: "tron:0xcd8690dc", + network: "tron:3448148188", }); ``` @@ -140,7 +144,7 @@ registerExactTronScheme(server); // prices to the network's default stablecoin import { x402Facilitator } from "@bankofai/x402-core/facilitator"; import { registerExactTronScheme } from "@bankofai/x402-tron/exact/facilitator"; -registerExactTronScheme(facilitator, { signer: facilitatorSigner, networks: "tron:0xcd8690dc" }); +registerExactTronScheme(facilitator, { signer: facilitatorSigner, networks: "tron:3448148188" }); ``` ## Networks & on-chain dependencies @@ -149,9 +153,9 @@ The `permit2` path depends on three on-chain contracts. Addresses live in `src/c | Network | chainId (TIP-712) | Permit2 | x402ExactPermit2Proxy | | --- | --- | --- | --- | -| `tron:0xcd8690dc` | 3448148188 | `TYQuuhGbEMxF7nZxUHV3uHJxAVVAegNU9h` | `TFGoaq2KjizijgjtkVxT7yjffW1A5T1j6F` | -| `tron:0x2b6653dc` | 728126428 | `TTJxU3P8rHycAyFY4kVtGNfmnMH4ezcuM9` | `TN49yaJmZMZoEdDCqjB4uPzQLHvYkGw95m` | -| `tron:0x94a9059e` | 2494104990 | `TJMkP7a3ucTMkvi17p7ChhTCw6zriFX3tg` | `TGZkC38n14f2GpBWPMQLF2BpmcpWW3QNhg` | +| `tron:3448148188` | 3448148188 | `TYQuuhGbEMxF7nZxUHV3uHJxAVVAegNU9h` | `TFGoaq2KjizijgjtkVxT7yjffW1A5T1j6F` | +| `tron:728126428` | 728126428 | `TTJxU3P8rHycAyFY4kVtGNfmnMH4ezcuM9` | `TN49yaJmZMZoEdDCqjB4uPzQLHvYkGw95m` | +| `tron:2494104990` | 2494104990 | `TJMkP7a3ucTMkvi17p7ChhTCw6zriFX3tg` | `TGZkC38n14f2GpBWPMQLF2BpmcpWW3QNhg` | > ⚠️ Mainnet uses real funds. The configured Permit2 and > `x402ExactPermit2Proxy` addresses are the deployed mainnet contracts; verify diff --git a/typescript/packages/mechanisms/tron/src/approvalPolicy.ts b/typescript/packages/mechanisms/tron/src/approvalPolicy.ts index b2659b6f..565e5da9 100644 --- a/typescript/packages/mechanisms/tron/src/approvalPolicy.ts +++ b/typescript/packages/mechanisms/tron/src/approvalPolicy.ts @@ -1,5 +1,6 @@ import { TronWeb } from "tronweb"; import type { Network } from "@bankofai/x402-core/types"; +import { normalizeTronNetwork } from "./network"; import { findByAddress } from "./shared/tokens"; /** How a TRC-20 token permits an existing non-zero Approval to be updated. */ @@ -43,13 +44,13 @@ export function createTrc20ApprovalPolicy( ): Trc20ApprovalPolicy { const allowedAssets = new Map( Object.entries(options.allowedAssets ?? {}).map(([network, assets]) => [ - network, + normalizeTronNetwork(network), new Set(assets.map(normalizeAddress)), ]), ); const strategies = new Map( Object.entries(options.strategies ?? {}).map(([network, entries]) => [ - network, + normalizeTronNetwork(network), new Map( Object.entries(entries).map(([token, strategy]) => [normalizeAddress(token), strategy]), ), @@ -58,11 +59,12 @@ export function createTrc20ApprovalPolicy( return { strategyFor(network, token) { + const canonicalNetwork = normalizeTronNetwork(network); const normalized = normalizeAddress(token); - const configured = strategies.get(network)?.get(normalized); + const configured = strategies.get(canonicalNetwork)?.get(normalized); if (configured) return configured; - if (allowedAssets.get(network)?.has(normalized)) return "zero-first"; - return findByAddress(network as Network, token)?.assetTransferMethod === "permit2" + if (allowedAssets.get(canonicalNetwork)?.has(normalized)) return "zero-first"; + return findByAddress(canonicalNetwork as Network, token)?.assetTransferMethod === "permit2" ? "zero-first" : "unsupported"; }, diff --git a/typescript/packages/mechanisms/tron/src/batch-settlement/client/voucher.ts b/typescript/packages/mechanisms/tron/src/batch-settlement/client/voucher.ts index 1a70122b..a3291188 100644 --- a/typescript/packages/mechanisms/tron/src/batch-settlement/client/voucher.ts +++ b/typescript/packages/mechanisms/tron/src/batch-settlement/client/voucher.ts @@ -12,7 +12,7 @@ import { BatchSettlementVoucherFields } from "../types"; * @param signer - Client wallet used to produce the TIP-712 signature. * @param channelId - Identifier of the payment channel (see `computeChannelId`). * @param maxClaimableAmount - Cumulative ceiling the receiver may claim (decimal string). - * @param network - CAIP-2 network identifier (e.g. `"tron:0xcd8690dc"`). + * @param network - CAIP-2 network identifier (e.g. `"tron:3448148188"`). * @returns Signed voucher fields ready to be included in a payment payload. */ export async function signVoucher( diff --git a/typescript/packages/mechanisms/tron/src/batch-settlement/facilitator/scheme.ts b/typescript/packages/mechanisms/tron/src/batch-settlement/facilitator/scheme.ts index e94b6a1a..d7910084 100644 --- a/typescript/packages/mechanisms/tron/src/batch-settlement/facilitator/scheme.ts +++ b/typescript/packages/mechanisms/tron/src/batch-settlement/facilitator/scheme.ts @@ -7,6 +7,7 @@ import { VerifyResponse, } from "@bankofai/x402-core/types"; import { FacilitatorTronSigner } from "../../signer"; +import { tronNetworksEqual } from "../../network"; import { BATCH_SETTLEMENT_SCHEME } from "../../shared/batch-settlement/constants"; import { isBatchSettlementDepositPayload, @@ -108,7 +109,7 @@ export class BatchSettlementTronScheme implements SchemeNetworkFacilitator { ) { return { isValid: false, invalidReason: Errors.ErrInvalidScheme }; } - if (payload.accepted.network !== requirements.network) { + if (!tronNetworksEqual(payload.accepted.network, requirements.network)) { return { isValid: false, invalidReason: Errors.ErrNetworkMismatch }; } @@ -154,7 +155,7 @@ export class BatchSettlementTronScheme implements SchemeNetworkFacilitator { network: requirements.network, }; } - if (payload.accepted.network !== requirements.network) { + if (!tronNetworksEqual(payload.accepted.network, requirements.network)) { return { success: false, errorReason: Errors.ErrNetworkMismatch, diff --git a/typescript/packages/mechanisms/tron/src/batch-settlement/server/scheme.ts b/typescript/packages/mechanisms/tron/src/batch-settlement/server/scheme.ts index 8c850478..c0840644 100644 --- a/typescript/packages/mechanisms/tron/src/batch-settlement/server/scheme.ts +++ b/typescript/packages/mechanisms/tron/src/batch-settlement/server/scheme.ts @@ -384,7 +384,7 @@ export class BatchSettlementTronScheme implements SchemeNetworkServer { * receiver, default token for the given network, and the provided facilitator. * * @param facilitator - Facilitator client for submitting onchain claims/settlements. - * @param network - CAIP-2 network identifier (e.g. `"tron:0xcd8690dc"`). + * @param network - CAIP-2 network identifier (e.g. `"tron:3448148188"`). * @returns A ready-to-use channel manager. */ createChannelManager( diff --git a/typescript/packages/mechanisms/tron/src/batch-settlement/server/verify.ts b/typescript/packages/mechanisms/tron/src/batch-settlement/server/verify.ts index aec7708c..06b5295f 100644 --- a/typescript/packages/mechanisms/tron/src/batch-settlement/server/verify.ts +++ b/typescript/packages/mechanisms/tron/src/batch-settlement/server/verify.ts @@ -18,6 +18,7 @@ import { import { BATCH_SETTLEMENT_SCHEME, voucherTypes } from "../../shared/batch-settlement/constants"; import type { ChannelConfig } from "../types"; import { createNonce, normalizeAddressForSigning } from "../../utils"; +import { tronNetworksEqual } from "../../network"; import { computeChannelId, getBatchSettlementTip712Domain, @@ -219,7 +220,8 @@ export async function handleEnrichPaymentRequiredResponse( const accept = ctx.requirements.find( req => - req.scheme === BATCH_SETTLEMENT_SCHEME && req.network === paymentPayload.accepted.network, + req.scheme === BATCH_SETTLEMENT_SCHEME && + tronNetworksEqual(req.network, paymentPayload.accepted.network), ); if (!accept) { return; diff --git a/typescript/packages/mechanisms/tron/src/constants.ts b/typescript/packages/mechanisms/tron/src/constants.ts index c455b5da..6506fb7e 100644 --- a/typescript/packages/mechanisms/tron/src/constants.ts +++ b/typescript/packages/mechanisms/tron/src/constants.ts @@ -1,13 +1,6 @@ -// --- CAIP-2 network identifiers --- -// TRON uses hex chain IDs in the CAIP-2 reference (not human-readable names), -// per upstream spec PR x402-foundation/x402#2076 review feedback. +import { TRON_MAINNET, TRON_NILE, TRON_SHASTA, withTronNetworkAliases } from "./network"; -/** TRON mainnet CAIP-2 id (chain id 0x2b6653dc). */ -export const TRON_MAINNET = "tron:0x2b6653dc"; -/** TRON Nile testnet CAIP-2 id (chain id 0xcd8690dc). */ -export const TRON_NILE = "tron:0xcd8690dc"; -/** TRON Shasta testnet CAIP-2 id (chain id 0x94a9059e). */ -export const TRON_SHASTA = "tron:0x94a9059e"; +export { TRON_MAINNET, TRON_NILE, TRON_SHASTA } from "./network"; // --- TIP-712 (TransferWithAuthorization) constants --- @@ -111,31 +104,31 @@ export const uptoPermit2WitnessTypes = { /** * Permit2 contract addresses per TRON network. */ -export const PERMIT2_ADDRESSES: Record = { - "tron:0x2b6653dc": "TTJxU3P8rHycAyFY4kVtGNfmnMH4ezcuM9", - "tron:0xcd8690dc": "TYQuuhGbEMxF7nZxUHV3uHJxAVVAegNU9h", - "tron:0x94a9059e": "TJMkP7a3ucTMkvi17p7ChhTCw6zriFX3tg", -}; +export const PERMIT2_ADDRESSES: Record = withTronNetworkAliases({ + [TRON_MAINNET]: "TTJxU3P8rHycAyFY4kVtGNfmnMH4ezcuM9", + [TRON_NILE]: "TYQuuhGbEMxF7nZxUHV3uHJxAVVAegNU9h", + [TRON_SHASTA]: "TJMkP7a3ucTMkvi17p7ChhTCw6zriFX3tg", +}); /** * x402ExactPermit2Proxy contract addresses per TRON network. * Enforces that Permit2 transfers can only go to the witness.to address. */ -export const X402_PERMIT2_PROXY_ADDRESSES: Record = { - "tron:0x2b6653dc": "TN49yaJmZMZoEdDCqjB4uPzQLHvYkGw95m", - "tron:0xcd8690dc": "TFGoaq2KjizijgjtkVxT7yjffW1A5T1j6F", - "tron:0x94a9059e": "TGZkC38n14f2GpBWPMQLF2BpmcpWW3QNhg", -}; +export const X402_PERMIT2_PROXY_ADDRESSES: Record = withTronNetworkAliases({ + [TRON_MAINNET]: "TN49yaJmZMZoEdDCqjB4uPzQLHvYkGw95m", + [TRON_NILE]: "TFGoaq2KjizijgjtkVxT7yjffW1A5T1j6F", + [TRON_SHASTA]: "TGZkC38n14f2GpBWPMQLF2BpmcpWW3QNhg", +}); /** * x402UptoPermit2Proxy contract addresses per TRON network. * Used by variable-amount settlement flows. */ -export const X402_UPTO_PERMIT2_PROXY_ADDRESSES: Record = { - "tron:0x2b6653dc": "TBLeFPkfDiweBbYmAPqnakaFBPDt9p93sR", - "tron:0xcd8690dc": "TKvcqQ7S2bYyys5ZZNpjj9xGiPhiwzHq1K", - "tron:0x94a9059e": "TMxpieW75DQiA9QaoTB1ifJWeQpuppSB1g", -}; +export const X402_UPTO_PERMIT2_PROXY_ADDRESSES: Record = withTronNetworkAliases({ + [TRON_MAINNET]: "TBLeFPkfDiweBbYmAPqnakaFBPDt9p93sR", + [TRON_NILE]: "TKvcqQ7S2bYyys5ZZNpjj9xGiPhiwzHq1K", + [TRON_SHASTA]: "TMxpieW75DQiA9QaoTB1ifJWeQpuppSB1g", +}); /** * ABI for x402ExactPermit2Proxy settle function on TRON. @@ -259,11 +252,11 @@ export const erc20ApproveAbi = [ /** * TRON chain IDs for TIP-712 signing. */ -export const TRON_CHAIN_IDS: Record = { - "tron:0x2b6653dc": 728126428, // 0x2b6653dc - "tron:0x94a9059e": 2494104990, // 0x94a9059e - "tron:0xcd8690dc": 3448148188, // 0xcd8690dc -}; +export const TRON_CHAIN_IDS: Record = withTronNetworkAliases({ + [TRON_MAINNET]: 728126428, + [TRON_SHASTA]: 2494104990, + [TRON_NILE]: 3448148188, +}); /** * Default fee limit for TRON contract calls in SUN (1 TRX = 1,000,000 SUN). diff --git a/typescript/packages/mechanisms/tron/src/exact/client/register.ts b/typescript/packages/mechanisms/tron/src/exact/client/register.ts index dc75ac77..dfcee751 100644 --- a/typescript/packages/mechanisms/tron/src/exact/client/register.ts +++ b/typescript/packages/mechanisms/tron/src/exact/client/register.ts @@ -1,5 +1,6 @@ import { x402Client, PaymentPolicy } from "@bankofai/x402-core/client"; import { Network } from "@bankofai/x402-core/types"; +import { normalizeTronNetwork, tronNetworksEqual } from "../../network"; import { ClientTronSigner } from "../../signer"; import { ExactTronScheme } from "./scheme"; @@ -34,15 +35,15 @@ export function registerExactTronScheme( if (config.networks && config.networks.length > 0) { config.networks.forEach(network => { - if (config.signer.network && network !== config.signer.network) { + if (config.signer.network && !tronNetworksEqual(network, config.signer.network)) { throw new Error( `TRON signer network ${config.signer.network} cannot be registered for ${network}`, ); } - client.register(network, tronScheme); + client.register(normalizeTronNetwork(network) as Network, tronScheme); }); } else if (config.signer.network) { - client.register(config.signer.network as Network, tronScheme); + client.register(normalizeTronNetwork(config.signer.network) as Network, tronScheme); } else { client.register("tron:*", tronScheme); } diff --git a/typescript/packages/mechanisms/tron/src/exact/facilitator/eip3009.ts b/typescript/packages/mechanisms/tron/src/exact/facilitator/eip3009.ts index 61f0ed77..4b4254d9 100644 --- a/typescript/packages/mechanisms/tron/src/exact/facilitator/eip3009.ts +++ b/typescript/packages/mechanisms/tron/src/exact/facilitator/eip3009.ts @@ -8,6 +8,7 @@ import { authorizationTypes, transferWithAuthorizationABI } from "../../constant import { FacilitatorTronSigner } from "../../signer"; import { ExactEIP3009Payload } from "../../types"; import { getTronChainId, normalizeAddressForSigning } from "../../utils"; +import { tronNetworksEqual } from "../../network"; import * as errors from "./errors"; /** @@ -35,7 +36,7 @@ export async function verifyEIP3009( return { isValid: false, invalidReason: errors.MISSING_TIP712_DOMAIN, payer }; } - if (payload.accepted.network !== requirements.network) { + if (!tronNetworksEqual(payload.accepted.network, requirements.network)) { return { isValid: false, invalidReason: errors.NETWORK_MISMATCH, payer }; } diff --git a/typescript/packages/mechanisms/tron/src/exact/facilitator/permit2Verification.ts b/typescript/packages/mechanisms/tron/src/exact/facilitator/permit2Verification.ts index 2c89df53..6c70d077 100644 --- a/typescript/packages/mechanisms/tron/src/exact/facilitator/permit2Verification.ts +++ b/typescript/packages/mechanisms/tron/src/exact/facilitator/permit2Verification.ts @@ -13,6 +13,7 @@ import { import type { FacilitatorTronSigner } from "../../signer"; import type { ExactPermit2Payload } from "../../types"; import { getTronChainId, normalizeAddressForSigning } from "../../utils"; +import { tronNetworksEqual } from "../../network"; import * as errors from "./errors"; const DEADLINE_BUFFER_SECONDS = 6; @@ -66,7 +67,7 @@ function validateAuthorizationFields( if (payload.accepted.scheme !== "exact" || requirements.scheme !== "exact") { return invalid(errors.INVALID_SCHEME, payer); } - if (payload.accepted.network !== requirements.network) { + if (!tronNetworksEqual(payload.accepted.network, requirements.network)) { return invalid(errors.NETWORK_MISMATCH, payer); } if (requirements.extra?.assetTransferMethod !== "permit2") { diff --git a/typescript/packages/mechanisms/tron/src/exact/facilitator/register.ts b/typescript/packages/mechanisms/tron/src/exact/facilitator/register.ts index 51df417f..5b92d553 100644 --- a/typescript/packages/mechanisms/tron/src/exact/facilitator/register.ts +++ b/typescript/packages/mechanisms/tron/src/exact/facilitator/register.ts @@ -1,5 +1,6 @@ import { x402Facilitator } from "@bankofai/x402-core/facilitator"; import { Network } from "@bankofai/x402-core/types"; +import { normalizeTronNetwork } from "../../network"; import { FacilitatorTronSigner } from "../../signer"; import { ExactTronScheme } from "./scheme"; @@ -22,6 +23,9 @@ export function registerExactTronScheme( facilitator: x402Facilitator, config: TronFacilitatorConfig, ): x402Facilitator { - facilitator.register(config.networks, new ExactTronScheme(config.signer)); + const networks = (Array.isArray(config.networks) ? config.networks : [config.networks]).map( + network => normalizeTronNetwork(network) as Network, + ); + facilitator.register([...new Set(networks)], new ExactTronScheme(config.signer)); return facilitator; } diff --git a/typescript/packages/mechanisms/tron/src/exact/server/register.ts b/typescript/packages/mechanisms/tron/src/exact/server/register.ts index e6c4bce5..7cecba61 100644 --- a/typescript/packages/mechanisms/tron/src/exact/server/register.ts +++ b/typescript/packages/mechanisms/tron/src/exact/server/register.ts @@ -1,5 +1,6 @@ import { x402ResourceServer } from "@bankofai/x402-core/server"; import { Network } from "@bankofai/x402-core/types"; +import { normalizeTronNetwork } from "../../network"; import { ExactTronScheme } from "./scheme"; /** @@ -22,7 +23,7 @@ export function registerExactTronScheme( ): x402ResourceServer { if (config.networks && config.networks.length > 0) { config.networks.forEach(network => { - server.register(network, new ExactTronScheme()); + server.register(normalizeTronNetwork(network) as Network, new ExactTronScheme()); }); } else { server.register("tron:*", new ExactTronScheme()); diff --git a/typescript/packages/mechanisms/tron/src/gasfree/client/register.ts b/typescript/packages/mechanisms/tron/src/gasfree/client/register.ts index 2f880e18..6ef52a1d 100644 --- a/typescript/packages/mechanisms/tron/src/gasfree/client/register.ts +++ b/typescript/packages/mechanisms/tron/src/gasfree/client/register.ts @@ -1,5 +1,6 @@ import { x402Client, PaymentPolicy } from "@bankofai/x402-core/client"; import { Network } from "@bankofai/x402-core/types"; +import { normalizeTronNetwork } from "../../network"; import { ClientTronSigner } from "../../signer"; import { GasFreeAPIClient, createGasFreeApiClients } from "../../shared/gasfree/api"; import { GASFREE_API_BASE_URLS } from "../../shared/gasfree/config"; @@ -50,7 +51,9 @@ export function registerExactGasFreeTronScheme( const scheme = new ExactGasFreeTronScheme(config.signer, { apiClients }); if (config.networks && config.networks.length > 0) { - config.networks.forEach(network => client.register(network, scheme)); + config.networks.forEach(network => + client.register(normalizeTronNetwork(network) as Network, scheme), + ); } else { client.register("tron:*", scheme); } diff --git a/typescript/packages/mechanisms/tron/src/gasfree/client/scheme.ts b/typescript/packages/mechanisms/tron/src/gasfree/client/scheme.ts index b1132c94..54fedbb2 100644 --- a/typescript/packages/mechanisms/tron/src/gasfree/client/scheme.ts +++ b/typescript/packages/mechanisms/tron/src/gasfree/client/scheme.ts @@ -16,6 +16,7 @@ import { } from "../../shared/gasfree/api"; import { assembleGasFreeTransaction } from "../../shared/gasfree/assemble"; import { findDefaultAsset } from "../../shared/defaultAssets"; +import { getTronNetworkValue, tronNetworksEqual, TRON_MAINNET } from "../../network"; /** * Options for the GasFree client scheme. @@ -36,7 +37,7 @@ export interface ExactGasFreeSchemeOptions { * @returns The min/max deltas in seconds. */ function getDeadlineBounds(network: string): { minDelta: number; maxDelta: number } { - if (network === "tron:0x2b6653dc") { + if (tronNetworksEqual(network, TRON_MAINNET)) { return { minDelta: 55, maxDelta: 595 }; } return { minDelta: 55, maxDelta: 3595 }; @@ -199,7 +200,7 @@ export class ExactGasFreeTronScheme implements SchemeNetworkClient { * @returns The relayer API client. */ private getApiClient(network: string): GasFreeAPIClient { - const client = this.options.apiClients[network]; + const client = getTronNetworkValue(this.options.apiClients, network); if (!client) { throw new Error(`GasFree is not configured for network: ${network}`); } diff --git a/typescript/packages/mechanisms/tron/src/gasfree/facilitator/register.ts b/typescript/packages/mechanisms/tron/src/gasfree/facilitator/register.ts index c92cd3fd..258ddda4 100644 --- a/typescript/packages/mechanisms/tron/src/gasfree/facilitator/register.ts +++ b/typescript/packages/mechanisms/tron/src/gasfree/facilitator/register.ts @@ -1,5 +1,6 @@ import { x402Facilitator } from "@bankofai/x402-core/facilitator"; import { Network } from "@bankofai/x402-core/types"; +import { normalizeTronNetwork } from "../../network"; import { FacilitatorTronSigner } from "../../signer"; import { GasFreeAPIClient, createGasFreeApiClients } from "../../shared/gasfree/api"; import { GASFREE_API_BASE_URLS } from "../../shared/gasfree/config"; @@ -30,6 +31,12 @@ export function registerExactGasFreeTronScheme( ): x402Facilitator { const apiClients = config.apiClients ?? createGasFreeApiClients(config.apiBaseUrls ?? GASFREE_API_BASE_URLS); - facilitator.register(config.networks, new ExactGasFreeTronScheme(config.signer, apiClients)); + const networks = (Array.isArray(config.networks) ? config.networks : [config.networks]).map( + network => normalizeTronNetwork(network) as Network, + ); + facilitator.register( + [...new Set(networks)], + new ExactGasFreeTronScheme(config.signer, apiClients), + ); return facilitator; } diff --git a/typescript/packages/mechanisms/tron/src/gasfree/facilitator/scheme.ts b/typescript/packages/mechanisms/tron/src/gasfree/facilitator/scheme.ts index 84fd1941..ff6432c6 100644 --- a/typescript/packages/mechanisms/tron/src/gasfree/facilitator/scheme.ts +++ b/typescript/packages/mechanisms/tron/src/gasfree/facilitator/scheme.ts @@ -9,6 +9,7 @@ import { import { FacilitatorTronSigner } from "../../signer"; import { ExactGasFreePayload } from "../../types"; import { normalizeAddressForSigning } from "../../utils"; +import { getTronNetworkValue, tronNetworksEqual } from "../../network"; import { GasFreeAPIClient } from "../../shared/gasfree/api"; import { assembleGasFreeTransaction } from "../../shared/gasfree/assemble"; import * as errors from "./errors"; @@ -79,7 +80,7 @@ export class ExactGasFreeTronScheme implements SchemeNetworkFacilitator { if (payload.accepted.scheme !== "exact_gasfree" || requirements.scheme !== "exact_gasfree") { return { isValid: false, invalidReason: errors.INVALID_SCHEME, payer }; } - if (payload.accepted.network !== requirements.network) { + if (!tronNetworksEqual(payload.accepted.network, requirements.network)) { return { isValid: false, invalidReason: errors.NETWORK_MISMATCH, payer }; } @@ -199,7 +200,7 @@ export class ExactGasFreeTronScheme implements SchemeNetworkFacilitator { * @returns The relayer API client. */ private getApiClient(network: string): GasFreeAPIClient { - const client = this.apiClients[network]; + const client = getTronNetworkValue(this.apiClients, network); if (!client) { throw new Error(`GasFree is not configured for network: ${network}`); } diff --git a/typescript/packages/mechanisms/tron/src/gasfree/server/register.ts b/typescript/packages/mechanisms/tron/src/gasfree/server/register.ts index e36e83f4..ea9c0a6e 100644 --- a/typescript/packages/mechanisms/tron/src/gasfree/server/register.ts +++ b/typescript/packages/mechanisms/tron/src/gasfree/server/register.ts @@ -1,5 +1,6 @@ import { x402ResourceServer } from "@bankofai/x402-core/server"; import { Network } from "@bankofai/x402-core/types"; +import { normalizeTronNetwork } from "../../network"; import { ExactGasFreeTronScheme } from "./scheme"; /** @@ -21,7 +22,9 @@ export function registerExactGasFreeTronScheme( config: TronGasFreeResourceServerConfig = {}, ): x402ResourceServer { if (config.networks && config.networks.length > 0) { - config.networks.forEach(network => server.register(network, new ExactGasFreeTronScheme())); + config.networks.forEach(network => + server.register(normalizeTronNetwork(network) as Network, new ExactGasFreeTronScheme()), + ); } else { server.register("tron:*", new ExactGasFreeTronScheme()); } diff --git a/typescript/packages/mechanisms/tron/src/index.ts b/typescript/packages/mechanisms/tron/src/index.ts index 1484dbe4..feffb767 100644 --- a/typescript/packages/mechanisms/tron/src/index.ts +++ b/typescript/packages/mechanisms/tron/src/index.ts @@ -144,3 +144,4 @@ export { isTronAddress, normalizeAddressForSigning, } from "./utils"; +export { normalizeTronNetwork, tronNetworksEqual, getTronNetworkValue } from "./network"; diff --git a/typescript/packages/mechanisms/tron/src/network.ts b/typescript/packages/mechanisms/tron/src/network.ts new file mode 100644 index 00000000..a6d78cee --- /dev/null +++ b/typescript/packages/mechanisms/tron/src/network.ts @@ -0,0 +1,96 @@ +/** Canonical TRON Mainnet CAIP-2 identifier. */ +export const TRON_MAINNET = "tron:728126428"; + +/** Canonical TRON Nile testnet CAIP-2 identifier. */ +export const TRON_NILE = "tron:3448148188"; + +/** Canonical TRON Shasta testnet CAIP-2 identifier. */ +export const TRON_SHASTA = "tron:2494104990"; + +/** + * Deprecated hexadecimal CAIP-2 aliases emitted by earlier x402 releases. + * + * Accept these at protocol boundaries for backwards compatibility, but emit + * the decimal identifiers above for new requirements and configuration. + */ +const LEGACY_TRON_NETWORK_ALIASES: Readonly> = { + "tron:0x2b6653dc": TRON_MAINNET, + "tron:0xcd8690dc": TRON_NILE, + "tron:0x94a9059e": TRON_SHASTA, +}; + +/** + * Convert a hexadecimal TRON CAIP-2 identifier to decimal form. Unknown + * non-hexadecimal identifiers are returned unchanged so callers can produce + * their existing domain-specific error messages. + * + * @param network - Candidate TRON CAIP-2 identifier. + * @returns The canonical decimal identifier when the input is a known alias. + */ +export function normalizeTronNetwork(network: string): string { + const legacyAlias = LEGACY_TRON_NETWORK_ALIASES[network.toLowerCase()]; + if (legacyAlias) { + return legacyAlias; + } + + const hexadecimalReference = /^tron:0x([0-9a-f]+)$/i.exec(network)?.[1]; + return hexadecimalReference + ? `tron:${BigInt(`0x${hexadecimalReference}`).toString(10)}` + : network; +} + +/** + * Compare two TRON network identifiers after normalizing legacy aliases. + * + * @param left - First network identifier. + * @param right - Second network identifier. + * @returns Whether both identifiers refer to the same TRON network. + */ +export function tronNetworksEqual(left: string, right: string): boolean { + return normalizeTronNetwork(left) === normalizeTronNetwork(right); +} + +/** + * Resolve a value from a network-keyed record using decimal or legacy hex. + * This also supports caller-owned records that still contain only hex keys. + * + * @param values - Values keyed by TRON CAIP-2 identifier. + * @param network - Decimal identifier or deprecated hexadecimal alias. + * @returns The configured value, if present. + */ +export function getTronNetworkValue( + values: Readonly>, + network: string, +): T | undefined { + const canonical = normalizeTronNetwork(network); + if (Object.prototype.hasOwnProperty.call(values, canonical)) { + return values[canonical]; + } + if (Object.prototype.hasOwnProperty.call(values, network)) { + return values[network]; + } + for (const [alias, target] of Object.entries(LEGACY_TRON_NETWORK_ALIASES)) { + if (target === canonical && Object.prototype.hasOwnProperty.call(values, alias)) { + return values[alias]; + } + } + return undefined; +} + +/** + * Add deprecated hexadecimal lookup keys to a canonical network record. + * Canonical decimal keys remain the source of truth and are emitted first. + * + * @param values - Values keyed by canonical decimal TRON identifiers. + * @returns A record addressable by both decimal identifiers and hex aliases. + */ +export function withTronNetworkAliases(values: Record): Record { + const compatible = { ...values }; + for (const [alias, canonical] of Object.entries(LEGACY_TRON_NETWORK_ALIASES)) { + const value = values[canonical]; + if (value !== undefined) { + compatible[alias] = value; + } + } + return compatible; +} diff --git a/typescript/packages/mechanisms/tron/src/resource-sponsoring/memoryCoordinator.ts b/typescript/packages/mechanisms/tron/src/resource-sponsoring/memoryCoordinator.ts index aa739bdc..d3ac6fca 100644 --- a/typescript/packages/mechanisms/tron/src/resource-sponsoring/memoryCoordinator.ts +++ b/typescript/packages/mechanisms/tron/src/resource-sponsoring/memoryCoordinator.ts @@ -3,6 +3,7 @@ import type { Trc20SponsoringCoordinator, Trc20SponsoringOperation, } from "./types"; +import { tronNetworksEqual } from "../network"; /** Capacity limits for the development/test coordinator. */ export interface InMemoryTrc20SponsoringCoordinatorOptions { @@ -137,7 +138,7 @@ export class InMemoryTrc20SponsoringCoordinator implements Trc20SponsoringCoordi const activeForPayer = [...this.operations.values()].find( candidate => - candidate.network === operation.network && + tronNetworksEqual(candidate.network, operation.network) && candidate.payer === operation.payer && candidate.key !== operation.key && candidate.status !== "recovered" && diff --git a/typescript/packages/mechanisms/tron/src/resource-sponsoring/policy.ts b/typescript/packages/mechanisms/tron/src/resource-sponsoring/policy.ts index 0e1fe110..0cd847fd 100644 --- a/typescript/packages/mechanisms/tron/src/resource-sponsoring/policy.ts +++ b/typescript/packages/mechanisms/tron/src/resource-sponsoring/policy.ts @@ -1,4 +1,5 @@ import { TronWeb } from "tronweb"; +import { normalizeTronNetwork } from "../network"; import type { Trc20ResourceSponsoringPolicy, Trc20SponsoringPolicyDecision } from "./types"; /** Bounds for a local allowlist-and-cost sponsorship policy. */ @@ -28,19 +29,20 @@ function normalizeAddress(address: string): string { export function createStaticTrc20ResourceSponsoringPolicy( options: StaticTrc20ResourceSponsoringPolicyOptions, ): Trc20ResourceSponsoringPolicy { - const networks = new Set(options.allowedNetworks); + const networks = new Set(options.allowedNetworks.map(normalizeTronNetwork)); const assets = new Map( Object.entries(options.allowedAssets).map(([network, addresses]) => [ - network, + normalizeTronNetwork(network), new Set(addresses.map(normalizeAddress)), ]), ); return { async preview(request, plan): Promise { - if (!networks.has(request.network)) { + const network = normalizeTronNetwork(request.network); + if (!networks.has(network)) { return { allowed: false, reason: "unsupported_network" }; } - if (!assets.get(request.network)?.has(normalizeAddress(request.asset))) { + if (!assets.get(network)?.has(normalizeAddress(request.asset))) { return { allowed: false, reason: "approval_asset_not_allowed" }; } if (options.maxReplacementCost != null && plan.replacementCost > options.maxReplacementCost) { diff --git a/typescript/packages/mechanisms/tron/src/rpc.ts b/typescript/packages/mechanisms/tron/src/rpc.ts index 3660039b..f5802803 100644 --- a/typescript/packages/mechanisms/tron/src/rpc.ts +++ b/typescript/packages/mechanisms/tron/src/rpc.ts @@ -1,19 +1,26 @@ /** * TRON RPC resolution — builds a `TronWeb` instance for a CAIP-2 network. * - * Centralizes the `tron:` → fullHost mapping so the client/facilitator + * Centralizes the `tron:` → fullHost mapping so the client/facilitator * factories take a `network` instead of a caller-built `TronWeb` (mirrors EVM's * `adapters/chains.ts`). Isolated in its own module so unit tests can `vi.mock` * it and inject a fake TronWeb without touching the network. */ import { TronWeb } from "tronweb"; import { log } from "@bankofai/x402-core"; +import { + getTronNetworkValue, + normalizeTronNetwork, + TRON_MAINNET, + TRON_NILE, + TRON_SHASTA, +} from "./network"; /** CAIP-2 network → default TronGrid fullHost (used when an API key is supplied). */ const TRON_RPC: Record = { - "tron:0x2b6653dc": "https://api.trongrid.io", - "tron:0x94a9059e": "https://api.shasta.trongrid.io", - "tron:0xcd8690dc": "https://nile.trongrid.io", + [TRON_MAINNET]: "https://api.trongrid.io", + [TRON_SHASTA]: "https://api.shasta.trongrid.io", + [TRON_NILE]: "https://nile.trongrid.io", }; /** @@ -24,11 +31,11 @@ const TRON_RPC: Record = { * the Python/legacy `TRON_FALLBACK_RPC_URLS` (commit 8a893b8). */ const TRON_FALLBACK_RPC: Record = { - "tron:0x2b6653dc": "https://hptg.bankofai.io", + [TRON_MAINNET]: "https://hptg.bankofai.io", // nileex is the official Nile endpoint and works without a TronGrid key // (the default nile.trongrid.io host gets rate-limited unkeyed). - "tron:0xcd8690dc": "https://api.nileex.io", - "tron:0x94a9059e": "https://api.shasta.trongrid.io", + [TRON_NILE]: "https://api.nileex.io", + [TRON_SHASTA]: "https://api.shasta.trongrid.io", }; /** Networks already warned about a missing API key (warn once per process). */ @@ -48,7 +55,7 @@ export interface BuildTronWebOptions { * Precedence: explicit `rpcUrl` → keyed TronGrid default (when `apiKey` is set) * → key-less fallback endpoint → TronGrid default. Exported for unit testing. * - * @param network - CAIP-2 id, e.g. `"tron:0xcd8690dc"`. + * @param network - CAIP-2 id, e.g. `"tron:3448148188"`. * @param opts - Optional RPC override / API key. * @returns The resolved fullHost. * @throws When the network is unknown and no `rpcUrl` is supplied. @@ -57,20 +64,21 @@ export function resolveTronRpcUrl(network: string, opts: BuildTronWebOptions = { if (opts.rpcUrl) { return opts.rpcUrl; } + const canonicalNetwork = normalizeTronNetwork(network); if (!opts.apiKey) { - const fallback = TRON_FALLBACK_RPC[network]; + const fallback = getTronNetworkValue(TRON_FALLBACK_RPC, network); if (fallback) { - if (!warnedNetworks.has(network)) { - warnedNetworks.add(network); + if (!warnedNetworks.has(canonicalNetwork)) { + warnedNetworks.add(canonicalNetwork); log.warn( - `[x402] No TronGrid API key for ${network}; routing RPC to fallback ${fallback}. ` + + `[x402] No TronGrid API key for ${canonicalNetwork}; routing RPC to fallback ${fallback}. ` + `Pass apiKey (e.g. from TRON_GRID_API_KEY) to use TronGrid.`, ); } return fallback; } } - const fullHost = TRON_RPC[network]; + const fullHost = getTronNetworkValue(TRON_RPC, network); if (!fullHost) { throw new Error(`No TRON RPC configured for network ${network}; pass rpcUrl.`); } @@ -80,7 +88,7 @@ export function resolveTronRpcUrl(network: string, opts: BuildTronWebOptions = { /** * Builds a `TronWeb` for a CAIP-2 network (contract reads + broadcast). * - * @param network - CAIP-2 id, e.g. `"tron:0xcd8690dc"`. + * @param network - CAIP-2 id, e.g. `"tron:3448148188"`. * @param opts - Optional RPC override / API key. * @returns A TronWeb instance. */ diff --git a/typescript/packages/mechanisms/tron/src/shared/batch-settlement/authorizerSigner.ts b/typescript/packages/mechanisms/tron/src/shared/batch-settlement/authorizerSigner.ts index c7489596..02ad88f8 100644 --- a/typescript/packages/mechanisms/tron/src/shared/batch-settlement/authorizerSigner.ts +++ b/typescript/packages/mechanisms/tron/src/shared/batch-settlement/authorizerSigner.ts @@ -13,7 +13,7 @@ import { computeChannelId, getBatchSettlementTip712Domain } from "./utils"; * * @param signer - Authorizer signer holding the `receiverAuthorizer` key. * @param claims - Voucher claims to include in the batch. - * @param network - CAIP-2 network identifier (e.g. `"tron:0xcd8690dc"`). + * @param network - CAIP-2 network identifier (e.g. `"tron:3448148188"`). * @returns TIP-712 signature over `ClaimBatch(ClaimEntry[] claims)`. */ export async function signClaimBatch( @@ -42,7 +42,7 @@ export async function signClaimBatch( * @param channelId - Channel to authorize refund for. * @param amount - Refund amount (capped to unclaimed escrow onchain). * @param nonce - Must match onchain `refundNonce(channelId)`. - * @param network - CAIP-2 network identifier (e.g. `"tron:0xcd8690dc"`). + * @param network - CAIP-2 network identifier (e.g. `"tron:3448148188"`). * @returns TIP-712 signature over `Refund(channelId, nonce, amount)`. */ export async function signRefund( diff --git a/typescript/packages/mechanisms/tron/src/shared/batch-settlement/constants.ts b/typescript/packages/mechanisms/tron/src/shared/batch-settlement/constants.ts index cea3c6b8..ef8bddf1 100644 --- a/typescript/packages/mechanisms/tron/src/shared/batch-settlement/constants.ts +++ b/typescript/packages/mechanisms/tron/src/shared/batch-settlement/constants.ts @@ -7,6 +7,13 @@ * exactly. Only the per-network `verifyingContract` address and `chainId` differ, * so the typed-data structures below are copied verbatim from `@bankofai/x402-evm`. */ +import { + getTronNetworkValue, + TRON_MAINNET, + TRON_NILE, + TRON_SHASTA, + withTronNetworkAliases, +} from "../../network"; /** Scheme identifier for the batch-settlement payment scheme. */ export const BATCH_SETTLEMENT_SCHEME = "batch-settlement" as const; @@ -14,39 +21,39 @@ export const BATCH_SETTLEMENT_SCHEME = "batch-settlement" as const; /** * Deployed `x402BatchSettlement` contract addresses per TRON network (Base58Check). */ -export const BATCH_SETTLEMENT_ADDRESSES: Record = { - "tron:0x2b6653dc": "TW9yNhTySkEHYfjnGQU2u4NAsdb1tW4fbm", - "tron:0xcd8690dc": "TWBwWHZWwH8TzrZnbxit1J645VGYY1K2fA", - "tron:0x94a9059e": "TA3MZHMLsgi8JMU1DL8H4gKp1YjJKATibf", -}; +export const BATCH_SETTLEMENT_ADDRESSES: Record = withTronNetworkAliases({ + [TRON_MAINNET]: "TW9yNhTySkEHYfjnGQU2u4NAsdb1tW4fbm", + [TRON_NILE]: "TWBwWHZWwH8TzrZnbxit1J645VGYY1K2fA", + [TRON_SHASTA]: "TA3MZHMLsgi8JMU1DL8H4gKp1YjJKATibf", +}); /** * Deployed `ERC3009DepositCollector` contract addresses per TRON network. */ -export const ERC3009_DEPOSIT_COLLECTOR_ADDRESSES: Record = { - "tron:0x2b6653dc": "TTWA7aWMdx4jfcbp8XRAS2JAd2sUhyF9qj", - "tron:0xcd8690dc": "TJUQ3BQt4YFg8EeevjiUa5LbfSGz5BxzRW", - "tron:0x94a9059e": "TRd1KBfy1iUs6R45oZrtbLUjtcSKzXAvPG", -}; +export const ERC3009_DEPOSIT_COLLECTOR_ADDRESSES: Record = withTronNetworkAliases({ + [TRON_MAINNET]: "TTWA7aWMdx4jfcbp8XRAS2JAd2sUhyF9qj", + [TRON_NILE]: "TJUQ3BQt4YFg8EeevjiUa5LbfSGz5BxzRW", + [TRON_SHASTA]: "TRd1KBfy1iUs6R45oZrtbLUjtcSKzXAvPG", +}); /** * Deployed `Permit2DepositCollector` contract addresses per TRON network. */ -export const PERMIT2_DEPOSIT_COLLECTOR_ADDRESSES: Record = { - "tron:0x2b6653dc": "TAg5qqp1K9x5KeSTWnRa8LT79B5HUjzSHY", - "tron:0xcd8690dc": "TEp6bCqSEKAr99sCiqANC84RtRwx7xGbA4", - "tron:0x94a9059e": "TNmfrxbKCHqPUTj9zHVfg4Dq8WNZXPyf1x", -}; +export const PERMIT2_DEPOSIT_COLLECTOR_ADDRESSES: Record = withTronNetworkAliases({ + [TRON_MAINNET]: "TAg5qqp1K9x5KeSTWnRa8LT79B5HUjzSHY", + [TRON_NILE]: "TEp6bCqSEKAr99sCiqANC84RtRwx7xGbA4", + [TRON_SHASTA]: "TNmfrxbKCHqPUTj9zHVfg4Dq8WNZXPyf1x", +}); /** * Resolve the `x402BatchSettlement` address for a network. * - * @param network - CAIP-2 network identifier (e.g. `"tron:0xcd8690dc"`). + * @param network - CAIP-2 network identifier (e.g. `"tron:3448148188"`). * @returns The Base58Check contract address. * @throws When no contract is configured for the network. */ export function getBatchSettlementAddress(network: string): string { - const address = BATCH_SETTLEMENT_ADDRESSES[network]; + const address = getTronNetworkValue(BATCH_SETTLEMENT_ADDRESSES, network); if (!address) { throw new Error(`No x402BatchSettlement contract configured for network ${network}`); } @@ -61,7 +68,7 @@ export function getBatchSettlementAddress(network: string): string { * @throws When no collector is configured for the network. */ export function getErc3009DepositCollectorAddress(network: string): string { - const address = ERC3009_DEPOSIT_COLLECTOR_ADDRESSES[network]; + const address = getTronNetworkValue(ERC3009_DEPOSIT_COLLECTOR_ADDRESSES, network); if (!address) { throw new Error(`No ERC3009DepositCollector configured for network ${network}`); } @@ -76,7 +83,7 @@ export function getErc3009DepositCollectorAddress(network: string): string { * @throws When no collector is configured for the network. */ export function getPermit2DepositCollectorAddress(network: string): string { - const address = PERMIT2_DEPOSIT_COLLECTOR_ADDRESSES[network]; + const address = getTronNetworkValue(PERMIT2_DEPOSIT_COLLECTOR_ADDRESSES, network); if (!address) { throw new Error(`No Permit2DepositCollector configured for network ${network}`); } diff --git a/typescript/packages/mechanisms/tron/src/shared/batch-settlement/utils.ts b/typescript/packages/mechanisms/tron/src/shared/batch-settlement/utils.ts index aeb3c616..c78b1221 100644 --- a/typescript/packages/mechanisms/tron/src/shared/batch-settlement/utils.ts +++ b/typescript/packages/mechanisms/tron/src/shared/batch-settlement/utils.ts @@ -45,7 +45,7 @@ export function hashTypedData( /** * Returns the full TIP-712 domain for the batch-settlement contract on a network. * - * @param network - CAIP-2 network identifier (e.g. `"tron:0xcd8690dc"`). + * @param network - CAIP-2 network identifier (e.g. `"tron:3448148188"`). * @returns Domain with `name`, `version`, `chainId`, and EVM-hex `verifyingContract`. */ export function getBatchSettlementTip712Domain(network: string): Eip712Domain { diff --git a/typescript/packages/mechanisms/tron/src/shared/defaultAssets.ts b/typescript/packages/mechanisms/tron/src/shared/defaultAssets.ts index d782f471..48c4383a 100644 --- a/typescript/packages/mechanisms/tron/src/shared/defaultAssets.ts +++ b/typescript/packages/mechanisms/tron/src/shared/defaultAssets.ts @@ -66,7 +66,7 @@ function toExactAssetInfo(token: TokenInfo): ExactDefaultAssetInfo { * * Resolves the network's default symbol (USDT) against the token registry. * - * @param network - The CAIP-2 network identifier (e.g., "tron:0xcd8690dc"). + * @param network - The CAIP-2 network identifier (e.g., "tron:3448148188"). * @returns The default asset configuration for the network. * @throws Error if no default asset is configured for the network. */ diff --git a/typescript/packages/mechanisms/tron/src/shared/extensions/resourceSponsoring.ts b/typescript/packages/mechanisms/tron/src/shared/extensions/resourceSponsoring.ts index b1dc088c..cbbbc6b3 100644 --- a/typescript/packages/mechanisms/tron/src/shared/extensions/resourceSponsoring.ts +++ b/typescript/packages/mechanisms/tron/src/shared/extensions/resourceSponsoring.ts @@ -1,6 +1,7 @@ import type { PaymentPayloadContext, PaymentRequirements } from "@bankofai/x402-core/types"; import { erc20AllowanceAbi, PERMIT2_ADDRESSES } from "../../constants"; import type { ClientTronSigner } from "../../signer"; +import { tronNetworksEqual } from "../../network"; import { createTrc20ApprovalPolicy } from "../../approvalPolicy"; import { TRC20_APPROVAL_LIFETIME_SECONDS, @@ -53,7 +54,7 @@ export async function trySignTrc20ApprovalExtension( if (declaration.info?.version !== TRC20_APPROVAL_RESOURCE_SPONSORING_VERSION) { throw new Error("trc20ApprovalResourceSponsoring: unsupported extension version"); } - if (!signer.network || signer.network !== requirements.network) { + if (!signer.network || !tronNetworksEqual(signer.network, requirements.network)) { throw new Error("approval_signer_network_mismatch"); } const permit2 = PERMIT2_ADDRESSES[requirements.network]; diff --git a/typescript/packages/mechanisms/tron/src/shared/gasfree/api.ts b/typescript/packages/mechanisms/tron/src/shared/gasfree/api.ts index 84b584bd..ec152273 100644 --- a/typescript/packages/mechanisms/tron/src/shared/gasfree/api.ts +++ b/typescript/packages/mechanisms/tron/src/shared/gasfree/api.ts @@ -6,6 +6,7 @@ * polling. Ported from the reference implementation. */ import { log } from "@bankofai/x402-core"; +import { normalizeTronNetwork } from "../../network"; export interface GasFreeResponse { code: number; @@ -304,7 +305,7 @@ export function createGasFreeApiClients( ): Record { const clients: Record = {}; for (const [network, url] of Object.entries(baseUrls)) { - clients[network] = new GasFreeAPIClient(url); + clients[normalizeTronNetwork(network)] = new GasFreeAPIClient(url); } return clients; } diff --git a/typescript/packages/mechanisms/tron/src/shared/gasfree/config.ts b/typescript/packages/mechanisms/tron/src/shared/gasfree/config.ts index 84f168d8..f9675eae 100644 --- a/typescript/packages/mechanisms/tron/src/shared/gasfree/config.ts +++ b/typescript/packages/mechanisms/tron/src/shared/gasfree/config.ts @@ -1,4 +1,11 @@ import { getTronChainId, tronAddressToEvm } from "../../utils"; +import { + getTronNetworkValue, + TRON_MAINNET, + TRON_NILE, + TRON_SHASTA, + withTronNetworkAliases, +} from "../../network"; /** * GasFree network configuration: on-chain controller/beacon addresses and the @@ -11,18 +18,18 @@ import { getTronChainId, tronAddressToEvm } from "../../utils"; */ /** GasFreeController contract addresses (Base58Check) per network. */ -export const GASFREE_CONTROLLER_ADDRESSES: Record = { - "tron:0x2b6653dc": "TFFAMQLZybALaLb4uxHA9RBE7pxhUAjF3U", - "tron:0x94a9059e": "TQghdCeVDA6CnuNVTUhfaAyPfTetqZWNpm", - "tron:0xcd8690dc": "THQGuFzL87ZqhxkgqYEryRAd7gqFqL5rdc", -}; +export const GASFREE_CONTROLLER_ADDRESSES: Record = withTronNetworkAliases({ + [TRON_MAINNET]: "TFFAMQLZybALaLb4uxHA9RBE7pxhUAjF3U", + [TRON_SHASTA]: "TQghdCeVDA6CnuNVTUhfaAyPfTetqZWNpm", + [TRON_NILE]: "THQGuFzL87ZqhxkgqYEryRAd7gqFqL5rdc", +}); /** GasFree beacon contract addresses (Base58Check) per network. */ -export const GASFREE_BEACON_ADDRESSES: Record = { - "tron:0x2b6653dc": "TSP9UW6FQhT76XD2jWA6ipGMx3yGbjDffP", - "tron:0x94a9059e": "TQ1jvA3nLDMDNbJoMPLzTPoqAg8NvZ5CCW", - "tron:0xcd8690dc": "TLtCGmaxH3PbuaF6kbybwteZcHptEdgQGC", -}; +export const GASFREE_BEACON_ADDRESSES: Record = withTronNetworkAliases({ + [TRON_MAINNET]: "TSP9UW6FQhT76XD2jWA6ipGMx3yGbjDffP", + [TRON_SHASTA]: "TQ1jvA3nLDMDNbJoMPLzTPoqAg8NvZ5CCW", + [TRON_NILE]: "TLtCGmaxH3PbuaF6kbybwteZcHptEdgQGC", +}); /** * Default GasFree relayer API base URLs per network. @@ -34,11 +41,11 @@ export const GASFREE_BEACON_ADDRESSES: Record = { * requires ApiKey/Secret auth) or your own relayer, override via the GasFree API * client / registration config. */ -export const GASFREE_API_BASE_URLS: Record = { - "tron:0x2b6653dc": "https://facilitator.bankofai.io/mainnet", - "tron:0x94a9059e": "https://facilitator.bankofai.io/shasta", - "tron:0xcd8690dc": "https://facilitator.bankofai.io/nile", -}; +export const GASFREE_API_BASE_URLS: Record = withTronNetworkAliases({ + [TRON_MAINNET]: "https://facilitator.bankofai.io/mainnet", + [TRON_SHASTA]: "https://facilitator.bankofai.io/shasta", + [TRON_NILE]: "https://facilitator.bankofai.io/nile", +}); /** * Get the GasFreeController address (Base58Check) for a network. @@ -48,7 +55,7 @@ export const GASFREE_API_BASE_URLS: Record = { * @throws Error if GasFree is not configured for the network. */ export function getGasFreeControllerAddress(network: string): string { - const addr = GASFREE_CONTROLLER_ADDRESSES[network]; + const addr = getTronNetworkValue(GASFREE_CONTROLLER_ADDRESSES, network); if (!addr) { throw new Error(`GasFreeController not configured for network: ${network}`); } @@ -63,7 +70,7 @@ export function getGasFreeControllerAddress(network: string): string { * @throws Error if no default API URL is configured for the network. */ export function getGasFreeApiBaseUrl(network: string): string { - const url = GASFREE_API_BASE_URLS[network]; + const url = getTronNetworkValue(GASFREE_API_BASE_URLS, network); if (!url) { throw new Error(`No GasFree API URL configured for network: ${network}`); } diff --git a/typescript/packages/mechanisms/tron/src/shared/tokens.ts b/typescript/packages/mechanisms/tron/src/shared/tokens.ts index 1c372a5f..788fff60 100644 --- a/typescript/packages/mechanisms/tron/src/shared/tokens.ts +++ b/typescript/packages/mechanisms/tron/src/shared/tokens.ts @@ -1,5 +1,6 @@ import type { AssetAmount, Network } from "@bankofai/x402-core/types"; import { convertToTokenAmount as convertCoreTokenAmount } from "@bankofai/x402-core/utils"; +import { normalizeTronNetwork, TRON_MAINNET, TRON_NILE, TRON_SHASTA } from "../network"; /** * Token registry for TRON networks. @@ -38,11 +39,11 @@ export interface TokenInfo { /** * Built-in TRC-20 tokens indexed by CAIP-2 network and uppercased symbol. * - * Note: `tron:0x2b6653dc`, `tron:0xcd8690dc`, and `tron:0x94a9059e` have Permit2 + + * Note: Mainnet, Nile, and Shasta have Permit2 + * x402Permit2Proxy deployments, so their tokens default to `permit2`. */ const TOKENS: Record> = { - "tron:0x2b6653dc": { + [TRON_MAINNET]: { USDT: { address: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", decimals: 6, @@ -60,7 +61,7 @@ const TOKENS: Record> = { assetTransferMethod: "permit2", }, }, - "tron:0xcd8690dc": { + [TRON_NILE]: { USDT: { address: "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf", decimals: 6, @@ -78,7 +79,7 @@ const TOKENS: Record> = { assetTransferMethod: "permit2", }, }, - "tron:0x94a9059e": { + [TRON_SHASTA]: { USDT: { address: "TG3XXyExBkPp9nzdajDZsozEu4BkaSJozs", decimals: 6, @@ -107,12 +108,12 @@ export function getDefaultAssetSymbol(): string { /** * Get token info by network and symbol (case-insensitive). * - * @param network - CAIP-2 network identifier (e.g. "tron:0xcd8690dc"). + * @param network - CAIP-2 network identifier (e.g. "tron:3448148188"). * @param symbol - Token symbol (e.g. "USDT"); matched case-insensitively. * @returns The token info, or undefined if not registered. */ export function getToken(network: Network, symbol: string): TokenInfo | undefined { - return TOKENS[network]?.[symbol.toUpperCase()]; + return TOKENS[normalizeTronNetwork(network)]?.[symbol.toUpperCase()]; } /** @@ -123,7 +124,7 @@ export function getToken(network: Network, symbol: string): TokenInfo | undefine * @returns The token info, or undefined if no token matches. */ export function findByAddress(network: Network, address: string): TokenInfo | undefined { - const tokens = TOKENS[network]; + const tokens = TOKENS[normalizeTronNetwork(network)]; if (!tokens) return undefined; const lower = address.toLowerCase(); return Object.values(tokens).find(t => t.address.toLowerCase() === lower); @@ -136,7 +137,7 @@ export function findByAddress(network: Network, address: string): TokenInfo | un * @returns A map of symbol to token info (empty object if none registered). */ export function getNetworkTokens(network: Network): Record { - return TOKENS[network] ?? {}; + return TOKENS[normalizeTronNetwork(network)] ?? {}; } /** @@ -146,10 +147,11 @@ export function getNetworkTokens(network: Network): Record { * @param token - The token info to register; keyed by its uppercased symbol. */ export function registerToken(network: Network, token: TokenInfo): void { - if (!TOKENS[network]) { - TOKENS[network] = {}; + const canonicalNetwork = normalizeTronNetwork(network); + if (!TOKENS[canonicalNetwork]) { + TOKENS[canonicalNetwork] = {}; } - TOKENS[network][token.symbol.toUpperCase()] = token; + TOKENS[canonicalNetwork][token.symbol.toUpperCase()] = token; } /** @@ -208,7 +210,7 @@ export function convertToTokenAmount(decimalAmount: string, decimals: number): s * * @param price - `" "` (e.g. `"1.25 USDT"`). * Whitespace-tolerant; symbol lookup is case-insensitive. - * @param network - CAIP-2 network identifier (e.g. `"tron:0xcd8690dc"`). + * @param network - CAIP-2 network identifier (e.g. `"tron:3448148188"`). * @returns The asset amount in smallest units, with token metadata in `extra`. * @throws If the format is invalid, the amount is not a non-negative decimal, * the token is not registered, or the amount has more decimal places diff --git a/typescript/packages/mechanisms/tron/src/signer.ts b/typescript/packages/mechanisms/tron/src/signer.ts index 6276a1ab..2bf93929 100644 --- a/typescript/packages/mechanisms/tron/src/signer.ts +++ b/typescript/packages/mechanisms/tron/src/signer.ts @@ -7,6 +7,7 @@ import { erc20ApproveAbi, } from "./constants"; import { buildTronWeb } from "./rpc"; +import { normalizeTronNetwork } from "./network"; import { tronAddressToEvm } from "./utils"; import { log } from "@bankofai/x402-core"; import { createTrc20ApprovalPolicy, type Trc20ApprovalPolicy } from "./approvalPolicy"; @@ -241,7 +242,7 @@ export function toFacilitatorTronSigner( /** Options for {@link createClientTronSigner}. */ export interface CreateClientTronSignerOptions { - /** CAIP-2 network, e.g. `"tron:0xcd8690dc"`. The TronWeb client is built from it. */ + /** Decimal CAIP-2 network, e.g. `"tron:3448148188"`. The TronWeb client is built from it. */ network: string; /** RPC fullHost override; falls back to the network's default. */ rpcUrl?: string; @@ -274,7 +275,7 @@ export interface CreateClientTronSignerOptions { * * @example * ```typescript - * const signer = await createClientTronSigner(wallet, { network: "tron:0xcd8690dc" }); + * const signer = await createClientTronSigner(wallet, { network: "tron:3448148188" }); * client.register("tron:*", new ExactTronScheme(signer)); * ``` */ @@ -312,7 +313,7 @@ export async function createClientTronSigner( return { ...base, - network: opts.network, + network: normalizeTronNetwork(opts.network), approvalPolicy, ensureAllowance: allowanceArgs => ensurePermit2Allowance( @@ -405,7 +406,7 @@ export type FacilitatorTronWallet = FacilitatorWallet; /** Options for {@link createFacilitatorTronSigner}. */ export interface FacilitatorTronSignerOptions { - /** CAIP-2 network, e.g. `"tron:0xcd8690dc"`. The TronWeb client is built from it. */ + /** CAIP-2 network, e.g. `"tron:3448148188"`. The TronWeb client is built from it. */ network: string; /** RPC fullHost override; falls back to the network's default. */ rpcUrl?: string; @@ -901,8 +902,8 @@ async function ensurePermit2Allowance( * * @example * ```typescript - * const signer = await createFacilitatorTronSigner(wallet, { network: "tron:0xcd8690dc" }); - * facilitator.register("tron:0xcd8690dc", new ExactTronScheme(signer)); + * const signer = await createFacilitatorTronSigner(wallet, { network: "tron:3448148188" }); + * facilitator.register("tron:3448148188", new ExactTronScheme(signer)); * ``` */ export async function createFacilitatorTronSigner( diff --git a/typescript/packages/mechanisms/tron/src/upto/facilitator/permit2.ts b/typescript/packages/mechanisms/tron/src/upto/facilitator/permit2.ts index f97c6432..eb69ff2f 100644 --- a/typescript/packages/mechanisms/tron/src/upto/facilitator/permit2.ts +++ b/typescript/packages/mechanisms/tron/src/upto/facilitator/permit2.ts @@ -16,6 +16,7 @@ import { import { FacilitatorTronSigner } from "../../signer"; import { UptoPermit2Payload } from "../../types"; import { getTronChainId, normalizeAddressForSigning } from "../../utils"; +import { tronNetworksEqual } from "../../network"; import * as errors from "./errors"; import { executeTrc20Sponsorship, @@ -56,7 +57,7 @@ export async function verifyUptoPermit2( return { isValid: false, invalidReason: errors.INVALID_SCHEME, payer }; } - if (payload.accepted.network !== requirements.network) { + if (!tronNetworksEqual(payload.accepted.network, requirements.network)) { return { isValid: false, invalidReason: errors.NETWORK_MISMATCH, payer }; } diff --git a/typescript/packages/mechanisms/tron/src/utils.ts b/typescript/packages/mechanisms/tron/src/utils.ts index dc9686dd..02e85e5d 100644 --- a/typescript/packages/mechanisms/tron/src/utils.ts +++ b/typescript/packages/mechanisms/tron/src/utils.ts @@ -1,10 +1,11 @@ import { TronWeb } from "tronweb"; import { TRON_CHAIN_IDS } from "./constants"; +import { normalizeTronNetwork } from "./network"; /** * Get the numeric chain ID for a TRON network identifier. * - * @param network - The network identifier in CAIP-2 format (e.g., "tron:0xcd8690dc") + * @param network - The network identifier in CAIP-2 format (e.g., "tron:3448148188") * @returns The numeric chain ID * @throws Error if the network is not a recognized TRON network */ @@ -13,7 +14,7 @@ export function getTronChainId(network: string): number { throw new Error(`Unsupported network format: ${network} (expected tron:*)`); } - const chainId = TRON_CHAIN_IDS[network]; + const chainId = TRON_CHAIN_IDS[normalizeTronNetwork(network)]; if (chainId === undefined) { throw new Error(`Unknown TRON network: ${network}`); } diff --git a/typescript/packages/mechanisms/tron/test/unit/advisory-fee-removal.test.ts b/typescript/packages/mechanisms/tron/test/unit/advisory-fee-removal.test.ts index 31f3cc4b..3c00d8c4 100644 --- a/typescript/packages/mechanisms/tron/test/unit/advisory-fee-removal.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/advisory-fee-removal.test.ts @@ -4,7 +4,7 @@ import { ExactTronScheme } from "../../src/exact/server/scheme"; import { UptoTronScheme } from "../../src/upto/server/scheme"; import { ExactGasFreeTronScheme } from "../../src/gasfree/server/scheme"; -const NETWORK = "tron:0xcd8690dc"; +const NETWORK = "tron:3448148188"; function requirements(scheme: "exact" | "upto"): PaymentRequirements { return { diff --git a/typescript/packages/mechanisms/tron/test/unit/batch-refund-network.test.ts b/typescript/packages/mechanisms/tron/test/unit/batch-refund-network.test.ts index ebb8f9db..d8e958a0 100644 --- a/typescript/packages/mechanisms/tron/test/unit/batch-refund-network.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/batch-refund-network.test.ts @@ -14,7 +14,7 @@ import type { PaymentRequired, PaymentRequirements } from "@bankofai/x402-core/t * * We drive scheme.refund() with a mocked 402 whose accepts list an EVM option * FIRST, and a signer whose readContract throws a sentinel. Reaching the sentinel - * proves the TRON requirement was selected (computeChannelId(tron:0xcd8690dc) ran and + * proves the TRON requirement was selected (computeChannelId(tron:3448148188) ran and * the flow got to the on-chain channel read); a regression would throw the * eip155 network error before that. */ @@ -40,7 +40,7 @@ function evmAccept(): PaymentRequirements { function tronAccept(): PaymentRequirements { return { scheme: "batch-settlement", - network: "tron:0xcd8690dc", + network: "tron:3448148188", asset: TRON_ASSET, amount: "1000000", payTo: TRON_PAYTO, @@ -75,7 +75,7 @@ describe("TRON batch-settlement refund — multi-network route", () => { const scheme = new BatchSettlementTronScheme(mockSigner()); const fetchImpl = vi.fn().mockResolvedValue(multiNetwork402()); - // Reaching the on-chain read (sentinel) proves computeChannelId(tron:0xcd8690dc) + // Reaching the on-chain read (sentinel) proves computeChannelId(tron:3448148188) // ran. A regression would throw "Unsupported network format: eip155:97". await expect(scheme.refund(URL, { fetch: fetchImpl })).rejects.toThrow("READCONTRACT_REACHED"); }); diff --git a/typescript/packages/mechanisms/tron/test/unit/batch-settlement/deposit-sig.test.ts b/typescript/packages/mechanisms/tron/test/unit/batch-settlement/deposit-sig.test.ts index d2af1127..604f04a4 100644 --- a/typescript/packages/mechanisms/tron/test/unit/batch-settlement/deposit-sig.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/batch-settlement/deposit-sig.test.ts @@ -34,7 +34,7 @@ import type { * and the channel-bound Permit2 `DepositWitness`. */ -const NETWORK = "tron:0xcd8690dc"; +const NETWORK = "tron:3448148188"; const PAYER_PK = "da146374a75310b9666e834ee4ad0866d6f4035967bfc76217c5a495fff9f0d0"; const ASSET = "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf"; const PAY_TO = "TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"; diff --git a/typescript/packages/mechanisms/tron/test/unit/batch-settlement/digest.test.ts b/typescript/packages/mechanisms/tron/test/unit/batch-settlement/digest.test.ts index 07231aae..e5d467f4 100644 --- a/typescript/packages/mechanisms/tron/test/unit/batch-settlement/digest.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/batch-settlement/digest.test.ts @@ -25,7 +25,7 @@ import type { FacilitatorTronSigner } from "../../../src/signer"; * contract), and (2) a client-signed voucher verifies via the facilitator path. */ -const NETWORK = "tron:0xcd8690dc"; +const NETWORK = "tron:3448148188"; const PAYER_PK = "da146374a75310b9666e834ee4ad0866d6f4035967bfc76217c5a495fff9f0d0"; // Read on-chain from the deployed Nile contract (CHANNEL_CONFIG_TYPEHASH()). diff --git a/typescript/packages/mechanisms/tron/test/unit/batch-settlement/lifecycle.test.ts b/typescript/packages/mechanisms/tron/test/unit/batch-settlement/lifecycle.test.ts index 704bf4bd..ae1758cf 100644 --- a/typescript/packages/mechanisms/tron/test/unit/batch-settlement/lifecycle.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/batch-settlement/lifecycle.test.ts @@ -18,7 +18,7 @@ import type { PaymentRequirements } from "@bankofai/x402-core/types"; * and how the cumulative voucher ceiling advances across charges. */ -const NETWORK = "tron:0xcd8690dc"; +const NETWORK = "tron:3448148188"; const PAYER_PK = "da146374a75310b9666e834ee4ad0866d6f4035967bfc76217c5a495fff9f0d0"; const PAY_TO = "TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"; diff --git a/typescript/packages/mechanisms/tron/test/unit/batch-settlement/server-hooks.test.ts b/typescript/packages/mechanisms/tron/test/unit/batch-settlement/server-hooks.test.ts index fdce7d2f..223b763a 100644 --- a/typescript/packages/mechanisms/tron/test/unit/batch-settlement/server-hooks.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/batch-settlement/server-hooks.test.ts @@ -29,7 +29,7 @@ import * as Errors from "../../../src/batch-settlement/errors"; * These are pure logic over an in-memory channel store; no chain access. */ -const NETWORK = "tron:0xcd8690dc"; +const NETWORK = "tron:3448148188"; const PAYER_PK = "da146374a75310b9666e834ee4ad0866d6f4035967bfc76217c5a495fff9f0d0"; const RECEIVER = "0x9876543210987654321098765432109876543210"; const RECEIVER_AUTHORIZER = "0x1111111111111111111111111111111111111111"; diff --git a/typescript/packages/mechanisms/tron/test/unit/client-allowance.test.ts b/typescript/packages/mechanisms/tron/test/unit/client-allowance.test.ts index 7c2375a6..72637077 100644 --- a/typescript/packages/mechanisms/tron/test/unit/client-allowance.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/client-allowance.test.ts @@ -22,7 +22,7 @@ vi.mock("../../src/rpc", () => ({ buildTronWeb: vi.fn() })); * reads the allowance and broadcasts that approve when (and only when) needed. */ -const NETWORK = "tron:0xcd8690dc"; +const NETWORK = "tron:3448148188"; const OWNER = "TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"; const TOKEN = "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf"; // nile USDT const PERMIT2 = PERMIT2_ADDRESSES[NETWORK]!; diff --git a/typescript/packages/mechanisms/tron/test/unit/facilitator-wallet.test.ts b/typescript/packages/mechanisms/tron/test/unit/facilitator-wallet.test.ts index 2b812a87..2fa11b29 100644 --- a/typescript/packages/mechanisms/tron/test/unit/facilitator-wallet.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/facilitator-wallet.test.ts @@ -14,7 +14,7 @@ vi.mock("../../src/rpc", () => ({ buildTronWeb: vi.fn() })); * from our ABI and pinned here to the known-correct canonical string. */ -const NETWORK = "tron:0xcd8690dc"; +const NETWORK = "tron:3448148188"; const FAC_ADDR = "TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"; const PROXY = "TFGoaq2KjizijgjtkVxT7yjffW1A5T1j6F"; const TOKEN = "0x" + "a".repeat(40); diff --git a/typescript/packages/mechanisms/tron/test/unit/flow-integration.test.ts b/typescript/packages/mechanisms/tron/test/unit/flow-integration.test.ts index 3794c5af..adb529f1 100644 --- a/typescript/packages/mechanisms/tron/test/unit/flow-integration.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/flow-integration.test.ts @@ -34,7 +34,7 @@ vi.mock("../../src/rpc", () => ({ buildTronWeb: vi.fn() })); * stubbed, keeping the suite offline and deterministic. */ -const NETWORK = "tron:0xcd8690dc"; +const NETWORK = "tron:3448148188"; const USDT = "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf"; const PAYER_PK = "da146374a75310b9666e834ee4ad0866d6f4035967bfc76217c5a495fff9f0d0"; const FAC_PK = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"; diff --git a/typescript/packages/mechanisms/tron/test/unit/gasfree-digest.test.ts b/typescript/packages/mechanisms/tron/test/unit/gasfree-digest.test.ts index edb69243..bf3f4720 100644 --- a/typescript/packages/mechanisms/tron/test/unit/gasfree-digest.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/gasfree-digest.test.ts @@ -20,7 +20,7 @@ import type { GasFreeAddressInfo, GasFreeProvider } from "../../src/shared/gasfr * verification fails here before any real submission. */ -const NETWORK = "tron:0xcd8690dc"; +const NETWORK = "tron:3448148188"; const PAYER_PK = "da146374a75310b9666e834ee4ad0866d6f4035967bfc76217c5a495fff9f0d0"; const FACIL_PK = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"; const PAY_TO = "TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"; diff --git a/typescript/packages/mechanisms/tron/test/unit/gasfree-flow.test.ts b/typescript/packages/mechanisms/tron/test/unit/gasfree-flow.test.ts index e500b2bc..11cce2ef 100644 --- a/typescript/packages/mechanisms/tron/test/unit/gasfree-flow.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/gasfree-flow.test.ts @@ -14,7 +14,7 @@ import { filterAffordableRequirements } from "../../src/shared/balance"; */ const addr = (pk: string) => TronWeb.address.fromPrivateKey(pk) as string; -const NETWORK = "tron:0xcd8690dc"; +const NETWORK = "tron:3448148188"; const USER = addr("0000000000000000000000000000000000000000000000000000000000000001"); const PAY_TO = "TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"; const ASSET = "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf"; // Nile USDT (6 decimals) @@ -165,7 +165,7 @@ describe("GasFree client createPaymentPayload", () => { extensions: { skipBalanceCheck: true }, }); const p = result.payload as ExactGasFreePayload; - // tron:0xcd8690dc max window = 3595s + // tron:3448148188 max window = 3595s const deadline = Number(p.gasfree.deadline); expect(deadline).toBeGreaterThanOrEqual(before + 3590); expect(deadline).toBeLessThanOrEqual(before + 3595 + 2); diff --git a/typescript/packages/mechanisms/tron/test/unit/network.test.ts b/typescript/packages/mechanisms/tron/test/unit/network.test.ts new file mode 100644 index 00000000..a1d511ca --- /dev/null +++ b/typescript/packages/mechanisms/tron/test/unit/network.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from "vitest"; +import { + PERMIT2_ADDRESSES, + TRON_CHAIN_IDS, + TRON_MAINNET, + TRON_NILE, + TRON_SHASTA, + getTronNetworkValue, + normalizeTronNetwork, + tronNetworksEqual, +} from "../../src"; + +describe("TRON CAIP-2 network identifiers", () => { + it("exports decimal identifiers as canonical constants", () => { + expect(TRON_MAINNET).toBe("tron:728126428"); + expect(TRON_NILE).toBe("tron:3448148188"); + expect(TRON_SHASTA).toBe("tron:2494104990"); + }); + + it.each([ + ["tron:0x2b6653dc", TRON_MAINNET], + ["tron:0xcd8690dc", TRON_NILE], + ["tron:0x94a9059e", TRON_SHASTA], + ["tron:0XCD8690DC", TRON_NILE], + ])("normalizes %s to %s", (legacy, canonical) => { + expect(normalizeTronNetwork(legacy)).toBe(canonical); + expect(tronNetworksEqual(legacy, canonical)).toBe(true); + }); + + it("leaves unknown non-hex identifiers unchanged", () => { + expect(normalizeTronNetwork("tron:unknown")).toBe("tron:unknown"); + }); + + it("resolves caller-owned records keyed by either representation", () => { + expect(getTronNetworkValue({ [TRON_NILE]: "decimal" }, "tron:0xcd8690dc")).toBe("decimal"); + expect(getTronNetworkValue({ "tron:0xcd8690dc": "hex" }, TRON_NILE)).toBe("hex"); + }); + + it("keeps direct lookup compatibility for exported network maps", () => { + expect(TRON_CHAIN_IDS["tron:0xcd8690dc"]).toBe(TRON_CHAIN_IDS[TRON_NILE]); + expect(PERMIT2_ADDRESSES["tron:0xcd8690dc"]).toBe(PERMIT2_ADDRESSES[TRON_NILE]); + }); +}); diff --git a/typescript/packages/mechanisms/tron/test/unit/permit2-digest.test.ts b/typescript/packages/mechanisms/tron/test/unit/permit2-digest.test.ts index 8914dfa9..a761e45c 100644 --- a/typescript/packages/mechanisms/tron/test/unit/permit2-digest.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/permit2-digest.test.ts @@ -24,7 +24,7 @@ import type { ExactPermit2Payload } from "../../src/types"; * contract, these assertions fail before any gas is spent. */ -const NETWORK = "tron:0xcd8690dc"; +const NETWORK = "tron:3448148188"; // Fixed test key → deterministic payer address (no network, no randomness). const PAYER_PK = "da146374a75310b9666e834ee4ad0866d6f4035967bfc76217c5a495fff9f0d0"; const PAY_TO = "TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"; diff --git a/typescript/packages/mechanisms/tron/test/unit/rpc.test.ts b/typescript/packages/mechanisms/tron/test/unit/rpc.test.ts index a33c101d..bc43af16 100644 --- a/typescript/packages/mechanisms/tron/test/unit/rpc.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/rpc.test.ts @@ -11,24 +11,28 @@ import { resolveTronRpcUrl } from "../../src/rpc"; describe("resolveTronRpcUrl", () => { it("uses an explicit rpcUrl override above everything else", () => { - expect(resolveTronRpcUrl("tron:0xcd8690dc", { rpcUrl: "https://my.host", apiKey: "k" })).toBe( + expect(resolveTronRpcUrl("tron:3448148188", { rpcUrl: "https://my.host", apiKey: "k" })).toBe( "https://my.host", ); }); it("uses the keyed TronGrid default when an API key is supplied", () => { - expect(resolveTronRpcUrl("tron:0xcd8690dc", { apiKey: "k" })).toBe("https://nile.trongrid.io"); - expect(resolveTronRpcUrl("tron:0x2b6653dc", { apiKey: "k" })).toBe("https://api.trongrid.io"); + expect(resolveTronRpcUrl("tron:3448148188", { apiKey: "k" })).toBe("https://nile.trongrid.io"); + expect(resolveTronRpcUrl("tron:728126428", { apiKey: "k" })).toBe("https://api.trongrid.io"); }); it("uses the key-less fallback for nile when no API key is set", () => { // nile.trongrid.io rate-limits unkeyed; nileex works key-less. - expect(resolveTronRpcUrl("tron:0xcd8690dc")).toBe("https://api.nileex.io"); + expect(resolveTronRpcUrl("tron:3448148188")).toBe("https://api.nileex.io"); + }); + + it("accepts the deprecated hexadecimal nile alias", () => { + expect(resolveTronRpcUrl("tron:3448148188")).toBe("https://api.nileex.io"); }); it("uses the key-less fallback for mainnet/shasta when no API key is set", () => { - expect(resolveTronRpcUrl("tron:0x2b6653dc")).toBe("https://hptg.bankofai.io"); - expect(resolveTronRpcUrl("tron:0x94a9059e")).toBe("https://api.shasta.trongrid.io"); + expect(resolveTronRpcUrl("tron:728126428")).toBe("https://hptg.bankofai.io"); + expect(resolveTronRpcUrl("tron:2494104990")).toBe("https://api.shasta.trongrid.io"); }); it("throws for an unknown network with no rpcUrl", () => { diff --git a/typescript/packages/mechanisms/tron/test/unit/selection.test.ts b/typescript/packages/mechanisms/tron/test/unit/selection.test.ts index 433e3508..ae0417a4 100644 --- a/typescript/packages/mechanisms/tron/test/unit/selection.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/selection.test.ts @@ -15,7 +15,7 @@ import { * No core changes: selection is a sync selector, affordability is async helpers. */ -const NETWORK = "tron:0xcd8690dc"; +const NETWORK = "tron:3448148188"; const USDT = "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf"; // 6 decimals const USDD = "TGjgvdTWWrybVLaVeFqSyVqJQWjxqRYbaK"; // 18 decimals diff --git a/typescript/packages/mechanisms/tron/test/unit/server.moneyConversion.test.ts b/typescript/packages/mechanisms/tron/test/unit/server.moneyConversion.test.ts index 4105ebfc..d7f2b986 100644 --- a/typescript/packages/mechanisms/tron/test/unit/server.moneyConversion.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/server.moneyConversion.test.ts @@ -3,8 +3,8 @@ import { ExactTronScheme } from "../../src/exact/server/scheme"; import { UptoTronScheme } from "../../src/upto/server/scheme"; import { BatchSettlementTronScheme } from "../../src/batch-settlement/server/scheme"; -// tron:0xcd8690dc USDT is 6 decimals -const NILE = "tron:0xcd8690dc"; +// tron:3448148188 USDT is 6 decimals +const NILE = "tron:3448148188"; const USDT_NILE = "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf"; const USDD_NILE = "TGjgvdTWWrybVLaVeFqSyVqJQWjxqRYbaK"; diff --git a/typescript/packages/mechanisms/tron/test/unit/signer-wallet.test.ts b/typescript/packages/mechanisms/tron/test/unit/signer-wallet.test.ts index bc7a4a3d..8f3d3d5f 100644 --- a/typescript/packages/mechanisms/tron/test/unit/signer-wallet.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/signer-wallet.test.ts @@ -42,7 +42,7 @@ function tron(): TronWeb { /** Build a client signer with the given TronWeb routed through the mocked builder. */ async function makeClient(tw: TronWeb, wallet: ClientTronWallet) { vi.mocked(buildTronWeb).mockReturnValue(tw); - return createClientTronSigner(wallet, { network: "tron:0xcd8690dc" }); + return createClientTronSigner(wallet, { network: "tron:3448148188" }); } describe("privateKeyTronWallet", () => { @@ -64,7 +64,7 @@ describe("createClientTronSigner (wallet-only)", () => { const signer = await makeClient(tw, wallet); expect(signer.address).toBe(await wallet.getAddress()); - expect(signer.network).toBe("tron:0xcd8690dc"); + expect(signer.network).toBe("tron:3448148188"); const [viaSigner, viaWallet] = await Promise.all([ signer.signTypedData(TYPED), wallet.signTypedData(TYPED), @@ -78,10 +78,26 @@ describe("createClientTronSigner (wallet-only)", () => { registerExactTronScheme(client as never, { signer }); - expect(client.register).toHaveBeenCalledWith("tron:0xcd8690dc", expect.anything()); + expect(client.register).toHaveBeenCalledWith("tron:3448148188", expect.anything()); expect(client.register).not.toHaveBeenCalledWith("tron:*", expect.anything()); }); + it("normalizes deprecated hexadecimal signer and registration networks", async () => { + vi.mocked(buildTronWeb).mockReturnValue(tron()); + const signer = await createClientTronSigner(privateKeyTronWallet(tron(), PK), { + network: "tron:3448148188", + }); + const client = { register: vi.fn(), registerPolicy: vi.fn() }; + + registerExactTronScheme(client as never, { + signer, + networks: ["tron:3448148188"], + }); + + expect(signer.network).toBe("tron:3448148188"); + expect(client.register).toHaveBeenCalledWith("tron:3448148188", expect.anything()); + }); + it("rejects explicit registration on a different network", async () => { const signer = await makeClient(tron(), privateKeyTronWallet(tron(), PK)); const client = { register: vi.fn(), registerPolicy: vi.fn() }; @@ -89,7 +105,7 @@ describe("createClientTronSigner (wallet-only)", () => { expect(() => registerExactTronScheme(client as never, { signer, - networks: ["tron:0x2b6653dc"], + networks: ["tron:728126428"], }), ).toThrow(/cannot be registered/); expect(client.register).not.toHaveBeenCalled(); diff --git a/typescript/packages/mechanisms/tron/test/unit/tokens.test.ts b/typescript/packages/mechanisms/tron/test/unit/tokens.test.ts index 01d05e94..82322636 100644 --- a/typescript/packages/mechanisms/tron/test/unit/tokens.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/tokens.test.ts @@ -21,54 +21,59 @@ const USDD_NILE = "TGjgvdTWWrybVLaVeFqSyVqJQWjxqRYbaK"; describe("token registry lookups", () => { it("resolves tokens by symbol case-insensitively", () => { - expect(getToken("tron:0xcd8690dc", "usdt")?.address).toBe(USDT_NILE); - expect(getToken("tron:0xcd8690dc", "USDT")?.decimals).toBe(6); - expect(getToken("tron:0xcd8690dc", "USDD")?.decimals).toBe(18); + expect(getToken("tron:3448148188", "usdt")?.address).toBe(USDT_NILE); + expect(getToken("tron:3448148188", "USDT")?.decimals).toBe(6); + expect(getToken("tron:3448148188", "USDD")?.decimals).toBe(18); + }); + + it("accepts deprecated hexadecimal network aliases", () => { + expect(getToken("tron:3448148188", "USDT")?.address).toBe(USDT_NILE); + expect(getNetworkTokens("tron:3448148188")).toBe(getNetworkTokens("tron:3448148188")); }); it("resolves tokens by address case-insensitively", () => { - expect(findByAddress("tron:0xcd8690dc", USDT_NILE.toLowerCase())?.symbol).toBe("USDT"); - expect(findByAddress("tron:0xcd8690dc", USDD_NILE)?.symbol).toBe("USDD"); + expect(findByAddress("tron:3448148188", USDT_NILE.toLowerCase())?.symbol).toBe("USDT"); + expect(findByAddress("tron:3448148188", USDD_NILE)?.symbol).toBe("USDD"); }); it("returns undefined for unknown network/token", () => { expect(getToken("tron:unknown", "USDT")).toBeUndefined(); - expect(findByAddress("tron:0xcd8690dc", "TNotARealAddress")).toBeUndefined(); + expect(findByAddress("tron:3448148188", "TNotARealAddress")).toBeUndefined(); }); it("lists all tokens for a network", () => { - const tokens = getNetworkTokens("tron:0xcd8690dc"); + const tokens = getNetworkTokens("tron:3448148188"); expect(Object.keys(tokens).sort()).toEqual(["USDD", "USDT"]); expect(getNetworkTokens("tron:unknown")).toEqual({}); }); it("mainnet, nile, and shasta USDT all default to permit2", () => { - expect(getToken("tron:0x2b6653dc", "USDT")?.assetTransferMethod).toBe("permit2"); - expect(getToken("tron:0xcd8690dc", "USDT")?.assetTransferMethod).toBe("permit2"); - expect(getToken("tron:0x94a9059e", "USDT")?.assetTransferMethod).toBe("permit2"); + expect(getToken("tron:728126428", "USDT")?.assetTransferMethod).toBe("permit2"); + expect(getToken("tron:3448148188", "USDT")?.assetTransferMethod).toBe("permit2"); + expect(getToken("tron:2494104990", "USDT")?.assetTransferMethod).toBe("permit2"); }); }); describe("getDecimals", () => { it("returns token decimals when registered", () => { - expect(getDecimals("tron:0xcd8690dc", USDT_NILE)).toBe(6); - expect(getDecimals("tron:0xcd8690dc", USDD_NILE)).toBe(18); + expect(getDecimals("tron:3448148188", USDT_NILE)).toBe(6); + expect(getDecimals("tron:3448148188", USDD_NILE)).toBe(18); }); it("falls back to 6 for unknown assets", () => { - expect(getDecimals("tron:0xcd8690dc", "TUnknown")).toBe(6); + expect(getDecimals("tron:3448148188", "TUnknown")).toBe(6); }); }); describe("buildAssetExtra", () => { it("omits name/version for plain permit2 tokens but keeps assetTransferMethod", () => { - const usdt = getToken("tron:0xcd8690dc", "USDT")!; + const usdt = getToken("tron:3448148188", "USDT")!; const extra = buildAssetExtra(usdt); expect(extra).toEqual({ assetTransferMethod: "permit2" }); }); it("shasta USDT is permit2 (name/version omitted)", () => { - const shasta = getToken("tron:0x94a9059e", "USDT")!; + const shasta = getToken("tron:2494104990", "USDT")!; const extra = buildAssetExtra(shasta); expect(extra).toEqual({ assetTransferMethod: "permit2" }); }); @@ -93,47 +98,47 @@ describe("buildAssetExtra", () => { describe("parsePrice", () => { it("converts whole and fractional amounts to smallest units", () => { - expect(parsePrice("1 USDT", "tron:0xcd8690dc")).toEqual({ + expect(parsePrice("1 USDT", "tron:3448148188")).toEqual({ asset: USDT_NILE, amount: "1000000", extra: { assetTransferMethod: "permit2" }, }); - expect(parsePrice("1.25 USDT", "tron:0xcd8690dc").amount).toBe("1250000"); - expect(parsePrice("0.000001 USDT", "tron:0xcd8690dc").amount).toBe("1"); + expect(parsePrice("1.25 USDT", "tron:3448148188").amount).toBe("1250000"); + expect(parsePrice("0.000001 USDT", "tron:3448148188").amount).toBe("1"); }); it("handles 18-decimal tokens with BigInt-safe conversion", () => { - expect(parsePrice("1 USDD", "tron:0xcd8690dc").amount).toBe("1000000000000000000"); - expect(parsePrice("0.5 USDD", "tron:0xcd8690dc").amount).toBe("500000000000000000"); + expect(parsePrice("1 USDD", "tron:3448148188").amount).toBe("1000000000000000000"); + expect(parsePrice("0.5 USDD", "tron:3448148188").amount).toBe("500000000000000000"); }); it("is whitespace-tolerant and symbol case-insensitive", () => { - expect(parsePrice(" 2.5 usdt ".trim(), "tron:0xcd8690dc").amount).toBe("2500000"); + expect(parsePrice(" 2.5 usdt ".trim(), "tron:3448148188").amount).toBe("2500000"); }); it("rejects malformed price formats", () => { - expect(() => parsePrice("1USDT", "tron:0xcd8690dc")).toThrow(/Expected/); - expect(() => parsePrice("USDT", "tron:0xcd8690dc")).toThrow(/Expected/); - expect(() => parsePrice("-1 USDT", "tron:0xcd8690dc")).toThrow(/non-negative decimal/); + expect(() => parsePrice("1USDT", "tron:3448148188")).toThrow(/Expected/); + expect(() => parsePrice("USDT", "tron:3448148188")).toThrow(/Expected/); + expect(() => parsePrice("-1 USDT", "tron:3448148188")).toThrow(/non-negative decimal/); }); it("rejects unknown tokens", () => { - expect(() => parsePrice("1 WBTC", "tron:0xcd8690dc")).toThrow(/Unknown token/); + expect(() => parsePrice("1 WBTC", "tron:3448148188")).toThrow(/Unknown token/); }); it("rejects precision overflow", () => { - expect(() => parsePrice("1.1234567 USDT", "tron:0xcd8690dc")).toThrow(/more decimal places/); + expect(() => parsePrice("1.1234567 USDT", "tron:3448148188")).toThrow(/more decimal places/); }); it("supports runtime-registered tokens", () => { - registerToken("tron:0xcd8690dc", { + registerToken("tron:3448148188", { address: "TTestTokenAddress0000000000000000000", decimals: 2, name: "Test", symbol: "TEST", version: "1", }); - expect(parsePrice("3.21 TEST", "tron:0xcd8690dc")).toEqual({ + expect(parsePrice("3.21 TEST", "tron:3448148188")).toEqual({ asset: "TTestTokenAddress0000000000000000000", amount: "321", extra: { name: "Test", version: "1" }, diff --git a/typescript/packages/mechanisms/tron/test/unit/trc20-approval-resource-sponsoring-client.test.ts b/typescript/packages/mechanisms/tron/test/unit/trc20-approval-resource-sponsoring-client.test.ts index 56b12e3c..792d0ec5 100644 --- a/typescript/packages/mechanisms/tron/test/unit/trc20-approval-resource-sponsoring-client.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/trc20-approval-resource-sponsoring-client.test.ts @@ -9,8 +9,8 @@ import type { ClientTronSigner } from "../../src/signer"; import { trySignTrc20ApprovalExtension } from "../../src/shared/extensions"; import { createTrc20ApprovalPolicy } from "../../src/approvalPolicy"; -const NETWORK = "tron:0xcd8690dc"; -const OTHER_NETWORK = "tron:0x2b6653dc"; +const NETWORK = "tron:3448148188"; +const OTHER_NETWORK = "tron:728126428"; const PAYER = "TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"; const TOKEN = "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf"; const SIGNED_APPROVAL = "0a02abcd"; diff --git a/typescript/packages/mechanisms/tron/test/unit/trc20-approval-resource-sponsoring-validator.test.ts b/typescript/packages/mechanisms/tron/test/unit/trc20-approval-resource-sponsoring-validator.test.ts index 9e5ef548..2dd75d71 100644 --- a/typescript/packages/mechanisms/tron/test/unit/trc20-approval-resource-sponsoring-validator.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/trc20-approval-resource-sponsoring-validator.test.ts @@ -21,7 +21,7 @@ import { BatchSettlementTronScheme as BatchFacilitator } from "../../src/batch-s import { computeChannelId } from "../../src/shared/batch-settlement/utils"; import { PERMIT2_DEPOSIT_COLLECTOR_ADDRESSES } from "../../src/shared/batch-settlement/constants"; -const NETWORK = "tron:0xcd8690dc"; +const NETWORK = "tron:3448148188"; const PRIVATE_KEY = "4f3edf983ac63ad7c24ee152a7494471b2a18551b7117f7f7f3f2c47c8f6e5ad"; const PAYER = TronWeb.address.fromPrivateKey(PRIVATE_KEY); const TOKEN = "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf"; diff --git a/typescript/packages/mechanisms/tron/test/unit/trc20-resource-sponsoring-runtime.test.ts b/typescript/packages/mechanisms/tron/test/unit/trc20-resource-sponsoring-runtime.test.ts index eda1bfb7..cf6dba0f 100644 --- a/typescript/packages/mechanisms/tron/test/unit/trc20-resource-sponsoring-runtime.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/trc20-resource-sponsoring-runtime.test.ts @@ -18,7 +18,7 @@ const TOKEN = "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf"; const SPENDER = "TYQuuhGbEMxF7nZxUHV3uHJxAVVAegNU9h"; const request: Trc20ApprovalResourceSponsoringRequest = { - network: "tron:0xcd8690dc", + network: "tron:3448148188", approvalTxID: APPROVAL_TX_ID, approvalTimestamp: String(Date.now()), approvalExpiration: String(Date.now() + 120_000), @@ -35,7 +35,7 @@ const request: Trc20ApprovalResourceSponsoringRequest = { x402Version: 2, accepted: { scheme: "exact", - network: "tron:0xcd8690dc", + network: "tron:3448148188", asset: TOKEN, amount: "1000000", payTo: PAYER, @@ -45,7 +45,7 @@ const request: Trc20ApprovalResourceSponsoringRequest = { }, paymentRequirements: { scheme: "exact", - network: "tron:0xcd8690dc", + network: "tron:3448148188", asset: TOKEN, amount: "1000000", payTo: PAYER, diff --git a/typescript/packages/mechanisms/tron/test/unit/trc20-resource-sponsoring-tronweb.test.ts b/typescript/packages/mechanisms/tron/test/unit/trc20-resource-sponsoring-tronweb.test.ts index 20993106..85cc1908 100644 --- a/typescript/packages/mechanisms/tron/test/unit/trc20-resource-sponsoring-tronweb.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/trc20-resource-sponsoring-tronweb.test.ts @@ -16,7 +16,7 @@ const RESOURCE_PERMISSION_OPERATIONS = `${"00".repeat(7)}06${"00".repeat(24)}`; function runtimeRequest(): Trc20ApprovalResourceSponsoringRequest { return { - network: "tron:0xcd8690dc", + network: "tron:3448148188", approvalTxID: "a".repeat(64), approvalTimestamp: String(Date.now()), approvalExpiration: String(Date.now() + 120_000), @@ -33,7 +33,7 @@ function runtimeRequest(): Trc20ApprovalResourceSponsoringRequest { x402Version: 2, accepted: { scheme: "exact", - network: "tron:0xcd8690dc", + network: "tron:3448148188", asset: TOKEN, amount: "1000000", payTo: PAYER, @@ -43,7 +43,7 @@ function runtimeRequest(): Trc20ApprovalResourceSponsoringRequest { }, paymentRequirements: { scheme: "exact", - network: "tron:0xcd8690dc", + network: "tron:3448148188", asset: TOKEN, amount: "1000000", payTo: PAYER, @@ -185,7 +185,7 @@ describe("TronWeb resource-sponsoring chain", () => { const readContract = vi.fn().mockResolvedValueOnce(0n).mockResolvedValueOnce(2_000_000n); const chain = await createTronWebResourceSponsoringChain({ tronWeb: mock.tronWeb, - network: "tron:0xcd8690dc", + network: "tron:3448148188", resourceOwnerSigner, readContract, allowedAssets: [TOKEN], @@ -216,7 +216,7 @@ describe("TronWeb resource-sponsoring chain", () => { const readContract = vi.fn().mockResolvedValueOnce(0n).mockResolvedValueOnce(2_000_000n); const chain = await createTronWebResourceSponsoringChain({ tronWeb: mock.tronWeb, - network: "tron:0xcd8690dc", + network: "tron:3448148188", resourceOwnerSigner, readContract, allowedAssets: [TOKEN], @@ -238,7 +238,7 @@ describe("TronWeb resource-sponsoring chain", () => { const mock = createTronWebMock(); const chain = await createTronWebResourceSponsoringChain({ tronWeb: mock.tronWeb, - network: "tron:0xcd8690dc", + network: "tron:3448148188", resourceOwnerSigner, readContract: vi.fn(async () => 0n), allowedAssets: [TOKEN], @@ -269,7 +269,7 @@ describe("TronWeb resource-sponsoring chain", () => { })); const chain = await createTronWebResourceSponsoringChain({ tronWeb: mock.tronWeb, - network: "tron:0xcd8690dc", + network: "tron:3448148188", resourceOwnerSigner: { getAddress: async () => OWNER, signResourceTransaction: ({ transaction }) => signTransaction(transaction), @@ -301,7 +301,7 @@ describe("TronWeb resource-sponsoring chain", () => { const signTransaction = vi.fn(async transaction => transaction); const chain = await createTronWebResourceSponsoringChain({ tronWeb: mock.tronWeb, - network: "tron:0xcd8690dc", + network: "tron:3448148188", resourceOwnerSigner: { getAddress: async () => OWNER, signResourceTransaction: ({ transaction }) => signTransaction(transaction), @@ -339,7 +339,7 @@ describe("TronWeb resource-sponsoring chain", () => { })); const chain = await createTronWebResourceSponsoringChain({ tronWeb: mock.tronWeb, - network: "tron:0xcd8690dc", + network: "tron:3448148188", resourceOwnerSigner: { getAddress: async () => OWNER, signResourceTransaction: ({ transaction }) => signTransaction(transaction), @@ -375,7 +375,7 @@ describe("TronWeb resource-sponsoring chain", () => { })); const chain = await createTronWebResourceSponsoringChain({ tronWeb: mock.tronWeb, - network: "tron:0xcd8690dc", + network: "tron:3448148188", resourceOwnerSigner: { getAddress: async () => OWNER, signResourceTransaction: ({ transaction }) => signTransaction(transaction), @@ -407,7 +407,7 @@ describe("TronWeb resource-sponsoring chain", () => { })); const chain = await createTronWebResourceSponsoringChain({ tronWeb: mock.tronWeb, - network: "tron:0xcd8690dc", + network: "tron:3448148188", resourceOwnerSigner: { getAddress: async () => OWNER, signResourceTransaction }, readContract: vi.fn(async () => 0n), allowedAssets: [TOKEN], @@ -423,7 +423,7 @@ describe("TronWeb resource-sponsoring chain", () => { expect(signResourceTransaction).toHaveBeenCalledWith({ intent: { - network: "tron:0xcd8690dc", + network: "tron:3448148188", action: "delegate", owner: OWNER, receiver: PAYER, @@ -457,7 +457,7 @@ describe("TronWeb resource-sponsoring chain", () => { })); const chain = await createTronWebResourceSponsoringChain({ tronWeb: mock.tronWeb, - network: "tron:0xcd8690dc", + network: "tron:3448148188", resourceOwnerSigner: { getAddress: async () => OWNER, signResourceTransaction }, readContract: vi.fn(async () => 0n), allowedAssets: [TOKEN], @@ -491,7 +491,7 @@ describe("TronWeb resource-sponsoring chain", () => { }); const chain = await createTronWebResourceSponsoringChain({ tronWeb: mock.tronWeb, - network: "tron:0xcd8690dc", + network: "tron:3448148188", resourceOwnerSigner: { getAddress: async () => OWNER, signResourceTransaction: async ({ transaction }) => ({ @@ -540,7 +540,7 @@ describe("TronWeb resource-sponsoring chain", () => { }); const chain = await createTronWebResourceSponsoringChain({ tronWeb: mock.tronWeb, - network: "tron:0xcd8690dc", + network: "tron:3448148188", resourceOwnerSigner: { getAddress: async () => OWNER, signResourceTransaction: async ({ transaction }) => ({ diff --git a/typescript/packages/mechanisms/tron/test/unit/upto-flow.test.ts b/typescript/packages/mechanisms/tron/test/unit/upto-flow.test.ts index 32f8f795..ba36eaa5 100644 --- a/typescript/packages/mechanisms/tron/test/unit/upto-flow.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/upto-flow.test.ts @@ -32,7 +32,7 @@ import type { PaymentPayload, PaymentRequirements } from "@bankofai/x402-core/ty * chain I/O is stubbed, keeping the suite offline and deterministic. */ -const NETWORK = "tron:0xcd8690dc"; +const NETWORK = "tron:3448148188"; const PAYER_PK = "da146374a75310b9666e834ee4ad0866d6f4035967bfc76217c5a495fff9f0d0"; const FAC_PK = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"; const PAY_TO = "TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"; From 34496314c808ab8308489113655073ca3d4bb982 Mon Sep 17 00:00:00 2001 From: roger-gan Date: Tue, 1 Sep 2026 09:23:47 +0800 Subject: [PATCH 2/2] fix(tron): canonicalize legacy network inputs --- .../core/src/server/x402ResourceServer.ts | 9 +- .../unit/server/x402ResourceServer.test.ts | 51 ++++++++++ .../core/test/unit/utils/utils.test.ts | 2 +- .../packages/mechanisms/tron/src/network.ts | 20 ++++ .../tron/src/resource-sponsoring/runtime.ts | 94 +++++++++++++++++-- .../mechanisms/tron/test/unit/rpc.test.ts | 2 +- .../tron/test/unit/signer-wallet.test.ts | 4 +- .../mechanisms/tron/test/unit/tokens.test.ts | 4 +- .../trc20-resource-sponsoring-runtime.test.ts | 86 +++++++++++++++++ 9 files changed, 254 insertions(+), 18 deletions(-) diff --git a/typescript/packages/core/src/server/x402ResourceServer.ts b/typescript/packages/core/src/server/x402ResourceServer.ts index f475972a..fbaa6efe 100644 --- a/typescript/packages/core/src/server/x402ResourceServer.ts +++ b/typescript/packages/core/src/server/x402ResourceServer.ts @@ -25,6 +25,7 @@ import { deepEqual, findByNetworkAndScheme, findSchemesByNetwork, + networkMatchesPattern, toComparableArray, } from "../utils"; import { log } from "../observability/logger"; @@ -719,7 +720,9 @@ export class x402ResourceServer { // Find the specific kind from the response (kinds are flat array with version in each element) return supportedResponse.kinds.find( kind => - kind.x402Version === x402Version && kind.network === network && kind.scheme === scheme, + kind.x402Version === x402Version && + networkMatchesPattern(kind.network, network) && + kind.scheme === scheme, ); } @@ -789,13 +792,13 @@ export class x402ResourceServer { // Parse the price using the scheme's price parser const parsedPrice = await SchemeNetworkServer.parsePrice( resourceConfig.price, - resourceConfig.network, + supportedKind.network, ); // Build base payment requirements from resource config const baseRequirements: PaymentRequirements = { scheme: SchemeNetworkServer.scheme, - network: resourceConfig.network, + network: supportedKind.network, amount: parsedPrice.amount, asset: parsedPrice.asset, payTo: resourceConfig.payTo, diff --git a/typescript/packages/core/test/unit/server/x402ResourceServer.test.ts b/typescript/packages/core/test/unit/server/x402ResourceServer.test.ts index 5c6e99d1..cec712e0 100644 --- a/typescript/packages/core/test/unit/server/x402ResourceServer.test.ts +++ b/typescript/packages/core/test/unit/server/x402ResourceServer.test.ts @@ -444,6 +444,36 @@ describe("x402ResourceServer", () => { expect(mockScheme.parsePriceCalls[0].network).toBe("test:network"); }); + it("accepts a legacy TRON network and emits the facilitator's decimal identifier", async () => { + const mockClient = new MockFacilitatorClient( + buildSupportedResponse({ + kinds: [ + { + x402Version: 2, + scheme: "test-scheme", + network: "tron:3448148188" as Network, + }, + ], + }), + ); + + const server = new x402ResourceServer(mockClient); + const mockScheme = new MockSchemeNetworkServer("test-scheme"); + + server.register("tron:3448148188" as Network, mockScheme); + await server.initialize(); + + const requirements = await server.buildPaymentRequirements({ + scheme: "test-scheme", + payTo: "recipient", + price: "$5.00", + network: "tron:0xcd8690dc" as Network, + }); + + expect(requirements[0].network).toBe("tron:3448148188"); + expect(mockScheme.parsePriceCalls[0].network).toBe("tron:3448148188"); + }); + it("should call enhancePaymentRequirements", async () => { const mockClient = new MockFacilitatorClient( buildSupportedResponse({ @@ -3094,6 +3124,27 @@ describe("x402ResourceServer", () => { expect(supportedKind).toBeUndefined(); }); + it("should match a deprecated hexadecimal TRON identifier", async () => { + const mockClient = new MockFacilitatorClient( + buildSupportedResponse({ + kinds: [ + { + x402Version: 2, + scheme: "exact", + network: "tron:3448148188" as Network, + }, + ], + }), + ); + + const server = new x402ResourceServer(mockClient); + await server.initialize(); + + const supportedKind = server.getSupportedKind(2, "tron:0xcd8690dc" as Network, "exact"); + + expect(supportedKind?.network).toBe("tron:3448148188"); + }); + it("should return facilitator extensions", async () => { const mockClient = new MockFacilitatorClient( buildSupportedResponse({ diff --git a/typescript/packages/core/test/unit/utils/utils.test.ts b/typescript/packages/core/test/unit/utils/utils.test.ts index 7e80ecef..3c50b50c 100644 --- a/typescript/packages/core/test/unit/utils/utils.test.ts +++ b/typescript/packages/core/test/unit/utils/utils.test.ts @@ -35,7 +35,7 @@ describe("Utils", () => { schemes.set("exact", "tronImpl"); map.set("tron:3448148188", schemes); - const result = findSchemesByNetwork(map, "tron:3448148188" as Network); + const result = findSchemesByNetwork(map, "tron:0xcd8690dc" as Network); expect(result?.get("exact")).toBe("tronImpl"); }); diff --git a/typescript/packages/mechanisms/tron/src/network.ts b/typescript/packages/mechanisms/tron/src/network.ts index a6d78cee..fa11d8f3 100644 --- a/typescript/packages/mechanisms/tron/src/network.ts +++ b/typescript/packages/mechanisms/tron/src/network.ts @@ -50,6 +50,26 @@ export function tronNetworksEqual(left: string, right: string): boolean { return normalizeTronNetwork(left) === normalizeTronNetwork(right); } +/** + * Lists canonical and deprecated representations that may identify the same + * durable TRON record. Canonical decimal form is always first. + * + * @param network - Decimal identifier or deprecated hexadecimal alias. + * @returns Representations to try when reading existing persisted state. + */ +export function getTronNetworkRepresentations(network: string): readonly string[] { + const canonical = normalizeTronNetwork(network); + const representations = new Set([canonical]); + const decimalReference = /^tron:(\d+)$/.exec(canonical)?.[1]; + if (decimalReference) { + const hexadecimalReference = BigInt(decimalReference).toString(16); + representations.add(`tron:0x${hexadecimalReference}`); + representations.add(`tron:0x${hexadecimalReference.toUpperCase()}`); + } + representations.add(network); + return [...representations]; +} + /** * Resolve a value from a network-keyed record using decimal or legacy hex. * This also supports caller-owned records that still contain only hex keys. diff --git a/typescript/packages/mechanisms/tron/src/resource-sponsoring/runtime.ts b/typescript/packages/mechanisms/tron/src/resource-sponsoring/runtime.ts index a5d5e65d..76f2f0ba 100644 --- a/typescript/packages/mechanisms/tron/src/resource-sponsoring/runtime.ts +++ b/typescript/packages/mechanisms/tron/src/resource-sponsoring/runtime.ts @@ -1,4 +1,5 @@ import { utils as tronUtils } from "tronweb"; +import type { Network } from "@bankofai/x402-core/types"; import { buildTrc20SponsoringPlan } from "./resourceSizing"; import type { ManagedTrc20ApprovalResourceSponsoringRuntime, @@ -17,6 +18,7 @@ import type { Trc20SponsorshipExecutionOptions, } from "../shared/extensions/trc20ApprovalContract"; import { createTrc20ApprovalPolicy, type Trc20ApprovalPolicy } from "../approvalPolicy"; +import { getTronNetworkRepresentations, normalizeTronNetwork } from "../network"; const DEFAULT_ENERGY_SAFETY_BPS = 12_000n; const DEFAULT_BANDWIDTH_SAFETY_BPS = 11_000n; @@ -67,7 +69,21 @@ function normalizedFailure(error: unknown, fallbackReason: string, fallbackMessa * @returns Network-qualified Approval transaction key. */ function operationKey(request: Trc20ApprovalResourceSponsoringRequest): string { - return `${request.network}:${request.approvalTxID.toLowerCase()}`; + return `${normalizeTronNetwork(request.network)}:${request.approvalTxID.toLowerCase()}`; +} + +/** + * Builds every operation key that may have been emitted before TRON network + * identifiers were canonicalized at the persistence boundary. + * + * @param request - Sponsorship request. + * @returns Canonical key first, followed by deprecated aliases. + */ +function operationKeys(request: Trc20ApprovalResourceSponsoringRequest): readonly string[] { + const approvalTxID = request.approvalTxID.toLowerCase(); + return getTronNetworkRepresentations(request.network).map( + network => `${network}:${approvalTxID}`, + ); } /** @@ -77,7 +93,33 @@ function operationKey(request: Trc20ApprovalResourceSponsoringRequest): string { * @returns Network-qualified payer scope. */ function payerScope(request: Trc20ApprovalResourceSponsoringRequest): string { - return `${request.network}:${request.payer}`; + return `${normalizeTronNetwork(request.network)}:${request.payer}`; +} + +/** + * Canonicalizes every network field bound into a sponsoring request. + * + * @param request - Sponsorship request received at a compatibility boundary. + * @returns Request whose TRON network fields use decimal CAIP-2 identifiers. + */ +function normalizeRequestNetworks( + request: Trc20ApprovalResourceSponsoringRequest, +): Trc20ApprovalResourceSponsoringRequest { + return { + ...request, + network: normalizeTronNetwork(request.network), + paymentPayload: { + ...request.paymentPayload, + accepted: { + ...request.paymentPayload.accepted, + network: normalizeTronNetwork(request.paymentPayload.accepted.network) as Network, + }, + }, + paymentRequirements: { + ...request.paymentRequirements, + network: normalizeTronNetwork(request.paymentRequirements.network) as Network, + }, + }; } /** @@ -103,7 +145,7 @@ function canonicalJson(value: unknown): string { * @param request - Sponsorship request. * @returns Lowercase SHA-256 digest. */ -function requestDigest(request: Trc20ApprovalResourceSponsoringRequest): string { +function rawRequestDigest(request: Trc20ApprovalResourceSponsoringRequest): string { // Version 1.1 persisted exact requests before `requiredAllowance` existed. // Its default value is already bound by paymentRequirements.amount, so omit // that redundant representation to keep durable retry digests compatible. @@ -122,6 +164,36 @@ function requestDigest(request: Trc20ApprovalResourceSponsoringRequest): string return tronUtils.code.byteArray2hexStr(tronUtils.crypto.SHA256(bytes)).toLowerCase(); } +/** + * Hashes a request after canonicalizing equivalent TRON network identifiers. + * + * @param request - Sponsorship request. + * @returns Lowercase SHA-256 digest. + */ +function requestDigest(request: Trc20ApprovalResourceSponsoringRequest): string { + return rawRequestDigest(normalizeRequestNetworks(request)); +} + +/** + * Checks both current canonical digests and authenticated legacy snapshots. + * + * @param operation - Existing durable operation. + * @param request - Incoming canonical request. + * @returns Whether both values bind the same logical request. + */ +function operationMatchesRequest( + operation: Trc20SponsoringOperation, + request: Trc20ApprovalResourceSponsoringRequest, +): boolean { + const expectedDigest = requestDigest(request); + if (operation.requestDigest === expectedDigest) return true; + + return ( + operation.requestDigest === rawRequestDigest(operation.request) && + requestDigest(operation.request) === expectedDigest + ); +} + /** * Applies account, balance, and allowance state-machine checks. * @@ -642,12 +714,12 @@ export function createTrc20ApprovalResourceSponsoringRuntime( /** * Runs or resumes one idempotent payer-serialized sponsorship operation. * - * @param request - Sponsorship request. + * @param receivedRequest - Sponsorship request received at the compatibility boundary. * @param executionOptions - Scheme checks that must be repeated immediately before broadcast. * @returns Terminal response for the synchronous x402 settle flow. */ async function executeNewOrExisting( - request: Trc20ApprovalResourceSponsoringRequest, + receivedRequest: Trc20ApprovalResourceSponsoringRequest, executionOptions?: Trc20SponsorshipExecutionOptions, ): Promise<{ success: boolean; @@ -655,10 +727,14 @@ export function createTrc20ApprovalResourceSponsoringRuntime( errorReason?: string; errorMessage?: string; }> { + const request = normalizeRequestNetworks(receivedRequest); return options.coordinator.runExclusive(payerScope(request), async () => { let current: Trc20SponsoringOperation | undefined; try { - current = await options.coordinator.get(operationKey(request)); + for (const key of operationKeys(request)) { + current = await options.coordinator.get(key); + if (current) break; + } if (!current) { const prepared = await preview(request); if (prepared.approvalState === "approval_satisfied") return { success: true }; @@ -676,7 +752,7 @@ export function createTrc20ApprovalResourceSponsoringRuntime( } current = admission.operation; } - if (current.requestDigest !== requestDigest(request)) { + if (!operationMatchesRequest(current, request)) { throw new SponsoringFailure("approval_transaction_reused"); } if (current.status === "recovered") { @@ -801,7 +877,7 @@ export function createTrc20ApprovalResourceSponsoringRuntime( return { async verify(request) { try { - await preview(request); + await preview(normalizeRequestNetworks(request)); return { isValid: true }; } catch (error) { const failure = normalizedFailure( @@ -822,7 +898,7 @@ export function createTrc20ApprovalResourceSponsoringRuntime( let recovered = 0; for (const operation of operations) { await options.coordinator.runExclusive( - `${operation.network}:${operation.payer}`, + `${normalizeTronNetwork(operation.network)}:${operation.payer}`, async () => { let current = operation; try { diff --git a/typescript/packages/mechanisms/tron/test/unit/rpc.test.ts b/typescript/packages/mechanisms/tron/test/unit/rpc.test.ts index bc43af16..9e2c4572 100644 --- a/typescript/packages/mechanisms/tron/test/unit/rpc.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/rpc.test.ts @@ -27,7 +27,7 @@ describe("resolveTronRpcUrl", () => { }); it("accepts the deprecated hexadecimal nile alias", () => { - expect(resolveTronRpcUrl("tron:3448148188")).toBe("https://api.nileex.io"); + expect(resolveTronRpcUrl("tron:0xcd8690dc")).toBe("https://api.nileex.io"); }); it("uses the key-less fallback for mainnet/shasta when no API key is set", () => { diff --git a/typescript/packages/mechanisms/tron/test/unit/signer-wallet.test.ts b/typescript/packages/mechanisms/tron/test/unit/signer-wallet.test.ts index 8f3d3d5f..3da7f104 100644 --- a/typescript/packages/mechanisms/tron/test/unit/signer-wallet.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/signer-wallet.test.ts @@ -85,13 +85,13 @@ describe("createClientTronSigner (wallet-only)", () => { it("normalizes deprecated hexadecimal signer and registration networks", async () => { vi.mocked(buildTronWeb).mockReturnValue(tron()); const signer = await createClientTronSigner(privateKeyTronWallet(tron(), PK), { - network: "tron:3448148188", + network: "tron:0xcd8690dc", }); const client = { register: vi.fn(), registerPolicy: vi.fn() }; registerExactTronScheme(client as never, { signer, - networks: ["tron:3448148188"], + networks: ["tron:0xcd8690dc"], }); expect(signer.network).toBe("tron:3448148188"); diff --git a/typescript/packages/mechanisms/tron/test/unit/tokens.test.ts b/typescript/packages/mechanisms/tron/test/unit/tokens.test.ts index 82322636..8642b75b 100644 --- a/typescript/packages/mechanisms/tron/test/unit/tokens.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/tokens.test.ts @@ -27,8 +27,8 @@ describe("token registry lookups", () => { }); it("accepts deprecated hexadecimal network aliases", () => { - expect(getToken("tron:3448148188", "USDT")?.address).toBe(USDT_NILE); - expect(getNetworkTokens("tron:3448148188")).toBe(getNetworkTokens("tron:3448148188")); + expect(getToken("tron:0xcd8690dc", "USDT")?.address).toBe(USDT_NILE); + expect(getNetworkTokens("tron:0xcd8690dc")).toBe(getNetworkTokens("tron:3448148188")); }); it("resolves tokens by address case-insensitively", () => { diff --git a/typescript/packages/mechanisms/tron/test/unit/trc20-resource-sponsoring-runtime.test.ts b/typescript/packages/mechanisms/tron/test/unit/trc20-resource-sponsoring-runtime.test.ts index cf6dba0f..2489fbdd 100644 --- a/typescript/packages/mechanisms/tron/test/unit/trc20-resource-sponsoring-runtime.test.ts +++ b/typescript/packages/mechanisms/tron/test/unit/trc20-resource-sponsoring-runtime.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it, vi } from "vitest"; +import { createHash } from "node:crypto"; import type { Trc20ApprovalResourceSponsoringRequest } from "../../src/shared/extensions/trc20ApprovalContract"; import { buildTrc20SponsoringPlan, @@ -53,6 +54,40 @@ const request: Trc20ApprovalResourceSponsoringRequest = { }, }; +function requestForNetwork( + network: Trc20ApprovalResourceSponsoringRequest["paymentRequirements"]["network"], +): Trc20ApprovalResourceSponsoringRequest { + return { + ...request, + network, + paymentPayload: { + ...request.paymentPayload, + accepted: { ...request.paymentPayload.accepted, network }, + }, + paymentRequirements: { ...request.paymentRequirements, network }, + }; +} + +function canonicalJson(value: unknown): string { + if (typeof value === "bigint") return JSON.stringify(value.toString()); + if (value === null || typeof value !== "object") return JSON.stringify(value); + if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`; + const record = value as Record; + return `{${Object.keys(record) + .sort() + .map(key => `${JSON.stringify(key)}:${canonicalJson(record[key])}`) + .join(",")}}`; +} + +function legacyRequestDigest(value: Trc20ApprovalResourceSponsoringRequest): string { + const { requiredAllowance, ...legacyRequest } = value; + const boundRequest = + requiredAllowance === undefined || requiredAllowance === value.paymentRequirements.amount + ? legacyRequest + : value; + return createHash("sha256").update(canonicalJson(boundRequest)).digest("hex"); +} + function preflight(): Trc20SponsoringPreflight { return { accountActivated: true, @@ -354,6 +389,57 @@ describe("TRC-20 Approval resource-sponsoring runtime", () => { expect(retried).not.toHaveProperty("errorReason", "approval_transaction_reused"); }); + it("uses one durable operation for hexadecimal and decimal network representations", async () => { + const harness = createHarness(); + harness.results.set(harness.ids.undelegateEnergy, "unknown"); + harness.results.set(harness.ids.undelegateBandwidth, "unknown"); + const admit = vi.spyOn(harness.coordinator, "admit"); + + const first = await harness.runtime.sponsor(requestForNetwork("tron:0xcd8690dc")); + const retried = await harness.runtime.sponsor(request); + + expect(first.success).toBe(true); + expect(retried).toMatchObject({ success: true }); + expect(admit).toHaveBeenCalledTimes(1); + expect(admit).toHaveBeenCalledWith( + expect.objectContaining({ + key: `tron:3448148188:${APPROVAL_TX_ID}`, + network: "tron:3448148188", + request: expect.objectContaining({ + network: "tron:3448148188", + paymentPayload: expect.objectContaining({ + accepted: expect.objectContaining({ network: "tron:3448148188" }), + }), + paymentRequirements: expect.objectContaining({ network: "tron:3448148188" }), + }), + }), + ); + }); + + it("resumes an operation persisted with a legacy key and digest", async () => { + const harness = createHarness(); + harness.results.set(harness.ids.undelegateEnergy, "unknown"); + harness.results.set(harness.ids.undelegateBandwidth, "unknown"); + const legacyRequest = requestForNetwork("tron:0xcd8690dc"); + const originalAdmit = harness.coordinator.admit.bind(harness.coordinator); + const admit = vi.spyOn(harness.coordinator, "admit").mockImplementation(operation => + originalAdmit({ + ...operation, + key: `tron:0xcd8690dc:${APPROVAL_TX_ID}`, + network: "tron:0xcd8690dc", + request: legacyRequest, + requestDigest: legacyRequestDigest(legacyRequest), + }), + ); + + const first = await harness.runtime.sponsor(request); + const retried = await harness.runtime.sponsor(request); + + expect(first.success).toBe(true); + expect(retried).toMatchObject({ success: true }); + expect(admit).toHaveBeenCalledTimes(1); + }); + it("revalidates the scheme after delegation and reclaims without broadcasting Approval", async () => { const harness = createHarness(); const result = await harness.runtime.sponsor(request, {