Creating a multisig account

This guide will show you how to setup a joint account.

Use case

Imagine that Alice and Bob have separate accounts, but they also want to have a shared account to buy groceries. If one of them goes out shopping, they should be able to transact with using their joint account without requiring explicit authorization from the other person.

This shared account appears in Symbol as 1-of-2 multisig. Creating a Multisig account permits Alice and Bob to share funds in a separate account. Since the account is configured as a 1-of-2, it’s only required the signature from one of them to issue transactions from the new account.

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

1-of-2 multisig account example

Prerequisites

  • Complete the getting started section.

  • Create a new account to turn into multisig.

  • Load the account with enough symbol.xym to pay for transaction fees.

  • Create accounts for Alice and Bob.

Note

To create create new accounts, follow this guide.

Method #01: Using the Desktop Wallet

  1. Log in to the account that you want to convert into a multisig account.

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

  3. Click on “Add a cosignatory” and provide the address or public key of the account you want to add as a cosignatory of the multisig. Click on the “+” button.

../../_images/create-multisig-2.gif
  1. Repeat step 3 for each account you want to add as a cosignatory of the multisig. In our example case, we have 2 accounts we want to add as cosignatories: Alice and Bob.

  2. Select the number of “Min. Approval” and “Min. Removal” for the multisig.

  3. Click “Send”. Review the information on the popup. Provide your wallet password and click “Confirm”.

../../_images/create-multisig-3.gif
  1. Log in to an account you selected as a potential cosignatory of the multisig. On the “Home” page, click on “Partial” transactions. Click on the pending Aggregate Bonded transaction. Provide your wallet password and click “Confirm”.

../../_images/create-multisig-4.gif
  1. Repeat step 7 for each account you added as a potential cosignatory for the multisig.

  2. Log in to the account being converted to a multisig. When the Aggregate Bonded transaction is complete (confirmed), you can check that it has been converted by going back to the “Multisig” page.

../../_images/create-multisig-5.png

Method #02: Using the SDK

1. First, define the accounts that will become cosignatories of the multisig account. Following our example, these are Alice and Bob addresses. Then, open the account that will be converted into multisig using its private key.

// replace with network type
const networkType = NetworkType.TEST_NET;
// replace with candidate multisig private key
const privateKey =
  'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF';
const account = Account.createFromPrivateKey(privateKey, networkType);
// replace with cosignatory 1 public key
const cosignatory1PublicKey =
  'D04AB232742BB4AB3A1368BD4615E4E6D0224AB71A016BAF8520A332C9778737';
const cosignatory1 = PublicAccount.createFromPublicKey(
  cosignatory1PublicKey,
  networkType,
);
// replace with cosignatory 2 public key
const cosignatory2PublicKey =
  '462EE976890916E54FA825D26BDD0235F5EB5B6A143C199AB0AE5EE9328E08CE';
const cosignatory2 = PublicAccount.createFromPublicKey(
  cosignatory2PublicKey,
  networkType,
);
// replace with network type
const networkType = symbol_sdk_1.NetworkType.TEST_NET;
// replace with candidate multisig private key
const privateKey =
  'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF';
const account = symbol_sdk_1.Account.createFromPrivateKey(
  privateKey,
  networkType,
);
// replace with cosignatory 1 public key
const cosignatory1PublicKey =
  'D04AB232742BB4AB3A1368BD4615E4E6D0224AB71A016BAF8520A332C9778737';
const cosignatory1 = symbol_sdk_1.PublicAccount.createFromPublicKey(
  cosignatory1PublicKey,
  networkType,
);
// replace with cosignatory 2 public key
const cosignatory2PublicKey =
  '462EE976890916E54FA825D26BDD0235F5EB5B6A143C199AB0AE5EE9328E08CE';
const cosignatory2 = symbol_sdk_1.PublicAccount.createFromPublicKey(
  cosignatory2PublicKey,
  networkType,
);

2. Create a MultisigAccountModificationTransaction to convert the shared account into a multisig account. Since we want to create a 1-of-2 multisig account, let’s set the minimum required signatures to 1.

const multisigAccountModificationTransaction = MultisigAccountModificationTransaction.create(
  Deadline.create(epochAdjustment),
  1,
  1,
  [cosignatory1.address, cosignatory2.address],
  [],
  networkType,
);
const multisigAccountModificationTransaction = symbol_sdk_1.MultisigAccountModificationTransaction.create(
  symbol_sdk_1.Deadline.create(epochAdjustment),
  1,
  1,
  [cosignatory1.address, cosignatory2.address],
  [],
  networkType,
);

3. Create an AggregateBondedTransaction, wrapping the MultisigAccountModificationTransaction defined in the previous step. This action is necessary because Alice and Bob must opt-in to become cosignatories of the new multisig account.

const aggregateTransaction = AggregateTransaction.createBonded(
  Deadline.create(epochAdjustment),
  [multisigAccountModificationTransaction.toAggregate(account.publicAccount)],
  networkType,
  [],
  UInt64.fromUint(2000000),
);
const aggregateTransaction = symbol_sdk_1.AggregateTransaction.createBonded(
  symbol_sdk_1.Deadline.create(epochAdjustment),
  [multisigAccountModificationTransaction.toAggregate(account.publicAccount)],
  networkType,
  [],
  symbol_sdk_1.UInt64.fromUint(2000000),
);
  1. Sign the AggregateTransaction using the private key of the future multisig account.

Note

To make the transaction only valid for your network, you will need to pass the first network generation hash. Open NODE_URL /node/info in a new browser tab and copy the meta.networkGenerationHash value.

// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash =
  '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = account.sign(
  aggregateTransaction,
  networkGenerationHash,
);
console.log(signedTransaction.hash);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash =
  '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = account.sign(
  aggregateTransaction,
  networkGenerationHash,
);
console.log(signedTransaction.hash);

5. Before sending an AggregateBondedTransaction, the future multisig account needs to lock at least 10 symbol.xym. This transaction is required to prevent spamming the network. After the HashLockTransaction has been confirmed, announce the AggregateTransaction signed in (4).

// 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 = account.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 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 = account.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(),
    );
});

6. Cosign the AggregateTransaction with the CLI using Alice’s account. Replace the hash A6A374E66B32A3D5133018EFA9CD6E3169C8EEA339F7CCBE29C47D07086E068C with the AggregateTransaction hash signed in (4).

symbol-cli transaction cosign --hash A6A374E66B32A3D5133018EFA9CD6E3169C8EEA339F7CCBE29C47D07086E068C --profile alice
  1. Cosign the AggregateTransaction with Bob’s account.

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

8. If everything goes well, the account is now set as multisig, being Alice and Bob accounts their cosignatories. You can get the list of the multisig accounts where Alice or Bob are cosignatories with the function MultisigHttp.getMultisigAccountInfo().

// replace with multisig address
const rawAddress = 'TAEG6L-KWXRA7-PSWUEE-ILQPG4-3V5CYZ-S5652T-JTUU';
const address = Address.createFromRawAddress(rawAddress);

// replace with node endpoint
const nodeUrl = 'NODE_URL';
const repositoryFactory = new RepositoryFactoryHttp(nodeUrl);
const multisigHttp = repositoryFactory.createMultisigRepository();

multisigHttp.getMultisigAccountInfo(address).subscribe(
  (multisigInfo) => console.log(multisigInfo),
  (err) => console.error(err),
);
// replace with multisig address
const rawAddress = 'TAEG6L-KWXRA7-PSWUEE-ILQPG4-3V5CYZ-S5652T-JTUU';
const address = symbol_sdk_1.Address.createFromRawAddress(rawAddress);
// replace with node endpoint
const nodeUrl = 'NODE_URL';
const repositoryFactory = new symbol_sdk_1.RepositoryFactoryHttp(nodeUrl);
const multisigHttp = repositoryFactory.createMultisigRepository();
multisigHttp.getMultisigAccountInfo(address).subscribe(
  (multisigInfo) => console.log(multisigInfo),
  (err) => console.error(err),
);
        // replace with node endpoint
        try (final RepositoryFactory repositoryFactory = new RepositoryFactoryVertxImpl(
            "NODE_URL")) {

            final MultisigRepository multisigRepository = repositoryFactory
                .createMultisigRepository();

            // replace with multisig address
            final String rawAddress = "TAEG6L-KWXRA7-PSWUEE-ILQPG4-3V5CYZ-S5652T-JTUU";
            final Address address = Address.createFromRawAddress(rawAddress);

            final MultisigAccountInfo multisigAccountInfo = multisigRepository
                .getMultisigAccountInfo(address).toFuture().get();

            final JsonHelper helper = new JsonHelperJackson2();
            System.out.println(helper.prettyPrint(multisigAccountInfo));

Follow the next guide to modify the number of required signatures.