@meteraprotocol/sdk v0.0.19
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 as1
in the blockchain. - To obtain a human-readable price, we divide the raw price by
10^decimals
, wheredecimals
represents the token's precision.
The calculation involves:
- Converting the raw price into a rational representation using
dbNumericToBigRational
. - 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: