Skip to main content

Plan-Sign-Execute

Every operation that moves funds (earn, withdraw, transfer) follows a three-step flow. This keeps Legend non-custodial — your users’ keys never touch our servers.

The three steps

1. Plan

Your server tells Legend what you want to do:
POST /accounts/acc_xxx/plan/earn
{
  "amount": "1000000",
  "asset": "USDC",
  "network": "base",
  "protocol": "compound"
}
Legend generates the exact on-chain transaction, computes the EIP-712 typed data, and returns a plan containing:
  • plan_id — A temporary identifier (valid for 2 minutes)
  • chart — The transaction details
  • chart.eip712_data.digest — The hash that needs to be signed
No on-chain state changes at this point. The plan is purely a preview.

2. Sign

The account’s signer (an EOA private key or Turnkey-managed key) signs the EIP-712 digest. This proves the key holder authorizes the transaction. The signing happens entirely on your side — Legend never sees the private key.

3. Execute

Your server sends the signature back to Legend:
POST /accounts/acc_xxx/plan/execute
{
  "plan_id": "pln_xxx",
  "signature": "0x..."
}
Legend verifies the signature, then submits the on-chain transaction. You get back status: "executing" and can monitor progress via the activities or events endpoints.

Why this pattern?

Non-custodial security. Legend constructs transactions but can never submit them without signer approval. Even if an attacker compromised a query key, they couldn’t move funds — they’d also need the signer key. Transparency. The plan step lets you (or your user) inspect exactly what will happen before committing. The EIP-712 typed data is a standardized, human-readable description of the transaction. Flexibility. The signer can be:
  • An EOA private key your server holds (for fully automated flows)
  • A Turnkey-managed key (for programmatic access with hardware-level security)
  • Presented to an end-user for manual approval (like a wallet confirmation)

EIP-712 signing

EIP-712 is an Ethereum standard for typed structured data signing. It produces signatures that are:
  • Bound to a specific contract — Can’t be replayed on a different contract
  • Bound to a specific chain — Can’t be replayed on a different network
  • Human-readable — Wallets can display what you’re signing
When you create a plan, the chart.eip712_data contains:
FieldDescription
digestThe final hash to sign (this is what you pass to your signer)
domain_separatorIdentifies the contract and chain
hash_structHash of the structured transaction data

Plan expiration

Plans expire after 2 minutes. This prevents stale transactions from being executed when market conditions or balances have changed. If a plan expires, simply create a new one.

Diagram

┌──────────────┐          ┌──────────────┐          ┌──────────────┐
│  Your Server │          │    Legend     │          │  Blockchain  │
└──────┬───────┘          └──────┬───────┘          └──────┬───────┘
       │                         │                         │
       │  POST /plan/earn        │                         │
       │────────────────────────>│                         │
       │                         │  read wallet state      │
       │                         │────────────────────────>│
       │                         │<────────────────────────│
       │                         │                         │
       │  { plan_id, digest }    │  generate chart         │
       │<────────────────────────│                         │
       │                         │                         │
       │  sign(digest)           │                         │
       │  (local, never leaves   │                         │
       │   your infrastructure)  │                         │
       │                         │                         │
       │  POST /plan/execute     │                         │
       │  { plan_id, signature } │                         │
       │────────────────────────>│                         │
       │                         │  verify signature       │
       │                         │  submit transaction     │
       │                         │────────────────────────>│
       │  { status: executing }  │                         │
       │<────────────────────────│                         │
       │                         │                         │
       │  GET /activities        │                         │
       │────────────────────────>│  wait for confirmation  │
       │                         │<────────────────────────│
       │  { status: confirmed }  │                         │
       │<────────────────────────│                         │