Creating an escrow with aggregate bonded transaction

Learn about aggregate bonded transactions 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, with the disbursement dependent on conditions agreed to by the transacting parties, or 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; or, a trust account held in the borrower’s name to pay obligations such as property taxes and insurance premiums.

See full description at Wikipedia.

In this example, imagine the two parties agree on a virtual service, implying that the escrow can be immediate.

How does it work?

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

How is it applied to NEM?

Normalizing the language into NEM related concepts:

contractual arrangement
Aggregate Transaction
third party receives and disburses money
No third party
primary transacting parties
Accounts
conditions agreed to by the transacting parties
Sign transaction
account established by a broker for holding funds
No account, it will happen atomically using an aggregate transaction
until the consummation or termination of a transaction
The transaction gets included in a block

Let’s get into some code

../../_images/aggregate-escrow-11.png

Multi-Asset Escrowed Transactions

Setting up the required accounts and mosaics

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

Owner Amount MosaicId Description
Alice 100 cat.currency Native currency mosaic
Ticket distributor 1 7cdf3b117a3c40cc Represents a museum ticket.

Before continuing, create the museum ticket mosaic.

Creating the escrow contract

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

  1. Create two transfer transactions:
  1. From Alice to the ticket distributor sending 100 cat.currency.
  2. From the ticket distributor to Alice sending 1 7cdf3b117a3c40cc (museum ticket).
const nodeUrl = 'http://localhost:3000';
const transactionHttp = new TransactionHttp(nodeUrl);
const listener = new Listener(nodeUrl);

const alicePrivateKey = process.env.PRIVATE_KEY as string;
const aliceAccount = Account.createFromPrivateKey(alicePrivateKey, NetworkType.MIJIN_TEST);

const ticketDistributorPublicKey = 'F82527075248B043994F1CAFD965F3848324C9ABFEC506BC05FBCF5DD7307C9D';
const ticketDistributorPublicAccount = PublicAccount.createFromPublicKey(ticketDistributorPublicKey, NetworkType.MIJIN_TEST);

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

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.MIJIN_TEST);
const nodeUrl = 'http://localhost:3000';
const transactionHttp = new TransactionHttp(nodeUrl);
const listener = new Listener(nodeUrl);

const alicePrivateKey = process.env.PRIVATE_KEY;
const aliceAccount = Account.createFromPrivateKey(alicePrivateKey, NetworkType.MIJIN_TEST);

const ticketDistributorPublicKey = 'F82527075248B043994F1CAFD965F3848324C9ABFEC506BC05FBCF5DD7307C9D';
const ticketDistributorPublicAccount = PublicAccount.createFromPublicKey(ticketDistributorPublicKey, NetworkType.MIJIN_TEST);

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

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.MIJIN_TEST);
  1. Wrap the defined transactions in an aggregate transaction.

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 case that signatures are required from other participants and the transaction is announced to the network, it is considered an aggregate bonded.

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

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

const signedTransaction = aliceAccount.sign(aggregateTransaction);
  1. When an aggregate transaction is bonded, Alice will need to lock at least 10 cat.currency. Once the ticket distributor signs the aggregate transaction, the amount of locked cat.currency becomes available again on Alice’s account, and the exchange will get through.
const lockFundsTransaction = LockFundsTransaction.create(
    Deadline.create(),
    new Mosaic(
        new MosaicId('0dc67fbe1cad29e3'), // Replace with your network currency mosaic id
        UInt64.fromUint(10000000)
    ),
    UInt64.fromUint(480),
    signedTransaction,
    NetworkType.MIJIN_TEST);

const lockFundsTransactionSigned = aliceAccount.sign(lockFundsTransaction);

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(),
    new Mosaic(
        new MosaicId('0dc67fbe1cad29e3'), // Replace with your network currency mosaic id
        UInt64.fromUint(10000000)
    ),
    UInt64.fromUint(480),
    signedTransaction,
    NetworkType.MIJIN_TEST);

const lockFundsTransactionSigned = aliceAccount.sign(lockFundsTransaction);

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));
});

Note

The listener implementation changes when used on the client side (e.g., Angular, React, Vue).

The distributor has not signed the aggregate bonded transaction yet, so exchange has not been completed. Check how to cosign the aggregate transaction in the following guide.

Is it possible without aggregate transactions?

It is not secure, since any event of the next list may happen:

  • The buyer does not pay.
  • The seller does not send the virtual goods.

What’s next?

Try to swap mosaics adding a third participant.

../../_images/aggregate-escrow-2.png

Multi-Asset Escrowed Transactions