RPP402-004Draft

RPP402-004: Payment Intent

RFCRPP402-004
TitlePayment Intent
StatusDraft
Version1.0.0
RequiresRPP402-003

Overview

A Payment Intent is an agent's signed authorization to pay a Commerce Session's total, in a specific asset, to specific recipients. It is the last step before Settlement (RPP402-005) and the point at which the Session's leg set freezes. RPP402-004 is also where the strategy decision locked in PRODUCT-STRATEGY.md becomes wire format: v1 supports both stablecoin and tokenized-real-world-asset settlement, not stablecoins only.

Motivation

Every prior-art payment protocol assumes the payer's spending authority is a stablecoin balance. That assumption doesn't hold for an agent operating inside a Robinhood Agentic Account, whose actual capital may be tokenized equity exposure. Restricting RPP402 to stablecoins-only would mean an Agentic Account agent has to off-ramp to a stablecoin before it can transact -an extra step, an extra fee, and a missed opportunity to demonstrate the one thing no other protocol can do. RPP402-004 makes the asset a first-class dimension of the Intent instead of assuming it away.

Problem

A Payment Intent has to answer three questions unambiguously before Settlement executes: how much, in what, and who authorized it. "How much" is easy for a stablecoin (a fixed decimal amount). It is not easy for a tokenized security, whose market price moves between Quote time and Settlement time -so RPP402-004 has to specify which of "a fixed number of tokens" or "a fixed USD notional, converted at settlement-time price" the Intent means, explicitly, per Intent.

Terminology

TermDefinition
Payment IntentThe intent_ object defined below.
Settlement AssetThe asset (stablecoin or tokenized security) an Intent is denominated and paid in.
Fixed-quantity modeA tokenized-security Intent that authorizes transfer of an exact token quantity.
Notional modeA tokenized-security Intent that authorizes transfer of whatever token quantity equals a fixed USD notional at settlement-time reference price.
Delegated authorityAuthorization sourced from a Robinhood Agentic Account's pre-granted capital/risk limits rather than a fresh signature.

Protocol

POST {endpoints.session}/{id}/intent (see RPP402-003 §Endpoints), issued against a Session in quoting status with ≥1 leg:

FieldTypeRequiredDescription
session_idstringyesMust be quoting with ≥1 leg.
payer_walletstringyesMust match the Session's agent_wallet.
assetAssetRefyesMust match exactly one asset already present across the Session's legs (a Session with legs in two assets requires two Intents, one per asset -see RPP402-003).
amount_modestringrequired if asset.type == "tokenized_security", omitted otherwise"fixed_quantity" or "notional_usd". Stablecoin Intents have no mode -the amount is always the exact decimal sum of the covered legs.
authorizationAuthorizationyesSee below.

Response, a PaymentIntent object:

FieldTypeDescription
rpp402_versionstring"1.0".
idstringintent_ prefixed.
session_idstring
payer_walletstring
assetAssetRef
amount_modestring | nullEchoes the request.
amountstringThe signed amount: token quantity (fixed_quantity or stablecoin) or USD notional (notional_usd). Always a decimal string.
statusstringpendingauthorized → (settling is Settlement's concern, not the Intent's) | revoked | expired.
authorizationAuthorizationEchoed back with server-assigned nonce.
created_atstring (RFC 3339)
expires_atstring (RFC 3339)Inherits the Session's expires_at unless the authorization mechanism imposes a shorter one.

Authorization

Two mechanisms in v1, distinguished by authorization.type:

eip712_signature -the general case. The agent signs an EIP-712 typed-data payload over { session_id, payer_wallet, asset, amount_mode, amount, nonce, expires_at } with the wallet's private key.

json
{ "type": "eip712_signature", "signer": "0xaaaa...1234", "signature": "0x...", "nonce": "0x9f3a..." }

agentic_account_delegation -for agents operating inside a Robinhood Agentic Account, where spending authority was already granted by the account owner as a capital/risk-limit policy rather than requested per-transaction. The Intent references that delegation instead of producing a fresh signature.

json
{ "type": "agentic_account_delegation", "account_id": "aa_7d21f0", "delegation_ref": "del_44a1", "risk_check": "within_limit" }

A Service or the Settlement layer (RPP402-005) MUST independently verify a delegation reference against Robinhood Chain's Agentic Account registry rather than trusting the client-asserted risk_check value -that field is informational for the agent's own logging, not a security control.

Settlement Assets

This section is the substantive difference from MPP and x402 named in RPP402-000:

  • stablecoin -behaves like any other payment protocol: amount is an exact decimal quantity of the stablecoin, no amount_mode.
  • tokenized_security -amount_mode is required:
    • "fixed_quantity": amount is an exact token quantity (e.g. "0.014" shares of NVDA.rh). Settlement transfers exactly that quantity regardless of price movement between Intent and Settlement.
    • "notional_usd": amount is a USD figure (e.g. "50.00"). Settlement (RPP402-005) converts it to a token quantity at the reference price sourced at settlement time and records both the USD notional and the resulting quantity on the Receipt (RPP402-006), so "how many tokens did this actually cost" is always reconstructable after the fact.

A Service's Discovery Document (RPP402-001) supported_assets entry for a tokenized_security does not by itself imply which amount_modes it accepts -that is negotiated at Intent time; a Service MAY reject an Intent whose amount_mode it doesn't support with unsupported-amount-mode (see Errors).

JSON Schema

json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://rpp402.com/schemas/v1/payment-intent.json",
  "title": "RPP402 Payment Intent",
  "type": "object",
  "required": ["rpp402_version", "id", "session_id", "payer_wallet", "asset", "amount", "status", "authorization", "created_at", "expires_at"],
  "properties": {
    "rpp402_version": { "type": "string", "const": "1.0" },
    "id": { "type": "string", "pattern": "^intent_[a-zA-Z0-9]{8,}$" },
    "session_id": { "type": "string", "pattern": "^sess_[a-zA-Z0-9]{8,}$" },
    "payer_wallet": { "type": "string", "pattern": "^0x[a-fA-F0-9]{40}$" },
    "asset": { "$ref": "https://rpp402.com/schemas/v1/asset-ref.json" },
    "amount_mode": { "type": ["string", "null"], "enum": ["fixed_quantity", "notional_usd", null] },
    "amount": { "type": "string", "pattern": "^[0-9]+(\\.[0-9]+)?$" },
    "status": { "type": "string", "enum": ["pending", "authorized", "revoked", "expired"] },
    "authorization": {
      "oneOf": [
        {
          "type": "object",
          "required": ["type", "signer", "signature", "nonce"],
          "properties": {
            "type": { "const": "eip712_signature" },
            "signer": { "type": "string", "pattern": "^0x[a-fA-F0-9]{40}$" },
            "signature": { "type": "string", "pattern": "^0x[a-fA-F0-9]+$" },
            "nonce": { "type": "string" }
          }
        },
        {
          "type": "object",
          "required": ["type", "account_id", "delegation_ref"],
          "properties": {
            "type": { "const": "agentic_account_delegation" },
            "account_id": { "type": "string", "pattern": "^aa_[a-zA-Z0-9]{4,}$" },
            "delegation_ref": { "type": "string", "pattern": "^del_[a-zA-Z0-9]{4,}$" },
            "risk_check": { "type": "string" }
          }
        }
      ]
    },
    "created_at": { "type": "string", "format": "date-time" },
    "expires_at": { "type": "string", "format": "date-time" }
  },
  "if": { "properties": { "asset": { "properties": { "type": { "const": "tokenized_security" } } } } },
  "then": { "required": ["amount_mode"] }
}

Examples

Stablecoin Intent (no amount_mode):

json
{
  "session_id": "sess_3fd8c2",
  "payer_wallet": "0xaaaa...1234",
  "asset": { "type": "stablecoin", "symbol": "USDG", "chain_id": "robinhood-1", "contract": "0x1111...aaaa" },
  "authorization": { "type": "eip712_signature", "signer": "0xaaaa...1234", "signature": "0x9d4e...", "nonce": "0x9f3a..." }
}

Tokenized-security Intent, notional mode, via Agentic Account delegation:

json
{
  "session_id": "sess_9a1b02",
  "payer_wallet": "0xaaaa...1234",
  "asset": { "type": "tokenized_security", "symbol": "NVDA.rh", "chain_id": "robinhood-1", "contract": "0x2222...bbbb", "issuer": "Robinhood Assets (Jersey) Limited" },
  "amount_mode": "notional_usd",
  "amount": "50.00",
  "authorization": { "type": "agentic_account_delegation", "account_id": "aa_7d21f0", "delegation_ref": "del_44a1", "risk_check": "within_limit" }
}

Security

  • An eip712_signature Intent MUST include a server-issued nonce, single-use, to prevent replay -a captured signed Intent cannot be resubmitted for a second Settlement.
  • expires_at bounds how long a signature or delegation reference remains valid for Settlement; RPP402-005 MUST reject a Settlement attempt against an expired Intent even if the signature itself is still cryptographically valid.
  • agentic_account_delegation authorizations MUST be checked against Robinhood Chain's Agentic Account registry at Settlement time, not cached from Intent-authorization time -the account owner may have revoked or reduced the delegation in between.
  • Notional-mode tokenized-security Intents are a price-oracle dependency: Settlement (RPP402-005) MUST source the reference price from the same oracle feed the Receipt (RPP402-006) discloses, so the conversion is independently auditable after the fact, not just asserted.

Errors

typestatusMeaning
https://rpp402.com/errors/session-not-quoting409Session isn't in quoting status.
https://rpp402.com/errors/asset-mode-required422tokenized_security asset without amount_mode.
https://rpp402.com/errors/unsupported-amount-mode422Service doesn't accept the requested amount_mode for this asset.
https://rpp402.com/errors/signature-invalid401EIP-712 signature doesn't verify.
https://rpp402.com/errors/delegation-revoked403agentic_account_delegation reference no longer valid.
https://rpp402.com/errors/nonce-reused409Replay attempt.

Best Practices

  • Prefer notional_usd for tokenized-security purchases where the agent cares about cost predictability more than exact share count; prefer fixed_quantity when the agent specifically wants a target position size.
  • Agents operating under an Agentic Account should default to agentic_account_delegation rather than requesting a fresh signature per Intent -it's both faster and keeps spending inside the account owner's pre-set risk limits rather than requiring a new ad hoc approval.
  • Always check PaymentIntent.expires_at before assuming an authorized Intent is still settleable -an Intent does not become invalid by itself, but Settlement will reject it past expiry.

Reference Implementation

Schema: packages/protocol -payment-intent.schema.json (Phase 4, this phase). Client: session.intent() in @rpp402/sdk (Phase 5). Onchain verification of both authorization types: packages/contracts (tracked alongside Phase 4/5, see that package's README).


Previous: RPP402-003 -Commerce Session · Next: RPP402-005 -Settlement