Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Github won't let me comment on lines not modified.

Some places where "ton" should be replaced by "gram":

TopUpTons
returns TON
a forced TON deposit
forwardTonAmount
hosted TON backing
Accept TONs

Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import "fees-management"
contract JettonMinter {
author: "SmartContract Chainlink Limited SEZC"
version: "0.0.1"
description: "link.chain.ton.wton.JettonMinter"
description: "link.chain.ton.wgram.JettonMinter"

storage: MinterStorage
incomingMessages: AllowedMessageToMinter
Expand Down Expand Up @@ -96,7 +96,7 @@ fun onInternalMessage(in: InMessage) {
queryId: msg.queryId
}
});
// If the send action cannot be executed, the tx must fail and bounce back to the wallet to restore wTON.
// If the send action cannot be executed, the tx must fail and bounce back to the wallet to restore wGRAM.
excessesMsg.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE | SEND_MODE_BOUNCE_ON_ACTION_FAIL);
}

Expand Down Expand Up @@ -193,7 +193,7 @@ struct (0x00) OnchainMetadataReply {
// --- Getters ---

get fun typeAndVersion(): (slice, slice) {
return ("link.chain.ton.wton.JettonMinter".literalSlice(), "0.0.1".literalSlice());
return ("link.chain.ton.wgram.JettonMinter".literalSlice(), "0.0.1".literalSlice());
}

get fun get_jetton_data(): JettonDataReply {
Expand Down

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Github won't let me comment on lines not modified.

Some places where "ton" should be replaced by "gram":

withdraw any TON surplus
backedTonAmount
Soft reserve check: keep TON that was

Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import "fees-management"
contract JettonWallet {
author: "SmartContract Chainlink Limited SEZC"
version: "0.0.1"
description: "link.chain.ton.wton.JettonWallet"
description: "link.chain.ton.wgram.JettonWallet"

storage: WalletStorage
incomingMessages: AllowedMessageToWallet
Expand Down Expand Up @@ -68,7 +68,7 @@ fun onInternalMessage(in: InMessage) {
val jettonBalanceNext = storage.jettonBalance + msg.jettonAmount;
val requiredReserve = requiredWalletReserve(jettonBalanceNext);
// Hard reserve check: the inbound transfer must fund the full post-credit backing floor
// before we credit wTON or allow any forward / excess action to run.
// before we credit wGRAM or allow any forward / excess action to run.
assert (contract.getOriginalBalance() >= requiredReserve + msg.forwardTonAmount) throw ERROR_UNSUFFICIENT_AMOUNT;
// Soft reserve check: keep TON that was already on the wallet before this message from
// being reclassified as outbound excess. This surplus is best-effort under RESERVE_MODE_AT_MOST.
Expand Down Expand Up @@ -127,7 +127,7 @@ fun onInternalMessage(in: InMessage) {
storage.jettonBalance -= msg.jettonAmount;
storage.save();

// Preserve the remaining wTON backing plus storage reserve exactly, or abort the transfer.
// Preserve the remaining wGRAM backing plus storage reserve exactly, or abort the transfer.
reserveToncoinsOnBalance(requiredWalletReserve(storage.jettonBalance), reserveModeExactFail());

val deployMsg = createMessage({
Expand Down Expand Up @@ -214,7 +214,7 @@ struct JettonWalletDataReply {
// --- Getters ---

get fun typeAndVersion(): (slice, slice) {
return ("link.chain.ton.wton.JettonWallet".literalSlice(), "0.0.1".literalSlice());
return ("link.chain.ton.wgram.JettonWallet".literalSlice(), "0.0.1".literalSlice());
}

get fun get_wallet_data(): JettonWalletDataReply {
Expand Down
5 changes: 5 additions & 0 deletions contracts/contracts/wgram/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Wrapped TON

A token escrow protocol to make TON behave as Jetton in a new asset called wGRAM.
Comment on lines +1 to +3

Documentation: [docs/contracts/overview/wgram](../../../docs/contracts/overview/wgram/index.md)
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const MIN_STORAGE_DURATION = 5 * 365 * 24 * 3600 // 5 years
// these constants are used to estimate gas fee (how much we should remain on balance for a swap to succeed);
// they must stay exactly equal to the calibrated measured gas values for the covered live paths;
// if those measured values drift, tests fail.
// actual consumed gas is measured by tests/gas-report/wton/Wton.spec.ts via yarn wton-gas-report
// actual consumed gas is measured by tests/gas-report/wgram/wgram.spec.ts via yarn wgram-gas-report
// GAS_CONSUMPTION_JettonTransfer is calibrated against the max sender-side path that reuses
// checkAmountIsEnoughToTransfer (wallet transfer sender vs mint sender candidate).
// GAS_CONSUMPTION_JettonReceive is calibrated against the max live receive branch with both
Expand Down
5 changes: 0 additions & 5 deletions contracts/contracts/wton/README.md

This file was deleted.

2 changes: 1 addition & 1 deletion contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"fmt-typescript:check": "prettier --check .",
"fmt-typescript": "prettier --write .",
"ccip-gas-report": "blueprint test --gas-report -- --config ccip-gas-report.config.ts",
"wton-gas-report": "blueprint test --gas-report -- --config wton-gas-report.config.ts",
"wgram-gas-report": "blueprint test --gas-report -- --config wgram-gas-report.config.ts",
"get-key-pair": "ts-node scripts/getKeyPair.ts"
},
"dependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import { Blockchain, SandboxContract, TreasuryContract, printTransactionFees } f

import { JettonMinter, builder as minterBuilder } from '../../../wrappers/jetton/JettonMinter'
import { JettonWallet, builder as walletBuilder } from '../../../wrappers/jetton/JettonWallet'
import { WTON_MINT_OPCODE } from '../../../wrappers/wton'
import { WTON_MINT_OPCODE } from '../../../wrappers/wgram'

const JETTON_DATA_URI = 'wton.gas'
const JETTON_DATA_URI = 'wgram.gas'

type ConfiguredGasConstants = {
GAS_CONSUMPTION_JettonTransfer: number
Expand All @@ -35,7 +35,7 @@ function readFeesManagementConstant(source: string, name: string) {
}

function readFeesManagementSource() {
const feesFile = path.join(__dirname, '../../../contracts/wton/fees-management.tolk')
const feesFile = path.join(__dirname, '../../../contracts/wgram/fees-management.tolk')
return fs.readFileSync(feesFile, 'utf8')
}

Expand Down Expand Up @@ -118,7 +118,7 @@ function internalTxTo(result: { transactions: Array<any> }, destination: Address
return tx
}

describe('wTON gas calibration', () => {
describe('wGRAM gas calibration', () => {
let blockchain: Blockchain
let minterCode: Cell
let walletCode: Cell
Expand All @@ -132,8 +132,8 @@ describe('wTON gas calibration', () => {
let nextQueryId: bigint

beforeAll(async () => {
minterCode = await compile('wton.JettonMinter')
walletCode = await compile('wton.JettonWallet')
minterCode = await compile('wgram.JettonMinter')
walletCode = await compile('wgram.JettonWallet')
})

beforeEach(async () => {
Expand Down Expand Up @@ -205,10 +205,10 @@ describe('wTON gas calibration', () => {
it('keeps fee-management gas constants aligned with measured wallet and minter execution', async () => {
const configured = readConfiguredGasConstants()
// Exercise the highest live receive branch: notify recipient owner and still send excesses.
const transferForwardPayload = beginCell().storeStringTail('wton.gas.forward').endCell()
const transferCustomPayload = beginCell().storeStringTail('wton.gas.custom').endCell()
const burnCustomPayload = beginCell().storeStringTail('wton.gas.burn').endCell()
const mintForwardPayload = beginCell().storeStringTail('wton.gas.mint-forward').endCell()
const transferForwardPayload = beginCell().storeStringTail('wgram.gas.forward').endCell()
const transferCustomPayload = beginCell().storeStringTail('wgram.gas.custom').endCell()
const burnCustomPayload = beginCell().storeStringTail('wgram.gas.burn').endCell()
const mintForwardPayload = beginCell().storeStringTail('wgram.gas.mint-forward').endCell()

const mintResult = await mintTo(alice.address, toNano('1.5'), {
tonAmount: toNano('0.3'),
Expand Down Expand Up @@ -277,7 +277,7 @@ describe('wTON gas calibration', () => {

it('keeps fee-shape constants aligned with live transfer and burn message bodies', () => {
const configured = readConfiguredShapeConstants()
const forwardPayload = beginCell().storeStringTail('wton.gas.shape').endCell()
const forwardPayload = beginCell().storeStringTail('wgram.gas.shape').endCell()
const maxCoins = (1n << 120n) - 1n

const transferBodyStats = cellStats(
Expand Down Expand Up @@ -375,7 +375,7 @@ describe('wTON gas calibration', () => {
destination: alice.address,
tonAmount: toNano('0.3'),
jettonAmount: toNano('0.7'),
// The wTON minter enforces transferInitiator == null on mint, so the actual outgoing
// The wGRAM minter enforces transferInitiator == null on mint, so the actual outgoing
// InternalTransferStep is smaller than transferBodyStats. We use transferBodyStats as
// a strict upper bound for the outgoing — if that bound fits inside the incoming,
// the real outgoing fits too.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import {
ERROR_INVALID_RECIPIENT,
WTON_MINT_OPCODE,
WTON_WITHDRAW_EXCESS_OPCODE,
} from '../../wrappers/wton'
} from '../../wrappers/wgram'
import * as bouncer from '../../wrappers/test/mock/Bouncer'

const JETTON_DATA_URI = 'wton.test'
const JETTON_DATA_URI = 'wgram.test'
const MASTERCHAIN_ZERO_ADDRESS = Address.parse(`-1:${'0'.repeat(64)}`)

type MintOptions = {
Expand All @@ -34,7 +34,7 @@ type MintOptions = {
value?: bigint
}

describe('wTON', () => {
describe('wGRAM', () => {
let blockchain: Blockchain

let minterCode: Cell
Expand All @@ -50,8 +50,8 @@ describe('wTON', () => {
let nextQueryId: bigint

beforeAll(async () => {
minterCode = await compile('wton.JettonMinter')
walletCode = await compile('wton.JettonWallet')
minterCode = await compile('wgram.JettonMinter')
walletCode = await compile('wgram.JettonWallet')
bouncerCode = await compile('tests.mock.Bouncer')
})

Expand All @@ -60,7 +60,7 @@ describe('wTON', () => {
const contract = blockchain.openContract(
JettonMinter.createFromConfig(
{
// wTON has no admin runtime path; deploy storage matches the get_jetton_data null admin.
// wGRAM has no admin runtime path; deploy storage matches the get_jetton_data null admin.
admin: null,
transferAdmin: null,
walletCode: customWalletCode,
Expand Down Expand Up @@ -494,7 +494,7 @@ describe('wTON', () => {
})

describe('minting', () => {
it('mints wTON into a backed wallet', async () => {
it('mints wGRAM into a backed wallet', async () => {
const mintAmount = toNano('1')
await mintTo(alice.address, { jettonAmount: mintAmount })

Expand Down Expand Up @@ -814,12 +814,12 @@ describe('wTON', () => {
expect(await totalSupply()).toEqual(0n)
})

it('rejects metadata changes because wTON has no admin opcode surface', async () => {
it('rejects metadata changes because wGRAM has no admin opcode surface', async () => {
const dataBefore = await minter.getJettonData()
const result = await minter.sendChangeContent(deployer.getSender(), {
message: {
queryId: nextQueryId++,
content: beginCell().storeStringTail('wton.changed').endCell(),
content: beginCell().storeStringTail('wgram.changed').endCell(),
},
})

Expand All @@ -836,7 +836,7 @@ describe('wTON', () => {
})

describe('transferring', () => {
it('transfers wTON between wallets', async () => {
it('transfers wGRAM between wallets', async () => {
const mintAmount = toNano('2')
const transferAmount = toNano('0.75')
await mintTo(alice.address, { jettonAmount: mintAmount })
Expand Down Expand Up @@ -1264,7 +1264,7 @@ describe('wTON', () => {
expect(await walletBalance(alice.address)).toEqual(mintAmount)
})

it('burns wTON and pays the nominated recipient', async () => {
it('burns wGRAM and pays the nominated recipient', async () => {
const mintAmount = toNano('1')
await mintTo(alice.address, { jettonAmount: mintAmount })

Expand Down Expand Up @@ -1792,7 +1792,7 @@ describe('wTON', () => {
}
}

// For wTON solvency we care about two invariants: supply matches wallet balances, and the
// For wGRAM solvency we care about two invariants: supply matches wallet balances, and the
// minter plus all wallet backings still cover that supply with the minter reserve on top.
async function assertCoreInvariants(owners: Address[]) {
const supply = await totalSupply()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Config } from 'jest'
const config: Config = {
preset: 'ts-jest',
testEnvironment: '@ton/sandbox/jest-environment',
testMatch: ['**/tests/gas-report/wton/**/*.spec.ts'],
testMatch: ['**/tests/gas-report/wgram/**/*.spec.ts'],
modulePathIgnorePatterns: ['/node_modules/', '/dist/', '/vendor/'],
testTimeout: 120000,
reporters: [
Expand All @@ -13,7 +13,7 @@ const config: Config = {
{
snapshotDir: '.snapshot',
contractDatabase: 'contract.abi.json',
reportName: 'wton-gas-report',
reportName: 'wgram-gas-report',
depthCompare: 2,
removeRawResult: true,
},
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion contracts/wrappers/jetton/JettonWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export type WithdrawTonsMessage = {
queryId: bigint
}

// wTON-specific extension: lets the wallet owner withdraw any TON surplus
// wGRAM-specific extension: lets the wallet owner withdraw any TON surplus
// sitting above the strict `jettonBalance + storage_fee`
export type AskToWithdrawExcess = {
queryId: bigint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import { CompilerConfig } from '@ton/blueprint'

export const compile: CompilerConfig = {
lang: 'tolk',
entrypoint: 'contracts/wton/JettonWallet.tolk',
entrypoint: 'contracts/wgram/JettonMinter.tolk',
withStackComments: true,
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import { CompilerConfig } from '@ton/blueprint'

export const compile: CompilerConfig = {
lang: 'tolk',
entrypoint: 'contracts/wton/JettonMinter.tolk',
entrypoint: 'contracts/wgram/JettonWallet.tolk',
withStackComments: true,
}
File renamed without changes.
2 changes: 1 addition & 1 deletion docs/contracts/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This repository includes many contracts for the TON network implemented in the [
- [CCIP](./overview/ccip/index.md)
- [Deployable](./overview/deployable.md)
- [MCMS](./overview/mcms/index.md)
- [wTON](./overview/wton/index.md)
- [wGRAM](./overview/wgram/index.md)

## Development guides

Expand Down
2 changes: 1 addition & 1 deletion docs/contracts/overview/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ sidebar_position: 1
- [CCIP](./ccip/index.md)
- [Deployable](./deployable.md)
- [MCMS](./mcms/index.md)
- [wTON](./wton/index.md)
- [wGRAM](./wgram/index.md)

### Examples

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
position: 6
label: wTON
label: wGRAM
collapsible: true
collapsed: true
link:
type: doc
id: contracts-overview-wton-index
id: contracts-overview-wgram-index
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
---
id: contracts-overview-wton-design
id: contracts-overview-wgram-design
title: Design
sidebar_label: Design
sidebar_position: 1
---

# wTON - Design
# wGRAM - Design

wTON is a fully backed Jetton wrapper around TON:
wGRAM is a fully backed Jetton wrapper around GRAM:

- Minting funds the recipient wallet with the TON backing and issues the same amount of wTON there.
- Burning destroys wTON in the wallet and routes the withdrawn TON back to the chosen payout destination via the minter.
- Transfers move both the wTON balance and its TON backing between wTON wallets.
- Transfers stay Jetton-compatible, so ordinary Jetton tooling can interact with wTON wallets.
- Minting funds the recipient wallet with the GRAM backing and issues the same amount of wGRAM there.
- Burning destroys wGRAM in the wallet and routes the withdrawn GRAM back to the chosen payout destination via the minter.
- Transfers move both the wGRAM balance and its GRAM backing between wGRAM wallets.
- Transfers stay Jetton-compatible, so ordinary Jetton tooling can interact with wGRAM wallets.

The implementation keeps the protocol surface intentionally small:

- `JettonMinter.tolk` tracks total supply, serves wallet-address requests, dispatches mint funding into wallets, and settles burn withdrawals.
- `JettonWallet.tolk` holds user balances, escrows the per-wallet TON backing, enforces owner-only transfer and burn requests, and processes incoming internal transfers.
- `JettonWallet.tolk` holds user balances, escrows the per-wallet GRAM backing, enforces owner-only transfer and burn requests, and processes incoming internal transfers.
- `fees-management.tolk` contains the storage, forward-fee, and gas constants that the runtime checks use to reject underfunded mint, transfer, and burn messages before balances move.

The main behavior differences from a generic Jetton are deliberate:

- wTON has no admin controls after deployment.
- wGRAM has no admin controls after deployment.
- Workflows are restricted to `MY_WORKCHAIN` so fee budgeting and refund paths stay deterministic.
- Mint bounce refunds are best-effort: supply is restored first, and any refund send is attempted with `IGNORE_ERRORS` rather than treated as protocol-critical.
- Burn payouts are protocol-critical, not best-effort. The minter sends the payout to `sendExcessesTo` under `SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE | SEND_MODE_BOUNCE_ON_ACTION_FAIL`. If the action phase fails — either because RAWRESERVE cannot keep the minter's rent reserve intact (post-msg balance below the floor) or because the subsequent payout send would push the balance below the just-set reserve floor — the transaction reverts, the compute-phase `totalSupply` decrement is undone, the burn notification bounces back to the wallet, and the wallet's `onBouncedMessage` restores the burned `jettonBalance`. Net effect: the burner keeps their wTON and no TON is moved. The recipient's compute-phase failure (i.e., a non-bounceable destination that throws on receive) is _not_ an action-phase failure for the minter — with `BounceMode.NoBounce` the TON is still deposited at the recipient address even if its code throws.
- Burn payouts are protocol-critical, not best-effort. The minter sends the payout to `sendExcessesTo` under `SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE | SEND_MODE_BOUNCE_ON_ACTION_FAIL`. If the action phase fails — either because RAWRESERVE cannot keep the minter's rent reserve intact (post-msg balance below the floor) or because the subsequent payout send would push the balance below the just-set reserve floor — the transaction reverts, the compute-phase `totalSupply` decrement is undone, the burn notification bounces back to the wallet, and the wallet's `onBouncedMessage` restores the burned `jettonBalance`. Net effect: the burner keeps their wGRAM and no GRAM is moved. The recipient's compute-phase failure (i.e., a non-bounceable destination that throws on receive) is _not_ an action-phase failure for the minter — with `BounceMode.NoBounce` the GRAM is still deposited at the recipient address even if its code throws.

## Jetton Version

Expand All @@ -39,4 +39,4 @@ Base Jetton Tolk implementation from <https://github.com/ton-blockchain/tolk-ben
>
> This version is straightforward - it is a forked Stablecoin contract with removed governance functionality and added burn mechanism. Until recent times, it was the most suitable Jetton for basic on-chain coin use cases.

Which is exactly what we need as a base for wTON (and CCTs), and the [ton-blockchain/tolk-bench](https://github.com/ton-blockchain/tolk-bench) is implemented in latest Tolk 1.4 and brings substantial gas improvements over using FunC originals.
Which is exactly what we need as a base for wGRAM (and CCTs), and the [ton-blockchain/tolk-bench](https://github.com/ton-blockchain/tolk-bench) is implemented in latest Tolk 1.4 and brings substantial gas improvements over using FunC originals.
Loading
Loading