URUOIdocs

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

CollateralLST mintRate source
JitoSOLJ1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPnJito stake pool Jito4APyf642JPZPx3hGc6WWJ8zPKtRbRs4P815Awbb
mSOLmSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7SoMarinade 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

EventFields
Syncedposition, swept, shares_out, rate_q64, debt_after
Borrowedposition, amount, debt_after
Repaidposition, amount, with_collateral, debt_after
Redeemedredeemer, amount
Unwoundposition, unwinder, amount, shares_out, debt_after

Index Synced to chart how much yield has paid each position.