Developer Center

Developer Center

  • Getting Started
  • Built-in Features
  • REST API Endpoints
  • Guides
  • Cheat Sheet

›Multisig Account

Getting Started

  • What is Sirius Chain
  • Setting up your workstation
  • Writing your first application

Built-in Features

  • Account
  • Mosaic (SDA)
  • Namespace
  • Transfer Transaction
  • Aggregate Transaction
  • Multisig Account
  • Metadata
  • Account Restriction
  • Cross-Chain Swaps
  • Exchange Market
  • Decentralized Exchange Market
  • Liquidity Provider
  • Storage

Protocol

  • Node
  • Block
  • Cryptography
  • Transaction
  • Validating
  • Consensus Algorithms
  • Receipt
  • Inflation

REST API

  • Overview
  • Tools
  • Serialization
  • Websockets
  • Status Errors

SDKs

  • Overview
  • Architecture
  • Languages
  • Extending Sirius Chain Capabilities
  • SDK Development
  • SDK Documentation

Wallets & Explorers

  • Wallets & Explorers

Cheat Sheet

  • Sirius Chain Cheat Sheet

Guides

  • Overview
  • External Guides
  • Account

    • Creating and opening an account
    • Getting account information
    • Getting the amount of XPX sent to an account
    • Reading transactions from an account

    Account Restriction

    • Preventing spam attacks with account restrictions

    Aggregate Transaction

    • Sending payouts with aggregate-complete transaction
    • Creating an escrow with aggregate bonded transaction
    • Asking for mosaics with aggregate-bonded transaction
    • Signing announced aggregate-bonded transactions

    Block

    • Listening to New Blocks
    • Getting block by height

    Cross Chain Swaps

    • Atomic cross-chain swap between Sirius public and private chains

    Metadata

    • Account Metadata
    • Mosaic Metadata
    • Namespace Metadata
    • Account Metadata (Deprecated since 0.7.0 Sirius Chain release)
    • Mosaic Metadata (Deprecated since 0.7.0 Sirius Chain release)
    • Namespace Metadata (Deprecated since 0.7.0 Sirius Chain release)

    Monitoring

    • Monitor transaction

    Mosaic

    • Creating a mosaic (SDA)
    • Getting the mosaic information
    • Getting the asset identifier behind a namespace with receipts

    Mosaic Levy

    • Modifying Mosaic Supply

    Multisig Account

    • Converting an account to multisig
    • Modifying a multisig account
    • Creating a multi-level multisig-account
    • Sending a multisig transaction

    Namespace

    • Registering a namespace
    • Registering a subnamespace
    • Getting the Namespace information
    • Linking a namespace to a mosaic
    • Linking namespace to account

    Transfer Transaction

    • Transfer transaction
    • Sending an encrypted message

    Storage

    • Data Modification Cancel
    • Data Modification
    • Download Channel
    • Download Payment
    • Drive Closure
    • Finish Download Channel
    • Prepare Bc Drive
    • Replicator Offboarding
    • Replicator Onboarding
    • Storage Payment
    • Verification Payment

Storage

  • Overview
  • Participate
  • External Economy
  • Roles
  • Verification
  • Challenge
  • Rewards
  • Transaction Schemas
  • Built-In Features

    • Drive
    • Replicator
    • Verifier
    • Supercontracts

    Protocols

    • Cross-Block Protocol
    • Fair Streaming

    Storage User Application

    • Overview
    • Getting Started
    • Managing Drives
    • Managing Drive Files
    • Downloading Data

Creating a multi-level multisig-account

Create a multi-level multisig account.

Following this guide, you will learn to create the following 3-level multisig account.

Multi-level multisig-account

Three-level multisig account example

Background Information

Multisig accounts can be cosignatories for other multisig accounts. Multi-level multisig accounts add “AND/OR” logic to multi-signature transactions.

Prerequisites

  • Text editor or IDE.
  • XPX-Chain-SDK or XPX-Chain-CLI.
  • Finish converting an account to multisig guide.

Getting into some code

  1. Define multisig account #2.
Golang
TypeScript
JavaScript
Java
conf, err := sdk.NewConfig(context.Background(), []string{"http://bctestnet1.brimstone.xpxsirius.io:3000"})
if err != nil {
panic(err)
}

// Use the default http client
client := sdk.NewClient(nil, conf)

multisig2, err := client.NewAccountFromPrivateKey(os.Getenv("MULTISIG_2_ACCOUNT_PRIVATE_KEY"))
if err != nil {
panic(err)
}

cosignatory5, err := client.NewAccountFromPublicKey(os.Getenv("COSIGNATORY_5_PUBLIC_KEY"))
if err != nil {
panic(err)
}

cosignatory6, err := client.NewAccountFromPublicKey(os.Getenv("COSIGNATORY_6_PUBLIC_KEY"))
if err != nil {
panic(err)
}

convertMultisigAccount2Transaction, err := client.NewModifyMultisigAccountTransaction(
sdk.NewDeadline(time.Hour),
1,
1,
[]*sdk.MultisigCosignatoryModification{
{sdk.Add, cosignatory5},
{sdk.Add, cosignatory6},
},
)
const multisig2PrivateKey = '<privateKey2>';
const multisigAccount2 = Account.createFromPrivateKey(multisig2PrivateKey, NetworkType.TEST_NET);

const cosignatoryAccount5PublicKey = '<publicKey5>';
const cosignatory5 = PublicAccount.createFromPublicKey(cosignatoryAccount5PublicKey, NetworkType.TEST_NET);

const cosignatoryAccount6PublicKey = '<publicKey6>';
const cosignatory6 = PublicAccount.createFromPublicKey(cosignatoryAccount6PublicKey, NetworkType.TEST_NET);

const convertMultisigAccount2Transaction = ModifyMultisigAccountTransaction.create(
Deadline.create(),
1,
1,
[
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.Add,
cosignatory5,
),
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.Add,
cosignatory6,
)],
NetworkType.TEST_NET);

const multisig2PrivateKey = '<privateKey2>';
const multisigAccount2 = Account.createFromPrivateKey(multisig2PrivateKey, NetworkType.TEST_NET);

const cosignatoryAccount5PublicKey = '<publicKey5>';
const cosignatory5 = PublicAccount.createFromPublicKey(cosignatoryAccount5PublicKey, NetworkType.TEST_NET);

const cosignatoryAccount6PublicKey = '<publicKey6>';
const cosignatory6 = PublicAccount.createFromPublicKey(cosignatoryAccount6PublicKey, NetworkType.TEST_NET);

const convertMultisigAccount2Transaction = ModifyMultisigAccountTransaction.create(
Deadline.create(),
1,
1,
[
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.Add,
cosignatory5,
),
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.Add,
cosignatory6,
)],
NetworkType.TEST_NET);

    // Create multisig #2 (1-of-2)

// Replace with the private key of the account that you want to convert into multisig
final String multisig2PrivateKey = "<privateKey2>";

// Replace with cosignatories public keys
final String cosignatory5PublicKey = "<publicKey5>";
final String cosignatory6PublicKey = "<publicKey6>";

final Account multisigAccount2 = Account.createFromPrivateKey(multisig2PrivateKey, NetworkType.TEST_NET);

final PublicAccount cosignatory5PublicAccount = PublicAccount.createFromPublicKey(cosignatory5PublicKey, NetworkType.TEST_NET);
final PublicAccount cosignatory6PublicAccount = PublicAccount.createFromPublicKey(cosignatory6PublicKey, NetworkType.TEST_NET);

final ModifyMultisigAccountTransaction convertMultisigAccount2Transaction = ModifyMultisigAccountTransaction.create(
Deadline.create(2, HOURS),
1,
1,
Arrays.asList(
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.ADD,
cosignatory5PublicAccount
),
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.ADD,
cosignatory6PublicAccount
)
),
NetworkType.TEST_NET
);
  1. Create multisig account #3.
Golang
TypeScript
JavaScript
Java
multisig3, err := client.NewAccountFromPrivateKey(os.Getenv("MULTISIG_3_ACCOUNT_PRIVATE_KEY"))
if err != nil {
panic(err)
}

cosignatory7, err := client.NewAccountFromPublicKey(os.Getenv("COSIGNATORY_7_PUBLIC_KEY"))
if err != nil {
panic(err)
}

cosignatory8, err := client.NewAccountFromPublicKey(os.Getenv("COSIGNATORY_8_PUBLIC_KEY"))
if err != nil {
panic(err)
}

cosignatory4, err := client.NewAccountFromPublicKey(os.Getenv("COSIGNATORY_4_PUBLIC_KEY"))
if err != nil {
panic(err)
}

convertMultisigAccount3Transaction, err := client.NewModifyMultisigAccountTransaction(
sdk.NewDeadline(time.Hour),
2,
1,
[]*sdk.MultisigCosignatoryModification{
{sdk.Add, cosignatory7},
{sdk.Add, cosignatory8},
{sdk.Add, cosignatory4},
},
)
const multisig3PrivateKey = "<privateKey3>";
const multisigAccount3 = Account.createFromPrivateKey(multisig3PrivateKey, NetworkType.TEST_NET);

const cosignatoryAccount7PublicKey = "<publicKey7>";
const cosignatory7 = PublicAccount.createFromPublicKey(cosignatoryAccount7PublicKey, NetworkType.TEST_NET);

const cosignatoryAccount8PublicKey = "<publicKey8>";
const cosignatory8 = PublicAccount.createFromPublicKey(cosignatoryAccount8PublicKey, NetworkType.TEST_NET);

const cosignatoryAccount4PublicKey = "<publicKey4>";
const cosignatory4 = PublicAccount.createFromPublicKey(cosignatoryAccount4PublicKey, NetworkType.TEST_NET);

const convertMultisigAccount3Transaction = ModifyMultisigAccountTransaction.create(
Deadline.create(),
2,
1,
[
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.Add,
cosignatory7,
),
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.Add,
cosignatory8,
),
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.Add,
cosignatory4,
)],
NetworkType.TEST_NET);

const multisig3PrivateKey = "<privateKey3>";
const multisigAccount3 = Account.createFromPrivateKey(multisig3PrivateKey, NetworkType.TEST_NET);

const cosignatoryAccount7PublicKey = "<publicKey7>";
const cosignatory7 = PublicAccount.createFromPublicKey(cosignatoryAccount7PublicKey, NetworkType.TEST_NET);

const cosignatoryAccount8PublicKey = "<publicKey8>";
const cosignatory8 = PublicAccount.createFromPublicKey(cosignatoryAccount8PublicKey, NetworkType.TEST_NET);

const cosignatoryAccount4PublicKey = "<publicKey4>";
const cosignatory4 = PublicAccount.createFromPublicKey(cosignatoryAccount4PublicKey, NetworkType.TEST_NET);

const convertMultisigAccount3Transaction = ModifyMultisigAccountTransaction.create(
Deadline.create(),
2,
1,
[
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.Add,
cosignatory7,
),
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.Add,
cosignatory8,
),
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.Add,
cosignatory4,
)],
NetworkType.TEST_NET);

    // Replace with the private key of the account that you want to convert into multisig
final String multisig3PrivateKey = "<privateKey3>";

// Replace with cosignatories public keys
final String cosignatory7PublicKey = "<publicKey7>";
final String cosignatory8PublicKey = "<publicKey8>";
final String cosignatory4PublicKey = "<publicKey4>";

final Account multisigAccount3 = Account.createFromPrivateKey(multisig3PrivateKey, NetworkType.TEST_NET);

final PublicAccount cosignatory7PublicAccount = PublicAccount.createFromPublicKey(cosignatory7PublicKey, NetworkType.TEST_NET);
final PublicAccount cosignatory8PublicAccount = PublicAccount.createFromPublicKey(cosignatory8PublicKey, NetworkType.TEST_NET);
final PublicAccount cosignatory4PublicAccount = PublicAccount.createFromPublicKey(cosignatory4PublicKey, NetworkType.TEST_NET);

final ModifyMultisigAccountTransaction convertMultisigAccount3Transaction = ModifyMultisigAccountTransaction.create(
Deadline.create(2, HOURS),
2,
1,
Arrays.asList(
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.ADD,
cosignatory7PublicAccount
),
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.ADD,
cosignatory8PublicAccount
),
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.ADD,
cosignatory4PublicAccount
)
),
NetworkType.TEST_NET
);

  1. Create multisig account #1.
Golang
TypeScript
JavaScript
Java
multisig1, err := client.NewAccountFromPrivateKey(os.Getenv("MULTISIG_1_ACCOUNT_PRIVATE_KEY"))
if err != nil {
panic(err)
}

convertMultisigAccount1Transaction, err := client.NewModifyMultisigAccountTransaction(
sdk.NewDeadline(time.Hour),
3,
1,
[]*sdk.MultisigCosignatoryModification{
{sdk.Add, multisig2.PublicAccount},
{sdk.Add, multisig3.PublicAccount},
{sdk.Add, cosignatory4},
},
)
const multisig1PrivateKey = "<privateKey1>";
const multisigAccount1 = Account.createFromPrivateKey(multisig1PrivateKey, NetworkType.TEST_NET);

const convertMultisigAccount1Transaction = ModifyMultisigAccountTransaction.create(
Deadline.create(),
3,
1,
[
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.Add,
multisigAccount2.publicAccount,
),
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.Add,
multisigAccount3.publicAccount,
),
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.Add,
cosignatory4,
)],
NetworkType.TEST_NET);

const multisig1PrivateKey = "<privateKey1>";
const multisigAccount1 = Account.createFromPrivateKey(multisig1PrivateKey, NetworkType.TEST_NET);

const convertMultisigAccount1Transaction = ModifyMultisigAccountTransaction.create(
Deadline.create(),
3,
1,
[
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.Add,
multisigAccount2.publicAccount,
),
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.Add,
multisigAccount3.publicAccount,
),
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.Add,
cosignatory4,
)],
NetworkType.TEST_NET);

    // Replace with the private key of the account that you want to convert into multisig
final String multisig1PrivateKey = "<privateKey1>";

final Account multisigAccount1 = Account.createFromPrivateKey(multisig1PrivateKey, NetworkType.TEST_NET);

final ModifyMultisigAccountTransaction convertMultisigAccount1Transaction = ModifyMultisigAccountTransaction.create(
Deadline.create(2, HOURS),
3,
1,
Arrays.asList(
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.ADD,
multisigAccount2.getPublicAccount()
),
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.ADD,
multisigAccount3.getPublicAccount()
),
new MultisigCosignatoryModification(
MultisigCosignatoryModificationType.ADD,
cosignatory4PublicAccount
)
),
NetworkType.TEST_NET
);

  1. Announce the transactions together using an aggregate bonded transaction. Make sure that the account #1 owns at least 10 xpx.
Golang
TypeScript
JavaScript
Java
convertMultisigAccount1Transaction.ToAggregate(multisig1.PublicAccount)
convertMultisigAccount2Transaction.ToAggregate(multisig2.PublicAccount)
convertMultisigAccount3Transaction.ToAggregate(multisig3.PublicAccount)
aggregateTransaction, err := client.NewCompleteAggregateTransaction(
sdk.NewDeadline(time.Hour),
[]sdk.Transaction{convertMultisigAccount1Transaction, convertMultisigAccount2Transaction, convertMultisigAccount3Transaction},
)
if err != nil {
panic(err)
}

signedAggregateTransaction, err := multisig1.Sign(aggregateTransaction)
if err != nil {
panic(err)
}

lockFundsTransaction, err := client.NewLockFundsTransaction(
sdk.NewDeadline(time.Hour),
sdk.XpxRelative(10),
sdk.Duration(1000),
signedAggregateTransaction,
)
if err != nil {
panic(err)
}

signedLockFundsTransaction, err := multisig1.Sign(lockFundsTransaction)
if err != nil {
panic(err)
}

_, err = client.Transaction.Announce(context.Background(), signedLockFundsTransaction)
if err != nil {
panic(err)
}

const aggregateTransaction = AggregateTransaction.createBonded(
Deadline.create(),
[
convertMultisigAccount1Transaction.toAggregate(multisigAccount1.publicAccount),
convertMultisigAccount2Transaction.toAggregate(multisigAccount2.publicAccount),
convertMultisigAccount3Transaction.toAggregate(multisigAccount3.publicAccount)
],
NetworkType.TEST_NET);

const signedTransaction = multisigAccount1.sign(aggregateTransaction, generationHash);

const lockFundsTransaction = LockFundsTransaction.create(
Deadline.create(),
NetworkCurrencyMosaic.createRelative(10),
UInt64.fromUint(1000),
signedTransaction,
NetworkType.TEST_NET);

const lockFundsTransactionSigned = multisigAccount1.sign(lockFundsTransaction, generationHash);

const transactionHttp = new TransactionHttp('http://bctestnet1.brimstone.xpxsirius.io:3000');

listener.open().then(() => {

transactionHttp
.announce(lockFundsTransactionSigned)
.subscribe(x => console.log(x), err => console.error(err));

listener
.confirmed(multisigAccount1.address)
.pipe(
filter((transaction) => transaction.transactionInfo !== undefined
&& transaction.transactionInfo.hash === lockFundsTransactionSigned.hash),
mergeMap(ignored => transactionHttp.announceAggregateBonded(signedTransaction))
)
.subscribe(announcedAggregateBonded => console.log(announcedAggregateBonded),
err => console.error(err));
});

const aggregateTransaction = AggregateTransaction.createBonded(
Deadline.create(),
[
convertMultisigAccount1Transaction.toAggregate(multisigAccount1.publicAccount),
convertMultisigAccount2Transaction.toAggregate(multisigAccount2.publicAccount),
convertMultisigAccount3Transaction.toAggregate(multisigAccount3.publicAccount)
],
NetworkType.TEST_NET);

const signedTransaction = multisigAccount1.sign(aggregateTransaction, generationHash);

const lockFundsTransaction = LockFundsTransaction.create(
Deadline.create(),
NetworkCurrencyMosaic.createRelative(10),
UInt64.fromUint(1000),
signedTransaction,
NetworkType.TEST_NET);

const lockFundsTransactionSigned = multisigAccount1.sign(lockFundsTransaction, generationHash);

const transactionHttp = new TransactionHttp('http://bctestnet1.brimstone.xpxsirius.io:3000');

listener.open().then(() => {

transactionHttp
.announce(lockFundsTransactionSigned)
.subscribe(x => console.log(x), err => console.error(err));

listener
.confirmed(multisigAccount1.address)
.pipe(
filter((transaction) => transaction.transactionInfo !== undefined
&& transaction.transactionInfo.hash === lockFundsTransactionSigned.hash),
mergeMap(ignored => transactionHttp.announceAggregateBonded(signedTransaction))
)
.subscribe(announcedAggregateBonded => console.log(announcedAggregateBonded),
err => console.error(err));
});

final AggregateTransaction aggregateTransaction = new TransactionBuilderFactory().aggregateBonded()
.innerTransactions(Arrays.asList(
convertMultisigAccount1Transaction.toAggregate(multisigAccount1.getPublicAccount()),
convertMultisigAccount2Transaction.toAggregate(multisigAccount2.getPublicAccount()),
convertMultisigAccount3Transaction.toAggregate(multisigAccount3.getPublicAccount())
)).deadline(new Deadline(2, ChronoUnit.HOURS)).networkType(NetworkType.TEST_NET);

final SignedTransaction aggregateSignedTransaction = multisigAccount1.sign(aggregateTransaction, generationHash);

// Creating the lock funds transaction and announce it
final LockFundsTransaction lockFundsTransaction = LockFundsTransaction.create(
Deadline.create(2, HOURS),
NetworkCurrencyMosaic.createRelative(BigInteger.valueOf(10)),
BigInteger.valueOf(1000),
pullTransactionSigned,
NetworkType.TEST_NET
);

final SignedTransaction lockFundsTransactionSigned = multisigAccount1.sign(lockFundsTransaction, generationHash);

final TransactionHttp transactionHttp = new TransactionHttp("http://bctestnet1.brimstone.xpxsirius.io:3000");

transactionHttp.announce(lockFundsTransactionSigned).toFuture().get();

System.out.println(lockFundsTransactionSigned.getHash());

final Listener listener = new Listener("http://bctestnet1.brimstone.xpxsirius.io:3000");

listener.open().get();

final Transaction transaction = listener.confirmed(multisigAccount1.getAddress()).take(1).toFuture().get();

transactionHttp.announceAggregateBonded(aggregateSignedTransaction).toFuture().get();
  1. The cosignatories must opt-in to become cosignatories. Cosign the announced aggregate transaction with the accounts #5, #6, #7, #8, and #4.
CLI
xpx2-cli transaction cosign --hash A6A374E66B32A3D5133018EFA9CD6E3169C8EEA339F7CCBE29C47D07086E068C --profile <account>

Note:

If the account #5 initiates an aggregate bonded transaction involving the account #1, which accounts should cosign the transaction?

Multi-level multisig-account complex

Sending an aggregate bonded transaction from a MLMA

← Modifying a multisig accountSending a multisig transaction →
  • Background Information
  • Prerequisites
  • Getting into some code
  • Follow our profile
  • Ask development questions
  • Join our Discord channel
  • Explore our Youtube channel
  • Explore Github
Protocol
BlockConsensus AlgorithmsCryptographyInflationNodeReceiptTransactionValidating
Built-in Features
AccountAggregate TransactionCross-Chain SwapsExchange MarketDecentralized Exchange MarketMetadataMosaicMultisig AccountNamespaceTransfer TransactionStorageLiquidity Provider
References
REST APISDKsCheat Sheet
Includes Documentation Forked from NEM
Copyright © 2025 Sirius Chain