Skip to content

Commit df37890

Browse files
authored
Merge pull request #238 from BitGo/BTC-3226/serde-wasm-convention
chore: add serde_wasm_bindgen convention for intent marshalling
2 parents b6d5608 + 97924f7 commit df37890

1 file changed

Lines changed: 64 additions & 1 deletion

File tree

CONVENTIONS.md

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,8 +372,71 @@ export function explainTransaction(hex, context): TransactionExplanation {
372372

373373
---
374374

375+
## 9. Pass objects to WASM via serde_wasm_bindgen, not JSON strings
376+
377+
**What:** When passing structured data (intents, build context) from TypeScript to Rust/WASM, pass the JS object directly and use `serde_wasm_bindgen::from_value()` in Rust. Do not JSON.stringify in TypeScript and `serde_json::from_str()` in Rust.
378+
379+
**Why:** JSON stringification is an unnecessary round-trip through string serialization. It adds a `serializeIntent()` function in TypeScript that shouldn't exist, bypasses serde_wasm_bindgen's type checking, and doesn't match the sol/dot pattern.
380+
381+
**Good:**
382+
383+
```typescript
384+
// TypeScript — pass object directly
385+
const tx = BuilderNamespace.buildTransaction(intent, context);
386+
```
387+
388+
```rust
389+
// Rust — deserialize from JS value
390+
pub fn build_transaction(intent: JsValue, context: JsValue) -> Result<WasmTransaction, WasmError> {
391+
let intent: TransactionIntent = serde_wasm_bindgen::from_value(intent)?;
392+
let ctx: BuildContext = serde_wasm_bindgen::from_value(context)?;
393+
// ...
394+
}
395+
```
396+
397+
**Bad:**
398+
399+
```typescript
400+
// ❌ Don't serialize to JSON string in TypeScript
401+
function serializeIntent(intent: TonIntent): string {
402+
return JSON.stringify(intent, (_, v) => (typeof v === "bigint" ? v.toString() : v));
403+
}
404+
const tx = BuilderNamespace.buildTransaction(serializeIntent(intent), context);
405+
```
406+
407+
```rust
408+
// ❌ Don't deserialize from JSON string in Rust
409+
pub fn build_transaction(intent_json: &str, context: JsValue) -> Result<WasmTransaction, WasmError> {
410+
let intent: TransactionIntent = serde_json::from_str(intent_json)?;
411+
// ...
412+
}
413+
```
414+
415+
**Handling BigInt:** If intent fields contain `bigint` values that `serde_wasm_bindgen` can't deserialize directly, use custom serde deserializers in Rust that accept both number and string:
416+
417+
```rust
418+
// Rust — custom deserializer handles both u64 and "123" string
419+
fn deserialize_u64<'de, D: Deserializer<'de>>(d: D) -> Result<u64, D::Error> {
420+
struct U64Visitor;
421+
impl<'de> Visitor<'de> for U64Visitor {
422+
type Value = u64;
423+
fn visit_u64<E>(self, v: u64) -> Result<u64, E> { Ok(v) }
424+
fn visit_str<E: de::Error>(self, v: &str) -> Result<u64, E> {
425+
v.parse().map_err(E::custom)
426+
}
427+
}
428+
d.deserialize_any(U64Visitor)
429+
}
430+
```
431+
432+
This keeps the TypeScript side clean (no serialization helpers) while handling the BigInt edge case in Rust.
433+
434+
**See:** `packages/wasm-solana/src/intent/types.rs`, `packages/wasm-dot/src/builder/types.rs`
435+
436+
---
437+
375438
## Summary
376439

377-
These 8 conventions define how BitGoWasm packages structure their APIs. They're architectural patterns enforced in code reviews — not general software practices or build requirements.
440+
These 9 conventions define how BitGoWasm packages structure their APIs. They're architectural patterns enforced in code reviews — not general software practices or build requirements.
378441

379442
When in doubt, look at wasm-solana and wasm-utxo — they're the reference implementations. Following these patterns from the start prevents review churn and keeps all packages consistent.

0 commit comments

Comments
 (0)