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:
| Guide | Why |
|---|---|
| Getting Started | Platform overview and setup |
| Api Basics | Required headers and request configuration |
| Authentication | How to obtain access tokens |
| Onboarding | The EVM registration flow this page diverges from |
| Withdrawals | The 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
| Aspect | EVM | XRPL |
|---|---|---|
| Account model | Smart Wallet (Account Abstraction), deployed by the client | Multisig ledger account, created by Wirex |
| Account creation | Deploy contract, install modules, register on-chain | POST /api/v1/accounts |
| Account control | User's key controls the wallet; the oracle holds a scoped executor | User's key is one of four signers, quorum of two |
| Address format | 0x + 40 hex characters | Classic r-address (base58), 25–35 characters |
| Signing curve | secp256k1 (EIP-191 personal_sign) | ed25519 or secp256k1 |
| Action confirmation | Signature only | Signature and public_key |
| Withdrawal | Two-phase: request, wait for delay, execute on-chain | POST /api/v1/withdrawal/execute, immediate |
| Who signs a withdrawal | The user's wallet | Wirex, using two of the account's signers |
| Settlement asset | USDC, USDT, AUSD, WUSD, WEUR | RLUSD |
| Smart contracts | ContractRegistry, ABIs, module calls | None |
| Transaction hash | 0x + 64 hex characters | 64 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.
| Address | Derived from | Used as |
|---|---|---|
| Owner address | The user's public key | The X-User-Address header, and the wallet_address field of POST /api/v2/user |
| Account address | Generated by Wirex | The 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.
| Signer | Held by |
|---|---|
| User key | The user |
| Processor key | Wirex |
| Validator key | Wirex |
| Recovery key | Independent 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"
}| Field | Type | Required | Description |
|---|---|---|---|
user_public_key | string | Yes | Hex-encoded XRPL public key. ED prefix for ed25519, 02 or 03 for secp256k1 |
Response
{
"wallet_address": "rBzvC6h3eoi8La1fFZPNbxQKwoAhqRaenw"
}| Field | Description |
|---|---|
wallet_address | The 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
}| Field | Type | Required | Description |
|---|---|---|---|
action_type | string | Yes | The action being confirmed |
message_signature | string | Yes | Hex-encoded signature over the message below |
public_key | string | Conditional | Required on XRPL. Ignored on EVM and Stellar |
nonce | integer | Yes | Unix 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 Reason | Error Details | Description |
|---|---|---|
ErrorMissingField | field: public_key | public_key omitted on XRPL |
ErrorInvalidField | field: public_key | Not hex, or does not derive to a valid address |
ErrorInvalidField | field: signature | Signature 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
}| Field | Type | Required | Description |
|---|---|---|---|
destination | string | Yes | Recipient classic r-address |
token_address | string | Yes | The RLUSD issuer address. Read it from GET /api/v1/wallet |
amount | number | Yes | Amount to withdraw. Must be greater than zero |
destination_tag | string | No | Accepted 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"
}| Field | Description |
|---|---|
transaction_hash | XRPL 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 Reason | Error Details | Description |
|---|---|---|
ErrorNotSupported | field: X-Chain-Id | Chain is not XRPL |
ErrorInvalidField | field: destination | Not a valid classic r-address |
ErrorInvalidField | field: token_address | Not a valid issuer address |
ErrorInvalidField | field: amount | Amount is zero or negative |
ErrorNotFound | field: token_address | Token is not registered on XRPL |
ErrorNotFound | — | No 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:
| Capability | Notes |
|---|---|
| Batch transfers | No batching primitive |
| Approval-based transfers | XRPL has no allowance or approve primitive |
| Off-ramp (crypto to fiat) withdrawals | Not implemented for XRPL |
| Synthetic token wrapping | RLUSD is held directly |
| Wallet migration flows | Not implemented for XRPL |
| ContractRegistry and ABIs | No smart contracts. ABI Reference does not apply |
| ExecutionDelayPolicy | No withdrawal time-lock |
| Global addresses | Bridged 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.

