Developer Center

Developer Center

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

›Aggregate Transaction

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 an escrow with aggregate bonded transaction

Learn about aggregate bonded transactions, by creating an escrow contract.

Background

An escrow is a contractual arrangement in which a third party receives and disburses money or documents for the primary transacting parties. Transacting parties will agree to the conditions in which the disbursement depends on. For example, it can be an account established by a broker for holding funds on behalf of the broker’s principal or some other person until the consummation or termination of a transaction. An escrow can also be a trust account held in the borrower’s name to pay obligations such as property taxes and insurance premiums.

Reference:

  • Wikipedia Contributors. "Escrow." Wikipedia, Wikipedia Foundation, 25 June 2019. en.wikipedia.org/wiki/Escrow.

For this example, imagine the two parties agree on a virtual service, implying that the escrow can be executed immediately:

  1. Buyer and seller agree on terms.
  2. Buyer submits payment to escrow.
  3. Seller delivers goods or services to buyer.
  4. Buyer approves goods or services.
  5. Escrow releases payment to the seller.

How is it applied to Sirius Chain?

Normalizing the language into Sirius Chain related concepts:

  • Contractual arrangement: A new type of transaction called Aggregate Transaction.
  • Third party receives and disburses money: There is no third party; we are going to use blockchain technology.
  • Primary transacting parties: Sirius Chain accounts will represent the participants.
  • Conditions agreed to by the transacting parties: When every participant signs the aggregate transaction.
  • Account established by a broker for holding funds: There will not be an intermediate account, the exchange will happen atomically using an aggregate transaction.
  • Until the consummation or termination of a transaction: The transaction gets included in a block or expires.

Prerequisites

  • XPX-Chain-SDK.
  • A text editor or IDE.
  • Finish creating a mosaic guide.
  • Finish sending payouts with aggregate complete transactions.
  • An account with XPX.

Getting into some code

Aggregate Escrow

Multi-Asset Escrowed Transactions

Setting up the required accounts and mosaics

Alice and a ticket distributor want to swap the following mosaics.

OwnerAmountMosaicIdDescription
Alice100xpxNative currency mosaic
Ticket distributor17cdf3b117a3c40ccRepresents a museum ticket.

Before continuing, create the two accounts loaded with xpx.

Then, create a mosaic with the ticket distributor account. This new mosaic will represent the ticket.

Creating the escrow contract

Alice will send a transaction to the ticket distributor exchanging 100 xpx for 1 7cdf3b117a3c40cc (museum ticket).

  1. Create two transfer transactions:

A. From Alice to the ticket distributor sending 100 XPX. B. From the ticket distributor to Alice sending 1 7cdf3b117a3c40cc.

**Note**

The museum ticket does not have the id 7cdf3b117a3c40cc in your network. Replace the MosaicId for the one you have created in the previous step.

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)

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

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

aliceTransferTransaction, err := client.NewTransferTransaction(sdk.NewDeadline(time.Hour), ticketDistributorPublicAccount.Address, []*sdk.Mosaic{sdk.XpxRelative(100)}, sdk.NewPlainMessage("send 100 xpx to distributor"))
if err != nil {
panic(err)
}

id, err := strconv.ParseInt("7cdf3b117a3c40cc", 16, 64)
if err != nil {
panic(err)
}
mosaicId, err := sdk.NewMosaicId(uint64(id))
if err != nil {
panic(err)
}
mosaic, err := sdk.NewMosaic(mosaicId, 1)
if err != nil {
panic(err)
}

ticketDistributorTransferTransaction, err := client.NewTransferTransaction(sdk.NewDeadline(time.Hour), aliceAccount.PublicAccount.Address, []*sdk.Mosaic{mosaic}, sdk.NewPlainMessage("send 1 museum ticket to alice"))
if err != nil {
panic(err)
}
const nodeUrl = 'http://bctestnet1.brimstone.xpxsirius.io:3000';
const transactionHttp = new TransactionHttp(nodeUrl);
const listener = new Listener(nodeUrl);

const alicePrivateKey = '<privateKey>';
const aliceAccount = Account.createFromPrivateKey(alicePrivateKey, NetworkType.TEST_NET);

const ticketDistributorPublicKey = '<publicKey>';
const ticketDistributorPublicAccount = PublicAccount.createFromPublicKey(ticketDistributorPublicKey, NetworkType.TEST_NET);

const aliceToTicketDistributorTx = TransferTransaction.create(
Deadline.create(),
ticketDistributorPublicAccount.address,
[NetworkCurrencyMosaic.createRelative(100)],
PlainMessage.create('send 100 xpx to distributor'),
NetworkType.TEST_NET);

const ticketDistributorToAliceTx = TransferTransaction.create(
Deadline.create(),
aliceAccount.address,
[new Mosaic(new MosaicId('7cdf3b117a3c40cc'), UInt64.fromUint(1))],
PlainMessage.create('send 1 museum ticket to alice'),
NetworkType.TEST_NET);
const nodeUrl = 'http://bctestnet1.brimstone.xpxsirius.io:3000';
const transactionHttp = new TransactionHttp(nodeUrl);
const listener = new Listener(nodeUrl);

const alicePrivateKey = '<privateKey>';
const aliceAccount = Account.createFromPrivateKey(alicePrivateKey, NetworkType.TEST_NET);

const ticketDistributorPublicKey = '<publicKey>';
const ticketDistributorPublicAccount = PublicAccount.createFromPublicKey( ticketDistributorPublicKey, NetworkType.TEST_NET);

const aliceToTicketDistributorTx = TransferTransaction.create(
Deadline.create(),
ticketDistributorPublicAccount.address,
[NetworkCurrencyMosaic.createRelative(100)],
PlainMessage.create('send 100 xpx to distributor'),
NetworkType.TEST_NET);

const ticketDistributorToAliceTx = TransferTransaction.create(
Deadline.create(),
aliceAccount.address,
[new Mosaic( new MosaicId('7cdf3b117a3c40cc'), UInt64.fromUint(1))],
PlainMessage.create('send 1 museum ticket to alice'),
NetworkType.TEST_NET);
    // Replace with private key
final String alicePrivateKey = "<privateKey>";

// Replace with public key
final String ticketDistributorPublicKey = "<publicKey>";

final Account aliceAccount = Account.createFromPrivateKey(alicePrivateKey, NetworkType.TEST_NET);
final PublicAccount ticketDistributorPublicAccount = PublicAccount.createFromPublicKey(ticketDistributorPublicKey, NetworkType.TEST_NET);

final TransferTransaction aliceToTicketDistributorTx = TransferTransaction.create(
Deadline.create(2, HOURS),
ticketDistributorPublicAccount.getAddress(),
Collections.singletonList(NetworkCurrencyMosaic.createRelative(BigInteger.valueOf(100))),
PlainMessage.create("send 100 xpx to distributor"),
NetworkType.TEST_NET
);

final TransferTransaction ticketDistributorToAliceTx = TransferTransaction.create(
Deadline.create(2, HOURS),
aliceAccount.getAddress(),
Collections.singletonList(new Mosaic(new MosaicId("7cdf3b117a3c40cc"), BigInteger.valueOf(1))),
PlainMessage.create("send 1 museum ticket to alice"),
NetworkType.TEST_NET
);

  1. Wrap the defined transactions in an aggregate transaction and sign it.

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

In the case that signatures are required from other participants and the transaction is announced to the network, it is considered an aggregate bonded.

Golang
TypeScript
JavaScript
Java
aliceTransferTransaction.ToAggregate(aliceAccount.PublicAccount)
ticketDistributorTransferTransaction.ToAggregate(ticketDistributorPublicAccount)

aggregateTransaction, err := client.NewBondedAggregateTransaction(sdk.NewDeadline(time.Hour), []sdk.Transaction{aliceTransferTransaction, ticketDistributorTransferTransaction})
if err != nil {
panic(err)
}

signedAggregateBoundedTransaction, err := aliceAccount.Sign(aggregateTransaction)
if err != nil {
panic(err)
}
const aggregateTransaction = AggregateTransaction.createBonded(Deadline.create(),
[aliceToTicketDistributorTx.toAggregate(aliceAccount.publicAccount),
ticketDistributorToAliceTx.toAggregate(ticketDistributorPublicAccount)],
NetworkType.TEST_NET);

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

const aggregateTransaction = AggregateTransaction.createBonded(Deadline.create(),
[aliceToTicketDistributorTx.toAggregate(aliceAccount.publicAccount),
ticketDistributorToAliceTx.toAggregate(ticketDistributorPublicAccount)],
NetworkType.TEST_NET);

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


final AggregateTransaction aggregateTransaction = new TransactionBuilderFactory().aggregateBonded()
.innerTransactions(Arrays.asList(
aliceToTicketDistributorTx.toAggregate(aliceAccount.getPublicAccount()),
ticketDistributorToAliceTx.toAggregate(ticketDistributorPublicAccount)
)).deadline(new Deadline(2, ChronoUnit.HOURS)).networkType(NetworkType.TEST_NET);

final SignedTransaction aggregateSignedTransaction = aliceAccount.sign(aggregateTransaction, generationHash);
  1. When an aggregate transaction is bonded, Alice will need to lock at least 10 xpx. Once the ticket distributor signs the aggregate transaction, the amount of locked xpx becomes available again on Alice’s account, and the exchange will get through.
Golang
TypeScript
JavaScript
Java
lockFundsTransaction, err := client.NewLockFundsTransaction(
sdk.NewDeadline(time.Hour),
sdk.XpxRelative(10),
sdk.Duration(1000),
signedAggregateBoundedTransaction,
)
if err != nil {
panic(err)
}

signedLockFundsTransaction, err := aliceAccount.Sign(lockFundsTransaction)
if err != nil {
panic(err)
}
// Announce transaction
_, err = client.Transaction.Announce(context.Background(), signedLockFundsTransaction)
if err != nil {
panic(err)
}
const lockFundsTransaction = LockFundsTransaction.create(
Deadline.create(),
NetworkCurrencyMosaic.createRelative(10),
UInt64.fromUint(1000),
signedTransaction,
NetworkType.TEST_NET);

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

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

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

listener
.confirmed(aliceAccount.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 lockFundsTransaction = LockFundsTransaction.create(
Deadline.create(),
NetworkCurrencyMosaic.createRelative(10),
UInt64.fromUint(480),
signedTransaction,
NetworkType.TEST_NET);

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

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

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

listener
.confirmed(aliceAccount.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));
});
 // Creating the lock funds transaction and announce it

final LockFundsTransaction lockFundsTransaction = LockFundsTransaction.create(
Deadline.create(2, HOURS),
NetworkCurrencyMosaic.createRelative(BigInteger.valueOf(10)),
BigInteger.valueOf(480),
aggregateSignedTransaction,
NetworkType.TEST_NET
);

final SignedTransaction lockFundsTransactionSigned = aliceAccount.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(aliceAccount.getAddress()).take(1).toFuture().get();

transactionHttp.announceAggregateBonded(aggregateSignedTransaction).toFuture().get();

The distributor has not signed the aggregate bonded transaction yet, so the exchange has not been completed.

Copy the aggregate transaction hash, and check how to cosign the aggregate transaction in the following guide.

Is it possible without aggregate transactions?

It is not secure, since:

  • Alice could decide not to pay the distributor after receiving the ticket.
  • The distributor could choose not to send the ticket after receiving the payment.

Using the aggregate transaction feature, we ensure that multiple transactions are executed at the same time when all the participants agree. The seller does not send the virtual goods.

What’s next?

Afterwards, try to swap mosaics between multiple participants.

Aggregate Escrow

Multi-Asset Escrowed Transactions

← Sending payouts with aggregate-complete transactionAsking for mosaics with aggregate-bonded transaction →
  • Background
  • Prerequisites
  • Getting into some code
    • Setting up the required accounts and mosaics
    • Creating the escrow contract
  • Is it possible without aggregate transactions?
  • What’s next?
  • 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