Agents & Provenance
Policies & Provenance
Vellar smart accounts are Soroban contracts, so they can carry programmable
on-chain policies — spending limits, multisig, allowlists, and provenance
— enforced by the account itself, not by your UI. The SDK exposes the full flow
on the wallet handle as wallet.policies.
Two policies matter most for agent payments, and they stack:
- Spending-limit — a cumulative rolling-window cap on how much an agent can move. Even a fully compromised key can't exceed it.
- Verified-only (provenance) — restricts an agent to paying only contracts whose source has been reproducibly verified against the deployed wasm. An unverified recipient is rejected on-chain.
Both are checked inside the wallet's __check_auth during authorization, so
they're enforced by Stellar consensus, not by your code — see x402
payments and agent keys for how an agent pays
under them.
Enabling policies
Pass apiUrl (your policy API gateway) to createVellarWallet. To deploy a
policy you also pass a policyAttach runtime that signs addPolicy with the
passkey:
const vellar = createVellarWallet({
network: "testnet",
appName: "My App",
kit,
sac,
backend,
isValidAddress,
apiUrl: "https://api.myapp.com", // your policy API gateway
policyAttach: {
// build kit.addPolicy(contractId) → passkey-sign → submit via your backend
async attachPolicy(policyContractId) {
const tx = await kit.addPolicy(policyContractId);
const signed = await kit.sign(tx);
return backend.submitTransaction({ signedXdr: signed.toXDR(), network });
},
// optional: resume the passkey for a keyId without prompting
async resume(keyId) {
await kit.connectWallet({ keyId });
},
},
});
Without apiUrl, wallet.policies throws. Without policyAttach, read /
generate / simulate still work but deploy() throws a clear error.
The flow
// 1. list the templates your gateway offers (with honest enforcement labels)
const templates = await vellar.policies.listTemplates();
// 2. generate the deployable artifacts for a definition (validates first)
const policy = await vellar.policies.generate({
version: "1",
type: "spending_limit",
owners: [vellar.session!.accountId],
spendingLimits: { dailyXlm: "100" }, // 100 XLM per rolling window
});
// 3. (optional) dry-run the on-chain deploy — surfaces cost/errors, no submit
const sim = await vellar.policies.simulate(policy.id);
// 4. attach it to the wallet — the ONLY passkey prompt in this flow
const { contractId, attachTxHash } = await vellar.policies.deploy(policy.id);
What deploy() actually does
- Deploy instance — your backend deploys a per-user policy contract instance, bound to the wallet, sponsor-funded server-side.
- Attach — the user passkey-signs
addPolicyto attach the instance. This is the only WebAuthn prompt — no silent signing. - Record — the completed attach is recorded via your gateway.
Provenance (verified-only)
The verified-only policy is the provenance layer. Instead of capping an
amount, it reads an on-chain attestation registry inside __check_auth and
rejects any payment whose recipient contract isn't attested as verified.
- The AttestationRegistry (a Soroban contract, live on testnet) is the on-chain source of truth for which contracts have reproducibly-verified source. An attestor mirrors verification outcomes into it, with ledger-based expiry so it fails closed.
- The verified-only policy reads that registry during authorization. Attest a contract → an agent's payment to it settles. Revoke the attestation → the identical payment is rejected on-chain and no funds move. Nothing changes but the verification status.
Attach it exactly like a spending limit (through wallet.policies), and stack
both so an agent key can pay up to a budget and only through verified code.
This is verified ≠ audited — it proves provenance (reproducible, attributable
source), not safety. See agent keys for the full mint flow.
API
| Method | Description |
|---|---|
policies.listTemplates() | Available policy templates + their on-chain enforcement |
policies.generate(def) | Validate a definition and produce the deployable artifacts |
policies.simulate(id) | Dry-run the deploy for the connected wallet (no submit) |
policies.deploy(id) | Instance deploy → passkey-sign addPolicy → record { contractId, attachTxHash } |
Your gateway
wallet.policies talks to these routes on your apiUrl gateway (instance
deploys are funded by your sponsor account, server-side):
GET /policies/templates
POST /policies/validate
POST /policies/generate
POST /policies/:id/simulate
POST /policies/:id/deploy-instance
POST /policies/deploy
Honesty
Each template declares how it is actually enforced on-chain (enforcementLabel)
— e.g. a spending limit is a dedicated policy contract enforcing a cumulative
rolling-window allowance; multisig/allowlists use the smart wallet's native
signer limits. The SDK never claims enforcement a template doesn't provide, and
a policy is inert until the passkey-signed attach lands.
Next steps
- Wallet Methods — every method on the wallet handle
- Security — the guarantees the SDK enforces