Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 31 additions & 11 deletions bridge-sdk/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@
> GENERATED from SDK docstrings by `codegen/gen_context.py` — do not
> edit by hand; edit the docstrings and regenerate.

Typed Python client that moves assets between Aleo, Ethereum and Solana
over the reviewed Hyperlane warp routes and Circle xReserve deployments
Typed Python client that moves assets between Aleo, Ethereum, Arc, Base, Arbitrum and Solana
over reviewed Hyperlane, Circle xReserve and native-USDC CCTP deployments
(`pip install aleo-bridge-sdk`, imports as `aleo_bridge`).
MCP alternative: `python -m aleo_bridge.mcp` exposes the same lifecycle as
tools; `aleo_bridge.agent.bridge_tools()` gives Claude-shape tool schemas.
Registry version `2026-08-31.solana-deposits.1`.
Registry version `2026-09-28.cctp-arc.1`.
CCTP supports Arc ↔ Ethereum/Base/Arbitrum. Pass `cctp={"speed": "fast",
"forwarding": True, "max_fee": "0.1"}` to quote; execute the returned plan to retain its ceiling.
Use `Bridge(..., evm={"arc": Ethereum(...), "base": Ethereum(...)})` for route-selected connections.
Arc native gas uses 18 decimals; ERC-20 USDC uses 6. They spend the same balance.
CCTP completion requires the exact message and mint receipt; a consumed nonce alone stays pending.
For stalled forwarding, `complete(progress, manual_mint=True)` explicitly authorizes a destination mint.
Recovery keeps the approved fee ceiling; never rerun execute after a broadcast.

Runnable examples ship in the package: start with
`python -m aleo_bridge.examples.quote_transfer --help`.
Expand Down Expand Up @@ -36,7 +43,7 @@ assert progress.next == "done", progress.error

Everything from the environment (spec §3.3); writes nothing to disk. Overrides: ethereum, solana, registry, checkpoints.

### `from_profile(home: 'Any' = None, *, network: 'str | None' = None, endpoint: 'str | None' = None, ethereum: 'Any' = None, solana: 'Any' = None) -> "'Bridge'"`
### `from_profile(home: 'Any' = None, *, network: 'str | None' = None, endpoint: 'str | None' = None, ethereum: 'Any' = None, solana: 'Any' = None, evm: 'Any' = None) -> "'Bridge'"`

The client for the local profile (spec §3.4), created on first use. *network*/*endpoint* apply only when
creating. Side-chain connections come from the arguments or the same env variables as ``from_env``.
Expand All @@ -54,7 +61,7 @@ back are dict entries instead — ``{"next": "failed", "error", "error_type"}``
when no store is bound. Finish any entry with ``recover`` → ``wait`` / ``resume`` / ``complete``,
never by starting a new transfer.

### `quote(self, *, source_chain: 'str | None' = None, source_asset: 'str | None' = None, destination_chain: 'str | None' = None, destination_asset: 'str | None' = None, bridge_protocol: 'str | None' = None, route=None, amount=None, amount_atomic=None, recipient: 'str', sender: 'str | None' = None, mint_mode: 'str' = 'public', secret_nonce: 'str' = '0scalar')`
### `quote(self, *, source_chain: 'str | None' = None, source_asset: 'str | None' = None, destination_chain: 'str | None' = None, destination_asset: 'str | None' = None, bridge_protocol: 'str | None' = None, route=None, amount=None, amount_atomic=None, recipient: 'str', sender: 'str | None' = None, mint_mode: 'str' = 'public', secret_nonce: 'str' = '0scalar', cctp=None)`

Price a transfer and get the plan that ``execute`` takes. Nothing is signed.

Expand Down Expand Up @@ -102,13 +109,15 @@ each changed ``Progress``. A transient error (flaky RPC/HTTP transport)
is retried up to ``max_consecutive_errors`` times, calling ``on_error``
on each tolerated retry; a non-transient error propagates immediately.

### `recover(self, checkpoint)`
### `recover(self, checkpoint, *, approval_replacement=None)`

Rebuild ``Progress`` from a saved checkpoint (``Checkpoint``, dict or JSON) — reads only.

Re-resolves the route from the live registry and reads chain state once;
``progress.next`` then says what to do: ``wait``, ``resume``, ``complete``,
``done`` or ``failed``.
CCTP can adopt an explicitly selected, confirmed ``approval_replacement``;
its original transaction must be absent and no burn may be submitted.

### `resume(self, progress, *, on_checkpoint=None, secret_nonce: 'str | None' = None, poll_seconds: 'float' = 1.0, timeout_seconds: 'float' = 120.0, proving: 'str' = 'delegate')`

Expand All @@ -118,13 +127,16 @@ Rebroadcasts the identical proved Aleo transaction (a duplicate response is
success) or, on EVM, re-scans history and only then authorizes the single
missing deposit/dispatch. Never repeats a confirmed step.

### `complete(self, progress, *, secret_nonce: 'str', on_checkpoint=None, proving: 'str' = 'delegate')`
### `complete(self, progress, *, secret_nonce: 'str | None' = None, on_checkpoint=None, proving: 'str' = 'delegate', manual_mint: 'bool' = False)`

Submit the private USDCx mint (``progress.next == "complete"``).
Claim a private USDCx or native-USDC CCTP destination mint.

Requires the same ``secret_nonce`` given to ``execute``; the SDK never
stored it. Submits exactly one ``private_mint`` and returns
``DESTINATION_CONFIRMING`` progress to ``wait`` on.
``DESTINATION_CONFIRMING`` progress to ``wait`` on. CCTP needs no secret
nonce, but requires a destination signer and gas. Set ``manual_mint=True``
to explicitly authorize fallback for stalled forwarding; an already
submitted destination transaction is observed rather than repeated.

### `pending(self) -> 'list'`

Expand Down Expand Up @@ -255,9 +267,9 @@ lifecycle layer (plan 4) re-quotes at the last responsible moment by calling thi

Live relayer payment for the route (the exact u64 the hook asserts); quote right before proving.

### `xreserve.burn(self, recipient: 'str', *, amount: 'Any' = None, amount_atomic: 'int | None' = None, mode: 'str' = 'private', record: 'str | None' = None, merkle_proof: 'str | None' = None) -> 'AleoCall[BurnReceipt]'`
### `xreserve.burn(self, recipient: 'str', *, amount: 'Any' = None, amount_atomic: 'int | None' = None, mode: 'str' = 'private', record: 'str | None' = None, merkle_proof: 'str | None' = None, route: 'Route | None' = None) -> 'AleoCall[BurnReceipt]'`

Burn USDCx for USDC on Ethereum. ``private`` (default) spends a Token record via the wrapper and needs a
Burn USDCx for USDC on the selected EVM route (Ethereum by default). ``private`` spends a Token record via the wrapper and needs a
freeze-list exclusion proof — both are resolved from chain state when not supplied. Minimum: more than
the 2 USDCx withdrawal fee. The Aleo burn-attestation service forwards accepted burns to Circle.

Expand Down Expand Up @@ -374,6 +386,14 @@ re-stating the plan's own amount is harmless). Without a plan, ``recipient`` is
| `hyperlane:hyperevm/aleo->aleo/aleo` | hyperlane | mainnet | metadata-required |
| `hyperlane:ethereum/usad->aleo/usad` | hyperlane | mainnet | metadata-required |
| `hyperlane:aleo/usad->ethereum/usad` | hyperlane | mainnet | metadata-required |
| `xreserve:arc/usdc->aleo/usdcx` | xreserve | mainnet | active |
| `xreserve:aleo/usdcx->arc/usdc` | xreserve | mainnet | active |
| `cctp:ethereum/usdc->arc/usdc` | cctp | mainnet | active |
| `cctp:arc/usdc->ethereum/usdc` | cctp | mainnet | active |
| `cctp:base/usdc->arc/usdc` | cctp | mainnet | active |
| `cctp:arc/usdc->base/usdc` | cctp | mainnet | active |
| `cctp:arbitrum/usdc->arc/usdc` | cctp | mainnet | active |
| `cctp:arc/usdc->arbitrum/usdc` | cctp | mainnet | active |

`metadata-required` routes are listed but refused by `quote`/`execute`
until their deployments are reviewed upstream.
Expand Down
78 changes: 78 additions & 0 deletions bridge-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -777,3 +777,81 @@ Applications that need to keep transaction contents out of the proving service
can configure local proving with `proving="local"` on `execute`, `resume`, or
`complete`. The application's machine then generates the proof and may need
to download proving parameters.
# Arc and native USDC

Bridge USDC between Arc and Aleo with xReserve, or between Arc and Ethereum,
Base, or Arbitrum with CCTP V2. All eight directions are mainnet routes.
Existing Ethereum and Solana calls keep their defaults.

```python
from aleo_bridge import Bridge, Ethereum

bridge = Bridge(aleo, evm={
"ethereum": Ethereum(ethereum_rpc, private_key=evm_key),
"arc": Ethereum(arc_rpc), # Reads destination delivery; forwarding needs no destination signer.
})
quote = bridge.quote(
source_chain="ethereum", source_asset="usdc", destination_chain="arc",
amount="5", recipient=recipient,
cctp={"speed": "fast", "forwarding": True, "max_fee": "0.1"},
)
# After reviewing quote.amount_out and quote.fees:
progress = bridge.execute(quote.plan, on_checkpoint=save_checkpoint)
```

`cctp` defaults to standard finality and forwarding. `max_fee` is a decimal USDC
ceiling. When omitted, the quote adds 10% headroom to current protocol and forwarding
fees, rounded up to the next USDC atomic unit and capped below the transfer amount.
Execute the returned plan to preserve that visible ceiling. Explicit caps are never
increased. Fees are refreshed before approval and burn. If fees exceed the cap after
approval, execution returns `SOURCE_SUBMISSION_PENDING` (`next="resume"`) with a
`sourceError` explanation. Resume that progress when fees fall within its saved cap;
no burn is submitted while fees exceed it. A restart can recover the approval checkpoint
and resume the same transfer. Do not execute a new transfer to retry a submitted one.
For forwarded delivery, `amount_out` deducts the full approved budget, while the
verified destination mint may deduct less. CCTP transfers and addresses are public.

CCTP recovery scans at most ten 1,000-block log batches per status call. Keep polling
the same client to reconcile older approvals or backfill older destination receipts.
Scan progress is held in memory and anchored to a checked block hash; restarting the
client safely restarts the scan. Incomplete source scans never authorize another burn,
and a used destination nonce alone never substitutes for mint receipt verification.

Use `bridge.evm("arc")` to read the Arc connection. Environment-based setup recognizes
`ARC_RPC_URL`, `BASE_RPC_URL`, and `ARBITRUM_RPC_URL`, sharing `EVM_PRIVATE_KEY`
when present. These mainnet-only RPC variables are ignored for testnet clients.
Explicit `evm` connections take precedence and must match the client's network. Read-only connections
do not need a key. Arc gas balances use 18 decimals; the USDC token interface uses
6 decimals. These are two views of the same funds, so keep a gas reserve rather
than counting them as separate assets.

Recover every interrupted transfer from its checkpoint. CCTP checkpoints retain
the source sender, options, fee ceiling, and submitted transaction identifiers;
they exclude message and attestation bodies. Recovery reads chain evidence and
does not sign. An approval with no visible transaction or receipt stays pending.
If you selected a confirmed replacement after verifying that the original is no
longer visible, pass `approval_replacement={"original_transaction_id": old_hash,
"replacement_transaction_id": new_hash}` to `recover`. The original hash remains
in the saved history. Replacement is refused after a burn has been submitted.

Set `forwarding=False` to submit the destination mint yourself with
`bridge.complete(progress)`. For stalled forwarding, explicitly authorize fallback
with `bridge.complete(progress, manual_mint=True)`. This requires a destination
signer and native gas. Completion refreshes the attestation and nonce first and
does not repeat a known pending destination transaction. A consumed nonce alone
does not prove delivery: the matching message event and exact USDC mint are required.

For Arc → Aleo, select `source_chain="arc"`, `source_asset="usdc"`, and
`destination_chain="aleo"`; public, record, and private mint modes work as on
Ethereum. Private mint completion still requires the original secret nonce.
Aleo → Arc burns use domain 26, enforce a two-USDCx minimum, and fetch a live
withdrawal fee estimate again before proving. Provider errors are surfaced instead
of presenting a static estimate as current. Public outbound xReserve delivery is
observed through the recipient balance; it has no CCTP-style transaction proof.

Checkpoints from registry `2026-08-31.solana-deposits.1` remain recoverable only
for the 22 original routes whose pinned deployment fingerprints still match.
Unknown versions and old version labels attached to new routes are rejected.

Runnable Arc examples and explicit live-test gates are described in
[examples/README.md](examples/README.md). No live test runs by default.
11 changes: 9 additions & 2 deletions bridge-sdk/codegen/gen_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,19 @@ def render() -> str:
"> GENERATED from SDK docstrings by `codegen/gen_context.py` — do not",
"> edit by hand; edit the docstrings and regenerate.",
"",
"Typed Python client that moves assets between Aleo, Ethereum and Solana",
"over the reviewed Hyperlane warp routes and Circle xReserve deployments",
"Typed Python client that moves assets between Aleo, Ethereum, Arc, Base, Arbitrum and Solana",
"over reviewed Hyperlane, Circle xReserve and native-USDC CCTP deployments",
"(`pip install aleo-bridge-sdk`, imports as `aleo_bridge`).",
"MCP alternative: `python -m aleo_bridge.mcp` exposes the same lifecycle as",
"tools; `aleo_bridge.agent.bridge_tools()` gives Claude-shape tool schemas.",
f"Registry version `{DEFAULT_REGISTRY.version}`.",
"CCTP supports Arc ↔ Ethereum/Base/Arbitrum. Pass `cctp={\"speed\": \"fast\",",
"\"forwarding\": True, \"max_fee\": \"0.1\"}` to quote; execute the returned plan to retain its ceiling.",
"Use `Bridge(..., evm={\"arc\": Ethereum(...), \"base\": Ethereum(...)})` for route-selected connections.",
"Arc native gas uses 18 decimals; ERC-20 USDC uses 6. They spend the same balance.",
"CCTP completion requires the exact message and mint receipt; a consumed nonce alone stays pending.",
"For stalled forwarding, `complete(progress, manual_mint=True)` explicitly authorizes a destination mint.",
"Recovery keeps the approved fee ceiling; never rerun execute after a broadcast.",
"",
"Runnable examples ship in the package: start with",
"`python -m aleo_bridge.examples.quote_transfer --help`.",
Expand Down
42 changes: 42 additions & 0 deletions bridge-sdk/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,45 @@ Each script presents the transfer as a tutorial, with inline comments explaining
what happens to the funds and when another action is needed. Client setup, quotes,
submission, progress, and error handling remain explicit in each script. Only
command-line argument definitions are shared in [_arguments.py](_arguments.py).
# Arc journeys

The following commands preview by default. Supply the RPC variables for each EVM
chain involved (`ARC_RPC_URL`, `ETHEREUM_RPC_URL`, `BASE_RPC_URL`,
`ARBITRUM_RPC_URL`). `--execute` loads `EVM_PRIVATE_KEY`; an Aleo-origin public
burn also needs `ALEO_PRIVATE_KEY`. Keep Aleo credits and native EVM gas funded.

```sh
python -m aleo_bridge.examples.bridge_arc_to_aleo --sender 0xYOUR_ADDRESS --recipient aleo1YOUR_ADDRESS --amount 5
python -m aleo_bridge.examples.bridge_ethereum_arc_aleo --sender 0xYOUR_ADDRESS --recipient aleo1YOUR_ADDRESS --amount 5
python -m aleo_bridge.examples.l2_arc_aleo_roundtrip --l2 base --step 1 --sender 0xYOUR_ADDRESS --recipient aleo1YOUR_ADDRESS --amount 5
```

The four-step example runs one explicit leg per invocation: L2 → Arc, Arc → Aleo,
Aleo → Arc, and Arc → L2. Use `--l2 arbitrum` for Arbitrum. Each later step reads
the previous completed leg's net receipt budget; it never spends a pre-existing
balance as return principal. Use your own Aleo recipient when you intend to return
the public USDCx. The xReserve withdrawal's balance observation is labeled as such.

All examples save each leg under `--journal` (default `~/.aleo-bridge/arc-journey`).
Rerun with the same arguments to recover, never a new journal to retry uncertain
submissions. Keep the state files. Run one process per journey. If submission
started but no checkpoint was saved, the example refuses another transfer and
requires inspection of source history. A provider handoff, pending attestation,
or timeout does not advance to the next leg.

`--arc-gas-reserve 0.10` leaves that much received USDC on Arc before spending
the remainder. This is a configurable budget, not a guarantee of future gas cost.
`--manual-mint` explicitly authorizes CCTP fallback if forwarding stalls; it needs
a funded destination signer.

Read-only integration checks require `BRIDGE_LIVE_READS=1` and the relevant RPC
variables. Funded checks additionally require the existing `BRIDGE_LIVE_FUNDS=1`,
an external `BRIDGE_LIVE_STATE_DIR`, and `BRIDGE_LIVE_MAINNET_ACK` acknowledgment.
Choose `evm-cctp`, `evm-xreserve`, or `aleo-xreserve` in
`BRIDGE_LIVE_MAINNET_CASES` for individual routes; choose `cctp-roundtrip` or
`arc-journey` for received-only roundtrips or four-leg public journeys. The
`aleo-arc` case burns a two-USDCx private record with a 0.10-USDC fee budget and
requires `BRIDGE_LIVE_ARC_RECIPIENT`; its Arc connection has no signer and it
verifies the exact destination transfer receipt and balance delta.
Without the explicit `BRIDGE_LIVE_MAINNET_EXECUTE` acknowledgment they only quote.
The tests never set any gate variables themselves.
Loading
Loading