New Features
- Safe ERC-4337 module migration helpers.
SafeAccountV0_3_0.createMigrateToSafeMultiChainSigAccountV1MetaTransactions(nodeRpcUrl, overrides?)builds thedisableModule+enableModule+setFallbackHandlerbatch that migrates a deployed Safe from the EntryPoint v0.7 module to the v0.9Safe4337MultiChainSignatureModule. Both modules are stateless, so no storage clearing is required. Unless{ skipPreflight: true }is passed, it first verifies on-chain that the account is actually a Safe running the old module (the module is enabled and is the current fallback handler) on a Safe version>= 1.4.1, turning a would-be cryptic on-chainAA23/AA24into a clear up-front error. - New Safe / transport readers.
SafeAccount.getFallbackHandler(nodeRpcUrl)(the active 4337 module),SafeAccount.getSafeVersion(nodeRpcUrl)(readsVERSION()),JsonRpcNode.getStorageAt(address, slot, blockTag?), and the exportedSAFE_FALLBACK_HANDLER_STORAGE_SLOTconstant. - Safe instance manual signing helpers.
SafeAccountandSafeMultiChainSigAccountexpose instance-level manual signing helpers that match the existing static EIP-712 helper API, so apps that already hold a Safe instance can sign without re-deriving the static call surface. SafeMultiChainSigAccount.estimateUserOperationGas. New instance method that estimates gas for a multi-chain-signature UserOperation against a bundler, matching the estimation surface already available on the other account classes.- Signer functions may return synchronously.
SignHashFnandSignTypedDataFnreturn types widen toHex | Promise<Hex>, so local-key signers can return a signature without wrapping it in aPromise. Existing async signers are unaffected. - UserOperation revert-reason decoding and AA-code parsing. New
decodeUserOperationRevertReason(receipt)reads the EntryPointUserOperationRevertReasonlog directly from a mined receipt and returns the decoded reason (Error string, Panic code, or empty for likely out-of-gas) with no extra RPC call, matching the receipt'suserOpHashso multi-op bundles return the right entry. EntryPointAAxxrevert codes (e.g.AA21) are parsed intoAbstractionKitError.aaCodeso callers can branch on a stable contract-defined code instead of message-text matching.UserOperationReverttype andparseAaCodehelper are exported. ethersruntime dependency removed. Account, paymaster, transport, signer, and utility surfaces now use an internalethereUtilsmodule for ABI encoding/decoding, keccak/hashing, typed-data, andBigInt-safe helpers, shrinking the install footprint. Public API shapes are unchanged.
Breaking Changes
UserOperationReceipt.logsandUserOperationReceiptResult.logsare now structuredLog[]instead of a JSON-encoded string. Callers that previously didJSON.parse(receipt.logs)should drop the parse and read the array directly:// Before const logs = JSON.parse(receipt.logs); // After const logs = receipt.logs;
- Removed the IIFE / UMD (
unpkg) browser build.dist/index.iife.jsand theunpkgfield inpackage.jsonwere removed. The<script>/ CDN build was effectively unusable as a standalone script: it externalized its runtime deps as page globals (ethersin 0.3.x,@noble/*in 0.4.0), so loading it without first providing those globals threwReferenceError. It was unused, so it was removed rather than maintained. Install via npm and use a bundler; the ESM (dist/index.mjs) and CJS (dist/index.cjs) entries are unchanged.
Bug Fixes
- Browser and React Native compatibility.
generateOnChainIdentifierusedBufferand ABIstringdecoding usedTextDecoder, both undefined in browsers without a polyfill and in React Native / Hermes, so those paths threwReferenceError. They now use internal pure-JS UTF-8 helpers (toUtf8Bytesand the newfromUtf8Bytes, not exported from the package), so the SDK runs in browsers (Vite, webpack, esbuild) and React Native out of the box.fromUtf8Bytesthrows aninvalid UTF-8error on malformed input (overlong, surrogate-range, out-of-range, or truncated sequences) rather than silently coercing the bytes into other characters, so consumers should not expect U+FFFD replacement characters. The Calibur, Safe, and Simple7702 executor-calldata decode paths now hex-encode the innerbytespayload instead of UTF-8-decoding it, which had corrupted the selector and arguments. SafeAccountV0_2_0.createMigrateToSafeAccountV0_3_0MetaTransactionspredecessor wiring. WhensafeV06ModuleAddresswas set explicitly, the v0.6 → v0.7 migration passed the module being disabled as its own linked-list predecessor, producingdisableModule(prev = module, module). It now exposes a dedicatedprevModuleAddressoverride (defaulting to the on-chain lookup) and shares the generic migration implementation. Default callers are unaffected.- v0.6
verificationGasLimitmultiplier incalculateUserOperationMaxGasCost. The paymaster multiplier was applied to the wrong factor on v0.6 UserOperations, undercounting the maximum gas cost. Fixed. - ERC-7677 / Candide paymaster
tokenCostrounds-to-zero on cheap-gas chains. On chains whereexchangeRate * maxGasCostWei < 10^18, the floor division collapsedtokenCostto0nand the prepended ERC-20 approval was also0n, causing the UserOperation to revert or the paymaster to absorb the full cost. The twoErc7677Paymaster/CandidePaymastercost paths and the publiccalculateUserOperationErc20TokenMaxGasCosthelper now floortokenCostto a minimum of1token smallest-unit.pimlico_getTokenQuotesandpm_supportedERC20Tokensresponses with a non-positiveexchangeRatenow throwPAYMASTER_ERRORinstead of silently feeding a zero rate into the math. createDisableModuleMetaTransactionmodule lookup now paginates. The predecessor lookup read a singlegetModulesPaginatedpage, so on a Safe with more enabled modules than the page size it could miss the target module or compute the wrong linked-list predecessor and produce an on-chain-revertingdisableModule. It now walks every page with a cursor.- Tenderly API key masked in error context.
callTenderlySimulateBundleerrors no longer surface the raw access key in their context payload.
Full Changelog: v0.3.8...v0.4.0