XRPL
Send GMP messages from the XRP Ledger with a payment and memos.
The XRP Ledger has no smart contracts, so Axelar has no gateway contract there. Instead, you send a message as a Payment to Axelar's multisig account, with the message in the transaction's memos. Axelar's verifiers check the payment, and Axelar routes the message through Amplifier.
XRPL can send GMP messages, but can't receive them: there's no contract on XRPL to execute them. XRPL can receive tokens through the Interchain Token Service, without a payload.
Using an AI agent?
Copy this page with Copy Markdown at the top and give it to your agent, with what you want to build or the app you want to make cross-chain. The copied page links to the Markdown of the other pages it refers to, so the agent can read them too. Review what it writes, and test on testnet before mainnet.
Addresses
| Mainnet | Testnet | |
|---|---|---|
| Chain name | xrpl | xrpl |
| Axelar multisig account | rfmS3zqrQrka8wVyhXifEeyTwe8AMz2Yhw | rNrjh1KGZk2jBR3wPfAQnoidtFFYQKbQn2 |
| RPC | wss://s1.ripple.com | wss://s.altnet.rippletest.net:51233 |
The multisig account's signers are Axelar's XRPL verifiers. The deployment configs list it as AxelarGateway: mainnet | testnet. For a funded testnet account, use the XRP faucet.
Send a message
- Estimate the fee with
xrplas the source chain. The result is in drops (1 XRP = 1,000,000 drops). - Send a Payment to the multisig account. The whole
Amountpays gas; any unused part is refunded. - Follow the message on Axelarscan by the transaction hash.
With xrpl.js, sending "hello" to a contract on XRPL EVM testnet:
import { Client, Wallet, convertStringToHex } from 'xrpl';
const AXELAR_MULTISIG = 'rNrjh1KGZk2jBR3wPfAQnoidtFFYQKbQn2'; // testnet
const memo = (type, data) => ({
Memo: { MemoType: convertStringToHex(type), MemoData: data },
});
const client = new Client('wss://s.altnet.rippletest.net:51233');
await client.connect();
const wallet = Wallet.fromSeed(process.env.XRPL_SEED);
// The payload, as hex without 0x. An EVM contract expects ABI-encoded data:
// cast abi-encode "f(string)" "hello"
const payload =
'0000000000000000000000000000000000000000000000000000000000000020' +
'0000000000000000000000000000000000000000000000000000000000000005' +
'68656c6c6f000000000000000000000000000000000000000000000000000000';
const result = await client.submitAndWait(
{
TransactionType: 'Payment',
Account: wallet.address,
Destination: AXELAR_MULTISIG,
Amount: '1000000', // 1 XRP in drops, all of it for gas
Memos: [
memo('type', convertStringToHex('call_contract')),
memo('destination_chain', convertStringToHex('xrpl-evm')),
memo('destination_address', convertStringToHex('0x0A90c0Af1B07f6AC34f3520348Dbfae73BDa358E')),
memo('payload', payload),
],
},
{ autofill: true, wallet },
);
console.log(result.result.hash);
await client.disconnect();The axelar-contract-deployments repository also has a script for this, xrpl/call-contract.js.
What the destination receives
The message has:
- source chain
xrpl; - source address: your XRPL account (
r…), as a string; - message ID:
0xfollowed by the transaction hash in lowercase; - payload: exactly the bytes of the
payloadmemo.
An EVM contract receives them in _execute, with sourceAddress an r… address. Check it as a string, not with toAddress().
Memos
Each memo has a MemoType, the field name, and a MemoData, the value. Both are hex:
MemoTypeandMemoDataare the hex of a UTF-8 string, e.g.convertStringToHex('call_contract'),- except the
payloadmemo, whoseMemoDatais the payload bytes themselves in hex, without0x.
To send a message:
MemoType | MemoData |
|---|---|
type | call_contract |
destination_chain | The destination's chain name, e.g. xrpl-evm |
destination_address | The destination contract's address in its chain's format; for EVM chains, 0x is optional |
payload | The payload, as raw bytes in hex. Required |
To add gas, see Add gas. Token transfers use interchain_transfer; see ITS on XRPL.
Rules
If a payment breaks any of these, the verifiers reject it. It never appears on Axelarscan, and there's no automatic refund:
- It's a
Paymentto the multisig account, and succeeded (tesSUCCESS). - It sets no flags, so partial payments aren't allowed, and the full
Amountis delivered. - Each memo type appears once.
- The payload fits in the memos: XRPL limits all memos of a transaction to 1 KB together.
Gas
Amount can be XRP, in drops, or an IOU token registered with the Interchain Token Service on XRPL, as { currency, issuer, value }.
Add gas
If the gas paid wasn't enough, send another Payment to the multisig account with these memos:
MemoType | MemoData |
|---|---|
type | add_gas |
msg_id | The original transaction hash: 64 lowercase hex characters, without 0x |
msg_id must be lowercase; explorers often show hashes in uppercase. The whole Amount of this payment is added as gas.
The AxelarJS SDK can build this payment with addGasToXrplChain. It takes the amount in XRP, and returns an unsigned transaction for you to sign and submit.
Refunds
Unused gas is refunded in XRP to the account that paid it, minus the XRP Ledger transaction fee. You can't choose another refund address. Refunds are sent from an account Axelar operates, not from the multisig account.