@wuwei-labs/srsly
Version:
TypeScript SDK for SRSLY
117 lines • 5.02 kB
JavaScript
/**
* @purpose SOL + ATLAS cost estimators for SRSLY protocol usage.
*
* Three quotes:
* - Owner setup (contract_create + contract_thread) — recoverable rents + infra + exec budget.
* - Borrower setup (borrower_create) — BorrowerState + managed ATA.
* - Per-rental (rental_accept) — SOL tx fee + ATLAS escrow + service fee.
*
* All functions use `rpc.getMinimumBalanceForRentExemption` so quotes reflect the
* target cluster's rent rate rather than hardcoded lamports.
*/
import { fetchContract } from '../accounts/contract';
import { fetchConfig } from '../accounts/config';
import { getRpcUrl } from '../utils/config';
import { createRpc } from '../utils/rpc';
import { convertSol } from '../params/sol';
import { CONTRACT_STATE_SIZE, RENTAL_STATE_SIZE, BORROWER_STATE_SIZE } from './sizes';
import { MINIMUM_THREAD_FUNDING, DEFAULT_EXECUTION_BUDGET, TX_BASE_FEE_LAMPORTS, PER_TICK_LAMPORTS, SECONDS_PER_DAY, SPL_ATA_SIZE, } from './constants';
async function minBalance(rpcUrl, size) {
const rpc = createRpc(rpcUrl ?? getRpcUrl());
const response = await rpc.getMinimumBalanceForRentExemption(BigInt(size)).send();
return BigInt(response);
}
export async function estimateOwnerSetupCost(opts = {}) {
const executionBudget = opts.executionBudget === undefined
? DEFAULT_EXECUTION_BUDGET
: convertSol(opts.executionBudget);
const [contractState, rentalState, ataRent] = await Promise.all([
minBalance(opts.rpcUrl, CONTRACT_STATE_SIZE),
minBalance(opts.rpcUrl, RENTAL_STATE_SIZE),
minBalance(opts.rpcUrl, SPL_ATA_SIZE),
]);
const ownerAta = opts.ownerAtaExists ? 0n : ataRent;
const rentLamports = contractState + rentalState + rentalState + ataRent + ownerAta;
const threadLamports = MINIMUM_THREAD_FUNDING + executionBudget;
const txFees = TX_BASE_FEE_LAMPORTS * 2n; // contract_create + contract_thread
return {
rentLamports,
threadLamports,
txFees,
total: rentLamports + threadLamports + txFees,
breakdown: {
contractState,
rentalStateActive: rentalState,
rentalStateQueued: rentalState,
contractAta: ataRent,
ownerAta,
threadInfraBase: MINIMUM_THREAD_FUNDING,
executionBudget,
},
};
}
export async function estimateBorrowerSetupCost(rpcUrl) {
const [borrowerState, managedAta] = await Promise.all([
minBalance(rpcUrl, BORROWER_STATE_SIZE),
minBalance(rpcUrl, SPL_ATA_SIZE),
]);
const rentLamports = borrowerState + managedAta;
const txFees = TX_BASE_FEE_LAMPORTS;
return {
rentLamports,
txFees,
total: rentLamports + txFees,
breakdown: { borrowerState, managedAta },
};
}
/**
* Quote the ATLAS cost a borrower will pay for a given rate + duration.
* Pure helper — does not build a transaction.
*/
export async function estimateRentalCost(params) {
const [{ data: contract }, { data: config }] = await Promise.all([
fetchContract(params.contract, params.rpcUrl),
fetchConfig(params.rpcUrl),
]);
const duration = BigInt(params.durationSeconds);
const stardustToAtlas = BigInt(config.stardustToAtlas);
const rate = BigInt(contract.rate);
// Mirror on-chain scaling: rate <1 ATLAS is treated as ATLAS-denominated.
const rateStardust = rate < stardustToAtlas ? saturatingMul(rate, stardustToAtlas) : rate;
const atlasEscrow = saturatingDiv(saturatingMul(rateStardust, duration), SECONDS_PER_DAY);
const feeBps = BigInt(config.serviceFeeBps);
const baseBps = BigInt(config.baseBps);
const atlasServiceFee = baseBps === 0n ? 0n : saturatingDiv(saturatingMul(atlasEscrow, feeBps), baseBps);
const atlasBid = params.bidStardust ?? 0n;
return {
solTxFees: TX_BASE_FEE_LAMPORTS,
atlasEscrow,
atlasServiceFee,
atlasBid,
atlasTotal: atlasEscrow + atlasBid,
serviceFeeBps: Number(feeBps),
};
}
/* -------------------------------------------------------------------------- */
/* Thread burn */
/* -------------------------------------------------------------------------- */
/**
* Lamports burned by a given number of cron ticks at `PER_TICK_LAMPORTS` each.
* Useful for sizing a custom `executionBudget` before calling
* `estimateOwnerSetupCost`.
*/
export function tickBudget(ticks) {
return BigInt(ticks) * PER_TICK_LAMPORTS;
}
/* -------------------------------------------------------------------------- */
/* u64 helpers */
/* -------------------------------------------------------------------------- */
const MAX_U64 = 18446744073709551615n;
function saturatingMul(a, b) {
const product = a * b;
return product > MAX_U64 ? MAX_U64 : product;
}
function saturatingDiv(a, b) {
return b === 0n ? 0n : a / b;
}
//# sourceMappingURL=estimate.js.map