Skip to content

feat(near): name every intent, and flag the ones that hand over authority - #460

Open
shahan-khatchadourian-anchorage wants to merge 1 commit into
shahankhatchadourian/near-g-sanitize-intent-stringsfrom
shahankhatchadourian/near-h-intent-type
Open

feat(near): name every intent, and flag the ones that hand over authority#460
shahan-khatchadourian-anchorage wants to merge 1 commit into
shahankhatchadourian/near-g-sanitize-intent-stringsfrom
shahankhatchadourian/near-h-intent-type

Conversation

@shahan-khatchadourian-anchorage

Copy link
Copy Markdown
Contributor

Stacked on #459.

The intent type reaches the screen

serde consumes the intent tag to select the variant, so the type is known and was dropped. Without it, transfer and ft_withdraw differ only by the presence of one Token field under an identical title — an instant, irreversible internal transfer reads like a withdraw. A batch had no boundary either, so a second intent's fields ran on from the first's.

   ├─ Intent: 1 of 2: Transfer
   ├─ To: bob.near
   ├─ Amount: 1 wNEAR
   ├─ Intent: 2 of 2: Add Public Key
   ├─ Add Public Key: ed25519:8rVv...
   └─ [warn] account-control: this key gains permanent authority over the
      account's entire intents balance, until it is explicitly removed

A single-intent envelope titles after its type: NEAR Intent: FT Withdraw.

This mirrors action_boundary_field and title_for on the transaction path, with one deliberate difference: the Intent field renders for a lone intent too, where action_boundary_field omits itself because the title covers it. A signed batch nests intents inside per-payload sections, so a section holding one intent has nothing else naming its type — its title comes from the enclosing borsh transaction.

Intents that hand over authority

add_public_key, remove_public_key, set_auth_by_predecessor_id and auth_call each rendered as one unremarkable line while granting authority rather than moving a named amount. Appended to a legitimate swap they read as part of it. Each now carries a warning naming the consequence — the treatment actions.rs already gives the AddKey action, which it breaks out field by field because "their absent forms widen the grant".

Two intents that cannot execute

Both rendered clean, the same way an expired deadline would without the check beside them:

  • An empty intents list does nothing but still spends the signer's nonce — a real no-op nonce burn a user can be tricked into signing.
  • A transfer to the signer's own account is refused by Transfer::execute_intent with InvalidIntent.

API

try_render_single_intent returns a RenderedEnvelope carrying the title alongside the fields, rather than making the caller re-parse the envelope to derive it.

Payload shape

This changes the emitted payload, so wallet renderers and any downstream snapshots need to expect:

  • one extra TextV2 labelled Intent per intent
  • a different Title for single-intent envelopes

Updated here: the e2e expected payload, the CLI display fixture, a CLI title assertion, and the two docs pages showing rendered output.

Scope

Purely additive to the field list. P9 — the Network field and network cross-check on the intents path — is deliberately not here, since it needs wallet-side coordination on a different axis; it follows as its own PR.

Testing

203 tests pass with default features, 187 with --no-default-features, clippy clean, full make test green.

🤖 Generated with Claude Code

…rity

The intent type never reached the screen. serde consumes the `intent` tag
to select the variant, so the value was known and dropped, leaving
`transfer` and `ft_withdraw` distinguishable only by the presence of one
`Token` field under an identical title -- an instant, irreversible internal
transfer reading like a withdraw. A batch had no boundary either, so a
second intent's fields ran on from the first's.

Emit an `Intent` field naming the variant, carrying its position when the
envelope holds more than one, and title a single-intent envelope after it
(`NEAR Intent: Transfer`). This mirrors `action_boundary_field` and
`title_for` on the transaction path, with one deliberate difference: the
field renders for a lone intent too, because a signed batch nests intents
inside per-payload sections and a section holding one intent has nothing
else to name its type.

    Intent: 1 of 2: Transfer
    To: bob.near
    Amount: 1 wNEAR
    Intent: 2 of 2: Add Public Key
    Add Public Key: ed25519:8rVv...
    [warn] account-control: this key gains permanent authority over the
    account's entire intents balance, until it is explicitly removed

The four account-control intents each rendered as one unremarkable line
while handing over authority rather than moving a named amount. Appended
to a legitimate swap they read as part of it. Each now carries a warning
naming the consequence, the treatment `actions.rs` already gives `AddKey`.

Two intents that cannot execute also rendered clean. An empty `intents`
list does nothing but still spends the signer's nonce, and a transfer to
the signer's own account is refused by `Transfer::execute_intent` -- the
same class as the expired-deadline check beside them.

`try_render_single_intent` returns the title alongside the fields rather
than making the caller re-parse the envelope to derive it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shahan-khatchadourian-anchorage
shahan-khatchadourian-anchorage force-pushed the shahankhatchadourian/near-h-intent-type branch from b374c87 to 5e3c34b Compare August 7, 2026 23:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant