Integration
How to call URUOI from your own code. The engine repository ships a TypeScript SDK (engine/sdk) with no Anchor dependency: PDA helpers, account decoders, the same fixed-point math the program uses, and one builder per instruction.
Addresses you need
| Collateral | LST mint | Rate source |
|---|---|---|
| JitoSOL | J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn | Jito stake pool Jito4APyf642JPZPx3hGc6WWJ8zPKtRbRs4P815Awbb |
| mSOL | mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So | Marinade state 8szGkuLTAux9XMgZ2vtY39jVSowEcpBfFfD8hXSEqdGC |
The URUOI program address is published here at deploy. Every other address is derived from it:
import { collateralPda, positionPda, vaultPda, usolMintPda, protocolPda } from "./sdk/src/index.js"
const JITOSOL = new PublicKey("J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn")
const JITO_POOL = new PublicKey("Jito4APyf642JPZPx3hGc6WWJ8zPKtRbRs4P815Awbb")
const collateral = collateralPda(JITOSOL) // ["collateral", lst_mint]
const position = positionPda(collateral, owner) // ["position", collateral, owner]
const vault = vaultPda(collateral) // ["vault", collateral]
const usol = usolMintPda() // ["usol_mint"]
Open, deposit and borrow in one transaction
import { Transaction } from "@solana/web3.js"
import { getAssociatedTokenAddressSync, TOKEN_2022_PROGRAM_ID } from "@solana/spl-token"
import { ix, usolMintPda } from "./sdk/src/index.js"
const lstAccount = getAssociatedTokenAddressSync(JITOSOL, owner)
const usolAccount = getAssociatedTokenAddressSync(usolMintPda(), owner, false, TOKEN_2022_PROGRAM_ID)
const tx = new Transaction().add(
ix.openPosition(owner, JITOSOL),
ix.deposit(owner, JITOSOL, JITO_POOL, lstAccount, 10_000_000_000n), // 10 JitoSOL
ix.borrow(owner, JITOSOL, JITO_POOL, usolAccount, 3_000_000_000n), // 3 uSOL
)
Create the uSOL associated token account first if it does not exist (Token-2022 program).
Read a position and project its payoff
import { decodePosition, readPoolRate, CollateralKind, value, ltvBps, projectRepayment } from "./sdk/src/index.js"
const pos = decodePosition((await connection.getAccountInfo(position))!.data)
const pool = readPoolRate(CollateralKind.SplStakePool, (await connection.getAccountInfo(JITO_POOL))!.data, BigInt(epoch))
const sol = value(pos.shares, pool.rate) // lamports
const ltv = ltvBps(pos.debt, sol) / 100 // percent
const p = projectRepayment({
shares: pos.shares,
debt: pos.debt,
rate: pool.rate,
growthPerEpoch: 0.0001723775523947424, // measured, JitoSOL, epoch 1050
epochSeconds: 115_505,
})
// p.epochs: epochs until the debt is zero; p.debtByEpoch: the debt after each epoch
projectRepayment runs the program's own sweep epoch by epoch, and the engine's tests check it against the program to within one epoch.
Run a keeper
sync needs no signer. A keeper that walks every position once per epoch keeps each debt current:
import { decodeCollateral, decodePosition, ix, PROGRAM_ID } from "./sdk/src/index.js"
// a Position account is 105 bytes: 8 discriminator + owner 32 + collateral 32 + shares 8 + debt 8 + snapshot 16 + bump 1
const positions = await connection.getProgramAccounts(PROGRAM_ID, { filters: [{ dataSize: 105 }] })
for (const { account } of positions) {
const pos = decodePosition(account.data)
if (pos.debt === 0n) continue
const col = decodeCollateral((await connection.getAccountInfo(pos.collateral))!.data)
await send(new Transaction().add(ix.sync(pos.owner, col.lstMint, col.rateSource))) // send: your own sign-and-send helper
}
Syncing is never required for correctness. Any instruction on a position settles it first, and a sync after a long gap sweeps the whole gap at once.
Redeem uSOL
redeem must list every collateral, in index order, four accounts each:
const tx = new Transaction().add(
ix.redeem(holder, usolAccount, [
{ lstMint: JITOSOL, rateSource: JITO_POOL, destination: holderJitoAccount },
{ lstMint: MSOL, rateSource: MARINADE_STATE, destination: holderMsolAccount },
], 1_000_000_000n), // 1 uSOL
)
Read each collateral's index from its account. A wrong order fails with CollateralAccountsMismatch.
Events
| Event | Fields |
|---|---|
Synced | position, swept, shares_out, rate_q64, debt_after |
Borrowed | position, amount, debt_after |
Repaid | position, amount, with_collateral, debt_after |
Redeemed | redeemer, amount |
Unwound | position, unwinder, amount, shares_out, debt_after |
Index Synced to chart how much yield has paid each position.