17 releases (8 breaking)
0.9.1 | Sep 13, 2023 |
---|---|
0.8.0 | Jun 13, 2023 |
0.7.6 | Aug 31, 2023 |
0.6.0 | Feb 24, 2023 |
0.4.0 | Nov 18, 2022 |
#5 in #cpi
61KB
341 lines
Credix Rust client
A crate to interact with the Credix program via CPI
NOTE: This crate was generated with the help of the Anchor generated IDL and adds a few extra functions in addition to the generated code. To know certain pda seeds or which accounts are mutable, please refer back to the Credix program IDL. It is available for each environment in the source of this crate.
Environments
Pre-mainnet
- Pre-mainnet: This is the Solana devnet testing environment for Credix programs. It is usually very close to the mainnet environment. Before releasing any feature to mainnet, the pre-mainnet program is updated.
Address:
crdRi38zEhQdzpsxnKur73WHBM9BSvXMSfGcbLyJCdP
.
Markets
- Market with seed
credix-marketplace
on pre-mainnet should be targeted for testing without withdraw epochs. - Market with seed
withdraw-epochs
on pre-mainnet should be targeted for testing with withdraw epochs.
Configuring the client
To target our pre-mainnet environment you can enable the pre-mainnet
feature
credix_client = { version="0.8.0", features = ["pre-mainnet"], default-features = false }
Mainnet
- Mainnet: This is our production environment.
Address:
CRDx2YkdtYtGZXGHZ59wNv1EwKHQndnRc1gT4p8i2vPX
Note: To use any market on any Credix environment, you will need an active Credix pass with certain permissions set. Contact Credix to have one issued.
Configuring the client
To target our mainnet environment you can enable the mainnet
feature
credix_client = { version="0.8.0", features = ["mainnet"], default-features = false }
Local development:
Getting the Credix binary
When creating a local setup it's advised to grab the pre-mainnet binary from the Solana devnet cluster when preparing for upcoming Credix mainnet releases. This can be done using following command:
solana program dump -u d crdRi38zEhQdzpsxnKur73WHBM9BSvXMSfGcbLyJCdP ./credix.so
Day to day stable testing can be done by grabbing the mainnet binary using following command:
solana program dump -u m CRDx2YkdtYtGZXGHZ59wNv1EwKHQndnRc1gT4p8i2vPX ./credix.so
Preparing the local cluster
To get to a working local environment certain instructions have to be called to set up everything. The following instructions should be called in the same order.
You can find more cargo docs information for each instruction.
You can find more cargo docs information for each account.
Credix program preparation
initialize_program_state
Initializes the ProgramState account that is shared across the entire Credix ecosystem for management purposes. This will allow you to create markets.
initialize_market
Creates a market.
issue_credix_pass
This gives permissions to any participant in the market. It can only be issued by either a key that is listed as a pass issuer in the MarketAdmins account or by the multisig specified in the ProgramState.
Any user interaction with a market requires an active Credix pass.
Market interactions
deposit_funds
This deposits funds into the pool in exchange for LP tokens.
create_withdraw_epoch
Creates a withdraw epoch. This needs to happen before anyone can participate in an epoch. The epoch needs to be created when the previous one is completely over.
create_withdraw_request
Creates a withdraw request. This is where you request a certain amount to withdraw. Depending on the amounts requested by other participants in the epoch and the money available in the market pool, a certain amount will be made available in a first instance. Then a second round happens where amounts that weren't withdrawn become available to the rest of the participants. The duration of the different phases and other parameters are specified on the market level.
redeem_request
Here we actually withdraw funds during either the redeem phase or the free for all phase.
Affecting the LP price
So far no interest repayments have happened so no change in the LP price has occurred. To include this scenario in your tests you need to do a few steps. Again, following instructions need to be done in order and before the withdraw epoch setup to see the effect and more information can be found in the same places as described before.
For convenience's sake we will provide some basic configurations to use to be able to trigger an interest repayment.
create_deal
Creates a deal.
Example config
max_funding_duration: 255,
deal_name: "Simple Deal",
arrangement_fees: 0,
arrangement_fee_percentage: Fraction::new(0,100),
migrated: false,
set_repayment_schedule
Sets the repayment schedule. This defines when repayments need to happen and according to what waterfall they need to be processed.
Example config
let now = system_time::now();
// now will contain the current unix timestamp in ms.
let principal = 1000000;
let config_for_instruction= RepaymentScheduleConfig {
periods: vec![
RepaymentPeriodInput {
waterfall_index: 0,
accrual_in_days: 30,
principal_expected: None,
time_frame: TimeFrame {
start: now,
end: now + 30 * SECONDS_IN_DAY - 1,
},
},
RepaymentPeriodInput {
waterfall_index: 1,
accrual_in_days: 30,
principal_expected: Some(principal),
time_frame: TimeFrame {
start: now + 30 * SECONDS_IN_DAY,
end: now + 60 * SECONDS_IN_DAY - 1,
},
}
],
start_ts: now,
daycount_convention: DaycountConvention::Act360,
waterfall_definitions: vec![
DistributionWaterfall {
waterfall_type: DistributionWaterfallType::Revolving,
tiers: vec![
WaterfallTier {
allocations: vec![RepaymentAllocation::Interest],
tranche_indices: vec![0],
charge: true,
slash: false,
},
],
},
DistributionWaterfall {
waterfall_type: DistributionWaterfallType::Amortization,
tiers: vec![
WaterfallTier {
allocations: vec![
RepaymentAllocation::Principal,
RepaymentAllocation::Interest
],
tranche_indices: vec![0],
charge: true,
slash: false,
}
],
},
],
};
set_tranches
Here we define the composition of the deal in tranches. Tranches are the investment opportunities of a deal.
Example config
This includes a tranche that is funded by the pool. This means that when the deal is activated, money will flow from the market's liquidity pool towards the deal.
vec![
TrancheConfig {
index: 0,
max_deposit_percentage: Fraction::new(1, 1).unwrap(),
early_withdrawal_principal: true,
funded_by_liquidity_pool: true,
name: "A".to_string(),
size: principal, // the variable we declared earlier
interest: Fraction::new(12, 10).unwrap(),
interest_performance_fee: Fraction::new(1, 10).unwrap(),
principal_performance_fee: Fraction::new(0, 1).unwrap(),
membership_fee: Fraction::new(1, 10).unwrap(),
late_interest: Fraction::new(1, 10).unwrap(),
early_principal: Fraction::new(1, 10).unwrap(),
late_principal: Fraction::new(1, 10).unwrap(),
},
],
open_deal
Once a deal has been fully configured, we have to change it's status to allow investors to invest into it. This opens the deal for funding.
activate_deal
Once a deal has been fully funded (which is automatically the case using the configs we provided), a deal needs to be activated. This marks the moment funds move from the liquidity pool towards the deal to fund the pool funded tranches. The actual borrowed amount becomes available to the borrower.
repay_deal
This allows the borrower to repay the deal. At this point interest will flow towards the pool and the LP price should rise and your investment should have RoI.
Getting the LP price
The LP token price is the TVL divided by the total supply of LP tokens. The TVL is equal to the total outstanding credit and the funds in the liquidity pool token account.
It can be found on-chain using following calculation:
let pool_liquidity = liquidity_pool_token_account.amount;
let outstanding_credit = global_market_state.pool_outstanding_credit;
let tvl = pool_liquidity + outstanding_credit;
let lp_supply = lp_token_mint.supply;
let lp_price = tvl / lp_supply;
Examples
On-chain
This is an example rust program made with anchor, here we have CPI for deposit, withdraw, create withdraw request and redeem withdraw request.
use anchor_lang::prelude::*;
use credix_client::cpi::accounts::{
CreateWithdrawRequest, DepositFunds, RedeemWithdrawRequest, WithdrawFunds,
};
use credix_client::cpi::{
create_withdraw_request, deposit_funds, redeem_withdraw_request, withdraw_funds,
};
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod integrator {
use super::*;
pub fn deposit_cpi(ctx: Context<DepositFundsCpi>, amount: u64) -> Result<()> {
let cpi_context = CpiContext::new(
ctx.accounts.credix_program.to_account_info(),
DepositFunds {
investor: ctx.accounts.investor.to_account_info(),
global_market_state: ctx.accounts.global_market_state.to_account_info(),
signing_authority: ctx.accounts.signing_authority.to_account_info(),
investor_token_account: ctx.accounts.investor_token_account.to_account_info(),
credix_pass: ctx.accounts.credix_pass.to_account_info(),
investor_lp_token_account: ctx.accounts.investor_lp_token_account.to_account_info(),
liquidity_pool_token_account: ctx
.accounts
.liquidity_pool_token_account
.to_account_info(),
lp_token_mint: ctx.accounts.lp_token_mint.to_account_info(),
rent: ctx.accounts.rent.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
token_program: ctx.accounts.token_program.to_account_info(),
associated_token_program: ctx.accounts.associated_token_program.to_account_info(),
base_token_mint: ctx.accounts.base_token_mint.to_account_info(),
},
);
deposit_funds(cpi_context, amount)?;
Ok(())
}
pub fn withdraw_cpi(ctx: Context<WithdrawFundsCpi>, amount: u64) -> Result<()> {
let cpi_context = CpiContext::new(
ctx.accounts.credix_program.to_account_info(),
WithdrawFunds {
investor: ctx.accounts.investor.to_account_info(),
global_market_state: ctx.accounts.global_market_state.to_account_info(),
signing_authority: ctx.accounts.signing_authority.to_account_info(),
investor_lp_token_account: ctx.accounts.investor_lp_token_account.to_account_info(),
investor_token_account: ctx.accounts.investor_token_account.to_account_info(),
liquidity_pool_token_account: ctx
.accounts
.liquidity_pool_token_account
.to_account_info(),
lp_token_mint: ctx.accounts.lp_token_mint.to_account_info(),
program_state: ctx.accounts.program_state.to_account_info(),
credix_treasury: ctx.accounts.credix_treasury.to_account_info(),
credix_treasury_token_account: ctx
.accounts
.credix_treasury_token_account
.to_account_info(),
treasury_pool_token_account: ctx
.accounts
.treasury_pool_token_account
.to_account_info(),
credix_pass: ctx.accounts.credix_pass.to_account_info(),
base_token_mint: ctx.accounts.base_token_mint.to_account_info(),
associated_token_program: ctx.accounts.associated_token_program.to_account_info(),
token_program: ctx.accounts.token_program.to_account_info(),
rent: ctx.accounts.rent.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
},
);
withdraw_funds(cpi_context, amount)?;
Ok(())
}
pub fn create_withdraw_request_cpi(
ctx: Context<WithdrawRequestCpi>,
amount: u64,
) -> Result<()> {
let accounts = ctx.accounts;
let cpi_context = CpiContext::new(
accounts.credix.to_account_info(),
CreateWithdrawRequest {
payer: accounts.payer.to_account_info(),
investor: accounts.investor.to_account_info(),
credix_pass: accounts.credix_pass.to_account_info(),
global_market_state: accounts.global_market_state.to_account_info(),
investor_lp_token_account: accounts.investor_lp_token_account.to_account_info(),
liquidity_pool_token_account: accounts
.liquidity_pool_token_account
.to_account_info(),
lp_token_mint: accounts.lp_token_mint.to_account_info(),
signing_authority: accounts.signing_authority.to_account_info(),
system_program: accounts.system_program.to_account_info(),
withdraw_epoch: accounts.withdraw_epoch.to_account_info(),
},
);
create_withdraw_request(cpi_context, amount)
}
pub fn redeem_request_cpi(ctx: Context<RedeemRequestCpi>, amount: u64) -> Result<()> {
let accounts = ctx.accounts;
let cpi_context = CpiContext::new(
accounts.credix.to_account_info(),
RedeemWithdrawRequest {
investor: accounts.investor.to_account_info(),
credix_pass: accounts.credix_pass.to_account_info(),
global_market_state: accounts.global_market_state.to_account_info(),
investor_lp_token_account: accounts.investor_lp_token_account.to_account_info(),
liquidity_pool_token_account: accounts
.liquidity_pool_token_account
.to_account_info(),
lp_token_mint: accounts.lp_token_mint.to_account_info(),
signing_authority: accounts.signing_authority.to_account_info(),
system_program: accounts.system_program.to_account_info(),
withdraw_epoch: accounts.withdraw_epoch.to_account_info(),
associated_token_program: accounts.associated_token_program.to_account_info(),
base_token_mint: accounts.base_token_mint.to_account_info(),
credix_treasury: accounts.credix_treasury.to_account_info(),
credix_treasury_token_account: accounts
.credix_treasury_token_account
.to_account_info(),
program_state: accounts.program_state.to_account_info(),
investor_token_account: accounts.investor_token_account.to_account_info(),
rent: accounts.rent.to_account_info(),
token_program: accounts.token_program.to_account_info(),
treasury_pool_token_account: accounts.treasury_pool_token_account.to_account_info(),
},
);
redeem_withdraw_request(cpi_context, amount)
}
}
#[derive(Accounts)]
pub struct DepositFundsCpi<'info> {
#[account(mut)]
pub investor: Signer<'info>,
pub credix_program: Program<'info, Credix>,
/// CHECK: test program
pub global_market_state: AccountInfo<'info>,
/// CHECK: test program
pub signing_authority: AccountInfo<'info>,
/// CHECK: test program
#[account(mut)]
pub investor_token_account: AccountInfo<'info>,
/// CHECK: test program
#[account(mut)]
pub liquidity_pool_token_account: AccountInfo<'info>,
/// CHECK: test program
#[account(mut)]
pub lp_token_mint: AccountInfo<'info>,
/// CHECK: test program
#[account(mut)]
pub investor_lp_token_account: AccountInfo<'info>,
/// CHECK: test program
pub credix_pass: AccountInfo<'info>,
/// CHECK: test program
pub base_token_mint: AccountInfo<'info>,
/// CHECK: test program
pub associated_token_program: AccountInfo<'info>,
pub rent: Sysvar<'info, Rent>,
/// CHECK: test program
pub token_program: AccountInfo<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct WithdrawFundsCpi<'info> {
#[account(mut)]
pub investor: Signer<'info>,
pub credix_program: Program<'info, Credix>,
#[account(mut)]
/// CHECK: test program
pub global_market_state: AccountInfo<'info>,
/// CHECK: test program
pub program_state: AccountInfo<'info>,
/// CHECK: test program
pub signing_authority: AccountInfo<'info>,
#[account(mut)]
/// CHECK: test program
pub investor_lp_token_account: AccountInfo<'info>,
#[account(mut)]
/// CHECK: test program
pub investor_token_account: AccountInfo<'info>,
#[account(mut)]
/// CHECK: test program
pub liquidity_pool_token_account: AccountInfo<'info>,
/// CHECK: test program
pub credix_treasury: AccountInfo<'info>,
#[account(mut)]
/// CHECK: test program
pub credix_treasury_token_account: AccountInfo<'info>,
#[account(mut)]
/// CHECK: test program
pub treasury_pool_token_account: AccountInfo<'info>,
#[account(mut)]
/// CHECK: test program
pub lp_token_mint: AccountInfo<'info>,
#[account(mut)]
/// CHECK: test program
pub credix_pass: AccountInfo<'info>,
/// CHECK: test program
pub base_token_mint: AccountInfo<'info>,
/// CHECK: test program
pub associated_token_program: AccountInfo<'info>,
/// CHECK: test program
pub token_program: AccountInfo<'info>,
pub rent: Sysvar<'info, Rent>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct WithdrawRequestCpi<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(mut)]
pub investor: Signer<'info>,
/// CHECK:
pub global_market_state: AccountInfo<'info>,
/// CHECK:
pub signing_authority: AccountInfo<'info>,
/// CHECK:
#[account()]
pub credix_pass: AccountInfo<'info>,
/// CHECK:
#[account(mut)]
pub withdraw_epoch: AccountInfo<'info>,
/// CHECK:
#[account()]
pub investor_lp_token_account: AccountInfo<'info>,
/// CHECK:
#[account()]
pub liquidity_pool_token_account: AccountInfo<'info>,
/// CHECK:
#[account()]
pub lp_token_mint: AccountInfo<'info>,
pub credix: Program<'info, Credix>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct RedeemRequestCpi<'info> {
#[account(mut)]
pub investor: Signer<'info>,
/// CHECK:
#[account(mut)]
pub global_market_state: AccountInfo<'info>,
/// CHECK:
#[account(mut)]
pub withdraw_epoch: AccountInfo<'info>,
/// CHECK:
#[account()]
pub program_state: AccountInfo<'info>,
/// CHECK: The check happens when verifying the PDA address.
#[account()]
pub signing_authority: AccountInfo<'info>,
/// CHECK:
#[account(mut)]
pub investor_lp_token_account: AccountInfo<'info>,
/// CHECK:
#[account(mut)]
pub investor_token_account: AccountInfo<'info>,
/// CHECK:
#[account(mut)]
pub liquidity_pool_token_account: AccountInfo<'info>,
/// CHECK: The address of account is known.
#[account()]
pub credix_treasury: AccountInfo<'info>,
/// CHECK:
#[account(mut)]
pub credix_treasury_token_account: AccountInfo<'info>,
/// CHECK:
#[account(mut)]
pub treasury_pool_token_account: AccountInfo<'info>,
/// CHECK:
#[account(mut)]
pub lp_token_mint: AccountInfo<'info>,
/// CHECK:
#[account(mut)]
pub credix_pass: AccountInfo<'info>,
/// CHECK:
#[account()]
pub base_token_mint: AccountInfo<'info>,
/// CHECK:
pub associated_token_program: AccountInfo<'info>,
/// CHECK:
pub token_program: AccountInfo<'info>,
pub rent: Sysvar<'info, Rent>,
pub system_program: Program<'info, System>,
pub credix: Program<'info, Credix>,
}
Off-chain
Typescript
For off-chain development we provide a Typescript client to help with gathering different accounts. See the README of that package to get started with it.
...
const marketName = "credix-marketplace";
const market = await client.fetchMarket(marketName);
Our client can help you with finding the keys of following accounts
const globalMarketState = market.address;
const programState = (await client.fetchProgramState()).address;
const signingAuthority = (await market.generateSigningAuthorityPDA())[0];
const investorLpTokenAccount = await market.findLPTokenAccount(investor);
const investorTokenAccount = await market.findBaseTokenAccount(investor);
const liquidityPoolTokenAccount = await market.findLiquidityPoolTokenAccount();
const credixTreasury = (await client.fetchProgramState()).credixTreasury;
const credixTreasuryTokenAccount = await market.findBaseTokenAccount(credixTreasury);
const treasuryPoolTokenAccount = market.treasury;
const lpTokenMint = market.lpMintPK;
const credixPass = (await market.fetchCredixPass(investor)).address
const baseTokenMint = await market.baseMintPK;
// Withdraw Epoch related accounts
const market = await client.fetchMarket(marketName);
const withdrawEpochAddress = WithdrawEpoch.generatePDA(market, market.latestWithdrawEpochIdx);
...
Rust
This crate also provides functions to help generate PDA's. See the Rust docs
Disclaimer
These examples are provided as is. Do not blindly copy paste for use in production.
Dependencies
~21–30MB
~523K SLoC