Compare commits

...

3 Commits

Author SHA1 Message Date
mDuo13
064abbfad3 Remove old multisigning code samples
These code samples weren't bad, but they didn't fully embody best
practices like reliable transaction submission, and they're fully
obsolete with the new multi-signing code samples in place.
2026-02-05 13:12:36 -08:00
mDuo13
a5c1fb0c5c Rewrite 'Send a Multi-Signed Transaction' tutorial 2026-02-05 13:02:21 -08:00
mDuo13
84af702ba5 New code samples for sending multi-signed tx 2026-02-04 16:17:17 -08:00
5 changed files with 294 additions and 566 deletions

View File

@@ -1,88 +0,0 @@
/*
* Create and submit a SignerListSet and multisign a transaction.
* Reference: https://xrpl.org/multi-signing.html
*/
import {
multisign,
Client,
AccountSet,
convertStringToHex,
SignerListSet,
} from 'xrpl'
const client = new Client('wss://s.altnet.rippletest.net:51233')
async function multisigning(): Promise<void> {
await client.connect()
/*
* This wallet creation is for demonstration purposes.
* In practice, users generally will not have all keys in one spot,
* hence, users need to implement a way to get signatures.
*/
const { wallet: wallet1 } = await client.fundWallet()
const { wallet: wallet2 } = await client.fundWallet()
const { wallet: walletMaster } = await client.fundWallet()
const signerListSet: SignerListSet = {
TransactionType: 'SignerListSet',
Account: walletMaster.classicAddress,
SignerEntries: [
{
SignerEntry: {
Account: wallet1.classicAddress,
SignerWeight: 1,
},
},
{
SignerEntry: {
Account: wallet2.classicAddress,
SignerWeight: 1,
},
},
],
SignerQuorum: 2,
}
const signerListResponse = await client.submit(signerListSet, {
wallet: walletMaster,
})
console.log('SignerListSet constructed successfully:')
console.log(signerListResponse)
const accountSet: AccountSet = {
TransactionType: 'AccountSet',
Account: walletMaster.classicAddress,
Domain: convertStringToHex('example.com'),
}
const accountSetTx = await client.autofill(accountSet, 2)
console.log('AccountSet transaction is ready to be multisigned:')
console.log(accountSetTx)
const { tx_blob: tx_blob1 } = wallet1.sign(accountSetTx, true)
const { tx_blob: tx_blob2 } = wallet2.sign(accountSetTx, true)
const multisignedTx = multisign([tx_blob1, tx_blob2])
console.log("Successfully multisigned the transaction")
console.log(multisignedTx)
const submitResponse = await client.submit(multisignedTx)
if (submitResponse.result.engine_result === 'tesSUCCESS') {
console.log('The multisigned transaction was accepted by the ledger:')
console.log(submitResponse)
if (submitResponse.result.tx_json.Signers) {
console.log(
`The transaction had ${submitResponse.result.tx_json.Signers.length} signatures`,
)
}
} else {
console.log(
"The multisigned transaction was rejected by rippled. Here's the response from rippled:",
)
console.log(submitResponse)
}
await client.disconnect()
}
void multisigning()

View File

@@ -0,0 +1,90 @@
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}
`)
// Set up multi-signing --------------------------------------------------------
// Skip this step if you are using an existing account with multi-signing
// already set up.
const algorithm = 'ed25519'
const signers = []
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}
`)
signers.push(signer)
}
const signerEntries = []
for (const signer of signers) {
signerEntries.push({
SignerEntry: {
Account: signer.address,
SignerWeight: 1
}
})
}
const signerListSetTx = {
TransactionType: 'SignerListSet',
Account: wallet.address,
SignerQuorum: 2,
SignerEntries: signerEntries
}
xrpl.validate(signerListSetTx)
console.log('Setting up multi-signing...')
const response = await client.submitAndWait(signerListSetTx, { wallet, autofill: true })
const listSetResultCode = response.result.meta.TransactionResult
if (listSetResultCode === 'tesSUCCESS') {
console.log('... done.')
} else {
console.error(`SignerListSet failed with code ${listSetResultCode}.`)
client.disconnect()
process.exit(1)
}
// Prepare transaction ---------------------------------------------------------
// This example uses a no-op AccountSet, but you could send almost any type
// of transaction the same way.
const numSigners = 2
const txInstructions = await client.autofill({
TransactionType: 'AccountSet',
Account: wallet.address
}, numSigners)
console.log('Transaction ready for signing:')
console.log(JSON.stringify(txInstructions, null, 2))
// Collect signatures ----------------------------------------------------------
const txSignedByKey1 = signers[0].sign(txInstructions, true)
console.log('Signed by signer #1:', JSON.stringify(txSignedByKey1, null, 2))
const txSignedByKey2 = signers[1].sign(txInstructions, true)
console.log('Signed by signer #2:', JSON.stringify(txSignedByKey2, null, 2))
// Combine signatures and submit -----------------------------------------------
const multisignedTx = xrpl.multisign([txSignedByKey1.tx_blob, txSignedByKey2.tx_blob])
console.log('Combined multi-signed transaction:',
JSON.stringify(multisignedTx, null, 2)
)
const response2 = await client.submitAndWait(multisignedTx)
const multisignedResultCode = response2.result.meta.TransactionResult
if (multisignedResultCode === 'tesSUCCESS') {
const txHash = response2.result.hash
console.log(`Multi-signed transaction ${txHash} succeeded!`)
} else {
console.error('Multi-signed transaction failed with result code',
multisignedResultCode
)
client.disconnect()
process.exit(1)
}
client.disconnect()

View File

@@ -1,79 +0,0 @@
"""
Example of how we can multisign a transaction.
This will only work with version 2.0.0-beta.0 or later.
Reference: https://xrpl.org/multi-signing.html
"""
from xrpl.clients import JsonRpcClient
from xrpl.models.requests import SubmitMultisigned
from xrpl.models.transactions import AccountSet, SignerEntry, SignerListSet
from xrpl.transaction import (
autofill,
autofill_and_sign,
multisign,
submit_and_wait,
sign,
)
from xrpl.utils import str_to_hex
from xrpl.wallet import generate_faucet_wallet
client = JsonRpcClient("https://s.altnet.rippletest.net:51234")
# Create a wallets to use for multisigning
# Prints debug info as it creates the wallet
master_wallet = generate_faucet_wallet(client, debug=True)
signer_wallet_1 = generate_faucet_wallet(client, debug=True)
signer_wallet_2 = generate_faucet_wallet(client, debug=True)
signer_entries = [
SignerEntry(account=signer_wallet_1.address, signer_weight=1),
SignerEntry(account=signer_wallet_2.address, signer_weight=1),
]
signer_list_set_tx = SignerListSet(
account=master_wallet.address,
signer_quorum=2,
signer_entries=signer_entries,
)
signed_signer_list_set_tx = autofill_and_sign(signer_list_set_tx, client, master_wallet)
print("Constructed SignerListSet and submitting it to the ledger...")
signed_list_set_tx_response = submit_and_wait(
signed_signer_list_set_tx, client
)
print("SignerListSet submitted, here's the response:")
print(signed_list_set_tx_response)
# Now that we've set up multisigning, let's try using it to submit an AccountSet
# transaction.
account_set_tx = AccountSet(
account=master_wallet.address, domain=str_to_hex("example.com")
)
autofilled_account_set_tx = autofill(account_set_tx, client, len(signer_entries))
print("AccountSet transaction is ready to be multisigned")
print(autofilled_account_set_tx)
# Since we created both signer keys, we can sign locally, but if you are building an app
# That allows multisigning, you would need to request signatures from the key holders.
tx_1 = sign(autofilled_account_set_tx, signer_wallet_1, multisign=True)
tx_2 = sign(autofilled_account_set_tx, signer_wallet_2, multisign=True)
multisigned_tx = multisign(autofilled_account_set_tx, [tx_1, tx_2])
print("Successfully multisigned the transaction")
print(multisigned_tx)
multisigned_tx_response = client.request(SubmitMultisigned(tx_json=multisigned_tx))
if multisigned_tx_response.result["engine_result"] == "tesSUCCESS":
print("The multisigned transaction was accepted by the ledger:")
print(multisigned_tx_response)
if multisigned_tx_response.result["tx_json"]["Signers"]:
print(
"The transaction had "
f"{len(multisigned_tx_response.result['tx_json']['Signers'])} signatures"
)
else:
print(
"The multisigned transaction was rejected by rippled."
"Here's the response from rippled:"
)
print(multisigned_tx_response)

View File

@@ -0,0 +1,88 @@
import json
from xrpl.clients import JsonRpcClient
from xrpl.wallet import generate_faucet_wallet, Wallet
from xrpl.models.transactions import SignerListSet, SignerEntry, AccountSet
from xrpl.transaction import (
autofill,
multisign,
sign,
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}
""")
# Set up multi-signing --------------------------------------------------------
# Skip this step if you are using an existing account with multi-signing
# already set up.
algorithm = "ed25519"
signers = []
for i in range(3):
signer = Wallet.create(algorithm=algorithm)
print(f"""Generated regular key pair:
Address: {signer.address}
Seed: {signer.seed}
Algorithm: {algorithm}
""")
signers.append(signer)
signer_entries = [
SignerEntry(account=signer.address, signer_weight=1)
for signer in signers
]
signer_list_set_tx = SignerListSet(
account=wallet.address,
signer_quorum=2,
signer_entries=signer_entries
)
print("Setting up multi-signing...")
try:
response = submit_and_wait(signer_list_set_tx, client, wallet)
except err:
print("Submitting SignerListSet transaction failed with error", err)
exit(1)
list_set_result_code = response.result["meta"]["TransactionResult"]
if list_set_result_code == "tesSUCCESS":
print("... done.")
else:
print(f"SignerListSet failed with code {list_set_result_code}.")
exit(1)
# Prepare transaction ---------------------------------------------------------
# This example uses a no-op AccountSet, but you could send almost any type
# of transaction the same way.
num_signers = 2
tx_prepared = autofill(AccountSet(account=wallet.address), client, num_signers)
print("Transaction ready for signing:")
print(json.dumps(tx_prepared.to_xrpl(), indent=2))
# Collect signatures ----------------------------------------------------------
tx_signed_by_key_1 = sign(tx_prepared, signers[0], multisign=True)
print("Signed by signer #1:", tx_signed_by_key_1)
tx_signed_by_key_2 = sign(tx_prepared, signers[1], multisign=True)
print("Signed by signer #2:", tx_signed_by_key_2)
# Combine signatures and submit -----------------------------------------------
multisigned_tx = multisign(tx_prepared, [tx_signed_by_key_1, tx_signed_by_key_2])
print("Combined multi-signed transaction:")
print(json.dumps(multisigned_tx.to_xrpl(), indent=2))
try:
response2 = submit_and_wait(multisigned_tx, client)
except err:
print("Submitting multi-signed transaction failed with error", err)
exit(1)
multisigned_result_code = response2.result["meta"]["TransactionResult"]
if multisigned_result_code == "tesSUCCESS":
tx_hash = response2.result["hash"]
print(f"Multi-signed transaction {tx_hash} succeeded!")
else:
print(f"Multi-signed transaction failed with result code {multisigned_result_code}")
exit(1)

View File

@@ -1,442 +1,159 @@
---
html: send-a-multi-signed-transaction.html
parent: manage-account-settings.html
seo:
description: Send a transaction authorized with multiple signatures.
description: Send a transaction authorized with multiple signatures.
labels:
- Security
---
# Send a Multi-Signed Transaction
The following procedure demonstrates how to create, sign, and submit a multi-signed transaction.
This tutorial shows how to send a transaction using [multi-signing](../../../concepts/accounts/multi-signing.md).
## Goals
By following this tutorial, you should learn how to send a transaction using a multi-signing list to authorize the transaction.
## Prerequisites
- You must have already [set up multi-signing](set-up-multi-signing.md) for your address.
To complete this tutorial, you should:
- Multi-signing must be available. Multi-signing has been enabled by an [**Amendment**](../../../concepts/networks-and-servers/amendments.md) to the XRP Ledger Consensus Protocol since 2016-06-27.
- Have a basic understanding of the XRP Ledger.
- Have an [XRP Ledger client library](../../../references/client-libraries.md), such as **xrpl.js**, installed.
- Understand how to [set up multi-signing](set-up-multi-signing.md) on an account, including how signer weights and quorums work.
## Source Code
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 %}.
## Steps
## 1. Create the transaction
Create a JSON object that represents the transaction you want to submit. You have to specify _everything_ about this transaction, including `Fee` and `Sequence`. Also include the field `SigningPubKey` as an empty string, to indicate that the transaction is multi-signed.
Keep in mind that the `Fee` for multi-signed transactions is significantly higher than for regularly-signed transactions. It should be at least (N+1) times the normal [transaction cost](../../../concepts/transactions/transaction-cost.md), where N is the number of signatures you plan to provide. Since it sometimes takes a while to collect signatures from multiple sources, you may want to specify more than the current minimum, in case the [transaction cost](../../../concepts/transactions/transaction-cost.md) increases in that time.
Here's an example transaction ready to be multi-signed:
### 1. Install dependencies
{% tabs %}
{% tab label="JavaScript" %}
From the code sample folder, use `npm` to install dependencies:
{% tab label="JSON" %}
```json
{
"TransactionType": "TrustSet",
"Account": "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
"Flags": 262144,
"LimitAmount": {
"currency": "USD",
"issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"value": "100"
},
"Sequence": 2,
"SigningPubKey": "",
"Fee": "30000"
}
```sh
npm i
```
(This transaction creates an accounting relationship from `rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC` to `rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh` with a maximum balance of 100 USD.)
{% /tab %}
{% tab label="Javascript" %}
{% code-snippet file="/_code-samples/multisigning/js/multisigning.ts" language="js" from="const accountSet: AccountSet = {" before="const { tx_blob" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/multisigning/py/multisigning.py" language="py" from="account_set_tx = AccountSet" before="# Since" /%}
{% /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 %}
## 2. Get one signature
### 2. Connect and get account(s)
Use the [sign_for method][] with the secret key and address of one of the members of your SignerList to get a signature for that member.
{% partial file="/docs/_snippets/secret-key-warning.md" /%}
```
$ rippled sign_for rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW <rsA2L..'s secret> '{
> "TransactionType": "TrustSet",
> "Account": "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
> "Flags": 262144,
> "LimitAmount": {
> "currency": "USD",
> "issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
> "value": "100"
> },
> "Sequence": 2,
> "SigningPubKey": "",
> "Fee": "30000"
> }'
Loading: "/etc/opt/ripple/rippled.cfg"
Connecting to 127.0.0.1:5005
{
"result" : {
"status" : "success",
"tx_blob" : "1200142200040000240000000263D5038D7EA4C680000000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E868400000000000753073008114A3780F5CB5A44D366520FC44055E8ED44D9A2270F3E010732102B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF744730450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E58114204288D2E47F8EF6C99BCC457966320D12409711E1F1",
"tx_json" : {
"Account" : "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
"Fee" : "30000",
"Flags" : 262144,
"LimitAmount" : {
"currency" : "USD",
"issuer" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"value" : "100"
},
"Sequence" : 2,
"Signers" : [
{
"Signer" : {
"Account" : "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"SigningPubKey" : "02B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF",
"TxnSignature" : "30450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E5"
}
}
],
"SigningPubKey" : "",
"TransactionType" : "TrustSet",
"hash" : "A94A6417D1A7AAB059822B894E13D322ED3712F7212CE9257801F96DE6C3F6AE"
}
}
}
```
Save the `tx_json` field of the response: it has the new signature in the `Signers` field. You can discard the value of the `tx_blob` field.
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).
## 3. Get additional signatures
You can collect additional signatures in parallel or in serial:
* In parallel: Use the `sign_for` command with the original JSON for the transaction. Each response has a single signature in the `Signers` array.
* In serial: Use the `sign_for` command with the `tx_json` value from the previous `sign_for` response. Each response adds a new signature to the existing `Signers` array.
{% partial file="/docs/_snippets/secret-key-warning.md" /%}
```
$ rippled sign_for rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v <rUpy..'s secret> '{
> "Account" : "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
> "Fee" : "30000",
> "Flags" : 262144,
> "LimitAmount" : {
> "currency" : "USD",
> "issuer" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
> "value" : "100"
> },
> "Sequence" : 2,
> "Signers" : [
> {
> "Signer" : {
> "Account" : "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
> "SigningPubKey" : "02B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF",
> "TxnSignature" : "30450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E5"
> }
> }
> ],
> "SigningPubKey" : "",
> "TransactionType" : "TrustSet",
> "hash" : "A94A6417D1A7AAB059822B894E13D322ED3712F7212CE9257801F96DE6C3F6AE"
> }'
Loading: "/etc/opt/ripple/rippled.cfg"
Connecting to 127.0.0.1:5005
{
"result" : {
"status" : "success",
"tx_blob" : "1200142200040000240000000263D5038D7EA4C680000000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E868400000000000753073008114A3780F5CB5A44D366520FC44055E8ED44D9A2270F3E010732102B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF744730450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E58114204288D2E47F8EF6C99BCC457966320D12409711E1E0107321028FFB276505F9AC3F57E8D5242B386A597EF6C40A7999F37F1948636FD484E25B744630440220680BBD745004E9CFB6B13A137F505FB92298AD309071D16C7B982825188FD1AE022004200B1F7E4A6A84BB0E4FC09E1E3BA2B66EBD32F0E6D121A34BA3B04AD99BC181147908A7F0EDD48EA896C3580A399F0EE78611C8E3E1F1",
"tx_json" : {
"Account" : "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
"Fee" : "30000",
"Flags" : 262144,
"LimitAmount" : {
"currency" : "USD",
"issuer" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"value" : "100"
},
"Sequence" : 2,
"Signers" : [
{
"Signer" : {
"Account" : "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"SigningPubKey" : "02B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF",
"TxnSignature" : "30450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E5"
}
},
{
"Signer" : {
"Account" : "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v",
"SigningPubKey" : "028FFB276505F9AC3F57E8D5242B386A597EF6C40A7999F37F1948636FD484E25B",
"TxnSignature" : "30440220680BBD745004E9CFB6B13A137F505FB92298AD309071D16C7B982825188FD1AE022004200B1F7E4A6A84BB0E4FC09E1E3BA2B66EBD32F0E6D121A34BA3B04AD99BC1"
}
}
],
"SigningPubKey" : "",
"TransactionType" : "TrustSet",
"hash" : "BD636194C48FD7A100DE4C972336534C8E710FD008C0F3CF7BC5BF34DAF3C3E6"
}
}
}
```
Depending on the SignerList you configured, you may need to repeat this step several times to get signatures from all the necessary parties.
## 4. Combine signatures and submit
If you collected the signatures in serial, the `tx_json` from the last `sign_for` response has all the signatures assembled, so you can use that as the argument to the [submit_multisigned method][].
If you collected the signatures in parallel, you must manually construct a `tx_json` object with all the signatures included. Take the `Signers` arrays from all the `sign_for` responses, and combine their contents into a single `Signers` array that has each signature. Add the combined `Signers` array to the original transaction JSON value, and use that as the argument to the [submit_multisigned method][].
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.
{% tabs %}
{% tab label="Commandline" %}
```
$ rippled submit_multisigned '{
> "Account" : "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
> "Fee" : "30000",
> "Flags" : 262144,
> "LimitAmount" : {
> "currency" : "USD",
> "issuer" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
> "value" : "100"
> },
> "Sequence" : 2,
> "Signers" : [
> {
> "Signer" : {
> "Account" : "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
> "SigningPubKey" : "02B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF",
> "TxnSignature" : "30450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E5"
> }
> },
> {
> "Signer" : {
> "Account" : "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v",
> "SigningPubKey" : "028FFB276505F9AC3F57E8D5242B386A597EF6C40A7999F37F1948636FD484E25B",
> "TxnSignature" : "30440220680BBD745004E9CFB6B13A137F505FB92298AD309071D16C7B982825188FD1AE022004200B1F7E4A6A84BB0E4FC09E1E3BA2B66EBD32F0E6D121A34BA3B04AD99BC1"
> }
> }
> ],
> "SigningPubKey" : "",
> "TransactionType" : "TrustSet",
> "hash" : "BD636194C48FD7A100DE4C972336534C8E710FD008C0F3CF7BC5BF34DAF3C3E6"
> }'
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": "1200142200040000240000000263D5038D7EA4C680000000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E868400000000000753073008114A3780F5CB5A44D366520FC44055E8ED44D9A2270F3E010732102B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF744730450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E58114204288D2E47F8EF6C99BCC457966320D12409711E1E0107321028FFB276505F9AC3F57E8D5242B386A597EF6C40A7999F37F1948636FD484E25B744630440220680BBD745004E9CFB6B13A137F505FB92298AD309071D16C7B982825188FD1AE022004200B1F7E4A6A84BB0E4FC09E1E3BA2B66EBD32F0E6D121A34BA3B04AD99BC181147908A7F0EDD48EA896C3580A399F0EE78611C8E3E1F1",
"tx_json": {
"Account": "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
"Fee": "30000",
"Flags": 262144,
"LimitAmount": {
"currency": "USD",
"issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"value": "100"
},
"Sequence": 2,
"Signers": [{
"Signer": {
"Account": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"SigningPubKey": "02B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF",
"TxnSignature": "30450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E5"
}
}, {
"Signer": {
"Account": "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v",
"SigningPubKey": "028FFB276505F9AC3F57E8D5242B386A597EF6C40A7999F37F1948636FD484E25B",
"TxnSignature": "30440220680BBD745004E9CFB6B13A137F505FB92298AD309071D16C7B982825188FD1AE022004200B1F7E4A6A84BB0E4FC09E1E3BA2B66EBD32F0E6D121A34BA3B04AD99BC1"
}
}],
"SigningPubKey": "",
"TransactionType": "TrustSet",
"hash": "BD636194C48FD7A100DE4C972336534C8E710FD008C0F3CF7BC5BF34DAF3C3E6"
}
}
}
```
Take note of the `hash` value from the response so you can check the results of the transaction later. (In this case, the hash is `BD636194C48FD7A100DE4C972336534C8E710FD008C0F3CF7BC5BF34DAF3C3E6`.)
{% /tab %}
{% tab label="Javascript" %}
{% code-snippet file="/_code-samples/multisigning/js/multisigning.ts" language="js" from="const { tx_blob" before="if (submitResponse" /%}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/multisigning/js/send-multi-signed-transaction.js" language="js" before="// Set up multi-signing" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/multisigning/py/multisigning.py" language="py" from="tx_1 =" before="if multisigned_tx_response" /%}
{% code-snippet file="/_code-samples/multisigning/py/send-multi-signed-transaction.py" language="py" before="# Set up multi-signing" /%}
{% /tab %}
{% /tabs %}
## 5. Close the ledger
### 3. Set up multi-signing
If you are using the live network, you can wait 4-7 seconds for the ledger to close automatically.
If you're running `rippled` in stand-alone mode, use the [ledger_accept method][] to manually close the ledger:
```
$ rippled ledger_accept
Loading: "/etc/opt/ripple/rippled.cfg"
Connecting to 127.0.0.1:5005
{
"result" : {
"ledger_current_index" : 7,
"status" : "success"
}
}
```
## 6. Confirm transaction results
Use the hash value from the response to the `submit_multisigned` command to look up the transaction using the [tx method][]. In particular, check that the `TransactionResult` is the string `tesSUCCESS`.
On the live network, you must also confirm that the `validated` field is set to the boolean `true`. If the field is not `true`, you might need to wait longer for the consensus process to finish; or your transaction may be unable to be included in a ledger for some reason.
In stand-alone mode, the server automatically considers a ledger to be `validated` if it has been manually closed.
Before you can send a multi-signed transaction, you have to have a signer list set up for your account. Since the sample code uses a newly funded account, it needs to do this one-time setup first. For a more detailed explanation of this process, see [Set Up Multi-Signing](set-up-multi-signing.md). **Skip this step if you are using an existing account that already has a signer list.**
{% tabs %}
{% tab label="Commandline" %}
```
$ rippled tx BD636194C48FD7A100DE4C972336534C8E710FD008C0F3CF7BC5BF34DAF3C3E6
Loading: "/etc/opt/ripple/rippled.cfg"
Connecting to 127.0.0.1:5005
{
"result": {
"Account": "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
"Fee": "30000",
"Flags": 262144,
"LimitAmount": {
"currency": "USD",
"issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"value": "100"
},
"Sequence": 2,
"Signers": [{
"Signer": {
"Account": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"SigningPubKey": "02B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF",
"TxnSignature": "30450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E5"
}
}, {
"Signer": {
"Account": "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v",
"SigningPubKey": "028FFB276505F9AC3F57E8D5242B386A597EF6C40A7999F37F1948636FD484E25B",
"TxnSignature": "30440220680BBD745004E9CFB6B13A137F505FB92298AD309071D16C7B982825188FD1AE022004200B1F7E4A6A84BB0E4FC09E1E3BA2B66EBD32F0E6D121A34BA3B04AD99BC1"
}
}],
"SigningPubKey": "",
"TransactionType": "TrustSet",
"date": 512172510,
"hash": "BD636194C48FD7A100DE4C972336534C8E710FD008C0F3CF7BC5BF34DAF3C3E6",
"inLedger": 6,
"ledger_index": 6,
"meta": {
"AffectedNodes": [{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8",
"PreviousTxnID": "B7E1D33DB7DEA3BB65BFAB2C80E02125F47FCCF6C957A7FDECD915B3EBE0C1DD",
"PreviousTxnLgrSeq": 4
}
}, {
"CreatedNode": {
"LedgerEntryType": "RippleState",
"LedgerIndex": "93E317B32022977C77810A2C558FBB28E30E744C68E73720622B797F957EC5FA",
"NewFields": {
"Balance": {
"currency": "USD",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "0"
},
"Flags": 2162688,
"HighLimit": {
"currency": "USD",
"issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"value": "0"
},
"LowLimit": {
"currency": "USD",
"issuer": "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
"value": "100"
}
}
}
}, {
"ModifiedNode": {
"FinalFields": {
"Account": "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
"Balance": "999960000",
"Flags": 0,
"OwnerCount": 6,
"Sequence": 3
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "A6B1BA6F2D70813100908EA84ABB7783695050312735E2C3665259F388804EA0",
"PreviousFields": {
"Balance": "999990000",
"OwnerCount": 5,
"Sequence": 2
},
"PreviousTxnID": "8FDC18960455C196A8C4DE0D24799209A21F4A17E32102B5162BD79466B90222",
"PreviousTxnLgrSeq": 5
}
}, {
"ModifiedNode": {
"FinalFields": {
"Flags": 0,
"Owner": "rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC",
"RootIndex": "C2728175908D82FB1DE6676F203D8D3C056995A9FA9B369EF326523F1C65A1DE"
},
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "C2728175908D82FB1DE6676F203D8D3C056995A9FA9B369EF326523F1C65A1DE"
}
}, {
"CreatedNode": {
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "D8120FC732737A2CF2E9968FDF3797A43B457F2A81AA06D2653171A1EA635204",
"NewFields": {
"Owner": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"RootIndex": "D8120FC732737A2CF2E9968FDF3797A43B457F2A81AA06D2653171A1EA635204"
}
}
}],
"TransactionIndex": 0,
"TransactionResult": "tesSUCCESS"
},
"status": "success",
"validated": true
}
}
```
{% /tab %}
{% tab label="Javascript" %}
{% code-snippet file="/_code-samples/multisigning/js/multisigning.ts" language="js" from="if (submitResponse" before="await client.disconnect" /%}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/multisigning/js/send-multi-signed-transaction.js" language="js" from="// Set up multi-signing" before="// Prepare transaction" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/multisigning/py/multisigning.py" language="py" from="if multisigned_tx_response" /%}
{% code-snippet file="/_code-samples/multisigning/py/send-multi-signed-transaction.py" language="py" from="# Set up multi-signing" before="# Prepare transaction" /%}
{% /tab %}
{% /tabs %}
### 4. Prepare the transaction
When sending a multi-signed transaction, you need to specify _all_ the details of the transaction before collecting signatures, so that the signers are signing the exact same transaction instructions. Instead of implicitly autofilling a transaction while signing it, you can explicitly use your client library's autofill function to set auto-fillable transaction fields like the `Fee`, `Sequence`, and `LastLedgerSequence`. Multi-signed transactions require a higher [transaction cost][] based on the number of signatures, so you should specify the expected number of signers to this function.
The sample code uses a no-op [AccountSet transaction][], but you can send almost any type of transaction using a multi-signature.
{% admonition type="warning" name="Caution: Sequence Numbers" %}
You have to set the transaction's sequence number in the `Sequence` field at this time. If you send any other transactions from the same account while you are collecting signatures, this transaction becomes invalid because you can't reuse or skip sequence numbers. If collecting signatures may take a while, you should [use a ticket](../transaction-sending/use-tickets.md) instead.
{% /admonition %}
{% tabs %}
{% tab label="JavaScript" %}
In **xrpl.js**, use the [`autofill(transaction, signerCount)` method](https://js.xrpl.org/classes/Client.html#autofill) of the Client instance to autofill a transaction for multi-signing.
{% code-snippet file="/_code-samples/multisigning/js/send-multi-signed-transaction.js" language="js" from="// Prepare transaction" before="// Collect signatures" /%}
{% /tab %}
{% tab label="Python" %}
In **xrpl-py**, use the [`xrpl.transaction.autofill(transaction, client, num_signers)` function](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.transaction.html#xrpl.transaction.autofill) to autofill a transaction for multi-signing.
{% code-snippet file="/_code-samples/multisigning/py/send-multi-signed-transaction.py" language="py" from="# Prepare transaction" before="# Collect signatures" /%}
{% /tab %}
{% /tabs %}
### 5. Collect signatures
Now, have each of your signers provide a signature for the prepared transaction. Provide the same prepared transaction JSON to each signer, so they can use their own private key to sign the transaction. Signatures for a multi-signed transaction are slightly different than a single-signed transaction, so be sure each signer uses the correct method for producing their signature.
{% tabs %}
{% tab label="JavaScript" %}
In **xrpl.js**, pass `true` as the second argument to the [`Wallet.sign(...)` method](https://js.xrpl.org/classes/Wallet.html#sign) to create a signature for a multi-signed transaction.
{% code-snippet file="/_code-samples/multisigning/js/send-multi-signed-transaction.js" language="js" from="// Collect signatures" before="// Combine signatures" /%}
{% /tab %}
{% tab label="Python" %}
In **xrpl-py**, pass `multisign=True` to the [`xrpl.transaction.sign(...)` function](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.transaction.html#xrpl.transaction.sign) to create a signature for a multi-signed transaction.
{% code-snippet file="/_code-samples/multisigning/py/send-multi-signed-transaction.py" language="py" from="# Collect signatures" before="# Combine signatures" /%}
{% /tab %}
{% /tabs %}
### 6. Combine signatures and submit the transaction
When you have enough signatures to authorize the transaction, combine them into a single transaction, and submit the transaction to the network.
{% tabs %}
{% tab label="JavaScript" %}
In **xrpl.js**, use the [`xrpl.multisign(...)` function](https://js.xrpl.org/functions/multisign.html) to combine a list of signatures into a single multi-signed transaction.
{% code-snippet file="/_code-samples/multisigning/js/send-multi-signed-transaction.js" language="js" from="// Combine signatures" /%}
{% /tab %}
{% tab label="Python" %}
In **xrpl-py**, use the [`xrpl.transaction.multisign(...)` function](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.transaction.html#xrpl.transaction.multisign) to combine a list of signatures into a single multi-signed transaction.
{% code-snippet file="/_code-samples/multisigning/py/send-multi-signed-transaction.py" language="py" from="# Combine signatures" /%}
{% /tab %}
{% /tabs %}
Confirming that the transaction succeeded and accomplished the intended purpose is largely the same as it would be when sending the transaction normally, except you should be aware of the potential for [malleability with multi-signatures](../../../concepts/transactions/finality-of-results/transaction-malleability.md#malleability-with-multi-signatures): if valid signatures can be added to or removed from the transaction, it could succeed with a different identifying hash than you expected. You can mitigate these risks with good operational security, including not submitting a transaction with more than the necessary number of signatures.
## See Also
For more information about multi-signing and related topics, see:
- **Concepts:**
- [Cryptographic Keys](../../../concepts/accounts/cryptographic-keys.md)
- [Multi-Signing](../../../concepts/accounts/multi-signing.md)
- [Issuing and Operational Addresses](../../../concepts/accounts/account-types.md)
- [Transaction Malleability](../../../concepts/transactions/finality-of-results/transaction-malleability.md)
- **Tutorials:**
- [Set Up Multi-Signing](set-up-multi-signing.md)
- [Disable Master Key Pair](disable-master-key-pair.md)
- [Use Tickets](../transaction-sending/use-tickets.md)
- **References:**
- **xrpl.js:**
- [`autofill(transaction, signerCount)`](https://js.xrpl.org/classes/Client.html#autofill)
- [`Wallet.sign(...)`](https://js.xrpl.org/classes/Wallet.html#sign)
- [`xrpl.multisign(...)`](https://js.xrpl.org/functions/multisign.html)
- **xrpl-py:**
- [`xrpl.transaction.autofill(transaction, client, num_signers)`](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.transaction.html#xrpl.transaction.autofill)
- [`xrpl.transaction.sign(...)`](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.transaction.html#xrpl.transaction.sign)
- [`xrpl.transaction.multisign(...)`](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.transaction.html#xrpl.transaction.multisign)
{% raw-partial file="/docs/_snippets/common-links.md" /%}