跳转至

Pendle

Connector for Pendle yield trading protocol.

almanak.connectors.pendle

Pendle Protocol Connector.

Pendle Finance is a permissionless yield-trading protocol that enables: - Tokenizing yield-bearing assets into PT (Principal) and YT (Yield) tokens - Trading PT and YT on Pendle's AMM - Providing liquidity to PT/SY pools - Redeeming PT at maturity

Components: - PendleSDK: Low-level protocol interactions - PendleAdapter: ActionType to SDK mapping - PendleReceiptParser: Transaction receipt parsing

Supported Chains: - Arbitrum (primary) - Ethereum

Example::

from almanak.connectors.pendle import (
    PendleSDK,
    PendleAdapter,
    PendleReceiptParser,
)

sdk = PendleSDK(rpc_url="https://arb1.arbitrum.io/rpc", chain="arbitrum")

Lazy attribute access (VIB-4835)

Strategy-facing symbols (PendleSDK, PendleAdapter, …) are exposed via PEP 562 __getattr__. Gateway boot may load the manifest-declared PendleGatewaySettings fragment while composing GatewaySettings; Python runs this __init__.py first, and an eager import of almanak.framework.intents.vocabulary would explode a circular config-init chain (see the matching note in enso/__init__.py). Lazy attributes avoid the cycle entirely.

PendleAdapter

PendleAdapter(
    rpc_url: str | None = None,
    chain: str = "arbitrum",
    wallet_address: str | None = None,
    api_client: PendleAPIClient | None = None,
    on_chain_reader: PendleOnChainReader | None = None,
    gateway_client: GatewayClient | None = None,
)

Adapter for Pendle Protocol operations.

This adapter translates between the framework's ActionType enum and Pendle's specific operations. It handles: - Token swaps to/from PT (Principal Token) - Token swaps to/from YT (Yield Token) - Liquidity provision (adding/removing) - PT/YT redemption at maturity

Example

adapter = PendleAdapter(rpc_url="https://arb1.arbitrum.io/rpc", chain="arbitrum")

Build a swap transaction

tx = adapter.build_swap( params=PendleSwapParams( market="0x...", token_in="0x...", token_out="0x...", amount_in=1018, min_amount_out=1018, receiver="0x...", swap_type="token_to_pt", ) )

Initialize the Pendle adapter.

Parameters:

Name Type Description Default
rpc_url str | None

DEPRECATED — direct RPC URL. Prefer gateway_client for any code path running in a strategy container.

None
chain str

Target chain (arbitrum, ethereum)

'arbitrum'
wallet_address str | None

Optional default wallet address for transactions

None
api_client PendleAPIClient | None

Optional PendleAPIClient for REST API quotes

None
on_chain_reader PendleOnChainReader | None

Optional PendleOnChainReader for on-chain fallback

None
gateway_client GatewayClient | None

Gateway client for routing eth_call through the gateway. Preferred over rpc_url.

None

supports_action

supports_action(action_type: ActionType) -> bool

Check if this adapter supports the given action type.

get_supported_actions

get_supported_actions() -> list[ActionType]

Get list of supported action types.

build_swap

build_swap(
    params: PendleSwapParams,
) -> PendleTransactionData

Build a swap transaction based on the swap type.

Parameters:

Name Type Description Default
params PendleSwapParams

Swap parameters including market, tokens, and amounts

required

Returns:

Type Description
PendleTransactionData

Transaction data ready for execution

build_swap_token_to_pt

build_swap_token_to_pt(
    market: str,
    token_in: str,
    amount_in: int,
    min_pt_out: int,
    receiver: str,
    slippage_bps: int = 50,
) -> PendleTransactionData

Build a token -> PT swap transaction.

This is a convenience method for the most common swap type.

Parameters:

Name Type Description Default
market str

Pendle market address

required
token_in str

Input token address

required
amount_in int

Amount of input token

required
min_pt_out int

Minimum PT to receive

required
receiver str

Address to receive PT

required
slippage_bps int

Slippage tolerance in basis points

50

Returns:

Type Description
PendleTransactionData

Transaction data

build_swap_pt_to_token

build_swap_pt_to_token(
    market: str,
    pt_amount: int,
    token_out: str,
    min_token_out: int,
    receiver: str,
    slippage_bps: int = 50,
) -> PendleTransactionData

Build a PT -> token swap transaction.

Parameters:

Name Type Description Default
market str

Pendle market address

required
pt_amount int

Amount of PT to swap

required
token_out str

Output token address

required
min_token_out int

Minimum token to receive

required
receiver str

Address to receive token

required
slippage_bps int

Slippage tolerance

50

Returns:

Type Description
PendleTransactionData

Transaction data

build_add_liquidity

build_add_liquidity(
    params: PendleLPParams,
) -> PendleTransactionData

Build an add liquidity transaction.

Parameters:

Name Type Description Default
params PendleLPParams

Liquidity parameters

required

Returns:

Type Description
PendleTransactionData

Transaction data

build_remove_liquidity

build_remove_liquidity(
    params: PendleLPParams,
) -> PendleTransactionData

Build a remove liquidity transaction.

Parameters:

Name Type Description Default
params PendleLPParams

Liquidity parameters

required

Returns:

Type Description
PendleTransactionData

Transaction data

build_redeem

build_redeem(
    params: PendleRedeemParams,
) -> PendleTransactionData

Build a PT+YT redemption transaction.

Parameters:

Name Type Description Default
params PendleRedeemParams

Redemption parameters

required

Returns:

Type Description
PendleTransactionData

Transaction data

build_approve

build_approve(
    token_address: str, amount: int | None = None
) -> PendleTransactionData

Build an approval transaction for the Pendle Router.

Parameters:

Name Type Description Default
token_address str

Token to approve

required
amount int | None

Amount to approve (defaults to max)

None

Returns:

Type Description
PendleTransactionData

Transaction data

get_router_address

get_router_address() -> str

Get the Pendle Router address for this chain.

get_gas_estimate

get_gas_estimate(action: PendleActionType) -> int

Get gas estimate for an action.

get_yt_to_asset_rate

get_yt_to_asset_rate(market: str) -> Decimal | None

Return how much underlying asset 1 YT is worth (asset per YT), or None on failure.

Derived from the Pendle PT/YT invariant: 1 PT + 1 YT settles to ~1 unit of the SY underlying asset, so ytToAsset ≈ 1 - ptToAsset. getPtToAssetRate is read through the existing gateway-routed on-chain reader (no new egress). Near expiry the PT rate converges toward 1.0, so this value decays toward 0 — matching YT's decay.

The result is clamped to >= 0 (post-maturity the PT rate can meet/exceed 1.0, at which point YT is worth ~nothing). Returns None if the rate cannot be read, letting callers fall back to a conservative minimal floor rather than guessing.

Used by the compiler to denominate the YT-sell min_amount_out floor in OUTPUT (underlying) units instead of raw YT count (VIB-5329).

estimate_output

estimate_output(
    market: str,
    token_in: str,
    amount_in: int,
    swap_type: str,
    slippage_bps: int = 50,
) -> int

Estimate output amount for a swap using a 3-tier cascade: 1. Pendle REST API quote (most accurate) 2. On-chain RouterStatic rate (good fallback) 3. Conservative 1% haircut estimate (last resort, always logged as WARNING)

Parameters:

Name Type Description Default
market str

Market address

required
token_in str

Input token address

required
amount_in int

Input amount in wei

required
swap_type str

Type of swap ("token_to_pt", "pt_to_token", etc.)

required
slippage_bps int

Slippage tolerance in basis points

50

Returns:

Type Description
int

Estimated output amount in wei

PendleLPParams dataclass

PendleLPParams(
    market: str,
    token: str,
    amount: int,
    min_amount: int,
    receiver: str,
    operation: str,
    slippage_bps: int = 50,
)

Parameters for Pendle liquidity operations.

to_dict

to_dict() -> dict[str, Any]

Convert to dictionary.

PendleRedeemParams dataclass

PendleRedeemParams(
    yt_address: str,
    py_amount: int,
    token_out: str,
    min_token_out: int,
    receiver: str,
    slippage_bps: int = 50,
)

Parameters for Pendle redemption operations.

to_dict

to_dict() -> dict[str, Any]

Convert to dictionary.

PendleSwapParams dataclass

PendleSwapParams(
    market: str,
    token_in: str,
    token_out: str,
    amount_in: int,
    min_amount_out: int,
    receiver: str,
    swap_type: str,
    slippage_bps: int = 50,
    token_mint_sy: str | None = None,
)

Parameters for Pendle swap operations.

to_dict

to_dict() -> dict[str, Any]

Convert to dictionary.

PendleAPIClient

PendleAPIClient(
    chain: str = "ethereum",
    cache_ttl_seconds: float = 15.0,
    max_cache_entries: int = 1000,
    api_key: str | None = None,
)

REST client for the Pendle v3 API.

Follows the DefiLlamaProvider pattern: rate limiting via threading.Lock, bounded TTL cache, and typed return values.

Example

client = PendleAPIClient(chain="ethereum") market = client.get_market_data("0x...") print(f"Implied APY: {market.implied_apy}")

quote = client.get_swap_quote( market="0x...", token_in="0x...", amount_in=10**18, swap_type="token_to_pt", ) print(f"Estimated output: {quote.amount_out}")

Initialize the Pendle API client.

Parameters:

Name Type Description Default
chain str

Target chain name (ethereum, arbitrum, etc.)

'ethereum'
cache_ttl_seconds float

Cache TTL in seconds (default 15)

15.0
max_cache_entries int

Maximum cache entries (default 1000)

1000
api_key str | None

Optional Pendle API key for higher rate limits

None

health property

health: dict[str, Any]

Return health metrics.

get_market_data

get_market_data(market_address: str) -> PendleMarketData

Fetch market data from Pendle API.

Parameters:

Name Type Description Default
market_address str

Market contract address

required

Returns:

Type Description
PendleMarketData

PendleMarketData with implied APY, PT price, liquidity, etc.

Raises:

Type Description
PendleAPIError

If the API call fails

get_swap_quote

get_swap_quote(
    market: str,
    token_in: str,
    amount_in: int,
    swap_type: str,
    slippage_bps: int = 50,
) -> PendleSwapQuote

Get a swap quote from the Pendle API.

Parameters:

Name Type Description Default
market str

Market contract address

required
token_in str

Input token address

required
amount_in int

Input amount in wei

required
swap_type str

One of "token_to_pt", "pt_to_token", "token_to_yt", "yt_to_token"

required
slippage_bps int

Slippage tolerance in basis points

50

Returns:

Type Description
PendleSwapQuote

PendleSwapQuote with estimated output and price impact

Raises:

Type Description
PendleAPIError

If the API call fails

get_market_list

get_market_list() -> list[PendleMarketData]

Fetch list of all active markets on this chain.

Returns:

Type Description
list[PendleMarketData]

List of PendleMarketData for all active markets

Raises:

Type Description
PendleAPIError

If the API call fails

get_pt_price

get_pt_price(market_address: str) -> Decimal

Get PT price in terms of the underlying asset.

Parameters:

Name Type Description Default
market_address str

Market contract address

required

Returns:

Type Description
Decimal

PT price as Decimal (e.g., 0.97 means PT trades at 3% discount)

Raises:

Type Description
PendleAPIError

If the API call fails

get_implied_apy

get_implied_apy(market_address: str) -> Decimal

Get the implied APY for a Pendle market.

Parameters:

Name Type Description Default
market_address str

Market contract address

required

Returns:

Type Description
Decimal

Implied APY as Decimal (e.g., 0.05 = 5%)

Raises:

Type Description
PendleAPIError

If the API call fails

clear_cache

clear_cache() -> None

Clear all cached data.

PendleAPIError

Bases: Exception

Raised when a Pendle API call fails.

PendleCompiler

Bases: BaseProtocolCompiler[BaseCompilerContext]

Compiler for Pendle swap and LP intents.

Pendle is its own primitive — PT/YT/SY tokens with a custom AMM — and is NOT concentrated liquidity. The pre-swap V3 leg (when paying with a non-Pendle base token) gets its DefaultSwapAdapter via ctx.services.default_swap_adapter(protocol) rather than the CL-specific factory fields.

preflight

preflight(
    ctx: BaseCompilerContext, intent: Any
) -> PreflightVerdict

Reject opening NEW Pendle exposure into an already-expired market (VIB-5374 / 2802).

Buying PT/YT or opening LP into an expired market burns gas on a router revert (the market no longer accepts new positions). Risk-reducing intents — selling PT/YT, LP_CLOSE, WITHDRAW (redeem) — are NEVER gated: closing an expired position is exactly what a strategy must do.

is_market_expired is gateway-backed (expiry() via PendleOnChainReader._gateway_eth_call) and only runs when a connected gateway is available; without one — or on a transient read failure — this fails open (defers to the compile path) rather than producing a false reject. Only a CONFIRMED-expired market yields INFEASIBLE.

PendleAsset dataclass

PendleAsset(
    address: str,
    symbol: str,
    decimals: int,
    chain_id: int,
    asset_type: str = "",
    underlying_address: str = "",
    expiry: int = 0,
)

A Pendle asset (PT, YT, SY, or LP token).

Attributes:

Name Type Description
address str

Token contract address

symbol str

Human-readable symbol (e.g., "PT-sUSDe-29MAY2025")

decimals int

Token decimals

chain_id int

Chain ID (1=Ethereum, 42161=Arbitrum)

asset_type str

One of "PT", "YT", "SY", "LP", "UNDERLYING"

underlying_address str

Address of the underlying yield-bearing asset

expiry int

Unix timestamp of maturity (0 if not applicable)

is_expired

is_expired(current_timestamp: int) -> bool

Check if the asset has expired.

PendleMarketData dataclass

PendleMarketData(
    market_address: str,
    chain_id: int,
    pt_address: str = "",
    pt_symbol: str = "",
    pt_decimals: int = 18,
    yt_address: str = "",
    yt_symbol: str = "",
    yt_decimals: int = 18,
    sy_address: str = "",
    underlying_address: str = "",
    underlying_symbol: str = "",
    expiry: int = 0,
    implied_apy: Decimal = Decimal("0"),
    underlying_apy: Decimal = Decimal("0"),
    pt_price_in_asset: Decimal = Decimal("0"),
    yt_price_in_asset: Decimal = Decimal("0"),
    liquidity_usd: Decimal = Decimal("0"),
    volume_24h_usd: Decimal = Decimal("0"),
    pt_discount: Decimal = Decimal("0"),
    is_expired: bool = False,
)

Market data for a Pendle market.

Attributes:

Name Type Description
market_address str

Market contract address

chain_id int

Chain ID

pt_address str

PT token address

yt_address str

YT token address

sy_address str

SY token address

underlying_address str

Underlying asset address

expiry int

Market expiry timestamp

implied_apy Decimal

Current implied APY (e.g., 0.05 = 5%)

underlying_apy Decimal

Underlying protocol's APY

pt_price_in_asset Decimal

PT price denominated in the underlying asset

yt_price_in_asset Decimal

YT price denominated in the underlying asset

liquidity_usd Decimal

Total market liquidity in USD

volume_24h_usd Decimal

24h trading volume in USD

pt_discount Decimal

PT discount from par (e.g., 0.03 = 3% discount)

is_expired bool

Whether the market has expired

PendleSwapQuote dataclass

PendleSwapQuote(
    market_address: str,
    token_in: str,
    token_out: str,
    amount_in: int,
    amount_out: int,
    price_impact_bps: int = 0,
    exchange_rate: Decimal = Decimal("0"),
    source: str = "estimate",
    warnings: list[str] = list(),
)

Quote for a Pendle swap operation.

Attributes:

Name Type Description
market_address str

Market contract address

token_in str

Input token address

token_out str

Output token address

amount_in int

Input amount in wei

amount_out int

Estimated output amount in wei

price_impact_bps int

Price impact in basis points

exchange_rate Decimal

Effective exchange rate (amount_out / amount_in adjusted for decimals)

source str

Where the quote came from ("api", "on_chain", "estimate")

PendleOnChainError

Bases: Exception

Raised when an on-chain read fails.

PendleOnChainReader

PendleOnChainReader(
    rpc_url: str | None = None,
    chain: str = "ethereum",
    cache_ttl_seconds: float = 30.0,
    gateway_client: GatewayClient | None = None,
    request_timeout_seconds: float = 30.0,
)

Reads Pendle market data directly from on-chain contracts.

Used as a fallback when the Pendle REST API is unavailable. The PT-to-asset rate is read from the per-chain PendlePYLpOracle (TWAP); readTokens / expiry are read from the market contract; getImpliedApy (informational only) is read from the RouterStatic when one is registered for the chain.

Supports two initialization modes:

Gateway mode (preferred for production): reader = PendleOnChainReader(gateway_client=client, chain="ethereum")

Direct mode (legacy, local development): reader = PendleOnChainReader(rpc_url="https://...", chain="ethereum")

Initialize the on-chain reader.

Parameters:

Name Type Description Default
rpc_url str | None

RPC endpoint URL (for direct/web3 mode).

None
chain str

Target chain (ethereum, arbitrum).

'ethereum'
cache_ttl_seconds float

Cache TTL for on-chain reads.

30.0
gateway_client GatewayClient | None

Gateway client (for gateway mode). Preferred over rpc_url.

None
request_timeout_seconds float

Bound on each blocking web3 RPC request in direct mode. Without it, web3 defaults to no timeout, so a hung RPC wedges the caller (and, when the reader is driven from the gateway, a worker thread) indefinitely.

30.0

Raises:

Type Description
ValueError

If chain is unsupported or neither rpc_url nor gateway_client provided.

get_pt_to_asset_rate

get_pt_to_asset_rate(market_address: str) -> Decimal

Get the PT-to-underlying-asset exchange rate via the PT oracle TWAP.

This is the key pricing function: it returns how much underlying asset 1 PT is worth. Before maturity, this is typically < 1.0 (PT trades at a discount). At maturity, it converges to 1.0.

Read from the per-chain PendlePYLpOracle using the 2-arg getPtToAssetRate(market, duration) TWAP call (duration fixed at PT_ORACLE_TWAP_DURATION_SECONDS). The read is gated on oracle readiness (:meth:_assert_oracle_ready) so a not-ready oracle surfaces as UNMEASURED rather than a fabricated rate.

Parameters:

Name Type Description Default
market_address str

Market contract address

required

Returns:

Type Description
Decimal

Exchange rate as Decimal (in 1e18 scale, normalized to human-readable)

Raises:

Type Description
PendleOnChainError

If the oracle is not ready or the RPC call fails

get_implied_apy

get_implied_apy(market_address: str) -> Decimal

Get the implied APY for a market.

Parameters:

Name Type Description Default
market_address str

Market contract address

required

Returns:

Type Description
Decimal

Implied APY as Decimal (e.g., 0.05 = 5%)

Raises:

Type Description
PendleOnChainError

If the RPC call fails

get_market_expiry_ts

get_market_expiry_ts(market_address: str) -> int | None

Return the market's on-chain expiry() as a unix timestamp, or None.

Single source of truth for the PT maturity timestamp: is_market_expired and get_days_to_maturity both derive from this read so a caller that needs the raw timestamp, days-remaining, and the expired flag observes one consistent expiry (VIB-5384).

expiry() reads the MARKET contract, not RouterStatic — do not gate on router_static (absent on Arbitrum).

Never raises — a failed read returns None (Empty≠Zero: the caller maps an unread expiry to an unmeasured maturity, never a fabricated 0). The result is cached so the expired / days / timestamp views stay coherent within the cache window.

Parameters:

Name Type Description Default
market_address str

Pendle market contract address.

required

Returns:

Type Description
int | None

Unix-seconds expiry timestamp, or None if it could not be read.

is_market_expired

is_market_expired(market_address: str) -> bool

Check if a market has expired.

Parameters:

Name Type Description Default
market_address str

Market contract address

required

Returns:

Type Description
bool

True if the market has expired

Raises:

Type Description
PendleOnChainError

If the RPC call fails

get_days_to_maturity

get_days_to_maturity(market_address: str) -> int | None

Return calendar days remaining until PT maturity, or None on failure.

Returns 0 when the market is already expired. Never raises — a failed expiry read returns None so callers can treat the absence of this data as non-fatal. Derived from the same get_market_expiry_ts read as the raw timestamp so the two stay consistent (VIB-5384).

Parameters:

Name Type Description Default
market_address str

Pendle market contract address.

required

Returns:

Type Description
int | None

Non-negative integer days to maturity, or None if the expiry

int | None

timestamp could not be read.

get_market_tokens

get_market_tokens(market_address: str) -> dict[str, str]

Get SY, PT, and YT addresses for a market.

Read from the market contract's no-arg readTokens() (chain-portable; no RouterStatic dependency).

Parameters:

Name Type Description Default
market_address str

Market contract address

required

Returns:

Type Description
dict[str, str]

Dict with keys "sy", "pt", "yt" mapping to addresses

Raises:

Type Description
PendleOnChainError

If the RPC call fails

estimate_pt_output

estimate_pt_output(
    market_address: str, amount_in: int
) -> int

Estimate PT output for a given input amount using the on-chain rate.

Parameters:

Name Type Description Default
market_address str

Market contract address

required
amount_in int

Input amount in wei

required

Returns:

Type Description
int

Estimated PT output in wei

BurnEventData dataclass

BurnEventData(
    receiver_sy: str,
    receiver_pt: str,
    net_lp_burned: int,
    net_sy_out: int,
    net_pt_out: int,
    market_address: str,
)

Parsed data from Pendle Burn (LP removal) event.

V3 Burn emits two indexed receivers: receiverSy (gets SY) and receiverPt (gets PT). Both are exposed here; in normal LP close they are the same address.

to_dict

to_dict() -> dict[str, Any]

Convert to dictionary.

MintEventData dataclass

MintEventData(
    receiver: str,
    net_lp_minted: int,
    net_sy_used: int,
    net_pt_used: int,
    market_address: str,
)

Parsed data from Pendle Mint (LP) event.

to_dict

to_dict() -> dict[str, Any]

Convert to dictionary.

ParsedSwapResult dataclass

ParsedSwapResult(
    token_in: str,
    token_out: str,
    amount_in: int,
    amount_out: int,
    amount_in_decimal: Decimal,
    amount_out_decimal: Decimal,
    effective_price: Decimal,
    slippage_bps: int,
    market_address: str,
    swap_type: str,
)

High-level swap result from Pendle.

to_dict

to_dict() -> dict[str, Any]

Convert to dictionary.

ParseResult dataclass

ParseResult(
    success: bool,
    events: list[PendleEvent] = list(),
    swap_events: list[SwapEventData] = list(),
    mint_events: list[MintEventData] = list(),
    burn_events: list[BurnEventData] = list(),
    redeem_events: list[RedeemPYEventData] = list(),
    redeem_sy_events: list[RedeemSYEventData] = list(),
    transfer_events: list[TransferEventData] = list(),
    swap_result: ParsedSwapResult | None = None,
    error: str | None = None,
    transaction_hash: str = "",
    block_number: int = 0,
    transaction_success: bool = True,
)

Result of parsing a Pendle receipt.

to_dict

to_dict() -> dict[str, Any]

Convert to dictionary.

PendleEvent dataclass

PendleEvent(
    event_type: PendleEventType,
    event_name: str,
    log_index: int,
    transaction_hash: str,
    block_number: int,
    contract_address: str,
    data: dict[str, Any],
    raw_topics: list[str] = list(),
    raw_data: str = "",
    timestamp: datetime = (lambda: datetime.now(UTC))(),
)

Parsed Pendle event.

to_dict

to_dict() -> dict[str, Any]

Convert to dictionary.

PendleEventType

Bases: Enum

Pendle event types.

PendleReceiptParser

PendleReceiptParser(
    chain: str = "arbitrum",
    token_in_decimals: int = 18,
    token_out_decimals: int = 18,
    quoted_price: Decimal | None = None,
    **kwargs: Any,
)

Parser for Pendle Protocol transaction receipts.

Uses the base infrastructure (EventRegistry, HexDecoder) for standardized event parsing while handling Pendle-specific event structures.

Example

parser = PendleReceiptParser(chain="arbitrum") result = parser.parse_receipt(receipt)

if result.success and result.swap_events: swap = result.swap_events[0] print(f"Swapped {swap.sy_amount} SY for {swap.pt_amount} PT")

Initialize the Pendle receipt parser.

Parameters:

Name Type Description Default
chain str

Chain name for address resolution

'arbitrum'
token_in_decimals int

Decimals for input token

18
token_out_decimals int

Decimals for output token

18
quoted_price Decimal | None

Expected price for slippage calculation

None

build_extract_kwargs

build_extract_kwargs(
    *, field: str, bundle_metadata: dict[str, Any]
) -> dict[str, Any]

Return Pendle-owned kwargs for ResultEnricher extraction calls.

VIB-3751: Pendle YT swaps need compiler metadata to reconstruct user-facing amounts from Transfer events. The Market Swap event reports an internal PT flash-mint for YT trades, so the generic framework cannot derive these parser-specific hints.

G-PT (VIB-4988 part 2): a PT redeem (primitive_money_legs on a WITHDRAW) needs the compiler-resolved PT token address and the underlying out-token identity/decimals to declare its money legs — a redeem emits no Market Swap event, so the swap-path PT-symbol resolution never fires.

parse_receipt

parse_receipt(
    receipt: dict[str, Any],
    quoted_amount_out: int | None = None,
    intent_swap_type: str | None = None,
    token_in_address: str | None = None,
    token_out_address: str | None = None,
    token_in_decimals: int | None = None,
    token_out_decimals: int | None = None,
    wallet_address: str | None = None,
) -> ParseResult

Parse a Pendle transaction receipt.

Parameters:

Name Type Description Default
receipt dict[str, Any]

Transaction receipt dictionary

required
quoted_amount_out int | None

Expected output for slippage calculation

None
intent_swap_type str | None

Compiler-supplied swap_type ("token_to_yt", "yt_to_token", "token_to_pt", "pt_to_token"). Required for YT swaps to reconstruct user-facing amounts (VIB-3751).

None
token_in_address str | None

Compiler-supplied input token address (for YT).

None
token_out_address str | None

Compiler-supplied output token address (for YT).

None
token_in_decimals int | None

Compiler-supplied input token decimals (for YT). Falls back to self.token_in_decimals (constructor default).

None
token_out_decimals int | None

Compiler-supplied output token decimals (for YT). Falls back to self.token_out_decimals (constructor default).

None
wallet_address str | None

Compiler-supplied user wallet address (for YT).

None

Returns:

Type Description
ParseResult

ParseResult with extracted events and swap data

extract_swap_amounts

extract_swap_amounts(
    receipt: dict[str, Any],
    *,
    expected_out: Decimal | None = None,
    intent_swap_type: str | None = None,
    token_in_address: str | None = None,
    token_out_address: str | None = None,
    token_in_decimals: int | None = None,
    token_out_decimals: int | None = None,
    wallet_address: str | None = None,
) -> SwapAmounts | None

Extract swap amounts from receipt for Result Enrichment.

Called by the framework after SWAP execution to populate ExecutionResult.swap_amounts.

Parameters:

Name Type Description Default
receipt dict[str, Any]

Transaction receipt dict.

required
expected_out Decimal | None

VIB-3203 — pre-slippage-discount quote in human (Decimal) units from the compiler's ActionBundle metadata. Overrides the parser's internal slippage_bps, which is None on the enrichment path because constructor quoted_price isn't available there.

None
intent_swap_type str | None

VIB-3751 — compiler-supplied swap_type ("token_to_yt", "yt_to_token", "token_to_pt", "pt_to_token"). Required for YT swaps to reconstruct user-facing amounts; without it the parser falls back to the legacy PT-direction inference which is wrong for YT trades.

None
token_in_address str | None

Input token address from compiler (for YT).

None
token_out_address str | None

Output token address from compiler (for YT).

None
token_in_decimals int | None

Input token decimals from compiler. Required for non-18-decimal markets (e.g., Plasma fUSDT0 = 6) so that YT amount reconstruction does not mis-scale by 10^12.

None
token_out_decimals int | None

Output token decimals from compiler.

None
wallet_address str | None

User wallet from compiler (for YT).

None

Returns:

Type Description
SwapAmounts | None

SwapAmounts dataclass or None if not found

extract_lp_minted

extract_lp_minted(receipt: dict[str, Any]) -> int | None

Extract LP tokens minted from receipt.

Called by the framework after LP_OPEN execution.

extract_lp_burned

extract_lp_burned(receipt: dict[str, Any]) -> int | None

Extract LP tokens burned from receipt.

Called by the framework after LP_CLOSE execution.

extract_position_id

extract_position_id(receipt: dict[str, Any]) -> str | None

Extract the Pendle LP market address as the position_id.

Returns the market address as a lowercase hex string (e.g. "0xabcd..."). This is the stable position identifier for Pendle's fungible LP: there is no NFT tokenId, so the market address itself is the unique position key. Checks mint_events first (LP_OPEN), falls back to burn_events (LP_CLOSE).

extract_lp_open_data

extract_lp_open_data(
    receipt: dict[str, Any],
) -> LPOpenData | None

Extract LPOpenData from a Pendle LP Mint receipt (LP_OPEN enrichment).

Maps MintEventData to the standard LPOpenData structure

amount0 = net_sy_used (raw SY tokens deposited) amount1 = net_pt_used (raw PT tokens deposited) liquidity = net_lp_minted (raw LP tokens minted) position_id = 0 (Pendle has no NFT tokenId; canonical position_id is the market address hex string from extract_position_id)

extract_lp_close_data

extract_lp_close_data(
    receipt: dict[str, Any],
) -> LPCloseData | None

Extract LPCloseData from a Pendle LP Burn receipt (LP_CLOSE enrichment).

Maps BurnEventData to the standard LPCloseData structure

amount0_collected = net_sy_out (raw SY tokens received) amount1_collected = net_pt_out (raw PT tokens received) liquidity_removed = net_lp_burned (raw LP tokens burned)

extract_position_id_result

extract_position_id_result(
    receipt: dict[str, Any],
) -> ExtractResult[str]

Fail-closed variant of :meth:extract_position_id — see VIB-5354.

extract_lp_open_data_result

extract_lp_open_data_result(
    receipt: dict[str, Any],
) -> ExtractResult[LPOpenData]

Fail-closed variant of :meth:extract_lp_open_data — see VIB-5354.

extract_lp_close_data_result

extract_lp_close_data_result(
    receipt: dict[str, Any],
) -> ExtractResult[LPCloseData]

Fail-closed variant of :meth:extract_lp_close_data — see VIB-5354.

extract_primitive_money_legs

extract_primitive_money_legs(
    receipt: dict[str, Any],
    *,
    pt_address: str | None = None,
    out_token_symbol: str | None = None,
    out_token_address: str | None = None,
    out_token_decimals: int | None = None,
) -> PrimitiveMoneyLegs | None

Declare the money legs for a Pendle redeem or LP_CLOSE (G-PT / VIB-4988, VIB-5302).

Two intents route through this single primitive_money_legs extractor; the Market Burn event (emitted only by an LP removal, never by a PT redeem) discriminates them. An LP_CLOSE delegates to :meth:_lp_close_money_legs (the received-underlying OUTPUT leg); the remainder of this method handles the PT-redeem WITHDRAW path described below.

A Pendle PT redeem is a WITHDRAW intent whose compiler names intent.token = the underlying out token and intent.market_id = the YT address; the PT token is on neither. So the legacy ledger guesser (lending path) lands token_in = underlying / token_out = "" and the canonical PT symbol — the only cross-boundary join key the accounting FIFO matcher and the position-events lifecycle use — never reaches the row. The PT redeem also emits RedeemPY / RedeemSY, never a Market Swap, so the swap-path PT-symbol resolution (:func:_resolve_pt_symbol inside :meth:_build_swap_result) never fires.

This declares the redeem's two money legs as a typed :class:PrimitiveMoneyLegs — the contract the US-009 ledger dispatcher prefers over its legacy guess (blueprint 27 §6.6) — so the row carries:

  • INPUT — the canonical maturity-bearing PT symbol (reverse-mapped from the compiler-supplied pt_address via :func:_resolve_pt_symbol), amount = the PT TOKEN COUNT (18-dec), basis-identical to the PT_BUY's PT amount_out so PT-quantity conserves (PEN6). Sourced from RedeemPY.net_py_redeemed (the netPYRedeemed PT count, PRE-maturity) or, at/after maturity where no RedeemPY fires, the PT-token Transfer value (:meth:_pt_transfer_amount). The SY Redeem.amountSyToRedeem is SY-ASSET denominated (≈ PT × SY-rate), NOT the PT count, and is never used for this leg — that mismatch was the PEN6 basis break this closes.
  • OUTPUT — the underlying token received (out_token_symbol), amount = amount_token_out from the SY Redeem event when present.

Empty != Zero (blueprint 27 §10.10): when _resolve_pt_symbol returns None (unknown catalogue address), or no PT count is resolvable (no RedeemPY and no PT Transfer), this returns None so the dispatcher falls back to its legacy path and the accounting builder's R6 degrade fires (ESTIMATED + unavailable_reason) — a PT symbol / count is never fabricated, and the SY-asset amount is never recorded as the PT count. The OUTPUT amount is UNMEASURED (never a measured zero, never an SY proxy) when no Redeem event carries a measured amount_token_out.

Returns None for a non-PT Pendle withdraw (no resolvable PT symbol / count), so a YT redeem / non-PT path is unchanged.

Parameters:

Name Type Description Default
receipt dict[str, Any]

Transaction receipt dict with a logs field.

required
pt_address str | None

Compiler-resolved PT token address (from redeem metadata).

None
out_token_symbol str | None

Underlying out-token symbol (from redeem metadata).

None
out_token_address str | None

Underlying out-token address (unused today; kept for symmetry with the swap-path resolution).

None
out_token_decimals int | None

Underlying out-token decimals (defaults to 18).

None

Returns:

Name Type Description
A PrimitiveMoneyLegs | None

class:PrimitiveMoneyLegs declaring the INPUT/OUTPUT legs, or

PrimitiveMoneyLegs | None

None when the receipt is not a resolvable PT redeem.

extract_primitive_money_legs_result

extract_primitive_money_legs_result(
    receipt: dict[str, Any],
    *,
    pt_address: str | None = None,
    out_token_symbol: str | None = None,
    out_token_address: str | None = None,
    out_token_decimals: int | None = None,
) -> ExtractResult[PrimitiveMoneyLegs]

Fail-closed variant of :meth:extract_primitive_money_legs — VIB-5354.

Forwards the compiler-threaded redeem / LP_CLOSE kwargs unchanged so the ResultEnricher (which builds them via :meth:build_extract_kwargs) gets the same INPUT/OUTPUT-leg semantics as the legacy method, now with a parse crash surfaced as ExtractError instead of a silent None.

ExtractMissing here preserves the legacy None contract: a non-PT / unmeasured Pendle withdraw or LP close where the extractor declines to declare typed legs (so the US-009 ledger dispatcher falls back to its legacy guess — blueprint 27 §6.6 / §10.10, Empty != Zero). It is NOT a parse error; only an actual crash / reported failure is ExtractError.

extract_redemption_amounts

extract_redemption_amounts(
    receipt: dict[str, Any],
) -> dict[str, int] | None

Extract redemption amounts from receipt.

Called by the framework after WITHDRAW/REDEEM execution.

RedeemPYEventData dataclass

RedeemPYEventData(
    caller: str,
    receiver: str,
    net_py_redeemed: int,
    net_sy_redeemed: int,
    yt_address: str,
)

Parsed data from Pendle RedeemPY event.

to_dict

to_dict() -> dict[str, Any]

Convert to dictionary.

SwapEventData dataclass

SwapEventData(
    caller: str,
    receiver: str,
    pt_to_account: int,
    sy_to_account: int,
    market_address: str,
)

Parsed data from Pendle Swap event.

is_buy_pt property

is_buy_pt: bool

Check if this is a buy PT operation (SY -> PT).

is_sell_pt property

is_sell_pt: bool

Check if this is a sell PT operation (PT -> SY).

pt_amount property

pt_amount: int

Get absolute PT amount.

sy_amount property

sy_amount: int

Get absolute SY amount.

to_dict

to_dict() -> dict[str, Any]

Convert to dictionary.

TransferEventData dataclass

TransferEventData(
    from_addr: str,
    to_addr: str,
    value: int,
    token_address: str,
)

Parsed data from ERC20 Transfer event.

to_dict

to_dict() -> dict[str, Any]

Convert to dictionary.

PendleMarketResolver

PendleMarketResolver(
    chain: str,
    api_client: PendleAPIClient | None = None,
    cache_ttl: float = 300.0,
)

Dynamic Pendle market discovery.

Uses live API lookups first and connector-owned static metadata when the API is unavailable or does not expose a lookup shape the framework needs.

The resolver is designed so that any agent (Claude Code, LangGraph, or a strategy's decide()) can discover valid markets without hardcoded addresses.

Initialize the resolver.

Parameters:

Name Type Description Default
chain str

Target chain name (ethereum, arbitrum, etc.)

required
api_client PendleAPIClient | None

Optional pre-configured API client. Created automatically if None.

None
cache_ttl float

How long to cache the market list (seconds). Default 5 min.

300.0

find_markets

find_markets(
    underlying: str | None = None, active_only: bool = True
) -> list[PendleMarketData]

Find Pendle markets, optionally filtered by underlying asset.

Parameters:

Name Type Description Default
underlying str | None

Filter by underlying asset symbol or address. Supports partial match (e.g., "sUSDe", "wstETH", "USDT").

None
active_only bool

If True, exclude expired markets.

True

Returns:

Type Description
list[PendleMarketData]

List of matching PendleMarketData, sorted by liquidity (desc).

get_best_market

get_best_market(underlying: str) -> PendleMarketData | None

Get the best active market for an underlying asset.

"Best" = highest liquidity among non-expired markets matching the underlying.

Parameters:

Name Type Description Default
underlying str

Underlying asset symbol or address (e.g., "sUSDe", "wstETH").

required

Returns:

Type Description
PendleMarketData | None

Best PendleMarketData or None if no matches.

resolve_by_pt_symbol

resolve_by_pt_symbol(
    pt_symbol: str,
) -> PendleMarketData | None

Resolve a PT symbol to its market data.

Tries API markets first (matching by pt_symbol field), then falls back to connector-owned market metadata.

Parameters:

Name Type Description Default
pt_symbol str

PT token symbol (e.g., "PT-sUSDe-7MAY2026", "PT-wstETH")

required

Returns:

Type Description
PendleMarketData | None

PendleMarketData or None if not found.

resolve_by_market_address

resolve_by_market_address(
    market_address: str,
) -> PendleMarketData | None

Resolve a market address to its data.

Parameters:

Name Type Description Default
market_address str

Market contract address.

required

Returns:

Type Description
PendleMarketData | None

PendleMarketData or None.

resolve_pt_token_info

resolve_pt_token_info(
    pt_symbol: str,
) -> tuple[str, int] | None

Resolve PT token symbol to (address, decimals).

Tries API first, falls back to connector-owned PT metadata.

Parameters:

Name Type Description Default
pt_symbol str

PT token symbol.

required

Returns:

Type Description
tuple[str, int] | None

(address, decimals) or None.

resolve_yt_token_info

resolve_yt_token_info(
    yt_symbol: str,
) -> tuple[str, int] | None

Resolve YT token symbol to (address, decimals).

Tries connector metadata, then attempts PT-equivalent cross-reference.

Parameters:

Name Type Description Default
yt_symbol str

YT token symbol.

required

Returns:

Type Description
tuple[str, int] | None

(address, decimals) or None.

resolve_market_address_from_pt_symbol

resolve_market_address_from_pt_symbol(
    pt_symbol: str,
) -> str | None

Resolve a PT symbol to its market address.

Parameters:

Name Type Description Default
pt_symbol str

PT token symbol.

required

Returns:

Type Description
str | None

Market address or None.

resolve_mint_sy_token

resolve_mint_sy_token(market_address: str) -> str | None

Resolve the token that mints SY for a market.

For most markets, this is the underlying asset. For yield-bearing token markets (like fUSDT0), it must be the yield-bearing token itself.

Falls back to connector-owned market mint metadata.

Parameters:

Name Type Description Default
market_address str

Market contract address.

required

Returns:

Type Description
str | None

Token address that mints SY, or None.

clear_cache

clear_cache() -> None

Clear the resolver's market cache, forcing a fresh API fetch.

LiquidityParams dataclass

LiquidityParams(
    receiver: str,
    market: str,
    token_in: str,
    amount_in: int,
    min_lp_out: int,
    slippage_bps: int = 50,
)

Parameters for liquidity operations.

MarketInfo dataclass

MarketInfo(
    market_address: str,
    sy_address: str,
    pt_address: str,
    yt_address: str,
    expiry: int,
    underlying_token: str,
    underlying_symbol: str,
)

Information about a Pendle market.

is_expired

is_expired(current_timestamp: int) -> bool

Check if the market has expired.

PendleActionType

Bases: Enum

Pendle action types.

PendleQuote dataclass

PendleQuote(
    token_in: str,
    token_out: str,
    amount_in: int,
    amount_out: int,
    price_impact_bps: int,
    gas_estimate: int,
    effective_price: Decimal,
)

Quote for a Pendle operation.

to_dict

to_dict() -> dict[str, Any]

Convert to dictionary.

PendleSDK

PendleSDK(
    rpc_url: str | None = None,
    chain: str = "arbitrum",
    token_resolver: TokenResolver | None = None,
    gateway_client: GatewayClient | None = None,
)

SDK for interacting with Pendle Protocol.

Pendle enables yield tokenization and trading through its AMM. This SDK builds transactions for: - Swapping tokens to/from PT (Principal Token) - Swapping tokens to/from YT (Yield Token) - Adding/removing liquidity - Minting/redeeming SY and PY tokens

Example

sdk = PendleSDK(rpc_url="https://arb1.arbitrum.io/rpc", chain="arbitrum")

Build swap transaction (WETH -> PT-wstETH)

tx = sdk.build_swap_exact_token_for_pt( receiver="0x...", market="0x...", token_in="0x...", # WETH amount_in=1018, # 1 WETH min_pt_out=1018, # Minimum PT to receive )

Initialize Pendle SDK.

Parameters:

Name Type Description Default
rpc_url str | None

DEPRECATED — direct RPC URL. Prefer gateway_client for any code path running in a strategy container.

None
chain str

Target chain (arbitrum, ethereum)

'arbitrum'
token_resolver TokenResolver | None

Optional TokenResolver instance. If None, uses singleton.

None
gateway_client GatewayClient | None

Gateway client for routing eth_call through the gateway's RpcService. Preferred over rpc_url.

None

router_abi property

router_abi: list[dict]

Load router ABI lazily.

erc20_abi property

erc20_abi: list[dict]

Load ERC20 ABI lazily.

get_router

get_router() -> Contract

Get the Pendle Router contract.

build_swap_exact_token_for_pt

build_swap_exact_token_for_pt(
    receiver: str,
    market: str,
    token_in: str,
    amount_in: int,
    min_pt_out: int,
    slippage_bps: int = 50,
    token_mint_sy: str | None = None,
) -> PendleTransactionData

Build a swap transaction from token to PT using swapExactTokenForPtSimple.

This uses the simplified Pendle V4 function that doesn't require ApproxParams or LimitOrderData, making encoding more reliable.

Parameters:

Name Type Description Default
receiver str

Address to receive the PT

required
market str

Market address

required
token_in str

Input token address

required
amount_in int

Amount of input token (in wei)

required
min_pt_out int

Minimum PT to receive

required
slippage_bps int

Slippage tolerance in basis points

50
token_mint_sy str | None

Token that mints SY (defaults to token_in if not specified). For yield-bearing token markets (like fUSDT0), this should be the yield-bearing token address, not the underlying.

None

Returns:

Type Description
PendleTransactionData

Transaction data for execution

build_swap_exact_pt_for_token

build_swap_exact_pt_for_token(
    receiver: str,
    market: str,
    pt_amount: int,
    token_out: str,
    min_token_out: int,
    slippage_bps: int = 50,
    token_redeem_sy: str | None = None,
) -> PendleTransactionData

Build a swap transaction from PT to token using swapExactPtForToken.

Uses the full Pendle V4 function with empty LimitOrderData (the Simple variant does not exist on the deployed router).

Parameters:

Name Type Description Default
receiver str

Address to receive the token

required
market str

Market address

required
pt_amount int

Amount of PT to swap

required
token_out str

Output token address

required
min_token_out int

Minimum output token to receive

required
slippage_bps int

Slippage tolerance in basis points

50
token_redeem_sy str | None

Token that redeems SY (defaults to token_out if not specified). For yield-bearing token markets, this should be the yield-bearing token address, not the underlying.

None

Returns:

Type Description
PendleTransactionData

Transaction data for execution

build_swap_exact_token_for_yt

build_swap_exact_token_for_yt(
    receiver: str,
    market: str,
    token_in: str,
    amount_in: int,
    min_yt_out: int,
    slippage_bps: int = 50,
    token_mint_sy: str | None = None,
) -> PendleTransactionData

Build a swap transaction from token to YT using swapExactTokenForYt.

Unlike PT swaps which use the Simple variant, YT swaps require ApproxParams for binary search of optimal flash swap size, plus LimitOrderData.

Parameters:

Name Type Description Default
receiver str

Address to receive the YT

required
market str

Market address

required
token_in str

Input token address

required
amount_in int

Amount of input token (in wei)

required
min_yt_out int

Minimum YT to receive

required
slippage_bps int

Slippage tolerance in basis points

50
token_mint_sy str | None

Token that mints SY (defaults to token_in)

None

Returns:

Type Description
PendleTransactionData

Transaction data for execution

build_swap_exact_yt_for_token

build_swap_exact_yt_for_token(
    receiver: str,
    market: str,
    yt_amount: int,
    token_out: str,
    min_token_out: int,
    slippage_bps: int = 50,
) -> PendleTransactionData

Build a swap transaction from YT to token using swapExactYtForToken.

Parameters:

Name Type Description Default
receiver str

Address to receive the token

required
market str

Market address

required
yt_amount int

Amount of YT to swap

required
token_out str

Output token address

required
min_token_out int

Minimum output token to receive

required
slippage_bps int

Slippage tolerance in basis points

50

Returns:

Type Description
PendleTransactionData

Transaction data for execution

build_add_liquidity_single_token

build_add_liquidity_single_token(
    receiver: str,
    market: str,
    token_in: str,
    amount_in: int,
    min_lp_out: int,
    slippage_bps: int = 50,
) -> PendleTransactionData

Build a transaction to add liquidity with a single token.

This adds liquidity to a Pendle market using a single input token. The router handles conversion to the proper ratio of SY and PT.

Parameters:

Name Type Description Default
receiver str

Address to receive LP tokens

required
market str

Market address

required
token_in str

Input token address

required
amount_in int

Amount of input token

required
min_lp_out int

Minimum LP tokens to receive

required
slippage_bps int

Slippage tolerance in basis points

50

Returns:

Type Description
PendleTransactionData

Transaction data for execution

build_remove_liquidity_single_token

build_remove_liquidity_single_token(
    receiver: str,
    market: str,
    lp_amount: int,
    token_out: str,
    min_token_out: int,
    slippage_bps: int = 50,
) -> PendleTransactionData

Build a transaction to remove liquidity to a single token.

Parameters:

Name Type Description Default
receiver str

Address to receive output token

required
market str

Market address

required
lp_amount int

Amount of LP tokens to burn

required
token_out str

Output token address

required
min_token_out int

Minimum output token to receive

required
slippage_bps int

Slippage tolerance in basis points

50

Returns:

Type Description
PendleTransactionData

Transaction data for execution

build_redeem_py_to_token

build_redeem_py_to_token(
    receiver: str,
    yt_address: str,
    py_amount: int,
    token_out: str,
    min_token_out: int,
    slippage_bps: int = 50,
) -> PendleTransactionData

Build a transaction to redeem PT+YT to token.

After maturity, PT can be redeemed 1:1 for the underlying. Before maturity, you need equal amounts of PT and YT to redeem.

Parameters:

Name Type Description Default
receiver str

Address to receive output token

required
yt_address str

YT contract address

required
py_amount int

Amount of PT+YT to redeem

required
token_out str

Output token address

required
min_token_out int

Minimum output token

required
slippage_bps int

Slippage tolerance

50

Returns:

Type Description
PendleTransactionData

Transaction data for execution

build_approve_tx

build_approve_tx(
    token_address: str,
    spender: str | None = None,
    amount: int = MAX_UINT256,
) -> PendleTransactionData

Build an ERC-20 approval transaction.

Parameters:

Name Type Description Default
token_address str

Token to approve

required
spender str | None

Spender address (defaults to router)

None
amount int

Amount to approve (defaults to max)

MAX_UINT256

Returns:

Type Description
PendleTransactionData

Transaction data for execution

PendleTransactionData dataclass

PendleTransactionData(
    to: str,
    value: int,
    data: str,
    gas_estimate: int,
    description: str,
    action_type: PendleActionType,
)

Transaction data for Pendle operations.

to_dict

to_dict() -> dict[str, Any]

Convert to dictionary.

SwapParams dataclass

SwapParams(
    receiver: str,
    market: str,
    min_out: int,
    token_in: str,
    amount_in: int,
    slippage_bps: int = 50,
)

Parameters for a swap operation.

amount_out_minimum property

amount_out_minimum: int

Calculate minimum output with slippage.

get_pendle_adapter

get_pendle_adapter(
    rpc_url: str | None = None,
    chain: str = "arbitrum",
    wallet_address: str | None = None,
    gateway_client: GatewayClient | None = None,
) -> PendleAdapter

Factory function to create a PendleAdapter instance.

get_pendle_sdk

get_pendle_sdk(
    rpc_url: str | None = None,
    chain: str = "arbitrum",
    gateway_client: GatewayClient | None = None,
) -> PendleSDK

Factory function to create a PendleSDK instance.