Used to check for browser translation.
用于检测浏览器翻译。
ブラウザの翻訳を検出する
Transaction Types and Usage

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

to

string

The address of the receiver in the exchange.

sender

TExchangeInfoV2

Details of the assets and tokens offered by the sender.

receiver

TExchangeInfoV2

Details of the assets and tokens demanded by the receiver.

expiredAt

google_protobuf_timestamp_pb.Timestamp

Optional. The timestamp when the exchange offer expires.

data

google_protobuf_any_pb.Any

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

value

type_pb.TBigUint

The amount of primary token (e.g., ABT) involved, in its smallest unit.

assets

Array<string>

A list of NFT asset addresses involved in this side of the exchange.

tokens

Array<type_pb.TTokenInput>

A list of secondary tokens involved, each with an address and value.

TTokenInput defines a specific secondary token amount:

Field

Type

Description

address

string

The address of the secondary token.

value

type_pb.TBigUint

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.

ArcBlock NodeGraphQLClientReceiver WalletSender WalletArcBlock NodeGraphQLClientReceiver WalletSender Walletclient.prepareExchange(...) (offer assets/tokens, demand tokens/assets)Get chain context (nonce, chainId, token decimal)Encode ExchangeV2Tx (Sender as "from", "itx" with offer/demand details)Signed Tx (tx1 - sender's signature)Send tx1 to Receiver (off-chain)client.finalizeExchange(tx1, ...) (receiver's wallet)Add Receiver's Multisig to tx1.signaturesListSign updated tx (tx2 - receiver's signature on combined tx)Signed Tx (tx2 - receiver's signature added)Send tx2 back to Sender (off-chain)client.exchange(tx2, ...) (sender's wallet)sendTx (broadcast tx2 to chain)Tx HashTx Hash

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

to

string

The address of the recipient.

value

type_pb.TBigUint

The amount of primary token to transfer, in its smallest unit.

assets

Array<string>

A list of NFT asset addresses to transfer.

tokens

Array<type_pb.TTokenInput>

A list of secondary tokens to transfer, each with an address and value.

data

google_protobuf_any_pb.Any

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

inputs

Array<type_pb.TTransactionInput>

A list of transaction inputs, each describing assets/tokens from a specific owner.

outputs

Array<type_pb.TTransactionInput>

A list of transaction outputs, each describing assets/tokens destined for a specific owner.

data

google_protobuf_any_pb.Any

Optional. Arbitrary data attached to the transaction.

TTransactionInput specifies the assets and tokens involved for each party:

Field

Type

Description

owner

string

The address of the account providing (input) or receiving (output) the assets/tokens.

assets

Array<string>

A list of NFT asset addresses.

tokens

Array<type_pb.TTokenInput>

A list of secondary tokens, each with an address and value.

value

type_pb.TBigUint

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.