Veto/docs
Meow Gateway

Counterparty Locking

Every beneficiary hashes to a stable identifier. Quarantine new payees, hold compromised ones, verify-once enforces human approval before first use.

A Counterparty is a payee — a bank account, a crypto address, a Meow-internal account. The gateway hashes the wire-level fields into a canonical beneficiary_hash, and every capsule binds that hash. You cannot mint or consume a capsule for a payee the gateway doesn't know about.

Normalized hashing

import { hashBeneficiary } from "@veto/spend-capsule-protocol";

hashBeneficiary({
  type: "bank_us",
  name: "Acme Corp",
  routing: "021000021",
  account_last4: "1234"
});
// → "sha256:7ee7f2426cda71548a0fae87c291ff42469358bcb65ff2a0ffaf763d15bae5f4"

Per-type normalization:

  • bank_us — lowercase name, strip routing whitespace, bind routing + last-4
  • bank_intl — uppercase IBAN + country ISO, lowercase name, BIC if present
  • crypto (eth/base/arb) — EIP-55 address
  • crypto (sol) — base58 address

The hash is stable across TS and Python SDKs — golden vectors in the protocol test suite ensure cross-language parity.

Lifecycle

unverified (quarantined) → verified → (optionally) held → verified
  • unverified — just seen; ap_strict_v1 requires HITL approval before first use
  • verified — a human clicked approve; capsules against this hash mint without an extra approval
  • held — operator marked the payee as compromised; mints deny immediately

A payee's state is per-org. Two organizations can independently verify the same bank account.

Invoice-swap defense

The hash is the PRIMARY drift anchor. A common attack on naive systems:

  1. Agent reads invoice saying "pay Acme at routing X, account Y"
  2. Something swaps the bytes in transit so routing X becomes X'
  3. Naive system pays the attacker's account

With a counterparty lock:

  1. Capsule is minted with counterparty_hash = hash(routing=X, account=Y)
  2. Runtime consume computes hash(routing=X', account=Y) — different bytes
  3. Drift check fails; the gateway denies + chains a beneficiary_hash_mismatch receipt

The receipt survives the attack attempt as audit evidence.

API

# Upsert (computes hash server-side):
POST /v1/counterparties
{
  "type": "bank_us",
  "display_name": "Acme Corp",
  "routing_number": "021000021",
  "account_last4": "1234",
  "account_holder_name": "Acme Corp",
  "operator_id": "op_ap"
}

# Verify (exits quarantine):
POST /v1/counterparties/$HASH/verify
{ "operator_id": "op_compliance" }

# Hold (immediate-reject on drift):
POST /v1/counterparties/$HASH/hold
{ "operator_id": "op_security", "reason": "vendor phishing incident 2026-04-18" }

Every verify / hold writes to the decision receipt chain with the operator id + reason, so the audit trail captures WHO trusted WHAT payee WHEN.