Skip to content

Commit e838713

Browse files
committed
refactor(sdk): serialize-transaction to use ox
1 parent c176cc9 commit e838713

File tree

14 files changed

+193
-70
lines changed

14 files changed

+193
-70
lines changed

packages/thirdweb/src/transaction/actions/gasless/providers/biconomy.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Address } from "abitype";
2-
import { type TransactionSerializable, encodeAbiParameters } from "viem";
2+
import { encodeAbiParameters } from "viem";
33
import { ZERO_ADDRESS } from "../../../../constants/addresses.js";
44
import { getContract } from "../../../../contract/contract.js";
55
import { isHex } from "../../../../utils/encoding/helpers/is-hex.js";
@@ -8,6 +8,7 @@ import { stringify } from "../../../../utils/json.js";
88
import type { Account } from "../../../../wallets/interfaces/wallet.js";
99
import type { PreparedTransaction } from "../../../prepare-transaction.js";
1010
import { readContract } from "../../../read-contract.js";
11+
import type { SerializableTransaction } from "../../../serialize-transaction.js";
1112
import type { WaitForReceiptOptions } from "../../wait-for-tx-receipt.js";
1213

1314
/**
@@ -27,7 +28,7 @@ type SendBiconomyTransactionOptions = {
2728
// TODO: update this to `Transaction<"prepared">` once the type is available to ensure only prepared transactions are accepted
2829
// biome-ignore lint/suspicious/noExplicitAny: library function that accepts any prepared transaction type
2930
transaction: PreparedTransaction<any>;
30-
serializableTransaction: TransactionSerializable;
31+
serializableTransaction: SerializableTransaction;
3132
gasless: BiconomyOptions;
3233
};
3334

packages/thirdweb/src/transaction/actions/gasless/providers/engine.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import type { Address } from "abitype";
2-
import type { TransactionSerializable } from "viem";
32
import { getContract } from "../../../../contract/contract.js";
43
import { stringify } from "../../../../utils/json.js";
54
import type { Account } from "../../../../wallets/interfaces/wallet.js";
65
import type { PreparedTransaction } from "../../../prepare-transaction.js";
76
import { readContract } from "../../../read-contract.js";
7+
import type { SerializableTransaction } from "../../../serialize-transaction.js";
88
import {
99
type WaitForReceiptOptions,
1010
waitForReceipt,
@@ -28,7 +28,7 @@ type SendengineTransactionOptions = {
2828
// TODO: update this to `Transaction<"prepared">` once the type is available to ensure only prepared transactions are accepted
2929
// biome-ignore lint/suspicious/noExplicitAny: library function that accepts any prepared transaction type
3030
transaction: PreparedTransaction<any>;
31-
serializableTransaction: TransactionSerializable;
31+
serializableTransaction: SerializableTransaction;
3232
gasless: EngineOptions;
3333
};
3434

packages/thirdweb/src/transaction/actions/gasless/providers/openzeppelin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import type { Address } from "abitype";
2-
import type { TransactionSerializable } from "viem";
32
import { getContract } from "../../../../contract/contract.js";
43
import { isHex } from "../../../../utils/encoding/helpers/is-hex.js";
54
import { stringify } from "../../../../utils/json.js";
65
import type { Account } from "../../../../wallets/interfaces/wallet.js";
76
import type { PreparedTransaction } from "../../../prepare-transaction.js";
87
import { readContract } from "../../../read-contract.js";
8+
import type { SerializableTransaction } from "../../../serialize-transaction.js";
99
import type { WaitForReceiptOptions } from "../../wait-for-tx-receipt.js";
1010

1111
/**
@@ -26,7 +26,7 @@ type SendOpenZeppelinTransactionOptions = {
2626
// TODO: update this to `Transaction<"prepared">` once the type is available to ensure only prepared transactions are accepted
2727
// biome-ignore lint/suspicious/noExplicitAny: library function that accepts any prepared transaction type
2828
transaction: PreparedTransaction<any>;
29-
serializableTransaction: TransactionSerializable;
29+
serializableTransaction: SerializableTransaction;
3030
gasless: OpenZeppelinOptions;
3131
};
3232

packages/thirdweb/src/transaction/actions/gasless/send-gasless-transaction.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type { TransactionSerializable } from "viem";
21
import type { Account } from "../../../wallets/interfaces/wallet.js";
32
import type { PreparedTransaction } from "../../prepare-transaction.js";
3+
import type { SerializableTransaction } from "../../serialize-transaction.js";
44
import { addTransactionToStore } from "../../transaction-store.js";
55
import type { WaitForReceiptOptions } from "../wait-for-tx-receipt.js";
66
import type { GaslessOptions } from "./types.js";
@@ -10,7 +10,7 @@ type SendGaslessTransactionOptions = {
1010
// TODO: update this to `Transaction<"prepared">` once the type is available to ensure only prepared transactions are accepted
1111
// biome-ignore lint/suspicious/noExplicitAny: library function that accepts any prepared transaction type
1212
transaction: PreparedTransaction<any>;
13-
serializableTransaction: TransactionSerializable;
13+
serializableTransaction: SerializableTransaction;
1414
gasless: GaslessOptions;
1515
};
1616

packages/thirdweb/src/transaction/actions/sign-transaction.test.ts

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,20 @@
1-
import {
2-
type TransactionSerializable,
3-
type TransactionSerializableBase,
4-
type TransactionSerializableEIP1559,
5-
type TransactionSerializableEIP2930,
6-
type TransactionSerializableLegacy,
7-
zeroAddress,
8-
} from "viem";
91
import { describe, expect, test } from "vitest";
102
import { ANVIL_PKEY_A } from "~test/test-wallets.js";
3+
import { ZERO_ADDRESS } from "../../constants/addresses.js";
114
import { fromGwei } from "../../utils/units.js";
125
import { signTransaction } from "./sign-transaction.js";
136

147
const BASE_TRANSACTION = {
158
gas: 21000n,
169
nonce: 785,
17-
} satisfies TransactionSerializableBase;
10+
} as const;
1811

1912
describe("eip1559", () => {
2013
const BASE_EIP1559_TRANSACTION = {
2114
...BASE_TRANSACTION,
2215
chainId: 1,
2316
type: "eip1559",
24-
} as const satisfies TransactionSerializableEIP1559;
17+
} as const;
2518

2619
test("default", () => {
2720
const signature = signTransaction({
@@ -69,7 +62,7 @@ describe("eip1559", () => {
6962
...BASE_EIP1559_TRANSACTION,
7063
accessList: [
7164
{
72-
address: zeroAddress,
65+
address: ZERO_ADDRESS,
7366
storageKeys: [
7467
"0x0000000000000000000000000000000000000000000000000000000000000001",
7568
"0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe",
@@ -105,7 +98,7 @@ describe("eip2930", () => {
10598
...BASE_TRANSACTION,
10699
chainId: 1,
107100
type: "eip2930",
108-
} as const satisfies TransactionSerializable;
101+
} as const;
109102

110103
test("default", () => {
111104
const signature = signTransaction({
@@ -124,13 +117,13 @@ describe("eip2930", () => {
124117
gasPrice: fromGwei("2"),
125118
accessList: [
126119
{
127-
address: zeroAddress,
120+
address: ZERO_ADDRESS,
128121
storageKeys: [
129122
"0x0000000000000000000000000000000000000000000000000000000000000000",
130123
],
131124
},
132125
],
133-
} as TransactionSerializableEIP2930,
126+
},
134127
privateKey: ANVIL_PKEY_A,
135128
});
136129

@@ -159,7 +152,7 @@ describe("legacy", () => {
159152
...BASE_TRANSACTION,
160153
gasPrice: fromGwei("2"),
161154
type: "legacy",
162-
} as const satisfies TransactionSerializableLegacy;
155+
} as const;
163156

164157
test("default", () => {
165158
const signature = signTransaction({

packages/thirdweb/src/transaction/actions/sign-transaction.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import type { TransactionSerializable } from "viem";
1+
import * as ox__Hash from "ox/Hash";
2+
import * as ox__Secp256k1 from "ox/Secp256k1";
23
import type { Hex } from "../../utils/encoding/hex.js";
3-
import { keccak256 } from "../../utils/hashing/keccak256.js";
4-
import { sign } from "../../utils/signatures/sign.js";
5-
import { serializeTransaction } from "../serialize-transaction.js";
4+
import {
5+
type SerializableTransaction,
6+
serializeTransaction,
7+
} from "../serialize-transaction.js";
68

79
export type SignTransactionOptions = {
8-
transaction: TransactionSerializable;
10+
transaction: SerializableTransaction;
911
privateKey: Hex;
1012
// TODO: Add optional custom serializer here
1113
};
@@ -32,14 +34,10 @@ export function signTransaction({
3234
transaction,
3335
privateKey,
3436
}: SignTransactionOptions): Hex {
35-
if (transaction.type === "eip4844") {
36-
transaction = { ...transaction, sidecars: false };
37-
}
38-
3937
const serializedTransaction = serializeTransaction({ transaction });
4038

41-
const signature = sign({
42-
hash: keccak256(serializedTransaction),
39+
const signature = ox__Secp256k1.sign({
40+
payload: ox__Hash.keccak256(serializedTransaction),
4341
privateKey: privateKey,
4442
});
4543
return serializeTransaction({

packages/thirdweb/src/transaction/actions/to-serializable-transaction.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import type { TransactionSerializable } from "viem";
21
import { getGasOverridesForTransaction } from "../../gas/fee-data.js";
32
import { getRpcClient } from "../../rpc/rpc.js";
43
import { getAddress } from "../../utils/address.js";
54
import { isZkSyncChain } from "../../utils/any-evm/zksync/isZkSyncChain.js";
65
import { resolvePromisedValue } from "../../utils/promise/resolve-promised-value.js";
76
import type { PreparedTransaction } from "../prepare-transaction.js";
7+
import type { SerializableTransaction } from "../serialize-transaction.js";
88
import { encode } from "./encode.js";
99
import { estimateGas } from "./estimate-gas.js";
1010

@@ -112,5 +112,5 @@ export async function toSerializableTransaction(
112112
accessList,
113113
value,
114114
...feeData,
115-
} satisfies TransactionSerializable;
115+
} satisfies SerializableTransaction;
116116
}

packages/thirdweb/src/transaction/actions/zksync/getEip721Domain.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import type { TransactionSerializable } from "viem";
21
import { hashBytecode } from "viem/zksync";
32
import type { Address } from "../../../utils/address.js";
43
import { toHex } from "../../../utils/encoding/hex.js";
54
import type {
65
EIP712SerializedTransaction,
76
EIP712TransactionOptions,
87
} from "../../prepare-transaction.js";
8+
import type { SerializableTransaction } from "../../serialize-transaction.js";
99

10-
export type EIP721TransactionSerializable = TransactionSerializable & {
10+
export type EIP721TransactionSerializable = SerializableTransaction & {
1111
from: Address;
1212
} & EIP712TransactionOptions;
1313
export const gasPerPubdataDefault = 50000n;

packages/thirdweb/src/transaction/serialize-transaction.ts

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import * as ox__TransactionEnvelopeEip2930 from "ox/TransactionEnvelopeEip2930";
55
import * as ox__TransactionEnvelopeLegacy from "ox/TransactionEnvelopeLegacy";
66
import type { Hex } from "../utils/encoding/hex.js";
77

8-
type SerializableTransaction = {
8+
export type SerializableTransaction = {
99
type?: string | undefined;
10-
r?: Hex;
11-
s?: Hex;
12-
v?: bigint;
13-
yParity?: number;
10+
r?: Hex | bigint;
11+
s?: Hex | bigint;
12+
v?: bigint | number;
13+
yParity?: bigint | number;
1414
accessList?:
1515
| ox__TransactionEnvelopeEip2930.TransactionEnvelopeEip2930["accessList"]
1616
| undefined;
@@ -22,12 +22,16 @@ type SerializableTransaction = {
2222
to?: string | null | undefined; // Must allow null for backwards compatibility
2323
nonce?: number | bigint | undefined;
2424
value?: bigint | undefined;
25+
gas?: bigint | undefined;
2526
gasLimit?: bigint | undefined;
2627
};
2728

2829
export type SerializeTransactionOptions = {
2930
transaction: SerializableTransaction;
30-
signature?: ox__Signature.Signature | undefined;
31+
signature?:
32+
| ox__Signature.Signature<true, Hex>
33+
| ox__Signature.Legacy<Hex, bigint>
34+
| undefined;
3135
};
3236

3337
/**
@@ -59,7 +63,26 @@ export function serializeTransaction(
5963

6064
// This is to maintain compatibility with our old interface (including the signature in the transaction object)
6165
const signature = (() => {
62-
if (options.signature) return options.signature;
66+
if (options.signature) {
67+
if (
68+
"v" in options.signature &&
69+
typeof options.signature.v !== "undefined"
70+
) {
71+
return ox__Signature.fromLegacy({
72+
r: ox__Hex.toBigInt(options.signature.r),
73+
s: ox__Hex.toBigInt(options.signature.s),
74+
v: Number(options.signature.v),
75+
});
76+
}
77+
78+
return {
79+
r: ox__Hex.toBigInt(options.signature.r),
80+
s: ox__Hex.toBigInt(options.signature.s),
81+
// We force the Signature type here because we filter for legacy type above
82+
yParity: (options.signature as unknown as ox__Signature.Signature)
83+
.yParity,
84+
};
85+
}
6386
if (
6487
typeof transaction.v === "undefined" &&
6588
typeof transaction.yParity === "undefined"
@@ -72,13 +95,19 @@ export function serializeTransaction(
7295
}
7396

7497
return {
75-
r: ox__Hex.toBigInt(transaction.r),
76-
s: ox__Hex.toBigInt(transaction.s),
98+
r:
99+
typeof transaction.r === "bigint"
100+
? transaction.r
101+
: ox__Hex.toBigInt(transaction.r),
102+
s:
103+
typeof transaction.s === "bigint"
104+
? transaction.s
105+
: ox__Hex.toBigInt(transaction.s),
77106
yParity:
78107
typeof transaction.v !== "undefined" &&
79108
typeof transaction.yParity === "undefined"
80109
? ox__Signature.vToYParity(Number(transaction.v))
81-
: (transaction.yParity as number),
110+
: Number(transaction.yParity),
82111
};
83112
})();
84113

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import * as ox__Hash from "ox/Hash";
2+
import * as ox__Hex from "ox/Hex";
3+
import * as ox__Signature from "ox/Signature";
4+
import { recoverAddress } from "viem";
5+
import { describe, expect, it } from "vitest";
6+
import { serializeTransaction } from "../../transaction/serialize-transaction.js";
7+
import { getKeylessTransaction } from "./keyless-transaction.js";
8+
9+
describe("getKeylessTransaction", () => {
10+
const mockTransaction = {
11+
to: "0x1234567890123456789012345678901234567890",
12+
value: 1000n,
13+
chainId: 1,
14+
gasPrice: 10n,
15+
};
16+
17+
const mockSignature = {
18+
r: "0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe",
19+
s: "0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe",
20+
v: 27n,
21+
} as const;
22+
23+
it("should return the correct signer address and serialized transaction", async () => {
24+
const serializedTransaction = serializeTransaction({
25+
transaction: mockTransaction,
26+
});
27+
28+
const hash = ox__Hash.keccak256(serializedTransaction);
29+
const expectedAddress = await recoverAddress({
30+
hash,
31+
signature: ox__Signature.toHex({
32+
r: ox__Hex.toBigInt(mockSignature.r),
33+
s: ox__Hex.toBigInt(mockSignature.s),
34+
yParity: ox__Signature.vToYParity(Number(mockSignature.v)),
35+
}),
36+
});
37+
38+
const result = await getKeylessTransaction({
39+
transaction: mockTransaction,
40+
signature: mockSignature,
41+
});
42+
43+
expect(result.signerAddress).toBe(expectedAddress);
44+
expect(result.transaction).toBe(
45+
serializeTransaction({
46+
transaction: mockTransaction,
47+
signature: mockSignature,
48+
}),
49+
);
50+
});
51+
52+
it("should throw an error if the transaction is invalid", async () => {
53+
const invalidTransaction = { ...mockTransaction, value: "invalid" };
54+
55+
await expect(
56+
getKeylessTransaction({
57+
// biome-ignore lint/suspicious/noExplicitAny: Testing invalid data
58+
transaction: invalidTransaction as any,
59+
signature: mockSignature,
60+
}),
61+
).rejects.toThrow();
62+
});
63+
});

0 commit comments

Comments
 (0)