Integrate the Ripio Bridge

The Ripio Bridge moves LATAM stablecoins between chains with a burn-and-mint model: tokens are burned on the source chain and minted on the destination chain by the bridge operator. Integrating takes a single contract call — no SDK required.

Overview

A bridge transfer has three steps:

  1. You call depositForBridge() on the source chain. The token amount (minus the route's fixed fee) is burned and a BridgeDepositInitiated event is emitted.
  2. The bridge operator observes the event and waits for source-chain finality.
  3. The operator mints the bridged amount to your recipient on the destination chain, emitting BridgeMintFulfilled. Minting is idempotent — exactly one mint per deposit.

The BridgeDeposit contract is deployed at the same address on every supported chain (factory deployment) and is verified on every explorer. Source code: BridgeDeposit.sol on GitHub.

Contract & token addresses

BridgeDeposit

0x465e642387d3d73a57CDc1368fFA53A800bA5D47 on every chain:

ChainChain IDExplorer
Ethereum1View contract ↗
Base8453View contract ↗
World Chain480View contract ↗
BNB Smart Chain56View contract ↗
Polygon137View contract ↗
Gnosis100View contract ↗
Celo42220View contract ↗

Stablecoins

Each token uses the same address on every supported chain. All tokens have 18 decimals.

TokenAddress
wARS0x0DC4F92879B7670e5f4e4e6e3c801D229129D90D
wBRL0xD76f5Faf6888e24D9F04Bf92a0c8B921FE4390e0
wMXN0x337E7456B420bD3481e7FA61fA9850343d610d34
wPEN0x4F34c8b3b5FB6D98Da888F0feA543d4d9C9F2eBE
wCLP0x61D450a098b6a7f69fC4b98CE68198fe59768651
wCOP0x8a1D45e102e886510e891d2Ec656a708991e2D76

Quickstart

The examples below use viem, but any EVM tooling works — it's a single contract call.

0. Constants and ABI

constants.ts
// BridgeDeposit — same address on every supported chain
const BRIDGE_DEPOSIT = '0x465e642387d3d73a57CDc1368fFA53A800bA5D47';

// Supported chain ids
const CHAINS = {
  ethereum: 1,
  base: 8453,
  worldchain: 480,
  bsc: 56,
  polygon: 137,
  gnosis: 100,
};

// Stablecoins — same address on every supported chain, all 18 decimals
const TOKENS = {
  wARS: '0x0DC4F92879B7670e5f4e4e6e3c801D229129D90D',
  wBRL: '0xD76f5Faf6888e24D9F04Bf92a0c8B921FE4390e0',
  wMXN: '0x337E7456B420bD3481e7FA61fA9850343d610d34',
  wPEN: '0x4F34c8b3b5FB6D98Da888F0feA543d4d9C9F2eBE',
  wCLP: '0x61D450a098b6a7f69fC4b98CE68198fe59768651',
  wCOP: '0x8a1D45e102e886510e891d2Ec656a708991e2D76',
};
abi.ts
import { parseAbi } from 'viem';

// Minimal ABI — enough for a full integration.
// Full ABI: verified contract on any block explorer, or the source on GitHub.
const BRIDGE_ABI = parseAbi([
  'function depositForBridge(address token, uint256 amount, uint256 destChainId, address destRecipient, bytes32 clientDepositId) returns (uint256 depositId)',
  'function routeConfigs(address token, uint256 destChainId) view returns (bool enabled, uint256 fixedFee)',
  'function remainingMintCapacity(address token) view returns (uint256 remaining, uint256 dailyMaxMint, uint256 mintedToday)',
  'event BridgeDepositInitiated(uint256 indexed depositId, address indexed token, address indexed from, uint256 amount, uint256 destChainId, address destRecipient, bytes32 clientDepositId)',
  'event BridgeMintFulfilled(address indexed token, address indexed to, uint256 amount, uint256 sourceChainId, bytes32 sourceTxHash, uint256 indexed sourceDepositId)',
]);

const ERC20_ABI = parseAbi([
  'function approve(address spender, uint256 amount) returns (bool)',
  'function allowance(address owner, address spender) view returns (uint256)',
]);

1. Check the route and its fee

Every route (token + destination chain) must be enabled and carries a fixed fee that is deducted from the deposited amount. Check before depositing:

check-route.ts
import { createPublicClient, http, formatUnits } from 'viem';
import { mainnet } from 'viem/chains';

const publicClient = createPublicClient({ chain: mainnet, transport: http() });

// Check the wARS route from Ethereum (1) to Base (8453)
const [enabled, fixedFee] = await publicClient.readContract({
  address: BRIDGE_DEPOSIT,
  abi: BRIDGE_ABI,
  functionName: 'routeConfigs',
  args: [TOKENS.wARS, 8453n],
});

if (!enabled) throw new Error('Route is not enabled');
console.log(`Fixed fee: ${formatUnits(fixedFee, 18)} wARS`);

2. Approve and deposit

The caller must first approve the BridgeDeposit contract for at least amount — the contract burns via burnFrom and pulls the fee via transferFrom.

deposit.ts
import { keccak256, encodePacked, parseUnits, parseEventLogs, pad } from 'viem';

const amount = parseUnits('1000', 18); // total, inclusive of the route's fixed fee

// 1. Approve the BridgeDeposit contract for at least `amount`
//    (the contract pulls via burnFrom + transferFrom)
const approveHash = await walletClient.writeContract({
  address: TOKENS.wARS,
  abi: ERC20_ABI,
  functionName: 'approve',
  args: [BRIDGE_DEPOSIT, amount],
});
await publicClient.waitForTransactionReceipt({ hash: approveHash });

// 2. Optional: your own id for off-chain correlation.
//    Echoed in BridgeDepositInitiated. Use pad('0x0', { size: 32 }) to skip.
const clientDepositId = keccak256(encodePacked(
  ['address', 'uint256', 'uint256'],
  [account.address, BigInt(Date.now()), BigInt(myInternalOrderId)],
));

// 3. Initiate the bridge — burns on the source chain
const depositHash = await walletClient.writeContract({
  address: BRIDGE_DEPOSIT,
  abi: BRIDGE_ABI,
  functionName: 'depositForBridge',
  args: [
    TOKENS.wARS,   // token
    amount,        // total amount (fee is deducted from it)
    8453n,         // destination chain id (Base)
    recipient,     // recipient on destination — can differ from sender
    clientDepositId,
  ],
});

// 4. Extract the depositId from the BridgeDepositInitiated event
const receipt = await publicClient.waitForTransactionReceipt({ hash: depositHash });
const [initiated] = parseEventLogs({
  abi: BRIDGE_ABI,
  eventName: 'BridgeDepositInitiated',
  logs: receipt.logs,
});
console.log('depositId:', initiated.args.depositId);

3. Watch for fulfillment on the destination chain

watch.ts
import { createPublicClient, http } from 'viem';
import { base } from 'viem/chains';

// Watch on the DESTINATION chain
const destClient = createPublicClient({ chain: base, transport: http() });

const unwatch = destClient.watchContractEvent({
  address: BRIDGE_DEPOSIT,
  abi: BRIDGE_ABI,
  eventName: 'BridgeMintFulfilled',
  args: { to: recipient },
  onLogs: (logs) => {
    for (const log of logs) {
      // Idempotent: exactly one mint per (sourceChainId, sourceDepositId)
      console.log('Minted', log.args.amount, 'for deposit', log.args.sourceDepositId);
    }
  },
});

4. Check mint capacity before large transfers

capacity.ts
// Minting is subject to a daily cap per token, per chain.
// Check capacity on the DESTINATION chain before large transfers —
// deposits above the remaining capacity are queued until the cap resets.
const [remaining, dailyMaxMint, mintedToday] = await destClient.readContract({
  address: BRIDGE_DEPOSIT,
  abi: BRIDGE_ABI,
  functionName: 'remainingMintCapacity',
  args: [TOKENS.wARS],
});

if (amount > remaining) {
  console.warn('Transfer exceeds remaining daily capacity — it will be queued');
}

Function reference

depositForBridge

Solidity signature
function depositForBridge(
    address token,           // stable to bridge
    uint256 amount,          // total amount, inclusive of the route's fixed fee
    uint256 destChainId,     // destination chain id
    address destRecipient,   // recipient on destination (can differ from sender)
    bytes32 clientDepositId  // optional, your own id for off-chain correlation
) returns (uint256 depositId)
ParameterDescription
tokenAddress of the stablecoin to bridge (see token table above).
amountTotal amount to pull from the caller, inclusive of the route's fixed fee. The recipient receives amount − fixedFee, so amount must exceed the fee.
destChainIdDestination chain id. The (token, destChainId) route must be enabled.
destRecipientRecipient on the destination chain — can differ from the sender.
clientDepositIdOptional bytes32 of your choosing, echoed in BridgeDepositInitiated for off-chain correlation. Pass bytes32(0) if unused.
Requirements
  • ERC-20 allowance for the BridgeDeposit contract must be ≥ amount.
  • The (token, destChainId) route must be enabled — check routeConfigs.
  • Minting on the destination chain is subject to a daily cap — check remainingMintCapacity(token) on the destination chain. Deposits above the remaining capacity are queued until the cap resets.

Events

BridgeDepositInitiated — source chain

FieldIndexedDescription
depositIdyesSequential id assigned by the contract
tokenyesBridged stablecoin
fromyesDepositor
amountnoTotal amount (inclusive of fee)
destChainIdnoDestination chain id
destRecipientnoRecipient on destination
clientDepositIdnoCaller-supplied correlation id

BridgeMintFulfilled — destination chain

FieldIndexedDescription
tokenyesMinted stablecoin
toyesRecipient
amountnoAmount minted (deposit amount minus fee)
sourceChainIdnoChain where the deposit was made
sourceTxHashnoDeposit transaction hash
sourceDepositIdyesThe depositId from the source chain

Fulfillment is idempotent: exactly one mint per (sourceChainId, sourceDepositId) pair.

Live routes & fees

Select a source chain to see which routes are enabled and their fixed fee, read live on-chain.

TokenBaseWorld ChainBNB Smart ChainPolygonGnosisCelo
wARS
wBRL
wMXN
wPEN
wCLP
wCOP

Fees are fixed per route, deducted from the deposited amount, and read live from routeConfigs(token, destChainId) on Ethereum.

Mint capacity

TokenEthereumBaseWorld ChainBNB Smart ChainPolygonGnosisCelo
wARS
wBRL
wMXN
wPEN
wCLP
wCOP

Remaining / daily max mint per token, read live from remainingMintCapacity(token) on each chain. Capacity resets daily; deposits above the remaining capacity are queued.

Tracking & status API

For off-chain correlation, generate a unique clientDepositId per transfer — for example keccak256(abi.encodePacked(sender, timestamp, yourInternalId)) — and match it against the BridgeDepositInitiated event.

Alternatively, poll our public status API using the deposit transaction hash:

Status API
# Look up a bridge operation by its source transaction hash
curl "https://bridge.ripio.com/api/operations/{sourceTxHash}?chainId=1"

# List operations for an address (add &pending=true for in-flight only)
curl "https://bridge.ripio.com/api/operations?address=0xYourAddress&limit=20"

Example response:

GET /api/operations/{txHash} — 200
{
  "operation": {
    "id": "…",
    "source_chain_id": 1,
    "source_tx_hash": "0x…",
    "dest_chain_id": 8453,
    "dest_tx_hash": "0x…",
    "deposit_id": "42",
    "token_symbol": "wARS",
    "token_address": "0x0DC4F92879B7670e5f4e4e6e3c801D229129D90D",
    "from_address": "0x…",
    "dest_recipient": "0x…",
    "amount_in": "1000000000000000000000",
    "fee_amount": "…",
    "amount_out": "…",
    "status": "COMPLETED",
    "error_reason": null,
    "created_at": "2026-07-21T12:00:00.000Z",
    "updated_at": "2026-07-21T12:15:00.000Z"
  }
}

Amounts are returned as raw 18-decimal integer strings. The status field follows this lifecycle:

StatusMeaning
DETECTEDDeposit transaction observed on the source chain
PENDING_FINALITYWaiting for source-chain confirmations
READY_FOR_MINTFinality reached, mint being prepared
QUEUED_DAILY_LIMITAmount exceeds remaining daily mint capacity — queued until the cap resets
MINTINGMint transaction submitted on the destination chain
COMPLETEDTokens minted to the destination recipient
FAILEDOperation failed — see error_reason

Questions?

Reach out through Ripio B2B — we're happy to walk your team through a test integration.

Developer Docs | Ripio Bridge