Skip to content

TON - Add Jetton support#1042

Open
peachbits wants to merge 2 commits intomasterfrom
matthew/jetton
Open

TON - Add Jetton support#1042
peachbits wants to merge 2 commits intomasterfrom
matthew/jetton

Conversation

@peachbits
Copy link
Copy Markdown
Contributor

@peachbits peachbits commented Mar 23, 2026

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Description

none

Note

High Risk
High risk because it changes TON transaction construction/fee estimation/broadcast flows and adds Jetton token balance + transfer handling, which can directly impact funds movement and history accuracy.

Overview
Adds TON Jetton (token) support by resolving per-token Jetton wallet addresses, polling Jetton balances, and parsing Jetton transfer notifications/requests into token EdgeTransactions (including memo support and parent-fee attribution).

Replaces prior Orbs-based network calls with a new dRPC HTTP layer (tonDrpc) used for address info, fee estimation, seqno lookup, and broadcasting (plus txid discovery for first-send wallets), and updates TON plugin config/init options to include dRPC settings and a builtin TON USDT token.

Reviewed by Cursor Bugbot for commit 2f6ab6c. Bugbot is set up for automated code reviews on this repo. Configure here.


if (typeof entry === 'object' && entry != null) {
const obj = entry as any
if (obj['@type'] === 'tvm.stackEntryNumber') {
return { type: 'num', value: obj.number?.value ?? obj.number }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Wrong field name in stack entry number parsing

Medium Severity

In parseStackEntry, the tvm.stackEntryNumber branch reads obj.number?.value but the Toncenter JSONRPC format nests the actual number as obj.number?.number (the inner object is {"@type": "tvm.numberDecimal", "number": "42"}). When this branch is hit, the fallback ?? obj.number returns the entire nested object, and BigInt(object) in parseStackNumber will throw a TypeError. The field access .value needs to be .number to correctly extract the numeric string.

Fix in Cursor Fix in Web


wallet: WalletContractV5R1
archiveTransactions: boolean
jettonWalletToTokenId: Map<string, string> = new Map()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Dead archiveTransactions field after Orbs removal

Low Severity

The archiveTransactions property is declared, initialized in the constructor, and assigned false in queryTransactions, but is never read anywhere. It was previously used by the now-removed Orbs client logic. This is dead code left over from the migration to dRPC.

Additional Locations (2)
Fix in Cursor Fix in Web

Orbs servers would return valid but incorrect data when called too often. This was most evident in the balance query when the endpoints would return an account as uninitialized which wipes out the balance. This leads to balances swinging back and forth. Reducing the query frequency and staggering the load between servers didn't resolve the issue. dRPC has both free and paid teirs and the new implementation will use the free and fallback to paid if necessary.
Add jetton wallet address resolution via dRPC runGetMethod on Jetton
Master contracts, jetton balance queries via get_wallet_data, and
jetton transaction detection from parse_tx output (jetton_notification
and jetton_request op codes). Support jetton spending with TEP-74
transfer message construction and fee estimation.

Transaction queries still rely solely on TonCenter TonClient but now
produce EdgeTransactions for both native TON and jetton transfers.

Include USDT as a builtin token.
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: signTx fails for uninitialized wallets querying seqno
    • Updated signTx to bypass the seqno get-method call and use seqno 0 when contractState is uninitialized, preserving first-send behavior for undeployed wallets.

Create PR

Or push these changes by commenting:

@cursor push dda9df4baa
Preview (dda9df4baa)
diff --git a/src/ton/TonEngine.ts b/src/ton/TonEngine.ts
--- a/src/ton/TonEngine.ts
+++ b/src/ton/TonEngine.ts
@@ -603,19 +603,24 @@
     )[0].asSlice()
     const transferMessage = loadMessageRelaxed(messageSlice)
 
-    const addr = this.wallet.address.toRawString()
-    const seqnoRaw = await this.tools.fetchDrpc('/runGetMethod', {
-      address: addr,
-      method: 'seqno',
-      stack: []
-    })
-    const { result: seqnoResult } = asDrpcRunGetMethod(seqnoRaw)
-    if (seqnoResult.exit_code !== 0) {
-      throw new Error(
-        `seqno query failed with exit_code ${seqnoResult.exit_code}`
-      )
+    let seqno: number
+    if (this.otherData.contractState === 'uninitialized') {
+      seqno = 0
+    } else {
+      const addr = this.wallet.address.toRawString()
+      const seqnoRaw = await this.tools.fetchDrpc('/runGetMethod', {
+        address: addr,
+        method: 'seqno',
+        stack: []
+      })
+      const { result: seqnoResult } = asDrpcRunGetMethod(seqnoRaw)
+      if (seqnoResult.exit_code !== 0) {
+        throw new Error(
+          `seqno query failed with exit_code ${seqnoResult.exit_code}`
+        )
+      }
+      seqno = parseSeqnoFromStack(seqnoResult.stack)
     }
-    const seqno = parseSeqnoFromStack(seqnoResult.stack)
 
     const transferArgs: Parameters<WalletContractV5R1['createTransfer']>[0] = {
       sendMode: SendMode.IGNORE_ERRORS + SendMode.PAY_GAS_SEPARATELY,

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2f6ab6c. Configure here.

`seqno query failed with exit_code ${seqnoResult.exit_code}`
)
}
const seqno = parseSeqnoFromStack(seqnoResult.stack)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

signTx fails for uninitialized wallets querying seqno

High Severity

The signTx method calls runGetMethod for seqno directly via the API and throws if exit_code !== 0. For uninitialized wallets (first-time sends), the contract doesn't exist on-chain, so this call returns a non-zero exit code, causing signTx to throw. The previous code used contract.getSeqno() from the @ton/ton SDK, which internally checks contract state and returns 0 for undeployed contracts. This blocks first-time transactions from new wallets entirely — despite makeSpend and broadcastTx both having explicit handling for the uninitialized state.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2f6ab6c. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant