Learn about aggregate bonded transactions creating an escrow contract.
An escrow is a contractual arrangement in which a third party receives and disburses money or documents for the primary transacting parties. This disbursement is dependent on the conditions agreed 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. See the full description at Wikipedia.
For this example, imagine that the two parties agree on a virtual service, implying that the escrow can be executed immediately:
Normalizing the previous description into Symbol related concepts:
symbol.xym
units.Alice and a ticket distributor want to swap the following mosaics.
Owner | Amount | MosaicId | Description |
---|---|---|---|
Alice | 100 | symbol.xym |
Native currency mosaic |
Ticket distributor | 1 | 7cdf3b117a3c40cc |
Represents a museum ticket. |
Before continuing, create the two accounts loaded with symbol.xym
.
You should also create a mosaic with the ticket distributor’s account.
This new mosaic will represent the ticket.
symbol.xym
.7cdf3b117a3c40cc
(museum ticket).Note
The museum ticket does not have the id 7cdf3b117a3c40cc
in your network. Replace the mosaic identifier for the one you have created in the previous step.
// replace with network type
const networkType = NetworkType.TEST_NET;
// replace with alice private key
const alicePrivateKey = '1111111111111111111111111111111111111111111111111111111111111111';
const aliceAccount = Account.createFromPrivateKey(alicePrivateKey, networkType);
// replace with ticket distributor public key
const ticketDistributorPublicKey = '20330294DC18D96BDEEF32FB02338A6462A0469CB451A081DE2F05B4302C0C0A';
const ticketDistributorPublicAccount = PublicAccount.createFromPublicKey(ticketDistributorPublicKey, networkType);
// replace with ticket mosaic id
const ticketMosaicId = new MosaicId('7cdf3b117a3c40cc');
// replace with ticket mosaic id divisibility
const ticketDivisibility = 0;
// replace with symbol.xym id
const networkCurrencyMosaicId = new MosaicId('5E62990DCAC5BE8A');
// replace with network currency divisibility
const networkCurrencyDivisibility = 6;
const aliceToTicketDistributorTx = TransferTransaction.create(
Deadline.create(),
ticketDistributorPublicAccount.address,
[new Mosaic (networkCurrencyMosaicId,
UInt64.fromUint(100 * Math.pow(10, networkCurrencyDivisibility)))],
PlainMessage.create('send 100 symbol.xym to distributor'),
networkType);
const ticketDistributorToAliceTx = TransferTransaction.create(
Deadline.create(),
aliceAccount.address,
[new Mosaic(ticketMosaicId,
UInt64.fromUint(1 * Math.pow(10, ticketDivisibility)))],
PlainMessage.create('send 1 museum ticket to customer'),
networkType);
// replace with network type
const networkType = symbol_sdk_1.NetworkType.TEST_NET;
// replace with alice private key
const alicePrivateKey = '1111111111111111111111111111111111111111111111111111111111111111';
const aliceAccount = symbol_sdk_1.Account.createFromPrivateKey(alicePrivateKey, networkType);
// replace with ticket distributor public key
const ticketDistributorPublicKey = '20330294DC18D96BDEEF32FB02338A6462A0469CB451A081DE2F05B4302C0C0A';
const ticketDistributorPublicAccount = symbol_sdk_1.PublicAccount.createFromPublicKey(ticketDistributorPublicKey, networkType);
// replace with ticket mosaic id
const ticketMosaicId = new symbol_sdk_1.MosaicId('7cdf3b117a3c40cc');
// replace with ticket mosaic id divisibility
const ticketDivisibility = 0;
// replace with symbol.xym id
const networkCurrencyMosaicId = new symbol_sdk_1.MosaicId('5E62990DCAC5BE8A');
// replace with network currency divisibility
const networkCurrencyDivisibility = 6;
const aliceToTicketDistributorTx = symbol_sdk_1.TransferTransaction.create(symbol_sdk_1.Deadline.create(), ticketDistributorPublicAccount.address, [new symbol_sdk_1.Mosaic(networkCurrencyMosaicId, symbol_sdk_1.UInt64.fromUint(100 * Math.pow(10, networkCurrencyDivisibility)))], symbol_sdk_1.PlainMessage.create('send 100 symbol.xym to distributor'), networkType);
const ticketDistributorToAliceTx = symbol_sdk_1.TransferTransaction.create(symbol_sdk_1.Deadline.create(), aliceAccount.address, [new symbol_sdk_1.Mosaic(ticketMosaicId, symbol_sdk_1.UInt64.fromUint(1 * Math.pow(10, ticketDivisibility)))], symbol_sdk_1.PlainMessage.create('send 1 museum ticket to customer'), networkType);
2. Wrap the defined transactions in an AggregateTransaction and sign it with Alice’s account. An AggregateTransaction 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—the ticket distributor—it is considered bonded.
const aggregateTransaction = AggregateTransaction.createBonded(Deadline.create(),
[aliceToTicketDistributorTx.toAggregate(aliceAccount.publicAccount),
ticketDistributorToAliceTx.toAggregate(ticketDistributorPublicAccount)],
networkType,
[],
UInt64.fromUint(2000000));
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash = '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = aliceAccount.sign(aggregateTransaction, networkGenerationHash);
console.log('Aggregate Transaction Hash:', signedTransaction.hash);
const aggregateTransaction = symbol_sdk_1.AggregateTransaction.createBonded(symbol_sdk_1.Deadline.create(), [aliceToTicketDistributorTx.toAggregate(aliceAccount.publicAccount),
ticketDistributorToAliceTx.toAggregate(ticketDistributorPublicAccount)], networkType, [], symbol_sdk_1.UInt64.fromUint(2000000));
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash = '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = aliceAccount.sign(aggregateTransaction, networkGenerationHash);
console.log('Aggregate Transaction Hash:', signedTransaction.hash);
3. When an AggregateTransaction is bonded, Alice will need to lock 10
symbol.xym
to prevent spamming the network.
Once the ticket distributor signs the AggregateTransaction, the amount of locked symbol.xym
becomes available again on Alice’s account, and the exchange will get through.
const hashLockTransaction = HashLockTransaction.create(
Deadline.create(),
new Mosaic (networkCurrencyMosaicId,
UInt64.fromUint(10 * Math.pow(10, networkCurrencyDivisibility))),
UInt64.fromUint(480),
signedTransaction,
networkType,
UInt64.fromUint(2000000));
const signedHashLockTransaction = aliceAccount.sign(hashLockTransaction, networkGenerationHash);
// replace with node endpoint
const nodeUrl = 'http://api-01.us-east-1.096x.symboldev.network:3000';
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());
});
const hashLockTransaction = symbol_sdk_1.HashLockTransaction.create(symbol_sdk_1.Deadline.create(), 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 = aliceAccount.sign(hashLockTransaction, networkGenerationHash);
// replace with node endpoint
const nodeUrl = 'http://api-01.us-east-1.096x.symboldev.network:3000';
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());
});
The distributor has not signed the AggregateBondedTransaction yet, so the exchange has not been completed.
It is not secure, since:
Using the AggregateTransaction feature, we ensure that multiple transactions are executed at the same time when all the participants agree.
Did you find what you were looking for? Give us your feedback.