Skip to content

Commit 7234e6b

Browse files
gregfromstljnsdlsactions-userjoaquim-vergesMananTank
authored
[SDK] Native privateKeyAccount implementation (#2525)
Signed-off-by: Joaquim Verges <joaquim.verges@gmail.com> Signed-off-by: jxom <jakemoxey@gmail.com> Signed-off-by: greg <gregfromstl@gmail.com> Signed-off-by: Jonas Daniels <jonas.daniels@outlook.com> Co-authored-by: Jonas Daniels <jonas.daniels@outlook.com> Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Joaquim Verges <joaquim.verges@gmail.com> Co-authored-by: Manan Tank <manantankm@gmail.com> Co-authored-by: jxom <jakemoxey@gmail.com> Co-authored-by: Nacho Iacovino <50103937+nachoiacovino@users.noreply.github.com> Co-authored-by: Winston Yeo <44563205+ElasticBottle@users.noreply.github.com> Co-authored-by: iketw <121973632+iketw@users.noreply.github.com> Co-authored-by: Winston Yeo <winstonyeo99@yahoo.com> Co-authored-by: Isaac Dubuque <idubuque@umich.edu>
1 parent 26a50b1 commit 7234e6b

Some content is hidden

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

47 files changed

+996
-113
lines changed

.changeset/angry-sheep-work.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Better private-key account support

legacy_packages/auth/test/evm.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { describe, it, beforeAll, beforeEach, expect } from "vitest";
66
// eslint-disable-next-line @typescript-eslint/no-var-requires
77
require("dotenv-mono").load();
88

9-
describe(
9+
describe.runIf(process.env.TW_SECRET_KEY)(
1010
"Wallet Authentication - EVM",
1111
{
1212
timeout: 60000,

legacy_packages/auth/test/smart-wallet.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { describe, it, beforeEach, beforeAll, expect } from "vitest";
77
// eslint-disable-next-line @typescript-eslint/no-var-requires
88
require("dotenv-mono").load();
99

10-
describe(
10+
describe.runIf(process.env.TW_SECRET_KEY)(
1111
"Wallet Authentication - EVM - Smart Wallet",
1212
{
1313
timeout: 60000,
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { describe, test, expect, beforeAll } from "vitest";
2+
import { privateKeyAccount } from "../wallets/private-key.js";
3+
import { TEST_CLIENT } from "../../test/src/test-clients.js";
4+
import { viemAdapter } from "./viem.js";
5+
import { defineChain } from "../chains/utils.js";
6+
import type { WalletClient } from "viem";
7+
8+
import { typedData } from "~test/typed-data.js";
9+
import { ANVIL_PKEY_A } from "~test/test-wallets.js";
10+
11+
const account = privateKeyAccount({
12+
privateKey: ANVIL_PKEY_A,
13+
client: TEST_CLIENT,
14+
});
15+
16+
describe("walletClient.toViem", () => {
17+
let walletClient: WalletClient;
18+
19+
beforeAll(() => {
20+
walletClient = viemAdapter.walletClient.toViem({
21+
client: TEST_CLIENT,
22+
account,
23+
chain: defineChain(31337),
24+
});
25+
});
26+
27+
test("should return an viem wallet client", async () => {
28+
expect(walletClient).toBeDefined();
29+
expect(walletClient.signMessage).toBeDefined();
30+
});
31+
32+
test("should sign typed data", async () => {
33+
expect(walletClient.signTypedData).toBeDefined();
34+
35+
if (!walletClient.account) {
36+
throw new Error("Account not found");
37+
}
38+
39+
const signature = await walletClient.signTypedData({
40+
...typedData.basic,
41+
primaryType: "Mail",
42+
account: walletClient.account,
43+
});
44+
45+
expect(signature).toMatchInlineSnapshot(
46+
`"0x32f3d5975ba38d6c2fba9b95d5cbed1febaa68003d3d588d51f2de522ad54117760cfc249470a75232552e43991f53953a3d74edf6944553c6bef2469bb9e5921b"`,
47+
);
48+
});
49+
50+
test("should contain a local account", async () => {
51+
expect(walletClient.account?.type).toBe("local");
52+
});
53+
});

packages/thirdweb/src/adapters/viem.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,22 @@ import {
44
type GetContractReturnType,
55
type PublicClient,
66
type Chain as ViemChain,
7+
type Account as ViemAccount,
78
type WalletClient,
89
createWalletClient,
910
custom,
1011
type TransactionSerializableEIP1559,
1112
} from "viem";
1213
import { getContract, type ThirdwebContract } from "../contract/contract.js";
13-
import type { Abi } from "abitype";
14+
import type { Abi, Address } from "abitype";
1415
import type { Chain } from "../chains/types.js";
1516
import type { ThirdwebClient } from "../client/client.js";
1617
import { resolveContractAbi } from "../contract/actions/resolve-abi.js";
1718
import { getRpcUrlForChain } from "../chains/utils.js";
18-
import type { Account } from "../wallets/interfaces/wallet.js";
19+
import { type Account, getAccountKey } from "../wallets/interfaces/wallet.js";
1920
import { getRpcClient } from "../rpc/rpc.js";
21+
import { secp256k1 } from "@noble/curves/secp256k1";
22+
import { toHex } from "../utils/encoding/hex.js";
2023

2124
export const viemAdapter = {
2225
contract: {
@@ -214,9 +217,26 @@ function toViemWalletClient(options: ToViemWalletClientOptions): WalletClient {
214217
},
215218
});
216219

220+
// viem defaults to JsonRpcAccounts so we pass the whole account if it's locally generated
221+
let viemAccountOrAddress: ViemAccount | Address;
222+
const possibleAccountKey = getAccountKey(account);
223+
if (possibleAccountKey && account.signTransaction) {
224+
viemAccountOrAddress = {
225+
...account,
226+
signTransaction: account.signTransaction,
227+
source: "custom",
228+
type: "local" as const,
229+
publicKey: toHex(
230+
secp256k1.getPublicKey(possibleAccountKey.slice(2), false),
231+
),
232+
};
233+
} else {
234+
viemAccountOrAddress = account.address;
235+
}
236+
217237
return createWalletClient({
218238
transport,
219-
account: account.address,
239+
account: viemAccountOrAddress,
220240
chain: viemChain,
221241
key: "thirdweb-wallet",
222242
});

packages/thirdweb/src/exports/thirdweb.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export {
108108
type SimulateOptions,
109109
} from "../transaction/actions/simulate.js";
110110
export { waitForReceipt } from "../transaction/actions/wait-for-tx-receipt.js";
111+
export { signTransaction } from "../transaction/actions/sign-transaction.js";
111112

112113
/**
113114
* EVENTS

packages/thirdweb/src/exports/transaction.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ export {
4343
simulateTransaction,
4444
type SimulateOptions,
4545
} from "../transaction/actions/simulate.js";
46+
export {
47+
signTransaction,
48+
type SignTransactionOptions,
49+
} from "../transaction/actions/sign-transaction.js";
4650

4751
//types & utils
4852
export {

packages/thirdweb/src/exports/utils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ export {
2727
resolveSignature,
2828
resolveSignatures,
2929
} from "../utils/signatures/resolve-signature.js";
30+
export { type SignOptions, sign } from "../utils/signatures/sign.js";
31+
export { signatureToHex } from "../utils/signatures/signature-to-hex.js";
32+
export {
33+
type SignMessageOptions,
34+
signMessage,
35+
} from "../utils/signatures/sign-message.js";
36+
export {
37+
type SignTypedDataOptions,
38+
signTypedData,
39+
} from "../utils/signatures/sign-typed-data.js";
3040

3141
// ------------------------------------------------
3242
// encoding

packages/thirdweb/src/exports/wallets.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export {
2121
// private-key
2222
export {
2323
privateKeyAccount,
24+
privateKeyToAccount,
2425
type PrivateKeyAccountOptions,
2526
} from "../wallets/private-key.js";
2627

packages/thirdweb/src/exports/wallets/local._ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ export type {
1313
LocalWalletLoadOrCreateOptions,
1414
LocalWalletSaveOptions,
1515
LocalWalletStorageData,
16-
} from "../../wallets/local/types._ts";
16+
} from "../../wallets/local/types._ts";

packages/thirdweb/src/extensions/common/read/getContractMetadata.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest";
22
import { getContractMetadata } from "./getContractMetadata.js";
33
import { USDC_CONTRACT } from "../../../../test/src/test-contracts.js";
44

5-
describe("shared.getContractMetadata", () => {
5+
describe.runIf(process.env.TW_SECRET_KEY)("shared.getContractMetadata", () => {
66
it("should return the correct contract metadata", async () => {
77
const metadata = await getContractMetadata({
88
contract: USDC_CONTRACT,

packages/thirdweb/src/extensions/erc1155/drops/read/getActiveClaimCondition.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { describe, it, expect } from "vitest";
33
import { DROP1155_CONTRACT } from "../../../../../test/src/test-contracts.js";
44
import { getActiveClaimCondition } from "./getActiveClaimCondition.js";
55

6-
describe("erc1155.getClaimConditions", () => {
6+
describe.runIf(process.env.TW_SECRET_KEY)("erc1155.getClaimConditions", () => {
77
it("should return the correct claim conditions", async () => {
88
const cc = await getActiveClaimCondition({
99
contract: DROP1155_CONTRACT,

packages/thirdweb/src/extensions/erc1155/read/getNFT.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { describe, it, expect } from "vitest";
33
import { getNFT } from "./getNFT.js";
44
import { DROP1155_CONTRACT } from "~test/test-contracts.js";
55

6-
describe("erc1155.getNFT", () => {
6+
describe.runIf(process.env.TW_SECRET_KEY)("erc1155.getNFT", () => {
77
it("without owner", async () => {
88
const nft = await getNFT({
99
contract: DROP1155_CONTRACT,

packages/thirdweb/src/extensions/erc20/read/getBalance.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { getBalance } from "./getBalance.js";
44
import { USDC_CONTRACT } from "~test/test-contracts.js";
55
import { VITALIK_WALLET } from "~test/addresses.js";
66

7-
describe("erc20.getBalance", () => {
7+
describe.runIf(process.env.TW_SECRET_KEY)("erc20.getBalance", () => {
88
it("should return the getBalance result", async () => {
99
const balance = await getBalance({
1010
contract: USDC_CONTRACT,

packages/thirdweb/src/extensions/erc721/drops/read/getActiveClaimCondition.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { describe, it, expect } from "vitest";
33
import { NFT_DROP_CONTRACT } from "../../../../../test/src/test-contracts.js";
44
import { getActiveClaimCondition } from "./getActiveClaimCondition.js";
55

6-
describe("erc721.getClaimConditions", () => {
6+
describe.runIf(process.env.TW_SECRET_KEY)("erc721.getClaimConditions", () => {
77
it("should return the correct claim conditions", async () => {
88
const cc = await getActiveClaimCondition({
99
contract: NFT_DROP_CONTRACT,

packages/thirdweb/src/extensions/erc721/read/getAllOwners.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { describe, it, expect } from "vitest";
33
import { getAllOwners } from "./getAllOwners.js";
44
import { AZUKI_CONTRACT, DOODLES_CONTRACT } from "~test/test-contracts.js";
55

6-
describe("erc721.getAllOwners", () => {
6+
describe.runIf(process.env.TW_SECRET_KEY)("erc721.getAllOwners", () => {
77
it("works for azuki", async () => {
88
const nfts = await getAllOwners({
99
contract: AZUKI_CONTRACT,

packages/thirdweb/src/extensions/erc721/read/getNFT.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { describe, it, expect } from "vitest";
33
import { getNFT } from "./getNFT.js";
44
import { DOODLES_CONTRACT } from "~test/test-contracts.js";
55

6-
describe("erc721.getNFT", () => {
6+
describe.runIf(process.env.TW_SECRET_KEY)("erc721.getNFT", () => {
77
it("without owner", async () => {
88
const nft = await getNFT({
99
contract: { ...DOODLES_CONTRACT },

packages/thirdweb/src/extensions/erc721/read/getNFTs.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { describe, it, expect } from "vitest";
33
import { getNFTs } from "./getNFTs.js";
44
import { AZUKI_CONTRACT, DOODLES_CONTRACT } from "~test/test-contracts.js";
55

6-
describe("erc721.getNFTs", () => {
6+
describe.runIf(process.env.TW_SECRET_KEY)("erc721.getNFTs", () => {
77
it("works for azuki", async () => {
88
const nfts = await getNFTs({
99
contract: AZUKI_CONTRACT,

packages/thirdweb/src/extensions/uniswap/read/getUniswapV3Pools.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
MOG_CONTRACT_ADDRESS,
99
} from "~test/test-contracts.js";
1010

11-
describe("uniswap.getUniswapV3Pool", () => {
11+
describe.runIf(process.env.TW_SECRET_KEY)("uniswap.getUniswapV3Pool", () => {
1212
it("should return the WETH/OHM pool address and fee", async () => {
1313
const pools = await getUniswapV3Pools({
1414
contract: UNISWAPV3_FACTORY_CONTRACT,
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useMutation, type UseMutationResult } from "@tanstack/react-query";
2+
import {
3+
estimateGas,
4+
type EstimateGasResult,
5+
} from "../../../../transaction/actions/estimate-gas.js";
6+
import type { PreparedTransaction } from "../../../../transaction/prepare-transaction.js";
7+
import { useActiveAccount } from "../wallets/wallet-hooks.js";
8+
9+
/**
10+
* A hook to estimate the gas for a given transaction.
11+
* @returns A mutation object to estimate gas.
12+
* @example
13+
* ```jsx
14+
* import { useEstimateGas } from "thirdweb/react";
15+
* const { mutate: estimateGas, data: gasEstimate } = useEstimateGas();
16+
*
17+
* // later
18+
* const estimatedGas = await estimateGas(tx);
19+
* ```
20+
*/
21+
export function useEstimateGas(): UseMutationResult<
22+
EstimateGasResult,
23+
Error,
24+
PreparedTransaction
25+
> {
26+
const account = useActiveAccount();
27+
28+
return useMutation({
29+
mutationFn: (transaction) => estimateGas({ transaction, account }),
30+
});
31+
}

packages/thirdweb/src/react/web/ui/ConnectWallet/ConnectWallet.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import type { Chain } from "../../../../chains/types.js";
2626
import type { ConnectLocale } from "./locale/types.js";
2727
import { WalletConnectionContext } from "../../../core/providers/wallet-connection.js";
2828
import { getDefaultWallets } from "../../wallets/defaultWallets.js";
29-
import { AutoConnect } from "../../../core/hooks/connection/useAutoConnect.js";
29+
import { AutoConnect } from "../../../core/hooks/connection/useAutoConnect.jsx";
3030
import ConnectModal from "./Modal/ConnectModal.js";
3131
import { useWalletConnectionCtx } from "../../../core/hooks/others/useWalletConnectionCtx.js";
3232
import { getConnectLocale } from "./locale/getConnectLocale.js";

packages/thirdweb/src/rpc/actions/eth_blockNumber.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const rpcClient = getRpcClient({
99
client: TEST_CLIENT,
1010
});
1111

12-
describe("eth_blockNumber", () => {
12+
describe.runIf(process.env.TW_SECRET_KEY)("eth_blockNumber", () => {
1313
it("should return the block number", async () => {
1414
const blockNumber = await eth_blockNumber(rpcClient);
1515
expect(blockNumber).toEqual(19139495n);

packages/thirdweb/src/rpc/actions/eth_getBalance.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const rpcClient = getRpcClient({
1818
client: TEST_CLIENT,
1919
});
2020

21-
describe("eth_getBalance", () => {
21+
describe.runIf(process.env.TW_SECRET_KEY)("eth_getBalance", () => {
2222
it("should return the correct balance at the given block", async () => {
2323
const vitalikBalance = await eth_getBalance(rpcClient, {
2424
address: VITALIK_WALLET,

packages/thirdweb/src/rpc/actions/eth_getLogs.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const rpcClient = getRpcClient({
99
client: TEST_CLIENT,
1010
});
1111

12-
describe("eth_getLogs", () => {
12+
describe.runIf(process.env.TW_SECRET_KEY)("eth_getLogs", () => {
1313
it("should return unparsed logs, without events", async () => {
1414
const logs = await eth_getLogs(rpcClient);
1515
expect(logs).toMatchInlineSnapshot(`[]`);

packages/thirdweb/src/rpc/rpc.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,17 @@ export function getRpcClient(
141141
inflight.reject(new Error("No response"));
142142
return;
143143
}
144+
// handle errors in the response
145+
if (response instanceof Error) {
146+
inflight.reject(response);
147+
return;
148+
}
149+
150+
// handle strings as responses??
151+
if (typeof response === "string") {
152+
inflight.reject(new Error(response));
153+
return;
154+
}
144155

145156
if ("error" in response) {
146157
inflight.reject(response.error);

packages/thirdweb/src/transaction/actions/estimate-gas-cost.test.ts

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,23 @@ import { describe, it, expect } from "vitest";
44
import { prepareContractCall } from "../prepare-contract-call.js";
55
import { estimateGasCost } from "./estimate-gas-cost.js";
66

7-
describe("transaction: estimateGasCost", () => {
8-
it("should estimateGasCost correctly", async () => {
9-
const tx = prepareContractCall({
10-
contract: USDC_CONTRACT,
11-
method: "function transfer(address, uint256) returns (bool)",
12-
params: [TEST_WALLET_A, 100n],
7+
describe.runIf(process.env.TW_SECRET_KEY)(
8+
"transaction: estimateGasCost",
9+
() => {
10+
it("should estimateGasCost correctly", async () => {
11+
const tx = prepareContractCall({
12+
contract: USDC_CONTRACT,
13+
method: "function transfer(address, uint256) returns (bool)",
14+
params: [TEST_WALLET_A, 100n],
15+
});
16+
const result = await estimateGasCost({
17+
transaction: tx,
18+
from: TEST_WALLET_A,
19+
});
20+
expect(result).toEqual({
21+
ether: "0.001003554133979864",
22+
wei: 1003554133979864n,
23+
});
1324
});
14-
const result = await estimateGasCost({
15-
transaction: tx,
16-
from: TEST_WALLET_A,
17-
});
18-
expect(result).toEqual({
19-
ether: "0.001003554133979864",
20-
wei: 1003554133979864n,
21-
});
22-
});
23-
});
25+
},
26+
);

0 commit comments

Comments
 (0)