@parabolfi/server
Version:
Server utilities for Parabol SDK
156 lines (153 loc) • 17.3 kB
JavaScript
import { GraphQueryType, BPS, ParabolSDKError, COUPON_DENOMINATOR, PARABOL_ENV, SupportedChainIds, validateNoteDataType, convertAPIResponseToNoteData, replacerBig, PositionStatus, getContractAddress, ContractTarget, validateAndTransformResponse, PARABOL_API_ENDPOINT, PARABOL_DEVELOPMENT_API_ENDPOINT, PARABOL_TESTNET_API_ENDPOINT } from '@parabolfi/core';
import { isAddress } from 'viem';
import { toZonedTime } from 'date-fns-tz';
import { differenceInDays, getUnixTime, format } from 'date-fns';
import a from 'big.js';
import v from 'axios';
var C={[GraphQueryType.FETCH_MIN_LEND_LIMIT]:`
query FetchMinLendLimit {
minLendLimit(id: "1") {
minLendLimit
}
}
`,[GraphQueryType.FETCH_PARABOL_CONFIG]:`
query FetchParabolConfig {
nonFungibleNotePositionContractInfo(id: "1") {
name
version
}
parabolUSDContractInfo(id: "1") {
name
version
}
}
`,[GraphQueryType.FETCH_ALL_POSITIONS]:`
query FetchAllPositions($walletAddress: Bytes!) {
positionInfos(
where: {lastOwner: $walletAddress}
orderBy: tokenId
orderDirection: desc
) {
tokenId
owner: lastOwner
initalOwner
initiator
coupon
principal
lendTimestamp
maturityTimestamp
partnerFeeBPS
partnerId
status
lendTransactionHash
}
}
`,[GraphQueryType.FETCH_FLOATING_INCOMES]:`
query FetchFloatingIncomes{
floatingIncomes(orderBy: day, orderDirection: desc) {
day
floatingIncome
accFloatingIncome
}
}
`,[GraphQueryType.FETCH_LEND_TRANSACTIONS]:`
query FetchLendTransactions($walletAddress: Bytes!) {
positionInfos(
where: {initalOwner: $walletAddress}
orderBy: tokenId
orderDirection: desc
) {
tokenId
initalOwner
coupon
principal
lendTimestamp
maturityTimestamp
partnerFeeBPS
partnerId
status
lendTransactionHash
}
}
`,[GraphQueryType.FETCH_CLAIMED_POSITIONS]:`
query FetchClaimedPositions($walletAddress: Bytes!) {
claimedPositionInfos(where: {beneficiary: $walletAddress}) {
beneficiary
claimTimestamp
fee
initiator
tokenId
principal
totalIncome
claimTransactionHash
}
}
`,[GraphQueryType.FETCH_PARTNER_CONFIG]:`
query FetchPartnerConfig($walletAddress: Bytes!) {
partnerInfos(
where: {partnerOwner: $walletAddress}
) {
id
partnerFeeBPS
partnerOwner
partnerVault
}
}
`,[GraphQueryType.FETCH_POSITION_TRANSFERS]:`
query FetchPositionTransfers($walletAddress: Bytes!) {
positionTransfers(
where: {
and: [
{or: [{from: $walletAddress}, {to: $walletAddress}]},
{from_not: "0x0000000000000000000000000000000000000000"},
{to_not: "0x0000000000000000000000000000000000000000"}
]
}
) {
tokenId
from
to
blockTimestamp
transactionHash
}
}
`,[GraphQueryType.FETCH_BALANCE]:`
query FetchBalance($walletAddress: ID!) {
parabolUSDBalance(id: $walletAddress) {
address
balance
}
}
`,[GraphQueryType.FETCH_PARABOL_USD_NONCE]:`
query FetchParabolUSDNonce($walletAddress: ID!) {
parabolUSDNonce(id: $walletAddress) {
nonce
id
}
}
`,[GraphQueryType.FETCH_NONFUNGIBLE_NOTE_POSITION_NONCE]:`
query FetchNonFungibleNotePositionNonce($walletAddress: ID!) {
positionNonce(id: $walletAddress) {
nonce
id
}
}
`,[GraphQueryType.FETCH_PARABOL_USD_ALLOWANCE]:`
query FetchParabolUSDAllowance($ownerToOperator: ID!) {
parabolUSDAllowance(id: $ownerToOperator) {
owner
spender
value
}
}
`,[GraphQueryType.FETCH_POSITION_ALLOWANCE]:`
query FetchPositionDetailedAllowance($ownerToOperator: ID!, $ownerTotokenId: ID!) {
positionOperatorAllowance(id: $ownerToOperator) {
approved
}
positionAllowance(id: $ownerTotokenId) {
spender
}
}
`};function D(){let i="America/New_York",t=toZonedTime("1970-01-01",i),e=toZonedTime(new Date,i);return differenceInDays(e,t)}function A(i,t){return i.minus(t)}function E(i){let t=i.div(86400);return a(t)}function _(){return a(getUnixTime(new Date))}function U(i,t,e,r){return i.times(t).times(A(r,e)).div(86400*360*BPS)}function q(i){if(i.length===0)throw ParabolSDKError.noteDataUnavailable("No notes found");let t=i.map((o,s)=>({rate:a(o.coupon),index:s})),e=Math.max(...t.map(o=>o.rate.toNumber()));return {rate:parseFloat(e.toString())/COUPON_DENOMINATOR}}function V(i){let t=D(),e=a(i.find(n=>n.day===t-1)?.floatingIncome?.toString()??i.find(n=>n.day===t-2)?.floatingIncome?.toString()??"0"),r=a(86400*360*1e8),o=a(1e25),s=e.times(r).div(o);return Number(s.div(1e6).toFixed(2))}function O(i,t){let{rate:e}=q(i),r=V(t);return {marketRate:(e+r).toString(),floatingRate:r.toString(),highestFixedRate:e.toString()}}function K(i,t,e,r){let o=_(),s=i.times(t).times(A(o.lt(r)?o:r,e)).div(86400*360*BPS),n=U(i,t,e,r);return {currentFixedIncome:s.round(0,a.roundDown),approximateFixedIncome:n.round(0,a.roundDown)}}function R(i,t,e){let r=E(t).round(0,a.roundDown),o=e.find(d=>d.day===r.toNumber())?.floatingIncome??a(0),s=r.add(1).times(86400).minus(t);return i.times(o).times(s).div(1e25).round(0,a.roundDown)}function j(i,t,e,r,o){let s,n,d=E(e).round(0,a.roundDown),c=E(t).round(0,a.roundDown);if(r.eq(0)||r.lt(c)||c.eq(d))return {lendDayFloatingIncome:a(0),totalFloatingIncome:a(0)};let l=R(i,t,o),p=o.find(m=>m.day===c.toNumber())?.accFloatingIncome??a(0);r.lt(d)?s=o.find(m=>m.day===r.toNumber())?.accFloatingIncome??a(0):(s=o.find(I=>I.day===d.minus(1).toNumber())?.accFloatingIncome??a(0),n=(o.find(I=>I.day===d.toNumber())?.accFloatingIncome??a(0)).minus(s).times(i).times(e.minus(d.times(86400))).div(1e25));let y=s.minus(p).times(i).times(86400).div(1e25).round(0,a.roundDown).add(l).add(n??a(0));return {lendDayFloatingIncome:l,totalFloatingIncome:y}}function W(i,t){let e=A(t,i),r=e.div(31104e3).round(0,a.roundDown),o=e.minus(r.times(31104e3)).div(2592e3).round(0,a.roundDown),s=e.minus(r.times(31104e3)).minus(o.times(2592e3)).div(86400).round(0,a.roundDown),n=_(),d=A(t,n.gt(t)?t:n),c=d.div(86400).round(0,a.roundDown),l=d.minus(c.times(86400)).div(3600).round(0,a.roundDown),p=d.minus(c.times(86400)).minus(l.times(3600)).div(60).round(0,a.roundDown);return {staticDuration:{year:r.toNumber(),month:o.toNumber(),day:s.toNumber()},dynamicDuration:{totalDays:c.toNumber(),hours:l.toNumber(),minutes:p.toNumber()}}}function J(i,t,e,r,o){let s=E(r).round(0,a.roundDown),n=E(o).round(0,a.roundDown);if(t.lt(s))return a(0);if(t.eq(s))return R(e,r,i);if(t.gte(n))return i.reduce((y,m)=>a(m.day).gte(s)&&a(m.day).lt(n)?y.plus(a(m.floatingIncome).times(e).times(86400).div(1e25)):y,a(0));let d=t.minus(s).toNumber(),c=a(0);for(let y=0;y<d;y++){let m=t.minus(y),I=i.find(w=>a(w.day).eq(m));I&&(c=c.plus(a(I.floatingIncome)));}c=c.div(d);let l=n.minus(t),p=c.times(l).times(e).times(86400).div(1e25);return i.reduce((y,m)=>a(m.day).gte(s)&&a(m.day).lte(t)?y.plus(a(m.floatingIncome).times(e).times(86400).div(1e25)):y,a(0)).plus(p).round(0,a.roundDown)}function Y(i,t,e,r,o){let s=!1,n=D(),d=a(o.reduce((m,I)=>Math.max(m,I.day),0)),c=E(r);a(n).gte(c)&&(s=!0);let{currentFixedIncome:l,approximateFixedIncome:p}=K(i,t,e,r),{totalFloatingIncome:h}=j(i,e,r,d,o),y=J(o,d,i,e,r);return {fixedCurrentIncome:l,fixedApproximateIncome:p,floatingCurrentIncome:h,floatingApproximateIncome:y,totalCurrentIncome:l.add(h),approximateIncome:s?l.add(h):p.add(y)}}function x(i,t){let r=i.toNumber()*1e3,o=t?"dd MMMM hh:mm a, yyyy":"dd MMMM, yyyy";return format(new Date(r),o)}function Z(i,t){let e=A(t,i),r=_(),o=A(r,i);if(e.gt(0)){let s=o.times(100).div(e);return Math.floor(Math.min(Math.max(s.toNumber(),0),100))}else return 0}function S(i,t){let e=a(0),r=a(0),o=a(0);return {extendedPositions:i.map(n=>{e=e.add(n.principal.round(0,a.roundDown));let d=Y(n.principal,n.coupon,n.lendTimestamp,n.maturityTimestamp,t);return r=r.add(d.fixedCurrentIncome),o=o.add(d.floatingCurrentIncome.round(0,a.roundDown)),{...n,maturityDate:x(n?.maturityTimestamp),lendingDate:x(n.lendTimestamp),duration:W(n.lendTimestamp,n.maturityTimestamp),progress:Z(n.lendTimestamp,n.maturityTimestamp),earning:d}}),totalLoaned:e,totalEarned_fixed:r,totalEarned_bonus:o}}var Q=class{apiKey;endpoint;env;logger;constructor(t={}){let e=t.apiKey||process.env.PARABOL_API_KEY;if(!e)throw new Error("API key is required for ParabolServer. Provide it in options or set PARABOL_API_KEY environment variable.");this.apiKey=e,this.env=t.env||PARABOL_ENV.Production,this.endpoint=this.getEndpoint(this.env),this.logger=t.logger||console.log;}async fetchParabolData(t={}){try{let e=[t.chain!==void 0?`chainid=${t.chain}`:null,t.maturity?.length?`maturity=${t.maturity.join(",")}`:null].filter(Boolean).join("&"),r=`${this.endpoint}notes${e?`?${e}`:""}`;if(t.chain!==void 0&&!Object.values(SupportedChainIds).includes(t.chain))throw ParabolSDKError.invalidChainId(t.chain);let o=this.getDefaultChain(),[s,n]=await Promise.all([v.get(r,{headers:{"Content-Type":"application/json","x-api-key":this.apiKey}}),this.fetchFloatingIncomes()]),d=s.data;validateNoteDataType(d);let c=d.data.map(convertAPIResponseToNoteData).sort((h,y)=>h.maturity_timestamp.toNumber()-y.maturity_timestamp.toNumber()),{marketRate:l,floatingRate:p}=O(c,n[o]);return {data:JSON.parse(JSON.stringify(c,replacerBig)),availableMaturities:d.availableMaturities??[],marketRate:l,floatingRate:p}}catch(e){throw this.logger(`Error fetching fixed notes: ${e.message}`,"error"),ParabolSDKError.parabolProxyFailed(e.cause)}}async fetchParabolConfig(t){try{return await this.executeQuery(GraphQueryType.FETCH_PARABOL_CONFIG,{chains:t})}catch(e){throw this.logger(`Error fetching parabol config: ${e.message}`,"error"),ParabolSDKError.parabolProxyFailed(e.cause)}}async fetchMinLendLimit(t){try{return await this.executeQuery(GraphQueryType.FETCH_MIN_LEND_LIMIT,{chains:[t]})}catch(e){throw this.logger(`Error fetching min lend limit: ${e.message}`,"error"),ParabolSDKError.parabolProxyFailed(e.cause)}}async fetchLendParameters(t){try{let[e,r]=await Promise.all([this.executeQuery(GraphQueryType.FETCH_MIN_LEND_LIMIT,{chains:[t]}),this.fetchParabolData({chain:t})]);if(!e[t])throw ParabolSDKError.noteDataUnavailable("No config found for chain");let{minLendLimit:o}=e[t],{data:s,availableMaturities:n}=r;return {minLendLimit:o,data:s,availableMaturities:n}}catch(e){throw this.logger(`Error fetching lend parameters: ${e.message}`,"error"),ParabolSDKError.parabolProxyFailed(e.cause)}}async fetchAllPositions(t,e,r){let o={};try{let[s,n]=await Promise.all([this.executeQuery(GraphQueryType.FETCH_ALL_POSITIONS,{walletAddress:t.toLowerCase(),chains:e}),this.fetchFloatingIncomes()]),d=r||(n?Object.values(n)[0]:[]);for(let[l,p]of Object.entries(s)){let{extendedPositions:h,totalLoaned:y,totalEarned_fixed:m,totalEarned_bonus:I}=S(p,r||d||[]);o[l]={positions:h,active:h.filter(w=>w.status===PositionStatus.ACTIVE),claimed:h.filter(w=>w.status===PositionStatus.CLAIMED),totalLoaned:y,totalEarned_fixed:m,totalEarned_bonus:I};}return JSON.parse(JSON.stringify(o,replacerBig))}catch(s){throw this.logger(`Error fetching all positions: ${s.message}`,"error"),ParabolSDKError.parabolProxyFailed(s.cause)}}async fetchLendTransactions(t,e){try{return await this.executeQuery(GraphQueryType.FETCH_LEND_TRANSACTIONS,{walletAddress:t.toLowerCase(),chains:e})}catch(r){throw this.logger(`Error fetching lend transactions: ${r.message}`,"error"),ParabolSDKError.parabolProxyFailed(r.cause)}}async fetchClaimedPositions(t,e){try{return await this.executeQuery(GraphQueryType.FETCH_CLAIMED_POSITIONS,{walletAddress:t.toLowerCase(),chains:e})}catch(r){throw this.logger(`Error fetching claimed positions: ${r.message}`,"error"),ParabolSDKError.parabolProxyFailed(r.cause)}}async fetchPositionTransfers(t,e){try{return await this.executeQuery(GraphQueryType.FETCH_POSITION_TRANSFERS,{walletAddress:t.toLowerCase(),chains:e})}catch(r){throw this.logger(`Error fetching position transfers: ${r.message}`,"error"),ParabolSDKError.parabolProxyFailed(r.cause)}}async fetchPartnerConfig(t,e){try{return await this.executeQuery(GraphQueryType.FETCH_PARTNER_CONFIG,{walletAddress:t.toLowerCase(),chains:e})}catch(r){throw this.logger(`Error fetching partner info: ${r.message}`,"error"),ParabolSDKError.parabolProxyFailed(r.cause)}}async fetchParabolUSDBalance(t,e){try{return await this.executeQuery(GraphQueryType.FETCH_BALANCE,{walletAddress:t.toLowerCase(),chains:e})}catch(r){throw this.logger(`Error fetching balance: ${r.message}`,"error"),ParabolSDKError.parabolProxyFailed(r.cause)}}async fetchParabolUSDNonce(t,e){try{return await this.executeQuery(GraphQueryType.FETCH_PARABOL_USD_NONCE,{walletAddress:t.toLowerCase(),chains:e})}catch(r){throw this.logger(`Error fetching parabol USD nonce: ${r.message}`,"error"),ParabolSDKError.parabolProxyFailed(r.cause)}}async fetchNonFungibleNotePositionNonce(t,e){try{return await this.executeQuery(GraphQueryType.FETCH_NONFUNGIBLE_NOTE_POSITION_NONCE,{walletAddress:t.toLowerCase(),chains:e})}catch(r){throw this.logger(`Error fetching non-fungible note position nonce: ${r.message}`,"error"),ParabolSDKError.parabolProxyFailed(r.cause)}}async fetchParabolUSDAllowance(t,e,r){try{return await this.executeQuery(GraphQueryType.FETCH_PARABOL_USD_ALLOWANCE,{ownerToOperator:`${t.toLowerCase()}-${e.toLowerCase()}`,chains:r})}catch(o){throw this.logger(`Error fetching parabol USD allowance: ${o.message}`,"error"),ParabolSDKError.parabolProxyFailed(o.cause)}}async fetchPositionAllowance(t,e,r){try{let o=getContractAddress(r,ContractTarget.ReserveStabilityPool);return await this.executeQuery(GraphQueryType.FETCH_POSITION_ALLOWANCE,{ownerToOperator:`${t.toLowerCase()}-${o.toLowerCase()}`,ownerToTokenId:`${t.toLowerCase()}-${e}`,chains:[r]})}catch(o){throw this.logger(`Error fetching position single allowance: ${o.message}`,"error"),ParabolSDKError.parabolProxyFailed(o.cause)}}async fetchFloatingIncomes(t){try{let e=t??this.getDefaultChain();return await this.executeQuery(GraphQueryType.FETCH_FLOATING_INCOMES,{chains:[e]})}catch(e){throw this.logger(`Error fetching floating incomes: ${e.message}`,"error"),ParabolSDKError.parabolProxyFailed(e.cause)}}async executeQuery(t,e){let r;try{this.validateVariables(e);let s=(await v.post(this.endpoint+"data",{query:C[t],variables:e},{headers:{"Content-Type":"application/json","x-api-key":this.apiKey}})).data,n={};for(let d of s){if(typeof d.data!="object"||d.data===null)throw ParabolSDKError.invalidAPIResponseFormat();let c=Object.keys(d.data);if(c.length===1){let l=c[0];r=l?d.data[l]:[];}else r=c.reduce((l,p)=>{let h=d.data[p];return {...l,[p]:h}},{});n[d.chain.toString()]=validateAndTransformResponse(t,r,e,d.chain);}return n}catch(o){throw this.logger(`Error executing query: ${o.message}`,"error"),ParabolSDKError.executeQueryFailed(o.code)}}validateVariables(t){let{walletAddress:e,chains:r,ownerToOperator:o,ownerToTokenId:s}=t;if(!Array.isArray(r)||r.length===0)throw ParabolSDKError.chainDetectionFailed();if(r.forEach(n=>{if(!Number.isInteger(n)||!Object.values(SupportedChainIds).includes(n))throw ParabolSDKError.invalidChainId(n)}),e!==void 0){if(typeof e!="string")throw ParabolSDKError.invalidAddress("Wallet address must be a string");if(!isAddress(e))throw ParabolSDKError.invalidAddress(`Invalid wallet address: ${e}`)}if(s!==void 0){let[n,d]=s.split("-");if(!n||!isAddress(n))throw ParabolSDKError.invalidAddress(`Invalid owner address in ownerToTokenId: ${n}`);let c=Number(d);if(isNaN(c)||!Number.isInteger(c)||c<0)throw ParabolSDKError.invalidTokenId()}if(o!==void 0){let[n,d]=o.split("-");if(!n||!isAddress(n))throw ParabolSDKError.invalidAddress(`Invalid owner address in ownerToOperator: ${n}`);if(!d||!isAddress(d))throw ParabolSDKError.invalidAddress(`Invalid operator address in ownerToOperator: ${d}`)}}getDefaultChain(){return this.env===PARABOL_ENV.Production?SupportedChainIds.BASE:SupportedChainIds.BASE_SEPOLIA}getEndpoint(t){switch(t){case PARABOL_ENV.Testnet:return PARABOL_TESTNET_API_ENDPOINT;case PARABOL_ENV.Development:return PARABOL_DEVELOPMENT_API_ENDPOINT;case PARABOL_ENV.Production:default:return PARABOL_API_ENDPOINT}}};
export { Q as ParabolServer, x as getActionDateAsString, D as getAmericaNewYorkTZDayIndex, U as getApproximateFixedIncome, _ as getCurrentTimestamp, J as getEstimationOfFloatingIncomeUntilMaturity, S as getExtendedPositionData, R as getFirstDayFloatingIncome, K as getFixedIncome, q as getFixedRate, j as getFloatingIncome, V as getFloatingRate, O as getMarketRates, Y as getNotePositionEarnings, W as getNotePositionTotalDuration, Z as getProgress, E as getTimestampDay, A as getTimestampDiff, C as graphQueries };