Web UI for the AR.IO ario-ant-escrow program — trustless multi-protocol
escrow for ANTs, ARIO tokens, and time-locked vaults (Arweave RSA-PSS-4096
/ Ethereum ECDSA secp256k1).
Forked from the AR.IO Solana registration app. Same wallet plumbing (Phantom, Solflare, Wander, MetaMask, WalletConnect), Vite build, and visual identity. The migration registration app sunsets after cutover; this fork repurposes that codebase as the long-lived multi-protocol ANT-escrow UI.
/#/ Landing — explainer + links to flows
/#/deposit Deposit ANT into escrow
/#/deposit-tokens Deposit liquid ARIO tokens into escrow
/#/deposit-vault Deposit a time-locked vault into escrow
/#/claim?ant=… Recipient flow (verify + sign + claim ANT/tokens/vault)
/#/manage?ant=… Depositor management (update_recipient + cancel)
/#/lookup?ant=… Read-only escrow inspector (no wallet needed)
The shell, routing, wallet adapters, and signing components are all in
place. The on-chain submit handlers are stubbed ("devnet wiring
pending") until the program ships to devnet (Phase 8 of
docs/ANT_ESCROW_IMPLEMENTATION_PLAN.md).
yarn install
yarn dev # vite, port 5173
yarn build # tsc + vite build → dist/yarn dev reads the RPC URL from VITE_SOLANA_RPC_URL (defaults to
mainnet-beta). Point at devnet for testing:
VITE_SOLANA_RPC_URL=https://api.devnet.solana.com yarn devArweave-recipient claim flows POST the user's RSA-PSS signature to the
off-chain AR.IO attestor service, which re-signs the canonical claim
message with Ed25519. The on-chain program then verifies the cheap
Ed25519 signature via Solana's native sigverify program. Without an
attestor URL, Arweave claims fail at submit time with a clear error.
Ethereum claims do not require the attestor (they verify directly
on-chain via secp256k1_recover).
VITE_ATTESTOR_URL=http://localhost:3030 yarn devThe page calls the attestor's /health once before issuing an
attestation and refuses to proceed if health.network doesn't match
the network the page was loaded with. See
migration/attestor/README.md for the service contract and
docs/DECISIONS.md ADR-017 for the architecture rationale.
The Deposit Tokens page transfers liquid ARIO tokens from the depositor's
wallet into an escrow-owned token account. The Deposit Vault page escrows a
time-locked vault position from ario-core.
Vault claim behavior: When a claimant claims an active vault (lock not
yet expired), the app builds a transaction containing both the escrow
claim_vault_* instruction and a sibling ario_core::vaulted_transfer
instruction. The escrow program verifies the sibling instruction via
sysvar::instructions introspection (same pattern as Solana's Ed25519
precompile). Tokens transfer to the payer's ATA first, then the
vaulted_transfer creates a time-locked vault for the claimant. Expired
vaults are claimed as liquid SPL transfers (no vaulted_transfer needed).
These already do exactly what the escrow app needs — zero changes:
| Component | What it gives us |
|---|---|
SolanaWalletConnect |
Phantom / Solflare / Wander connect; depositor + claimant tx submission |
ArweaveWalletConnect |
Wander / ArConnect; Arweave-recipient claim signing |
EthereumWalletConnect |
MetaMask / WalletConnect; Ethereum-recipient claim signing |
SourceAddressSigner |
Multi-protocol signing dispatcher (abstracts per-wallet APIs) |
SourceWalletConnect |
Wallet selection UI |
WalletIcons |
Branding |
useTurboAttestation,useAttestationStatus,useAOAssetLookup— Turbo bundler integration is registration-specific.RegistrationProgress,ExistingRegistrationCheck,CountdownTimer— registration-window UX.ao-asset-lookup.ts— AO process lookups (the escrow app reads only Solana state).RegisterPage,StatusPage— replaced byDeposit / Deposit Tokens / Deposit Vault / Claim / Manage / Lookup.
Each page has a stubbed submit button labelled "(devnet wiring pending)". To wire it up against a deployed devnet program:
- Bump the
@ar.io/sdkdep to the version that exportsANTEscrowfrom@ar.io/sdk/solana(it ships in the same PR as the program). - Per page, instantiate the client:
import { ANTEscrow } from '@ar.io/sdk/solana'; const escrow = ANTEscrow.init({ rpc, rpcSubscriptions, signer });
- Replace the stub
<button disabled>with the real call:await escrow.deposit({ antMint, recipient: { protocol, publicKey }, });
- For the claim page, fetch escrow state first to drive the signing flow:
const state = await escrow.get(antMint); const message = canonicalMessage({ network: 'solana-mainnet', antMint, claimant, nonce: state.nonce, }); const signature = state.recipientProtocol === 'arweave' ? await arweaveWallet.signMessage(message) : await ethereumWallet.signMessage(message); await escrow.claimArweave({ antMint, claimant, signature, saltLen: 32 });
- React 18 + TypeScript + Vite (same as
solana-registration-app) @ar.io/sdk— escrow client + canonical-message helper@solana/kit— tx pipeline@solana/wallet-adapter-*— Solana wallet connectionarweave-wallet-kit(viaSourceWalletConnect) — Arweave walletethers— Ethereum signing & address derivation
License: AGPL-3.0-or-later (matches the rest of the repo).