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 an AccountPermissionUpdateContract that sets up the permission. The current owner permission normally authorizes this transaction. If an existing active permission enables AccountPermissionUpdateContract (ID 46), the transaction can instead select that active permission. For an account still using the default single-key configuration, sign with the original account key.

⚠️

If the usable signing weight of the new permissions cannot meet their thresholds and no other usable permission can change the configuration again, the account permissions become unrecoverable. Read the checklist under Updating permissions and rehearse on a testnet first.

const updateTxn = await tronWeb.transactionBuilder.updateAccountPermissions(
  "TAccount...XX",   // owner_address
  {
    type: 0,
    permission_name: "owner",
    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)
  [{
    type: 2,
    permission_name: "active0",
    threshold: 2,
    operations: "0200000000000000000000000000000000000000000000000000000000000000",
    keys: [
      { address: "TAlice...AA", weight: 1 },
      { address: "TBob1...BB",  weight: 1 },
      { address: "TCarol...CC", weight: 1 }
    ]
  }]                // a permission update must include at least one active permission
);

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

Before broadcasting, use TronWeb's getSignWeight method to confirm that the transaction has met the permission threshold. The method calls wallet/getsignweight and returns the signature-validation result without broadcasting the transaction.

const signWeight = await tronWeb.trx.getSignWeight(signedByAliceAndBob);
console.log(signWeight);

The following excerpt omits the transaction object returned in the response:

{
  "approved_list": [
    "41aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "41bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
  ],
  "current_weight": 2,
  "permission": {
    "threshold": 2,
    "keys": [
      {"address": "41aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "weight": 1},
      {"address": "41bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "weight": 1},
      {"address": "41cccccccccccccccccccccccccccccccccccccccc", "weight": 1}
    ]
  },
  "result": {
    "code": "ENOUGH_PERMISSION"
  }
}

Transactions built locally by TronWeb use hexadecimal addresses by default, so the response contains addresses beginning with 41. Use tronWeb.address.fromHex() if Base58Check addresses are needed.

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. A transaction carrying two or more signatures is charged the surcharge in chain parameter #23, getMultiSignFee, once. The current Mainnet value is 1 TRX; query wallet/getchainparameters for the live value. The 2-signature transaction above pays the normal transfer cost plus this surcharge, and adding another signature does not charge it again. 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.

The transaction still pays only one multi-signature surcharge, although the additional signature increases its serialized size.

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