MOONEUM

Approvals & signing

Every write that moves funds — a payment, a trade, an Earn deposit, a Borrow draw — shares one lifecycle, and your integration should be built around it rather than assuming a synchronous result. Mooneum is non-custodial with maker-checker controls: the API can prepare and request, but execution waits for policy, approvers, and a signature from the org's own keys.

The lifecycle

  1. Create — your API call creates a draft order (POST /api/v1/trade-orders, /earn-orders, /borrow-orders, or a payment).
  2. Submit — POST …/{id}/submit sends it into the pipeline; policy rules evaluate (limits, whitelists, pair/protocol restrictions).
  3. Approval — if a threshold requires sign-off, the order waits. Approvers act in-app, or programmatically: GET /api/v1/approvals lists pending items and POST /api/v1/approvals/{domain}/{id}/decide records an APPROVE/REJECT (client.decide(id, 'APPROVE')).
  4. Signing — the transaction is signed by the org's wallet or a paired Mooneum Signer device. A Wallet.SigningRequested event fires when a signature is being waited on.
  5. Broadcast & confirm — the signed transaction publishes on-chain and confirms.

The events to listen for

All four verticals publish one shared event stream, with a domain field (payment | trade | earn | borrow):

Order.Created → Order.Submitted → [Order.Approved | Order.Rejected]
  → Wallet.SigningRequested → Wallet.TransactionSigned
  → Order.Broadcast → [Order.Confirmed | Order.Failed]

Subscribe via webhooks or WebSockets; the complete catalogue with payload fields is in the events reference. Multi-leg orders (an Earn withdrawal with a CLAIM leg, an approval-then-swap) carry a leg field so you can tell which step just happened.

Design for 'pending', not 'done'

A 2xx from submit means "accepted into the pipeline," not "executed." Persist the order id, treat Order.Confirmed (or Order.Failed / Order.Rejected) as the terminal signal, and use idempotency keys on create calls so retries never double-submit.

What this means for common integrations

  • A payout job creates + submits payments, then reconciles on Order.Confirmed — it never needs (and shouldn't have) approval or signing permissions of its own.
  • An approvals bot (e.g. posting to Slack) reads GET /api/v1/approvals and calls decide() after a human clicks — the API key needs Approve on just that product.
  • A treasury dashboard is read-only: portfolio, positions, audit logs. No part of this lifecycle applies to reads.

Scopes for all of this are per product and per action — see the permissions reference.