Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 59 additions & 12 deletions guides/DEVELOPERS_INTEGRATORS/GETTING_STARTED_INTEGRATOR.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,51 @@ Be aware of:
* Contact us on telegram to be added to our "Integrators" list. We will make a best effort to ping
you at least one week before any program update goes live.

## Migration: Unified Integration Interface

This release intentionally removes the old protocol-specific user entrypoints for wrapped
integrations and replaces them with one shared interface.

### Old -> New instruction mapping

| Old instruction | New instruction |
|----------------|-----------------|
| `kamino_deposit` | `integration_deposit` |
| `kamino_withdraw` | `integration_withdraw` |
| `drift_deposit` | `integration_deposit` |
| `drift_withdraw` | `integration_withdraw` |
| `solend_deposit` | `integration_deposit` |
| `solend_withdraw` | `integration_withdraw` |
| `juplend_deposit` | `integration_deposit` |
| `juplend_withdraw` | `integration_withdraw` |

### What integrators must change

- Stop building protocol-specific user deposit/withdraw instructions. They no longer exist in the
program interface.
- Select the protocol behavior from `bank.config.asset_tag`.
- For `integration_deposit`, pass the protocol-specific accounts in `remaining_accounts`.
- For `integration_withdraw`, pass protocol-specific accounts first in `remaining_accounts`, then
append the usual health/risk accounts.
- Update any discriminator allowlists or transaction inspectors that referenced
`*_withdraw` integration discriminators. There is now a single `integration_withdraw`
discriminator for all wrapped integrations.
- Re-run all client-side account builders. This migration is not only a method rename; the account
packing model changed as well.

### Exact account layouts

The exact per-protocol account layouts are enforced by the program and mirrored in our TS helpers.
Use the builders under:

- `tests/utils/kamino-instructions.ts`
- `tests/utils/drift-instructions.ts`
- `tests/utils/solend-instructions.ts`
- `tests/utils/juplend/user-instructions.ts`
- `tests/utils/integration-account-layouts.ts`

Those files are the current source of truth for the required ordering and optional-account padding.

## Important Instructions (click to learn more)

<details>
Expand All @@ -53,12 +98,12 @@ the bank, and you attempt to deposit 10, `deposit_up_to_limit` = true will depos
</details>

<details>
<summary> <b>kamino_deposit</b> - deposit into a Kamino Bank</summary>
<summary> <b>integration_deposit</b> - deposit into an integration Bank (Kamino, Drift, Solend, JupLend)</summary>

- Check `bank.config.asset_tag` ASSET_TAG_KAMINO (3) is allowed with this instruction. Others
have their own deposit instruction.
- Check `bank.config.asset_tag` and pass the protocol-specific accounts in `remaining_accounts`.
- Supported tags are the wrapped integration banks: Kamino (3), Drift (4), Solend (5), JupLend (6).
- No Risk Engine check, always considered risk-free
- `amount` is in native token (of the Kamino reserve), in native decimal, e.g. 1 SOL = 1 \* 10^9
- `amount` is in native underlying token decimals for every supported integration deposit.
</details>

<details>
Expand All @@ -74,13 +119,15 @@ by configuring `amount` will always leave the Balance on your account, even with
</details>

<details>
<summary> <b>kamino_withdraw</b> - withdraw from a Kamino Bank</summary>

- Check `bank.config.asset_tag` ASSET_TAG_KAMINO (3) is allowed with this instruction. Others
have their own deposit instruction.
- Requires a Risk Engine check (pass banks and oracles in remaining accounts)
- `amount` is in **collateral** token, which always uses native decimal. Perform a conversion
from liquidity -> collateral token.
<summary> <b>integration_withdraw</b> - withdraw from an integration Bank (Kamino, Drift, Solend, JupLend)</summary>

- Check `bank.config.asset_tag` and pass protocol accounts first in `remaining_accounts`, followed
by the usual risk-engine bank/oracle accounts.
- Requires a Risk Engine check for the post-withdraw health validation.
- The program splits `remaining_accounts` by protocol-specific account count, so ordering matters.
- `amount` semantics depend on the wrapped protocol:
- Kamino / Solend: amount is in collateral-share units.
- Drift / JupLend: amount is in native underlying token units.
- Can fail if the Bank doesn't have enough liquidity, or the Account after the action would fail the
risk check.
</details>
Expand Down Expand Up @@ -139,4 +186,4 @@ that week.

- Requires a Risk Engine check (pass banks and oracles in remaining accounts)
- Cannot be called by CPI
</details>
</details>
35 changes: 20 additions & 15 deletions guides/DEVELOPERS_INTEGRATORS/JUPLEND_INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ vault`.
- Liquidity token or underlying token - The token users deposit/withdraw (e.g. USDC, SOL), in
native decimals.
- Refreshing Lending - The "Lending" account stores information about the exchange rate of
fTokens/underlying. The refresh instruction is called `updateRate`. Our `juplend_deposit` and
`juplend_withdraw` handlers call this internally. For liquidation and other risk-sensitive flows,
fTokens/underlying. The refresh instruction is called `updateRate`. Our unified
`integration_deposit` and `integration_withdraw` JupLend flows call this internally. For
liquidation and other risk-sensitive flows,
include `updateRate` for all involved Juplend banks in the same tx before health checks that read JupLend state.
- Withdraw intermediary ATA - The bank's `integration_acc_3` account. JupLend withdrawals first land
here, then marginfi forwards tokens to the user's destination token account. Currently, this is an
Expand All @@ -43,15 +44,15 @@ vault`.
intermediary ATA (`integration_acc_3`) before first withdraw. This is not created by
`lending_pool_add_bank_juplend` or `juplend_init_position`. This requirement will be removed when
juplend allows withdrawal to pda accounts.
- `juplend_deposit(amount)` (user) - Deposit underlying tokens (native amount). Internally calls
JupLend `updateRate`, deposits through CPI, verifies minted fTokens, and credits the user's
marginfi position.
- `juplend_withdraw(amount, withdraw_all)` (user) - Withdraw underlying tokens (native amount).
Internally calls JupLend `updateRate`, burns fTokens via CPI, then transfers tokens from the
withdraw intermediary ATA to the user's destination token account.
- `integration_deposit(amount)` (user, JupLend bank) - Deposit underlying tokens (native amount).
Internally calls JupLend `updateRate`, deposits through CPI, verifies minted fTokens, and
credits the user's marginfi position.
- `integration_withdraw(amount, withdraw_all)` (user, JupLend bank) - Withdraw underlying tokens
(native amount). Internally calls JupLend `updateRate`, burns fTokens via CPI, then transfers
tokens from the withdraw intermediary ATA to the user's destination token account.
- `lending_account_liquidate` or `start_liquidation` / `end_liquidation` (liquidators) - Liquidation
still uses the standard marginfi liquidation instructions. If seized collateral includes JupLend
assets, the liquidator/receiver claims those assets with `juplend_withdraw`.
assets, the liquidator/receiver claims those assets with `integration_withdraw`.
- Native JupLend `updateRate` (Lending program) - Used to refresh JupLend exchange rates before risk
checks. Must run before withdraw, borrow, etc for every bank involved, in the same tx.

Expand All @@ -75,8 +76,12 @@ integrations, wrapped Juplend banks do not earn interest through marginfi's inte
`asset_share_value`; yield is captured through Juplend's `token_exchange_price` (fToken appreciation
vs underlying).

Wrapped Juplend banks use dedicated deposit/withdraw instructions (`juplend_deposit`,
`juplend_withdraw`) and cannot be borrowed.
Wrapped Juplend banks use the shared integration deposit/withdraw interface
(`integration_deposit`, `integration_withdraw`) and cannot be borrowed.

If you previously built `juplend_deposit` or `juplend_withdraw`, migrate those user flows to the
shared integration interface and pack JupLend protocol accounts first in `remaining_accounts`
before any marginfi health/risk accounts.

For risk checks such as borrows and liquidations, remember that Juplend pricing depends on the
Lending state exchange rate. Include the correct remaining risk accounts and refresh with
Expand Down Expand Up @@ -111,8 +116,8 @@ Juplend risk accounts are the `bank, oracle, Lending` accounts, in that order.
- Rewards accrue over time but are **materialized on rate refresh** (`updateRate`).
- Rewards refresh whenever funds are updated (e.g. on deposit/withdraw/borrow/repay, etc), thus a
pool that hasn't had activity in some time may show stale rewards.
- In mrgn, `juplend_deposit` and `juplend_withdraw` already call `updateRate` internally, so those
user actions crank rewards automatically.
- In mrgn, the JupLend variants of `integration_deposit` and `integration_withdraw` already call
`updateRate` internally, so those user actions crank rewards automatically.
- For pure valuation/health freshness outside deposit/withdraw flows, include native Jup
`updateRate` in the same transaction before risk checks.
- Previewing earned rewards without cranking is somewhat complex, you may prefer to approximate:
Expand All @@ -125,8 +130,8 @@ Juplend risk accounts are the `bank, oracle, Lending` accounts, in that order.

- Users do **not** call a Juplend claim instruction through mrgn to receive rewards.
- Rewards appear as higher collateral valuation (e.g. visible after `healthPulse`).
- To realize rewards into wallet tokens, users withdraw (`juplend_withdraw`), which burns fTokens at
the newer exchange rate and returns more underlying.
- To realize rewards into wallet tokens, users withdraw with `integration_withdraw`, which burns
fTokens at the newer exchange rate and returns more underlying.

### Stopping / Changing Rewards (Juplend Admin)

Expand Down
29 changes: 22 additions & 7 deletions guides/DEVELOPERS_INTEGRATORS/KAMINO_INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ an obligation on that bank (caveat: the bank's `liquidity_vault_authority` actua
obligation under the hood). In simple terms, Mrgn banks are like any other user/depositor of the
Kamino platform, enjoying no special access or privileges.

Wrapped banks have their own deposit and withdraw instructions, unique to Kamino wrapped banks. They
can never borrow. Wrapped instructions are very similar to their mrgn equivalents, except they also
require you to pass a variety of Kamino-specific accounts tied to the reserve/obligation that the
bank uses.
Wrapped banks now use the shared `integration_deposit` and `integration_withdraw` interface. They
can never borrow. The Kamino variant still requires Kamino-specific reserve/obligation accounts,
but those accounts are now passed through `remaining_accounts` rather than a dedicated Kamino user
instruction.

Remember to refresh reserves and the bank's obligation before any deposit/withdrawal.

Expand Down Expand Up @@ -103,12 +103,27 @@ proportional method to be determined in the future.
If a user has a wrapped Kamino position, remember to refresh any reserves/obligations that are
involved before attempting to liquidate.

Liquidators may claim a wrapped Kamino position, which must be withdrawn using the separate
"withdraw" instruction that is applicable only to wrapped banks.
Liquidators may claim a wrapped Kamino position, which must be withdrawn using
`integration_withdraw` on the wrapped bank.

The withdraw instruction requires refreshing the reserve/obligation and consumes a significant
amount of CU: it is recommended to do this in a separate tx to the liquidation ix in most cases.

## Migration Notes

If you previously built `kamino_deposit` or `kamino_withdraw`, migrate to:

- `integration_deposit` for user deposits
- `integration_withdraw` for user withdrawals

For Kamino withdraws, `remaining_accounts` must be packed as:

1. Kamino protocol accounts
2. Marginfi health/risk accounts

Do not interleave them. The shared integration withdraw flow uses the protocol account count to
split `remaining_accounts` before running the post-withdraw health check.

## Token Amount Types by Instruction

| Instruction | Token Amount Type | Notes |
Expand All @@ -131,4 +146,4 @@ Integrating with another venue carries certain additional risks:
- **Account layout changes**: Changes to Reserve/Obligation structures can break deserialization and
validation
- **Liquidity constraints**: Insufficient reserve liquidity or withdrawal halts prevent users from
accessing funds despite marginfi allowing the operation
accessing funds despite marginfi allowing the operation
17 changes: 17 additions & 0 deletions guides/DEVELOPERS_INTEGRATORS/PACKING_RISK_ACCOUNTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@ remaining accounts.

In some instances, you must also pack other accounts into remaining accounts.

### Integration Withdrawals

For `integration_withdraw`, `remaining_accounts` has two contiguous sections:

1. protocol-specific integration accounts
2. normal marginfi health/risk accounts

This ordering is required. The program first consumes the protocol section based on the bank's
`asset_tag`, then interprets the rest as health accounts.

Examples:

- Kamino withdraw: `[kamino protocol accounts..., health accounts...]`
- Drift withdraw: `[drift protocol accounts..., health accounts...]`
- Solend withdraw: `[solend protocol accounts..., health accounts...]`
- JupLend withdraw: `[juplend protocol accounts..., health accounts...]`

### T22 Mints

If the Mint for a given Bank is Token22, pack it first, before all the risk accounts.
Expand Down
14 changes: 13 additions & 1 deletion programs/marginfi/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,15 @@ pub enum MarginfiError {
JuplendInitPositionDepositInsufficient, // 6511
#[msg("Invalid Juplend withdraw intermediary ATA")]
InvalidJuplendWithdrawIntermediaryAta, // 6512
// **************END JUPLEND ERRORS
// **************END JUPLEND ERRORS

// **************INTEGRATION ERRORS
#[msg("Unsupported integration protocol for this asset tag")]
UnsupportedIntegration = 600, // 6600
#[msg("Integration protocol account count mismatch")]
IntegrationAccountCountMismatch, // 6601
#[msg("Integration protocol account key mismatch")]
IntegrationAccountKeyMismatch, // 6602
}

impl From<MarginfiError> for ProgramError {
Expand Down Expand Up @@ -659,6 +667,10 @@ impl From<u32> for MarginfiError {
6511 => MarginfiError::JuplendInitPositionDepositInsufficient,
6512 => MarginfiError::InvalidJuplendWithdrawIntermediaryAta,

6600 => MarginfiError::UnsupportedIntegration,
6601 => MarginfiError::IntegrationAccountCountMismatch,
6602 => MarginfiError::IntegrationAccountKeyMismatch,

_ => MarginfiError::InternalLogicError,
}
}
Expand Down
Loading
Loading