Smart Contracts
TransviaEscrow — the settlement brain on Arc Testnet. Deposits, atomic settlement, EIP-712 claims, refunds.
Transvia settles on one contract: TransviaEscrow — open source, MIT,
dependency-free Solidity (^0.8.24, no OpenZeppelin). It locks USDC
deposits per transfer, settles the FX + payout + fee legs atomically, and
lets recipients authorize payouts with EIP-712 signatures.
Network facts
| Fact | Value |
|---|---|
| Network | Arc Testnet (Circle's EVM L1) |
| Chain ID | 5042002 |
| Gas token | USDC |
| USDC (ERC-20, 6 dp) | 0x3600000000000000000000000000000000000000 |
| Explorer | testnet.arcscan.app |
| Gas floor | 20 Gwei maxFeePerGas (Arc mempool minimum) |
The deployed escrow contract address is recorded in the repository
(contracts/README.md) together with the full deployment record — that file
is the authoritative source and addresses are never hardcoded in docs.
The contract surface
| Function | Caller | What it does |
|---|---|---|
deposit(intentId, amountUsdc, recipient, timeoutAt, rate) | sender | Pulls USDC and locks it against the intent |
settle(intentId, oracleRate) | agent only | FX check → net payout → fee split, one atomic tx |
claim(intentId, signature, deadline) | anyone (recipient-signed) | EIP-712 close-out; relays allowed |
refund(intentId) | sender or agent | Full refund once the timeout has passed |
agentFallback(intentId, reason) | agent only | Compliance/timeout refund, even pre-timeout |
pause() / unpause() | owner | Circuit breaker over all money movement |
One transfer, as a UML sequence
Sender Transvia API TransviaEscrow (Arc) Settle engine Recipient
│ │ │ │ │
│ POST /api/send (quoteId, recipient) │ │ │
│──────────────▶│ │ │ │
│ │deposit(intentId, amountUsdc, │ │
│ │recipient, timeoutAt, rate) │ │
│ │────────────────────▶│ │ │
│ │ Deposited │ │ │
│ │◀────────────────────│ │ │
│ │ │ settle(intentId, │ │
│ │ │ oracleRate) │ │
│ │◀────────────────────│────────────────────│ │
│ │ note: one atomic tx — FX leg (±2% band) │
│ │ payout leg (net USDC) · fees 35/5/10 bps │
│ │◀────────────────────│───────────────────────────────────▶│
│ │ FxLegSettled · PayoutReleased · FeeSplit │
│ │ │ claim(intentId, EIP-712 sig) │
│ │ │◀───────────────────────────────────│
│ │ Claimed │ (anyone may relay — │
│ │◀────────────────────│ the signature authorizes) │Read it left to right: the deposit is a user action, settle() is the
automation engine acting as the contract's only agent, and the final
claim() belongs to the recipient — gasless, relayer-agnostic, authorized
by an EIP-712 signature over the Claim(intentId, recipient, deadline)
struct, not by whoever broadcasts it.
Settlement, honestly
The destination-currency conversion (USDC → EUR, MXN, …) is computed at
quote time offchain and locked to the transfer. What the escrow settles
onchain is net USDC: the recipient is paid from escrow, and the
settlement transaction emits the conversion data — FxLegSettled carries
the rate and destination amount for the offchain FX layer. The onchain
truth is simple and verifiable: who was paid, how much net USDC, and at
what rate.
The three settlement legs
settle() executes in one transaction and reverts as a whole if any leg
fails:
- FX leg — the oracle rate must be within ±2% of the rate locked at
deposit, else the whole settlement reverts (
RateOutOfBounds). - Payout leg — net USDC (gross minus fees) is transferred to the recipient, fixed at deposit.
- Fee split — 0.35% FX/liquidity, 0.10% protocol, and 0.05% automation go to three immutable treasuries in the same tx. Sub-unit dust stays with the recipient.
Events are the indexing surface
Index TransviaEscrow's own events — not raw USDC Transfer logs. On Arc,
native-USDC transfers surface through the system emitter rather than the
token contract:
Deposited · FxLegSettled · PayoutReleased · FeeSplit · Claimed ·
Refunded · AgentFallback
EIP-712 claims
The recipient signs a Claim(string intentId, address recipient, uint256 deadline) authorization over the contract's domain (TransviaEscrow,
version 1). Anyone may relay the call — authorization comes from the
signature, not the caller — so the recipient pays no gas. If the automation
agent never settled, claim pays out the full locked amount with zero
fees: the agentless fast path. No service provided, no fee taken.
Testing and deployment
Thirteen Foundry tests cover deposit locking, the Arc zero-address guard, the 3-leg settle, the ±2% rate band, real EIP-712 claim signatures, refund gating, and the pause breaker:
cd contracts && forge build && forge test -vvvDeploys run against Arc Testnet; if a transaction is rejected as
underpriced, append --with-gas-price 20gwei (the 20 Gwei floor). The
complete record — contract address, deploy transaction, deployer — lives in
contracts/README.md. See Architecture for how the
app talks to it.