Skip to content

Repository files navigation

Toolkit for interacting with SUI network

Features

  • Transfer SUI, Custom Coin and objects.
  • Move call
  • Programmable transaction
  • Query on-chain data
  • HD wallet multi-accounts

Pre-requisites

@mysten/sui is a peer dependency, so install it alongside sui-kit:

npm install @scallop-io/sui-kit @mysten/sui

How to use

Init SuiKit

import { SuiKit } from '@scallop-io/sui-kit';

// The following types of secret key are supported:
// 1. base64 key from SUI cli keystore file
// 2. 32 bytes hex key
// 3. 64 bytes legacy hex key
const secretKey = '<Secret key>';
const suiKit1 = new SuiKit({ secretKey });

// 12 or 24 words mnemonics
const mnemonics = '<Mnemonics>';
const suiKit2 = new SuiKit({ mnemonics });

// It will create a HD wallet with a random mnemonics
const suiKit3 = new SuiKit();

// Override options
const suiKit = new SuiKit({
  mnemonics: '<Mnemonics>',
  // 'testnet' | 'mainnet' | 'devnet' | 'localnet', default is 'mainnet'
  networkType: 'testnet',
  // the fullnode urls, default is the preconfig fullnode url for the given network type
  // It will rotate the fullnode when the current fullnode is not available
  fullnodeUrls: ['<SUI fullnode1>', '<SUI fullnode2>'],
});

Transfer

You can use SuiKit to transfer SUI, custom coins, and any objects.

const recipient1 = '0x123'; // replace with real address
const recipient2 = '0x456'; // replace with real address

// transfer SUI to single recipient
await suiKit.transferSui(recipient1, 1000);
// transfer SUI to multiple recipients
await suiKit.transferSuiToMany([recipient1, recipient2], [1000, 2000]);

const coinType = '<pkgId>::custom_coin::CUSTOM_COIN';
// Transfer custom coin to single recipient
await suiKit.transferCoin(recipient1, 1000, coinType);
// Transfer custom coin to multiple recipients
await suiKit.transferCoinToMany(
  [recipient1, recipient2],
  [1000, 2000],
  coinType
);

// Transfer objects
const objectIds = ['<objId1>', '<objId2>'];
await suiKit.transferObjects(objectIds, recipient1);

Stake SUI

You can use SuiKit to stake SUI.

/**
 * This is an example of using SuiKit to stake SUI
 */
const stakeAmount = 1000;
const validatorAddress = '0x123'; // replace with real address
suiKit.stakeSui(stakeAmount, validatorAddress).then(() => {
  console.log('Stake SUI success');
});

Move call

You can use SuiKit to call move functions.

const res = await suiKit.moveCall({
  target: '0x2::coin::join',
  arguments: [coin0, coin1],
  typeArguments: [coinType],
});
console.log(res);

How to pass arguments? Suppose you have a move function like this:

public entry fun test_args(
  addrs: vector<address>,
  name: vector<u8>,
  numbers: vector<u64>,
  bools: vector<bool>,
  coins: vector<Coin<SUI>>,
  ctx: &mut TxContext,
) {
  // ...
}

You can pass the arguments like this:

const addr1 =
  '0x656b875c9c072a465048fc10643470a39ba331727719df46c004973fcfb53c95';
const addr2 =
  '0x10651e50cdbb4944a8fd77665d5af27f8abde6eb76a12b97444809ae4ddb1aad';
const coin1 =
  '0xd4a01b597b87986b04b65e04049499b445c0ee901fe8ba310b1cf29feaa86876';
const coin2 =
  '0x4d4a01b597b87986b04b65e04049499b445c0ee901fe8ba310b1cf29feaa8687';
suiKit.moveCall({
  target: `${pkgId}::module::test_args`,
  arguments: [
    // pass vector<address>, need to specify the vecType as 'address'
    { value: [addr1, addr2], vecType: 'address' },
    // pass vector<u8>, need to specify the vecType as 'u8'
    { value: [10, 20], vecType: 'u8' },
    // pass vector<u64>, default vecType for number array is 'u64', so no need to specify
    [34324, 234234],
    // pass vector<bool>, default vecType for boolean array is 'bool', so no need to specify
    [true, false],
    // pass vector<Coin<SUI>>, no need to specify the vecType for object array
    [coin1, coin2],
  ],
});

All the supported types are:

  • address
  • u8
  • u16
  • u32
  • u64
  • u128
  • u256
  • bool
  • object

Programmable transaction

With programmable transaction, you can send a transaction with multiple actions. The following is an example using flashloan to make arbitrage. (check here for the corresponding Move contract code)

import { SuiKit, SuiTxBlock } from '@scallop-io/sui-kit';
import * as process from 'process';
import * as dotenv from 'dotenv';
dotenv.config();

const treasuryA =
  '0xe5042357d2c2bb928f37e4d12eac594e6d02327d565e801eaf9aca4c7340c28c';
const treasuryB =
  '0xdd2f53171b8c886fad20e0bfecf1d4eede9d6c75762f169a9f3c3022e5ce7293';
const dexPool =
  '0x8a13859a8d930f3238ddd31180a5f0914e5b8dbaa31e18387066b61a563fedf9';

const pkgId =
  '0x3c316b6af0586343ce8e6b4be890305a1f83b7e196366f6435b22b6e3fc8e3d9';

(async () => {
  const mnemonics = process.env.MNEMONICS;
  const suiKit = new SuiKit({ mnemonics });
  const sender = suiKit.currentAddress;

  const tx = new SuiTxBlock();
  // 1. Make a flash loan for coinB
  const [coinB, loan] = tx.moveCall(`${pkgId}::custom_coin_b::flash_loan`, [
    treasuryB,
    10 ** 9,
  ]);
  // 2. Swap from coinB to coinA, ratio is 1:1
  const coinA = tx.moveCall(`${pkgId}::dex::swap_a`, [dexPool, coinB]);
  // 3. Swap from coinA back to coinB, ratio is 1:2
  const coinB2 = tx.moveCall(`${pkgId}::dex::swap_b`, [dexPool, coinA]);
  // 4. Repay flash loan
  const [paybackCoinB] = tx.splitCoins(coinB2, [10 ** 9]);
  tx.moveCall(`${pkgId}::custom_coin_b::payback_loan`, [
    treasuryB,
    paybackCoinB,
    loan,
  ]);
  // 5. Transfer profits to sender
  tx.transferObjects([coinB2], sender);

  // 5. Execute transaction
  const res = await suiKit.signAndSendTxn(tx);
  console.log(res);
})();

SuiTxBlock and Transaction

SuiTxBlock extends Transaction from @mysten/sui, so every base method and property is available directly on it — there is no wrapped .txBlock to reach through.

const tx = new SuiTxBlock();
tx.setSender(sender);
const coin = tx.object('<objId>');
const [amount] = tx.splitCoins(tx.gas, [1000]);
const bytes = await tx.build({ client: suiKit.client });

You can also copy or restore a transaction as a SuiTxBlock:

import { Transaction } from '@mysten/sui/transactions';

// copy an existing Transaction (or SuiTxBlock)
const copy = new SuiTxBlock(new Transaction());
// restore from `toJSON()` output or BCS transaction bytes
const restored = SuiTxBlock.from(await tx.toJSON());
// restore from transaction kind bytes
const kind = SuiTxBlock.fromKind(
  await tx.build({ client: suiKit.client, onlyTransactionKind: true })
);

The overridden helpers accept sui-kit's looser argument types, so object ids, object refs and shared object refs work anywhere an object is expected:

tx.splitCoins({ objectId: '<objId>', version: '1', digest: '<digest>' }, [1000]);
tx.mergeCoins('<destObjId>', ['<srcObjId1>', '<srcObjId2>']);
tx.transferObjects(['<objId>'], recipient);

Two differences from the base class to keep in mind:

  • moveCall takes positional arguments (target, args, typeArgs) as shown above; the base { target, arguments, typeArguments } object form is not accepted.
  • Because moveCall narrows the base signature, TypeScript may require a cast when passing a SuiTxBlock to an API typed as Transaction. SuiKit methods such as signTxn, signAndSendTxn, dryRunTxn and inspectTxn accept it directly.

Multi-accounts

SuiKit follows bip32 & bip39 standard, so you can use it to manage multiple accounts. When init SuiKit, you can pass in your mnemonics to create a wallet with multiple accounts.

/**
 * This is an example of using SuiKit to manage multiple accounts.
 */
import { SuiKit } from '@scallop-io/sui-kit';

async function checkAccounts(suiKit: SuiKit) {
  const displayAccounts = async (suiKit: SuiKit, accountIndex: number) => {
    const coinType = '0x2::sui::SUI';
    const addr = suiKit.getAddress({ accountIndex });
    const balance = (await suiKit.getBalance(coinType, { accountIndex }))
      .balance;
    console.log(`Account ${accountIndex}: ${addr} has ${balance} SUI`);
  };
  // log the first 10 accounts
  const numAccounts = 10;
  for (let i = 0; i < numAccounts; i++) {
    await displayAccounts(suiKit, i);
  }
}

async function internalTransferSui(
  suiKit: SuiKit,
  fromAccountIndex: number,
  toAccountIndex: number,
  amount: number
) {
  const toAddr = suiKit.getAddress({ accountIndex: toAccountIndex });
  console.log(
    `Transfer ${amount} SUI from account ${fromAccountIndex} to account ${toAccountIndex}`
  );
  return await suiKit.transferSui(toAddr, amount, {
    accountIndex: fromAccountIndex,
  });
}

const mnemonics = process.env.MNEMONICS;
const suiKit = new SuiKit({ mnemonics });
checkAccounts(suiKit).then(() => {});
// transfer 1000 SUI from account 0 to account 1
internalTransferSui(suiKit, 0, 1, 1000).then(() => {});

Publish & upgrade Move package

We have a standalone npm package to help you publish and upgrade Move package based on sui-kit.

Please refer to the repository: sui-package-kit

Migration Guide

If you're upgrading from v1.x to v2.0.0, please refer to the Migration Guide.

Ask DeepWiki

About

Toolkit for interacting with SUI network in typescript

Topics

Resources

Stars

187 stars

Watchers

17 watching

Forks

Releases

Packages

Used by

Contributors

Languages