@wuwei-labs/srsly
Version:
TypeScript SDK for SRSLY
174 lines • 8.24 kB
JavaScript
"use strict";
/**
* @purpose Simplified reserveRental instruction wrapper
*
* Thin convenience wrapper around Codama-generated reserveRental instruction.
* Reserves a rental contract using ATLAS for a GBM-style auction.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.reserveRental = reserveRental;
const kit_1 = require("@solana/kit");
const instructions_1 = require("../generated/codama/instructions");
const config_1 = require("../utils/config");
const signer_1 = require("../utils/signer");
const amount_1 = require("../params/amount");
const duration_1 = require("../params/duration");
const contract_1 = require("../accounts/contract");
const config_2 = require("../accounts/config");
const fleet_1 = require("../accounts/fleet");
const borrower_1 = require("../accounts/borrower");
const rental_1 = require("../accounts/rental");
const types_1 = require("../generated/codama/types");
const sage_1 = require("../pda/sage");
const srsly_1 = require("../pda/srsly");
const token_1 = require("../pda/token");
const thread_1 = require("../pda/thread");
const instructions_2 = require("../utils/instructions");
const bidMath_1 = require("../utils/bidMath");
/**
* Bid on a contract via the GBM-style reservation auction. The winner
* gets an exclusive window to accept once the current rental ends.
*
* @param params - Reservation parameters
* @param config - Optional SDK configuration overrides
* @returns Array of instructions (Kit format or web3.js if PublicKey in config)
*
* @example
* ```typescript
* // Reserve with 5 ATLAS for a 7-day rental
* const ixs = await reserveRental({
* challenger: wallet,
* contract: "CONTRACT_ADDRESS",
* amount: 5,
* duration: { days: 7 },
* computeUnits: 200_000
* });
* ```
*/
async function reserveRental(params, config) {
// Merge config
const finalConfig = (0, config_1.mergeConfig)(config);
// Validate required params
if (!params.challenger) {
throw new Error('challenger is required');
}
if (!params.contract) {
throw new Error('contract is required');
}
if (!params.challengerProfile) {
throw new Error('challengerProfile is required for reserveRental');
}
if (!params.duration) {
throw new Error('duration is required');
}
const bidPoints = params.bidPoints ?? 0;
let bidAtlasValue = params.bidAtlas !== undefined ? (0, amount_1.convertAmount)(params.bidAtlas) : 0n;
const userProvidedBid = params.bidPoints !== undefined || params.bidAtlas !== undefined;
if (bidPoints > 0 && bidAtlasValue > 0n) {
throw new Error('bidPoints and bidAtlas are mutually exclusive');
}
// Auto-fill the ATLAS bid when the caller omitted both bid params AND
// there is an existing defender to outbid. First reservations allow
// zero-bid, so we leave those untouched.
const autoMinBid = params.autoMinBid ?? true;
if (autoMinBid && !userProvidedBid) {
const floor = await (0, bidMath_1.getMinimumBid)({
contractAddress: params.contract,
rpcUrl: config?.rpcUrl,
});
if (floor.defenderBidStardust > 0n) {
bidAtlasValue = floor.minBidStardust;
}
}
// Convert challenger to transaction signer
const challengerSigner = (0, signer_1.toTransactionSigner)(params.challenger);
// Get network-specific addresses
const addresses = (0, config_1.getAddresses)(config);
const programAddress = (0, kit_1.address)(addresses.srsly);
const sageProgram = (0, kit_1.address)(addresses.sage);
// Fetch config for mint
const configAccount = await (0, config_2.fetchConfig)(finalConfig.rpcUrl);
const atlasMint = configAccount.data.atlasMint;
// Fetch contract to get contractTokenAccount
const contractAccount = await (0, contract_1.fetchContract)(params.contract, finalConfig.rpcUrl);
// Derive rental PDAs
const activeRental = await (0, srsly_1.deriveActiveRental)(params.contract);
const rentalState = await (0, srsly_1.deriveQueuedRental)(params.contract);
// Derive rental authority
const rentalAuthority = await (0, srsly_1.deriveRentalAuthority)();
// Derive contract thread and close_rental fiber (fiber index 1)
const contractThread = await (0, thread_1.deriveContractThread)(params.contract, rentalAuthority);
const closeRentalFiber = await (0, thread_1.deriveFiber)(contractThread, 1);
// Derive contract token account (ATA of contract PDA)
const contractAddress = (0, kit_1.address)(params.contract);
const contractTokenAccount = await (0, token_1.deriveAssociatedTokenAccount)(contractAddress, atlasMint);
// Derive challenger's token account
const challengerTokenAccount = await (0, token_1.deriveAssociatedTokenAccount)(challengerSigner.address, atlasMint);
// Convert duration to seconds
const durationSeconds = (0, duration_1.convertDuration)(params.duration);
const referrer = params.referrer
? (0, kit_1.address)(params.referrer)
: undefined;
// Build instruction input
const instructionInput = {
challenger: challengerSigner,
contract: contractAddress,
contractThread,
closeRentalFiber,
contractTokenAccount,
activeRental,
challengerTokenAccount,
rentalState,
referrer,
sageProgram,
bidPoints,
bidAtlas: bidAtlasValue,
duration: BigInt(durationSeconds),
};
// If there's a current queued reservation, provide defender accounts
// Fetch queued rental to check if there's an existing reservation holder
try {
const queuedRental = await (0, rental_1.fetchRental)(rentalState, finalConfig.rpcUrl);
if (queuedRental.data.status === types_1.RentalStatus.Queued) {
const defenderBorrowerState = queuedRental.data.borrowerState;
const challengerBorrowerState = await (0, srsly_1.deriveBorrowerState)(challengerSigner);
if (defenderBorrowerState === challengerBorrowerState) {
// Self-contest: challenger is the current defender.
// Program reuses challenger_state, but we still need defender_managed_token_account
// for the ATLAS refund path.
const challengerAccount = await (0, borrower_1.fetchBorrower)(challengerBorrowerState, finalConfig.rpcUrl);
instructionInput.defenderManagedTokenAccount = challengerAccount.data.managedTokenAccount;
}
else {
instructionInput.defenderState = defenderBorrowerState;
const defenderAccount = await (0, borrower_1.fetchBorrower)(defenderBorrowerState, finalConfig.rpcUrl);
instructionInput.defenderManagedTokenAccount = defenderAccount.data.managedTokenAccount;
}
}
}
catch {
// Queued rental doesn't exist or is Available — no defender
}
// Derive SAGE accounts for remaining_accounts
const challengerProfileAddr = (0, kit_1.address)(params.challengerProfile);
const fleetState = await (0, fleet_1.fetchFleet)(contractAccount.data.fleet, finalConfig.rpcUrl);
const gameAccounts = await (0, sage_1.deriveGameAccounts)(challengerProfileAddr, fleetState.faction, contractAccount.data.gameId);
// Call Codama-generated instruction
const kitInstruction = await (0, instructions_1.getReserveRentalInstructionAsync)(instructionInput, {
programAddress,
});
// Append SAGE accounts as remaining_accounts
const sageRemainingAccounts = [
{ address: challengerProfileAddr, role: kit_1.AccountRole.READONLY },
{ address: gameAccounts.profileFaction, role: kit_1.AccountRole.READONLY },
{ address: gameAccounts.starbase, role: kit_1.AccountRole.READONLY },
{ address: gameAccounts.starbasePlayer, role: kit_1.AccountRole.READONLY },
];
kitInstruction.accounts.push(...sageRemainingAccounts);
// Prepare instructions with optional compute budget
return (0, instructions_2.prepareInstructions)(kitInstruction, {
computeUnits: params.computeUnits,
PublicKey: finalConfig.PublicKey,
});
}
//# sourceMappingURL=reserveRental.js.map