Skip to content

Commit 45ca033

Browse files
[thirdweb] feat: Replace walletClient with wallet in viemAdapter (#6282)
1 parent a0f3557 commit 45ca033

File tree

5 files changed

+356
-19
lines changed

5 files changed

+356
-19
lines changed

.changeset/weak-kids-love.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Deprecated `viemAdapter.walletClient` in favor of `viemAdapter.wallet` to take wallet instances instead of accounts
6+
7+
BEFORE:
8+
9+
```ts
10+
import { viemAdapter } from "thirdweb/adapters/viem";
11+
12+
const walletClient = viemAdapter.walletClient.toViem({
13+
account, // Account
14+
chain,
15+
client,
16+
});
17+
```
18+
19+
AFTER:
20+
21+
```ts
22+
import { viemAdapter } from "thirdweb/adapters/viem";
23+
24+
const walletClient = viemAdapter.wallet.toViem({
25+
wallet, // now pass a connected Wallet instance instead of an account
26+
chain,
27+
client,
28+
});
29+
```
30+
31+
This allows for full wallet lifecycle management with the viem adapter, including switching chains, adding chains, events and more.

apps/portal/src/app/typescript/v5/adapters/page.mdx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,18 @@ You can use an existing wallet client from viem with the thirdweb SDK by convert
5656
```ts
5757
import { viemAdapter } from "thirdweb/adapters/viem";
5858

59-
// convert a viem wallet client to a thirdweb account
59+
// convert a viem wallet client to a thirdweb wallet
6060
const walletClient = createWalletClient(...);
61-
const account = await viemAdapter.walletClient.fromViem({
61+
const thirdwebWallet = await viemAdapter.wallet.fromViem({
6262
walletClient,
6363
});
6464

6565

6666
// convert a thirdweb account to viem wallet client
67-
const viemClientWallet = viemAdapter.walletClient.toViem({
67+
const viemClientWallet = viemAdapter.wallet.toViem({
6868
client,
6969
chain,
70-
account,
70+
wallet,
7171
});
7272
```
7373

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import type { Account as ViemAccount } from "viem";
2+
import { privateKeyToAccount as viemPrivateKeyToAccount } from "viem/accounts";
3+
import { beforeAll, describe, expect, test } from "vitest";
4+
import { USDT_ABI } from "~test/abis/usdt.js";
5+
import {
6+
USDT_CONTRACT_ADDRESS,
7+
USDT_CONTRACT_WITH_ABI,
8+
} from "~test/test-contracts.js";
9+
import { ANVIL_PKEY_A, TEST_ACCOUNT_B } from "~test/test-wallets.js";
10+
import { typedData } from "~test/typed-data.js";
11+
12+
import { ANVIL_CHAIN, FORKED_ETHEREUM_CHAIN } from "../../test/src/chains.js";
13+
import { TEST_CLIENT } from "../../test/src/test-clients.js";
14+
import { randomBytesBuffer } from "../utils/random.js";
15+
import { privateKeyToAccount } from "../wallets/private-key.js";
16+
import { toViemContract, viemAdapter } from "./viem.js";
17+
18+
const account = privateKeyToAccount({
19+
privateKey: ANVIL_PKEY_A,
20+
client: TEST_CLIENT,
21+
});
22+
23+
describe("walletClient.toViem", () => {
24+
let walletClient: ReturnType<typeof viemAdapter.walletClient.toViem>;
25+
26+
beforeAll(() => {
27+
walletClient = viemAdapter.walletClient.toViem({
28+
client: TEST_CLIENT,
29+
account,
30+
chain: ANVIL_CHAIN,
31+
});
32+
});
33+
34+
test("should return an viem wallet client", async () => {
35+
expect(walletClient).toBeDefined();
36+
expect(walletClient.signMessage).toBeDefined();
37+
});
38+
39+
test("should sign message", async () => {
40+
if (!walletClient.account) {
41+
throw new Error("Account not found");
42+
}
43+
const expectedSig = await account.signMessage({ message: "hello world" });
44+
const sig = await walletClient.signMessage({
45+
account: walletClient.account,
46+
message: "hello world",
47+
});
48+
expect(sig).toBe(expectedSig);
49+
});
50+
51+
test("should sign raw message", async () => {
52+
if (!walletClient.account) {
53+
throw new Error("Account not found");
54+
}
55+
const bytes = randomBytesBuffer(32);
56+
const expectedSig = await account.signMessage({ message: { raw: bytes } });
57+
const sig = await walletClient.signMessage({
58+
account: walletClient.account,
59+
message: { raw: bytes },
60+
});
61+
expect(sig).toBe(expectedSig);
62+
});
63+
64+
test("should sign typed data", async () => {
65+
expect(walletClient.signTypedData).toBeDefined();
66+
67+
if (!walletClient.account) {
68+
throw new Error("Account not found");
69+
}
70+
71+
const signature = await walletClient.signTypedData({
72+
...typedData.basic,
73+
primaryType: "Mail",
74+
account: walletClient.account,
75+
});
76+
77+
expect(signature).toMatchInlineSnapshot(
78+
`"0x32f3d5975ba38d6c2fba9b95d5cbed1febaa68003d3d588d51f2de522ad54117760cfc249470a75232552e43991f53953a3d74edf6944553c6bef2469bb9e5921b"`,
79+
);
80+
});
81+
82+
test("should contain a json-rpc account", async () => {
83+
expect(walletClient.account?.type).toBe("json-rpc");
84+
});
85+
86+
test("should send a transaction", async () => {
87+
if (!walletClient.account) {
88+
throw new Error("Account not found");
89+
}
90+
91+
const txHash = await walletClient.sendTransaction({
92+
account: walletClient.account,
93+
chain: {
94+
id: ANVIL_CHAIN.id,
95+
name: ANVIL_CHAIN.name || "",
96+
rpcUrls: {
97+
default: { http: [ANVIL_CHAIN.rpc] },
98+
},
99+
nativeCurrency: {
100+
name: ANVIL_CHAIN.nativeCurrency?.name || "Ether",
101+
symbol: ANVIL_CHAIN.nativeCurrency?.symbol || "ETH",
102+
decimals: ANVIL_CHAIN.nativeCurrency?.decimals || 18,
103+
},
104+
},
105+
to: TEST_ACCOUNT_B.address,
106+
value: 10n,
107+
});
108+
expect(txHash).toBeDefined();
109+
expect(txHash.slice(0, 2)).toBe("0x");
110+
});
111+
112+
test("should get address on live chain", async () => {
113+
walletClient = viemAdapter.walletClient.toViem({
114+
client: TEST_CLIENT,
115+
account,
116+
chain: FORKED_ETHEREUM_CHAIN,
117+
});
118+
119+
const address = await walletClient.getAddresses();
120+
expect(address[0]).toBeDefined();
121+
expect(address[0]).toBe(account.address);
122+
});
123+
124+
test("should match thirdweb account signature", async () => {
125+
const message = "testing123";
126+
127+
const rawViemAccount = viemPrivateKeyToAccount(ANVIL_PKEY_A);
128+
const twSignature = await account.signMessage({ message });
129+
const viemTwSignature = await walletClient.signMessage({
130+
message,
131+
account: walletClient.account as ViemAccount,
132+
});
133+
const viemSignature = await rawViemAccount.signMessage({ message });
134+
135+
expect(viemSignature).toEqual(twSignature);
136+
expect(viemTwSignature).toEqual(twSignature);
137+
});
138+
139+
test("should convert thirdweb contract to viem contract", async () => {
140+
const result = await toViemContract({
141+
thirdwebContract: USDT_CONTRACT_WITH_ABI,
142+
});
143+
expect(result.abi).toEqual(USDT_ABI);
144+
expect(result.address).toBe(USDT_CONTRACT_ADDRESS);
145+
});
146+
147+
test("should throw when switching chain", async () => {
148+
await expect(
149+
walletClient.switchChain({
150+
id: FORKED_ETHEREUM_CHAIN.id,
151+
}),
152+
).rejects.toThrowErrorMatchingInlineSnapshot(`
153+
[UnknownRpcError: An unknown RPC error occurred.
154+
155+
Details: Can't switch chains because only an account was passed to 'viemAdapter.walletClient.toViem()', please pass a connected wallet instance instead.
156+
Version: viem@2.22.17]
157+
`);
158+
});
159+
});

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

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,26 @@ import {
66
USDT_CONTRACT_ADDRESS,
77
USDT_CONTRACT_WITH_ABI,
88
} from "~test/test-contracts.js";
9-
import { ANVIL_PKEY_A, TEST_ACCOUNT_B } from "~test/test-wallets.js";
9+
import {
10+
ANVIL_PKEY_A,
11+
TEST_ACCOUNT_B,
12+
TEST_IN_APP_WALLET_A,
13+
} from "~test/test-wallets.js";
1014
import { typedData } from "~test/typed-data.js";
11-
1215
import { ANVIL_CHAIN, FORKED_ETHEREUM_CHAIN } from "../../test/src/chains.js";
1316
import { TEST_CLIENT } from "../../test/src/test-clients.js";
1417
import { randomBytesBuffer } from "../utils/random.js";
15-
import { privateKeyToAccount } from "../wallets/private-key.js";
1618
import { toViemContract, viemAdapter } from "./viem.js";
1719

18-
const account = privateKeyToAccount({
19-
privateKey: ANVIL_PKEY_A,
20-
client: TEST_CLIENT,
21-
});
20+
const wallet = TEST_IN_APP_WALLET_A;
2221

2322
describe("walletClient.toViem", () => {
24-
let walletClient: ReturnType<typeof viemAdapter.walletClient.toViem>;
23+
let walletClient: ReturnType<typeof viemAdapter.wallet.toViem>;
2524

2625
beforeAll(() => {
27-
walletClient = viemAdapter.walletClient.toViem({
26+
walletClient = viemAdapter.wallet.toViem({
2827
client: TEST_CLIENT,
29-
account,
28+
wallet,
3029
chain: ANVIL_CHAIN,
3130
});
3231
});
@@ -37,7 +36,8 @@ describe("walletClient.toViem", () => {
3736
});
3837

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

5151
test("should sign raw message", async () => {
52-
if (!walletClient.account) {
52+
const account = wallet.getAccount();
53+
if (!walletClient.account || !account) {
5354
throw new Error("Account not found");
5455
}
5556
const bytes = randomBytesBuffer(32);
@@ -110,19 +111,27 @@ describe("walletClient.toViem", () => {
110111
});
111112

112113
test("should get address on live chain", async () => {
113-
walletClient = viemAdapter.walletClient.toViem({
114+
walletClient = viemAdapter.wallet.toViem({
114115
client: TEST_CLIENT,
115-
account,
116+
wallet,
116117
chain: FORKED_ETHEREUM_CHAIN,
117118
});
118119

120+
const account = wallet.getAccount();
121+
if (!walletClient.account || !account) {
122+
throw new Error("Account not found");
123+
}
119124
const address = await walletClient.getAddresses();
120125
expect(address[0]).toBeDefined();
121126
expect(address[0]).toBe(account.address);
122127
});
123128

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

127136
const rawViemAccount = viemPrivateKeyToAccount(ANVIL_PKEY_A);
128137
const twSignature = await account.signMessage({ message });
@@ -143,4 +152,11 @@ describe("walletClient.toViem", () => {
143152
expect(result.abi).toEqual(USDT_ABI);
144153
expect(result.address).toBe(USDT_CONTRACT_ADDRESS);
145154
});
155+
156+
test("should switch chain", async () => {
157+
await walletClient.switchChain({
158+
id: FORKED_ETHEREUM_CHAIN.id,
159+
});
160+
expect(await walletClient.getChainId()).toBe(FORKED_ETHEREUM_CHAIN.id);
161+
});
146162
});

0 commit comments

Comments
 (0)