npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@synfutures/oyster-sdk

v0.2.29

Published

SynFutures V3 SDK

Downloads

627

Readme

SynFutures V3 SDK

SDK for SynFutures V3

💻 Getting Started

npm:

npm install @synfutures/oyster-sdk

yarn:

yarn add @synfutures/oyster-sdk

📝 Environment Variables Requirement

This package depends on the more fundamental package @derivation-tech/web3-core. Therefore, some environment variables need to be provided as required during runtime. Please refer to here.

👀 Note

  1. Before carrying out operations like trade, place, etc., please check the user's allowance for Gate.
  2. When interacting with Gate (such as deposit, withdraw), the precision of the token is its own precision. However, when interacting with Instrument, the precision of the token is fixed at 18.

ABIs

The SDK provides ABIs of several important contracts to facilitate your interaction with SynFuturesV3. You can obtain them in the following ways:

  • Extract the original JSON files directly from the SDK. The paths are as follows:
/oyster-sdk/src/abis/CexMarket.json
/oyster-sdk/src/abis/Config.json
/oyster-sdk/src/abis/DexV2Market.json
/oyster-sdk/src/abis/Gate.json
/oyster-sdk/src/abis/Instrument.json
/oyster-sdk/src/abis/Observer.json
  • Use them directly from the SDK
import { INSTRUMENT_ABI } from '@synfutures/oyster-sdk';
import { Contract } from 'ethers';

// Print the ABI of the Instrument contract
console.info(JSON.stringify(INSTRUMENT_ABI, null, 2));

// Use INSTRUMENT_ABI directly from the SDK
new Contract(address, INSTRUMENT_ABI, signerOrProvider);

Examples

  1. Query instruments and pairs information
  2. Query accout information
  3. Deposit to Gate
  4. Withdraw from Gate
  5. Trade
  6. Adjust position, withdraw half of the available margin
  7. Place order
  8. Batch place orders
  9. Bulk cancel order
  10. Add liquidity
  11. Add asymmetric liquidity
  12. Remove liquidity
  13. Query user operation history
  14. Query pair kline chart data
  15. Query pair depth chart data
  16. Query pair funding rate data
  17. Query user single pair info
  18. Get Volume Chart
  19. Query Account Portfolio Info
  20. Query Account Range History
  21. Query Asset Transfer History
  22. Query Deposit Withdraw History
  23. Estimate Earning APY
  24. Query market info
  25. Direct trade interface
  26. Direct place interface
  27. Direct add interface
  28. Direct remove interface
  29. Direct cancel interface
  30. Update pair
  31. Settle trader
  32. Parse tx
  33. Restrictions on add, trade and place
  34. Query subgraph using referral code as filter

Prerequisites

To successfully run the blew examples:

  • Please put BLAST_RPC=https://rpc.ankr.com/blast into the .env file where you run your command.
  • Also set your private key ALICE_PRIVATE_KEY=your_own_private_key if you want to send transactions.
  • Or you can set your provider and wallet directly in the code.
const sdk = SynFuturesV3.getInstance('blast');
sdk.setProvider(new ethers.providers.JsonRpcProvider('https://rpc.ankr.com/blast'));
const keystore = readFileSync('keystore.json', 'utf-8');
const signer1 = ethers.Wallet.fromEncryptedJsonSync(keystore, 'password');
const signer2 = ethers.Wallet.fromMnemonic(
    'test test test test test test test test test test test test',
    "m/44'/60'/0'/0/1",
);
const signer3 = new ethers.Wallet('private key');

Query instruments and pairs information

import { ethers } from 'ethers';
import { InstrumentCondition, SynFuturesV3 } from '@synfutures/oyster-sdk';

async function main() {
    const sdk = SynFuturesV3.getInstance('blast');

    const instruments = await sdk.getAllInstruments();

    for (const instrument of instruments) {
        // only show instruments spot price in NORMAL condition
        if (instrument.state.condition === InstrumentCondition.NORMAL) {
            console.log(instrument.info.symbol, ethers.utils.formatEther(instrument.spotPrice));
        }
        // show all pairs symbol, mark price and fair price
        for (const [expiry, pair] of instrument.pairs) {
            console.log(
                pair.symbol,
                expiry,
                ethers.utils.formatEther(pair.markPrice),
                ethers.utils.formatEther(pair.fairPriceWad),
            );
        }
    }
}

// ts-node src/demo.ts
main().catch(console.error);

Query accout information

import { ethers } from 'ethers';
import { SynFuturesV3, PERP_EXPIRY } from '@synfutures/oyster-sdk';

async function main() {
    const sdk = SynFuturesV3.getInstance('blast');

    // get signer address
    const signer = process.argv[2];

    const instruments = await sdk.getAllInstruments();

    function getInstrumentBySymbol(symbol: string) {
        const instrument = instruments.find((i) => i.info.symbol === symbol);
        if (!instrument) {
            throw new Error('unknown symbol: ' + symbol);
        }
        return instrument;
    }

    const instrument = getInstrumentBySymbol('BTC-USDB-PYTH');

    // get user account
    const account = await sdk.getPairLevelAccount(signer, instrument.info.addr, PERP_EXPIRY);

    console.log(
        `Position balance: ${ethers.utils.formatUnits(account.position.balance)}, size: ${ethers.utils.formatUnits(
            account.position.size,
        )}, entryNotional: ${ethers.utils.formatUnits(
            account.position.entryNotional,
        )}, entrySocialLossIndex: ${ethers.utils.formatUnits(
            account.position.entrySocialLossIndex,
        )}, entryFundingIndex: ${ethers.utils.formatUnits(account.position.entryFundingIndex, 18)}`,
    );

    for (const order of account.orders) {
        console.log(
            `Order id: ${order.oid}, size: ${ethers.utils.formatUnits(
                order.size,
                18,
            )}, balance: ${ethers.utils.formatUnits(order.balance, 18)}, tick: ${order.tick}, nonce: ${order.nonce}`,
        );
    }

    for (const range of account.ranges) {
        console.log(
            `Range id: ${range.rid}, size: ${ethers.utils.formatUnits(range.balance, 18)}, from: ${
                range.tickLower
            }, to: ${range.tickLower}`,
        );
    }
}

// ts-node src/demo.ts 0xYOUR_ACCOUNT_ADDRESS_HERE
main().catch(console.error);

Deposit to Gate

import { ethers } from 'ethers';
import { SynFuturesV3 } from '@synfutures/oyster-sdk';

async function main() {
    const sdk = SynFuturesV3.getInstance('blast');

    // get your own signer
    const signer = new ethers.Wallet(process.env.ALICE_PRIVATE_KEY as string, sdk.ctx.provider);

    // get USDB token info
    const usdb = await sdk.ctx.getTokenInfo('USDB');

    // approve
    await sdk.ctx.erc20.approveIfNeeded(
        signer,
        usdb.address,
        sdk.config.contractAddress.gate,
        ethers.constants.MaxUint256,
    );

    // deposit
    await sdk.deposit(signer, usdb.address, ethers.utils.parseUnits('10', usdb.decimals));

    console.log('Deposit 10 USDB to gate');
}

main().catch(console.error);

Withdraw from Gate

import { SynFuturesV3 } from '@synfutures/oyster-sdk';
import { ethers } from 'ethers';

async function main() {
    const sdk = SynFuturesV3.getInstance('blast');

    // get your own signer
    const signer = new ethers.Wallet(process.env.ALICE_PRIVATE_KEY as string, sdk.ctx.provider);

    // 1. withdraw USDB
    // get USDB token info
    const usdb = await sdk.ctx.getTokenInfo('USDB');
    await sdk.withdraw(signer, usdb.address, ethers.utils.parseUnits('10', usdb.decimals));
    console.log('Withdraw 10 USDB from the gate');

    // 2. withdraw WETH
    await sdk.withdraw(
        signer,
        sdk.ctx.wrappedNative.address,
        ethers.utils.parseUnits('0.01', await sdk.ctx.wrappedNative.decimals()),
    );

    // 3. withdraw all WETH to ETH
    await sdk.withdraw(
        signer,
        NATIVE_TOKEN_ADDRESS,
        await sdk.contracts.gate.reserveOf(sdk.ctx.wrappedNative.address, signer.address),
    );
}

main().catch(console.error);

Trade

import { ethers } from 'ethers';
import { SynFuturesV3, PERP_EXPIRY, Side } from '@synfutures/oyster-sdk';

async function main() {
    const sdk = SynFuturesV3.getInstance('blast');

    await sdk.init();

    // get your own signer
    const signer = new ethers.Wallet(process.env.ALICE_PRIVATE_KEY as string, sdk.ctx.provider);

    const instruments = await sdk.getAllInstruments();

    function getInstrumentBySymbol(symbol: string) {
        const instrument = instruments.find((i) => i.info.symbol === symbol);
        if (!instrument) {
            throw new Error('unknown symbol: ' + symbol);
        }
        return instrument;
    }

    const instrument = getInstrumentBySymbol('BTC-USDB-PYTH');

    const pair = instrument.pairs.get(PERP_EXPIRY)!;

    // inquire quotation and how much BTC is equivalent to 500 USDB
    const { baseAmount, quotation } = await sdk.inquireByQuote(pair, Side.LONG, ethers.utils.parseUnits('500', 18));

    // update cache for signer
    await sdk.syncVaultCacheWithAllQuotes(await signer.getAddress());

    // simulate result
    const {
        tradePrice, // average trade price in quote token
        estimatedTradeValue, // traded value in quote token, i.e. entryNotional
        minTradeValue, // if no existing position, return a minimum trade value related to instrument imr
        tradingFee, // trading fee in quote token, excluding stability fee
        stabilityFee, // stability fee in quote token for pushing the fair price away from mark
        margin, // required margin in quote token
        leverageWad, // leverage in WAD format
        priceImpactWad, // price impact = (postFair - preFair) / preFair, represented in WAD format
        realized, // realized pnl, funding and social loss counted
        simulationMainPosition, // main position after trade
        marginToDepositWad, // if current margin is not enough, margin to deposit in WAD format
        limitTick, // average price limit represented in tick, if trade price is out of limit price, tx will revert
        exceedMaxLeverage, // return true if leverage exceeds max leverage
    } = sdk.simulateTrade(
        await sdk.getPairLevelAccount(await signer.getAddress(), instrument.info.addr, PERP_EXPIRY),
        quotation,
        Side.LONG,
        baseAmount,
        undefined, // we want to estimate the required margin, so pass in undefined here
        ethers.utils.parseUnits('4', 18), // leverage, precision is 18
        100, // slippage, 100 means 100 / 10000 = 1%
    );

    // trade
    await sdk.intuitiveTrade(
        signer,
        pair,
        Side.LONG,
        baseAmount,
        margin, // required margin
        tradePrice,
        100, // slippage, 100 means 100 / 10000 = 1%
        Math.floor(Date.now() / 1000) + 300, // deadline, set to 5 minutes later
    );

    // use referral code
    // NOTICE: channel code must be 6 bytes long
    const channel = '8test8';
    const getReferralCode = (channel: string): string => {
        return '\xff\xff' + channel; // 0xffff means you are sending tx using SDK and private key
    };
    await sdk.intuitiveTrade(
        signer,
        pair,
        Side.LONG,
        baseAmount,
        margin, // required margin
        tradePrice,
        100, // slippage, 100 means 100 / 10000 = 1%
        Math.floor(Date.now() / 1000) + 300, // deadline, set to 5 minutes later,
        {}, // here is overrides, you can pass in custom overrides for gas price and limit
        getReferralCode(channel),
    );

    console.log(
        `Open a long position of ${ethers.utils.formatEther(
            baseAmount,
        )} BTC(≈ 500 USDB) with ${ethers.utils.formatUnits(margin, 18)} USDB and ${ethers.utils.formatUnits(
            leverageWad,
        )} leverage`,
    );
}

main().catch(console.error);

Adjust position, withdraw half of the available margin

import { ethers } from 'ethers';
import { SynFuturesV3, PERP_EXPIRY } from '@synfutures/oyster-sdk';

async function main() {
    const sdk = SynFuturesV3.getInstance('blast');

    await sdk.init();

    // get your own signer
    const signer = new ethers.Wallet(process.env.ALICE_PRIVATE_KEY as string, sdk.ctx.provider);

    const instruments = await sdk.getAllInstruments();

    function getInstrumentBySymbol(symbol: string) {
        const instrument = instruments.find((i) => i.info.symbol === symbol);
        if (!instrument) {
            throw new Error('unknown symbol: ' + symbol);
        }
        return instrument;
    }

    const instrument = getInstrumentBySymbol('BTC-USDB-PYTH');

    const pair = instrument.pairs.get(PERP_EXPIRY)!;

    const account = await sdk.getPairLevelAccount(await signer.getAddress(), instrument.info.addr, PERP_EXPIRY);

    // calculate the maximum amount of margin that can be withdrawn
    const available = account.position.getMaxWithdrawableMargin();

    await sdk.adjustMargin(
        signer,
        pair,
        false, // we try to withdraw the margin, so pass false here, otherwise pass true
        available.div(2), // withdraw half of the available margin
        Math.floor(Date.now() / 1000) + 300, // deadline, set to 5 minutes later
    );

    console.log(`Withdraw ${ethers.utils.formatUnits(available.div(2), 18)} USDB margin`);
}

main().catch(console.error);

Place order

import { ethers } from 'ethers';
import { SynFuturesV3, PERP_EXPIRY, Side, TickMath } from '@synfutures/oyster-sdk';

async function main() {
    const sdk = SynFuturesV3.getInstance('blast');

    await sdk.init();

    // get your own signer
    const signer = new ethers.Wallet(process.env.ALICE_PRIVATE_KEY as string, sdk.ctx.provider);

    const instruments = await sdk.getAllInstruments();

    function getInstrumentBySymbol(symbol: string) {
        const instrument = instruments.find((i) => i.info.symbol === symbol);
        if (!instrument) {
            throw new Error('unknown symbol: ' + symbol);
        }
        return instrument;
    }

    const instrument = getInstrumentBySymbol('BTC-USDB-PYTH');

    const pair = instrument.pairs.get(PERP_EXPIRY)!;

    // update cache for signer
    await sdk.syncVaultCacheWithAllQuotes(await signer.getAddress());

    // we try to place a short order,
    // so the price of the order must be higher than the fair price
    // NOTICE: tick must be aligned with PEARL_SPACING, i.e. ORDER_SPACING
    // order spacing is 5, so the tick must be divisible by 5
    const targetTick = alignTick(pair.amm.tick + 100, PEARL_SPACING);

    // simulate result
    const result = sdk.simulateOrder(
        await sdk.getPairLevelAccount(await signer.getAddress(), instrument.info.addr, PERP_EXPIRY),
        targetTick,
        ethers.utils.parseEther('0.2'),
        Side.SHORT,
        ethers.utils.parseUnits('4', 18),
    );

    // place order
    await sdk.limitOrder(
        signer,
        pair,
        targetTick,
        ethers.utils.parseEther('0.2'),
        result.balance,
        Side.SHORT,
        Math.floor(Date.now() / 1000) + 300, // deadline, set to 5 minutes later
    );

    // use referral code
    // NOTICE: channel code must be 6 bytes long
    const channel = '8test8';
    const getReferralCode = (channel: string): string => {
        return '\xff\xff' + channel; // 0xffff means you are sending tx using SDK and private key
    };
    await sdk.limitOrder(
        signer,
        pair,
        targetTick,
        ethers.utils.parseEther('0.2'),
        result.balance,
        Side.SHORT,
        Math.floor(Date.now() / 1000) + 300, // deadline, set to 5 minutes later
        {}, // here is overrides, you can pass in custom overrides for gas price and limit
        getReferralCode(channel),
    );

    console.log(
        `Place a 4 leveraged limit order of 0.2 BTC at ${ethers.utils.formatUnits(
            TickMath.getWadAtTick(targetTick),
            18,
        )}`,
    );
}

main().catch(console.error);

Batch place orders

export async function demoBatchPlace(): Promise<void> {
    const sdk = SynFuturesV3.getInstance('Blast');

    await sdk.init();

    // get your own signer
    const signer = new ethers.Wallet(process.env.ALICE_PRIVATE_KEY as string, sdk.ctx.provider);

    const instruments = await sdk.getAllInstruments();

    function getInstrumentBySymbol(symbol: string) {
        const instrument = instruments.find((i) => i.info.symbol === symbol);
        if (!instrument) {
            throw new Error('unknown symbol: ' + symbol);
        }
        return instrument;
    }

    const instrument = getInstrumentBySymbol('BTC-USDB-PYTH');
    const account = await sdk.getPairLevelAccount(await signer.getAddress(), instrument.info.addr, PERP_EXPIRY);
    await sdk.syncVaultCacheWithAllQuotes(account.traderAddr);
    const pair = instrument.getPairModel(PERP_EXPIRY)!;
    const ticks = Array.from({ length: 9 }, (_, i) =>
        alignTick(pair.amm.tick + PEARL_SPACING * (i + 1), PEARL_SPACING),
    );
    const ratios = [1111, 1111, 1111, 1111, 1111, 1111, 1111, 1111, 1112]; // ratios must add up to 10000
    const leverage = ethers.utils.parseEther('5');
    const size = ethers.utils.parseEther('1');
    const res = sdk.simulateBatchPlace(account, ticks, ratios, size, Side.SHORT, leverage);
    for (const order of res.orders) {
        console.log(
            formatUnits(order.baseSize, 18),
            formatUnits(wmul(order.baseSize, sqrtX96ToWad(pair.amm.sqrtPX96)), 18),
            formatUnits(wdiv(wmul(order.baseSize, sqrtX96ToWad(pair.amm.sqrtPX96)), order.balance), 18),
            formatUnits(order.balance, 18),
            formatUnits(order.minFeeRebate, 18),
        );
    }
    console.log(
        formatUnits(
            res.orders.reduce((acc, order) => acc.add(order.balance), ethers.BigNumber.from(0)),
            18,
        ),
    );
    console.log(formatUnits(res.marginToDepositWad, 18));
    console.log(formatUnits(res.minOrderValue, 18));

    // to simulate batch place with frontend input, use simulateBatchOrder interface
    const lowerTick = TickMath.getTickAtPWad(pair.fairPriceWad.mul(10050).div(10000));
    const upperTick = TickMath.getTickAtPWad(pair.fairPriceWad.mul(10150).div(10000));
    const orderCount = 9;
    const res2 = sdk.simulateBatchOrder(
        account,
        lowerTick,
        upperTick,
        orderCount,
        BatchOrderSizeDistribution.FLAT,
        size,
        Side.SHORT,
        leverage,
    );
    for (const order of res2.orders) {
        console.log(
            order.tick,
            formatUnits(TickMath.getWadAtTick(order.tick), 18),
            order.ratio,
            formatUnits(order.baseSize, 18),
            formatUnits(wmul(order.baseSize, sqrtX96ToWad(pair.amm.sqrtPX96)), 18),
            formatUnits(wdiv(wmul(order.baseSize, sqrtX96ToWad(pair.amm.sqrtPX96)), order.balance), 18),
            formatUnits(order.balance, 18),
            formatUnits(order.minFeeRebate, 18),
        );
    }
    console.log(
        formatUnits(
            res2.orders.reduce((acc, order) => acc.add(order.balance), ethers.BigNumber.from(0)),
            18,
        ),
    );
    console.log(formatUnits(res2.marginToDepositWad, 18));
    console.log(formatUnits(res2.minOrderValue, 18));

    await sdk.batchPlace(signer, instrument.info.addr, {
        expiry: PERP_EXPIRY,
        ticks,
        ratios,
        size,
        leverage,
        deadline: now() + 300, // deadline, set to 5 minutes from now
    });
}

demoBatchPlace().catch(console.error);

Bulk Cancel order

import { SynFuturesV3, PERP_EXPIRY } from '@synfutures/oyster-sdk';
import { ethers } from 'ethers';

async function main() {
    const sdk = SynFuturesV3.getInstance('blast');

    await sdk.init();

    // get your own signer
    const signer = new ethers.Wallet(process.env.ALICE_PRIVATE_KEY as string, sdk.ctx.provider);

    const instruments = await sdk.getAllInstruments();

    function getInstrumentBySymbol(symbol: string) {
        const instrument = instruments.find((i) => i.info.symbol === symbol);
        if (!instrument) {
            throw new Error('unknown symbol: ' + symbol);
        }
        return instrument;
    }

    const instrument = getInstrumentBySymbol('BTC-USDB-PYTH');

    const account = await sdk.getPairLevelAccount(await signer.getAddress(), instrument.info.addr, PERP_EXPIRY);

    // cancel all orders
    // NOTICE: the order amount must be [1, 8], otherwise an error will be thrown
    await sdk.batchCancelOrder(signer, account, account.orders, Math.floor(Date.now() / 1000) + 300);

    console.log('Cancel all orders:', account.orders.map((order) => order.oid).join(','));
}

main().catch(console.error);

Add liquidity

import { ethers } from 'ethers';
import { SynFuturesV3, PERP_EXPIRY, TickMath } from '@synfutures/oyster-sdk';

async function main() {
    const sdk = SynFuturesV3.getInstance('blast');

    await sdk.init();

    // get your own signer
    const signer = new ethers.Wallet(process.env.ALICE_PRIVATE_KEY as string, sdk.ctx.provider);

    const instruments = await sdk.getAllInstruments();

    function getInstrumentBySymbol(symbol: string) {
        const instrument = instruments.find((i) => i.info.symbol === symbol);
        if (!instrument) {
            throw new Error('unknown symbol: ' + symbol);
        }
        return instrument;
    }

    const instrument = getInstrumentBySymbol('BTC-USDB-PYTH');

    // update cache for signer
    await sdk.syncVaultCacheWithAllQuotes(await signer.getAddress());

    // margin to add liquidity
    const margin = ethers.utils.parseUnits('1000', 18);

    const {
        tickDelta, // tick range to add, calculated based on alphaWad
        liquidity, // amount of liquidity to add
        // NOTICE: range lower tick and upper tick are aligned with RANGE_SPACING, but normally you are taken care of and don't have to deal with range spacing yourself
        upperPrice, // range upper price, alinged with RANGE_SPACING
        lowerPrice, // range lower price, aligned with RANGE_SPACING
        lowerPosition, // new net position if get removed at lower price
        lowerLeverageWad, // new leverage if get removed at lower price
        upperPosition, // new net position if get removed at upper price
        upperLeverageWad, // new leverage if get removed at upper price
        sqrtStrikeLowerPX96, // tx would revert if fair price is lower than this, calculated with slippage
        sqrtStrikeUpperPX96, // tx would revert if fair price is higher than this
        marginToDepositWad, // if gate reserve is not enough to cover the margin, you need to deposit this amount
        minMargin, // margin required
        minEffectiveQuoteAmount, // minimum quote needed to add liquidity based on instrument imr and quote token setting
        equivalentAlpha, // actual alpha, slightly different from input alpha
    } = await sdk.simulateAddLiquidity(
        await signer.getAddress(),
        {
            marketType: instrument.marketType,
            baseSymbol: instrument.info.base.symbol,
            quoteSymbol: instrument.info.quote.symbol,
        },
        PERP_EXPIRY,
        ethers.utils.parseUnits('1.8', 18), // alpha, liquidity range factor, 1.8 means [1/1.8, 1.8]x current price
        margin,
        100, // // slippage, 100 means 100 / 10000 = 1%
    );

    await sdk.addLiquidity(
        signer,
        {
            marketType: instrument.marketType,
            baseSymbol: instrument.info.base.symbol,
            quoteSymbol: instrument.info.quote.symbol,
        },
        PERP_EXPIRY,
        tickDelta,
        margin,
        sqrtStrikeLowerPX96,
        sqrtStrikeUpperPX96,
        Math.floor(Date.now() / 1000) + 300, // deadline, set to 5 minutes later
    );

    // use referral code
    // NOTICE: channel code must be 6 bytes long
    const channel = '8test8';
    const getReferralCode = (channel: string): string => {
        return '\xff\xff' + channel; // 0xffff means you are sending tx using SDK and private key
    };
    await sdk.addLiquidity(
        signer,
        {
            marketType: instrument.marketType,
            baseSymbol: instrument.info.base.symbol,
            quoteSymbol: instrument.info.quote.symbol,
        },
        PERP_EXPIRY,
        tickDelta,
        margin,
        sqrtStrikeLowerPX96,
        sqrtStrikeUpperPX96,
        Math.floor(Date.now() / 1000) + 300, // deadline, set to 5 minutes later
        {}, // here is overrides, you can pass in custom overrides for gas price and limit
        getReferralCode(channel),
    );

    console.log(
        `Add 1000 USDB liquidity from tick ${TickMath.getTickAtSqrtRatio(
            sqrtStrikeLowerPX96,
        )} to tick ${TickMath.getTickAtSqrtRatio(sqrtStrikeUpperPX96)}`,
    );
}

main().catch(console.error);

Add asymmetric liquidity

export async function demoAddAsymmetricLiquidity(): Promise<void> {
    const sdk = SynFuturesV3.getInstance('Blast');

    await sdk.init();

    // get your own signer
    const signer = new ethers.Wallet(process.env.ALICE_PRIVATE_KEY as string, sdk.ctx.provider);

    const instruments = await sdk.getAllInstruments();

    function getInstrumentBySymbol(symbol: string) {
        const instrument = instruments.find((i) => i.info.symbol === symbol);
        if (!instrument) {
            throw new Error('unknown symbol: ' + symbol);
        }
        return instrument;
    }

    const instrument = getInstrumentBySymbol('BTC-USDB-PYTH');

    // update cache for signer
    await sdk.syncVaultCacheWithAllQuotes(await signer.getAddress());

    // margin to add liquidity
    const margin = ethers.utils.parseUnits('1000', 18);

    // return value almost same as simulateAddLiquidity, tickDelta => tickDeltaLower and tickDeltaUpper
    const res = await sdk.simulateAddLiquidityWithAsymmetricRange(
        await signer.getAddress(),
        {
            marketType: instrument.marketType,
            baseSymbol: instrument.info.base.symbol,
            quoteSymbol: instrument.info.quote.symbol,
        },
        PERP_EXPIRY,
        ethers.utils.parseUnits('1.8', 18), // alpha lower, liquidity range factor, currPx / 1.8
        ethers.utils.parseUnits('2', 18), // alpha upper, liquidity range factor, currPx * 2
        margin,
        100, // // slippage, 100 means 100 / 10000 = 1%
    );

    await sdk.addLiquidityWithAsymmetricRange(
        signer,
        {
            marketType: instrument.marketType,
            baseSymbol: instrument.info.base.symbol,
            quoteSymbol: instrument.info.quote.symbol,
        },
        PERP_EXPIRY,
        res.tickDeltaLower,
        res.tickDeltaUpper,
        margin,
        res.sqrtStrikeLowerPX96,
        res.sqrtStrikeUpperPX96,
        now() + 300, // deadline, set to 5 minutes from now
    );

    // use referral code
    // NOTICE: channel code must be 6 bytes long
    const channel = '8test8';
    const getReferralCode = (channel: string): string => {
        return '\xff\xff' + channel; // 0xffff means you are sending tx using SDK and private key
    };

    await sdk.addLiquidityWithAsymmetricRange(
        signer,
        {
            marketType: instrument.marketType,
            baseSymbol: instrument.info.base.symbol,
            quoteSymbol: instrument.info.quote.symbol,
        },
        PERP_EXPIRY,
        res.tickDeltaLower,
        res.tickDeltaUpper,
        margin,
        res.sqrtStrikeLowerPX96,
        res.sqrtStrikeUpperPX96,
        now() + 300, // deadline, set to 5 minutes from now
        {}, // here is overrides, you can pass in custom overrides for gas price and limit
        getReferralCode(channel),
    );
}

Remove liquidity

import { ethers } from 'ethers';
import { SynFuturesV3, PERP_EXPIRY } from '@synfutures/oyster-sdk';

async function main() {
    const sdk = SynFuturesV3.getInstance('blast');

    await sdk.init();

    // get your own signer
    const signer = new ethers.Wallet(process.env.ALICE_PRIVATE_KEY as string, sdk.ctx.provider);

    const instruments = await sdk.getAllInstruments();

    function getInstrumentBySymbol(symbol: string) {
        const instrument = instruments.find((i) => i.info.symbol === symbol);
        if (!instrument) {
            throw new Error('unknown symbol: ' + symbol);
        }
        return instrument;
    }

    const instrument = getInstrumentBySymbol('BTC-USDB-PYTH');

    // update cache for signer
    await sdk.syncVaultCacheWithAllQuotes(await signer.getAddress());

    // get user account
    const account = await sdk.getPairLevelAccount(await signer.getAddress(), instrument.info.addr, PERP_EXPIRY);

    const range = account.ranges[0];

    const result = sdk.simulateRemoveLiquidity(account, range, 100);

    await sdk.removeLiquidity(
        signer,
        instrument.pairs.get(PERP_EXPIRY)!,
        await signer.getAddress(),
        range,
        result.sqrtStrikeLowerPX96,
        result.sqrtStrikeUpperPX96,
        Math.floor(Date.now() / 1000) + 300, // deadline, set to 5 minutes later
    );

    console.log(
        `Remove ${ethers.utils.formatUnits(range.balance, 18)} USDB liquidity from tick ${range.tickLower} to tick ${
            range.tickUpper
        }`,
    );
}

main().catch(console.error);

Get Volume Chart

import { PERP_EXPIRY, SynFuturesV3, VolumeChartDataProvider } from '@synfutures/oyster-sdk';
async function main() {
    function now() {
        return Math.floor(Date.now() / 1000);
    }
    function getInstrumentBySymbol(symbol: string) {
        const instrument = instruments.find((i) => i.info.symbol === symbol);
        if (!instrument) {
            throw new Error('unknown symbol: ' + symbol);
        }
        return instrument;
    }

    const sdk = SynFuturesV3.getInstance('blast');
    const instruments = await sdk.getAllInstruments();
    const instrument = getInstrumentBySymbol('BTC-USDB-PYTH');

    const dataProvider = new VolumeChartDataProvider(sdk.ctx.chainId);
    const volumeChartData = await dataProvider.getVolumeData(instrument.info.addr, PERP_EXPIRY, 0, now());
    for (const data of volumeChartData) {
        console.log('timestamp:', data.timestamp, 'baseVolume:', data.baseVolume, 'quoteVolume:', data.quoteVolume);
    }
}
main().catch(console.error);

Query Account Portfolio Info

import { SynFuturesV3, PERP_EXPIRY, InstrumentLevelAccountModel, sqrtX96ToWad, TickMath, ZERO } from '@synfutures/oyster-sdk';
import { formatEther } from 'ethers/lib/utils';
import { ethers } from 'ethers';

async function main() {
    const sdk = SynFuturesV3.getInstance('blast');
    await sdk.initInstruments();
    const accAddr = '0xYOUR_ACCOUNT_ADDRESS_HERE';
    await sdk.syncVaultCacheWithAllQuotes(accAddr);

    console.log('print balance in gate contract');
    for (const symbol of Object.keys(sdk.config.quotesParam)) {
        const tokenInfo = await sdk.ctx.getTokenInfo(symbol);
        const tokenBalanceInVault = await sdk.getCachedVaultBalance(tokenInfo.address, accAddr);
        console.log(
            `Token ${symbol} balance in vault: ${ethers.utils.formatUnits(tokenBalanceInVault, tokenInfo.decimals)}`,
        );
    }

    const allInstrumentLevelAccount: InstrumentLevelAccountModel[] = await sdk.getInstrumentLevelAccounts(accAddr);
    for (const instrumentLevelAccount of allInstrumentLevelAccount) {
        for (const [, pairLevelAccountModel] of instrumentLevelAccount.portfolios) {
            if (pairLevelAccountModel.ranges.length > 0) {
                console.log('print ranges for pair:', pairLevelAccountModel.rootPair.symbol);
                for (const range of pairLevelAccountModel.ranges) {
                    console.log('range:', range.tickLower, range.tickUpper, 'enterPrice', formatEther(sqrtX96ToWad(range.sqrtEntryPX96)), 'fees Earned', formatEther(range.feeEarned), 'value locked', formatEther(range.valueLocked);
                    console.log('lowerPrice', formatEther(TickMath.getWadAtTick(range.tickLower)), 'upperPrice', formatEther(TickMath.getWadAtTick(range.tickUpper)));
                    console.log('lowerLiquidationPrice', formatEther(range.lowerPositionModelIfRemove.liquidationPrice), 'upperLiquidationPrice', formatEther(range.upperPositionModelIfRemove.liquidationPrice));
                }
            }
            if (pairLevelAccountModel.position.size !== ZERO) {
                console.log('print position for pair:', pairLevelAccountModel.rootPair.symbol);
                console.log('position size:', formatEther(pairLevelAccountModel.position.size), 'side:', pairLevelAccountModel.position.side, 'leverage:', formatEther(pairLevelAccountModel.position.leverageWad),
                    'position value', formatEther(pairLevelAccountModel.position.getEquity()), 'margin', formatEther(pairLevelAccountModel.position.balance), 'maxWithdrawableMargin', formatEther(pairLevelAccountModel.position.getMaxWithdrawableMargin()));
                console.log('unrealized Pnl', formatEther(pairLevelAccountModel.position.unrealizedPnl));
                console.log('unrealized Funding', formatEther(pairLevelAccountModel.position.unrealizedFundingFee));
                console.log('liquidate price', formatEther(pairLevelAccountModel.position.liquidationPrice));
            }

            if (pairLevelAccountModel.orders.length > 0) {
                console.log('print orders for pair:', pairLevelAccountModel.rootPair.symbol);
                for (const order of pairLevelAccountModel.orders) {
                    console.log('order price:', order.limitPrice, 'size:', formatEther(order.size), 'taken', formatEther(order.taken), 'margin:', formatEther(order.balance), 'leverage:', formatEther(order.leverageWad), 'side:', order.side);
                }
            }

        }
    }
}

main().catch(console.error);

Query Account Range History

import { SynFuturesV3, TickMath, formatExpiry } from '@synfutures/oyster-sdk';
import { formatEther } from 'ethers/lib/utils';
async function main() {
    const sdk = SynFuturesV3.getInstance('blast');
    const accAddr = '0xYOUR_ACCOUNT_ADDRESS_HERE';

    function now() {
        return Math.floor(Date.now() / 1000);
    }

    // get user range history
    const userFundingHistory = await sdk.subgraph.getTransactionEvents({
        eventNames: ['Add', 'Remove'],
        traders: [accAddr],
        // instrumentAddr: instrument.info.addr, // optional
        // expiry: pair.amm.expiry, // optional
        // to get all history, just ignore startTs and endTs
        startTs: now() - 3600 * 24 * 30, // 30 days ago, get 1 month history
        endTs: now(),
    });

    // print user range history
    for (const event of userFundingHistory) {
        console.log(
            'instrumentAddress',
            event.address,
            'expiry',
            formatExpiry(parseInt(event.args.expiry)),
            'event:',
            event.name,
            'timestamp:',
            event.timestamp,
            'tickLowerPrice',
            formatEther(TickMath.getWadAtTick(parseInt(event.args.tickLower))),
            'tickUpperPrice',
            formatEther(TickMath.getWadAtTick(parseInt(event.args.tickUpper))),
        );
    }
}
main().catch(console.error);

Query Asset Transfer History

import { SynFuturesV3, TickMath, formatExpiry } from '@synfutures/oyster-sdk';
import { formatUnits } from 'ethers/lib/utils';

async function main() {
    const sdk = SynFuturesV3.getInstance('blast');
    const accAddr = '0xYOUR_ACCOUNT_ADDRESS_HERE';

    function now() {
        return Math.floor(Date.now() / 1000);
    }

    // get user transfer history
    const userTransferHistory = await sdk.subgraph.getTransactionEvents({
        eventNames: ['Gather', 'Scatter'],
        traders: [accAddr],
        // instrumentAddr: instrument.info.addr, // optional
        // expiry: pair.amm.expiry, // optional
        // to get all history, just ignore startTs and endTs
        startTs: now() - 3600 * 24 * 30, // 30 days ago, get 1 month history
        endTs: now(),
    });

    // print user transfer history
    for (const event of userTransferHistory) {
        console.log(
            'instrumentAddress',
            event.address,
            'expiry',
            formatExpiry(parseInt(event.args.expiry)),
            'event:',
            event.name,
            'timestamp:',
            event.timestamp,
        );
        const quoteAddr = event.args.quote;
        const tokenInfo = await sdk.ctx.getTokenInfo(quoteAddr);
        console.log(
            'Type',
            event.name === 'Gather' ? 'Transfer In' : 'Transfer Out',
            'Token',
            tokenInfo.symbol,
            'amount',
            formatUnits(event.args.quantity, tokenInfo.decimals),
        );
    }
}

main().catch(console.error);

Query Deposit Withdraw History

import { NATIVE_TOKEN_ADDRESS, SynFuturesV3, TickMath, formatExpiry } from '@synfutures/oyster-sdk';
import { formatUnits } from 'ethers/lib/utils';

async function main() {
    const sdk = SynFuturesV3.getInstance('blast');
    const accAddr = '0xYOUR_ACCOUNT_ADDRESS_HERE';

    function now() {
        return Math.floor(Date.now() / 1000);
    }

    // get user transfer history
    const userDWHistory = await sdk.subgraph.getTransactionEvents({
        eventNames: ['Deposit', 'Withdraw'],
        traders: [accAddr],
        // instrumentAddr: instrument.info.addr, // optional
        // expiry: pair.amm.expiry, // optional
        // to get all history, just ignore startTs and endTs
        startTs: now() - 3600 * 24 * 30, // 30 days ago, get 1 month history
        endTs: now(),
    });

    // print user transfer history
    for (const event of userDWHistory) {
        console.log('event:', event.name, 'timestamp:', event.timestamp);
        const quoteAddr = event.args.quote;
        const tokenInfo =
            quoteAddr.toLowerCase() === NATIVE_TOKEN_ADDRESS.toLowerCase()
                ? await sdk.ctx.wrappedNativeToken
                : await sdk.ctx.getTokenInfo(quoteAddr);
        console.log('Token', tokenInfo.symbol, 'amount', formatUnits(event.args.quantity, tokenInfo.decimals));
    }
}

main().catch(console.error);

Estimate Earning APY

import { SynFuturesV3, PERP_EXPIRY } from '@synfutures/oyster-sdk';
import { parseEther } from 'ethers/lib/utils';

async function main() {
    function getInstrumentBySymbol(symbol: string) {
        const instrument = instruments.find((i) => i.info.symbol === symbol);
        if (!instrument) {
            throw new Error('unknown symbol: ' + symbol);
        }
        return instrument;
    }

    const sdk = SynFuturesV3.getInstance('blast');
    const instruments = await sdk.getAllInstruments();
    const instrument = getInstrumentBySymbol('BTC-USDB-PYTH');
    const pair = instrument.pairs.get(PERP_EXPIRY)!;
    // alpha means the width of the range -> 1.5 means [1/1.5, 1.5]x current price
    const alpha = '1.5';
    // compute APY need pairs data
    const pairDatas = await sdk.subgraph.getPairsData();
    // get the pair data for the instrument
    const pairData = pairDatas.find(
        (pd) => pd.instrumentAddr.toLowerCase() === instrument.info.addr.toLowerCase() && pd.expiry === PERP_EXPIRY,
    );
    //can estimate the apy
    const fee_24hrs = pairData!.poolFee24h;
    const apy = sdk.estimateAPY(pair, fee_24hrs, parseEther(alpha));
    console.log('estimated apy:', apy);
}

main().catch(console.error);

Query user operation history

import { SynFuturesV3 } from '@synfutures/oyster-sdk';

async function main() {
    const sdk = SynFuturesV3.getInstance('blast');

    await sdk.init();

    // get signer address
    const signer = process.argv[2];

    console.log(
        'Account history:',
        await sdk.subgraph.getVirtualTrades({
            traders: [signer],
        }),
    );
}

main().catch(console.error);

Query pair kline

/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { CHAIN_ID, now } from '@derivation-tech/web3-core';
import { parseEther } from 'ethers/lib/utils';
import { PERP_EXPIRY } from './constants';
import { IKlineDataProvider, KlineDataProvider, KlineInterval } from './chart/kline';
import { SynFuturesV3 } from './synfuturesV3Core';

export async function demoKline(): Promise<void> {
    const synfV3 = SynFuturesV3.getInstance('Blast');
    const allInstruments = await synfV3.getAllInstruments();
    const instrument = allInstruments.find((i) => i.info.symbol.includes('BTC-USDB'))!;
    const klineProvider: IKlineDataProvider = new KlineDataProvider(CHAIN_ID.BLAST);
    // enum KlineInterval {
    //     MINUTE = '1m',
    //     FIVE_MINUTE = '5m',
    //     FIFTEEN_MINUTE = '15m',
    //     THIRTY_MINUTE = '30m',
    //     HOUR = '1h',
    //     FOUR_HOUR = '4h',
    //     WEEK = '1w',
    //     DAY = '1d',
    // }
    const klineData = await klineProvider.getKlineData(
        instrument.info.addr,
        PERP_EXPIRY,
        KlineInterval.HOUR,
        0, // start time
        now(), // end time, use now() to get the latest timestamp in seconds
        parseEther('0.01'), // filter out trade value less 0.1 quote token
    );
    console.log(klineData);
}

demoKline().catch(console.error);

Query pair depth

/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { CHAIN_ID } from '@derivation-tech/web3-core';
import { parseEther } from 'ethers/lib/utils';
import { PERP_EXPIRY } from './constants';
import { SynFuturesV3 } from './synfuturesV3Core';
import { DepthChartDataProvider } from './chart/depth';

export async function demoDepth(): Promise<void> {
    const synfV3 = SynFuturesV3.getInstance('Blast');
    const allInstruments = await synfV3.getAllInstruments();
    const instrument = allInstruments.find((i) => i.info.symbol.includes('BTC-USDB'))!;
    const depthProvider = new DepthChartDataProvider(CHAIN_ID.BLAST);
    const pair = instrument.state.pairStates.get(PERP_EXPIRY)!;

    // get depth chart data from subgraph
    const depth1 = await depthProvider.getDepthData(
        instrument.info.addr,
        PERP_EXPIRY,
        pair.amm.liquidity,
        pair.amm.sqrtPX96,
        instrument.setting.initialMarginRatio,
    );
    console.log(depth1);

    const IMR_STEP_RATIO_MAP = {
        100: [5, 10, 15],
        300: [5, 15, 35],
        500: [5, 25, 50],
        1000: [5, 50, 100],
    };

    // get default range depth chart data from Observer contract's onchain query
    const depth2 = await depthProvider.getDepthDataFromObserver(
        synfV3.contracts.observer,
        instrument.info.addr,
        PERP_EXPIRY,
        IMR_STEP_RATIO_MAP[instrument.setting.initialMarginRatio as keyof typeof IMR_STEP_RATIO_MAP][1],
    );
    console.log(depth2);

    // get full range depth chart data from Observer
    const depth3 = await depthProvider.getDepthRangeDataFromObserver(
        synfV3.contracts.observer,
        instrument.info.addr,
        PERP_EXPIRY,
        IMR_STEP_RATIO_MAP[instrument.setting.initialMarginRatio as keyof typeof IMR_STEP_RATIO_MAP][1],
    );
    console.log(depth3);

    // get custom price range depth chart data from Observer
    const depth4 = await depthProvider.getDepthRangeDataFromObserver(
        synfV3.contracts.observer,
        instrument.info.addr,
        PERP_EXPIRY,
        IMR_STEP_RATIO_MAP[instrument.setting.initialMarginRatio as keyof typeof IMR_STEP_RATIO_MAP][1],
        false, // set to true when pair's price is inverted, e.g., pair is USDB-WETH-PYTH-PERP, but the price is shown as WETH-USDB
        parseEther('50000'), // lower price in WAD
        parseEther('70000'), // upper price in WAD
    );
    console.log(depth4);

    // get order book data, (price, size, size sum)
    const orderBook: {
        left: { price: number; size: number; sizeSum: number }[];
        right: { price: number; size: number; sizeSum: number }[];
    } = {
        left: [],
        right: [],
    };
    for (const data of depth3.left) {
        orderBook.left.push({
            price: data.price,
            size: data.base,
            sizeSum:
                orderBook.left.length === 0 ? data.base : orderBook.left[orderBook.left.length - 1].sizeSum + data.base,
        });
    }
    for (const data of depth3.right) {
        orderBook.right.push({
            price: data.price,
            size: data.base,
            sizeSum:
                orderBook.right.length === 0
                    ? data.base
                    : orderBook.right[orderBook.right.length - 1].sizeSum + data.base,
        });
    }
    console.log(orderBook);
}

demoDepth().catch(console.error);

Query pair funding rate

/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { formatWad, fromWad } from '@derivation-tech/web3-core';
import { PERP_EXPIRY } from './constants';
import { SynFuturesV3 } from './synfuturesV3Core';
import { FundingChartDataProvider, FundingChartInterval } from './chart/funding';
import { wdiv } from './math';
import { BigNumber } from 'ethers';

export async function demoFundingRate(): Promise<void> {
    const synfV3 = SynFuturesV3.getInstance('blast');
    const allInstruments = await synfV3.getAllInstruments();
    const instrument = allInstruments.find((i) => i.info.symbol.includes('BTC-USDB'))!;
    console.log(instrument.info.symbol);
    const pair = instrument.getPairModel(PERP_EXPIRY)!;
    console.log(pair.amm.status);
    const fundingProvider = new FundingChartDataProvider(synfV3);

    // get last hour funding rate
    const lastHourFundingRate = await fundingProvider.getLastHourFundingRate(pair);
    console.log(
        `last hour funding rate: ${fromWad(lastHourFundingRate.longFundingRate)}, ${fromWad(
            lastHourFundingRate.shortFundingRate,
        )}`,
    );

    // get history funding rate chart data
    console.log(`history one hour funding rate: `);
    const oneHourFundingRate = await fundingProvider.getFundingRateData(FundingChartInterval.HOUR, pair);
    console.log(oneHourFundingRate.length);
    for (const rate of oneHourFundingRate) {
        console.log(rate.timestamp, formatWad(rate.longFundingRate), formatWad(rate.shortFundingRate));
    }
    console.log(`history eight hour funding rate: `);
    const eightHourFundingRate = await fundingProvider.getFundingRateData(FundingChartInterval.EIGHT_HOUR, pair);
    console.log(eightHourFundingRate.length);
    for (const rate of eightHourFundingRate) {
        console.log(rate.timestamp, formatWad(rate.longFundingRate), formatWad(rate.shortFundingRate));
    }

    // get estimated projected funding rate
    const fair = pair.fairPriceWad;
    const mark = pair.markPrice;
    const projectedFundingRate = wdiv(fair.sub(mark), mark).div(24);
    const longPayShort = fair.gt(mark);
    let projectedLongFundingRate: BigNumber;
    let projectedShortFundingRate: BigNumber;
    if (longPayShort) {
        projectedLongFundingRate = projectedFundingRate.mul(-1);
        projectedShortFundingRate = projectedFundingRate.mul(pair.amm.totalLong).div(pair.amm.totalShort);
    } else {
        projectedShortFundingRate = projectedFundingRate;
        projectedLongFundingRate = projectedFundingRate.mul(pair.amm.totalShort).div(pair.amm.totalLong).mul(-1);
    }
    console.log(
        `projected one hour funding rate is for long: ${fromWad(projectedLongFundingRate)}, for short: ${fromWad(
            projectedShortFundingRate,
        )}`,
    );
}

demoFundingRate().catch(console.error);

Query user single pair info

/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { now } from '@derivation-tech/web3-core';
import { PERP_EXPIRY } from './constants';
import { SynFuturesV3 } from './synfuturesV3Core';

export async function demoUserPairInfo(): Promise<void> {
    const synfV3 = SynFuturesV3.getInstance('blast');
    const allInstruments = await synfV3.getAllInstruments();
    const instrument = allInstruments.find((i) => i.info.symbol.includes('BTC-USDB'))!;
    console.log(instrument.info.symbol);
    const pair = instrument.getPairModel(PERP_EXPIRY)!;
    const trader = ''; // TODO: fill in the trader address

    // get user trade history
    const userTradeHistory = await synfV3.subgraph.getVirtualTrades({
        traders: [trader],
        instrumentAddr: instrument.info.addr,
        expiry: pair.amm.expiry,
        // to get all history, just ignore startTs and endTs
        startTs: now() - 3600 * 24 * 30, // 30 days ago, get 1 month history
        endTs: now(),
    });
    console.log(userTradeHistory);

    // get user order history
    const userOrderHistory = await synfV3.subgraph.getUserOrders({
        traders: [trader],
        instrumentAddr: instrument.info.addr,
        expiry: pair.amm.expiry,
        // to get all history, just ignore startTs and endTs
        startTs: now() - 3600 * 24 * 30, // 30 days ago, get 1 month history
        endTs: now(),
    });
    console.log(userOrderHistory);

    // get user funding history
    const userFundingHistory = await synfV3.subgraph.getTransactionEvents({
        eventNames: ['FundingFee'],
        traders: [trader],
        instrumentAddr: instrument.info.addr,
        expiry: pair.amm.expiry,
        // to get all history, just ignore startTs and endTs
        startTs: now() - 3600 * 24 * 30, // 30 days ago, get 1 month history
        endTs: now(),
    });
    console.log(userFundingHistory);

    // get user position
    const userAccount = await synfV3.getPairLevelAccount(trader, instrument.info.addr, pair.amm.expiry);
    console.log(userAccount.getMainPosition());
    console.log(userAccount.orders);
}

demoUserPairInfo().catch(console.error);

Query market info

/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { formatWad } from '@derivation-tech/web3-core';
import { SynFuturesV3 } from './synfuturesV3Core';
import { wmul } from './math';

export async function demoMarketInfoPage(): Promise<void> {
    const synfV3 = SynFuturesV3.getInstance('blast');
    const allInstruments = await synfV3.getAllInstruments();
    const allPairsData = await synfV3.subgraph.getPairsData();
    for (const pairData of allPairsData) {
        const pair = allInstruments
            .find((i) => i.info.addr.toLowerCase() === pairData.instrumentAddr.toLowerCase())!
            .getPairModel(pairData.expiry)!;
        console.log(
            `${pair.symbol}, ${formatWad(pair.fairPriceWad)}, ${formatWad(pairData.priceChange24h)}, ${formatWad(
                pairData.high24h,
            )}, ${formatWad(pairData.low24h)}, ${formatWad(pairData.volume24h)}, ${formatWad(
                wmul(pair.amm.openInterests, pair.fairPriceWad), // represented in quote token, need to convert to dollar value
            )}`,
        );
    }
}

demoMarketInfoPage().catch(console.error);

Direct trade interface

/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { ethers } from 'ethers';
import { INT24_MAX, INT24_MIN, NULL_DDL, PERP_EXPIRY } from './constants';
import { SynFuturesV3 } from './synfuturesV3Core';
import { Side } from './types';
import { parseEther } from 'ethers/lib/utils';
import { ZERO } from '@derivation-tech/web3-core';

export async function demoTrade(): Promise<void> {
    const synfV3 = SynFuturesV3.getInstance('blast');
    const allInstruments = await synfV3.getAllInstruments();
    const instrument = allInstruments.find((i) => i.info.symbol.includes('BTC-USDB'))!;
    const pair = instrument.getPairModel(PERP_EXPIRY);
    // get your own signer
    const signer = new ethers.Wallet(process.env.ALICE_PRIVATE_KEY as string, synfV3.ctx.provider);
    // want to open a long position with 1 BTC, inquire the quotation first
    const side = Side.LONG;
    const size = parseEther('0.1');
    await synfV3.syncVaultCacheWithAllQuotes(signer.address); // get signer's account
    const account = await synfV3.getPairLevelAccount(signer.address, instrument.info.addr, pair.amm.expiry);
    const { quotation } = await synfV3.inquireByBase(pair, side, size);

    // margin and leverage are optional
    // 1. undefined margin, margin will be calculated based on leverage
    // 2. undefined leverage, leverage will be calculated based on margin
    // 3. both undefined, leverage will be calculated based on new size and equity
    const { margin } = synfV3.simulateTrade(
        account,
        quotation,
        side,
        size,
        undefined,
        parseEther('4'), // leverage in WAD format
        100, // slippage in bps, 100 bps = 1%
    );

    await synfV3.trade(signer, instrument.info.addr, {
        expiry: pair.amm.expiry,
        size,
        amount: margin,
        // if the traded average price exceeds the limit price represented in tick format, trade would revert
        // say I want to long btc with 1 btc, and I don't want average trade price to exceed 60k, then set limitTick to
        // TickMath.getTickAtPWad(parseEther('60000'))
        limitTick: size.gt(ZERO) ? INT24_MAX : INT24_MIN,
        deadline: NULL_DDL, // set tx deadline, if desire 10 minutes ddl, use now() + 10 * 60
    });

    // use referral code
    // NOTICE: channel code must be 6 bytes long
    const channel = '8test8';
    const getReferralCode = (channel: string): string => {
        return '\xff\xff' + channel; // 0xffff means you are sending tx using SDK and private key
    };
    await synfV3.trade(
        signer,
        instrument.info.addr,
        {
            expiry: pair.amm.expiry,
            size,
            amount: margin,
            // if the traded average price exceeds the limit price represented in tick format, trade would revert
            // say I want to long btc with 1 btc, and I don't want average trade price to exceed 60k, then set limitTick to
            // TickMath.getTickAtPWad(parseEther('60000'))
            limitTick: size.gt(ZERO) ? INT24_MAX : INT24_MIN,
            deadline: NULL_DDL, // set tx deadline, if desire 10 minutes ddl, use now() + 10 * 60
        },
        {}, // here is overrides, you can pass in custom overrides for gas price and limit
        getReferralCode(channel),
    );
}

demoTrade().catch(console.error);

Direct place interface

/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { ethers } from 'ethers';
import { PEARL_SPACING, PERP_EXPIRY } from './constants';
import { SynFuturesV3 } from './synfuturesV3Core';
import { Side } from './types';
import { parseEther } from 'ethers/lib/utils';
import { formatWad, now } from '@derivation-tech/web3-core';
import { alignTick } from './common';
import { TickMath } from './math';

export async function demoPlace(): Promise<void> {
    const synfV3 = SynFuturesV3.getInstance('blast');
    const allInstruments = await synfV3.getAllInstruments();
    const instrument = allInstruments.find((i) => i.info.symbol.includes('BTC-USDB'))!;
    const pair = instrument.getPairModel(PERP_EXPIRY);
    // get your own signer
    const signer = new ethers.Wallet(process.env.ALICE_PRIVATE_KEY as string, synfV3.ctx.provider);
    // say you want to place an short limit order of size 1 BTC at price 5% higher from current fair price with 10x leverage
    // order cannot be placed too far from mark price, which is 2 * imr, and order cannot be too trivial
    // to get minOrderValue
    const minOrderValue = instrument.minOrderValue;
    console.log(formatWad(minOrderValue));
    const size = parseEther('1').mul(-1);
    const leverage = parseEther('10');
    const placePrice = pair.fairPriceWad.mul(105).div(100);
    const tick = alignTick(TickMath.getTickAtPWad(placePrice), PEARL_SPACING); // tick needs to be aligned with pearl spacing
    await synfV3.syncVaultCacheWithAllQuotes(signer.address);
    const account = await synfV3.getPairLevelAccount(signer.address, instrument.info.addr, PERP_EXPIRY);
    const { balance: margin } = synfV3.simulateOrder(account, tick, size, Side.SHORT, leverage);
    await synfV3.place(signer, instrument.info.addr, {
        expiry: pair.amm.expiry,
        tick, // the price you want to place your order in tick format
        size: size,
        amount: margin,
        deadline: now() + 10 * 60,
    });

    // use referral code
    // NOTICE: channel code must be 6 bytes long
    const channel = '8test8';
    const getReferralCode = (channel: string): string => {
        return '\xff\xff' + channel; // 0xffff means you are sending tx using SDK and private key
    };
    await synfV3.place(
        signer,
        instrument.info.addr,
        {
            expiry: pair.amm.expiry,
            tick, // the price you want to place your order in tick format
            size: size,
            amount: margin,
            deadline: now() + 10 * 60,
        },
        {}, // here is overrides, you can pass in custom overrides for gas price and limit
        getReferralCode(channel),
    );
}

demoPlace().catch(console.error);

Direct add liquidity interface

/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { ethers } from 'ethers';
import { NULL_DDL, PERP_EXPIRY, RATIO_BASE } from './constants';
import { SynFuturesV3 } from './synfuturesV3Core';
import { parseEther } from 'ethers/lib/utils';
import { formatWad, fromWad } from '@derivation-tech/web3-core';
import { tickDeltaToAlphaWad } from './common';
import { TICK_DELTA_MAX } from './math';

export async function demoAdd(): Promise<void> {
    const synfV3 = SynFuturesV3.getInstance('blast');
    const allInstruments = await synfV3.getAllInstruments();
    const instrument = allInstruments.find((i) => i.info.symbol.includes('BTC-USDB'))!;
    const pair = instrument.getPairModel(PERP_EXPIRY);
    // get your own signer
    const signer = new ethers.Wallet(process.env.ALICE_PRIVATE_KEY as string, synfV3.ctx.provider);
    const margin = parseEther('1000'); // margin to add liquidity, has to be larger than minRangeValue
    console.log(formatWad(instrument.minRangeValue));
    // alphaWad has to be larger than (1 + imr) and less than maxAlphaWad which is round [1/5, 5]x
    const minAlphaWad = parseEther(((RATIO_BASE + instrument.setting.initialMarginRatio) / RATIO_BASE).toString());
    const maxAlphaWad = tickDeltaToAlphaWad(TICK_DELTA_MAX);
    console.log(fromWad(minAlphaWad), fromWad(maxAlphaWad));
    const alphaWad = parseEther('1.8'); // liquidity range factor, 1.8 means [1/1.8, 1.8]x current price
    await synfV3.syncVaultCacheWithAllQuotes(signer.address);
    const rangeSimulation = await synfV3.simulateAddLiquidity(
        signer.address,
        {
            marketType: instrument.marketType,
            baseSymbol: instrument.info.base.symbol,
            quoteSymbol: instrument.info.quote.symbol,
        },
        pair.amm.expiry,
        alphaWad,
        margin,
        100, // slippage, 100 means 100 / 10000 = 1%
    );
    // limit tick is calculated based on sqrtStrikeLowerPX96 and sqrtStrikeUpperPX96, which are calculated based on slippage
    const limitTicks = synfV3.encodeLimitTicks(
        rangeSimulation.sqrtStrikeLowerPX96,
        rangeSimulation.sqrtStrikeUpperPX96,
    );
    await synfV3.add(signer, instrument.info.addr, {
        expiry: pair.amm.expiry,
        // can use tickDelta larger than calcMinTickDelta(imr) and smaller than TICK_DELTA_MAX
        tickDelta: rangeSimulation.tickDelta,
        amount: margin,
        limitTicks,
        deadline: NULL_DDL,
    });

    // use referral code
    // NOTICE: channel code must be 6 bytes long
    const channel = '8test8';
    const getReferralCode = (channel: string): string => {
        return '\xff\xff' + channel; // 0xffff means you are sending tx using SDK and private key
    };
    await synfV3.add(
        signer,
        instrument.info.addr,
        {
            expiry: pair.amm.expiry,
            // can use tickDelta larger than calcMinTickDelta(imr) and smaller than TICK_DELTA_MAX
            tickDelta: rangeSimulation.tickDelta,
            amount: margin,
            limitTicks,
            deadline: NULL_DDL,
        },
        {}, // here is overrides, you can pass in custom overrides for gas price and limit
        getReferralCode(channel),
    );
}

demoAdd().catch(console.error);

Direct remove liquidity interface

almost the same with removeLiquidity interface

/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { ethers } from 'ethers';
import { PERP_EXPIRY } from './constants';
import { SynFuturesV3 } from './synfuturesV3Core';
import { now } from '@derivation-tech/web3-core';

export async function demoRemove(): Promise<void> {
    const synfV3 = SynFuturesV3.getInstance('blast');
    const allInstruments = await synfV3.getAllInstruments();
    const instrument = allInstruments.find((i) => i.info.symbol.includes('BTC-USDB'))!;
    const pair = instrument.getPairModel(PERP_EXPIRY);
    // get your own signer
    const signer = new ethers.Wallet(process.env.ALICE_PRIVATE_KEY as string, synfV3.ctx.provider);
    await synfV3.syncVaultCacheWithAllQuotes(signer.address);
    const account = await synfV3.getPairLevelAccount(signer.address, instrument.info.addr, pair.amm.expiry);
    const range = account.ranges[0];

    const result = synfV3.simulateRemoveLiquidity(account, range, 100);

    await synfV3.remove(signer, instrument.info.addr, {
        expiry: pair.amm.expiry,
        target: signer.address,
        tickLower: range.tickLower,
        tickUpper: range.tickUpper,
        limitTicks: synfV3.encodeLimitTicks(result.sqrtStrikeLowerPX96, result.sqrtStrikeUpperPX96), // calculated based on slippage
        deadline: now() + 60,
    });
}

demoRemove().catch(console.error);

Direct cancel order interface

/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { ethers } from 'ethers';
import { PERP_EXPIRY } from './constants';
import { SynFuturesV3 } from './synfuturesV3Core';
import { now } from '@derivation-tech/web3-core';

export async function demoCancel(): Promise<void> {
    const synfV3 = SynFuturesV3.getInstance('blast');
    const allInstruments = await synfV3.getAllInstruments();
    const instrument = allInstruments.find((i) => i.info.symbol.includes('BTC-USDB'))!;
    const pair = instrument.getPairModel(PERP_EXPIRY);
    // get your own signer
    const signer = new ethers.Wallet(process.env.ALICE_PRIVATE_KEY as string, synfV3.ctx.provider);
    await synfV3.syncVaultCacheWithAllQuotes(signer.address);

    const account = await synfV3.getPairLevelAccount(signer.address, instrument.info.addr, pair.amm.expiry);

    await synfV3.cancel(signer, instrument.info.addr, {
        expiry: pair.amm.expiry,
        tick: account.orders[0].tick, // the price of the order you want to cancel in tick format
        deadline: now() + 60,
    });
}

demoCancel().catch(console.error);

Update pair

/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { ethers } from 'ethers';
import { PERP_EXPIRY } from './constants';
import { SynFuturesV3 } from './synfuturesV3Core';

export async function demoUpdate(): Promise<void> {
    const synfV3 = SynFuturesV3.getInstance('blast');
    const allInstruments = await synfV3.getAllInstruments();
    const instrument = allInstruments.find((i) => i.info.symbol.includes('BTC-USDB'))!;
    const pair = instrument.getPairModel(PERP_EXPIRY);

    // get your own signer
    const signer = new ethers.Wallet(process.env.ALICE_PRIVATE_KEY as string, synfV3.ctx.provider);

    await synfV3.update(signer, instrument.info.addr, pair.amm.expiry);
}

demoUpdate().catch(console.error);

Settle User

/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { ethers } from 'ethers';
import { PERP_EXPIRY } from './constants';
import { SynFuturesV3 } from './synfuturesV3Core';

export async function demoSettle(): Promise<void> {
    const synfV3 = SynFuturesV3.getInstance('blast');
    const allInstruments = await synfV3.getAllInstruments();
    const instrument = allInstruments.find((i) => i.info.symbol.includes('BTC-USDB'))!;
    const pair = instrument.getPairModel(PERP_EXPIRY);

    // get your own signer
    const signer = new ethers.Wallet(process.env.ALICE_PRIVATE_KEY as string, synfV3.ctx.provider);