LogoLogo
  • 👋Welcome to Zeta
  • The Zeta DEX
    • 🔗Official Links
    • 📚Learn how to trade on Zeta
      • 🎓Introduction to Perpetuals
      • 💸Compatible Wallets
      • 📖Getting Started on Zeta
      • ✅Order Types
      • 💟Margin Account Parameters
      • 📑Asset Parameters
      • ⚖️Funding Rates
    • 🔧Trading Specifications
      • Fee Tiers
      • Delisting Process
    • 🏗️Exchange Architecture
      • Margin Accounts
      • Margin System
        • Collateral Framework
        • Liquidations & Insurance
          • Insurance Fund
          • Socialized Loss
      • Oracles
      • Orderbook & Matching Engine
      • Trigger orders
      • Trading Mechanism
      • Perpetual Funding System
    • 📱Mobile is LIVE !
    • 🌉Bridging Guide
      • 💱From Exchanges
      • ⛓️From Other Chains
  • Rewards Programs
    • 🪙$ZEX
      • 🪙Staking
        • Staking $ZEX: Step-by-Step Guide
    • 💱Trading Rewards Program
      • Z-Score Active Boosts
        • 🎁Z-Loot
      • 🥩Stakers' Boosted Trading Rewards
      • $BERA Listing (Live)
      • 🗃️Ended Trading Campaigns and Boosts
        • 🪐Jupiter x Zeta (Finished)
        • 1️⃣Season 1 (Finished)
          • 1️⃣Z-Score Season 1
            • 👥NFT Community Partners
          • ⚡S1 Zeta Cards
        • 2️⃣Season 2 (Finished)
          • 2️⃣Z-Score Season 2
            • 🍴Bitcoin Halving (Finished)
            • 🐶$WIF Listing (Finished)
            • 🔴$RNDR Listing (Finished)
            • ⬛$TNSR Listing (Finished)
            • 🔮Pyth Stakers (Finished)
            • 🟥Backpack Boost (Finished)
            • 🏁AssetDash Elements (Finished)
          • ⚡S2 Zeta Cards (Finished)
          • 🎉Community Airdrop (Finished)
        • $POPCAT Listing (Finished)
        • 👯Referral Z-Score Boost (Finished)
        • $EIGEN Listing (Finished)
        • 🟡$DBR Listing (Finished)
        • 🟡$GOAT Listing (Finished)
        • 🟪$DRIFT Listing (Live)
        • 🟪$PNUT Listing (Finished)
        • $PENGU Listing (Finished)
        • $TRUMP Listing (Finished)
    • 🔒Final Epoch
    • 🚰Maker Rewards Program
    • 👨‍👩‍👦‍👦Referrals Program
      • Genesis Epoch Referral Boost (Finished)
      • Breakpoint Referral Boost
    • 🫂Content & Community Programs
      • Creators Program
      • Community Referral Program
  • Build with Zeta
    • 💽SDKs
      • 🐍Python SDK
        • Market Making Bot
      • 🧱Typescript SDK
        • 📚Examples
          • 👶Basic example
          • ⚙️Cranking
          • 🌊Liquidator
          • 🚅Subscription
          • 📬Versioned Transactions
    • 😴REST Data API
    • 💥Program ABI
    • 🎨Brand Assets
      • 📣Brand Book
      • 📰Media Kit
      • 🎨Logo & Visual Guidelines
  • Zeta X
  • Zeta Node
    • 🌐Validator Details
  • Legal
    • Terms and Conditions
    • Staking Terms and Conditions
    • Privacy Policy
    • Location Restrictions
    • TradingView
    • Audit Reports
  • Educational Resources
    • 🆕New to Solana? Start Here!
    • 🆘Support
Powered by GitBook
On this page

Was this helpful?

  1. Build with Zeta
  2. SDKs
  3. Typescript SDK
  4. Examples

Cranking

Common crank instructions that keep our exchange moving

PreviousBasic exampleNextLiquidator

Last updated 1 year ago

Was this helpful?

Github repository:

There are multiple permissionless instructions that "crank", keeping the zeta platform in an up to date state. This example runs all the necessary instructions.

  1. Crank event queue: Process maker fill events to ensure that user margin accounts are in the correct state after trades occur.

  2. Update pricing: Recalculate mark prices and greeks for our products on an expiry basis. This will also update perp funding rates.

  3. Rebalance insurance vault: Moves funds collected from fees between margin accounts and the insurance vault (which is used to ensure platform security).

  4. Apply perp funding: Periodically apply any unpaid funding to all margin accounts holding perp positions.

  5. Prune expired TIF orders: When an order of TIF expires it must be pruned to get deleted. This happens automatically if a counterparty tries to trade against it however.

Code
require("dotenv").config();

import {
  Wallet,
  Exchange,
  Network,
  utils,
  programTypes,
  constants,
  Market,
  assets,
  types,
} from "@zetamarkets/sdk";
import { PublicKey, Connection, Keypair } from "@solana/web3.js";

let network: Network;
switch (process.env["network"]) {
  case "localnet":
    network = Network.LOCALNET;
    break;
  case "devnet":
    network = Network.DEVNET;
    break;
  case "mainnet":
    network = Network.MAINNET;
    break;
  default:
    throw Error("Unsupported network type!");
}

const NETWORK_URL = process.env["network_url"]!;
const MAX_ACCOUNTS_TO_FETCH = 99;
let crankingMarkets = new Array(constants.ACTIVE_MARKETS - 1).fill(false);
console.log(NETWORK_URL);
const assetList = [constants.Asset.BTC];

export async function sleep(ms: number) {
  await new Promise((resolve) => setTimeout(resolve, ms, undefined));
}

async function main() {
  // Generate a new keypair for wallet otherwise load from a private key.
  const userKey = Keypair.generate();
  const wallet = new Wallet(userKey);

  // Load from private_key stored in .env file.
  // const privateKey = Keypair.fromSecretKey(
  //   new Uint8Array(JSON.parse(Buffer.from(process.env.private_key).toString()))
  // );
  // const wallet = new Wallet(privateKey);

  // Create a solana web3 connection to devnet.
  const connection = new Connection(NETWORK_URL, "confirmed");

  // Airdropping SOL.
  // Only able to be done on localnet/devnet.
  if (network != Network.MAINNET) {
    await connection.requestAirdrop(wallet.publicKey, 100000000);
  }

  const loadExchangeConfig = types.defaultLoadExchangeConfig(
    network,
    connection,
    utils.defaultCommitment(),
    0, // ThrottleMs - increase if you are running into rate limit issues on startup.
    true
  );

  // We load the exchange with a valid wallet containing SOL to call permissionless zeta functions.
  await Exchange.load(loadExchangeConfig, wallet);

  // Display state of zeta markets
  setInterval(
    async function () {
      utils.displayState();
    },
    process.env.DISPLAY_STATE_INTERVAL
      ? parseInt(process.env.DISPLAY_STATE_INTERVAL)
      : 30000
  );

  /**
   * Cranks the serum dex event queue for each zeta market. This will process trades that consist of maker fills.
   * All other events are atomically processed at time of call such as taker fills and cancels.
   * Functionality here will keep track of markets that are currently being cranked, markets that have empty event queues
   * as well as allowing specification of whether only live markets are being cranked.
   * This will flush event queues completely upon call.
   * This function will poll all market event queues asynchronously so is quite expensive in terms of RPC requests per second.
   * Use crankExchangeThrottle if you are running into rate limit issues.
   */
  setInterval(
    async function () {
      assetList.map(async (asset) => {
        await utils.crankMarket(asset);
      });
    },
    process.env.CRANK_EXCHANGE_INTERVAL
      ? parseInt(process.env.CRANK_EXCHANGE_INTERVAL)
      : 5000
  );

  // Update pricing on live markets
  setInterval(
    async function () {
      await updatePricing();
    },
    process.env.UPDATE_PRICING_INTERVAL
      ? parseInt(process.env.UPDATE_PRICING_INTERVAL)
      : 5000
  );

  // Apply funding to any users holding perp positions
  setInterval(
    async function () {
      await applyFunding();
    }.bind(this),
    process.env.APPLY_FUNDING_INTERVAL
      ? parseInt(process.env.APPLY_FUNDING_INTERVAL)
      : 120_000
  );

  setInterval(
    async function () {
      await pruneExpiredTIFOrders();
    }.bind(this),
    process.env.PRUNE_EXPIRED_TIF_INTERVAL
      ? parseInt(process.env.PRUNE_EXPIRED_TIF_INTERVAL)
      : 60000
  );

  // Rebalance zeta vault and insurance fund
  setInterval(
    async function () {
      await rebalanceInsuranceVault();
    }.bind(this),
    process.env.REBALANCE_INSURANCE_VAULT_INTERVAL
      ? parseInt(process.env.REBALANCE_INSURANCE_VAULT_INTERVAL)
      : 20000
  );
}

/**
 * This calls zeta's permissionless update pricing function through the Exchange object.
 * Cranks zeta's on-chain pricing ensuring all our greeks and theos are up-to-date.
 */
async function updatePricing() {
  // Get relevant expiry indices.

  await Promise.all(
    assetList.map(async (asset) => {
      try {
        console.log(`[${assets.assetToName(asset)}] Update pricing`);
        await Exchange.updatePricing(asset);
      } catch (e) {
        console.error(
          `[${assets.assetToName(asset)}] Update pricing failed. ${e}`
        );
      }
    })
  );
}

async function applyFunding() {
  let marginAccPubkeys = [];
  try {
    marginAccPubkeys = await utils.getAllProgramAccountAddresses(
      types.ProgramAccountType.MarginAccount
    );
  } catch (e) {
    throw Error("Account address fetch error on applyFunding()!");
  }
  for (
    let i = 0;
    i < marginAccPubkeys.length;
    i += constants.MAX_FUNDING_ACCOUNTS
  ) {
    // Grab set of margin accounts
    let marginAccounts = [];
    try {
      marginAccounts =
        await Exchange.program.account.marginAccount.fetchMultiple(
          marginAccPubkeys.slice(i, i + constants.MAX_FUNDING_ACCOUNTS)
        );
    } catch (e) {
      throw Error("Account data fetch error on applyFunding()!");
    }

    // In that set: Check if any have non-zero perp positions
    // If they do, apply funding on them
    let fundingAccounts = new Map<constants.Asset, PublicKey[]>();
    for (var asset of Exchange.assets) {
      fundingAccounts.set(asset, []);
    }

    for (let j = 0; j < marginAccounts.length; j++) {
      if (marginAccounts[j].perpProductLedger.position.size != 0) {
        fundingAccounts
          .get(assets.fromProgramAsset(marginAccounts[j].asset))
          .push(marginAccPubkeys[i + j]);
      }
    }

    // This will automatically break into multiple txs if there are too many
    for (var asset of Exchange.assets) {
      await utils.applyPerpFunding(asset, fundingAccounts.get(asset));
    }
  }
}

async function pruneExpiredTIFOrders() {
  let subExchanges = Exchange.getAllSubExchanges();

  try {
    await Promise.all(
      subExchanges.map((se) => {
        return utils.pruneExpiredTIFOrders(se.asset);
      })
    );
    console.log("Pruned expired TIF orders.");
  } catch (e) {
    console.log("Failed to prune expired TIF orders.", `Error=${e}`);
  }
}

/**
 * Rebalances the zeta vault and the insurance vault to ensure consistent platform security.
 * Checks all margin accounts for non-zero rebalance amounts and rebalances them all.
 */
async function rebalanceInsuranceVault() {
  let marginAccPubkeys: PublicKey[];
  try {
    marginAccPubkeys = await utils.getAllProgramAccountAddresses(
      types.ProgramAccountType.MarginAccount
    );
  } catch (e) {
    console.log(
      "rebalanceInsuranceVault account address fetch error",
      `Error=${e}`
    );
  }

  for (let i = 0; i < marginAccPubkeys.length; i += MAX_ACCOUNTS_TO_FETCH) {
    let marginAccs: any[];
    try {
      marginAccs = await Exchange.program.account.marginAccount.fetchMultiple(
        marginAccPubkeys.slice(i, i + MAX_ACCOUNTS_TO_FETCH)
      );
    } catch (e) {
      console.log(
        "rebalanceInsuranceVault margin account fetch error",
        `Error=${e}`
      );
    }

    let remainingAccounts: any[] = new Array();
    for (let j = 0; j < marginAccs.length; j++) {
      if (marginAccs[j].rebalanceAmount.toNumber() != 0) {
        remainingAccounts.push({
          pubkey: marginAccPubkeys[i + j],
          isSigner: false,
          isWritable: true,
        });
      }
    }
    console.log(
      `[REBALANCE INSURANCE VAULT] for ${remainingAccounts.length} accounts.`
    );
    try {
      await Exchange.rebalanceInsuranceVault(remainingAccounts);
    } catch (e) {
      console.log(
        "rebalanceInsuranceVault vault error on transaction",
        `Error=${e}`
      );
    }
  }
}

main().catch(console.error.bind(console));
💽
🧱
📚
⚙️
https://github.com/zetamarkets/sdk/tree/main/examples/cranking