@frak-labs/core-sdk 0.1.0-beta.b0bd1f8a → 0.1.0-beta.c7e026e5

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.
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "url": "https://twitter.com/QNivelais"
12
12
  }
13
13
  ],
14
- "version": "0.1.0-beta.b0bd1f8a",
14
+ "version": "0.1.0-beta.c7e026e5",
15
15
  "description": "Core SDK of the Frak wallet, low level library to interact directly with the frak ecosystem.",
16
16
  "repository": {
17
17
  "url": "https://github.com/frak-id/wallet",
@@ -0,0 +1,153 @@
1
+ import type { Hex } from "viem";
2
+ import { beforeEach, describe, expect, test, vi } from "vitest";
3
+ import { referralInteraction } from "./referralInteraction";
4
+
5
+ vi.mock("../../utils", () => ({
6
+ FrakContextManager: {
7
+ parse: vi.fn(),
8
+ },
9
+ }));
10
+
11
+ vi.mock("../index", () => ({
12
+ watchWalletStatus: vi.fn(),
13
+ }));
14
+
15
+ vi.mock("./processReferral", () => ({
16
+ processReferral: vi.fn(),
17
+ }));
18
+
19
+ describe("referralInteraction", () => {
20
+ const mockClient = {
21
+ request: vi.fn(),
22
+ } as any;
23
+
24
+ const mockProductId =
25
+ "0x0000000000000000000000000000000000000000000000000000000000000002" as Hex;
26
+
27
+ beforeEach(() => {
28
+ vi.clearAllMocks();
29
+ Object.defineProperty(global, "window", {
30
+ value: { location: { href: "https://example.com?frak=test" } },
31
+ writable: true,
32
+ });
33
+ });
34
+
35
+ test("should parse context from window location", async () => {
36
+ const { FrakContextManager } = await import("../../utils");
37
+ const { watchWalletStatus } = await import("../index");
38
+ const { processReferral } = await import("./processReferral");
39
+
40
+ vi.mocked(FrakContextManager.parse).mockReturnValue({} as any);
41
+ vi.mocked(watchWalletStatus).mockResolvedValue(null as any);
42
+ vi.mocked(processReferral).mockResolvedValue("success");
43
+
44
+ await referralInteraction(mockClient);
45
+
46
+ expect(FrakContextManager.parse).toHaveBeenCalledWith({
47
+ url: "https://example.com?frak=test",
48
+ });
49
+ });
50
+
51
+ test("should get current wallet status", async () => {
52
+ const { FrakContextManager } = await import("../../utils");
53
+ const { watchWalletStatus } = await import("../index");
54
+ const { processReferral } = await import("./processReferral");
55
+
56
+ vi.mocked(FrakContextManager.parse).mockReturnValue({} as any);
57
+ vi.mocked(watchWalletStatus).mockResolvedValue({
58
+ wallet: "0x123" as Hex,
59
+ interactionSession: true,
60
+ } as any);
61
+ vi.mocked(processReferral).mockResolvedValue("success");
62
+
63
+ await referralInteraction(mockClient);
64
+
65
+ expect(watchWalletStatus).toHaveBeenCalledWith(mockClient);
66
+ });
67
+
68
+ test("should call processReferral with all parameters", async () => {
69
+ const { FrakContextManager } = await import("../../utils");
70
+ const { watchWalletStatus } = await import("../index");
71
+ const { processReferral } = await import("./processReferral");
72
+
73
+ const mockContext = { r: "0xreferrer" as Hex };
74
+ const mockWalletStatus = { wallet: "0x123" as Hex };
75
+ const mockModalConfig = { type: "login" };
76
+ const mockOptions = { alwaysAppendUrl: true };
77
+
78
+ vi.mocked(FrakContextManager.parse).mockReturnValue(mockContext as any);
79
+ vi.mocked(watchWalletStatus).mockResolvedValue(mockWalletStatus as any);
80
+ vi.mocked(processReferral).mockResolvedValue("success");
81
+
82
+ await referralInteraction(mockClient, {
83
+ productId: mockProductId,
84
+ modalConfig: mockModalConfig as any,
85
+ options: mockOptions,
86
+ });
87
+
88
+ expect(processReferral).toHaveBeenCalledWith(mockClient, {
89
+ walletStatus: mockWalletStatus,
90
+ frakContext: mockContext,
91
+ modalConfig: mockModalConfig,
92
+ productId: mockProductId,
93
+ options: mockOptions,
94
+ });
95
+ });
96
+
97
+ test("should return result from processReferral", async () => {
98
+ const { FrakContextManager } = await import("../../utils");
99
+ const { watchWalletStatus } = await import("../index");
100
+ const { processReferral } = await import("./processReferral");
101
+
102
+ vi.mocked(FrakContextManager.parse).mockReturnValue({} as any);
103
+ vi.mocked(watchWalletStatus).mockResolvedValue(null as any);
104
+ vi.mocked(processReferral).mockResolvedValue("success");
105
+
106
+ const result = await referralInteraction(mockClient);
107
+
108
+ expect(result).toBe("success");
109
+ });
110
+
111
+ test("should return undefined on error", async () => {
112
+ const { FrakContextManager } = await import("../../utils");
113
+ const { watchWalletStatus } = await import("../index");
114
+ const { processReferral } = await import("./processReferral");
115
+
116
+ vi.mocked(FrakContextManager.parse).mockReturnValue({} as any);
117
+ vi.mocked(watchWalletStatus).mockResolvedValue(null as any);
118
+ vi.mocked(processReferral).mockRejectedValue(new Error("Test error"));
119
+
120
+ const consoleSpy = vi
121
+ .spyOn(console, "warn")
122
+ .mockImplementation(() => {});
123
+
124
+ const result = await referralInteraction(mockClient);
125
+
126
+ expect(result).toBeUndefined();
127
+ expect(consoleSpy).toHaveBeenCalled();
128
+
129
+ consoleSpy.mockRestore();
130
+ });
131
+
132
+ test("should work with empty options", async () => {
133
+ const { FrakContextManager } = await import("../../utils");
134
+ const { watchWalletStatus } = await import("../index");
135
+ const { processReferral } = await import("./processReferral");
136
+
137
+ vi.mocked(FrakContextManager.parse).mockReturnValue({} as any);
138
+ vi.mocked(watchWalletStatus).mockResolvedValue(null as any);
139
+ vi.mocked(processReferral).mockResolvedValue("no-referrer");
140
+
141
+ const result = await referralInteraction(mockClient, {});
142
+
143
+ expect(result).toBe("no-referrer");
144
+ expect(processReferral).toHaveBeenCalledWith(
145
+ mockClient,
146
+ expect.objectContaining({
147
+ modalConfig: undefined,
148
+ productId: undefined,
149
+ options: undefined,
150
+ })
151
+ );
152
+ });
153
+ });
@@ -0,0 +1,253 @@
1
+ import { vi } from "vitest";
2
+
3
+ vi.mock("../displayModal", () => ({
4
+ displayModal: vi.fn(),
5
+ }));
6
+
7
+ import type { Address } from "viem";
8
+ import { describe, expect, it } from "../../../tests/vitest-fixtures";
9
+ import type { FrakClient } from "../../types";
10
+ import { modalBuilder } from "./modalBuilder";
11
+
12
+ describe("modalBuilder", () => {
13
+ const mockClient = {
14
+ config: {
15
+ metadata: {
16
+ name: "Test App",
17
+ },
18
+ },
19
+ request: vi.fn(),
20
+ } as unknown as FrakClient;
21
+
22
+ describe("initialization", () => {
23
+ it("should create builder with base params", () => {
24
+ const builder = modalBuilder(mockClient, {});
25
+
26
+ expect(builder.params).toBeDefined();
27
+ expect(builder.params.steps.login).toEqual({});
28
+ expect(builder.params.steps.openSession).toEqual({});
29
+ });
30
+
31
+ it("should create builder with custom login params", () => {
32
+ const builder = modalBuilder(mockClient, {
33
+ login: { allowSso: true },
34
+ });
35
+
36
+ expect(builder.params.steps.login).toEqual({ allowSso: true });
37
+ });
38
+
39
+ it("should create builder with custom openSession params", () => {
40
+ const builder = modalBuilder(mockClient, {
41
+ openSession: {},
42
+ });
43
+
44
+ expect(builder.params.steps.openSession).toEqual({});
45
+ });
46
+
47
+ it("should create builder with metadata", () => {
48
+ const builder = modalBuilder(mockClient, {
49
+ metadata: {
50
+ header: { title: "Test Title" },
51
+ },
52
+ });
53
+
54
+ expect(builder.params.metadata).toEqual({
55
+ header: { title: "Test Title" },
56
+ });
57
+ });
58
+ });
59
+
60
+ describe("sendTx step", () => {
61
+ it("should add sendTransaction step", () => {
62
+ const builder = modalBuilder(mockClient, {});
63
+ const withTx = builder.sendTx({
64
+ tx: {
65
+ to: "0x1234567890123456789012345678901234567890" as Address,
66
+ data: "0xdata" as `0x${string}`,
67
+ },
68
+ });
69
+
70
+ expect(withTx.params.steps.sendTransaction).toEqual({
71
+ tx: {
72
+ to: "0x1234567890123456789012345678901234567890",
73
+ data: "0xdata",
74
+ },
75
+ });
76
+ });
77
+
78
+ it("should preserve previous steps when adding sendTx", () => {
79
+ const builder = modalBuilder(mockClient, {
80
+ login: { allowSso: true },
81
+ });
82
+ const withTx = builder.sendTx({
83
+ tx: {
84
+ to: "0x1234567890123456789012345678901234567890" as Address,
85
+ },
86
+ });
87
+
88
+ expect(withTx.params.steps.login).toEqual({ allowSso: true });
89
+ expect(withTx.params.steps.sendTransaction).toBeDefined();
90
+ });
91
+ });
92
+
93
+ describe("reward step", () => {
94
+ it("should add reward final step", () => {
95
+ const builder = modalBuilder(mockClient, {});
96
+ const withReward = builder.reward();
97
+
98
+ expect(withReward.params.steps.final).toEqual({
99
+ action: { key: "reward" },
100
+ });
101
+ });
102
+
103
+ it("should add reward step with options", () => {
104
+ const builder = modalBuilder(mockClient, {});
105
+ const withReward = builder.reward({
106
+ autoSkip: true,
107
+ });
108
+
109
+ expect(withReward.params.steps.final).toEqual({
110
+ autoSkip: true,
111
+ action: { key: "reward" },
112
+ });
113
+ });
114
+ });
115
+
116
+ describe("sharing step", () => {
117
+ it("should add sharing final step", () => {
118
+ const builder = modalBuilder(mockClient, {});
119
+ const withSharing = builder.sharing({
120
+ popupTitle: "Share!",
121
+ text: "Check this out",
122
+ link: "https://example.com",
123
+ });
124
+
125
+ expect(withSharing.params.steps.final).toEqual({
126
+ action: {
127
+ key: "sharing",
128
+ options: {
129
+ popupTitle: "Share!",
130
+ text: "Check this out",
131
+ link: "https://example.com",
132
+ },
133
+ },
134
+ });
135
+ });
136
+
137
+ it("should add sharing step with additional options", () => {
138
+ const builder = modalBuilder(mockClient, {});
139
+ const withSharing = builder.sharing(
140
+ { text: "Share text", link: "https://example.com" },
141
+ { autoSkip: false }
142
+ );
143
+
144
+ expect(withSharing.params.steps.final).toEqual({
145
+ autoSkip: false,
146
+ action: {
147
+ key: "sharing",
148
+ options: {
149
+ text: "Share text",
150
+ link: "https://example.com",
151
+ },
152
+ },
153
+ });
154
+ });
155
+ });
156
+
157
+ describe("chaining", () => {
158
+ it("should chain sendTx and reward", () => {
159
+ const builder = modalBuilder(mockClient, {});
160
+ const chained = builder
161
+ .sendTx({
162
+ tx: {
163
+ to: "0x1234567890123456789012345678901234567890" as Address,
164
+ },
165
+ })
166
+ .reward();
167
+
168
+ expect(chained.params.steps.sendTransaction).toBeDefined();
169
+ expect(chained.params.steps.final).toEqual({
170
+ action: { key: "reward" },
171
+ });
172
+ });
173
+
174
+ it("should chain sendTx and sharing", () => {
175
+ const builder = modalBuilder(mockClient, {});
176
+ const chained = builder
177
+ .sendTx({
178
+ tx: {
179
+ to: "0x1234567890123456789012345678901234567890" as Address,
180
+ },
181
+ })
182
+ .sharing({ link: "https://example.com" });
183
+
184
+ expect(chained.params.steps.sendTransaction).toBeDefined();
185
+ expect(chained.params.steps.final?.action).toEqual({
186
+ key: "sharing",
187
+ options: { link: "https://example.com" },
188
+ });
189
+ });
190
+ });
191
+
192
+ describe("display", () => {
193
+ it("should call displayModal when display is invoked", async () => {
194
+ const { displayModal } = await import("../displayModal");
195
+
196
+ const mockResponse = {
197
+ login: {
198
+ wallet: "0x1234567890123456789012345678901234567890" as Address,
199
+ },
200
+ openSession: {
201
+ startTimestamp: 1234567890,
202
+ endTimestamp: 1234567900,
203
+ },
204
+ };
205
+ vi.mocked(displayModal).mockResolvedValue(mockResponse as any);
206
+
207
+ const builder = modalBuilder(mockClient, {});
208
+ await builder.display();
209
+
210
+ expect(displayModal).toHaveBeenCalledWith(
211
+ mockClient,
212
+ builder.params
213
+ );
214
+ });
215
+
216
+ it("should apply metadata override when provided", async () => {
217
+ const { displayModal } = await import("../displayModal");
218
+
219
+ vi.mocked(displayModal).mockResolvedValue({} as any);
220
+
221
+ const builder = modalBuilder(mockClient, {
222
+ metadata: { header: { title: "Original" } },
223
+ });
224
+ await builder.display(() => ({
225
+ header: { title: "Overridden" },
226
+ }));
227
+
228
+ expect(displayModal).toHaveBeenCalledWith(
229
+ mockClient,
230
+ expect.objectContaining({
231
+ metadata: { header: { title: "Overridden" } },
232
+ })
233
+ );
234
+ });
235
+
236
+ it("should return displayModal result", async () => {
237
+ const { displayModal } = await import("../displayModal");
238
+
239
+ const mockResponse = {
240
+ login: {
241
+ wallet: "0x1234567890123456789012345678901234567890" as Address,
242
+ },
243
+ openSession: {},
244
+ };
245
+ vi.mocked(displayModal).mockResolvedValue(mockResponse as any);
246
+
247
+ const builder = modalBuilder(mockClient, {});
248
+ const result = await builder.display();
249
+
250
+ expect(result).toEqual(mockResponse);
251
+ });
252
+ });
253
+ });
@@ -0,0 +1,164 @@
1
+ import { vi } from "vitest";
2
+
3
+ vi.mock("../displayModal", () => ({
4
+ displayModal: vi.fn(),
5
+ }));
6
+
7
+ import type { Address, Hex } from "viem";
8
+ import { describe, expect, it } from "../../../tests/vitest-fixtures";
9
+ import type { FrakClient } from "../../types";
10
+ import { sendTransaction } from "./sendTransaction";
11
+
12
+ describe("sendTransaction", () => {
13
+ const mockClient = {
14
+ config: {
15
+ metadata: {
16
+ name: "Test App",
17
+ },
18
+ },
19
+ request: vi.fn(),
20
+ } as unknown as FrakClient;
21
+
22
+ describe("basic usage", () => {
23
+ it("should call displayModal with correct params for single tx", async () => {
24
+ const { displayModal } = await import("../displayModal");
25
+
26
+ const mockTxHash =
27
+ "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" as Hex;
28
+ vi.mocked(displayModal).mockResolvedValue({
29
+ login: {
30
+ wallet: "0x1234567890123456789012345678901234567890" as Address,
31
+ },
32
+ sendTransaction: { hash: mockTxHash },
33
+ } as any);
34
+
35
+ const txParams = {
36
+ to: "0x1234567890123456789012345678901234567890" as Address,
37
+ data: "0xdeadbeef" as Hex,
38
+ };
39
+
40
+ await sendTransaction(mockClient, { tx: txParams });
41
+
42
+ expect(displayModal).toHaveBeenCalledWith(mockClient, {
43
+ metadata: undefined,
44
+ steps: {
45
+ login: {},
46
+ sendTransaction: { tx: txParams },
47
+ },
48
+ });
49
+ });
50
+
51
+ it("should return transaction hash", async () => {
52
+ const { displayModal } = await import("../displayModal");
53
+
54
+ const mockTxHash =
55
+ "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" as Hex;
56
+ vi.mocked(displayModal).mockResolvedValue({
57
+ login: {
58
+ wallet: "0x1234567890123456789012345678901234567890" as Address,
59
+ },
60
+ sendTransaction: { hash: mockTxHash },
61
+ } as any);
62
+
63
+ const result = await sendTransaction(mockClient, {
64
+ tx: {
65
+ to: "0x1234567890123456789012345678901234567890" as Address,
66
+ },
67
+ });
68
+
69
+ expect(result).toEqual({ hash: mockTxHash });
70
+ });
71
+ });
72
+
73
+ describe("batched transactions", () => {
74
+ it("should handle multiple transactions", async () => {
75
+ const { displayModal } = await import("../displayModal");
76
+
77
+ const mockTxHash =
78
+ "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" as Hex;
79
+ vi.mocked(displayModal).mockResolvedValue({
80
+ login: {},
81
+ sendTransaction: { hash: mockTxHash },
82
+ } as any);
83
+
84
+ const txParams = [
85
+ {
86
+ to: "0x1234567890123456789012345678901234567890" as Address,
87
+ data: "0xdata1" as Hex,
88
+ },
89
+ {
90
+ to: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" as Address,
91
+ data: "0xdata2" as Hex,
92
+ },
93
+ ];
94
+
95
+ await sendTransaction(mockClient, { tx: txParams });
96
+
97
+ expect(displayModal).toHaveBeenCalledWith(mockClient, {
98
+ metadata: undefined,
99
+ steps: {
100
+ login: {},
101
+ sendTransaction: { tx: txParams },
102
+ },
103
+ });
104
+ });
105
+ });
106
+
107
+ describe("with metadata", () => {
108
+ it("should pass metadata to displayModal", async () => {
109
+ const { displayModal } = await import("../displayModal");
110
+
111
+ vi.mocked(displayModal).mockResolvedValue({
112
+ login: {},
113
+ sendTransaction: { hash: "0x123" as Hex },
114
+ } as any);
115
+
116
+ const metadata = {
117
+ header: {
118
+ title: "Send ETH",
119
+ icon: "https://example.com/icon.png",
120
+ },
121
+ context: "Send 100 wei to recipient",
122
+ };
123
+
124
+ await sendTransaction(mockClient, {
125
+ tx: {
126
+ to: "0x1234567890123456789012345678901234567890" as Address,
127
+ value: "0x64" as Hex,
128
+ },
129
+ metadata,
130
+ });
131
+
132
+ expect(displayModal).toHaveBeenCalledWith(mockClient, {
133
+ metadata,
134
+ steps: {
135
+ login: {},
136
+ sendTransaction: {
137
+ tx: {
138
+ to: "0x1234567890123456789012345678901234567890",
139
+ value: "0x64",
140
+ },
141
+ },
142
+ },
143
+ });
144
+ });
145
+ });
146
+
147
+ describe("error handling", () => {
148
+ it("should propagate errors from displayModal", async () => {
149
+ const { displayModal } = await import("../displayModal");
150
+
151
+ vi.mocked(displayModal).mockRejectedValue(
152
+ new Error("Transaction rejected")
153
+ );
154
+
155
+ await expect(
156
+ sendTransaction(mockClient, {
157
+ tx: {
158
+ to: "0x1234567890123456789012345678901234567890" as Address,
159
+ },
160
+ })
161
+ ).rejects.toThrow("Transaction rejected");
162
+ });
163
+ });
164
+ });
@@ -0,0 +1,290 @@
1
+ import { beforeEach, vi } from "vitest";
2
+
3
+ vi.mock("../displayModal", () => ({
4
+ displayModal: vi.fn(),
5
+ }));
6
+
7
+ vi.mock("viem/siwe", () => ({
8
+ generateSiweNonce: vi.fn(() => "mock-nonce-123456"),
9
+ }));
10
+
11
+ import type { Address, Hex } from "viem";
12
+ import { describe, expect, it } from "../../../tests/vitest-fixtures";
13
+ import type { FrakClient } from "../../types";
14
+ import { siweAuthenticate } from "./siweAuthenticate";
15
+
16
+ describe("siweAuthenticate", () => {
17
+ const mockClient = {
18
+ config: {
19
+ domain: "example.com",
20
+ metadata: {
21
+ name: "Test App",
22
+ },
23
+ },
24
+ request: vi.fn(),
25
+ } as unknown as FrakClient;
26
+
27
+ const mockClientWithoutDomain = {
28
+ config: {
29
+ metadata: {
30
+ name: "Test App",
31
+ },
32
+ },
33
+ request: vi.fn(),
34
+ } as unknown as FrakClient;
35
+
36
+ beforeEach(() => {
37
+ vi.stubGlobal("window", {
38
+ location: {
39
+ host: "window.example.com",
40
+ },
41
+ });
42
+ });
43
+
44
+ describe("basic usage", () => {
45
+ it("should call displayModal with SIWE params", async () => {
46
+ const { displayModal } = await import("../displayModal");
47
+
48
+ const mockResponse = {
49
+ login: {
50
+ wallet: "0x1234567890123456789012345678901234567890" as Address,
51
+ },
52
+ siweAuthenticate: {
53
+ message: "Sign in to Test App",
54
+ signature: "0xsignature" as Hex,
55
+ },
56
+ };
57
+ vi.mocked(displayModal).mockResolvedValue(mockResponse as any);
58
+
59
+ await siweAuthenticate(mockClient, {});
60
+
61
+ expect(displayModal).toHaveBeenCalledWith(mockClient, {
62
+ metadata: undefined,
63
+ steps: {
64
+ login: {},
65
+ siweAuthenticate: {
66
+ siwe: expect.objectContaining({
67
+ domain: "example.com",
68
+ nonce: "mock-nonce-123456",
69
+ uri: "https://example.com",
70
+ version: "1",
71
+ }),
72
+ },
73
+ },
74
+ });
75
+ });
76
+
77
+ it("should return SIWE authentication result", async () => {
78
+ const { displayModal } = await import("../displayModal");
79
+
80
+ const mockSiweResult = {
81
+ message: "I confirm that I want to use my Frak wallet",
82
+ signature:
83
+ "0xsig1234567890123456789012345678901234567890123456789012345678901234" as Hex,
84
+ };
85
+ vi.mocked(displayModal).mockResolvedValue({
86
+ login: {},
87
+ siweAuthenticate: mockSiweResult,
88
+ } as any);
89
+
90
+ const result = await siweAuthenticate(mockClient, {});
91
+
92
+ expect(result).toEqual(mockSiweResult);
93
+ });
94
+ });
95
+
96
+ describe("SIWE parameter handling", () => {
97
+ it("should use default statement when not provided", async () => {
98
+ const { displayModal } = await import("../displayModal");
99
+
100
+ vi.mocked(displayModal).mockResolvedValue({
101
+ login: {},
102
+ siweAuthenticate: { message: "", signature: "0x" as Hex },
103
+ } as any);
104
+
105
+ await siweAuthenticate(mockClient, {});
106
+
107
+ expect(displayModal).toHaveBeenCalledWith(
108
+ mockClient,
109
+ expect.objectContaining({
110
+ steps: expect.objectContaining({
111
+ siweAuthenticate: expect.objectContaining({
112
+ siwe: expect.objectContaining({
113
+ statement:
114
+ "I confirm that I want to use my Frak wallet on: Test App",
115
+ }),
116
+ }),
117
+ }),
118
+ })
119
+ );
120
+ });
121
+
122
+ it("should use custom statement when provided", async () => {
123
+ const { displayModal } = await import("../displayModal");
124
+
125
+ vi.mocked(displayModal).mockResolvedValue({
126
+ login: {},
127
+ siweAuthenticate: { message: "", signature: "0x" as Hex },
128
+ } as any);
129
+
130
+ await siweAuthenticate(mockClient, {
131
+ siwe: { statement: "Custom sign in message" },
132
+ });
133
+
134
+ expect(displayModal).toHaveBeenCalledWith(
135
+ mockClient,
136
+ expect.objectContaining({
137
+ steps: expect.objectContaining({
138
+ siweAuthenticate: expect.objectContaining({
139
+ siwe: expect.objectContaining({
140
+ statement: "Custom sign in message",
141
+ }),
142
+ }),
143
+ }),
144
+ })
145
+ );
146
+ });
147
+
148
+ it("should use custom nonce when provided", async () => {
149
+ const { displayModal } = await import("../displayModal");
150
+
151
+ vi.mocked(displayModal).mockResolvedValue({
152
+ login: {},
153
+ siweAuthenticate: { message: "", signature: "0x" as Hex },
154
+ } as any);
155
+
156
+ await siweAuthenticate(mockClient, {
157
+ siwe: { nonce: "custom-nonce" },
158
+ });
159
+
160
+ expect(displayModal).toHaveBeenCalledWith(
161
+ mockClient,
162
+ expect.objectContaining({
163
+ steps: expect.objectContaining({
164
+ siweAuthenticate: expect.objectContaining({
165
+ siwe: expect.objectContaining({
166
+ nonce: "custom-nonce",
167
+ }),
168
+ }),
169
+ }),
170
+ })
171
+ );
172
+ });
173
+
174
+ it("should use window.location.host when domain not in config", async () => {
175
+ const { displayModal } = await import("../displayModal");
176
+
177
+ vi.mocked(displayModal).mockResolvedValue({
178
+ login: {},
179
+ siweAuthenticate: { message: "", signature: "0x" as Hex },
180
+ } as any);
181
+
182
+ await siweAuthenticate(mockClientWithoutDomain, {});
183
+
184
+ expect(displayModal).toHaveBeenCalledWith(
185
+ mockClientWithoutDomain,
186
+ expect.objectContaining({
187
+ steps: expect.objectContaining({
188
+ siweAuthenticate: expect.objectContaining({
189
+ siwe: expect.objectContaining({
190
+ domain: "window.example.com",
191
+ uri: "https://window.example.com",
192
+ }),
193
+ }),
194
+ }),
195
+ })
196
+ );
197
+ });
198
+
199
+ it("should use custom uri when provided", async () => {
200
+ const { displayModal } = await import("../displayModal");
201
+
202
+ vi.mocked(displayModal).mockResolvedValue({
203
+ login: {},
204
+ siweAuthenticate: { message: "", signature: "0x" as Hex },
205
+ } as any);
206
+
207
+ await siweAuthenticate(mockClient, {
208
+ siwe: { uri: "https://custom-uri.com" },
209
+ });
210
+
211
+ expect(displayModal).toHaveBeenCalledWith(
212
+ mockClient,
213
+ expect.objectContaining({
214
+ steps: expect.objectContaining({
215
+ siweAuthenticate: expect.objectContaining({
216
+ siwe: expect.objectContaining({
217
+ uri: "https://custom-uri.com",
218
+ }),
219
+ }),
220
+ }),
221
+ })
222
+ );
223
+ });
224
+
225
+ it("should use custom version when provided", async () => {
226
+ const { displayModal } = await import("../displayModal");
227
+
228
+ vi.mocked(displayModal).mockResolvedValue({
229
+ login: {},
230
+ siweAuthenticate: { message: "", signature: "0x" as Hex },
231
+ } as any);
232
+
233
+ await siweAuthenticate(mockClient, {
234
+ siwe: { version: "2" as any },
235
+ });
236
+
237
+ expect(displayModal).toHaveBeenCalledWith(
238
+ mockClient,
239
+ expect.objectContaining({
240
+ steps: expect.objectContaining({
241
+ siweAuthenticate: expect.objectContaining({
242
+ siwe: expect.objectContaining({
243
+ version: "2",
244
+ }),
245
+ }),
246
+ }),
247
+ })
248
+ );
249
+ });
250
+ });
251
+
252
+ describe("with metadata", () => {
253
+ it("should pass metadata to displayModal", async () => {
254
+ const { displayModal } = await import("../displayModal");
255
+
256
+ vi.mocked(displayModal).mockResolvedValue({
257
+ login: {},
258
+ siweAuthenticate: { message: "", signature: "0x" as Hex },
259
+ } as any);
260
+
261
+ const metadata = {
262
+ header: {
263
+ title: "Sign In",
264
+ },
265
+ context: "Sign in to access your account",
266
+ };
267
+
268
+ await siweAuthenticate(mockClient, { metadata });
269
+
270
+ expect(displayModal).toHaveBeenCalledWith(mockClient, {
271
+ metadata,
272
+ steps: expect.any(Object),
273
+ });
274
+ });
275
+ });
276
+
277
+ describe("error handling", () => {
278
+ it("should propagate errors from displayModal", async () => {
279
+ const { displayModal } = await import("../displayModal");
280
+
281
+ vi.mocked(displayModal).mockRejectedValue(
282
+ new Error("SIWE authentication rejected")
283
+ );
284
+
285
+ await expect(siweAuthenticate(mockClient, {})).rejects.toThrow(
286
+ "SIWE authentication rejected"
287
+ );
288
+ });
289
+ });
290
+ });