|
| 1 | +--- |
| 2 | +description: Learn how to create a SNIP-20 token on Secret Network |
| 3 | +--- |
| 4 | + |
| 5 | +# Secret Tokens (SNIP-20) |
| 6 | + |
| 7 | +### Introduction |
| 8 | + |
| 9 | +In this tutorial, we are going to create our own SNIP-20 token on Secret Network using Secret Labs' SNIP-20 reference implementation contract, and we will learn how to upload, instantiate, execute, and query our SNIP-20 contract using Secret.js. Let's dive in! |
| 10 | + |
| 11 | +### Source Code |
| 12 | + |
| 13 | +You can clone the source code [here](https://github.com/scrtlabs/snip20-reference-impl), which we will reference throughout the course of this documentation. |
| 14 | + |
| 15 | +### Prerequisites |
| 16 | + |
| 17 | +Use [the following guide ](https://docs.scrt.network/secret-network-documentation/development/getting-started/setting-up-your-environment)to set up your developer environment. |
| 18 | + |
| 19 | +### Build and Deploy Contract |
| 20 | + |
| 21 | +Now that you've cloned the SNIP-20 reference implementation repo above, let's compile the contract. In your terminal run `make compile-optimized`. |
| 22 | + |
| 23 | +{% hint style="info" %} |
| 24 | +In Rust, a Makefile can be used to automate tasks such as building the project, running tests, or even generating documentation. **`Make compile-optimized`** is running the following optimizer command, which you can view in the Makefile: |
| 25 | +{% endhint %} |
| 26 | + |
| 27 | +#### Optimizer command |
| 28 | + |
| 29 | +``` |
| 30 | +RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown |
| 31 | +``` |
| 32 | + |
| 33 | +### Configuring Secret.js |
| 34 | + |
| 35 | +1. In your root project folder, create a new folder called `node.` |
| 36 | +2. In your `node` folder, create a new javascript file called`index.js`. |
| 37 | +3. Run `npm init -y` to create a package.json file. |
| 38 | +4. Add `"type" : "module"` to your package.json file. |
| 39 | +5. Install secret.js:`npm i secretjs` |
| 40 | + |
| 41 | +### Uploading the SNIP-20 Contract |
| 42 | + |
| 43 | +In your index.js file, paste the following (be sure to replace the wallet seed phrase with your wallet seed phrase): |
| 44 | + |
| 45 | +{% code overflow="wrap" %} |
| 46 | +```javascript |
| 47 | +import { Wallet, SecretNetworkClient, EncryptionUtilsImpl, fromUtf8, MsgExecuteContractResponse } from "secretjs"; |
| 48 | +import * as fs from "fs"; |
| 49 | + |
| 50 | +const wallet = new Wallet( |
| 51 | + "your walltet seed phrase to go here" |
| 52 | +); |
| 53 | + |
| 54 | +const txEncryptionSeed = EncryptionUtilsImpl.GenerateNewSeed(); |
| 55 | + |
| 56 | +const contract_wasm = fs.readFileSync("../contract.wasm.gz"); |
| 57 | + |
| 58 | +const codeId = 1072; |
| 59 | +const contractCodeHash = "26af567eadde095c909ca6ecf58806235877e5b7ec9bfe30f1057e005f548b17"; |
| 60 | +const contractAddress = "secret1xez6pv463a0elalnj0z53w60fz6tgclv368dw0"; |
| 61 | + |
| 62 | +const secretjs = new SecretNetworkClient({ |
| 63 | + chainId: "pulsar-3", |
| 64 | + url: "https://api.pulsar.scrttestnet.com", |
| 65 | + wallet: wallet, |
| 66 | + walletAddress: wallet.address, |
| 67 | + txEncryptionSeed: txEncryptionSeed |
| 68 | +}); |
| 69 | + |
| 70 | +let upload_contract = async () => { |
| 71 | + let tx = await secretjs.tx.compute.storeCode( |
| 72 | + { |
| 73 | + sender: wallet.address, |
| 74 | + wasm_byte_code: contract_wasm, |
| 75 | + source: "", |
| 76 | + builder: "", |
| 77 | + }, |
| 78 | + { |
| 79 | + gasLimit: 4_000_000, |
| 80 | + } |
| 81 | + ); |
| 82 | + |
| 83 | + const codeId = Number( |
| 84 | + tx.arrayLog.find((log) => log.type === "message" && log.key === "code_id") |
| 85 | + .value |
| 86 | + ); |
| 87 | + |
| 88 | + console.log("codeId: ", codeId); |
| 89 | + // contract hash, useful for contract composition |
| 90 | + const contractCodeHash = (await secretjs.query.compute.codeHashByCodeId({code_id: codeId})).code_hash; |
| 91 | + console.log(`Contract hash: ${contractCodeHash}`); |
| 92 | + } |
| 93 | + |
| 94 | +upload_contract(); |
| 95 | +``` |
| 96 | +{% endcode %} |
| 97 | + |
| 98 | +Run `node index.js` in your terminal to execute the `upload_contract()` function. Upon successful execution, a codeId and contract hash will be returned: |
| 99 | + |
| 100 | +``` |
| 101 | +codeId: 1070 |
| 102 | +Contract hash: 26af567eadde095c909ca6ecf58806235877e5b7ec9bfe30f1057e005f548b17 |
| 103 | +``` |
| 104 | + |
| 105 | +### Instantiating the SNIP-20 Contract |
| 106 | + |
| 107 | +In your index.js file, paste the following: |
| 108 | + |
| 109 | +```javascript |
| 110 | +let instantiate_contract = async () => { |
| 111 | + const initMsg = { |
| 112 | + name: "Zebra", |
| 113 | + symbol: "ZBRA", |
| 114 | + decimals: 6, |
| 115 | + prng_seed: Buffer.from("Something really random").toString("base64"), |
| 116 | + admin: wallet.address, |
| 117 | + initial_balances: [ |
| 118 | + { |
| 119 | + address: wallet.address, |
| 120 | + amount: "1000000000", |
| 121 | + }, |
| 122 | + ], |
| 123 | + }; |
| 124 | + |
| 125 | + let tx = await secretjs.tx.compute.instantiateContract( |
| 126 | + { |
| 127 | + code_id: codeId, |
| 128 | + sender: wallet.address, |
| 129 | + code_hash: contractCodeHash, |
| 130 | + init_msg: initMsg, |
| 131 | + label: " Snip-20 Example" + Math.ceil(Math.random() * 10000), |
| 132 | + }, |
| 133 | + { |
| 134 | + gasLimit: 400_000, |
| 135 | + } |
| 136 | + ); |
| 137 | + |
| 138 | + //Find the contract_address in the logs |
| 139 | + const contractAddress = tx.arrayLog.find( |
| 140 | + (log) => log.type === "message" && log.key === "contract_address" |
| 141 | + ).value; |
| 142 | + |
| 143 | + console.log(contractAddress); |
| 144 | + }; |
| 145 | + |
| 146 | + instantiate_contract(); |
| 147 | +``` |
| 148 | + |
| 149 | +The `initMsg` object in our `index.js` file is referencing the instantiation message defined in [msg.rs at line 20](https://github.com/scrtlabs/snip20-reference-impl/blob/81ad9714e50b890a50d8394dcac718950da127b6/src/msg.rs#L20). Notice that we chose to omit the optional `config` variable. If we include `config`, there is a variety of additional contract functionality that we could program, such as burn, mint, admin privileges, etc [as seen here](https://github.com/scrtlabs/snip20-reference-impl/blob/81ad9714e50b890a50d8394dcac718950da127b6/src/msg.rs#L42). |
| 150 | + |
| 151 | +Now we are going to instantiate some ZBRA coin. If you want to create your own coin name, update the `name, symbol,` and `amount` fields respectively. Be sure to comment out `upload_contract()` and now run `node index.js` to call `instantiate_contract()`. Upon successful execution, a contract address will be returned: |
| 152 | + |
| 153 | +``` |
| 154 | +secret1xez6pv463a0elalnj0z53w60fz6tgclv368dw0 |
| 155 | +``` |
| 156 | + |
| 157 | +### Query the Token Info |
| 158 | + |
| 159 | +To check that the instantiation of our SNIP-20 ZEBRA token was successful, let's query the smart contract's token info: |
| 160 | + |
| 161 | +```javascript |
| 162 | +let query_token_info = async () => { |
| 163 | + const tokenInfoQuery = await secretjs.query.compute.queryContract({ |
| 164 | + contract_address: contractAddress, |
| 165 | + query: { |
| 166 | + token_info: {}, |
| 167 | + }, |
| 168 | + code_hash: contractCodeHash, |
| 169 | + }); |
| 170 | + |
| 171 | + console.log(tokenInfoQuery); |
| 172 | +}; |
| 173 | +query_token_info(); |
| 174 | +``` |
| 175 | + |
| 176 | +The following is returned upon successful query: |
| 177 | + |
| 178 | +``` |
| 179 | +token_info: { name: 'Zebra', symbol: 'ZBRA', decimals: 6, total_supply: null } |
| 180 | +} |
| 181 | +``` |
| 182 | + |
| 183 | +{% hint style="info" %} |
| 184 | +The reason `total supply` is `null` is because we chose to make `total supply` hidden in our instantiation message. If you want it to be public, then in the[ `InitConfig` variable](https://github.com/scrtlabs/snip20-reference-impl/blob/81ad9714e50b890a50d8394dcac718950da127b6/src/msg.rs#L45) set `public_total_supply` to true. |
| 185 | +{% endhint %} |
| 186 | + |
| 187 | +### SNIP-20 Contract Messages |
| 188 | + |
| 189 | +Now that we have successfully instantiated our SNIP-20 contract, let's send an [execution message](https://github.com/scrtlabs/snip20-reference-impl/blob/81ad9714e50b890a50d8394dcac718950da127b6/src/msg.rs#L91) to better understand the contract's functionality. |
| 190 | + |
| 191 | +Start by adding the token to your Keplr wallet. Click on Keplr, select the hamburger icon, select "Add Token", and then paste in your token's contract address. If you need to fund your wallet to execute the transaction, you can do so using the [pulsar-3 faucet here](https://faucet.pulsar.scrttestnet.com/). You should now see your token in your Keplr wallet! |
| 192 | + |
| 193 | +<figure><img src="../../../../.gitbook/assets/Screen Shot 2023-04-10 at 2.08.50 PM.png" alt=""><figcaption><p>keplr wallet with ZBRA token</p></figcaption></figure> |
| 194 | + |
| 195 | +Let's [transfer some tokens](https://github.com/scrtlabs/snip20-reference-impl/blob/81ad9714e50b890a50d8394dcac718950da127b6/src/msg.rs#L107) to another wallet address. The transfer message is defined in msg.rs as follows: |
| 196 | + |
| 197 | +```javascript |
| 198 | + Transfer { |
| 199 | + recipient: String, |
| 200 | + amount: Uint128, |
| 201 | + memo: Option<String>, |
| 202 | + decoys: Option<Vec<Addr>>, |
| 203 | + entropy: Option<Binary>, |
| 204 | + padding: Option<String>, |
| 205 | + } |
| 206 | +``` |
| 207 | + |
| 208 | +Now let's execute the transfer message with secret.js. Be sure to update the `recipient` wallet address with your own wallet before executing the code below. For testing purposes, I am using two Keplr wallet connected to the Secret Network testnet in order to move funds back and forth: |
| 209 | + |
| 210 | +```javascript |
| 211 | +let transfer_snip20 = async (receiver_wallet) => { |
| 212 | + let executeMsg = { |
| 213 | + transfer: { |
| 214 | + owner: wallet.address, |
| 215 | + amount: "10000000", |
| 216 | + recipient: receiver_wallet, |
| 217 | + }, |
| 218 | + }; |
| 219 | + |
| 220 | + let tx = await secretjs.tx.compute.executeContract( |
| 221 | + { |
| 222 | + sender: wallet.address, |
| 223 | + contract_address: contractAddress, |
| 224 | + code_hash: contractCodeHash, |
| 225 | + msg: executeMsg, |
| 226 | + }, |
| 227 | + { |
| 228 | + gasLimit: 100_000, |
| 229 | + } |
| 230 | + ); |
| 231 | + console.log(tx); |
| 232 | +}; |
| 233 | + |
| 234 | +transfer_snip20("secret1f9zykwvwc6jyhv6dtsjwx03e92j08nyffwuwcu"); |
| 235 | +``` |
| 236 | + |
| 237 | +Congrats! You just successfully transferred your own SNIP-20 token on Secret Network! 🎉 |
0 commit comments