Skip to content

Commit 6372b7e

Browse files
committed
fix: wasm-ton parser gaps and tx id computation
- Parse withdraw amount from SingleNominator and Whales withdrawal message bodies, exposed as withdrawAmount on ParsedSendAction - Fix Transaction.id() to use cell hash instead of SHA-256 of BOC bytes, matching TON blockchain convention and TonWeb behavior - Expose withdrawAmount in JS via try_into_js_value and parser.ts types - Strengthen transaction tests to assert exact id and withdrawAmount values BTC-3246
1 parent e5b0770 commit 6372b7e

5 files changed

Lines changed: 68 additions & 13 deletions

File tree

packages/wasm-ton/js/parser.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export interface ParsedSendAction {
2727
bodyOpcode?: number;
2828
memo?: string;
2929
jettonTransfer?: JettonTransferFields;
30+
/** Withdraw amount from the message body (SingleNominator/Whales withdrawal types). */
31+
withdrawAmount?: bigint;
3032
}
3133

3234
/** A fully parsed TON transaction */

packages/wasm-ton/src/parser.rs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@ use ton_contracts::wallet::v4r2::{WalletV4R2Op, WalletV4R2SignBody};
55
use crate::error::WasmTonError;
66
use crate::transaction::Transaction;
77

8-
/// Body parse result: (opcode, memo, jetton_transfer)
9-
type BodyParseResult = (Option<u32>, Option<String>, Option<JettonTransferFields>);
8+
/// Body parse result: (opcode, memo, jetton_transfer, withdraw_amount)
9+
type BodyParseResult = (
10+
Option<u32>,
11+
Option<String>,
12+
Option<JettonTransferFields>,
13+
Option<u64>,
14+
);
1015

1116
/// Transaction type enum
1217
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -65,6 +70,8 @@ pub struct ParsedSendAction {
6570
pub state_init: bool,
6671
pub memo: Option<String>,
6772
pub jetton_transfer: Option<JettonTransferFields>,
73+
/// Withdraw amount encoded in the message body (SingleNominator/Whales withdrawal types).
74+
pub withdraw_amount: Option<u64>,
6875
}
6976

7077
/// A fully parsed TON transaction
@@ -143,7 +150,8 @@ fn parse_sign_body_actions(
143150
let state_init = msg.init.is_some();
144151

145152
// Parse body
146-
let (body_opcode, memo, jetton_transfer) = parse_message_body(&msg.body)?;
153+
let (body_opcode, memo, jetton_transfer, withdraw_amount) =
154+
parse_message_body(&msg.body)?;
147155

148156
parsed.push(ParsedSendAction {
149157
mode: action.mode,
@@ -159,6 +167,7 @@ fn parse_sign_body_actions(
159167
state_init,
160168
memo,
161169
jetton_transfer,
170+
withdraw_amount,
162171
});
163172
}
164173
Ok(parsed)
@@ -173,12 +182,12 @@ fn parse_message_body(body: &Cell) -> Result<BodyParseResult, WasmTonError> {
173182

174183
// Empty body
175184
if bits_left == 0 {
176-
return Ok((None, None, None));
185+
return Ok((None, None, None, None));
177186
}
178187

179188
// Need at least 32 bits for opcode
180189
if bits_left < 32 {
181-
return Ok((None, None, None));
190+
return Ok((None, None, None, None));
182191
}
183192

184193
let opcode: u32 = parser
@@ -196,16 +205,34 @@ fn parse_message_body(body: &Cell) -> Result<BodyParseResult, WasmTonError> {
196205
};
197206
}
198207
let memo = String::from_utf8_lossy(&bytes).to_string();
199-
return Ok((Some(0), Some(memo), None));
208+
return Ok((Some(0), Some(memo), None, None));
200209
}
201210

202211
if opcode == JETTON_TRANSFER_OPCODE {
203212
let jetton = parse_jetton_transfer_body(&mut parser)?;
204-
return Ok((Some(opcode), None, Some(jetton)));
213+
return Ok((Some(opcode), None, Some(jetton), None));
214+
}
215+
216+
if opcode == WHALES_WITHDRAW_OPCODE || opcode == SINGLE_NOMINATOR_WITHDRAW_OPCODE {
217+
let withdraw_amount = parse_withdraw_amount_body(&mut parser)?;
218+
return Ok((Some(opcode), None, None, Some(withdraw_amount)));
205219
}
206220

207221
// Other known opcodes
208-
Ok((Some(opcode), None, None))
222+
Ok((Some(opcode), None, None, None))
223+
}
224+
225+
/// Parse query_id + amount from a withdrawal message body (Whales or SingleNominator).
226+
fn parse_withdraw_amount_body(
227+
parser: &mut tlb_ton::de::CellParser<'_>,
228+
) -> Result<u64, WasmTonError> {
229+
let _query_id: u64 = parser
230+
.unpack(())
231+
.map_err(|e| WasmTonError::new(&format!("withdraw: failed to read query_id: {e}")))?;
232+
let amount_big: BigUint = parser
233+
.unpack_as::<_, Grams>(())
234+
.map_err(|e| WasmTonError::new(&format!("withdraw: failed to read amount: {e}")))?;
235+
Ok(biguint_to_u64(&amount_big))
209236
}
210237

211238
fn parse_jetton_transfer_body(

packages/wasm-ton/src/transaction.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,18 @@ impl Transaction {
106106
Ok(STANDARD.encode(&self.boc_bytes))
107107
}
108108

109-
/// Get the transaction ID (SHA-256 hash of the BOC, base64url encoded).
109+
/// Get the transaction ID (cell hash of the root message cell, base64url encoded).
110+
///
111+
/// Uses the standard TON cell hash algorithm, matching what TonWeb and TON explorers compute.
110112
pub fn id(&self) -> String {
111-
let hash = Sha256::digest(&self.boc_bytes);
112-
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(hash)
113+
self.message
114+
.to_cell(())
115+
.map(|cell| base64::engine::general_purpose::URL_SAFE.encode(cell.hash()))
116+
.unwrap_or_else(|_| {
117+
// Should never happen for a valid deserialized transaction
118+
let hash = Sha256::digest(&self.boc_bytes);
119+
base64::engine::general_purpose::URL_SAFE.encode(hash)
120+
})
113121
}
114122

115123
fn serialize_boc(&self) -> Result<Vec<u8>, WasmTonError> {

packages/wasm-ton/src/wasm/try_into_js_value.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,15 @@ impl TryIntoJsValue for ParsedSendAction {
209209
.map_err(|_| JsConversionError::new("Failed to set jettonTransfer"))?;
210210
}
211211

212+
if let Some(withdraw_amount) = self.withdraw_amount {
213+
js_sys::Reflect::set(
214+
&obj,
215+
&JsValue::from_str("withdrawAmount"),
216+
&TryIntoJsValue::try_to_js_value(&withdraw_amount)?,
217+
)
218+
.map_err(|_| JsConversionError::new("Failed to set withdrawAmount"))?;
219+
}
220+
212221
Ok(obj.into())
213222
}
214223
}

packages/wasm-ton/test/transaction.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,15 @@ describe("Transaction", () => {
2020
it("should deserialize a signed send transaction", () => {
2121
const tx = fromBase64(signedSendTx);
2222
assert.ok(tx);
23-
assert.ok(tx.id);
2423
assert.ok(tx.destination);
2524
});
2625

26+
it("should compute cell hash as transaction id", () => {
27+
const tx = fromBase64(signedSendTx);
28+
// Cell hash of the root external message cell, base64url with padding
29+
assert.equal(tx.id, "tuyOkyFUMv_neV_FeNBH24Nd4cML2jUgDP4zjGkuOFI=");
30+
});
31+
2732
it("should get signable payload", () => {
2833
const tx = fromBase64(signedSendTx);
2934
const payload = tx.signablePayload();
@@ -45,20 +50,24 @@ describe("Transaction", () => {
4550
assert.ok(parsed.sendActions.length > 0);
4651
assert.ok(parsed.sender);
4752
assert.ok(parsed.signature);
53+
assert.equal(parsed.sendActions[0].withdrawAmount, undefined);
4854
});
4955

5056
it("should parse a whales deposit transaction", () => {
5157
const tx = fromBase64(whalesDepositTx);
5258
const parsed = parseTransaction(tx);
5359
assert.equal(parsed.transactionType, "WhalesDeposit");
5460
assert.ok(parsed.sendActions.length > 0);
61+
assert.equal(parsed.sendActions[0].withdrawAmount, undefined);
5562
});
5663

57-
it("should parse a single nominator withdraw transaction", () => {
64+
it("should parse a single nominator withdraw transaction with withdrawAmount", () => {
5865
const tx = fromBase64(singleNominatorWithdrawTx);
5966
const parsed = parseTransaction(tx);
6067
assert.equal(parsed.transactionType, "SingleNominatorWithdraw");
6168
assert.ok(parsed.sendActions.length > 0);
69+
assert.equal(typeof parsed.sendActions[0].withdrawAmount, "bigint");
70+
assert.equal(parsed.sendActions[0].withdrawAmount, 932178112330000n);
6271
});
6372
});
6473
});

0 commit comments

Comments
 (0)