# XRPL (https://docs.axelar.dev/its/integrations/xrpl)



The XRPL Blockchain is unique in comparison to other blockchains integrated with Axelar. Unlike other blockchains, XRPL does not support smart contracts. As such there is no Interchain Token Service contract deployed on the chain the way there is with other EVM and non-EVM blockchain integrations; the same XRPL Multisig Gateway account that handles GMP also handles ITS transfers, and the deployment configs refer to that single address as both `AxelarGateway` and `InterchainTokenService`.

## XRPL Gateway [#xrpl-gateway]

The integration leverages the [XRPL Multisig Signing](https://xrpl.org/docs/concepts/accounts/multi-signing) account type to facilitate token transfers. The [gateway](https://github.com/axelarnetwork/axelar-amplifier-xrpl/tree/xrpl/contracts) is controlled by the [Axelar Verifier set](https://axelarscan.io/verifiers).

The testnet address for the XRPL Gateway is: [rNrjh1KGZk2jBR3wPfAQnoidtFFYQKbQn2](https://testnet.xrpl.org/accounts/rNrjh1KGZk2jBR3wPfAQnoidtFFYQKbQn2).

The mainnet address for the XRPL Gateway is: [rfmS3zqrQrka8wVyhXifEeyTwe8AMz2Yhw](https://livenet.xrpl.org/accounts/rfmS3zqrQrka8wVyhXifEeyTwe8AMz2Yhw).

Axelar's integration to XRPL allows for the cross-chain transfer of both the XRPL native token (XRP itself) as well as custom tokens ([IOU](https://learn.xrpl.org/glossary/iou/)).

## Transfer Token [#transfer-token]

The interchain transfer flow is similar to that of the regular [GMP flow](https://docs.axelar.dev/llms.mdx/gmp/integrations/xrpl/content.md). As with the regular GMP flow, you send a [payment transaction](https://xrpl.org/docs/references/protocol/transactions/types/payment) to the Gateway account, but with `interchain_transfer` as the `type` memo rather than `call_contract`.

The Gateway becomes the custodian of your token on XRPL while the transfer is routed via the [ITS Hub](https://docs.axelar.dev/llms.mdx/its/how-it-works/its-hub/content.md) to the destination chain (and back, when bridged back).

### Required memos [#required-memos]

You attach the following memos to the payment. Each field name goes into `MemoType` and the value into `MemoData`, both as the hex of a UTF-8 string, except `payload`, whose `MemoData` is the payload bytes themselves in hex. See [Memos](https://docs.axelar.dev/llms.mdx/gmp/integrations/xrpl/content.md#memos) for the rules that apply to every XRPL payment.

1. `type`: always `interchain_transfer` for ITS transfers.
2. `destination_address`: the recipient address on the destination chain, hex-encoded **without** any `0x` prefix.
3. `destination_chain`: the [Axelar chain name](https://axelarscan.io/resources/chains) of the destination chain (for example `xrpl-evm`, `ethereum`, `hedera`).
4. `gas_fee_amount`: the portion of `Amount` set aside to pay [cross-chain transaction fees](https://docs.axelar.dev/llms.mdx/gmp/gas/gas-service/content.md). See [Encoding `gas_fee_amount`](#encoding-gas_fee_amount) for the required format.
5. `payload`: ABI-encoded calldata for the destination Executable. Include this memo **only** when the destination address is a smart contract that you want to invoke. When the destination is an externally-owned account (EOA) or any address that is not an executing contract, omit the `payload` memo entirely; an empty or zero-length payload is still treated as a GMP call and will fail.

### How `gas_fee_amount` relates to `Amount` [#how-gas_fee_amount-relates-to-amount]

A single XRPL Payment carries one `Amount`. The validators interpret that `Amount` as **both** the amount to bridge **and** the source of gas:

```
transfer_amount = Amount - gas_fee_amount
```

The `transfer_amount` (in the same currency as `Amount`) is what the ITS Hub forwards to the destination chain. The `gas_fee_amount` portion is credited to the Axelar gas service to pay relayers along the cross-chain route.

Two consequences worth keeping in mind:

* **The initial gas allocation is paid in the same asset that is being sent.** A single XRPL Payment cannot carry an `Amount` and a `gas_fee_amount` in different currencies. If you later need to top up gas (for example because the initial allocation was too low, or because you want to add XRP gas to an IOU transfer), use [Add Gas](#insufficient-gas), which is a separate Payment and accepts any supported token.
* **`Amount` must include the gas.** If you want the recipient on the destination chain to receive `X` tokens, then `Amount = X + gas_fee_amount` on the source side.

<Callout type="warning">
  `gas_fee_amount` &#x2A;*must be strictly less than `Amount`**. Otherwise Axelar
  verifiers reject the message, the payment is never indexed on Axelarscan,
  and the funds remain at the Gateway multisig without an automated refund
  path.
</Callout>

### Encoding `gas_fee_amount` [#encoding-gas_fee_amount]

The format of the `gas_fee_amount` memo depends on the type of `Amount` in the Payment. There is no per-memo currency or issuer field; those are inherited from `Amount`.

| `Amount` type                  | Source token | `gas_fee_amount` memo format                                                                       |
| ------------------------------ | ------------ | -------------------------------------------------------------------------------------------------- |
| Drops string, e.g. `"1000000"` | XRP          | An integer drops string (parsed as a `u64`), e.g. `"100000"` for 0.1 XRP gas.                      |
| Issued object                  | IOU          | A decimal string (parsed as an `XRPLTokenAmount`) in the same `currency` and `issuer` as `Amount`. |

<Callout type="warning">
  A memo that does not match the format expected for the `Amount` type
  (for example a decimal like `"0.18"` in an XRP/drops payment) cannot be
  verified by Axelar, so the message is never indexed on Axelarscan and
  [Add Gas](#insufficient-gas) cannot recover it.
</Callout>

### Example: sending XRP [#example-sending-xrp]

This example sends 1 XRP, allocating 0.1 XRP to gas. The destination chain receives the equivalent of 0.9 XRP.

```json
{
    TransactionType: "Payment",
    Account: "<user XRPL address>",
    Destination: "<gateway XRPL address>",
    Amount: "1000000", // 1 XRP, in drops
    Memos: [
        {
            Memo: {
                MemoType: "74797065", // hex("type")
                MemoData: "696E746572636861696E5F7472616E73666572", // hex("interchain_transfer")
            },
        },
        {
            Memo: {
                MemoType: "64657374696E6174696F6E5F61646472657373", // hex("destination_address")
                MemoData: "30413930633041663142303766364143333466333532303334384462666165373342446133353845", // hex("0A90c0Af1B07f6AC34f3520348Dbfae73BDa358E"), an EVM address with no 0x prefix
            },
        },
        {
            Memo: {
                MemoType: "64657374696E6174696F6E5F636861696E", // hex("destination_chain")
                MemoData: "7872706C2D65766D", // hex("xrpl-evm")
            },
        },
        {
            Memo: {
                MemoType: "6761735F6665655F616D6F756E74", // hex("gas_fee_amount")
                MemoData: "313030303030", // hex("100000"), i.e. 100000 drops = 0.1 XRP
            },
        },
    ],
}
```

Axelar parses the `gas_fee_amount` memo as a `u64` drops value (0.1 XRP), computes `transfer_amount = 1 XRP - 0.1 XRP = 0.9 XRP`, and the ITS Hub forwards the equivalent of 0.9 XRP to the destination chain.

The hex casing of `MemoType` and `MemoData` does not matter on submission; the XRPL canonical wire format is uppercase, which is what XRPL nodes and explorers will return.

### Example: sending an IOU [#example-sending-an-iou]

This example sends `1` of an IOU (currency `ABC`, issuer `r4DVHyEisbgQRAXCiMtP2xuz5h3dDkwqf1`), allocating `0.05` to gas. The destination chain receives the equivalent of `0.95 ABC`.

```json
{
    TransactionType: "Payment",
    Account: "<user XRPL address>",
    Destination: "<gateway XRPL address>",
    Amount: {
        currency: "ABC",                                  // IOU currency code
        issuer: "r4DVHyEisbgQRAXCiMtP2xuz5h3dDkwqf1",     // IOU issuer
        value: "1",                                       // 1 ABC, including the gas portion
    },
    Memos: [
        {
            Memo: {
                MemoType: "74797065", // hex("type")
                MemoData: "696E746572636861696E5F7472616E73666572", // hex("interchain_transfer")
            },
        },
        {
            Memo: {
                MemoType: "64657374696E6174696F6E5F61646472657373", // hex("destination_address")
                MemoData: "30413930633041663142303766364143333466333532303334384462666165373342446133353845", // hex("0A90c0Af1B07f6AC34f3520348Dbfae73BDa358E"), an EVM address with no 0x prefix
            },
        },
        {
            Memo: {
                MemoType: "64657374696E6174696F6E5F636861696E", // hex("destination_chain")
                MemoData: "7872706C2D65766D", // hex("xrpl-evm")
            },
        },
        {
            Memo: {
                MemoType: "6761735F6665655F616D6F756E74", // hex("gas_fee_amount")
                MemoData: "302E3035", // hex("0.05"), i.e. 0.05 ABC
            },
        },
    ],
}
```

Axelar parses the `gas_fee_amount` memo as an `XRPLTokenAmount` in the same `(currency, issuer)` as `Amount` (here `0.05 ABC`), computes `transfer_amount = 1 - 0.05 = 0.95 ABC`, and the ITS Hub forwards `0.95 ABC` to the destination chain.

### Sending a GMP message with the transfer [#sending-a-gmp-message-with-the-transfer]

To trigger an Executable on the destination chain, include a fifth memo of type `payload` with the ABI-encoded calldata:

```json
{
    Memo: {
        MemoType: "7061796C6F6164", // hex("payload")
        // ABI-encoded calldata to invoke the destination Executable; example payload below encodes the string "GMP works too?"
        MemoData: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000E474D5020776F726B7320746F6F3F000000000000000000000000000000000000",
    },
}
```

The `destination_address` for this case is the contract address of the Executable on the destination chain. Omit the `payload` memo for plain token transfers.

## Example [#example]

A full example can be found [here](https://github.com/axelarnetwork/axelar-contract-deployments/blob/main/xrpl/interchain-transfer.js).

The example makes use of the [XRPL Client library](https://www.npmjs.com/package/xrpl), which simplifies connecting to the XRPL network, building transactions, signing them, and submitting them.

The key function in this example is `interchainTransfer()`:

```js
async function interchainTransfer(config, wallet, client, chain, options, args) {
    // encodeITSDestination produces the canonical raw-bytes hex for the destination
    // chain type; hex() then wraps it for XRPL memo transport.
    const destinationAddress = encodeITSDestination(config.chains, args.destinationChain, args.destinationAddress);

    // The gas fee is denominated in the same token as the transfer: parseTokenAmount
    // yields a drops string for XRP, or a decimal value for IOUs.
    const parsedGasFee = parseTokenAmount(args.token, options.gasFeeAmount);
    const gasFeeAmount = typeof parsedGasFee === 'string' ? parsedGasFee : parsedGasFee.value;

    await client.sendPayment(
        wallet,
        {
            destination: chain.contracts.InterchainTokenService.address,
            amount: parseTokenAmount(args.token, args.amount), // token is either "XRP" or "<currency>.<issuer-address>"
            memos: [
                { memoType: hex('type'), memoData: hex('interchain_transfer') },
                { memoType: hex('destination_address'), memoData: hex(destinationAddress.replace('0x', '')) },
                { memoType: hex('destination_chain'), memoData: hex(args.destinationChain) },
                { memoType: hex('gas_fee_amount'), memoData: hex(gasFeeAmount) },
                ...(options.payload ? [{ memoType: hex('payload'), memoData: options.payload }] : []),
            ],
        },
        options,
    );
}
```

The script converts `--gasFeeAmount` into the format the memo requires (a `u64` drops string for XRP, a decimal string for IOUs), so you pass it in the same denomination as the transferred token. See [Encoding `gas_fee_amount`](#encoding-gas_fee_amount) for the underlying memo format.

### Sending XRP [#sending-xrp]

To send 1 XRP with 0.1 XRP allocated to gas:

```bash
node xrpl/interchain-transfer.js -e testnet -n xrpl XRP 1 xrpl-evm 0x312dba807EAE77f01EF3dd21E885052f8F617c5B --gasFeeAmount 0.1
```

`--gasFeeAmount` is denominated in XRP (`0.1` = 0.1 XRP); the script converts it to the drops value carried in the memo.

### Sending an IOU [#sending-an-iou]

To send `1 ABC.r4DVHyEisbgQRAXCiMtP2xuz5h3dDkwqf1` with `0.05 ABC` allocated to gas:

```bash
node xrpl/interchain-transfer.js -e testnet -n xrpl ABC.r4DVHyEisbgQRAXCiMtP2xuz5h3dDkwqf1 1 xrpl-evm 0x312dba807EAE77f01EF3dd21E885052f8F617c5B --gasFeeAmount 0.05
```

`--gasFeeAmount` is a decimal string in the same currency as the token argument.

Once the command is run, you can track the cross-chain transaction on the [Axelarscan explorer](https://testnet.axelarscan.io/).

## Insufficient gas [#insufficient-gas]

If `gas_fee_amount` is well-formed but not enough to cover the actual Axelar-side relaying costs, the message is indexed on the GMP API as `INSUFFICIENT_GAS` and you can unblock it by topping up via [Add Gas](https://docs.axelar.dev/llms.mdx/gmp/integrations/xrpl/content.md#add-gas).

This recovery path only works for messages that Axelar verifiers were able to verify in the first place. Payments whose `gas_fee_amount` is malformed or not strictly less than `Amount` are never indexed on Axelarscan and cannot be recovered through Add Gas; validate the memo format on the source side to avoid that case.


---

All pages of these docs, as Markdown: https://docs.axelar.dev/llms.txt