> ## Documentation Index
> Fetch the complete documentation index at: https://jephalabs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Template Cookbook

> Design DAG templates for your transfer routes

# 03 - Template Cookbook

> A collection of template examples by lane.
> For detailed rules, see `docs/reference/compiler/plan-compilation.md`.

***

## Register from the Dashboard (Recommended)

If you select a preset on the dashboard's **Templates** page (`/routes`), you do not need to write JSON by hand:

1. Click the lane card you want in the **Presets** grid at the bottom of the Templates page
2. In **Customize**, change only the core fields such as asset, exchange, and network
3. Check the result in Graph Preview and JSON Preview
4. Register it with the **Register Template** button

Currently available presets: **CEX Transfer**, **CCTP Bridge**, **Gateway Transfer**, **Multi-hop (CEX->CEX->Chain->Gateway)**

> For a custom template that is not covered by a preset, use the JSON examples below and register it directly through the API.

***

## Register Directly Through the API

### Template Structure at a Glance

```
{
  "template_key": "identifier",
  "name": "human-readable name",
  "version": 1,
  "nodes": [node array],
  "edges": [edge array],
  "approval_policy": {"mode": "manual_first"},
  "completion_policy": {"completion_assurance": "..."},
  "risk_controls": {}
}
```

**Required node fields:** `node_key`, `node_kind`, `action_type`, `executor_selector`, `has_side_effect`
**Required edge fields:** `from`, `to`, `edge_type` (usually `"on_success"`)

***

## 1. CEX 3-Node (Upbit -> Bybit XRP)

The simplest shape: withdrawal -> withdrawal observe -> deposit observe.

```json theme={null}
{
    "template_key": "cex.upbit_to_bybit.xrp",
    "name": "Upbit to Bybit XRP",
    "version": 1,
    "nodes": [
        {
            "node_key": "withdraw",
            "node_kind": "action",
            "action_type": "cex_withdrawal",
            "executor_selector": {"action_type": "cex_withdrawal"},
            "config": {
                "asset": "XRP",
                "source_exchange": "upbit",
                "destination_exchange": "bybit",
                "network": "XRP"
            },
            "has_side_effect": true,
            "timeout_policy": {"submit_seconds": 120},
            "retry_policy": {},
            "risk_patch": {}
        },
        {
            "node_key": "withdraw_observe",
            "node_kind": "observe",
            "action_type": "cex_withdrawal_status",
            "executor_selector": {"action_type": "cex_withdrawal_status"},
            "config": {"asset": "XRP", "source_exchange": "upbit"},
            "has_side_effect": false,
            "timeout_policy": {"observe_seconds": 1800},
            "retry_policy": {},
            "risk_patch": {}
        },
        {
            "node_key": "deposit_observe",
            "node_kind": "observe",
            "action_type": "cex_deposit_status",
            "executor_selector": {"action_type": "cex_deposit_status"},
            "config": {"asset": "XRP", "destination_exchange": "bybit", "match_mode": "txid"},
            "has_side_effect": false,
            "timeout_policy": {"observe_seconds": 3600},
            "retry_policy": {},
            "risk_patch": {}
        }
    ],
    "edges": [
        {"from": "withdraw", "to": "withdraw_observe", "edge_type": "on_success"},
        {"from": "withdraw_observe", "to": "deposit_observe", "edge_type": "on_success"}
    ],
    "approval_policy": {"mode": "manual_first"},
    "completion_policy": {"completion_assurance": "destination_credited"},
    "risk_controls": {"asset_allowlist": ["XRP"], "max_amount": "1000"}
}
```

**Key points:**

* `completion_assurance: "destination_credited"` -> the terminal node is `cex_deposit_status` (the `destination_provider_observe` family)
* `match_mode` is required on the terminal node
* `risk_controls` is free-form and only `max_amount` binds (fail-closed at dispatch). The `asset_allowlist` in every template below is descriptive — hash-pinned, but not enforced. See the [v3 endpoints reference](/reference/api/v3-endpoints)

***

## 2. CCTP 5-Node (Base Sepolia -> Arbitrum Sepolia)

Cross-chain USDC transfer via CCTP. Requires a signer.

```json theme={null}
{
    "template_key": "cctp.base-to-arb-sepolia.usdc",
    "name": "CCTP USDC Base Sepolia -> Arbitrum Sepolia",
    "version": 1,
    "nodes": [
        {
            "node_key": "burn",
            "node_kind": "action",
            "action_type": "cctp_burn",
            "executor_selector": {"action_type": "cctp_burn", "chain_family": "evm"},
            "signer_selector": {"chain_family": "evm"},
            "config": {
                "source_chain_id": 84532,
                "source_domain_id": 6,
                "destination_chain_id": 421614,
                "destination_domain_id": 3,
                "token_messenger_address": "0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5",
                "burn_token_address": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
                "destination_caller": "0xYOUR_SIGNER_ADDRESS",
                "token_decimals": 6,
                "max_fee_raw": 500000,
                "min_finality_threshold": 1000,
                "gas_buffer_multiplier": "1.3"
            },
            "has_side_effect": true,
            "timeout_policy": {},
            "retry_policy": {},
            "risk_patch": {}
        },
        {
            "node_key": "attestation",
            "node_kind": "observe",
            "action_type": "protocol_observe",
            "executor_selector": {"action_type": "protocol_observe"},
            "config": {"probe_key": "cctp_iris", "source_domain_id": 6},
            "has_side_effect": false,
            "timeout_policy": {"observe_seconds": 600},
            "retry_policy": {},
            "risk_patch": {}
        },
        {
            "node_key": "mint",
            "node_kind": "action",
            "action_type": "cctp_mint",
            "executor_selector": {"action_type": "cctp_mint", "chain_family": "evm"},
            "signer_selector": {"chain_family": "evm"},
            "config": {
                "destination_chain_id": 421614,
                "destination_domain_id": 3,
                "message_transmitter_address": "0xaCF1ceeF35caAc005e559DCB4211986aA0e55344",
                "destination_caller": "0xYOUR_SIGNER_ADDRESS",
                "gas_buffer_multiplier": "1.3"
            },
            "has_side_effect": true,
            "timeout_policy": {},
            "retry_policy": {},
            "risk_patch": {}
        },
        {
            "node_key": "mint_receive_observe",
            "node_kind": "observe",
            "action_type": "destination_chain_receive_observe",
            "executor_selector": {"action_type": "destination_chain_receive_observe", "chain_family": "evm"},
            "config": {
                "probe_key": "evm",
                "chain_id": 421614,
                "match_mode": "txid",
                "token_contract": "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d",
                "token_decimals": 6,
                "amount_match": "at_least",
                "amount_floor_deduction_raw": 500
            },
            "has_side_effect": false,
            "timeout_policy": {"observe_seconds": 900},
            "retry_policy": {},
            "risk_patch": {}
        },
        {
            "node_key": "mint_finality",
            "node_kind": "observe",
            "action_type": "destination_chain_finality_observe",
            "executor_selector": {"action_type": "destination_chain_finality_observe", "chain_family": "evm"},
            "config": {"probe_key": "evm", "chain_id": 421614, "confirmations_required": 2},
            "has_side_effect": false,
            "timeout_policy": {"observe_seconds": 300},
            "retry_policy": {},
            "risk_patch": {}
        }
    ],
    "edges": [
        {"from": "burn", "to": "attestation", "edge_type": "on_success"},
        {"from": "attestation", "to": "mint", "edge_type": "on_success"},
        {"from": "mint", "to": "mint_receive_observe", "edge_type": "on_success"},
        {"from": "mint_receive_observe", "to": "mint_finality", "edge_type": "on_success"}
    ],
    "approval_policy": {"mode": "manual_first"},
    "completion_policy": {"completion_assurance": "destination_finalized"},
    "risk_controls": {}
}
```

**Key points:**

* Put the signer address in `destination_caller` (you can confirm it through signer health)
* `signer_selector: {"chain_family": "evm"}` -> matches the active signer registry row
* `confirmations_required` is required on the terminal node
* Environment variables: `MG_EVM_RPC_ENDPOINTS_JSON`, `MG_LOCAL_SIGNER_*`

***

## 3. Gateway 3-Node (Base Sepolia -> Arbitrum Sepolia)

Immediate cross-chain transfer through the Gateway unified balance.

```json theme={null}
{
    "template_key": "gateway.base-to-arb-sepolia.usdc",
    "name": "Gateway USDC Base Sepolia -> Arbitrum Sepolia",
    "version": 1,
    "nodes": [
        {
            "node_key": "intent",
            "node_kind": "action",
            "action_type": "gateway_intent",
            "executor_selector": {"action_type": "gateway_intent", "chain_family": "evm"},
            "signer_selector": {"chain_family": "evm"},
            "config": {
                "source_domain": 6,
                "destination_domain": 3,
                "source_chain_id": 84532,
                "destination_chain_id": 421614,
                "gateway_wallet": "0x0077777d7eba4688bdef3e311b846f25870a19b9",
                "gateway_minter": "0x0022222abe238cc2c7bb1f21003f0a260052475b",
                "source_token": "0x036cbd53842c5426634e7929541ec2318f3dcf7e",
                "destination_token": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
                "max_fee_raw": 2000000
            },
            "has_side_effect": true,
            "timeout_policy": {},
            "retry_policy": {},
            "risk_patch": {}
        },
        {
            "node_key": "mint",
            "node_kind": "action",
            "action_type": "gateway_mint",
            "executor_selector": {"action_type": "gateway_mint", "chain_family": "evm"},
            "signer_selector": {"chain_family": "evm"},
            "config": {
                "destination_chain_id": 421614,
                "gateway_minter": "0x0022222abe238cc2c7bb1f21003f0a260052475b"
            },
            "has_side_effect": true,
            "timeout_policy": {},
            "retry_policy": {},
            "risk_patch": {}
        },
        {
            "node_key": "mint_finality",
            "node_kind": "observe",
            "action_type": "destination_chain_finality_observe",
            "executor_selector": {"action_type": "destination_chain_finality_observe", "chain_family": "evm"},
            "config": {"probe_key": "evm", "chain_id": 421614, "confirmations_required": 2},
            "has_side_effect": false,
            "timeout_policy": {"observe_seconds": 300},
            "retry_policy": {},
            "risk_patch": {}
        }
    ],
    "edges": [
        {"from": "intent", "to": "mint", "edge_type": "on_success"},
        {"from": "mint", "to": "mint_finality", "edge_type": "on_success"}
    ],
    "approval_policy": {"mode": "manual_first"},
    "completion_policy": {"completion_assurance": "destination_finalized"},
    "risk_controls": {}
}
```

**Key points:**

* Gateway is faster than CCTP (no attestation polling; burn intent signature -> immediate mint)
* Contract addresses differ by network - check Circle's official docs
* Environment variables: `MG_GATEWAY_API_BASE_URL`, `MG_EVM_RPC_ENDPOINTS_JSON`, `MG_LOCAL_SIGNER_*`

***

## 4. Multi-Hop 9-Node (Upbit -> Binance -> Base -> Gateway)

A composite CEX-to-CEX-to-chain-to-Gateway route. `$ref:` dynamically references output from earlier nodes.

### Why multi-hop

* Upbit does not support direct withdrawals to Base (only routes such as Tron/ERC-20 are available)
* Route through Binance for the Base withdrawal, then deposit into Gateway

### Graph Structure

```
Phase 1: CEX -> CEX (Upbit -> Binance, Tron USDC)
  [1] upbit_withdraw         -> action   FIRE
  [2] upbit_withdraw_observe -> observe
  [3] binance_deposit_observe -> observe

Phase 2: CEX -> On-chain (Binance -> Base)
  [4] binance_withdraw         -> action   FIRE  <- $ref:binance_deposit_observe.credited_amount
  [5] binance_withdraw_observe -> observe
  [6] base_receive_observe     -> observe

Phase 3: Gateway deposit (Base -> unified balance)
  [7] gateway_approve          -> action   FIRE  <- $ref:base_receive_observe.credited_amount
  [8] gateway_deposit          -> action   FIRE  <- $ref:base_receive_observe.credited_amount
  [9] deposit_finality         -> observe  DONE
```

### `$ref:` Usage Points

| Node               | Config Key | Reference                                      | Description                    |
| ------------------ | ---------- | ---------------------------------------------- | ------------------------------ |
| `binance_withdraw` | `amount`   | `$ref:binance_deposit_observe.credited_amount` | Net credited amount after fees |
| `gateway_approve`  | `amount`   | `$ref:base_receive_observe.credited_amount`    | On-chain received amount       |
| `gateway_deposit`  | `amount`   | `$ref:base_receive_observe.credited_amount`    | Same amount                    |

### Template JSON

```json theme={null}
{
    "template_key": "multihop.upbit_binance_base_gateway",
    "name": "Upbit -> Binance -> Base -> Gateway Deposit",
    "version": 1,
    "nodes": [
        {
            "node_key": "upbit_withdraw",
            "node_kind": "action",
            "action_type": "cex_withdrawal",
            "executor_selector": {"action_type": "cex_withdrawal"},
            "config": {
                "source_exchange": "upbit",
                "destination_exchange": "binance",
                "network": "tron",
                "asset": "USDC"
            },
            "has_side_effect": true,
            "timeout_policy": {"submit_seconds": 120},
            "retry_policy": {},
            "risk_patch": {}
        },
        {
            "node_key": "upbit_withdraw_observe",
            "node_kind": "observe",
            "action_type": "cex_withdrawal_status",
            "executor_selector": {"action_type": "cex_withdrawal_status"},
            "config": {"source_exchange": "upbit", "asset": "USDC"},
            "has_side_effect": false,
            "timeout_policy": {"observe_seconds": 1800},
            "retry_policy": {},
            "risk_patch": {}
        },
        {
            "node_key": "binance_deposit_observe",
            "node_kind": "observe",
            "action_type": "cex_deposit_status",
            "executor_selector": {"action_type": "cex_deposit_status"},
            "config": {
                "destination_exchange": "binance",
                "asset": "USDC",
                "network": "tron",
                "match_mode": "txid"
            },
            "has_side_effect": false,
            "timeout_policy": {"observe_seconds": 3600},
            "retry_policy": {},
            "risk_patch": {}
        },
        {
            "node_key": "binance_withdraw",
            "node_kind": "action",
            "action_type": "cex_withdrawal",
            "executor_selector": {"action_type": "cex_withdrawal"},
            "config": {
                "source_exchange": "binance",
                "network": "base",
                "asset": "USDC",
                "amount": "$ref:binance_deposit_observe.credited_amount"
            },
            "has_side_effect": true,
            "timeout_policy": {"submit_seconds": 120},
            "retry_policy": {},
            "risk_patch": {}
        },
        {
            "node_key": "binance_withdraw_observe",
            "node_kind": "observe",
            "action_type": "cex_withdrawal_status",
            "executor_selector": {"action_type": "cex_withdrawal_status"},
            "config": {"source_exchange": "binance", "asset": "USDC"},
            "has_side_effect": false,
            "timeout_policy": {"observe_seconds": 1800},
            "retry_policy": {},
            "risk_patch": {}
        },
        {
            "node_key": "base_receive_observe",
            "node_kind": "observe",
            "action_type": "destination_chain_receive_observe",
            "executor_selector": {"action_type": "destination_chain_receive_observe", "chain_family": "evm"},
            "config": {"chain_id": 8453, "match_mode": "event"},
            "has_side_effect": false,
            "timeout_policy": {"observe_seconds": 600},
            "retry_policy": {},
            "risk_patch": {}
        },
        {
            "node_key": "gateway_approve",
            "node_kind": "action",
            "action_type": "gateway_approve",
            "executor_selector": {"action_type": "gateway_approve", "chain_family": "evm"},
            "signer_selector": {"chain_family": "evm"},
            "config": {
                "source_chain_id": 8453,
                "source_token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
                "gateway_wallet": "0x0077777d7eba4688bdef3e311b846f25870a19b9",
                "amount": "$ref:base_receive_observe.credited_amount"
            },
            "has_side_effect": true,
            "timeout_policy": {"submit_seconds": 60},
            "retry_policy": {},
            "risk_patch": {}
        },
        {
            "node_key": "gateway_deposit",
            "node_kind": "action",
            "action_type": "gateway_deposit",
            "executor_selector": {"action_type": "gateway_deposit", "chain_family": "evm"},
            "signer_selector": {"chain_family": "evm"},
            "config": {
                "source_chain_id": 8453,
                "source_token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
                "gateway_wallet": "0x0077777d7eba4688bdef3e311b846f25870a19b9",
                "amount": "$ref:base_receive_observe.credited_amount"
            },
            "has_side_effect": true,
            "timeout_policy": {"submit_seconds": 60},
            "retry_policy": {},
            "risk_patch": {}
        },
        {
            "node_key": "deposit_finality",
            "node_kind": "observe",
            "action_type": "destination_chain_finality_observe",
            "executor_selector": {"action_type": "destination_chain_finality_observe", "chain_family": "evm"},
            "config": {"probe_key": "evm", "chain_id": 8453, "confirmations_required": 12},
            "has_side_effect": false,
            "timeout_policy": {"observe_seconds": 300},
            "retry_policy": {},
            "risk_patch": {}
        }
    ],
    "edges": [
        {"from": "upbit_withdraw", "to": "upbit_withdraw_observe", "edge_type": "on_success"},
        {"from": "upbit_withdraw_observe", "to": "binance_deposit_observe", "edge_type": "on_success"},
        {"from": "binance_deposit_observe", "to": "binance_withdraw", "edge_type": "on_success"},
        {"from": "binance_withdraw", "to": "binance_withdraw_observe", "edge_type": "on_success"},
        {"from": "binance_withdraw_observe", "to": "base_receive_observe", "edge_type": "on_success"},
        {"from": "base_receive_observe", "to": "gateway_approve", "edge_type": "on_success"},
        {"from": "gateway_approve", "to": "gateway_deposit", "edge_type": "on_success"},
        {"from": "gateway_deposit", "to": "deposit_finality", "edge_type": "on_success"}
    ],
    "approval_policy": {"mode": "manual_first"},
    "completion_policy": {"completion_assurance": "destination_finalized"},
    "risk_controls": {"asset_allowlist": ["USDC"], "max_amount": "50000"}
}
```

### Runtime Data Flow

How `$ref:` resolves during movement execution:

```
[3] binance_deposit_observe completes
    -> provider_refs: {"credited_amount": "9995.00", "txid": "T9f8a...", ...}

[4] At binance_withdraw dispatch time
    -> looks up "binance_deposit_observe.credited_amount" = "9995.00" in namespaced_refs
    -> config.amount: "$ref:..." -> replaced with "9995.00"
    -> the executor sees only the resolved config

[6] base_receive_observe completes
    -> provider_refs: {"credited_amount": "9995.00", "tx_hash": "0xabc...", ...}

[7] At gateway_approve dispatch time
    -> "base_receive_observe.credited_amount" = "9995.00"
    -> config.amount replaced

[8] gateway_deposit - same behavior
```

> If the target key for `$ref:` does not exist, the node immediately becomes FAILED (`error_code: CONFIG_REF_UNRESOLVED`).

***

## `$ref:` Rules Summary

| Item            | Rule                                                                |
| --------------- | ------------------------------------------------------------------- |
| Format          | `$ref:node_key.field_name` (exactly one `.`)                        |
| Scope           | Top-level string values in `config` only (nested dict/list ignored) |
| Target          | A key in `provider_refs` from an ancestor node in the DAG           |
| Failure policy  | Fail-closed - becomes `FAILED` if it cannot be resolved             |
| `node_key`      | `.` is forbidden                                                    |
| Executor impact | None - `provider_context` keeps the existing flat merge behavior    |

***

## Common Pitfalls

| Mistake                                               | Symptom                   | Correct value                                                                                          |
| ----------------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------ |
| `edge_type: "then"`                                   | 422 rejection             | `"on_success"`                                                                                         |
| `chain_id` (Gateway/CCTP config)                      | `MISSING_SOURCE_CHAIN_ID` | `source_chain_id` / `destination_chain_id`                                                             |
| Missing `has_side_effect`                             | hash mismatch             | Declare it on every node                                                                               |
| Missing `match_mode` on the terminal node             | 422 rejection             | Required on `destination_provider_observe` / `destination_chain_receive_observe` family terminal nodes |
| Missing `confirmations_required` on the terminal node | 422 rejection             | Required on `destination_chain_finality_observe` family terminal nodes                                 |
| `$ref:` target is a sibling                           | 422 rejection             | Only ancestors (earlier nodes) can be referenced                                                       |
| `node_key` contains `.`                               | 422 rejection             | `.` forbidden (conflicts with the `$ref` separator)                                                    |

> Full compilation behavior: `docs/reference/compiler/plan-compilation.md`

***

**Next:** [04-going-live.md](/quickstart/04-going-live) - prepare for production deployment
