Trading Transactions
Trading transactions on the ArcBlock blockchain facilitate the exchange of assets and tokens between different parties. This section focuses on the ExchangeV2Tx
for comprehensive asset and token swaps, and TransferV2Tx
/TransferV3Tx
for various transfer mechanisms, including those supporting multiple inputs and outputs, and multi-party signatures. Understanding these transaction types is crucial for building applications that involve economic interactions on the chain.
For a general overview of transaction concepts, refer to Transaction Types and Usage.
Exchange Transactions (ExchangeV2Tx)#
The ExchangeV2Tx
transaction type enables a flexible exchange of primary tokens, secondary tokens, and NFTs between two parties. It is designed to support multi-party signatures, ensuring that both the offerer and the demander explicitly agree to the terms of the exchange.
Structure of ExchangeV2Tx#
The ExchangeV2Tx
is structured to detail both the assets offered by the sender and the assets demanded by the receiver. The core components are defined as follows:
Field | Type | Description |
---|---|---|
|
| The address of the receiver in the exchange. |
|
| Details of the assets and tokens offered by the sender. |
|
| Details of the assets and tokens demanded by the receiver. |
|
| Optional. The timestamp when the exchange offer expires. |
|
| Optional. Arbitrary data attached to the transaction (e.g., a memo). |
TExchangeInfoV2
further specifies the types and amounts of assets and tokens involved:
Field | Type | Description |
---|---|---|
|
| The amount of primary token (e.g., ABT) involved, in its smallest unit. |
|
| A list of NFT asset addresses involved in this side of the exchange. |
|
| A list of secondary tokens involved, each with an address and value. |
TTokenInput
defines a specific secondary token amount:
Field | Type | Description |
---|---|---|
|
| The address of the secondary token. |
|
| The amount of the secondary token in its smallest unit. |
Multi-Party Exchange Process#
Exchange transactions typically involve a two-step signing process to ensure both parties agree to the exchange terms. The @arcblock/graphql-client
simplifies this with prepareExchange
and finalizeExchange
methods.
Usage Example#
Here’s how to perform an exchange using the GraphQLClient
:
const { fromRandom } = require('@ocap/wallet');
const GraphQLClient = require('@arcblock/graphql-client');
const endpoint = process.env.OCAP_API_HOST || 'http://127.0.0.1:4000'; // testnet
const client = new GraphQLClient(`${endpoint}/api`);
const sleep = (timeout) => new Promise((resolve) => setTimeout(resolve, timeout));
(async () => {
try {
const sender = fromRandom();
const receiver = fromRandom();
console.log({ sender: sender.address, receiver: receiver.address });
// 1. Declare sender and receiver accounts
await client.declare({ moniker: 'sender', wallet: sender });
await client.declare({ moniker: 'receiver', wallet: receiver });
await sleep(3000);
// 2. Receiver check-in to ensure they have some primary tokens for gas if needed
await client.checkin({ wallet: receiver });
await sleep(3000);
// 3. Create an asset for the sender to offer
let [hash, assetAddress] = await client.createAsset({
moniker: 'asset-to-exchange',
data: { typeUrl: 'json', value: { key: 'value', sn: Math.random() } },
wallet: sender,
});
console.log('Created asset:', assetAddress, 'Tx Hash:', hash);
await sleep(3000);
// 4.1 Sender prepares and signs the exchange transaction (tx1)
const tx1 = await client.prepareExchange({
receiver: receiver.address,
offerAssets: [assetAddress],
demandToken: 5, // Receiver demands 5 primary tokens
wallet: sender,
});
console.log('Sender prepared tx1:', JSON.stringify(tx1, null, 2));
// 4.2 Receiver finalizes and signs the transaction (tx2)
// In a real application, tx1 would be sent off-chain to the receiver.
const tx2 = await client.finalizeExchange({
tx: tx1,
wallet: receiver,
});
console.log('Receiver finalized tx2:', JSON.stringify(tx2, null, 2));
// 4.3 Sender sends the finalized exchange transaction to the chain
// In a real application, tx2 would be sent back off-chain to the sender.
hash = await client.exchange({
tx: tx2,
wallet: sender,
});
console.log('Exchange transaction hash:', hash);
console.log('View exchange tx:', `${endpoint}/explorer/txs/${hash}`);
} catch (err) {
console.error('Error during exchange:', err);
}
})();
This example illustrates a scenario where the sender
offers an NFT asset and demands 5 primary tokens, and the receiver
agrees to provide 5 primary tokens in exchange for the NFT. Both parties must sign the transaction for it to be valid.
Delegated Exchange#
The ExchangeV2Tx
also supports delegated signatures, where an authorized delegatee can act on behalf of the original account. This is facilitated by the delegator
parameter in methods like prepareExchange
.
const { fromRandom } = require('@ocap/wallet');
const GraphQLClient = require('@arcblock/graphql-client');
const endpoint = process.env.OCAP_API_HOST || 'http://127.0.0.1:4000';
const client = new GraphQLClient(`${endpoint}/api`);
const sleep = (timeout) => new Promise((resolve) => setTimeout(resolve, timeout));
(async () => {
try {
const alice = fromRandom(); // Delegator for sender
const bob = fromRandom(); // Receiver
const betty = fromRandom(); // Delegatee for sender
const lily = fromRandom(); // Delegatee for receiver
const declare = async (wallet, moniker) => {
const hash = await client.declare({ moniker: `user_${moniker}`, wallet });
console.log(`${moniker}.declare.result`, hash);
};
const checkin = async (wallet, moniker) => {
const hash = await client.checkin({ wallet });
console.log(`${moniker}.checkin.result`, hash);
};
const delegate = async (from, to, label) => {
const [hash] = await client.delegate({
from,
to,
privileges: [{ typeUrl: 'fg:t:exchange_v2', rules: [] }], // Delegate ExchangeV2Tx privilege
});
console.log(`${label}.delegate.hash`, hash);
};
// Declare all accounts
await declare(alice, 'alice');
await declare(bob, 'bob');
await declare(betty, 'betty');
await declare(lily, 'lily');
await sleep(3000);
// Check-in main accounts
await checkin(alice, 'alice');
await checkin(bob, 'bob');
await sleep(3000);
// Alice delegates ExchangeV2Tx privilege to Betty
await delegate(alice, betty, 'alice');
// Bob delegates ExchangeV2Tx privilege to Lily
await delegate(bob, lily, 'bob');
await sleep(3000);
// Create an asset for Bob to offer
let [, assetAddress] = await client.createAsset({
moniker: 'asset-for-delegate-exchange',
data: { typeUrl: 'json', value: { sn: Math.random() } },
wallet: bob,
});
console.log('Created asset by Bob:', assetAddress);
await sleep(3000);
// 4.1 Betty (Alice's delegatee) prepares and signs the transaction (tx1)
const tx1 = await client.prepareExchange({
receiver: bob.address,
offerToken: 5, // Alice (via Betty) offers 5 primary tokens
demandAssets: [assetAddress], // Alice (via Betty) demands Bob's asset
wallet: betty, // Betty's wallet signs
delegator: alice.address, // Alice is the delegator
});
console.log('tx1 (signed by Betty as Alice):', JSON.stringify(tx1, null, 2));
// 4.2 Lily (Bob's delegatee) finalizes and signs the transaction (tx2)
const tx2 = await client.finalizeExchange({
tx: tx1,
wallet: lily, // Lily's wallet signs
delegator: bob.address, // Bob is the delegator
});
console.log('tx2 (signed by Lily as Bob):', JSON.stringify(tx2, null, 2));
// 4.3 Betty (Alice's delegatee) sends the finalized exchange tx to the chain
await sleep(3000);
const hashExchange = await client.exchange({
tx: tx2,
wallet: betty, // Betty's wallet sends
});
console.log('Exchange transaction hash:', hashExchange);
} catch (err) {
console.error('Error during delegated exchange:', err);
}
})();
This advanced example demonstrates a delegated exchange where both the offerer and demander use delegatees to sign the transaction, showcasing the flexibility of multi-party and delegated signature capabilities.
Transfer Transactions (TransferV2Tx)#
The TransferV2Tx
is used for straightforward transfers of primary tokens, secondary tokens, or NFTs from one account to another. It supports multiple types of assets within a single transaction.
Structure of TransferV2Tx#
Field | Type | Description |
---|---|---|
|
| The address of the recipient. |
|
| The amount of primary token to transfer, in its smallest unit. |
|
| A list of NFT asset addresses to transfer. |
|
| A list of secondary tokens to transfer, each with an address and value. |
|
| Optional. Arbitrary data attached to the transaction (e.g., a memo). |
Usage Example#
To transfer assets or tokens using client.transfer
:
const { fromRandom } = require('@ocap/wallet');
const GraphQLClient = require('@arcblock/graphql-client');
const endpoint = process.env.OCAP_API_HOST || 'http://127.0.0.1:4000';
const client = new GraphQLClient(`${endpoint}/api`);
const sleep = (timeout) => new Promise((resolve) => setTimeout(resolve, timeout));
(async () => {
try {
const sender = fromRandom();
const receiver = fromRandom();
console.log({ sender: sender.address, receiver: receiver.address });
// 1. Declare sender and receiver
await client.declare({ moniker: 'sender', wallet: sender });
await client.declare({ moniker: 'receiver', wallet: receiver });
await sleep(3000);
// 2. Create an asset for the sender to transfer
let [hash, assetAddress] = await client.createAsset({
moniker: 'asset-to-be-transferred',
data: { typeUrl: 'json', value: { value: 'something valuable', sn: Math.random() } },
wallet: sender,
});
console.log('Created asset:', assetAddress, 'Tx Hash:', hash);
await sleep(3000);
// 3. Transfer the asset to the receiver
hash = await client.transfer({
to: receiver.address,
assets: [assetAddress],
memo: 'this is the transfer note for the asset',
wallet: sender,
});
console.log('Asset transfer transaction hash:', hash);
console.log('View transfer tx:', `${endpoint}/explorer/txs/${hash}`);
// Example of transferring primary token
await sleep(3000);
hash = await client.checkin({ wallet: sender }); // Ensure sender has tokens
console.log('Sender check-in tx:', hash);
await sleep(3000);
hash = await client.transfer({
to: receiver.address,
token: 10, // Transfer 10 primary tokens
memo: 'this is the transfer note for primary token',
wallet: sender,
});
console.log('Primary token transfer transaction hash:', hash);
console.log('View transfer tx:', `${endpoint}/explorer/txs/${hash}`);
// Example of transferring secondary token
await sleep(3000);
let tokenAddress;
[hash, tokenAddress] = await client.createToken({
name: 'Demo Token', description: 'A demo secondary token',
symbol: `DT_${Math.floor(Math.random() * 1000)}`, unit: 'D', decimal: 10, totalSupply: 1000,
wallet: sender
});
console.log('Created secondary token:', tokenAddress, 'Tx Hash:', hash);
await sleep(3000);
hash = await client.transfer({
to: receiver.address,
tokens: [{ address: tokenAddress, value: 50 }], // Transfer 50 secondary tokens
memo: 'this is the transfer note for secondary token',
wallet: sender,
});
console.log('Secondary token transfer transaction hash:', hash);
console.log('View transfer tx:', `${endpoint}/explorer/txs/${hash}`);
} catch (err) {
console.error('Error during transfer:', err);
}
})();
This example demonstrates how to transfer an NFT asset, primary tokens, and secondary tokens using the versatile client.transfer
method. The memo
field allows attaching additional context to the transaction.
Advanced Transfer Transactions (TransferV3Tx)#
The TransferV3Tx
is an advanced transfer type that supports multiple inputs and outputs within a single transaction. This allows for complex scenarios such as atomic swaps (multi-asset exchange without an explicit exchange type), batch transfers from multiple sources to multiple destinations, or consolidating funds from various accounts. It also inherently supports multi-party signatures, where each input owner must sign the transaction.
Structure of TransferV3Tx#
Field | Type | Description |
---|---|---|
|
| A list of transaction inputs, each describing assets/tokens from a specific owner. |
|
| A list of transaction outputs, each describing assets/tokens destined for a specific owner. |
|
| Optional. Arbitrary data attached to the transaction. |
TTransactionInput
specifies the assets and tokens involved for each party:
Field | Type | Description |
---|---|---|
|
| The address of the account providing (input) or receiving (output) the assets/tokens. |
|
| A list of NFT asset addresses. |
|
| A list of secondary tokens, each with an address and value. |
|
| The amount of primary token involved. |
Usage Example#
While the GraphQLClient
provides helper methods for TransferV3Tx
like signTransferV3Tx
and multiSignTransferV3Tx
, a full end-to-end example is not directly provided in the examples
data source. However, you can construct the itx
object manually and leverage the generic sign
and send
methods or the multi-signature capabilities.
Here's a conceptual example of how TransferV3Tx
could be structured for a multi-party transfer:
const { fromRandom } = require('@ocap/wallet');
const GraphQLClient = require('@arcblock/graphql-client');
const endpoint = process.env.OCAP_API_HOST || 'http://127.0.0.1:4000';
const client = new GraphQLClient(`${endpoint}/api`);
const sleep = (timeout) => new Promise((resolve) => setTimeout(resolve, timeout));
(async () => {
try {
const alice = fromRandom(); // Sender 1
const bob = fromRandom(); // Sender 2
const charlie = fromRandom(); // Receiver
await client.declare({ moniker: 'alice', wallet: alice });
await client.declare({ moniker: 'bob', wallet: bob });
await client.declare({ moniker: 'charlie', wallet: charlie });
await sleep(3000);
await client.checkin({ wallet: alice });
await client.checkin({ wallet: bob });
await sleep(3000);
// Create an asset for Alice
let [, assetA] = await client.createAsset({
moniker: 'AssetA',
data: { typeUrl: 'json', value: { sn: Math.random() } },
wallet: alice,
});
console.log('Alice created AssetA:', assetA);
await sleep(3000);
// Create an asset for Bob
let [, assetB] = await client.createAsset({
moniker: 'AssetB',
data: { typeUrl: 'json', value: { sn: Math.random() } },
wallet: bob,
});
console.log('Bob created AssetB:', assetB);
await sleep(3000);
// Construct the TransferV3Tx itx object
const transferV3Itx = {
inputs: [
{
owner: alice.address,
assets: [assetA],
value: client.fromTokenToUnit(5), // Alice contributes 5 primary tokens
},
{
owner: bob.address,
assets: [assetB],
value: client.fromTokenToUnit(3), // Bob contributes 3 primary tokens
},
],
outputs: [
{
owner: charlie.address,
assets: [assetA, assetB],
value: client.fromTokenToUnit(8), // Charlie receives 8 primary tokens and both assets
},
],
data: { type: 'json', value: { memo: 'Joint transfer to Charlie' } },
};
// Encode the transaction for signing
let encodedTx = await client.encodeTransferV3Tx({
tx: { itx: transferV3Itx },
wallet: alice, // Use Alice's wallet for encoding context, but signatures will be added separately
});
// Alice signs her part of the transaction
const txSignedByAlice = await client.multiSignTransferV3Tx({
tx: encodedTx.object,
wallet: alice,
});
console.log('Transaction signed by Alice:', JSON.stringify(txSignedByAlice, null, 2));
// Bob signs his part of the transaction (txSignedByAlice would be passed to Bob off-chain)
const txSignedByBob = await client.multiSignTransferV3Tx({
tx: txSignedByAlice,
wallet: bob,
});
console.log('Transaction signed by Bob:', JSON.stringify(txSignedByBob, null, 2));
// Send the fully signed transaction to the chain (can be sent by any signatory)
const hash = await client.sendTransferV3Tx({
tx: txSignedByBob,
wallet: alice, // Alice sends the transaction
});
console.log('TransferV3Tx hash:', hash);
console.log('View transfer tx:', `${endpoint}/explorer/txs/${hash}`);
} catch (err) {
console.error('Error during TransferV3Tx:', err);
}
})();
This example demonstrates how TransferV3Tx
can combine multiple inputs from different owners and direct them to various outputs. The use of multiSignTransferV3Tx
is crucial for collecting signatures from all involved input owners, ensuring the integrity and authorization of the complex transfer.
This section detailed the ExchangeV2Tx
, TransferV2Tx
, and TransferV3Tx
transaction types, highlighting their structures, purposes, and how to use them with the GraphQLClient
for trading and complex transfer operations. You now have a comprehensive understanding of how to facilitate various asset and token movements on the ArcBlock blockchain. Continue to the Staking Transactions section to learn about operations related to network security and participation.