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
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:
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:
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.