@mytonswap/sdk
Version:
MyTonSwap Dex Aggregator SDK
577 lines (563 loc) • 19.2 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
MyTonSwapClient: () => MyTonSwapClient,
fromNano: () => fromNano,
toNano: () => toNano
});
module.exports = __toCommonJS(src_exports);
// src/core/request.ts
var import_axios = __toESM(require("axios"), 1);
var import_lodash = require("lodash");
// src/constants/index.ts
var defaultBaseUrl = process.env.BASE_URL ?? "https://app.mytonswap.com/api/";
var baseUrls = ["https://app.mytonswap.com/api/", "https://devtest.mytonswap.com/api/"];
// package.json
var package_default = {
name: "@mytonswap/sdk",
description: "MyTonSwap Dex Aggregator SDK",
author: {
name: "MyTonSwap",
url: "https://github.com/mytonswap"
},
homepage: "https://docs.mytonswap.com",
contributors: [
{
email: "ho3einwave@gmail.com",
name: "Ho3einWave"
}
],
repository: {
type: "git",
url: "git+https://github.com/MyTonSwap/sdk.git"
},
main: "dist/index.cjs",
module: "dist/index.js",
types: "dist/index.d.ts",
keywords: [
"MyTonSwap",
"TON",
"The Open Network",
"TON Blockchain",
"SDK",
"Swap",
"Telegram",
"stonfi",
"ston.fi",
"dedust",
"DEX",
"DeFi"
],
version: "1.1.2",
type: "module",
scripts: {
build: "tsup"
},
devDependencies: {
"@types/bun": "^1.1.10",
"@types/lodash": "^4.17.9",
tsup: "^8.3.0"
},
peerDependencies: {
typescript: "^5.0.0"
},
dependencies: {
"@ton/ton": "^15.0.0",
axios: "^1.7.7",
"axios-retry": "^4.5.0",
lodash: "^4.17.21"
}
};
// src/core/request.ts
var import_axios_retry = __toESM(require("axios-retry"), 1);
var Request = class {
constructor(client) {
this.client = client;
(0, import_axios_retry.default)(this.axiosInstance, this.attemptOptions);
this.axiosInstance.interceptors.response.use((res) => {
if (baseUrls.includes(res.config.baseURL ?? "")) {
res.data = res.data.data;
}
return res;
});
}
axiosInstance = import_axios.default.create();
attemptOptions = {
retries: 3,
retryDelay: (retryCount) => {
return retryCount * 1e3;
}
};
/**
* send
*/
async send(userOptions) {
const defaultOptions = {
baseURL: this.client.options?.baseUrl ?? defaultBaseUrl,
headers: userOptions.baseURL ? {} : {
...this.client.options?.apiKey && {
"x-api-key": this.client.options.apiKey
},
...this.client.options?.headers ?? {},
"sdk-version": package_default.version
},
method: "GET"
};
const options = (0, import_lodash.defaultsDeep)(userOptions, defaultOptions, {
headers: this.getDefaultHeaders
});
const response = await this.faultTolerantRequest(options);
this.handleErrors(response);
return response.data;
}
handleErrors(responseBody) {
if (!responseBody) return;
if (responseBody.data.statusCode <= 200 && responseBody.data.statusCode >= 400) {
throw new Error(
`Request failed with ${responseBody.data.statusCode}
Reason: ${responseBody.data.errorData}`
);
} else {
return;
}
}
/**
* faultTolerantRequest
*/
async faultTolerantRequest(options) {
try {
return await this.axiosInstance.request(options);
} catch (err) {
throw err;
}
}
/**
* getDefaultHeaders
*/
getDefaultHeaders() {
return {
stats_id: "SDK"
};
}
};
// src/core/services.ts
var Services = class {
client;
constructor(client) {
this.client = client;
}
};
// src/services/assets/assets.service.ts
var Assets = class extends Services {
/**
* Retrieves an exact asset from the server.
*
* @param {string} asset - The token address you want.
* @returns {Promise<Asset | null>} A promise that resolves to the token if found, or null if not found.
*/
async getExactAsset(token_address) {
const assets = await this.client.request.send({
url: "/swap-process/data/assets/find/exactSearch",
method: "POST",
data: {
assets: [token_address]
}
});
if (assets.length > 0) {
return assets[0];
} else {
return null;
}
}
/**
* Retrieves a list of assets from the server based on the provided asset addresses.
*
* @param {string[]} assetsAddress - An array of asset addresses to fetch.
* @returns {Promise<Asset[]>} A promise that resolves to an array of assets.
*/
async getAssets(assetsAddress) {
const listOfAssets = await this.client.request.send({
url: "/swap-process/data/assets",
method: "POST",
data: {
assets: assetsAddress
}
});
return listOfAssets.list;
}
/**
* Retrieves a paginated list of assets from the server.
*
* @param {number} [page=1] page - The page number to retrieve. Defaults to 1.
* @param {boolean} [warning=false] warning - A boolean indicating whether to include warnings. Defaults to false.
* @param {string} [phrase=''] phrase - An optional search phrase to filter the assets.
* @returns {Promise<PaginatedAssets>} A promise that resolves to a paginated list of assets.
*/
async getPaginatedAssets(page = 1, warning = false, phrase) {
const listOfAssets = await this.client.request.send({
url: `/swap-process/data/assets/find/${page}?warning=${warning}${phrase ? `&search=${phrase}` : ""}`
});
return listOfAssets;
}
/**
* Retrieves a paginated list of asset pairs from the server.
*
* @param {string} assetAddress - The address of the asset to fetch pairs for.
* @param {number} [page=1] - The page number to retrieve. Defaults to 1.
* @param {boolean} [warning=false] - A boolean indicating whether to include warnings. Defaults to false.
* @param {string} [searchPhrase=''] - An optional search phrase to filter the asset pairs.
* @returns {Promise<PaginatedAssets>} A promise that resolves to a paginated list of asset pairs.
*/
async getPairs(assetAddress, page = 1, warning = false, searchPhrase = "") {
const listOfPairs = await this.client.request.send({
url: `https://app.mytonswap.com/api/swap-process/data/assets/pairs/${assetAddress}?page=${page}&warning=${warning}&search=${searchPhrase}`
});
return listOfPairs;
}
};
// src/services/router/router.service.ts
var Router = class extends Services {
/**
* Finds the best route for a given input and output asset address, pay amount, and optional slippage and DEX.
*
* @param {string} inputAssetAddress - The address of the input asset.
* @param {string} outputAssetAddress - The address of the output asset.
* @param {bigint} payAmount - The amount to be paid.
* @param {number} [slippage] - Optional slippage percentage.
* @param {Dex} [forceDex] - Optional DEX to force the route through.
* @returns {Promise<BestRoute>} A promise that resolves to the best route.
*
* @todo Add validation for address and slippage.
* @todo If the user doesn't input an address, get the address by asset service.
*/
async findBestRoute(inputAssetAddress, outputAssetAddress, payAmount, slippage, forceDex) {
const body = {
token0: inputAssetAddress,
token1: outputAssetAddress,
amount: payAmount.toString(),
slippage: slippage ?? "auto",
token0_symbol: "SDK",
token1_symbol: "SDK",
init: true,
dex: forceDex
};
const data = this.client.request.send({
url: "v2/routes/pair",
method: "POST",
data: body
});
return data;
}
};
// src/core/client.ts
var import_ton2 = require("@ton/ton");
// src/services/tonapi/tonapi.service.ts
var import_ton = require("@ton/ton");
var TonApi = class extends Services {
/**
* Fetches the jetton data for a given wallet address and jetton address.
*
* @param {string} walletAddr - The wallet address to fetch the jetton data for.
* @param {string} jettonAddress - The jetton address to fetch the data from.
* @returns {Promise<Balance>} A promise that resolves to the balance data.
*/
async getJettonData(walletAddr, jettonAddress) {
const data = await this.client.request.send({
baseURL: "https://tonapi.io/v2",
maxBodyLength: Infinity,
url: `/accounts/${walletAddr}/jettons/${jettonAddress}?supported_extensions=custom_payload`
});
return data;
}
/**
* Retrieves a custom payload for a specific wallet and jetton address.
*
* @param {string} walletAddr - The address of the wallet.
* @param {string} jettonAddress - The address of the jetton.
* @returns {Promise<CustomPayload>} A promise that resolves to the custom payload.
*/
async getCustomPayload(walletAddr, jettonAddress) {
const data = await this.client.request.send({
baseURL: "https://tonapi.io/v2",
maxBodyLength: Infinity,
url: `/jettons/${jettonAddress}/transfer/${walletAddr}/payload`
});
return data;
}
/**
* Retrieves wallet assets for a given wallet address, including balances and rates for jettons.
*
* @param {string} walletAddress - The address of the wallet to retrieve assets for.
* @param {string[]} [currencies=['usd']] - An array of currency codes to retrieve rates for.
* @param {boolean} [custom_payload=true] - Whether to include custom payload in the request.
* @returns {Promise<Map<string, Balance>>} A promise that resolves to a map of balances keyed by jetton addresses.
*/
async getWalletAssets(walletAddress, currencies = ["usd"], custom_payload = true) {
const { balances } = await this.client.request.send({
baseURL: "https://tonapi.io/v2",
maxBodyLength: Infinity,
url: `/accounts/${walletAddress}/jettons?currencies=${currencies.join(",")}${custom_payload ? "&supported_extensions=custom_payload" : ""}`
});
const addresses = balances.map((item) => item.jetton.address).join(",");
const { rates } = await this.client.request.send({
baseURL: "https://tonapi.io/v2",
url: `/rates?tokens=${addresses.length > 0 ? addresses : ""},EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c¤cies=usd`
});
const { balance: tonBalance } = await this.client.request.send({
baseURL: "https://tonapi.io/v2",
url: `/accounts/${walletAddress}`
});
balances.push({
balance: String(tonBalance),
wallet_address: {
address: walletAddress,
is_scam: false,
is_wallet: true
},
jetton: {
address: "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c",
symbol: "TON",
name: "TON",
image: "https://asset.ston.fi/img/EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c/4ecd4687e0b5b8ff21a7fbe03f9d281c26a2dc13eac7b7d16048cc693fe0ec39",
decimals: 9,
verification: ""
}
});
const newBalances = balances.reduce((map, item) => {
const userFriendlyAddr = (0, import_ton.address)(item.jetton.address).toString();
item.price = rates[userFriendlyAddr];
map.set(userFriendlyAddr, item);
return map;
}, /* @__PURE__ */ new Map());
return newBalances;
}
/**
* Fetches the rates of specified assets from the TON API.
*
* @param {string[]} assetsAddresses - An array of asset addresses to fetch rates for.
* @returns {Promise<Map<string, Prices>>} A promise that resolves to a map where the keys are user-friendly asset addresses and the values are their corresponding prices.
*/
async getAssetsRates(assetsAddresses) {
const addresses = assetsAddresses.join(",");
const { rates } = await this.client.request.send({
baseURL: "https://tonapi.io/v2",
url: `/rates?tokens=${addresses.length > 0 ? addresses : ""},EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c¤cies=usd`
});
const ratesMap = assetsAddresses.reduce((map, item) => {
const userFriendlyAddr = (0, import_ton.address)(item).toString();
map.set(userFriendlyAddr, rates[userFriendlyAddr].prices);
return map;
}, /* @__PURE__ */ new Map());
return ratesMap;
}
/**
* waitForTransactionResult
*/
/**
* Waits for a transaction result by periodically checking the transaction status.
*
* @param {string} hash - The hash of the transaction to wait for.
* @param {number} [period_ms=3000] - The period in milliseconds to wait between checks.
* @param {number} [maxRetry=30] - The maximum number of retries before giving up.
* @returns {Promise<TransactionEvent>} - A promise that resolves with the transaction result when complete.
* @throws {Error} - Throws an error if the maximum number of retries is reached.
*/
async waitForTransactionResult(hash, period_ms = 3e3, maxRetry = 30) {
let retries = 0;
while (retries <= maxRetry) {
try {
let result = await this.client.tonapi.getTransactionEvent(hash);
while (this.allTransactionComplete(result) === "inprogress") {
await new Promise((resolve) => setTimeout(resolve, period_ms));
result = await this.client.tonapi.getTransactionEvent(hash);
retries++;
if (retries > maxRetry) {
throw new Error("Max retries reached");
}
}
return result;
} catch (error) {
await new Promise((resolve) => setTimeout(resolve, period_ms));
retries++;
if (retries > maxRetry) {
throw new Error("Max retries reached");
}
}
}
throw new Error("Max retries reached");
}
/**
* Fetches a transaction event from the TON API using the provided hash.
*
* @param {string} hash - The hash of the transaction event to retrieve.
* @returns {Promise<TransactionEvent>} A promise that resolves to the transaction event.
*/
async getTransactionEvent(hash) {
const event = await this.client.request.send({
baseURL: "https://tonapi.io/v2",
url: `/events/${hash}`
});
return event;
}
/**
* Checks if all transactions in the given event are complete.
*
* @param {TransactionEvent} event - The transaction event to check.
* @returns {string} - Returns `ok` if all transactions are complete and successful, otherwise `failed` or `inprogress`.
* @throws {Error} - Throws an error if any transaction action has a status other than 'ok'.
*/
allTransactionComplete(event) {
if (event.in_progress) return "inprogress";
if (event.actions.some((item) => item.status !== "ok")) return "failed";
return "ok";
}
};
// src/services/swap/swap.service.ts
var Swap = class extends Services {
/**
* Creates a swap request using the provided user wallet address and best route.
*
* @param {string} userWalletAddress - The address of the user's wallet.
* @param {BestRoute} bestRoute - The best route from router.
* @param {string} [app_id] - Optional application ID to include in the request headers.
* @returns {Promise<SwapResponse>} A promise that resolves to the swap response.
*/
async createSwap(userWalletAddress, bestRoute, app_id) {
return await this.client.request.send({
method: "POST",
headers: {
...app_id ? { "app-id": app_id } : {}
},
url: "v2/routes/boc",
data: {
userWallet: userWalletAddress,
bestRoute
}
});
}
};
// src/core/client.ts
var MyTonSwapClient = class {
options;
request = new Request(this);
assets = new Assets(this);
router = new Router(this);
tonapi = new TonApi(this);
swap = new Swap(this);
tonClient;
constructor(options) {
this.options = options;
this.tonClient = new import_ton2.TonClient({
endpoint: "https://toncenter.com/api/v2/jsonRPC",
apiKey: options?.tonCenterApiKey
});
}
};
// src/utils/convert.ts
function toNano(src, decimals = 9) {
if (typeof src === "bigint") {
return src * 10n ** BigInt(decimals);
} else {
if (typeof src === "number") {
if (!Number.isFinite(src)) {
throw Error("Invalid number");
}
if (parseInt(Math.log10(src).toString()) <= 6) {
src = src.toLocaleString("en", {
minimumFractionDigits: decimals,
useGrouping: false
});
} else if (src - Math.trunc(src) === 0) {
src = src.toLocaleString("en", { maximumFractionDigits: 0, useGrouping: false });
} else {
throw Error("Not enough precision for a number value. Use string value instead");
}
}
let neg = false;
while (src.startsWith("-")) {
neg = !neg;
src = src.slice(1);
}
if (src === ".") {
throw Error("Invalid number");
}
let parts = src.split(".");
if (parts.length > 2) {
throw Error("Invalid number");
}
let whole = parts[0];
let frac = parts[1];
if (!whole) {
whole = "0";
}
if (!frac) {
frac = "0";
}
if (frac.length > decimals) {
throw Error("Invalid number");
}
while (frac.length < decimals) {
frac += "0";
}
let r = BigInt(whole) * 10n ** BigInt(decimals) + BigInt(frac);
if (neg) {
r = -r;
}
return r;
}
}
function fromNano(src, decimals = 9) {
let v = BigInt(src);
let neg = false;
if (v < 0) {
neg = true;
v = -v;
}
let frac = v % 10n ** BigInt(decimals);
let facStr = frac.toString();
while (facStr.length < decimals) {
facStr = "0" + facStr;
}
facStr = facStr.match(/^([0-9]*[1-9]|0)(0*)/)[1];
let whole = v / 10n ** BigInt(decimals);
let wholeStr = whole.toString();
let value = `${wholeStr}${facStr === "0" ? "" : `.${facStr}`}`;
if (neg) {
value = "-" + value;
}
return value;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
MyTonSwapClient,
fromNano,
toNano
});