{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://swapeazi.io/schemas/offramp-mandate/v1.json",
  "title": "SwapEazi Off-ramp Delegated-Authority Mandate v1",
  "description": "The signed authority an AI agent presents on every off-ramp settlement. A KYC'd principal signs the body once with an ed25519 key. Every limit in the body is enforced deterministically against the signed bytes, so a caller cannot widen scope, purpose, beneficiaries, or amounts. This file mirrors the schema emitted at runtime by lib/mandate/offramp.ts (OFFRAMP_MANDATE_JSON_SCHEMA).",
  "type": "object",
  "required": ["body", "signature"],
  "properties": {
    "body": {
      "type": "object",
      "required": ["schemaVersion", "mandateId", "principal", "agent", "scope", "issuedAt", "expiresAt", "nonce"],
      "properties": {
        "schemaVersion": { "const": "off-1.0.0" },
        "mandateId": { "type": "string", "description": "Unique id for this mandate." },
        "principal": {
          "type": "object",
          "required": ["principalId", "principalKycRef"],
          "properties": {
            "principalId": { "type": "string", "description": "Stable principal identity (DID, B2B id, or wallet-derived)." },
            "principalKycRef": { "type": "string", "description": "Reference to the principal's KYC of record. Not the underlying PII." }
          }
        },
        "agent": {
          "type": "object",
          "required": ["agentId"],
          "properties": { "agentId": { "type": "string", "description": "The delegated agent this authority is granted to." } }
        },
        "scope": {
          "type": "object",
          "required": ["maxPerTx", "maxPerPeriod", "period", "allowedBeneficiaries", "allowedCorridors", "deliveryTypes"],
          "properties": {
            "maxPerTx": { "type": "number", "exclusiveMinimum": 0, "description": "Maximum single payout in the corridor currency." },
            "maxPerPeriod": { "type": "number", "exclusiveMinimum": 0, "description": "Maximum total payout per period in the corridor currency." },
            "period": { "enum": ["daily", "weekly", "monthly"] },
            "allowedBeneficiaries": {
              "type": "array",
              "minItems": 1,
              "description": "Pinned allowlist. The agent names only beneficiaryRef at settle time. lookupHash pins the payout target so an injected recipient is rejected.",
              "items": {
                "type": "object",
                "required": ["beneficiaryRef", "lookupHash"],
                "properties": {
                  "beneficiaryRef": { "type": "string" },
                  "lookupHash": { "type": "string" }
                }
              }
            },
            "allowedCorridors": {
              "type": "array",
              "minItems": 1,
              "items": { "type": "string", "examples": ["ZAR:ZA"] },
              "description": "Currency and jurisdiction pairs this authority may settle into."
            },
            "deliveryTypes": {
              "type": "array",
              "minItems": 1,
              "items": { "enum": ["bank", "mobile_money"] }
            },
            "allowedPurposes": {
              "type": "array",
              "minItems": 1,
              "items": { "enum": ["medical", "groceries", "education", "utilities", "trade_supplier", "family_support", "other"] },
              "description": "Optional purpose allowlist. When present, a settle call must declare a purpose from this list or it is denied (PURPOSE_NOT_ALLOWED). A mandate scoped to groceries cannot be spent on anything else. Omit to leave the mandate unconstrained by purpose."
            }
          }
        },
        "issuedAt": { "type": "string", "format": "date-time" },
        "expiresAt": { "type": "string", "format": "date-time" },
        "revoked": {
          "type": ["object", "null"],
          "properties": {
            "at": { "type": "string" },
            "reason": { "type": "string" }
          }
        },
        "nonce": { "type": "string", "description": "Anti-replay nonce, unique per mandate." },
        "x402": {
          "type": "object",
          "description": "Optional binding so a caller can present this mandate in an x402 payment context, no SwapEazi SDK required.",
          "properties": {
            "network": { "type": "string", "examples": ["base"] },
            "asset": { "type": "string", "examples": ["USDC"] },
            "payTo": { "type": "string" }
          }
        }
      }
    },
    "signature": {
      "type": "object",
      "required": ["alg", "publicKey", "value"],
      "properties": {
        "alg": { "const": "ed25519" },
        "publicKey": { "type": "string", "description": "hex SPKI public key of the principal." },
        "value": { "type": "string", "description": "hex ed25519 signature over the canonicalized body." }
      }
    }
  }
}
