Docs
Integrations

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

MainnetTestnet
Chain namexrplxrpl
Axelar multisig accountrfmS3zqrQrka8wVyhXifEeyTwe8AMz2YhwrNrjh1KGZk2jBR3wPfAQnoidtFFYQKbQn2
RPCwss://s1.ripple.comwss://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

  1. Estimate the fee with xrpl as the source chain. The result is in drops (1 XRP = 1,000,000 drops).
  2. Send a Payment to the multisig account. The whole Amount pays gas; any unused part is refunded.
  3. 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: 0x followed by the transaction hash in lowercase;
  • payload: exactly the bytes of the payload memo.

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:

  • MemoType and MemoData are the hex of a UTF-8 string, e.g. convertStringToHex('call_contract'),
  • except the payload memo, whose MemoData is the payload bytes themselves in hex, without 0x.

To send a message:

MemoTypeMemoData
typecall_contract
destination_chainThe destination's chain name, e.g. xrpl-evm
destination_addressThe destination contract's address in its chain's format; for EVM chains, 0x is optional
payloadThe 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 Payment to the multisig account, and succeeded (tesSUCCESS).
  • It sets no flags, so partial payments aren't allowed, and the full Amount is 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:

MemoTypeMemoData
typeadd_gas
msg_idThe 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.

On this page