XRPL General Message Passing (GMP)

The XRPL Blockchain is unique compared to other blockchains integrated with Axelar. Unlike other blockchains, XRPL does not support smart contracts. As such there is no Axelar Gateway and Axelar Gas Service deployed on the chain the way there is with other EVM and non-EVM blockchain integrations. Since XRPL has no contracts there is also no ability to receive GMP messages from other blockchains, you can only send messages to other blockchains.

The integration leverages the XRPL Multisig Signing and proof of stake security via the Amplifier Protocol to facilitate GMP. The gateway is controlled by the Axelar Verifier set.

The testnet address for the XRPL Gateway is: rNrjh1KGZk2jBR3wPfAQnoidtFFYQKbQn2.

The mainnet address for the XRPL Gateway is: rfmS3zqrQrka8wVyhXifEeyTwe8AMz2Yhw.

The flow for sending a GMP message from XRPL involves sending a payment transaction from your XRPL account to the XRPL Gateway with a specified memo field. Once the transaction is confirmed, Axelar’s relayer will pickup any transaction sent to the Gateway with the call_contract message type specified at which the point the Amplifier GMP flow will commence.

Payment transaction types in XRPL represent a transfer of value. They require the following fields:

  1. Transaction Type: The type of XRPL transaction being sent.
  2. Account: The sender’s account address.
  3. Amount: The amount of XRP being sent to cover the cross-chain transaction fees.
  4. Destination: The destination of the transaction.
  5. Memos: A special field in the payment transaction that is used to specify the message type, destination chain, destination address, and GMP payload.

When sending a GMP message the memo field contains the relevant metadata for the transaction.

The memo must contain the following fields:

  1. type: The type of transaction being sent (in this case, it should be of type call_contract).
  2. destination_address: The address of the destination contract on the destination chain.
  3. destination_chain: The name of the destination chain.
  4. payload: The payload of the transaction being sent.

All the data passed into the memo must be encoded in hex format as a list of objects. Each object has a MemoType representing the type data being passed in as well as a MemoData representing the data being passed in.

For a complete JSON example of the entire required XRPL GMP fields you can look at the following:

{
TransactionType: "Payment",
Account: user.address,
Amount: "1000000", // (1 xrp) used to pay cross-chain transaction fees.
// Amount: { // alternatively, an IOU token amount can be used to cover gas fees
// currency: "ABC", // IOU's currency code
// issuer: "r4DVHyEisbgQRAXCiMtP2xuz5h3dDkwqf1", // IOU issuer's account address
// value: "1" // IOU amount to allocated to gas fees (in this case, 1 ABC.r4DVH)
// },
Destination: gateway.address,
Memos: [
{
Memo: {
MemoType: "74797065", // hex("type")
MemoData: "63616c6c5f636f6e7472616374" // hex("call_contract")
},
},
{
Memo: {
MemoType: "64657374696e6174696f6e5f61646472657373", // hex("destination_address")
MemoData: "30413930633041663142303766364143333466333532303334384462666165373342446133353845" // hex("0A90c0Af1B07f6AC34f3520348Dbfae73BDa358E"), in this case: (0x is omitted)
},
},
{
Memo: {
MemoType: "64657374696E6174696F6E5F636861696E", // hex("destination_chain")
MemoData: "7872706c2d65766d2d6465766e6574", // destination chain, hex encoded - hex("xrpl-evm-devnet"), in this case
},
},
{
Memo: {
MemoType: "7061796c6f6164", // hex("payload")
// abi-encoded payload for executable destination contract address:
MemoData: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000e474d5020776f726b7320746f6f3f000000000000000000000000000000000000",
},
},
],
}

A full example can be found here.

The example makes use of the XRPL Client library

The XRPL client simplifies the process of connecting to the XRPL network, building transactions, signing them, and submitting them for processing. Key components of the XRPL client include:

The key function in this example is the callContract() function written out as follows.

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

This function constructs the memo that will be used for the transaction, passing in the type, destination_address, destination_chain, and payload. The request functionality of the XRPL client is then used to submit a signed transaction.

To interact with the example run the following command

Terminal window
node xrpl/call-contract.js -e testnet -n xrpl xrpl-evm 0x72d12C77D1eE1DB002F4437E4a96bAc87c3EF948 --gasFeeAmount 1 --gasFeeToken XRP --payload 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000568656c6c6f000000000000000000000000000000000000000000000000000000 -y

This will send a message to the 0x72d12C77D1eE1DB002F4437E4a96bAc87c3EF948 address on the XRPL-EVM chain, with an abi encoded message of ‘hello’.

Once the command is run you should see a transaction on the Axelarscan explorer where you can track the entire cross-chain transaction.

When making cross-chain transactions gas payments are paid out entirely on the source chain by the sender. Axelar’s microservices handle the gas payments on the destination chain as well as on the Axelar network itself. In the event that the gas payment made on the source chain is insufficient to cover the gas fees of the cross-chain transaction, the transaction may get stuck mid-way through the cross-chain transaction. This can happen for a number of reasons for example if the gas price on the destination chain spikes after the transaction has already been sent on the source chain. To unblock a stuck transaction you can make use of the add_gas message to the XRPL Gateway to top up the underfunded transaction.

The memo must contain the following fields:

  1. type: The type of message being sent, in this case, it should be of type add_gas.
  2. msg_id: The transaction hash of the underfunded message that needs to be topped up.

The message id must be passed as a hex encoded string. For example the message for the transaction with the id c7c653d2df83622c277da55df7fe6466098f5bc2e466e1251f42772d07016c8c must be passed in as 6367636535636464646638333632326332373764613535646637666536343630363039386635626332653436366531323531663432373732643037303136633863 (hex encoded).

For a complete JSON example of the entire required XRPL Add Gas fields you can look at the following:

{
TransactionType: "Payment",
Account: user.address, // sender's account address
Amount: "1000000", // amount of XRP, in drops (in this case, 1 XRP), to top-up gas fees with
Destination: gateway.address, // Axelar gateway's account address
Memos: [
{
Memo: {
MemoType: "74797065", // hex("type")
MemoData: "6164645f676173" // hex("add_gas")
},
},
{
Memo: {
MemoType: "6d73675f6964", // hex("msg_id")
MemoData: "63376336353364326466383336323263323737646135356466376665363436363039386635626332653436366531323531663432373732643037303136633863"
},
},
],
...
}

A full example can be found here.

The key function in this example is the addGas() function written out as follows.

async function callContract(config, wallet, client, chain, options, args) {
await client.sendPayment(
wallet,
{
destination: chain.contracts.AxelarGateway.address,
amount: parseTokenAmount(options.token, options.amount),
memos: [
{ memoType: hex('type'), memoData: hex('add_gas') },
{ memoType: hex('msg_id'), memoData: hex(options.msgId.toLowerCase().replace('0x', '')) },
],
},
options,
);
}

This function constructs the memo that will be used for the transaction, passing in the type and msg_id.

To interact with the example run the following command

Terminal window
node xrpl/add-gas.js -e devnet-amplifier -n xrpl-dev --amount 0.1 --token XRP --msgId <msg-id>

This will send 0.1 XRP to the underfunded transaction with the msg-id that you specify.

Edit on GitHub