A lending protocol for the Token program on the Solana blockchain inspired by Aave and Compound.
Lending Market: 6T4XxKerq744sSuj3jaoV6QiZ8acirf4TrPwQzHAoSy5
Lending program id: port_variable_rate_lending_instructions::id()
Staking program id: port_staking_instructions::id()
| Asset Name | Reserve Address | | :--- | ----: | | SOL | X9ByyhmtQH3Wjku9N5obPy54DbVjZV7Z99TPJZ2rwcs | | USDC | DcENuKuYd6BWGhKfGr7eARxodqG12Bz1sN5WA8NwvLRx | | USDT | 4tqY9Hv7e8YhNQXuH75WKrZ7tTckbv2GfFVxmVcScW5s | | PAI | DSw99gXoGzvc4N7cNGU7TJ9bCWFq96NU2Cczi1TabDx2 | | SRM | ZgS3sv1tJAor2rbGMFLeJwxsEGDiHkcrR2ZaNHZUpyF | | BTC | DSST29PMCVkxo8cf5ht9LxrPoMc8jAZt98t6nuJywz8p | | MER | BnhsmYVvNjXK3TGDHLj1Yr1jBGCmD1gZMkAyCwoXsHwt | | mSOL | 9gDF5W94RowoDugxT8cM29cX8pKKQitTp2uYVrarBSQ7 | | pSOL | GRJyCEezbZQibAEfBKCRAg5YoTPP2UcRSTC7RfzoMypy |
Actually all the information below you can get by parsing the reserve data. Here is provided for convenience.
| Asset Name | pToken Mint | | :--- | ---: | | SOL | 8ezDtNNhX91t1NbSLe8xV2PcCEfoQjEm2qDVGjt3rjhg | | USDC | FgSsGV8GByPaMERxeQJPvZRZHf7zCBhrdYtztKorJS58 | | USDT | 3RudPTAkfcq9Q9Jk8SVeCoecCBmdKMj6q5smsWzxqtqZ | | PAI | GaqxUwFGGrDouYLqghchmZU97Y1rNhyF7noMTJNvpQPa | | SRM | 77TBgKmTNtMdGrt1ewNRb56F2Xw6fNLZZj33JZ3oGwXh | | BTC | QN2HkkBaWHfYSU5bybyups9z1UHu8Eu7QeeyMbjD2JA | | MER | 6UgGnLA3Lfe8NBLAESctsUXWdP3zjMFzSLEZxS3tiaKh | | mSOL | Dt1Cuau5m5CSmun8hZstjEh9RszxAmejnq7ZaHNcuXfA |
| Asset Name | Oracle Pubkey | | :--- | ---: | | SOL | H6ARHf6YXhGYeQfUzQNGk6rDNnLBQKrenN712K4AQJEG | | USDC | N / A | | USDT | 3vxLXJqLqF3JG5TCbYycbKWRBbCJQLxQmBGCkyqEEefL | | PAI | N/A | | SRM | 3NBReDRTLKMQEKiLD5tGcx4kXbTf88b7f2xLS9UuGjym | | BTC | GVXRSBjFk6e6J3NbVPXohDJetcTjaeeuykUpbQF8UoMU | | MER | G4AQpTYKH1Fmg38VpFQbv6uKYQMpRhJzNPALhp7hqdrs | | pSOL | H6ARHf6YXhGYeQfUzQNGk6rDNnLBQKrenN712K4AQJEG |
| Asset Name | Supply Public Keys | | :--- | ---: | | SOL | BLAFX12cDmsumyB6k3L6whJZqNqySaWeCmS5rVuzy3SS | | USDC | 2xPnqU4bWhUSjZ74CibY63NrtkHHw5eKntsxf8dzwiid | | USDT | QyvfrbqH7Mo8W5tHN31nzbfNiwFwqPqahjm9fnzo5EJ | | PAI | 42kNZrAuwZHLtuc7jvVX7zMfkfgwbPynqzFB3zdkAEGM | | SRM | DjhMNdgdbxNud1gmc4DUwrQqJxNbjhxiwNnhc4usSXmQ | | BTC | FZKP27Zxz9GbW86hhq3d1egzpBH5ZnYkyjQZVf86NQJ8 | | MER | 6UmrawFZgdPvMe6BLZdZCNRFz9u2TWsu5enFbTufA3a1 |
Lending Program id: pdQ2rQQU5zH2rDgZ7xH2azMBJegUzUyunJ5Jd637hC4
Staking program id: port_staking_instructions::id()
Lending Market: H27Quk3DSbu55T4dCr1NddTTSAezXwHU67FPCZVKLhSW
| Asset Name | Reserve Address | | :--- | ----: | | SOL | 6FeVStQAGPWvfWijDHF7cTWRCi7He6vTT3ubfNhe9SPt | | USDC | G1CcAWGhfxhHQaivC1Sh5CWVta6P4dc7a5BDSg9ERjV1 | | USDT | B4dnCXcWXSXy1g3fGAmF6P2XgsLTFYaQxYpsU3VCB33Q | | BTC | A8krqNC1WpWYhqUe2Y5WbLd1Zy4y2rRN5wJC8o9Scbyk | | MER | FdPnmYS7Ma8jfSy7UHAN5QM6teoqwd3vLQtoU6r2Umwy |
Actually all the information below you can get by parsing the reserve data. Here is provided for convenience.
| Asset Name | pToken Mint | | :--- | ---: | | SOL | So11111111111111111111111111111111111111112 | | USDC | G6YKv19AeGZ6pUYUwY9D7n4Ry9ESNFa376YqwEkUkhbi | | USDT | 9NGDi2tZtNmCCp8SVLKNuGjuWAVwNF3Vap5tT8km5er9 | | BTC | EbwEYuUQHxcSHszxPBhA2nT2JxhiNwJedwjsctJnLmsC | | MER | Tm9LcR74uJHPw3zY3j3nSh5xfcyaLbvXgAtTJwbqnnp |
| Asset Name | pToken Mint | | :--- | ---: | | SOL | Hk4Rp3kaPssB6hnjah3Mrqpt5CAXWGoqFT5dVsWA3TaM | | USDC | HyxraiKfdajDbYTC6MVRToEUBdevBN5M5gfyR4LC3WSF | | USDT | 4xEXmSfLFPkZaxdL98XkoxKpXEvchPVs21GYqa8DvbAm | | BTC | 95XGx3cM83Z1Bbx8pJurAHwxJjvShTJE4BtfgMWfV6NB | | MER | FQzruvtLTk6qtPNEAJHQWMVs4M9UMP9T3cGAVfUskHfP |
| Asset Name | Oracle Pubkey | | :--- | ---: | | SOL | J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix | | USDC | N/A | | USDT | 38xoQ4oeJCBrcVvca2cGk7iV1dAfrmTR1kmhSCJQ8Jto | | BTC | HovQMDrbAgAYPCmHVSrezcSmkMtXSSUsLDFANExrZh2J | | MER | 6Z3ejn8DCWQFBuAcw29d3A5jgahEpmycn7YDMX7yRNrn |
| Asset Name | Supply Public Keys | | :--- | ---: | | SOL | AbKeR7nQdHPDddiDQ71YUsz1F138a7cJMfJVtpdYUSvE | | USDC | GAPyFes3o7S7coY9nsuhaRZBEA7DdQPHBfVdY2DdgNua | | USDT | AeGbAqYZUURTykyCsgAUfopBMqQ3eAwrDxYhXoRhiw8q | | BTC | 75iyCxiPoj3MaUVo3SynmhaN3cbLDEhd4d9VHik6Kkvr | | MER | AMjhzse1TtTcKBFw5tQPLGtVoEsL4gt9YowNnzMKEGUr |
For assets that have liquidity mining reward, you need first create a stake account in order to collateralize and get the reward.
First, get the data of the reserve you want deposit into and unpack it to get the field reserve.config.deposit_staking_pool
which
is the staking pool id, and generates the seed and keypair by using the Solana built in sha256
hashing function
solana_sdk::hash::hashv(&[owner.as_ref(), staking_pool.as_ref(), staking_program_id.as_ref()])
, where the owner should be your
wallet's public key, and generate the keypair for stake account by solana_sdk::signer::keypair::keypair_from_seed
. Then
call create_account
and create_stake_account
instruction to create the stake account. Owner need to sign the instruction of claiming reward.
rust
let account_seed = hashv( & [owner.as_ref(), staking_pool.as_ref(), staking_program_id.as_ref()]);
let stake_account_key_pair = keypair_from_seed(account_seed.as_ref()).unwrap();
let instructions = vec![
create_account(
&payer.pubkey(),
&stake_account_key_pair.pubkey(),
stake_account_rent,
StakeAccount::LEN as u64,
&staking_program_id
),
create_stake_account(
staking_program_id,
stake_account_key_pair.pubkey(),
staking_pool,
owner
)
];
Assuming that you already have your stake account or you are depositing to a reserve without liquidity mining reward, you need refresh all the reserves the obligation has interacted with and the obligation itself in the same instruction before you depositing / withdrawing / repaying / liquidating.
rust
let mut refresh_instructions = vec![];
//reserve_map is a map of reserve pubkey of reserve data, you can get the oracle pubkey from the reserve data or can hard code it in a config file.
let to_refresh = reserve_map.iter().filter( | (k, _) | {
obligation
.borrows
.iter()
.map(| li | li.borrow_reserve)
.chain(obligation.deposits.iter().map(| ob | ob.deposit_reserve))
.any( | r | r == * * k)
});
refresh_instructions.extend(
to_refresh.map( | (k, v) | refresh_reserve(lending_program_id, * k, v.liquidity.oracle_pubkey)),
);
refresh_instructions.push(
refresh_obligation(
program_id,
obligation_pubkey,
obligation
.deposits
.iter()
.map(|d| d.deposit_reserve)
.chain(obligation.borrows.iter().map(|b| b.borrow_reserve))
.collect(),
)
);
rust
let mut transaction = Transaction::new_with_payer(
&[
create_account(
&payer.pubkey(),
&obligation_keypair.pubkey(),
rent.minimum_balance(Obligation::LEN),
Obligation::LEN as u64,
&port_finance_variable_rate_lending::id(),
),
init_obligation(
port_finance_variable_rate_lending::id(),
obligation.pubkey,
lending_market.pubkey,
user_accounts_owner.pubkey(),
),
],
Some( & payer.pubkey()),
);
rust
deposit_reserve_liquidity(
port_variable_rate_lending::id(),
liquidity_amount,
user_liquidity_token_account_pubkey,
user_collateral_token_account_pubkey,
reserve_pubkey,
reserve.liquidity.supply_pubkey,
reserve.collateral.mint_pubkey,
self.pubkey,
user_transfer_authority.pubkey()
)
rust
deposit_obligation_collateral(
port_variable_rate_lending::id(),
liquidity_amount,
user_collateral_token_account_pubkey,
reserve.collateral.supply_pubkey,
reserve_pubkey,
obligation_pubkey,
lending_market.pubkey,
obligation.owner,
user_transfer_authority.pubkey(),
Some(stake_account_pubkey),
Some(staking_pool_pubkey),
)
rust
deposit_reserve_liquidity_and_obligation_collateral(
port_variable_rate_lending::id(),
liquidity_amount,
user_liquidity_token_account_pubkey,
user_collateral_token_account_pubkey,
reserve_pubkey,
reserve.liquidity.supply_pubkey,
reserve.collateral.mint_pubkey,
lending_market_pubkey,
reserve.collateral.supply_pubkey,
obligation_pubkey,
obligation.owner,
user_transfer_authority.pubkey(),
Some(stake_account_pubkey),
Some(staking_pool_pubkey),
)
There will be a coming withdrawAndRedeem
instruction soon, while it is under auditing. So now, you need first
uncollateralized the asset then withdraw.
rust
withdraw_obligation_collateral(
port_finance_variable_rate_lending::id(),
WITHDRAW_AMOUNT,
reserve.collateral.supply_pubkey,
sol_test_reserve.user_collateral_pubkey,
sol_test_reserve.pubkey,
test_obligation.pubkey,
lending_market.pubkey,
test_obligation.owner,
Some(stake_account_pubkey),
Some(staking_pool_pubkey),
)
rust
redeem_reserve_collateral(
port_finance_variable_rate_lending::id(),
COLLATERAL_AMOUNT,
user_collateral_token_account_pubkey,
user_liquidity_token_account_pubkey,
reserve_pubkey,
reserve.collateral.mint_pubkey,
reserve.liquidity.supply_pubkey,
lending_market.pubkey,
user_transfer_authority.pubkey(),
)
To repay, you can pass number greater then amount you borrow to repay all, for example you can pass u64::MAX
in to
repay all.
rust
repay_obligation_liquidity(
port_finance_variable_rate_lending::id(),
liquidity_amount,
user_liquidity_token_account_pubkey,
reserve.liquidity.supply_pubkey,
reserve_pubkey,
obligation_pubkey,
lending_market.pubkey,
user_transfer_authority.pubkey(),
)
Please refer to https://github.com/port-finance/liquidator
We had an instruction with the following signature for flash loan:
rust
pub enum LendingInstruction {
// ....
/// Make a flash loan.
///
/// Accounts expected by this instruction:
///
/// 0. `[writable]` Source liquidity token account.
/// Minted by reserve liquidity mint.
/// Must match the reserve liquidity supply.
/// 1. `[writable]` Destination liquidity token account.
/// Minted by reserve liquidity mint.
/// 2. `[writable]` Reserve account.
/// 3. `[]` Lending market account.
/// 4. `[]` Derived lending market authority.
/// 5. `[]` Flash loan receiver program account.
/// Must implement an instruction that has tag of 0 and a signature of `(repay_amount: u64)`
/// This instruction must return the amount to the source liquidity account.
/// 6. `[]` Token program id.
/// 7. `[writable]` Flash loan fee receiver account.
/// Must match the reserve liquidity fee receiver.
/// 8. `[writable]` Host fee receiver.
/// .. `[any]` Additional accounts expected by the receiving program's `ReceiveFlashLoan` instruction.
FlashLoan {
/// The amount that is to be borrowed
amount: u64,
},
}
In the implementation, we do the following in order:
amount
from the source liquidity account to the destination liquidity accountReceiveFlashLoan
function (the flash loan receiver program is required to have this function with tag 0
).
The additional account required for ReceiveFlashLoan
is given from the 10th account of the FlashLoan
instruction, i.e. after host fee receiver.ReceiveFlashLoan
function.The flash loan receiver program should have a ReceiveFlashLoan
instruction which executes the user-defined operation and return the funds to the reserve in the end.
```rust pub enum FlashLoanReceiverInstruction {
/// Receive a flash loan and perform user-defined operation and finally return the fund back.
///
/// Accounts expected:
///
/// 0. `[writable]` Source liquidity (matching the destination from above).
/// 1. `[writable]` Destination liquidity (matching the source from above).
/// 2. `[]` Token program id
/// .. `[any]` Additional accounts provided to the lending program's `FlashLoan` instruction above.
ReceiveFlashLoan {
// Amount that is loaned to the receiver program
amount: u64
}
}
```
You can view a sample implementation here.