VYRE Docs Mainnet

Test USDC and the bridge

USDC is VYRE's currency. On mainnet it's real USDC and there are two ways to get it: from Arc, or from another chain through Arc. There's no faucet on mainnet. The sections below describe the testnet, which has a faucet and test USDC; the mainnet sections come first where they differ.

On mainnet

Bridge in from Arc (deposit)

Call depositEth() on VYRE's bridge Inbox 0xC0190ec9bba882aE3452c6187C8D5636D36db299 (on Arc, chain ID 5042, explorer https://explorer.arc.io) with the amount as the transaction's value. The same address is credited on VYRE, usually within seconds.

cast send 0xC0190ec9bba882aE3452c6187C8D5636D36db299 "depositEth()" \
  --value 5ether \
  --rpc-url https://rpc.mainnet.arc.io \
  --account arc-wallet

(5ether means 5 USDC here: the native currency has 18 decimals on both chains.) The SDK's depositFromArc does the same with arcMainnet. A deposit from a smart-contract wallet, including a wallet upgraded to a smart account (EIP-7702), is credited to its aliased address on VYRE: deposit from a plain wallet.

Withdraw to Arc

A withdrawal moves USDC from VYRE back to Arc through the chain's own bridge: start it on VYRE (withdrawEth(destination) on the ArbSys precompile), wait until VYRE's state covering it is confirmed on Arc, then claim it from the bridge's Outbox 0xA14a1dbBcFd6ACf538c606e3F3AC6DeA0b882fa9. On mainnet that wait is about a day at launch (172,800 Arc blocks). Register the network with @arbitrum/sdk like this (the rest is as on the testnet below):

sdk.registerCustomArbitrumNetwork({
  chainId: 3045, parentChainId: 5042, name: 'vyre', confirmPeriodBlocks: 172800,
  isCustom: true, isTestnet: false, isBold: true,
  ethBridge: {
    bridge: '0x28BeF76aa877500e00615B77E8e4bE54E89DBB71',
    inbox: '0xC0190ec9bba882aE3452c6187C8D5636D36db299',
    sequencerInbox: '0x6ED18AeE76f0B54490eEA2C7c6e2beD83b2401FD',
    outbox: '0xA14a1dbBcFd6ACf538c606e3F3AC6DeA0b882fa9',
    rollup: '0x9a1ae90D33AeA2c81292905370FE1E66b5357AA0',
  },
})

On the testnet

The faucet

vyrechain.com/faucet sends 2 USDC on VYRE Testnet to a wallet address, once every 24 hours per wallet and per network. That pays for thousands of transactions (a transfer costs a few millionths of a dollar: vyrechain.com/gas). No wallet connection or sign-in is needed: paste an address. Scripts can use the faucet API.

Test USDC is limited, so it's recycled: when you're done testing, send what's left back from the faucet page or to the faucet's address, 0x35dBe9A21C26cbC4fE5ef73e87F6Ca7d2aFa36bd, from any wallet, and it goes to the next person. Send back at least 2 USDC since your last drip and your next one comes after 6 hours instead of 24. The faucet page lists the top givers: the wallets that gave back more than they got.

When the day's drips are all given out, Circle's faucet gives 20 test USDC at a time on Arc: bring it in with the bridge below.

Bridge in from Arc (deposit)

For more test USDC, take it from Circle's faucet on Arc testnet and bridge it in.

Arc Testnet is chain ID 5042002, explorer testnet.arcscan.app. These free RPCs need no key: https://rpc.testnet.arc.io (Circle's), https://rpc.quicknode.testnet.arc.io, https://rpc.drpc.testnet.arc.io, https://rpc.blockdaemon.testnet.arc.io and https://arc-testnet-rpc.publicnode.com.

  1. At faucet.circle.com, pick Arc Testnet and USDC and paste your address (20 USDC per request).
  2. On Arc testnet, call depositEth() on VYRE's bridge Inbox 0x52f90cb0ace79963736b1DCEb07587F780F47d98 with the amount as the transaction's value. The same address is credited on VYRE, usually within 3 to 5 seconds (Arc confirms in under 2 s).

With Foundry:

cast send 0x52f90cb0ace79963736b1DCEb07587F780F47d98 "depositEth()" \
  --value 5ether \
  --rpc-url https://rpc.testnet.arc.io \
  --account arc-wallet

(5ether means 5 USDC here: the native currency has 18 decimals on both chains.)

With viem, or the SDK's depositFromArc:

import { createWalletClient, custom, parseEther, parseAbi } from 'viem'
import { arcTestnet } from '@vyrechain/sdk'

const wallet = createWalletClient({ chain: arcTestnet, transport: custom(window.ethereum) })
const [account] = await wallet.requestAddresses()
await wallet.writeContract({
  account,
  address: '0x52f90cb0ace79963736b1DCEb07587F780F47d98',
  abi: parseAbi(['function depositEth() payable returns (uint256)']),
  functionName: 'depositEth',
  value: parseEther('5'),
})

A deposit from a smart-contract wallet, including a wallet upgraded to a smart account (EIP-7702), is credited to its aliased address on VYRE (the address plus 0x1111000000000000000000000000000000001111). Deposit from a plain wallet (an EOA): the faucet page refuses any other, and so does the SDK's depositFromArc unless told it's intended.

From another chain

USDC on Ethereum Sepolia, Base Sepolia or Arbitrum Sepolia comes to VYRE through Arc. Circle's CCTP burns it there and mints it on Arc, and Circle delivers that mint itself (its Forwarding Service, which pays Arc's gas), so nothing is needed on Arc. Then bridge it in from Arc, as above.

Ethereum SepoliaBase SepoliaArbitrum Sepolia
Chain ID1115511184532421614
CCTP domain063
USDC (6 decimals)0x1c7D4B196Cb0C7B01d743Fbc6116a902379C72380x036CbD53842c5426634e7929541eC2318f3dCF7e0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d

Arc testnet is CCTP domain 26. Circle's TokenMessengerV2 is 0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA on all four chains. Test USDC for them comes from faucet.circle.com; their gas is Sepolia ETH.

  1. Fees. GET https://iris-api-sandbox.circle.com/v2/burn/USDC/fees/{sourceDomain}/26?forward=true. In the entry whose finalityThreshold is 1000 (fast), minimumFee is the fast-transfer fee in basis points of the amount and forwardFee.high is the delivery fee in USDC units (6 decimals). maxFee is the delivery fee plus the fast-transfer fee on the amount, rounded up: about 0.02 USDC. It comes out of the amount (Circle keeps all of maxFee, and amount - maxFee arrives on Arc), and it moves every few seconds with Arc's gas: read it right before the burn.
  2. Approve TokenMessengerV2 on the USDC contract for exactly the amount.
  3. Burn: depositForBurnWithHook(amount, 26, recipient, usdc, 0x0…0, maxFee, 1000, hookData), with the recipient left-padded to 32 bytes and hookData 0x636374702d666f7277617264 followed by 20 zero bytes (cctp-forward, version 0: the request for Circle's delivery). Leave destinationCaller zero: Circle only delivers messages anyone may receive.
  4. Wait: GET https://iris-api-sandbox.circle.com/v2/messages/{sourceDomain}?transactionHash={burn} until forwardState is CONFIRMED or COMPLETE; forwardTxHash is the mint on Arc. From Base or Arbitrum Sepolia that's seconds; from Ethereum Sepolia, about 20 seconds more.

Send to a plain wallet you control on Arc: USDC minted to a contract can only be moved by that contract, and a smart-contract wallet's address on one chain may not be yours on another. A maxFee that covers the delivery fee but not the fast-transfer fee still arrives, at standard speed; one below the delivery fee isn't delivered: the transfer waits, signed, until someone mints it on Arc with receiveMessage(message, attestation) on MessageTransmitterV2 (0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275), from the messages API's message and attestation.

With the SDK, which checks all of this before anything is sent:

import { createPublicClient, createWalletClient, custom, http, parseUnits } from 'viem'
import { cctpSource, quoteFromChain, burnToArc, waitForArcMint, toNativeUsdc } from '@vyrechain/sdk'

const base = cctpSource(84532) // Base Sepolia
const [account] = await window.ethereum.request({ method: 'eth_requestAccounts' })
const client = createPublicClient({ chain: base.chain, transport: http() })
const wallet = createWalletClient({ account, chain: base.chain, transport: custom(window.ethereum) })

// amounts on the source chain have 6 decimals
const quote = await quoteFromChain({ sourceChainId: base.id, amount: parseUnits('10', 6) })
const burn = await burnToArc(wallet, client, { amount: quote.amount, maxFee: quote.maxFee })
const mint = await waitForArcMint(base.id, burn.hash, { recipient: burn.recipient })
console.log('minted on Arc:', mint.forwardTxHash, toNativeUsdc(mint.received ?? quote.receive)) // 18 decimals on Arc

Then bridge it in with depositFromArc, keeping a little USDC on Arc for that transaction's gas.

Withdraw to Arc

A withdrawal moves USDC from VYRE back to Arc through the chain's own bridge. It's two transactions with a wait between them, until VYRE's state covering the withdrawal is confirmed on Arc:

  1. On VYRE: call withdrawEth(destination) on the ArbSys precompile at 0x0000000000000000000000000000000000000064, with the amount as the value.
  2. Wait until the state covering that block is confirmed on Arc. On the testnet (test settings: a check every 15 minutes, a 40-block challenge period) that's usually under half an hour.
  3. On Arc: claim it from the bridge's Outbox 0xCffF3dF492fDF6c446736CAd53b1E0e436dD56F0 (executeTransaction, with a proof from the node).

Pick the destination with care. The claim pays it with a plain call from the bridge and reverts if that call fails, so a withdrawal to a contract that refuses USDC can never be claimed: its USDC stays in the bridge. One to a system address (0x…0001 and the others up to 0xffff, where nobody can move it) is lost. So is one to Arc's USDC contract itself, 0x3600000000000000000000000000000000000000 (the token and not a wallet), the address most likely pasted by mistake as "the USDC address": Arc refuses a payment into it (Blocked address) and the bridge refuses to call it, so the claim can never succeed. Any address Arc's USDC issuer has blocklisted fails the same way. The SDK's withdrawToArc refuses the system addresses and Arc's USDC contract; given an Arc client (arcClient) it also refuses a contract on Arc (unless allowContractDestination is set) and any destination with code that doesn't take a payment from the bridge, naming a blocked address as such (a plain wallet with no code is not tried this way, so a blocklisted wallet is not caught).

The @arbitrum/sdk package (VYRE's bridge uses the same contracts) does steps 2 and 3 for you. It uses ethers v5:

const { ethers } = require('ethers') // v5
const sdk = require('@arbitrum/sdk')

sdk.registerCustomArbitrumNetwork({
  chainId: 7357, parentChainId: 5042002, name: 'vyre-testnet', confirmPeriodBlocks: 40,
  isCustom: true, isTestnet: true, isBold: true,
  ethBridge: {
    bridge: '0xa0CC23059e5789De9a7a9335bfA6d299a86d9b12',
    inbox: '0x52f90cb0ace79963736b1DCEb07587F780F47d98',
    sequencerInbox: '0x1DC82fEAeD8dACa8c97A693fb82cb6CE3a9b2dCd',
    outbox: '0xCffF3dF492fDF6c446736CAd53b1E0e436dD56F0',
    rollup: '0x38Dc065eA29326359ddD23766F588191210A8665',
  },
})

const arc = new ethers.providers.StaticJsonRpcProvider('https://rpc.testnet.arc.io', 5042002)
const vyre = new ethers.providers.StaticJsonRpcProvider('https://testnet-rpc.vyrechain.com', 7357)
const onArc = new ethers.Wallet(process.env.PRIVATE_KEY, arc)
const onVyre = onArc.connect(vyre)

// 1. start on VYRE
const arbSys = new ethers.Contract('0x0000000000000000000000000000000000000064',
  ['function withdrawEth(address destination) payable returns (uint256)'], onVyre)
const tx = await arbSys.withdrawEth(onArc.address, { value: ethers.utils.parseEther('1') })
const receipt = await tx.wait()

// 2. wait until it's confirmed on Arc (checks every 15 s)
const [message] = await new sdk.ChildTransactionReceipt(receipt).getChildToParentMessages(onArc)
await message.waitUntilReadyToExecute(vyre, 15000)

// 3. claim on Arc
const claim = await message.execute(vyre)
await claim.wait()

On mainnet a withdrawal takes about a day at launch; a fast exit is coming.

VYRE's testnet has no value: its USDC is for testing. On mainnet, trading is risky and nothing here is advice. USDC is issued by Circle; VYRE is an independent project, not affiliated with or endorsed by Circle. Questions: team@vyrechain.com