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:
- You call
depositForBridge()on the source chain. The token amount (minus the route's fixed fee) is burned and aBridgeDepositInitiatedevent is emitted. - The bridge operator observes the event and waits for source-chain finality.
- 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:
| Chain | Chain ID | Explorer |
|---|---|---|
| Ethereum | 1 | View contract ↗ |
| Base | 8453 | View contract ↗ |
| World Chain | 480 | View contract ↗ |
| BNB Smart Chain | 56 | View contract ↗ |
| Polygon | 137 | View contract ↗ |
| Gnosis | 100 | View contract ↗ |
| Celo | 42220 | View contract ↗ |
Stablecoins
Each token uses the same address on every supported chain. All tokens have 18 decimals.
| Token | Address |
|---|---|
| wARS | 0x0DC4F92879B7670e5f4e4e6e3c801D229129D90D |
| wBRL | 0xD76f5Faf6888e24D9F04Bf92a0c8B921FE4390e0 |
| wMXN | 0x337E7456B420bD3481e7FA61fA9850343d610d34 |
| wPEN | 0x4F34c8b3b5FB6D98Da888F0feA543d4d9C9F2eBE |
| wCLP | 0x61D450a098b6a7f69fC4b98CE68198fe59768651 |
| wCOP | 0x8a1D45e102e886510e891d2Ec656a708991e2D76 |
Quickstart
The examples below use viem, but any EVM tooling works — it's a single contract call.
0. Constants and ABI
// 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',
};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:
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.
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
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
// 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
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)| Parameter | Description |
|---|---|
token | Address of the stablecoin to bridge (see token table above). |
amount | Total amount to pull from the caller, inclusive of the route's fixed fee. The recipient receives amount − fixedFee, so amount must exceed the fee. |
destChainId | Destination chain id. The (token, destChainId) route must be enabled. |
destRecipient | Recipient on the destination chain — can differ from the sender. |
clientDepositId | Optional bytes32 of your choosing, echoed in BridgeDepositInitiated for off-chain correlation. Pass bytes32(0) if unused. |
- 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
| Field | Indexed | Description |
|---|---|---|
depositId | yes | Sequential id assigned by the contract |
token | yes | Bridged stablecoin |
from | yes | Depositor |
amount | no | Total amount (inclusive of fee) |
destChainId | no | Destination chain id |
destRecipient | no | Recipient on destination |
clientDepositId | no | Caller-supplied correlation id |
BridgeMintFulfilled — destination chain
| Field | Indexed | Description |
|---|---|---|
token | yes | Minted stablecoin |
to | yes | Recipient |
amount | no | Amount minted (deposit amount minus fee) |
sourceChainId | no | Chain where the deposit was made |
sourceTxHash | no | Deposit transaction hash |
sourceDepositId | yes | The 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.
| Token | → Base | → World Chain | → BNB Smart Chain | → Polygon | → Gnosis | → Celo |
|---|---|---|---|---|---|---|
Fees are fixed per route, deducted from the deposited amount, and read live from routeConfigs(token, destChainId) on Ethereum.
Mint capacity
| Token | |||||||
|---|---|---|---|---|---|---|---|
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:
# 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:
{
"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:
| Status | Meaning |
|---|---|
DETECTED | Deposit transaction observed on the source chain |
PENDING_FINALITY | Waiting for source-chain confirmations |
READY_FOR_MINT | Finality reached, mint being prepared |
QUEUED_DAILY_LIMIT | Amount exceeds remaining daily mint capacity — queued until the cap resets |
MINTING | Mint transaction submitted on the destination chain |
COMPLETED | Tokens minted to the destination recipient |
FAILED | Operation failed — see error_reason |
Questions?
Reach out through Ripio B2B — we're happy to walk your team through a test integration.