A decentralized order book system on the TON blockchain for peer-to-peer token exchanges (jetton ↔ jetton, TON ↔ jetton) with slippage protection, fee management, and order matching capabilities.
Status: Passed ✅
The smart contracts have been audited and verified. Full audit report is available here:
- Audit
- Project Structure
- Architecture
- Core Contracts
- Utility Modules
- Precision Constants
- Fee Structure
- Slippage Protection
- Bounce Handling
- Exchange Flow
- TypeScript Wrappers
- Scripts
- Testing
- Deployed Contracts
- Development
- Contributing
contracts/ Smart contracts (Tolk)
utils/ Shared utilities (errors, opcodes, fees, types, messages, fee_calc)
wrappers/ TypeScript wrappers implementing Contract from @ton/core
tests/ Test suites (Jest + @ton/sandbox)
scripts/ Deployment and interaction scripts (Blueprint)
helpers/ Shared script utilities (API client)
The system consists of four core contracts with a modular utility-based architecture:
VaultFactory ──creates──► Vault (VaultJetton / VaultTon)
│
├── creates ──► Order
├── sends fees ──► FeeCollector
└── transfers tokens to matched orders
Two separate VaultFactory instances are deployed — one compiled with VaultJetton code (for jetton vaults) and one with VaultTon code (for TON vaults). Each factory produces vaults of its respective type.
Represents a single trading order with exchange parameters.
Storage:
| Field | Type | Description |
|---|---|---|
| owner | address | Order creator |
| vault | address | Vault holding funds for this order |
| oppositeVault | address | Vault of the counter-party token |
| feeInfo | Cell<FeeInfo>? | Provider and matcher fee configuration |
| exchangeInfo | Cell<ExchangeInfo> | Amount, priceRate, slippage, jetton info |
| createdAt | uint32 | Creation timestamp |
| closeFlag | bool | Whether the order has been closed |
Operations:
| Opcode | Name | Description |
|---|---|---|
0x2d0e1e1b |
InitOrder | Initialize with amount, priceRate, slippage, feeInfo |
0x47ff7e25 |
MatchOrder | Initiate matching (called by matcher) |
0xdfe29f63 |
InternalMatchOrder | Internal matching with slippage/fee validation (from vault) |
0x55feb42a |
SuccessMatch | Confirm match and update state |
0x52e80bac |
CloseOrder | Close order and return remaining funds |
Holds jetton tokens and manages jetton-based order creation and transfers.
Storage:
| Field | Type | Description |
|---|---|---|
| jettonWalletAddress | address? | Associated jetton wallet address |
| vault_factory | address | Parent factory |
| codesInfo | Cell<CodesInfo> | Order + FeeCollector compiled code |
| fromJetton | Cell<JettonInfo>? | Jetton minter info |
| randomHash | uint256 | Random hash for vault uniqueness |
| amount | coins | Total deposited amount |
Holds native TON and manages TON-based order creation and transfers.
Storage:
| Field | Type | Description |
|---|---|---|
| vault_factory | address | Parent factory |
| codesInfo | Cell<CodesInfo> | Order + FeeCollector compiled code |
| randomHash | uint256 | Random hash for vault uniqueness |
| amount | coins | Total deposited amount |
Factory for deploying vault instances. Each factory is compiled with a specific vault code (VaultJetton or VaultTon).
Operations:
| Opcode | Name | Description |
|---|---|---|
0x2717c4a2 |
CreateVault | Deploy a new vault |
Parameters for sendCreateVault:
jettonMaster: Address | null— jetton minter address (null for TON vaults)jettonWalletAddress: Address | null— pre-computed jetton wallet address for the vaultrandomHash: bigint | null— random hash for uniqueness
Per-vault, per-matcher contract that accumulates matcher fees from successful matches.
Storage:
| Field | Type | Description |
|---|---|---|
| vault | address | Associated vault |
| owner | address | Matcher (fee recipient) |
| amount | coins | Accumulated fees |
Operations:
| Opcode | Name | Description |
|---|---|---|
0xfc7532f4 |
AddFee | Add fee (called by vault only) |
0xec9a92f6 |
WithDraw | Withdraw all fees (called by owner only) |
Address derivation: FeeCollector addresses are deterministic, computed from contractAddress(workchain, { code: feeCollectorCode, data: cell(vault, owner, 0) }). This allows off-chain pre-computation.
All contracts share a set of utility modules in contracts/utils/:
| Module | Purpose |
|---|---|
messages.tolk |
Message sending: sendSimpleMessage, sendBouncedMessage, sendJettonTransfer, sendFeeToCollector, sendFeeToProvider |
fee_calc.tolk |
calculateFees (provider + matcher fee deduction), addFeesToRate (adjust rate for slippage validation) |
types.tolk |
Shared structs: JettonInfo, FeeInfo, TransferAddresses |
generate_addresses.tolk |
Deterministic address generation: generateOrder, generateFeeCollector |
fees.tolk |
Gas constants for all operations |
errors.tolk |
Error codes |
op_codes.tolk |
Operation codes |
| Code | Name | Description |
|---|---|---|
| 403 | ERR_INVALID_SENDER | Unauthorized sender |
| 406 | ERR_AMOUNT_NOT_AVAILABLE | Insufficient order amount |
| 407 | ERR_INVALID_AMOUNT | Invalid amount value |
| 422 | ERR_INSUFFICIENT_GAS | Not enough gas |
| 427 | ERR_FORWARD_PAYLOAD_REQUIRED | Missing forward payload |
| 428 | ERR_FROM_JETTON_REQUIRED | From-jetton info required |
| 429 | ERR_INVALID_JETTON_WALLET | Jetton wallet mismatch |
| 430 | ERR_INVALID_SLIPPAGE | Slippage check failed |
| 431 | ERR_ORDER_ALREADY_INITIALIZED | Double initialization attempt |
| 432 | ERR_VAULT_ALREADY_HAS_JETTON | Vault jetton already set |
| 433 | ERR_CLOSE_FLAG | Order already closed |
| Constant | Value (TON) |
|---|---|
| GAS_STORAGE | 0.01 |
| GAS_MIN_ORDER_STORAGE | 0.003 |
| GAS_ORDER_FULL_MATCH | 1.0 |
| GAS_JETTON_WALLET_TRANSFER | 0.05 |
| GAS_VAULT_FACTORY_CREATE_VAULT | 0.03 |
| GAS_VAULT_FACTORY_INIT | 0.01 |
| GAS_VAULT_JETTON_TRANSFER | 0.01883 |
| GAS_VAULT_TON_TRANSFER | 0.1 |
| GAS_VAULT_WITHDRAW | 0.004154 |
| GAS_ORDER_CLOSE_ORDER | 0.05 |
| GAS_ORDER_INIT | 0.04 |
| GAS_CREATE_ORDER_JETTON | 0.1 |
| GAS_FEE_COLLECTOR_WITHDRAW | 0.05 |
| GAS_FEE_COLLECTOR_ADD_FEE | 0.001 |
The system uses two precision bases for on-chain fixed-point arithmetic:
| Constant | Value | Usage |
|---|---|---|
| PRICE_PRECISION | 10^18 (1e18) |
Base unit for priceRate. A rate of PRICE_PRECISION means 1:1 exchange. 2 * PRICE_PRECISION means 1 token = 2 counter-tokens. |
| SLIPPAGE_PRECISION | 10^9 (1e9 = 100%) |
Base unit for slippage tolerance. SLIPPAGE_PRECISION * 2 / 100 = 2% slippage. |
Exchange math:
anotherGetAmount = mulDivFloor(matchAmount, priceRate, PRICE_PRECISION)
getAmount = mulDivFloor(anotherGetAmount, PRICE_PRECISION, priceRate)
Dual fee model with provider and matcher fees:
- Provider Fee: Charged by the vault provider (
feeNum / feeDenomratio) - Matcher Fee: Charged by the order matcher (
matcherFeeNum / matcherFeeDenomratio)
Fee calculation (calculateFees from fee_calc.tolk):
- Calculate provider fee and matcher fee from the transfer amount
- Return net transfer amount after fee deductions
- Handle zero fees gracefully
Fees are deducted during order matching. Matcher fees are accumulated in per-vault, per-matcher FeeCollector contracts. The matcher can withdraw accumulated fees at any time via sendWithDraw.
Orders include slippage tolerance parameters. During matching:
- Price rates are adjusted to include fees using
addFeesToRatefromfee_calc.tolk - Adjusted rates are compared against original rates with slippage tolerance using
isWithinSlippage - Both orders must validate each other's rates — the match proceeds only if prices are within acceptable ranges
The Order contract implements a two-path bounce handler for InternalMatchOrder failures:
Triggered by errors that indicate a malformed or malicious match attempt. The order burns matcher gas by sending to the zero address:
computePhase == null— transaction ran out of gasERR_INVALID_SLIPPAGE(430) — price out of rangeERR_CLOSE_FLAG(433) — order already closed
Triggered by non-fatal errors (e.g., ERR_AMOUNT_NOT_AVAILABLE 406). The order restores its amount and sends OP_CODE_RETURN_MATCHER_FAILED back to the matcher, allowing retry.
1. Deploy VaultFactory ──► VaultFactory (with VaultJetton or VaultTon code)
2. Create Vault ──► VaultFactory.sendCreateVault(jettonMaster, jettonWalletAddress, randomHash)
3. Deposit + Create Order ──► Send jettons/TON to vault → vault auto-creates Order
4. Match Orders ──► Matcher calls order1.sendMatchOrder(anotherOrderOwner, createdAt, amount)
5. Internal Match ──► order1 → vault → order2 (InternalMatchOrder with slippage validation)
6. Success Match ──► order2 → vault → order1 (SuccessMatch with token transfer + fee deduction)
7. Fees ──► Vault sends fees to FeeCollector and provider
8. Close Order ──► Owner calls order.sendCloseOrder() → remaining funds returned
9. Withdraw Fees ──► Matcher calls feeCollector.sendWithDraw()
All wrappers are in wrappers/ and implement the Contract interface from @ton/core:
| Wrapper | File | Description |
|---|---|---|
| Order | Order.ts |
Create orders, match, close, getData |
| VaultJetton | VaultJetton.ts |
Jetton vault interactions |
| VaultTon | VaultTon.ts |
TON vault interactions |
| VaultFactory | VaultFactory.ts |
Create vaults via factory |
| FeeCollector | MatcherFeeCollector.ts |
Withdraw fees, getData |
| JettonWallet | JettonWallet.ts |
Jetton wallet interactions (createOrder via transfer) |
| JettonMinter | JettonMinter.ts |
Jetton minter interactions |
| HighloadWalletV3 | HighloadWalletV3.ts |
Highload wallet for batch operations (up to 254 messages per tx) |
| HighloadQueryId | HighloadQueryId.ts |
Query ID management for HighloadWalletV3 |
All scripts are in scripts/. Blueprint scripts are run with npx blueprint run <scriptName>.
| Script | Run With | Description |
|---|---|---|
deployVaultFactory.ts |
npx blueprint run |
Deploy VaultFactory (jetton or TON variant) |
createVault.ts |
npx blueprint run |
Create a vault with pre-computed jettonWalletAddress |
createOrder.ts |
npx blueprint run |
Create an order with correct PRICE_PRECISION / SLIPPAGE_PRECISION |
matchOrder.ts |
npx blueprint run |
Match two orders |
closeOrder.ts |
npx blueprint run |
Close a single order |
withDrawMatcherFeeCollector.ts |
npx blueprint run |
Withdraw fees from a FeeCollector |
| Script | Run With | Description |
|---|---|---|
batchCloseOrder.ts |
npx blueprint run |
Close all deployed orders for the connected wallet. Fetches orders from the Open4Dev API. |
batchWithdraw.ts |
npx blueprint run |
Withdraw fees from all FeeCollectors. Computes FeeCollector addresses for each vault + connected wallet, checks balances, sends Withdraw. |
Standalone scripts supporting three wallet types: V4R2, V5R1, and HighloadV3.
| Script | Description |
|---|---|
batchCloseOrderMnemonic.ts |
Batch close orders via mnemonic wallet |
batchWithdrawMnemonic.ts |
Batch withdraw fees via mnemonic wallet |
Environment variables:
| Variable | Required | Default | Description |
|---|---|---|---|
MNEMONIC |
Yes | — | 24 words separated by spaces |
WALLET_VERSION |
No | V4R2 |
V4R2 | V5R1 | HighloadV3 |
NETWORK |
No | testnet |
mainnet | testnet |
TONCENTER_API_KEY |
No | — | Toncenter API key (recommended for mainnet) |
Usage:
# V4R2 — sends messages in batches of 4, waits for seqno between batches
MNEMONIC="word1 word2 ..." WALLET_VERSION=V4R2 NETWORK=testnet \
npx ts-node scripts/batchCloseOrderMnemonic.ts
# V5R1 — sends messages in batches of 255
MNEMONIC="word1 word2 ..." WALLET_VERSION=V5R1 NETWORK=mainnet \
npx ts-node scripts/batchWithdrawMnemonic.ts
# HighloadV3 — sends ALL messages in a single transaction (up to 254 per internal transfer, auto-chained)
MNEMONIC="word1 word2 ..." WALLET_VERSION=HighloadV3 NETWORK=mainnet \
npx ts-node scripts/batchCloseOrderMnemonic.tsscripts/helpers/api.ts provides a shared client for the Open4Dev API:
fetchOrders(ownerRawAddress, status?)— fetch orders with pagination (default status:deployed)fetchVaults()— fetch all vaults with pagination
Run tests:
npx jest --runInBand # sequential (recommended — avoids compilation timeouts)
npx blueprint test # or via blueprintTest suites (93 tests across 5 suites):
| Suite | File | Coverage |
|---|---|---|
| Order | Order.spec.ts |
Order creation, matching (full + partial), slippage validation, fee calculations, close order, bounce handling (penalty + non-penalty), PRICE_PRECISION math with non-1:1 rates |
| VaultFactory | VaultFactory.spec.ts |
Vault creation, order creation through vault, matching via vault, partial matches |
| VaultJetton | VaultJetton.spec.ts |
Jetton vault initialization, deposits, transfers |
| VaultTon | VaultTon.spec.ts |
TON vault initialization, deposits, transfers |
| FeeCollector | FeeCollector.spec.ts |
Fee accumulation, withdrawal, authorization |
- Partial matches with rate ≠ 1:1 — verifies exact exchange amounts using PRICE_PRECISION math
- Bounce handling — penalty path (slippage error, close flag, out of gas) and non-penalty path (amount not available → return to matcher)
- All InternalMatchOrder bounce tests verify order amount restoration
- Multi-step partial matching with amount verification after each step
| Vault Type | Mainnet Address |
|---|---|
| VaultFactory with VaultJetton | EQBcZQhXBU9GfDGLjo5tBKFpnq2tmLEBNLpS8ldqAunaDQUa |
| VaultFactory with VaultTon | EQB20cDxfCIfIw4B4XOqNPAF9pDxheBUbNYUjCYzakbMdMMl |
| Property | Value |
|---|---|
| Vault Address | Created via TON VaultFactory |
| Token | Jetton Address | Vault Address |
|---|---|---|
| BUILD | EQBYnUrIlwBrWqp_rl-VxeSBvTR2VmTfC4ManQ657n_BUILD |
EQDJ-N9sbbh2vNmbJ9DANEWpHdZqW2y8qAAnoBS5rY9fVdLO |
| cbBTC | EQDhyPzbIjJT_WnY3gGprjSYUK9fiGMjWMezxO8MZiUdfb_B |
EQCYdHD4Pwz5ZtDlyCmSc5XjnfefXeAK2TE1Vz356xr6ILSZ |
| DOGS | EQCvxJy4eG8hyHBFsZ7eePxrRsUQSFE_jpptRAYBmcG_DOGS |
EQCzqK8_LNpqyXviutwVJUhw30FcAs6YL8HO9cMNCEaAybpt |
| NOT | EQAvlWFDxGF2lXm67y4yzC17wYKD9A0guwPkMs1gOsM__NOT |
EQD7vaWSbY38DqQ0zY2hNvagO2M-AuL7InUHN4_x2ThceN6J |
| PX | EQB420yQsZobGcy0VYDfSKHpG2QQlw-j1f_tPu1J488I__PX |
EQA6kzh2-YZbJa5L9PUu7dDCQDs52-uVQDxBIXFFp6b0ATmZ |
| USDT | EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs |
EQBrozHGTEwumr5ND62CpUXqmfYyi1UucbIj-15ZJnlFLe9U |
| XAUT | EQA1R_LuQCLHlMgOo1S4G7Y7W1cd0FrAkbA10Zq7rddKxi9k |
EQA9cFoL4hcjOsMTYHHxG0hNyGoikG11oabAQuVQwRKFUhq5 |
To be added
- Node.js 18+
- Blueprint (TON development framework)
- Tolk compiler (≥ 1.0.0)
{
"@ton/core": "^0.62.0",
"@ton/ton": "^16.0.0",
"@ton/crypto": "^3.3.0",
"@ton/sandbox": "0.41.0",
"@ton/blueprint": ">=0.38.0"
}npx blueprint build # Compile contracts
npx blueprint test # Run tests
npx blueprint run # Run a script (interactive selection)
npx tsc --noEmit # Type-check TypeScriptIf your jetton is not supported, submit a pull request modifying the calculateJettonWallet function in vault_jetton.tolk.
Warning: If the
calculateJettonWalletfunction in the vault does not match your jetton wallet smart contract, the vault will not create the order.
