// NOTE: In this example, we use the same mnemonic for both user and validator.
// The reason is that we want to simplify the example.
// This will make it easier to whitelist during the proposal.
// It takes a bit of time to whitelist, so you can skip this step 3 and do it manually.
// Some possible errors: //
location=0000000000000000000000000000000000000000000000000000000000000001::object,
code=524289 -> The object (pair) is already created, skip step 1 //
location=0000000000000000000000000000000000000000000000000000000000000001::object,
code=393218 -> The object (pair) is not created yet, retry step 1
import {
AccAddress,
bcs,
RESTClient,
MnemonicKey,
MsgDelegate,
MsgExecute,
MsgSubmitProposal,
MsgVote,
MsgWhitelist,
MsgWithdrawDelegatorReward,
Wallet,
} from '@initia/initia.js'
import {
ProposalStatus,
VoteOption,
} from '@initia/initia.proto/cosmos/gov/v1/gov'
import { delay } from 'bluebird'
import { sha3_256 } from '@noble/hashes/sha3'
import { concatBytes, toBytes } from '@noble/hashes/utils'
import { toHex } from '@cosmjs/encoding'
import { MsgUndelegate } from 'vm/move/msgs/staking'
const user = new Wallet( new RESTClient('http://localhost:1317', { gasPrices:
'0.015uinit', gasAdjustment: '1.75' }), new MnemonicKey({ // TODO: put your
mnemonic here mnemonic: 'mimic exist actress ...' }) )
const validator = new Wallet( new RESTClient('http://localhost:1317', {
gasPrices: '0.015uinit', gasAdjustment: '1.75' }), new MnemonicKey({ // TODO:
put your mnemonic here mnemonic: 'mimic exist actress ...' }) )
function coinMetadata(creator: string, symbol: string) { const
OBJECT_FROM_SEED_ADDRESS_SCHEME = 0xfe const addrBytes =
bcs.address().serialize(creator).toBytes() const seed = toBytes(symbol) const
bytes = new Uint8Array([...concatBytes(addrBytes, seed),
OBJECT_FROM_SEED_ADDRESS_SCHEME]) const sum =
sha3_256.create().update(bytes).digest() return toHex(sum) }
async function getLastProposalId(restClient: RESTClient): Promise<number> {
const [proposals, pagination] = await restClient.gov.proposals() if
(proposals.length === 0) return 0 return proposals[proposals.length - 1].id }
async function getProposalStatus(restClient: RESTClient, proposalId: number):
Promise<ProposalStatus | null> { const proposal = await
restClient.gov.proposal(proposalId) return proposal ? proposal.status : null }
async function checkProposalPassed(restClient: RESTClient, proposalId: number):
Promise<void> { for (;;) {
console.log(`checking proposal ${proposalId} status... in ${restClient.URL}/cosmos/gov/v1/proposals/${proposalId}`)
const status = await getProposalStatus(restClient, proposalId)
if (status === ProposalStatus.PROPOSAL_STATUS_PASSED) return
if (status === ProposalStatus.PROPOSAL_STATUS_REJECTED) throw new Error(`proposal ${proposalId} rejected`)
if (status === ProposalStatus.PROPOSAL_STATUS_FAILED) throw new Error(`proposal ${proposalId} failed`)
await delay(5_000)
} }
async function provideLiquidity( lp_metadata: string, coin_a_amount: number,
coin_b_amount: number, min_liquidity: number | null ) { const msg = new
MsgExecute( user.key.accAddress, '0x1', 'dex', 'provide_liquidity_script', [], [
bcs.string().serialize(lp_metadata).toBase64(),
bcs.u64().serialize(coin_a_amount).toBase64(),
bcs.u64().serialize(coin_b_amount).toBase64(),
bcs.option(bcs.u64()).serialize(min_liquidity).toBase64() ] )
const signedTx = await user.createAndSignTx({ msgs: [msg] }) await
user.rest.tx.broadcast(signedTx).catch(console.log) }
async function createPairScript( sender: Wallet, name: string, symbol: string,
swap_fee_rate: number, coin_a_weight: number, coin_b_weight: number,
coin_a_metadata: string, coin_b_metadata: string, coin_a_amount: number,
coin_b_amount: number ) { const msg = new MsgExecute( sender.key.accAddress,
'0x1', 'dex', 'create_pair_script', [], [
bcs.string().serialize(name).toBase64(),
bcs.string().serialize(symbol).toBase64(),
bcs.bigdecimal().serialize(swap_fee_rate).toBase64(),
bcs.bigdecimal().serialize(coin_a_weight).toBase64(),
bcs.bigdecimal().serialize(coin_b_weight).toBase64(),
bcs.object().serialize(coin_a_metadata).toBase64(),
bcs.object().serialize(coin_b_metadata).toBase64(),
bcs.u64().serialize(coin_a_amount).toBase64(),
bcs.u64().serialize(coin_b_amount).toBase64() ] )
const signedTx = await sender.createAndSignTx({ msgs: [msg] }) await
sender.rest.tx.broadcast(signedTx).catch(console.log) }
async function whitelistLP(lpMetadata: string) { const msgWhiteList = new
MsgWhitelist( 'init10d07y265gmmuvt4z0w9aw880jnsr700j55nka3', // authority
AccAddress.fromHex(lpMetadata), // metadata '1000000000000000000' // weight for
reward (10^18) )
const proposal = new MsgSubmitProposal( [msgWhiteList], '100000000uinit', //
deposit user.key.accAddress, // proposer 'uinit', // metadata 'awesome
proposal', // title 'it is awesome', // summary false // expedited )
const proposalId = (await getLastProposalId(user.rest)) + 1
// if there's only one validator, it will exceed the quorum (cause we set as
user = validator) const vote = new MsgVote(proposalId, user.key.accAddress,
VoteOption.VOTE_OPTION_YES, '') const signedTx = await user.createAndSignTx({
msgs: [proposal, vote]})
await user.rest.tx.broadcast(signedTx).catch(console.log) await
checkProposalPassed(user.rest, proposalId) }
async function delegateLP( lpMetadata: string, amount: number ) { // we can get
lp denom from lp metadata by adding 'move/' prefix // if lp metadata is
ff5c7412979... // then lp denom is move/ff5c7412979... const msg = new
MsgDelegate( user.key.accAddress, // delegator validator.key.valAddress, //
validator `${amount}move/${lpMetadata}` // lp token )
const signedTx = await user.createAndSignTx({ msgs: [msg] }) await
user.rest.tx.broadcast(signedTx).catch(console.log) }
async function withdrawRewards() { const msg = new MsgWithdrawDelegatorReward(
user.key.accAddress, validator.key.valAddress )
const signedTx = await user.createAndSignTx({ msgs: [msg] }) await
user.rest.tx.broadcast(signedTx).catch(console.log }
// NOTE: if you uncomment step 2, there will be an error // because it takes a
bit of time to create a pair async function main() { console.log('user:',
user.key.accAddress) console.log('validator:', validator.key.valAddress)
// step 1: create pair script await createPairScript( user, 'init-usdc',
'init-usdc', 0.003, 0.5, 0.5, coinMetadata('0x1', 'uinit'), coinMetadata('0x1',
'uusdc'), 100_000_000, 100_000_000 )
const lpMetadata = coinMetadata(user.key.accAddress, 'init-usdc') //
ff5c7412979176c5e7f084a6... console.log('step 1 done, lp metadata:', lpMetadata)
// step 2 (optional): provide liquidity // you will get LP tokens when you
create a pair, so you can skip this step // await provideLiquidity( //
lpMetadata, // 100_000_000, // 100_000_000, // 100_000 // ) // console.log('step
2 provide liquidity done')
// step 3: whitelist LP // this step could take a while (check your
'expedited_voting_period' time in genesis.json) await whitelistLP(lpMetadata)
console.log('step 3 whitelist done')
// step 4: delegate LP tokens to the validator await delegateLP(lpMetadata,
100_000) console.log('step 4 delegate done')
// step 5: withdraw rewards // await withdrawRewards() // console.log('step 5
withdraw done') }
if (require.main === module) { main() }