0.0.19 • Published 7 months ago

@meteraprotocol/sdk v0.0.19

Weekly downloads
-
License
MIT
Repository
-
Last release
7 months ago

SDK Usage Guide

Overview

This SDK allows developers to interact with the Metera API, facilitating portfolio management, token operations, and order handling.


Installation

Install the SDK via npm:

npm install @meteraprotocol/sdk

Example Usage

Fetching Portfolio Data

With the SDK installed, you can fetch portfolio data as follows:

import { api } from '@meteraprotocol/sdk';

const config = new api.Configuration({
  basePath: 'https://metera-public-api-sample.com',
});

const sdk = api.SDKApiFactory();

const getPortfolios = async () => {
  return (await sdk.portfoliosGet()).data;
};

const getPortfolioState = async (portfolioId: string) => {
  return (await sdk.portfoliosStateIdGet(portfolioId)).data;
};

const getPortfolioPrice = async (
  portfolioId: string,
  period: api.PortfoliosPricePostRequestPeriodEnum,
) => {
  const sdk = api.SDKApiFactory();
  return (
    await sdk.portfoliosPricePost({
      portfolioId,
      period,
    })
  ).data;
};

getPortfolios()
  .then((res) => console.dir(res, { depth: null }))
  .catch(console.error);

getPortfolioState('porfolioId')
  .then((res) => console.dir(res, { depth: null }))
  .catch(console.error);

getPortfolioPrice('porfolioId', '7d')
  .then((res) => console.dir(res, { depth: null }))
  .catch(console.error);

Fetching Token Prices and Weights

Token prices and weights can be retrieved and processed as follows:

import { api } from '@meteraprotocol/sdk';
import { Prices } from '@meteraprotocol/core';
import { BigNumber } from 'bignumber.js';
import { BigRational } from 'big-rational-ts';

function bigNumberToBigRational(bigNumber: BigNumber): BigRational {
  if (bigNumber.eq(0)) return new BigRational(0n, 1n);
  const [numerator, denominator] = bigNumber
    .toFraction()
    .map((x) => BigInt(x.toFixed(0)));
  return new BigRational(numerator, denominator).reduce();
}

const portfolioState = (await sdk.portfoliosStateIdGet(portfolioId))
  .data as api.GetPortfolioStateResponse200;

const prices: Prices = Object.fromEntries(
  portfolioState.assets.map((asset) => {
    const rawPrice = bigNumberToBigRational(BigNumber(asset.price));
    const priceRootToken = rawPrice
      .div(new BigRational(10n ** BigInt(asset.asset.decimals), 1n))
      .reduce();
    return [asset.asset.id, priceRootToken];
  }),
);

const weights = Object.fromEntries(
  portfolioState.assets!.map((asset) => {
    const weight = new BigRational(
      BigInt(asset.weightNum),
      BigInt(asset.weightDenom),
    );
    return [asset.asset.id, weight];
  }),
);

Price Calculation

In blockchain systems, numerical values like token prices are often represented as integers (big integers) instead of decimals. This approach ensures precision and avoids rounding errors during on-chain computations. For instance:

  • A price of 0.001 might be stored as 1 in the blockchain.
  • To obtain a human-readable price, we divide the raw price by 10^decimals, where decimals represents the token's precision.

The calculation involves:

  1. Converting the raw price into a rational representation using dbNumericToBigRational.
  2. Dividing the raw price by 10^decimals to normalize it for human-readable usage.

This process retains the precision necessary for financial computations while aligning with blockchain-native practices of using integers.


Placing an Order

All orders require the use of the computeIntegration function to handle transaction computations. The result of this function gives the necessary data to create a deposit or withdrawal order, including the token amounts and mtk supply of the state after the order is executed.

Deposit Order

To create a deposit order:

import { computeInteraction } from '@meteraprotocol/core';

const stateBeforeDeposit = {
  assets: Object.fromEntries(
    portfolioState.assets.map((a) => [a.asset.id, BigInt(a.amount)]),
  ),
  mtkSupply: BigInt(portfolioState.supply),
};

const stateAfterDeposit = computeInteraction(
  prices,
  weights,
  stateBeforeDeposit,
  new BigRational(50n, 1n), // Deposit 50 ADA
);

const tokensToDeposit = Object.entries(stateAfterDeposit.assets).map(
  ([id, amount]) => {
    const assetBefore = stateBeforeDeposit.assets[id];
    const assetAfter = amount.getNumerator() / amount.getDenominator();
    return { id, amount: (assetAfter - assetBefore).toString() };
  },
);

const depositOrder = (
  await api.ordersCreatePost({
    address: '<cardano_address>',
    portfolioId: '<portfolio_id>',
    tokens: tokensToDeposit,
    maxBatcherFee: portfolioState.batcherFee,
    minMtkAcceptable: '1',
  })
).data;

Knowing the state before and after the deposit, we can calculate the amount of tokens to be deposited and create the order. The minMtkAcceptable parameter is the minimum amount of MTKs that the user is willing to accept for the deposit (we recommend to set it at '1').

Withdrawal Order

To create a withdrawal order we need to pass the minWorthAcceptable instead of minMtkAcceptable and also the amount of MTKs to be withdrawn.

import { computeInteraction } from '@meteraprotocol/core';

const stateBeforeWithdraw = {
  assets: Object.fromEntries(
    portfolioState.assets.map((a) => [a.asset.id, BigInt(a.amount)]),
  ),
  mtkSupply: BigInt(portfolioState.supply),
};

const stateAfterWithdraw = computeInteraction(
  prices,
  weights,
  stateBeforeWithdraw,
  new BigRational(50n, 1n), // Withdraw 50 ADA
);

const tokensToWithdraw = Object.entries(stateAfterWithdraw.assets).map(
  ([id, amount]) => {
    const assetBefore = stateBeforeWithdraw.assets[id];
    const assetAfter = amount.getNumerator() / amount.getDenominator();
    return { id, amount: (assetAfter - assetBefore).toString() };
  },
);

const mtkAfter =
  stateAfterWithdraw.mtkSupply.getNumerator() /
  stateAfterWithdraw.mtkSupply.getDenominator();
const mtkBefore = stateBeforeWithdraw.mtkSupply;

const withdrawOrder = (
  await api.ordersCreatePost({
    address: '<cardano_address>',
    portfolioId: '<portfolio_id>',
    tokens: tokensToWithdraw,
    amount: (mtkBefore - mtkAfter).toString(),
    maxBatcherFee: portfolioState.batcherFee,
    minWorthAcceptable: '1',
  })
).data;

Submitting the Order

After creating the order, you can submit it as follows, you will need a lucid instance with a wallet to sign the transaction.

import { ErrorResponse } from '@meteraprotocol/sdk';

if ('error' in depositOrder) {
  console.log(depositOrder.error);
} else {
  const cbor = depositOrder.cbor;
  const signedTx = await lucid.fromTx(cbor).sign().complete();
  const signedCbor = signedTx.toString();
  console.log('Submitting order');
  const { data: res } = await sdk.ordersSubmitPost({
    cbor: signedCbor,
    id: depositOrder.id,
  });

  if (typeof res !== 'string') {
    console.log('Error submitting order', (res as api.ErrorResponse).error);
  } else {
    console.log('Order submitted', res);
  }
}

UI Mint Burn Component Usage Guide

First of all you will need to instanciate the sdk and the lucid wallet. But now we import the ui related components and types from "@meteraprotocol/sdk/ui".

import { api } from '@meteraprotocol/sdk';
import { MintBurn, WalletApi } from '@meteraprotocol/sdk/ui';
import type { NextPage } from 'next';
import { useEffect, useState } from 'react';
const Home = () => {
  const [wallet, setWallet] = useState<WalletApi | null>(null);
  const [portfolio, setPortfolio] = useState<IPortfolioState | null>(null);
  // load wallet & portfolio info
  useEffect(() => {
    const meteraAPIConfig = new api.Configuration({
      basePath: process.env.NEXT_PUBLIC_BACKEND_URL,
    });
    const meteraAPI = api.SDKApiFactory(meteraAPIConfig);

    const newWallet = await window.cardano[walletName].enable();
    setWallet(newWallet);
    loadPortfolio(meteraAPI, setPortfolio);
  }, []);
};

You will need to setup the following environment variable:

NEXT_PUBLIC_BACKEND_URL=https://your-backend-url.com

Also this configuration on your next.config.js file is needed:

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
  webpack: (config) => {
    config.experiments = { ...config.experiments, topLevelAwait: true };
    return config;
  },
};

module.exports = nextConfig;

Now we need to fetch and parse the portfolio data. The following code takes a random portfolio and parses the response into a usable format:

import { api } from '@meteraprotocol/sdk';
import { IPortfolioState } from '@meteraprotocol/sdk/ui/types';

export const loadPortfolio = async (
  meteraAPI: ReturnType<typeof SDKApiFactory>,
  setPortfolio: Dispatch<SetStateAction<IPortfolioState | null>>,
) => {
  try {
    const portfolios = await meteraAPI.portfoliosGet();
    if ('error' in portfolios.data) {
      console.log('ERROR FETCHING PORTFOLIOS');
      return;
    }
    const portfolioId = portfolios.data[0].id;
    const portfolioState = await meteraAPI.portfoliosStateIdGet(portfolioId);
    if ('error' in portfolioState.data) {
      console.log('ERROR FETCHING PORTFOLIO STATE');
      return;
    }
    setPortfolio(parseStatusResponse(portfolioState.data));
  } catch (err) {
    console.log(err);
  }
};

// THIS IS A HELPER FUNCTION THAT PARSES THE RESPONSE FROM THE API INTO A USABLE FORMAT
export const parseStatusResponse = (
  statusResponse: api.GetPortfolioStateResponse200,
) => ({
  portfolio: {
    ...statusResponse.portfolio,
    createdAt: new Date(statusResponse.portfolio.createdAt),
    featured: BigInt(statusResponse.portfolio.featured),
  },
  price: statusResponse.price,
  supply: BigInt(statusResponse.supply),
  platformFee: BigInt(statusResponse.platformFee),
  assets: statusResponse.assets.map((asset) => ({
    ...asset,
    priceCreatedAt: new Date(asset.priceCreatedAt),
    amount: BigInt(asset.amount),
    weightNum: BigInt(asset.weightNum),
    weightDenom: BigInt(asset.weightDenom),
  })),
  entryFee: BigInt(statusResponse.entryFee),
  exitFee: BigInt(statusResponse.exitFee),
  batcherFee: BigInt(statusResponse.batcherFee),
});

You can use the MintBurn component as follows:

<MintBurn
  apiBaseUrl={process.env.NEXT_PUBLIC_BACKEND_URL!}
  network="Preview"
  portfolio={portfolio}
  type="mint"
  wallet={{ wallet }}
  //OPTIONAL STYLING
  containerProps={{
    width: '',
  }} // control the size of the modal. minWidth is 530px.
  background="" // string for the modal color
  primaryButtonColor="" // string for the main Buy button color
  hoverButtonColor="" // string for the hover Buy button color
/>

Styling the MintBurn component:

The minwidth

Now we are all set, you can now run your application and see the MintBurn component in action. You should be able to see something like this:

MintBurn frontend