Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/little-kids-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"thirdweb": minor
---

Update underlying APIs to use Ox for transaction serialization
74 changes: 55 additions & 19 deletions packages/thirdweb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,24 +127,60 @@
},
"typesVersions": {
"*": {
"adapters/*": ["./dist/types/exports/adapters/*.d.ts"],
"auth": ["./dist/types/exports/auth.d.ts"],
"chains": ["./dist/types/exports/chains.d.ts"],
"contract": ["./dist/types/exports/contract.d.ts"],
"deploys": ["./dist/types/exports/deploys.d.ts"],
"event": ["./dist/types/exports/event.d.ts"],
"extensions/*": ["./dist/types/exports/extensions/*.d.ts"],
"pay": ["./dist/types/exports/pay.d.ts"],
"react": ["./dist/types/exports/react.d.ts"],
"react-native": ["./dist/types/exports/react-native.d.ts"],
"rpc": ["./dist/types/exports/rpc.d.ts"],
"storage": ["./dist/types/exports/storage.d.ts"],
"transaction": ["./dist/types/exports/transaction.d.ts"],
"utils": ["./dist/types/exports/utils.d.ts"],
"wallets": ["./dist/types/exports/wallets.d.ts"],
"wallets/*": ["./dist/types/exports/wallets/*.d.ts"],
"modules": ["./dist/types/exports/modules.d.ts"],
"social": ["./dist/types/exports/social.d.ts"]
"adapters/*": [
"./dist/types/exports/adapters/*.d.ts"
],
"auth": [
"./dist/types/exports/auth.d.ts"
],
"chains": [
"./dist/types/exports/chains.d.ts"
],
"contract": [
"./dist/types/exports/contract.d.ts"
],
"deploys": [
"./dist/types/exports/deploys.d.ts"
],
"event": [
"./dist/types/exports/event.d.ts"
],
"extensions/*": [
"./dist/types/exports/extensions/*.d.ts"
],
"pay": [
"./dist/types/exports/pay.d.ts"
],
"react": [
"./dist/types/exports/react.d.ts"
],
"react-native": [
"./dist/types/exports/react-native.d.ts"
],
"rpc": [
"./dist/types/exports/rpc.d.ts"
],
"storage": [
"./dist/types/exports/storage.d.ts"
],
"transaction": [
"./dist/types/exports/transaction.d.ts"
],
"utils": [
"./dist/types/exports/utils.d.ts"
],
"wallets": [
"./dist/types/exports/wallets.d.ts"
],
"wallets/*": [
"./dist/types/exports/wallets/*.d.ts"
],
"modules": [
"./dist/types/exports/modules.d.ts"
],
"social": [
"./dist/types/exports/social.d.ts"
]
}
},
"browser": {
Expand Down Expand Up @@ -181,7 +217,6 @@
"fuse.js": "7.0.0",
"input-otp": "^1.4.1",
"mipd": "0.0.7",
"ox": "0.4.0",
"uqr": "0.1.2",
"viem": "2.21.54"
},
Expand All @@ -194,6 +229,7 @@
"ethers": "^5 || ^6",
"expo-linking": "^6",
"expo-web-browser": "^13 || ^14",
"ox": "0.4.0",
"react": "^18 || ^19",
"react-native": "*",
"react-native-aes-gcm-crypto": "^0.2",
Expand Down
92 changes: 92 additions & 0 deletions packages/thirdweb/src/adapters/ethers5.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as ethers5 from "ethers5";
import * as ethers6 from "ethers6";
import { describe, expect, it, test } from "vitest";
import { USDT_CONTRACT } from "~test/test-contracts.js";
import { ANVIL_CHAIN, FORKED_ETHEREUM_CHAIN } from "../../test/src/chains.js";
Expand All @@ -10,6 +11,7 @@ import { randomBytesBuffer } from "../utils/random.js";
import { privateKeyToAccount } from "../wallets/private-key.js";
import {
fromEthersContract,
fromEthersSigner,
toEthersContract,
toEthersProvider,
toEthersSigner,
Expand Down Expand Up @@ -149,4 +151,94 @@ describe("ethers5 adapter", () => {
const _decimals = await decimals({ contract: thirdwebContract });
expect(_decimals).toBe(6);
});

test("toEthersProvider should return a valid provider", async () => {
const provider = toEthersProvider(ethers5, TEST_CLIENT, ANVIL_CHAIN);
expect(provider).toBeDefined();
expect(provider.getBlockNumber).toBeDefined();

// Test if the provider can fetch the block number
const blockNumber = await provider.getBlockNumber();
expect(typeof blockNumber).toBe("number");
});

test("toEthersProvider should throw error with invalid ethers version", () => {
const invalidEthers = ethers6;
expect(() =>
// biome-ignore lint/suspicious/noExplicitAny: Testing invalid data
toEthersProvider(invalidEthers as any, TEST_CLIENT, ANVIL_CHAIN),
).toThrow(
"You seem to be using ethers@6, please use the `ethers6Adapter()`",
);
});
});

describe("fromEthersSigner", () => {
it("should convert an ethers5 Signer to an Account", async () => {
const wallet = new ethers5.Wallet(ANVIL_PKEY_A);
const account = await fromEthersSigner(wallet);

expect(account).toBeDefined();
expect(account.address).toBe(await wallet.getAddress());
});

it("should sign a message", async () => {
const wallet = new ethers5.Wallet(ANVIL_PKEY_A);
const account = await fromEthersSigner(wallet);

const message = "Hello, world!";
const signature = await account.signMessage({ message });

expect(signature).toBe(await wallet.signMessage(message));
});

it("should sign a transaction", async () => {
const wallet = new ethers5.Wallet(
ANVIL_PKEY_A,
ethers5.getDefaultProvider(),
);
const account = await fromEthersSigner(wallet);

const transaction = {
to: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
value: 1n,
};

const signedTransaction = await account.signTransaction?.(transaction);

expect(signedTransaction).toBe(await wallet.signTransaction(transaction));
});

it("should sign typed data", async () => {
const wallet = new ethers5.Wallet(ANVIL_PKEY_A);
const account = await fromEthersSigner(wallet);

const domain = {
name: "Ether Mail",
version: "1",
chainId: 1,
verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
};

const types = {
Person: [
{ name: "name", type: "string" },
{ name: "wallet", type: "address" },
],
};

const value = {
name: "Alice",
wallet: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
};

const signature = await account.signTypedData({
primaryType: "Person",
domain,
types,
message: value,
});

expect(signature).toBeDefined();
});
});
11 changes: 6 additions & 5 deletions packages/thirdweb/src/adapters/ethers5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import type { Abi } from "abitype";
import * as universalethers from "ethers";
import type * as ethers5 from "ethers5";
import type * as ethers6 from "ethers6";
import type { AccessList, Hex, TransactionSerializable } from "viem";
import type { AccessList, Hex } from "viem";
import type { Chain } from "../chains/types.js";
import { getRpcUrlForChain } from "../chains/utils.js";
import type { ThirdwebClient } from "../client/client.js";
import { type ThirdwebContract, getContract } from "../contract/contract.js";
import { toSerializableTransaction } from "../transaction/actions/to-serializable-transaction.js";
import { waitForReceipt } from "../transaction/actions/wait-for-tx-receipt.js";
import type { PreparedTransaction } from "../transaction/prepare-transaction.js";
import type { SerializableTransaction } from "../transaction/serialize-transaction.js";
import { toHex } from "../utils/encoding/hex.js";
import type { Account } from "../wallets/interfaces/wallet.js";

Expand Down Expand Up @@ -39,7 +40,7 @@ function assertEthers5(
): asserts ethers is typeof ethers5 {
if (!isEthers5(ethers)) {
throw new Error(
"You seem to be using ethers@6, please use the `ethers6Adapter()",
"You seem to be using ethers@6, please use the `ethers6Adapter()`",
);
}
}
Expand Down Expand Up @@ -279,6 +280,7 @@ export function toEthersProvider(
client: ThirdwebClient,
chain: Chain,
): ethers5.providers.Provider {
assertEthers5(ethers);
const url = getRpcUrlForChain({ chain, client });
const headers: HeadersInit = {
"Content-Type": "application/json",
Expand Down Expand Up @@ -588,7 +590,7 @@ export async function toEthersSigner(
* @internal
*/
function alignTxToEthers(
tx: TransactionSerializable,
tx: SerializableTransaction,
): ethers5.ethers.utils.Deferrable<ethers5.ethers.providers.TransactionRequest> {
const { to: viemTo, type: viemType, gas, ...rest } = tx;
// massage "to" to fit ethers
Expand Down Expand Up @@ -623,8 +625,7 @@ function alignTxToEthers(
gasLimit: gas,
to,
type,
accessList: tx.accessList as ethers5.utils.AccessListish | undefined,
};
} as ethers5.ethers.utils.Deferrable<ethers5.ethers.providers.TransactionRequest>;
}

async function alignTxFromEthers(
Expand Down
5 changes: 3 additions & 2 deletions packages/thirdweb/src/adapters/ethers6.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
import * as universalethers from "ethers";
import type * as ethers5 from "ethers5";
import type * as ethers6 from "ethers6";
import type { AccessList, Hex, TransactionSerializable } from "viem";
import type { AccessList, Hex } from "viem";
import type { Chain } from "../chains/types.js";
import { getRpcUrlForChain } from "../chains/utils.js";
import type { ThirdwebClient } from "../client/client.js";
import { type ThirdwebContract, getContract } from "../contract/contract.js";
import { toSerializableTransaction } from "../transaction/actions/to-serializable-transaction.js";
import type { PreparedTransaction } from "../transaction/prepare-transaction.js";
import type { SerializableTransaction } from "../transaction/serialize-transaction.js";
import { toHex } from "../utils/encoding/hex.js";
import { resolvePromisedValue } from "../utils/promise/resolve-promised-value.js";
import type { Account } from "../wallets/interfaces/wallet.js";
Expand Down Expand Up @@ -493,7 +494,7 @@
* @returns The aligned transaction object.
* @internal
*/
function alignTxToEthers(tx: TransactionSerializable) {
function alignTxToEthers(tx: SerializableTransaction) {

Check warning on line 497 in packages/thirdweb/src/adapters/ethers6.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/adapters/ethers6.ts#L497

Added line #L497 was not covered by tests
const { type: viemType, ...rest } = tx;

// massage "type" to fit ethers
Expand Down
9 changes: 5 additions & 4 deletions packages/thirdweb/src/adapters/viem.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { type Account as ViemAccount, zeroAddress } from "viem";
import type { Account as ViemAccount } from "viem";
import { privateKeyToAccount as viemPrivateKeyToAccount } from "viem/accounts";
import { beforeAll, describe, expect, test } from "vitest";
import { USDT_ABI } from "~test/abis/usdt.js";
import {
USDT_CONTRACT_ADDRESS,
USDT_CONTRACT_WITH_ABI,
} from "~test/test-contracts.js";
import { ANVIL_PKEY_A } from "~test/test-wallets.js";
import { ANVIL_PKEY_A, TEST_ACCOUNT_B } from "~test/test-wallets.js";
import { typedData } from "~test/typed-data.js";

import { ANVIL_CHAIN, FORKED_ETHEREUM_CHAIN } from "../../test/src/chains.js";
Expand Down Expand Up @@ -87,6 +87,7 @@ describe("walletClient.toViem", () => {
if (!walletClient.account) {
throw new Error("Account not found");
}

const txHash = await walletClient.sendTransaction({
account: walletClient.account,
chain: {
Expand All @@ -101,8 +102,8 @@ describe("walletClient.toViem", () => {
decimals: ANVIL_CHAIN.nativeCurrency?.decimals || 18,
},
},
to: zeroAddress,
value: 0n,
to: TEST_ACCOUNT_B.address,
value: 10n,
});
expect(txHash).toBeDefined();
expect(txHash.slice(0, 2)).toBe("0x");
Expand Down
2 changes: 1 addition & 1 deletion packages/thirdweb/src/exports/wallets.native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export const authenticateWithRedirect = () => {
export type {
CoinbaseWalletCreationOptions,
CoinbaseSDKWalletConnectionOptions,
} from "../wallets/coinbase/coinbaseWebSDK.js";
} from "../wallets/coinbase/coinbase-web.js";

export type {
WalletEmitter,
Expand Down
2 changes: 1 addition & 1 deletion packages/thirdweb/src/exports/wallets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export {
export type {
CoinbaseWalletCreationOptions,
CoinbaseSDKWalletConnectionOptions,
} from "../wallets/coinbase/coinbaseWebSDK.js";
} from "../wallets/coinbase/coinbase-web.js";

export type {
WalletEmitter,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type TypedData, type TypedDataDefinition, hashTypedData } from "viem";
import * as ox__TypedData from "ox/TypedData";
import type { ThirdwebContract } from "../../contract/contract.js";
import { isHex } from "../../utils/encoding/hex.js";
import { isValidSignature } from "./__generated__/isValidSignature/read/isValidSignature.js";
Expand All @@ -7,11 +7,11 @@ import { isValidSignature } from "./__generated__/isValidSignature/read/isValidS
* @extension ERC1271
*/
export type CheckContractWalletSignTypedDataOptions<
typedData extends TypedData | Record<string, unknown>,
typedData extends ox__TypedData.TypedData | Record<string, unknown>,
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData,
> = {
contract: ThirdwebContract;
data: TypedDataDefinition<typedData, primaryType>;
data: ox__TypedData.Definition<typedData, primaryType>;
signature: string;
};
const MAGIC_VALUE = "0x1626ba7e";
Expand Down Expand Up @@ -42,15 +42,19 @@ const MAGIC_VALUE = "0x1626ba7e";
* @returns A promise that resolves with a boolean indicating if the signature is valid.
*/
export async function checkContractWalletSignedTypedData<
typedData extends TypedData | Record<string, unknown>,
typedData extends ox__TypedData.TypedData | Record<string, unknown>,
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData,
>(options: CheckContractWalletSignTypedDataOptions<typedData, primaryType>) {
if (!isHex(options.signature)) {
throw new Error("The signature must be a valid hex string.");
}
const result = await isValidSignature({
contract: options.contract,
hash: hashTypedData(options.data),
hash: ox__TypedData.hashStruct({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ugh types clashed here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I'm just trying to use ox as much as possible in the underlying functions, so we don't need to wrap and/or rewrite internal utils we're not planning to export

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i meant the casts underneath this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes they do

primaryType: options.data.primaryType,
data: options.data.message as Record<string, unknown>,
types: options.data.types as ox__TypedData.Definition["types"],
}),
signature: options.signature,
});
return result === MAGIC_VALUE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function usePreloadWalletProviders({
switch (true) {
case COINBASE === w.id: {
const { getCoinbaseWebProvider } = await import(
"../../../wallets/coinbase/coinbaseWebSDK.js"
"../../../wallets/coinbase/coinbase-web.js"
);
await getCoinbaseWebProvider(
w.getConfig() as CreateWalletArgs<typeof COINBASE>[1],
Expand Down
Loading
Loading