Skip to content

Commit 4413c75

Browse files
authored
Merge pull request #236 from BitGo/BTC-3226/update-conventions
chore: update conventions to explicitly ban TypeScript enum
2 parents bd0c502 + f99ceee commit 4413c75

2 files changed

Lines changed: 19 additions & 5 deletions

File tree

CLAUDE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# BitGoWasm Project Rules
2+
3+
**Read `CONVENTIONS.md` in this repo** — it contains hard rules for API design patterns (Uint8Array over strings, fromBytes as default factory, no unnecessary base conversions, as const over enum). These rules are non-negotiable.

CONVENTIONS.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,17 @@ const total = parseInt(amount) + parseInt(fee); // ❌ Loses precision
104104
- IDE autocomplete
105105
- Exhaustiveness checking in switch statements
106106
- Less repetitive than `enum` (no `Key = "Key"` duplication)
107+
- TypeScript `enum` values cause type cast issues when crossing WASM/serde boundaries. The `as const` pattern avoids this entirely.
107108

108109
**Good:**
109110

110111
```typescript
111112
export const TransactionType = ["Send", "StakingActivate", "StakingDeactivate"] as const;
112113
export type TransactionType = (typeof TransactionType)[number];
113114

115+
export const TonStakingType = ["TonWhales", "SingleNominator", "MultiNominator"] as const;
116+
export type TonStakingType = (typeof TonStakingType)[number];
117+
114118
function handleTx(type: TransactionType) {
115119
switch (type) {
116120
case "Send":
@@ -125,11 +129,16 @@ function handleTx(type: TransactionType) {
125129
**Bad:**
126130

127131
```typescript
132+
// ❌ TypeScript enum — causes type cast issues across WASM boundaries
133+
enum TransactionType {
134+
Send = "Send",
135+
StakingActivate = "StakingActivate",
136+
}
137+
128138
// ❌ No type safety, typos not caught
129139
function handleTx(type: string) {
130140
if (type === "send") {
131141
// Oops, wrong case
132-
// ...
133142
}
134143
}
135144

@@ -259,11 +268,13 @@ parsed.addSignature(pubkey, sig); // Wrong object type
259268

260269
- `static fromBytes(bytes: Uint8Array)` — deserialize
261270
- `toBytes(): Uint8Array` — serialize
262-
- `toBroadcastFormat(): string` — serialize to broadcast-ready format (0x-prefixed hex for Substrate, hex for UTXO, base64 for Solana)
271+
- `toBroadcastFormat(): Uint8Array` — serialize to broadcast-ready bytes. Default return type is `Uint8Array` (same as `toBytes()` for most chains). Callers encode to whatever string format the RPC needs: `Buffer.from(tx.toBroadcastFormat()).toString('base64')` for TON, `.toString('hex')` for UTXO, etc.
263272
- `getId(): string` — transaction ID / hash
264273
- `get wasm(): WasmType` (internal) — access underlying WASM instance
265274

266-
`toBroadcastFormat()` is the standard name for "give me the string I submit to the network". The encoding varies by chain but the method name is consistent. Don't add `toHex()` as a separate method — if callers want hex they can do `Buffer.from(tx.toBytes()).toString('hex')`.
275+
`toBroadcastFormat()` returns `Uint8Array` by default, consistent with Convention #1 (prefer Uint8Array). Don't add `toHex()`, `toBase64()`, or other encoding methods. Callers handle string encoding at the boundary.
276+
277+
**Exception:** Chains where the 0x-prefix footgun applies (Substrate/DOT) may return a hex string to avoid the `Buffer.from('0x...', 'hex')` silent truncation issue. This is the same exception as `fromHex()` — only justified for 0x-prefixed ecosystems.
267278

268279
**Why:**
269280

@@ -285,8 +296,8 @@ export class Transaction {
285296
return this._wasm.to_bytes();
286297
}
287298

288-
toBroadcastFormat(): string {
289-
return this._wasm.to_hex(); // or to_base64(), etc.
299+
toBroadcastFormat(): Uint8Array {
300+
return this.toBytes(); // default: same as toBytes()
290301
}
291302

292303
getId(): string {

0 commit comments

Comments
 (0)