Skip to content

Commit

Permalink
[thirdweb] feat: Replace walletClient with wallet in viemAdapter (#6282)
Browse files Browse the repository at this point in the history
  • Loading branch information
joaquim-verges authored Feb 18, 2025
1 parent a0f3557 commit 45ca033
Show file tree
Hide file tree
Showing 5 changed files with 356 additions and 19 deletions.
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

0 comments on commit 45ca033

Please sign in to comment.