Skip to main contentPASS
Read the docs

Integration

Integration model

The interfaces an integrator works with, where the check belongs in an execution path, and how decisions are surfaced.

An integration adds one thing to a system that already works: a point at which a proposed execution is evaluated before it becomes irreversible. Everything else — credential issuance, mandate assembly, adapter coverage — sits behind that point and is invisible to the code path that moves capital. The integration question is therefore narrow and answerable. Where does the check go, what does it return, and what happens to the answer.

The single decision point

An execution path should contain exactly one authoritative decision point. Not zero, and not several.

AGENT / CALLER
      |
      v
 DECISION POINT           <- one per execution path
      |
      +--> preflight --> DECISION (outcome, reason)
      |
      v
 EXECUTE  |  REFUSE + REASON

The reason for insisting on one is accounting rather than tidiness. A mandate carries stateful limits — in the running example, a $10,000 daily exposure limit alongside a $2,500 maximum transaction. Limits of that kind must be debited once per execution. If two checks in the same path each record consumption, a single $2,500 trade consumes $5,000 of daily capacity and the actor is throttled by an artefact of the wiring. If neither records consumption because each assumes the other did, the limit does not bind at all. One point decides, and that same point accounts.

Where to place it

Three placements are architecturally sound. They differ in how close they sit to the moment capital actually moves, and in how much of the surrounding system they require you to change.

Inside the executing contract. The check runs in the same transaction as the state change it governs. Nothing can be reordered between the decision and its effect, and the transaction's atomicity binds them without further machinery. The cost is intrusiveness: the contract has to call the check, which means you must own or be able to upgrade it. This placement suits venues and execution contracts built under your own control.

In a guard or module in front of an account. A module on a smart account inspects outgoing calls before they are dispatched. Destination protocols need no modification, and coverage extends automatically to calls the account has not made yet. The difficulty is that at this layer you observe calldata rather than meaning, which is precisely the problem an adapter exists to solve. A guard that cannot decode a call has not established what the call does, and must refuse it with INVALID_EXECUTION_CONTEXT rather than pass it through.

At an agent's execution boundary. The check runs offchain, where an autonomous actor turns a plan into a signed instruction. It is the earliest and most informative placement: refusals cost no gas, arrive before signing, and can be explained to an operator or fed back to the agent in time to change the plan. It is also the weakest as an enforcement boundary, because it constrains the agent rather than the key. An actor whose key can sign elsewhere is not bound by a check it can route around.

PlacementProximity to irreversibilityIntrusivenessConstrains
Executing contractSame transaction as the effectRequires owning the contractThe state change itself
Account guard or moduleSame transaction, before dispatchAccount configuration onlyEvery call the account makes
Agent execution boundaryBefore signingNoneThe agent's own behaviour

The placements are not exclusive, and the arrangement the architecture anticipates combines them: offchain preflight for the operator experience and for the agent's own planning, plus an onchain check for enforcement. Both evaluate the same policy and return the same Reason Code. Where they disagree, the enforcing layer is authoritative — the offchain answer described a moment that has since passed.

The onchain interface

The evaluation contract exposes a check over a normalized Execution Intent. The intent is what an adapter produces; it is not raw calldata.

interface IPassPolicy {
    struct ExecutionIntent {
        address actor;
        bytes32 subject;
        bytes32 action;
        address adapter;
        address asset;
        uint256 value;
        bytes32 contextHash;
    }
 
    /// Evaluates an intent against the mandate bound to actor.
    /// Pure evaluation: records no consumption.
    function check(ExecutionIntent calldata intent)
        external
        view
        returns (bool allowed, bytes32 reason);
 
    /// Evaluates and records consumption atomically.
    /// Derives the execution identifier from the intent and the nonce.
    /// Reverts with the reason code on a blocked intent.
    function checkAndConsume(
        ExecutionIntent calldata intent,
        uint256 nonce
    ) external returns (bytes32 executionId, bytes32 reason);
}

Three properties of this shape are deliberate. A reason is returned on every path, including the permitted one, where it is AUTHORIZED, so a log of approvals speaks the same vocabulary as a log of refusals and there is no silent success. Evaluation and consumption are separated: check is a view and can be called from anywhere, including by a caller with no intention of executing, and it reports what the answer is now while saying nothing about whether it will still hold. Only checkAndConsume debits the daily counter, and only the enforcing layer should call it. Reason Codes cross the ABI boundary as fixed byte values rather than as enum indices, so adding a code to the registry never renumbers an existing one, and an integration compiled against an older copy of the registry misreads none of the codes it already knows.

What the intent carries

FieldMeaning
actorThe account proposing execution, bound by a mandate
subjectThe party on whose behalf the actor acts
actionThe normalized operation, such as BUY, SELL or BRIDGE
adapterThe adapter that decoded the call, which establishes the venue
assetThe asset the action concerns
valueThe exposure the action creates, in the mandate's accounting unit
contextHashA commitment to the execution conditions the adapter observed

The subject field resolves to the passport holding that party's credentials, which is how a policy condition on STOCK_TOKEN_ELIGIBLE reaches an attestation the actor does not hold itself. In the running example the actor is 0x81...29F, the subject is 0x3A...F02, and that subject resolves to passport 0x7F...94A, which carries IDENTITY_VERIFIED, NON_US_PERSON, EEA_ELIGIBLE, AML_CHECKED and STOCK_TOKEN_ELIGIBLE.

The contextHash field commits to the surrounding conditions the adapter observed: chain, venue parameters, any deadline. The evaluating contract cannot re-derive those conditions from the other fields, so the adapter carries them forward as a commitment. An intent whose context the enforcing layer cannot reproduce is refused with INVALID_EXECUTION_CONTEXT, which is the mechanism by which an undecodable or unrecognized call becomes an explicit refusal rather than an unexamined pass.

The offchain shape

Offchain preflight returns the same information in a form that a calling system and a human operator can both read.

type ReasonCode =
  | 'AUTHORIZED'
  | 'MISSING_CREDENTIAL'
  | 'CREDENTIAL_EXPIRED'
  | 'CREDENTIAL_REVOKED'
  | 'ASSET_NOT_ALLOWED'
  | 'ACTION_NOT_ALLOWED'
  | 'ADAPTER_NOT_ALLOWED'
  | 'TX_LIMIT_EXCEEDED'
  | 'DAILY_LIMIT_EXCEEDED'
  | 'MANDATE_EXPIRED'
  | 'MANDATE_SUSPENDED'
  | 'ACTOR_NOT_AUTHORIZED'
  | 'INVALID_EXECUTION_CONTEXT';
 
interface ExecutionIntent {
  actor: string;
  subject: string;
  action: string;
  adapter: string;
  asset: string;
  value: string;
  contextHash: string;
}
 
interface Decision {
  outcome: 'AUTHORIZED' | 'BLOCKED';
  reason: ReasonCode;
  actor: string;
  subject: string;
  action: string;
  asset: string;
  value: string;
  limits: {
    transaction: string;
    dailyRemaining: string;
  };
  evaluatedAt: string;
}
 
interface Preflight {
  evaluate(intent: ExecutionIntent): Promise<Decision>;
}

Onchain the decision is returned in reduced form: allowed is true exactly when outcome is AUTHORIZED. That boolean is redundant with a comparison against the code and is kept anyway, because control flow branches on the flag while operators, logs and support tooling read the code. Monetary values cross the boundary as decimal strings rather than as numbers, because a limit compared in binary floating point is a limit that occasionally fails to hold at exactly the value where it matters most.

A rendered decision for the running example, where the actor proposes a $4,000 AAPL purchase against a $2,500 maximum transaction, carries enough context to explain itself.

{
  "outcome": "BLOCKED",
  "reason": "TX_LIMIT_EXCEEDED",
  "actor": "0x81...29F",
  "subject": "0x3A...F02",
  "action": "BUY",
  "asset": "AAPL",
  "value": "4000.00",
  "limits": {
    "transaction": "2500.00",
    "dailyRemaining": "10000.00"
  },
  "evaluatedAt": "2026-04-11T09:12:44Z"
}

One proposed execution can fail several constraints at once. The order of evaluation is therefore fixed, so that the same intent produces the same code in every implementation and on both sides of the boundary: execution context first, because an intent that cannot be constructed cannot be judged; then actor authority; then mandate state; then credentials; then permission over action, asset and adapter; then limits. The first failing check produces the decision. Ordering this way also means a refusal names the most fundamental obstacle rather than an incidental one. An actor bound to no mandate is told ACTOR_NOT_AUTHORIZED, not TX_LIMIT_EXCEEDED.

Surfacing the answer

To a human operator, a refusal is useful in proportion to how specifically it names the constraint. "Blocked" is not an answer. Show the code, the limit, the observed value, and the part interfaces often omit: who can change it. In the case above the actor cannot widen its own transaction ceiling; the party who issued the mandate can. Naming that party converts a dead end into a request.

To a calling system, return the code as a stable machine value rather than a rendered message. Retry behaviour is a property of the reason. A caller that receives DAILY_LIMIT_EXCEEDED may sensibly try again once the window resets, while a caller that receives ASSET_NOT_ALLOWED should not retry at all, because nothing about the passage of time will change the answer. Collapsing every refusal into one generic error discards the only information that would let the caller behave correctly, and tends to produce agents that retry blocked instructions indefinitely.

Responding to each family of refusal

FamilyCodesParty that can change the answer
CredentialMISSING_CREDENTIAL, CREDENTIAL_EXPIRED, CREDENTIAL_REVOKEDThe issuer, by issuance or renewal against the passport
PermissionACTION_NOT_ALLOWED, ASSET_NOT_ALLOWED, ADAPTER_NOT_ALLOWEDThe mandate issuer, by deliberate amendment
LimitTX_LIMIT_EXCEEDED, DAILY_LIMIT_EXCEEDEDThe actor, by resizing or by waiting for the window; the issuer, by amendment
Mandate stateMANDATE_EXPIRED, MANDATE_SUSPENDEDThe mandate controller, by reissue or by lifting the suspension
AuthorityACTOR_NOT_AUTHORIZEDThe mandate issuer, by binding the actor where that is intended
ContextINVALID_EXECUTION_CONTEXTThe integrator, by extending adapter coverage

That table names who can change the answer. What the calling actor should itself do on receiving each code is a separate question, treated in agents.

Credential failures are the most operationally routine. An expired attestation is an administrative condition and clears when the issuer renews it against the passport. A revoked one is not: revocation is a withdrawal someone performed on purpose, and an integration should treat it as terminal until the issuer acts rather than as a transient fault to be retried around.

Permission failures should be slow. If the actor in the running example proposes a third asset, ASSET_NOT_ALLOWED is the mandate working as specified; the same mandate blocks bridging, so a proposed bridge returns ACTION_NOT_ALLOWED however small the amount and however complete the subject's credentials. The remedy is an amendment by the issuer, and the deliberation that amendment requires is the point of the mechanism. Automation across these families must therefore be asymmetric. An agent may reasonably resize a $4,000 order to $2,500 or less in response to TX_LIMIT_EXCEEDED, because a smaller order stays inside authority already granted. An agent that responds to ASSET_NOT_ALLOWED by widening its own mandate has reduced the permission layer to a formality.

Context failures belong to the integrator rather than to any counterparty. They mean the decision point met a call it could not describe, and refusing is the correct default: an unrecognized call is not a safe call, it is an unevaluated one. Persistent INVALID_EXECUTION_CONTEXT at a given venue is a request for adapter coverage, not a condition for the actor to work around.

Binding a decision to its execution

A decision describes one proposed execution at one moment. If it is not bound to that execution, two failure modes follow. The first is reuse: a single approval at $2,500, replayed four times, moves $10,000 while the accounting records $2,500. The second is drift, where a check and the execution it authorized are separated in time, and in the interval a credential expires or a mandate is suspended.

INTENT
  |
  v
executionId = hash(intent fields, nonce)
  |
  v
DECISION recorded against executionId
  |
  +-- already consumed? --> refuse, no re-check
  |
  v
EXECUTE --> debit daily exposure once

The identifier is derived from the content of the intent — actor, subject, action, adapter, asset and value — together with a nonce, so that two economically distinct executions cannot collide and the same execution cannot be presented twice. It must be derived by the evaluating contract rather than supplied by the caller. An identifier the caller chooses establishes nothing, because a caller free to choose one is free to choose a fresh one for a second presentation of the same intent. That is why the interface above takes a nonce and returns the identifier it computed.

The identifier is consumable once. A second presentation observes it consumed and is refused without re-evaluation, which is the behaviour you want, because re-evaluating would sometimes succeed.

Where the decision point sits inside the executing contract, most of this machinery disappears: check and effect share a transaction and cannot be separated. That is the strongest argument for placing the decision point as close to irreversibility as the surrounding system permits.

Accounting follows the same discipline. Offchain preflight should evaluate without recording consumption, because an intent that is authorized and then never submitted, or submitted and reverted, would otherwise burn capacity the actor never used. Preflight predicts; the enforcing layer accounts.

What these definitions are

The interfaces on this page are specification artefacts. They fix the shape of a boundary — what an integrator must be able to construct, what a check must return, and which values stay stable across implementations — so that independent implementations agree on one vocabulary. They describe a boundary, not a distribution. Normative field definitions and the complete Reason Code table live in the specification; implementation-facing material is collected under developers.

It is worth being precise about what a correct integration achieves. A decision point constrains authorization: it establishes that this actor was permitted to take this action, for this subject, within these limits, at this venue. It leaves market, smart-contract, oracle and governance risk untouched, as the security model sets out: an authorized execution is a permitted one, not necessarily a sound one.