stxcity-sdk
Version:
SDK for interacting with Stxcity
222 lines • 11.1 kB
JavaScript
;
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