Aqua / Liquid integration

May 24, 2026 · View on GitHub

This guide describes integrating the Policy Engine with Aqua-family wallets transacting on the Liquid network (notably USDT on Liquid). Aqua's native stack makes the sidecar API pattern (see INTEGRATION.md) the natural choice; the embedded TypeScript module is not directly importable into Aqua's native runtime.

Deployment shape

The Policy Engine runs as a long-running localhost service on the same machine as the Aqua wallet (desktop installer or server-side). Aqua calls it over http://127.0.0.1:8001/evaluate before broadcasting any transaction.

┌──────────────────────────┐   POST /evaluate    ┌───────────────────┐
│  Aqua wallet (native)    │ ──────────────────→ │  Policy sidecar   │
│  - Compose Liquid tx     │                     │  Node + evaluator │
│  - About to sign         │ ←────────────────── │  localhost:8001   │
│                          │  PolicyEvaluation   │                   │
│  If allow → sign + send  │  Credential (JSON)  │                   │
│  If deny  → DO NOT SIGN  │                     │                   │
└──────────────────────────┘                     └───────────────────┘

The sidecar systemd unit ships with policy-core-impl.

Pre-settlement call

Aqua calls the sidecar with:

POST /evaluate
Content-Type: application/json

{
  "proposal": {
    "rail": "liquid",
    "canonicalBytes": "<hex of Liquid tx pre-sign serialisation>",
    "humanReadable": {
      "from": "<liquid address>",
      "to": "<liquid address>",
      "amount": "5000.00",
      "asset": "USDT-Liquid"
    }
  },
  "delegationCredential": { /* signed ObserverDelegationCredential */ },
  "attestations": [ /* optional pre-fetched counterparty attestations */ ]
}

Response is a signed PolicyEvaluationCredential (same shape as for any other rail).

Liquid-specific canonicalisation

The proposalHash is the SHA-256 of the canonical Liquid pre-sign bytes. Use the standard Elements (Liquid) transaction serialisation from libwally / elements-cpp; do not reimplement.

The reference implementation publishes the exact serialisation at https://docs.observerprotocol.org/policy/canonicalization/liquid.

Allowlist DID resolution for Liquid counterparties

When a delegation's tradingMandate.counterparty.allowList carries DID entries, the engine resolves each DID to its Liquid-rail wallet binding via the Observer Protocol wallet-binding registry. Per the engine's fail-closed rule on allowList, DIDs that cannot be resolved (no Liquid-rail binding for that DID) are treated as not-on-the-list and the action is denied.

Raw Liquid address entries in allowList evaluate directly without registry lookup.

Trust-data integration

USDT-on-Liquid counterparties may or may not have OP attestations. The evaluatedWithAttestations: false flag in the returned credential signals which evaluations were attestation-bare. For Aqua deployments where attestation enrichment is desired, deploy the engine alongside an OP attestation-fetch service or supply the attestations field pre-fetched.

When the sidecar is unavailable

If http://127.0.0.1:8001/evaluate returns a non-2xx response or fails to respond, Aqua MUST treat the result as deny. Pre-settlement enforcement is fail-closed; a missing decision is not the same as a positive decision.

A healthcheck endpoint (GET /health) is provided so Aqua can pre-flight the sidecar's availability and surface degraded state to the user before they attempt to compose a transaction.

Reference deployment

For desktop Aqua, the policy-core sidecar can ship as a bundled Node binary launched alongside the wallet. For server-side / hosted Aqua deployments, the sidecar runs as a separate systemd unit on the same host. The proprietary policy-core-impl repository carries the deployment manifests, healthcheck wiring, and the per-rail canonicalisation modules.