In this quick start, you'll lean how to create a DEX and take a short tour through the zkLink SDK. It is essential to follow the suggested order of topics and make sure to have a solid understanding of zkLink's architecture by reading the overview beforehand. Additionally, to receive notifications from zkLink server, you will need to prepare the WebSocket.
Assuming there are two traders, Alice and Bob, the steps to be covered include:
Creating accounts for Bob and Alice
Alice deposits coin1 and Bob deposit coin2
Alice using coin1(id=18) to buy coin2(id=19) from Bob
Alice withdrawing half of the coin2 she just acquired
Create Account and Deposit
zkLink will create a user account when the user makes their first deposit. The DEX will not take any action except for receiving the user account creation event and the user balance update event from the websocket. For instance, when Alice makes her first deposit, you will receive the following data:
{"type":"TxExecuteResult","txHash":"0x05f2cf78d7b7a4a6b9f3c7e0c78d0c3a97438b9806e21834ee36530eb62d30fd","tx":{"type":"Deposit","from":"0x76ed7d63d9266f07ec86d44237daca3637a6650d","to":"0xd81418a80a0df6feaea04467f908bc1cb1fc5be7","fromChainId":7,"subAccountId":2,"l1SourceToken":17,"l2TargetToken":17,"amount":"1000000000000000000000000000","serialId":26,"l2Hash":"0xa0f46cc2c2ee1480350c4b1c2a3b1b70a55f799078ae1a248ed6cf71431e0270","ethHash":null },"receipt":{"executed":true,"executedTimestamp":1702099337833969,"success":true,"failReason":null,"block":null,"index":null },"updates":[// As the to_account (accountId is 1) does not exist, an AccountCreate is involved {"type":"AccountCreate","updateId":0,"accountId":11,"address":"0xd81418a80a0df6feaea04467f908bc1cb1fc5be7" },// The balance of the to_account increased, depositAmount=newBalance-oldBalance {"type":"BalanceUpdate","updateId":1,"accountId":11,"subAccountId":0,"coinId":18,"oldBalance":"0","newBalance":"1000000000000000000000000000","oldNonce":0,"newNonce":0 },// The 'global asset' account records the increase of on-chain asset reserves {"type":"BalanceUpdate","updateId":17,"accountId":1,"subAccountId":2,"coinId":18,"oldBalance":"3030000000000000000000","newBalance":"3040000000000000000000","oldNonce":0,"newNonce":0 } ]}
Now, you can maintain a new account with the account ID '31' with a balance '1000000000000000000000000000' of a coin ID (or token ID) of '18'. Similarly, and also same of Bob’s account information. For example:
To obtain the token ID information, you will need to query the RPC getSupportTokens. For instance, you can retrieve the 'ZKL' token information from the RPC response.
Next, Alice and Bob place orders, and the DEX will match them and send them to zkLink. Prior to users placing orders, you need to ensure the trade pair price is prepared. You can accomplish this by obtaining oracle.
Afterward, Alice and Bob need to create an order and sign the order with the BitKeep wallet or Metamask wallet, and then send it to the DEX server.
Bob, as the maker, will create the maker order:
import init,*as wasm from"./web-dist/zklink-sdk-web.js";async create_maker_order() {awaitinit();//use stand window.ethereum as metamask ..//await window.ethereum.request({ method: 'eth_requestAccounts' });//const provider = window.ethereum;constprovider=window.bitkeep &&window.bitkeep.ethereum;awaitprovider.request({ method:'eth_requestAccounts' });constsigner=newwasm.JsonRpcSigner(provider);awaitsigner.initZklinkSigner(null);console.log(signer);let account_id =31;let sub_account_id =0;// get the user nonce from rpc `getUser`let nonce =1;// get the slot id from rpc `getAccountOrderSlots`let slot_id =1;let base_token_id =18;let quote_token_id =19;let amount ="5000000";let price ="1000000000000";let is_sell =true;let maker_fee_ratio =5;let taker_fee_ratio =1; let maker_order = new wasm.Order(account_id, sub_account_id, slot_id, nocne, base_token_id,quote_token_id, amount, price, is_sell, maker_fee_ratio, taker_fee_ratio);
let signed_order =signer.createSignedOrder(maker_order);console.log(signed_order);}
And Alice, as the taker, will create the taker order:
let account_id =32;let sub_account_id =0;// get the user nonce from rpc `getUser`let nonce =1;// get the slot id from rpc `getAccountOrderSlots`let slot_id =1;let base_token_id =18;let quote_token_id =19;let amount ="5000000";let price ="1000000000000";let is_sell =false;let maker_fee_ratio =5;let taker_fee_ratio =1; let taker_order = new wasm.Order(account_id, sub_account_id, slot_id, nocne, base_token_id,quote_token_id, amount, price, is_sell, maker_fee_ratio, taker_fee_ratio);
let signed_order =signer.createSignedOrder(maker_order);let taker =signer.createSignedOrder(taker_order);console.log(taker);
Before creating the order matching transaction, you will need to generate a submitter private key. This key will allow you to sign the OrderMatching transaction. Additionally, it's essential to notify zkLink to add the submitter public key to the whitelist. By doing so, zkLink will be able to verify the transactions' signatures from the DEX and reject those whose submitter public keys are not on the whitelist.
Create OrderMatching Transaction
Once the taker order and the maker order are matched in the order matching engine, you can create the OrderMatching transaction. Sign the transaction with the submitter private key and then send it to zkLink using the sendTransaction RPC interface.
After sending the transaction to zkLink, you can receive the transaction state updates through the WebSocket.
"updates": [// Orderslot of the maker {"type":"OrderUpdate","updateId":0,"accountId":31,"subAccountId":0,"slotId":1,"oldTidyOrder": {"nonce":1,"residue":"54800000000000000" },"newTidyOrder": {"nonce":2,"residue":"0" } },// The balance of token0 in the maker account decreased {"type":"BalanceUpdate","updateId":1,"accountId":31,"subAccountId":0,"coinId":18,"oldBalance":"10000000000000000000","newBalance":"5000000000000000000","oldNonce":1,"newNonce":1 },// The balance of token1 in the maker account increased {"type":"BalanceUpdate","updateId":2,"accountId":31,"subAccountId":1,"coinId":19,"oldBalance":"0","newBalance":"5000000","oldNonce":1,"newNonce":1 },// Orderslot of the taker {"type":"OrderUpdate","updateId":3,"accountId":32,"subAccountId":0,"slotId":163,"oldTidyOrder": {"nonce":0,"residue":"1467400000000000000" },"newTidyOrder": {"nonce":0,"residue":"1412600000000000000" } },// The balance of token1 in the taker account decreased {"type":"BalanceUpdate","updateId":4,"accountId":32,"subAccountId":1,"coinId":19,"oldBalance":"10000000","newBalance":"5000000","oldNonce":1,"newNonce":1 },// The balance of token0 in the taker account increased {"type":"BalanceUpdate","updateId":5,"accountId":32,"subAccountId":0,"coinId":18,"oldBalance":"0","newBalance":"10000000000000000000","oldNonce":1,"newNonce":1 },// The submitter balance of token0 decreased due to transaction fees {"type":"BalanceUpdate","updateId":6,"accountId":6,"subAccountId":1,"coinId":18,"oldBalance":"9999680162000000000000","newBalance":"9999679771000000000000","oldNonce":224,"newNonce":224 },// The submitter collect token0 as transaction fee {"type":"BalanceUpdate","updateId":7,"accountId":6,"subAccountId":0,"coinId":18,"oldBalance":"3529001244641983488661500000","newBalance":"3529001244727972908661500000","oldNonce":224,"newNonce":224 },// The submitter collect token1 as transaction fee {"type":"BalanceUpdate","updateId":8,"accountId":6,"subAccountId":0,"coinId":19,"oldBalance":"1594000000063937300000000000","newBalance":"1594000000063964700000000000","oldNonce":224,"newNonce":224 },// The balance of the fee account increased {"type":"BalanceUpdate","updateId":9,"accountId":0,"subAccountId":0,"coinId":18,"oldBalance":"483683000000000000","newBalance":"484074000000000000","oldNonce":0,"newNonce":0 }]
From the state updates, you can verify that the transaction has been successfully executed. Subsequently, you can check the users' updated balance: