モチベーション
オーダーをTakerが記入(Fill)するときのプロセスを明記する。fill_order_erc20.ts
を読み解く。間違い、ご意見あればTwitter経由でご連絡ください。
コントラクトの全体的な流れ
- MakerがほしいZRXとそれと交換するWETHを指定
- Maker, Takerが0x PeoxyにZRXとWETHを動かす許可を与える
- TakerがWETHコントラクトにETHをデポジットし、
Maker ZRX Approval
Taker WETH Approval
Taker WETH Deposit
を得る - 有効期限とエクスチェンジコントラクトのアドレスを指定
- Orderの作成
- 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)があるかを確認する。この一連の処理結果はターミナルに反映される。詳細はこちらから確認できる。
コントラクト全体
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);
}
})();