Skip to content

Commit

Permalink
feat: add pendle quote source for balmy
Browse files Browse the repository at this point in the history
  • Loading branch information
dglowinski committed Dec 9, 2024
1 parent 047c769 commit 55d7a00
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 3 deletions.
10 changes: 8 additions & 2 deletions src/swapService/config/mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,13 @@ const mainnetRoutingConfig: ChainRoutingConfig = [
},
// SPECIAL CASE TOKENS
{
strategy: StrategyPendle.name(),
match: {}, // strategy supports() will match pendle PT tokens on input/output
strategy: StrategyBalmySDK.name(),
config: {
sourcesFilter: {
includeSources: ["pendle", "li-fi", "open-ocean"],
},
},
match: { isPendlePT: true },
},
{
strategy: StrategyMTBILL.name(),
Expand Down Expand Up @@ -130,6 +135,7 @@ const mainnetRoutingConfig: ChainRoutingConfig = [
"uniswap",
],
},
tryExactOut: true,
},
match: {},
},
Expand Down
1 change: 1 addition & 0 deletions src/swapService/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export async function runPipeline(
}

// TODO timeouts on balmy
// TODO review and add sources
// TODO tokenlist, interfaces
// TODO price impact
// TODO cache pipeline, tokenlists
Expand Down
2 changes: 2 additions & 0 deletions src/swapService/strategies/balmySDK/customSourceList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { IFetchService, IProviderService } from "@balmy/sdk"
import { LocalSourceList } from "@balmy/sdk/dist/services/quotes/source-lists/local-source-list"
import { CustomLiFiQuoteSource } from "./lifiQuoteSource"
import { CustomOneInchQuoteSource } from "./oneInchQuoteSource"
import { CustomPendleQuoteSource } from "./pendleQuoteSource"
type ConstructorParameters = {
providerService: IProviderService
fetchService: IFetchService
Expand All @@ -10,6 +11,7 @@ type ConstructorParameters = {
const customSources = {
"1inch": new CustomOneInchQuoteSource(),
"li-fi": new CustomLiFiQuoteSource(),
pendle: new CustomPendleQuoteSource(),
}
export class CustomSourceList extends LocalSourceList {
constructor({ providerService, fetchService }: ConstructorParameters) {
Expand Down
159 changes: 159 additions & 0 deletions src/swapService/strategies/balmySDK/pendleQuoteSource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { findToken } from "@/swapService/utils"
import { Chains } from "@balmy/sdk"
import { isSameAddress } from "@balmy/sdk"
import type {
BuildTxParams,
IQuoteSource,
QuoteParams,
QuoteSourceMetadata,
SourceQuoteResponse,
SourceQuoteTransaction,
} from "@balmy/sdk/dist/services/quotes/quote-sources/types"
import {
addQuoteSlippage,
calculateAllowanceTarget,
failed,
} from "@balmy/sdk/dist/services/quotes/quote-sources/utils"
import qs from "qs"
import { Address, getAddress } from "viem"

// https://api-v2.pendle.finance/core/docs#/Chains/ChainsController_getSupportedChainIds
export const PENDLE_METADATA: QuoteSourceMetadata<PendleSupport> = {
name: "Pendle",
supports: {
chains: [
Chains.ETHEREUM.chainId,
Chains.OPTIMISM.chainId,
Chains.BNB_CHAIN.chainId,
Chains.MANTLE.chainId,
Chains.BASE.chainId,
Chains.ARBITRUM.chainId,
],
swapAndTransfer: true,
buyOrders: false,
},
logoURI: "",
}
type PendleSupport = { buyOrders: false; swapAndTransfer: true }
type CustomOrAPIKeyConfig =
| { customUrl: string; apiKey?: undefined }
| { customUrl?: undefined; apiKey: string }
type PendleConfig = CustomOrAPIKeyConfig
type PendleData = { tx: SourceQuoteTransaction }
export class CustomPendleQuoteSource
implements IQuoteSource<PendleSupport, PendleConfig, PendleData>
{
getMetadata() {
return PENDLE_METADATA
}

async quote(
params: QuoteParams<PendleSupport, PendleConfig>,
): Promise<SourceQuoteResponse<PendleData>> {
const { dstAmount, to, data } = await this.getQuote(params)

const quote = {
sellAmount: params.request.order.sellAmount,
buyAmount: BigInt(dstAmount),
allowanceTarget: calculateAllowanceTarget(params.request.sellToken, to),
customData: {
tx: {
to,
calldata: data,
},
},
}

return addQuoteSlippage(
quote,
params.request.order.type,
params.request.config.slippagePercentage,
)
}

async buildTx({
request,
}: BuildTxParams<PendleConfig, PendleData>): Promise<SourceQuoteTransaction> {
return request.customData.tx
}

private async getQuote({
components: { fetchService },
request: {
chainId,
sellToken,
buyToken,
order,
config: { slippagePercentage, timeout },
accounts: { takeFrom, recipient },
},
config,
}: QuoteParams<PendleSupport, PendleConfig>) {
const queryParams = {
receiver: recipient || takeFrom,
slippage: slippagePercentage / 100, // 1 = 100%
enableAggregator: true,
tokenIn: sellToken,
tokenOut: buyToken,
amountIn: order.sellAmount.toString(),
}

const queryString = qs.stringify(queryParams, {
skipNulls: true,
arrayFormat: "comma",
})
const tokenIn = findToken(chainId, getAddress(sellToken))
const tokenOut = findToken(chainId, getAddress(buyToken))

const pendleMarket =
tokenIn.meta?.pendleMarket || tokenOut.meta?.pendleMarket

const url = `${getUrl()}/${chainId}/markets/${pendleMarket}/swap?${queryString}`
const response = await fetchService.fetch(url, {
timeout,
headers: getHeaders(config),
})

if (!response.ok) {
failed(
PENDLE_METADATA,
chainId,
sellToken,
buyToken,
(await response.text()) || `Failed with status ${response.status}`,
)
}
const {
data: { amountOut: dstAmount },
tx: { to, data },
} = await response.json()

return { dstAmount, to, data }
}

isConfigAndContextValidForQuoting(
config: Partial<PendleConfig> | undefined,
): config is PendleConfig {
return true
}

isConfigAndContextValidForTxBuilding(
config: Partial<PendleConfig> | undefined,
): config is PendleConfig {
return true
}
}

function getUrl() {
return "https://api-v2.pendle.finance/core/v1/sdk"
}

function getHeaders(config: PendleConfig) {
const headers: Record<string, string> = {
accept: "application/json",
}
if (config.apiKey) {
headers["Authorization"] = `Bearer ${config.apiKey}`
}
return headers
}
6 changes: 5 additions & 1 deletion src/swapService/strategies/strategyBalmySDK.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,13 @@ export class StrategyBalmySDK {
"li-fi": {
apiKey: String(process.env.LIFI_API_KEY),
},
pendle: {
apiKey: String(process.env.PENDLE_API_KEY),
},
},
},
},
}
} as BuildParams
this.sdk = buildSDK(buildParams)
this.match = match
this.config = config
Expand Down Expand Up @@ -292,6 +295,7 @@ export class StrategyBalmySDK {
sourcesFilter?: SourcesFilter,
) {
// TODO type
// console.log('this.sdk.quoteService: ', this.sdk.quoteService);
const bestQuote = await this.sdk.quoteService.getBestQuote({
request: this.#getSDKQuoteFromSwapParams(swapParams, sourcesFilter),
config: {
Expand Down
7 changes: 7 additions & 0 deletions src/swapService/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ export function matchParams(
if (match.isRepay) {
if (swapParams.isRepay !== match.isRepay) return false
}
if (match.isPendlePT) {
if (
!swapParams.tokenIn.meta?.isPendlePT &&
!swapParams.tokenOut.meta?.isPendlePT
)
return false
}

return true
}
Expand Down

0 comments on commit 55d7a00

Please sign in to comment.