Modifying a multisig account

Modify an existing multisig account.

First, you are going to turn a 1-of-2 multisig account into a 2-of-2. Then, you will add a new cosignatory, becoming a 2-of-3. Finally, after removing a cosignatory, you will know how to perform all sort of modifications to multisig accounts.

Getting into some code

Editing minApproval

Alice and Bob are cosignatories of the 1-of-2 multisig account. This means that at least one of their account’s signatures is required to authorize multisig transactions. In other words, we can say that the minApproval parameter of the multisig is currently set to 1.

Multisig accounts are editable at the blockchain level. In this case, we want to make both cosignatories required, shifting to a 2-of-2 multisig instead. You can achieve this by increasing the minApproval parameter in one unit.

../../_images/multisig-2-of-2.png

2-of-2 multisig account example

One of the accounts, for example Alice’s, will announce a MultisigAccountModificationTransaction to increase minApprovalDelta.

  1. First, define Alice account as the cosignatory and the multisig account using its public key.
const cosignatoryPrivateKey = process.env.COSIGNATORY_PRIVATE_KEY as string;
const cosignatoryAccount = Account.createFromPrivateKey(cosignatoryPrivateKey, NetworkType.MIJIN_TEST);

const multisigAccountPublicKey = process.env.MULTISIG_ACCOUNT_PUBLIC_KEY as string;
const multisigAccount = PublicAccount.createFromPublicKey(multisigAccountPublicKey, NetworkType.MIJIN_TEST);
  1. Define a MultisigAccountModificationTransaction to increase the minAprovalDelta in one unit.
const multisigAccountModificationTransaction = MultisigAccountModificationTransaction.create(
    Deadline.create(),
    1,
    0,
    [],
    NetworkType.MIJIN_TEST);
  1. Wrap the MultisigAccountModificationTransaction in an AggregateTransaction, attaching the multisig public key as the signer.

An AggregateTransaction is complete if, before announcing it to the network, all required cosignatories have signed it. If valid, it will be included in a block.

As only one cosignature is required (1-of-2), Alice can sign the transaction and announce it to the network.

const aggregateTransaction = AggregateTransaction.createComplete(
    Deadline.create(),
    [multisigAccountModificationTransaction.toAggregate(multisigAccount)],
    NetworkType.MIJIN_TEST,
    []);

const networkGenerationHash = process.env.NETWORK_GENERATION_HASH as string;
const signedTransaction = cosignatoryAccount.sign(aggregateTransaction, networkGenerationHash);

const transactionHttp = new TransactionHttp('http://localhost:3000');
transactionHttp
    .announce(signedTransaction)
    .subscribe(x => console.log(x), err => console.error(err));

Once confirmed, the minApproval value of the multisig will be set to 2, having our 2-of-2 multisig.

Note

If you want to decrease the minApproval parameter, set minApprovalDelta with a negative value. In this case -1.

Adding a new cosignatory

Alice and Bob want to add Carol as a cosignatory of the multisig account to achieve 2-of-3 cosignatures required.

../../_images/multisig-2-of-31.png

2-of-3 multisig account example

  1. Create a MultisigAccountModificationTransaction adding Carol as a cosignatory. The multisig account will become a 2-of-3, since you are not increasing the minApprovalDelta.
const cosignatoryPrivateKey = process.env.COSIGNATORY_PRIVATE_KEY as string;
const cosignatoryAccount = Account.createFromPrivateKey(cosignatoryPrivateKey, NetworkType.MIJIN_TEST);

const multisigAccountPublicKey = process.env.MULTISIG_ACCOUNT_PUBLIC_KEY as string;
const multisigAccount = PublicAccount.createFromPublicKey(multisigAccountPublicKey, NetworkType.MIJIN_TEST);

const newCosignatoryPublicKey = process.env.NEW_COSIGNATORY_PUBLIC_KEY as string;
const newCosignatoryAccount = PublicAccount.createFromPublicKey(newCosignatoryPublicKey, NetworkType.MIJIN_TEST);

const multisigCosignatoryModification = new MultisigCosignatoryModification(CosignatoryModificationAction.Add, newCosignatoryAccount);
  1. Create a MultisigAccountModificationTransaction adding the previous modification.
const multisigAccountModificationTransaction = MultisigAccountModificationTransaction.create(
    Deadline.create(),
    0,
    0,
    [multisigCosignatoryModification],
    NetworkType.MIJIN_TEST);
  1. Wrap the MultisigAccountModificationTransaction in an AggregateBondedTransaction and sign it.
const aggregateTransaction = AggregateTransaction.createBonded(
    Deadline.create(),
    [multisigAccountModificationTransaction.toAggregate(multisigAccount)],
    NetworkType.MIJIN_TEST);

const networkGenerationHash = process.env.NETWORK_GENERATION_HASH as string;
const signedTransaction = cosignatoryAccount.sign(aggregateTransaction, networkGenerationHash);
console.log(signedTransaction.hash);
  1. Before sending an AggregateBondedTransaction, Alice needs to lock at least 10 cat.currency. This transaction is required to prevent spamming the network. After the HashLockTransaction has been confirmed, announce the AggregateTransaction.
const hashLockTransaction = HashLockTransaction.create(
    Deadline.create(),
    NetworkCurrencyMosaic.createRelative(10),
    UInt64.fromUint(480),
    signedTransaction,
    NetworkType.MIJIN_TEST);

const signedHashLockTransaction = cosignatoryAccount.sign(hashLockTransaction, networkGenerationHash);

const nodeUrl = 'http://localhost:3000';
const transactionHttp = new TransactionHttp(nodeUrl);
const listener = new Listener(nodeUrl);

const announceHashLockTransaction = (signedHashLockTransaction: SignedTransaction) => {
    return transactionHttp.announce(signedHashLockTransaction);
};

const announceAggregateTransaction = (listener: Listener,
                                      signedHashLockTransaction: SignedTransaction,
                                      signedAggregateTransaction: SignedTransaction,
                                      senderAddress: Address) => {
    return listener
        .confirmed(senderAddress)
        .pipe(
            filter((transaction) => transaction.transactionInfo !== undefined
                && transaction.transactionInfo.hash === signedHashLockTransaction.hash),
            mergeMap(ignored => {
                listener.terminate();
                return transactionHttp.announceAggregateBonded(signedAggregateTransaction)
            })
        );
};

listener.open().then(() => {
    merge(announceHashLockTransaction(signedHashLockTransaction),
        announceAggregateTransaction(listener, signedHashLockTransaction, signedTransaction, cosignatoryAccount.address))
        .subscribe(x => console.log('Transaction confirmed:', x.message),
            err=> console.log(err));
});
  1. Cosign the AggregateTransaction hash with Carols’s account. She has to opt-in to become a multisig cosignatory.
nem2-cli transaction cosign --hash A6A374E66B32A3D5133018EFA9CD6E3169C8EEA339F7CCBE29C47D07086E068C --profile carol
  1. Cosign the AggregateTransaction with Bob’s account. The amount of cat.currency locked becomes available again on Alice’s account and Carol is added to the multisig.
nem2-cli transaction cosign --hash A6A374E66B32A3D5133018EFA9CD6E3169C8EEA339F7CCBE29C47D07086E068C --profile bob

Removing a cosignatory

Once you have added Carol, let’s delete a cosignatory from the multisig.

The following code shows how to remove a cosignatory from the 2-of-3 multisig account with minRemoval set to 1.

The minRemoval parameter indicates the number of required signatures to delete an account from the multisig. You can increase or decrease it the same way you modify minApproval parameter.

const multisigAccountPublicKey = process.env.MULTISIG_ACCOUNT_PUBLIC_KEY as string;
const multisigAccount = PublicAccount.createFromPublicKey(multisigAccountPublicKey, NetworkType.MIJIN_TEST);

const cosignatoryToRemovePublicKey = process.env.COSIGNATORY_TO_REMOVE_PUBLIC_KEY as string;
const cosignatoryToRemove = PublicAccount.createFromPublicKey(cosignatoryToRemovePublicKey, NetworkType.MIJIN_TEST);

const cosignatoryPrivateKey = process.env.COSIGNATORY_PRIVATE_KEY as string;
const cosignatoryAccount =  Account.createFromPrivateKey(cosignatoryPrivateKey, NetworkType.MIJIN_TEST);

const multisigCosignatoryModification = new MultisigCosignatoryModification(CosignatoryModificationAction.Remove,cosignatoryToRemove);

const multisigAccountModificationTransaction = MultisigAccountModificationTransaction.create(
    Deadline.create(),
    0,
    0,
    [multisigCosignatoryModification],
    NetworkType.MIJIN_TEST);

const aggregateTransaction = AggregateTransaction.createComplete(
    Deadline.create(),
    [multisigAccountModificationTransaction.toAggregate(multisigAccount)],
    NetworkType.MIJIN_TEST,
    []);

const networkGenerationHash = process.env.NETWORK_GENERATION_HASH as string;
const signedTransaction = cosignatoryAccount.sign(aggregateTransaction, networkGenerationHash);

const transactionHttp = new TransactionHttp('http://localhost:3000');
transactionHttp
    .announce(signedTransaction)
    .subscribe(x => console.log(x), err => console.error(err));

The multisig modification transaction is wrapped in an AggregateCompleteTransaction, as only one account is required to delete others from the multisig.

What’s next?

Learn more about multi-level multisig accounts.