@faktoryfun/core-sdk
Version:
The official SDK for interacting with Faktory tokens and DEX contracts
835 lines (834 loc) • 36.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FaktorySDK = void 0;
const transactions_1 = require("@stacks/transactions");
const network_1 = require("@stacks/network");
// Helper function to get network object
function getNetworkObject(network) {
switch (network) {
case "mainnet":
return network_1.STACKS_MAINNET;
case "testnet":
return network_1.STACKS_TESTNET;
case "devnet":
case "mocknet":
return network_1.STACKS_DEVNET;
default:
throw new Error(`Unsupported network: ${network}`);
}
}
class FaktorySDK {
constructor(config) {
this.SBTC_CONTRACT = {
mainnet: {
address: "SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4",
name: "sbtc-token",
assetName: "sbtc-token",
},
testnet: {
address: "STV9K21TBFAK4KNRJXF5DFP8N7W46G4V9RJ5XDY2",
name: "sbtc-token",
assetName: "sbtc-token",
},
devnet: {
address: "STV9K21TBFAK4KNRJXF5DFP8N7W46G4V9RJ5XDY2",
name: "sbtc-token",
assetName: "sbtc-token",
},
mocknet: {
address: "STV9K21TBFAK4KNRJXF5DFP8N7W46G4V9RJ5XDY2",
name: "sbtc-token",
assetName: "sbtc-token",
},
};
this.network = config.network;
this.apiHost =
config.apiHost ||
(this.network === "testnet"
? "https://faktory-testnet-be.vercel.app/api"
: "https://faktory-be.vercel.app/api");
this.apiKey = config.apiKey || "blablahnotsharing";
this.hiroApiKey = config.hiroApiKey;
}
async fetch(endpoint, options = {}) {
const url = `${this.apiHost}${endpoint}`;
const response = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${this.apiKey}`,
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
// New method - user handles deployment
async getTokenDeployParams(input) {
// Validate required fields
const requiredFields = [
"symbol",
"name",
"description",
"supply",
"targetStx",
"creatorAddress",
"initialBuyAmount",
"targetAmm",
];
for (const field of requiredFields) {
if (input[field] === undefined || input[field] === null) {
throw new Error(`Missing required field: ${field}`);
}
}
if (input.supply > 1000000000) {
throw new Error("Token supply cannot exceed 1 billion tokens for better relatability");
}
return this.fetch("/tokens/create", {
method: "POST",
body: JSON.stringify(input),
headers: {
"Content-Type": "application/json",
},
});
}
// Buy params builder
async getBuyParams({ dexContract, inAmount, // amount is in STX or BTC units
senderAddress, slippage = 15, }) {
const networkObj = getNetworkObject(this.network);
const tokenInfo = await this.getTokenInfo(dexContract);
// Check if token is BTC denominated
const isBtcDenominated = tokenInfo.denomination === "btc";
// Convert based on denomination
let ustx = isBtcDenominated
? inAmount * 100000000 // Convert to satoshis (10^8)
: inAmount * 1000000; // Convert to microSTX (10^6)
const [contractAddress, contractName] = dexContract.split(".");
const [tokenAddress, tokenName] = tokenInfo.tokenContract.split(".");
const isExternal = this.isExternalDex(contractName);
const sbtcContract = this.SBTC_CONTRACT[this.network];
if (isBtcDenominated) {
// For BTC-denominated tokens, we handle differently
// Get buy quote - using new v7 method name
const buyQuoteCV = await (0, transactions_1.fetchCallReadOnlyFunction)({
contractAddress,
contractName,
functionName: "get-in", // Keep same function name
functionArgs: [(0, transactions_1.uintCV)(ustx)],
network: networkObj,
senderAddress,
});
const buyQuoteJSON = (0, transactions_1.cvToJSON)(buyQuoteCV);
const stxToGrad = buyQuoteJSON.value.value["stx-to-grad"]?.value;
if (stxToGrad) {
const maxAllowed = Math.floor(Number(stxToGrad) * 1.15); // 15% leeway
if (Number(ustx) > maxAllowed) {
console.log(`Buy amount (${ustx}) exceeds the recommended amount needed to graduate plus 15% leeway. Adjusting to ${maxAllowed}.`);
ustx = maxAllowed;
}
}
// Internal DEX format
const quoteAmount = buyQuoteJSON.value.value["tokens-out"].value;
const newStx = Number(buyQuoteJSON.value.value["new-stx"].value);
const tokenBalance = buyQuoteJSON.value.value["ft-balance"]?.value;
if (!tokenBalance) {
throw new Error("Missing token balance from quote response");
}
const slippagePercent = BigInt(100 - slippage);
const minTokensOut = (BigInt(quoteAmount) * slippagePercent) / BigInt(100);
const currentStxBalance = Number(buyQuoteJSON.value.value["total-stx"].value);
const isLastBuy = tokenInfo.targetStx &&
currentStxBalance + ustx >=
tokenInfo.targetStx * Math.pow(10, tokenInfo.decimals);
if (!tokenInfo.targetStx) {
throw new Error("Missing target STX from quote response");
}
try {
// v7 Post-conditions - using new human-readable format
const postConditions = isLastBuy
? [
{
type: "ft-postcondition",
address: senderAddress,
condition: "lte",
amount: ustx.toString(),
asset: `${sbtcContract.address}.${sbtcContract.name}::${sbtcContract.assetName}`,
},
{
type: "ft-postcondition",
address: `${contractAddress}.${contractName}`,
condition: "gte",
amount: tokenBalance.toString(),
asset: `${tokenAddress}.${tokenName}::${tokenInfo.symbol}`,
},
{
type: "ft-postcondition",
address: `${contractAddress}.${contractName}`,
condition: "gte",
amount: (tokenInfo.targetStx * Math.pow(10, tokenInfo.decimals)).toString(),
asset: `${sbtcContract.address}.${sbtcContract.name}::${sbtcContract.assetName}`,
},
]
: [
{
type: "ft-postcondition",
address: senderAddress,
condition: "lte",
amount: ustx.toString(),
asset: `${sbtcContract.address}.${sbtcContract.name}::${sbtcContract.assetName}`,
},
{
type: "ft-postcondition",
address: `${contractAddress}.${contractName}`,
condition: "gte",
amount: minTokensOut.toString(),
asset: `${tokenAddress}.${tokenName}::${tokenInfo.symbol}`,
},
];
return {
contractAddress,
contractName,
functionName: "buy",
functionArgs: [
(0, transactions_1.contractPrincipalCV)(tokenAddress, tokenName),
(0, transactions_1.uintCV)(ustx),
],
network: networkObj,
anchorMode: transactions_1.AnchorMode.Any,
postConditionMode: transactions_1.PostConditionMode.Deny,
postConditions,
};
}
catch (error) {
console.error(`Error creating post conditions:`, error);
throw error;
}
}
else {
// Original STX-denominated logic with v7 updates
// Get buy quote - using new v7 method name
const buyQuoteCV = await (0, transactions_1.fetchCallReadOnlyFunction)({
contractAddress,
contractName,
functionName: isExternal ? "get-buyable-tokens" : "get-in",
functionArgs: [(0, transactions_1.uintCV)(ustx)],
network: networkObj,
senderAddress,
});
const buyQuoteJSON = (0, transactions_1.cvToJSON)(buyQuoteCV);
let quoteAmount;
let newStx;
let tokenBalance;
if (isExternal) {
// External DEX format
quoteAmount = buyQuoteJSON.value.value["buyable-token"].value;
const stxBalance = buyQuoteJSON.value.value["stx-balance"].value;
const stxBuy = buyQuoteJSON.value.value["stx-buy"].value;
newStx = Number(stxBalance) - Number(stxBuy);
tokenBalance = buyQuoteJSON.value.value["token-balance"]?.value;
// Check recommend-stx-amount limit for external DEX with 15% leeway
const recommendedStxAmount = buyQuoteJSON.value.value["recommend-stx-amount"]?.value;
if (recommendedStxAmount) {
const maxAllowed = Math.floor(Number(recommendedStxAmount) * 1.15); // 15% leeway
if (Number(ustx) > maxAllowed) {
console.log(`Buy amount (${ustx}) exceeds the recommended amount plus 15% leeway. Adjusting to ${maxAllowed}.`);
ustx = maxAllowed;
}
}
}
else {
// Internal DEX format
quoteAmount = buyQuoteJSON.value.value["tokens-out"].value;
newStx = Number(buyQuoteJSON.value.value["new-stx"].value);
tokenBalance = buyQuoteJSON.value.value["ft-balance"]?.value;
const stxToGrad = buyQuoteJSON.value.value["stx-to-grad"]?.value;
if (stxToGrad) {
const maxAllowed = Math.floor(Number(stxToGrad) * 1.15); // 15% leeway
if (Number(ustx) > maxAllowed) {
console.log(`Buy amount (${ustx}) exceeds the remaining amount needed to graduate plus 15% leeway. Adjusting to ${maxAllowed}.`);
ustx = maxAllowed;
}
}
}
if (!tokenBalance) {
throw new Error("Missing token balance from quote response");
}
const slippagePercent = BigInt(100 - slippage);
const minTokensOut = (BigInt(quoteAmount) * slippagePercent) / BigInt(100);
let currentStxBalance;
if (isExternal) {
currentStxBalance = Number(buyQuoteJSON.value.value["stx-balance"].value);
}
else {
currentStxBalance = Number(buyQuoteJSON.value.value["total-stx"].value);
}
const isLastBuy = tokenInfo.targetStx &&
currentStxBalance + ustx >=
tokenInfo.targetStx * Math.pow(10, tokenInfo.decimals);
if (!tokenInfo.targetStx) {
throw new Error("Missing target STX from quote response");
}
// v7 Post-conditions - using new human-readable format
const postConditions = isLastBuy
? [
{
type: "stx-postcondition",
address: senderAddress,
condition: "lte",
amount: ustx.toString(),
},
{
type: "ft-postcondition",
address: `${contractAddress}.${contractName}`,
condition: "gte",
amount: tokenBalance.toString(),
asset: `${tokenAddress}.${tokenName}::${tokenInfo.symbol}`,
},
{
type: "stx-postcondition",
address: `${contractAddress}.${contractName}`,
condition: "gte",
amount: (tokenInfo.targetStx * Math.pow(10, tokenInfo.decimals)).toString(),
},
]
: [
{
type: "stx-postcondition",
address: senderAddress,
condition: "lte",
amount: ustx.toString(),
},
{
type: "ft-postcondition",
address: `${contractAddress}.${contractName}`,
condition: "gte",
amount: minTokensOut.toString(),
asset: `${tokenAddress}.${tokenName}::${tokenInfo.symbol}`,
},
];
return {
contractAddress,
contractName,
functionName: "buy",
functionArgs: [
(0, transactions_1.contractPrincipalCV)(tokenAddress, tokenName),
(0, transactions_1.uintCV)(ustx),
],
network: networkObj,
anchorMode: transactions_1.AnchorMode.Any,
postConditionMode: transactions_1.PostConditionMode.Deny,
postConditions,
};
}
}
// Sell params builder
async getSellParams({ dexContract, amount, // amount is in TOKEN units
senderAddress, slippage = 15, }) {
const networkObj = getNetworkObject(this.network);
const tokenInfo = await this.getTokenInfo(dexContract);
const amountWithDecimals = BigInt(amount) * BigInt(Math.pow(10, tokenInfo.decimals));
const isBtcDenominated = tokenInfo.denomination === "btc";
const sbtcContract = this.SBTC_CONTRACT[this.network];
const [contractAddress, contractName] = dexContract.split(".");
const [tokenAddress, tokenName] = tokenInfo.tokenContract.split(".");
const isExternal = this.isExternalDex(contractName);
// Using v7 method name
const sellQuoteCV = await (0, transactions_1.fetchCallReadOnlyFunction)({
contractAddress,
contractName,
functionName: isExternal ? "get-sellable-stx" : "get-out",
functionArgs: [(0, transactions_1.uintCV)(amountWithDecimals)],
network: networkObj,
senderAddress,
});
const sellQuoteJSON = (0, transactions_1.cvToJSON)(sellQuoteCV);
const quoteAmount = sellQuoteJSON.value.value["stx-out"].value;
const slippagePercent = BigInt(100 - slippage);
const minStxOut = (BigInt(quoteAmount) * slippagePercent) / BigInt(100);
// v7 Post-conditions
const postConditions = [
{
type: "ft-postcondition",
address: senderAddress,
condition: "lte",
amount: amountWithDecimals.toString(),
asset: `${tokenAddress}.${tokenName}::${tokenInfo.symbol}`,
},
isBtcDenominated
? {
type: "ft-postcondition",
address: `${contractAddress}.${contractName}`,
condition: "gte",
amount: minStxOut.toString(),
asset: `${sbtcContract.address}.${sbtcContract.name}::${sbtcContract.assetName}`,
}
: {
type: "stx-postcondition",
address: `${contractAddress}.${contractName}`,
condition: "gte",
amount: minStxOut.toString(),
},
];
return {
contractAddress,
contractName,
functionName: "sell",
functionArgs: [
(0, transactions_1.contractPrincipalCV)(tokenAddress, tokenName),
(0, transactions_1.uintCV)(amountWithDecimals),
],
network: networkObj,
anchorMode: transactions_1.AnchorMode.Any,
postConditionMode: transactions_1.PostConditionMode.Deny,
postConditions,
};
}
isExternalDex(contractName) {
return !contractName.endsWith("faktory-dex");
}
// Read-only functions - updated to use v7 method names
async getIn(dexContract, senderAddress, stx // amount in STX units
) {
const networkObj = getNetworkObject(this.network);
const [contractAddress, contractName] = dexContract.split(".");
const isExternal = this.isExternalDex(contractName);
const ustx = stx * 1000000; // Convert to microSTX
const result = await (0, transactions_1.fetchCallReadOnlyFunction)({
contractAddress,
contractName,
functionName: isExternal ? "get-buyable-tokens" : "get-in",
functionArgs: [(0, transactions_1.uintCV)(ustx)],
network: networkObj,
senderAddress,
});
return (0, transactions_1.cvToJSON)(result);
}
async getOut(dexContract, senderAddress, amount // amount in TOKEN units
) {
const networkObj = getNetworkObject(this.network);
const [contractAddress, contractName] = dexContract.split(".");
const isExternal = this.isExternalDex(contractName);
const tokenInfo = await this.getTokenInfo(dexContract);
const amountWithDecimals = amount * Math.pow(10, tokenInfo.decimals);
const result = await (0, transactions_1.fetchCallReadOnlyFunction)({
contractAddress,
contractName,
functionName: isExternal ? "get-sellable-stx" : "get-out",
functionArgs: [(0, transactions_1.uintCV)(amountWithDecimals)],
network: networkObj,
senderAddress,
});
return (0, transactions_1.cvToJSON)(result);
}
async getOpen(dexContract, senderAddress) {
const networkObj = getNetworkObject(this.network);
const [contractAddress, contractName] = dexContract.split(".");
const result = await (0, transactions_1.fetchCallReadOnlyFunction)({
contractAddress,
contractName,
functionName: "get-open",
functionArgs: [],
network: networkObj,
senderAddress,
});
return (0, transactions_1.cvToJSON)(result);
}
async getVerifiedTokens(options) {
const url = new URL("/tokens", this.apiHost);
const params = new URLSearchParams({ verified: "true" });
if (options?.search)
params.append("search", options.search);
if (options?.sortOrder)
params.append("sortOrder", options.sortOrder);
if (options?.page)
params.append("page", options.page.toString());
if (options?.limit)
params.append("limit", options.limit.toString());
if (options?.daoOnly)
params.append("daoOnly", "true");
return this.fetch(url.pathname + "?" + params.toString(), { method: "GET" });
}
async getDaoTokens(options) {
return this.getVerifiedTokens({
...options,
daoOnly: true,
});
}
async getTokenInfo(dexContract) {
const response = await this.fetch(`/tokens/${dexContract}`);
return {
symbol: response.data.symbol,
decimals: response.data.decimals,
targetStx: response.data.targetStx,
tokenContract: response.data.tokenContract,
denomination: response.data.denomination || "btc",
};
}
async verifyTransfer(tokenAddress) {
return this.fetch(`/tokens/verify-transfer?token_address=${tokenAddress}`, { method: "GET" });
}
async getToken(dexContract) {
return this.fetch(`/tokens/${dexContract}`, {
method: "GET",
});
}
async getTokenTrades(tokenContract) {
if (!tokenContract) {
throw new Error("Token contract is required");
}
return this.fetch(`/tokens/trades/${tokenContract}`, {
method: "GET",
});
}
// ===== PRE-LAUNCH METHODS =====
// Helper to get seat price based on contract type
getSeatPrice(contractType) {
// Helico: 6,900 sats per seat (200 total seats)
// AI DAOs: 20,000 sats per seat (20 total seats)
// Meme tokens: 69,000 sats per seat (20 total seats)
switch (contractType) {
case "helico":
return 6900;
case "dao":
return 20000;
case "meme":
default:
return 69000;
}
}
// Get seat limits based on contract type
getSeatLimits(contractType) {
const limits = {
helico: { TOTAL_SEATS: 200, MIN_USERS: 100, MAX_SEATS_PER_USER: 7 },
dao: { TOTAL_SEATS: 20, MIN_USERS: 10, MAX_SEATS_PER_USER: 7 },
meme: { TOTAL_SEATS: 20, MIN_USERS: 10, MAX_SEATS_PER_USER: 7 },
};
return limits[contractType];
}
// Helper to detect contract type from total seats
detectContractTypeFromSeats(totalSeats) {
if (totalSeats === 200)
return "helico";
if (totalSeats === 20)
return "meme"; // Default to meme for 20 seats
return "meme";
}
// Enhanced contract type detection with helico support
static detectContractType(prelaunchContract, tokenInfo) {
const contractName = prelaunchContract.split(".")[1];
// Check for helico first (special case with 200 seats)
if (tokenInfo?.preHash === "helico" || contractName.includes("helico")) {
return "helico";
}
// Check for DAO indicators
if (contractName.includes("ai") ||
contractName.includes("dao") ||
tokenInfo?.daoToken) {
return "dao";
}
// Default to meme
return "meme";
}
// Enhanced pricing info
static getSeatPricing(contractType = "meme") {
const prices = {
helico: {
sats: 6900,
btc: 0.000069,
description: "0.00006900 BTC per seat (Helico - 200 seats total)",
totalSeats: 200,
minUsers: 100,
},
dao: {
sats: 20000,
btc: 0.0002,
description: "0.00020000 BTC per seat (AI DAO - 20 seats total)",
totalSeats: 20,
minUsers: 10,
},
meme: {
sats: 69000,
btc: 0.00069,
description: "0.00069000 BTC per seat (Meme Token - 20 seats total)",
totalSeats: 20,
minUsers: 10,
},
};
return {
contractType,
...prices[contractType],
maxSeatsPerUser: 7,
usd: null,
};
}
// Buy seats in pre-launch (buy-up-to function) - Enhanced with backend logic
async getBuySeatsParams({ prelaunchContract, seatCount, senderAddress, contractType, tokenInfo, // Add optional token info for enhanced detection
}) {
const networkObj = getNetworkObject(this.network);
const [contractAddress, contractName] = prelaunchContract.split(".");
const sbtcContract = this.SBTC_CONTRACT[this.network];
// Enhanced contract type detection using token info
const detectedType = contractType ||
FaktorySDK.detectContractType(prelaunchContract, tokenInfo);
const seatLimits = this.getSeatLimits(detectedType);
// Get contract status to understand current state
const statusCV = await (0, transactions_1.fetchCallReadOnlyFunction)({
contractAddress,
contractName,
functionName: "get-contract-status",
functionArgs: [],
network: networkObj,
senderAddress,
});
const statusJSON = (0, transactions_1.cvToJSON)(statusCV);
const isDistributionPeriod = statusJSON.value.value["is-distribution-period"].value;
if (isDistributionPeriod) {
throw new Error("Pre-launch period has ended, distribution has started");
}
// Get user info to check current seats owned
const userInfoCV = await (0, transactions_1.fetchCallReadOnlyFunction)({
contractAddress,
contractName,
functionName: "get-user-info",
functionArgs: [(0, transactions_1.principalCV)(senderAddress)],
network: networkObj,
senderAddress,
});
const userInfoJSON = (0, transactions_1.cvToJSON)(userInfoCV);
const currentSeats = Number(userInfoJSON.value.value["seats-owned"].value);
// Get remaining seats
const remainingSeatsCV = await (0, transactions_1.fetchCallReadOnlyFunction)({
contractAddress,
contractName,
functionName: "get-remaining-seats",
functionArgs: [],
network: networkObj,
senderAddress,
});
const remainingSeats = Number((0, transactions_1.cvToJSON)(remainingSeatsCV).value.value["remainin-seats"].value);
// Get total seats taken and users (mimicking backend logic)
const contractStatusData = statusJSON.value.value;
const totalSeatsTaken = Number(contractStatusData["total-seats-taken"].value);
const totalUsers = Number(contractStatusData["total-users"].value);
// Calculate max seats allowed using backend logic
const seatsRemaining = seatLimits.TOTAL_SEATS - totalSeatsTaken;
const usersRemaining = Math.max(0, seatLimits.MIN_USERS - totalUsers);
const maxPossible = Math.max(0, seatsRemaining - usersRemaining + 1);
const maxSeatsAllowed = Math.min(maxPossible, seatLimits.MAX_SEATS_PER_USER - currentSeats);
if (seatCount > maxSeatsAllowed) {
throw new Error(`Cannot buy ${seatCount} seats. Maximum additional seats allowed: ${maxSeatsAllowed}`);
}
if (seatCount > remainingSeats) {
throw new Error(`Cannot buy ${seatCount} seats. Only ${remainingSeats} seats remaining.`);
}
// Calculate cost based on detected contract type
const PRICE_PER_SEAT = this.getSeatPrice(detectedType);
const totalCost = seatCount * PRICE_PER_SEAT;
// Check if this purchase will complete the pre-launch
const willCompleteLaunch = remainingSeats === seatCount;
// Post-conditions for buy-up-to
const postConditions = [
{
type: "ft-postcondition",
address: senderAddress,
condition: "lte",
amount: totalCost.toString(),
asset: `${sbtcContract.address}.${sbtcContract.name}::${sbtcContract.assetName}`,
},
];
// Add special post condition for the last seat purchase
if (willCompleteLaunch) {
const totalAccumulatedBtc = seatLimits.TOTAL_SEATS * PRICE_PER_SEAT;
postConditions.push({
type: "ft-postcondition",
address: `${contractAddress}.${contractName}`,
condition: "gte",
amount: totalAccumulatedBtc.toString(),
asset: `${sbtcContract.address}.${sbtcContract.name}::${sbtcContract.assetName}`,
});
}
return {
contractAddress,
contractName,
functionName: "buy-up-to",
functionArgs: [(0, transactions_1.uintCV)(seatCount)],
network: networkObj,
anchorMode: transactions_1.AnchorMode.Any,
postConditionMode: transactions_1.PostConditionMode.Deny,
postConditions,
estimatedCost: totalCost, // Return for UI display
currentSeats,
maxAdditionalSeats: maxSeatsAllowed,
detectedType,
willCompleteLaunch, // New: indicates if this purchase completes the pre-launch
remainingSeats, // New: for UI display
};
}
// Refund seats from pre-launch
async getRefundParams({ prelaunchContract, senderAddress, contractType = "meme", }) {
const networkObj = getNetworkObject(this.network);
const [contractAddress, contractName] = prelaunchContract.split(".");
const sbtcContract = this.SBTC_CONTRACT[this.network];
// Get contract status to ensure we're still in pre-launch
const statusCV = await (0, transactions_1.fetchCallReadOnlyFunction)({
contractAddress,
contractName,
functionName: "get-contract-status",
functionArgs: [],
network: networkObj,
senderAddress,
});
const statusJSON = (0, transactions_1.cvToJSON)(statusCV);
const isDistributionPeriod = statusJSON.value.value["is-distribution-period"].value;
if (isDistributionPeriod) {
throw new Error("Cannot refund after distribution has started");
}
// Get user's current seats
const userInfoCV = await (0, transactions_1.fetchCallReadOnlyFunction)({
contractAddress,
contractName,
functionName: "get-user-info",
functionArgs: [(0, transactions_1.principalCV)(senderAddress)],
network: networkObj,
senderAddress,
});
const userInfoJSON = (0, transactions_1.cvToJSON)(userInfoCV);
const userSeats = Number(userInfoJSON.value.value["seats-owned"].value);
if (userSeats === 0) {
throw new Error("No seats owned to refund");
}
// Calculate refund amount based on contract type
const PRICE_PER_SEAT = this.getSeatPrice(contractType);
const refundAmount = userSeats * PRICE_PER_SEAT;
// Post-conditions for Pre-launch contract - user should receive sBTC back
const postConditions = [
{
type: "ft-postcondition",
address: `${contractAddress}.${contractName}`,
condition: "lte",
amount: refundAmount.toString(),
asset: `${sbtcContract.address}.${sbtcContract.name}::${sbtcContract.assetName}`,
},
];
return {
contractAddress,
contractName,
functionName: "refund",
functionArgs: [],
network: networkObj,
anchorMode: transactions_1.AnchorMode.Any,
postConditionMode: transactions_1.PostConditionMode.Deny,
postConditions,
refundAmount,
seatsToRefund: userSeats,
};
}
// Get pre-launch contract status and info
async getPrelaunchStatus(prelaunchContract, senderAddress) {
const networkObj = getNetworkObject(this.network);
const [contractAddress, contractName] = prelaunchContract.split(".");
// Get contract status
const statusCV = await (0, transactions_1.fetchCallReadOnlyFunction)({
contractAddress,
contractName,
functionName: "get-contract-status",
functionArgs: [],
network: networkObj,
senderAddress,
});
// Get user info
const userInfoCV = await (0, transactions_1.fetchCallReadOnlyFunction)({
contractAddress,
contractName,
functionName: "get-user-info",
functionArgs: [(0, transactions_1.principalCV)(senderAddress)],
network: networkObj,
senderAddress,
});
// Get remaining seats
const remainingSeatsCV = await (0, transactions_1.fetchCallReadOnlyFunction)({
contractAddress,
contractName,
functionName: "get-remaining-seats",
functionArgs: [],
network: networkObj,
senderAddress,
});
// Get max seats allowed
const maxSeatsCV = await (0, transactions_1.fetchCallReadOnlyFunction)({
contractAddress,
contractName,
functionName: "get-max-seats-allowed",
functionArgs: [],
network: networkObj,
senderAddress,
});
// Get seat holders
const seatHoldersCV = await (0, transactions_1.fetchCallReadOnlyFunction)({
contractAddress,
contractName,
functionName: "get-seat-holders",
functionArgs: [],
network: networkObj,
senderAddress,
});
return {
status: (0, transactions_1.cvToJSON)(statusCV),
userInfo: (0, transactions_1.cvToJSON)(userInfoCV),
remainingSeats: (0, transactions_1.cvToJSON)(remainingSeatsCV),
maxSeatsAllowed: (0, transactions_1.cvToJSON)(maxSeatsCV),
seatHolders: (0, transactions_1.cvToJSON)(seatHoldersCV),
};
}
// Claim vested tokens from pre-launch
async getClaimParams({ prelaunchContract, tokenContract, senderAddress, }) {
const networkObj = getNetworkObject(this.network);
const [contractAddress, contractName] = prelaunchContract.split(".");
const [tokenAddress, tokenName] = tokenContract.split(".");
// ULTRA SIMPLE - No status checks, no post-conditions
// Let the contract handle everything
return {
contractAddress,
contractName,
functionName: "claim",
functionArgs: [(0, transactions_1.contractPrincipalCV)(tokenAddress, tokenName)],
network: networkObj,
anchorMode: transactions_1.AnchorMode.Any,
postConditionMode: transactions_1.PostConditionMode.Allow, // Allow mode - no restrictions
claimableAmount: 0, // Contract will determine
seatsOwned: 0, // Contract will determine
};
}
// Additional methods to add to the FaktorySDK class
// Trigger fee airdrop for pre-launch participants
async getTriggerFeeAirdropParams({ prelaunchContract, senderAddress, }) {
const networkObj = getNetworkObject(this.network);
const [contractAddress, contractName] = prelaunchContract.split(".");
// Simple - no status checks, let contract handle validation
return {
contractAddress,
contractName,
functionName: "trigger-fee-airdrop",
functionArgs: [],
network: networkObj,
anchorMode: transactions_1.AnchorMode.Any,
postConditionMode: transactions_1.PostConditionMode.Allow, // Allow mode - no restrictions
};
}
// Check if market is open (DEX is active, pre-launch complete)
async isMarketOpen(prelaunchContract, senderAddress) {
const networkObj = getNetworkObject(this.network);
const [contractAddress, contractName] = prelaunchContract.split(".");
const result = await (0, transactions_1.fetchCallReadOnlyFunction)({
contractAddress,
contractName,
functionName: "is-market-open",
functionArgs: [],
network: networkObj,
senderAddress,
});
return (0, transactions_1.cvToJSON)(result);
}
}
exports.FaktorySDK = FaktorySDK;