diff --git a/.gitignore b/.gitignore index dcb571c..aabe9ad 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,6 @@ coverage.json __pycache__/ .npmrc -.editorconfig \ No newline at end of file +.editorconfig + +*/.idea \ No newline at end of file diff --git a/backend-api-demo/README.md b/backend-api-demo/README.md new file mode 100644 index 0000000..7d93cf0 --- /dev/null +++ b/backend-api-demo/README.md @@ -0,0 +1,21 @@ +# Pendle Limit Order API Demo + +This repository is designed for developers seeking to interact with Pendle Backend using TypeScript. + +The demo project demonstrates how to +- Get list of pendle markets and assets +- Get asset's price (swapping prices, spot prices, historical prices) +- Get market historical info + +## Getting Started +To run examples: +- Change directory to the demo project folder: `cd limit-order-api-demo` +- Install required dependencies with `npm install` +- Execute the index.ts file using `npm run start` + +## Api specifications +* [Documentation](https://api-v2.pendle.finance/external/docs#/) + +## Additional Notes + +Please be aware that the code examples provided in this repository are for educational purposes and are not intended for production use. They serve as a starting point for integrating Pendle Backend functionality into your decentralized application (dApp). diff --git a/backend-api-demo/package-lock.json b/backend-api-demo/package-lock.json new file mode 100644 index 0000000..126cc19 --- /dev/null +++ b/backend-api-demo/package-lock.json @@ -0,0 +1,97 @@ +{ + "name": "backend-api-demo", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "backend-api-demo", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "axios": "^1.7.3" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.7.3", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "license": "MIT" + } + } +} diff --git a/backend-api-demo/package.json b/backend-api-demo/package.json new file mode 100644 index 0000000..9424886 --- /dev/null +++ b/backend-api-demo/package.json @@ -0,0 +1,15 @@ +{ + "name": "backend-api-demo", + "version": "1.0.0", + "description": "Pendle Backend API Demo", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "npx ts-node ./src/index.ts" + }, + "author": "Manh Cao Duy", + "license": "ISC", + "dependencies": { + "axios": "^1.7.3" + } +} diff --git a/backend-api-demo/src/const.ts b/backend-api-demo/src/const.ts new file mode 100644 index 0000000..6566424 --- /dev/null +++ b/backend-api-demo/src/const.ts @@ -0,0 +1 @@ +export const EXTERNAL_DOMAIN = `https://api-v2.pendle.finance/api/external`; \ No newline at end of file diff --git a/backend-api-demo/src/get-asset-prices.ts b/backend-api-demo/src/get-asset-prices.ts new file mode 100644 index 0000000..df9c226 --- /dev/null +++ b/backend-api-demo/src/get-asset-prices.ts @@ -0,0 +1,87 @@ +import { EXTERNAL_DOMAIN } from "./const"; +import axios from "axios"; + +interface GetSpotPriceParam { + chainId: number; +} + +interface GetSpotPriceQuery { + address?: string[]; +} + +interface GetSpotPriceResponse { + prices: Record; +} + +interface GetHistoricalPricesParam { + chainId: number; + address: string; +} + +interface GetHistoricalPricesQuery { + timeFrame?: "hour" | "day" | "week"; + timestampStart?: Date; + timestampEnd?: Date; +} + +interface HistoricalPriceData { + timestamps: number[]; + highs: number[]; + lows: number[]; + opens: number[]; + closes: number[]; +} + +interface GetHistoricalPricesResponse { + total: number; + limit: number; + currency: string; + timestampStart: string; + timestampEnd: string; + data: HistoricalPriceData; +} + +export async function getAssetPrices() { + // This is an example of how to get all spot prices of assets on Ethereum + + const param: GetSpotPriceParam = { + chainId: 1, // Ethereum + }; + + const targetPath = `/v1/${param.chainId}/assets/prices`; + + const { data } = await axios.get(EXTERNAL_DOMAIN + targetPath); + + const { prices } = data; + + const key = Object.keys(prices)[0]; + console.log(`prices of ${key} is ${prices[key]} USD`); +} + +export async function getHistoricalAssetPrices() { + // This is an example of how to get daily historical prices from 2024 Aug 5th to Aug 11st of PT-USDO++ on Ethereum + + const param: GetHistoricalPricesParam = { + chainId: 1, // Ethereum + address: '0x270d664d2fc7d962012a787aec8661ca83df24eb' // PT-USD0++ 31OCT 2024 + }; + + const query: GetHistoricalPricesQuery = { + timeFrame: "day", + timestampStart: new Date("2024-08-05T00:00:00.000+00:00"), + timestampEnd: new Date("2024-08-11T00:00:00.000+00:00"), + }; + + const targetPath = `/v1/${param.chainId}/assets/${param.address}/historical-prices`; + const { data: response } = await axios.get(EXTERNAL_DOMAIN + targetPath, { params: query }); + console.log('response data', {total: response.total, limit: response.limit, currency: response.currency, timestampStart: response.timestampStart, timestampEnd: response.timestampEnd}); + + const {data} = response; + console.log('first data point info', { + timestamp: data.timestamps[0], + high: data.highs[0], + low: data.lows[0], + open: data.opens[0], + close: data.closes[0], + }) +} \ No newline at end of file diff --git a/backend-api-demo/src/get-list-assets.ts b/backend-api-demo/src/get-list-assets.ts new file mode 100644 index 0000000..e6f209f --- /dev/null +++ b/backend-api-demo/src/get-list-assets.ts @@ -0,0 +1,37 @@ +import axios from "axios"; +import { EXTERNAL_DOMAIN } from "./const"; + +interface Param { + chainId: number; +} + +interface AssetInfo { + name: string; + decimals: number; + address: string; + symbol: string; + tags: string[]; + expiry: string; +} + +interface Response { + assets: AssetInfo[]; +} + +export async function getAssetList() { + // This is an example of how to get list of Pendle assets on Ethereum + + const param: Param = { + chainId: 1, // Ethereum + } + + const targetPath = `/v1/${param.chainId}/assets/all`; + + const { data } = await axios.get(EXTERNAL_DOMAIN + targetPath); + + const { assets } = data; + + const {name, address, decimals, expiry, symbol, tags} = assets[0]; + + console.log('first asset', {name, address, decimals, expiry, symbol, tags}); +} \ No newline at end of file diff --git a/backend-api-demo/src/get-list-markets.ts b/backend-api-demo/src/get-list-markets.ts new file mode 100644 index 0000000..991415c --- /dev/null +++ b/backend-api-demo/src/get-list-markets.ts @@ -0,0 +1,55 @@ +import axios from "axios"; +import { EXTERNAL_DOMAIN } from "./const"; + +interface Param { + chainId: number; +} + +interface MarketInfo { + name: string; + address: string; + expiry: string; + pt: string; + yt: string; + sy: string; +} + +interface Response { + markets: MarketInfo[]; +} + +export async function getActiveMarkets() { + // This is an example of how to get list of active Pendle markets on Ethereum + + const param: Param = { + chainId: 1, // Ethereum + } + + const targetPath = `/v1/${param.chainId}/markets/active`; + + const { data } = await axios.get(EXTERNAL_DOMAIN + targetPath); + + const { markets } = data; + + const {name, address, expiry, pt, sy, yt} = markets[0]; + + console.log('first active market', {name, address, expiry, pt, sy, yt}); +} + +export async function getInactiveMarkets() { + // This is an example of how to get list of inactive Pendle markets on Ethereum + + const param: Param = { + chainId: 1, // Ethereum + } + + const targetPath = `/v1/${param.chainId}/markets/inactive`; + + const { data } = await axios.get(EXTERNAL_DOMAIN + targetPath); + + const { markets } = data; + + const {name, address, expiry, pt, sy, yt} = markets[0]; + + console.log('first inactive market', {name, address, expiry, pt, sy, yt}); +} \ No newline at end of file diff --git a/backend-api-demo/src/get-market-historical-data.ts b/backend-api-demo/src/get-market-historical-data.ts new file mode 100644 index 0000000..ce780e7 --- /dev/null +++ b/backend-api-demo/src/get-market-historical-data.ts @@ -0,0 +1,55 @@ +import { EXTERNAL_DOMAIN } from "./const"; +import axios from "axios"; + +interface Param { + chainId: number; + address: string; +} + +interface Query { + timeFrame?: "hour" | "day" | "week"; + timestampStart?: Date; + timestampEnd?: Date; +} + +interface MarketHistoricalData { + timestamps: number[]; + underlyingApys: number[]; + impliedApys: number[]; +} + +interface Response { + total: number; + limit: number; + currency: string; + timestampStart: string; + timestampEnd: string; + data: MarketHistoricalData; +} + +export async function getMarketHistoricalData() { + // This is an example of how to get daily historical data from 2024 Aug 5th to Aug 11st of USDO++ market on Ethereum + + const param: Param = { + chainId: 1, // Ethereum + address: '0x00b321d89a8c36b3929f20b7955080baed706d1b', // USDO++ + } + + const query: Query = { + timeFrame: 'day', + timestampStart: new Date("2024-08-05T00:00:00.000+00:00"), + timestampEnd: new Date("2024-08-11T00:00:00.000+00:00"), + } + + const targetPath = `/v1/${param.chainId}/markets/${param.address}/historical-data`; + + const { data: response } = await axios.get(EXTERNAL_DOMAIN + targetPath, {params: query}); + console.log('response data', {total: response.total, limit: response.limit, timestampStart: response.timestampStart, timestampEnd: response.timestampEnd}); + + const {data} = response; + console.log('first data point info', { + timestamp: data.timestamps[0], + underlyingApy: data.underlyingApys[0], + impliedApy: data.impliedApys[0], + }) +} \ No newline at end of file diff --git a/backend-api-demo/src/get-swapping-prices.ts b/backend-api-demo/src/get-swapping-prices.ts new file mode 100644 index 0000000..311fe3e --- /dev/null +++ b/backend-api-demo/src/get-swapping-prices.ts @@ -0,0 +1,31 @@ +import axios from "axios"; +import { EXTERNAL_DOMAIN } from "./const"; + +interface Param { + chainId: number; + address: string; +} + +interface Response { + yieldTokenToPtRate: number; + ptToYieldTokenRate: number; + yieldTokenToYtRate: number; + ytToYieldTokenRate: number; +} + +export async function getSwappingPrices() { + // This is an example of how to get swapping prices of USD0++ market on Ethereum + + const param: Param = { + chainId: 1, // Ethereum + address: '0x00b321d89a8c36b3929f20b7955080baed706d1b', // USDO++ + } + + const targetPath = `/v1/${param.chainId}/markets/${param.address}/swapping-price`; + + const { data } = await axios.get(EXTERNAL_DOMAIN + targetPath); + + const { yieldTokenToPtRate, ptToYieldTokenRate, yieldTokenToYtRate, ytToYieldTokenRate} = data; + + console.log('swapping prices', {yieldTokenToPtRate, ptToYieldTokenRate, yieldTokenToYtRate, ytToYieldTokenRate}); +} \ No newline at end of file diff --git a/backend-api-demo/src/index.ts b/backend-api-demo/src/index.ts new file mode 100644 index 0000000..275e675 --- /dev/null +++ b/backend-api-demo/src/index.ts @@ -0,0 +1,17 @@ +import { getAssetList } from "./get-list-assets"; +import { getActiveMarkets, getInactiveMarkets } from "./get-list-markets"; +import { getAssetPrices, getHistoricalAssetPrices } from "./get-asset-prices"; +import { getMarketHistoricalData } from "./get-market-historical-data"; +import { getSwappingPrices } from "./get-swapping-prices"; + +async function main() { + await getAssetList(); + await getActiveMarkets(); + await getInactiveMarkets(); + await getAssetPrices(); + await getSwappingPrices(); + await getHistoricalAssetPrices(); + await getMarketHistoricalData(); +} + +main() \ No newline at end of file diff --git a/backend-api-demo/tsconfig.json b/backend-api-demo/tsconfig.json new file mode 100644 index 0000000..a7d6849 --- /dev/null +++ b/backend-api-demo/tsconfig.json @@ -0,0 +1,63 @@ +{ + "compilerOptions": { + /* Basic Options */ + "incremental": true, /* Enable incremental compilation */ + "target": "es2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./dist", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + /* Experimental Options */ + "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + "resolveJsonModule": true, + }, + "exclude": [ + "node_modules", + "dist" + ] +}