Quick Start

This section gives you a working example on how to quickly open and close a position.

import { SudoAPI, OracleAPI, SudoDataAPI, IPositionInfo, IPositionConfig, getConsts, parseSymbolKey, joinSymbol } from 'sudo-sdk';
import { Transaction } from '@mysten/sui/transactions';
import { SuiClient } from '@mysten/sui/client';
import { decodeSuiPrivateKey } from '@mysten/sui.js/cryptography';
import {
  Ed25519Keypair,
} from '@mysten/sui.js/keypairs/ed25519';
import { useCallback, useEffect, useState } from 'react';

type SudoApiCall = (api: SudoAPI) => Promise<Transaction>;
interface ITokenBalance {
  symbol: string;
  balance: string;
  objectId: string;
}

const privateKey = <yourPrivateKey>;
const rpc = <yourRpc>;
const network = 'mainnet';
const GAS_BUDGET = 100000000;

const { secretKey } = decodeSuiPrivateKey(privateKey);
const kpImport0 = Ed25519Keypair.fromSecretKey(secretKey);
const pk = kpImport0.getPublicKey();
const signer = pk.toSuiAddress();

const provider = new SuiClient({ url: rpc });
const consts = getConsts(network);

const openPosition = true;
const mode = 'long';
const orderType = 'limit';

const collateralToken = 'USDC';
const indexToken = 'SUI';
const leverage = 10;
const payAmount = 100;
const symbol = joinSymbol(mode, indexToken);

const amountToDecrease = 100;
const indexPrice = 100;
const priceSlippage = 0.003;
const collateralSlippage = 0.5;
const RELAYER_FEE = 1;

export function sudo_sdk_sample() {
  const [prices, setPrices] = useState<{ [key: string]: number }>({});
  const [positions, setPositions] = useState<IPositionInfo[]>([]);
  const [positionConfigMap, setPositionConfigMap] = useState<{
    [key: string]: IPositionConfig;
  }>({});

  useEffect(() => {
    const oracleAPI = new OracleAPI(network);

    Promise.all([
      oracleAPI.subOraclePrices(
        Array.from(
          new Set([...Object.keys(oracleAPI.consts.pythFeeder.feeder)]),
        ),
        priceInfo => {
          setPrices(prevPrice => ({
            ...prevPrice,
            [priceInfo.id]: priceInfo
              .getPriceUnchecked()
              .getPriceAsNumberUnchecked(),
          }));
        },
      ),
      
    ]);
  }, [network]);

  const fetchPositions = useCallback(async () => {
    const dataAPI = new SudoDataAPI(network, provider);
    const capInfoList = await dataAPI.getPositionCapInfoList(signer);
    const infoList = await dataAPI.getPositionInfoList(
      capInfoList,
      signer,
    );
    setPositions(infoList);

  }, [signer, network, setPositions]);

  useEffect(() => {
    fetchPositions();
  }, [fetchPositions]);

  const fetchPositionConfigs = useCallback(async () => {
    const symbols = Object.keys(consts.sudoCore.symbols),
    if (symbols.length === 0) return;
  
    const dataAPI = new SudoDataAPI(network, provider);

    await Promise.all([
      ...symbols.map(async symbol => {
        const [direction, indexToken] = parseSymbolKey(symbol);
        const config = await dataAPI.getPositionConfig(
          indexToken,
          direction === 'long',
        );
        setPositionConfigMap(prevMap => ({
          ...prevMap,
          [`sudo-${symbol}`]: config,
        }));
      }),
    ]);
  }, [
    network,
    setPositionConfigMap,
  ]);

  useEffect(() => {
    fetchPositionConfigs();
  }, [fetchPositionConfigs]);

  const executeSudoApiCall = async (
    apiCall: SudoApiCall,
  ) => {
    const sudoAPI = new SudoAPI(network, provider);
    const txb = await apiCall(sudoAPI);
    
    txb.setSender(signer);
    txb.setGasBudget(GAS_BUDGET);
    const bytes = await txb.build({ client: provider });
    const serializedSignature = (await kpImport0.signTransactionBlock(bytes)).signature;
  
    const res = await provider.executeTransactionBlock({
      transactionBlock: bytes,
      signature: serializedSignature,
      options: {
        showEffects: false,
        showEvents: false,
      },
    });
  };
  
  async function getCoins(
    owner: string,
    coinType: string,
  ) {
    let _continue = true;
    let cursor = null;
    let coins: ITokenBalance[] = [];
    while (_continue) {
      const tmp: any = await provider.getCoins({ owner, coinType, cursor });
      if (tmp.hasNextPage) {
        cursor = tmp.nextCursor;
      } else {
        _continue = false;
      }
      coins = coins.concat(
        tmp.data.map((coin: any) => ({
          // eslint-disable-next-line
          symbol: coin.coinType.split('::')[2],
          balance: coin.balance.toString(),
          objectId: coin.coinObjectId,
        })),
      );
    }
    return coins;
  }
  
  if (openPosition) {
    const coins = await getCoins(
      signer,
      consts.coins[collateralToken].module,
    );
    const coinObjects = coins.map(e => e.objectId);
  
    executeSudoApiCall(
      async sudoApi => {
        return sudoApi.openPosition(
          collateralToken,
          indexToken,
          leverage,
          payAmount,
          positionConfigMap[`sudo-${symbol}`],
          coinObjects,
          mode === 'long',
          prices[indexToken],
          prices[collateralToken],
          priceSlippage,
          collateralSlippage,
          orderType === 'limit',
          false,
          orderType === 'limit' ? BigInt(RELAYER_FEE * 1e9) : BigInt(1),
        );
      }
    ).finally(() => {});
  } else {
    executeSudoApiCall(
      async sudoApi => {
        return sudoApi.decreasePosition(
          positions[0].id,
          positions[0].collateralToken,
          positions[0].indexToken,
          positions[0].positionAmount,
          amountToDecrease,
          positions[0].long,
          prices[positions[0].indexToken],
          indexPrice || 0,
          prices[positions[0].collateralToken],
          orderType === 'limit',
          false,
          priceSlippage,
          collateralSlippage,
          orderType === 'limit' ? BigInt(RELAYER_FEE * 1e9) : BigInt(1),
        );
      }
    ).finally(() => {});
  }  
}

Last updated