Account Permission Management transaction example

A complete walkthrough of a 2-of-3 multi-signature transaction: setup, building, signing, weight check, and broadcast.

This page walks through a complete multi-signature transaction on TRON. The scenario: a 2-of-3 owner permission, where any two of three signers can authorize transfers on the account. The walkthrough covers configuration, transaction building, signature collection, weight verification, and broadcast.

📘

Prerequisites


Scenario

Three parties — Alice, Bob, and Carol — jointly control an account. The account's owner permission is configured for 2-of-3:

  • threshold: 2
  • keys: Alice (weight 1), Bob (weight 1), Carol (weight 1)

Any two of them can authorize a transfer; any one alone cannot.


Step 1 — Configure the 2-of-3 permission

The first step is a one-time AccountPermissionUpdateContract that sets up the permission. The transaction must be signed by whoever currently controls the account (typically the original single-key owner). After this transaction confirms, the account is multi-sig.

const updateTxn = await tronWeb.transactionBuilder.updateAccountPermissions(
  "TAccount...XX",   // owner_address
  {
    threshold: 2,
    keys: [
      { address: "TAlice...AA", weight: 1 },
      { address: "TBob1...BB",  weight: 1 },
      { address: "TCarol...CC", weight: 1 }
    ]
  },
  null,             // no witness permission (not an SR account)
  []                // no active permissions in this example
);

const signedUpdate = await tronWeb.trx.sign(updateTxn, originalOwnerKey);
const updateResult = await tronWeb.trx.sendRawTransaction(signedUpdate);
console.log(updateResult.txid);

Once the transaction is solidified (~1 minute), the account is under 2-of-3 control.


Step 2 — Build the transaction to be co-signed

Suppose the multi-sig account needs to transfer 100 TRX to a vendor. Any of the three signers can build the unsigned transaction:

const txn = await tronWeb.transactionBuilder.sendTrx(
  "TVendor...DD",    // recipient
  100_000_000,        // 100 TRX in sun
  "TAccount...XX"    // sender (the multi-sig account)
);

// Set Permission_id explicitly to indicate this transaction is signed under the owner permission
txn.raw_data.contract[0].Permission_id = 0;

Step 3 — Collect signatures

Signatures can be collected one at a time. Each signer recovers the transaction object, signs it with their key, and passes the partially-signed transaction to the next signer.

Alice signs:

const signedByAlice = await tronWeb.trx.multiSign(txn, aliceKey, 0);
// signedByAlice.signature is now [<alice_sig>]

The third argument (0) is the Permission_id — telling the SDK which permission this signature targets.

Pass signedByAlice to Bob.

Bob signs:

const signedByAliceAndBob = await tronWeb.trx.multiSign(signedByAlice, bobKey, 0);
// .signature is now [<alice_sig>, <bob_sig>]

The transaction is now fully signed (sum of weights = 1 + 1 = 2, meeting the threshold of 2). It is ready to broadcast. Carol's signature is not needed.


Step 4 — Inspect signature weight before broadcast

To confirm the transaction has met the threshold without broadcasting, query the network:

BASE_URL=https://api.trongrid.io   # example — replace with any TRON node (TronGrid, third-party, or self-hosted)
POST ${BASE_URL}/wallet/getsignweight
{
  "transaction": <signedByAliceAndBob serialized>
}

The response includes:

{
  "approved_list": ["TAlice...AA", "TBob1...BB"],
  "current_weight": 2,
  "permission": {
    "threshold": 2,
    "keys": [...]
  },
  "result": {
    "code": "NOT_ENOUGH_PERMISSION" | "ENOUGH_PERMISSION"
  }
}

When current_weight ≥ threshold, result.code is ENOUGH_PERMISSION and the transaction can be broadcast. While still collecting signatures, the code is NOT_ENOUGH_PERMISSION. Other codes such as SIGNATURE_FORMAT_ERROR indicate malformed inputs.


Step 5 — Broadcast

const result = await tronWeb.trx.sendRawTransaction(signedByAliceAndBob);
console.log(result.txid);

Wait for the containing block to solidify (~1 minute) before considering the transfer final. See Consensus and DPoS — Block solidification.

💰

Multi-sig surcharge. TRON charges an additional 1 TRX per signature beyond the first when a transaction carries multiple signatures (chain parameter #23 getMultiSignFee, current Mainnet default; query live via wallet/getchainparameters). The 2-signature transaction above pays the normal transfer cost plus 1 TRX surcharge. Budget for this when designing multi-sig hot paths — see Account Permission Management § Fees.


Variations

Carol signs instead of Bob

Equivalent to the above — any two of the three keys reach threshold 2.

Adding extra signatures

Adding Carol's signature on top of Alice's and Bob's is harmless. The total weight (1 + 1 + 1 = 3) exceeds the threshold (2), and the transaction is still valid. Extra signatures don't cause rejection.

A signature from outside the keys list

If a fourth party not in the keys list signs the transaction, the entire transaction is rejected with PermissionException at validation time — the unknown signature does not just contribute 0 weight, it invalidates the whole transaction. The same applies to duplicate signers (the same key signing twice) and to transactions whose signature count exceeds the permission's key count. Multi-sig validation is strict; see Signature validation § Multi-signature validation.

Active permissions for delegated subsets

For routine vendor payments, set up an active permission with a smaller threshold and an operations bitmap that allows only TransferContract. Then daily transfers can go through a single delegate key, while owner-level changes (modifying permissions, deploying contracts) still need 2-of-3.

For active permission setup, see Account Permission Management.


APIs

APIDescription
wallet/accountpermissionupdateConfigure permissions
wallet/getsignweightCheck collected signature weight
wallet/getapprovedlistList signers so far
wallet/broadcasttransactionSubmit fully-signed transaction

Related resources