Skip to content

05 — Cross-chain funding router

The agent's wallet (Mode A on Privy, Mode B on Virtuals' Base/Solana/ARC) is rarely on the same chain a venue settles on. The funding router is the layer that makes "deposit USDC anywhere → the agent funds the venue" a first-class operation. Reference: venue_funding_router.py.

1. The chain problem, stated precisely

Agent wallet native chains:   base, solana, arc        (Virtuals)   or wherever Privy put it
Venue settlement chains:      polymarket → polygon
                              hyperliquid → arbitrum
                              tradexyz   → arbitrum
                              pumpfun    → solana
                              virtuals/degenclaw/bankr/base → base

Source chains a dev may want to accept (CAIP-2):

ChainCAIP-2
Abstracteip155:2741
Arbitrumeip155:42161
Baseeip155:8453
BNB Chaineip155:56
Ethereumeip155:1
HyperEVMeip155:999
Hypercoreeip155:1337
Monadeip155:143
Optimismeip155:10
Polygoneip155:137
Solanasolana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp
Trontron:20191129
Tempo(emerging)
ARC(Virtuals-native)

The reference implementation's currently-wired source set (SUPPORTED_SOURCE_CHAINS): base, ethereum, bnb, solana, arbitrum, avalanche, polygon, tempo. The CAIP-2 table above is the target the design extends to — and it is exactly why we keep the chain map data-driven rather than Virtuals' 3-chain limit.

2. The three routing rails

OAW composes three funding rails, picking per venue:

Rail 1 — Chain-agnostic deposit address (Privy useDepositAddress)

Privy generates a unique deposit address; the user sends crypto from any supported source chain / any wallet / any exchange, and Privy auto-bridges/swaps to the destination wallet. This is the cleanest "fund from anywhere" rail and the reason a Privy-native dev gets cross-chain funding nearly for free. OAW surfaces it as a funding option for the agent wallet and for any venue whose target is the agent wallet's own chain.

Rail 2 — Venue-native deposit bridge (e.g. Polymarket any-chain bridge)

Some venues ship their own any-chain deposit bridge. Polymarket accepts USDC from many chains into a generated deposit address and bridges to Polygon. OAW's Polymarket route uses this directly: it generates deposit addresses, initiates no transfer itself, and tells the user "send USDC on any chain here."

Rail 3 — Cross-chain stablecoin transfer (swap/bridge engine)

When the agent already holds USDC in its wallet and the venue needs it on another chain, OAW routes through a stablecoin transfer engine (reference: stablecoin_route_service) that previews and (gated) executes a bridge/swap from the agent balance to the venue's native chain.

Rail selection (reference logic)

target_chain = VENUE_FUNDING_CHAIN[venue]          # polymarket→polygon, hyperliquid→arbitrum, …
if venue == "polymarket":        rail = venue_native_deposit_bridge   # Rail 2
elif target_chain in SUPPORTED_SOURCE_CHAINS: rail = cross_chain_stablecoin_transfer  # Rail 3
else:                            status = funding_route_unsupported
# Rail 1 (Privy deposit address) is offered for funding the agent wallet itself.

3. Funding is two slices: resolve (read) and execute (gated)

The reference implementation is deliberately split so the read path can never move money.

Slice 1 — resolve_funding_plan(...)read-only, moves nothing

Given (user, venue, amount_usd) returns:

jsonc
{
  "venue": "polymarket",
  "funding_target": { "chain": "polygon", "address": "0x…" },   // from the venue's execution binding
  "available_funding_usd": "37.50",                              // agent balance (authority ledger)
  "supported_source_chains": ["base","ethereum","bnb","solana","arbitrum","avalanche","polygon","tempo"],
  "route": {
    "kind": "venue_native_deposit_bridge | cross_chain_stablecoin_transfer",
    "target_chain": "polygon",
    "accepts_any_chain_usdc": true,
    "detail": "Send USDC on any supported chain to the bridge deposit address; it auto-bridges to Polygon."
  },
  "status": "ready | funding_required | funding_target_unresolved | funding_route_unsupported | funding_venue_unsupported",
  "no_live_venue_action": true,
  "secrets_exposed": false
}

Statuses (verified): ready, funding_required, funding_target_unresolved, funding_route_unsupported, funding_venue_unsupported.

Slice 2 — execute_funding(...)gated, fail-closed money movement

Behavior by rail:

  • Polymarketcreate_deposit_addresses (any-chain deposit addresses). No OAW-initiated transfer; status deposit_addresses_ready. The user (or an upstream wallet) sends; the bridge delivers.
  • Cross-chain venuesstablecoin_route_service: policy_decision (kill-switch / max-transfer / daily-cap / destination-chain validation) → preview_route. An actual transfer happens only when all hold: execute=True and explicit approval and the fail-closed config flag VENUE_FUNDING_EXECUTE_ENABLED. Otherwise ready_but_live_locked / requires_approval, execution_performed: false.

Pre-bind authority is required first — funding never auto-creates a wallet (funding is not a bind; if there's no authority it returns agent_wallet_required).

4. Where the funding target comes from

The venue's funding target (chain + address) is not invented by the router; it is read from the venue's execution binding (the venue adapter's funding_target() / the execution-binding builder). This keeps a single source of truth: the address you fund is the address the venue will actually trade from.

funding_target = execution_binding(venue).funder_address  on  VENUE_FUNDING_CHAIN[venue]
available       = authority.available_funding_usd          (the agent balance ledger)

5. The agent-authority-aware source resolution

The router resolves the source wallet from the parent authority — Mode A (Privy address) or Mode B (Virtuals address) — via the agent_authority block (06). This is why funding "just works" regardless of mode: the source is whatever the active authority's wallet is, on whatever chain it's on, and the rails bridge to the target.

6. Surfaces

The router is callable three ways (reference):

  • Service: VenueFundingRouter.resolve_funding_plan(...) / execute_funding(...).
  • REST: POST /api/venues/{venue}/funding/route (resolve) and POST /api/venues/{venue}/funding/execute (gated).
  • Chat: "fund my Polymarket with $50" → reasoning classifier (06) → preview by default; "confirm / send it" → gated execute. Negations/questions ("should I fund Hyperliquid?") never trigger a transfer.

7. What L3 gives a dev

python
plan = await oaw.funding.resolve(user, "polymarket", amount_usd=50)
# plan.route.kind == "venue_native_deposit_bridge"; plan.status == "funding_required"
res  = await oaw.funding.execute(user, "polymarket", amount_usd=50, execute=False)
# → deposit addresses; no funds moved

Money is on the right chain. L4 (06) makes execution authority-aware and safe.

Released under the Apache-2.0 License.