@wuwei-labs/srsly
Version:
TypeScript SDK for SRSLY
143 lines • 6.54 kB
JavaScript
/**
* @purpose Simplified createContract instruction wrapper
*
* Thin convenience wrapper around Codama-generated createContract instruction.
* Handles parameter conversion and optional web3.js compatibility.
*/
import { address } from '@solana/kit';
import { getCreateContractInstructionAsync, getCreateContractThreadInstructionAsync, } from '../generated/codama/instructions';
import { mergeConfig, getAddresses } from '../utils/config';
import { toTransactionSigner, getSignerAddress } from '../utils/signer';
import { convertAmount, stardustToAtlas } from '../params/amount';
import { convertDuration } from '../params/duration';
import { normalizeSchedule } from '../params/schedule';
import { convertSol } from '../params/sol';
import { fetchFleet } from '../accounts/fleet';
import { fetchConfig } from '../accounts/config';
import { prepareInstructions } from '../utils/instructions';
import { deriveContract, deriveRentalAuthority } from '../pda/srsly';
import { deriveContractThread, deriveFiber } from '../pda/thread';
import { deriveAssociatedTokenAccount } from '../pda/token';
/**
* List a Star Atlas fleet for rent. Sets the per-period rate, allowed
* duration window, fee, and (optionally) automation parameters.
*
* @param params - Contract creation parameters
* @param config - Optional SDK configuration overrides
* @returns Kit instruction (or web3.js if PublicKey in config)
*
* @example
* ```typescript
* // With global config - auto-fetch ownerProfile and gameId from fleet
* setSdkConfig({ network: 'atlasnet' });
* const ix = await createContract({
* owner: wallet,
* fleet: fleetAddress,
* rate: 100,
* durationMax: { days: 30 }
* });
* ```
*/
export async function createContract(params, config) {
// Merge config
const finalConfig = mergeConfig(config);
// Validate required params
if (!params.owner) {
throw new Error('owner is required');
}
if (!params.fleet) {
throw new Error('fleet is required');
}
if (!params.rate) {
throw new Error('rate is required');
}
if (!params.durationMax) {
throw new Error('durationMax is required');
}
// Convert owner to transaction signer
const ownerSigner = toTransactionSigner(params.owner);
// Fetch fleet data (source of truth for ownerProfile and gameId)
const fleetState = await fetchFleet(params.fleet, finalConfig.rpcUrl);
const ownerProfile = fleetState.ownerProfile;
const gameId = fleetState.gameId;
// Convert parameters
const rateStardust = convertAmount(params.rate);
// Validate minimum rate (1 ATLAS = 100_000_000 stardust)
const MIN_RATE_STARDUST = 100000000n;
if (rateStardust < MIN_RATE_STARDUST) {
throw new Error(`Rate too low. Minimum rate is 1 ATLAS (100,000,000 stardust). ` +
`Got: ${rateStardust} stardust (${stardustToAtlas(rateStardust)} ATLAS)`);
}
const durationMinSeconds = params.durationMin ? convertDuration(params.durationMin) : 3600; // Default 1 hour
const durationMaxSeconds = convertDuration(params.durationMax);
// Validate duration logic
if (durationMaxSeconds < durationMinSeconds) {
throw new Error(`durationMax (${durationMaxSeconds}s) must be >= durationMin (${durationMinSeconds}s)`);
}
// Get atlasMint from on-chain config
const configAccount = await fetchConfig(finalConfig.rpcUrl);
const atlasMint = configAccount.data.atlasMint;
const addresses = getAddresses(config);
const sageProgram = address(addresses.sage);
const programAddress = address(addresses.srsly);
// Convert cancelDelayMin duration to seconds. Default mirrors
// `durationMin` so a borrower can never cancel out of a rental
// earlier than the contract's own minimum lease window — the owner
// is guaranteed at least one full minimum-duration of revenue.
const cancelDelayMinSeconds = params.cancelDelayMin !== undefined
? BigInt(convertDuration(params.cancelDelayMin))
: BigInt(durationMinSeconds);
// Call Codama-generated instruction
const kitInstruction = await getCreateContractInstructionAsync({
owner: ownerSigner,
fleet: address(params.fleet),
ownerProfile: address(ownerProfile),
gameId: address(gameId),
mint: atlasMint,
sageProgram,
rate: rateStardust,
durationMin: BigInt(durationMinSeconds),
durationMax: BigInt(durationMaxSeconds),
ownerKeyIndex: params.ownerKeyIndex ?? 0,
cancelDelayMin: cancelDelayMinSeconds,
reservationsDisabled: params.reservationsDisabled ?? true,
contestedThreshold: params.contestedThreshold ?? 1,
}, { programAddress });
// Default: bundle thread creation for automation
if (params.automated !== false) {
const paymentsFreq = normalizeSchedule(params.paymentsFreq ?? '@daily');
// Derive PDAs locally (contract doesn't exist on-chain yet)
const contractAddress = await deriveContract(params.fleet);
const contractTokenAccount = await deriveAssociatedTokenAccount(contractAddress, atlasMint);
const rentalAuthority = await deriveRentalAuthority();
const contractThread = await deriveContractThread(contractAddress, rentalAuthority);
const fiber0 = await deriveFiber(contractThread, 0);
const closeRentalFiber = await deriveFiber(contractThread, 1);
const ownerAddress = getSignerAddress(params.owner);
const MINIMUM_THREAD_FUNDING = 40000000n;
const DEFAULT_EXECUTION_BUDGET = 2190000n; // 1 year daily @ 0.000006 SOL/tick
const threadIx = await getCreateContractThreadInstructionAsync({
payer: ownerSigner,
mint: atlasMint,
contract: contractAddress,
contractThread,
fiber0,
closeRentalFiber,
owner: ownerAddress,
contractTokenAccount,
sageProgram,
lamports: MINIMUM_THREAD_FUNDING +
(params.threadLamports ? convertSol(params.threadLamports) : DEFAULT_EXECUTION_BUDGET),
paymentsFreq,
}, { programAddress });
return prepareInstructions([kitInstruction, threadIx], {
computeUnits: params.computeUnits,
PublicKey: finalConfig.PublicKey,
});
}
return prepareInstructions(kitInstruction, {
computeUnits: params.computeUnits,
PublicKey: finalConfig.PublicKey,
});
}
//# sourceMappingURL=createContract.js.map