# 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 ```toml 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 ```toml 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: ```sh solana program dump -u d crdRi38zEhQdzpsxnKur73WHBM9BSvXMSfGcbLyJCdP ./credix.so ``` Day to day stable testing can be done by grabbing the mainnet binary using following command: ```sh 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](https://docs.rs/credix_client/latest/credix_client/instruction/index.html) information for each instruction. You can find more [cargo docs](https://docs.rs/credix_client/latest/credix_client/state/index.html) 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 ```rust 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 ```rust 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. ```rust 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: ```rust 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. ```Rust 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, 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, 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, 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, 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](https://www.npmjs.com/package/@credix/credix-client) to help with gathering different accounts. See the README of that package to get started with it. ```typescript ... 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](https://docs.rs/credix_client/latest/credix_client/state/) ### Disclaimer These examples are provided as is. Do not blindly copy paste for use in production.