Skip to content

Release v1.0.0#74

Merged
Will-Guan merged 103 commits into
mainfrom
release-v1.0.0
Jul 2, 2026
Merged

Release v1.0.0#74
Will-Guan merged 103 commits into
mainfrom
release-v1.0.0

Conversation

@roger-gan

@roger-gan roger-gan commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Description

Tests

Checklist

  • I have formatted and linted my code
  • All new and existing tests pass
  • My commits are signed (required for merge) -- you may need to rebase if you initially pushed unsigned commits

app and others added 30 commits June 15, 2026 17:25
feat(ts): add typescript_new monorepo
…for v1.0.0-beta.0 publish

- Rename all 17 publishable packages @x402/* -> @bankofai/x402-* (names, deps,
  workspace: refs, source imports, README badges)
- Add "publishConfig": { "access": "public" } to publishable packages
- Mark legacy/* packages (x402, x402-fetch, ...) as private (not published)
- Unify all publishable package versions to 1.0.0-beta.0
- Regenerate pnpm-lock.yaml to match the real 25-project workspace
  (previous lockfile was the typescript/ workspace's, copied in by mistake)

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…esolutions

The lockfile committed in a6c71ca was regenerated from scratch and drifted some
transitive deps below what the source code requires — notably resolving
@noble/curves to 1.9.1 in @bankofai/x402-extensions, whose secp256k1/p256 type
lacks the `.Point` member used in offer-receipt/did.ts (TS2339, DTS build fails).

Replace it with the upstream monorepo's lockfile (renamed @x402/* ->
@bankofai/x402-*), which pins @noble/curves to 1.9.7 and matches the exact
resolutions the source was built against. `pnpm install --lockfile-only` reports
"Already up to date" — no drift. extensions/package.json is unchanged (^1.9.0).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…ckfile

The upstream lockfile used as the base in 71a0e9d predated site/package.json's
dependency on @bankofai/x402-hedera (workspace:*), so `pnpm install
--frozen-lockfile` failed with ERR_PNPM_OUTDATED_LOCKFILE. Add the workspace
link entry to the site importer. Verified against all 25 workspace projects:
every package.json dependency is now present in the lockfile importers, and
@noble/curves stays pinned at 1.9.7.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
….gitignore

The root .gitignore has the standard Python `lib/` build-output rule (unanchored),
which also matched typescript_new/site/lib/. When typescript_new was imported
(9c172bb) this excluded the only TS source under any lib/ dir —
site/lib/animations.tsx — so `pnpm build` failed with 5 "Can't resolve
'@/lib/animations'" errors in the site app.

Add a .gitignore negation for typescript_new/site/lib/ and restore the file from
upstream. Verified it exports textStagger, fadeInUp, AnimatedGrid, AnimatedCard
(the symbols the site components import).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Trim typescript_new to an EVM + TRON SDK:

- Delete mechanism packages: aptos, avm, hedera, stellar, svm (keep evm, tron)
- Delete @bankofai/x402-paywall (browser payment UI; server-side, EVM/SVM/AVM
  only, no TRON) — it is an optional peer of the http middlewares, which fall
  back to static HTML when it is absent, so no middleware source changes needed
- Delete site/ (private x402 demo Next.js app; not published)

Consumer cleanup:
- Remove the optional @bankofai/x402-paywall peer from express/hono/fastify/next
- Drop build:paywall-deps script; narrow test:integration to core/evm/tron
- Remove site from pnpm-workspace.yaml and the site#build turbo task
- Update fetch JSDoc example (svm -> tron) and README package tables
- Regenerate pnpm-lock.yaml: 18 importers, consistent with all package.json,
  @noble/curves stays 1.9.7

Publishable packages: 17 -> 11 (core, extensions, mcp, axios, express, fastify,
fetch, hono, next, evm, tron).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…keys

createClientTronSigner / createFacilitatorTronSigner take a wallet
(AgentWallet / FacilitatorAgentWallet), not a raw private key. The README
examples passed PK directly and dropped the required await on the async
createClientTronSigner. Rewrite the Signers section to build wallets that keep
the key out of the SDK, matching the verified test helpers.
…_new

These were present upstream but not carried into typescript_new at import, so
`pnpm format` / `format:check` (which run `prettier -c .prettierrc`) had no
config to read. Restore both from upstream. Build/publish are unaffected; this
only re-enables code formatting.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
New overlay `@bankofai/x402-evm/facilitator/agent-wallet` exposes
`createFacilitatorEvmSigner(publicClient, wallet, options?)` — the EVM
counterpart of `createFacilitatorTronSigner`. It builds, wallet-signs, and
broadcasts settlement txs internally via viem + a key-custody wallet, so
facilitators no longer hand-wire readContract/writeContract/sendTransaction/
waitForTransactionReceipt/getCode/verifyTypedData (sdk-issues #1).

- Broadcast normalizes the signed-tx hex with strip-then-prefix, tolerating
  agent-wallet dropping the `0x` (sdk-issues #2); survives an upstream fix.
- `FacilitatorEvmPublicClient` is a `Pick<viem PublicClient, ...>` so a real
  client is assignable without a cast under strictFunctionTypes; the loose/
  strict impedance is narrowed once inside the factory (sdk-issues #5).
- Pin the tsup DTS heap (NODE_OPTIONS=--max-old-space-size=8192) to stop the
  worker OOM that silently dropped esm .d.mts (sdk-issues #6).
- Additive-only: upstream signer.ts / index.ts untouched, exported via a new
  subpath so future upstream merges stay conflict-free.
- Unit tests cover build/sign/broadcast, 0x tolerance, gas override,
  defaultGas, dataSuffix, and read pass-throughs.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Client-side counterpart of createFacilitatorEvmSigner, symmetric with TRON's
createClientTronSigner. Adapts an @bankofai/agent-wallet wallet to a
ClientEvmSigner: resolves the address eagerly, re-adds the 0x prefix that
agent-wallet strips from signatures (signature analog of sdk-issues #2), and
wires readContract for permit2 enrichment — so client code never touches a
raw key.

- Additive overlay at @bankofai/x402-evm/client/agent-wallet; upstream
  signer.ts / index.ts untouched, exported via a new subpath.
- Unit tests: eager address resolution, 0x tolerance, readContract wiring,
  optional public client.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
examples_new/typescript: a runnable client -> server -> facilitator loop for
@bankofai/x402-*, scoped to EVM + TRON.

- Each example self-contained; chain setup isolated in chains/{evm,tron}.ts and
  self-skips a chain whose wallet/address is absent (EVM-only / TRON-only / both).
- Wallet custody via @bankofai/agent-wallet (resolveWallet); examples never read
  a raw key. Uses create{Client,Facilitator}EvmSigner and the TRON equivalents.
- facilitator/basic (:4022, agent-wallet), servers/express (:4021, keyless),
  clients/fetch.
- Workspace links the in-repo SDK packages via pnpm-workspace.yaml.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
TRON's mainstream tokens (USDT/USDD) lack ERC-3009, so they settle via
permit2, which needs a one-time on-chain approve(Permit2, MaxUint256)
from the token owner. The Python client does this automatically; the TS
client did not, leaving the approve to the app.

Mirror the Python behavior:
- AgentWallet gains an optional signTransaction; ClientTronSigner gains an
  optional ensureAllowance (defaults exposed via createClientTronSigner's
  allowanceMode option).
- createPermit2Payload calls ensureAllowance with amount+fee before signing.
- ensurePermit2Allowance reads allowance(owner, Permit2) and, when short,
  broadcasts approve(Permit2, MaxUint256), waiting for receipt SUCCESS.

Deliberate divergences from Python: spender is the canonical Permit2 (not
PaymentPermit), allowance-read errors surface rather than being swallowed,
and a clear error is thrown early when an approve is needed but the wallet
cannot sign transactions. The facilitator build/sign/broadcast and receipt
poll are factored into shared helpers and reused unchanged.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
The fetch client's TRON adapter now exposes signTransaction, letting the
signer auto-broadcast the one-time Permit2 approve that USDT/USDD need on
first use — parity with the Python client.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
toClientAgentWallet now exposes signTransaction, so the Nile e2e client
signer can auto-broadcast the one-time Permit2 approve. Verified on-chain:
resetting the payer's allowance to 0 and running the first payment auto-
approves (allowance → MaxUint256) before signing, then settles.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
ensureAllowance waited on getTransactionInfo, which only populates after
the block solidifies (~19 confirmations, ~57s on Nile) — making the first
permit2 payment block ~60s. The ERC-20 allowance is live as soon as the
approve is packed (~3s), so wait on getTransaction's contractRet instead.

Adds pollTransactionPacked (getTransaction → ret[0].contractRet, 1.5s
interval, 60s cap) and uses it for the approve; revert is still caught via
contractRet !== "SUCCESS". The facilitator settlement path keeps the
solidified getTransactionInfo wait (parity with Python and the legacy TS
client). Verified on Nile: first-payment createPaymentPayload dropped from
~60.7s to ~1.1s, allowance 0 → MaxUint256, verify + settle succeed.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Plain ERC-20 tokens (no ERC-3009 / no EIP-2612, e.g. BSC USDC) settle via
Permit2 and need a one-time approve(Permit2). The erc20ApprovalGasSponsoring
extension has the client sign that approve offline and the facilitator
broadcast it bundled with settle — but the agent-wallet bridge factories
didn't expose the needed capabilities.

- createClientEvmSigner: forward signTransaction (re-adding the 0x agent-wallet
  strips), so the client can sign the approve tx. getTransactionCount /
  estimateFeesPerGas already flow from the viem publicClient.
- createFacilitatorEvmSigner: add sendTransactions + GasSponsoringFacilitatorEvmSigner
  so the signer satisfies the extension's Erc20ApprovalGasSponsoringSigner
  (pre-signed tx -> sendRawTransaction; call intent -> wallet-signed).

Both are additive to the BankofAI overlay files; upstream signer.ts/index.ts
and the exact mechanism flow are untouched. Tests added for both paths.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Parity with the demo's SufficientBalancePolicy: a beforePaymentCreation hook
checks the payer can cover amount + fee before signing, aborting with a clear
error instead of a confusing on-chain settle failure. The new SDK's payment
selection is synchronous, so the async on-chain balance read runs as this hook;
it guards the chosen requirement (TRON only — EVM has no client checkBalance).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Aligns examples_new with the BankofAI network/token support docs.

- TRON: advertise USDT and USDD on tron:nile (both permit2; client auto-approve).
- EVM: switch to BSC testnet (eip155:97), advertising DHLU (ERC-3009 -> exact
  eip3009, gasless) and USDC (plain BEP-20 -> exact permit2 + gas-sponsored
  approve via the erc20ApprovalGasSponsoring extension). Token capabilities and
  the DHLU EIP-712 domain ("DA HULU","1") verified on-chain.
- Refactor the EVM client/server/facilitator modules to per-network tables
  (EVM_NETWORKS / EVM_TOKENS): each network gets its own publicClient + signer
  registered under its exact CAIP-2 id (removes the misleading eip155:* wildcard
  that backed every chain with a single publicClient). Adding a chain is one
  table entry; no SDK changes (explicit-asset prices, not DEFAULT_STABLECOINS).
- Pre-list BSC mainnet (USDC/USDT/EPS, all permit2 — verified) and TRON mainnet
  as commented-ready entries; document the support matrix + "adding a chain/token"
  in the READMEs. Wire @bankofai/x402-extensions; fix stale EVM_PRIVATE_KEY ->
  AGENT_WALLET_PRIVATE_KEY in env templates.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Replace the three committed per-app .env-local (empty tracked files — a footgun
for accidentally committing a filled-in key) with one shared root .env-local:
all three apps now load ../../.env-local, which is gitignored.

- Add .gitignore (.env-local, .env, node_modules, dist).
- .env-local.example trimmed to essentials; one AGENT_WALLET_PRIVATE_KEY serves
  client + facilitator on both chains. Drop TRON_PRIVATE_KEY entirely (agent-wallet's
  env provider is single-key; the network only selects the signer type).
- Ports fixed in code (server 4021, facilitator 4022) — no PORT in env.
- READMEs updated to `cp .env-local.example .env-local`.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
First real `tsc` run (after `pnpm install` populated node_modules) surfaced
both refactor fallout and pre-existing latent bugs:

- Network type: Object.keys/entries widen network ids to `string`; cast to the
  CAIP-2 `${string}:${string}` so register()/accepts accept them.
- tryResolveWallet now returns a wallet typed with signTypedData (resolveWallet
  is typed as the base Wallet; signTypedData lives on Eip712Capable).
- facilitator settle hook: ctx.paymentRequirements -> ctx.requirements.
- TRON_NETWORK annotated as `${string}:${string}` (was widening to string).
- express async middleware cast to RequestHandler.

All three apps (server/client/facilitator) now pass `pnpm typecheck`. Also
commit the examples pnpm-lock.yaml for reproducible installs.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
The beforePaymentCreation hook (added in 316dfae) only guarded the *chosen*
requirement and aborted if unaffordable — it couldn't re-select an affordable
token the way the demo's SufficientBalancePolicy does (new SDK selection is
synchronous, so async balance filtering can't run before selection). Net value
was just a slightly earlier/clearer error, and it could be counterproductive in
multi-token scenarios (abort on the first option instead of paying with the
affordable one). Remove it; an unaffordable payment is rejected at the
facilitator anyway, and token choice is better handled by a selector.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
The facilitator's waitForTransactionReceipt polled getTransactionInfo, which
only populates after the block solidifies (~19 confirmations, ~57s on Nile) —
so every settle blocked ~57s. Switch it to pollTransactionPacked (the same
getTransaction.contractRet path the client approve already uses): the settle
result is known as soon as the tx is packed (~3s), and TRON does not reorg
packed blocks in practice. Removes the now-unused pollTransactionReceipt and
TronTxInfo. Client + facilitator now both confirm at packed speed.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
pollTransactionPacked read getTransaction.ret[0].contractRet in the sub-second window after broadcast, where TRON returns an unreliable preconfirm result that can read REVERT for a tx that ultimately succeeds. That false REVERT made facilitator settle return success:false, so the resource server re-challenged and the client got a 402 after the payment had already landed on-chain.

Delay before the first read to skip the preconfirm window, and require two consecutive identical contractRet reads before trusting the result. Shared by client ensureAllowance and facilitator waitForTransactionReceipt, so the approve-confirmation path is fixed too. Verified live on tron:nile (USDT + USDD).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
createClientEvmSigner detached wallet.signTransaction into a local const, so calling it lost its `this`; agent-wallet's LocalSigner.signTransaction reads this._impl and threw "Cannot read properties of undefined (reading '_impl')". This broke the ERC-20 approval gas-sponsoring path (BSC USDC permit2). signTypedData was unaffected because it is invoked as wallet.signTypedData(...). Bind to the wallet.

Verified live on BSC testnet (eip155:97): USDC permit2 + gas-sponsored approve now signs and settles.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
tryResolveWallet passed the short family name ("evm"/"tron") to @bankofai/agent-wallet's resolveWallet, which requires a CAIP-2 id (must start with eip155: or tron:). "evm" was rejected, so the facilitator and client silently skipped EVM and registered TRON only; "tron" worked only by accident. Map the family to a representative CAIP-2 id — address derivation is chain-id-independent within a family.

Verified live: facilitator now registers eip155:97 and BSC testnet payments (DHLU eip3009, USDC permit2) succeed.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…lient

resolveDeadline now honors a caller-requested deadline via
extensions.paymentPermitContext.meta.validBefore (matching the Python
client's source), falls back to the max window otherwise, rejects values
below the network min, and clamps over-window values down with an
[x402]-prefixed console.warn (per docs/solutions.md #2 "warn when
clamping", styled after core). Removes the previously dead minDelta path.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
ensurePermit2Allowance now coerces the Permit2 allowance read with BigInt()
before comparing against the required amount — tronweb contract reads can
surface a string/number, which misbehaves or throws against a bigint; this
matches readGasFreeBalance's coercion. buildSignAndBroadcast now fails fast
when a successful broadcast returns no txid, instead of returning "" and
letting the caller poll an empty hash until timeout.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
roger-gan and others added 28 commits June 29, 2026 15:52
The resource-server middleware treats a structured facilitator failure
(verify isValid=false / settle success=false) as a normal 402 and
returns it without logging — only unexpected exceptions are logged. So a
rejected payment looks silent on the server while the client just sees
402 {}, hiding the real invalidReason/errorReason.

Add attachPaymentLogging() that registers observers on the resource
server's existing hook surface (onAfterVerify/onVerifyFailure/
onBeforeSettle/onAfterSettle/onSettleFailure), mirroring the SDK's own
attachFacilitatorLogging instead of wrapping the facilitator client.
Wired unconditionally in the express example (no env gate).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…resource server

Mirror the facilitator's logging overlay on the resource-server side and
share one log format between them.

- Add paymentLogFormat.ts: single source of truth for the verify/settle
  message strings, levels, and field shapes.
- Add resourceServerLogging.ts: attachResourceServerLogging() (hooks on
  the existing x402ResourceServer surface) + createResourceServer()
  convenience, the symmetric counterpart of createFacilitator().
- Refactor facilitatorLogging.ts onto the shared format (behavior and
  emitted messages unchanged; existing tests stay green).
- Export createResourceServer from the core barrel.

The two roles differ in which hook carries an invalid verify (facilitator
onVerifyFailure vs resource-server onAfterVerify with isValid=false), but
emit an identical "x402: verify failed" line. Adds unit tests covering
both roles' paths.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Replace the manual new x402ResourceServer + bespoke logging-hooks wiring
with the SDK's createResourceServer, which pre-attaches the unified
verify/settle logging. Drops the example-local loggingHooks.ts.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
GasFree's feeTo is the relayer's service provider: verify rejects any
address not in the relayer's registered provider list with
gasfree_fee_to_mismatch. The facilitator's getExtra defaulted feeTo to
its own wallet (signer.getAddresses()[0]), which is not a provider — so
the client signed serviceProvider = facilitator wallet and every gasfree
payment failed verification (observed against the hosted facilitator,
whose advertised feeTo was not in /config/provider/all).

getExtra now advertises feeTo only when explicitly configured to a real
provider; otherwise it omits feeTo and the client picks one from the
relayer's provider list. This mirrors the Python facilitator's fee_quote,
which selects a provider rather than the facilitator address. The exact
scheme is unchanged — there the facilitator IS the fee recipient.

Adds gasfree-feeto.test.ts covering the omit-by-default behavior and the
explicit-provider passthrough.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…examples

Bring the remaining four example servers in line with express: swap the
plain new x402ResourceServer for createResourceServer, which pre-attaches
the unified verify/settle logging. Without it, a successful or rejected
payment was silent on these servers (the middleware only logs unexpected
exceptions), so a 402 gave no clue why.

mcp previously imported x402ResourceServer from @bankofai/x402-mcp (a
re-export of the core class), so createResourceServer from core is
type-compatible with createPaymentWrapper.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Mirror the fetch/exact client: the upto and batch-settlement EVM clients
read the chain (allowance, channel state via channels(bytes32)) over
viem's default BSC testnet node, which is frequently unreachable and
fails the run with "fetch failed". Pass an optional EVM_RPC_URL through
createClientEvmSigner so the EVM path can target a reliable endpoint;
unset falls back to the viem default (unchanged). Documented in the
.env-upto/.env-batch examples.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
The batch-settlement refund probe picked the first accept matching the
scheme, ignoring network. On a route that advertises batch-settlement on
multiple networks (e.g. EVM + TRON), this handed an eip155 requirement to
the TRON refund, which threw "Unsupported network format: eip155:97" in
computeChannelId → getTronChainId. Filter the probe to the package's own
chain family (tron:* / eip155:*).

The EVM file is upstream-derived; the same latent bug exists upstream
(refund takes no network and picks accepts[0]), but only surfaces once a
single route mixes VM families — which upstream never does. The eip155:*
filter is correct for the EVM package and worth contributing upstream.

Adds multi-network refund probe tests for both packages (a leading
foreign-family accept must be skipped).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Reorganize the four .env-*.example files into fixed SHARED / CLIENT /
SERVER / FACILITATOR sections, matching which process actually reads each
var (verified against process.env usage in clients/, servers/, facilitator/).
Notably the server never signs — it only holds payout addresses and calls
the facilitator. Content unchanged; only regrouped with clarifying comments.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Directory reshape:
- Move previous-generation projects (python, typescript, examples, e2e,
  integration, scripts, specs) + old root manifests + assets into legacy/,
  which is reference-only and slated for removal.
- Promote typescript_new/ -> typescript/ and examples_new/ -> examples/,
  rewriting workspace/lockfile links and doc references off the _new suffix.

CI:
- Drop the 5 legacy-only workflows (check_python, check_typescript, check_e2e,
  nightly_testnet, publish_pypi); only audit-pr.yml remains. .github and
  .claude carry no legacy/ references.

.claude/rules realigned to the new TypeScript-only SDK:
- Remove python and exact_permit rules (no python; permit2 is now an `exact`
  assetTransferMethod, not a separate scheme).
- Rewrite index, common, typescript, networks, exact, exact_gasfree for the
  new package layout (core/mechanisms/{evm,tron}/extensions/http/mcp), upstream
  fork+overlay model, signer factories, and vitest unit/integration testing.
- Add upto, batch-settlement, auth-capture scheme sheets.
- Stop citing specs/ (old content); rules now reference the package source.

Functional refs preserved (legacy/scripts/upload_pkgs.sh; e2e harness is
self-relative). No new CI added for the new SDK yet.
The /x402:create-scenario wizard scaffolded declarative python e2e
scenarios (e2e/scenarios/ + integration/run.py), a harness that now lives
in legacy/ and has no equivalent in the vitest-based new SDK. Remove it and
repoint the scheme-author agent's follow-ups (vitest tests + register)
and the root CLAUDE.md wizard list.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Its purpose was drafting a specs/<scheme>.md spec document — the spec-first
model that no longer applies in the new SDK (specs are legacy; a new scheme
is classes + rule sheet + vitest tests). Drop the agent and its mentions in
root CLAUDE.md and the rules index.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Rewrite version/installation/quick-start/schemes/development for the
1.0.0-beta.0 granular @bankofai/x402-* packages: drop the Python SDK and
the umbrella package, replace old class names with the real new API
(x402Client + wrapFetchWithPayment, createClient<Chain>Signer,
Exact<Chain>Scheme, createResourceServer, x402Facilitator), list the
current schemes (exact eip3009/permit2, upto, batch-settlement,
auth-capture, exact_gasfree), and point at examples/typescript instead of
the removed bsc-testnet-smoke / specs paths. Architecture, flow, networks,
security, and license sections preserved.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Drop the Python development section and the python/x402 + typescript/packages/x402
layout; document the pnpm/turbo monorepo (build/test/lint, vitest unit vs
integration), the upstream fork+overlay rule for core/mechanisms/evm, the
ESM/no-any/BigInt coding standards, and the legacy/ note. Generic sections
(reporting, PRs, security, license) preserved.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
TRON is the primary chain — show the full TRON (Nile) client snippet first
(createClientTronSigner with apiKey, register under tron:*), and demote EVM
(BSC) to the symmetric "swap the imports" variant.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Remove the Demo Repository header link and the "Try it out" demo line.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Add USDT (0x337610d2..., 18 dec, plain BEP-20) alongside DHLU and USDC
in the express server EVM_TOKENS table. Uses the same permit2 + gas-
sponsored approve path as USDC. SDK code unchanged.

Update .env-exact.example PAY_TARGETS comment to list USDT.
TOKEN_ADDRESSES was a flat global map, so the USDT symbol collided between
BSC testnet and TRON Nile. Index by chain family (eip155/tron) so
eip155:97@USDT resolves to the BSC testnet USDT contract while
tron:nile@USDT still resolves to the TRON Nile USDT contract.
The previous address 0x337610d2...428 has no bytecode on eip155:97
(verified via eth_getCode), causing facilitator verify to fail with
asset_not_deployed_contract. The correct BSC testnet USDT is
0x7ef95a0FEE0Dd31b22626fA2e10Ee6A223F8a684 (symbol USDT, name
Tether USD, 18 decimals, plain BEP-20 → permit2 path).
The Tether USD deployment 0x7ef95a0F...8a684 had no test balance available.
Switch to 0x337610d2...4ddd (symbol USDT, name 'USDT Token', 18 dec) which
is the BSC testnet USDT that test funds were transferred to (2 USDT
confirmed via tx 0xfe49955c...). Both are valid USDT deployments on
eip155:97; this one has faucet/test-transfer liquidity.
Apply EIP-55 checksum casing to the USDT contract address for
consistency with the other token addresses (DHLU, USDC) which are
already checksummed.
…ceipts

pollTransactionPacked used tronWeb.trx.getTransaction, which reads the
fullNode preconfirm contractRet. On mainnet the preconfirm REVERT can
persist beyond the 2-read confirmation window (~3s), causing false
settle failures (transaction actually succeeds on-chain but facilitator
reports reverted).

Switch to the fullNode wallet/gettransactioninfobyid endpoint and wait
for blockNumber (populated once the tx is packed into a confirmed block,
~3-6s). The receipt.result is then authoritative — no preconfirm window.
This mirrors tronpy's get_transaction_info (same fullNode endpoint),
which legacy Python used for fast + accurate receipts.

tronweb's own trx.getTransactionInfo hits the solidityNode variant
(walletsolidity/gettransactioninfobyid) which only returns after block
solidification (~19 confirmations / ~60s) — too slow. Calling the
fullNode endpoint directly gets the same accuracy at ~3-6s.
Resolve conflicts:
- README.md: keep TypeScript-only 1.0.0 version (release branch)
- test_tron_signer.py: accept move to legacy/python/ (directory renamed)
Document the TypeScript-only SDK rewrite, BSC USDT support, token
symbol per-chain-family resolution, and the TRON settle receipt
accuracy fix.
All @bankofai/x402-* packages bumped from 1.0.0-beta.0 to 1.0.0
(stable release). Root workspace version aligned from 0.0.2 to 1.0.0.
TRON exact/upto server schemes had a local convertToTokenAmount that
used parseFloat + String() — this silently produced garbage amounts for
small values (e.g. $0.0000001 → parseFloat → 1e-7 → String → '1e-7' →
split('.') → '1e7') and accepted scientific notation. Replace with the
core utils convertToTokenAmount + numberToDecimalString, which:
- rejects scientific notation explicitly
- throws 'too small' when the amount can't be represented at the token's
  decimal precision, instead of silently returning 0 or garbage

batch-settlement already used the core version; exact and upto were the
only stragglers. Add moneyConversion tests covering normal, boundary
(6-dec), and too-small cases for both schemes.
Audit reports are local analysis artifacts, not part of the published
repo. Add audit/ to .gitignore and remove from tracking (files kept
locally).
@Will-Guan Will-Guan merged commit dd7dfd5 into main Jul 2, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants