In this tutorial, we will explore how to customize the standard implementation of Jetton tokens on TON. We will guide you through the update process using the Blueprint environment and Sandbox. Specifically, we will introduce a capped supply and a mint price per token to our minter contract. Users will be able to mint tokens at the preset price until the capped supply is reached.
Start for free and get your app to production levels immediately. No credit card required. You can sign up with your GitHub, X, Google, or Microsoft account.
Let’s briefly recap what we know about fungible tokens on TON. TON fungible tokens are called Jettons. The Jetton standard is detailed in TEP74, while the specification for token metadata is outlined in TEP64. The Jetton standard (TEP74) covers:
The method for Jetton transfers.
How to retrieve common information (name, circulating supply, etc.) about a given Jetton asset.
Since we have updated the mentioned functions, we now need to update their calls with the respective parameters. Here is an example of the updated getters:
Note that in this tutorial, we will only work with the Jetton Minter wrapper.
Next, we will update the wrapper to include the new capped supply and token price functionality. The wrapper will provide functions to retrieve and manipulate these fields.
1
Updating minter config
We need to update the configuration type in JettonMinter.ts to include the new fields capped_supply and price.
In this version, we are packing the jetton minting message within the minter contract itself. This means that our interface now composes a simple message containing the TON amount and the recipient’s address.
staticmintMessage(from: Address, to: Address, query_id:number| bigint =0){returnbeginCell().storeUint(Op.mint,32).storeUint(query_id,64)// op, queryId.storeAddress(to).endCell();}asyncsendMint(provider: ContractProvider, via: Sender, to: Address, forward_ton_amount: bigint, total_ton_amount: bigint){if(total_ton_amount < forward_ton_amount){thrownewError("Total ton amount should be > forward amount");}await provider.internal(via,{ sendMode: SendMode.PAY_GAS_SEPARATELY, body: JettonMinter.mintMessage(this.address, to), value: total_ton_amount});}
3
Updating getters
As we added new propertes, the getters also must be updated. Note that we modified getJettonData and added getTokenPrice.
We will add test cases to verify that the capped supply and token price functionality work as expected.
1
Testing minting within capped supply
This test verifies jetton minting based on the TON sent, ensuring the correct jetton amount is minted and updates both user balance and total supply accordingly.
it('should mint correct amount of jettons based on the sent TON amount',async()=>{// Calculate costs of mintingconst jettonsToPurchase =(await jettonMinter.getSupplyPrice()).cappedSupply;const jettonsCost = jettonsToPurchase * price;const amountToSend = jettonsCost +toNano('1');// Assuming 1 TON for storage feesconst forwardFee =toNano('0.01');const expectedMintedJettons = jettonsCost / price;// Retrieve initial balance and supplyconst userJettonWallet =awaituserWallet(user.address);const initUserJettonBalance =await userJettonWallet.getJettonBalance();const initJettonSupply =(await jettonMinter.getJettonData()).totalSupply;// Send the minting messageconst res =await jettonMinter.sendMint( user.getSender(), user.address, forwardFee, amountToSend);// Verify the transactionexpect(res.transactions).toHaveTransaction({ on: userJettonWallet.address, op: Op.internal_transfer, success:true, deploy:true});// Verify that the user's minted jettons match the expected amountconst currentUserJettonBalance =await userJettonWallet.getJettonBalance();const mintedUserJettons = currentUserJettonBalance - initUserJettonBalance;expect(mintedUserJettons).toEqual(expectedMintedJettons);// Verify that the total supply matches the expected amount of minted jettonsconst updatedTotalSupply =(await jettonMinter.getJettonData()).totalSupply;const mintedTotalSupply = updatedTotalSupply - initJettonSupply;expect(mintedTotalSupply).toEqual(expectedMintedJettons);printTransactionFees(res.transactions);});
2
Testing minting above capped supply
This test checks that minting beyond the capped supply fails, verifying that the transaction is aborted with the correct exit code.
it('should not mint more than capped supply',async()=>{// Calculate costs of mintingconst jettonsToPurchase =(await jettonMinter.getSupplyPrice()).cappedSupply +1n;const jettonsCost = jettonsToPurchase * price;const amountToSend = jettonsCost +toNano('1');// Assuming 1 TON for storage feesconst forwardFee =toNano('0.01');// Send the minting messageconst res =await jettonMinter.sendMint( user.getSender(), user.address, forwardFee, amountToSend);// Verify the transactionexpect(res.transactions).toHaveTransaction({ from: user.address, to: jettonMinter.address, aborted:true,// High exit codes are considered to be fatal exitCode:256,});});
To test the contracts using Sandbox, run the command:
We walked through the customization of the Jetton token standard on TON, focusing on key update steps using Blueprint and Sandbox. The process involved updating the minter contract, its wrapper, and the associated tests.
Multiple years of software development and Web3 expertise. Creator of the open-source Compare Dashboard for RPC provider performance benchmarking. Core contributor to the DevEx team’s pump.fun trading bot. Author of technical tutorials on EVM blockchains, Solana, TON and Subgraphs.