> ## Documentation Index
> Fetch the complete documentation index at: https://trustodian-recovery.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# XRP vaults

> Reading the signer list from the chain and multi-signing a payment out of the vault

An XRP vault is an ordinary XRP Ledger account whose **signer list** replaced its own master key.
Nothing about it is stored off the chain, so this page is a procedure rather than a restore.

> [!WARNING]
>
> The master key of a Trustodian XRP vault is disabled. The account moves only when a quorum of its
> signer list signs. There is no single key that can rescue it.

## What you need

**1. The vault's address**

From your [Recovery Kit](https://trustodian-recovery.mintlify.site/recovery-kit.md), on the vault's page. It is an ordinary `r...` address.

**2. Enough owners' keys to reach the quorum**

Each signer is an owner's **ordinary XRP wallet key** — not a separate cosigner key. Each owner
derives theirs from their own recovery phrase at the index they joined with, as described on
[XRP Ledger](https://trustodian-recovery.mintlify.site/xrp.md).

**3. An XRPL node and xrpl.js**

`npm install xrpl`. A public node such as `wss://xrplcluster.com` is enough; nothing here needs a
node of your own.

## Read the signer list from the chain

Do this first. It is authoritative — the kit records what the vault was configured with, the chain
holds what it actually has.

```js theme={null}
import { Client } from 'xrpl'

const client = new Client('wss://xrplcluster.com')
await client.connect()

const { result } = await client.request({
  command: 'account_objects',
  account: VAULT_ADDRESS,
  type: 'signer_list',
})

console.log(result.account_objects[0])
// SignerQuorum: the total weight required
// SignerEntries: one entry per owner, each with an Account and a SignerWeight
```

Check the quorum and the signer addresses against the **Threshold** and **Owners** printed on the
vault's page in your kit. If they disagree, trust the chain and find out why before signing anything.

## Turn a phrase-derived key into a signing wallet

Deriving on `m/44'/144'/0'/0/{index}` gives you a public and a private key, not a family seed. That
is the form `Wallet` takes directly:

```js theme={null}
import { Wallet } from 'xrpl'

// publicKey:  33-byte compressed, uppercase hex
// privateKey: the same key, uppercase hex, prefixed with 00 for secp256k1
const wallet = new Wallet(publicKey, `00${privateKeyHex}`.toUpperCase())
```

If you already hold a family seed instead, `Wallet.fromSeed(seed)` is equivalent.

## Move the funds

**1. Build the transaction once, sized for the number of signatures**

A multi-signed transaction pays a fee of `(1 + number of signatures) × base fee`. Passing the
signer count to `autofill` is what sets it; the default would be too low and the transaction would
be rejected.

```js theme={null}
import { xrpToDrops } from 'xrpl'

const prepared = await client.autofill({
  TransactionType: 'Payment',
  Account: VAULT_ADDRESS,
  Destination: DESTINATION_ADDRESS,
  Amount: xrpToDrops('10'),
}, SIGNER_COUNT)
```

**2. Have each owner sign that exact object**

The second argument, `true`, is what makes it a multi-signature rather than a normal one.

```js theme={null}
const blobA = walletA.sign(prepared, true).tx_blob
const blobB = walletB.sign(prepared, true).tx_blob
```

Owners can do this separately, on their own machines, and send only the resulting `tx_blob`. A blob
is useless on its own and carries no private key.

**3. Combine and submit**

```js theme={null}
import { multisign } from 'xrpl'

const result = await client.submitAndWait(multisign([blobA, blobB]))
console.log(result.result.meta.TransactionResult) // tesSUCCESS
```

> [!WARNING]
>
> **Every owner must sign the identical prepared transaction.** `Sequence`, `Fee` and
> `LastLedgerSequence` are part of what gets signed, so if one owner re-runs `autofill` instead of
> signing the object they were given, their signature will not combine with the others. Prepare once,
> distribute that object, collect blobs.

## What you cannot do

* **Empty the account.** The XRP Ledger holds a reserve that cannot be sent. Moving it requires
  deleting the account, which has its own conditions.
* **Send to an exchange without a destination tag.** A payment that needs one and arrives without it
  is credited to nobody.

## Changing the signer list

The same procedure with a `SignerListSet` transaction in place of the `Payment`. It is signed by a
quorum of the current signer list, exactly like a payment, because the master key is off.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.