LoginSignup
2
0

More than 5 years have passed since last update.

0x.jsを使って0xプロトコルのスマートコントラクトを使用する(詳細編_Taker側の処理)

Last updated at Posted at 2018-12-19

モチベーション

オーダーをTakerが記入(Fill)するときのプロセスを明記する。fill_order_erc20.tsを読み解く。間違い、ご意見あればTwitter経由でご連絡ください。

コントラクトの全体的な流れ

  1. MakerがほしいZRXとそれと交換するWETHを指定
  2. Maker, Takerが0x PeoxyにZRXとWETHを動かす許可を与える
  3. TakerがWETHコントラクトにETHをデポジットし、Maker ZRX Approval Taker WETH Approval Taker WETH Depositを得る
  4. 有効期限とエクスチェンジコントラクトのアドレスを指定
  5. Orderの作成
  6. Fill Order発動

その他メモ:このコントラクトは0x.jsから複数のファイルをインポート。特に大事なのはOrder
Web3Wrapper。これは@0x/web3-wrapperからインポートしている。

このコントラクトは、ZRX→WETH用である。takerは0xのExchange Contractを通してfillする。

1. MakerがほしいZRXとそれと交換するWETHを指定

export async function scenarioAsync(): Promise<void> {
     PrintUtils.printScenario('Fill Order');
     // Initialize the ContractWrappers, this provides helper functions around calling
     // 0x contracts as well as ERC20/ERC721 token contracts on the blockchain
     const contractWrappers = new ContractWrappers(providerEngine, { networkId: NETWORK_CONFIGS.networkId });
     // Initialize the Web3Wrapper, this provides helper functions around fetching
     // account information, balances, general contract logs
     const web3Wrapper = new Web3Wrapper(providerEngine);
     const [maker, taker] = await web3Wrapper.getAvailableAddressesAsync();
     const zrxTokenAddress = contractAddresses.zrxToken;
     const etherTokenAddress = contractAddresses.etherToken;
     const printUtils = new PrintUtils(
         web3Wrapper,
         contractWrappers,
         { maker, taker },

         { WETH: etherTokenAddress, ZRX: zrxTokenAddress },
     );
     printUtils.printAccounts();

constで定数を指定している。ここで指定された値は後使うことになる。web3Wrapperは分かりづらいが、node_modules/@0x/web3-wrapper/から来ている。

2. Maker, Takerが0x PeoxyにZRXとWETHを動かす許可を与える

const makerZRXApprovalTxHash = await contractWrappers.erc20Token.setUnlimitedProxyAllowanceAsync(
     zrxTokenAddress,
     maker,
);
await printUtils.awaitTransactionMinedSpinnerAsync('Maker ZRX Approval', makerZRXApprovalTxHash);

0x ERC20 ProxyにmakerAccountを代表してZRXを移動することを許可するコントラクト。makerZRXApprovalTxHashなどは0x.jsのドキュメントからその役割を確認することができる。

const takerWETHApprovalTxHash = await contractWrappers.erc20Token.setUnlimitedProxyAllowanceAsync(
     etherTokenAddress,
     taker,
);
await printUtils.awaitTransactionMinedSpinnerAsync('Taker WETH Approval', takerWETHApprovalTxHash);

上記コントラクトと構文は一緒。0x ERC20 ProxyにtakerAccountを代表してWETHを移動することを許可するコントラクト。

ここからTakerのWETHと、MakerのZRXを交換するコントラクトがfill_order_erc20.tsであるとわかる。

3. TakerがWETHコントラクトにETHをデポジットし、ETHと同額のWETHを受け取ることで、Maker ZRX Approval Taker WETH Approval Taker WETH Depositを得る

const takerWETHDepositTxHash = await contractWrappers.etherToken.depositAsync(
     etherTokenAddress,
     takerAssetAmount,
     taker,
);
await printUtils.awaitTransactionMinedSpinnerAsync('Taker WETH Deposit', takerWETHDepositTxHash);

PrintUtils.printData('Setup', [
     ['Maker ZRX Approval', makerZRXApprovalTxHash],
     ['Taker WETH Approval', takerWETHApprovalTxHash],
     ['Taker WETH Deposit', takerWETHDepositTxHash],
]);

TakerはETHをWETHに交換しなければZRXと交換できないので、ETHをWETHコントラクトにデポジットすることで同額のWETHを受け取る。

4. 取引期限と取引コントラクトのアドレスを指定

const randomExpiration = getRandomFutureDateInSeconds();
const exchangeAddress = contractAddresses.exchange;

デモなのでランダムに有効期限を指定している。エクスチェンジコントラクトアドレスはネットワークにより異なるので、そのアドレスを指定している。

5. オーダーを作成する

const order: Order = {
    exchangeAddress,
    makerAddress: maker,
    takerAddress: NULL_ADDRESS,
    senderAddress: NULL_ADDRESS,
    feeRecipientAddress: NULL_ADDRESS,
    expirationTimeSeconds: randomExpiration,
    salt: generatePseudoRandomSalt(),
    makerAssetAmount,
    takerAssetAmount,
    makerAssetData,
    takerAssetData,
    makerFee: ZERO,
    takerFee: ZERO,
};

printUtils.printOrder(order);

オーダーを作成する。パラメーターは予め決まっている。詳細はこちらから確認できる。
特に大事なのはfeeRecipientAddress。リレイヤーはここに自分のアドレスを入れることによって手数料を得ることができる。

6.Fillオーダーの発動

await printUtils.fetchAndPrintContractAllowancesAsync();
await printUtils.fetchAndPrintContractBalancesAsync();


const orderHashHex = orderHashUtils.getOrderHashHex(order);
const signature = await signatureUtils.ecSignHashAsync(providerEngine, orderHashHex, maker);
const signedOrder = { ...order, signature };

await contractWrappers.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, takerAssetAmount, taker);


txHash = await contractWrappers.exchange.fillOrderAsync(signedOrder, takerAssetAmount, taker, {
     gasLimit: TX_DEFAULTS.gas,
});
txReceipt = await printUtils.awaitTransactionMinedSpinnerAsync('fillOrder', txHash);
printUtils.printTransaction('fillOrder', txReceipt, [['orderHash', orderHashHex]]);

await printUtils.fetchAndPrintContractBalancesAsync();


providerEngine.stop();
 }

void (async () => {
   try {
         if (!module.parent) {
             await scenarioAsync();
         }
   } catch (e) {
       console.log(e);
       providerEngine.stop();
       process.exit(1);
   }
})();

オーダーの残高の確認後、オーダーハッシュを作成し署名する。 fillOrderを呼び出す前にオーダーを検証しMaker, Takerの残高があるか、0xスマートコントラクトに価値を交換する許可(Allowance)があるかを確認する。この一連の処理結果はターミナルに反映される。詳細はこちらから確認できる。

コントラクト全体

fill_order_erc20.ts
import {
     assetDataUtils,
     BigNumber,
     ContractWrappers,
     generatePseudoRandomSalt,
     Order,
     orderHashUtils,
     signatureUtils,
 } from '0x.js';
 import { Web3Wrapper } from '@0x/web3-wrapper';

 import { NETWORK_CONFIGS, TX_DEFAULTS } from '../configs';
 import { DECIMALS, NULL_ADDRESS, ZERO } from '../constants';
 import { contractAddresses } from '../contracts';
 import { PrintUtils } from '../print_utils';
 import { providerEngine } from '../provider_engine';
 import { getRandomFutureDateInSeconds } from '../utils';

 /**
  * In this scenario, the maker creates and signs an order for selling ZRX for WETH.
  * The taker takes this order and fills it via the 0x Exchange contract.
  */
 export async function scenarioAsync(): Promise<void> {
     PrintUtils.printScenario('Fill Order');
     // Initialize the ContractWrappers, this provides helper functions around calling
     // 0x contracts as well as ERC20/ERC721 token contracts on the blockchain
     const contractWrappers = new ContractWrappers(providerEngine, { networkId: NETWORK_CONFIGS.networkId });
     // Initialize the Web3Wrapper, this provides helper functions around fetching
     // account information, balances, general contract logs
     const web3Wrapper = new Web3Wrapper(providerEngine);
     const [maker, taker] = await web3Wrapper.getAvailableAddressesAsync();
     const zrxTokenAddress = contractAddresses.zrxToken;
     const etherTokenAddress = contractAddresses.etherToken;
     const printUtils = new PrintUtils(
         web3Wrapper,
         contractWrappers,
         { maker, taker },
         { WETH: etherTokenAddress, ZRX: zrxTokenAddress },
     );
     printUtils.printAccounts();

     // the amount the maker is selling of maker asset
     const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), DECIMALS);
     // the amount the maker wants of taker asset
     const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(0.1), DECIMALS);
     // 0x v2 uses hex encoded asset data strings to encode all the information needed to identify an asset
     const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxTokenAddress);
     const takerAssetData = assetDataUtils.encodeERC20AssetData(etherTokenAddress);
     let txHash;
     let txReceipt;

     // Allow the 0x ERC20 Proxy to move ZRX on behalf of makerAccount
     const makerZRXApprovalTxHash = await contractWrappers.erc20Token.setUnlimitedProxyAllowanceAsync(
         zrxTokenAddress,
         maker,
     );
     await printUtils.awaitTransactionMinedSpinnerAsync('Maker ZRX Approval', makerZRXApprovalTxHash);

     // Allow the 0x ERC20 Proxy to move WETH on behalf of takerAccount
     const takerWETHApprovalTxHash = await contractWrappers.erc20Token.setUnlimitedProxyAllowanceAsync(
         etherTokenAddress,
         taker,
     );
     await printUtils.awaitTransactionMinedSpinnerAsync('Taker WETH Approval', takerWETHApprovalTxHash);

     // Convert ETH into WETH for taker by depositing ETH into the WETH contract
     const takerWETHDepositTxHash = await contractWrappers.etherToken.depositAsync(
         etherTokenAddress,
         takerAssetAmount,
         taker,
     );
     await printUtils.awaitTransactionMinedSpinnerAsync('Taker WETH Deposit', takerWETHDepositTxHash);

     PrintUtils.printData('Setup', [
         ['Maker ZRX Approval', makerZRXApprovalTxHash],
         ['Taker WETH Approval', takerWETHApprovalTxHash],
         ['Taker WETH Deposit', takerWETHDepositTxHash],
     ]);

     // Set up the Order and fill it
     const randomExpiration = getRandomFutureDateInSeconds();
     const exchangeAddress = contractAddresses.exchange;

     // Create the order
     const order: Order = {
         exchangeAddress,
         makerAddress: maker,
         takerAddress: NULL_ADDRESS,
         senderAddress: NULL_ADDRESS,
         feeRecipientAddress: NULL_ADDRESS,
         expirationTimeSeconds: randomExpiration,
         salt: generatePseudoRandomSalt(),
         makerAssetAmount,
         takerAssetAmount,
         makerAssetData,
         takerAssetData,
         makerFee: ZERO,
         takerFee: ZERO,
     };

     printUtils.printOrder(order);

     // Print out the Balances and Allowances
     await printUtils.fetchAndPrintContractAllowancesAsync();
     await printUtils.fetchAndPrintContractBalancesAsync();

     // Generate the order hash and sign it
     const orderHashHex = orderHashUtils.getOrderHashHex(order);
     const signature = await signatureUtils.ecSignHashAsync(providerEngine, orderHashHex, maker);
     const signedOrder = { ...order, signature };

     // Validate the order is Fillable before calling fillOrder
     // This checks both the maker and taker balances and allowances to ensure it is fillable
     // up to takerAssetAmount
     await contractWrappers.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, takerAssetAmount, taker);

     // Fill the Order via 0x Exchange contract
     txHash = await contractWrappers.exchange.fillOrderAsync(signedOrder, takerAssetAmount, taker, {
         gasLimit: TX_DEFAULTS.gas,
     });
     txReceipt = await printUtils.awaitTransactionMinedSpinnerAsync('fillOrder', txHash);
     printUtils.printTransaction('fillOrder', txReceipt, [['orderHash', orderHashHex]]);

     // Print the Balances
     await printUtils.fetchAndPrintContractBalancesAsync();

     // Stop the Provider Engine
     providerEngine.stop();
 }

 void (async () => {
     try {
         if (!module.parent) {
             await scenarioAsync();
         }
     } catch (e) {
         console.log(e);
         providerEngine.stop();
         process.exit(1);
     }
 })();
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0