Skip to content

UDT (sudt) Operations Tutorial

LinFeng edited this page Jun 20, 2022 · 2 revisions

Simple UDT(SUDT) script is a type script, as the RFC25 says UDT only support two operations issue and transfer. SUDT can work with any lock script, but in practice SUDT mostly work with anyone-can-pay and cheque lock script. The anyone-can-pay lock let user transfer to a cell with some amount of SUDT without require to unlock the cell. The cheque lock let user issue or transfer to some address that unneeded to provide the required CKB capacity. More details please see the rfcs of two lock scripts.

In this tutorial we will demonstrate several scenarios:

  • Issue some SUDT to a cheque address
    • Create a cell with SUDT type script and anyone-can-pay lock script for receiving the SUDT (empty-sudt-acp cell)
    • Claim the SUDT to the new created anyone-can-pay cell
    • Create another empty-sudt-acp cell with different address
    • Transfer a part of the claimed SUDT to the new created empty-sudt-acp cell
  • Issue some SUDT to an anyone-can-pay address
  • Transfer some SUDT to a cheque address (for claim)
    • Claim the SUDT to an anyone-can-pay address
  • Iransfer some SUDT to a cheque address (for withdraw)
    • Withdraw the SUDT to an anyone-can-pay address

Prepare the tuturial

Deploy related contract binaries

First we build the contract binaries.

This will build simple_udt and anyone_can_pay:

git clone https://github.com/nervosnetwork/ckb-production-scripts
cd ckb-production-scripts
git checkout e570c11aff3eca12a47237c21598429088c610d5
git submodule update --init --recursive
make all-via-docker

And the binaries are located in build/simple_udt and build/anyone_can_pay.

This will build cheque:

cargo install ckb-capsule --version=0.4.3
git clone https://github.com/nervosnetwork/ckb-cheque-script.git
cd ckb-cheque-script
git checkout 4ca3e62ae39c32cfcc061905515a2856cad03fd8
git submodule update --init --recursive
cd contracts/ckb-cheque-script/ckb-lib-secp256k1/ckb-production-scripts
git submodule update --init --recursive
cd .. && make all-via-docker
cd ../../..
capsule build

And the binary is located in build/release/ckb-cheque-script

We will use ckb-cli deploy subcommand to deploy the contract binaries

# Since the implementation of the subcomand PR is not merged, we will build it from source
git clone -b add-deploy-subcommand https://github.com/TheWaWaR/ckb-cli.git 
cd ckb-cli
make prod
ln -s $PWD/target/release/ckb-cli $HOME/.cargo/bin/ckb-cli-deploy

Then we copy the binaries to a direcotry and create a deployment config file.

mkdir -p deploy-files/{contracts,migrations}
cd deploy-files
cp ckb-production-scripts/build/simple_udt ckb-production-scripts/build/anyone_can_pay ckb-cheque-script/build/release/ckb-cheque-script contracts
touch deployment.toml

And the deployment.toml will be like this:

[[cells]]
name = "cheque_lock"
enable_type_id = true
location = { file = "contracts/ckb-cheque-script" }

[[cells]]
name = "acp_lock"
enable_type_id = true
location = { file = "contracts/anyone_can_pay" }

[[cells]]
name = "simple_udt"
enable_type_id = true
location = { file = "contracts/simple_udt" }


# reference to on-chain cells, this config is referenced by dep_groups.cells
[[cells]]
name = "secp256k1_data"
enable_type_id = false
location = { tx_hash = "<genesis-cellbase-tx-hash>", index = 3 }
 
# Dep group cells
[[dep_groups]]
name = "cheque_lock_dep"
cells = [
  "secp256k1_data",
  "cheque_lock"
]
[[dep_groups]]
name = "acp_lock_dep"
cells = [
  "secp256k1_data",
  "acp_lock"
]

# Replace with your own lock if you want to unlock deployed cells.
# For example the secp256k1 lock
[lock]
code_hash = "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"
args = "<sighash-address-lock-args>"
hash_type = "type"

# For unlocking inputs with multisig lock script
[multisig_config]
sighash_addresses = [
  # "ckt1qyq111111111111111111111111111111111111111",
  # "ckt1qyq222222222222222222222222222222222222222",
  # "ckt1qyq333333333333333333333333333333333333333",
]
require_first_n = 1
threshold = 2

The genesis-cellbase-tx-hash can get from ckb-cli util genesis-scripts in the output's secp256k1_data field.

The sighash-address-lock-args the your ckb-cli account id.

Then we run follow command to deploy the binaries:

ckb-cli-deploy deploy gen-txs \
    --deployment-config ./deployment.toml \
    --migration-dir ./migrations \
    --from-address <your-account> \
    --info-file ./info.json \
    --sign-now

ckb-cli-deploy deploy apply-txs --migration-dir ./migrations --info-file ./info.json

Create cell_deps.json

The cell_deps.json is a config file to tell ckb-cli the script id (code_hash + hash_type) of a type of script so we can build the script by given args, and also the cell_dep information of the script id. Currently there are only 3 kind of cell_dep are allowed:

  • acp for anyone-can-pay lock script
  • cheque for cheque lock script
  • sudt for simple udt type script

Below is an example content:

{  
  "items": {
    "cheque": {
      "script_id": {
        "hash_type": "type",
        "code_hash": "0xb62470c53fe232c8120f81bd65d27d7d88f1e2bfd5e493d0219a5afd5be3b962"
      },
      "cell_dep": {
        "out_point": {
          "tx_hash": "0x7c444551da09140abf7c8bbb956cea7d3d3feeb9676c29281bb66e505be48e99",
          "index": "0x0"
        },
        "dep_type": "dep_group"
      }
    },
    "acp": {
      "script_id": {
        "hash_type": "type",
        "code_hash": "0x23c28bc9918408180105fdeb173933e8b6bbf200f8e1c286af994e38ba8d3d3e"
      },
      "cell_dep": {
        "out_point": {
          "tx_hash": "0x7c444551da09140abf7c8bbb956cea7d3d3feeb9676c29281bb66e505be48e99",
          "index": "0x1"
        },
        "dep_type": "dep_group"
      }
    },
    "sudt": {
      "script_id": {
        "hash_type": "type",
        "code_hash": "0x662e860980e557e3f3b1fb25cf19729bb86fcbb4344f80746843c6712439c5f0"
      },
      "cell_dep": {
        "out_point": {
          "tx_hash": "0x0cd76992bbc022a7fd37dc8a1765b106b4b42551bd37c242897bdc2e02350fed",
          "index": "0x2"
        },
        "dep_type": "code"
      }
    } 
  }
}

The actual field value should be from deploy-files/migrations/yyyy-mm-dd-xxxx.json

Prepare temporary accounts

Here we need 3 accounts, one is for SUDT owner, and the other two is for normal SDUT account.

mkdir /tmp/tmp-ckb-cli-home
export CKB_CLI_HOME=/tmp/tmp-ckb-cli-home

# account owner
ckb-cli account new
address:
  testnet: ckt1qyq86vaa6e8tsruv5ngcd5tp7lcvcewxy7cquuksvj

# account 1
ckb-cli account new
address:
  testnet: ckt1qyqfjslcvyaay029vvfxtn80rxnwmlma43xscrqn85

# account 2
ckb-cli account new
address:
  testnet: ckt1qyq9qaekmruccau7u3eff4wsv8v74gxmlptqj2lcte

Then we transfer some CKB to those addresses.

ckb-cli wallet transfer --from-account <miner-address> --to-address ckt1qyq86vaa6e8tsruv5ngcd5tp7lcvcewxy7cquuksvj --capacity 10000.0
ckb-cli wallet transfer --from-account <miner-address> --to-address ckt1qyqfjslcvyaay029vvfxtn80rxnwmlma43xscrqn85 --capacity 10000.0
ckb-cli wallet transfer --from-account <miner-address> --to-address ckt1qyq9qaekmruccau7u3eff4wsv8v74gxmlptqj2lcte --capacity 10000.0

SUDT operation scenarios

Issue some SUDT to a cheque address

Issue 2000 SUDT to a cheque address which the sender is the sudt owner and the receiver is account 1.

ckb-cli sudt issue \
    --owner ckt1qyq86vaa6e8tsruv5ngcd5tp7lcvcewxy7cquuksvj \
    --udt-to ckt1qyqfjslcvyaay029vvfxtn80rxnwmlma43xscrqn85:2000 \
    --to-cheque-address \
    --cell-deps ./cell_deps.json

The output:

receivers:
  - address: ckt1qzmzgux98l3r9jqjp7qm6ewj047c3u0zhl27fy7syxd94l2muwukyqfydu2yhskr8feutg9wdvltwqtf3vey5uy2s8u8hdcakd5me8tesxl5c22f0h37sxs5xgzjg
    amount: "2000"
transaction-hash: 0xefe513ae065293c0cce8a962f9c7a0386eaa1d1495e91d93611183dbb1119562

The address is the cheque address. We can use this address to query the amount.

ckb-cli sudt get-amount \
    --owner ckt1qyq86vaa6e8tsruv5ngcd5tp7lcvcewxy7cquuksvj \
    --address ckt1qzmzgux98l3r9jqjp7qm6ewj047c3u0zhl27fy7syxd94l2muwukyqfydu2yhskr8feutg9wdvltwqtf3vey5uy2s8u8hdcakd5me8tesxl5c22f0h37sxs5xgzjg \
    --cell-deps ./cell_deps.json

The output:

cell_count: 1
cells:
  - amount: "2000"
    out_point:
      index: 0x0
      tx_hash: 0xefe513ae065293c0cce8a962f9c7a0386eaa1d1495e91d93611183dbb1119562
total_amount: "2000"

Then we create a cell with SUDT type script and anyone-can-pay lock script (account 1) for receiving the SUDT.

ckb-cli sudt new-empty-acp \
    --owner ckt1qyq86vaa6e8tsruv5ngcd5tp7lcvcewxy7cquuksvj \
    --to ckt1qyqfjslcvyaay029vvfxtn80rxnwmlma43xscrqn85 \
    --cell-deps ./cell_deps.json 

The output:

acp-address: ckt1qq3u9z7fjxzqsxqpqh77k9eex05tdwljqruwrs5x47v5uw96357nuqveg0uxzw7j84zkxyn9enh3nfhdla76cngw3fvxw
transaction-hash: 0xf6f37d3b777a318326d92b5496f28f6d3a4477554de09885172ff45e9a33ecbc

Query the amount of the new created anyone-can-pay cell.

ckb-cli sudt get-amount \
    --owner ckt1qyq86vaa6e8tsruv5ngcd5tp7lcvcewxy7cquuksvj \
    --address ckt1qq3u9z7fjxzqsxqpqh77k9eex05tdwljqruwrs5x47v5uw96357nuqveg0uxzw7j84zkxyn9enh3nfhdla76cngw3fvxw \
    --cell-deps ./cell_deps.json

The output:

cell_count: 1
cells:
  - amount: "0"
    out_point:
      index: 0x0
      tx_hash: 0xf6f37d3b777a318326d92b5496f28f6d3a4477554de09885172ff45e9a33ecbc
total_amount: "0"

Claim the SUDT to the new created anyone-can-pay-cell

ckb-cli sudt cheque-claim \
    --owner ckt1qyq86vaa6e8tsruv5ngcd5tp7lcvcewxy7cquuksvj \
    --sender ckt1qyq86vaa6e8tsruv5ngcd5tp7lcvcewxy7cquuksvj \
    --receiver ckt1qyqfjslcvyaay029vvfxtn80rxnwmlma43xscrqn85 \
    --cell-deps ./cell_deps.json

Query the SUDT amount of account 1.

ckb-cli sudt get-amount \
    --owner ckt1qyq86vaa6e8tsruv5ngcd5tp7lcvcewxy7cquuksvj \
    --address ckt1qq3u9z7fjxzqsxqpqh77k9eex05tdwljqruwrs5x47v5uw96357nuqveg0uxzw7j84zkxyn9enh3nfhdla76cngw3fvxw \
    --cell-deps ./cell_deps.json

The output:

cell_count: 1
cells:
  - amount: "2000"
    out_point:
      index: 0x0
      tx_hash: 0xf29806ea07769381e20479319611a9717c00a09e1cc3a02cf27a25073efa86f9
total_amount: "2000"

Then we create another anyone-can-pay cell (account 2), the output:

acp-address: ckt1qq3u9z7fjxzqsxqpqh77k9eex05tdwljqruwrs5x47v5uw96357nuq2swumd37vvw70wgu556hgxrk025rdls4sn8j5qk
transaction-hash: 0x11a370d042e79f7b48ce2f25d1306a4af91dc15722568873ce7a4efac8ff604b

Transfer a part of the claimd SUDT to new cell.

ckb-cli sudt transfer \
    --owner ckt1qyq86vaa6e8tsruv5ngcd5tp7lcvcewxy7cquuksvj \
    --sender ckt1qq3u9z7fjxzqsxqpqh77k9eex05tdwljqruwrs5x47v5uw96357nuqveg0uxzw7j84zkxyn9enh3nfhdla76cngw3fvxw \
    --udt-to ckt1qq3u9z7fjxzqsxqpqh77k9eex05tdwljqruwrs5x47v5uw96357nuq2swumd37vvw70wgu556hgxrk025rdls4sn8j5qk:600 \
    --to-acp-address \
    --cell-deps ./cell_deps.json

Then if we query the two above anyone-can-pay address the amount will be:

  • account 1: 1400
  • account 2: 600

Issue some SUDT to an anyone-can-pay address

Issue 300 SUDT to account 1's anyone-can-pay address

ckb-cli sudt issue \
    --owner ckt1qyq86vaa6e8tsruv5ngcd5tp7lcvcewxy7cquuksvj \
    --udt-to ckt1qq3u9z7fjxzqsxqpqh77k9eex05tdwljqruwrs5x47v5uw96357nuqveg0uxzw7j84zkxyn9enh3nfhdla76cngw3fvxw:300 \
    --to-acp-address \
    --cell-deps ./cell_deps.json

Then if we query the account 1's amount, it become 1700.

Transfer some SUDT to a cheque address (for claim)

Transfer 500 SUDT from account 1 anyone-can-pay address to account 2 cheque address:

ckb-cli sudt transfer \
    --owner ckt1qyq86vaa6e8tsruv5ngcd5tp7lcvcewxy7cquuksvj \
    --sender ckt1qq3u9z7fjxzqsxqpqh77k9eex05tdwljqruwrs5x47v5uw96357nuqveg0uxzw7j84zkxyn9enh3nfhdla76cngw3fvxw \
    --udt-to ckt1qyq9qaekmruccau7u3eff4wsv8v74gxmlptqj2lcte:500 \
    --to-cheque-address \
    --cell-deps ./cell_deps.json

Then if we query the two above anyone-can-pay address the amount will be:

  • account 1: 1200
  • account 2: 600

Claim the SUDT to account 2:

ckb-cli sudt cheque-claim \
    --owner ckt1qyq86vaa6e8tsruv5ngcd5tp7lcvcewxy7cquuksvj \
    --sender ckt1qyqfjslcvyaay029vvfxtn80rxnwmlma43xscrqn85 \
    --receiver ckt1qyq9qaekmruccau7u3eff4wsv8v74gxmlptqj2lcte \
    --cell-deps ./cell_deps.json

Query two account's amounts again:

  • account 1: 1200
  • account 2: 1100

Transfer some SUDT to a cheque address (for withdraw)

Transfer 500 SUDT from account 1 anyone-can-pay address to account 2 cheque address:

ckb-cli sudt transfer \
    --owner ckt1qyq86vaa6e8tsruv5ngcd5tp7lcvcewxy7cquuksvj \
    --sender ckt1qq3u9z7fjxzqsxqpqh77k9eex05tdwljqruwrs5x47v5uw96357nuqveg0uxzw7j84zkxyn9enh3nfhdla76cngw3fvxw \
    --udt-to ckt1qyq9qaekmruccau7u3eff4wsv8v74gxmlptqj2lcte:500 \
    --to-cheque-address \
    --cell-deps ./cell_deps.json

Query two account's amounts:

  • account 1: 700
  • account 2: 1100

Then we will withdraw after 6 epochs (10800 blocks):

# First change ckb-miner.toml miner.workers.value = 1
ckb miner -l 10800

Withdraw the SUDT amount:

ckb-cli sudt cheque-withdraw \
    --owner ckt1qyq86vaa6e8tsruv5ngcd5tp7lcvcewxy7cquuksvj \
    --sender ckt1qyqfjslcvyaay029vvfxtn80rxnwmlma43xscrqn85 \
    --receiver ckt1qyq9qaekmruccau7u3eff4wsv8v74gxmlptqj2lcte \
    --to-acp-address \
    --cell-deps ./cell_deps.json

Query two account's amounts:

  • account 1: 1200
  • account 2: 1100