@tontonfun/sdk
v0.1.1
Published
TypeScript SDK for the tonton bonding-curve launchpad on TON
Maintainers
Readme
@tontonfun/sdk
TypeScript SDK for the tonton bonding-curve launchpad on TON.
Wallet-agnostic — every transaction builder returns a plain
{ to, value, body } envelope. Plug it into TonConnect, @ton/ton's
WalletContract, blueprint, or any other sender.
Install
npm install @tontonfun/sdk @ton/core @ton/tonQuick start
Read curve state
import { TonClient } from '@ton/ton';
import { Address } from '@ton/core';
import { getCurveState } from '@tontonfun/sdk';
const client = new TonClient({
endpoint: 'https://toncenter.com/api/v2/jsonRPC',
apiKey: process.env.TONCENTER_API_KEY,
});
const state = await getCurveState(client, Address.parse('EQBM0PUX...'));
// { tokensSold, tonCollected, graduated, launched, initSettled, jettonMaster }Buy
import { prepareBuyTx, envelopeToTonConnect, tonToNano } from '@tontonfun/sdk';
const tx = prepareBuyTx({
curve: 'EQBM0PUX...', // curve address
tonAmount: tonToNano(1), // user spends 1 TON
curveState: state,
slippagePct: 5, // optional, default 5
});
// Hand off to TonConnect:
await tonConnectUI.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [envelopeToTonConnect(tx)],
});
console.log(`Expect ~${tx.quote.jettonsOut} jettons, min ${tx.quote.minJettonsOut}`);Sell
import {
prepareSellTx,
envelopeToTonConnect,
getJettonWalletAddress,
} from '@tontonfun/sdk';
// Resolve the seller's jetton wallet for this token.
const sellerJettonWallet = await getJettonWalletAddress(
client,
state.jettonMaster,
Address.parse(userAddress),
);
const tx = prepareSellTx({
sellerJettonWallet,
curve: 'EQBM0PUX...',
seller: userAddress,
jettonAmount: 500_000_000_000_000n, // 500k tokens (9 decimals)
curveState: state,
// slippagePct defaults to 0 — see "Slippage gotcha" below
});
await tonConnectUI.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [envelopeToTonConnect(tx)],
});Launch a new token
import {
buildLaunchToken,
buildOffchainContent,
newRandomQueryId,
LAUNCH_VALUE_TON,
MAINNET_FACTORY,
} from '@tontonfun/sdk';
const content = buildOffchainContent('ipfs://bafy.../metadata.json');
const body = buildLaunchToken({ queryId: newRandomQueryId(), content });
await tonConnectUI.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [{
address: MAINNET_FACTORY,
amount: LAUNCH_VALUE_TON.toString(),
payload: body.toBoc().toString('base64'),
}],
});Off-chain quoting
All math mirrors the deployed contract bit-for-bit. Use it for previews, charts, and "what would I get" calculators without an RPC round-trip:
import {
quoteBuyDetailed,
quoteSellOffchain,
spotPriceTonPerToken,
curveProgress,
} from '@tontonfun/sdk';
const { jettonsOut, protocolFee, creatorFee, refund } = quoteBuyDetailed(
tonToNano(1),
state.tokensSold,
state.tonCollected,
);
const price = spotPriceTonPerToken(state.tokensSold, state.tonCollected);
const progress = curveProgress(state.tokensSold); // 0..1Tap-to-earn custom games
If a creator enables tap-to-earn and wants to build their own game UI, use the tap client against the hosted tonton API. The launch id is the curve address.
Browser game flow
Use TonConnect proof auth for normal wallets, then send tap events with a fresh idempotency key per click or batch.
import { TapToEarnClient, createIdempotencyKey } from '@tontonfun/sdk';
const tap = new TapToEarnClient({
baseUrl: 'https://api.tonton.fun',
launch: curveAddress,
});
const { payload } = await tap.createTonProofPayload();
// Give `payload` to TonConnect as the proof payload when connecting.
// After wallet connection:
const session = await tap.createTonProofSession({
wallet: tonConnectWallet.account.address,
account: tonConnectWallet.account,
proof: tonConnectWallet.connectItems?.tonProof?.proof,
});
await tap.tap(session.sessionToken, {
idempotencyKey: createIdempotencyKey('tap'),
points: '1',
metadata: { source: 'my-custom-game' },
});
const player = await tap.getPlayer(tonConnectWallet.account.address);
const leaderboard = await tap.getLeaderboard({ limit: 25 });Server-side missions, referrals, or custom scoring
Creators can generate a launch API key from their tap page settings. Keep that key on your server only, then submit mission/referral points in batches.
import { TapToEarnServerClient, createIdempotencyKey } from '@tontonfun/sdk';
const tapServer = new TapToEarnServerClient({
baseUrl: 'https://api.tonton.fun',
launch: process.env.TONTON_CURVE_ADDRESS!,
launchKey: process.env.TONTON_TAP_KEY!,
});
await tapServer.addPoints([
{
idempotencyKey: createIdempotencyKey('mission'),
wallet: userWallet,
points: '50',
eventType: 'mission',
metadata: { mission: 'follow-x' },
},
]);Claims
After the tap window is finalized, fetch the claim proof and send the claim transaction from the user wallet.
const claim = await tap.getClaim(userWallet);
const status = await tap.getClaimStatus(userWallet);
const manifest = await tap.getManifest();Gotchas
Body format — Tact, not strict TEP-74
The deployed wallet and curve contracts are generated by Tact,
where the jetton forwardPayload: Slice as remaining field appends the
slice's bits raw, with no Either-tag prefix. If you build your own sell
body and add a storeBit(false) (TEP-74 strict inline), the curve will read
the SELL op shifted by 1 bit (0x29A2A626 instead of 0x53454C4C), fail its
op check, and refund every time. buildSellTransfer in this SDK handles it
correctly — don't hand-roll the body.
Sell slippage is off by default
Any positive minTonOut can trip the contract's quoteSell-vs-sell drift on
fast-moving state and trigger a refund. prepareSellTx defaults
slippagePct to 0 (translates to minTonOut = 0). Bound exposure by
ensuring jettonAmount <= curveState.tokensSold — sells larger than
tokensSold hit the contract's "more out than owed" refund path.
forward_ton_amount upper bound
The curve refunds when the notification's TON value exceeds ~0.1 TON. Don't
override forwardTon above 100_000_000n unless you've verified the new
limit on chain.
What's not in this SDK
- Wallet senders. TonConnect, mnemonic-signing wallets, browser extensions — the SDK returns envelopes; you choose how to sign and send.
- IPFS / metadata upload. Use Pinata, web3.storage, or your own pinning
service; the SDK only consumes the resulting
ipfs://URI. - Indexing. The decoders in
codec.ts(tryDecodeBuyTokens,tryDecodeTransferNotify, etc.) are enough to build your own indexer, but the indexer itself isn't shipped. - Tap API keys in browsers. Launch keys are server-side secrets. Browser tap games should use TonConnect proof sessions instead.
License
MIT
