This code is provided strictly for educational purposes and has not undergone any formal security audit. It may contain errors, vulnerabilities, or other issues that could pose risks to the integrity of your system or data.
By using this code, you acknowledge and agree that:
- No Warranty: The code is provided "as is" without any warranty of any kind, either express or implied. The entire risk as to the quality and performance of the code is with you.
- Educational Use Only: This code is intended solely for educational and learning purposes. It is not intended for use in any mission-critical or production systems.
- No Liability: In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the use or performance of this code.
- Security Risks: The code may not have been tested for security vulnerabilities. It is your responsibility to conduct a thorough security review before using this code in any sensitive or production environment.
- No Support: The authors of this code may not provide any support, assistance, or updates. You are using the code at your own risk and discretion.
Before using this code, it is recommended to consult with a qualified professional and perform a comprehensive security assessment. By proceeding to use this code, you agree to assume all associated risks and responsibilities.
Before we proceed, we should install a couple of things. Also, if you are using a Windows machine, it's recommended to use WSL2.
On Ubuntu/Debian/WSL2(Ubuntu):
sudo apt update
sudo apt install curl git-all cmake gcc libssl-dev pkg-config libclang-dev libpq-dev build-essential -y
On MacOs:
brew install curl cmake git libpq
If you don't have brew
installed, run this:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Next, we need rust and cargo:
curl https://sh.rustup.rs -sSf | sh
If you are using Github codespaces, it's recommended to use pre-built binaries rather than building them from source.
To download pre-built binaries, you should run download-sui-binaries.sh
in the terminal.
This scripts takes three parameters (in this particular order) - version
, environment
and os
:
- sui version, for example
1.15.0
. You can lookup a more up-to-date version available here SUI Github releases. environment
- that's the environment that you are targeting, in our case it'sdevnet
. Other available options are:testnet
andmainnet
.os
- name of the os. If you are using Github codespaces, putubuntu-x86_64
. Other available options are:macos-arm64
,macos-x86_64
,ubuntu-x86_64
,windows-x86_64
(not for WSL).
To donwload SUI binaries for codespace, run this command:
./download-sui-binaries.sh "v1.15.0" "devnet" "ubuntu-x86_64"
and restart your terminal window.
If you prefer to build the binaries from source, run this command in your terminal:
cargo install --locked --git https://github.com/MystenLabs/sui.git --branch devnet sui
cargo install --git https://github.com/move-language/move move-analyzer --branch sui-move --features "address32"
To run a local network with a pre-built binary (recommended way), run this command:
RUST_LOG="off,sui_node=info" sui-test-validator
Optionally, you can run it from sources.
git clone --branch devnet https://github.com/MystenLabs/sui.git
cd sui
RUST_LOG="off,sui_node=info" cargo run --bin sui-test-validator
https://chrome.google.com/webstore/detail/sui-wallet/opcgpfmipidbgpenhmajoajpbobppdil?hl=en-GB
Once the local node is running (using sui-test-validator
), you should the url of a local node - http://127.0.0.1:9000
(or similar).
Also, another url in the output is the url of a local faucet - http://127.0.0.1:9123
.
Next, we need to configure a local node. To initiate the configuration process, run this command in the terminal:
sui client active-address
The prompt should tell you that there is no configuration found:
Config file ["/home/codespace/.sui/sui_config/client.yaml"] doesn't exist, do you want to connect to a Sui Full node server [y/N]?
Type y
and in the following prompts provide a full node url http://127.0.0.1:9000
and a name for the config, for example, localnet
.
On the last prompt you will be asked which key scheme to use, just pick the first one (0
for ed25519
).
After this, you should see the ouput with the wallet address and a mnemonic phrase to recover this wallet. You can save so later you can import this wallet into SUI Wallet.
Additionally, you can create more addresses and to do so, follow the next section - Create addresses
.
For this tutorial we need two separate addresses. To create an address run this command in the terminal:
sui client new-address ed25519
where:
ed25519
is the key scheme (other available options are:ed25519
,secp256k1
,secp256r1
)
And the output should be similar to this:
╭─────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Created new keypair and saved it to keystore. │
├────────────────┬────────────────────────────────────────────────────────────────────────────────┤
│ address │ 0x05db1e318f1e4bc19eb3f2fa407b3ebe1e7c3cd8147665aacf2595201f731519 │
│ keyScheme │ ed25519 │
│ recoveryPhrase │ lava perfect chef million beef mean drama guide achieve garden umbrella second │
╰────────────────┴────────────────────────────────────────────────────────────────────────────────╯
Use recoveryPhrase
words to import the address to the wallet app.
curl --location --request POST 'http://127.0.0.1:9123/gas' --header 'Content-Type: application/json' \
--data-raw '{
"FixedAmountRequest": {
"recipient": "<ADDRESS>"
}
}'
<ADDRESS>
- replace this by the output of this command that returns the active address:
sui client active-address
You can switch to another address by running this command:
sui client switch --address <ADDRESS>
abd run the HTTP request to mint some SUI tokens to this account as well.
Also, you can top up the balance via the wallet app. To do that, you need to import an account to the wallet.
To build tha package, you should run this command:
sui move build
If the package is built successfully, the next step is to publish the package:
sui client publish --gas-budget 100000000 --json
Here we do not specify the path to the package dir so it will use the current dir - .
After the contract is published we need to extract some object ids from the output. Here is the list of env variable that we source in the current shell and their values:
PACKAGE_ID
- the id of the published package. The json path to it is.objectChanges[].packageId
ORIGINAL_UPGRADE_CAP_ID
- the upgrade cap id that we might need if we find ourselves in the situation when we need to upgrade the contract. Path:.objectChanges[].objectId
where.objectChanges[].objectType
is0x2::package::UpgradeCap
SUI_FEE_COIN_ID
the id of the SUI coin that we are going to use to pay the fee for the pool creation. Take any from the output of this command:sui client gas --json
ACCOUNT_ID1
- currently active address, assign the output of this command:sui client active-address
. Repeat the same for the secondary account and assign the output toACCOUNT_ID1
CLOCK_OBJECT_ID
- the id of theClock
object, default to0x6
BASE_COIN_TYPE
- the type of the SUI coin, default to0x2::sui::SUI
QUOTE_COIN_TYPE
- the type of the quote coin that we deployed for the sake of this tutorial. The coin isWBTC
in thewbtc
module in the$PACKAGE_ID
package. So the value will look like this:<PACKAGE_ID>::wbtc::WBTC
WBTC_TREASURY_CAP_ID
it's the treasury cap id that is needed for token mint operations. In the publish output you should look for the object withobjectType
0x2::coin::TreasuryCap<$PACKAGE_ID::wbtc::WBTC>
(replace$PACKAGE_ID
with the actual package id) and this object also hasobjectId
- that's the value that we are looking for.
Now we create a pool:
sui client call --package $PACKAGE_ID --module book --function new_pool --type-args $BASE_COIN_TYPE $QUOTE_COIN_TYPE --args $SUI_FEE_COIN_ID --gas-budget 10000000000 --json
After the pool is created, check the output and locate the object with the type 0xdee9::clob_v2::Pool<$BASE_COIN_TYPE, $QUOTE_COIN_TYPE>
($BASE_COIN_TYPE
, $QUOTE_COIN_TYPE
- replace with the actual values) and assign the value from the objectId
property to $POOL_ID
.
In this case, the pool is created with the default values for REFERENCE_TAKER_FEE_RATE
and REFERENCE_MAKER_REBATE_RATE
where the former one is 2_500_000
(0.25%) and the latter one is 1_500_000
(0.15%).
In case you'd like to customize these values, you should use create_customized_pool
:
public fun create_customized_pool<BaseAsset, QuoteAsset>(
tick_size: u64,
lot_size: u64,
taker_fee_rate: u64,
maker_rebate_rate: u64,
creation_fee: Coin<SUI>,
ctx: &mut TxContext,
)
For limit orders we need to have custodian accounts (more details here):
sui client call --package $PACKAGE_ID --module book --function new_custodian_account --gas-budget 10000000000
In the publish output you should look for the object with objectType
0xdee9::custodian_v2::AccountCap
and assign the value objectId
to the env variable ACCOUNT1_CAP
.
Repeat the same for the secondary account that we created previously (use sui client switch --address <ADDRESS>
) and the output assign to ACCOUNT2_CAP.
Now we need to mint WBTC tokens. Switch to the address that you've used to publish the contract and run this command to mint tokens:
sui client call --function mint --module wbtc --package $PACKAGE_ID --args $WBTC_TREASURY_CAP_ID "10000000000" $ACCOUNT_ID1 --gas-budget 10000000 --json
Re-run the same command but change ACCOUNT_ID1
to ACCOUNT_ID2
.
Check the output and look for an object with objectType
that has a value like this String("0x2::coin::Coin<$PACKAGE_ID::wbtc::WBTC>")
and get the value of objectId
and assign it ot ACCOUNT1_WBTC_OBJECT_ID
.
Now we need to make deposits (both base and quote) to ACCOUNT1_CAP_ID
(for limit orders).
First, prepare the BASE_COIN_ID
variable by assigning the output of sui client gas
(any of the values) and the run this command:
sui client call --package $PACKAGE_ID --module book --function make_base_deposit --args $POOL_ID $BASE_COIN_ID $ACCOUNT1_CAP_ID --type-args $BASE_COIN_TYPE $QUOTE_COIN_TYPE --gas-budget 10000000000 --json
Now deposit the quote coin:
sui client call --package $PACKAGE_ID --module book --function make_quote_deposit --args $POOL_ID $ACCOUNT1_WBTC_OBJECT_ID $ACCOUNT1_CAP_ID --type-args $BASE_COIN_TYPE $QUOTE_COIN_TYPE --gas-budget 10000000000 --json
The arguments strictly match the signature of the function that is called.
Let's place multiple limit orders:
sui client call --package $PACKAGE_ID --module book --function place_limit_order --args "$POOL_ID" 42 5000000000 200 0 true 1888824053000 0 $CLOCK_OBJECT_ID "$ACCOUNT1_CAP" --type-args $BASE_COIN_TYPE $QUOTE_COIN_TYPE --gas-budget 10000000000 --json
sui client call --package $PACKAGE_ID --module book --function place_limit_order --args "$POOL_ID" 42 5000000000 300 0 true 1888824053000 0 $CLOCK_OBJECT_ID "$ACCOUNT1_CAP" --type-args $BASE_COIN_TYPE $QUOTE_COIN_TYPE --gas-budget 10000000000 --json
sui client call --package $PACKAGE_ID --module book --function place_limit_order --args "$POOL_ID" 42 2000000000 1000 0 true 1888824053000 0 $CLOCK_OBJECT_ID "$ACCOUNT1_CAP" --type-args $BASE_COIN_TYPE $QUOTE_COIN_TYPE --gas-budget 10000000000 --json
sui client call --package $PACKAGE_ID --module book --function place_limit_order --args "$POOL_ID" 42 20000000000 1000 0 false 1888824053000 0 $CLOCK_OBJECT_ID "$ACCOUNT1_CAP" --type-args $BASE_COIN_TYPE $QUOTE_COIN_TYPE --gas-budget 10000000000 --json
where:
1888824053000
is a timestamp somewhere in the future, to avoid order expiration for the sake of simplicity42
is an id of the order, can be any number.
Notice that these commands are mostly the same except for the arguments for priect
, quantity
and is_bid
.
is_bid
indicated whether you want to sell or buy assets. More details here: 2.1.4 Place Limit Order.
Now we need to place a market order to buy/sell assets. We are going to do this using our secondary account.
First, switch to the secondary account:
sui client switch --address <ADDRESS>
Now place the order:
sui client call --package $PACKAGE_ID --module book --function place_base_market_order --args $POOL_ID $ACCOUNT2_CAP $SUI_COIN_ID 942 "false" $CLOCK_OBJECT_ID --type-args $BASE_COIN_TYPE $QUOTE_COIN_TYPE --gas-budget 10000000000 --json
where:
942
is an id of the order, can be any number
Check the output of this command and find the balanceChanges
array where the balance changes will be listed, you should see the exchanged assets. Also, you can double-check this in the wallet app.
The amount of tokens won't be precisly the number you expect because there is a deepbook fee, more details here.
More info about the market orders can be found here.
Now let's try to run a swap order. Re-run the commands the place limit orders (using the first account) and the switch back to the secondary account, and run the swap order:
sui client call --package $PACKAGE_ID --module book --function swap_exact_base_for_quote --args "$POOL_ID" 42 $ACCOUNT2_CAP 1500 $BASE_COIN_ID $CLOCK_OBJECT_ID --type-args $BASE_COIN_TYPE $QUOTE_COIN_TYPE --gas-budget 10000000000 --json
where:
BASE_COIN_ID
is the id of the SUI coin, get the value fromsui client gas --json
42
is an id of the order, can be any number
Check the output of this command and find the balanceChanges
array where the balance changes will be listed, you should see the exchanged assets. Also, you can double-check this in the wallet app.
After you deposited either base or quote assets you can withdraw them back to your main address:
sui client call --package $PACKAGE_ID --module book --function withdraw_base --args "$POOL_ID" 100 $ACCOUNT2_CAP --type-args $BASE_COIN_TYPE $QUOTE_COIN_TYPE --gas-budget 10000000000 --json
sui client call --package $PACKAGE_ID --module book --function withdraw_quote --args "$POOL_ID" 100 $ACCOUNT2_CAP --type-args $BASE_COIN_TYPE $QUOTE_COIN_TYPE --gas-budget 10000000000 --json