# Send a Multi-Signed Transaction This tutorial shows how to send a transaction using [multi-signing](/es-es/docs/concepts/accounts/multi-signing). ## Goals By following this tutorial, you should learn how to send a transaction using a multi-signing list to authorize the transaction. ## Prerequisites To complete this tutorial, you should: - Have a basic understanding of the XRP Ledger. - Have an [XRP Ledger client library](/es-es/docs/references/client-libraries), such as **xrpl.js**, installed. - Understand how to [set up multi-signing](/es-es/docs/tutorials/best-practices/key-management/set-up-multi-signing) 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 code samples section of this website's repository. ## Steps ### 1. Install dependencies JavaScript From the code sample folder, use `npm` to install dependencies: ```sh npm i ``` Python 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 ``` ### 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. JavaScript 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} `) Python 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} """) ### 3. Set up multi-signing 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](/es-es/docs/tutorials/best-practices/key-management/set-up-multi-signing). **Skip this step if you are using an existing account that already has a signer list.** JavaScript // 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) } Python # 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 key pair for signer {i + 1}: 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 Exception as 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) ### 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](/docs/concepts/transactions/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](/docs/references/protocol/transactions/types/accountset), but you can send almost any type of transaction using a multi-signature. 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](/es-es/docs/tutorials/best-practices/transaction-sending/use-tickets) instead. 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. // 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)) 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. # 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)) ### 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. 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. // 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)) 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. # 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) ### 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. 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. // 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() 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. # 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 Exception as 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) 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](/es-es/docs/concepts/transactions/finality-of-results/transaction-malleability#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](/es-es/docs/concepts/accounts/cryptographic-keys) - [Multi-Signing](/es-es/docs/concepts/accounts/multi-signing) - [Account Types](/es-es/docs/concepts/accounts/account-types) - [Transaction Malleability](/es-es/docs/concepts/transactions/finality-of-results/transaction-malleability) - **Tutorials:** - [Set Up Multi-Signing](/es-es/docs/tutorials/best-practices/key-management/set-up-multi-signing) - [Disable Master Key Pair](/es-es/docs/tutorials/best-practices/key-management/disable-master-key-pair) - [Use Tickets](/es-es/docs/tutorials/best-practices/transaction-sending/use-tickets) - **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)