@onrank/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ONRANK
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # @onrank/sdk
2
+
3
+ Quote and trade [ONRANK](https://t.me/OnRankBot) coins from your own bot, wallet or terminal. Pure TypeScript:
4
+ the SDK **builds unsigned messages** that the user signs with their wallet (TON Connect or any wallet SDK).
5
+ It never holds a key and never signs.
6
+
7
+ ```bash
8
+ npm i @onrank/sdk @ton/core
9
+ ```
10
+
11
+ ## 60-second integration
12
+
13
+ ```ts
14
+ import { OnrankClient, applySlippage } from "@onrank/sdk";
15
+
16
+ const onrank = new OnrankClient({ toncenterKey: process.env.TONCENTER_KEY }); // key optional
17
+
18
+ const coin = await onrank.coin(10001); // $RANK — or onrank.coins() for the list
19
+ const { quote, minOut, message, market } = await onrank.trade({
20
+ coin,
21
+ side: "buy", // or "sell"
22
+ amount: 5_000_000_000n, // buy: nanoTON to swap · sell: coin units (9 decimals)
23
+ slippageBps: 200, // 2 %
24
+ owner: userWalletAddress,
25
+ });
26
+ // quote.amountOut = coins you get (buy) / nanoTON you get (sell); minOut = what the contract must at least deliver
27
+ await tonConnectUI.sendTransaction({ validUntil: Math.floor(Date.now() / 1000) + 300, messages: [message] });
28
+ ```
29
+
30
+ `trade()` reads the live curve or pool from toncenter, **checks the contract code hash** against the published
31
+ build, applies the slippage and returns `{ address, amount, payload }`.
32
+
33
+ ## Without the client (no network in the SDK)
34
+
35
+ ```ts
36
+ import { buildCurveBuy, buildSell, quoteCurveBuyForTonIn, quoteCurveSell, decodeCurveData, applySlippage } from "@onrank/sdk";
37
+
38
+ const state = decodeCurveData(stackFromYourOwnRpc); // get_curve_data
39
+ const q = quoteCurveBuyForTonIn(state, 5_000_000_000n); // same integer math as the contract
40
+ const msg = buildCurveBuy({ curve, tonIn: 5_000_000_000n, minOut: applySlippage(q.amountOut, 200) });
41
+
42
+ const s = quoteCurveSell(state, coinAmount);
43
+ const sell = buildSell({ rewardWallet, owner, coinAmount, minTon: applySlippage(s.amountOut, 200) });
44
+ // rewardWallet = get_wallet_address(owner) on the coin's master (TEP-74)
45
+ ```
46
+
47
+ Pool coins: `quotePoolBuy` / `quotePoolSell` with `decodePoolData(get_pool_data)`, `buildPoolBuy`; selling on a v2
48
+ pool is the same `buildSell` (the master routes it).
49
+
50
+ ## Events
51
+
52
+ `decodeTradeEvent(topic, body)` decodes `CurveTrade`, `PoolSwap`, `CurveGraduated`, `LaunchCreated` from the
53
+ external out-messages of the contracts; `topicOf(dest)` extracts the topic. See
54
+ [EVENTS.md](https://onrank.lol/integration/EVENTS.md).
55
+
56
+ ## Guarantees
57
+
58
+ - Message layouts are tested bit-for-bit against the ONRANK app's own builders.
59
+ - Quote formulas are pinned to the on-chain `get_buy_quote` / `get_sell_quote` results of the live $RANK curve.
60
+ - Builders refuse `minOut`/`minTon = 0`: no trade without slippage protection.
61
+
62
+ Full protocol reference: [onrank.lol/integration/README.md](https://onrank.lol/integration/README.md) · [onrank.lol/developers](https://onrank.lol/developers) · contracts: [onrank-contracts](https://github.com/SOLEIIL/onrank-contracts).
63
+ MIT.
@@ -0,0 +1,57 @@
1
+ import { Address, type TupleItem } from "@ton/core";
2
+ import { type LauncherVersion } from "./constants.js";
3
+ import { type TonConnectMessage } from "./messages.js";
4
+ import { type Quote } from "./quotes.js";
5
+ export type ApiCoin = {
6
+ seq: string;
7
+ symbol: string | null;
8
+ name: string | null;
9
+ master: string;
10
+ curve: string;
11
+ pool: string | null;
12
+ version: LauncherVersion;
13
+ sellOnly: boolean;
14
+ hidden?: boolean;
15
+ };
16
+ export type ClientOptions = {
17
+ /** toncenter v2 base, default https://toncenter.com/api/v2 */
18
+ toncenter?: string;
19
+ /** optional toncenter API key (raises the rate limit) */
20
+ toncenterKey?: string;
21
+ /** ONRANK API base, default https://onrank.lol */
22
+ api?: string;
23
+ fetch?: typeof fetch;
24
+ };
25
+ /** toncenter v2 `runGetMethod` stack → @ton/core TupleItem[] (numbers, cells, slices, null, tuples). */
26
+ export declare function toncenterStackToTuple(stack: unknown[]): TupleItem[];
27
+ export declare class OnrankClient {
28
+ private readonly tc;
29
+ private readonly key;
30
+ private readonly api;
31
+ private readonly f;
32
+ constructor(o?: ClientOptions);
33
+ runGetMethod(address: Address | string, method: string, stack?: Array<["num", string]>): Promise<TupleItem[]>;
34
+ /** Code hash (base64) of an account, to compare with `CODE_HASH` before trusting an address. */
35
+ codeHash(address: Address | string): Promise<string | null>;
36
+ /** Coins listed by the ONRANK API (hidden coins excluded). */
37
+ coins(): Promise<ApiCoin[]>;
38
+ coin(seq: string | number): Promise<ApiCoin>;
39
+ /** The holder's RewardWallet for a coin (TEP-74 `get_wallet_address`). */
40
+ walletAddress(master: Address | string, owner: Address | string): Promise<Address>;
41
+ /**
42
+ * Quote + unsigned message in one call. Reads the live market (curve or pool) from the chain, checks its code hash,
43
+ * applies `slippageBps` and returns what the user must sign. Never signs. Throws on sell-only coins for buys.
44
+ */
45
+ trade(p: {
46
+ coin: ApiCoin;
47
+ side: "buy" | "sell";
48
+ amount: bigint;
49
+ slippageBps: number;
50
+ owner: Address | string;
51
+ }): Promise<{
52
+ quote: Quote;
53
+ minOut: bigint;
54
+ message: TonConnectMessage;
55
+ market: "curve" | "pool";
56
+ }>;
57
+ }
package/dist/client.js ADDED
@@ -0,0 +1,136 @@
1
+ // Optional network layer: reads on-chain state through toncenter v2 (no key needed for light use) and lists coins
2
+ // through the public ONRANK API. Everything here is read-only; the messages it returns are for the user to sign.
3
+ import { Cell, beginCell } from "@ton/core";
4
+ import { CODE_HASH, MAINNET } from "./constants.js";
5
+ import { decodeCurveData, decodePoolData, parseAddress } from "./decode.js";
6
+ import { applySlippage, buildCurveBuy, buildPoolBuy, buildSell, buildPoolSellV1 } from "./messages.js";
7
+ import { isCurveOpen, quoteCurveBuyForTonIn, quoteCurveSell, quotePoolBuy, quotePoolSell } from "./quotes.js";
8
+ /** toncenter v2 `runGetMethod` stack → @ton/core TupleItem[] (numbers, cells, slices, null, tuples). */
9
+ export function toncenterStackToTuple(stack) {
10
+ return stack.map((x) => {
11
+ const [kind, value] = x;
12
+ if (kind === "num")
13
+ return { type: "int", value: BigInt(value) };
14
+ if (kind === "cell" || kind === "slice" || kind === "builder")
15
+ return { type: kind === "cell" ? "cell" : "slice", cell: Cell.fromBase64(value.bytes) };
16
+ if (kind === "null")
17
+ return { type: "null" };
18
+ if (kind === "list" || kind === "tuple")
19
+ return { type: "tuple", items: toncenterStackToTuple((value.elements ?? [])) };
20
+ throw new Error(`unknown stack item ${kind}`);
21
+ });
22
+ }
23
+ export class OnrankClient {
24
+ tc;
25
+ key;
26
+ api;
27
+ f;
28
+ constructor(o = {}) {
29
+ this.tc = o.toncenter ?? "https://toncenter.com/api/v2";
30
+ this.key = o.toncenterKey;
31
+ this.api = o.api ?? MAINNET.api;
32
+ this.f = o.fetch ?? fetch;
33
+ }
34
+ async runGetMethod(address, method, stack = []) {
35
+ const a = typeof address === "string" ? parseAddress(address) : address;
36
+ const r = await this.f(`${this.tc}/runGetMethod`, {
37
+ method: "POST",
38
+ headers: { "content-type": "application/json", ...(this.key ? { "X-API-Key": this.key } : {}) },
39
+ body: JSON.stringify({ address: a.toString(), method, stack }),
40
+ });
41
+ const j = (await r.json());
42
+ if (!j.ok || !j.result)
43
+ throw new Error(`toncenter: ${j.error ?? r.status}`);
44
+ if (j.result.exit_code !== 0)
45
+ throw new Error(`${method}: exit ${j.result.exit_code}`);
46
+ return toncenterStackToTuple(j.result.stack);
47
+ }
48
+ /** Code hash (base64) of an account, to compare with `CODE_HASH` before trusting an address. */
49
+ async codeHash(address) {
50
+ const a = typeof address === "string" ? parseAddress(address) : address;
51
+ const r = await this.f(`${this.tc}/getAddressInformation?address=${encodeURIComponent(a.toString())}`, { headers: this.key ? { "X-API-Key": this.key } : {} });
52
+ const j = (await r.json());
53
+ if (!j.ok || !j.result?.code)
54
+ return null;
55
+ return Cell.fromBase64(j.result.code).hash().toString("base64");
56
+ }
57
+ /** Coins listed by the ONRANK API (hidden coins excluded). */
58
+ async coins() {
59
+ const r = await this.f(`${this.api}/api/v1/coins`);
60
+ if (!r.ok)
61
+ throw new Error(`api: ${r.status}`);
62
+ return (await r.json()).items;
63
+ }
64
+ async coin(seq) {
65
+ const r = await this.f(`${this.api}/api/v1/coins/${seq}`);
66
+ if (!r.ok)
67
+ throw new Error(`api: ${r.status}`);
68
+ return (await r.json());
69
+ }
70
+ /** The holder's RewardWallet for a coin (TEP-74 `get_wallet_address`). */
71
+ async walletAddress(master, owner) {
72
+ const m = typeof master === "string" ? parseAddress(master) : master;
73
+ const o = typeof owner === "string" ? parseAddress(owner) : owner;
74
+ const slice = beginCell().storeAddress(o).endCell().toBoc().toString("base64");
75
+ const r = await this.f(`${this.tc}/runGetMethod`, {
76
+ method: "POST",
77
+ headers: { "content-type": "application/json", ...(this.key ? { "X-API-Key": this.key } : {}) },
78
+ body: JSON.stringify({ address: m.toString(), method: "get_wallet_address", stack: [["tvm.Slice", slice]] }),
79
+ });
80
+ const j = (await r.json());
81
+ if (!j.ok || !j.result || j.result.exit_code !== 0)
82
+ throw new Error("get_wallet_address failed");
83
+ const items = toncenterStackToTuple(j.result.stack);
84
+ const first = items[0];
85
+ if (!first || (first.type !== "slice" && first.type !== "cell"))
86
+ throw new Error("get_wallet_address: unexpected stack");
87
+ return first.cell.beginParse().loadAddress();
88
+ }
89
+ /**
90
+ * Quote + unsigned message in one call. Reads the live market (curve or pool) from the chain, checks its code hash,
91
+ * applies `slippageBps` and returns what the user must sign. Never signs. Throws on sell-only coins for buys.
92
+ */
93
+ async trade(p) {
94
+ const owner = typeof p.owner === "string" ? parseAddress(p.owner) : p.owner;
95
+ const version = p.coin.version ?? 2;
96
+ if (p.side === "buy" && p.coin.sellOnly)
97
+ throw new Error("this coin is sell-only");
98
+ if (p.coin.pool) {
99
+ const pool = parseAddress(p.coin.pool);
100
+ if (version === 2) {
101
+ const h = await this.codeHash(pool);
102
+ if (h !== CODE_HASH.PoolV2)
103
+ throw new Error(`pool code hash mismatch: ${h}`);
104
+ }
105
+ const state = decodePoolData(await this.runGetMethod(pool, "get_pool_data"));
106
+ if (p.side === "buy") {
107
+ const quote = quotePoolBuy(state, p.amount);
108
+ const minOut = applySlippage(quote.amountOut, p.slippageBps);
109
+ return { quote, minOut, market: "pool", message: buildPoolBuy({ pool, tonIn: p.amount, minOut, version }) };
110
+ }
111
+ const quote = quotePoolSell(state, p.amount);
112
+ const minTon = applySlippage(quote.amountOut, p.slippageBps);
113
+ const rewardWallet = await this.walletAddress(p.coin.master, owner);
114
+ const message = version === 2 ? buildSell({ rewardWallet, owner, coinAmount: p.amount, minTon, version }) : buildPoolSellV1({ rewardWallet, pool, owner, coinAmount: p.amount, minTon });
115
+ return { quote, minOut: minTon, market: "pool", message };
116
+ }
117
+ const curve = parseAddress(p.coin.curve);
118
+ if (version === 2) {
119
+ const h = await this.codeHash(curve);
120
+ if (h !== CODE_HASH.CurveV2)
121
+ throw new Error(`curve code hash mismatch: ${h}`);
122
+ }
123
+ const state = decodeCurveData(await this.runGetMethod(curve, "get_curve_data"));
124
+ if (!isCurveOpen(state))
125
+ throw new Error("curve is not open (graduated or not started): refresh the coin");
126
+ if (p.side === "buy") {
127
+ const quote = quoteCurveBuyForTonIn(state, p.amount);
128
+ const minOut = applySlippage(quote.amountOut, p.slippageBps);
129
+ return { quote, minOut, market: "curve", message: buildCurveBuy({ curve, tonIn: p.amount, minOut }) };
130
+ }
131
+ const quote = quoteCurveSell(state, p.amount);
132
+ const minTon = applySlippage(quote.amountOut, p.slippageBps);
133
+ const rewardWallet = await this.walletAddress(p.coin.master, owner);
134
+ return { quote, minOut: minTon, market: "curve", message: buildSell({ rewardWallet, owner, coinAmount: p.amount, minTon, version }) };
135
+ }
136
+ }
@@ -0,0 +1,82 @@
1
+ /** Opcodes of the messages an integrator sends (launcher v2 contracts; v1 shares them). */
2
+ export declare const Op: {
3
+ /** Curve: buy with the attached TON minus BUY_GAS. */
4
+ readonly Buy: 1129644034;
5
+ /** Pool: buy with the attached TON minus SWAP_GAS. */
6
+ readonly SwapTonForCoin: 1347354625;
7
+ /** v1 pool only: forward payload of a TEP-74 transfer to the pool. */
8
+ readonly SwapCoinForTon: 1347354626;
9
+ /** TEP-74 transfer (v1 pool sell). */
10
+ readonly Transfer: 260734629;
11
+ /** TEP-74 burn on the user's RewardWallet — with a SellIntent payload it is a sale. */
12
+ readonly Burn: 1499400124;
13
+ /** Burn custom payload: sell for at least `minTon`. */
14
+ readonly SellIntent: 1381433368;
15
+ };
16
+ /** Gas the contracts require (nanoTON). Excess is refunded by the contracts; a margin costs nothing. */
17
+ export declare const Gas: {
18
+ /** Curve.BUY_GAS — deducted from the attached value before the swap. */
19
+ readonly curveBuy: 150000000n;
20
+ /** Pool.SWAP_GAS (v1). */
21
+ readonly poolSwapV1: 200000000n;
22
+ /** PoolV2.SWAP_GAS. */
23
+ readonly poolSwapV2: 150000000n;
24
+ /** RewardWallet.WALLET_BURN_GAS (v1). */
25
+ readonly walletBurnV1: 120000000n;
26
+ /** RewardWalletV2.WALLET_BURN_GAS. */
27
+ readonly walletBurnV2: 160000000n;
28
+ /** RewardWallet.WALLET_TRANSFER_GAS (v1 pool sell). */
29
+ readonly walletTransfer: 100000000n;
30
+ /** v1 pool sell: forward TON so a refused sale can return the coins. */
31
+ readonly poolSellForward: 150000000n;
32
+ /** Safety margin added on sells (refunded). */
33
+ readonly margin: 30000000n;
34
+ };
35
+ /** Exit codes worth showing to a user (contracts/common/errors.tolk). */
36
+ export declare const ExitCode: {
37
+ readonly NotFromOwner: 402;
38
+ readonly InsufficientValue: 410;
39
+ readonly BudgetExceeded: 451;
40
+ readonly SettleInFlight: 476;
41
+ readonly CurveNotOpen: 478;
42
+ readonly SlippageExceeded: 479;
43
+ readonly BelowMinTrade: 480;
44
+ readonly PoolEmpty: 491;
45
+ };
46
+ /** Event topics (248-bit value in the external out-message destination). */
47
+ export declare const Topic: {
48
+ readonly LaunchCreated: 1178722305n;
49
+ readonly CurveTrade: 1129701377n;
50
+ readonly CurveGraduated: 1129701378n;
51
+ readonly PoolSwap: 1347411969n;
52
+ readonly PoolRefund: 1347411970n;
53
+ readonly FeeSplit: 1179901953n;
54
+ };
55
+ /** Mainnet core accounts (also served live by GET https://onrank.lol/api/config). */
56
+ export declare const MAINNET: {
57
+ readonly factory: "EQB18IIqz56m9AAwNpX0Ai61_LNa4yquhT3QRNk4geiLwdhA";
58
+ readonly pot: "EQCpS9EiKrk0W7pCEybeKiCbzLL1b-CSTeiHHgVGKCQD5Mc1";
59
+ readonly potRelay: "EQBv-rQifdeTG6AMWw9OEp9x9YLEBGs__tkWj5sy8ulgiOFT";
60
+ readonly minter: "EQBAUS2t-elNMrkdZtv7Tl1pMZhTJrZqdogyCK-2UxmjjPzu";
61
+ readonly collection: "EQBKCClSs3RhzCXqQrLf8uyUPUBuCmU4P4YGY51tJyNMYgMe";
62
+ readonly buyback: "EQBtLFO9DvXI6WV0uraqVWu6qH8o7gQbk6Awfxg7Kj8Z_phO";
63
+ readonly usdt: "EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs";
64
+ readonly api: "https://onrank.lol";
65
+ };
66
+ /** Code hashes (base64) of the deployed v2 contracts — compare before trusting an address. */
67
+ export declare const CODE_HASH: {
68
+ readonly FactoryV2: "UhyECvtffP/+4hoszJv5w2jH21HAO81k0g4e78RWqBk=";
69
+ readonly CurveV2: "x1+u3TP0Q7h7k/X9GByNy6WqOCvnJ4KVL1hR7t2tWcM=";
70
+ readonly PoolV2: "4yPKye3U58+n21m6EC5vdwvii5A10umUOjjR2elP9t8=";
71
+ readonly RewardMasterV2: "XSZIxXypgXYGEwADqjEcTRTu9mXqu1xb+z7Vu/3fjBw=";
72
+ readonly RewardWalletV2: "qahjc6e276BatQUKZ9wJ4iuKe6Crnv0FbCXcSR+Kr+A=";
73
+ readonly FeeSplitterV2: "5cXxuRBVXd5tEU3TKojemQ9zzEEwo1hpLnkjnCiDSO4=";
74
+ readonly RewardVaultV2: "vzEyUk5gEDQxHIfokjDqvgmuwFsOQnnKwpyBrIVRE/A=";
75
+ readonly Pot: "lxv0tCBCh1oQk0f5oBU0KCTmebivPtMicSoSp5h4Phw=";
76
+ readonly PotRelay: "h7MamCDtM7iPXwEquvm5JaSgCAHLgf3EkVq8Y7Zz2tY=";
77
+ readonly DeskMinter: "QdIxvaBs/3OE7xHOPy4bPP5SzHtMBriVW+TywiLifxA=";
78
+ readonly DeskCollection: "LHhgnY0QJjXpBWNkpYjjCJK1elxludK0gBHdLq4IYeU=";
79
+ readonly BuybackVault: "WXP9MVAA2AT/dITksu2ewZGJo9hdBlV7LAeG/s6C9Xo=";
80
+ };
81
+ /** Launcher family of a coin: v1 coins are sell-only and use a different pool sell path. */
82
+ export type LauncherVersion = 1 | 2;
@@ -0,0 +1,80 @@
1
+ /** Opcodes of the messages an integrator sends (launcher v2 contracts; v1 shares them). */
2
+ export const Op = {
3
+ /** Curve: buy with the attached TON minus BUY_GAS. */
4
+ Buy: 0x43550002,
5
+ /** Pool: buy with the attached TON minus SWAP_GAS. */
6
+ SwapTonForCoin: 0x504f0001,
7
+ /** v1 pool only: forward payload of a TEP-74 transfer to the pool. */
8
+ SwapCoinForTon: 0x504f0002,
9
+ /** TEP-74 transfer (v1 pool sell). */
10
+ Transfer: 0x0f8a7ea5,
11
+ /** TEP-74 burn on the user's RewardWallet — with a SellIntent payload it is a sale. */
12
+ Burn: 0x595f07bc,
13
+ /** Burn custom payload: sell for at least `minTon`. */
14
+ SellIntent: 0x52570018,
15
+ };
16
+ /** Gas the contracts require (nanoTON). Excess is refunded by the contracts; a margin costs nothing. */
17
+ export const Gas = {
18
+ /** Curve.BUY_GAS — deducted from the attached value before the swap. */
19
+ curveBuy: 150000000n,
20
+ /** Pool.SWAP_GAS (v1). */
21
+ poolSwapV1: 200000000n,
22
+ /** PoolV2.SWAP_GAS. */
23
+ poolSwapV2: 150000000n,
24
+ /** RewardWallet.WALLET_BURN_GAS (v1). */
25
+ walletBurnV1: 120000000n,
26
+ /** RewardWalletV2.WALLET_BURN_GAS. */
27
+ walletBurnV2: 160000000n,
28
+ /** RewardWallet.WALLET_TRANSFER_GAS (v1 pool sell). */
29
+ walletTransfer: 100000000n,
30
+ /** v1 pool sell: forward TON so a refused sale can return the coins. */
31
+ poolSellForward: 150000000n,
32
+ /** Safety margin added on sells (refunded). */
33
+ margin: 30000000n,
34
+ };
35
+ /** Exit codes worth showing to a user (contracts/common/errors.tolk). */
36
+ export const ExitCode = {
37
+ NotFromOwner: 402,
38
+ InsufficientValue: 410,
39
+ BudgetExceeded: 451,
40
+ SettleInFlight: 476,
41
+ CurveNotOpen: 478,
42
+ SlippageExceeded: 479,
43
+ BelowMinTrade: 480,
44
+ PoolEmpty: 491,
45
+ };
46
+ /** Event topics (248-bit value in the external out-message destination). */
47
+ export const Topic = {
48
+ LaunchCreated: 0x4641e001n,
49
+ CurveTrade: 0x4355e001n,
50
+ CurveGraduated: 0x4355e002n,
51
+ PoolSwap: 0x504fe001n,
52
+ PoolRefund: 0x504fe002n,
53
+ FeeSplit: 0x4653e001n,
54
+ };
55
+ /** Mainnet core accounts (also served live by GET https://onrank.lol/api/config). */
56
+ export const MAINNET = {
57
+ factory: "EQB18IIqz56m9AAwNpX0Ai61_LNa4yquhT3QRNk4geiLwdhA",
58
+ pot: "EQCpS9EiKrk0W7pCEybeKiCbzLL1b-CSTeiHHgVGKCQD5Mc1",
59
+ potRelay: "EQBv-rQifdeTG6AMWw9OEp9x9YLEBGs__tkWj5sy8ulgiOFT",
60
+ minter: "EQBAUS2t-elNMrkdZtv7Tl1pMZhTJrZqdogyCK-2UxmjjPzu",
61
+ collection: "EQBKCClSs3RhzCXqQrLf8uyUPUBuCmU4P4YGY51tJyNMYgMe",
62
+ buyback: "EQBtLFO9DvXI6WV0uraqVWu6qH8o7gQbk6Awfxg7Kj8Z_phO",
63
+ usdt: "EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs",
64
+ api: "https://onrank.lol",
65
+ };
66
+ /** Code hashes (base64) of the deployed v2 contracts — compare before trusting an address. */
67
+ export const CODE_HASH = {
68
+ FactoryV2: "UhyECvtffP/+4hoszJv5w2jH21HAO81k0g4e78RWqBk=",
69
+ CurveV2: "x1+u3TP0Q7h7k/X9GByNy6WqOCvnJ4KVL1hR7t2tWcM=",
70
+ PoolV2: "4yPKye3U58+n21m6EC5vdwvii5A10umUOjjR2elP9t8=",
71
+ RewardMasterV2: "XSZIxXypgXYGEwADqjEcTRTu9mXqu1xb+z7Vu/3fjBw=",
72
+ RewardWalletV2: "qahjc6e276BatQUKZ9wJ4iuKe6Crnv0FbCXcSR+Kr+A=",
73
+ FeeSplitterV2: "5cXxuRBVXd5tEU3TKojemQ9zzEEwo1hpLnkjnCiDSO4=",
74
+ RewardVaultV2: "vzEyUk5gEDQxHIfokjDqvgmuwFsOQnnKwpyBrIVRE/A=",
75
+ Pot: "lxv0tCBCh1oQk0f5oBU0KCTmebivPtMicSoSp5h4Phw=",
76
+ PotRelay: "h7MamCDtM7iPXwEquvm5JaSgCAHLgf3EkVq8Y7Zz2tY=",
77
+ DeskMinter: "QdIxvaBs/3OE7xHOPy4bPP5SzHtMBriVW+TywiLifxA=",
78
+ DeskCollection: "LHhgnY0QJjXpBWNkpYjjCJK1elxludK0gBHdLq4IYeU=",
79
+ BuybackVault: "WXP9MVAA2AT/dITksu2ewZGJo9hdBlV7LAeG/s6C9Xo=",
80
+ };
@@ -0,0 +1,56 @@
1
+ import { Address, Cell, type ExternalAddress, type TupleItem } from "@ton/core";
2
+ import type { CurveState, PoolState } from "./quotes.js";
3
+ /** `get_curve_data` → the six fields the quotes need (roles / params cells are left on the stack). */
4
+ export declare function decodeCurveData(items: TupleItem[]): CurveState;
5
+ /** `get_pool_data`. */
6
+ export declare function decodePoolData(items: TupleItem[]): PoolState;
7
+ /** `get_buy_quote` / `get_sell_quote` → `(amountOut, fee)`. */
8
+ export declare function decodeQuote(items: TupleItem[]): {
9
+ amountOut: bigint;
10
+ fee: bigint;
11
+ };
12
+ export type TradeEvent = {
13
+ kind: "CurveTrade";
14
+ isBuy: boolean;
15
+ trader: string;
16
+ ton: bigint;
17
+ coin: bigint;
18
+ fee: bigint;
19
+ virtualTon: bigint;
20
+ virtualToken: bigint;
21
+ realTon: bigint;
22
+ } | {
23
+ kind: "PoolSwap";
24
+ isBuy: boolean;
25
+ trader: string;
26
+ ton: bigint;
27
+ coin: bigint;
28
+ lpFee: bigint;
29
+ protocolFee: bigint;
30
+ reserveTon: bigint;
31
+ reserveCoin: bigint;
32
+ } | {
33
+ kind: "CurveGraduated";
34
+ pool: string;
35
+ liquidityTon: bigint;
36
+ liquidityCoin: bigint;
37
+ protocolFee: bigint;
38
+ } | {
39
+ kind: "LaunchCreated";
40
+ seq: bigint;
41
+ creator: string;
42
+ master: string;
43
+ vault: string;
44
+ splitter: string;
45
+ curve: string;
46
+ rewardCount: number;
47
+ firstBuyTon: bigint;
48
+ codeVersion: number;
49
+ preminted: bigint;
50
+ };
51
+ /** Topic of an external out-message destination, or null when it is not an ONRANK log. */
52
+ export declare function topicOf(dest: ExternalAddress | null | undefined): bigint | null;
53
+ /** Decodes the trading-relevant events; returns null for other topics. Throws on a malformed body. */
54
+ export declare function decodeTradeEvent(topic: bigint, body: Cell): TradeEvent | null;
55
+ /** Address helper: accepts raw (`0:…`) or friendly forms. */
56
+ export declare function parseAddress(a: string): Address;
package/dist/decode.js ADDED
@@ -0,0 +1,68 @@
1
+ // Decoders for get-method results and events. Get-method stacks are `TupleItem[]` as returned by @ton/ton /
2
+ // @ton/core's TupleReader (toncenter v2 `runGetMethod` stacks need the usual num/cell conversion first).
3
+ import { Address, TupleReader } from "@ton/core";
4
+ import { Topic } from "./constants.js";
5
+ function optAddress(r) {
6
+ const item = r.peek();
7
+ if (item.type === "null") {
8
+ r.pop();
9
+ return null;
10
+ }
11
+ return r.readAddress().toString();
12
+ }
13
+ /** `get_curve_data` → the six fields the quotes need (roles / params cells are left on the stack). */
14
+ export function decodeCurveData(items) {
15
+ const r = new TupleReader(items);
16
+ return { state: r.readNumber(), virtualTon: r.readBigNumber(), virtualToken: r.readBigNumber(), realTon: r.readBigNumber(), sold: r.readBigNumber(), pool: optAddress(r) };
17
+ }
18
+ /** `get_pool_data`. */
19
+ export function decodePoolData(items) {
20
+ const r = new TupleReader(items);
21
+ return { reserveTon: r.readBigNumber(), reserveCoin: r.readBigNumber(), lpFeeBps: r.readNumber(), protocolFeeBps: r.readNumber(), volumeTon: r.readBigNumber(), nonce: r.readBigNumber(), master: r.readAddress().toString(), splitter: r.readAddress().toString() };
22
+ }
23
+ /** `get_buy_quote` / `get_sell_quote` → `(amountOut, fee)`. */
24
+ export function decodeQuote(items) {
25
+ const r = new TupleReader(items);
26
+ return { amountOut: r.readBigNumber(), fee: r.readBigNumber() };
27
+ }
28
+ /** Topic of an external out-message destination, or null when it is not an ONRANK log. */
29
+ export function topicOf(dest) {
30
+ if (!dest || dest.bits !== 256)
31
+ return null;
32
+ if (dest.value >> 248n !== 0n)
33
+ return null;
34
+ return dest.value;
35
+ }
36
+ /** Decodes the trading-relevant events; returns null for other topics. Throws on a malformed body. */
37
+ export function decodeTradeEvent(topic, body) {
38
+ const s = body.beginParse();
39
+ switch (topic) {
40
+ case Topic.CurveTrade:
41
+ return { kind: "CurveTrade", isBuy: s.loadBit(), trader: s.loadAddress().toString(), ton: s.loadCoins(), coin: s.loadCoins(), fee: s.loadCoins(), virtualTon: s.loadCoins(), virtualToken: s.loadCoins(), realTon: s.loadCoins() };
42
+ case Topic.PoolSwap:
43
+ return { kind: "PoolSwap", isBuy: s.loadBit(), trader: s.loadAddress().toString(), ton: s.loadCoins(), coin: s.loadCoins(), lpFee: s.loadCoins(), protocolFee: s.loadCoins(), reserveTon: s.loadCoins(), reserveCoin: s.loadCoins() };
44
+ case Topic.CurveGraduated:
45
+ return { kind: "CurveGraduated", pool: s.loadAddress().toString(), liquidityTon: s.loadCoins(), liquidityCoin: s.loadCoins(), protocolFee: s.loadCoins() };
46
+ case Topic.LaunchCreated: {
47
+ const seq = s.loadUintBig(64);
48
+ const creator = s.loadAddress().toString();
49
+ const addrs = s.loadRef().beginParse();
50
+ const master = addrs.loadAddress().toString();
51
+ const vault = addrs.loadAddress().toString();
52
+ const more = addrs.loadRef().beginParse();
53
+ const splitter = more.loadAddress().toString();
54
+ const curve = more.loadAddress().toString();
55
+ const rewardCount = s.loadUint(8);
56
+ const firstBuyTon = s.loadCoins();
57
+ const codeVersion = s.remainingBits >= 16 ? s.loadUint(16) : 1;
58
+ const preminted = s.remainingBits >= 4 ? s.loadCoins() : 0n;
59
+ return { kind: "LaunchCreated", seq, creator, master, vault, splitter, curve, rewardCount, firstBuyTon, codeVersion, preminted };
60
+ }
61
+ default:
62
+ return null;
63
+ }
64
+ }
65
+ /** Address helper: accepts raw (`0:…`) or friendly forms. */
66
+ export function parseAddress(a) {
67
+ return a.includes(":") ? Address.parseRaw(a) : Address.parse(a);
68
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./constants.js";
2
+ export * from "./messages.js";
3
+ export * from "./quotes.js";
4
+ export * from "./decode.js";
5
+ export * from "./client.js";
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // @onrank/sdk — quote and trade ONRANK coins from any product. Pure builders (no keys, no network); the optional
2
+ // `OnrankClient` in ./client.ts adds toncenter reads and the public REST API.
3
+ export * from "./constants.js";
4
+ export * from "./messages.js";
5
+ export * from "./quotes.js";
6
+ export * from "./decode.js";
7
+ export * from "./client.js";
@@ -0,0 +1,57 @@
1
+ import { Address } from "@ton/core";
2
+ import { type LauncherVersion } from "./constants.js";
3
+ /** What a wallet expects: destination, nanoTON to attach (decimal string), base64 BoC body. */
4
+ export type TonConnectMessage = {
5
+ address: string;
6
+ amount: string;
7
+ payload: string;
8
+ };
9
+ /** Any 64-bit query id; the default is time-based, good enough to tell transactions apart in a wallet history. */
10
+ export declare function defaultQueryId(): bigint;
11
+ /**
12
+ * Buy on the curve. `tonIn` is what gets swapped; the message attaches `tonIn + Gas.curveBuy`.
13
+ * `minOut`: minimum coins accepted (quote × (1 − slippage)); the curve refunds everything below it (exit 479).
14
+ * `recipient` defaults to the signer — leave it unset unless you intentionally buy for someone else.
15
+ */
16
+ export declare function buildCurveBuy(p: {
17
+ curve: Address;
18
+ tonIn: bigint;
19
+ minOut: bigint;
20
+ queryId?: bigint;
21
+ recipient?: Address;
22
+ }): TonConnectMessage;
23
+ /** Buy on the pool after graduation. Attaches `tonIn + SWAP_GAS` (0.15 TON on v2, 0.2 on v1). */
24
+ export declare function buildPoolBuy(p: {
25
+ pool: Address;
26
+ tonIn: bigint;
27
+ minOut: bigint;
28
+ queryId?: bigint;
29
+ version?: LauncherVersion;
30
+ }): TonConnectMessage;
31
+ /**
32
+ * Sell on the curve **or** the v2 pool: a burn on the owner's RewardWallet with a SellIntent. The master routes it to
33
+ * whichever market is live and pays `owner` in TON; a refused sale re-mints the coins to `owner`.
34
+ * `rewardWallet` = `get_wallet_address(owner)` on the coin's master. `minTon`: minimum TON accepted.
35
+ */
36
+ export declare function buildSell(p: {
37
+ rewardWallet: Address;
38
+ owner: Address;
39
+ coinAmount: bigint;
40
+ minTon: bigint;
41
+ queryId?: bigint;
42
+ version?: LauncherVersion;
43
+ }): TonConnectMessage;
44
+ /**
45
+ * v1 pool sell only (legacy coins): TEP-74 transfer of the coins to the pool with a SwapCoinForTon forward payload.
46
+ * v2 pools take `buildSell` instead.
47
+ */
48
+ export declare function buildPoolSellV1(p: {
49
+ rewardWallet: Address;
50
+ pool: Address;
51
+ owner: Address;
52
+ coinAmount: bigint;
53
+ minTon: bigint;
54
+ queryId?: bigint;
55
+ }): TonConnectMessage;
56
+ /** `quote × (10000 − slippageBps) / 10000`, floored — the value to pass as minOut / minTon. */
57
+ export declare function applySlippage(quote: bigint, slippageBps: number): bigint;
@@ -0,0 +1,74 @@
1
+ // Unsigned messages, ready for TON Connect `sendTransaction({ messages: [msg] })`. Pure functions: no network, no keys.
2
+ // Layouts mirror the contracts (contracts/contracts/launcher-v2/*.tolk) bit for bit.
3
+ import { beginCell } from "@ton/core";
4
+ import { Gas, Op } from "./constants.js";
5
+ const boc = (c) => c.toBoc().toString("base64");
6
+ const msg = (to, amount, body) => ({ address: to.toString({ bounceable: true }), amount: amount.toString(), payload: boc(body) });
7
+ /** Any 64-bit query id; the default is time-based, good enough to tell transactions apart in a wallet history. */
8
+ export function defaultQueryId() {
9
+ return (BigInt(Date.now()) << 20n) | BigInt(Math.floor(Math.random() * 0xfffff));
10
+ }
11
+ /**
12
+ * Buy on the curve. `tonIn` is what gets swapped; the message attaches `tonIn + Gas.curveBuy`.
13
+ * `minOut`: minimum coins accepted (quote × (1 − slippage)); the curve refunds everything below it (exit 479).
14
+ * `recipient` defaults to the signer — leave it unset unless you intentionally buy for someone else.
15
+ */
16
+ export function buildCurveBuy(p) {
17
+ if (p.tonIn <= 0n)
18
+ throw new Error("tonIn must be > 0");
19
+ if (p.minOut <= 0n)
20
+ throw new Error("minOut must be > 0 (never trade without slippage protection)");
21
+ const body = beginCell().storeUint(Op.Buy, 32).storeUint(p.queryId ?? defaultQueryId(), 64).storeCoins(p.minOut).storeAddress(p.recipient ?? null).endCell();
22
+ return msg(p.curve, p.tonIn + Gas.curveBuy, body);
23
+ }
24
+ /** Buy on the pool after graduation. Attaches `tonIn + SWAP_GAS` (0.15 TON on v2, 0.2 on v1). */
25
+ export function buildPoolBuy(p) {
26
+ if (p.tonIn <= 0n)
27
+ throw new Error("tonIn must be > 0");
28
+ if (p.minOut <= 0n)
29
+ throw new Error("minOut must be > 0 (never trade without slippage protection)");
30
+ const body = beginCell().storeUint(Op.SwapTonForCoin, 32).storeUint(p.queryId ?? defaultQueryId(), 64).storeCoins(p.minOut).endCell();
31
+ return msg(p.pool, p.tonIn + ((p.version ?? 2) === 2 ? Gas.poolSwapV2 : Gas.poolSwapV1), body);
32
+ }
33
+ /**
34
+ * Sell on the curve **or** the v2 pool: a burn on the owner's RewardWallet with a SellIntent. The master routes it to
35
+ * whichever market is live and pays `owner` in TON; a refused sale re-mints the coins to `owner`.
36
+ * `rewardWallet` = `get_wallet_address(owner)` on the coin's master. `minTon`: minimum TON accepted.
37
+ */
38
+ export function buildSell(p) {
39
+ if (p.coinAmount <= 0n)
40
+ throw new Error("coinAmount must be > 0");
41
+ if (p.minTon <= 0n)
42
+ throw new Error("minTon must be > 0 (never trade without slippage protection)");
43
+ const intent = beginCell().storeUint(Op.SellIntent, 32).storeCoins(p.minTon).endCell();
44
+ const body = beginCell().storeUint(Op.Burn, 32).storeUint(p.queryId ?? defaultQueryId(), 64).storeCoins(p.coinAmount).storeAddress(p.owner).storeMaybeRef(intent).endCell();
45
+ return msg(p.rewardWallet, ((p.version ?? 2) === 2 ? Gas.walletBurnV2 : Gas.walletBurnV1) + Gas.margin, body);
46
+ }
47
+ /**
48
+ * v1 pool sell only (legacy coins): TEP-74 transfer of the coins to the pool with a SwapCoinForTon forward payload.
49
+ * v2 pools take `buildSell` instead.
50
+ */
51
+ export function buildPoolSellV1(p) {
52
+ if (p.coinAmount <= 0n)
53
+ throw new Error("coinAmount must be > 0");
54
+ if (p.minTon <= 0n)
55
+ throw new Error("minTon must be > 0");
56
+ const swap = beginCell().storeUint(Op.SwapCoinForTon, 32).storeCoins(p.minTon).endCell();
57
+ const body = beginCell()
58
+ .storeUint(Op.Transfer, 32)
59
+ .storeUint(p.queryId ?? defaultQueryId(), 64)
60
+ .storeCoins(p.coinAmount)
61
+ .storeAddress(p.pool)
62
+ .storeAddress(p.owner)
63
+ .storeMaybeRef(null)
64
+ .storeCoins(Gas.poolSellForward)
65
+ .storeMaybeRef(swap)
66
+ .endCell();
67
+ return msg(p.rewardWallet, Gas.poolSellForward + Gas.walletTransfer + Gas.margin, body);
68
+ }
69
+ /** `quote × (10000 − slippageBps) / 10000`, floored — the value to pass as minOut / minTon. */
70
+ export function applySlippage(quote, slippageBps) {
71
+ if (!Number.isInteger(slippageBps) || slippageBps < 0 || slippageBps >= 10_000)
72
+ throw new Error("slippageBps must be an integer in [0, 10000)");
73
+ return (quote * BigInt(10_000 - slippageBps)) / 10000n;
74
+ }
@@ -0,0 +1,57 @@
1
+ export type Quote = {
2
+ amountOut: bigint;
3
+ fee: bigint;
4
+ };
5
+ /** What `get_curve_data` returns (first six fields; roles and params are cells). */
6
+ export type CurveState = {
7
+ /** 1 = open (trades accepted); anything else: not opened yet or graduated */
8
+ state: number;
9
+ virtualTon: bigint;
10
+ virtualToken: bigint;
11
+ /** TON really held by the curve (what backs sells) */
12
+ realTon: bigint;
13
+ /** coins sold so far (out of `curveSupply`) */
14
+ sold: bigint;
15
+ /** set at graduation */
16
+ pool: string | null;
17
+ };
18
+ /** Curve parameters that the formulas need (Factory defaults on mainnet: feeBps 100, curveSupply 800 M coins). */
19
+ export type CurveParams = {
20
+ feeBps: number;
21
+ curveSupply: bigint;
22
+ };
23
+ export declare const MAINNET_CURVE_PARAMS: CurveParams;
24
+ /**
25
+ * Coins received for `tonAttached` sent to the curve (BUY_GAS is deducted here, like `get_buy_quote`).
26
+ * Returns amountOut 0 when the attached value does not cover the gas.
27
+ */
28
+ export declare function quoteCurveBuy(s: CurveState, tonAttached: bigint, params?: CurveParams): Quote;
29
+ /** Same as `quoteCurveBuy` but from the amount you want swapped (the message attaches gas on top). */
30
+ export declare function quoteCurveBuyForTonIn(s: CurveState, tonIn: bigint, params?: CurveParams): Quote;
31
+ /** Net TON received for selling `coinIn` on the curve (`get_sell_quote`). The contract also needs gross ≤ realTon. */
32
+ export declare function quoteCurveSell(s: CurveState, coinIn: bigint, params?: CurveParams): Quote;
33
+ /** Whether the curve can still sell: open and not graduated. */
34
+ export declare function isCurveOpen(s: CurveState): boolean;
35
+ /** What `get_pool_data` returns. */
36
+ export type PoolState = {
37
+ reserveTon: bigint;
38
+ reserveCoin: bigint;
39
+ lpFeeBps: number;
40
+ protocolFeeBps: number;
41
+ volumeTon: bigint;
42
+ nonce: bigint;
43
+ master: string;
44
+ splitter: string;
45
+ };
46
+ /** Coins received for `tonIn` (after SWAP_GAS) on the pool (`get_buy_quote`). */
47
+ export declare function quotePoolBuy(p: PoolState, tonIn: bigint): Quote;
48
+ /** Net TON received for `coinIn` on the pool (`get_sell_quote`). */
49
+ export declare function quotePoolSell(p: PoolState, coinIn: bigint): Quote;
50
+ /** Spot price in nanoTON per whole coin (1e9 units), from either market's reserves. */
51
+ export declare function spotPriceNano(s: {
52
+ virtualTon: bigint;
53
+ virtualToken: bigint;
54
+ } | {
55
+ reserveTon: bigint;
56
+ reserveCoin: bigint;
57
+ }): bigint;
package/dist/quotes.js ADDED
@@ -0,0 +1,69 @@
1
+ // Quote formulas copied from the contracts (CurveV2.tolk, PoolV2.tolk). Integer arithmetic on bigint, same rounding.
2
+ // Pin them with the fixtures in test/quotes.test.ts (live get_buy_quote / get_sell_quote results).
3
+ import { Gas } from "./constants.js";
4
+ export const MAINNET_CURVE_PARAMS = { feeBps: 100, curveSupply: 800000000n * 1000000000n };
5
+ const coinOut = (s, tonIn) => (s.virtualToken * tonIn) / (s.virtualTon + tonIn);
6
+ const tonInFor = (s, out) => {
7
+ const num = s.virtualTon * out;
8
+ const den = s.virtualToken - out;
9
+ return (num + den - 1n) / den;
10
+ };
11
+ const tonOut = (s, coinIn) => (s.virtualTon * coinIn) / (s.virtualToken + coinIn);
12
+ const feeOnNet = (net, feeBps) => (net * BigInt(feeBps)) / BigInt(10_000 - feeBps);
13
+ const min = (a, b) => (a < b ? a : b);
14
+ /**
15
+ * Coins received for `tonAttached` sent to the curve (BUY_GAS is deducted here, like `get_buy_quote`).
16
+ * Returns amountOut 0 when the attached value does not cover the gas.
17
+ */
18
+ export function quoteCurveBuy(s, tonAttached, params = MAINNET_CURVE_PARAMS) {
19
+ if (tonAttached <= Gas.curveBuy)
20
+ return { amountOut: 0n, fee: 0n };
21
+ const amountIn = tonAttached - Gas.curveBuy;
22
+ let fee = (amountIn * BigInt(params.feeBps)) / 10000n;
23
+ let out = coinOut(s, amountIn - fee);
24
+ const remaining = params.curveSupply - s.sold;
25
+ if (out > remaining) {
26
+ // last lot: the buyer pays exactly what those coins cost, fee included; the rest is refunded by the contract
27
+ out = remaining;
28
+ const net = tonInFor(s, out);
29
+ fee = min(feeOnNet(net, params.feeBps), amountIn - net);
30
+ }
31
+ return { amountOut: out, fee };
32
+ }
33
+ /** Same as `quoteCurveBuy` but from the amount you want swapped (the message attaches gas on top). */
34
+ export function quoteCurveBuyForTonIn(s, tonIn, params = MAINNET_CURVE_PARAMS) {
35
+ return quoteCurveBuy(s, tonIn + Gas.curveBuy, params);
36
+ }
37
+ /** Net TON received for selling `coinIn` on the curve (`get_sell_quote`). The contract also needs gross ≤ realTon. */
38
+ export function quoteCurveSell(s, coinIn, params = MAINNET_CURVE_PARAMS) {
39
+ const gross = tonOut(s, coinIn);
40
+ const fee = (gross * BigInt(params.feeBps)) / 10000n;
41
+ return { amountOut: gross - fee, fee };
42
+ }
43
+ /** Whether the curve can still sell: open and not graduated. */
44
+ export function isCurveOpen(s) {
45
+ return s.state === 1 && s.pool === null;
46
+ }
47
+ /** Coins received for `tonIn` (after SWAP_GAS) on the pool (`get_buy_quote`). */
48
+ export function quotePoolBuy(p, tonIn) {
49
+ const lpFee = (tonIn * BigInt(p.lpFeeBps)) / 10000n;
50
+ const protocolFee = (tonIn * BigInt(p.protocolFeeBps)) / 10000n;
51
+ const net = tonIn - lpFee - protocolFee;
52
+ if (p.reserveCoin === 0n)
53
+ return { amountOut: 0n, fee: lpFee + protocolFee };
54
+ return { amountOut: (p.reserveCoin * net) / (p.reserveTon + net), fee: lpFee + protocolFee };
55
+ }
56
+ /** Net TON received for `coinIn` on the pool (`get_sell_quote`). */
57
+ export function quotePoolSell(p, coinIn) {
58
+ if (p.reserveCoin === 0n)
59
+ return { amountOut: 0n, fee: 0n };
60
+ const gross = (p.reserveTon * coinIn) / (p.reserveCoin + coinIn);
61
+ const lpFee = (gross * BigInt(p.lpFeeBps)) / 10000n;
62
+ const protocolFee = (gross * BigInt(p.protocolFeeBps)) / 10000n;
63
+ return { amountOut: gross - lpFee - protocolFee, fee: lpFee + protocolFee };
64
+ }
65
+ /** Spot price in nanoTON per whole coin (1e9 units), from either market's reserves. */
66
+ export function spotPriceNano(s) {
67
+ const [ton, coin] = "virtualTon" in s ? [s.virtualTon, s.virtualToken] : [s.reserveTon, s.reserveCoin];
68
+ return coin === 0n ? 0n : (ton * 1000000000n) / coin;
69
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@onrank/sdk",
3
+ "version": "0.1.0",
4
+ "description": "ONRANK (TON launchpad) integration SDK: on-chain quotes and unsigned TON Connect messages for buying and selling coins on the curve and the pool. No keys, no network required.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.build.json",
21
+ "typecheck": "tsc --noEmit",
22
+ "test": "vitest run",
23
+ "prepublishOnly": "npm run typecheck && npm test && npm run build"
24
+ },
25
+ "peerDependencies": {
26
+ "@ton/core": ">=0.56.0"
27
+ },
28
+ "devDependencies": {
29
+ "@ton/core": "^0.63.1",
30
+ "@ton/crypto": "^3.3.0",
31
+ "typescript": "^5.8.3",
32
+ "vitest": "^3.2.4"
33
+ },
34
+ "keywords": [
35
+ "ton",
36
+ "onrank",
37
+ "launchpad",
38
+ "bonding-curve",
39
+ "ton-connect",
40
+ "trading-bot"
41
+ ],
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/SOLEIIL/onrank-sdk"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "homepage": "https://onrank.lol/developers",
50
+ "bugs": {
51
+ "url": "https://github.com/SOLEIIL/onrank-sdk/issues"
52
+ }
53
+ }