Skip to main content
The NEAR module decodes raw NEAR transactions into VisualSign payloads, and renders both halves of the NEAR Intents protocol: the pre-signature intent a user is about to sign, and the signed batch an execute_intents call submits on-chain.

Architecture overview

Two input formats, one chain identity

NEAR carries two distinct payload shapes under a single CHAIN_NEAR identity, discriminated purely by input format:
  • A borsh-encoded transaction (near::sign_transaction) — the standard NEAR wire format (near_primitives::transaction::Transaction or SignedTransaction), hex or base64 encoded.
  • A pre-signature intents envelope (near::sign_intent) — a bare DefusePayload JSON object, the message a user signs before it is wrapped in a signature standard and submitted to solvers.
Borsh bytes are never valid JSON, so the two formats never collide: the parser attempts a borsh decode first, and only falls back to validating the input as a JSON envelope if that fails. Input that is neither is rejected — never guessed at or partially reinterpreted.

Account model

NEAR is account-based. Accounts are either named (alice.near) or implicit — derived deterministically from a public key, with no on-chain registration step. HSM-generated keys always produce implicit accounts: a 64-character hex string for ed25519 keys, an EVM-style 0x… address for secp256k1 keys. A single user signing on multiple chains therefore appears as multiple, unlinked implicit accounts on intents.near — one per key.

Key components

For a borsh transaction, the parser produces:
  • Top-level fields: Network, From, To.
  • Per-action fields — a native Transfer renders an Amount; a FunctionCall renders Method plus, for the three token-movement methods the parser understands (ft_transfer, ft_transfer_call, ft_withdraw), the resolved recipient and amount. An execute_intents call to intents.near additionally decodes and renders the full signed intent batch inline. Any other action or method renders its generic label only — a partially understood call never masquerades as a fully decoded one.
For an intents envelope (either pre-signature or inside a decoded execute_intents batch), the parser produces:
  • Envelope fields: Signer, Verifying Contract, Deadline, Nonce. A pre-signature envelope is preceded by the same Network field the borsh path renders, since the resolved network is part of every token-metadata signature scope.
  • For a signed batch specifically, a Standard and Signature (or a signature-invalid warning) per entry, since verification and structural decode are independent — a bad signature still renders the intents it wraps.
  • Per-intent fields, one section per intent in the batch.

Supported intent types

ft_withdraw, nft_withdraw, and mt_withdraw each carry an optional storage_deposit (a second, unconditional wNEAR debit alongside the withdrawal, never refunded on failure) and msg (which switches the call into its _transfer_call form) — both render when present, since either changes what the withdrawal actually does beyond moving the named asset.

Signature standards

The intents preset verifies all seven signature standards the protocol defines — NEP-413, ERC-191, TIP-191, raw ed25519, WebAuthn, TonConnect, and SEP-53 — using the protocol’s own canonical implementation (near/intents) rather than a reimplementation, so new intent variants and signing standards stay correct as the protocol evolves. Verification recovers a signing key; whether that key is checked against the claimed account depends on the account’s shape. For an implicit account, the account id is itself derived from the key, so the recovered key is compared against it directly — a mismatch renders as a hard finding, not a hedge. For a named account (alice.near), that key-to-account relationship lives in on-chain access-key state the parser doesn’t have, so it renders as an explicit “not verified” caveat instead. ERC-191 and TIP-191 recover a secp256k1 key rather than pass/fail: a well-formed signature always recovers some key, so a tampered message recovers a different key rather than failing outright. A malformed recovery byte is rejected before it reaches verification, since the native ecrecover backend otherwise panics on out-of-range input — a malformed payload must be treated as invalid input, never a crash.

Visualization strategy

  • Amounts in native units where resolvable — a wrap.near (wNEAR) transfer renders as 1 wNEAR, not 1000000000000000000000000 raw units. Unresolved assets render the raw base-unit amount tagged with the asset id, rather than guessing at decimals — a wrong decimals value would silently misrender an amount. Beyond the compiled-in seed table, a wallet can supply symbol/decimals for additional asset ids per request, optionally signed by a curator key whose curve is chosen by the asset’s origin chain (NEAR, Ethereum, or Solana) rather than a single NEAR-wide curve — see Chain Metadata.
  • Fail-closed method-args decodingft_transfer, ft_transfer_call, and ft_withdraw args decode only when they parse as exactly the known shape (deny_unknown_fields); anything else falls back to the generic FunctionCall view rather than rendering a partial, possibly misleading subset of fields.
  • Deadlines and invalid signatures render as soft findings alongside the content they qualify — the intents still render even when a deadline has passed or a signature fails verification, with the problem surfaced as a warning field rather than blocking the render outright.

Using parser_cli

The CLI accepts either input format directly — hex/base64 for a transaction, or raw JSON for an intents envelope:
Output:
An intents envelope decodes the same way:
Output:
The --network flag selects the rendered Network header — NEAR_MAINNET (the default) or NEAR_TESTNET. An unrecognized value errors before anything renders, rather than silently falling back to mainnet. The resolved network also has to agree with the accounts being rendered: an account whose suffix contradicts it (.testnet under mainnet, or .near under testnet) is refused rather than displayed under a network it does not belong to. This covers the transaction’s own accounts and every rendered envelope’s signer_id/verifying_contract. Since the default is mainnet, decoding a .testnet envelope needs --network NEAR_TESTNET explicitly. Implicit (64-hex) accounts carry no suffix and are not constrained.

Supplying token metadata locally

Assets the compiled-in seed table doesn’t cover render their raw base-unit amount tagged unresolved <asset id>. To give the CLI a symbol and decimals for one, pass --near-token-metadata-mappings:
The envelope withdraws my-token.near, the asset the mapping covers, so the Amount field renders as 1 MYTOKEN rather than 100000000 (unresolved nep141:my-token.near). Two details differ from the Ethereum and Solana mapping flags:
  • @ separates the fields, not :. NEAR Intents asset ids contain their own colons (nep141:wrap.near), so a colon-delimited format would truncate the id at its first embedded colon.
  • The CLI signs each entry it loads with its local development key, because it runs the require-signed posture — an unsigned entry is dropped rather than trusted. That key is allowlisted only in builds that enable the dev-signing feature, which parser_cli does and the enclave binary does not. The posture is a per-deployment choice, not a property of the parser: parser_cli pins require-signed, while parser_app registers the converter with the permissive default.
Repeat the flag for each asset. A mapping that fails to parse or whose file can’t be read is reported and skipped; the rest still load.

Configuring curator keys

Signer identity is resolved per origin chain from three environment variables, each a comma-separated list of hex public keys: Each populates only its own domain, so a key trusted for Ethereum-origin assets does not thereby vouch for NEAR-native ones. A variable left unset leaves that domain with no enrolled curators, and a signature checked against it is never recognized. An unset domain is fail-closed but not inert: under the permissive posture the entry is still accepted as a gap fill for an asset the seed table does not cover, and marked unverified. It cannot override a curated seed. Under the require-signed posture it is refused outright. So a deployment that wants signed token metadata to count as verified has to enrol the curator keys; without them, supplying a signature has the same effect as omitting one.

Implementation details

Source code available at:

Resources