Skip to content

Commit 0d34e8d

Browse files
committed
feat(wasm-dot): simplify builder to use metadata-only path with subxt
- Remove manual SCALE encoding fallback, always use metadata - Add clean call encoding via subxt dynamic API (calls.rs) - Implement batch support matching txwrapper-polkadot pattern - Make metadataHex required in Material type - Add builder tests with westend metadata fixture - Verify batch encoding matches standalone call encoding BTC-0
1 parent c28bb0e commit 0d34e8d

17 files changed

Lines changed: 1015 additions & 865 deletions

File tree

packages/wasm-dot/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ serde-wasm-bindgen = "0.6"
2525
# SCALE codec (Substrate/Polkadot serialization)
2626
parity-scale-codec = { version = "3.6", features = ["derive"] }
2727

28+
# Subxt for client-side Substrate types (WASM compatible)
29+
# Note: We use subxt_core::ext::scale_value for dynamic call building
30+
subxt-core = { version = "0.37", default-features = false }
31+
2832
# Crypto
2933
blake2 = "0.10"
3034

201 KB
Binary file not shown.

packages/wasm-dot/js/builder.ts

Lines changed: 95 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,111 @@
11
/**
2-
* TypeScript wrapper for BuilderNamespace
2+
* Transaction building from high-level intents.
3+
*
4+
* Provides the `buildTransaction()` function for building DOT transactions.
5+
* Follows wallet-platform pattern: buildTransaction(intent, context)
36
*/
47

5-
import {
6-
BuilderNamespace,
7-
BuildContextJs,
8-
MaterialBuilderJs,
9-
ValidityBuilderJs,
10-
} from './wasm/wasm_dot';
11-
import { DotTransaction } from './transaction';
12-
import type { BuildContext, TransactionIntent } from './types';
8+
import { BuilderNamespace } from "./wasm/wasm_dot";
9+
import { DotTransaction } from "./transaction";
10+
import type { TransactionIntent, BuildContext, BatchIntent } from "./types";
1311

1412
/**
15-
* DOT Transaction Builder
13+
* Build a DOT transaction from an intent and context.
14+
*
15+
* This function takes a declarative TransactionIntent and BuildContext,
16+
* producing a Transaction object that can be inspected, signed, and serialized.
17+
*
18+
* The returned transaction is unsigned - signatures should be added via
19+
* `addSignature()` before serializing with `toBytes()` and broadcasting.
20+
*
21+
* @param intent - What to do (transfer, stake, etc.)
22+
* @param context - How to build it (sender, nonce, material, validity, referenceBlock)
23+
* @returns A Transaction object that can be inspected, signed, and serialized
24+
* @throws Error if the intent cannot be built (e.g., invalid addresses)
25+
*
26+
* @example
27+
* ```typescript
28+
* import { buildTransaction } from '@bitgo/wasm-dot';
29+
*
30+
* // Build a simple DOT transfer
31+
* const tx = buildTransaction(
32+
* { type: 'transfer', to: '5FHneW46...', amount: '1000000000000', keepAlive: true },
33+
* {
34+
* sender: '5EGoFA95omzemRssELLDjVenNZ68aXyUeqtKQScXSEBvVJkr',
35+
* nonce: 5,
36+
* material: {
37+
* genesisHash: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3',
38+
* chainName: 'Polkadot',
39+
* specName: 'polkadot',
40+
* specVersion: 9150,
41+
* txVersion: 9
42+
* },
43+
* validity: { firstValid: 1000, maxDuration: 2400 },
44+
* referenceBlock: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3'
45+
* }
46+
* );
47+
*
48+
* // Inspect the transaction
49+
* console.log(tx.nonce);
50+
*
51+
* // Get the signable payload for signing
52+
* const payload = tx.signablePayload();
1653
*
17-
* Provides methods for building DOT transactions from intents
54+
* // Add signature and serialize
55+
* tx.addSignature(signerPubkey, signature);
56+
* const txBytes = tx.toBytes();
57+
* ```
58+
*
59+
* @example
60+
* ```typescript
61+
* // Build with batch (multiple operations)
62+
* const tx = buildTransaction(
63+
* {
64+
* type: 'batch',
65+
* calls: [
66+
* { type: 'transfer', to: recipient, amount: '1000000000000' },
67+
* { type: 'stake', amount: '5000000000000', payee: { type: 'staked' } }
68+
* ],
69+
* atomic: true
70+
* },
71+
* context
72+
* );
73+
* ```
1874
*/
19-
export class DotBuilder {
20-
/**
21-
* Build a transaction from an intent
22-
*
23-
* @param intent - Transaction intent describing what to do
24-
* @param context - Build context with sender, nonce, material, validity
25-
* @returns Transaction ready for signing
26-
*
27-
* @example
28-
* ```typescript
29-
* const tx = DotBuilder.buildTransaction({
30-
* type: 'transfer',
31-
* to: '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty',
32-
* amount: '1000000000000',
33-
* keepAlive: true,
34-
* }, context);
35-
* ```
36-
*/
37-
static buildTransaction(intent: TransactionIntent, context: BuildContext): DotTransaction {
38-
const ctx = createBuildContext(context);
39-
const inner = BuilderNamespace.buildTransaction(intent, ctx);
40-
return DotTransaction.fromInner(inner as any);
41-
}
75+
export function buildTransaction(intent: TransactionIntent, context: BuildContext): DotTransaction {
76+
// Convert BigInt amounts to strings for JSON serialization
77+
const serializedIntent = serializeIntent(intent);
78+
const serializedContext = serializeContext(context);
4279

43-
/**
44-
* Build a transfer transaction
45-
*
46-
* Convenience method for simple transfers
47-
*
48-
* @param to - Recipient address (SS58)
49-
* @param amount - Amount in planck (as string for BigInt)
50-
* @param keepAlive - Use transferKeepAlive (default: true)
51-
* @param context - Build context
52-
* @returns Transaction ready for signing
53-
*/
54-
static buildTransfer(
55-
to: string,
56-
amount: string,
57-
keepAlive: boolean,
58-
context: BuildContext
59-
): DotTransaction {
60-
const ctx = createBuildContext(context);
61-
const inner = BuilderNamespace.buildTransfer(to, amount, keepAlive, ctx);
62-
return DotTransaction.fromInner(inner as any);
63-
}
80+
const inner = BuilderNamespace.buildTransaction(serializedIntent, serializedContext);
81+
return DotTransaction.fromInner(inner as any);
82+
}
6483

65-
/**
66-
* Build a staking (bond) transaction
67-
*
68-
* @param amount - Amount to stake in planck (as string for BigInt)
69-
* @param payee - Where to send staking rewards ("staked", "stash", "controller", or address)
70-
* @param context - Build context
71-
* @returns Transaction ready for signing
72-
*/
73-
static buildStake(amount: string, payee: string, context: BuildContext): DotTransaction {
74-
const ctx = createBuildContext(context);
75-
const inner = BuilderNamespace.buildStake(amount, payee, ctx);
76-
return DotTransaction.fromInner(inner as any);
84+
/**
85+
* Serialize intent for WASM, converting BigInt to string
86+
*/
87+
function serializeIntent(intent: TransactionIntent): TransactionIntent {
88+
if ("amount" in intent && typeof intent.amount === "bigint") {
89+
return { ...intent, amount: intent.amount.toString() };
7790
}
78-
79-
/**
80-
* Build an unstake (unbond) transaction
81-
*
82-
* @param amount - Amount to unstake in planck (as string for BigInt)
83-
* @param context - Build context
84-
* @returns Transaction ready for signing
85-
*/
86-
static buildUnstake(amount: string, context: BuildContext): DotTransaction {
87-
const ctx = createBuildContext(context);
88-
const inner = BuilderNamespace.buildUnstake(amount, ctx);
89-
return DotTransaction.fromInner(inner as any);
91+
if (intent.type === "batch") {
92+
return {
93+
...intent,
94+
calls: (intent as BatchIntent).calls.map(serializeIntent),
95+
};
9096
}
97+
return intent;
9198
}
9299

93100
/**
94-
* Create a BuildContextJs from BuildContext
101+
* Serialize context for WASM, converting BigInt to string
95102
*/
96-
function createBuildContext(ctx: BuildContext): BuildContextJs {
97-
const material = new MaterialBuilderJs(
98-
ctx.material.genesisHash,
99-
ctx.material.chainName,
100-
ctx.material.specName,
101-
ctx.material.specVersion,
102-
ctx.material.txVersion
103-
);
104-
const validity = new ValidityBuilderJs(ctx.validity.firstValid, ctx.validity.maxDuration);
105-
return new BuildContextJs(
106-
ctx.sender,
107-
ctx.nonce,
108-
ctx.tip ?? '0',
109-
material,
110-
validity,
111-
ctx.referenceBlock
112-
);
103+
function serializeContext(context: BuildContext): BuildContext {
104+
return {
105+
...context,
106+
tip: context.tip?.toString() ?? "0",
107+
};
113108
}
109+
110+
// Re-export types for convenience
111+
export type { TransactionIntent, BuildContext } from "./types";

packages/wasm-dot/js/index.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This module provides:
55
* - Transaction parsing (decode extrinsics)
66
* - Signature operations (add signatures to unsigned transactions)
7-
* - Transaction building from intents
7+
* - Transaction building from intents (following wasm-solana pattern)
88
*/
99

1010
import {
@@ -14,25 +14,20 @@ import {
1414
MaterialJs,
1515
ValidityJs,
1616
ParseContextJs,
17-
BuildContextJs,
18-
MaterialBuilderJs,
19-
ValidityBuilderJs,
20-
} from './wasm/wasm_dot';
17+
} from "./wasm/wasm_dot";
2118

19+
// Export WASM classes for advanced usage
2220
export {
2321
WasmTransaction,
2422
ParserNamespace,
2523
BuilderNamespace,
2624
MaterialJs,
2725
ValidityJs,
2826
ParseContextJs,
29-
BuildContextJs,
30-
MaterialBuilderJs,
31-
ValidityBuilderJs,
3227
};
3328

3429
// Re-export types
35-
export * from './types';
36-
export * from './transaction';
37-
export * from './parser';
38-
export * from './builder';
30+
export * from "./types";
31+
export * from "./transaction";
32+
export * from "./parser";
33+
export * from "./builder";

packages/wasm-dot/js/parser.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
* TypeScript wrapper for ParserNamespace
33
*/
44

5-
import { ParserNamespace, MaterialJs, ParseContextJs } from './wasm/wasm_dot';
6-
import type { ParseContext, ParsedTransaction, TransactionOutput } from './types';
5+
import { ParserNamespace, MaterialJs, ParseContextJs } from "./wasm/wasm_dot";
6+
import type { ParseContext, ParsedTransaction, TransactionOutput } from "./types";
77

88
/**
99
* DOT Transaction Parser
@@ -69,7 +69,8 @@ function createParseContext(ctx: ParseContext): ParseContextJs {
6969
ctx.material.chainName,
7070
ctx.material.specName,
7171
ctx.material.specVersion,
72-
ctx.material.txVersion
72+
ctx.material.txVersion,
73+
ctx.material.metadataHex,
7374
);
7475
return new ParseContextJs(material, ctx.sender ?? null);
7576
}

packages/wasm-dot/js/transaction.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,8 @@
22
* TypeScript wrapper for WasmTransaction
33
*/
44

5-
import {
6-
WasmTransaction,
7-
MaterialJs,
8-
ValidityJs,
9-
ParseContextJs,
10-
} from './wasm/wasm_dot';
11-
import type { Material, Validity, ParseContext, Era } from './types';
5+
import { WasmTransaction, MaterialJs, ValidityJs, ParseContextJs } from "./wasm/wasm_dot";
6+
import type { Material, Validity, ParseContext, Era } from "./types";
127

138
/**
149
* DOT Transaction wrapper
@@ -119,7 +114,8 @@ export class DotTransaction {
119114
material.chainName,
120115
material.specName,
121116
material.specVersion,
122-
material.txVersion
117+
material.txVersion,
118+
material.metadataHex,
123119
);
124120
const validityJs = new ValidityJs(validity.firstValid, validity.maxDuration);
125121
this.inner.setContext(materialJs, validityJs, referenceBlock);
@@ -181,7 +177,8 @@ function createParseContext(ctx: ParseContext): ParseContextJs {
181177
ctx.material.chainName,
182178
ctx.material.specName,
183179
ctx.material.specVersion,
184-
ctx.material.txVersion
180+
ctx.material.txVersion,
181+
ctx.material.metadataHex,
185182
);
186183
return new ParseContextJs(material, ctx.sender ?? null);
187184
}

0 commit comments

Comments
 (0)