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
31 changes: 31 additions & 0 deletions .changeset/weak-kids-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
"thirdweb": patch
---

Deprecated `viemAdapter.walletClient` in favor of `viemAdapter.wallet` to take wallet instances instead of accounts

BEFORE:

```ts
import { viemAdapter } from "thirdweb/adapters/viem";

const walletClient = viemAdapter.walletClient.toViem({
account, // Account
chain,
client,
});
```

AFTER:

```ts
import { viemAdapter } from "thirdweb/adapters/viem";

const walletClient = viemAdapter.wallet.toViem({
wallet, // now pass a connected Wallet instance instead of an account
chain,
client,
});
```

This allows for full wallet lifecycle management with the viem adapter, including switching chains, adding chains, events and more.
8 changes: 4 additions & 4 deletions apps/portal/src/app/typescript/v5/adapters/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,18 @@ You can use an existing wallet client from viem with the thirdweb SDK by convert
```ts
import { viemAdapter } from "thirdweb/adapters/viem";

// convert a viem wallet client to a thirdweb account
// convert a viem wallet client to a thirdweb wallet
const walletClient = createWalletClient(...);
const account = await viemAdapter.walletClient.fromViem({
const thirdwebWallet = await viemAdapter.wallet.fromViem({
walletClient,
});


// convert a thirdweb account to viem wallet client
const viemClientWallet = viemAdapter.walletClient.toViem({
const viemClientWallet = viemAdapter.wallet.toViem({
client,
chain,
account,
wallet,
});
```

Expand Down
159 changes: 159 additions & 0 deletions packages/thirdweb/src/adapters/viem-legacy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
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, 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";
import { TEST_CLIENT } from "../../test/src/test-clients.js";
import { randomBytesBuffer } from "../utils/random.js";
import { privateKeyToAccount } from "../wallets/private-key.js";
import { toViemContract, viemAdapter } from "./viem.js";

const account = privateKeyToAccount({
privateKey: ANVIL_PKEY_A,
client: TEST_CLIENT,
});

describe("walletClient.toViem", () => {
let walletClient: ReturnType<typeof viemAdapter.walletClient.toViem>;

beforeAll(() => {
walletClient = viemAdapter.walletClient.toViem({
client: TEST_CLIENT,
account,
chain: ANVIL_CHAIN,
});
});

test("should return an viem wallet client", async () => {
expect(walletClient).toBeDefined();
expect(walletClient.signMessage).toBeDefined();
});

test("should sign message", async () => {
if (!walletClient.account) {
throw new Error("Account not found");
}
const expectedSig = await account.signMessage({ message: "hello world" });
const sig = await walletClient.signMessage({
account: walletClient.account,
message: "hello world",
});
expect(sig).toBe(expectedSig);
});

test("should sign raw message", async () => {
if (!walletClient.account) {
throw new Error("Account not found");
}
const bytes = randomBytesBuffer(32);
const expectedSig = await account.signMessage({ message: { raw: bytes } });
const sig = await walletClient.signMessage({
account: walletClient.account,
message: { raw: bytes },
});
expect(sig).toBe(expectedSig);
});

test("should sign typed data", async () => {
expect(walletClient.signTypedData).toBeDefined();

if (!walletClient.account) {
throw new Error("Account not found");
}

const signature = await walletClient.signTypedData({
...typedData.basic,
primaryType: "Mail",
account: walletClient.account,
});

expect(signature).toMatchInlineSnapshot(
`"0x32f3d5975ba38d6c2fba9b95d5cbed1febaa68003d3d588d51f2de522ad54117760cfc249470a75232552e43991f53953a3d74edf6944553c6bef2469bb9e5921b"`,
);
});

test("should contain a json-rpc account", async () => {
expect(walletClient.account?.type).toBe("json-rpc");
});

test("should send a transaction", async () => {
if (!walletClient.account) {
throw new Error("Account not found");
}

const txHash = await walletClient.sendTransaction({
account: walletClient.account,
chain: {
id: ANVIL_CHAIN.id,
name: ANVIL_CHAIN.name || "",
rpcUrls: {
default: { http: [ANVIL_CHAIN.rpc] },
},
nativeCurrency: {
name: ANVIL_CHAIN.nativeCurrency?.name || "Ether",
symbol: ANVIL_CHAIN.nativeCurrency?.symbol || "ETH",
decimals: ANVIL_CHAIN.nativeCurrency?.decimals || 18,
},
},
to: TEST_ACCOUNT_B.address,
value: 10n,
});
expect(txHash).toBeDefined();
expect(txHash.slice(0, 2)).toBe("0x");
});

test("should get address on live chain", async () => {
walletClient = viemAdapter.walletClient.toViem({
client: TEST_CLIENT,
account,
chain: FORKED_ETHEREUM_CHAIN,
});

const address = await walletClient.getAddresses();
expect(address[0]).toBeDefined();
expect(address[0]).toBe(account.address);
});

test("should match thirdweb account signature", async () => {
const message = "testing123";

const rawViemAccount = viemPrivateKeyToAccount(ANVIL_PKEY_A);
const twSignature = await account.signMessage({ message });
const viemTwSignature = await walletClient.signMessage({
message,
account: walletClient.account as ViemAccount,
});
const viemSignature = await rawViemAccount.signMessage({ message });

expect(viemSignature).toEqual(twSignature);
expect(viemTwSignature).toEqual(twSignature);
});

test("should convert thirdweb contract to viem contract", async () => {
const result = await toViemContract({
thirdwebContract: USDT_CONTRACT_WITH_ABI,
});
expect(result.abi).toEqual(USDT_ABI);
expect(result.address).toBe(USDT_CONTRACT_ADDRESS);
});

test("should throw when switching chain", async () => {
await expect(
walletClient.switchChain({
id: FORKED_ETHEREUM_CHAIN.id,
}),
).rejects.toThrowErrorMatchingInlineSnapshot(`
[UnknownRpcError: An unknown RPC error occurred.

Details: Can't switch chains because only an account was passed to 'viemAdapter.walletClient.toViem()', please pass a connected wallet instance instead.
Version: [email protected]]
`);
});
});
44 changes: 30 additions & 14 deletions packages/thirdweb/src/adapters/viem.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,26 @@ import {
USDT_CONTRACT_ADDRESS,
USDT_CONTRACT_WITH_ABI,
} from "~test/test-contracts.js";
import { ANVIL_PKEY_A, TEST_ACCOUNT_B } from "~test/test-wallets.js";
import {
ANVIL_PKEY_A,
TEST_ACCOUNT_B,
TEST_IN_APP_WALLET_A,
} from "~test/test-wallets.js";
import { typedData } from "~test/typed-data.js";

import { ANVIL_CHAIN, FORKED_ETHEREUM_CHAIN } from "../../test/src/chains.js";
import { TEST_CLIENT } from "../../test/src/test-clients.js";
import { randomBytesBuffer } from "../utils/random.js";
import { privateKeyToAccount } from "../wallets/private-key.js";
import { toViemContract, viemAdapter } from "./viem.js";

const account = privateKeyToAccount({
privateKey: ANVIL_PKEY_A,
client: TEST_CLIENT,
});
const wallet = TEST_IN_APP_WALLET_A;

describe("walletClient.toViem", () => {
let walletClient: ReturnType<typeof viemAdapter.walletClient.toViem>;
let walletClient: ReturnType<typeof viemAdapter.wallet.toViem>;

beforeAll(() => {
walletClient = viemAdapter.walletClient.toViem({
walletClient = viemAdapter.wallet.toViem({
client: TEST_CLIENT,
account,
wallet,
chain: ANVIL_CHAIN,
});
});
Expand All @@ -37,7 +36,8 @@ describe("walletClient.toViem", () => {
});

test("should sign message", async () => {
if (!walletClient.account) {
const account = wallet.getAccount();
if (!walletClient.account || !account) {
throw new Error("Account not found");
}
const expectedSig = await account.signMessage({ message: "hello world" });
Expand All @@ -49,7 +49,8 @@ describe("walletClient.toViem", () => {
});

test("should sign raw message", async () => {
if (!walletClient.account) {
const account = wallet.getAccount();
if (!walletClient.account || !account) {
throw new Error("Account not found");
}
const bytes = randomBytesBuffer(32);
Expand Down Expand Up @@ -110,19 +111,27 @@ describe("walletClient.toViem", () => {
});

test("should get address on live chain", async () => {
walletClient = viemAdapter.walletClient.toViem({
walletClient = viemAdapter.wallet.toViem({
client: TEST_CLIENT,
account,
wallet,
chain: FORKED_ETHEREUM_CHAIN,
});

const account = wallet.getAccount();
if (!walletClient.account || !account) {
throw new Error("Account not found");
}
const address = await walletClient.getAddresses();
expect(address[0]).toBeDefined();
expect(address[0]).toBe(account.address);
});

test("should match thirdweb account signature", async () => {
const message = "testing123";
const account = wallet.getAccount();
if (!walletClient.account || !account) {
throw new Error("Account not found");
}

const rawViemAccount = viemPrivateKeyToAccount(ANVIL_PKEY_A);
const twSignature = await account.signMessage({ message });
Expand All @@ -143,4 +152,11 @@ describe("walletClient.toViem", () => {
expect(result.abi).toEqual(USDT_ABI);
expect(result.address).toBe(USDT_CONTRACT_ADDRESS);
});

test("should switch chain", async () => {
await walletClient.switchChain({
id: FORKED_ETHEREUM_CHAIN.id,
});
expect(await walletClient.getChainId()).toBe(FORKED_ETHEREUM_CHAIN.id);
});
});
Loading
Loading