跳到主要內容
Series · How can we build decentralized markets on ArcBlock?

Technical appendix: Exchange and Transfer protocols

Robert
ARCBlockchainDIDArchitecture

ArcBlock defines exchange as a native transaction type. An application can describe both parties' asset baskets and use shared signing and validation semantics rather than first deploying its own exchange contract. Finding the parties and negotiating terms remain application responsibilities.

This appendix places public structures and APIs directly beside the explanation. Protocol fields, SDK parameters and proposed application workflows are separate layers. Full references are Transaction Types, Understanding Transactions and the SDK transaction API.

Exchange has been a native transaction type since chain 1.0. The 2019 DApps Workshop documented the earlier ExchangeTx, and the public SDK data-type reference retains its structure. Each earlier ExchangeInfo contained a native amount, value, and an assets list. ExchangeInfoV2 adds tokens, identifying other tokens and quantities. V2 extends the asset representation; it is not the point at which the chain first acquired exchange support.

Two asset baskets

The public protocol defines the following structures. TokenInput identifies a token and quantity; ExchangeInfoV2 describes native value, individual assets and other tokens delivered by one side.

protobuf
message TokenInput {
  string address = 1;
  string value = 2;
}

message ExchangeInfoV2 {
  BigUint value = 1;
  repeated string assets = 2;
  repeated TokenInput tokens = 3;
}

message ExchangeV2Tx {
  string to = 1;
  ExchangeInfoV2 sender = 2;
  ExchangeInfoV2 receiver = 3;
  google.protobuf.Timestamp expired_at = 4;
  google.protobuf.Any data = 15;
}

The ExchangeV2 definition is broader than a page selling one token for another. sender and receiver are baskets; to identifies the counterparty. The protocol explicitly considers tokens for assets, tokens plus assets for assets, and assets for assets.

Alice might offer two transferable assets for an agreed token amount from Bob. Each wallet should present both complete baskets rather than an abstract total price. Execution still depends on balances, ownership, transfer restrictions, token access rules and required signatures. Atomicity makes an allowed exchange indivisible; it does not make restricted assets transferable.

Native exchange semantics let applications share validation rules. General contracts leave more customization with application authors. These place complexity at different layers: a native primitive does not automatically provide dynamic auctions or arbitrary fees, while a generic contract need not be redeployed for every trade.

From transaction contents to authorization

The exchange payload lives inside itx. The outer transaction carries the sender, chain identifier, nonce and signatures. This is the type representation used in the public documentation; the SDK must encode transaction data before submission.

typescript
type Transaction = {
  from: string;
  pk: string;
  nonce: number;
  chainId: string;
  delegator: string;
  signature: string;
  signatures: Array<Multisig>;
  itx: Any;
};

signature and signatures distinguish the primary signature from additional participants' authorization. itx holds the transaction type and contents. Signing must follow the SDK's encoding and signature scope for that type; signing arbitrary JSON text is not equivalent. Transaction structure and signatures.

The high-level SDK divides a known-counterparty exchange into three actions. Adapted from the public example, this shows the call relationship. It assumes a connected client, an assetAddress and appropriate wallets; it is not a complete script for real funds.

javascript
// Alice's environment: prepare the agreed offer for Bob.
const offerTx = await client.prepareExchange({
  receiver: bobAddress,
  offerAssets: [assetAddress],
  demandToken: 10,
  wallet: aliceWallet,
});

// Bob's environment: inspect the decoded terms before signing.
const finalTx = await client.finalizeExchange({
  tx: offerTx,
  wallet: bobWallet,
});

// Submit after both parties have authorized the transaction.
const txHash = await client.exchange({
  tx: finalTx,
  wallet: aliceWallet,
});

The calls can occur in different participants' environments. offerTx and finalTx travel between them; private keys do not. The quantity illustrates the API. Production applications must follow the SDK's unit conventions, handle precision and verify that signed contents match the agreement. prepareExchange, finalizeExchange and exchange.

This supports delivery after agreement. It is not automatically an anonymous open-order protocol: the documented prepareExchange parameters require a receiver. An application can publish an intent off-chain and construct a directed transaction after finding a counterparty. Removing the recipient must not be assumed to produce an order anyone can fill.

TransferV3 describes a different composition

TransferV2 describes delivery to one recipient. TransferV3 describes multiple inputs and outputs. Its model is related to, but distinct from, a bilateral exchange.

protobuf
message TransactionInput {
  string owner = 1;
  repeated TokenInput tokens = 2;
  repeated string assets = 3;
}

message TransferV3Tx {
  repeated TransactionInput inputs = 1;
  repeated TransactionInput outputs = 2;
  google.protobuf.Any data = 15;
}

Each side of TransferV3Tx lists owners and assets. It can support investigation of multiple funding sources or fixed distributions: who supplies which tokens and who receives them. Asset conservation and authorization remain necessary. An outputs array cannot create a balance.

The type definition is not the entire execution contract. Current protocol constraints on input and output owners prevent treating an arbitrary barter graph with the same owner on both sides as a valid TransferV3 transaction. Particular token types with issuer authority also have rules beyond ordinary input authorization. Applications must validate the supported asset path, not merely test whether JSON encodes.

Use the Exchange model to reason about a directed bilateral exchange and investigate TransferV3 for multiple payment sources or distributions. Complex multilateral swaps, dynamic fees and partial fills need separate designs.

Quote lifecycle is still an application concern

An expired_at field does not establish that every deployed version enforces expiry. A production application must verify the target chain's expiry, replay and cancellation behavior. Graying out an offer in the interface does not invalidate spending authority. If settlement cannot enforce a required condition, the authorization flow or underlying mechanism must change before offering that behavior.

ObjectWho interprets itEssential distinction
Published intentPublishing and discovery servicesInterest is not spending authority
Quote and sessionApplications, agents and validatorsAgreement in chat is not a protocol signature
Signed transactionWallet and chain validationWithdrawal from an index may not revoke a signature
Settlement outcomeChain confirmation and reconciliationA transaction identifier is not confirmed success

Cancellation, expiry, partial fills, requoting and concurrent commitments must be designed together. An agent negotiating two trades does not make one asset deliverable twice. A submission timeout does not establish failure. Persisting session-to-transaction relationships makes recovery and reconciliation possible.

ArcBlock's distinction is concrete: exchange and certain composite transfers have a shared protocol vocabulary, connected to identity and authorization through DIDs and wallets. Developers can concentrate on market organization before settlement and recovery afterward. Primitives provide settlement, and Blocklets provide software users can run. The personal market application must implement the work between them.