> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sumvin.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Signing a Stamped Mandate

> How to sign a Stamped Mandate with user keys and agent keys

A <Tooltip headline="Stamped Mandate" tip="A signed authorisation a user grants for specific scoped actions — delivered on the wire as a PINT." cta="Glossary →" href="/glossary">Stamped Mandate</Tooltip> is a user's signed authorisation for a set of scoped actions. A Stamped Mandate is delivered over the API as a `PINT` (header `x-sumvin-pint-token`).

<Snippet file="product-term-disambiguation.mdx" />

Once you've constructed a [PINT payload](/identity/pint), it needs to be signed before it can be exchanged for a JWT. The Sumvin ecosystem supports two signer types, each using different cryptographic curves and verification methods.

## Signer Types

| Signer Type | Key Curve         | Verification Method                               | Use Case                          |
| ----------- | ----------------- | ------------------------------------------------- | --------------------------------- |
| `user`      | secp256k1         | Standard ECDSA recovery                           | User signing directly via wallet  |
| `agent`     | P-256 (secp256r1) | EIP-1271 `isValidSignature` against Safe contract | AI agent signing on user's behalf |

The SIS automatically determines the signer type based on whether the signing wallet has a registered agent key.

## User Signing (secp256k1)

When the user signs directly, use standard Ethereum EIP-712 signing via their wallet. This is the most common path for user-initiated flows.

### TypeScript (viem)

```typescript theme={null}
import { createWalletClient, custom } from "viem";
import { sei } from "viem/chains";

const walletClient = createWalletClient({
  chain: sei,
  transport: custom(window.ethereum),
});

const domain = {
  name: "Sumvin Purchase Intent",
  version: "1",
  chainId: 1329n,
  verifyingContract: "0xE23c9A70BC749EBddd8c78a864fd911D04E9e992",
} as const;

const types = {
  PurchaseIntent: [
    { name: "wallet", type: "address" },
    { name: "nonce", type: "uint256" },
    { name: "statement", type: "string" },
    { name: "scopes", type: "string[]" },
    { name: "resources", type: "string[]" },
    { name: "maxAmount", type: "uint256" },
    { name: "maxAmountToken", type: "address" },
    { name: "expiresAt", type: "uint256" },
  ],
} as const;

const message = {
  wallet: "0xE23c9A70BC749EBddd8c78a864fd911D04E9e992",
  nonce: 42n,
  statement: "Purchase authorization for partner X",
  scopes: ["sr:us:pint:identity:proof_of_personhood", "sr:us:pint:personalization:read"],
  resources: ["sr:us:pint:abc123"],
  maxAmount: 0n,
  maxAmountToken: "0x0000000000000000000000000000000000000000",
  expiresAt: 1740000000n,
};

const signature = await walletClient.signTypedData({
  account: "0xUserEOAAddress...",
  domain,
  types,
  primaryType: "PurchaseIntent",
  message,
});

// signature is a hex string: "0x..."
```

### Python (eth-account)

```python theme={null}
from eth_account import Account
from eth_account.messages import encode_typed_data

domain_data = {
    "name": "Sumvin Purchase Intent",
    "version": "1",
    "chainId": 1329,
    "verifyingContract": "0xE23c9A70BC749EBddd8c78a864fd911D04E9e992",
}

message_types = {
    "PurchaseIntent": [
        {"name": "wallet", "type": "address"},
        {"name": "nonce", "type": "uint256"},
        {"name": "statement", "type": "string"},
        {"name": "scopes", "type": "string[]"},
        {"name": "resources", "type": "string[]"},
        {"name": "maxAmount", "type": "uint256"},
        {"name": "maxAmountToken", "type": "address"},
        {"name": "expiresAt", "type": "uint256"},
    ]
}

message_data = {
    "wallet": "0xE23c9A70BC749EBddd8c78a864fd911D04E9e992",
    "nonce": 42,
    "statement": "Purchase authorization for partner X",
    "scopes": ["sr:us:pint:identity:proof_of_personhood", "sr:us:pint:personalization:read"],
    "resources": ["sr:us:pint:abc123"],
    "maxAmount": 0,
    "maxAmountToken": "0x0000000000000000000000000000000000000000",
    "expiresAt": 1740000000,
}

signable = encode_typed_data(
    domain_data, message_types, message_data, "PurchaseIntent"
)

signed = Account.sign_message(signable, private_key="0xYourPrivateKey...")
signature = signed.signature.hex()
```

## Agent Signing (P-256)

When an AI agent signs a Stamped Mandate on the user's behalf, it uses a P-256 key that has been registered as an authorised signer on the user's Safe smart account. Verification happens on-chain via [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) — the SIS calls `isValidSignature` on the Safe contract to confirm the agent key is authorised.

```typescript theme={null}
import { p256 } from "@noble/curves/p256";

// Agent's P-256 private key
const agentPrivateKey = new Uint8Array(/* ... */);

// Hash the EIP-712 typed data (same hash as user signing)
const typedDataHash = hashTypedData({ domain, types, primaryType: "PurchaseIntent", message });

// Sign with P-256
const sig = p256.sign(typedDataHash.slice(2), agentPrivateKey);
const signature = "0x" + sig.toDERHex();
```

When exchanging an agent-signed PINT, the SIS detects the agent key automatically — no need to specify `signer_type` in the request:

```json theme={null}
{
  "pint": { "...": "..." },
  "signature": "0x...",
  "audience": "partner-x.example.com"
}
```

<Warning>
  Agent signing requires the P-256 key to be registered on the user's Safe contract. If the key is not an authorised signer, the EIP-1271 verification will fail with error code `PINT-401-002`.
</Warning>

## After Signing

Once you have the signed Stamped Mandate — the PINT payload and its signature — pass both to the [token exchange endpoint](/identity/token-exchange) to receive a SIS-signed JWT.
