Files
orionprotocol-sdk/PMM-sample.md
2024-03-29 07:27:52 +03:00

7.5 KiB

Orion Protocol SDK logo

PMM example

Overview

This is special example which shows how to use PMM liquidity without using SDK, only using few libraries

import {ethers, Wallet} from "ethers";
import {z} from "zod";
import hmacSHA256 from "crypto-js/hmac-sha256";
import Hex from "crypto-js/enc-hex";

///////////////////////////////
///////////////////////////////
const pmmOrderQuotationSchema = z.object({
  info: z.string().default(''),
  makerAsset: z.string().default(''),
  takerAsset: z.string().default(''),
  maker: z.string().default(''),
  allowedSender: z.string().default(''),
  makingAmount: z.string().default(''),
  takingAmount: z.string().default(''),
});

const pmmOrderSchema = z.object({
  order: pmmOrderQuotationSchema.default({}),
  signature: z.string().default(''),
  success: z.boolean().default(false),
  error: z.string().default(''),
});

const orionRFQContractABI =
        [
          {
            "inputs": [
              {
                "components": [
                  {
                    "internalType": "uint256",
                    "name": "info",
                    "type": "uint256"
                  },
                  {
                    "internalType": "address",
                    "name": "makerAsset",
                    "type": "address"
                  },
                  {
                    "internalType": "address",
                    "name": "takerAsset",
                    "type": "address"
                  },
                  {
                    "internalType": "address",
                    "name": "maker",
                    "type": "address"
                  },
                  {
                    "internalType": "address",
                    "name": "allowedSender",
                    "type": "address"
                  },
                  {
                    "internalType": "uint256",
                    "name": "makingAmount",
                    "type": "uint256"
                  },
                  {
                    "internalType": "uint256",
                    "name": "takingAmount",
                    "type": "uint256"
                  }
                ],
                "internalType": "struct OrderRFQLib.OrderRFQ",
                "name": "order",
                "type": "tuple"
              },
              {
                "internalType": "bytes",
                "name": "signature",
                "type": "bytes"
              },
              {
                "internalType": "uint256",
                "name": "flagsAndAmount",
                "type": "uint256"
              }
            ],
            "name": "fillOrderRFQ",
            "outputs": [],
            "stateMutability": "nonpayable",
            "type": "function"
          }
        ];

function encode_utf8(s : string) {
  return unescape(encodeURIComponent(s));
}
function sign(message : string, key: string) {
  return hmacSHA256(
          encode_utf8(message),
          encode_utf8(key)
  ).toString(Hex);
}

function generateHeaders(body : any, method : string, path : string, timestamp : number, apiKey : string, secretKey : string) {
  const sortedBody = Object.keys(body)
          .sort()
          .map((key) => (
                  `${key}=${body[key]}`
          )).join('&');

  const payload = timestamp + method.toUpperCase() + path + sortedBody;

  const signature = sign(payload, secretKey);

  const httpOptions = {
    headers: {
      'API-KEY': apiKey,
      'ACCESS-TIMESTAMP': timestamp.toString(),
      'ACCESS-SIGN': signature
    }
  };
  return httpOptions;
}

//
async function RFQOrder(
        tokenFrom: string,
        tokenTo: string,
        fromTokenAmount: string,
        apiKey: string, //
        secretKey: string,
        wallet: string
) : Promise<z.infer<typeof pmmOrderSchema>> {

  //  NB: replace with correct URL for networks
  const apiUrl = 'https://testing.orion.xyz/bsc-testnet/backend'
          //      const apiUrl = 'https://trade.orion.xyz/bsc-mainnet/backend'
          , path = '/rfq'
          , url = `${apiUrl}/api/v1/integration/pmm`+path
          , headers = {
            'Content-Type': 'application/json',
          }
          , data = {
            "baseToken":tokenFrom, // USDT
            "quoteToken":tokenTo, // ORN
            "amount": fromTokenAmount, // 100
            "taker": wallet,
            "feeBps": 0
          }
          , method = 'POST'
          , timestamp = Date.now()
          , signatureHeaders = generateHeaders(data, method, path, timestamp, apiKey, secretKey)
          , compiledHeaders = {...headers, ...signatureHeaders.headers, }
          , body = JSON.stringify(data)
  ;

  let res  = pmmOrderSchema.parse({});

  try {
    const result = await fetch(url,{
      headers: compiledHeaders,
      method,
      body
    });

    //  const data = await result.text();
    //  console.log(data);

    const json = await result.json();
    console.log(json);
    const parseResult = pmmOrderSchema.safeParse(json);

    if(!parseResult.success) {
      //  Try to parse error answer
      const errorSchema = z.object({error: z.object({code: z.number(), reason: z.string()})});

      const errorParseResult = errorSchema.safeParse(json);

      if(!errorParseResult.success)
        throw Error(`Unrecognized answer from aggregator: ${json}`);

      throw Error(errorParseResult.data.error.reason);
    }

    res.order = parseResult.data.order;
    res.signature = parseResult.data.signature;
    res.error = '';
    res.success = true;
  }
  catch(err) {
    res.error = `${err}`;
  }
  return res;
}


(async() => {
  const apiKey = '958153f1-b8b9-3ec4-84eb-2147429105d9';
  const secretKey = 'secretKey';

  //  BNB testnet tokens and router
  const assetORN = '0xf223eca06261145b3287a0fefd8cfad371c7eb34';
  const assetUSDT = '0xcb2951e90d8dcf16e1fa84ac0c83f48906d6a744';
  const router = '0x89357522C0ed6E557D39dC75290859246077bdfC';

  //  BNB mainnet tokens and router
  //  const assetORN = '0xe4ca1f75eca6214393fce1c1b316c237664eaa8e';
  //  const assetUSDT = '0x55d398326f99059ff775485246999027b3197955';
  //  const router = '0xcb2D40EabC4f4c92Ee993Eb3D67f7717bE476E76';

  const rfqOrder = await RFQOrder(
          assetORN,   //  Spending asset
          assetUSDT,  //  Receiving asset
          '1000000000',        //  Amount in "satoshi" of spending asset
          apiKey,
          secretKey,
          '0x61Eed69c0d112C690fD6f44bB621357B89fBE67F'  //  Can be any address, ignored for now
  );

  if(!rfqOrder.success) {
    console.log(rfqOrder.error);
    return;
  }

  //  ... here you can check order prices, etc.
  console.log(rfqOrder);

  //  Calldata according to provided rfqOrder
  const contractInterface = new ethers.Interface(orionRFQContractABI);
  const calldata = contractInterface.encodeFunctionData("fillOrderRFQ", [rfqOrder.order, rfqOrder.signature, 0]);
  console.log('Calldata = ', calldata);

  //  Call router with this data
  //  Replace with your private key
  const yourWalletPrivateKey = '0xf1.......';

  const tx = {
    to: router,
    data: calldata
  }

  const provider = new ethers.JsonRpcProvider("https://data-seed-prebsc-1-s1.binance.org:8545/");
  const signer = new ethers.Wallet(yourWalletPrivateKey, provider);
  console.log('Address = ', signer.address);
  const transactionResponse = await signer.sendTransaction(tx);
  console.log("Transaction hash:", transactionResponse.hash);
  await transactionResponse.wait();
})();