如何在去中心化交易所中(DEX)集成0x协议

什么是0x协议,它的工作机制是怎样的?这个本文将介绍0x协议,包括它的链下订单中继(撮合)、去中心化交易中继器, 以及如何在以太坊公链或私链上通过0x智能合约构建自己的去中心化交易所(DEX)。
什么是0x协议
0x是一种开放、以太坊上支持点对点资产交换的协议,其开源基础架构使开发人员和企业能够构建自己的交易所来交易所有ERC-20和ERC-721资产。
0x协议特性
0x协议有以下特性
· 安全的非托管交易
无需存款或取款,就可以直接实现钱包对钱包的资产交易。
· 灵活的订单类型
可以选择以当前价格卖出资产,或允许潜在买家出价。
· 构建业务
通过在每次交易中收取费用,可以使产品货币化,还可加入0x生态系统中越来越丰富的中继器。
上面的0x协议特性可实现我们的去中心化兑换。
0x协议优势
0x协议使用模块化方式交易以太坊区块链上资产,优势有:
· 可靠的智能合约
0x协议的智能合约通过了两轮严格的安全审核。
· 可扩展架构
0x的模块化管道支持开发者通过扩展API嵌入自己的智能合约。
· 高效设计
0x协议的链下订单撮合、链上结算,是一种节省手续费的兑换方式。
0x协议可用于很多场景,例如游戏、收藏品、预测市场、去中心化交易的订单簿、 去中心化贷款等等。
其次,0x的智能合约,是模块化的,可以通过治理进行升级, 而不会影响系统的其他组件,也不会引起市场的中断。
0x协议的智能合约
0x协议的智能合约包含有:
· Exchange Contract:资产兑换合约
· ERC20 Proxy contract:ERC20代理合约
· ERC721 Proxy contract:ERC721代理合约
资产兑换合约
包含0x协议的业务逻辑,是以下功能的入口:
· 提供订单
· 订单取消
· 执行交易
· 签名验证
· 在系统中注册新资产
ERC20代理合约
该合约代表用户要转让ERC20代币。因此,每个用户(ERC20代币持有者)都必须授权(approve)该合约可以操作自己持有的ERC20代币。
ERC721 代理合约
该合约代表用户要转让ERC721代币。因此,每个用户(ERC721代币持有者)都必须授权(approve)该合约可以操作自己持有的ERC721代币。
为了部署及使用0x协议智能合约,需要先安装0x.js。0x.js是一个与0x协议交互的 JavaScript库,利用它就可以轻松地调用0x协议的智能合约来创建、取消或验证订单,以及检查 ERC20和ERC721代币持有者的授权额度和余额。
深入0x 合约架构
0x协议采用链下订单撮合、链上结算的模式,密码学签名的订单可以在链下通过任意渠道通信。感兴趣的对手方可以将这些订单中的一个或多个注入到0x的资产兑换合约中,进行链上交易结算。

0x协议可以交换任何ERC20或ERC721资产。上图显示了当Taker(吃单者)向0x资产兑换合约(Exchange)提交订单时资产转移的实际处理流程(以下需要对应的上图的标号):
注:兑换交易中,Maker(挂单者)和Taker(吃单者)。挂单是提供流动性,其订单是被动成交的。吃单刚好相反,是利用流动性主动交易。
1. Taker(吃单者)调用资产兑换合约的fillOrder()方法提交签名订单。
2. 资产兑换合约将订单传递给相应的ERC20代理合约,实际的代币转账是在代理合约上进行的。注意:Maker和Taker必须先授权ERC20代理合约,然后再提交订单。
3. 在ERC20代理合约中调用(挂单者)ERC20代币合约的transferFrom()方法
4. 如果挂单者的ERC20合约调用失败,则整个交易回滚。
5. 交易从代理返回到兑换合约。
6. 资产兑换合约将订单传递到ERC20代理合同。
7. 在资产代理合约中调用(吃单者)ERC20合约的transferFrom()方法
8. 如果吃单者的ERC20合约调用失败,则整个交易回滚。
9. 交易从代理返回到兑换合约。
10. 返回交易执行结果
接下来,我们将讨论使用0x.js库在以太坊上的部署0x智能合约,以便交易资产,使用 npm 安装0x.js:
npm install 0x.js
部署0x智能合约
要与智能合约进行交互,我们需要部署0x智能合约,然后通过 0x.js库用合约地址与合约进行交互。
资产兑换合约
资产兑换合约的源代码在 此github[1] ,资产兑换合约构造函数没有参数,部署的时候不需要提供参数,部署者(msg.sender)将是合约的所有者(Owner)。所有者能够在兑换合约中设置代理合约的地址。
ERC20代理合约
使用ERC20代理合约的源代码[2] 进行部署,代理合约的构造函数不需要参数,部署者(msg.sender)将是合约的所有者。所有者将能够在ERC20代理合约中设置资产兑换合约的地址。
ERC721代理合约
使用ERC721代理合约源代码[3] 进行部署,代理合约的构造函数不需要参数,部署者(msg.sender)将是合约的所有者。所有者将能够在ERC721代理合约中设置资产兑换合约的地址。
与 0x 协议交互
与0x交互的最终目标是挂单者使用0x.js库创建订单,吃单者使用fillOrder()函数提交订单进行兑换。
注册合约
部署完以上合约后,需要在资产代理合约中设置兑换合约的地址,在兑换合约中设置资产代理合约的地址。
· 调用资产兑换合约的registerAssetProxy(ERC20 或 ERC71 代理合约地址)方法将记录资产代理合约的地址,兑换代币交易是在代理合约中进行的。该方法只能由资产交换智能合约的所有者调用。
· 调用ERC20代理合约的addAuthorizedAddress(兑换合约)方法注册兑换合约。
· 调用ERC20代理合约的removeAuthorizedAddress(兑换地址)方法删除兑换合约。
合约配置
使用资产兑换合约和资产代理合约地址通过0x.js库 进行交互 :
let contractConfig = {
  contractAddresses: {
    erc20Proxy: proxyAddress.toLowerCase(),
    erc721Proxy: “0x1d7022f5b17d2f8b695918fb48fa1089c9f85401”,
    exchange: exchangeAddress.toLowerCase() 
  },
  networkId: networkId
};
  
const contractWrappers = new ContractWrappers(holderEngine, contractConfig);
现在就可以与部署在专用或测试网络上的0x协议智能合约进行交互。请记住添加RPC节点提供者以便与区块链进行交互。
实例化
为了与0x.js库进行交互,我们需要导入(import)如下所示相关的软件包
const {
  assetDataUtils,BigNumber,ContractWrappers,
  generatePseudoRandomSalt,orderHashUtils,signatureUtils
} = require(‘0x.js’);
const TX_DEFAULTS = { gas: 400000 };
const { RPCSubprovider, Web3ProviderEngine } = require(‘0x.js’);
let newWallet = new ethers.Wallet(wallet.signingKey.privateKey, prov);
const holderWallet = new PrivateKeyWalletSubprovider(wallet.signingKey.privateKey.slice(2));
添加RPC订阅节点提供者:
const holderEngine = new Web3ProviderEngine();
holderEngine.addProvider(holderWallet);
// 订阅节点提供者可以使用自定义URL分别连接以太坊主网、测试网或私链。
holderEngine.addProvider(new RPCSubprovider(providerUrl));
holderEngine.start();
获取0x合约地址并实例化合约包装器:
const contractWrappers = new ContractWrappers(holderEngine, contractConfig);
const web3Wrapper = new Web3Wrapper(providerEngine);
const contractAddresses = getContractAddressesForNetworkOrThrow(100);//networkID
ERC20合约授权
在挂单者(持有 tokenA)可以创建一个订单并且吃单者(持有 token B)将提交订单进行兑换之前,需要新进行授权。
先获取相关合约地址:
// 合约地址
const tokenAAddress = contractAddresses.tokenA;
const tokenBAddress = contractAddresses.tokenB;
const exchange = contractAddresses.exchange;
所有地址都可以从0x.js库获取到。
// 将有关资产的所有必要信息编码为十六进制字符串
const makerAssetData = assetDataUtils.encodeERC20AssetData(tokenAAddress);
const takerAssetData = assetDataUtils.encodeERC20AssetData(tokenBAddress);
const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS);
const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(200), DECIMALS);
const NULL_ADDRESS = ‘0x0000000000000000000000000000000000000000’;
const ZERO = new BigNumber(0);
const DECIMALS = 18;
现在挂单者和吃单者应该授权相应的资产代理合约,以便代理合约可以分别代表挂单者和吃单者转移代币:
// 授权
const makerApprovalTxHash = await contractWrappers.erc20Token.setUnlimitedProxyAllowanceAsync(
    tokenAAddress,
    maker,
);
await web3Wrapper.awaitTransactionSuccessAsync(makerApprovalTxHash);
// 授权
const takerApprovalTxHash = await contractWrappers.erc20Token.setUnlimitedProxyAllowanceAsync(
    tokenBAddress,
    taker,
);
await web3Wrapper.awaitTransactionSuccessAsync(takerApprovalTxHash);
在挂单者和吃单者授权资产代理合约之后,代理合约就可以分别代表挂单者和吃单者转移代币了。
接下来,挂单者将创建一个委托订单并在链下签名,而吃单者将在链上执行订单。
创建订单、验证与吃单
创建订单:
const order = {
  exchangeAddress: exchangeAddress,
  makerAddress: maker,//address of maker
  takerAddress: taker,//address of taker
  senderAddress: taker,// address of sender
  feeRecipientAddress: NULL_ADDRESS,//fee in the form of native currency of platform
  expirationTimeSeconds: randomExpiration,//订单过期时间
  salt: generatePseudoRandomSalt(),// 随机数,用来区分订单
  makerAssetAmount,// 挂单资产数量
  takerAssetAmount,// taker asset amount
  makerAssetData,//encoded address of tokenA
  takerAssetData,//encoded address of tokenB
  makerFee: ZERO,//fee if required
  takerFee: ZERO,//fee if required
};
现在我们创建了一个资产交换委托订单。接下来在调用0x.js库的getOrderHash()函数获得订单哈希值以便进行签名。这个哈希根据EIP712[4]对订单计算出来的:
const orderHashHex = orderHashUtils.getOrderHashHex(order);
获取订单的哈希后,挂单者使用0x.js库的ecSignHashAsync()方法对订单签名。
const signature = await signatureUtils.ecSignHashAsync(providerEngine, orderHashHex, maker);
const signedOrder = { …order, signature };
吃单者可以使用资产交易合约的validateFillOrderThrowIfInvalidAsync`方法验证订单是否可以执行:
await contractWrappers.exchange.validateFillOrderThrowIfInvalidAsync(
  signedOrder, 
  takerAssetAmount, 
  taker
);
最终,吃单者调用资产交易合约的fillOrderAsync方法执行订单:
try{
  txHash = await contractWrappers.exchange.fillOrderAsync(
    signedOrder, 
    takerAssetAmount, 
    taker, 
    {TX_DEFAULTS,}
  );
  
  var transaction = await web3Wrapper.awaitTransactionSuccessAsync(txHash);
}
catch(error){}
让我们快速回顾一下到目前为止所学到的知识,然后通过介绍已经建立在0x上的项目来结束我们的讨论。