Adding a new signer to a multisig account

This guide will show you how to add a new cosignatory to a multisig account.

Use case

Imagine that Alice and Bob want to add Carol, a third participant, as a cosignatory of a multisig 2-of-2 multisig account. However, they don’t want to increase the number of signatures needed to accept transactions, so the new account will require only 2-of-3 cosignatures to transact.

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

2-of-3 multisig account example

Prerequisites

Method #01: Using the Desktop Wallet

  1. Log in to an account that is a cosignatory of the multisig account. This could be Alice or Bob’s account.

  2. Click on “Multisig” on the left-side menu.

  3. Select the multisig wallet you want to modify from the dropdown menu from the top field. This will convert the “Operation Type” to “Modifying account multisig properties”.

  4. Click on “Add a cosignatory” and provide the address or public key of the account you want to add as a new signer to the multisig. Click “Send”. Review the information on the popup. Enter your wallet password and click “Confirm”.

resources/images/screenshots/add-signer-1.gif
  1. If the multisig account has the “minimum approval” set to a number greater than 1, log in to another cosignatory account and cosign the transaction. Repeat this step until the minimum approval number is satisfied.

resources/images/screenshots/add-signer-2.gif
  1. You can check that the new signer has been added by navigating to the “Multisig” page. The new signer should be listed under “Cosignatories”.

resources/images/screenshots/add-signer-3.png

Method #02: Using the SDK

  1. Open a new file. Define the public keys of the multisig account and the new account to be added into new variables.

// replace with network type
const networkType = NetworkType.TEST_NET;
// replace with multisig public key
const multisigAccountPublicKey =
  '3A537D5A1AF51158C42F80A199BB58351DBF3253C4A6A1B7BD1014682FB595EA';
const multisigAccount = PublicAccount.createFromPublicKey(
  multisigAccountPublicKey,
  networkType,
);
// replace with new cosignatory public key
const newCosignatoryPublicKey =
  '17E42BDF5B7FF5001DC96A262A1141FFBE3F09A3A45DE7C095AAEA14F45C0DA0';
const newCosignatoryAccount = PublicAccount.createFromPublicKey(
  newCosignatoryPublicKey,
  networkType,
);
// replace with network type
const networkType = bitxor_sdk_1.NetworkType.TEST_NET;
// replace with multisig public key
const multisigAccountPublicKey =
    '3A537D5A1AF51158C42F80A199BB58351DBF3253C4A6A1B7BD1014682FB595EA';
const multisigAccount = bitxor_sdk_1.PublicAccount.createFromPublicKey(
    multisigAccountPublicKey,
    networkType,
);
// replace with new cosignatory public key
const newCosignatoryPublicKey =
    '17E42BDF5B7FF5001DC96A262A1141FFBE3F09A3A45DE7C095AAEA14F45C0DA0';
const newCosignatoryAccount = bitxor_sdk_1.PublicAccount.createFromPublicKey(
    newCosignatoryPublicKey,
    networkType,
);
  1. Create a MultisigAccountModificationTransaction, adding the new participant as a cosignatory.

Note

Following the previous example, the multisig account will become a 2-of-3, since we are adding a new cosignatory but not increasing the minApprovalDelta.

const multisigAccountModificationTransaction = MultisigAccountModificationTransaction.create(
  Deadline.create(epochAdjustment),
  0,
  0,
  [newCosignatoryAccount.address],
  [],
  networkType,
);
const multisigAccountModificationTransaction = bitxor_sdk_1.MultisigAccountModificationTransaction.create(
    bitxor_sdk_1.Deadline.create(epochAdjustment),
    0,
    0, [newCosignatoryAccount.address], [],
    networkType,
);
  1. Wrap the MultisigAccountModificationTransaction in an Aggregate Transaction. Sign it with a cosignatory of the original multisig account.

const aggregateTransaction = AggregateTransaction.createBonded(
  Deadline.create(epochAdjustment),
  [multisigAccountModificationTransaction.toAggregate(multisigAccount)],
  networkType,
  [],
  UInt64.fromUint(2000000),
);

// replace with cosignatory private key
const cosignatoryPrivateKey =
  '1111111111111111111111111111111111111111111111111111111111111111';
const cosignatoryAccount = Account.createFromPrivateKey(
  cosignatoryPrivateKey,
  networkType,
);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash =
  '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = cosignatoryAccount.sign(
  aggregateTransaction,
  networkGenerationHash,
);
console.log(signedTransaction.hash);
const aggregateTransaction = bitxor_sdk_1.AggregateTransaction.createBonded(
    bitxor_sdk_1.Deadline.create(epochAdjustment), [multisigAccountModificationTransaction.toAggregate(multisigAccount)],
    networkType, [],
    bitxor_sdk_1.UInt64.fromUint(2000000),
);
// replace with cosignatory private key
const cosignatoryPrivateKey =
    '1111111111111111111111111111111111111111111111111111111111111111';
const cosignatoryAccount = bitxor_sdk_1.Account.createFromPrivateKey(
    cosignatoryPrivateKey,
    networkType,
);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash =
    '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = cosignatoryAccount.sign(
    aggregateTransaction,
    networkGenerationHash,
);
console.log(signedTransaction.hash);

4. Before sending an AggregateBondedTransaction, an account must lock at least 10 bitxor. This transaction is required to prevent spamming the network. After the HashLockTransaction has been confirmed, announce the AggregateTransaction.

// replace with bitxor id
const networkCurrencyTokenId = new TokenId('5E62990DCAC5BE8A');
// replace with network currency divisibility
const networkCurrencyDivisibility = 6;

const hashLockTransaction = HashLockTransaction.create(
  Deadline.create(epochAdjustment),
  new Token(
    networkCurrencyTokenId,
    UInt64.fromUint(10 * Math.pow(10, networkCurrencyDivisibility)),
  ),
  UInt64.fromUint(480),
  signedTransaction,
  networkType,
  UInt64.fromUint(2000000),
);

const signedHashLockTransaction = cosignatoryAccount.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(),
    );
});
// replace with bitxor id
const networkCurrencyTokenId = new bitxor_sdk_1.TokenId('5E62990DCAC5BE8A');
// replace with network currency divisibility
const networkCurrencyDivisibility = 6;
const hashLockTransaction = bitxor_sdk_1.HashLockTransaction.create(
    bitxor_sdk_1.Deadline.create(epochAdjustment),
    new bitxor_sdk_1.Token(
        networkCurrencyTokenId,
        bitxor_sdk_1.UInt64.fromUint(
            10 * Math.pow(10, networkCurrencyDivisibility),
        ),
    ),
    bitxor_sdk_1.UInt64.fromUint(480),
    signedTransaction,
    networkType,
    bitxor_sdk_1.UInt64.fromUint(2000000),
);
const signedHashLockTransaction = cosignatoryAccount.sign(
    hashLockTransaction,
    networkGenerationHash,
);
// replace with node endpoint
const nodeUrl = 'NODE_URL';
const repositoryFactory = new bitxor_sdk_1.RepositoryFactoryHttp(nodeUrl);
const listener = repositoryFactory.createListener();
const receiptHttp = repositoryFactory.createReceiptRepository();
const transactionHttp = repositoryFactory.createTransactionRepository();
const transactionService = new bitxor_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 new account must opt-in first in order to become a cosignatory of the multisig account. Cosign the AggregateTransaction hash with the account to be added to the multisig.

bitxor-cli transaction cosign --hash A6A374E66B32A3D5133018EFA9CD6E3169C8EEA339F7CCBE29C47D07086E068C --profile carol

6. Cosign the AggregateTransaction with all other multisig account participants required to reach quorum. The amount of bitxor locked becomes available again on the account that sent the HashLockTransaction, and the new participant is added to the multisig.

bitxor-cli transaction cosign --hash A6A374E66B32A3D5133018EFA9CD6E3169C8EEA339F7CCBE29C47D07086E068C --profile bob

Follow the next guide to remove a cosignatory from a multisig account.