UNPKG

@wuwei-labs/srsly

Version:
235 lines 10.5 kB
"use strict"; /** * @purpose Simplified closeContract instruction wrapper * * Thin convenience wrapper around Codama-generated closeContract instruction. * Closes a contract with no active rentals and claims remaining payments. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.closeContract = closeContract; 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 contract_1 = require("../accounts/contract"); const config_2 = require("../accounts/config"); const rental_1 = require("../accounts/rental"); const thread_1 = require("../accounts/thread"); const token_1 = require("../pda/token"); const srsly_1 = require("../pda/srsly"); const thread_2 = require("../pda/thread"); const instructions_2 = require("../utils/instructions"); /** * Close a rental contract and refund rent to the owner. Idempotent — * recovers cleanly from partial prior closes. When no rental is active, * the contract's automation thread is closed in the same transaction. * * @param params - Contract closing parameters * @param config - Optional SDK configuration overrides * @returns Kit instruction (or web3.js if PublicKey in config) * * @example * ```typescript * // Option 1: Provide fleet only (RECOMMENDED - idempotent) * const ix = await closeContract({ * owner: wallet, * fleet: fleetAddress * }); * * // Option 2: Provide contract only (contract MUST exist) * const ix2 = await closeContract({ * owner: wallet, * contract: contractAddress * }); * * // Option 3: Provide both (most explicit, no fetching) * const ix3 = await closeContract({ * owner: wallet, * fleet: fleetAddress, * contract: contractAddress * }); * ``` */ async function closeContract(params, config) { // Merge config const finalConfig = (0, config_1.mergeConfig)(config); // Validate required params if (!params.owner) { throw new Error('owner is required'); } if (!params.fleet && !params.contract) { throw new Error('Either fleet or contract must be provided'); } // Convert owner to transaction signer const ownerSigner = (0, signer_1.toTransactionSigner)(params.owner); // Get atlasMint from on-chain config const configAccount = await (0, config_2.fetchConfig)(finalConfig.rpcUrl); const atlasMint = configAccount.data.atlasMint; const addresses = (0, config_1.getAddresses)(config); const programAddress = (0, kit_1.address)(addresses.srsly); // Resolve fleet and contract addresses let fleetAddress; let contractAddress; if (params.fleet && params.contract) { // Both provided - use as-is fleetAddress = (0, kit_1.address)(params.fleet); contractAddress = (0, kit_1.address)(params.contract); } else if (params.fleet) { // Only fleet provided - derive contract from fleet PDA fleetAddress = (0, kit_1.address)(params.fleet); contractAddress = await (0, srsly_1.deriveContract)(fleetAddress, programAddress); } else if (params.contract) { // Only contract provided - fetch contract to get fleet contractAddress = (0, kit_1.address)(params.contract); } else { // Should never reach here due to validation above throw new Error('Either fleet or contract must be provided'); } // Always fetch contract — needed for fleet (if not provided) and thread check const contractAccount = await (0, contract_1.fetchContract)(contractAddress, finalConfig.rpcUrl); if (!params.fleet) { fleetAddress = contractAccount.data.fleet; } // Get sageProgram from config if not provided const sageProgram = params.sageProgram ? (0, kit_1.address)(params.sageProgram) : (0, kit_1.address)(addresses.sage); // Derive contract token account (ATA of contract PDA for ATLAS) const contractTokenAccount = await (0, token_1.deriveAssociatedTokenAccount)(contractAddress, atlasMint); // Check contract state const SYSTEM_PROGRAM_ID = '11111111111111111111111111111111'; const hasThread = contractAccount.data.thread !== SYSTEM_PROGRAM_ID; const hasActiveSlot = contractAccount.data.activeRental !== SYSTEM_PROGRAM_ID; const hasQueuedSlot = contractAccount.data.queuedRental !== SYSTEM_PROGRAM_ID; // Check if active rental is expired let activeExpired = false; if (hasActiveSlot) { const activeRental = await (0, rental_1.fetchRental)(contractAccount.data.activeRental, finalConfig.rpcUrl); const now = Math.floor(Date.now() / 1000); if (activeRental.data.endTime <= BigInt(now)) { activeExpired = true; } } // Resolve queued borrower accounts when a queued rental exists let queuedBorrowerState; let queuedBorrowerTokenAccount; if (hasQueuedSlot) { let queuedBorrower; if (params.queuedBorrower) { queuedBorrower = (0, kit_1.address)(params.queuedBorrower); } else { const queuedRental = await (0, rental_1.fetchRental)(contractAccount.data.queuedRental, finalConfig.rpcUrl); queuedBorrower = queuedRental.data.borrower; } queuedBorrowerState = await (0, srsly_1.deriveBorrowerState)(queuedBorrower); queuedBorrowerTokenAccount = await (0, token_1.deriveAssociatedTokenAccount)(queuedBorrower, atlasMint); } // Helper: build a closeContractThread instruction const buildThreadCloseIx = async () => { const rentalAuthority = await (0, srsly_1.deriveRentalAuthority)(); const contractThread = await (0, thread_2.deriveContractThread)(contractAddress, rentalAuthority); const threadAccount = await (0, thread_1.fetchThreadByAddress)(contractThread, finalConfig.rpcUrl); const fiberIds = Array.from(threadAccount.data.fiberIds); const fiberAccounts = await Promise.all(fiberIds.map(async (id) => ({ address: await (0, thread_2.deriveFiber)(contractThread, id), role: kit_1.AccountRole.WRITABLE, }))); const ix = await (0, instructions_1.getCloseContractThreadInstructionAsync)({ owner: ownerSigner, contract: contractAddress, thread: contractThread }, { programAddress }); ix.accounts.push(...fiberAccounts); return ix; }; // Helper: build a closeContract instruction with optional queued borrower accounts const buildCloseContractIx = async (includeQueued) => { return (0, instructions_1.getCloseContractInstructionAsync)({ owner: ownerSigner, contract: contractAddress, mint: atlasMint, fleet: fleetAddress, contractTokenAccount: contractTokenAccount, sageProgram, ...(includeQueued && queuedBorrowerState && queuedBorrowerTokenAccount ? { queuedBorrowerState, queuedBorrowerTokenAccount } : {}), }, { programAddress }); }; // Compose instructions based on scenario const ixs = []; if (activeExpired && hasQueuedSlot) { // Expired active + queued: multi-instruction composition // 1. closeRental — settles expired active const { closeRental } = await Promise.resolve().then(() => __importStar(require('./closeRental'))); const closeRentalResult = await closeRental({ payer: params.owner, contract: contractAddress }, config); ixs.push(...closeRentalResult.instructions); // 2. closeContractThread (if thread exists) if (hasThread) { ixs.push(await buildThreadCloseIx()); } // 3. closeContract — force-closes queued, sets to_close ixs.push(await buildCloseContractIx(true)); // 4. closeContract — final close (no active, no queued, no thread) ixs.push(await buildCloseContractIx(false)); } else if (activeExpired && !hasQueuedSlot) { // Expired active only, no queued — settle then close const { closeRental } = await Promise.resolve().then(() => __importStar(require('./closeRental'))); const closeRentalResult = await closeRental({ payer: params.owner, contract: contractAddress }, config); ixs.push(...closeRentalResult.instructions); if (hasThread) { ixs.push(await buildThreadCloseIx()); } ixs.push(await buildCloseContractIx(false)); } else if (!activeExpired && hasQueuedSlot) { // Non-expired active + queued — deferred close with queued borrower accounts ixs.push(await buildCloseContractIx(true)); } else { // No active rental (or no rental at all) — simple close if (hasThread) { ixs.push(await buildThreadCloseIx()); } ixs.push(await buildCloseContractIx(false)); } return (0, instructions_2.prepareInstructions)(ixs, { computeUnits: params.computeUnits, PublicKey: finalConfig.PublicKey, }); } //# sourceMappingURL=closeContract.js.map