Axelar<>XRPL Integration Overview

The Axelar<>XRPL integration enables message passing between XRPL and other Axelar-supported blockchains via the Axelar Amplifier network. Amplifier is a set of CosmWasm contracts on the Axelar chain that lets any external chain connect to the Axelar interchain network without changes to Axelar's core. Each connected chain plugs in through a small group of per-chain contracts (Gateway, Voting Verifier, and Multisig Prover) used to verify and route messages to a destination chain. The integration allows transferring XRP and tokens issued on XRPL to other supported chains and back. It also supports bridging non-XRPL tokens between their native blockchain and XRPL, as well as sending messages to other chains (General Message Passing).

XRPL is different from other chain integrations because it has no smart contract layer. Instead, the XRPL gateway is implemented as a multisig account whose signers are the active Axelar verifier set, and that signer set is kept in sync by SignerListSet transactions on XRPL. Users initiate cross-chain actions by sending an XRPL Payment to that account with structured Memos; the Amplifier side then processes the transaction through the three XRPL-specific contracts (xrpl-gateway, xrpl-voting-verifier, xrpl-multisig-prover), along with the generic Amplifier contracts and the ITS Hub.

High Level Architecture

Incoming Flow

flowchart TD
subgraph Axelar
	G1{"xrpl-gateway"}
    G2{"Gateway"}
	Vo{"xrpl-voting-verifier"}
	R{"Router"}
    S{"Service Registry"}
end

Relayer --"VerifyMessages([M1,M2])"-->G1
G1 --"VerifyMessages([M1,M2])"--> Vo
Vo --"ActiveVerifiers"--> S
Verifiers --"Vote(poll_id, votes)"--> Vo

Relayer --"RouteIncomingMessages([M1,M2])"-->G1
G1 --"RouteMessages([M1,M2])"-->R
R --"RouteMessages([M1,M2])"-->G2

Outgoing Flow

flowchart TD
subgraph Axelar
    G2{"xrpl-gateway"}
    P{"xrpl-multisig-prover"}
    M{"Multisig"}
    S{"Service Registry"}
end

Relayer --"ConstructProof(cc_id, payload)"-->P
P --"OutgoingMessages([M1.id,M2.id])"-->G2
P --"ActiveVerifiers"-->S
P --"StartSigningSession(verifier_set_id, payload_hash)"-->M
Verifiers --"SubmitSignature(session_id, signature)"-->M
Relayer --"Proof(multisig_session_id)" --> P
P --"Multisig(session_id)"-->M

For the message-level walkthroughs of each direction, see Message Flows to and from XRPL.

Contract Overview

Gateway

xrpl-gateway is the entry and exit point on Axelar for XRPL traffic. It accepts inbound XRPL messages from the relayer for verification, routes verified user-payment variants through the router (and through the ITS Hub for token transfers), confirms gas top-ups, and stores outbound messages from the router for the multisig prover to pick up. It also acts as the ITS edge for XRPL: it holds the token-id registry, queries the ITS Hub for destination decimals, scales amounts between chains, and tracks accrued gas per token id.

Voting Verifier

xrpl-voting-verifier runs stake-weighted polls over XRPL transactions for all five XRPLMessage variants. When the xrpl-gateway calls VerifyMessages, the voting verifier opens a poll, takes a weighted snapshot of the active verifier set from the service registry, and emits a messages_poll_started event. Off-chain verifiers cast their votes; once quorum is reached, the contract emits wasm-quorum_reached, which is what triggers the relayer's next action (routing or confirmation, depending on the message variant).

Multisig Prover

xrpl-multisig-prover builds the XRPL transactions that the multisig account needs to send: Payment for token deliveries, SignerListSet for verifier set rotation, TicketCreate to refill the ticket pool, and TrustSet to open trust lines for XRPL-native IOUs. It opens signing sessions on the generic multisig contract, manages the available-ticket pool and sequence numbers, tracks the XRP fee reserve that funds outbound transaction fees, and holds the current/next verifier set.

Router

router maintains the registry of connected chains and their gateways and routes verified messages between them. The xrpl-gateway is a registered chain gateway and uses it both inbound (delivering verified messages on toward the ITS Hub or directly to a destination chain) and outbound (receiving messages whose destination is XRPL from other source chains).

Multisig

multisig is the generic CosmWasm contract on Axelar that coordinates signing sessions. The xrpl-multisig-prover creates a session whenever it builds a new XRPL transaction; verifiers submit per-signer signature shares; once quorum is met the contract emits signing_completed and publishes the assembled signatures.

Service Registry

service-registry tracks verifier bonding, authorization, and per-chain support. The xrpl-voting-verifier and xrpl-multisig-prover both query it for the active verifier set when opening polls and signing sessions.

Coordinator

coordinator tracks per-chain prover registrations and the verifier sets active on each chain. It serves as the unbonding gate for the service registry, so verifiers cannot withdraw stake while still active on any chain.

Axelarnet Gateway

The Axelarnet Gateway (in the upstream repo) is the contract through which Axelar-resident contracts such as the ITS Hub send and receive cross-chain messages via the generic router. Every ITS message in either direction between XRPL and another chain passes through it.

ITS Hub

The ITS Hub (in the upstream repo) is the chain-agnostic router for Interchain Token Service messages. It validates the source ITS edge, tracks per-chain token supply, and rescales amounts between chains with different decimal precisions. The xrpl-gateway sends SEND_TO_HUB envelopes to this contract when translating an XRPL inbound interchain transfer; the Hub re-wraps them as RECEIVE_FROM_HUB for delivery to the destination chain. The same machinery runs in reverse when a source chain sends an ITS transfer destined for XRPL.

Message Semantics

The xrpl-gateway accepts an XRPL-native union type, XRPLMessage, whose variants capture the five categories of XRPL transactions the bridge cares about. User-facing variants like InterchainTransferMessage and CallContractMessage are translated into generic router_api::Message objects and forwarded through the router. AddGasMessage is also user-initiated: it tops up gas for an existing cross-chain message and is confirmed on the gateway, but it does not become a router message. Operational/control variants such as AddReservesMessage and ProverMessage are handled directly by the XRPL-specific contracts and also never become router messages.

The full definition lives in upstream packages/xrpl-types/src/msg.rs.

#![allow(unused)]
fn main() {
pub enum XRPLMessage {
    InterchainTransferMessage(XRPLInterchainTransferMessage),
    CallContractMessage(XRPLCallContractMessage),
    AddGasMessage(XRPLAddGasMessage),
    AddReservesMessage(XRPLAddReservesMessage),
    ProverMessage(XRPLProverMessage),
}
}

CrossChainId construction on XRPL

The CrossChainId struct itself (defined in upstream packages/router-api/src/primitives.rs) is the generic Amplifier identifier shared across all chains. What differs is how its message_id field is populated for XRPL traffic: for InterchainTransferMessage and CallContractMessage, the xrpl-gateway constructs the cc_id as:

#![allow(unused)]
fn main() {
CrossChainId {
    source_chain: "xrpl",
    message_id: "0x<hex of the 32-byte XRPL tx hash>",
}
}

The transaction hash is XRPL's SHA-512-half of the signed transaction (32 bytes), rendered as a 0x-prefixed hex string. The other three variants do not become generic router messages and therefore have no associated CrossChainId.

Inbound user payments

InterchainTransferMessage and CallContractMessage are the two variants reported by the relayer when a user sends an XRPL Payment to the multisig gateway account. They are verified by the xrpl-voting-verifier and routed by the xrpl-gateway.

#![allow(unused)]
fn main() {
pub struct XRPLInterchainTransferMessage {
    pub tx_id: HexTxHash,                  // the user's XRPL Payment tx hash
    pub source_address: XRPLAccountId,     // the XRPL user (20-byte account id)
    pub destination_chain: ChainNameRaw,
    pub destination_address: nonempty::String, // raw bytes of destination addr, hex
    pub payload_hash: Option<[u8; 32]>,    // optional, only set if a payload memo is present
    pub transfer_amount: XRPLPaymentAmount, // tokens to deliver (excluding gas)
    pub gas_fee_amount: XRPLPaymentAmount,  // gas, in the same denomination
}

pub struct XRPLCallContractMessage {
    pub tx_id: HexTxHash,
    pub source_address: XRPLAccountId,
    pub destination_chain: ChainNameRaw,
    pub destination_address: nonempty::String,
    pub payload_hash: [u8; 32],            // required; pure GMP always has a payload
    pub gas_fee_amount: XRPLPaymentAmount, // equal to the Payment Amount (no transfer component)
}
}

Inbound user/operator top-ups

AddGasMessage lets a user top up the gas allocation of an existing cross-chain message they previously initiated. AddReservesMessage lets an operator (typically the relayer itself) top up the multisig account's XRP reserve. Both are verified by the xrpl-voting-verifier; AddGasMessage is confirmed on the xrpl-gateway, AddReservesMessage on the xrpl-multisig-prover. Neither produces a router_api::Message.

#![allow(unused)]
fn main() {
pub struct XRPLAddGasMessage {
    pub tx_id: HexTxHash,
    pub msg_id: HexTxHash,             // tx_id of the original message being topped up
    pub amount: XRPLPaymentAmount,
    pub source_address: XRPLAccountId,
}

pub struct XRPLAddReservesMessage {
    pub tx_id: HexTxHash,
    pub amount: u64,                   // XRP-only, denominated in drops
}
}

Outbound prover confirmation

ProverMessage is the inverse direction: the multisig account has just submitted a prover-built XRPL transaction (a Payment, SignerListSet, TicketCreate, or TrustSet) and the relayer is reporting that inclusion back to Axelar so the xrpl-multisig-prover can release the ticket, clear the payload, and (for SignerListSet) promote the new verifier set.

#![allow(unused)]
fn main() {
pub struct XRPLProverMessage {
    pub tx_id: HexTxHash,           // the on-ledger tx hash
    pub unsigned_tx_hash: HexTxHash, // the canonical-serialization hash; used to match the prover's stored payload
}
}

Supporting XRPL types

These types appear inside the message variants above and are also defined in packages/xrpl-types.

#![allow(unused)]
fn main() {
/// 20-byte XRPL account id. Display form is the base58 classic address (rXXX...).
pub struct XRPLAccountId([u8; 20]);

/// Either native XRP (drops) or an issued currency amount.
/// See "Supported Values and Tokens" for the full conversion rules.
pub enum XRPLPaymentAmount {
    Drops(u64),                          // native XRP
    Issued(XRPLToken, XRPLTokenAmount),  // IOU
}

pub struct XRPLToken {
    pub issuer: XRPLAccountId,
    pub currency: XRPLCurrency,
}

/// 160-bit XRPL currency code (3-char ASCII or 40-char hex form).
pub struct XRPLCurrency([u8; 20]);

/// XRPL's mantissa+exponent format. See "Supported Values and Tokens"
/// for the canonicalization rules.
pub struct XRPLTokenAmount {
    mantissa: u64,
    exponent: i64,
}
}

For the full conversion rules between these XRPL amount types and Uint256 (the form the ITS Hub uses), and for the constraints on currency codes, addresses, and trust lines, see Supported Values and Tokens on XRPL.

Message Flows to and from XRPL

This document gives a high level walkthrough of what happens when a cross chain message moves from XRPL to another chain connected to Axelar, or from another chain to XRPL. Each on-Axelar contract mentioned below links inline to its per-contract page.

The integration uses the same Axelar Amplifier protocol that other chains plug into, but XRPL has a unique design because it does not have smart contracts. Everything that would normally be done by an on chain "gateway" contract on the external chain is done instead by a single XRPL multisig account, controlled by the active Axelar verifier set. So, unlike most other integrations where the chain-specific logic lives on the edge chain contracts, in this case the XRPL-specific logic lives in three CosmWasm contracts deployed on Axelar.

For unfamiliar XRPL or Axelar terminology, the glossary gives a short definition. Linked terms throughout the document point into that glossary.

Three flows are described below. Flow A covers a pure general message passing (GMP) call originating on XRPL with no token transfer attached. Flow B covers an Interchain Token Service (ITS) transfer from XRPL to a destination chain. Flow C covers an ITS transfer in the reverse direction: from a source chain to XRPL. There is no inbound pure GMP flow because XRPL has no executable destination; only ITS token deliveries land on XRPL.

Three Axelar resident contracts do the XRPL specific work

The three XRPL contracts absorb every responsibility that would otherwise live in chain side contracts on a smart contract chain:

  • xrpl-gateway is the entry and exit point for XRPL messages on Axelar. It is also the ITS edge for XRPL (the role that on other chains is split out into a separate contract). It accepts inbound messages from a relayer, asks the voting verifier to confirm them, translates them into ITS Hub messages, and forwards them through the router. In the other direction it stores outbound messages received from the router for the prover to pick up.
  • xrpl-voting-verifier runs polls over XRPL transactions. The off chain XRPL handler reads the ledger via JSON RPC and votes.
  • xrpl-multisig-prover builds the XRPL transactions that the multisig account needs to send, opens signing sessions on the generic multisig contract, and tracks XRPL specific state (the ticket pool, the reserve budget, trust lines, and the current and next verifier set).

The rest of the protocol is unchanged from the upstream Amplifier. The generic router, multisig, service-registry, and coordinator contracts behave exactly as they do for any other chain. The Axelarnet Gateway and the ITS Hub live in the canonical axelar-amplifier repository and are shared infrastructure used by every chain.

Off chain, two distinct processes do the work that bridges the two ledgers:

  • The verifiers running ampd: the same off chain daemon every Amplifier verifier already runs for every chain. For XRPL they additionally run an XRPL specific handler binary that watches the Axelar chain for poll events, looks up the cited XRPL transactions via JSON RPC, and votes. The same handler also signs outbound XRPL transactions when the multisig prover opens a signing session.
  • The XRPL relayer: a set of Rust services that monitor the XRPL ledger for activity on the multisig account, push relevant transactions to the Axelar chain contracts, and submit prover produced XRPL transactions back to the ledger. Relaying is permissionless; anyone can run the same software.

How a user signals intent on XRPL

Because XRPL cannot host a callContract style entry point, the user expresses their intent through the only primitive XRPL gives them: an XRPL Payment transaction. The payment is addressed to the multisig account. Structured Memos on the payment tell the bridge what to do.

For an interchain token transfer the memo schema is:

Memo keyRequiredMeaning
typeyesThe literal interchain_transfer (or call_contract for a pure GMP call)
destination_chainyesThe case sensitive chain name
destination_addressyesThe recipient address on the destination chain, hex encoded, without any leading 0x
gas_fee_amountyesHow much of the payment is gas, in the same denomination as the Amount field
payloadoptionalAn ABI encoded payload, only present for general message passing calls that carry data

The full Amount field of the Payment is the sum of the transfer amount and the gas fee. The gas fee is later deducted on Axelar by the gateway when accounting accrued gas per token id.

Flow A: GMP from XRPL to a destination chain

A user on XRPL wants to make a pure general message passing call to a contract on another Axelar connected chain. No tokens are transferred; only an opaque payload is delivered to the destination contract. This is signalled with the call_contract memo type. The entire Amount of the XRPL Payment is treated as the gas fee for the cross chain call.

Sequence

sequenceDiagram
    autonumber
    actor User
    participant XRPL as XRPL ledger
    participant Relayer as XRPL relayer
    box LightYellow Axelar
    participant XGW as xrpl-gateway
    participant XVV as xrpl-voting-verifier
    participant Router
    end
    actor Verifiers
    participant Dest as Destination chain side

    User->>XRPL: Payment to multisig with Memos (type=call_contract, payload, ...)
    Relayer->>XRPL: poll account_tx
    XRPL-->>Relayer: new payment detected
    Relayer->>XGW: VerifyMessages (CallContractMessage)
    XGW->>XVV: VerifyMessages
    XVV-->>Verifiers: emit messages_poll_started
    Verifiers->>XRPL: tx RPC lookup
    XRPL-->>Verifiers: validated tx data
    Verifiers->>XVV: Vote
    Note over XVV: quorum reached
    XVV-->>Relayer: wasm-quorum_reached event (delivered by the Axelar GMP API)
    Relayer->>XGW: RouteIncomingMessages
    XGW->>XGW: verify payload hash, build plain Message
    XGW->>Router: route message
    Router->>Dest: deliver via destination gateway
    Note over Dest: destination prover signs, relayer submits,<br/>destination contract executes the call

General steps

  1. User Payment to the multisig account. The user signs and submits an XRPL Payment to the multisig account. Required memos: type=call_contract, destination_chain, destination_address (raw bytes of the destination contract, hex encoded), gas_fee_amount (equal to Amount, since the whole payment is gas), and payload (ABI encoded calldata for the destination contract). The XRPL transaction hash becomes the cross chain id for the rest of the flow.

  2. Off chain detection. The XRPL relayer polls account_tx on the multisig account, sees the new payment, classifies it by memo type as a CallContractMessage, and calls VerifyMessages on the xrpl-gateway.

  3. The voting verifier opens a poll. The gateway forwards the CallContractMessage to the xrpl-voting-verifier, which takes a weighted snapshot of the active verifiers for chain xrpl and emits a messages_poll_started event.

  4. Verifiers vote. Each verifier running ampd and the XRPL handler fetches the cited XRPL transaction and checks: the transaction is validated, its destination is the multisig account, delivered_amount equals Amount (defends against tfPartialPayment), the memos parse correctly, the gas_fee_amount equals Amount (no transfer component), and keccak256(payload) matches the message's payload hash. The handler casts a Vote. As soon as quorum is reached, the voting verifier emits wasm-quorum_reached.

  5. Routing to the destination chain. The Axelar GMP API pushes the wasm-quorum_reached task to the relayer, which calls RouteIncomingMessages on the xrpl-gateway. For each verified CallContractMessage the gateway builds a plain router_api::Message carrying the original source_address (the XRPL user), the user supplied destination_chain and destination_address, and payload_hash = keccak256(payload). The message is handed straight to the generic router.

  6. Outbound GMP leg. From here the flow is identical to any other outbound Amplifier GMP. The router delivers to the destination chain's gateway slot; the destination chain's multisig-prover constructs execute_data for that chain's gateway; the generic multisig contract collects signatures; a relayer broadcasts the execute_data to the destination's Axelar Gateway. The upstream Amplifier overview describes this leg.

  7. Delivery on the destination chain. The destination's Axelar Gateway approves the message; the executor invokes the application contract at destination_address with the payload.

About ITS

The remaining two flows use the Interchain Token Service (ITS), a chain agnostic protocol layered on top of Amplifier GMP that handles the deployment, linking, and transfer of tokens across chains. ITS adds two pieces to a generic GMP message: a per-token identifier shared across chains, and a central routing contract on Axelar (the ITS Hub) that validates, tracks per chain supply, and rescales amounts between chains with different decimal precisions. The protocol is described in the upstream ITS Hub repository.

A very common ITS interaction on XRPL is moving native XRP to a smart contract chain and back, using Wrapped XRP (wXRP) as the destination representation such as XRPL-EVM:

  • From XRPL to XRPL-EVM: a user pays native XRP to the XRPL multisig account; the XRP is locked in the multisig (the multisig is the gateway, not the issuer); on the destination chain, the local ITS edge mints wXRP to the recipient.
  • From XRPL-EVM back to XRPL: a user calls interchainTransfer on the XRPL-EVM ITS edge with their wXRP; the wXRP is burned locally; the XRPL multisig sends an equivalent amount of native XRP from its balance to the recipient on XRPL.

The same mechanism applies to other tokens: an XRPL-native IOU bridged out is locked on XRPL and minted on the destination, and the reverse direction is the symmetric burn-and-unlock. The two flows below describe each direction in detail.

Flow B: ITS transfer from XRPL to a destination chain

A user on XRPL wants to send value (native XRP or an IOU) to a recipient on another Axelar connected chain. In this flow XRPL is the source chain and the other chain is the destination. The route goes through the ITS Hub.

Sequence

sequenceDiagram
    autonumber
    actor User
    participant XRPL as XRPL ledger
    participant Relayer as XRPL relayer
    box LightYellow Axelar
    participant XGW as xrpl-gateway
    participant XVV as xrpl-voting-verifier
    participant Router
    participant Hub as ITS Hub
    end
    actor Verifiers
    participant Dest as Destination chain side

    User->>XRPL: Payment to multisig with Memos (type=interchain_transfer, ...)
    Relayer->>XRPL: poll account_tx
    XRPL-->>Relayer: new payment detected
    Relayer->>XGW: VerifyMessages (InterchainTransferMessage)
    XGW->>XVV: VerifyMessages
    XVV-->>Verifiers: emit messages_poll_started
    Verifiers->>XRPL: tx RPC lookup
    XRPL-->>Verifiers: validated tx data
    Verifiers->>XVV: Vote
    Note over XVV: quorum reached
    XVV-->>Relayer: wasm-quorum_reached event (delivered by the Axelar GMP API)
    Relayer->>XGW: RouteIncomingMessages
    XGW->>Hub: query destination decimals
    Hub-->>XGW: decimals
    XGW->>XGW: scale amount, wrap as SEND_TO_HUB
    XGW->>Router: route message
    Router->>Hub: deliver via Axelarnet Gateway
    Hub->>Hub: validate, decrement source supply,<br/>rescale, wrap as RECEIVE_FROM_HUB
    Hub->>Router: route to destination chain
    Router->>Dest: deliver via destination gateway
    Note over Dest: destination prover signs,<br/>relayer submits, ITS edge mints or unlocks
    Dest-->>User: tokens delivered

General steps

  1. User Payment to the multisig account. The user signs and submits an ordinary XRPL Payment transaction whose destination is the multisig account. The transaction's memos encode the destination chain, the destination address (hex encoded raw bytes, since address formats differ across chains), the amount to allocate as gas, and optionally a payload for a GMP call. The XRPL transaction hash becomes the cross chain id for the rest of the flow. The user's own XRPL key signs the transaction; the multisig account is the recipient, not the sender.

  2. Off chain detection. The XRPL relayer polls the multisig account's transaction history via XRPL's account_tx RPC. When it detects a new payment, it classifies it by memo type and constructs an InterchainTransferMessage (or CallContractMessage for a pure GMP payload). It then calls VerifyMessages on the xrpl-gateway.

  3. The voting verifier opens a poll. The gateway forwards the messages to the xrpl-voting-verifier, which takes a snapshot of the active verifier set from the service registry for chain xrpl and opens a poll. A messages_poll_started event is emitted, carrying the candidate messages.

  4. Verifiers vote. Each verifier running ampd and the XRPL handler binary sees the poll event, fetches the cited XRPL transaction over its own XRPL JSON RPC connection, and checks several invariants: the transaction is validated, its destination is the multisig account, delivered_amount equals Amount (which guards against the XRPL tfPartialPayment exploit), the memos parse correctly, and the resulting message matches the one under poll. The handler then casts a Vote. The moment a vote pushes the weighted tally past the quorum threshold, the voting verifier emits a wasm-quorum_reached event and the message becomes queryable as SucceededOnSourceChain (the formal EndPoll call happens later, on a separate path).

  5. Routing into the ITS Hub. Once the Axelar GMP API picks up the wasm-quorum_reached event, RouteIncomingMessages is called on the xrpl-gateway. For each verified message the gateway looks up the token id the message refers to, queries the ITS Hub for the destination chain's registered decimals so it can scale the amount correctly, and constructs a HubMessage::SendToHub(InterchainTransfer) payload. The wrapped message is handed to the generic router, which delivers it to the Axelarnet Gateway. The Axelarnet Gateway hands it to the ITS Hub for processing.

  6. ITS Hub processing. The Hub validates that the source address is the registered XRPL ITS edge, decrements the per chain supply tally for the token, applies any further decimal scaling required by the destination chain, checks that neither side is frozen, and re wraps the inner message as HubMessage::ReceiveFromHub(InterchainTransfer). It then calls CallContract on the Axelarnet Gateway again, addressed to the ITS edge on the destination chain.

  7. Second GMP leg leaves Axelar. From this point the message is an ordinary outbound Amplifier GMP toward the destination chain. The destination chain's multisig-prover constructs an execute_data blob, the generic multisig contract collects signatures from the active verifier set, and a relayer broadcasts the resulting calldata to the destination chain's gateway. This is the same machinery used by every other Amplifier chain. The upstream Amplifier overview describes this leg in detail.

  8. Delivery on the destination chain. The destination chain's Axelar Gateway calls into the ITS edge contract, which decodes RECEIVE_FROM_HUB(INTERCHAIN_TRANSFER), verifies that the original source chain is in its trusted chains list, and gives the recipient the tokens. The exact mechanism depends on the local token manager type (mint, unlock, and so on). If the original payment carried a payload memo, the ITS edge additionally calls an executable hook on the recipient contract.

Flow C: ITS transfer from a source chain to XRPL

A user on another Axelar connected chain wants to send value to a recipient on XRPL. In this flow the other chain is the source and XRPL is the destination. Because the XRPL ledger does not run arbitrary code, the destination side of the flow is the multisig account submitting a signed XRPL Payment to the recipient.

Sequence

sequenceDiagram
    autonumber
    actor User
    participant Src as Source chain side
    box LightYellow Axelar
    participant Hub as ITS Hub
    participant XGW as xrpl-gateway
    participant XMP as xrpl-multisig-prover
    participant MS as Multisig
    participant XVV as xrpl-voting-verifier
    end
    participant Relayer as XRPL relayer
    actor Verifiers
    participant XRPL as XRPL ledger
    actor Recipient

    User->>Src: interchainTransfer to xrpl
    Note over Src,Hub: standard inbound GMP leg<br/>source voting-verifier votes,<br/>router and Axelarnet Gateway route
    Src->>Hub: deliver SEND_TO_HUB(InterchainTransfer)
    Hub->>Hub: validate, increment XRPL supply,<br/>rescale, wrap as RECEIVE_FROM_HUB
    Hub->>XGW: deliver via Axelarnet Gateway and router
    XGW->>XGW: store outgoing message
    XGW-->>Relayer: wasm-routing_outgoing event<br/>(delivered by the Axelar GMP API as a ConstructProofTask)
    Relayer->>XMP: ConstructProof
    XMP->>XGW: read outgoing message
    XGW-->>XMP: message
    XMP->>XMP: allocate ticket*, build XRPL Payment tx
    XMP->>MS: StartSigningSession
    MS-->>Verifiers: emit signing_started
    Verifiers->>MS: SubmitSignature
    Note over MS: quorum reached
    MS-->>Relayer: wasm-signing_completed event<br/>(delivered by the Axelar GMP API as a GatewayTxTask, with the signed XRPL tx blob)
    Relayer->>XRPL: submit signed Payment
    XRPL->>Recipient: native XRP or IOU delivered
    Relayer->>XRPL: observe outbound multisig tx
    XRPL-->>Relayer: tx confirmed in ledger
    Relayer->>XGW: VerifyMessages with ProverMessage
    XGW->>XVV: VerifyMessages
    XVV-->>Verifiers: emit messages_poll_started
    Verifiers->>XVV: Vote
    Note over XVV: quorum reached
    XVV-->>Relayer: wasm-quorum_reached event (delivered by the Axelar GMP API)
    Relayer->>XMP: ConfirmProverMessage
    XMP->>XMP: release ticket, clear payload

* For background on why XRPL transactions use tickets and how the pool is managed, see Ticket in the glossary.

General steps

  1. User call on the source chain. The user invokes the ITS edge contract on their source chain with an interchain transfer call whose destination chain is xrpl and whose destination address is the recipient's classic XRPL address encoded as raw bytes. The local token manager burns or locks the user's tokens, depending on the manager type configured for that token id.

  2. First GMP leg leaves the source chain. The source ITS edge wraps an INTERCHAIN_TRANSFER payload in a SEND_TO_HUB envelope and calls callContract("axelar", ITS Hub address, payload) on the source chain's Axelar gateway. Verifiers observe the resulting on chain event, the voting verifier on the source chain side reaches quorum, the router routes the message into the Axelarnet Gateway, and the Axelarnet Gateway calls Execute on the ITS Hub. This is the same machinery used for any source chain. See the upstream Amplifier overview, the generic gateway, and the generic multisig-prover for context.

  3. ITS Hub processing. The Hub validates the source ITS edge, increments the registered supply for the XRPL token instance, rescales the amount into XRPL's six decimals (native XRP) or the IOU exponent format, checks neither side is frozen, and re wraps the inner message as HubMessage::ReceiveFromHub(InterchainTransfer). It then calls CallContract again, addressed to the xrpl-gateway on Axelar.

  4. Outgoing message lands on the xrpl-gateway. The router delivers the message to the xrpl-gateway. The gateway stores it in its OUTGOING_MESSAGES map keyed by cc_id. From this point the message is queued for inclusion in the next XRPL transaction the multisig prover builds.

  5. Prover constructs an XRPL Payment. Storing the outgoing message causes the xrpl-gateway to emit a wasm-routing_outgoing event. The Axelar GMP API picks the event up and pushes a ConstructProofTask to the relayer, which responds by calling ConstructProof(cc_id, payload) on the xrpl-multisig-prover. The prover fetches the message from the gateway, decodes ReceiveFromHub(InterchainTransfer), checks that the inner data field is empty (general message passing into XRPL is not supported, since XRPL has no executable destination), parses the destination as a classic XRPL address, converts the amount into the appropriate XRPL payment value (native drops or an IOU with a 54 bit mantissa and exponent — see Supported Values and Tokens on XRPL for the full conversion rules and constraints), allocates a ticket from its available ticket pool, and builds an XRPL Payment transaction whose sequence field is the allocated ticket.

  6. Signing session opens. The prover calls StartSigningSession on the generic multisig contract. The blob the signers must sign is the XRPL canonical serialization of the unsigned transaction. Each verifier signs a slightly different digest, because XRPL multi signing requires each signer's own XRPL account address to be appended to the canonical blob before hashing with SHA 512 half. Verifiers run the XRPL multisig handler under ampd. They read the signing session event, compute the per signer digest, hand it to tofnd for signing, and submit the resulting signature back to the multisig contract. Quorum follows the rules in service-registry.

  7. Submission to XRPL. Once the multisig session reaches quorum, the generic multisig contract emits a wasm-signing_completed event. The Axelar GMP API picks it up, assembles the collected signatures into the final multi signed XRPL transaction blob, and pushes a GatewayTxTask containing that blob to the relayer's includer. The includer submits the blob to the XRPL ledger as a Payment from the multisig account to the recipient. For native XRP this delivers drops directly. For a remote origin IOU (a token whose XRPL representation is issued by the multisig account itself), the recipient must already have a TrustSet open against the multisig account for the relevant currency code, otherwise XRPL rejects the payment.

  8. Confirmation of delivery on Axelar. Confirmation of an outbound XRPL transaction is implicit. The relayer's subscriber sees the multisig account's own outbound payment when it next polls, and the relayer reports it back to Axelar by calling VerifyMessages on the xrpl-gateway with a ProverMessage variant. The gateway forwards it to the xrpl-voting-verifier which opens a poll on this new message; verifiers confirm that the transaction landed on XRPL with the expected unsigned tx hash. Once quorum is reached, the voting verifier emits a wasm-quorum_reached event; the Axelar GMP API pushes the corresponding task to the relayer, which responds by calling ConfirmProverMessage on the xrpl-multisig-prover. The ticket used by the transaction is released back into the available pool, and the payload is cleared. If the verifiers report FailedOnChain instead of Succeeded, the ticket is still consumed, but the payload stays stored so it can be retried or the value refunded.

For unfamiliar terms used in this document, see the glossary.

Supported Values and Tokens on XRPL

This page explains everything a developer or integrator needs to know about which tokens the bridge supports, how amounts are represented on XRPL, how amounts get converted when bridging in either direction, and what XRPL-side constraints (trust lines, reserves, currency codes) you have to satisfy. The relevant source lives in packages/xrpl-types, the XRPL Gateway, and the XRPL Multisig Prover.

XRP vs IOU: the two value primitives

XRPL has exactly two primitive value types:

  • Native XRP, the chain's native currency. Always denominated in drops as a 64-bit unsigned integer. 1 XRP = 106 drops, so XRP behaves as a 6-decimal token from the bridge's point of view.
  • IOU (issued currency), XRPL's primitive for everything that is not XRP. An IOU is uniquely identified by the pair (currency_code, issuer_account). Holders track their balance through a trust line opened against the issuer.

The bridge exposes both as cross-chain assets. They share the same on-chain message envelope (the Amount field of an XRPL Payment), but their internal representation and the conversion rules between an XRPL amount and an Axelar Uint256 are different.

In the Rust types these are unified by a single enum (packages/xrpl-types/src/types.rs):

pub enum XRPLPaymentAmount {
    Drops(u64),                          // native XRP, in drops
    Issued(XRPLToken, XRPLTokenAmount),  // IOU: token id + a mantissa/exponent amount
}

pub struct XRPLToken {
    pub issuer: XRPLAccountId,
    pub currency: XRPLCurrency,
}

pub struct XRPLTokenAmount {
    mantissa: u64,
    exponent: i64,
}

XRP: native amounts in drops

XRP amounts are 64-bit unsigned integers of drops. There is a protocol-level cap on the maximum drops in circulation (and therefore on any single transfer):

ConstantValueSource
XRP_DECIMALS6packages/xrpl-types/src/types.rs
XRP_MAX_UINT100_000_000_000_000_000 (= 1017 drops, 100 billion XRP)same

When the bridge converts an amount coming from a smart-contract chain (delivered as Uint256 by the ITS Hub) into an XRPL drops amount, the multisig prover's compute_xrpl_amount checks source_amount <= XRP_MAX_UINT and otherwise returns InvalidTransferAmount. Any value above the cap is rejected at proof construction time.

IOU: 54-bit mantissa, 8-bit exponent

XRPL's IOU amounts are a custom floating-point format, documented here. The bridge stores them as (mantissa: u64, exponent: i64) and serializes them into XRPL's wire format with the following constraints (packages/xrpl-types/src/types.rs):

ConstantValue
MIN_MANTISSA1_000_000_000_000_000 (= 1015)
MAX_MANTISSA10_000_000_000_000_000 - 1 (= 1016 - 1)
MIN_EXPONENT-96
MAX_EXPONENT80
XRPL_ISSUED_TOKEN_DECIMALS15 (the assumed decimals for the canonical Uint256 → IOU conversion)

The on-wire binary layout for a non-zero IOU amount is:

1 bit  : 1 ("not XRP")
1 bit  : sign (always 1, positive)
8 bits : exponent + 97
54 bits: mantissa

(The mantissa is always normalized to MIN_MANTISSA <= mantissa <= MAX_MANTISSA, so it fits in 54 bits even though it spans roughly 15 to 16 decimal digits.)

For a zero amount XRPL uses the special encoding 0x8000000000000000.

The bridge never produces non-positive or non-canonical IOU amounts: zeros pass through as the canonical zero encoding, and any amount that would normalize outside the [MIN_MANTISSA, MAX_MANTISSA] window after exponent adjustment is rejected.

Canonicalization: turning a Uint256 into an XRPL IOU amount

When the XRPL Multisig Prover is delivering tokens to XRPL (Flow C), the ITS Hub hands it an integer amount in the source chain's decimals as a Uint256. The prover converts it to an XRPLTokenAmount via canonicalize_token_amount(amount: Uint256, decimals: u8):

  1. Start with the raw integer mantissa and an exponent of -decimals (so the represented value is mantissa * 10^(-decimals)).
  2. While mantissa < MIN_MANTISSA and exponent > MIN_EXPONENT, multiply by 10 and subtract 1 from the exponent.
  3. While mantissa > MAX_MANTISSA, divide by 10 and add 1 to the exponent (overflow if exponent would exceed MAX_EXPONENT).
  4. If exponent < MIN_EXPONENT, the value rounds to 0 and the result is the canonical zero.
  5. If exponent > MAX_EXPONENT or the mantissa cannot be normalized into the window, the operation errors.

Source: canonicalize_mantissa and canonicalize_token_amount, both in packages/xrpl-types/src/types.rs.

For the reverse direction (XRPL inbound), the XRPL Gateway calls scale_to_decimals(amount: XRPLTokenAmount, destination_decimals: u8) to convert the IOU (mantissa, exponent) into a Uint256 aligned to whatever decimal precision the destination chain registered for the token with the ITS Hub. The same function returns zero if the input mantissa is zero, multiplies by 10^(exponent + destination_decimals) when the result is integer, and divides otherwise (silently flooring sub-precision dust).

Currency codes

XRPL currency codes are 160-bit values (20 bytes) but the wire format accepts them in two human-readable forms:

FormRegexNotes
3-char ASCII^[A-Za-z0-9\?\!@#\$%\^&\*<>\(\)\{\}\[\]|]{3}$The legacy XRPL form. The 3 characters are placed at bytes [12..15] of the 20-byte value; all other bytes are zero.
40-char hex^[A-Fa-f0-9]{40}$ (excluding any string starting with 00)The "non-standard" 160-bit currency code form. Used for codes longer than 3 characters or with characters outside the legacy ASCII set.

Two values are explicitly reserved and rejected by XRPLCurrency::new:

  • The literal ASCII XRP (0x...58 52 50...), which would collide with native XRP at the wire-format level.
  • The all-zero 20-byte value (no currency).

The bridge enforces these as XRPLError::ReservedCurrency. Source: XRPLCurrency::is_reserved.

Token IDs

The bridge identifies each cross-chain token with the standard ITS 32-byte tokenId. On XRPL specifically the xrpl-gateway maintains three registries:

MapPurpose
XRPL_TOKEN_TO_LOCAL_TOKEN_ID: Map<XRPLToken, TokenId>Lookups for XRPL-native IOUs: the issuer is some XRPL account other than the multisig account, and the token has been registered via RegisterLocalToken.
XRPL_CURRENCY_TO_REMOTE_TOKEN_ID: Map<XRPLCurrency, TokenId>Lookups for remote-origin tokens: the XRPL representation is an IOU issued by the multisig account itself, registered via RegisterRemoteToken.
TOKEN_ID_TO_XRPL_TOKEN: Map<TokenId, XRPLToken>Reverse lookup.

Native XRP has its own special token id computed once at gateway instantiation: tokenId(salt = keccak256("XRP" zero-padded to 20 bytes)) with deployer rrrrrrrrrrrrrrrrrrrrrhoLvTp (XRPL's canonical null-account). This is exposed via the gateway's XrpTokenId query.

Supported token categories

Across the whole bridge, three categories of tokens can move to and from XRPL:

CategoryXRPL representationHow a user receives it on XRPLHow tokens are "minted" on XRPL outbound delivery
Native XRPDropsDrops sent by the multisig accountMultisig account pays drops out of its own balance (sourced from inbound transfers)
XRPL-native IOU (origin = XRPL, issuer ≠ multisig)IOU (currency, issuer)Multisig account sends IOU it received on inbound transfers; multisig must have a TrustSet to the issuer.Held in the multisig account's balance, sent out via Payment of that IOU
Remote-origin token (origin = another chain)IOU (currency, multisig_issuer)Multisig account mints the IOU to the recipient. Recipient must already have a TrustSet against the multisig account for that currency code.New IOU created on the fly; no external balance needed

Trust line setup

XRPL holders track IOU balances via trust lines. Two scenarios apply:

  • For XRPL-native IOUs (issuer is some external XRPL account), the multisig account itself must have a TrustSet open against the issuer before it can hold the IOU and pay it back out. Operators trigger this by calling TrustSet { token_id } on the XRPL Multisig Prover, which builds and signs an XRPL TrustSet transaction.
  • For remote-origin tokens (issuer is the multisig account), the recipient on XRPL must have a TrustSet open against the multisig account before any inbound delivery can succeed. Without it the XRPL ledger rejects the Payment. This is a user-side requirement; the bridge does not open trust lines on the recipient's behalf.

Each trust line is also an "owned object" on the XRPL ledger and therefore consumes an owner reserve on the holder.

Address formats

XRPL accounts are referenced two ways in the bridge:

  • In the on-XRPL world, as classic addresses: base58 strings starting with r, for example rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY. Underneath this is a 20-byte account id (RIPEMD160(SHA256(public_key))).
  • Inside the on-Axelar contracts, as the type-safe XRPLAccountId (20 bytes). Serialization to/from the classic-address string form is handled by the xrpl_account_id_string serde module.

When a non-XRPL chain wants to send tokens to an XRPL recipient (Flow C), it uses the recipient's classic-address bytes encoded as the ITS destination_address. The XRPL Multisig Prover parses the bytes back into an XRPLAccountId at proof construction time and rejects malformed addresses.

Decimal handling across chains

The ITS Hub tracks per-chain decimals for every token in the TokenInstance map. Each chain registers its native decimals when the token is first deployed or linked there:

Chain categoryDecimals registered with the ITS HubNotes
XRPL (XRP)6Matches the drops representation.
XRPL (IOU)15The XRPL_ISSUED_TOKEN_DECIMALS constant. Used as the assumed precision for the Uint256 ↔ mantissa/exponent conversion.
Smart-contract chainsWhatever each chain registers (e.g., 18 for most EVM ERC-20s, 6 for USDC variants, 9 for Solana SPL, …)Defined per token, per chain.

When a transfer crosses a decimal boundary, the ITS Hub rescales the integer amount before forwarding. For XRPL the actual canonicalization into the mantissa/exponent form (or the truncation into drops) happens at the XRPL Multisig Prover, using the per-chain decimals it queries from the gateway. The reverse direction (XRPL outbound, Flows A and B) is symmetric: the XRPL Gateway queries the destination chain's decimals via the ITS Hub before scaling and wrapping the message.

Tickets

XRPL enforces a strict per-account Sequence number on every transaction (the same role an EVM nonce plays). For the multisig account that backs the bridge, this would normally mean only one transaction can be in flight at a time, and confirmations would have to land strictly in order. To get around that, the XRPL Multisig Prover uses tickets: pre-allocated sequence numbers that the multisig account reserves in advance, drawn from a pool of up to 250.

Tickets let many bridge transactions be signed and submitted in parallel, and let them confirm out of order. Operations that fundamentally have to be serial (pool refill, verifier set rotation, trust line management) still use a plain sequence number.

Transaction typeSequence fieldWhy
PaymentTicketMany in flight at once; can confirm out of order
TicketCreatePlain sequenceRefills the ticket pool; must be strictly ordered
SignerListSetPlain sequenceVerifier set rotation; one at a time
TrustSetPlain sequenceOne trust line at a time

Ticket assignment

At any point a set of available tickets is tracked by the prover: ticket numbers that have been created with a TicketCreate transaction but that are not known to have been consumed by a transaction that made it to the XRPL ledger. New outbound transactions get assigned available tickets in order until the pool is exhausted.

Confirmed tickets Available tickets
tx1
tx2
tx3
tx4
tx5
 
 
 
       
    Last assigned ticket    Next sequence number

If the number of available tickets falls below a configurable threshold, the prover constructs a new TicketCreate transaction (see Ticket creation flow below).

Ticket reuse under contention

If transactions are produced faster than the ticket pool can be refilled, ticket assignment wraps around to the first available ticket (similar to a round-robin scheduler). This can lead to multiple in-flight transactions being assigned the same ticket number:

tx1
tx2
tx3
tx4
tx8
tx5
tx6
tx7
 
       
   Last assigned ticket     Next sequence number

This is safe because of how XRPL handles ticket consumption: only one of the transactions sharing a ticket will ultimately be included in the ledger. Once that happens, the ticket is consumed permanently by that single transaction; every other transaction that tried to use the same ticket will be rejected by the ledger.

tx1
tx2
tx3
tx8
tx5
tx6
tx7
 
       
   Last assigned ticket     Next sequence number

Ticket creation flow

XRPL limits the number of available tickets at any given time to 250. When the size of the prover's ticket pool drops below a configurable threshold, a new TicketCreate transaction must be submitted to top it back up.

The flow:

  1. A Payment transaction is confirmed on the XRPL ledger (and observed on Axelar through the standard ProverMessage verification path), which reduces the number of available tickets below the creation threshold.
  2. The relayer requests a new TicketCreate transaction from the XRPL Multisig Prover.
  3. The prover serializes a TicketCreate that asks the ledger to create 250 - len(available_tickets) new tickets. There may actually be fewer usable tickets on XRPL than the prover currently knows about (in-flight transactions that have not been confirmed yet on Axelar), but there can never be more, so this bound is safe.
  4. Axelar verifiers sign the new TicketCreate transaction through the standard multi-sign session.
  5. The relayer submits the signed transaction to XRPL.
  6. Once the transaction is included in a validated ledger, the relayer reports the inclusion back to Axelar as a ProverMessage.
  7. Axelar verifiers vote on whether the XRPL transaction has been finalized.
  8. If the poll succeeds, the newly created tickets become available to be assigned to subsequent Payment transactions.

Fee reserve

In addition to the ticket pool, the prover tracks a FEE_RESERVE counter (in XRP drops) that represents the multisig account's available XRP for paying transaction fees. Every new transaction the prover builds is gated by ensure_sufficient_fee_reserve, which checks the budget against:

xrpl_base_reserve + xrpl_owner_reserve * (251 + trust_line_count) + tx_fee

The + 251 term accounts for the maximum 250 tickets plus the multisig account's own SignerList, each of which counts as an "owned object" on XRPL and contributes to the reserve requirement. The reserve is topped up via ConfirmAddReservesMessage after the XRPL Voting Verifier confirms an operator-initiated XRP top-up AddReservesMessage.

Firewalling and Flow Limits

The Axelar Amplifier protocol's hub-and-spoke topology is the basis of a security property called firewalling: a single compromised chain cannot exfiltrate more of a token from the bridged system than has legitimately been deposited into it. This page explains how the property is enforced and when it applies.

The hub-and-spoke topology

Every ITS-routed cross-chain transfer passes through the ITS Hub on Axelar. No two connected chains exchange tokens directly: a transfer from chain A to chain B is always two GMP legs glued together at the Hub:

A's ITS edge  →  axelarnet-gateway  →  ITS Hub  →  axelarnet-gateway  →  B's ITS edge
              ────── leg 1 ──────                 ────── leg 2 ──────

This shape means the Hub sees every interchain transfer in the ecosystem, exactly once. That central observation point is what lets the Hub enforce a global invariant per (chain, tokenId).

The Hub supply invariant

Per (chain, tokenId) the Hub stores a TokenInstance (contracts/interchain-token-service/src/state.rs):

#![allow(unused)]
fn main() {
pub struct TokenInstance {
    pub supply: TokenSupply,
    pub decimals: u8,
}

pub enum TokenSupply {
    /// The total token supply bridged to this chain.
    /// ITS Hub will not allow bridging back more than this amount of the
    /// token from the corresponding chain.
    Tracked(Uint256),
    Untracked,
}
}

On every InterchainTransfer the Hub runs apply_to_transfer:

#![allow(unused)]
fn main() {
interceptors::subtract_supply_amount(storage, &source_chain, &transfer)?;
let transfer = interceptors::apply_scaling_factor_to_amount(...)?;
interceptors::add_supply_amount(storage, &destination_chain, &transfer)?;
}

The subtract_supply_amount interceptor calls supply.checked_sub(amount) on the source chain's TokenInstance. If Tracked and the subtraction would underflow, the Hub raises:

#![allow(unused)]
fn main() {
#[error("token supply invariant violated for token {token_id} on chain {chain}")]
TokenSupplyInvariantViolated { token_id: TokenId, chain: ChainNameRaw },
}

and the entire transfer reverts. If Untracked, the subtraction is a no-op.

The invariant maintained per (chain, token) when Tracked is therefore:

Total bridged INTO the chain − Total bridged OUT of the chain ≥ 0

Equivalently: the Hub will not let more of a token leave a chain via ITS than has been bridged in via ITS. This is firewalling.

Why this protects against a single-chain failure

Suppose a chain's ITS edge is compromised, or a relayer crafts a fraudulent inbound report. The bad message reaches the Hub from that chain claiming a large outbound transfer (i.e., trying to "pull" tokens out of the bad chain to elsewhere). The Hub looks up the bad chain's TokenInstance.supply, attempts checked_sub(fraud_amount) from it, finds the counter does not cover it, and reverts. No tokens move. The fraud is contained to the compromised chain; the broader bridged system is untouched.

This is the property meant by "firewalling": one chain's failure does not become every chain's failure. The Hub is the chokepoint that enforces local conservation.

Edge Chain Flow Limits

Each edge chain's ITS provides the ability to configure per-window flow limits ("X tokens per Y hours") on every token it bridges. Since XRPL has no smart contracts, it cannot host this property. Its counterparts on the other side of the bridge can, though: in the XRPL to XRPL-EVM round-trip example, where XRP is bridged between the two chains, the XRPL-EVM edge can throttle the amount of XRP that flows in either direction within a 6-hour window, because XRPL-EVM is an EVM chain and runs the standard Solidity ITS.

How a limit is set on the XRPL-EVM edge

On EVM chains, the flow limit is configured by calling setFlowLimits on the InterchainTokenService contract:

// InterchainTokenService.sol (Solidity ITS edge)

/**
 * @notice Used to set a flow limit for a token manager that has the service as its operator.
 * @param tokenIds   An array of the tokenIds of the tokenManagers to set the flow limits of.
 * @param flowLimits The flowLimits to set.
 */
function setFlowLimits(bytes32[] calldata tokenIds, uint256[] calldata flowLimits)
    external
    onlyRole(uint8(Roles.OPERATOR))
{
    uint256 length = tokenIds.length;
    if (length != flowLimits.length) revert LengthMismatch();

    for (uint256 i; i < length; ++i) {
        deployedTokenManager(tokenIds[i]).setFlowLimit(flowLimits[i]);
    }
}

The call takes parallel arrays of tokenIds and flowLimits and forwards each one to the corresponding TokenManager. Internally, each TokenManager.setFlowLimit writes the new cap to its own storage slot, and from that point on, every inbound or outbound transfer is gated against the new value.

Service Registry

The service registry keeps track of the pool of verifiers that vote and sign for each chain. The core functionalities, such as registering a new service, verifier authorization and un-authorization, can only be called from a governance address. Verifier bonding and unbonding, as well as registering support for specific chains, are called by the verifier themselves. The service registry is used by ampd, the voting verifier, and the multisig prover.

The term service refers to an upper-level entity that includes several chains. The difference is in how they are related to each other, which is hierarchical: a service is like an umbrella that regulates the activities of several chains that fall under its purview. The service defines common parameters, such as verifier requirements, bonding details, and unbonding periods, which are applicable to all associated chains. Thus, a single instance of service registry is used to organize and coordinate activities across all chains.

Interface

pub enum ExecuteMsg {
    // Can only be called by governance account.
    RegisterService {
        service_name: String,
        coordinator_contract: String,
        min_num_verifiers: u16,
        max_num_verifiers: Option<u16>,
        min_verifier_bond: nonempty::Uint128,
        bond_denom: String,
        unbonding_period_days: u16,
        description: String,
    },

    // Updates modifiable fields of the service. Permission: Governance.
    UpdateService {
        service_name: String,
        updated_service_params: UpdatedServiceParams,
    },

    // Authorizes verifiers to join a service. Verifiers must still bond sufficient stake
    // to participate. Permission: Governance.
    AuthorizeVerifiers {
        verifiers: Vec<String>,
        service_name: String,
    },

    // Revoke authorization for specified verifiers. Verifiers bond remains unchanged.
    // Permission: Governance.
    UnauthorizeVerifiers {
        verifiers: Vec<String>,
        service_name: String,
    },

    // Jail verifiers. Jailed verifiers are not allowed to unbond or claim stake.
    // Permission: Governance.
    JailVerifiers {
        verifiers: Vec<String>,
        service_name: String,
    },

    // Register support for the specified chains. Permission: Specific(verifier).
    RegisterChainSupport {
        service_name: String,
        chains: Vec<ChainName>,
    },

    // Deregister support for the specified chains. Permission: Specific(verifier).
    DeregisterChainSupport {
        service_name: String,
        chains: Vec<ChainName>,
    },

    // Locks up any funds sent with the message as stake. Marks the sender as a potential
    // verifier that can be authorized. Permission: Any.
    BondVerifier { service_name: String },

    // Initiates unbonding of staked funds for the sender. Permission: Any.
    UnbondVerifier { service_name: String },

    // Claim previously staked funds that have finished unbonding for the sender. Permission: Any.
    ClaimStake { service_name: String },
}

pub enum QueryMsg {
    // Returns the weighted active verifier list for the given service and chain.
    ActiveVerifiers {
        service_name: String,
        chain_name: ChainName,
    },

    // Returns the Service config for the given service name.
    Service { service_name: String },

    // Returns details (registration, weight, supported chains) for a specific verifier.
    Verifier {
        service_name: String,
        verifier: String,
    },
}

// Fields that can be patched via UpdateService. Any None field leaves the existing value
// unchanged.
pub struct UpdatedServiceParams {
    pub min_num_verifiers: Option<u16>,
    pub max_num_verifiers: Option<Option<u16>>,
    pub min_verifier_bond: Option<nonempty::Uint128>,
    pub unbonding_period_days: Option<u16>,
}

Service Registry graph

flowchart TD
subgraph Axelar
    Vr{"Voting Verifier"}
    R{"Service Registry"}
end
OC{"Verifiers"}

Vr -- "ActiveVerifiers" --> R
OC -- "De/RegisterChainSupport" --> R
OC -- "Un/BondVerifier" --> R
OC -- "ClaimStake" --> R

Service Registry sequence diagram

sequenceDiagram
autonumber
box LightYellow Axelar
    participant Service Registry
end
actor Governance
actor Verifier

Governance->>+Service Registry: Register Service
Governance->>+Service Registry: Authorize Verifiers

Verifier->>+Service Registry: Bond Verifier
Verifier->>+Service Registry: Register Chain Support

  1. Governance registers a new service by providing the necessary parameters for the service. It can later modify these parameters via UpdateService.
  2. Governance authorizes verifiers to join the service by sending an AuthorizeVerifiers message.
  3. Verifiers bond to the service, providing stake, by sending a BondVerifier message with appropriate funds included. Note that authorizing and bonding can be done in any order.
  4. Verifiers register support for specific chains within the service by specifying service name and chain names.

Notes

  1. For the process of signing, verifiers need to register their public key in advance to be able to participate; the details of which are available in the multisig documentation.

Router Contract

The router contract is responsible for routing messages to and from registered gateways, as well as handling chain registration, gateway upgrades, chain freezing, and an emergency global routing kill switch.
Governance registers chains and upgrades gateway addresses. The router admin or governance can freeze chains and toggle the global routing kill switch.

Interface

pub enum ExecuteMsg {
    // Registers a new chain with the router. Permission: Governance.
    RegisterChain {
        chain: ChainName,
        gateway_address: Address,
        msg_id_format: MessageIdFormat,
    },
    // Changes the gateway address associated with a particular chain. Permission: Governance.
    UpgradeGateway {
        chain: ChainName,
        contract_address: Address,
    },

    // Freezes the specified chains in the specified directions. Permission: Elevated (admin or governance).
    FreezeChains {
        chains: HashMap<ChainName, GatewayDirection>,
    },
    // Unfreezes the specified chains in the specified directions. Permission: Elevated.
    UnfreezeChains {
        chains: HashMap<ChainName, GatewayDirection>,
    },

    // Emergency command to stop all amplifier routing. Permission: Elevated.
    DisableRouting,
    // Resumes routing after an emergency shutdown. Permission: Elevated.
    EnableRouting,

    // Routes each message to the gateway registered to the destination chain.
    // Permission: Specific(gateway). Called by a registered gateway.
    RouteMessages(Vec<Message>),
}

pub enum QueryMsg {
    // Returns the ChainEndpoint for the given chain.
    ChainInfo(ChainName),

    // Returns a paginated list of chains registered with the router.
    Chains {
        start_after: Option<ChainName>,
        limit: Option<u32>,
    },

    // Returns whether the global routing kill switch is currently enabled.
    IsEnabled,
}

pub struct RouterInstantiated {
    pub admin: Addr,
    pub governance: Addr,
    pub axelarnet_gateway: Addr,
}

pub struct ChainRegistered {
    pub name: ChainName,
    pub gateway: Addr,
}

pub struct GatewayInfo {
    pub chain: ChainName,
    pub gateway_address: Addr,
}

pub struct GatewayUpgraded {
    pub gateway: GatewayInfo,
}

pub struct ChainFrozen {
    pub name: ChainName,
    pub direction: GatewayDirection,
}

pub struct ChainUnfrozen {
    pub name: ChainName,
    pub direction: GatewayDirection,
}

pub struct MessageRouted {
    pub msg: Message,
}

pub struct RoutingDisabled;
pub struct RoutingEnabled;

Router graph

graph TD

ea[External Gateway A]
ra[Relayer A]
subgraph Axelar
ta[Gateway A]
c[Router]
tb[Gateway B]
m[Multisig Prover]
end
rb[Relayer B]
eb[External Gateway B]
rc[Relayer C]

ea--emit event-->ra
ra--RouteMessages-->ta
ta--RouteMessages-->c
c--emit event-->rb
rb--RouteMessages-->tb
tb--get messages-->m
m--construct proof-->rc
rc--send proof-->eb

Message Routing sequence diagram

sequenceDiagram
autonumber
participant External Gateway A
participant Relayer A
box LightYellow Axelar
participant Gateway A
participant Router
participant Gateway B
participant Multisig Prover
end
participant Relayer B
participant External Gateway B
participant Relayer C

External Gateway A->>+Relayer A: event emitted
Relayer A->>+Gateway A: gateway::ExecuteMsg::RouteMessages
Gateway A->>+Router: router::ExecuteMsg::RouteMessages
Router-->>Relayer B: emit MessageRouted event
Relayer B->>+Gateway B: gateway::ExecuteMsg::RouteMessages
Multisig Prover->>+Relayer C: constructs proof
Relayer C->>+External Gateway B: sends proof
  1. The External Gateway emits an event that is picked up by the Relayer.
  2. Relayer relays the event to the Gateway as a message.
  3. Gateway receives the incoming messages, verifies the messages, and then passes the messages to the Router.
  4. The Router emits a MessageRouted event, which is picked up by Relayer B. The relayer then calls RouteMessages on Gateway B to forward the message.
  5. The Multisig Prover takes the messages stored in the destination Gateway and constructs a proof.
  6. The Relayer sends the proof, which also contains messages, to the destination's External Gateway.

Notes

  1. External Gateways are deployed on blockchains other than Axelar, such as Ethereum and Avalanche, while internal gateways reside on Axelar's chain.

Multisig contract

This contract is used by prover contracts during proof construction to start a signing session and collect signatures from participants.

flowchart TD
subgraph Axelar
  b[Prover]
  m[Multisig]
end
s[Verifier]

b--StartSigningSession-->m
s--SubmitSignature-->m
b--Multisig (query)-->m
  • StartSigningSession: The multisig contract receives a binary message from the prover. It uses the current active verifier set to back a new signing session and then emits an event to notify signers that a message is pending signature. The prover may optionally pass a sig_verifier address that overrides the default per-signature check.
  • SubmitSignature: Each signer signs the message using their own private key and submits the signature. The contract validates that the signer is a participant in the verifier set associated with the session.
  • Multisig (query): The prover queries this to retrieve the current state of the session, the collected signatures so far, and the verifier set with participant information. The final proof is assembled by the prover once the multisig session is completed.

Signing Sequence Diagram

sequenceDiagram
participant Relayer
box LightYellow Axelar
participant Prover
participant Multisig
end
actor Signers


Relayer->>+Prover: ExecuteMsg::ConstructProof
Prover->>+Multisig: ExecuteMsg::StartSigningSession
Multisig-->>Signers: emit SigningStarted event
Multisig->>-Prover: reply with session ID
deactivate Prover
loop Collect signatures
  Signers->>+Multisig: ExecuteMsg::SubmitSignature
  Multisig-->>Relayer: emit SignatureSubmitted event
end
Multisig-->>-Relayer: emit SigningCompleted event
Relayer->>+Prover: QueryMsg::Proof
Prover->>+Multisig: QueryMsg::Multisig
Multisig-->>-Prover: reply with status, current signatures vector, and verifier set
Prover-->>-Relayer: returns data and proof

Custom signature verification

If the multisig contract does not natively support the required signature verification, the sig_verifier parameter in ExecuteMsg::StartSigningSession can be set by the prover to specify a custom signature verification contract. The custom contract must implement the following interface defined in packages/signature-verifier-api:

pub enum QueryMsg {
    #[returns(bool)]
    VerifySignature {
        signature: HexBinary,
        message: HexBinary,
        public_key: HexBinary,
        signer_address: String,
        session_id: Uint64,
    },
}

When a custom verification contract is specified, on each SubmitSignature call the multisig contract will query the custom contract's VerifySignature to validate the signature. The custom contract returns true if the signature is valid or false otherwise.

sequenceDiagram
actor Signers
box LightYellow Axelar
participant Multisig
participant SignatureVerifier
end

Signers->>+Multisig: ExecuteMsg::SubmitSignature
Multisig->>SignatureVerifier: QueryMsg::VerifySignature
SignatureVerifier->>Multisig: returns true/false
deactivate Multisig

Authorization

Prior to calling StartSigningSession, the prover contract must be authorized. To authorize one or more contracts, governance calls AuthorizeCallers with a map of contract address to chain name. Authorization can be revoked by either governance or the contract admin via UnauthorizeCallers. Each authorization is scoped to a single chain name: a contract authorized for chain X can only start signing sessions for chain X.

Governance or the admin can also globally pause and resume signing via DisableSigning / EnableSigning.

Interface

pub enum ExecuteMsg {
    // Open a new signing session. Permission: Specific(authorized) — only contracts that
    // were previously approved via AuthorizeCallers can call this.
    StartSigningSession {
        verifier_set_id: String,
        msg: HexBinary,
        chain_name: ChainName,
        // Optional address of a contract that overrides the default signature check.
        // See "Custom signature verification" above.
        sig_verifier: Option<String>,
    },

    // Submit a signature from a participant in the verifier set for the session.
    // Permission: Any (the verifier set check is part of execution).
    SubmitSignature {
        session_id: Uint64,
        signature: HexBinary,
    },

    // Register a new verifier set so future signing sessions can reference it by id.
    // Permission: Any (callable by provers as part of their rotation flow).
    RegisterVerifierSet { verifier_set: VerifierSet },

    // Register a public key for the sender. To prevent registering someone else's key, the
    // sender must sign their own address using the private key being registered.
    // Permission: Any.
    RegisterPublicKey {
        public_key: PublicKey,
        signed_sender_address: HexBinary,
    },

    // Authorize a set of contracts to call StartSigningSession. Each entry is scoped to a
    // specific chain name. Permission: Governance.
    AuthorizeCallers {
        contracts: HashMap<String, ChainName>,
    },

    // Revoke authorization for a set of contracts. Permission: Elevated (admin or governance).
    UnauthorizeCallers {
        contracts: HashMap<String, ChainName>,
    },

    // Emergency command to stop all signing. Permission: Elevated.
    DisableSigning,

    // Resumes signing after an emergency shutdown. Permission: Elevated.
    EnableSigning,
}

#[derive(QueryResponses)]
pub enum QueryMsg {
    // Returns the Multisig session state, collected signatures, and verifier set.
    #[returns(Multisig)]
    Multisig { session_id: Uint64 },

    // Returns a previously registered VerifierSet by its id.
    #[returns(VerifierSet)]
    VerifierSet { verifier_set_id: String },

    // Returns a registered public key for a verifier and key type.
    #[returns(PublicKey)]
    PublicKey {
        verifier_address: String,
        key_type: KeyType,
    },

    // Returns whether the given contract is currently authorized to start signing sessions
    // for the given chain.
    #[returns(bool)]
    IsCallerAuthorized {
        contract_address: String,
        chain_name: ChainName,
    },
}

pub struct Multisig {
    pub state: MultisigState,
    pub verifier_set: VerifierSet,
    pub signatures: HashMap<String, Signature>,
}

pub enum MultisigState {
    Pending,
    Completed { completed_at: u64 },
}

Events

pub enum Event {
    // Emitted when a new signing session is open.
    SigningStarted {
        session_id: Uint64,
        verifier_set_id: String,
        pub_keys: HashMap<String, PublicKey>,
        msg: MsgToSign,
        chain_name: ChainName,
        expires_at: u64,
    },
    // Emitted when a participant submits a signature.
    SignatureSubmitted {
        session_id: Uint64,
        participant: Addr,
        signature: Signature,
    },
    // Emitted when a signing session was completed.
    SigningCompleted {
        session_id: Uint64,
        completed_at: u64,
        chain_name: ChainName,
    },
    // Emitted when a PublicKey is registered.
    PublicKeyRegistered {
        verifier: Addr,
        public_key: PublicKey,
    },
    // Emitted when a contract is authorized to create signing sessions for a chain.
    CallerAuthorized {
        contract_address: Addr,
        chain_name: ChainName,
    },
    // Emitted when a contract is unauthorized.
    CallerUnauthorized {
        contract_address: Addr,
        chain_name: ChainName,
    },
    // Emitted when signing is globally enabled.
    SigningEnabled,
    // Emitted when signing is globally disabled.
    SigningDisabled,
}

Gateway

Not used on the XRPL side. XRPL traffic uses the XRPL Gateway, which absorbs the gateway role plus the ITS edge, the token-id registry, and gas accounting (responsibilities that on other chains live in separate contracts). The generic gateway contract documented here is the template deployed for most other connected amplifier chains, so a cross-chain message from XRPL to, say, XRPL-EVM still passes through XRPL-EVM's generic gateway on the destination side.

The name gateway used in this documentation refers to those entities which reside on axelar chain, which can also be called internal gateways. On the other hand we have external gateways, which are gateways deployed on external chains connected to Axelar.

The gateway contract is how messages enter the amplifier protocol. Here are the steps taken throughout the lifecycle of a message:

  1. User sends a message to the external gateway. We call this an incoming message.
  2. Incoming messages are sent to the gateway via VerifyMessages.
  3. The gateway calls VerifyMessages on the verifier, which submits the messages for verification (or just returns true if already verified).
  4. The messages are verified asynchronously, and the verification status is stored in the verifier.
  5. Once the messages are verified, RouteMessages is called at the gateway, which forwards the verified messages to the router.
  6. The router forwards each message to the gateway registered to the destination chain specified in the message.
  7. The prover retrieves the messages from the gateway, organizes them into a payload and submits the payload for signing.
  8. The relayer sends the signed payload to the external gateway.

Gateway graph

flowchart TD
subgraph Axelar
    Sg{"Source Gateway"}
    Dg{"Destination Gateway"}
    Rt{"Router"}
end
Rl{"Relayer"}
Eg{"External Gateway"}

Rl -- "Listen for Message:M1 Event" --> Eg
Rl -- "M1" --> Sg
Sg -- "M1" --> Rt
Rt -- "M1" --> Dg

Interface

pub struct InstantiateMsg {
    pub verifier_address: String,
    pub router_address: String,
}

pub enum ExecuteMsg {
    // Trigger verification at the linked voting verifier for any of the given messages
    // that is still unverified. Permission: Any.
    VerifyMessages(Vec<Message>),

    // Forward the given messages to the next step of the routing layer. If the messages
    // are coming in from an external chain, they must already be verified.
    // Permission: Any.
    RouteMessages(Vec<Message>),
}

pub enum QueryMsg {
    // Messages stored for delivery to the chain corresponding to this gateway, queried
    // by the multisig prover during proof construction.
    OutgoingMessages(Vec<CrossChainId>),
}

The gateway only needs to know the address of the two contracts it works with: the voting verifier and the router.

Voting Verifier

Not used on the XRPL side. XRPL messages are verified by the XRPL Voting Verifier, which handles XRPL's union message type with five variants (InterchainTransferMessage, CallContractMessage, AddGasMessage, AddReservesMessage, ProverMessage). The generic voting-verifier documented here is the template deployed for most other connected amplifier chains.

The voting verifier verifies batches of messages via RPC voting. Polls are created and votes are cast via a generic voting module, which the voting verifier uses. The generic voting module does not know the meaning of the polls, and simply returns a Poll ID to the voting verifier. The voting verifier internally maps a Poll ID to the messages in the poll, storing the results in storage. The gateway can then query the voting verifier to check message verification status.

There are two types of polls: messages polls and verifier set polls. Messages polls are used to verify incoming messages, while verifier set polls are used to verify that the external gateway has updated its stored verifier set. Verifier set polls are a necessary component of the verifier set update flow. See update and confirm VerifierSet sequence diagram for more details.

Interface

pub enum ExecuteMsg {
    // Computes the results of a poll. For all verified messages, marks them as verified
    // in storage so the gateway can later query their status. Permission: Any.
    EndPoll { poll_id: PollId },

    // Casts votes for the specified poll. Permission: Any (only active verifiers count
    // toward quorum).
    Vote { poll_id: PollId, votes: Vec<Vote> },

    // Returns a vector indicating current verification status for each message; starts a
    // poll for any not yet verified messages. Permission: Any.
    VerifyMessages(Vec<Message>),

    // Starts a poll to confirm a verifier set update on the external gateway. Permission: Any.
    VerifyVerifierSet {
        message_id: nonempty::String,
        new_verifier_set: VerifierSet,
    },

    // Update the threshold used for new polls. Permission: Governance.
    UpdateVotingThreshold {
        new_voting_threshold: MajorityThreshold,
    },
}

pub enum QueryMsg {
    // Returns a PollResponse for the given poll id.
    Poll { poll_id: PollId },

    // Returns the verification status of each message in the input.
    MessagesStatus(Vec<Message>),

    // Returns the verification status of the given verifier set.
    VerifierSetStatus(VerifierSet),

    // Returns the currently configured voting threshold.
    CurrentThreshold,
}

Verifier graph

flowchart TD
subgraph Axelar
    G{"Gateway"}
    Vr{"Voting Verifier"}
    R{"Service Registry"}
end
OC{"Verifiers"}

G--"VerifyMessages([M, M', M''])"-->Vr
Vr--"ActiveVerifiers"-->R
OC--"Vote(poll_id, votes)"-->Vr
OC--"EndPoll(poll_id)"-->Vr

Message Verification Sequence Diagram

sequenceDiagram
participant Gateway
participant Voting Verifier
participant Service Registry
participant OC as Verifiers


Gateway->>Voting Verifier: VerifyMessages([M,M',M''])

Voting Verifier->>Service Registry: ActiveVerifiers
Service Registry-->>Voting Verifier: list of verifiers and stake
Voting Verifier->>OC: emit event with poll_id and messages
Voting Verifier-->>Gateway: [false,false,false]

OC->>Voting Verifier: Vote(poll_id, votes)
OC->>Voting Verifier: Vote(poll_id, votes)

OC->>Voting Verifier: EndPoll(poll_id)
note right of Voting Verifier: Poll results are now stored.

opt After poll has ended
    Gateway->>Voting Verifier: query MessagesStatus([M,M',M''])
    Voting Verifier-->>Gateway: return [MessageStatus, ...]
end

Multisig prover contract

Not used on the XRPL side. XRPL outbound transactions are built by the XRPL Multisig Prover, which builds XRPL-native transactions (Payment, SignerListSet, TicketCreate, TrustSet), manages tickets and the XRP fee reserve, and uses the XRPL SMT\0-prefixed SHA-512-half multi-signing digest. The generic multisig-prover documented here is the template deployed for most other connected amplifier chains.

The prover contract is responsible for transforming gateway messages into a payload that is ready to be sent to the destination gateway. It calls the multisig contract to generate the signature proof and finally encodes both the data and proof so that relayers can take it and send it to the destination chain gateway.

Interface

pub enum ExecuteMsg {
    // Start building a proof that includes specified messages. Permission: Any.
    // Queries the gateway for actual message contents.
    ConstructProof(Vec<CrossChainId>),

    // Triggers a verifier set rotation if the registered set has diverged from the current
    // one by more than `verifier_set_diff_threshold`. Permission: Elevated (admin or governance).
    UpdateVerifierSet,

    // Promotes a previously-prepared NextVerifierSet to be the current set, once a poll on
    // the destination chain's SignersRotated event has confirmed it. Permission: Any.
    ConfirmVerifierSet,

    // Updates the signing threshold used for future verifier sets. The threshold currently
    // in use does not change; the verifier set must be updated and confirmed for the change
    // to take effect. Permission: Governance.
    UpdateSigningThreshold {
        new_signing_threshold: MajorityThreshold,
    },

    // Replaces the contract's admin address. Permission: Governance.
    UpdateAdmin {
        new_admin_address: String,
    },
}

#[derive(QueryResponses)]
pub enum QueryMsg {
    // Returns the current proof for a given multisig session, including the encoded
    // execute_data once signing completes.
    #[returns(ProofResponse)]
    Proof { multisig_session_id: Uint64 },

    // Returns the current active verifier set, or None if uninitialized.
    #[returns(Option<VerifierSetResponse>)]
    CurrentVerifierSet,

    // Returns the next (pending) verifier set, or None if no rotation is in progress.
    #[returns(Option<VerifierSetResponse>)]
    NextVerifierSet,
}

pub enum ProofStatus {
    Pending,
    Completed { execute_data: HexBinary }, // encoded data and proof sent to destination gateway
}

pub struct ProofResponse {
    pub multisig_session_id: Uint64,
    pub message_ids: Vec<CrossChainId>,
    pub payload: Payload,
    pub status: ProofStatus,
}

pub struct VerifierSetResponse {
    pub id: String,
    pub verifier_set: multisig::verifier_set::VerifierSet,
}

Events

pub enum Event {
    ProofUnderConstruction {
        destination_chain: ChainName,
        payload_id: PayloadId,
        multisig_session_id: Uint64,
        msg_ids: Vec<CrossChainId>,
    },
}

Proof construction graph

graph TD

r[Relayer]
subgraph Axelar
b[Prover]
g[Gateway]
m[Multisig]
end
s[Signer]

r--ConstructProof-->b
b--OutgoingMessages-->g
g-.->b
b--StartSigningSession-->m
b--Multisig-->m
s--SubmitSignature-->m

Proof construction sequence diagram

sequenceDiagram
autonumber
participant Relayer
box LightYellow Axelar
participant Prover
participant Gateway
participant Multisig
end
actor Signers

Relayer->>+Prover: ExecuteMsg::ConstructProof
alt payload not created previously
  Prover->>+Gateway: QueryMsg::OutgoingMessages
  Gateway-->>-Prover: query result
  alt newer VerifierSet exists
    Prover->>Prover: update next VerifierSet
  end
else previously created payload found
  Prover->>Prover: retrieves payload from storage
end
Prover->>+Multisig: ExecuteMsg::StartSigningSession
Multisig-->>Signers: emit SigningStarted event
Multisig->>-Prover: reply with session ID
Prover-->>Relayer: emit ProofUnderConstruction event
deactivate Prover
loop Collect signatures
	Signers->>+Multisig: signature collection
end
Multisig-->>-Relayer: emit SigningCompleted event
Relayer->>+Prover: QueryMsg::Proof
Prover->>+Multisig: QueryMsg::Multisig
Multisig-->>-Prover: reply with status, current signatures vector and snapshot
Prover-->>-Relayer: returns ProofResponse
  1. Relayer asks Prover contract to construct proof providing a list of cross chain ids.
  2. If no payload for the given messages was previously created, it queries the gateway for the messages to construct it.
  3. With the retrieved messages, the Prover contract transforms them into a payload digest that needs to be signed by the multisig.
  4. If a previous payload was found for the given message ids, the Prover retrieves it from storage instead of querying the gateway and building it again.
  5. The Multisig contract is called asking to sign the payload digest.
  6. Multisig emits event SigningStarted indicating a new multisig session has started.
  7. Multisig triggers a reply in Prover returning the newly created session ID which is then stored with the payload for reference.
  8. Prover contract emits event ProofUnderConstruction which includes the ID of the proof being constructed.
  9. Signers submit their signatures until threshold is reached.
  10. Multisig emits event indicating the multisig session has been completed.
  11. Relayer queries Prover for the proof, using the multisig session ID.
  12. Prover queries Multisig for the multisig session.
  13. Multisig replies with the multisig state, the list of collected signatures so far and the snapshot of participants.
  14. If the Multisig state is Completed, the Prover finalizes constructing the proof and returns the ProofResponse struct which includes the proof itself and the data to be sent to the destination gateway. If the state is not completed, the Prover returns the ProofResponse struct with the status field set to Pending.

Update and confirm VerifierSet graph

graph TD

r[Relayer]
subgraph Axelar
b[Prover]
v[Voting Verifier]
m[Multisig]
s[Service Registry]
end

r--UpdateVerifierSet-->b
b--ActiveVerifiers-->s
b--RegisterVerifierSet-->m
r--ConfirmVerifierSet-->b
b--VerifierSetStatus-->v

Update and confirm VerifierSet sequence diagram

sequenceDiagram
autonumber
participant External Gateway
participant Relayer
box LightYellow Axelar
participant Prover
participant Service Registry
participant Voting Verifier
participant Multisig
end
actor Verifier
actor Signers
Relayer->>+Prover: ExecuteMsg::UpdateVerifierSet
alt existing VerifierSet stored
  Prover->>+Service Registry: QueryMsg::ActiveVerifiers
  Service Registry-->>-Prover: save new VerifierSet as next VerifierSet
  Prover->>+Multisig: ExecuteMsg::StartSigningSession (for rotate signers message)
  loop Collect signatures
	  Signers->>+Multisig: signature collection
  end
end
Relayer->>+Prover: QueryMsg::Proof
Prover-->>-Relayer: returns ProofResponse (new verifier set signed by old verifier set)
Relayer-->>External Gateway: send new VerifierSet to the gateway, signed by old VerifierSet
External Gateway-->>+Relayer: emit SignersRotated event
Relayer->>+Voting Verifier: ExecuteMsg::VerifyVerifierSet
Verifier->>+External Gateway: look up SignersRotated event, verify event matches verifier set in poll
Verifier->>+Voting Verifier: ExecuteMsg::Vote
Relayer->>+Voting Verifier: ExecuteMsg::EndPoll
Relayer->>+Prover: ExecuteMsg::ConfirmVerifierSet
Prover->>+Voting Verifier: QueryMsg::VerifierSetStatus
Voting Verifier-->>-Prover: true
Prover->>+Multisig: ExecuteMsg::RegisterVerifierSet
  1. The Relayer calls Prover to update the VerifierSet.
  2. The Prover calls Service Registry to get a VerifierSet.
  3. If a newer VerifierSet was found, the new VerifierSet is stored as the next VerifierSet. The prover creates payload for the new verifier set.
  4. The Multisig contract is called asking to sign the binary message.
  5. Signers submit their signatures until threshold is reached.
  6. Relayer queries Prover for the proof, using the multisig session id.
  7. If the Multisig state is Completed, the Prover finalizes constructing the proof and returns the ProofResponse struct which includes the proof itself and the data to be sent to the External Chain's gateway. If the state is not completed, the Prover returns the ProofResponse struct with the status field set to Pending.
  8. Relayer sends proof and data to the External Gateway.
  9. The gateway on the External Gateway processes the commands in the data and emits event SignersRotated.
  10. The event SignersRotated picked up by the Relayer, the Relayer calls Voting Verifier to create a poll.
  11. The Verifiers see the PollStarted event and look up the SignersRotated event on the External Gateway and verify the event matches the verifier set in the poll.
  12. The Verifiers then vote on whether the event matches the verifiers or not.
  13. The Relayer calls the Voting Verifier to end the poll and emit PollEnded event.
  14. Once the poll is completed, the Relayer calls the Prover to confirm if the VerifierSet was updated.
  15. The Prover queries the Voting Verifier to check if the VerifierSet is confirmed.
  16. The Voting Verifier returns that the VerifierSet is confirmed.
  17. The Prover stores the VerifierSet in itself and in Multisig.

Coordinator

Some contracts, like the multisig provers, are deployed per chain and unknown to one another. The coordinator contract keeps track of these chain-dependent provers and coordinates any interaction that requires knowledge of all of them. For example, the ability for a verifier to unbond their stake requires checking that it is not part of any active verifier set on any chain. Provers also push their currently active verifier set to the coordinator whenever it changes, so the coordinator can answer those cross-chain questions.

Interface

pub struct InstantiateMsg {
    /// Governance address. Calls all governance-permissioned messages such as RegisterProverContract.
    pub governance_address: String,
    /// Service registry contract address on axelar. Used by VerifierInfo queries to resolve registration data.
    pub service_registry: String,
}

pub enum ExecuteMsg {
    // Registers a multisig prover address against a chain name. Used as part of the registry
    // the coordinator maintains for cross-chain bookkeeping. Permission: Governance.
    RegisterProverContract {
        chain_name: ChainName,
        new_prover_addr: String,
    },

    // Replaces the active verifier set the coordinator tracks for the calling prover.
    // Provers should call this whenever their active set changes so the coordinator can
    // answer ReadyToUnbond and VerifierInfo queries consistently across chains.
    // Permission: Specific(prover) — only the prover registered for some chain can call.
    SetActiveVerifiers { verifiers: HashSet<String> },
}

pub enum QueryMsg {
    // Returns true if the verifier is not part of any active verifier set tracked by the
    // coordinator. Used by the service registry as the unbonding gate.
    ReadyToUnbond { verifier_address: String },

    // Returns the verifier's registration entry, total weight, supported chains, and the
    // set of chains the verifier is currently signing for.
    VerifierInfo {
        service_name: String,
        verifier: String,
    },
}

pub struct VerifierInfo {
    pub verifier: Verifier,
    pub weight: nonempty::Uint128,
    pub supported_chains: Vec<ChainName>,
    pub actively_signing_for: HashSet<Addr>,
}

Coordinator graph

flowchart TD

Co{"Coordinator"}
Go{"Governance"}
SR{"Service Registry"}
PrA{"Prover A"}
PrB{"Prover B"}
PrC{"Prover C"}


Go -- "RegisterProverContract" --> Co
PrA -- "SetActiveVerifiers" --> Co
PrB -- "SetActiveVerifiers" --> Co
PrC -- "SetActiveVerifiers" --> Co
SR -- "ReadyToUnbond" --> Co
SR -- "VerifierInfo" --> Co

XRPL Gateway

Source: contracts/xrpl-gateway.

The XRPL Gateway is the Axelar-side entry and exit point for all XRPL traffic. On other Amplifier chains a thin "gateway" contract simply moves verified messages between a voting verifier and the router; on XRPL the gateway is overloaded and also absorbs the ITS edge role, the token-id registry, and the gas accounting that on other chains lives in separate contracts. However, since XRPL has no smart contracts, every responsibility that would normally be on the external chain now lives on Axelar.

What XRPL-specific work this contract absorbs compared to the generic gateway

ConcernGeneric GatewayXRPL Gateway
Verifying incoming messages with the voting verifierYesYes
Storing outgoing messages for the proverYesYes
Routing verified inbound to the routerYesYes (RouteIncomingMessages)
Acting as ITS edge (translating to ITS Hub messages)No (separate ITS edge contract on the chain)Yes: holds the token-id registry, computes SEND_TO_HUB wrappers, queries Hub for destination decimals, scales amounts
Token registration / linking / metadata pushingNoYes: RegisterLocalToken, RegisterRemoteToken, RegisterTokenMetadata, LinkToken, DeployRemoteToken
Gas accounting for inbound messagesNoYes: tracks accrued gas per token id; ConfirmAddGasMessages

Interface

pub struct InstantiateMsg {
    pub admin_address: String,
    pub governance_address: String,
    pub verifier_address: String,        // the xrpl-voting-verifier
    pub router_address: String,
    pub its_hub_address: String,         // ITS Hub contract on Axelar
    pub its_hub_chain_name: ChainName,
    pub chain_name: ChainName,           // "xrpl"
    pub xrpl_multisig_address: XRPLAccountId, // the on-XRPL gateway account
}

pub enum ExecuteMsg {
    // ITS-edge token registration. Operations gated to admin/governance.

    // Register XRPL token metadata for use in custom token linking. Permission: Elevated.
    RegisterTokenMetadata { xrpl_token: XRPLTokenOrXrp },

    // Register an XRPL-native token (issuer != multisig) as an interchain token.
    // Derives tokenId = linked_token_id(chain_hash, issuer, currency_hash). Permission: Elevated.
    RegisterLocalToken { xrpl_token: XRPLToken },

    // Register a remote-origin token: maps an XRPL currency code to a token id whose XRPL
    // representation is an IOU issued by the multisig itself. Permission: Elevated.
    RegisterRemoteToken {
        token_id: TokenId,
        xrpl_currency: XRPLCurrency,
    },

    // Send a HubMessage::SendToHub(LinkToken) to the ITS Hub. Permission: Elevated.
    LinkToken {
        token_id: TokenId,
        destination_chain: ChainNameRaw,
        link_token: LinkToken,
    },

    // Send a HubMessage::SendToHub(DeployInterchainToken) to the ITS Hub. Permission: Elevated.
    DeployRemoteToken {
        xrpl_token: XRPLTokenOrXrp,
        destination_chain: ChainNameRaw,
        token_metadata: TokenMetadata,
    },

    // GMP machinery (called by relayers).

    // Trigger verification at the voting verifier for any of the given XRPL messages that
    // is still unverified. Permission: Any.
    VerifyMessages(Vec<XRPLMessage>),

    // Store outgoing messages received from the router (delivered to XRPL by the prover).
    // Misnamed for router compatibility: this is "RouteOutgoingMessages" semantically.
    // Permission: Specific(router).
    RouteMessages(Vec<Message>),

    // Route already-verified inbound messages on to the ITS Hub or to the destination
    // chain (for pure GMP), depending on the XRPLMessage variant. Permission: Any.
    RouteIncomingMessages(Vec<WithPayload<XRPLMessage>>),

    // Credit a verified XRPLAddGasMessage to the accrued gas pool for its token id.
    // Permission: Any.
    ConfirmAddGasMessages(Vec<XRPLAddGasMessage>),

    // Admin / killswitch.
    UpdateAdmin { new_admin_address: String },           // Elevated
    EnableExecution,                                     // Elevated
    DisableExecution,                                    // Elevated
}

pub enum QueryMsg {
    // Messages stored for delivery to XRPL (consumed by the multisig prover).
    OutgoingMessages(Vec<CrossChainId>),

    // Token-id registry lookups.
    XrplToken(TokenId),           // tokenId -> XRPLToken
    XrplTokenId(XRPLToken),       // XRPLToken -> tokenId
    XrpTokenId,                   // tokenId for native XRP
    LinkedTokenId(XRPLToken),     // derive linked tokenId without persisting

    // Per-chain decimals for a token, as registered with the ITS Hub.
    TokenInstanceDecimals { chain_name: ChainNameRaw, token_id: TokenId },

    // Translate an inbound XRPL message to the canonical ITS or GMP form (used by the
    // relayer to preview a RouteIncomingMessages payload).
    InterchainTransfer {
        message: XRPLInterchainTransferMessage,
        payload: Option<nonempty::HexBinary>,
    },
    CallContract {
        message: XRPLCallContractMessage,
        payload: nonempty::HexBinary,
    },

    IsEnabled,
}

XRPL message variants

The gateway accepts a single enum XRPLMessage (defined in upstream packages/xrpl-types) with five variants:

VariantDirectionPurpose
InterchainTransferMessageInbound user paymentToken transfer through ITS to another chain (or to an executable on the destination if payload is set).
CallContractMessageInbound user paymentPure GMP call (no token transfer). Skips the ITS Hub entirely.
AddGasMessageInbound user top-upAdds gas to an in-flight cross-chain message identified by its original tx hash.
AddReservesMessageInbound operator top-upXRP-only payment to top up the multisig account's XRPL reserves. Confirmed by the multisig prover, not the gateway.
ProverMessageOutbound from the multisigReports that a prover-built XRPL transaction has landed on the ledger. Confirmed by the multisig prover.

The gateway is the verification entry point for all five variants (via VerifyMessages), but only handles InterchainTransferMessage, CallContractMessage, and AddGasMessage in its routing/confirmation logic. AddReservesMessage and ProverMessage are confirmed by the XRPL Multisig Prover.

Routing graph: inbound XRPL message

sequenceDiagram
autonumber
participant Relayer
box LightYellow Axelar
participant XGW as xrpl-gateway
participant XVV as xrpl-voting-verifier
participant Hub as ITS Hub
participant Router
end

Relayer->>XGW: VerifyMessages
XGW->>XVV: VerifyMessages
XVV-->>XGW: per-message status
XGW-->>Relayer: response
Note over XVV: poll runs and quorum is reached
Relayer->>XGW: RouteIncomingMessages
Note over XGW: InterchainTransferMessage path
XGW->>Hub: query destination decimals
Hub-->>XGW: decimals
XGW->>XGW: scale amount and wrap as SEND_TO_HUB
XGW->>Router: RouteMessages via Hub
Note over XGW: CallContractMessage path
XGW->>XGW: build plain Message, no Hub wrap
XGW->>Router: RouteMessages direct GMP
Relayer->>XGW: ConfirmAddGasMessages
Note over XGW: AddGasMessage path
XGW->>XGW: credit accrued gas for the token id
  1. The relayer submits an XRPLMessage for verification.
  2. The gateway forwards to the XRPL Voting Verifier, which opens a poll.
  3. After quorum is reached the relayer triggers the appropriate follow-up entry point. InterchainTransferMessage and CallContractMessage are routed via RouteIncomingMessages; AddGasMessage is confirmed via the separate ConfirmAddGasMessages entry point.
  4. For InterchainTransferMessage, the gateway acts as the ITS edge: looks up the token id, queries the ITS Hub for destination decimals, scales the amount, wraps the inner ITS message as HubMessage::SendToHub(InterchainTransfer), and hands it to the router.
  5. For CallContractMessage, the gateway builds a plain router_api::Message with no Hub wrapping. The router delivers it straight to the destination chain's gateway as a pure GMP call.
  6. For AddGasMessage, the gateway credits the verified top-up amount to GAS_ACCRUED[token_id]; no router involvement.

Routing graph: outbound to XRPL

sequenceDiagram
autonumber
participant Router
box LightYellow Axelar
participant XGW as xrpl-gateway
participant XMP as xrpl-multisig-prover
end

Router->>XGW: RouteMessages
Note over XGW: gate: source_address must be the ITS Hub
XGW->>XGW: store in OUTGOING_MESSAGES
XMP->>XGW: query OutgoingMessages
XGW-->>XMP: stored Messages

Messages arriving from the router via RouteMessages are not re-verified (the router is trusted), but the gateway rejects any message whose source_address is not the ITS Hub on Axelar. Only ITS-routed messages can be delivered to XRPL: pure GMP from another chain destined for XRPL is not supported. Accepted messages are stored in the OUTGOING_MESSAGES map keyed by cc_id. The XRPL Multisig Prover later reads them via the OutgoingMessages query when constructing an outbound XRPL Payment.

XRPL Voting Verifier

Source: contracts/xrpl-voting-verifier.

The XRPL Voting Verifier runs stake-weighted polls over XRPL transactions. Each poll asks the active verifier set whether a specific XRPL transaction happened on the XRPL ledger, what its outcome was, and whether its contents match the message under verification. Off chain, each verifier's ampd handler fetches the cited transaction over XRPL JSON RPC, applies XRPL-specific consistency checks, and casts a Vote. Quorum results are stored on chain and queried by the XRPL Gateway (or, for ProverMessage polls, indirectly used by the XRPL Multisig Prover) to decide what to do next.

What XRPL-specific work this contract absorbs compared to the generic voting verifier

ConcernGeneric Voting VerifierXRPL Voting Verifier
Stake-weighted pollsYesYes
Vote / EndPoll / VerifyMessages interfaceYesYes
Message typeGeneric router_api::MessageXRPL-native XRPLMessage enum (five variants), so a single poll can cover inbound user payments, outbound prover transactions, gas top-ups, and reserve top-ups
Verifier set update pollsSeparate VerifyVerifierSet execute variantNot needed: verifier set rotation rides on a ProverMessage(SignerListSet) poll, no separate variant
source_gateway_address field typeStringXRPLAccountId — type-safe XRPL classic address
confirmation_height typeu64u32 (sufficient for XRPL ledger indexes)
Parameter updatesMultiple split-out execute variants in upstream; older UpdateVotingThreshold hereUnified UpdateVotingParameters with Option-typed fields for selective updates
Admin / killswitchNot in the upstream version inherited hereYes: UpdateAdmin, EnableExecution, DisableExecution

Interface

pub struct InstantiateMsg {
    pub admin_address: nonempty::String,
    pub governance_address: nonempty::String,
    pub service_registry_address: nonempty::String,
    pub service_name: nonempty::String,
    // The XRPL multisig classic address. Type-safe, distinct from a generic string.
    pub source_gateway_address: XRPLAccountId,
    pub voting_threshold: MajorityThreshold,
    pub block_expiry: nonempty::Uint64,
    pub confirmation_height: u32,
    pub source_chain: ChainName,
}

pub enum ExecuteMsg {
    // Compute the results of a poll. Anyone can call. Emits a PollEnded event.
    // Permission: Any.
    EndPoll { poll_id: PollId },

    // Cast votes for the specified poll. Caller must be a participant in the snapshot.
    // Permission: Any (the snapshot check happens during execution).
    Vote { poll_id: PollId, votes: Vec<Vote> },

    // Return a per-message verification status, opening a poll for any not-yet-verified
    // messages. Called by the XRPL Gateway. Permission: Any.
    VerifyMessages(Vec<XRPLMessage>),

    // Update voting parameters. Each field is Option so callers can patch a subset.
    // Changes apply to future polls only. Permission: Governance.
    UpdateVotingParameters {
        voting_threshold: Option<MajorityThreshold>,
        block_expiry: Option<nonempty::Uint64>,
        confirmation_height: Option<u32>,
    },

    // Killswitch and admin rotation. Permission: Elevated.
    EnableExecution,
    DisableExecution,
    UpdateAdmin { new_admin_address: String },
}

pub enum QueryMsg {
    // Returns the poll state, the messages it covers, and its status (in progress / finished).
    Poll { poll_id: PollId },

    // Returns the verification status of each input XRPLMessage.
    MessagesStatus(Vec<XRPLMessage>),

    // Returns the currently active voting threshold, block expiry, and confirmation height.
    VotingParameters,
}

pub enum PollData {
    Messages(Vec<XRPLMessage>),
}

pub struct PollResponse {
    pub poll: WeightedPoll,
    pub data: PollData,
    pub status: PollStatus,
}

pub struct MessageStatus {
    pub message: XRPLMessage,
    pub status: VerificationStatus,
}

Verification sequence

sequenceDiagram
autonumber
participant XGW as xrpl-gateway
box LightYellow Axelar
participant XVV as xrpl-voting-verifier
participant SR as Service Registry
end
actor Verifiers

XGW->>XVV: VerifyMessages
XVV->>SR: ActiveVerifiers
SR-->>XVV: weighted snapshot
XVV-->>Verifiers: emit messages_poll_started
XVV-->>XGW: per-message status, initially false or in-progress

loop Verifiers vote
    Verifiers->>XVV: tx RPC lookup on XRPL
    Verifiers->>XVV: Vote
    Note over XVV: emit wasm-quorum_reached when threshold crossed
end

Note over XVV: the wasm-quorum_reached event drives the relayer's next call,<br/>which depends on the XRPLMessage variant being verified

General steps

  1. The XRPL Gateway forwards an array of XRPLMessages via VerifyMessages. For any message that already has a final status (e.g., SucceededOnSourceChain), no poll is opened; only unverified messages produce a new poll.
  2. The voting verifier takes a weighted snapshot of the active verifiers for chain xrpl from the Service Registry. The snapshot is bound to the poll for its entire lifetime.
  3. A messages_poll_started event is emitted carrying the candidate XRPLMessages. Off chain, each verifier's XRPL handler picks the event up, fetches the cited XRPL transactions via JSON RPC, runs XRPL-specific consistency checks (multi-sign destination, delivered_amount == Amount to close the tfPartialPayment exploit, memo parsing, payload hash), and casts a Vote.
  4. The moment a vote pushes weighted tally past quorum, the contract emits a wasm-quorum_reached event. The Axelar GMP API delivers a task to the XRPL relayer. The relayer's next action depends on the XRPLMessage variant: for InterchainTransferMessage or CallContractMessage it calls RouteIncomingMessages on the gateway; for ProverMessage it calls ConfirmProverMessage on the multisig prover.
  5. EndPoll is called separately to finalize the poll status. It is not on the critical path for routing.

Why there's no VerifyVerifierSet variant

On the generic voting-verifier, verifier set rotations are confirmed by a separate VerifyVerifierSet execute variant that opens a different kind of poll. On XRPL the gateway is an XRPL account, not a contract, so the rotation event is the inclusion of a SignerListSet transaction on the XRPL ledger. That inclusion is reported back to Axelar as a ProverMessage (with the SignerListSet unsigned tx hash) and confirmed through the same VerifyMessages poll machinery used for any other XRPL transaction. The verifier set rotation is then promoted by the multisig prover's ConfirmProverMessage handler.

XRPL Multisig Prover

Source: contracts/xrpl-multisig-prover.

The XRPL Multisig Prover constructs fully-canonical XRPL transactions that the on-XRPL multisig account submits to deliver Axelar-to-XRPL traffic. On other Amplifier chains the prover only has to produce an execute_data blob that the destination chain's gateway will then verify and execute; on XRPL there is no destination contract to do that verification, so the prover's output is the on-chain transaction itself. The prover therefore also owns all the XRPL-specific bookkeeping that this implies: a pool of pre-allocated tickets for parallel out-of-order in-flight transactions, an XRP reserve budget for the multisig account, trust line management for receiving local IOUs, and the current/next verifier set view used to populate the multisig account's SignerList.

What XRPL-specific work this contract absorbs compared to the generic prover

ConcernGeneric Multisig ProverXRPL Multisig Prover
ConstructProof / signing session lifecycleYesYes
UpdateVerifierSet / ConfirmVerifierSetYes (ConfirmVerifierSet from voting verifier)Replaced by ConfirmProverMessage (verifier rotation rides a SignerListSet confirmation)
OutputEncoded calldata for destination chain's gatewayAn XRPL canonical-serialized transaction (Payment / SignerListSet / TicketCreate / TrustSet)
Sequence managementN/A (no per-account nonce on Axelar side)Yes: maintains AVAILABLE_TICKETS pool, NEXT_SEQUENCE_NUMBER, LAST_ASSIGNED_TICKET_NUMBER
Fee/reserve budgetN/AYes: FEE_RESERVE tracks the multisig account's XRP balance budget; ConfirmAddReservesMessage credits top-ups
Per-token plumbingN/AYes: TrustSet execute variant for opening trust lines so the multisig account can receive a local-origin IOU
Proof status queryReturns full ProofResponse with execute_dataReturns unsigned_tx_hash plus Completed { execute_data: signed XRPL tx blob }

Interface

pub struct InstantiateMsg {
    pub admin_address: String,
    pub governance_address: String,
    pub gateway_address: String,             // the xrpl-gateway
    pub multisig_address: String,            // the generic multisig contract
    pub coordinator_address: String,
    pub service_registry_address: String,
    pub voting_verifier_address: String,     // the xrpl-voting-verifier
    pub signing_threshold: MajorityThreshold,
    pub service_name: String,
    pub chain_name: ChainName,               // "xrpl"
    pub verifier_set_diff_threshold: u32,
    pub xrpl_multisig_address: XRPLAccountId,

    // XRPL-specific fee + reserve configuration.
    pub xrpl_transaction_fee: u64,           // base fee per signer; final tx fee = fee * (1 + n_signers)
    pub xrpl_base_reserve: u64,              // protocol base reserve
    pub xrpl_owner_reserve: u64,             // per-owned-object reserve
    pub initial_fee_reserve: u64,            // bootstrap budget

    // XRPL ticket pool bootstrap.
    pub ticket_count_threshold: u32,         // refill the pool when available count drops below this
    pub available_tickets: Vec<u32>,         // initial set of usable tickets
    pub next_sequence_number: u32,
    pub last_assigned_ticket_number: u32,
}

pub enum ExecuteMsg {
    // Build an XRPL Payment for a single outgoing message and open a signing session.
    // Permission: Any (the relayer calls this; gas is paid via accrued gas accounting).
    ConstructProof {
        cc_id: CrossChainId,
        payload: HexBinary,
    },

    // Trigger a verifier set rotation: build an XRPL SignerListSet signed by the
    // outgoing set. Permission: Elevated (admin or governance).
    UpdateVerifierSet,

    // Confirm that a prover-built XRPL transaction landed on the ledger.
    // After the xrpl-voting-verifier confirms the corresponding ProverMessage poll, the
    // relayer calls this to release the ticket, clear the payload, and (for SignerListSet)
    // promote NextVerifierSet -> CurrentVerifierSet. Permission: Any.
    ConfirmProverMessage { prover_message: XRPLProverMessage },

    // Credit a verified XRPLAddReservesMessage to FEE_RESERVE. Permission: Any.
    ConfirmAddReservesMessage { add_reserves_message: XRPLAddReservesMessage },

    // Build an XRPL TicketCreate transaction. Permission: Any (the relayer calls when the
    // pool drops below threshold; the contract decides internally how many to mint).
    TicketCreate,

    // Build an XRPL TrustSet transaction for the multisig account to be able to hold
    // the given local-origin IOU. Permission: Elevated.
    TrustSet { token_id: TokenId },

    // Admin / parameter updates.
    DisableExecution,                                        // Elevated
    EnableExecution,                                         // Elevated
    UpdateFeeReserve { new_fee_reserve: u64 },               // Elevated
    UpdateSigningThreshold {                                 // Governance
        new_signing_threshold: MajorityThreshold,
    },
    UpdateXrplTransactionFee { new_transaction_fee: u64 },   // Elevated
    UpdateXrplReserves {                                     // Elevated
        new_base_reserve: u64,
        new_owner_reserve: u64,
    },
    UpdateAdmin { new_admin_address: String },               // Elevated

    // Verifies a signature against an XRPL multi-sign digest (which includes the signer's
    // own XRPL address). Used by the multisig contract during signature submission when
    // sig_verifier is set. Permission: Any.
    VerifySignature {
        signature: HexBinary,
        message: HexBinary,
        public_key: HexBinary,
        signer_address: String,
        session_id: Uint64,
    },
}

pub enum QueryMsg {
    // Returns the unsigned tx hash and either ProofStatus::Pending or
    // ProofStatus::Completed { execute_data: <signed XRPL tx blob> }.
    Proof { multisig_session_id: Uint64 },

    // Same XRPL-specific signature check as the execute variant, callable as a query.
    VerifySignature { /* same args */ },

    // The current active verifier set view (the one mirrored into the XRPL SignerList).
    CurrentVerifierSet,

    // The pending NextVerifierSet, if a rotation is in progress.
    NextVerifierSet,

    // Look up the in-flight multisig session for a specific cc_id.
    MultisigSession { cc_id: CrossChainId },

    // Recommends how many tickets the next TicketCreate should produce. 0 means no refill needed.
    TicketCreate,

    IsEnabled,

    AvailableTickets,
    NextSequenceNumber,
    FeeReserve,
}

pub struct ProofResponse {
    pub unsigned_tx_hash: HexTxHash,
    pub status: ProofStatus,
}

pub enum ProofStatus {
    Pending,
    Completed { execute_data: HexBinary },
}

Proof construction sequence

sequenceDiagram
autonumber
participant Relayer
box LightYellow Axelar
participant XMP as xrpl-multisig-prover
participant XGW as xrpl-gateway
participant MS as Multisig
end
actor Signers

Relayer->>XMP: ConstructProof
XMP->>XGW: OutgoingMessages
XGW-->>XMP: stored router Message
XMP->>XMP: decode RECEIVE_FROM_HUB,<br/>allocate ticket from AVAILABLE_TICKETS,<br/>build XRPLPaymentTx canonical bytes
XMP->>MS: StartSigningSession with canonical bytes
MS-->>Signers: emit SigningStarted
MS-->>XMP: reply with session ID
XMP-->>Relayer: emit ProofUnderConstruction event
loop Collect signatures
    Signers->>MS: SubmitSignature
end
MS-->>Relayer: emit SigningCompleted
Relayer->>XMP: Proof query with session ID
XMP->>MS: Multisig query
MS-->>XMP: state and signatures
XMP-->>Relayer: ProofResponse with signed XRPL tx blob

General steps

  1. The relayer calls ConstructProof(cc_id, payload) after the ITS Hub has routed an inbound message into the XRPL Gateway's OUTGOING_MESSAGES map.
  2. The prover queries the XRPL Gateway for the stored message, decodes HubMessage::ReceiveFromHub(InterchainTransfer), and verifies data is empty (XRPL has no executable destination; the multisig prover rejects payloads).
  3. It parses the destination as a classic XRPL address, converts the amount into the right XRPLPaymentAmount (drops for XRP, or 54-bit-mantissa + exponent for an IOU), allocates a ticket from AVAILABLE_TICKETS, and builds an XRPL Payment transaction whose Sequence field is the allocated ticket.
  4. The prover opens a signing session at the generic multisig contract, passing the XRPL canonical serialization as the message to sign. Each verifier signs a slightly different digest, because XRPL multi-signing requires appending the signer's own XRPL account id to the canonical bytes before the SHA-512-half hash.
  5. Once quorum is reached, the relayer queries Proof { multisig_session_id } and gets back ProofStatus::Completed { execute_data: <signed XRPL tx blob> }. It then broadcasts the blob to the XRPL ledger.
  6. The relayer's subscriber sees the multisig account's outbound transaction land on the ledger and reports it back via VerifyMessages with a ProverMessage. Once the XRPL Voting Verifier confirms it, the relayer calls ConfirmProverMessage which releases the ticket and clears the stored payload.

Update and confirm VerifierSet graph

graph TD

Relayer
subgraph Axelar
    XMP[xrpl-multisig-prover]
    SR[Service Registry]
    XVV[xrpl-voting-verifier]
    MS[Multisig]
    Coord[Coordinator]
end

Relayer -- UpdateVerifierSet --> XMP
XMP -- ActiveVerifiers --> SR
XMP -- RegisterVerifierSet --> MS
XMP -- SetActiveVerifiers --> Coord
Relayer -- ConfirmProverMessage --> XMP
XMP -- MessagesStatus(ProverMessage) --> XVV

Update and confirm VerifierSet sequence

sequenceDiagram
autonumber
participant XRPL as XRPL ledger
participant Relayer
box LightYellow Axelar
participant XGW as xrpl-gateway
participant XMP as xrpl-multisig-prover
participant SR as Service Registry
participant MS as Multisig
participant XVV as xrpl-voting-verifier
end
actor Signers

Relayer->>XMP: UpdateVerifierSet
XMP->>SR: ActiveVerifiers
SR-->>XMP: candidate verifier set
Note over XMP: continue only if diff<br/>exceeds the configured threshold
XMP->>XMP: save as NextVerifierSet
XMP->>MS: StartSigningSession with SignerListSet bytes,<br/>signed by the current verifier set
loop Collect signatures
    Signers->>MS: SubmitSignature
end
Relayer->>XMP: Proof query
XMP-->>Relayer: signed SignerListSet tx
Relayer->>XRPL: submit signed SignerListSet
XRPL-->>Relayer: tx confirmed in ledger
Relayer->>XGW: VerifyMessages with ProverMessage
Note over XVV: standard ProverMessage poll runs
Relayer->>XMP: ConfirmProverMessage
XMP->>XVV: MessagesStatus query
XVV-->>XMP: SucceededOnSourceChain
XMP->>XMP: promote NextVerifierSet to CurrentVerifierSet
XMP->>MS: RegisterVerifierSet

General steps

  1. The relayer (or governance) calls UpdateVerifierSet.
  2. The prover queries the Service Registry for the active verifier set for chain xrpl. If the diff against the current set exceeds verifier_set_diff_threshold, the candidate becomes the prover's NextVerifierSet.
  3. The prover builds an XRPL SignerListSet transaction that mirrors the new verifier set into the multisig account's on-XRPL SignerList, with weights scaled to fit XRPL's u16 weight field, and a quorum derived from the prover's signing_threshold. The transaction is signed by the outgoing verifier set.
  4. The relayer broadcasts the signed SignerListSet to the XRPL ledger. The transaction inclusion is then reported back to Axelar as a ProverMessage and confirmed through the standard VerifyMessages poll machinery.
  5. ConfirmProverMessage is called after the poll reaches quorum. The prover promotes NextVerifierSet to CurrentVerifierSet, calls RegisterVerifierSet on the generic multisig contract so subsequent signing sessions reference the new set, and pushes the new set to the Coordinator via SetActiveVerifiers for cross-chain bookkeeping.

Why ConfirmProverMessage replaces the generic ConfirmVerifierSet

On the generic prover, after a rotateSigners calldata is broadcast to the destination chain, the destination's gateway emits a SignersRotated event which is verified by the destination chain's voting verifier. ConfirmVerifierSet reads that result and promotes the set. On XRPL there is no contract to emit an event: a SignerListSet transaction landing in the ledger is the event. The prover unifies this with the generic message-confirmation path by treating every prover-built outbound XRPL transaction (Payment, SignerListSet, TicketCreate, TrustSet) as a ProverMessage that the XRPL Voting Verifier polls on. ConfirmProverMessage then dispatches on the inner transaction type to release the ticket, promote the verifier set, refresh available tickets, or activate a trust line.

Tickets, sequence numbers, and fee reserve

See Tickets for the full treatment of how the prover manages XRPL sequence numbers, the ticket pool (assignment, reuse under contention, refill flow), and the FEE_RESERVE budget.

Access Control for Contract Messages

Each execute message in the Amplifier contracts is annotated with a permission scope. The vocabulary is defined by the msgs-derive macro and a message can have any of the following:

  • Any: anyone can call it. Default for user-facing methods.
  • Governance: only the configured governance address.
  • Admin: only the configured admin address.
  • Elevated: admin or governance.
  • Specific(role): only an address that holds the named role. Common variants in this codebase: Specific(gateway), Specific(verifier), Specific(prover), Specific(authorized).
  • Proxy(role): a single trusted contract may also call this on behalf of the named role. Used today for Proxy(coordinator) so the coordinator can register chains/callers on behalf of governance.

Only execute messages with restricted access are listed below. Messages tagged Any are omitted.

Router

Governance

#![allow(unused)]
fn main() {
RegisterChain {
    chain: ChainName,
    gateway_address: Address,
    msg_id_format: MessageIdFormat,
},

UpgradeGateway {
    chain: ChainName,
    contract_address: Address,
},
}

Elevated

#![allow(unused)]
fn main() {
FreezeChains {
    chains: HashMap<ChainName, GatewayDirection>,
},

UnfreezeChains {
    chains: HashMap<ChainName, GatewayDirection>,
},

DisableRouting,

EnableRouting,
}

Specific(gateway)

Only a registered chain gateway may call this:

#![allow(unused)]
fn main() {
RouteMessages(Vec<Message>)
}

Voting Verifier

Governance

#![allow(unused)]
fn main() {
UpdateVotingThreshold {
    new_voting_threshold: MajorityThreshold,
}
}

Multisig Prover

Governance

#![allow(unused)]
fn main() {
UpdateSigningThreshold {
    new_signing_threshold: MajorityThreshold,
},

UpdateAdmin {
    new_admin_address: String,
}
}

Elevated

#![allow(unused)]
fn main() {
UpdateVerifierSet
}

Multisig

Governance

#![allow(unused)]
fn main() {
AuthorizeCallers {
    contracts: HashMap<String, ChainName>,
}
}

Elevated

#![allow(unused)]
fn main() {
UnauthorizeCallers {
    contracts: HashMap<String, ChainName>,
},

DisableSigning,

EnableSigning,
}

Specific(authorized)

Only a contract previously authorized via AuthorizeCallers may call this, and only for the chain name it was authorized for:

#![allow(unused)]
fn main() {
StartSigningSession {
    verifier_set_id: String,
    msg: HexBinary,
    chain_name: ChainName,
    sig_verifier: Option<String>,
}
}

Service Registry

Governance

#![allow(unused)]
fn main() {
RegisterService {
    service_name: String,
    coordinator_contract: String,
    min_num_verifiers: u16,
    max_num_verifiers: Option<u16>,
    min_verifier_bond: nonempty::Uint128,
    bond_denom: String,
    unbonding_period_days: u16,
    description: String,
},

UpdateService {
    service_name: String,
    updated_service_params: UpdatedServiceParams,
},

AuthorizeVerifiers {
    verifiers: Vec<String>,
    service_name: String,
},

UnauthorizeVerifiers {
    verifiers: Vec<String>,
    service_name: String,
},

JailVerifiers {
    verifiers: Vec<String>,
    service_name: String,
}
}

Specific(verifier)

Called by the verifier address itself:

#![allow(unused)]
fn main() {
RegisterChainSupport {
    service_name: String,
    chains: Vec<ChainName>,
},

DeregisterChainSupport {
    service_name: String,
    chains: Vec<ChainName>,
}
}

Coordinator

Governance

#![allow(unused)]
fn main() {
RegisterProverContract {
    chain_name: ChainName,
    new_prover_addr: String,
}
}

Specific(prover)

Only a prover address that was previously registered via RegisterProverContract may call this, and only with respect to the chain it was registered for:

#![allow(unused)]
fn main() {
SetActiveVerifiers {
    verifiers: HashSet<String>,
}
}

Glossary

This page defines the XRPL and Axelar specific terms used in the rest of the book.

XRPL terms

account_tx

The XRPL JSON RPC method that returns a paginated list of transactions involving a given account. The XRPL relayer polls account_tx for the multisig account to discover new user payments and to detect inclusion of multisig prover outputs.

Canonical serialization

The deterministic XRPL byte format used when hashing a transaction for signing. Every field is sorted by its protocol defined type_code and field_code and encoded in a fixed wire format. Because XRPL nodes all agree on this serialization, any signer can independently produce the same bytes from the same transaction structure and arrive at the same digest. Multi signing makes this rule sensitive: each signer must append their own account address to the canonical bytes before hashing.

Classic address

The base58 encoded XRPL account identifier, starting with r (for example, rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY). It is the human readable form of a 20 byte account id derived from RIPEMD160(SHA256(public_key)).

Currency code

A 3 character ASCII code (such as USD) or a 40 character (20 byte) hex code identifying an IOU. The first form is reserved for common ASCII codes; the second form is required for any code that is not exactly 3 ASCII letters. The pair (currency, issuer) uniquely identifies an IOU on XRPL.

DisableMaster (Master Key)

Every XRPL account is created with a master key derived from its account seed. DisableMaster is an AccountSet flag that turns off the master key, leaving the account controllable only through the SignerList. The multisig account that acts as the bridge gateway has DisableMaster set, so no single party can move funds out of it.

Drop

The smallest unit of native XRP. One XRP equals 106 drops. All XRP amounts in XRPL transactions are denominated in drops as a u64 integer. XRP itself has 6 decimals from the bridge's point of view.

IOU (issued currency)

XRPL's primitive for any token that is not XRP. An IOU is identified by the pair (currency_code, issuer_account) and is created implicitly when its issuer makes a Payment of that currency to an account that has opened a trust line. The bridge uses IOUs to represent tokens that originate on other chains: the multisig account is the issuer and mints the IOU to the recipient.

Memos

A transaction field on XRPL that carries arbitrary byte data. Each memo entry holds three optional sub fields: MemoType, MemoData, and MemoFormat, each encoded as hex. The bridge uses memos as a structured key value store. The exact set of expected keys depends on the value of the type memo: an interchain_transfer or call_contract payment encodes destination_chain, destination_address, gas_fee_amount, and an optional payload; an add_gas payment encodes only msg_id; an add_reserves payment carries no further keys.

Multisig account

A regular XRPL account that has had a SignerListSet transaction applied to define a list of signers, each with a weight, plus a quorum threshold. Once configured this way (and with the master key disabled), the account can only send transactions that have been signed by enough listed signers to reach the quorum. The bridge gateway is exactly such an account; the signer list mirrors the active Axelar verifier set.

Payment

The XRPL transaction type used to move value, either native XRP between accounts or an IOU between accounts that share a trust line. The bridge uses Payment both inbound (the user paying the multisig account) and outbound (the multisig account paying the recipient).

Reserve

A minimum XRP balance every XRPL account must keep in order to exist. There is a fixed base reserve per account, plus an owner reserve per owned object (each ticket, each trust line, each signer list entry, and so on). The multisig account's reserve is the dominant variable cost of running the bridge over time; it grows with the number of trust lines the gateway opens to receive local origin IOUs.

Sequence number

A per account counter that increments with every transaction the account sends. Comparable to an EVM nonce. Native XRPL transactions must use strictly increasing sequence numbers, which serializes activity for a given account. Tickets exist to relax this for parallel work.

SHA 512 half

The XRPL hashing primitive: compute SHA 512 over the input and take the first 256 bits. Used for all transaction and signing hashes. Multi signing prepends the 4 byte SMT\0 prefix and appends the signer's account id before hashing.

SignerList / SignerListSet

The XRPL transaction type that configures or updates an account's multi signing rules. The transaction lists signers (by public key or account id) with weights, and sets a quorum threshold. The bridge uses SignerListSet to rotate the gateway's signer list to the next Axelar verifier set when the set changes.

Ticket

An XRPL primitive that reserves a sequence number for later use. The multisig account periodically issues TicketCreate transactions to pre allocate a batch of up to 250 tickets. Each subsequent outbound payment consumes one ticket from the pool, which allows multiple in flight bridge transactions to be confirmed out of order without sequence collisions. Tickets are owned objects and count against the account reserve while held.

tfPartialPayment

A Payment transaction flag that, when set, allows the delivered amount to be less than the Amount field (XRPL uses this for path finding through illiquid trust lines). The bridge defends against it: the off chain verifiers reject any inbound payment whose delivered_amount is not equal to Amount, and they also reject payments with any flag set. Without this defense an attacker could spend a tiny amount on XRPL and have a much larger transfer credited on the destination chain.

Trust line

The on chain relationship between two XRPL accounts allowing one (the holder) to hold an IOU issued by the other (the issuer). Trust lines are XRPL's mechanism for opt-in token holding: an account never automatically receives an IOU unless it has previously opened a trust line to the issuer with a non-zero limit. The trust line records the holder, the issuer, the currency code, and the maximum amount the holder is willing to hold. It is created and updated by the TrustSet transaction.

Two trust line scenarios apply to the bridge:

  • For IOUs that originate on XRPL: the multisig account needs a trust line against the issuer so it can hold and later send the IOU back out on outbound transfers.
  • For IOUs that represent assets from another chain: the recipient on XRPL needs a trust line against the multisig account (which is the issuer of these remote-origin IOUs) before they can receive the IOU. Without it the XRPL ledger rejects the inbound Payment.

TrustSet

The XRPL transaction type that creates or modifies a trust line. Submitted by the holder side of the relationship, citing the issuer account, the currency code, and a numeric limit. Unique to XRPL: it does not exist in EVM, Solana, or other chain models the bridge connects to. Each open trust line is an "owned object" on the holder's account and consumes an owner reserve in XRP, which is why opening many local-IOU trust lines on the multisig account drives the dominant variable cost of running the bridge.

The bridge issues TrustSet transactions in one specific case: when an operator calls TrustSet { token_id } on the XRPL Multisig Prover to allow the multisig account to hold a newly-registered XRPL-native IOU. End-user recipients open their own TrustSet transactions independently of the bridge (using a normal XRPL wallet) before they can receive remote-origin tokens.

XRP

The native currency of the XRPL ledger. Denominated in drops at the protocol level. Used by the bridge both as a transferable asset and as the fee/reserve currency for the multisig account itself.

Axelar Amplifier protocol terms

ampd

The Amplifier off chain daemon that each verifier runs. It maintains the Tendermint RPC connection to the Axelar chain, subscribes to chain events, broadcasts Cosmos transactions, and exposes a local gRPC server for chain specific handler binaries to plug into. Documentation lives upstream.

Amplifier

Axelar's modular cross chain protocol. Anyone can permissionlessly connect a new chain by deploying a small set of CosmWasm contracts on Axelar and integrating with the protocol's verifier set. The XRPL integration is one such connection.

Axelar GMP API

A hosted service that sits in front of the Axelar chain and exposes a single HTTP interface for external chain relayers. It abstracts away the internals of Axelar routing: relayers do not need to talk directly to the CosmWasm contracts, subscribe to Tendermint events, or build Cosmos transactions themselves. Instead they post broadcast requests to the API (targeting a contract address) and pull tasks from it for events emitted by the Axelar chain.

Axelarnet Gateway

The CosmWasm contract on Axelar that lets Axelar resident contracts (such as the ITS Hub) send and receive cross chain messages. Documentation lives upstream.

Coordinator

The CosmWasm contract that owns deployment metadata for each connected chain's contract set and acts as the unbonding gate when a verifier wants to withdraw their stake. See coordinator.

Cross chain id (cc_id)

The identifier shared across chains for a single message. Consists of (chain, id), where chain is the source chain name and id is the chain native message identifier (for XRPL, the transaction hash). The router and gateways use cc_id to track a message end to end.

GMP (general message passing)

The capability for one chain to call a contract on another chain, optionally with attached calldata. Token transfers are a special case of GMP; pure GMP carries only calldata. The bridge supports pure GMP from XRPL (a call_contract payment) but not into XRPL (because XRPL has no executable destination).

ITS (Interchain Token Service)

The Axelar protocol layer on top of GMP that handles cross chain tokens: deployment, linking, and transfers. Documentation lives upstream alongside the ITS Hub and the Solidity ITS edge implementation.

ITS edge

The per chain ITS contract that talks to the ITS Hub. On smart contract chains this is a dedicated contract (Solidity, Move, and so on). On XRPL the role is absorbed by xrpl-gateway.

ITS Hub

The CosmWasm contract on Axelar that routes ITS messages between edges. It validates the source, tracks per chain supply for every token id, rescales amounts for decimal differences between chains, and re wraps messages between the SendToHub and ReceiveFromHub envelopes. Documentation lives upstream.

Multisig contract

The generic CosmWasm contract on Axelar that coordinates signing sessions. The XRPL prover opens sessions on this contract; verifiers submit signature shares; once quorum is met, the contract exposes the assembled signatures for the relayer to fetch. See multisig.

Poll

A verification session opened by a voting verifier contract. A weighted snapshot of the active verifier set is taken from the service registry at the moment the poll is opened, and verifiers vote on the candidate messages. A poll ends when its block expiry is reached; results are stored on chain.

Quorum

The weighted threshold required for a poll or signing session to reach consensus. Configured at the voting verifier level (for polls) and at the prover level (for signing sessions). For XRPL the prover's signing_threshold is mirrored into the XRPL SignerListSet, so that the on ledger multisig rules match the on chain Axelar rules for outbound transactions.

Relayer

An off chain process that watches one chain for events and submits the corresponding messages to the next chain in the GMP path. Relayers are permissionless: anyone can run the same code. For XRPL, a dedicated Rust relayer handles XRPL specific quirks (ticket creation, fee reserves, etc.); its source is at axelarnetwork/axelar-relayer-xrpl.

Router

The CosmWasm contract on Axelar that maintains the registry of connected chains and their gateways and routes verified messages from a source chain's gateway to a destination chain's gateway. See router.

Service Registry

The CosmWasm contract that tracks verifier bonding, authorization, and per chain support. The voting verifier and the prover both query it for the active verifier set when opening polls or signing sessions. See service-registry.

Token id

A 32 byte identifier shared across all chains for a given linked token set. Token ids are derived deterministically from a deployer plus salt or canonical bytes, so the same id can be computed on any chain that knows the inputs. The ITS Hub indexes per chain token state by token id.

Token manager

A per token, per chain construct that defines how tokens move on that chain. Common types are MINT_BURN, MINT_BURN_FROM, LOCK_UNLOCK, LOCK_UNLOCK_FEE, and NATIVE_INTERCHAIN_TOKEN. The XRPL side does not have explicit token manager contracts; the multisig account fills the role implicitly by issuing or burning IOUs and by holding or releasing native XRP.

tofnd

The Axelar key management daemon that ampd talks to over gRPC. It holds verifier signing keys and produces signatures on request, supporting both ECDSA (secp256k1) and Ed25519 (the algorithm is selected per request via the Algorithm field). The Cosmos transactions that ampd broadcasts to the Axelar chain are ECDSA-signed; the XRPL multi-sign shares are also ECDSA, but other chains that the verifier supports may use Ed25519 (for example, Solana). Documentation lives upstream.

Verifier

An off chain operator that participates in the Amplifier protocol by bonding stake in the service registry, voting in polls, and signing in multisig sessions. Verifiers are not chain specific; the same verifier can support many chains by registering chain support for each.

Voting Verifier

The CosmWasm contract that runs polls over messages reported from an external chain. There is one voting verifier per connected chain. For XRPL it is xrpl-voting-verifier, which has an XRPL specific poll variant.

Bridge specific terms

AddGas / AddGasMessage

An XRPL message variant used to top up the gas allocation of an in flight cross chain message that the user underfunded. The user sends an XRPL payment to the multisig account with memo type=add_gas and msg_id=<original message id>. After voting verifier confirmation the additional amount is credited to the gateway's accrued gas tally.

AddReserves / AddReservesMessage

An XRPL message variant used by operators to top up the multisig account's XRP balance so it can keep paying transaction fees and meet its reserve requirements. The operator sends XRP to the multisig account with memo type=add_reserves. After confirmation the prover's tracked fee reserve increases.

CallContractMessage

An XRPL message variant used for pure general message passing from XRPL. The XRPL Payment carries no transferable value beyond the gas fee, but it carries a payload memo whose contents are delivered to the destination contract. There is no inbound counterpart; XRPL cannot host an executable destination.

InterchainTransferMessage

An XRPL message variant used for cross chain token transfers originating on XRPL. The Payment carries the transfer amount plus the gas fee, and optional payload.

ProverMessage

An XRPL specific XRPLMessage (the enum defined in upstream packages/xrpl-types) variant. After the multisig account submits a prover-built XRPL transaction (a Payment, SignerListSet, TicketCreate, or TrustSet) to the ledger, the relayer reports the inclusion (or failure) of that transaction back to Axelar as a ProverMessage. The xrpl-voting-verifier opens a poll on it, verifiers look up the XRPL transaction over JSON RPC and vote, and once quorum is reached the relayer calls ConfirmProverMessage on the xrpl-multisig-prover. That call releases the ticket, clears the stored payload, and (for SignerListSet) promotes the new verifier set. This is the only mechanism by which the Axelar side learns that an outbound XRPL transaction landed, because XRPL has no contract that could emit an event for it.

Wrapped XRP (wXRP)

The on chain representation of native XRP on chains other than XRPL. On XRPL EVM in particular, wXRP is an ERC-20 contract minted by the local ITS edge when XRP arrives over the bridge and burned when XRP leaves to be redeemed for native XRP on XRPL. The bridge treats wXRP and native XRP as two representations of the same token id, with the ITS Hub tracking per chain supply.

xrpl-gateway

The CosmWasm contract on Axelar that acts as the gateway and ITS edge for XRPL traffic. See xrpl-gateway.

xrpl-multisig-prover

The CosmWasm contract on Axelar that builds and tracks XRPL transactions submitted by the multisig account. See xrpl-multisig-prover.

xrpl-voting-verifier

The CosmWasm contract on Axelar that runs polls over XRPL transactions, including outbound prover transactions reported back as ProverMessage. See xrpl-voting-verifier.