Ripple (XRPL) Integration Addendum

XRPL-specific endpoints, addresses, signing, and settlement behavior that differ from the EVM integration.

Before You Start

Read the following guides before proceeding:

GuideWhy
Getting StartedPlatform overview and setup
Api BasicsRequired headers and request configuration
AuthenticationHow to obtain access tokens
OnboardingThe EVM registration flow this page diverges from
WithdrawalsThe EVM withdrawal flow this page replaces

Overview

XRP Ledger (XRPL) is a non-EVM chain. It is selected with the X-Chain-Id header, the same way Base or
any other supported chain is selected.

This page documents only where XRPL behavior differs from the EVM integration. Anything not listed
here is unchanged: authentication, KYC, user profiles, activity feeds, statements, recipients, card
management, 3DS, and card top-up all work identically on XRPL.

Two endpoints exist only on XRPL and reject every other chain:

POST /api/v1/accounts
POST /api/v1/withdrawal/execute

Differences at a Glance

AspectEVMXRPL
Account modelSmart Wallet (Account Abstraction), deployed by the clientMultisig ledger account, created by Wirex
Account creationDeploy contract, install modules, register on-chainPOST /api/v1/accounts
Account controlUser's key controls the wallet; the oracle holds a scoped executorUser's key is one of four signers, quorum of two
Address format0x + 40 hex charactersClassic r-address (base58), 25–35 characters
Signing curvesecp256k1 (EIP-191 personal_sign)ed25519 or secp256k1
Action confirmationSignature onlySignature and public_key
WithdrawalTwo-phase: request, wait for delay, execute on-chainPOST /api/v1/withdrawal/execute, immediate
Who signs a withdrawalThe user's walletWirex, using two of the account's signers
Settlement assetUSDC, USDT, AUSD, WUSD, WEURRLUSD
Smart contractsContractRegistry, ABIs, module callsNone
Transaction hash0x + 64 hex characters64 hex characters, no prefix

Chain Selection

Both XRPL-only endpoints read X-Chain-Id and reject any chain that is not XRPL.

The XRPL chain ID is a Wirex-assigned identifier. It is not an EIP-155 chain ID, because XRPL does not
define one. The value used in the sandbox environment is:

X-Chain-Id: 9223372036854775805

Confirm the chain ID for your target environment with Wirex before going to production. Sandbox and
production values are assigned per environment.

Sending any other chain ID to an XRPL-only endpoint returns HTTP 400:

{
  "error_reason": "ErrorNotSupported",
  "error_description": "Chain not supported for account creation",
  "error_category": { "category": "CategoryValidationFailure", "http_status_code": 400 },
  "error_details": [
    { "key": "field", "details": "X-Chain-Id" },
    { "key": "X-Chain-Id", "details": "Only XRPL chain supported" }
  ]
}

Addresses and Keys

XRPL onboarding produces two distinct addresses. Keeping them straight is the single most common
source of integration errors.

AddressDerived fromUsed as
Owner addressThe user's public keyThe X-User-Address header, and the wallet_address field of POST /api/v2/user
Account addressGenerated by WirexThe address funds are held at and withdrawn from

The account address is returned as wallet_address by POST /api/v1/accounts. The field name
wallet_address therefore refers to different addresses in that response and in the
POST /api/v2/user request body. Read the table above before wiring them together.

Only classic r-addresses are accepted. X-addresses are rejected, including in the destination field
of a withdrawal, despite an error message that mentions them.

The user's key is one of four entries on the account's signer list. See Keys and Account Control.


Keys and Account Control

The user generates their own keypair and keeps it. Wirex never receives the user's private key, and holds
no share or fragment of it.

The account is governed by an XRPL signer list of four independent keys of equal weight, with a quorum of
two. Any two signers together authorize a transaction.

SignerHeld by
User keyThe user
Processor keyWirex
Validator keyWirex
Recovery keyIndependent recovery provider

Each signer holds a distinct key and signs its own copy of a transaction; the signatures are then combined.
These are not shares of one split key, and there is no reconstruction step.

At account creation the user delegates operational authority to Wirex's two signers, which together meet
the quorum. That delegation is what lets withdrawals, card fee payments, and card top-ups execute
immediately, without prompting the user to sign each transaction — the behavior the rest of this guide
describes.

The delegation is revocable. The user's key and the recovery provider's key also meet the quorum, so the
user can move funds and replace the signer list at any time, without Wirex's participation.

The account's master key is disabled permanently at setup, so the signer list is the only path to
authorizing a transaction.


Onboarding

The EVM flow deploys a Smart Wallet and registers it on-chain before calling the API. XRPL replaces that
entirely with one API call. There is no contract deployment, no module installation, and no
ContractRegistry.

1. Generate keypair  →  2. POST /api/v1/accounts  →  3. Wait for provisioning  →  4. POST /api/v2/user

Step 1 — Generate an XRPL keypair

Generate an ed25519 keypair. Retain the seed; the public key and the derived classic address are both
needed below. secp256k1 keys are also accepted.

Step 2 — Create the account

POST /api/v1/accounts

Authenticate with your partner access token. This endpoint does not require the X-User-Address header,
because the user does not exist yet.

Request

{
  "user_public_key": "ED5F5AC8B98974A3CA843326D9B88CEBD0560177B973EE0B149F782CFAA06DC66A"
}
FieldTypeRequiredDescription
user_public_keystringYesHex-encoded XRPL public key. ED prefix for ed25519, 02 or 03 for secp256k1

Response

{
  "wallet_address": "rBzvC6h3eoi8La1fFZPNbxQKwoAhqRaenw"
}
FieldDescription
wallet_addressThe XRPL account address that will hold the user's funds
const response = await fetch(`${baseUrl}/api/v1/accounts`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-Chain-Id': chainId,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ user_public_key: userPublicKey })
});
const { wallet_address: accountAddress } = await response.json();
response = requests.post(
    f"{base_url}/api/v1/accounts",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-Chain-Id": chain_id,
        "Content-Type": "application/json"
    },
    json={"user_public_key": user_public_key}
)
account_address = response.json()["wallet_address"]
body, _ := json.Marshal(map[string]string{"user_public_key": userPublicKey})
req, _ := http.NewRequest("POST", baseURL+"/api/v1/accounts", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-Chain-Id", chainId)
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var data AccountCreateResponse
json.NewDecoder(resp.Body).Decode(&data)

Step 3 — Wait for provisioning

POST /api/v1/accounts returns as soon as the account address is allocated. Wirex then provisions the
account on the ledger: it funds the XRP reserve, installs the signer list, opens a trust line for each
supported token, and disables the master key.

The account cannot receive or send funds until provisioning completes. Wait for the wallets webhook
before proceeding. Do not treat the HTTP 200 as a readiness signal.

Wirex funds the XRP base reserve and opens the trust lines. Neither is the partner's responsibility.

Step 4 — Register the user

POST /api/v2/user

Send the owner address — the classic r-address derived from user_public_key — as
wallet_address. Do not send the account address returned in Step 2.

This mirrors the EVM flow, where wallet_address carries the user's EOA rather than the Smart Wallet
address. KYC and verification then proceed unchanged.


Action Confirmation Signatures

Sensitive actions, such as retrieving card details, require a signed confirmation token. The endpoint is
unchanged:

POST /api/v1/confirmation/signature/verify

On EVM, the signer's address is recovered from the signature. XRPL signatures do not carry the public
key, so it must be supplied explicitly.

Request

{
  "action_type": "GetCardDetails",
  "message_signature": "C09C5DBA3544685EB140C71745C2E8FDD85F31A95AF896083E690424C473CB2A8500C39DB69284FFBFF4685404BAD99F5436A7FBA7C9C17179AE4BB0996A0008",
  "public_key": "EDA57EBBCB502C2009EFE17229E8DC865DCCB192C52D7888D624DC9EBADDB815F0",
  "nonce": 1234567890
}
FieldTypeRequiredDescription
action_typestringYesThe action being confirmed
message_signaturestringYesHex-encoded signature over the message below
public_keystringConditionalRequired on XRPL. Ignored on EVM and Stellar
nonceintegerYesUnix timestamp used to build the message

The signed message is the exact string:

By signing this I confirm that I am executing action {action_type} at {nonce}

Sign the raw message. XRPL uses no EIP-191 prefix and no pre-hashing.

The verification algorithm is chosen from the public key prefix: ED selects ed25519, 02 or 03
selects secp256k1 with a DER-encoded signature. The public key must derive to the address in
X-User-Address, otherwise the request is rejected.

Error ReasonError DetailsDescription
ErrorMissingFieldfield: public_keypublic_key omitted on XRPL
ErrorInvalidFieldfield: public_keyNot hex, or does not derive to a valid address
ErrorInvalidFieldfield: signatureSignature does not verify against the message

Withdrawals

The EVM withdrawal is a two-phase, client-executed flow: request the transfer, wait out the
ExecutionDelayPolicy time-lock, then submit the execution transaction from the Smart Wallet.

None of that applies on XRPL. A withdrawal is a single API call, executed immediately, co-signed by
Wirex under the authority the user delegated at account creation. There is no time-lock, no transfer
request to poll, and no user signature. See Keys and Account Control.

POST /api/v1/withdrawal/execute

Request

{
  "destination": "rBzvC6h3eoi8La1fFZPNbxQKwoAhqRaenw",
  "token_address": "rG31cLyErnqeVj2eomEjBZtq7PYaupGYzL",
  "amount": 10.50
}
FieldTypeRequiredDescription
destinationstringYesRecipient classic r-address
token_addressstringYesThe RLUSD issuer address. Read it from GET /api/v1/wallet
amountnumberYesAmount to withdraw. Must be greater than zero
destination_tagstringNoAccepted but not applied. See Known Limitations

token_address is the issuer's r-address, not a contract address. Take it from the balance entry whose
token_symbol is RLUSD in the unified balance response rather than hardcoding it.

amount is a JSON number. It is truncated — not rounded — to 6 decimal places. Send at most 6 decimal
places to avoid silently losing precision.

Response

{
  "transaction_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
FieldDescription
transaction_hashXRPL transaction hash, 64 lowercase hex characters, no 0x prefix

XRPL explorers display this hash in uppercase. It is the same hash.

const response = await fetch(`${baseUrl}/api/v1/withdrawal/execute`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userAddress,
    'X-Chain-Id': chainId,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    destination: destinationAddress,
    token_address: tokenAddress,
    amount: 10.50
  })
});
const { transaction_hash: transactionHash } = await response.json();
response = requests.post(
    f"{base_url}/api/v1/withdrawal/execute",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-User-Address": user_address,
        "X-Chain-Id": chain_id,
        "Content-Type": "application/json"
    },
    json={
        "destination": destination_address,
        "token_address": token_address,
        "amount": 10.50
    }
)
transaction_hash = response.json()["transaction_hash"]
body, _ := json.Marshal(WithdrawalExecuteRequest{
    Destination:  destinationAddress,
    TokenAddress: tokenAddress,
    Amount:       10.50,
})
req, _ := http.NewRequest("POST", baseURL+"/api/v1/withdrawal/execute", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userAddress)
req.Header.Set("X-Chain-Id", chainId)
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var data WithdrawalExecuteResponse
json.NewDecoder(resp.Body).Decode(&data)

Errors

Error ReasonError DetailsDescription
ErrorNotSupportedfield: X-Chain-IdChain is not XRPL
ErrorInvalidFieldfield: destinationNot a valid classic r-address
ErrorInvalidFieldfield: token_addressNot a valid issuer address
ErrorInvalidFieldfield: amountAmount is zero or negative
ErrorNotFoundfield: token_addressToken is not registered on XRPL
ErrorNotFoundNo wallet found for the user

Deposits

A user is funded by sending RLUSD to their account address — the wallet_address from
POST /api/v1/accounts. No destination tag or memo is required, and the account does not require one.

Wirex detects the payment from the ledger, credits the balance, and emits the standard activities and
balances webhooks. There is no XRPL deposit-address endpoint; the account address is the deposit target.

Bridged deposits via global addresses are not available on XRPL.


Settlement Asset

RLUSD is the settlement asset on XRPL, with 6 decimals. XRP is used only for the account reserve and
transaction fees, and is never a settlement asset.

The unified WUSD and WEUR wrapping described in unified balance is specific to
Base. On XRPL, balances are held directly in RLUSD.

A trust line to the RLUSD issuer is required before an account can hold RLUSD. Wirex opens it during
provisioning. If you send RLUSD from your own XRPL account, that account needs its own trust line.


Cards on XRPL

Card issuance, management, transactions, 3DS, and top-up behave identically to EVM.

One difference: where the EVM flow pays a card issuance fee by submitting a transfer from the user's
Smart Wallet, on XRPL the fee is paid through POST /api/v1/withdrawal/execute, using the invoice's
recipient address as destination. Use the returned transaction_hash as the payment transaction hash.

This follows from the delegation described in Keys and Account Control: value
movement is co-signed by Wirex through the API rather than broadcast by the client.


Events and Webhooks

Webhook payloads are unchanged. The wallets, balances, cards, card-limits, 3ds, activities,
and recipients webhooks all fire on XRPL.

The erc-withdrawals webhook does not fire. It is delivered when an ERC20 withdrawal is requested and
pending user signature — a state that does not exist on XRPL, where a withdrawal is executed immediately
and requires no user signature.

Track XRPL withdrawals through the transaction_hash returned by the call, and through the activities
and balances webhooks.


Not Supported on XRPL

These platform capabilities have no XRPL implementation and return an error if invoked:

CapabilityNotes
Batch transfersNo batching primitive
Approval-based transfersXRPL has no allowance or approve primitive
Off-ramp (crypto to fiat) withdrawalsNot implemented for XRPL
Synthetic token wrappingRLUSD is held directly
Wallet migration flowsNot implemented for XRPL
ContractRegistry and ABIsNo smart contracts. ABI Reference does not apply
ExecutionDelayPolicyNo withdrawal time-lock
Global addressesBridged deposits are unavailable

Do not offer crypto-to-fiat withdrawal from an XRPL account. Confirm the availability of incoming bank
transfers into an XRPL account with Wirex before relying on them.


Known Limitations

X-addresses are rejected

Only classic r-addresses are accepted. Validation errors on the destination field mention X-address
support, which is not implemented.