feat: integrate TransactionBuilder into Client#48
Conversation
solidsnakedev
commented
Oct 27, 2025
- Add newTx() method to Client interfaces (ReadOnly, Signing)
- Implement SignBuilder and SubmitBuilder for transaction lifecycle
- Add generic type support (SignBuilder vs TransactionResultBase)
- Refactor protocol parameters resolution to 2-way (BuildOptions > Provider)
- Fix type inference with default generic in makeTxBuilder
- Update test files to use new protocol parameters pattern
…n flow - Add newTx() method to Client interfaces (ReadOnly, Signing) - Implement SignBuilder and SubmitBuilder for transaction lifecycle - Add generic type support (SignBuilder vs TransactionResultBase) - Refactor protocol parameters resolution to 2-way (BuildOptions > Provider) - Fix type inference with default generic in makeTxBuilder - Update test files to use new protocol parameters pattern
There was a problem hiding this comment.
Pull Request Overview
This PR integrates the TransactionBuilder into the Client API, enabling type-safe transaction building workflows. The integration introduces a two-tier builder pattern (SignBuilder → SubmitBuilder) and refactors protocol parameters resolution to support both automatic (from provider) and manual (via BuildOptions) configuration.
Key Changes:
- Added
newTx()method to Client interfaces returning type-safe builders based on wallet capability - Implemented SignBuilder and SubmitBuilder for the transaction lifecycle
- Refactored protocol parameters to be resolved during
build()instead of constructor - Migrated cryptographic operations from libsodium to @noble/curves
- Added effect-runtime utility for clean error handling
Reviewed Changes
Copilot reviewed 105 out of 108 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/evolution/src/sdk/client/Client.ts | Added newTx() method signatures and PrivateKeyWalletConfig interface |
| packages/evolution/src/sdk/client/ClientImpl.ts | Implemented newTx() for ReadOnlyClient and SigningClient with wallet-based builder configuration |
| packages/evolution/src/sdk/builders/TransactionBuilder.ts | Added generic type parameter TResult and refactored config to use wallet/provider instead of direct parameters |
| packages/evolution/src/sdk/builders/SignBuilder.ts | Extended to include TransactionResultBase methods and removed SubmitBuilder definition |
| packages/evolution/src/sdk/builders/TransactionResult.ts | New base interface for built transactions providing unsigned tx, fake witnesses, and fee estimation |
| packages/evolution/src/sdk/builders/SignBuilderImpl.ts | Implementation of SignBuilder delegating to wallet's signTx method |
| packages/evolution/src/sdk/builders/SubmitBuilder.ts | New interface for signed transactions ready for blockchain submission |
| packages/evolution/src/sdk/builders/SubmitBuilderImpl.ts | Implementation of SubmitBuilder delegating to provider's submitTx method |
| packages/evolution/src/sdk/wallet/Derivation.ts | Changed walletFromSeed/walletFromPrivateKey to return Effects and added keyStore/keyHash fields to result |
| packages/evolution/src/sdk/wallet/WalletNew.ts | Changed address/rewardAddress to methods returning Effects instead of direct Effect properties |
| packages/evolution/src/core/PrivateKey.ts | Migrated from libsodium to @noble/curves for cryptographic operations |
| packages/evolution/src/core/VKey.ts | Migrated from libsodium to @noble/curves for public key derivation |
| packages/evolution/src/core/Bip32PrivateKey.ts | Migrated from libsodium to @noble/curves and changed Either namespace to return Effects |
| packages/evolution/src/core/Bip32PublicKey.ts | Migrated from libsodium to @noble/curves for key derivation |
| packages/evolution/src/utils/effect-runtime.ts | New utility for running Effects with cleaned stack traces |
| packages/evolution/src/sdk/provider/internal/BlockfrostEffect.ts | Changed submitTx to use Uint8Array with proper content-type handling |
| packages/evolution/test/*.test.ts | Updated test files to pass protocol parameters via BuildOptions instead of TxBuilderConfig |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)
packages/evolution/src/sdk/wallet/Derivation.ts:1
- [nitpick] The pattern of checking Either result._tag and throwing the left value is used repeatedly throughout this function. Consider extracting this into a helper function to reduce duplication and improve maintainability.
import { mnemonicToEntropy } from "@scure/bip39"
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| readonly address: () => Effect.Effect<Address.Address, WalletError> | ||
| readonly rewardAddress: () => Effect.Effect<RewardAddress.RewardAddress | null, WalletError> |
There was a problem hiding this comment.
[nitpick] The address and rewardAddress methods now return functions that return Effects, creating an extra layer of indirection. Consider whether this additional wrapping is necessary or if the methods could directly be Effects (as they were before). This change affects all wallet implementations and increases complexity.
| readonly address: () => Effect.Effect<Address.Address, WalletError> | |
| readonly rewardAddress: () => Effect.Effect<RewardAddress.RewardAddress | null, WalletError> | |
| readonly address: Effect.Effect<Address.Address, WalletError> | |
| readonly rewardAddress: Effect.Effect<RewardAddress.RewardAddress | null, WalletError> |
| (input: A) => | ||
| Schema.decodeEither(schema)(input).pipe( | ||
| Either.mapLeft((e) => new ErrorClass({ message: "Failed to decode input", cause: e })) | ||
| Either.mapLeft((e) => new ErrorClass({ message: e.message, cause: e })) |
There was a problem hiding this comment.
[nitpick] The error message is now extracted from the error object (e.message) instead of using a generic message. This is an improvement, but consider adding null/undefined checks or using optional chaining to handle cases where e.message might not exist.
| * Wallet creation is synchronous - sodium initialization and key derivation | ||
| * happen lazily on first crypto operation (signTx, signMessage). |
There was a problem hiding this comment.
The comment mentions 'sodium initialization' but the code has migrated away from libsodium to @noble/curves. Update the documentation to reflect the current implementation using @noble/curves.
| * Wallet creation is synchronous - sodium initialization and key derivation | |
| * happen lazily on first crypto operation (signTx, signMessage). | |
| * Wallet creation is synchronous - cryptographic key derivation and signing | |
| * (using @noble/curves) happen lazily on first crypto operation (signTx, signMessage). |
| const { prefix, words } = yield* ParseResult.try({ | ||
| try: () => bech32.decode(fromA as any, 1023), | ||
| catch: (error) => new ParseResult.Type(ast, fromA, `Failed to decode Bech32: ${(error as Error).message}`) | ||
| catch: (error) => new ParseResult.Type(ast, fromA, `Failed to decode bech32 string: ${(error as Error).message}`) |
There was a problem hiding this comment.
The error message has been changed from 'Failed to decode Bech32' to 'Failed to decode bech32 string', which is more descriptive. However, using (error as Error).message without proper type guards could lead to runtime errors if error is not an Error instance. Consider adding a type guard or optional chaining.
| catch: (error) => new ParseResult.Type(ast, fromA, `Failed to decode bech32 string: ${(error as Error).message}`) | |
| catch: (error) => new ParseResult.Type(ast, fromA, `Failed to decode bech32 string: ${error?.message ?? String(error)}`) |