# Client-side signing

In production, agents keep their private keys. The sandbox can hold keys for convenience, but every signing operation also works with keys that never leave the agent's process: deliveries, mandates, dispute rulings. `AgentSigner` in `@quitanza/sdk` is the client-side signer.

## Quickstart

```ts
import { AgentSigner, Quitanza } from "@quitanza/sdk";

const qz = new Quitanza({ baseUrl: "http://localhost:4280" });

// Keys are generated (or supplied) locally and never transmitted.
const buyer = new AgentSigner();
const provider = new AgentSigner();

// Parties are named by raw public key instead of sandbox agent id.
const escrow = await qz.escrows.create({
  payer: { publicKey: buyer.publicKey, label: "buyer" },
  payee: { publicKey: provider.publicKey, label: "provider" },
  amount: "25.00",
  asset: "USDC",
  terms: {
    description: "the agreed report",
    checks: [{ kind: "shape", requiredFields: ["report"] }]
  }
});
await qz.escrows.fund(escrow.id);

// The provider signs the delivery locally …
const signed = provider.signDelivery(escrow.id, { report: "done" });
// … and only the payload, timestamp and signature go over the wire.
const { verdict, quitanza } = await qz.escrows.submitSignedDelivery(escrow.id, signed);
```

The engine accepts the delivery only if the signer is the escrow's payee, the content hash matches the payload, and the Ed25519 signature verifies over canonical `{escrowId, contentHash, submittedAt}`.

## What else signs client-side

**Mandates.** A principal signs spending caps for an agent key; the API only ever sees the signature:

```ts
const principal = new AgentSigner();
const mandate = await qz.mandates.register(
  principal.signMandate({
    agent: buyer.publicKey,
    perEscrowMax: "50.00",
    cumulativeMax: "200.00",
    allowedAssets: ["USDC"],
    expiresAt: "2026-12-31T00:00:00.000Z"
  })
);
// pass mandate.id as mandateId at escrow creation; every cap is enforced.
```

**Dispute rulings.** A ruling needs both parties' signatures, or the named arbiter's:

```ts
const dispute = await qz.disputes.get(escrow.id);
await qz.disputes.resolve(escrow.id, "release", "accepted out-of-band", {
  signatures: [
    buyer.signRuling(escrow.id, dispute.id, "release", "accepted out-of-band"),
    provider.signRuling(escrow.id, dispute.id, "release", "accepted out-of-band")
  ]
});
```

## Key handling

`new AgentSigner()` generates a fresh Ed25519 keypair; `new AgentSigner(keys)` restores one you persisted. The signer exposes `publicKey` (hex, safe to share) and keeps `keys.privateKey` in process memory. Store it as you would any secret. The sandbox path (`payerAgentId`/`payeeAgentId`, `agentId` on deliveries) remains available for demos where key custody does not matter.
