|
| 1 | +# v1.4.4 Release Notes |
| 2 | + |
| 3 | +## New Features |
| 4 | + |
| 5 | +### Account Abstraction (AA) Wallet Support — `txHashResolver` |
| 6 | + |
| 7 | +Added support for Account Abstraction wallets (e.g. **ZeroDev**, **Dynamic Global Wallet**) via a new optional `txHashResolver` configuration parameter. |
| 8 | + |
| 9 | +#### Problem |
| 10 | + |
| 11 | +When using AA wallets, `writeContract` returns a **UserOperation hash** instead of a standard **transaction hash**. The SDK's `waitForTransactionReceipt` only works with regular transaction hashes, causing AA wallet transactions to fail silently or hang. |
| 12 | + |
| 13 | +#### Solution |
| 14 | + |
| 15 | +A new `txHashResolver` option can be passed when creating a `StoryClient`. This function resolves UserOperation hashes into on-chain transaction hashes before the SDK waits for receipts. The resolver is applied transparently — **all existing SDK methods work without any changes**. |
| 16 | + |
| 17 | +#### Usage |
| 18 | + |
| 19 | +**Normal wallets** — no changes needed: |
| 20 | + |
| 21 | +```typescript |
| 22 | +const client = StoryClient.newClient({ |
| 23 | + transport: http("https://aeneid.storyrpc.io"), |
| 24 | + account: privateKeyToAccount("0x..."), |
| 25 | +}); |
| 26 | +``` |
| 27 | + |
| 28 | +**ZeroDev AA wallet — Using Kernel Client directly (recommended)**: |
| 29 | + |
| 30 | +ZeroDev's `createKernelAccountClient` returns a viem smart account client whose `writeContract` internally sends the UserOperation, waits for the bundler to include it on-chain, and returns the **real tx hash**. Therefore **`txHashResolver` is NOT needed** — simply pass the kernel client as the wallet: |
| 31 | + |
| 32 | +```typescript |
| 33 | +import { createKernelAccountClient } from "@zerodev/sdk"; |
| 34 | + |
| 35 | +const kernelClient = await createKernelAccountClient({ /* ... */ }); |
| 36 | + |
| 37 | +const client = StoryClient.newClientUseWallet({ |
| 38 | + transport: http("https://aeneid.storyrpc.io"), |
| 39 | + wallet: kernelClient, |
| 40 | + // No txHashResolver needed — kernel client handles it internally |
| 41 | +}); |
| 42 | + |
| 43 | +// All SDK methods work as usual |
| 44 | +const result = await client.ipAsset.mintAndRegisterIpAssetWithPilTerms({ |
| 45 | + spgNftContract: "0x...", |
| 46 | + terms: [], |
| 47 | +}); |
| 48 | +``` |
| 49 | + |
| 50 | +**Raw UserOp wallet + txHashResolver (for AA wallets that return userOpHash)**: |
| 51 | + |
| 52 | +Some AA wallet integrations return the **UserOperation hash** directly from `writeContract` without internally waiting for the bundler receipt. For these wallets, configure `txHashResolver` to convert the userOpHash into the real on-chain tx hash: |
| 53 | + |
| 54 | +```typescript |
| 55 | +const client = StoryClient.newClientUseWallet({ |
| 56 | + transport: http("https://aeneid.storyrpc.io"), |
| 57 | + wallet: rawAAWallet, // writeContract returns userOpHash |
| 58 | + txHashResolver: async (userOpHash) => { |
| 59 | + const receipt = await bundlerClient.waitForUserOperationReceipt({ |
| 60 | + hash: userOpHash, |
| 61 | + }); |
| 62 | + return receipt.receipt.transactionHash; |
| 63 | + }, |
| 64 | +}); |
| 65 | +``` |
| 66 | + |
| 67 | +> **How to tell?** If the hash returned by the AA wallet's `writeContract` cannot be found as a transaction on a block explorer, it is a userOpHash and you need `txHashResolver`. If it can be found on-chain directly, the wallet already handles resolution internally and no resolver is needed. |
| 68 | +
|
| 69 | +**Dynamic Global Wallet**: |
| 70 | + |
| 71 | +```typescript |
| 72 | +const client = StoryClient.newClientUseWallet({ |
| 73 | + transport: http("https://aeneid.storyrpc.io"), |
| 74 | + wallet: dynamicWalletClient, |
| 75 | + txHashResolver: async (userOpHash) => { |
| 76 | + // Use Dynamic's bundler client to resolve the hash |
| 77 | + const receipt = await dynamicBundlerClient.waitForUserOperationReceipt({ |
| 78 | + hash: userOpHash, |
| 79 | + }); |
| 80 | + return receipt.receipt.transactionHash; |
| 81 | + }, |
| 82 | +}); |
| 83 | +``` |
| 84 | + |
| 85 | +#### Supported Factory Methods |
| 86 | + |
| 87 | +`txHashResolver` is supported by all three client creation methods: |
| 88 | + |
| 89 | +- `StoryClient.newClient({ ..., txHashResolver })` |
| 90 | +- `StoryClient.newClientUseWallet({ ..., txHashResolver })` |
| 91 | +- `StoryClient.newClientUseAccount({ ..., txHashResolver })` |
| 92 | + |
| 93 | +#### API Reference |
| 94 | + |
| 95 | +```typescript |
| 96 | +/** |
| 97 | + * A function that resolves a hash returned by writeContract into an actual |
| 98 | + * transaction hash that can be used with waitForTransactionReceipt. |
| 99 | + * |
| 100 | + * @param hash - The hash returned by writeContract (could be a userOpHash or txHash) |
| 101 | + * @returns The resolved on-chain transaction hash |
| 102 | + */ |
| 103 | +type TxHashResolver = (hash: Hash) => Promise<Hash>; |
| 104 | +``` |
| 105 | + |
| 106 | +## Changed Files |
| 107 | + |
| 108 | +| File | Change | |
| 109 | +|------|--------| |
| 110 | +| `packages/core-sdk/src/types/config.ts` | Added `TxHashResolver` type; added `txHashResolver` to `StoryConfig`, `UseWalletStoryConfig`, `UseAccountStoryConfig` | |
| 111 | +| `packages/core-sdk/src/client.ts` | Added `applyTxHashResolver()` method; patched `rpcClient.waitForTransactionReceipt` when resolver is provided; forwarded resolver in factory methods | |
| 112 | +| `packages/core-sdk/test/unit/client.test.ts` | Added unit tests that verify resolver wiring, hash transformation, and constructor-time patching | |
| 113 | +| `packages/core-sdk/test/integration/txHashResolver.test.ts` | ZeroDev E2E integration tests (6 passing), covering both AA wallet modes | |
| 114 | +| `packages/core-sdk/package.json` | Added `@zerodev/sdk`, `@zerodev/ecdsa-validator` as devDependencies | |
| 115 | + |
| 116 | +## Test Coverage Notes |
| 117 | + |
| 118 | +- **Unit tests**: Validate that `txHashResolver` is invoked before receipt polling and that the resolved hash is used for receipt lookup. |
| 119 | +- **Integration tests — Pass-through & Simulated**: Cover normal wallet pass-through and simulated AA hash mapping behavior. |
| 120 | +- **Integration tests — ZeroDev E2E (2 scenarios)**: |
| 121 | + - **Kernel client as wallet**: Verifies that `writeContract` returns a real txHash and the SDK works correctly without a resolver. |
| 122 | + - **Raw userOp wallet + txHashResolver**: Uses a custom wallet wrapper (whose `writeContract` returns a userOpHash) to verify end-to-end that the resolver converts the userOpHash into a real on-chain txHash. |
| 123 | +- ZeroDev E2E tests require `BUNDLER_RPC_URL` and `WALLET_PRIVATE_KEY` in `.env`. Tests are automatically skipped when these are not configured. |
0 commit comments