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
3 changes: 2 additions & 1 deletion packages/sdk/src/blob-submitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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",
Expand Down
17 changes: 17 additions & 0 deletions packages/sdk/src/sign-blob-transaction.ts
Original file line number Diff line number Diff line change
@@ -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<Signature> {
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)'
);
}
2 changes: 1 addition & 1 deletion packages/sdk/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type {
} from 'ethers';

// Re-export ethers types
export type Signer = EthersSigner & { signRawTransaction(rawTx: BytesLike): Promise<Signature> };
export type Signer = EthersSigner & { signRawTransaction?: (rawTx: BytesLike) => Promise<Signature> };
export type Provider = EthersProvider;
export type FeeData = EthersFeeData;
export type TransactionResponse = EthersTransactionResponse;
Expand Down
31 changes: 31 additions & 0 deletions packages/sdk/test/__unit__/sign-blob-transaction.test.ts
Original file line number Diff line number Diff line change
@@ -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'
);
});
});