WDK integration (Tether)

August 13, 2026 · View on GitHub

Corrected 2026-08-03. These snippets named @observer-protocol/policy-core and a function evaluate({...}). Neither exists: the published package is @observer-protocol/policy-engine and the exported entry point is enforceMandate(ctx, credential, config, resolvedTransfer). Signatures below are taken from the published type definitions.

This guide describes how to integrate the Observer Protocol Policy Engine as a pre-settlement hook inside the Tether WDK. The pattern is wallet-embedded (see INTEGRATION.md for pattern selection).

What gets added to the WDK build

One npm dependency:

npm install @observer-protocol/policy-engine@rc   # `@rc` is required: a bare name resolves to
                                                  # `latest`, which is held at the last non-rc release

(The package is published from packages/policy-engine/ in this repository — runtime and types together, MIT. PROVENANCE.md in that directory records where each core module came from.)

Where the hook lives

Inside the WDK's transaction-signing pipeline, immediately before the call that signs the unsigned transaction. The hook receives the unsigned tx + the delegation credential that authorises the signing agent, and returns a decision.

import { enforceMandate } from '@observer-protocol/policy-engine';
import type { PolicyEvaluationCredential } from '@observer-protocol/policy-engine';

async function preSettlementHook(
  unsignedTx: WDKUnsignedTransaction,
  delegationCredential: ObserverDelegationCredential,
): Promise<PolicyEvaluationCredential> {
  const decision = await evaluate({
    proposal: {
      rail: unsignedTx.rail,                       // e.g. 'ethereum-mainnet' | 'tron'
      canonicalBytes: unsignedTx.preSignBytes(),   // rail-native canonical pre-sign hex
      humanReadable: unsignedTx.toJSON(),
    },
    delegationCredential,
    attestations: await fetchAttestationsForCounterparty(unsignedTx),
  });

  if (decision.credentialSubject.decision === 'deny') {
    // Surface the structured deny reason to the agent / human operator.
    throw new PolicyDenied(decision);
  }
  return decision;
}

The WDK signer is wrapped to call the hook first and store the returned credential alongside the broadcast record.

Per-rail canonicalisation

The proposalHash inside the returned PolicyEvaluationCredential is the SHA-256 of canonicalBytes. For each rail the WDK supports, the canonical pre-sign bytes are:

RailCanonical pre-sign bytes
EVM mainnet / testnetsRLP encoding of the unsigned transaction (the same bytes a signer normally hashes)
TRON (TRC-20 USDT)Protobuf-serialised raw_data of the Transaction
Bitcoin / Lightning HTLCsStandard sighash preimage
SolanaCompiled message bytes pre-signature

The reference implementation publishes the exact canonicalisation per rail at https://docs.observerprotocol.org/policy/canonicalization/{rail}. WDK implementations MUST consume the canonical pre-sign bytes from the rail's standard libraries — never reimplement.

What gets stored on success

For each allowed transaction, the WDK stores three artifacts together:

  1. The signed transaction (as today).
  2. The PolicyEvaluationCredential (new) — signed by the engine, bound to the proposal hash and the delegation credential hash.
  3. The broadcast receipt / on-chain tx hash (as today).

These three together form the audit trail: here is the action, here is the cryptographic decision that authorised it, here is the on-chain confirmation.

Denial surface

On deny, the WDK MUST NOT sign. The structured deny reason (ruleType, ruleField, message) is surfaced to the user / agent so they can adjust the proposal or escalate.

The PolicyEvaluationCredential on deny is still emitted and SHOULD be stored — both for audit purposes and because a denied attempt may itself be reportable (compliance, fraud detection, behavioural analysis).

Latency budget

Target: <100ms per evaluation (p95). The engine's declarative rules evaluate in <5ms; attestation lookups are the dominant cost when not pre-fetched. WDK implementations SHOULD pre-fetch counterparty attestations alongside other counterparty resolution (address lookups, ENS, etc.) and pass them via the attestations field.

Verifying the credential later

Anyone — counterparty, auditor, regulator — can independently verify the returned PolicyEvaluationCredential:

  1. Resolve the issuer DID (e.g. did:web:observerprotocol.org).
  2. Extract the assertionMethod-listed verification key referenced in proof.verificationMethod.
  3. Verify the Ed25519 signature over the JCS-canonical form of the credential (excluding proof).
  4. Optionally cross-check against https://observerprotocol.org/.well-known/key-scoping.json to confirm the signing key is governance-scoped for this credential type.

No call back to Observer Protocol's servers is required for verification.