Compare commits

...

2 Commits

Author SHA1 Message Date
mDuo13
6c9cee12da Rewrite 'Set Up Multi-signing' Tutorial 2026-02-03 15:38:02 -08:00
mDuo13
4eb7f0d8fc Add new code samples for setting up multisigning 2026-02-02 16:51:47 -08:00
5 changed files with 283 additions and 197 deletions

View File

@@ -0,0 +1,9 @@
{
"name": "multisigning",
"version": "3.0.0",
"license": "MIT",
"dependencies": {
"xrpl": "^4.5.0"
},
"type": "module"
}

View File

@@ -0,0 +1,92 @@
import xrpl from 'xrpl'
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233')
await client.connect()
console.log('Funding new wallet from faucet...')
const { wallet } = await client.fundWallet()
console.log(`Funded. Master key pair:
Address: ${wallet.address}
Seed: ${wallet.seed}
`)
// Generate key pairs to use as signers ----------------------------------------
// If each signer represents a separate person, they should generate their own
// key pairs and send you just the address. These key pairs don't need to be
// funded accounts in the ledger.
const algorithm = 'ed25519'
const signerAddresses = []
for (let i = 0; i < 3; i++) {
const signer = xrpl.Wallet.generate(algorithm)
console.log(`Generated key pair for signer ${i + 1}:
Address: ${signer.address}
Seed: ${signer.seed}
Algorithm: ${algorithm}
`)
signerAddresses.push(signer.address)
}
// Send SignerListSet transaction ----------------------------------------------
// This example sets up a 2-of-3 requirement with all signers weighted equally
const signerEntries = []
for (const signerAddress of signerAddresses) {
signerEntries.push({
SignerEntry: {
Account: signerAddress,
SignerWeight: 1
}
})
}
const signerListSetTx = {
TransactionType: 'SignerListSet',
Account: wallet.address,
SignerQuorum: 2,
SignerEntries: signerEntries
}
xrpl.validate(signerListSetTx)
console.log('Signing and submitting the SignerListSet transaction:',
JSON.stringify(signerListSetTx, null, 2))
const response = await client.submitAndWait(signerListSetTx, { wallet, autofill: true })
// Check result of the SignerListSet transaction -------------------------------
console.log(JSON.stringify(response.result, null, 2))
const listSetResultCode = response.result.meta.TransactionResult
if (listSetResultCode === 'tesSUCCESS') {
console.log('Signer list set successfully.')
} else {
console.error(`SignerListSet failed with code ${listSetResultCode}.`)
client.disconnect()
process.exit(1)
}
// Confirm signer list ---------------------------------------------------------
const accountInfoResp = await client.request({
command: 'account_info',
account: wallet.address,
ledger_index: 'validated',
signer_lists: true
})
if (accountInfoResp.error) {
console.error('Error looking up account:', accountInfoResp.error)
client.disconnect()
process.exit(1)
}
if (accountInfoResp.result.signer_lists) {
const lists = accountInfoResp.result.signer_lists
console.log(`Account has ${lists.length} signer list(s):`)
for (const l of lists) {
console.log(` List #${l.SignerListID} Quorum = ${l.SignerQuorum}`)
for (const SEWrapper of l.SignerEntries) {
const se = SEWrapper.SignerEntry
console.log(` Signer ${se.Account} Weight = ${se.SignerWeight}`)
}
}
} else {
console.error(`❌ No signer lists associated with ${wallet.address}`)
client.disconnect()
process.exit(1)
}
client.disconnect()

View File

@@ -0,0 +1 @@
xrpl-py>=4.4.0

View File

@@ -0,0 +1,84 @@
import json
from xrpl.clients import JsonRpcClient
from xrpl.models.requests import AccountInfo
from xrpl.models.transactions import SignerEntry, SignerListSet
from xrpl.wallet import generate_faucet_wallet, Wallet
from xrpl.transaction import submit_and_wait
client = JsonRpcClient("https://s.altnet.rippletest.net:51234")
print("Funding new wallet from faucet...")
wallet = generate_faucet_wallet(client)
print(f"""Funded. Master key pair:
Address: {wallet.address}
Seed: {wallet.seed}
""")
# Generate key pairs to use as signers -----------------------------------------
# If each signer represents a separate person, they should generate their own
# key pairs and send you just the address. These key pairs don't need to be
# funded accounts in the ledger.
algorithm = "ed25519"
signer_addresses = []
for i in range(3):
signer = Wallet.create(algorithm=algorithm)
print(f"""Generated regular key pair:
Address: {signer.address}
Seed: {signer.seed}
Algorithm: {algorithm}
""")
signer_addresses.append(signer.address)
# Send SignerListSet transaction -----------------------------------------------
# This example sets up a 2-of-3 requirement with all signers weighted equally
signer_list_set_tx = SignerListSet(
account=wallet.address,
signer_quorum=2,
signer_entries=[
SignerEntry(account=signer_address, signer_weight=1)
for signer_address in signer_addresses
],
)
print(
"Signing and submitting the SignerListSet transaction:",
json.dumps(signer_list_set_tx.to_xrpl(), indent=2),
)
try:
response = submit_and_wait(signer_list_set_tx, client, wallet)
except err:
print("Submitting SignerListSet transaction failed with error", err)
exit(1)
# Check result of the SignerListSet transaction --------------------------------
print(json.dumps(response.result, indent=2))
signer_list_set_result_code = response.result["meta"]["TransactionResult"]
if signer_list_set_result_code == "tesSUCCESS":
print("Signer list set successfully.")
else:
print(f"SignerListSet failed with code {signer_list_set_result_code}.")
exit(1)
# Confirm signer list ----------------------------------------------------------
try:
account_info_resp = client.request(
AccountInfo(account=wallet.address, ledger_index="validated", signer_lists=True)
)
except err:
print("Error requesting account_info:", err)
exit(1)
if not account_info_resp.is_successful():
print("Error looking up account:", account_info_resp.result)
exit(1)
if account_info_resp.result.get("signer_lists"):
lists = account_info_resp.result["signer_lists"]
print(f"Account has {len(lists)} signer list(s):")
for l in lists:
print(f" List #{l['SignerListID']} Quorum = {l['SignerQuorum']}")
for se_wrapper in l["SignerEntries"]:
se = se_wrapper["SignerEntry"]
print(f" Signer {se['Account']} Weight = {se['SignerWeight']}")
else:
print(f"❌ No signer lists associated with {wallet.address}")
exit(1)

View File

@@ -1,6 +1,4 @@
---
html: set-up-multi-signing.html
parent: manage-account-settings.html
seo:
description: Add a signer list to your account to enable multi-signing.
labels:
@@ -8,239 +6,141 @@ labels:
---
# Set Up Multi-Signing
[Multi-signing](../../../concepts/accounts/multi-signing.md) is one of three ways to authorize [transactions](../../../concepts/transactions/index.md) for the XRP Ledger, alongside signing with [regular keys and master keys](../../../concepts/accounts/cryptographic-keys.md). You can configure your [address](../../../concepts/accounts/index.md) to allow any combination of the three methods to authorize transactions.
This tutorial shows up how to set up [multi-signing](../../../concepts/accounts/multi-signing.md) on your XRP Ledger account, which allows you to authorize transactions using signatures from a combination of keys. Various configurations are possible, but this tutorial shows a straightforward configuration requiring 2 of 3 signers to authorize a transaction.
This tutorial demonstrates how to enable multi-signing for an address.
## Goals
By following this tutorial, you should learn how to:
- Generate key pairs you can use for multi-signing.
- Add or update a signer list on your account.
- Look up the signer list associated with an account, including its configured weights and quorum.
## Prerequisites
- You must have a funded XRP Ledger [address](../../../concepts/accounts/index.md) with enough spare XRP to send transactions and meet the [reserve requirement](../../../concepts/accounts/reserves.md) of a new signer list.
To complete this tutorial, you should:
- With the [MultiSignReserve amendment][] enabled, multi-signing requires {% $env.PUBLIC_OWNER_RESERVE %} for the account reserve, regardless of the number of signers and signatures you use. (The MultiSignReserve amendment has been enabled in the production XRP Ledger since **2019-04-07**.)
- Have a basic understanding of the XRP Ledger.
- Have an [XRP Ledger client library](../../../references/client-libraries.md), such as **xrpl.js**, installed.
- Have a basic understanding of [Cryptographic Keys](../../../concepts/accounts/cryptographic-keys.md) and [Multi-Signing](../../../concepts/accounts/multi-signing.md).
- If you are on a test network that does not have the [MultiSignReserve amendment][] enabled, multi-signing requires more than the usual amount of XRP for the [account reserve](../../../concepts/accounts/reserves.md), increasing with the number of signers in the list.
## Source Code
- You must have access to a tool that can generate key pairs in the XRP Ledger format. If you are using a `rippled` server for this, you must have admin access because the [wallet_propose method][] is admin-only.
You can find the complete source code for this tutorial's examples in the {% repo-link path="_code-samples/multisigning/" %}code samples section of this website's repository{% /repo-link %}.
- Alternatively, if you are authorizing others who already have XRP Ledger addresses to be signers for your address, you only need to know the account addresses of those people or entities.
## Steps
- Multi-signing must be available. (The MultiSign amendment has been enabled in the production XRP Ledger since **2016-06-27**.)
## 1. Design Your Configuration
Decide how many signers you want to include (up to 32). Choose a quorum number for your signer list and weights for your signers based on how many signatures you want to require for a given transaction. For a straightforward "M-of-N" signing setup, assign each signer weight **`1`** and set your list's quorum to be "M", the number of signatures to require.
## 2. Prepare member keys
You need one or more validly-formed XRP Ledger addresses to include as members of your signer list. You or your chosen signers must know the secret keys associated with these addresses. The addresses can be funded accounts that exist in the ledger, but they do not need to be.
You can generate new addresses using the [wallet_propose method][]. For example:
```
$ rippled wallet_propose
Loading: "/etc/opt/ripple/rippled.cfg"
Connecting to 127.0.0.1:5005
{
"result" : {
"account_id" : "rnRJ4dpSBKDR2M1itf4Ah6tZZm5xuNZFPH",
"key_type" : "secp256k1",
"master_key" : "FLOG SEND GOES CUFF GAGE FAT ANTI DEL GUM TIRE ISLE BEAR",
"master_seed" : "snheH5UUjU4CWqiNVLny2k21TyKPC",
"master_seed_hex" : "A9F859765EB8614D26809836382AFB82",
"public_key" : "aBR4hxFXcDNHnGYvTiqb2KU8TTTV1cYV9wXTAuz2DjBm7S8TYEBU",
"public_key_hex" : "03C09A5D112B393D531E4F092E3A5769A5752129F0A9C55C61B3A226BB9B567B9B",
"status" : "success"
}
}
```
Take note of the `account_id` (XRP Ledger Address) and `master_seed` (secret key) for each one you generate.
## 3. Send SignerListSet transaction
[Sign and submit](../../../concepts/transactions/index.md#signing-and-submitting-transactions) a [SignerListSet transaction][] in the normal (single-signature) way. This associates a signer list with your XRP Ledger address, so that a combination of signatures from the members of that signer list can multi-sign later transactions on your behalf.
In this example, the signer list has 3 members, with the weights and quorum set up such that multi-signed transactions need a signature from `rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW` plus at least one signature from the other two members of the list.
{% partial file="/docs/_snippets/secret-key-warning.md" /%}
### 1. Install dependencies
{% tabs %}
{% tab label="JavaScript" %}
From the code sample folder, use `npm` to install dependencies:
{% tab label="Commandline" %}
```sh
npm i
```
$ rippled submit shqZZy2Rzs9ZqWTCQAdqc3bKgxnYq '{
> "Flags": 0,
> "TransactionType": "SignerListSet",
> "Account": "rnBFvgZphmN39GWzUJeUitaP22Fr9be75H",
> "Fee": "10000",
> "SignerQuorum": 3,
> "SignerEntries": [
> {
> "SignerEntry": {
> "Account": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
> "SignerWeight": 2
> }
> },
> {
> "SignerEntry": {
> "Account": "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v",
> "SignerWeight": 1
> }
> },
> {
> "SignerEntry": {
> "Account": "raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n",
> "SignerWeight": 1
> }
> }
> ]
> }'
Loading: "/etc/opt/ripple/rippled.cfg"
Connecting to 127.0.0.1:5005
{
"result" : {
"engine_result" : "tesSUCCESS",
"engine_result_code" : 0,
"engine_result_message" : "The transaction was applied. Only final in a validated ledger.",
"status" : "success",
"tx_blob" : "12000C2200000000240000000120230000000368400000000000271073210303E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D74473045022100BEDFA12502C66DDCB64521972E5356F4DB965F553853D53D4C69B4897F11B4780220595202D1E080345B65BAF8EBD6CA161C227F1B62C7E72EA5CA282B9434A6F04281142DECAB42CA805119A9BA2FF305C9AFA12F0B86A1F4EB1300028114204288D2E47F8EF6C99BCC457966320D12409711E1EB13000181147908A7F0EDD48EA896C3580A399F0EE78611C8E3E1EB13000181143A4C02EA95AD6AC3BED92FA036E0BBFB712C030CE1F1",
"tx_json" : {
"Account" : "rnBFvgZphmN39GWzUJeUitaP22Fr9be75H",
"Fee" : "10000",
"Flags" : 0,
"Sequence" : 1,
"SignerEntries" : [
{
"SignerEntry" : {
"Account" : "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"SignerWeight" : 2
}
},
{
"SignerEntry" : {
"Account" : "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v",
"SignerWeight" : 1
}
},
{
"SignerEntry" : {
"Account" : "raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n",
"SignerWeight" : 1
}
}
],
"SignerQuorum" : 3,
"SigningPubKey" : "0303E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D",
"TransactionType" : "SignerListSet",
"TxnSignature" : "3045022100BEDFA12502C66DDCB64521972E5356F4DB965F553853D53D4C69B4897F11B4780220595202D1E080345B65BAF8EBD6CA161C227F1B62C7E72EA5CA282B9434A6F042",
"hash" : "3950D98AD20DA52EBB1F3937EF32F382D74092A4C8DF9A0B1A06ED25200B5756"
}
}
}
```
{% /tab %}
{% tab label="Javascript" %}
{% code-snippet file="/_code-samples/multisigning/js/multisigning.ts" language="js" from=" const { wallet: wallet1" before="const accountSet:" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/multisigning/py/multisigning.py" language="py" from="master_wallet =" before="# Now that" /%}
{% /tab %}
From the code sample folder, set up a virtual environment and use `pip` to install dependencies:
```sh
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
{% /tab %}
{% /tabs %}
Make sure that the [Transaction Result](../../../references/protocol/transactions/transaction-results/index.md) is [**`tesSUCCESS`**](../../../references/protocol/transactions/transaction-results/tes-success.md). Otherwise, the transaction failed. If you have a problem in stand-alone mode or a non-production network, check that [multi-sign is enabled](../../../infrastructure/testing-and-auditing/start-a-new-genesis-ledger-in-stand-alone-mode.md#settings-in-new-genesis-ledgers).
{% admonition type="info" name="Note" %}Without the [MultiSignReserve amendment][], the more members in the signer list, the more XRP your address must have for purposes of the [owner reserve](../../../concepts/accounts/reserves.md#owner-reserves). If your address does not have enough XRP, the transaction fails with [`tecINSUFFICIENT_RESERVE`](../../../references/protocol/transactions/transaction-results/tec-codes.md). With the [MultiSignReserve amendment][] enabled, the XRP your address must have for purposes of the [owner reserve](../../../concepts/accounts/reserves.md#owner-reserves) is 5 XRP, regardless of the number of members in the signer list. See also: [Signer Lists and Reserves](../../../references/protocol/ledger-data/ledger-entry-types/signerlist.md#signer-lists-and-reserves).{% /admonition %}
### 2. Connect and get account(s)
To get started, import the client library and instantiate an API client. For this tutorial, you need one account, which the sample code funds using the Testnet faucet; you could also use an existing account.
## 4. Wait for validation
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/multisigning/js/set-up-multi-signing.js" language="js" before="// Generate key pairs" /%}
{% /tab %}
{% raw-partial file="/docs/_snippets/wait-for-validation.md" /%}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/multisigning/py/set-up-multi-signing.py" language="py" before="# Generate key pairs" /%}
{% /tab %}
{% /tabs %}
### 3. Prepare signer keys
## 5. Confirm the new signer list
Each signer on your list needs a key pair they can use to sign transactions. As the account owner, you only need to know the addresses, not the secret keys, of each signer. (One party knowing all the secret keys might defeat the purpose of multi-signing, depending on your use case.) These addresses _can_ have funded accounts in the ledger, but they don't have to.
Use the [account_objects method][] to confirm that the signer list is associated with the address in the latest validated ledger.
Each signer should securely generate and store their own key pair, as you would any time you generate keys for an XRP Ledger account.
Normally, an account can own many objects of different types (such as trust lines and offers). If you funded a new address for this tutorial, the signer list is the only object in the response.
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/multisigning/js/set-up-multi-signing.js" language="js" from="// Generate key pairs" before="// Send SignerListSet transaction" /%}
{% /tab %}
```
$ rippled account_objects rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC validated
Loading: "/etc/opt/ripple/rippled.cfg"
Connecting to 127.0.0.1:5005
{
"result" : {
"account" : "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
"account_objects" : [
{
"Flags" : 0,
"LedgerEntryType" : "SignerList",
"OwnerNode" : "0000000000000000",
"PreviousTxnID" : "8FDC18960455C196A8C4DE0D24799209A21F4A17E32102B5162BD79466B90222",
"PreviousTxnLgrSeq" : 5,
"SignerEntries" : [
{
"SignerEntry" : {
"Account" : "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"SignerWeight" : 2
}
},
{
"SignerEntry" : {
"Account" : "raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n",
"SignerWeight" : 1
}
},
{
"SignerEntry" : {
"Account" : "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v",
"SignerWeight" : 1
}
}
],
"SignerListID" : 0,
"SignerQuorum" : 3,
"index" : "79FD203E4DDDF2EA78B798C963487120C048C78652A28682425E47C96D016F92"
}
],
"ledger_hash" : "56E81069F06492FB410A70218C08169BE3AB3CFD5AEA20E999662D81DC361D9F",
"ledger_index" : 5,
"status" : "success",
"validated" : true
}
}
```
{% tab label="Python" %}
{% code-snippet file="/_code-samples/multisigning/py/set-up-multi-signing.py" language="py" from="# Generate key pairs" before="# Send SignerListSet transaction" /%}
{% /tab %}
{% /tabs %}
If the signer list is present with the expected contents, then your address is ready to multi-sign.
For purposes of this tutorial, the sample code generates three key pairs and saves their addresses into an array.
## 6. Further steps
### 4. Send SignerListSet transaction
At this point, your address is ready to [send a multi-signed transaction](send-a-multi-signed-transaction.md). You may also want to:
Use a [SignerListSet transaction][] to assign a multi-signing list to your account. For this transaction, you set the total weight needed for a quorum in the `SignerQuorum` field, and the list of signers with their individual weights in the `SignerEntries` field. For 2-of-3 multi-signing, give each of the three signers a weight of `1` and set the quorum to `2`. Note that each object in the `SignerEntries` array must be a wrapper object with a single `SignerEntry` field.
* [Disable the address's master key pair](disable-master-key-pair.md).
* [Remove the address's regular key pair](change-or-remove-a-regular-key-pair.md) (if you previously set one) by sending a [SetRegularKey transaction][].
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/multisigning/js/set-up-multi-signing.js" language="js" from="// Send SignerListSet transaction" before="// Confirm signer list" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/multisigning/py/set-up-multi-signing.py" language="py" from="# Send SignerListSet transaction" before="# Confirm signer list" /%}
{% /tab %}
{% /tabs %}
{% admonition type="success" name="Tip: Replacing Signer Lists" %}
If you already have a multi-signing list configured for your account, you can use this same process to replace it with a new list. You can even use the old list to authorize the transaction.
{% /admonition %}
### 5. Confirm that the signer list is assigned to your account
If the SignerListSet transaction succeeded, the list should now be associated with your account. For further confirmation, you can look up the list using the [account_info method][].
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/multisigning/js/set-up-multi-signing.js" language="js" from="// Confirm signer list" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/multisigning/py/set-up-multi-signing.py" language="py" from="# Confirm signer list" /%}
{% /tab %}
{% /tabs %}
{% admonition type="info" name="Note" %}
There currently is no way to have more than one multi-signing list assigned to your account, but the protocol is built to be expandable; every signer list has an ID number of `0`, so that a future [amendment](../../../concepts/networks-and-servers/amendments.md) could allow lists with other IDs. This is why the `signer_lists` field is an array in the `account_info` response.
{% /admonition %}
## Next Steps
At this point, you can [send a multi-signed transaction](send-a-multi-signed-transaction.md). You may also want to:
* [Disable the master key pair](disable-master-key-pair.md).
* [Remove the regular key pair](change-or-remove-a-regular-key-pair.md) (if you previously set one)
## See Also
- **Concepts:**
- [Cryptographic Keys](../../../concepts/accounts/cryptographic-keys.md)
- [Multi-Signing](../../../concepts/accounts/multi-signing.md)
- [Cryptographic Keys](../../../concepts/accounts/cryptographic-keys.md)
- [Multi-Signing](../../../concepts/accounts/multi-signing.md)
- **Tutorials:**
- [Install rippled](../../../infrastructure/installation/index.md)
- [Assign a Regular Key Pair](assign-a-regular-key-pair.md)
- [Reliable Transaction Submission](../../../concepts/transactions/reliable-transaction-submission.md)
- [Enable Public Signing](../../../infrastructure/configuration/enable-public-signing.md)
- [Send a Multi-signed Transaction](send-a-multi-signed-transaction.md)
- [Assign a Regular Key Pair](assign-a-regular-key-pair.md)
- [Reliable Transaction Submission](../../../concepts/transactions/reliable-transaction-submission.md)
- **References:**
- [wallet_propose method][]
- [account_objects method][]
- [sign_for method][]
- [submit_multisigned method][]
- [SignerListSet transaction][]
- [SignerList object](../../../references/protocol/ledger-data/ledger-entry-types/signerlist.md)
- [account_info method][]
- [SignerListSet transaction][]
- [SignerList entry][]
{% raw-partial file="/docs/_snippets/common-links.md" /%}