Skip to content

Commit 16e5347

Browse files
committed
[SDK] Feature: Ox Transaction Envelopes (#5598)
CNCT-2303 Woah. This is sick. <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on updating the `thirdweb` package to enhance transaction serialization by integrating the `ox` library, renaming files, and refactoring code for better consistency in handling typed data and signatures. ### Detailed summary - Replaced instances of `coinbaseWebSDK.js` with `coinbase-web.js`. - Updated the `parseTypedData` function to handle hex `chainId`. - Refactored signature handling in various files to use `ox` library. - Introduced new types and updated existing ones for better type safety. - Added tests for `parseTypedData` functionality. - Adjusted imports across multiple files to reflect new structure. - Enhanced error handling and validation for typed data signatures. > The following files were skipped due to too many changes: `packages/thirdweb/src/adapters/ethers5.test.ts`, `packages/thirdweb/src/utils/hashing/hashTypedData.ts`, `packages/thirdweb/src/wallets/coinbase/coinbase-web.test.ts`, `packages/thirdweb/src/transaction/serialize-transaction.ts`, `packages/thirdweb/src/transaction/serialize-transaction.test.ts`, `pnpm-lock.yaml` > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 406c885 commit 16e5347

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+2083
-1420
lines changed

.changeset/little-kids-wash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": minor
3+
---
4+
5+
Update underlying APIs to use Ox for transaction serialization

packages/thirdweb/package.json

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -127,24 +127,60 @@
127127
},
128128
"typesVersions": {
129129
"*": {
130-
"adapters/*": ["./dist/types/exports/adapters/*.d.ts"],
131-
"auth": ["./dist/types/exports/auth.d.ts"],
132-
"chains": ["./dist/types/exports/chains.d.ts"],
133-
"contract": ["./dist/types/exports/contract.d.ts"],
134-
"deploys": ["./dist/types/exports/deploys.d.ts"],
135-
"event": ["./dist/types/exports/event.d.ts"],
136-
"extensions/*": ["./dist/types/exports/extensions/*.d.ts"],
137-
"pay": ["./dist/types/exports/pay.d.ts"],
138-
"react": ["./dist/types/exports/react.d.ts"],
139-
"react-native": ["./dist/types/exports/react-native.d.ts"],
140-
"rpc": ["./dist/types/exports/rpc.d.ts"],
141-
"storage": ["./dist/types/exports/storage.d.ts"],
142-
"transaction": ["./dist/types/exports/transaction.d.ts"],
143-
"utils": ["./dist/types/exports/utils.d.ts"],
144-
"wallets": ["./dist/types/exports/wallets.d.ts"],
145-
"wallets/*": ["./dist/types/exports/wallets/*.d.ts"],
146-
"modules": ["./dist/types/exports/modules.d.ts"],
147-
"social": ["./dist/types/exports/social.d.ts"]
130+
"adapters/*": [
131+
"./dist/types/exports/adapters/*.d.ts"
132+
],
133+
"auth": [
134+
"./dist/types/exports/auth.d.ts"
135+
],
136+
"chains": [
137+
"./dist/types/exports/chains.d.ts"
138+
],
139+
"contract": [
140+
"./dist/types/exports/contract.d.ts"
141+
],
142+
"deploys": [
143+
"./dist/types/exports/deploys.d.ts"
144+
],
145+
"event": [
146+
"./dist/types/exports/event.d.ts"
147+
],
148+
"extensions/*": [
149+
"./dist/types/exports/extensions/*.d.ts"
150+
],
151+
"pay": [
152+
"./dist/types/exports/pay.d.ts"
153+
],
154+
"react": [
155+
"./dist/types/exports/react.d.ts"
156+
],
157+
"react-native": [
158+
"./dist/types/exports/react-native.d.ts"
159+
],
160+
"rpc": [
161+
"./dist/types/exports/rpc.d.ts"
162+
],
163+
"storage": [
164+
"./dist/types/exports/storage.d.ts"
165+
],
166+
"transaction": [
167+
"./dist/types/exports/transaction.d.ts"
168+
],
169+
"utils": [
170+
"./dist/types/exports/utils.d.ts"
171+
],
172+
"wallets": [
173+
"./dist/types/exports/wallets.d.ts"
174+
],
175+
"wallets/*": [
176+
"./dist/types/exports/wallets/*.d.ts"
177+
],
178+
"modules": [
179+
"./dist/types/exports/modules.d.ts"
180+
],
181+
"social": [
182+
"./dist/types/exports/social.d.ts"
183+
]
148184
}
149185
},
150186
"browser": {
@@ -181,7 +217,6 @@
181217
"fuse.js": "7.0.0",
182218
"input-otp": "^1.4.1",
183219
"mipd": "0.0.7",
184-
"ox": "0.4.0",
185220
"uqr": "0.1.2",
186221
"viem": "2.21.54"
187222
},
@@ -194,6 +229,7 @@
194229
"ethers": "^5 || ^6",
195230
"expo-linking": "^6",
196231
"expo-web-browser": "^13 || ^14",
232+
"ox": "0.4.0",
197233
"react": "^18 || ^19",
198234
"react-native": "*",
199235
"react-native-aes-gcm-crypto": "^0.2",

packages/thirdweb/src/adapters/ethers5.test.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as ethers5 from "ethers5";
2+
import * as ethers6 from "ethers6";
23
import { describe, expect, it, test } from "vitest";
34
import { USDT_CONTRACT } from "~test/test-contracts.js";
45
import { ANVIL_CHAIN, FORKED_ETHEREUM_CHAIN } from "../../test/src/chains.js";
@@ -10,6 +11,7 @@ import { randomBytesBuffer } from "../utils/random.js";
1011
import { privateKeyToAccount } from "../wallets/private-key.js";
1112
import {
1213
fromEthersContract,
14+
fromEthersSigner,
1315
toEthersContract,
1416
toEthersProvider,
1517
toEthersSigner,
@@ -149,4 +151,94 @@ describe("ethers5 adapter", () => {
149151
const _decimals = await decimals({ contract: thirdwebContract });
150152
expect(_decimals).toBe(6);
151153
});
154+
155+
test("toEthersProvider should return a valid provider", async () => {
156+
const provider = toEthersProvider(ethers5, TEST_CLIENT, ANVIL_CHAIN);
157+
expect(provider).toBeDefined();
158+
expect(provider.getBlockNumber).toBeDefined();
159+
160+
// Test if the provider can fetch the block number
161+
const blockNumber = await provider.getBlockNumber();
162+
expect(typeof blockNumber).toBe("number");
163+
});
164+
165+
test("toEthersProvider should throw error with invalid ethers version", () => {
166+
const invalidEthers = ethers6;
167+
expect(() =>
168+
// biome-ignore lint/suspicious/noExplicitAny: Testing invalid data
169+
toEthersProvider(invalidEthers as any, TEST_CLIENT, ANVIL_CHAIN),
170+
).toThrow(
171+
"You seem to be using ethers@6, please use the `ethers6Adapter()`",
172+
);
173+
});
174+
});
175+
176+
describe("fromEthersSigner", () => {
177+
it("should convert an ethers5 Signer to an Account", async () => {
178+
const wallet = new ethers5.Wallet(ANVIL_PKEY_A);
179+
const account = await fromEthersSigner(wallet);
180+
181+
expect(account).toBeDefined();
182+
expect(account.address).toBe(await wallet.getAddress());
183+
});
184+
185+
it("should sign a message", async () => {
186+
const wallet = new ethers5.Wallet(ANVIL_PKEY_A);
187+
const account = await fromEthersSigner(wallet);
188+
189+
const message = "Hello, world!";
190+
const signature = await account.signMessage({ message });
191+
192+
expect(signature).toBe(await wallet.signMessage(message));
193+
});
194+
195+
it("should sign a transaction", async () => {
196+
const wallet = new ethers5.Wallet(
197+
ANVIL_PKEY_A,
198+
ethers5.getDefaultProvider(),
199+
);
200+
const account = await fromEthersSigner(wallet);
201+
202+
const transaction = {
203+
to: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
204+
value: 1n,
205+
};
206+
207+
const signedTransaction = await account.signTransaction?.(transaction);
208+
209+
expect(signedTransaction).toBe(await wallet.signTransaction(transaction));
210+
});
211+
212+
it("should sign typed data", async () => {
213+
const wallet = new ethers5.Wallet(ANVIL_PKEY_A);
214+
const account = await fromEthersSigner(wallet);
215+
216+
const domain = {
217+
name: "Ether Mail",
218+
version: "1",
219+
chainId: 1,
220+
verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
221+
};
222+
223+
const types = {
224+
Person: [
225+
{ name: "name", type: "string" },
226+
{ name: "wallet", type: "address" },
227+
],
228+
};
229+
230+
const value = {
231+
name: "Alice",
232+
wallet: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
233+
};
234+
235+
const signature = await account.signTypedData({
236+
primaryType: "Person",
237+
domain,
238+
types,
239+
message: value,
240+
});
241+
242+
expect(signature).toBeDefined();
243+
});
152244
});

packages/thirdweb/src/adapters/ethers5.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import type { Abi } from "abitype";
22
import * as universalethers from "ethers";
33
import type * as ethers5 from "ethers5";
44
import type * as ethers6 from "ethers6";
5-
import type { AccessList, Hex, TransactionSerializable } from "viem";
5+
import type { AccessList, Hex } from "viem";
66
import type { Chain } from "../chains/types.js";
77
import { getRpcUrlForChain } from "../chains/utils.js";
88
import type { ThirdwebClient } from "../client/client.js";
99
import { type ThirdwebContract, getContract } from "../contract/contract.js";
1010
import { toSerializableTransaction } from "../transaction/actions/to-serializable-transaction.js";
1111
import { waitForReceipt } from "../transaction/actions/wait-for-tx-receipt.js";
1212
import type { PreparedTransaction } from "../transaction/prepare-transaction.js";
13+
import type { SerializableTransaction } from "../transaction/serialize-transaction.js";
1314
import { toHex } from "../utils/encoding/hex.js";
1415
import type { Account } from "../wallets/interfaces/wallet.js";
1516

@@ -39,7 +40,7 @@ function assertEthers5(
3940
): asserts ethers is typeof ethers5 {
4041
if (!isEthers5(ethers)) {
4142
throw new Error(
42-
"You seem to be using ethers@6, please use the `ethers6Adapter()",
43+
"You seem to be using ethers@6, please use the `ethers6Adapter()`",
4344
);
4445
}
4546
}
@@ -279,6 +280,7 @@ export function toEthersProvider(
279280
client: ThirdwebClient,
280281
chain: Chain,
281282
): ethers5.providers.Provider {
283+
assertEthers5(ethers);
282284
const url = getRpcUrlForChain({ chain, client });
283285
const headers: HeadersInit = {
284286
"Content-Type": "application/json",
@@ -588,7 +590,7 @@ export async function toEthersSigner(
588590
* @internal
589591
*/
590592
function alignTxToEthers(
591-
tx: TransactionSerializable,
593+
tx: SerializableTransaction,
592594
): ethers5.ethers.utils.Deferrable<ethers5.ethers.providers.TransactionRequest> {
593595
const { to: viemTo, type: viemType, gas, ...rest } = tx;
594596
// massage "to" to fit ethers
@@ -623,8 +625,7 @@ function alignTxToEthers(
623625
gasLimit: gas,
624626
to,
625627
type,
626-
accessList: tx.accessList as ethers5.utils.AccessListish | undefined,
627-
};
628+
} as ethers5.ethers.utils.Deferrable<ethers5.ethers.providers.TransactionRequest>;
628629
}
629630

630631
async function alignTxFromEthers(

packages/thirdweb/src/adapters/ethers6.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ import type { Abi } from "abitype";
22
import * as universalethers from "ethers";
33
import type * as ethers5 from "ethers5";
44
import type * as ethers6 from "ethers6";
5-
import type { AccessList, Hex, TransactionSerializable } from "viem";
5+
import type { AccessList, Hex } from "viem";
66
import type { Chain } from "../chains/types.js";
77
import { getRpcUrlForChain } from "../chains/utils.js";
88
import type { ThirdwebClient } from "../client/client.js";
99
import { type ThirdwebContract, getContract } from "../contract/contract.js";
1010
import { toSerializableTransaction } from "../transaction/actions/to-serializable-transaction.js";
1111
import type { PreparedTransaction } from "../transaction/prepare-transaction.js";
12+
import type { SerializableTransaction } from "../transaction/serialize-transaction.js";
1213
import { toHex } from "../utils/encoding/hex.js";
1314
import { resolvePromisedValue } from "../utils/promise/resolve-promised-value.js";
1415
import type { Account } from "../wallets/interfaces/wallet.js";
@@ -493,7 +494,7 @@ export function toEthersSigner(
493494
* @returns The aligned transaction object.
494495
* @internal
495496
*/
496-
function alignTxToEthers(tx: TransactionSerializable) {
497+
function alignTxToEthers(tx: SerializableTransaction) {
497498
const { type: viemType, ...rest } = tx;
498499

499500
// massage "type" to fit ethers

packages/thirdweb/src/adapters/viem.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { type Account as ViemAccount, zeroAddress } from "viem";
1+
import type { Account as ViemAccount } from "viem";
22
import { privateKeyToAccount as viemPrivateKeyToAccount } from "viem/accounts";
33
import { beforeAll, describe, expect, test } from "vitest";
44
import { USDT_ABI } from "~test/abis/usdt.js";
55
import {
66
USDT_CONTRACT_ADDRESS,
77
USDT_CONTRACT_WITH_ABI,
88
} from "~test/test-contracts.js";
9-
import { ANVIL_PKEY_A } from "~test/test-wallets.js";
9+
import { ANVIL_PKEY_A, TEST_ACCOUNT_B } from "~test/test-wallets.js";
1010
import { typedData } from "~test/typed-data.js";
1111

1212
import { ANVIL_CHAIN, FORKED_ETHEREUM_CHAIN } from "../../test/src/chains.js";
@@ -87,6 +87,7 @@ describe("walletClient.toViem", () => {
8787
if (!walletClient.account) {
8888
throw new Error("Account not found");
8989
}
90+
9091
const txHash = await walletClient.sendTransaction({
9192
account: walletClient.account,
9293
chain: {
@@ -101,8 +102,8 @@ describe("walletClient.toViem", () => {
101102
decimals: ANVIL_CHAIN.nativeCurrency?.decimals || 18,
102103
},
103104
},
104-
to: zeroAddress,
105-
value: 0n,
105+
to: TEST_ACCOUNT_B.address,
106+
value: 10n,
106107
});
107108
expect(txHash).toBeDefined();
108109
expect(txHash.slice(0, 2)).toBe("0x");

packages/thirdweb/src/exports/wallets.native.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export const authenticateWithRedirect = () => {
107107
export type {
108108
CoinbaseWalletCreationOptions,
109109
CoinbaseSDKWalletConnectionOptions,
110-
} from "../wallets/coinbase/coinbaseWebSDK.js";
110+
} from "../wallets/coinbase/coinbase-web.js";
111111

112112
export type {
113113
WalletEmitter,

packages/thirdweb/src/exports/wallets.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export {
117117
export type {
118118
CoinbaseWalletCreationOptions,
119119
CoinbaseSDKWalletConnectionOptions,
120-
} from "../wallets/coinbase/coinbaseWebSDK.js";
120+
} from "../wallets/coinbase/coinbase-web.js";
121121

122122
export type {
123123
WalletEmitter,

packages/thirdweb/src/extensions/erc1271/checkContractWalletSignedTypedData.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type TypedData, type TypedDataDefinition, hashTypedData } from "viem";
1+
import * as ox__TypedData from "ox/TypedData";
22
import type { ThirdwebContract } from "../../contract/contract.js";
33
import { isHex } from "../../utils/encoding/hex.js";
44
import { isValidSignature } from "./__generated__/isValidSignature/read/isValidSignature.js";
@@ -7,11 +7,11 @@ import { isValidSignature } from "./__generated__/isValidSignature/read/isValidS
77
* @extension ERC1271
88
*/
99
export type CheckContractWalletSignTypedDataOptions<
10-
typedData extends TypedData | Record<string, unknown>,
10+
typedData extends ox__TypedData.TypedData | Record<string, unknown>,
1111
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData,
1212
> = {
1313
contract: ThirdwebContract;
14-
data: TypedDataDefinition<typedData, primaryType>;
14+
data: ox__TypedData.Definition<typedData, primaryType>;
1515
signature: string;
1616
};
1717
const MAGIC_VALUE = "0x1626ba7e";
@@ -42,15 +42,19 @@ const MAGIC_VALUE = "0x1626ba7e";
4242
* @returns A promise that resolves with a boolean indicating if the signature is valid.
4343
*/
4444
export async function checkContractWalletSignedTypedData<
45-
typedData extends TypedData | Record<string, unknown>,
45+
typedData extends ox__TypedData.TypedData | Record<string, unknown>,
4646
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData,
4747
>(options: CheckContractWalletSignTypedDataOptions<typedData, primaryType>) {
4848
if (!isHex(options.signature)) {
4949
throw new Error("The signature must be a valid hex string.");
5050
}
5151
const result = await isValidSignature({
5252
contract: options.contract,
53-
hash: hashTypedData(options.data),
53+
hash: ox__TypedData.hashStruct({
54+
primaryType: options.data.primaryType,
55+
data: options.data.message as Record<string, unknown>,
56+
types: options.data.types as ox__TypedData.Definition["types"],
57+
}),
5458
signature: options.signature,
5559
});
5660
return result === MAGIC_VALUE;

packages/thirdweb/src/react/web/utils/usePreloadWalletProviders.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export function usePreloadWalletProviders({
2121
switch (true) {
2222
case COINBASE === w.id: {
2323
const { getCoinbaseWebProvider } = await import(
24-
"../../../wallets/coinbase/coinbaseWebSDK.js"
24+
"../../../wallets/coinbase/coinbase-web.js"
2525
);
2626
await getCoinbaseWebProvider(
2727
w.getConfig() as CreateWalletArgs<typeof COINBASE>[1],

0 commit comments

Comments
 (0)