From 70b1ce381ea553ea5b021427fffa8a58e56ede50 Mon Sep 17 00:00:00 2001 From: Arunim Shukla <54760103+arunimshukla@users.noreply.github.com> Date: Wed, 23 Sep 2026 08:47:53 +0530 Subject: [PATCH] fix(sdk): support standard ethers wallets for direct EIP-7594 submission --- packages/sdk/src/blob-submitter.ts | 3 +- packages/sdk/src/sign-blob-transaction.ts | 17 ++++++++++ packages/sdk/src/types.ts | 2 +- .../__unit__/sign-blob-transaction.test.ts | 31 +++++++++++++++++++ 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 packages/sdk/src/sign-blob-transaction.ts create mode 100644 packages/sdk/test/__unit__/sign-blob-transaction.test.ts diff --git a/packages/sdk/src/blob-submitter.ts b/packages/sdk/src/blob-submitter.ts index 5bf34bd..8e17ef3 100644 --- a/packages/sdk/src/blob-submitter.ts +++ b/packages/sdk/src/blob-submitter.ts @@ -21,6 +21,7 @@ import { commitmentToVersionedHash } from './kzg.js'; import { SerializeEIP7495 } from './serialize.js'; +import { signBlobTransaction } from './sign-blob-transaction.js'; export interface BlobSubmitterConfig { rpcUrl: string; @@ -113,7 +114,7 @@ export class BlobSubmitter { } ] const unsignedSerializedTX = SerializeEIP7495(tx, null, blobs); - const signature = await signer.signRawTransaction(unsignedSerializedTX) + const signature = await signBlobTransaction(signer, unsignedSerializedTX); const serializedTX = SerializeEIP7495(tx, signature, blobs); const txHash = await this.provider._perform({ method: "broadcastTransaction", diff --git a/packages/sdk/src/sign-blob-transaction.ts b/packages/sdk/src/sign-blob-transaction.ts new file mode 100644 index 0000000..88e1b7a --- /dev/null +++ b/packages/sdk/src/sign-blob-transaction.ts @@ -0,0 +1,17 @@ +import { ethers, type Signature } from 'ethers'; +import type { Signer } from './types.js'; + +/** Sign the digest of an unsigned EIP-7594 blob transaction. */ +export async function signBlobTransaction(signer: Signer, unsignedTx: string): Promise { + if (typeof signer.signRawTransaction === 'function') { + return signer.signRawTransaction(unsignedTx); + } + + if (signer instanceof ethers.BaseWallet) { + return signer.signingKey.sign(ethers.keccak256(unsignedTx)); + } + + throw new Error( + 'EIP-7594 direct submission requires an ethers BaseWallet or a signer with signRawTransaction(rawTx)' + ); +} diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index ca5251b..28ea6fb 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -18,7 +18,7 @@ import type { } from 'ethers'; // Re-export ethers types -export type Signer = EthersSigner & { signRawTransaction(rawTx: BytesLike): Promise }; +export type Signer = EthersSigner & { signRawTransaction?: (rawTx: BytesLike) => Promise }; export type Provider = EthersProvider; export type FeeData = EthersFeeData; export type TransactionResponse = EthersTransactionResponse; diff --git a/packages/sdk/test/__unit__/sign-blob-transaction.test.ts b/packages/sdk/test/__unit__/sign-blob-transaction.test.ts new file mode 100644 index 0000000..af058cf --- /dev/null +++ b/packages/sdk/test/__unit__/sign-blob-transaction.test.ts @@ -0,0 +1,31 @@ +import { ethers } from 'ethers'; +import { signBlobTransaction } from '../../src/sign-blob-transaction.js'; +import type { Signer } from '../../src/types.js'; + +const unsignedTx = '0x03c0'; + +describe('EIP-7594 blob transaction signing', () => { + test('a standard ethers Wallet signs the raw transaction digest', async () => { + const wallet = new ethers.Wallet(`0x${'11'.repeat(32)}`); + const signature = await signBlobTransaction(wallet, unsignedTx); + + expect(ethers.recoverAddress(ethers.keccak256(unsignedTx), signature)).toBe(wallet.address); + }); + + test('a custom raw transaction signer is still supported', async () => { + const wallet = new ethers.Wallet(`0x${'22'.repeat(32)}`); + const signature = wallet.signingKey.sign(ethers.keccak256(unsignedTx)); + const customSigner = { + signRawTransaction: jest.fn(async () => signature), + } as unknown as Signer; + + expect(await signBlobTransaction(customSigner, unsignedTx)).toBe(signature); + expect(customSigner.signRawTransaction).toHaveBeenCalledWith(unsignedTx); + }); + + test('unsupported signers fail with an actionable error', async () => { + await expect(signBlobTransaction({} as Signer, unsignedTx)).rejects.toThrow( + 'requires an ethers BaseWallet or a signer with signRawTransaction' + ); + }); +});