Skip to content

feat(request): fund Tempo to Base through Relay contract deposits and Stargate (stack 7/8) - #284

Draft
brendanjryan wants to merge 5 commits into
brendan/machine-fetchfrom
brendan/machine-fetch-7-funding-routes
Draft

brendanjryan wants to merge 5 commits into
brendan/machine-fetchfrom
brendan/machine-fetch-7-funding-routes

Conversation

@brendanjryan

Copy link
Copy Markdown
Contributor

Stacked on #279. Makes cross-chain funding work on Tempo mainnet. Before this, every bridged payment to Base failed with E_PAYMENT "No trusted cross-chain route can fund the payment", before any money moved.

Why it failed

On mainnet, Relay answers the CLI's strict deposit-address quote with HTTP 400: "strict is not supported for this origin chain". Without strict mode, it refuses exact output. The local e2e didn't catch this, because its stand-in relay mimicked the deposit-address API.

What changed

Relay contract-deposit mode. This is the MF-2 route in src/payment/upstream/relay-route.ts.

  • The route accepts exactly two calls, and rejects any quote that adds, reorders, or changes them:
    • approve(source token → depository, amount);
    • depositErc20(wallet, token, amount, protocol.v2.orderId) on Relay's pinned v2 depository, 0x4cD00E387622C35bDDB9b4c962C136462338BC31. That address matches Relay's chains API.
  • Both calls are re-encoded from the checked values and sent in one Tempo transaction.
  • The deposit amount is bounded by the quote plus slippage.

Stargate route. This is MF-21, in src/payment/upstream/stargate-route.ts. It bridges USDC.e to native USDC.

  • It sizes amountLD so the receipt covers the exact output, and sets minAmountLD to that output.
  • It pays LayerZero's fee in LZD (approve, wrap, approve) and calls sendToken, all in one transaction.
  • It tracks delivery through LayerZero Scan.
  • Its fee counts toward amountIn, because machine-fetch ranks routes by amountIn alone (MF-22).

Source selection. Each payment funds from USDC.e (via Relay or Stargate, whichever is cheapest) or from OUSD (via Relay), depending on which one the wallet holds enough of. A resumed attempt always reconciles from the token it was submitted in. Recovery rebuilds the route by name.

UX fixes:

  • --funding off with --payment-network base now pays from USDC already held on Base. Before, it silently paid the Tempo offer.
  • A missing source balance is reported as E_INSUFFICIENT_FUNDS before any quote.
  • When no route can fund a payment, the error now says why each route declined.

Mainnet, quote only

scripts/probe-funding-routes.mjs runs the real routes' quote() with execution disabled:

Source → Base USDC $0.01 $1 $5
USDC.e via Relay 0.0313 1.0218 5.0238
USDC.e via Stargate 0.2684 1.2585 5.2589
OUSD via Relay 0.0363 1.0367 5.0787

Real Relay quotes pass the new validation, and Relay from USDC.e is the cheapest at about $0.022 flat. Stargate has a ~$0.258 flat LayerZero fee, so it only wins on large bridges.

Tests

  • Unit tests: hostile-quote suites for both routes, with 23 contract-deposit cases and 20 Stargate cases. The full suite passes, 3,081 tests.
  • e2e, 23 of 23 passing. It runs against a local depository and a local Stargate (test/e2e/contracts/LocalBridges.sol) plus stand-ins for Relay's API and LayerZero Scan. It covers:
    • Relay contract deposits and 10 hostile quotes;
    • Stargate selected when it quotes cheapest;
    • falling back to OUSD;
    • refusing before any quote when no source covers the payment;
    • kill-and-recover for both routes.

Upstream items found here: MF-21, MF-22, MF-23 (Relay errors drop the provider's reason), and MF-24 (multi-source crossChainFund).

… Stargate

Relay rejects strict deposit addresses from Tempo ("strict is not supported for
this origin chain"), so cross-chain funding could never run on mainnet. The
Relay route now asks for a contract deposit and accepts only an approve of the
source token to Relay's pinned v2 depository (0x4cD0...BC31, matching Relay's
chains API) and depositErc20(wallet, token, amount, protocol.v2.orderId),
re-encoded from the checked values and sent atomically.

A Stargate route bridges USDC.e to native USDC: it sizes amountLD so the
receipt covers the exact output, pays LayerZero's fee in LZD, and sends the
fee wrap, approvals and sendToken in one Tempo transaction, tracking delivery
through LayerZero Scan. Its fee counts toward amountIn, since machine-fetch
ranks routes by amountIn alone (MF-22).

Each payment funds from USDC.e (Relay and Stargate) or OUSD (Relay), whichever
the wallet holds enough of; a resumed attempt reconciles from the token it was
submitted in. Also: --funding off with --payment-network <evm chain> pays that
chain from held USDC instead of the Tempo offer, missing balances are reported
before quoting, and route refusal reasons are shown.

Tested by unit hostile-quote suites for both routes, a Docker e2e matrix with
a local depository and Stargate (23/23), and a quote-only mainnet probe
(scripts/probe-funding-routes.mjs).
A bridge refused before any operation was claimed (over --max-funding-spend,
no route, no source balance, or another attempt still in flight) left its
attempt record behind, listed as not_started by 'funding list'. Attempts with
a claimed operation are still always kept for recovery.
… cheapest route

Both routes now check the source balance against the quoted debit before
anything is signed. Before, a wallet holding enough to pass the rough pre-quote
check but less than the quote would sign a deposit that reverted on chain,
which the durable funder must treat as possibly having moved funds. Such a
refusal is reported as E_INSUFFICIENT_FUNDS with the amount needed and held.

When routes are refused by --max-funding-spend, the error now reports the
cheapest route's requirement; it reported whichever route refused first,
which could be Stargate's 0.288 instead of Relay's 0.051.
The Tempo DEX funder took the first input the wallet held enough of, and only
checked the access key's limit for it when the store recorded one. A key whose
limits list leaves a token out cannot spend it on chain, so a wallet holding
only pathUSD under a key limited to OUSD and USDC.e signed a swap the chain
refused, reported against the payment token instead of pathUSD.

The funder now skips inputs the key may not spend: the stored limit when the
store has one, else the chain's (the remaining limit of a published limited
key, and zero for a token an unpublished key's signed limits leave out). When
no spendable input is held it refuses before signing and names the token, and
the swap's inputs are pinned to the chosen one so Mppx cannot pick another.
Fixes Phase 2 behavior (#277); found by the live mainnet run.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant