Agent wallet guide · October 2026

Privy XRPL wallets for AI agents: what the policy checks, and what it lets through.

Frits Zwager, Fizzl · 21 October 2026, updated 22 October · Based on Privy's XRPL documentation; verdicts from presign-guard against the XRP Ledger mainnet

Update, 22 October 2026: Privy changed its denylist example the day after we reported the gap. Its fallback rule is now "Allow other Payments", limited to TransactionType Payment, with a new warning: "A Payment.Destination denylist only screens Payment transactions. … Never pair a denylist with an unconditional ALLOW." Thanks to the Privy team. Policies copied from the old example still have the gap described below, so check yours.

On 13 October Privy and Ripple announced XRP Ledger wallets in Privy: an app or an agent creates an XRPL wallet, builds a transaction with xrpl.js, and Privy signs it with xrpl_signTransaction. Privy's policy engine decides what each wallet may sign. For an agent that pays x402 sellers in RLUSD, that policy is the guard between the agent and its funds.

This guide says what the policy can read, where Privy's own sanctions denylist example leaves a gap, a tighter policy for x402 payments, and how to check the transactions a policy can't read before they are signed.

What a Privy XRPL policy reads

Privy's documentation: "The policy engine inspects fields on these transaction types: Payment, OfferCreate, OfferCancel, and TrustSet." A rule can match their fields, such as Payment.Destination or Payment.Amount.value.

Every other type has no fields a rule can match. Only a rule without transaction conditions decides it: an empty condition list, or a system condition such as the time. A request that no rule allows is refused, which is the safe default. raw_sign requests have no decoded transaction at all, so their rules can only use system conditions.

The gap in the old denylist example

Until 22 October, Privy's example "Denylist Payment destinations" had two rules for xrpl_signTransaction: deny a Payment whose Payment.Destination is in a condition set of sanctioned addresses, then "Allow all other requests" with an empty condition list. Privy's note explained it: "every other request falls through to the ALLOW rule." A policy copied from it still works this way.

So that denylist stops one transaction type. The same value can reach the same address in other ways, and the account itself can be handed over:

TransactionWhat it doesOld denylist examplepresign-guard
Payment to a sanctioned addressPays itRefusedred · SANCTIONED_ADDRESS
CheckCreate to that addressA check it can cashSignedred · SANCTIONED_ADDRESS, XRPL_CHECK_TO_OTHER
EscrowCreate to that addressFunds it receives laterSignedred · SANCTIONED_ADDRESS, XRPL_ESCROW_TO_OTHER
PaymentChannelCreate to that addressA channel it can claim fromSignedred · SANCTIONED_ADDRESS, XRPL_CHANNEL_TO_OTHER
Batch with that Payment insideThe outer type is Batch, so no Payment.Destination to matchSignedred · SANCTIONED_ADDRESS (inner transaction)
AccountDeleteSends all remaining XRP to the destinationSignedred · XRPL_ACCOUNT_DELETE
SetRegularKey to another keyThat key can sign for the accountSignedred · XRPL_REGULAR_KEY_CHANGE
SignerListSet with other signersThey can sign for the accountSignedred · XRPL_SIGNER_LIST_CHANGE
DelegateSet with PaymentAnother account can pay itself from yoursSignedred · XRPL_DELEGATE_FUNDS

The "Old denylist example" column follows the rule order Privy documented until 22 October; we did not send these through a Privy app. With Privy's updated example, all of them except the Payment fall to the default refusal. The presign-guard column is the real check, run on 21 October against the mainnet with an address from the OFAC SDN list.

An agent rarely asks for these on its own. It does when a prompt injection, a malicious tool result or a seller's instructions tell it to, which is exactly the case a policy is there for.

A tighter policy for an agent that pays x402 sellers in RLUSD

Allow only what the agent needs, deny sanctioned destinations, and let everything else fall to Privy's default refusal:

{
  "version": "1.0",
  "name": "x402 on the XRP Ledger: RLUSD only, capped, never to a sanctioned address",
  "chain_type": "xrpl",
  "rules": [
    {
      "name": "Never pay a sanctioned address",
      "method": "xrpl_signTransaction",
      "conditions": [
        { "field_source": "xrpl_transaction", "field": "Payment.Destination", "operator": "in_condition_set", "value": "<PRIVY_XRPL_SET_ID>" }
      ],
      "action": "DENY"
    },
    {
      "name": "RLUSD from Ripple's issuer, at most 1 per payment",
      "method": "xrpl_signTransaction",
      "conditions": [
        { "field_source": "xrpl_transaction", "field": "TransactionType", "operator": "eq", "value": "Payment" },
        { "field_source": "xrpl_transaction", "field": "Payment.Amount.currency", "operator": "eq", "value": "524C555344000000000000000000000000000000" },
        { "field_source": "xrpl_transaction", "field": "Payment.Amount.issuer", "operator": "eq", "value": "rMxCKbEDwqr76QuheSUMdEGf4B9xJ8m5De" },
        { "field_source": "xrpl_transaction", "field": "Payment.Amount.value", "operator": "lte", "value": "1" }
      ],
      "action": "ALLOW"
    },
    {
      "name": "Hold RLUSD: a trust line to Ripple's issuer only",
      "method": "xrpl_signTransaction",
      "conditions": [
        { "field_source": "xrpl_transaction", "field": "TransactionType", "operator": "eq", "value": "TrustSet" },
        { "field_source": "xrpl_transaction", "field": "TrustSet.LimitAmount.currency", "operator": "eq", "value": "524C555344000000000000000000000000000000" },
        { "field_source": "xrpl_transaction", "field": "TrustSet.LimitAmount.issuer", "operator": "eq", "value": "rMxCKbEDwqr76QuheSUMdEGf4B9xJ8m5De" }
      ],
      "action": "ALLOW"
    }
  ]
}

When the agent needs more: check before signing

Some agents do need other types, for example a DEX order or an escrow. Then the policy can't judge them, and the check has to happen before xrpl_signTransaction is called. presign-guard reads every XRPL transaction type in the table above, every inner transaction of a Batch, and screens every destination, new key, signer and delegate against the OFAC SDN list. With Privy's Node SDK:

import { encodeForSigning } from "xrpl";

// prepared = await xrpl.autofill({ TransactionType: ..., Account: walletAddress, ... })
// payFetch: any fetch that pays x402, e.g. createSafeFetch from x402-safe-fetch
const res = await payFetch("https://presign-guard.fizzl.eu/v1/check", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ type: "xrpl", network: "xrpl:0", tx: prepared }),
});
const { verdict, reasons } = await res.json();
if (verdict !== "green") {
  // red: don't sign. orange: show the reasons to a person first.
  throw new Error(`presign-guard ${verdict}: ${reasons.map((r) => r.code).join(", ")}`);
}

const { data } = await privy.wallets().rpc(walletId, {
  method: "xrpl_signTransaction",
  params: { transaction: encodeForSigning({ ...prepared, SigningPubKey: walletPublicKey }), encoding: "hex" },
});

The full check costs $0.01, paid over x402 in RLUSD on the XRP Ledger or USDC on Base, Solana, Algorand or Stellar. A failed check is never charged and never comes back green. For the verdict only, free and limited per hour, an agent can call presign_quick_check on presign-guard's MCP server (https://presign-guard.fizzl.eu/mcp) with the same {"type": "xrpl", "tx": {…}}.

Before paying a seller, x402 Doctor's preflight also checks the seller's XRPL side: whether the payout account exists, has the RLUSD trust line, accepts the payment, and is not on the sanctions list.

Questions

Which XRPL transaction types can a Privy policy read?

Payment, OfferCreate, OfferCancel and TrustSet, through the xrpl_transaction field source. Other types have no fields a rule can match: only a rule without transaction conditions (an empty list or a system condition such as the time) decides them. A request that no rule allows is refused.

Does Privy's XRPL denylist example stop payments to a sanctioned address?

Since 22 October 2026, yes: its fallback ALLOW now covers only Payment, so every other transaction type falls to Privy's default refusal, and a warning names the cases a destination denylist misses. Before that, its second rule, "Allow all other requests", allowed every other signing request, so a CheckCreate, EscrowCreate or PaymentChannelCreate to the same address, a Batch with that Payment inside, SetRegularKey, SignerListSet, AccountDelete and DelegateSet were all signed. Policies copied from the old example still do that: check yours.

What policy should an agent that pays x402 sellers in RLUSD use?

Allow only what it needs: a Payment in RLUSD from Ripple's issuer up to a cap, and a TrustSet to that issuer; deny Payments to a condition set of sanctioned addresses; let everything else fall to Privy's default refusal. Keep raw_sign refused. The privy-sanctions-sync example in Fizzl13/x402-examples keeps the condition set in step with the OFAC SDN list.

How do I check an XRPL transaction that the policy can't read?

Send it to presign-guard before calling xrpl_signTransaction: POST https://presign-guard.fizzl.eu/v1/check with {"type": "xrpl", "network": "xrpl:0", "tx": {…}} ($0.01 over x402), or the free presign_quick_check tool on presign-guard's MCP server for the verdict only. It judges every type above, every inner transaction of a Batch, and screens every destination, new key, signer and delegate against the OFAC SDN list. Sign only on green.

Links