UNPKG

stxcity-sdk

Version:

SDK for interacting with Stxcity

222 lines 11.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.buyBondingTokenHandler = void 0; const axios_1 = __importDefault(require("axios")); const connect_1 = require("@stacks/connect"); const transactions_1 = require("@stacks/transactions"); const hiroAPIHelpers_1 = __importDefault(require("../lib/hiroAPIHelpers")); const config_1 = require("../config"); const constants_1 = require("../lib/constants"); const checkValidBondingTokenHandler_1 = require("./checkValidBondingTokenHandler"); const buyBondingTokenHandler = (params, context) => __awaiter(void 0, void 0, void 0, function* () { try { const requiredFields = ['stxAmount', 'dexContractId', 'senderAddress', 'tokenContractId', 'tokenSymbol', 'slippage']; validateInputAndThrow(params, requiredFields); // check valid token try { const isValidtoken = yield (0, checkValidBondingTokenHandler_1.checkValidBondingTokenHandler)(params.dexContractId, params.tokenContractId); if (!isValidtoken) throw new Error(config_1.MALICIOUS_TOKEN_MESSAGE); } catch (error) { throw new Error(config_1.MALICIOUS_TOKEN_MESSAGE); } const stxAmountAfterDecimals = params.stxAmount * Math.pow(10, 6); if (isNaN(stxAmountAfterDecimals) || stxAmountAfterDecimals <= 0) { throw new Error("Invalid STX amount provided."); } const stxAmountAfterDecimalsCV = (0, transactions_1.uintCV)(stxAmountAfterDecimals); const stxAmountAfterDecimalsHex = (0, transactions_1.cvToHex)(stxAmountAfterDecimalsCV); const dexDeployer = params.dexContractId.split(".")[0]; const dexContractName = params.dexContractId.split(".")[1]; if (!dexDeployer || !dexContractName) { throw new Error("Invalid dexContractId format."); } const response = yield getBuyableToken(dexDeployer, dexContractName, params.senderAddress, stxAmountAfterDecimalsHex); if (response.data.result) { const buyableInfoCV = (0, transactions_1.hexToCV)(response.data.result); const buyableInfoData = (0, transactions_1.cvToJSON)(buyableInfoCV); const buyableInfoObj = buyableInfoData.value.value; const buyableTokenValue = parseInt(buyableInfoObj["buyable-token"].value); const buyableToken = Number(buyableTokenValue - Math.floor((buyableTokenValue * params.slippage) / 100)); let postConditions; // check if this is the last buyer const fetchProgressTokenBonding = yield fetchTokenBondingProgress(params.dexContractId, params.tokenContractId); if (!fetchProgressTokenBonding) { throw new Error("Failed to fetch token bonding progress."); } const dexSTXBalanceAfterDecimals = fetchProgressTokenBonding.dexStxBalanceWithDecimals; const dexTokenBalanceWithDecimals = fetchProgressTokenBonding.dexTokenBalanceWithDecimal; const stxTargetAmount = yield getSTXTargetAmount(params.dexContractId); if (isNaN(stxTargetAmount) || stxTargetAmount <= 0) { throw new Error("Invalid STX target amount."); } // minus the STX fee too const stxcityFee = (stxAmountAfterDecimals * 1) / 100; const leftOverSTX = Number(stxTargetAmount - dexSTXBalanceAfterDecimals + stxcityFee); const contractNameForPC = validateAndUseString(params.tokenContractId); if (!contractNameForPC) { throw new Error("Invalid token contract ID."); } // this is last buyer if (stxAmountAfterDecimals >= leftOverSTX) { // users send stx to dex const userSendSTXToDexPC = transactions_1.Pc.principal(params.senderAddress) .willSendLte(stxAmountAfterDecimals) .ustx(); // dex send stx to Velar and stxcity const dexSendSTXPC = transactions_1.Pc.principal(params.dexContractId) .willSendGte(stxTargetAmount) .ustx(); // dex send tokens to Velar const dexSendTokensPC = transactions_1.Pc.principal(params.dexContractId) .willSendGte(BigInt(dexTokenBalanceWithDecimals)) .ft(contractNameForPC, params.tokenSymbol); postConditions = [userSendSTXToDexPC, dexSendSTXPC, dexSendTokensPC]; } else { // normal buyer const userSendSTXPC = transactions_1.Pc.principal(params.senderAddress) .willSendLte(stxAmountAfterDecimals) .ustx(); const dexSendTokensPC = transactions_1.Pc.principal(params.dexContractId) .willSendGte(BigInt(buyableToken)) .ft(contractNameForPC, params.tokenSymbol); postConditions = [userSendSTXPC, dexSendTokensPC]; } const handleOnFinish = (data) => { if (data) { if (params.onFinish) { const modifiedData = Object.assign(Object.assign({}, data), { status: "Buy successfully" }); params.onFinish(modifiedData); } } else { throw new Error("No data returned on finish."); } }; const handleOnCancel = () => { if (params.onCancel) { params.onCancel(); } }; yield (0, connect_1.openContractCall)({ network: context.network, anchorMode: transactions_1.AnchorMode.Any, contractAddress: dexDeployer, contractName: dexContractName, appDetails: constants_1.appDetails, functionName: "buy", functionArgs: [ (0, transactions_1.principalCV)(params.tokenContractId), // token-trait (0, transactions_1.uintCV)(stxAmountAfterDecimals), // x value ], postConditions, postConditionMode: transactions_1.PostConditionMode.Deny, onFinish: handleOnFinish, onCancel: handleOnCancel, }); } else { throw new Error("This token unavailable to buy!"); } } catch (error) { throw error; } }); exports.buyBondingTokenHandler = buyBondingTokenHandler; const getBuyableToken = (dexDeployer, dexContractName, senderAdress, stxAmountAfterDecimalsHex) => __awaiter(void 0, void 0, void 0, function* () { const apiURL = `${config_1.configs.STACKS_NETWORK_API_HOST}/v2/contracts/call-read/${dexDeployer}/${dexContractName}/get-buyable-tokens`; const requestData = { sender: senderAdress, arguments: [stxAmountAfterDecimalsHex], }; return axios_1.default.post(apiURL, requestData, { headers: (0, hiroAPIHelpers_1.default)(), }); }); const fetchTokenBondingProgress = (dexContractId, tokenContractId) => __awaiter(void 0, void 0, void 0, function* () { try { const baseUrl = `${config_1.configs.STACKS_NETWORK_API_HOST}/extended/v1/address/${dexContractId}/balances`; const response = yield fetch(baseUrl, { method: "GET", headers: (0, hiroAPIHelpers_1.default)(), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = yield response.json(); const stxBalance = Number(data.stx.balance); const stxBalanceNotDecimals = stxBalance / Math.pow(10, 6); const tokenIds = Object.keys(data.fungible_tokens); let dexTokenBalance = 0; let dexTokenBalanceWithDecimal = 0; tokenIds.forEach((tokenId) => { if (tokenId.includes(tokenContractId)) { dexTokenBalanceWithDecimal = Number(data.fungible_tokens[tokenId].balance); dexTokenBalance = dexTokenBalanceWithDecimal / Math.pow(10, 6); } }); let result = { dexStxBalance: stxBalanceNotDecimals, dexStxBalanceWithDecimals: stxBalance, dexTokenBalance: dexTokenBalance, dexTokenBalanceWithDecimal: dexTokenBalanceWithDecimal, }; return result; } catch (_a) { throw new Error("error loading dex balance"); } }); const getSTXTargetAmount = (dexContractId) => __awaiter(void 0, void 0, void 0, function* () { var _a; try { const dexDeployer = dexContractId.split(".")[0]; const contractName = dexContractId.split(".")[1]; const contractSourceUri = `${config_1.configs.STACKS_NETWORK_API_HOST}/v2/contracts/source/${dexDeployer}/${contractName}`; const sourceCodeResponse = yield axios_1.default.get(contractSourceUri, { headers: (0, hiroAPIHelpers_1.default)(), }); const sourceCode = sourceCodeResponse.data.source; const stxTargetAmountVal = Number(((_a = sourceCode.match(/define-constant STX_TARGET_AMOUNT u(\d+)/)) === null || _a === void 0 ? void 0 : _a[1]) || ""); return stxTargetAmountVal; } catch (_b) { throw new Error(`error loading dex balance`); } }); const validateAndUseString = (inputString) => { const parts = inputString.split("."); return inputString; }; const validateInputAndThrow = (inputData, requiredFields) => { requiredFields.forEach(field => { if (!inputData.hasOwnProperty(field)) { throw new Error(`Missing required field: ${field}`); } }); Object.keys(inputData).forEach((key) => { if (inputData[key] === null || inputData[key] === undefined || inputData[key] === "") { throw new Error(`Field "${key}" cannot be null, undefined or empty`); } }); if (inputData.slippage < 0 || inputData.slippage > 100) { throw new Error("Slippage must be a number between 0 and 100."); } }; //# sourceMappingURL=buyBondingTokenHandler.js.map