Smart Contract Integration

This page describes how to integrate Illuminate into your smart contracts with examples.

Looking up markets

We can begin with finding supported tokens and pools within an Illuminate market. A market is defined by a tuple of underlying (address) and maturity (uint256).

address marketPlace = 0x9A74762723685c5EEE2f94b80a427dE1bf029426;

// To look up a market, you will need the maturity and underlying for a given market.
// In this example, we'll look up information about the USDC-MAR23 market.
uint256 maturity = 1680393600; // ~April 02, 2023
address underlying = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; // USDC

// First, we'll look up the supported principal tokens for the market.
// To do so, call the markets method on the MarketPlace contract. This method takes
// in an underlying, maturity and principal enum. The enum value, defined below, 
// returns the PT for a given market. A null address is returned if there is no PT
// for that particular market.
enum Principals {
   Illuminate, // 0
   Swivel, // 1
   Yield, // 2
   Element, // 3
   Pendle, // 4
   Tempus, // 5
   Sense, // 6
   Apwine, // 7
   Notional // 8
}

// To look up Pendle's PT for the USDC-MAR23 market:
address pendlePT = IMarketPlace(marketPlace).markets(underlying, maturity, 4);

// Additionally, we can get the Yield Space Pool for the Illuminate principal token
// by calling the pools method:
address iptPool = IMarketPlace(marketPlace).pools(underlying, maturity);

Lending

The Lender contract provides convenience lend methods that swap between the underlying and supported PTs. Each protocol has a lend method that results in users receiving Illuminate principal tokens (iPTs).

address lender = 0x8dF84b03F73a680E04b0A59EE173219026333107;

// This example is for USDC-MAR23 market, lending on Notional
uint8 principal = 8; // Notional's enum value from the MarketPlace contract
uint256 maturity = 1680393600; // ~April 02, 2023
address underlying = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; // USDC
uint256 amount = 100000000; // $100 USDC
uint256 minReceived = 90000000; // Slippage control: receive at least 90 iPTs

// First, approve the Lender to spend the user's underlying
IERC20(underlying).approve(lender, amount);

// Second, lend the underlying via Illuminate. 
// The amount of iPTs is returned by the lend method 
uint256 received = ILender(lender).lend(principal, maturity, underlying, amount, minReceived);

Minting

If a user already has PTs, they can wrap them into iPTs via the Lender contract's mint method.

address lender = 0x8dF84b03F73a680E04b0A59EE173219026333107;

// In this example, let's assume the user has Swivel's zcToken for this market
uint8 principal = 1; // Swivel's enum value from MarketPlace contract
uint256 maturity = 1680393600; // ~April 02, 2023
address underlying = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; // USDC
uint256 amount = 100000000; // $100 zcTokens
address zcToken = 0x3476303e9038833AeC9ccCd12747BD0E0d026a8B; // Swivel's PT

// First, approve the Lender to spend the user's principal token
IERC20(zcToken).approve(lender, amount);

// Wrap the zcToken in an iPT. The user will receive iPTs 1:1 for each PT sent 
// to the Lender
ILender(lender).mint(principal, underlying, maturity, amount);

Redeeming

Once a market matures, users can redeem the underlying asset via the Redeemer contract.

Execution of the redemption should only be done after the Redeemer has redeemed the supported principal tokens from the Lender contract. Redeeming prior to this will result in lost funds.

address redeemer = 0x7690e18b7c7BE861976fCBb8E5053D2a2ebaB1AD;

// Get the market that has matured
uint256 maturity = 1680393600; // ~April 02, 2023
address underlying = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; // USDC

// Execute the redemption
IRedeemer(redeemer).redeem(underlying, maturity);

Swapping

Prior to maturity, users may swap their iPTs for the underlying, and vise versa via a Yield Space Pool.

address marketPlace = 0x9A74762723685c5EEE2f94b80a427dE1bf029426;

// Get the market that will be used to swap on
address iPT = 0x49494b3CB41829011471A059a72A16652D95aD6f; // Illuminate's principal token
uint256 maturity = IERC5095(iPT).maturity(); // e.g. 1680393600 (~April 02, 2023)
address underlying = IERC5095(iPT).underlying(); // e.g. USDC
uint256 amount = 100000000; // $100 USDC (or iPTs, which have the same # of decimals)
uint256 slippage = 101000000; // Minimum number of tokens to receive in the swap

// First example: swap underlying for iPTs
// First, approve the MarketPlace contract to spend the underlying
IERC20(underlying).approve(marketPlace, amount);

// Conduct the swap
IMarketPlace(marketPlace).buyPrincipalToken(underlying, maturity, amount, slippage);

-----------------------------------------

// Second example: swap iPTs for underlying
// First, approve the MarketPlace contract to spend the iPT
IERC20(iPT).approve(marketPlace, amount);

// Conduct the swap
IMarketPlace(marketPlace).sellPrincipalToken(underlying, maturity, amount, slippage);

Note that there are two other methods that can be used to faciliate swaps: buyUnderlying and sellUnderlying. These methods provide different slippage configurations, and require a similar flow to execute.

Swapping with EIP4626 (EIP5095) Interfaces

Prior to maturity, users may swap their iPTs for the underlying, and vise versa via a Yield Space Pool.

// Get the market that will be used to swap on
address iPT = 0x49494b3CB41829011471A059a72A16652D95aD6f; // Illuminate's principal token
uint256 maturity = IERC5095(iPT).maturity(); // e.g. 1680393600 (~April 02, 2023)
address underlying = IERC5095(iPT).underlying(); // e.g. USDC
uint256 amount = 100000000; // $100 USDC (or iPTs, which have the same # of decimals)
uint256 slippage = 101000000; // Minimum number of tokens to receive in the swap

// First example: swap underlying for iPTs
// First, approve the iPT contract to spend the underlying
IERC20(underlying).approve(iPT, amount);

// Conduct the swap through EIP5095 interfaces -- 
// Spends `amount` on iPTs through a YieldSpace pool, receiving at minimum `slippage`
IERC5095(iPT).deposit(address(this), amount, slippage);

Checking for Paused States

Integrated protocols are subject to being paused on a principal or market basis by the admin of each respective contract. Below, we demonstrate how to check the paused state of the contracts.

// The Lender contract stores the halted variable, which stops the
// entire Illuminate protocol.
address lender = 0x8dF84b03F73a680E04b0A59EE173219026333107;

// Fetch the halted flag
bool isIlluminateHalted = ILender(lender).halted();

// To determine if a particular protocol is stopped, check the paused mapping.
// This mapping uses the enum mapping from the MarketPlace to define the protocol.
// In this example, we check if Sense is paused on Illuminate.
bool isSensePaused = ILender(lender).paused(6);

// The MarketPlace may also pause a market, by setting the iPT address to 0 via the
// setPrincipal call. To check if a market is paused, call markets.
address marketPlace = 0x9A74762723685c5EEE2f94b80a427dE1bf029426;

// In this example, we'll check if the USDC-MAR23 market has been paused
uint256 maturity = 1680393600; // ~April 02, 2023
address underlying = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; // USDC
address isUSDCMAR23Paused = IMarketPlace(marketPlace).markets(underlying, maturity, 0);
requre(isUSDCMAR23Paused != address(0), 'market paused');

// In addition, the Redeemer contract can pause the redemption of iPT on a market
// basis. This can be looked up via the maturity and underlying pair.
address redeemer = 0x7690e18b7c7BE861976fCBb8E5053D2a2ebaB1AD;

// In this example, we'll look up the USDC-MAR23 market from above.
bool isPTRedemptionPaused = IRedeemer(redeemer).paused(underlying, maturity);

Last updated