Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 21 additions & 48 deletions packages/wasm-dot/js/builder.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,46 @@
/**
* Transaction building from high-level intents.
* Transaction building from high-level business intents.
*
* Provides the `buildTransaction()` function for building DOT transactions.
* Follows wallet-platform pattern: buildTransaction(intent, context)
* The crate handles intent composition internally (e.g., stake with proxy
* automatically produces a batchAll of bond + addProxy).
*/

import { BuilderNamespace } from "./wasm/wasm_dot.js";
import { DotTransaction } from "./transaction.js";
import type { TransactionIntent, BuildContext } from "./types.js";

/**
* Build a DOT transaction from an intent and context.
* Build a DOT transaction from a business-level intent and context.
*
* This function takes a declarative TransactionIntent and BuildContext,
* producing a Transaction object that can be inspected, signed, and serialized.
* The intent describes *what* to do (payment, stake, etc.) and the context
* provides *how* to build it (sender, nonce, material, validity).
* Multi-call intents are batched automatically.
*
* The returned transaction is unsigned - signatures should be added via
* `addSignature()` before serializing with `toBytes()` and broadcasting.
*
* @param intent - What to do (transfer, stake, etc.)
* @param context - How to build it (sender, nonce, material, validity, referenceBlock)
* @returns A Transaction object that can be inspected, signed, and serialized
* @param intent - Business intent (payment, stake, unstake, claim, etc.)
* @param context - Build context (sender, nonce, material, validity, referenceBlock)
* @returns An unsigned DotTransaction ready for signing
* @throws Error if the intent cannot be built (e.g., invalid addresses)
*
* @example
* ```typescript
* import { buildTransaction } from '@bitgo/wasm-dot';
*
* // Build a simple DOT transfer
* // Payment
* const tx = buildTransaction(
* { type: 'transfer', to: '5FHneW46...', amount: 1000000000000n, keepAlive: true },
* {
* sender: '5EGoFA95omzemRssELLDjVenNZ68aXyUeqtKQScXSEBvVJkr',
* nonce: 5,
* material: {
* genesisHash: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3',
* chainName: 'Polkadot',
* specName: 'polkadot',
* specVersion: 9150,
* txVersion: 9
* },
* validity: { firstValid: 1000, maxDuration: 2400 },
* referenceBlock: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3'
* }
* { type: 'payment', to: '5FHneW46...', amount: 1000000000000n },
* context
* );
*
* // Inspect the transaction
* console.log(tx.nonce);
*
* // Get the signable payload for signing
* const payload = tx.signablePayload();
*
* // Add signature and serialize
* tx.addSignature(signerPubkey, signature);
* const txBytes = tx.toBytes();
* ```
* // New stake (produces batchAll of bond + addProxy)
* const stakeTx = buildTransaction(
* { type: 'stake', amount: 5000000000000n, proxyAddress: '5Grwva...' },
* context
* );
*
* @example
* ```typescript
* // Build with batch (multiple operations)
* const tx = buildTransaction(
* {
* type: 'batch',
* calls: [
* { type: 'transfer', to: recipient, amount: 1000000000000n },
* { type: 'stake', amount: 5000000000000n, payee: { type: 'staked' } }
* ],
* atomic: true
* },
* // Full unstake (produces batchAll of removeProxy + chill + unbond)
* const unstakeTx = buildTransaction(
* { type: 'unstake', amount: 5000000000000n, stopStaking: true, proxyAddress: '5Grwva...' },
* context
* );
* ```
Expand Down
110 changes: 52 additions & 58 deletions packages/wasm-dot/js/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
/**
* TypeScript type definitions for wasm-dot
*
* Follows wallet-platform pattern: buildTransaction(intent, context)
* - intent: what to do (transfer, stake, etc.) - single operation
* buildTransaction(intent, context)
* - intent: business-level intent (payment, stake, unstake, etc.)
* - context: how to build it (sender, nonce, material, validity)
*
* The crate handles intent composition internally. For example, a stake
* intent with a proxy address produces a batchAll(bond, addProxy) extrinsic.
*/

// =============================================================================
Expand Down Expand Up @@ -54,9 +57,9 @@ export interface ParseContext {
material: Material;
/** Sender address (optional, helps with decoding) */
sender?: string;
/** Reference block hash (not in extrinsic bytes pass-through for consumers) */
/** Reference block hash (not in extrinsic bytes, pass-through for consumers) */
referenceBlock?: string;
/** Block number when transaction becomes valid (not in extrinsic bytes pass-through for consumers) */
/** Block number when transaction becomes valid (not in extrinsic bytes, pass-through for consumers) */
blockNumber?: number;
}

Expand All @@ -65,9 +68,7 @@ export interface ParseContext {
// =============================================================================

/**
* Build context - contains all non-intent data needed to build a transaction
*
* Matches wallet-platform's material + nonce + validity pattern.
* Build context: contains all non-intent data needed to build a transaction.
*/
export interface BuildContext {
/** Sender address (SS58 encoded) */
Expand All @@ -89,46 +90,54 @@ export interface BuildContext {
// =============================================================================

/**
* Transaction intent - a single operation to perform
* Business-level transaction intent.
*
* Discriminated union using the `type` field.
* For multiple operations, use the `batch` intent type.
* Discriminated union using the `type` field. The crate handles composing
* these into the correct Polkadot extrinsic calls, including automatic
* batching when multiple calls are needed (e.g., stake with proxy).
*/
export type TransactionIntent =
| TransferIntent
| TransferAllIntent
| PaymentIntent
| ConsolidateIntent
| StakeIntent
| UnstakeIntent
| WithdrawUnbondedIntent
| ChillIntent
| AddProxyIntent
| RemoveProxyIntent
| BatchIntent;

export interface TransferIntent {
type: "transfer";
| ClaimIntent
| FillNonceIntent;

/** Transfer DOT to a recipient */
export interface PaymentIntent {
type: "payment";
/** Recipient address (SS58) */
to: string;
/** Amount in planck */
amount: bigint;
/** Use transferKeepAlive (default: true) */
/** Use transferKeepAlive to prevent reaping (default: true) */
keepAlive?: boolean;
}

export interface TransferAllIntent {
type: "transferAll";
/** Sweep all DOT to a recipient (transferAll) */
export interface ConsolidateIntent {
type: "consolidate";
/** Recipient address (SS58) */
to: string;
/** Keep account alive after transfer */
/** Keep sender account alive after transfer (default: true) */
keepAlive?: boolean;
}

/**
* Stake DOT.
*
* - With `proxyAddress`: new stake, produces batchAll(bond, addProxy)
* - Without `proxyAddress`: top-up, produces bondExtra
*/
export interface StakeIntent {
type: "stake";
/** Amount to stake in planck */
amount: bigint;
/** Where to send staking rewards */
/** Reward destination (default: Staked / compound) */
payee?: StakePayee;
/** Proxy address for new stake. Absent means top-up (bondExtra). */
proxyAddress?: string;
}

export type StakePayee =
Expand All @@ -137,48 +146,33 @@ export type StakePayee =
| { type: "controller" }
| { type: "account"; address: string };

/**
* Unstake DOT.
*
* - `stopStaking=true` + `proxyAddress`: full unstake, produces
* batchAll(removeProxy, chill, unbond)
* - `stopStaking=false`: partial unstake, produces unbond
*/
export interface UnstakeIntent {
type: "unstake";
/** Amount to unstake in planck */
amount: bigint;
/** Full unstake (remove proxy + chill) or partial (just unbond). Default: false */
stopStaking?: boolean;
/** Proxy address to remove (required when stopStaking=true) */
proxyAddress?: string;
}

export interface WithdrawUnbondedIntent {
type: "withdrawUnbonded";
/** Number of slashing spans (usually 0) */
/** Claim (withdraw unbonded) DOT after the unbonding period */
export interface ClaimIntent {
type: "claim";
/** Number of slashing spans (default: 0) */
slashingSpans?: number;
}

export interface ChillIntent {
type: "chill";
}

export interface AddProxyIntent {
type: "addProxy";
/** Delegate address (SS58) */
delegate: string;
/** Proxy type (Any, NonTransfer, Staking, etc.) */
proxyType: string;
/** Delay in blocks */
delay?: number;
}

export interface RemoveProxyIntent {
type: "removeProxy";
/** Delegate address (SS58) */
delegate: string;
/** Proxy type */
proxyType: string;
/** Delay in blocks */
delay?: number;
}

export interface BatchIntent {
type: "batch";
/** List of intents to execute */
calls: TransactionIntent[];
/** Use batchAll (atomic) instead of batch (default: true) */
atomic?: boolean;
/** Zero-value self-transfer to advance the account nonce */
export interface FillNonceIntent {
type: "fillNonce";
}

// =============================================================================
Expand Down
Loading