> ## Documentation Index
> Fetch the complete documentation index at: https://docs.privy.io/llms.txt
> Use this file to discover all available pages before exploring further.

# XRPL

> Create XRPL wallets, sign transactions, and enforce policies with Privy

With Privy, your app can create embedded XRPL wallets, sign transactions, and enforce
Privy's [policy engine](/controls/policies/overview) to constrain what each wallet can do.

XRPL wallets use the secp256k1 curve with BIP-44 derivation path `m/44'/144'/0'/0/{index}`.
Your app signs XRPL transactions via the `xrpl_signTransaction` RPC method or via
[raw hash signing](/wallets/using-wallets/other-chains). Then, submit the signed transaction
to the network using [xrpl.js](https://js.xrpl.org/).

## Features

* **Embedded XRPL wallets**: create wallets with the `xrpl` chain type. Addresses use the standard `r`-prefix format.
* **Transaction signing**: sign any XRPL transaction via `xrpl_signTransaction`, which returns a DER-encoded signature and a ready-to-submit transaction blob.
* **Raw hash signing**: sign arbitrary hashes with `raw_sign` for full control over serialization.
* **Policy enforcement**: constrain transactions with Privy's policy engine — restrict destinations, cap amounts, and limit transaction types.
* **Key export**: export the hex private key or BIP-39 seed phrase for full portability.

## Account activation

<Warning>
  XRPL wallets do not exist on-ledger until they receive the base reserve (currently 10 XRP).
  Calling `account_info` on an unfunded address returns `actNotFound`. Your app must fund the wallet
  before it can submit transactions.
</Warning>

On testnet, fund wallets using the [XRPL faucet](https://xrpl.org/resources/dev-tools/xrp-faucets):

```bash theme={"system"}
POST https://faucet.altnet.rippletest.net/accounts
```

## Create a wallet

```typescript {skip-check} theme={"system"}
import {PrivyClient} from '@privy-io/node';

const privy = new PrivyClient({
  appId: process.env.PRIVY_APP_ID,
  appSecret: process.env.PRIVY_APP_SECRET
});

const wallet = await privy.wallets().create({chain_type: 'xrpl'});
// wallet.address: r-address (e.g. "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")
// wallet.public_key: 33-byte compressed secp256k1 pubkey, hex, no 0x prefix
```

## Sign a transaction

Sign an XRPL transaction using the `xrpl_signTransaction` RPC method. Your app builds and
encodes the transaction with xrpl.js, passes the prefixed bytes to Privy, and receives a
signed transaction blob ready to submit.

<View title="REST API" icon="terminal">
  To sign a transaction, make a `POST` request to:

  ```bash theme={"system"} theme={"system"}
  https://api.privy.io/v1/wallets/<wallet_id>/rpc
  ```

  ### Usage

  ```bash theme={"system"} theme={"system"}
  $ curl --request POST https://api.privy.io/v1/wallets/<wallet_id>/rpc \
  -u "<your-privy-app-id>:<your-privy-app-secret>" \
  -H "privy-app-id: <your-privy-app-id>" \
  -H "privy-authorization-signature: <authorization-signature>" \
  -H 'Content-Type: application/json' \
  -d '{
    "method": "xrpl_signTransaction",
    "params": {
      "transaction": "53545800<STObject-encoded transaction bytes in hex>",
      "encoding": "hex"
    }
  }'
  ```

  A successful response looks like:

  ```json theme={"system"} theme={"system"}
  {
    "method": "xrpl_signTransaction",
    "data": {
      "txn_signature": "<DER-encoded ECDSA signature, hex, no 0x prefix>",
      "signed_transaction": "<full signed transaction blob, hex, no 0x prefix>",
      "encoding": "hex"
    }
  }
  ```

  ### Parameters

  <ParamField body="method" type="string" required>
    Must be `"xrpl_signTransaction"`.
  </ParamField>

  <ParamField body="params.transaction" type="string" required>
    The XRPL signing prefix `53545800` concatenated with the STObject-encoded transaction bytes, as an
    even-length hex string. Use `encodeForSigning()` from xrpl.js to produce this directly.
  </ParamField>

  <ParamField body="params.encoding" type="string" required>
    Must be `"hex"`.
  </ParamField>

  ### Returns

  <ResponseField name="data.txn_signature" type="string">
    DER-encoded ECDSA signature (hex, no `0x` prefix). This is the value for the `TxnSignature` field
    in the signed transaction.
  </ResponseField>

  <ResponseField name="data.signed_transaction" type="string">
    The fully signed XRPL transaction blob (hex, no `0x` prefix), ready to pass to `client.submit()`
    from xrpl.js.
  </ResponseField>

  <ResponseField name="data.encoding" type="string">
    Always `"hex"`.
  </ResponseField>
</View>

<View title="NodeJS" icon="node-js">
  ```typescript theme={"system"} {skip-check} theme={"system"}
  import {PrivyClient} from '@privy-io/node';
  import {Client as XrplClient, encodeForSigning, xrpToDrops} from 'xrpl';

  const privy = new PrivyClient({
    appId: process.env.PRIVY_APP_ID,
    appSecret: process.env.PRIVY_APP_SECRET
  });

  const walletId = 'insert-wallet-id';
  const walletAddress = 'insert-wallet-address';
  const walletPublicKey = 'insert-wallet-public-key';

  // 1. Connect to XRPL and build a transaction
  const xrpl = new XrplClient('wss://s.altnet.rippletest.net:51233');
  await xrpl.connect();

  const prepared = await xrpl.autofill({
    TransactionType: 'Payment',
    Account: walletAddress,
    Destination: 'ra5nK24KXen9AHvsdFTKHSANinZseWnPcX',
    Amount: xrpToDrops('10')
  });

  // 2. Add SigningPubKey and encode with the signing prefix
  const forSigning = {...prepared, SigningPubKey: walletPublicKey};
  const prefixedHex = encodeForSigning(forSigning);

  // 3. Sign via xrpl_signTransaction RPC
  const {data} = await privy.wallets().rpc(walletId, {
    method: 'xrpl_signTransaction',
    params: {
      transaction: prefixedHex,
      encoding: 'hex'
    }
  });

  // 4. Submit the signed transaction blob
  const result = await xrpl.submit(data.signed_transaction);
  console.log('Result:', result.result.engine_result);

  await xrpl.disconnect();
  ```
</View>

## Sign a raw hash

For full control over serialization, your app can compute the XRPL signing hash and sign it
directly with `raw_sign` in hash mode.

<View title="REST API" icon="terminal">
  To sign a raw hash, make a `POST` request to:

  ```bash theme={"system"} theme={"system"}
  https://api.privy.io/v1/wallets/<wallet_id>/raw_sign
  ```

  ### Usage

  ```bash theme={"system"} theme={"system"}
  $ curl --request POST https://api.privy.io/v1/wallets/<wallet_id>/raw_sign \
  -u "<your-privy-app-id>:<your-privy-app-secret>" \
  -H "privy-app-id: <your-privy-app-id>" \
  -H "privy-authorization-signature: <authorization-signature>" \
  -H 'Content-Type: application/json' \
  -d '{
    "params": {
      "hash": "0x<SHA-512Half of the signing-prefixed transaction bytes>"
    }
  }'
  ```

  A successful response looks like:

  ```json theme={"system"} theme={"system"}
  {
    "method": "raw_sign",
    "data": {
      "signature": "<compact (r||s) ECDSA signature, hex, no 0x prefix>",
      "encoding": "hex"
    }
  }
  ```

  ### Parameters

  <ParamField body="params.hash" type="string" required>
    The hash to sign, as an even-length hex string prefixed with `0x`. For XRPL, this is the
    SHA-512Half of the signing-prefixed, STObject-encoded transaction bytes.
  </ParamField>

  ### Returns

  <ResponseField name="data.signature" type="string">
    Compact (`r||s`) ECDSA signature, hex, no `0x` prefix. XRPL requires a DER-encoded signature for
    `TxnSignature`, so convert this before attaching it to the transaction.
  </ResponseField>

  <ResponseField name="data.encoding" type="string">
    Always `"hex"`.
  </ResponseField>
</View>

<View title="NodeJS" icon="node-js">
  ```typescript theme={"system"} {skip-check} theme={"system"}
  import {PrivyClient} from '@privy-io/node';
  import {Signature} from '@noble/secp256k1';
  import {createHash} from 'node:crypto';
  import {encode, encodeForSigning} from 'xrpl';

  const privy = new PrivyClient({
    appId: process.env.PRIVY_APP_ID,
    appSecret: process.env.PRIVY_APP_SECRET
  });

  // Compute the SHA-512Half of the signing-prefixed transaction
  const forSigning = {...prepared, SigningPubKey: walletPublicKey};
  const prefixedHex = encodeForSigning(forSigning);
  const sha512Half = createHash('sha512')
    .update(Buffer.from(prefixedHex, 'hex'))
    .digest()
    .subarray(0, 32);

  // Sign the hash with Privy
  const {signature} = await privy.wallets().rawSign(walletId, {
    params: {
      hash: `0x${sha512Half.toString('hex')}`
    }
  });

  // Convert compact signature (r||s) to DER for XRPL's TxnSignature
  const compactHex = signature.replace(/^0x/, '');
  const derHex = Signature.fromCompact(compactHex).toDERHex();

  // Attach the signature, encode the final blob, and submit
  const signedTx = encode({...forSigning, TxnSignature: derHex});
  const result = await xrpl.submit(signedTx);
  console.log('Result:', result.result.engine_result);
  ```
</View>

<Info>
  `xrpl_signTransaction` handles DER encoding and blob assembly automatically. Use `raw_sign` only
  when your app needs to control the full signing pipeline.
</Info>

## Export keys

XRPL wallets support both private key export (raw hex format) and BIP-39 seed phrase export.

The exported private key is a raw hex secp256k1 key. XRPL's native "family seed" format
(base58-encoded 16-byte seed) is not compatible with BIP-44 derivation and is not supported.

## Enforce policies

XRPL wallets support Privy's policy engine, so your app can constrain transactions signed via
`xrpl_signTransaction`:

* **Transaction types**: restrict which transaction types a wallet can sign using the `TransactionType` field.
* **Payment destinations**: allowlist or denylist recipient addresses using `Payment.Destination`.
* **Amount limits**: cap XRP payment amounts using `Payment.Amount.drops`, or IOU values using `Payment.Amount.value`.
* **Offer limits**: constrain DEX offers using `OfferCreate.TakerPays` and `OfferCreate.TakerGets` fields.
* **Trust line limits**: restrict trust line creation using `TrustSet.LimitAmount` fields.
* **System conditions**: gate signing on values such as the current timestamp using the `system` field source.

See [XRPL policy examples](/controls/policies/example-policies/xrpl) for complete configuration examples.

### Supported transaction types for policy evaluation

Policies can evaluate decoded fields on these transaction types: `Payment`, `OfferCreate`,
`OfferCancel`, and `TrustSet`.

<Info>
  Wallets with policies can sign any valid XRPL transaction type. However, if the transaction type
  is not supported for policy evaluation, the request is denied unless a matching system-level rule
  (e.g., a time-based condition) allows it.
</Info>
