Why a PINT rather than a bearer token
A bearer token asserts “whoever holds this is allowed.” A PINT asserts “this specific user signed this specific authorisation.” That gives you four properties a bearer token can’t:- Cryptographic consent. The user’s signature is over the full payload — the scopes, the spend cap, the expiry.
- Independently verifiable. A verifier can check the EIP-712 signature against the user’s Safe without calling Sumvin, at the Enhanced verification tier.
- Scope-enveloped. The signed payload carries scopes with per-scope parameters (amount caps, asset, chain, time window), so authorisation is fine-grained end-to-end.
- Agent-compatible. The same shape works whether the user signs with a passkey or an agent key signs on their behalf.
When a partner creates one
Any time an action needs explicit user authorisation:- Identity proof (KYC status, proof of personhood, age claim)
- On-chain spend via the user’s Safe
- Creating an for perpetual search
- Linking a bank account or running a ramp session
- Card management
PINT vs JWT
These are easy to confuse and different:| PINT | PINT JWT | |
|---|---|---|
| What it is | EIP-712 signed payload | ES256-signed JWT issued by SIS |
| Who signs | The user (or their agent) | SIS |
| Direction | Input to token exchange | Output to send to verifiers |
| Consumed by | SIS token exchange, Enhanced-tier verifiers | All verifiers |
x-sumvin-pint-token is consuming the JWT; an Enhanced-tier verifier additionally consumes the original PINT via X-Pint-Signature + X-Pint-Payload.
Domain Separator
The EIP-712 domain separator identifies the signing context and prevents cross-protocol replay attacks:| Field | Type | Description |
|---|---|---|
name | string | Always "Sumvin Purchase Intent" |
version | string | Schema version. Currently "1" |
chainId | uint256 | Target chain ID (e.g., 1329 for Sei) |
verifyingContract | address | The user’s Safe Smart Account address |
verifyingContract is the user’s Safe wallet address — the same address that appears in the wallet field of the PINT payload.
PurchaseIntent Type
The structured data that the user signs:Field Reference
| Field | Type | Required | Description |
|---|---|---|---|
wallet | address | Yes | The user’s Safe Smart Account address |
nonce | uint256 | Yes | Monotonically increasing per-wallet counter for replay prevention |
statement | string | Yes | Human-readable summary of the intent (shown to user at signing time) |
scopes | string[] | Yes | Authorised scopes in SRI format (e.g., ["sr:us:pint:identity:proof_of_personhood"]) |
resources | string[] | Yes | SRI URIs for associated resources |
maxAmount | uint256 | Yes | Spend cap in token units. 0 means no limit |
maxAmountToken | address | Yes | Token denomination for maxAmount. Zero address (0x000...) means USD-equivalent |
expiresAt | uint256 | Yes | Unix timestamp after which the PINT is no longer valid |
Example PINT Payload
Constructing the EIP-712 Hash
To sign a PINT, you construct the EIP-712 typed data hash following the standard:This example uses viem, but any EIP-712 compliant library will produce the same hash. See Signing a PINT for the full signing flow.
Versioning
The EIP-712 specification is versioned via the domain separator’sversion field. The current version is "1". When the type structure changes, the version will increment, and both old and new versions will be supported during a migration window.
Schema validation is enforced server-side at the SIS token exchange endpoint — if the PINT payload doesn’t match the canonical type structure for the declared version, the exchange returns 400 Bad Request with error code PINT-400-001.
Where PINTs appear
- Payment request links — user-originated payment URLs backed by a signed PINT.
- Global x402 acceptance — every identity’s persistent x402 endpoint, authorised per-settlement against a PINT.