UNPKG

@wuwei-labs/srsly

Version:
245 lines 11.9 kB
"use strict"; /** * @purpose Simplified acceptRental instruction wrapper * * Thin convenience wrapper around Codama-generated acceptRental instruction. * Accepts a rental contract and begins the rental period. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.acceptRental = acceptRental; 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 fleet_1 = require("../accounts/fleet"); const contract_1 = require("../accounts/contract"); const config_2 = require("../accounts/config"); const rental_1 = require("../accounts/rental"); const borrower_1 = require("../accounts/borrower"); 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 types_1 = require("../generated/codama/types"); const discountAuth_1 = require("../utils/discountAuth"); /** * Activate a rental: transfer fleet control to the borrower for the * chosen duration and pay the first period's rate. * * @param params - Rental acceptance parameters * @param config - Optional SDK configuration overrides * @returns Array of instructions (Kit format or web3.js if PublicKey in config) * * @example * ```typescript * const ixs = await acceptRental({ * borrower: wallet, * borrowerProfile: "BORROWER_PROFILE_ADDRESS", * contract: "CONTRACT_ADDRESS", * duration: { days: 7 }, * computeUnits: 400_000 * }); * ``` */ async function acceptRental(params, config) { // Merge config const finalConfig = (0, config_1.mergeConfig)(config); // Validate required params if (!params.borrower) { throw new Error('borrower is required'); } if (!params.contract) { throw new Error('contract is required'); } if (!params.duration) { throw new Error('duration is required'); } if (!params.borrowerProfile) { throw new Error('borrowerProfile is required'); } // acceptRental issues several CPIs (SAGE rental_open, SLYvault deposit, // optional discount verify, optional thread + queued-rental cleanup), // so the 200k default budget reliably runs out. Force the caller to // size it explicitly. if (!params.computeUnits || params.computeUnits < 400_000) { throw new Error(`computeUnits must be at least 400,000 for acceptRental (got ${params.computeUnits ?? 'undefined'}).`); } // Convert borrower to transaction signer const borrowerSigner = (0, signer_1.toTransactionSigner)(params.borrower); // Fetch config const configAccount = await (0, config_2.fetchConfig)(finalConfig.rpcUrl); // Fetch contract to get fleet, gameId const contractAccount = await (0, contract_1.fetchContract)(params.contract, finalConfig.rpcUrl); const fleet = contractAccount.data.fleet; const gameId = contractAccount.data.gameId; // Client-side rate safety check if (params.rate != null) { const expectedRate = (0, amount_1.convertAmount)(params.rate); const contractRate = BigInt(contractAccount.data.rate); if (expectedRate !== contractRate) { throw new Error(`Contract rate mismatch: expected ${expectedRate} stardust but contract has ${contractRate} stardust. The rate may have changed since you last fetched it.`); } } // Fetch fleet data for faction (needed to derive SAGE accounts) const fleetState = await (0, fleet_1.fetchFleet)(fleet, finalConfig.rpcUrl); const faction = fleetState.faction; const borrowerProfile = (0, kit_1.address)(params.borrowerProfile); // Derive all SAGE game accounts (profile faction, starbase, starbase player) const gameAccounts = await (0, sage_1.deriveGameAccounts)(borrowerProfile, faction, gameId); // Derive active rental PDA (no more slot logic) const rentalState = await (0, srsly_1.deriveActiveRental)(params.contract); // Resolve expiredBorrowerState: use param if provided, otherwise auto-detect // When the same borrower re-rents their own expired contract, skip passing // expiredBorrowerState to avoid Anchor's duplicate mutable account error. const borrowerStatePda = await (0, srsly_1.deriveBorrowerState)(borrowerSigner); let expiredBorrowerState; let activeExpired = false; let expiredReferrer; let expiredDiscountBps = 0; let expiredBorrowerManagedTA; if (params.expiredBorrowerState) { const explicit = (0, kit_1.address)(params.expiredBorrowerState); // Don't pass if it matches the current borrower's state (same-borrower re-rent) if (explicit !== borrowerStatePda) { expiredBorrowerState = explicit; } } try { const activeRental = await (0, rental_1.fetchRental)(rentalState, finalConfig.rpcUrl); if (activeRental.data.status !== types_1.RentalStatus.Available) { // Check if expired (end_time in the past) const now = Math.floor(Date.now() / 1000); if (now >= Number(activeRental.data.endTime)) { activeExpired = true; expiredReferrer = (0, kit_1.isOption)(activeRental.data.referrer) ? ((0, kit_1.unwrapOption)(activeRental.data.referrer) ?? undefined) : undefined; expiredDiscountBps = activeRental.data.discountBps; // Fetch expired borrower's managed ATA for discount refund if (expiredDiscountBps > 0 && expiredReferrer) { try { const expiredBorrowerAccount = await (0, borrower_1.fetchBorrower)(activeRental.data.borrowerState, finalConfig.rpcUrl); expiredBorrowerManagedTA = expiredBorrowerAccount.data.managedTokenAccount; } catch { // Can't fetch — skip discount refund account } } } // Auto-detect expiredBorrowerState if not explicitly provided if (!params.expiredBorrowerState) { if (activeRental.data.borrowerState !== borrowerStatePda) { expiredBorrowerState = activeRental.data.borrowerState; } } } } catch { // Rental state doesn't exist or can't be fetched — no expired rental to replace } // Derive rental authority PDA const rentalAuthority = await (0, srsly_1.deriveRentalAuthority)(); // Derive contract thread and close_rental fiber (fiber index 1) — only if thread configured const thread = (0, kit_1.address)(contractAccount.data.thread); const hasThread = thread !== '11111111111111111111111111111111'; const contractThread = hasThread ? await (0, thread_1.deriveContractThread)(params.contract, rentalAuthority) : undefined; const closeRentalFiber = hasThread && contractThread ? await (0, thread_1.deriveFiber)(contractThread, 1) : undefined; // Convert duration parameter const durationSeconds = (0, duration_1.convertDuration)(params.duration); // Get network-specific addresses (atlasMint sourced from on-chain config) const atlasMint = configAccount.data.atlasMint; const addresses = (0, config_1.getAddresses)(config); const sageProgram = (0, kit_1.address)(addresses.sage); const programAddress = (0, kit_1.address)(addresses.srsly); const referrer = params.referrer ? (0, kit_1.address)(params.referrer) : undefined; // 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 vault accounts for fee flush const vaultAddress = (0, kit_1.address)(configAccount.data.slyvault); const vaultTokenAccount = await (0, token_1.deriveAssociatedTokenAccount)(vaultAddress, atlasMint); // Derive borrower token account (ATA) const borrowerTokenAccount = await (0, token_1.deriveAssociatedTokenAccount)(borrowerSigner.address, atlasMint); // Resolve queued rental accounts (named optional accounts) const SYSTEM_PROGRAM_ID = '11111111111111111111111111111111'; const queuedRentalKey = contractAccount.data.queuedRental; let queuedBorrowerProfile; let queuedBorrowerProfileFaction; if (queuedRentalKey !== SYSTEM_PROGRAM_ID) { const queuedRentalPda = await (0, srsly_1.deriveQueuedRental)(params.contract); try { const queuedRentalState = await (0, rental_1.fetchRental)(queuedRentalPda, finalConfig.rpcUrl); if (queuedRentalState.data.status === types_1.RentalStatus.Queued) { queuedBorrowerProfile = queuedRentalState.data.borrowerProfile; queuedBorrowerProfileFaction = await (0, sage_1.deriveProfileFaction)(queuedBorrowerProfile); } } catch { // Queued rental can't be fetched — skip } } // Call Codama-generated acceptRental instruction const acceptRentalIx = await (0, instructions_1.getAcceptRentalInstructionAsync)({ borrower: borrowerSigner, borrowerTokenAccount, fleet: (0, kit_1.address)(fleet), expiredBorrowerState, contract: contractAddress, contractTokenAccount, vault: vaultAddress, vaultTokenAccount, ...(contractThread && { contractThread }), ...(closeRentalFiber && { closeRentalFiber }), referrer, // Named optional accounts for expired fee flush ...(activeExpired && expiredReferrer && { expiredReferrer }), ...(activeExpired && expiredDiscountBps > 0 && expiredBorrowerManagedTA && { expiredBorrowerTokenAccount: expiredBorrowerManagedTA, }), // Named optional accounts for queued activation ...(queuedBorrowerProfile && { queuedBorrowerProfile }), ...(queuedBorrowerProfileFaction && { queuedBorrowerProfileFaction }), sageProgram, duration: BigInt(durationSeconds), ...(params.discountAuth ? (() => { const d = (0, discountAuth_1.deserializeDiscountAuth)(params.discountAuth); return { discountSignature: d.discountSignature, discountMemberNonce: d.discountMemberNonce, discountBps: d.discountBps, discountExpiresAt: d.discountExpiresAt, }; })() : { discountSignature: null, discountMemberNonce: null, discountBps: null, discountExpiresAt: null, }), }, { programAddress }); // Append SAGE base accounts as remaining accounts [0..4] const sageRemainingAccounts = [ { address: borrowerProfile, role: kit_1.AccountRole.READONLY }, { address: gameAccounts.profileFaction, role: kit_1.AccountRole.READONLY }, { address: (0, kit_1.address)(gameId), role: kit_1.AccountRole.READONLY }, { address: gameAccounts.starbase, role: kit_1.AccountRole.READONLY }, { address: gameAccounts.starbasePlayer, role: kit_1.AccountRole.WRITABLE }, ]; acceptRentalIx.accounts.push(...sageRemainingAccounts); // Prepare instruction with optional compute budget return (0, instructions_2.prepareInstructions)(acceptRentalIx, { computeUnits: params.computeUnits, PublicKey: finalConfig.PublicKey, }); } //# sourceMappingURL=acceptRental.js.map