This guide will show you how to connect multiple multisig accounts to achieve advanced on-chain authorization logic.
Multisig accounts can have as cosignatories other multisig accounts and add “AND/OR” logic to multi-signature transactions.
In this guide, we are going to create a complex 3-level multisig account.
For example, if the account #5 initiates an AggregateBondedTransaction involving the account #1, the accounts #7 or #8 and #4 should cosign the transaction in order to be included in a block.
Complete converting an account to multisig guide.
Create accounts for every multisig level.
Load the root multisig account with enough symbol.xym
to pay for the transaction fee.
Define the multisig account #2.
// replace with network type
const networkType = NetworkType.TEST_NET;
// replace with private key
const multisig2PrivateKey =
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF';
const multisigAccount2 = Account.createFromPrivateKey(
multisig2PrivateKey,
networkType,
);
// replace with public key
const cosignatoryAccount5PublicKey =
'17E42BDF5B7FF5001DC96A262A1141FFBE3F09A3A45DE7C095AAEA14F45C0DA0';
const cosignatory5 = PublicAccount.createFromPublicKey(
cosignatoryAccount5PublicKey,
networkType,
);
// replace with public key
const cosignatoryAccount6PublicKey =
'D04AB232742BB4AB3A1368BD4615E4E6D0224AB71A016BAF8520A332C9778737';
const cosignatory6 = PublicAccount.createFromPublicKey(
cosignatoryAccount6PublicKey,
networkType,
);
const convertMultisigAccount2Transaction = MultisigAccountModificationTransaction.create(
Deadline.create(epochAdjustment),
1,
1,
[cosignatory5.address, cosignatory6.address],
[],
networkType,
);
// replace with network type
const networkType = symbol_sdk_1.NetworkType.TEST_NET;
// replace with private key
const multisig2PrivateKey = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF';
const multisigAccount2 = symbol_sdk_1.Account.createFromPrivateKey(multisig2PrivateKey, networkType);
// replace with public key
const cosignatoryAccount5PublicKey = '17E42BDF5B7FF5001DC96A262A1141FFBE3F09A3A45DE7C095AAEA14F45C0DA0';
const cosignatory5 = symbol_sdk_1.PublicAccount.createFromPublicKey(cosignatoryAccount5PublicKey, networkType);
// replace with public key
const cosignatoryAccount6PublicKey = 'D04AB232742BB4AB3A1368BD4615E4E6D0224AB71A016BAF8520A332C9778737';
const cosignatory6 = symbol_sdk_1.PublicAccount.createFromPublicKey(cosignatoryAccount6PublicKey, networkType);
const convertMultisigAccount2Transaction = symbol_sdk_1.MultisigAccountModificationTransaction.create(symbol_sdk_1.Deadline.create(epochAdjustment), 1, 1, [cosignatory5.address, cosignatory6.address], [], networkType);
Define the multisig account #3.
// replace with private key
const multisig3PrivateKey =
'1111111111111111111111111111111111111111111111111111111111111111';
const multisigAccount3 = Account.createFromPrivateKey(
multisig3PrivateKey,
networkType,
);
// replace with public key
const cosignatoryAccount7PublicKey =
'38C22255DE39952C5D18803EC305A888D5DDE2C59BF3D4EFFAE6FC5FFCBF4F5D';
const cosignatory7 = PublicAccount.createFromPublicKey(
cosignatoryAccount7PublicKey,
networkType,
);
// replace with public key
const cosignatoryAccount8PublicKey =
'9F784BF20318AE3CA6246C0EC2207FE095FFF7A84B6787E7E3C2CE4C3B92A2EA';
const cosignatory8 = PublicAccount.createFromPublicKey(
cosignatoryAccount8PublicKey,
networkType,
);
// replace with public key
const cosignatoryAccount4PublicKey =
'EB2B065D27C6A6FB322F2E568E1AAD9CD6C0F155675E2837058D4811F5C0247D';
const cosignatory4 = PublicAccount.createFromPublicKey(
cosignatoryAccount4PublicKey,
networkType,
);
const convertMultisigAccount3Transaction = MultisigAccountModificationTransaction.create(
Deadline.create(epochAdjustment),
2,
1,
[cosignatory7.address, cosignatory8.address, cosignatory4.address],
[],
networkType,
);
// replace with private key
const multisig3PrivateKey = '1111111111111111111111111111111111111111111111111111111111111111';
const multisigAccount3 = symbol_sdk_1.Account.createFromPrivateKey(multisig3PrivateKey, networkType);
// replace with public key
const cosignatoryAccount7PublicKey = '38C22255DE39952C5D18803EC305A888D5DDE2C59BF3D4EFFAE6FC5FFCBF4F5D';
const cosignatory7 = symbol_sdk_1.PublicAccount.createFromPublicKey(cosignatoryAccount7PublicKey, networkType);
// replace with public key
const cosignatoryAccount8PublicKey = '9F784BF20318AE3CA6246C0EC2207FE095FFF7A84B6787E7E3C2CE4C3B92A2EA';
const cosignatory8 = symbol_sdk_1.PublicAccount.createFromPublicKey(cosignatoryAccount8PublicKey, networkType);
// replace with public key
const cosignatoryAccount4PublicKey = 'EB2B065D27C6A6FB322F2E568E1AAD9CD6C0F155675E2837058D4811F5C0247D';
const cosignatory4 = symbol_sdk_1.PublicAccount.createFromPublicKey(cosignatoryAccount4PublicKey, networkType);
const convertMultisigAccount3Transaction = symbol_sdk_1.MultisigAccountModificationTransaction.create(symbol_sdk_1.Deadline.create(epochAdjustment), 2, 1, [cosignatory7.address, cosignatory8.address, cosignatory4.address], [], networkType);
Define the multisig account #1.
// replace with private key
const multisig1PrivateKey =
'0000000000000000000000000000000000000000000000000000000000000000';
const multisigAccount1 = Account.createFromPrivateKey(
multisig1PrivateKey,
networkType,
);
const convertMultisigAccount1Transaction = MultisigAccountModificationTransaction.create(
Deadline.create(epochAdjustment),
3,
1,
[
multisigAccount2.publicAccount.address,
multisigAccount3.publicAccount.address,
cosignatory4.address,
],
[],
networkType,
);
// replace with private key
const multisig1PrivateKey = '0000000000000000000000000000000000000000000000000000000000000000';
const multisigAccount1 = symbol_sdk_1.Account.createFromPrivateKey(multisig1PrivateKey, networkType);
const convertMultisigAccount1Transaction = symbol_sdk_1.MultisigAccountModificationTransaction.create(symbol_sdk_1.Deadline.create(epochAdjustment), 3, 1, [
multisigAccount2.publicAccount.address,
multisigAccount3.publicAccount.address,
cosignatory4.address,
], [], networkType);
4. Announce the transactions together using an AggregateBondedTransaction.
The account #1 must lock 10
symbol.xym
to announce the transaction.
const aggregateTransaction = AggregateTransaction.createBonded(
Deadline.create(epochAdjustment),
[
convertMultisigAccount2Transaction.toAggregate(
multisigAccount2.publicAccount,
),
convertMultisigAccount3Transaction.toAggregate(
multisigAccount3.publicAccount,
),
convertMultisigAccount1Transaction.toAggregate(
multisigAccount1.publicAccount,
),
],
networkType,
[],
UInt64.fromUint(2000000),
);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash =
'1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = multisigAccount1.sign(
aggregateTransaction,
networkGenerationHash,
);
console.log(signedTransaction.hash);
// replace with symbol.xym id
const networkCurrencyMosaicId = new MosaicId('5E62990DCAC5BE8A');
// replace with network currency divisibility
const networkCurrencyDivisibility = 6;
const hashLockTransaction = HashLockTransaction.create(
Deadline.create(epochAdjustment),
new Mosaic(
networkCurrencyMosaicId,
UInt64.fromUint(10 * Math.pow(10, networkCurrencyDivisibility)),
),
UInt64.fromUint(480),
signedTransaction,
networkType,
UInt64.fromUint(2000000),
);
const signedHashLockTransaction = multisigAccount1.sign(
hashLockTransaction,
networkGenerationHash,
);
// replace with node endpoint
const nodeUrl = 'NODE_URL';
const repositoryFactory = new RepositoryFactoryHttp(nodeUrl);
const listener = repositoryFactory.createListener();
const receiptHttp = repositoryFactory.createReceiptRepository();
const transactionHttp = repositoryFactory.createTransactionRepository();
const transactionService = new TransactionService(transactionHttp, receiptHttp);
listener.open().then(() => {
transactionService
.announceHashLockAggregateBonded(
signedHashLockTransaction,
signedTransaction,
listener,
)
.subscribe(
(x) => console.log(x),
(err) => console.log(err),
() => listener.close(),
);
});
const aggregateTransaction = symbol_sdk_1.AggregateTransaction.createBonded(symbol_sdk_1.Deadline.create(epochAdjustment), [
convertMultisigAccount2Transaction.toAggregate(multisigAccount2.publicAccount),
convertMultisigAccount3Transaction.toAggregate(multisigAccount3.publicAccount),
convertMultisigAccount1Transaction.toAggregate(multisigAccount1.publicAccount),
], networkType, [], symbol_sdk_1.UInt64.fromUint(2000000));
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash = '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = multisigAccount1.sign(aggregateTransaction, networkGenerationHash);
console.log(signedTransaction.hash);
// replace with symbol.xym id
const networkCurrencyMosaicId = new symbol_sdk_1.MosaicId('5E62990DCAC5BE8A');
// replace with network currency divisibility
const networkCurrencyDivisibility = 6;
const hashLockTransaction = symbol_sdk_1.HashLockTransaction.create(symbol_sdk_1.Deadline.create(epochAdjustment), new symbol_sdk_1.Mosaic(networkCurrencyMosaicId, symbol_sdk_1.UInt64.fromUint(10 * Math.pow(10, networkCurrencyDivisibility))), symbol_sdk_1.UInt64.fromUint(480), signedTransaction, networkType, symbol_sdk_1.UInt64.fromUint(2000000));
const signedHashLockTransaction = multisigAccount1.sign(hashLockTransaction, networkGenerationHash);
// replace with node endpoint
const nodeUrl = 'NODE_URL';
const repositoryFactory = new symbol_sdk_1.RepositoryFactoryHttp(nodeUrl);
const listener = repositoryFactory.createListener();
const receiptHttp = repositoryFactory.createReceiptRepository();
const transactionHttp = repositoryFactory.createTransactionRepository();
const transactionService = new symbol_sdk_1.TransactionService(transactionHttp, receiptHttp);
listener.open().then(() => {
transactionService
.announceHashLockAggregateBonded(signedHashLockTransaction, signedTransaction, listener)
.subscribe((x) => console.log(x), (err) => console.log(err), () => listener.close());
});
5. The potential cosignatories must opt-in to become cosignatories. Cosign the announced AggregateTransaction with the accounts #5, #6, #7, #8, and #4.
symbol-cli transaction cosign --hash A6A374E66B32A3D5133018EFA9CD6E3169C8EEA339F7CCBE29C47D07086E068C --profile <account>