thirdweb 5.65.0 → 5.65.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/cjs/chains/utils.js +14 -6
  2. package/dist/cjs/chains/utils.js.map +1 -1
  3. package/dist/cjs/extensions/erc20/write/transferBatch.js +65 -18
  4. package/dist/cjs/extensions/erc20/write/transferBatch.js.map +1 -1
  5. package/dist/cjs/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.js +1 -1
  6. package/dist/cjs/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.js.map +1 -1
  7. package/dist/cjs/utils/any-evm/zksync/isZkSyncChain.js +13 -2
  8. package/dist/cjs/utils/any-evm/zksync/isZkSyncChain.js.map +1 -1
  9. package/dist/cjs/version.js +1 -1
  10. package/dist/cjs/wallets/smart/index.js +1 -1
  11. package/dist/cjs/wallets/smart/index.js.map +1 -1
  12. package/dist/esm/chains/utils.js +8 -2
  13. package/dist/esm/chains/utils.js.map +1 -1
  14. package/dist/esm/extensions/erc20/write/transferBatch.js +64 -18
  15. package/dist/esm/extensions/erc20/write/transferBatch.js.map +1 -1
  16. package/dist/esm/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.js +1 -1
  17. package/dist/esm/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.js.map +1 -1
  18. package/dist/esm/utils/any-evm/zksync/isZkSyncChain.js +13 -2
  19. package/dist/esm/utils/any-evm/zksync/isZkSyncChain.js.map +1 -1
  20. package/dist/esm/version.js +1 -1
  21. package/dist/esm/wallets/smart/index.js +1 -1
  22. package/dist/esm/wallets/smart/index.js.map +1 -1
  23. package/dist/types/chains/utils.d.ts +10 -0
  24. package/dist/types/chains/utils.d.ts.map +1 -1
  25. package/dist/types/extensions/erc20/write/transferBatch.d.ts +31 -0
  26. package/dist/types/extensions/erc20/write/transferBatch.d.ts.map +1 -1
  27. package/dist/types/utils/any-evm/zksync/isZkSyncChain.d.ts.map +1 -1
  28. package/dist/types/version.d.ts +1 -1
  29. package/package.json +1 -1
  30. package/src/chains/utils.test.ts +123 -0
  31. package/src/chains/utils.ts +8 -2
  32. package/src/extensions/erc20/write/transferBatch.test.ts +102 -10
  33. package/src/extensions/erc20/write/transferBatch.ts +79 -25
  34. package/src/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.tsx +2 -1
  35. package/src/utils/any-evm/zksync/isZkSyncChain.ts +12 -2
  36. package/src/version.ts +1 -1
  37. package/src/wallets/smart/index.ts +1 -1
  38. package/src/wallets/utils/getWalletBalance.test.ts +2 -4
@@ -10,7 +10,10 @@ import type {
10
10
  LegacyChain,
11
11
  } from "./types.js";
12
12
 
13
- const CUSTOM_CHAIN_MAP = new Map<number, Chain>();
13
+ /**
14
+ * @internal Exported for tests
15
+ */
16
+ export const CUSTOM_CHAIN_MAP = new Map<number, Chain>();
14
17
 
15
18
  /**
16
19
  * Defines a chain with the given options.
@@ -98,7 +101,10 @@ function isLegacyChain(
98
101
  return "rpc" in chain && Array.isArray(chain.rpc) && "slug" in chain;
99
102
  }
100
103
 
101
- function convertLegacyChain(legacyChain: LegacyChain): Chain {
104
+ /**
105
+ * @internal
106
+ */
107
+ export function convertLegacyChain(legacyChain: LegacyChain): Chain {
102
108
  const RPC_URL = getThirdwebDomains().rpc;
103
109
  return {
104
110
  id: legacyChain.chainId,
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { beforeAll, describe, expect, it } from "vitest";
2
2
  import { ANVIL_CHAIN } from "~test/chains.js";
3
3
  import { TEST_CONTRACT_URI } from "~test/ipfs-uris.js";
4
4
  import { TEST_CLIENT } from "~test/test-clients.js";
@@ -8,19 +8,23 @@ import {
8
8
  TEST_ACCOUNT_C,
9
9
  TEST_ACCOUNT_D,
10
10
  } from "~test/test-wallets.js";
11
- import { getContract } from "../../../contract/contract.js";
11
+ import {
12
+ type ThirdwebContract,
13
+ getContract,
14
+ } from "../../../contract/contract.js";
12
15
  import { deployERC20Contract } from "../../../extensions/prebuilts/deploy-erc20.js";
13
16
  import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
14
17
  import { balanceOf } from "../__generated__/IERC20/read/balanceOf.js";
15
18
  import { mintTo } from "./mintTo.js";
16
- import { transferBatch } from "./transferBatch.js";
19
+ import { optimizeTransferContent, transferBatch } from "./transferBatch.js";
17
20
 
18
21
  const chain = ANVIL_CHAIN;
19
22
  const client = TEST_CLIENT;
20
23
  const account = TEST_ACCOUNT_A;
24
+ let contract: ThirdwebContract;
21
25
 
22
26
  describe.runIf(process.env.TW_SECRET_KEY)("erc20: transferBatch", () => {
23
- it("should transfer tokens to multiple recipients", async () => {
27
+ beforeAll(async () => {
24
28
  const address = await deployERC20Contract({
25
29
  type: "TokenERC20",
26
30
  account,
@@ -31,15 +35,16 @@ describe.runIf(process.env.TW_SECRET_KEY)("erc20: transferBatch", () => {
31
35
  contractURI: TEST_CONTRACT_URI,
32
36
  },
33
37
  });
34
- const contract = getContract({
38
+ contract = getContract({
35
39
  address,
36
40
  chain,
37
41
  client,
38
42
  });
39
-
40
- // Mint 100 tokens
43
+ }, 60_000_000);
44
+ it("should transfer tokens to multiple recipients", async () => {
45
+ // Mint 200 tokens
41
46
  await sendAndConfirmTransaction({
42
- transaction: mintTo({ contract, to: account.address, amount: 100 }),
47
+ transaction: mintTo({ contract, to: account.address, amount: 200 }),
43
48
  account,
44
49
  });
45
50
 
@@ -61,6 +66,14 @@ describe.runIf(process.env.TW_SECRET_KEY)("erc20: transferBatch", () => {
61
66
  to: TEST_ACCOUNT_D.address,
62
67
  amount: 25,
63
68
  },
69
+ {
70
+ to: TEST_ACCOUNT_B.address.toLowerCase(),
71
+ amount: 25,
72
+ },
73
+ {
74
+ to: TEST_ACCOUNT_B.address,
75
+ amountWei: 25n * 10n ** 18n,
76
+ },
64
77
  ],
65
78
  }),
66
79
  });
@@ -73,9 +86,88 @@ describe.runIf(process.env.TW_SECRET_KEY)("erc20: transferBatch", () => {
73
86
  balanceOf({ contract, address: TEST_ACCOUNT_D.address }),
74
87
  ]);
75
88
 
76
- expect(balanceA).toBe(25n * 10n ** 18n);
77
- expect(balanceB).toBe(25n * 10n ** 18n);
89
+ expect(balanceA).toBe(75n * 10n ** 18n);
90
+ expect(balanceB).toBe(75n * 10n ** 18n);
78
91
  expect(balanceC).toBe(25n * 10n ** 18n);
79
92
  expect(balanceD).toBe(25n * 10n ** 18n);
80
93
  });
94
+
95
+ it("should optimize the transfer content", async () => {
96
+ const content = await optimizeTransferContent({
97
+ contract,
98
+ batch: [
99
+ {
100
+ to: TEST_ACCOUNT_B.address,
101
+ amount: 25,
102
+ },
103
+ {
104
+ to: TEST_ACCOUNT_C.address,
105
+ amount: 25,
106
+ },
107
+ {
108
+ to: TEST_ACCOUNT_D.address,
109
+ amount: 25,
110
+ },
111
+ {
112
+ // Should work
113
+ to: TEST_ACCOUNT_B.address.toLowerCase(),
114
+ amount: 25,
115
+ },
116
+ {
117
+ to: TEST_ACCOUNT_B.address,
118
+ amountWei: 25n * 10n ** 18n,
119
+ },
120
+ ],
121
+ });
122
+
123
+ expect(content).toStrictEqual([
124
+ {
125
+ to: TEST_ACCOUNT_B.address,
126
+ amountWei: 75n * 10n ** 18n,
127
+ },
128
+ {
129
+ to: TEST_ACCOUNT_C.address,
130
+ amountWei: 25n * 10n ** 18n,
131
+ },
132
+ {
133
+ to: TEST_ACCOUNT_D.address,
134
+ amountWei: 25n * 10n ** 18n,
135
+ },
136
+ ]);
137
+ });
138
+
139
+ it("an already-optimized content should not be changed", async () => {
140
+ const content = await optimizeTransferContent({
141
+ contract,
142
+ batch: [
143
+ {
144
+ to: TEST_ACCOUNT_B.address,
145
+ amountWei: 25n * 10n ** 18n,
146
+ },
147
+ {
148
+ to: TEST_ACCOUNT_C.address,
149
+ amount: 25,
150
+ },
151
+ {
152
+ to: TEST_ACCOUNT_D.address,
153
+ amount: 25,
154
+ },
155
+ ],
156
+ });
157
+
158
+ expect(content).toStrictEqual([
159
+ {
160
+ to: TEST_ACCOUNT_B.address,
161
+ amountWei: 25n * 10n ** 18n,
162
+ },
163
+ {
164
+ to: TEST_ACCOUNT_C.address,
165
+ amountWei: 25n * 10n ** 18n,
166
+ },
167
+ {
168
+ to: TEST_ACCOUNT_D.address,
169
+ amountWei: 25n * 10n ** 18n,
170
+ },
171
+ ]);
172
+ });
81
173
  });
@@ -53,34 +53,88 @@ export function transferBatch(
53
53
  return multicall({
54
54
  contract: options.contract,
55
55
  asyncParams: async () => {
56
+ const content = await optimizeTransferContent(options);
56
57
  return {
57
- data: await Promise.all(
58
- options.batch.map(async (transfer) => {
59
- let amount: bigint;
60
- if ("amount" in transfer) {
61
- // if we need to parse the amount from ether to gwei then we pull in the decimals extension
62
- const { decimals } = await import("../read/decimals.js");
63
- // it's OK to call this multiple times because the call is cached
64
- // if this fails we fall back to `18` decimals
65
- const d = await decimals(options).catch(() => 18);
66
- // turn ether into gwei
67
- amount = toUnits(transfer.amount.toString(), d);
68
- } else {
69
- amount = transfer.amountWei;
70
- }
71
- return encodeTransfer({
72
- to: transfer.to,
73
- value: amount,
74
- overrides: {
75
- erc20Value: {
76
- amountWei: amount,
77
- tokenAddress: options.contract.address,
78
- },
58
+ data: content.map((item) => {
59
+ return encodeTransfer({
60
+ to: item.to,
61
+ value: item.amountWei,
62
+ overrides: {
63
+ erc20Value: {
64
+ amountWei: item.amountWei,
65
+ tokenAddress: options.contract.address,
79
66
  },
80
- });
81
- }),
82
- ),
67
+ },
68
+ });
69
+ }),
83
70
  };
84
71
  },
85
72
  });
86
73
  }
74
+
75
+ /**
76
+ * Records with the same recipient (`to`) can be packed into one transaction
77
+ * For example, the data below:
78
+ * ```ts
79
+ * [
80
+ * {
81
+ * to: "wallet-a",
82
+ * amount: 1,
83
+ * },
84
+ * {
85
+ * to: "wallet-A",
86
+ * amountWei: 1000000000000000000n,
87
+ * },
88
+ * ]
89
+ * ```
90
+ *
91
+ * can be packed to:
92
+ * ```ts
93
+ * [
94
+ * {
95
+ * to: "wallet-a",
96
+ * amountWei: 2000000000000000000n,
97
+ * },
98
+ * ]
99
+ * ```
100
+ * @internal
101
+ */
102
+ export async function optimizeTransferContent(
103
+ options: BaseTransactionOptions<TransferBatchParams>,
104
+ ): Promise<Array<{ to: string; amountWei: bigint }>> {
105
+ const groupedRecords = await options.batch.reduce(
106
+ async (accPromise, record) => {
107
+ const acc = await accPromise;
108
+ let amountInWei: bigint;
109
+ if ("amount" in record) {
110
+ // it's OK to call this multiple times because the call is cached
111
+ const { decimals } = await import("../read/decimals.js");
112
+ // if this fails we fall back to `18` decimals
113
+ const d = await decimals(options).catch(() => undefined);
114
+ if (d === undefined) {
115
+ throw new Error(
116
+ `Failed to get the decimals for contract: ${options.contract.address}`,
117
+ );
118
+ }
119
+ amountInWei = toUnits(record.amount.toString(), d);
120
+ } else {
121
+ amountInWei = record.amountWei;
122
+ }
123
+ const existingRecord = acc.find(
124
+ (r) => r.to.toLowerCase() === record.to.toLowerCase(),
125
+ );
126
+ if (existingRecord) {
127
+ existingRecord.amountWei = existingRecord.amountWei + amountInWei;
128
+ } else {
129
+ acc.push({
130
+ to: record.to,
131
+ amountWei: amountInWei,
132
+ });
133
+ }
134
+
135
+ return acc;
136
+ },
137
+ Promise.resolve([] as { to: string; amountWei: bigint }[]),
138
+ );
139
+ return groupedRecords;
140
+ }
@@ -984,7 +984,8 @@ function SwapScreenContent(props: {
984
984
 
985
985
  const disableContinue =
986
986
  (swapRequired && !quoteQuery.data) || isNotEnoughBalance;
987
- const switchChainRequired = props.payer.chain.id !== fromChain.id;
987
+ const switchChainRequired =
988
+ props.payer.wallet.getChain()?.id !== fromChain.id;
988
989
 
989
990
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
990
991
  function getErrorMessage(err: any) {
@@ -16,10 +16,20 @@ export async function isZkSyncChain(chain: Chain) {
16
16
  chain.id === 4654 ||
17
17
  chain.id === 333271 ||
18
18
  chain.id === 37111 ||
19
- chain.id === 978658
19
+ chain.id === 978658 ||
20
+ chain.id === 531050104 ||
21
+ chain.id === 4457845
20
22
  ) {
21
23
  return true;
22
24
  }
23
25
 
24
- return false;
26
+ // fallback to checking the stack on rpc
27
+ try {
28
+ const { getChainMetadata } = await import("../../../chains/utils.js");
29
+ const chainMetadata = await getChainMetadata(chain);
30
+ return chainMetadata.stackType === "zksync_stack";
31
+ } catch {
32
+ // If the network check fails, assume it's not a ZkSync chain
33
+ return false;
34
+ }
25
35
  }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = "5.65.0";
1
+ export const version = "5.65.2";
@@ -624,7 +624,7 @@ async function _sendUserOp(args: {
624
624
  });
625
625
  // wait for tx receipt rather than return the userOp hash
626
626
  const receipt = await waitForUserOpReceipt({
627
- ...options,
627
+ ...bundlerOptions,
628
628
  userOpHash,
629
629
  });
630
630
 
@@ -3,7 +3,6 @@ import { ANVIL_CHAIN } from "~test/chains.js";
3
3
  import { TEST_CONTRACT_URI } from "~test/ipfs-uris.js";
4
4
  import { TEST_CLIENT } from "~test/test-clients.js";
5
5
  import { TEST_ACCOUNT_A } from "~test/test-wallets.js";
6
- import { defineChain } from "../../chains/utils.js";
7
6
  import { getContract } from "../../contract/contract.js";
8
7
  import { mintTo } from "../../extensions/erc20/write/mintTo.js";
9
8
  import { deployERC20Contract } from "../../extensions/prebuilts/deploy-erc20.js";
@@ -57,12 +56,11 @@ describe.runIf(process.env.TW_SECRET_KEY)("getWalletBalance", () => {
57
56
  expect(result.displayValue).toBe(amount.toString());
58
57
  });
59
58
 
60
- it("should work for un-named token", async () => {
59
+ it("should work for native currency", async () => {
61
60
  const result = await getWalletBalance({
62
61
  address: TEST_ACCOUNT_A.address,
63
62
  client: TEST_CLIENT,
64
- chain: defineChain(97),
65
- tokenAddress: "0xd66c6B4F0be8CE5b39D52E0Fd1344c389929B378",
63
+ chain: ANVIL_CHAIN,
66
64
  });
67
65
  expect(result).toBeDefined();
68
66
  expect(result.decimals).toBe(18);