Skip to content

feat(request)!: pay MPP and x402 from any Tempo stablecoin, on Tempo or Base, through machine-fetch - #290

Draft
brendanjryan wants to merge 41 commits into
mainfrom
brendan/machine-fetch-integration
Draft

brendanjryan wants to merge 41 commits into
mainfrom
brendan/machine-fetch-integration

Conversation

@brendanjryan

@brendanjryan brendanjryan commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Integrates machine-fetch into tempo request, so the wallet can pay MPP and x402 challenges in a token it doesn't hold, or on another chain (Base first), funded from its Tempo stablecoins. This replaces the nine-PR stack (#274–#279, #284, #285, #287) with one PR. The commits are kept in stack order, so it still reads layer by layer.

What it does

  • Plans every charge through machine-fetch before signing (src/payment/funding.ts). --max-spend becomes machine-fetch's payment limit over the USD tokens the CLI lists. Behavior change: a charge in an unlisted token is refused with E_LIMIT_EXCEEDED instead of being treated as six-decimal.
  • Tempo DEX funding. A Tempo charge in a USD token the wallet lacks (OUSD, for example) is bought on the Tempo DEX from USDC.e, pathUSD or OUSD, in the same transaction. Inputs the access key can't spend are skipped. It's controlled by --funding auto|tempo|off, TEMPO_FUNDING, config funding, and --max-slippage-bps. The CLI owns this funder (src/payment/tempo-funding.ts) and passes autoSwap: false to machine-fetch, so --funding off never swaps.
  • EVM and x402 offers from a remote payer. MPP evm charges and x402 exact offers in USDC on Base (Base Sepolia on testnet) are paid from a per-wallet secp256k1 payer key.
    • The key is created by the wallet's SDK keystore and kept in store.json as tempo-cli.remotePayers.
    • remote-payers.json is a write-once backup.
    • The key is never printed.
    • Offers outside the destination table, in another token, or for another EIP-3009 domain are refused before signing.
    • Offers are ranked deterministically, and --payment-network overrides the ranking.
    • whoami and keys list show the payer.
  • Cross-chain funding to Base. With --funding auto, it bridges exactly the shortfall to the payer, within --max-funding-spend. It picks the cheapest route the wallet can cover, through machine-fetch's own routes:
    • Relay (RelayProtocolRoute) from USDC.e or OUSD: a contract deposit into Relay's pinned v2 depository. machine-fetch recomputes and checks the Relay order before signing.
    • Stargate (StargateRoute) from USDC.e: the OFT sendToken call, with LayerZero's fee wrapped into LZD in the same transaction.
    • The CLI adds only its progress lines. Refusals map through machine-fetch's FundingLimitError and SourceBalanceError.
    • Each attempt is saved before any funds move, so a crashed run is reconciled rather than bridged again. tempo wallet funding list and tempo wallet funding recover <id> inspect and reconcile attempts.
  • Dependencies from npm: machine-fetch 0.4.1, which includes the fixes and routes upstreamed from this work (tempoxyz/machine-fetch#88–fix(request): strip credentials on cross-origin redirects #92, docs: remove redundant toon output flags #95–Pin GitHub Actions to full-length commit SHAs #101, chore: release 0.10.0 #105) and mppx 0.13.6, the first release with the auto-swap allowlist (feat(tempo): auto-swap allowlist (autoSwap.fallback) and OUSD in the default currencies wevm/mppx#1054) and reads without the account (fix(tempo): read auto-swap balances and quotes without the account wevm/mppx#1055). Nothing is vendored, and one mppx serves the whole install and bundle.
  • Dry-run bridge preview: with --funding auto, --dry-run on a Base offer the payer can't cover quotes the real Relay and Stargate routes. funding reports the shortfall, the route, the source token, amount_in, the fee, gas, whether --max-funding-spend covers it, and each route's quote or why it declined. Nothing is signed or saved.
  • MACH first: a machine-enabled charge that the wallet holds enough MACH for is left to MACH, which the SDK tries first. Stablecoin funding no longer swaps or refuses before that.

Fixes from the live mainnet runs

  • Bridge completion (machine-fetch 0.3.2): a bridge counts as completed only once the payer's Base balance shows the delivery, polled until the deadline (tempoxyz/machine-fetch#89). Recovery also accepts a destination transaction the provider names (Relay's fill, LayerZero's delivery) that transferred the output, so a delivery a killed run already spent still settles (chore(deps-dev): bump the development-dependencies group across 1 directory with 5 updates #90). Polling covers mainnet.base.org serving a fill's receipt before "latest" reads include it, which made two live bridges end in E_PAYMENT_OUTCOME_UNKNOWN; a stand-in RPC reproduces that in the e2e.
  • Stargate hash saved before broadcast: the signed send's hash is saved before it is broadcast (chore(deps): bump actions/attest from 4.2.1 to 4.2.2 in the github-actions group #91), so a run killed in between is still reconciled. Before, recovery returned action_required. A saved send that never landed counts as refunded once it expires.
  • Deadline: cross-chain funding gets machine-fetch's 300 s cross-chain default (formatTokenUnits and formatCreditBalance produce malformed output for negative BigInt values #88), instead of the 30 s payment default.
  • Access-key limits: a Tempo DEX swap counts twice against the key, once for the approve and once for the DEX's pull. A key whose limit covered the swap once used to pass the checks and then revert with an opaque E_PAYMENT. The DEX funder and wallet swap now require twice the input, read the on-chain remaining limit rather than the stored grant, and explain the refusal.
  • --funding off: a server offering only x402 used to fail with "missing WWW-Authenticate header". It now pays from USDC the payer already holds, and never bridges.
  • Receipts: EVM and x402 receipts record the settlement tx hash, taken from payment-response or an MPP evm receipt.
  • Errors: an unreachable provider is named, for example "Relay is unreachable (…)".

Verified

  • 3,119 unit tests, test types, lint, docs and changelog checks, on the tree merged with main.
  • pnpm test:e2e: 39 of 39 on local Docker chains (anvil Tempo plus an EVM chain, with stand-in Relay, Stargate and LayerZero Scan). It covers:
    • DEX funding, and every way a key's limit can refuse a swap;
    • x402 and MPP evm payments;
    • Relay and Stargate, including bridging only what a partially funded payer lacks;
    • 8 hostile Relay quotes, each rejected before signing;
    • kills before and after recording, and after paying;
    • refunds, and failed LayerZero deliveries;
    • a provider that reports success before delivering;
    • a server that refuses payment after the bridge;
    • no-route, cap, unlisted-token and expired-key refusals.
  • A quote-only mainnet probe (scripts/probe-funding-routes.mjs). Relay from USDC.e costs about $0.021–0.024; LayerZero's fee for Stargate rose from about $0.26 to $1.26 on 2026-10-20.
  • Live on mainnet:
    • MPP payments from a held token and from OUSD bought on the DEX;
    • Relay-bridged x402 payments from USDC.e and OUSD, with settlement hashes;
    • a Stargate delivery.

Upstream

Security review requested for the remote payer key handling (src/wallet/remote-payer.ts, src/wallet/store.ts) and the route call validation (src/payment/upstream/).

Dogfooding found

Switching to machine-fetch's own routes found that every bridge failed with an unpublished access key, the state of a fresh login until its first payment. Its reads went out from the key, which the chain rejects (KeyNotFound), and its gas quote left out the cost of publishing the key. Both are fixed upstream in tempoxyz/machine-fetch#105, released as 0.4.1.

scripts/vendor.sh mppx|machine-fetch <repository> <ref> builds a package at
one commit, from GitHub or a local clone, packs it as it would be published
(mppx's own publish preparation included), and keeps its dist/,
package.json, and LICENSE in vendor/<package> with a VENDORED file naming the
commit. vendor/ is checked in as built: formatting and linting skip it, and
its dist/ is not ignored.
tempoxyz/machine-fetch main @ d168943 (0.3.0, "chore: version packages"),
pinned by commit. It includes MF-17 and MF-18, merged as #66 and #67.
wevm/mppx brendan/wallet-cli-needs @ 8295134: the mppx@0.12.0 tag plus the
auto-swap allowlist (autoSwap.fallback), OUSD in the default auto-swap
currencies, and auto-swap reads without the account. Those commits are merged
upstream on mppx main (0.13.x) as 82810df (#1054) and 0acc2bc (#1055); no npm
release has them, and wallet-cli stays on 0.12 until it chooses to upgrade.
Depend on vendor/mppx and vendor/machine-fetch, pinned by commit, through
file: dependencies, which minimumReleaseAge does not apply to. A pnpm override
resolves every mppx, including machine-fetch's and accounts' peers, to the
vendored copy, so the install and the bundle hold exactly one;
test/vendor.test.ts checks both. vendor/ stays until wallet-cli chooses to
upgrade a package; re-vendoring is one scripts/vendor.sh run.
Runs the CLI bundles against anvil --network tempo (on testnet's chain ID, so
the CLI's testnet network works through TEMPO_RPC_URL) and a plain anvil
standing in for Base, adapted from machine-fetch's test/e2e. It deploys local
OUSD and USDC with DEX books and fee AMM pools, EIP-3009 USDC on the EVM node,
a wallet store with an unpublished access key, local MPP and x402 servers, and
a stand-in relay that serves Relay's quote and status endpoints, settles
deposits on Tempo, and releases USDC from inventory.

The first scenarios pay an MPP charge in a held token through the access key,
bridge through the stand-in relay, and settle an x402 authorization.
Charges go through planAndFund (src/payment/funding.ts): mppxRequirement,
planPayment, then machine-fetch's Mppx extension, and the credential is
created with the context it prepared. There are no funders yet, so payments
are unchanged.

--max-spend becomes maxPayment as an asset list: every USD token the CLI
lists for the network at the cap. A charge in any other token is now refused
with E_LIMIT_EXCEEDED instead of being compared as if it had six decimals.
enforceMaxSpend still refuses an over-cap charge early with the same cap.
machine-fetch error codes map to the CLI's codes and ctas.

src/shared/chains.ts holds the trusted token table; TEMPO_DEV_CHAINS adds
development tokens, which the e2e harness uses for its local chain.
A Tempo charge in a listed USD token the wallet does not hold now buys it on
the Tempo DEX in the same transaction, through machine-fetch's tempoDex and
Mppx auto-swap, from OUSD, then USDC.e, then pathUSD, with slippage from
--max-slippage-bps (default 50). The funder reads the balance first: a wallet
that holds the token gets an available receipt and no swap, and one whose
balance cannot be read pays as before.

Before signing it refuses a swap the access key's stored limit cannot cover
(E_LIMIT_EXCEEDED with the keys update cta) or no configured input can cover
(E_INSUFFICIENT_FUNDS with the fund cta), since Mppx appends its own inputs.
MACH settlement never swaps. --funding auto|tempo|off, TEMPO_FUNDING, and the
funding config setting choose the mode; auto behaves as tempo until
cross-chain funding lands.

The receipt and --write-meta name the swap's input and maximum, and --dry-run
reports the funder when the balance is short. The e2e harness pays an MPP
charge in USDC from OUSD on the local chain.
tempo request now considers MPP evm charges and x402 exact offers (unless
--funding off), on a chain in the destination table and in its USDC: Base for
mainnet, Base Sepolia for testnet, plus TEMPO_DEV_CHAINS destinations. An
offer elsewhere, in another token, or for another EIP-3009 domain is refused
before signing. Mppx's evm method signs exactly the selected offer.

The payer is the wallet's remote payer (D2): one secp256k1 key per wallet,
created on first use under a file lock and kept in remote-payers.json (0600)
beside the wallet store, which the CLI and the accounts SDK rewrite whole. The
key never appears in output; whoami reports its address and USDC. A
--private-key signer pays for itself.

Offers are ranked deterministically: a Tempo offer in a held token, a Tempo
offer a DEX swap funds, an EVM offer the payer already funds, then (with
--funding auto) one to bridge; --payment-network overrides. Balances are read
only when both kinds are offered. An unfunded payer is refused before signing
with its address. --dry-run quotes EVM offers without creating a payer.
With --funding auto (or TEMPO_EXPERIMENTAL_CROSS_CHAIN=1) an EVM offer the
remote payer cannot cover is funded by machine-fetch's crossChainFund: exact
output from OUSD on Tempo to USDC at the payer, through Relay deposit
addresses, with operations in machine-fetch's file store.

src/payment/upstream/relay-route.ts (MF-2) supplies relayProtocol's
guarantees, submit, and status: a quote must have one transaction step that
transfers OUSD on Tempo, from the wallet, to the step's deposit address, for
the quoted input up to slippage, and must deliver exactly the shortfall of
the destination USDC to the payer. submit revalidates the saved quote and
sends only that transfer through the access key. --max-funding-spend caps
the OUSD spend, gas included, and is refused before signing.

The attempt ID is saved under ~/.tempo/wallet/funding/attempts before
funding, keyed by the request (method, URL, body) and the payment terms
(src/payment/upstream/requirement-key.ts, MF-14), and passed with the payer
to extension().preparePayment (MF-17, MF-18). A rerun reuses it, so funding a
crash interrupted is reconciled, not repeated; a new attempt first settles
earlier ones to the same destination and payer. RECOVERY_REQUIRED maps to
E_PAYMENT_OUTCOME_UNKNOWN with a recover cta.

tempo wallet funding list|recover show and reconcile saved funding without
sending anything. The e2e matrix bridges an x402 payment through the
stand-in relay, rejects eight hostile quotes before signing, recovers after
SIGKILL mid-bridge without a second deposit, and settles a refund.
… 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.
…eystore

The remote payer's key was kept in its own remote-payers.json because the CLI
rewrote store.json whole, dropping anything it did not know. It is now created
by the wallet's secp256k1 keystore (Keystore.secp256k1().createKey()) and its
handle is stored in store.json as the tempo-cli.remotePayers item, written
through the accounts SDK's filesystem storage, alongside the access keys.

saveWalletState now keeps every other item in the store, so CLI rewrites and
logout keep the payer; the SDK already rewrites only the item it sets. Because
the store has several writers across processes and no shared lock, and losing
the key strands its funds, remote-payers.json stays as a write-once backup:
written at creation, never rewritten or removed, and used to restore any payer
the store loses. An existing file is migrated into the store on first use.
keys list now shows the active wallet's remote payer.
….3 API

Vendors machine-fetch main (cec3535, past d168943), whose API moved to
namespaced modules on flat subpaths (Payment, Funder, Policy, Errors,
MppxFetch, CrossChainFunder, CrossChain, Evm, RelayProtocolRoute, FileStore),
error classes instead of codes, and operationId instead of attemptId.

The CLI is migrated without changing what it pays or how it funds:
- fundingError maps by error class (Policy.LimitExceededError,
  Funder.NoRouteError, ...) and reports each error's shortMessage.
- Payment.plan / MppxFetch.extension take flat policy options; the extension
  runs with autoSwap: false, so its new built-in Tempo auto-swap never applies.
  The CLI's own Tempo DEX funder (now CLI-owned, since tempoDex was removed
  upstream) still swaps only from inputs the access key may spend.
- Cross-chain requests carry operationId; token accounts name their owner.
- The CLI's Relay and Stargate routes stay: upstream's new RelayProtocolRoute
  with viem clients is a follow-up to evaluate against Tempo contract deposits.

Verified: 3,099 unit tests, test types, lint, docs, e2e 24/24, and the
mainnet quote-only probe (Relay from USDC.e and OUSD, Stargate).
Picks up machine-fetch #83 (reject the removed nested policy option), #84
(enforce maxFundingSpend on every Tempo route; keep the built-in Tempo context
out of a CLI extension's compose), and #85/#86 (Viem-backed Relay route
hardening, unused by the CLI). No CLI changes needed: 3,099 unit tests, e2e
24/24, and the mainnet quote-only probe pass.
…ing minutes, check the swap's remaining key limit, record x402 settlement hashes

- A bridge's completed status waits for the destination balance to show the delivery; Relay and
  LayerZero report success before Base's RPC reflects it, which surfaced a delivered bridge as
  E_PAYMENT_OUTCOME_UNKNOWN.
- Cross-chain funding gets a 300 s deadline instead of machine-fetch's 30 s default; a Stargate
  send took 23 s to deliver live.
- wallet swap checks the access key's on-chain remaining limit, not the stored grant, and refuses
  with the keys update cta instead of falling back to wallet-app approval. The funder's spend
  check uses the same reading.
- EVM and x402 receipts record the settlement transaction from payment-response (or an MPP evm
  payment-receipt).
…-integration

# Conflicts:
#	README.md
#	SKILL.md
#	test/mcp-services.test.ts
…saction

A run killed after paying but before recording it leaves the operation submitted, and the
payer's balance back at zero, so 'funding recover' reported pending forever. Relay and Stargate
now report the destination transactions of a completed operation, and a completion counts once
the balance covers the payment or one of those transactions transferred the output to the payer.
A run killed between broadcasting a Stargate send and recording its hash left recovery to a
human (action_required). Each quote now saves the Tempo block it was made at, and recovery
without a hash looks for the wallet's one OFTSent of exactly that amount and endpoint since then.

e2e: the local Stargate mock emits OFTSent; new cases cover that recovery, a server that refuses
the payment after the bridge delivers (the next request pays from the delivery), and a run killed
after paying but before recording it (recover confirms the spent delivery by its fill).
…-only offers with --funding off

- A Tempo DEX swap's approve and the DEX's pull both count against the access key's limit, so a
  key whose limit covered the input once passed the checks and then reverted in simulation with
  an opaque E_PAYMENT. The DEX funder and wallet swap now require twice the input, and the error
  says why.
- --funding off on a server that offers only x402 (or MPP evm) failed with "missing
  WWW-Authenticate header"; it now pays from USDC the payer already holds, never bridging.
- A route whose provider cannot be reached now says so ("Relay is unreachable (…)").

e2e: partial payer balance bridges only the difference, no-route refusal, --funding off/tempo never
bridge, a bridged --dry-run moves nothing, nothing held covers the swap, --max-spend refusal,
wallet swap against the on-chain remaining limit, and the double-counted swap limit.
…owed the delivery

Two live bridges ended in E_PAYMENT_OUTCOME_UNKNOWN although Relay delivered. mainnet.base.org
serves a transaction's receipt before 'latest' reads include its block (Flashblocks
preconfirmations), so the delivery check saw the fill while machine-fetch's single balance read,
moments later, did not. The delivery check now records the block that showed the delivery, and
the funder's balance read waits for the RPC to reach that block and reads at it.

e2e: a stand-in RPC that serves receipts before state; the case fails without this change.
…-integration

# Conflicts:
#	src/request/dry-run.ts
…holds enough of it

The SDK pays a machine-enabled charge with MACH before the advertised token. Stablecoin funding
ran first, so a wallet holding MACH but not the token either swapped OUSD for nothing or was
refused for lack of stablecoins. Funding now steps aside while MACH covers the charge, and
--dry-run previews no swap then.
SKILL.md explains paying from other tokens and chains: the --funding modes, the remote payer,
approving a bridge's fee, the access-key limit a DEX swap needs, and reconciling
E_PAYMENT_OUTCOME_UNKNOWN with funding recover. The stack's changelog entries are consolidated to
describe the final behavior against main, and --max-funding-spend's help names its source token.
`tempo request --dry-run` on an x402 or MPP `evm` offer the remote payer could not cover reported
no `funding`, so the only way to see what a bridge would cost was a real request refused by
`--max-funding-spend 0`, which pays outright when the payer already holds the price.

With `--funding auto`, the dry run now quotes the routes the payment would use for the payer's
shortfall, from the first Tempo source token holding enough, and picks as machine-fetch picks: the
cheapest quote within `--max-funding-spend`, else the cheapest one so the cap it needs is visible.
`funding` reports the shortfall, route, source token, `amount_in`, fee, gas, total, the cap and
whether it covers the total, and every route's quote or why it declined. Nothing is signed, no
attempt is claimed, and no operation is saved; a wallet without a remote payer is quoted to its
Tempo address and still gets none.

e2e: the bridged dry run reports the stand-in Relay's quote and moves nothing.
…d of vendoring them

machine-fetch 0.3.1 is its main at bafeca5, the same build vendor/ held (dist is byte-identical).
mppx 0.13.6 is the first release with the auto-swap allowlist (#1054) and reads without the
account (#1055). Both are excluded from the 7-day release-age gate, like accounts@0.21.0. One
mppx stays in the install and the bundle (test/mppx-install.test.ts). vendor/, scripts/vendor.sh,
and the scanner and formatter exceptions for it are removed.
@socket-security

socket-security Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedmachine-fetch@​0.4.1771009993100
Updatedmppx@​0.12.0 ⏵ 0.13.699 +1100100 +198100

View full report

@socket-security

socket-security Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

All alerts resolved. Learn more about Socket for GitHub.

This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored.

Ignoring alerts on:

  • machine-fetch@0.4.1
  • mppx@0.13.6

View full report

0.3.2 carries the five cross-chain fixes upstreamed from this integration: a 300-second default
deadline, polling the destination balance until the deadline, confirming a spent delivery from
its destination transactions, saving a reference before broadcasting, and budgeting an access
key for both spends of an auto-swap. CrossChainStatus now has destinationTxs, so the CLI's own
DeliveredStatus field is dropped in favor of it.
machine-fetch 0.3.2 gives a cross-chain funder its own 300-second default deadline when policy
sets none (tempoxyz/machine-fetch#88), so the CLI no longer overrides `deadlineSeconds` for a
bridged payment. The dry-run preview quotes with the funder's own default.

A test runs a payment through machine-fetch with a policy that sets no deadline and checks the
bridge's quote gets 300 seconds.
machine-fetch 0.3.2 polls the destination balance until the deadline after a route completes
(tempoxyz/machine-fetch#89) and, in `recover`, confirms a delivery a payment already spent from
the route's `destinationTxs` (tempoxyz/machine-fetch#90). That covers what the CLI wrapped every
route in, so this drops:

- `confirmedDelivery`, which turned `completed` into `pending` until the delivery was visible.
- `DeliveryFloor` and `destinationBalanceAt`, which kept machine-fetch's single balance read off
  a lagging node by reading no earlier than the block that showed the delivery.
- The `target` plumbing, and the `DeliveredStatus` alias of `CrossChain.Status`.

The funders get the CLI's destination readers instead: `balance` reads the payer's USDC at
"latest", and `destinationDelivery`, now returning only the amount, is the `delivered` reader.
`funding recover` and the reconciliation before a new funding still ask the route first, so
refunded and pending operations read as before, then confirm a completion with the funder's
`recover`; one not yet visible stays pending.

Base's read lag (mainnet.base.org serves receipts before "latest" state) is still covered: in the
lagging-RPC e2e test, logging each read showed the first three balance reads after completion
returned 0 and the fourth the delivery, so machine-fetch's polling alone is enough. Unit tests run
each case the wrapper handled through machine-fetch with the CLI's readers.
machine-fetch 0.3.2 hands a route `saveReference` to save its reference before broadcasting
(tempoxyz/machine-fetch#91). The Stargate route now signs its send, saves the signed
transaction's hash, and only then broadcasts it, so a run killed in between still leaves the
hash for recovery. A save that fails stops the send, and a broadcast whose hash differs from the
one saved is rejected.

A saved send may never land, so the route signs it valid only until the quote expires (Tempo's
`validBefore`) and reports one with no receipt as refunded once Tempo is a minute past that.

New quotes no longer record the source block, so the `OFTSent` log search only runs for
operations saved before this, which recorded the hash after broadcasting. The e2e test for a
never-recorded hash now rewrites the saved operation to that earlier shape, and the kill
mid-bridge test checks that the hash saved is the send's own.
#290 ships as one PR, so the copies of machine-fetch behavior it replaced never released; the
cross-chain funding entry already describes the final behavior.
…utes

Depend on machine-fetch 0.4.1 and use its RelayProtocolRoute (contract deposits with the Relay
order recomputed and checked, the v2 depository pinned) and StargateRoute, signing through the
access-key Tempo client and confirming deliveries through a Base client. Both refuse a quote the
wallet cannot cover before signing. The CLI keeps only its progress lines, as thin wrappers around
each route's submit and status.

wallet-cli's own relay-route.ts, stargate-route.ts and funding-limit.ts go: refusals map through
machine-fetch's Funder.FundingLimitError and Funder.SourceBalanceError via Funder.cheapestRefusal.
The quote-only probe now runs machine-fetch's routes against mainnet.

Dogfooding this found that every bridge failed with an unpublished access key, a fresh login's
until its first payment; fixed upstream in tempoxyz/machine-fetch#105 (0.4.1).
…te routes

- The stand-in Relay serves a /chains registry and real orders whose ID is the order's EIP-712
  hash, as machine-fetch recomputes it; its expired mode expires the order itself, and its hostile
  recipient tampers with the order.
- The stand-in LayerZero Scan names the destination transaction of a delivery.
- The cap and preview cases match machine-fetch's gas estimate instead of a fixed 0.02 reserve.
- Two cases go: Relay reporting success before naming its fill (machine-fetch requires the fill;
  the lagging-RPC case covers the live failure) and finding a send saved without its hash (an
  interim format that never shipped; the hash is now saved before broadcast).
@brendanjryan

Copy link
Copy Markdown
Contributor Author

@SocketSecurity ignore npm/machine-fetch@0.4.1 npm/mppx@0.13.6

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