@b3dotfun/sdk 0.1.70-alpha.17 → 0.1.70-alpha.18

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.
@@ -5,6 +5,7 @@ export interface UnifiedTransactionParams {
5
5
  }
6
6
  export declare function useUnifiedChainSwitchAndExecute(): {
7
7
  switchChainAndExecute: (targetChainId: number, params: UnifiedTransactionParams) => Promise<string | undefined>;
8
+ switchChainAndExecuteWithGlobalAccount: (targetChainId: number, params: UnifiedTransactionParams, expectedGlobalAccountAddress?: string) => Promise<string | undefined>;
8
9
  switchChainAndExecuteWithEOA: (targetChainId: number, params: UnifiedTransactionParams) => Promise<string | undefined>;
9
10
  isSwitchingOrExecuting: boolean;
10
11
  isActiveSmartWallet: boolean | undefined;
@@ -15,13 +15,11 @@ const thirdweb_2 = require("thirdweb");
15
15
  const viem_1 = require("viem");
16
16
  const wagmi_1 = require("wagmi");
17
17
  const B3ConfigProvider_1 = require("../components/B3Provider/B3ConfigProvider");
18
- const useB3Account_1 = require("../components/B3Provider/useB3Account");
19
18
  const useAccountWallet_1 = require("./useAccountWallet");
20
19
  function useUnifiedChainSwitchAndExecute() {
21
20
  const { switchChainAsync } = (0, wagmi_1.useSwitchChain)();
22
21
  const [isSwitchingOrExecuting, setIsSwitchingOrExecuting] = (0, react_2.useState)(false);
23
- const { isActiveSmartWallet, isActiveEOAWallet, connectedEOAWallet } = (0, useAccountWallet_1.useAccountWallet)();
24
- const aaAccount = (0, useB3Account_1.useB3Account)();
22
+ const { isActiveSmartWallet, isActiveEOAWallet, connectedSmartWallet, connectedEOAWallet } = (0, useAccountWallet_1.useAccountWallet)();
25
23
  // From context, not process.env: partnerId is hashed into the server intent key and must match the ecosystem wallet.
26
24
  const { partnerId } = (0, B3ConfigProvider_1.useB3Config)();
27
25
  // Handle EOA wallet chain switch and execute transaction
@@ -100,10 +98,18 @@ function useUnifiedChainSwitchAndExecute() {
100
98
  setIsSwitchingOrExecuting(false);
101
99
  }
102
100
  }, [connectedEOAWallet, switchChainAsync]);
103
- // Handle AA wallet transaction (no chain switch needed for AA)
104
- const handleAASendTransaction = (0, react_2.useCallback)(async (targetChainId, params) => {
105
- if (!aaAccount) {
106
- react_1.toast.error("Smart wallet not connected");
101
+ // Handle Global Account transaction (no chain switch needed for AA).
102
+ // Resolve the signer from the connected ecosystem wallet rather than the active
103
+ // wallet so callers can explicitly require the Global Account.
104
+ const handleGlobalAccountSendTransaction = (0, react_2.useCallback)(async (targetChainId, params, expectedGlobalAccountAddress) => {
105
+ const globalAccount = connectedSmartWallet?.getAccount();
106
+ if (!globalAccount) {
107
+ react_1.toast.error("Global Account not connected");
108
+ return;
109
+ }
110
+ if (expectedGlobalAccountAddress &&
111
+ globalAccount.address.toLowerCase() !== expectedGlobalAccountAddress.toLowerCase()) {
112
+ react_1.toast.error("Connected Global Account does not match the authenticated account");
107
113
  return;
108
114
  }
109
115
  try {
@@ -137,7 +143,7 @@ function useUnifiedChainSwitchAndExecute() {
137
143
  react_1.toast.info("Sending transaction…");
138
144
  const start = performance.now();
139
145
  const sendTxResponse = await (0, thirdweb_2.sendTransaction)({
140
- account: aaAccount,
146
+ account: globalAccount,
141
147
  transaction,
142
148
  });
143
149
  const end = performance.now();
@@ -153,17 +159,18 @@ function useUnifiedChainSwitchAndExecute() {
153
159
  finally {
154
160
  setIsSwitchingOrExecuting(false);
155
161
  }
156
- }, [aaAccount, partnerId]);
162
+ }, [connectedSmartWallet, partnerId]);
157
163
  // Unified switch chain and execute function
158
164
  const switchChainAndExecute = (0, react_2.useCallback)(async (targetChainId, params) => {
159
165
  // Check which wallet type is active
160
166
  if (isActiveSmartWallet) {
161
- return handleAASendTransaction(targetChainId, params);
167
+ return handleGlobalAccountSendTransaction(targetChainId, params);
162
168
  }
163
169
  return handleEOASwitchChainAndSendTransaction(targetChainId, params);
164
- }, [isActiveSmartWallet, handleAASendTransaction, handleEOASwitchChainAndSendTransaction]);
170
+ }, [isActiveSmartWallet, handleGlobalAccountSendTransaction, handleEOASwitchChainAndSendTransaction]);
165
171
  return {
166
172
  switchChainAndExecute,
173
+ switchChainAndExecuteWithGlobalAccount: handleGlobalAccountSendTransaction,
167
174
  switchChainAndExecuteWithEOA: handleEOASwitchChainAndSendTransaction,
168
175
  isSwitchingOrExecuting,
169
176
  isActiveSmartWallet,
@@ -5,6 +5,7 @@ export interface UnifiedTransactionParams {
5
5
  }
6
6
  export declare function useUnifiedChainSwitchAndExecute(): {
7
7
  switchChainAndExecute: (targetChainId: number, params: UnifiedTransactionParams) => Promise<string | undefined>;
8
+ switchChainAndExecuteWithGlobalAccount: (targetChainId: number, params: UnifiedTransactionParams, expectedGlobalAccountAddress?: string) => Promise<string | undefined>;
8
9
  switchChainAndExecuteWithEOA: (targetChainId: number, params: UnifiedTransactionParams) => Promise<string | undefined>;
9
10
  isSwitchingOrExecuting: boolean;
10
11
  isActiveSmartWallet: boolean | undefined;
@@ -9,13 +9,11 @@ import { prepareTransaction, sendTransaction as twSendTransaction } from "thirdw
9
9
  import { isAddress } from "viem";
10
10
  import { useSwitchChain } from "wagmi";
11
11
  import { useB3Config } from "../components/B3Provider/B3ConfigProvider.js";
12
- import { useB3Account } from "../components/B3Provider/useB3Account.js";
13
12
  import { useAccountWallet } from "./useAccountWallet.js";
14
13
  export function useUnifiedChainSwitchAndExecute() {
15
14
  const { switchChainAsync } = useSwitchChain();
16
15
  const [isSwitchingOrExecuting, setIsSwitchingOrExecuting] = useState(false);
17
- const { isActiveSmartWallet, isActiveEOAWallet, connectedEOAWallet } = useAccountWallet();
18
- const aaAccount = useB3Account();
16
+ const { isActiveSmartWallet, isActiveEOAWallet, connectedSmartWallet, connectedEOAWallet } = useAccountWallet();
19
17
  // From context, not process.env: partnerId is hashed into the server intent key and must match the ecosystem wallet.
20
18
  const { partnerId } = useB3Config();
21
19
  // Handle EOA wallet chain switch and execute transaction
@@ -94,10 +92,18 @@ export function useUnifiedChainSwitchAndExecute() {
94
92
  setIsSwitchingOrExecuting(false);
95
93
  }
96
94
  }, [connectedEOAWallet, switchChainAsync]);
97
- // Handle AA wallet transaction (no chain switch needed for AA)
98
- const handleAASendTransaction = useCallback(async (targetChainId, params) => {
99
- if (!aaAccount) {
100
- toast.error("Smart wallet not connected");
95
+ // Handle Global Account transaction (no chain switch needed for AA).
96
+ // Resolve the signer from the connected ecosystem wallet rather than the active
97
+ // wallet so callers can explicitly require the Global Account.
98
+ const handleGlobalAccountSendTransaction = useCallback(async (targetChainId, params, expectedGlobalAccountAddress) => {
99
+ const globalAccount = connectedSmartWallet?.getAccount();
100
+ if (!globalAccount) {
101
+ toast.error("Global Account not connected");
102
+ return;
103
+ }
104
+ if (expectedGlobalAccountAddress &&
105
+ globalAccount.address.toLowerCase() !== expectedGlobalAccountAddress.toLowerCase()) {
106
+ toast.error("Connected Global Account does not match the authenticated account");
101
107
  return;
102
108
  }
103
109
  try {
@@ -131,7 +137,7 @@ export function useUnifiedChainSwitchAndExecute() {
131
137
  toast.info("Sending transaction…");
132
138
  const start = performance.now();
133
139
  const sendTxResponse = await twSendTransaction({
134
- account: aaAccount,
140
+ account: globalAccount,
135
141
  transaction,
136
142
  });
137
143
  const end = performance.now();
@@ -147,17 +153,18 @@ export function useUnifiedChainSwitchAndExecute() {
147
153
  finally {
148
154
  setIsSwitchingOrExecuting(false);
149
155
  }
150
- }, [aaAccount, partnerId]);
156
+ }, [connectedSmartWallet, partnerId]);
151
157
  // Unified switch chain and execute function
152
158
  const switchChainAndExecute = useCallback(async (targetChainId, params) => {
153
159
  // Check which wallet type is active
154
160
  if (isActiveSmartWallet) {
155
- return handleAASendTransaction(targetChainId, params);
161
+ return handleGlobalAccountSendTransaction(targetChainId, params);
156
162
  }
157
163
  return handleEOASwitchChainAndSendTransaction(targetChainId, params);
158
- }, [isActiveSmartWallet, handleAASendTransaction, handleEOASwitchChainAndSendTransaction]);
164
+ }, [isActiveSmartWallet, handleGlobalAccountSendTransaction, handleEOASwitchChainAndSendTransaction]);
159
165
  return {
160
166
  switchChainAndExecute,
167
+ switchChainAndExecuteWithGlobalAccount: handleGlobalAccountSendTransaction,
161
168
  switchChainAndExecuteWithEOA: handleEOASwitchChainAndSendTransaction,
162
169
  isSwitchingOrExecuting,
163
170
  isActiveSmartWallet,
@@ -5,6 +5,7 @@ export interface UnifiedTransactionParams {
5
5
  }
6
6
  export declare function useUnifiedChainSwitchAndExecute(): {
7
7
  switchChainAndExecute: (targetChainId: number, params: UnifiedTransactionParams) => Promise<string | undefined>;
8
+ switchChainAndExecuteWithGlobalAccount: (targetChainId: number, params: UnifiedTransactionParams, expectedGlobalAccountAddress?: string) => Promise<string | undefined>;
8
9
  switchChainAndExecuteWithEOA: (targetChainId: number, params: UnifiedTransactionParams) => Promise<string | undefined>;
9
10
  isSwitchingOrExecuting: boolean;
10
11
  isActiveSmartWallet: boolean | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b3dotfun/sdk",
3
- "version": "0.1.70-alpha.17",
3
+ "version": "0.1.70-alpha.18",
4
4
  "source": "src/index.ts",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "react-native": "./dist/cjs/index.native.js",
@@ -0,0 +1,156 @@
1
+ import { act, renderHook } from "@testing-library/react";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { useUnifiedChainSwitchAndExecute } from "../useUnifiedChainSwitchAndExecute";
4
+
5
+ const mocks = vi.hoisted(() => ({
6
+ eoaGetAccount: vi.fn(),
7
+ globalAccountGetAccount: vi.fn(),
8
+ intentCreate: vi.fn(),
9
+ prepareTransaction: vi.fn(),
10
+ sendTransaction: vi.fn(),
11
+ toastError: vi.fn(),
12
+ toastInfo: vi.fn(),
13
+ toastSuccess: vi.fn(),
14
+ useAccountWallet: vi.fn(),
15
+ }));
16
+
17
+ vi.mock("@b3dotfun/sdk/anyspend", () => ({
18
+ getChainName: (chainId: number) => `Chain ${chainId}`,
19
+ getNativeToken: () => ({ name: "Ether", symbol: "ETH", decimals: 18 }),
20
+ }));
21
+
22
+ vi.mock("@b3dotfun/sdk/global-account/app", () => ({
23
+ default: {
24
+ service: () => ({ create: mocks.intentCreate }),
25
+ },
26
+ }));
27
+
28
+ vi.mock("@b3dotfun/sdk/global-account/react", () => ({
29
+ toast: {
30
+ dismiss: vi.fn(),
31
+ error: mocks.toastError,
32
+ info: mocks.toastInfo,
33
+ success: mocks.toastSuccess,
34
+ },
35
+ }));
36
+
37
+ vi.mock("@b3dotfun/sdk/shared/constants/chains/supported", () => ({
38
+ getThirdwebChain: (chainId: number) => ({ id: chainId }),
39
+ supportedChains: [],
40
+ }));
41
+
42
+ vi.mock("@b3dotfun/sdk/shared/utils/thirdweb", () => ({ client: {} }));
43
+
44
+ vi.mock("thirdweb", () => ({
45
+ prepareTransaction: mocks.prepareTransaction,
46
+ sendTransaction: mocks.sendTransaction,
47
+ }));
48
+
49
+ vi.mock("viem", () => ({ isAddress: () => true }));
50
+
51
+ vi.mock("wagmi", () => ({
52
+ useSwitchChain: () => ({ switchChainAsync: vi.fn() }),
53
+ }));
54
+
55
+ vi.mock("../../components/B3Provider/B3ConfigProvider", () => ({
56
+ useB3Config: () => ({ partnerId: "test-partner" }),
57
+ }));
58
+
59
+ vi.mock("../useAccountWallet", () => ({
60
+ useAccountWallet: mocks.useAccountWallet,
61
+ }));
62
+
63
+ const GLOBAL_ACCOUNT_ADDRESS = "0x1111111111111111111111111111111111111111";
64
+ const OTHER_GLOBAL_ACCOUNT_ADDRESS = "0x2222222222222222222222222222222222222222";
65
+ const EOA_ADDRESS = "0x3333333333333333333333333333333333333333";
66
+ const TRANSACTION_HASH = `0x${"a".repeat(64)}`;
67
+
68
+ const globalAccount = { address: GLOBAL_ACCOUNT_ADDRESS };
69
+ const preparedTransaction = { prepared: true };
70
+ const transactionParams = {
71
+ to: "0x4444444444444444444444444444444444444444",
72
+ data: "0x1234",
73
+ value: BigInt(0),
74
+ };
75
+
76
+ describe("useUnifiedChainSwitchAndExecute", () => {
77
+ beforeEach(() => {
78
+ vi.clearAllMocks();
79
+
80
+ mocks.globalAccountGetAccount.mockReturnValue(globalAccount);
81
+ mocks.eoaGetAccount.mockReturnValue({ address: EOA_ADDRESS });
82
+ mocks.intentCreate.mockResolvedValue({});
83
+ mocks.prepareTransaction.mockReturnValue(preparedTransaction);
84
+ mocks.sendTransaction.mockResolvedValue({ transactionHash: TRANSACTION_HASH });
85
+ mocks.useAccountWallet.mockReturnValue({
86
+ connectedSmartWallet: { getAccount: mocks.globalAccountGetAccount },
87
+ connectedEOAWallet: { getAccount: mocks.eoaGetAccount },
88
+ isActiveSmartWallet: false,
89
+ isActiveEOAWallet: true,
90
+ });
91
+ });
92
+
93
+ it("uses the connected Global Account even when the EOA wallet is active", async () => {
94
+ const { result } = renderHook(() => useUnifiedChainSwitchAndExecute());
95
+
96
+ let transactionHash: string | undefined;
97
+ await act(async () => {
98
+ transactionHash = await result.current.switchChainAndExecuteWithGlobalAccount(
99
+ 8453,
100
+ transactionParams,
101
+ GLOBAL_ACCOUNT_ADDRESS,
102
+ );
103
+ });
104
+
105
+ expect(transactionHash).toBe(TRANSACTION_HASH);
106
+ expect(mocks.globalAccountGetAccount).toHaveBeenCalledOnce();
107
+ expect(mocks.eoaGetAccount).not.toHaveBeenCalled();
108
+ expect(mocks.sendTransaction).toHaveBeenCalledWith({
109
+ account: globalAccount,
110
+ transaction: preparedTransaction,
111
+ });
112
+ });
113
+
114
+ it("rejects a signer that does not match the authenticated Global Account", async () => {
115
+ const { result } = renderHook(() => useUnifiedChainSwitchAndExecute());
116
+
117
+ let transactionHash: string | undefined;
118
+ await act(async () => {
119
+ transactionHash = await result.current.switchChainAndExecuteWithGlobalAccount(
120
+ 8453,
121
+ transactionParams,
122
+ OTHER_GLOBAL_ACCOUNT_ADDRESS,
123
+ );
124
+ });
125
+
126
+ expect(transactionHash).toBeUndefined();
127
+ expect(mocks.toastError).toHaveBeenCalledWith("Connected Global Account does not match the authenticated account");
128
+ expect(mocks.prepareTransaction).not.toHaveBeenCalled();
129
+ expect(mocks.sendTransaction).not.toHaveBeenCalled();
130
+ expect(mocks.eoaGetAccount).not.toHaveBeenCalled();
131
+ });
132
+
133
+ it("fails closed when no Global Account signer is connected", async () => {
134
+ mocks.useAccountWallet.mockReturnValue({
135
+ connectedSmartWallet: undefined,
136
+ connectedEOAWallet: { getAccount: mocks.eoaGetAccount },
137
+ isActiveSmartWallet: false,
138
+ isActiveEOAWallet: true,
139
+ });
140
+ const { result } = renderHook(() => useUnifiedChainSwitchAndExecute());
141
+
142
+ let transactionHash: string | undefined;
143
+ await act(async () => {
144
+ transactionHash = await result.current.switchChainAndExecuteWithGlobalAccount(
145
+ 8453,
146
+ transactionParams,
147
+ GLOBAL_ACCOUNT_ADDRESS,
148
+ );
149
+ });
150
+
151
+ expect(transactionHash).toBeUndefined();
152
+ expect(mocks.toastError).toHaveBeenCalledWith("Global Account not connected");
153
+ expect(mocks.sendTransaction).not.toHaveBeenCalled();
154
+ expect(mocks.eoaGetAccount).not.toHaveBeenCalled();
155
+ });
156
+ });
@@ -10,7 +10,6 @@ import { prepareTransaction, sendTransaction as twSendTransaction } from "thirdw
10
10
  import { isAddress } from "viem";
11
11
  import { useSwitchChain } from "wagmi";
12
12
  import { useB3Config } from "../components/B3Provider/B3ConfigProvider";
13
- import { useB3Account } from "../components/B3Provider/useB3Account";
14
13
  import { useAccountWallet } from "./useAccountWallet";
15
14
 
16
15
  export interface UnifiedTransactionParams {
@@ -23,8 +22,7 @@ export function useUnifiedChainSwitchAndExecute() {
23
22
  const { switchChainAsync } = useSwitchChain();
24
23
  const [isSwitchingOrExecuting, setIsSwitchingOrExecuting] = useState(false);
25
24
 
26
- const { isActiveSmartWallet, isActiveEOAWallet, connectedEOAWallet } = useAccountWallet();
27
- const aaAccount = useB3Account();
25
+ const { isActiveSmartWallet, isActiveEOAWallet, connectedSmartWallet, connectedEOAWallet } = useAccountWallet();
28
26
 
29
27
  // From context, not process.env: partnerId is hashed into the server intent key and must match the ecosystem wallet.
30
28
  const { partnerId } = useB3Config();
@@ -119,11 +117,26 @@ export function useUnifiedChainSwitchAndExecute() {
119
117
  [connectedEOAWallet, switchChainAsync],
120
118
  );
121
119
 
122
- // Handle AA wallet transaction (no chain switch needed for AA)
123
- const handleAASendTransaction = useCallback(
124
- async (targetChainId: number, params: UnifiedTransactionParams): Promise<string | undefined> => {
125
- if (!aaAccount) {
126
- toast.error("Smart wallet not connected");
120
+ // Handle Global Account transaction (no chain switch needed for AA).
121
+ // Resolve the signer from the connected ecosystem wallet rather than the active
122
+ // wallet so callers can explicitly require the Global Account.
123
+ const handleGlobalAccountSendTransaction = useCallback(
124
+ async (
125
+ targetChainId: number,
126
+ params: UnifiedTransactionParams,
127
+ expectedGlobalAccountAddress?: string,
128
+ ): Promise<string | undefined> => {
129
+ const globalAccount = connectedSmartWallet?.getAccount();
130
+ if (!globalAccount) {
131
+ toast.error("Global Account not connected");
132
+ return;
133
+ }
134
+
135
+ if (
136
+ expectedGlobalAccountAddress &&
137
+ globalAccount.address.toLowerCase() !== expectedGlobalAccountAddress.toLowerCase()
138
+ ) {
139
+ toast.error("Connected Global Account does not match the authenticated account");
127
140
  return;
128
141
  }
129
142
 
@@ -162,7 +175,7 @@ export function useUnifiedChainSwitchAndExecute() {
162
175
  toast.info("Sending transaction…");
163
176
  const start = performance.now();
164
177
  const sendTxResponse = await twSendTransaction({
165
- account: aaAccount,
178
+ account: globalAccount,
166
179
  transaction,
167
180
  });
168
181
  const end = performance.now();
@@ -178,7 +191,7 @@ export function useUnifiedChainSwitchAndExecute() {
178
191
  setIsSwitchingOrExecuting(false);
179
192
  }
180
193
  },
181
- [aaAccount, partnerId],
194
+ [connectedSmartWallet, partnerId],
182
195
  );
183
196
 
184
197
  // Unified switch chain and execute function
@@ -186,15 +199,16 @@ export function useUnifiedChainSwitchAndExecute() {
186
199
  async (targetChainId: number, params: UnifiedTransactionParams): Promise<string | undefined> => {
187
200
  // Check which wallet type is active
188
201
  if (isActiveSmartWallet) {
189
- return handleAASendTransaction(targetChainId, params);
202
+ return handleGlobalAccountSendTransaction(targetChainId, params);
190
203
  }
191
204
  return handleEOASwitchChainAndSendTransaction(targetChainId, params);
192
205
  },
193
- [isActiveSmartWallet, handleAASendTransaction, handleEOASwitchChainAndSendTransaction],
206
+ [isActiveSmartWallet, handleGlobalAccountSendTransaction, handleEOASwitchChainAndSendTransaction],
194
207
  );
195
208
 
196
209
  return {
197
210
  switchChainAndExecute,
211
+ switchChainAndExecuteWithGlobalAccount: handleGlobalAccountSendTransaction,
198
212
  switchChainAndExecuteWithEOA: handleEOASwitchChainAndSendTransaction,
199
213
  isSwitchingOrExecuting,
200
214
  isActiveSmartWallet,