flowstack-sdk 0.2.1
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/LICENSE +21 -0
- package/README.md +2278 -0
- package/dist/api/index.d.mts +1 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1065 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/index.mjs +977 -0
- package/dist/api/index.mjs.map +1 -0
- package/dist/index-BkACA2ls.d.mts +1546 -0
- package/dist/index-BkACA2ls.d.ts +1546 -0
- package/dist/index.d.mts +2325 -0
- package/dist/index.d.ts +2325 -0
- package/dist/index.js +10817 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +10610 -0
- package/dist/index.mjs.map +1 -0
- package/dist/types-BmCPwbGH.d.mts +153 -0
- package/dist/types-BmCPwbGH.d.ts +153 -0
- package/dist/wallet/index.d.mts +195 -0
- package/dist/wallet/index.d.ts +195 -0
- package/dist/wallet/index.js +1205 -0
- package/dist/wallet/index.js.map +1 -0
- package/dist/wallet/index.mjs +1190 -0
- package/dist/wallet/index.mjs.map +1 -0
- package/package.json +110 -0
|
@@ -0,0 +1,1205 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var wagmi = require('wagmi');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
var reactAuth = require('@privy-io/react-auth');
|
|
7
|
+
var wagmi$1 = require('@privy-io/wagmi');
|
|
8
|
+
var reactQuery = require('@tanstack/react-query');
|
|
9
|
+
var chains = require('viem/chains');
|
|
10
|
+
|
|
11
|
+
// src/wallet/useWalletAuth.ts
|
|
12
|
+
|
|
13
|
+
// src/mock/fixtures.ts
|
|
14
|
+
({
|
|
15
|
+
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1e3).toISOString()
|
|
16
|
+
});
|
|
17
|
+
[
|
|
18
|
+
{
|
|
19
|
+
workspaceId: "ws_demo_1",
|
|
20
|
+
name: "Demo Workspace",
|
|
21
|
+
description: "A demo workspace for testing",
|
|
22
|
+
datasetCount: 3,
|
|
23
|
+
visualizationCount: 5,
|
|
24
|
+
modelCount: 1,
|
|
25
|
+
createdAt: "2024-01-15T10:00:00Z",
|
|
26
|
+
lastAccessed: (/* @__PURE__ */ new Date()).toISOString()
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
workspaceId: "ws_analytics",
|
|
30
|
+
name: "Analytics Project",
|
|
31
|
+
description: "Customer analytics and insights",
|
|
32
|
+
datasetCount: 7,
|
|
33
|
+
visualizationCount: 12,
|
|
34
|
+
modelCount: 2,
|
|
35
|
+
createdAt: "2024-02-20T14:30:00Z",
|
|
36
|
+
lastAccessed: new Date(Date.now() - 2 * 24 * 60 * 60 * 1e3).toISOString()
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
workspaceId: "ws_ml_project",
|
|
40
|
+
name: "ML Experiments",
|
|
41
|
+
description: "Machine learning model experiments",
|
|
42
|
+
datasetCount: 5,
|
|
43
|
+
visualizationCount: 8,
|
|
44
|
+
modelCount: 4,
|
|
45
|
+
createdAt: "2024-03-10T09:15:00Z",
|
|
46
|
+
lastAccessed: new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString()
|
|
47
|
+
}
|
|
48
|
+
];
|
|
49
|
+
[
|
|
50
|
+
{
|
|
51
|
+
id: "user_mock_123",
|
|
52
|
+
email: "demo@example.com",
|
|
53
|
+
name: "Demo User",
|
|
54
|
+
role: "owner",
|
|
55
|
+
status: "active",
|
|
56
|
+
tenantId: "t_mock_tenant",
|
|
57
|
+
createdAt: "2024-01-01T10:00:00Z",
|
|
58
|
+
lastLoginAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
59
|
+
lastActivityAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
60
|
+
metadata: { plan: "pro", company: "Demo Inc" }
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "user_admin_456",
|
|
64
|
+
email: "admin@example.com",
|
|
65
|
+
name: "Admin User",
|
|
66
|
+
role: "admin",
|
|
67
|
+
status: "active",
|
|
68
|
+
tenantId: "t_mock_tenant",
|
|
69
|
+
createdAt: "2024-01-15T14:30:00Z",
|
|
70
|
+
lastLoginAt: new Date(Date.now() - 2 * 60 * 60 * 1e3).toISOString(),
|
|
71
|
+
lastActivityAt: new Date(Date.now() - 30 * 60 * 1e3).toISOString()
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "user_member_789",
|
|
75
|
+
email: "member@example.com",
|
|
76
|
+
name: "Team Member",
|
|
77
|
+
role: "member",
|
|
78
|
+
status: "active",
|
|
79
|
+
tenantId: "t_mock_tenant",
|
|
80
|
+
createdAt: "2024-02-10T09:00:00Z",
|
|
81
|
+
lastLoginAt: new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString(),
|
|
82
|
+
lastActivityAt: new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString()
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "user_suspended_101",
|
|
86
|
+
email: "suspended@example.com",
|
|
87
|
+
name: "Suspended User",
|
|
88
|
+
role: "member",
|
|
89
|
+
status: "suspended",
|
|
90
|
+
tenantId: "t_mock_tenant",
|
|
91
|
+
createdAt: "2024-02-20T11:00:00Z",
|
|
92
|
+
lastLoginAt: "2024-03-01T08:00:00Z",
|
|
93
|
+
metadata: { suspendReason: "Policy violation" }
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: "user_pending_102",
|
|
97
|
+
email: "pending@example.com",
|
|
98
|
+
name: "New User",
|
|
99
|
+
role: "viewer",
|
|
100
|
+
status: "pending_verification",
|
|
101
|
+
tenantId: "t_mock_tenant",
|
|
102
|
+
createdAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1e3).toISOString()
|
|
103
|
+
}
|
|
104
|
+
];
|
|
105
|
+
[
|
|
106
|
+
{
|
|
107
|
+
id: "act_1",
|
|
108
|
+
userId: "user_mock_123",
|
|
109
|
+
activityType: "login",
|
|
110
|
+
description: "Logged in from Chrome on macOS",
|
|
111
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: "act_2",
|
|
115
|
+
userId: "user_mock_123",
|
|
116
|
+
activityType: "query_execute",
|
|
117
|
+
description: 'Executed query: "Show top customers"',
|
|
118
|
+
timestamp: new Date(Date.now() - 30 * 60 * 1e3).toISOString(),
|
|
119
|
+
resourceType: "workspace",
|
|
120
|
+
resourceId: "ws_demo_1"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: "act_3",
|
|
124
|
+
userId: "user_mock_123",
|
|
125
|
+
activityType: "dataset_upload",
|
|
126
|
+
description: "Uploaded dataset: sales_data.csv",
|
|
127
|
+
timestamp: new Date(Date.now() - 60 * 60 * 1e3).toISOString(),
|
|
128
|
+
resourceType: "dataset",
|
|
129
|
+
resourceId: "ds_sales"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: "act_4",
|
|
133
|
+
userId: "user_mock_123",
|
|
134
|
+
activityType: "workspace_create",
|
|
135
|
+
description: "Created workspace: Analytics Project",
|
|
136
|
+
timestamp: new Date(Date.now() - 2 * 60 * 60 * 1e3).toISOString(),
|
|
137
|
+
resourceType: "workspace",
|
|
138
|
+
resourceId: "ws_analytics"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: "act_5",
|
|
142
|
+
userId: "user_mock_123",
|
|
143
|
+
activityType: "logout",
|
|
144
|
+
description: "Logged out",
|
|
145
|
+
timestamp: new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString()
|
|
146
|
+
}
|
|
147
|
+
];
|
|
148
|
+
var FlowstackContext = react.createContext(null);
|
|
149
|
+
function useFlowstackOptional() {
|
|
150
|
+
return react.useContext(FlowstackContext);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/wallet/useWalletAuth.ts
|
|
154
|
+
function useWalletAuth() {
|
|
155
|
+
const wagmiConfig = wagmi.useConfig();
|
|
156
|
+
const flowstack = useFlowstackOptional();
|
|
157
|
+
const [isConnected, setIsConnected] = react.useState(false);
|
|
158
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
159
|
+
const [address, setAddress] = react.useState(null);
|
|
160
|
+
const [isEmbeddedWallet, setIsEmbeddedWallet] = react.useState(false);
|
|
161
|
+
const [authMethod, setAuthMethod] = react.useState(null);
|
|
162
|
+
const [error, setError] = react.useState(null);
|
|
163
|
+
const baseUrl = flowstack?.config?.baseUrl || "https://sage-api.flowstack.fun";
|
|
164
|
+
react.useEffect(() => {
|
|
165
|
+
if (flowstack?.credentials?.apiKey) {
|
|
166
|
+
try {
|
|
167
|
+
const parts = flowstack.credentials.apiKey.split(".");
|
|
168
|
+
if (parts.length < 2) return;
|
|
169
|
+
const payload = JSON.parse(atob(parts[1]));
|
|
170
|
+
if (payload.wallet_address) {
|
|
171
|
+
setAddress(payload.wallet_address);
|
|
172
|
+
setIsConnected(true);
|
|
173
|
+
}
|
|
174
|
+
} catch {
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}, [flowstack?.credentials]);
|
|
178
|
+
const loginWithPrivy = react.useCallback(async () => {
|
|
179
|
+
setIsLoading(true);
|
|
180
|
+
setError(null);
|
|
181
|
+
try {
|
|
182
|
+
const { usePrivy } = await import('@privy-io/react-auth');
|
|
183
|
+
throw new Error(
|
|
184
|
+
"loginWithPrivy must be called from a component wrapped in WalletProvider. Use the LoginButton component instead."
|
|
185
|
+
);
|
|
186
|
+
} catch (e) {
|
|
187
|
+
setError(e instanceof Error ? e.message : "Privy login failed");
|
|
188
|
+
} finally {
|
|
189
|
+
setIsLoading(false);
|
|
190
|
+
}
|
|
191
|
+
}, []);
|
|
192
|
+
const loginWithSIWE = react.useCallback(async () => {
|
|
193
|
+
setIsLoading(true);
|
|
194
|
+
setError(null);
|
|
195
|
+
try {
|
|
196
|
+
const { getAccount, signMessage } = await import('@wagmi/core');
|
|
197
|
+
const account = getAccount(wagmiConfig);
|
|
198
|
+
if (!account.address) {
|
|
199
|
+
throw new Error("No wallet connected. Connect MetaMask first.");
|
|
200
|
+
}
|
|
201
|
+
const nonceResp = await fetch(`${baseUrl}/auth/wallet/nonce`);
|
|
202
|
+
if (!nonceResp.ok) throw new Error("Failed to get SIWE nonce");
|
|
203
|
+
const { nonce } = await nonceResp.json();
|
|
204
|
+
const domain = typeof window !== "undefined" ? window.location.host : "localhost";
|
|
205
|
+
const origin = typeof window !== "undefined" ? window.location.origin : "http://localhost";
|
|
206
|
+
const chainId = account.chainId ?? wagmiConfig.chains?.[0]?.id ?? 42161;
|
|
207
|
+
const siweMessage = [
|
|
208
|
+
`${domain} wants you to sign in with your Ethereum account:`,
|
|
209
|
+
account.address,
|
|
210
|
+
"",
|
|
211
|
+
"Sign in to Casino with INFER tokens",
|
|
212
|
+
"",
|
|
213
|
+
`URI: ${origin}`,
|
|
214
|
+
`Version: 1`,
|
|
215
|
+
`Chain ID: ${chainId}`,
|
|
216
|
+
`Nonce: ${nonce}`,
|
|
217
|
+
`Issued At: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
218
|
+
].join("\n");
|
|
219
|
+
const signature = await signMessage(wagmiConfig, { message: siweMessage });
|
|
220
|
+
const verifyResp = await fetch(`${baseUrl}/auth/wallet/verify`, {
|
|
221
|
+
method: "POST",
|
|
222
|
+
headers: { "Content-Type": "application/json" },
|
|
223
|
+
body: JSON.stringify({ message: siweMessage, signature })
|
|
224
|
+
});
|
|
225
|
+
if (!verifyResp.ok) {
|
|
226
|
+
const err = await verifyResp.json();
|
|
227
|
+
throw new Error(err.detail || "SIWE verification failed");
|
|
228
|
+
}
|
|
229
|
+
const data = await verifyResp.json();
|
|
230
|
+
if (flowstack?.setCredentials) {
|
|
231
|
+
flowstack.setCredentials({
|
|
232
|
+
apiKey: data.session_token,
|
|
233
|
+
tenantId: data.tenant_id,
|
|
234
|
+
userId: data.user_id,
|
|
235
|
+
expiresAt: data.expires_at
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
setAddress(data.wallet_address);
|
|
239
|
+
setIsConnected(true);
|
|
240
|
+
setIsEmbeddedWallet(false);
|
|
241
|
+
setAuthMethod("siwe");
|
|
242
|
+
} catch (e) {
|
|
243
|
+
setError(e instanceof Error ? e.message : "SIWE login failed");
|
|
244
|
+
} finally {
|
|
245
|
+
setIsLoading(false);
|
|
246
|
+
}
|
|
247
|
+
}, [baseUrl, flowstack]);
|
|
248
|
+
const login = react.useCallback(async (method = "privy") => {
|
|
249
|
+
if (method === "privy") {
|
|
250
|
+
await loginWithPrivy();
|
|
251
|
+
} else {
|
|
252
|
+
await loginWithSIWE();
|
|
253
|
+
}
|
|
254
|
+
}, [loginWithPrivy, loginWithSIWE]);
|
|
255
|
+
const logout = react.useCallback(() => {
|
|
256
|
+
setIsConnected(false);
|
|
257
|
+
setAddress(null);
|
|
258
|
+
setAuthMethod(null);
|
|
259
|
+
setIsEmbeddedWallet(false);
|
|
260
|
+
setError(null);
|
|
261
|
+
flowstack?.logout?.();
|
|
262
|
+
}, [flowstack]);
|
|
263
|
+
return {
|
|
264
|
+
isConnected,
|
|
265
|
+
isLoading,
|
|
266
|
+
address,
|
|
267
|
+
isEmbeddedWallet,
|
|
268
|
+
authMethod,
|
|
269
|
+
error,
|
|
270
|
+
login,
|
|
271
|
+
logout
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
var POLL_INTERVAL_MS = 15e3;
|
|
275
|
+
function useInferBalance() {
|
|
276
|
+
const flowstack = useFlowstackOptional();
|
|
277
|
+
const [data, setData] = react.useState(null);
|
|
278
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
279
|
+
const [error, setError] = react.useState(null);
|
|
280
|
+
const intervalRef = react.useRef(null);
|
|
281
|
+
const baseUrl = flowstack?.config?.baseUrl || "https://sage-api.flowstack.fun";
|
|
282
|
+
const apiKey = flowstack?.credentials?.apiKey;
|
|
283
|
+
const tenantId = flowstack?.credentials?.tenantId;
|
|
284
|
+
const fetchBalance = react.useCallback(async () => {
|
|
285
|
+
if (!apiKey) return;
|
|
286
|
+
setIsLoading(true);
|
|
287
|
+
setError(null);
|
|
288
|
+
try {
|
|
289
|
+
const resp = await fetch(`${baseUrl}/billing/infer/balance`, {
|
|
290
|
+
headers: {
|
|
291
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
292
|
+
"X-Tenant-ID": tenantId || ""
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
if (resp.status === 400) {
|
|
296
|
+
setData(null);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (!resp.ok) {
|
|
300
|
+
throw new Error(`Balance check failed: ${resp.status}`);
|
|
301
|
+
}
|
|
302
|
+
const json = await resp.json();
|
|
303
|
+
setData({
|
|
304
|
+
balanceWei: json.balance_wei,
|
|
305
|
+
heldWei: json.held_wei,
|
|
306
|
+
availableWei: json.available_wei,
|
|
307
|
+
balance: json.infer_balance,
|
|
308
|
+
available: json.infer_available,
|
|
309
|
+
queryCredits: json.query_credits
|
|
310
|
+
});
|
|
311
|
+
} catch (e) {
|
|
312
|
+
setError(e instanceof Error ? e.message : "Failed to fetch balance");
|
|
313
|
+
} finally {
|
|
314
|
+
setIsLoading(false);
|
|
315
|
+
}
|
|
316
|
+
}, [apiKey, baseUrl, tenantId]);
|
|
317
|
+
react.useEffect(() => {
|
|
318
|
+
fetchBalance();
|
|
319
|
+
intervalRef.current = setInterval(fetchBalance, POLL_INTERVAL_MS);
|
|
320
|
+
return () => {
|
|
321
|
+
if (intervalRef.current) clearInterval(intervalRef.current);
|
|
322
|
+
};
|
|
323
|
+
}, [fetchBalance]);
|
|
324
|
+
return { data, isLoading, error, refetch: fetchBalance };
|
|
325
|
+
}
|
|
326
|
+
var POLL_INTERVAL_MS2 = 15e3;
|
|
327
|
+
function useAgentBalance() {
|
|
328
|
+
const flowstack = useFlowstackOptional();
|
|
329
|
+
const [data, setData] = react.useState(null);
|
|
330
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
331
|
+
const [error, setError] = react.useState(null);
|
|
332
|
+
const intervalRef = react.useRef(null);
|
|
333
|
+
const baseUrl = flowstack?.config?.baseUrl || "https://sage-api.flowstack.fun";
|
|
334
|
+
const apiKey = flowstack?.credentials?.apiKey;
|
|
335
|
+
const tenantId = flowstack?.credentials?.tenantId;
|
|
336
|
+
const fetchBalance = react.useCallback(async () => {
|
|
337
|
+
if (!apiKey) return;
|
|
338
|
+
setIsLoading(true);
|
|
339
|
+
setError(null);
|
|
340
|
+
try {
|
|
341
|
+
const resp = await fetch(`${baseUrl}/billing/agent/balance`, {
|
|
342
|
+
headers: {
|
|
343
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
344
|
+
"X-Tenant-ID": tenantId || ""
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
if (resp.status === 400) {
|
|
348
|
+
setData(null);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (!resp.ok) {
|
|
352
|
+
throw new Error(`AGENT balance check failed: ${resp.status}`);
|
|
353
|
+
}
|
|
354
|
+
const json = await resp.json();
|
|
355
|
+
setData({
|
|
356
|
+
balanceWei: json.balance_wei,
|
|
357
|
+
heldWei: json.held_wei,
|
|
358
|
+
availableWei: json.available_wei,
|
|
359
|
+
balance: json.agent_balance,
|
|
360
|
+
available: json.agent_available,
|
|
361
|
+
buildCredits: json.build_credits
|
|
362
|
+
});
|
|
363
|
+
} catch (e) {
|
|
364
|
+
setError(e instanceof Error ? e.message : "Failed to fetch AGENT balance");
|
|
365
|
+
} finally {
|
|
366
|
+
setIsLoading(false);
|
|
367
|
+
}
|
|
368
|
+
}, [apiKey, baseUrl, tenantId]);
|
|
369
|
+
react.useEffect(() => {
|
|
370
|
+
fetchBalance();
|
|
371
|
+
intervalRef.current = setInterval(fetchBalance, POLL_INTERVAL_MS2);
|
|
372
|
+
return () => {
|
|
373
|
+
if (intervalRef.current) clearInterval(intervalRef.current);
|
|
374
|
+
};
|
|
375
|
+
}, [fetchBalance]);
|
|
376
|
+
return { data, isLoading, error, refetch: fetchBalance };
|
|
377
|
+
}
|
|
378
|
+
var CONTRACTS = {
|
|
379
|
+
"arbitrum-sepolia": {
|
|
380
|
+
payment: "0x879101330bcB251CBB775559419cB6389346ee8c",
|
|
381
|
+
token: "0xD31f5765F92D7D3fF0463eeaa14C157d423aF9E1"
|
|
382
|
+
},
|
|
383
|
+
"arbitrum": {
|
|
384
|
+
payment: "0x879101330bcB251CBB775559419cB6389346ee8c",
|
|
385
|
+
token: "0xD31f5765F92D7D3fF0463eeaa14C157d423aF9E1"
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
var ERC20_APPROVE_ABI = [
|
|
389
|
+
{
|
|
390
|
+
name: "approve",
|
|
391
|
+
type: "function",
|
|
392
|
+
inputs: [
|
|
393
|
+
{ name: "spender", type: "address" },
|
|
394
|
+
{ name: "amount", type: "uint256" }
|
|
395
|
+
],
|
|
396
|
+
outputs: [{ name: "", type: "bool" }],
|
|
397
|
+
stateMutability: "nonpayable"
|
|
398
|
+
}
|
|
399
|
+
];
|
|
400
|
+
var DEPOSIT_ABI = [
|
|
401
|
+
{
|
|
402
|
+
name: "deposit",
|
|
403
|
+
type: "function",
|
|
404
|
+
inputs: [{ name: "amount", type: "uint256" }],
|
|
405
|
+
outputs: [],
|
|
406
|
+
stateMutability: "nonpayable"
|
|
407
|
+
}
|
|
408
|
+
];
|
|
409
|
+
function useDeposit(chain = "arbitrum-sepolia") {
|
|
410
|
+
const wagmiConfig = wagmi.useConfig();
|
|
411
|
+
const [isDepositing, setIsDepositing] = react.useState(false);
|
|
412
|
+
const [txHash, setTxHash] = react.useState(null);
|
|
413
|
+
const [error, setError] = react.useState(null);
|
|
414
|
+
const contracts = CONTRACTS[chain] || CONTRACTS["arbitrum-sepolia"];
|
|
415
|
+
const deposit = react.useCallback(async (inferAmount) => {
|
|
416
|
+
setIsDepositing(true);
|
|
417
|
+
setError(null);
|
|
418
|
+
setTxHash(null);
|
|
419
|
+
try {
|
|
420
|
+
const { writeContract, waitForTransactionReceipt } = await import('@wagmi/core');
|
|
421
|
+
const { parseUnits } = await import('viem');
|
|
422
|
+
const amountWei = parseUnits(inferAmount.toString(), 18);
|
|
423
|
+
const approveTx = await writeContract(wagmiConfig, {
|
|
424
|
+
address: contracts.token,
|
|
425
|
+
abi: ERC20_APPROVE_ABI,
|
|
426
|
+
functionName: "approve",
|
|
427
|
+
args: [contracts.payment, amountWei]
|
|
428
|
+
});
|
|
429
|
+
await waitForTransactionReceipt(wagmiConfig, { hash: approveTx });
|
|
430
|
+
const depositTx = await writeContract(wagmiConfig, {
|
|
431
|
+
address: contracts.payment,
|
|
432
|
+
abi: DEPOSIT_ABI,
|
|
433
|
+
functionName: "deposit",
|
|
434
|
+
args: [amountWei]
|
|
435
|
+
});
|
|
436
|
+
await waitForTransactionReceipt(wagmiConfig, { hash: depositTx });
|
|
437
|
+
setTxHash(depositTx);
|
|
438
|
+
return depositTx;
|
|
439
|
+
} catch (e) {
|
|
440
|
+
const msg = e instanceof Error ? e.message : "Deposit failed";
|
|
441
|
+
setError(msg);
|
|
442
|
+
return null;
|
|
443
|
+
} finally {
|
|
444
|
+
setIsDepositing(false);
|
|
445
|
+
}
|
|
446
|
+
}, [contracts]);
|
|
447
|
+
return { deposit, isDepositing, txHash, error };
|
|
448
|
+
}
|
|
449
|
+
function useBuyInfer(options = {}) {
|
|
450
|
+
const [isBuying, setIsBuying] = react.useState(false);
|
|
451
|
+
const [status, setStatus] = react.useState("idle");
|
|
452
|
+
const [error, setError] = react.useState(null);
|
|
453
|
+
const buy = react.useCallback((amount) => {
|
|
454
|
+
if (!options.apiKey) {
|
|
455
|
+
setError("On-ramp not configured. Set onRampConfig in FlowstackConfig.");
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (!options.walletAddress) {
|
|
459
|
+
setError("No wallet address. Sign up first.");
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
setIsBuying(true);
|
|
463
|
+
setStatus("pending");
|
|
464
|
+
setError(null);
|
|
465
|
+
const baseUrl = options.environment === "production" ? "https://buy.moonpay.com" : "https://buy-sandbox.moonpay.com";
|
|
466
|
+
const params = new URLSearchParams({
|
|
467
|
+
apiKey: options.apiKey,
|
|
468
|
+
currencyCode: "infer_arbitrum",
|
|
469
|
+
// MoonPay currency code for INFER on Arbitrum
|
|
470
|
+
walletAddress: options.walletAddress,
|
|
471
|
+
...amount ? { baseCurrencyAmount: amount.toString() } : {},
|
|
472
|
+
colorCode: "#d4a843",
|
|
473
|
+
// Casino amber
|
|
474
|
+
showWalletAddressForm: "false"
|
|
475
|
+
});
|
|
476
|
+
const widgetUrl = `${baseUrl}?${params.toString()}`;
|
|
477
|
+
const popup = window.open(
|
|
478
|
+
widgetUrl,
|
|
479
|
+
"moonpay",
|
|
480
|
+
"width=500,height=700,left=200,top=100"
|
|
481
|
+
);
|
|
482
|
+
if (!popup) {
|
|
483
|
+
window.location.href = widgetUrl;
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
const pollInterval = setInterval(() => {
|
|
487
|
+
if (popup.closed) {
|
|
488
|
+
clearInterval(pollInterval);
|
|
489
|
+
setIsBuying(false);
|
|
490
|
+
setStatus("completed");
|
|
491
|
+
}
|
|
492
|
+
}, 1e3);
|
|
493
|
+
}, [options]);
|
|
494
|
+
return { buy, isBuying, status, error };
|
|
495
|
+
}
|
|
496
|
+
var POLL_INTERVAL_MS3 = 6e4;
|
|
497
|
+
function useAppAccess(siteId, opts) {
|
|
498
|
+
const flowstack = useFlowstackOptional();
|
|
499
|
+
const [hasAccess, setHasAccess] = react.useState(true);
|
|
500
|
+
const [queriesUsed, setQueriesUsed] = react.useState(0);
|
|
501
|
+
const [queriesRemaining, setQueriesRemaining] = react.useState(null);
|
|
502
|
+
const [paymentMode, setPaymentMode] = react.useState(null);
|
|
503
|
+
const [unlockPriceCents, setUnlockPriceCents] = react.useState(null);
|
|
504
|
+
const [unlockPriceLabel, setUnlockPriceLabel] = react.useState(null);
|
|
505
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
506
|
+
const [error, setError] = react.useState(null);
|
|
507
|
+
const intervalRef = react.useRef(null);
|
|
508
|
+
const baseUrl = flowstack?.config?.baseUrl || "https://sage-api.flowstack.fun";
|
|
509
|
+
const apiKey = flowstack?.credentials?.apiKey;
|
|
510
|
+
const fetchStatus = react.useCallback(async () => {
|
|
511
|
+
if (!siteId || !apiKey) return;
|
|
512
|
+
setIsLoading(true);
|
|
513
|
+
setError(null);
|
|
514
|
+
try {
|
|
515
|
+
const params = new URLSearchParams({ site_id: siteId });
|
|
516
|
+
if (opts?.builderTenantId) params.set("builder_tenant_id", opts.builderTenantId);
|
|
517
|
+
const resp = await fetch(`${baseUrl}/billing/app-access/status?${params}`, {
|
|
518
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
519
|
+
});
|
|
520
|
+
if (!resp.ok) {
|
|
521
|
+
setHasAccess(true);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
const data = await resp.json();
|
|
525
|
+
setHasAccess(data.has_access ?? true);
|
|
526
|
+
setQueriesUsed(data.queries_used ?? 0);
|
|
527
|
+
setQueriesRemaining(data.queries_remaining ?? null);
|
|
528
|
+
setPaymentMode(data.payment_mode ?? null);
|
|
529
|
+
setUnlockPriceCents(data.unlock_price_cents ?? null);
|
|
530
|
+
setUnlockPriceLabel(data.unlock_price_label ?? null);
|
|
531
|
+
} catch (err) {
|
|
532
|
+
setError(err.message || "Failed to check access status");
|
|
533
|
+
setHasAccess(true);
|
|
534
|
+
} finally {
|
|
535
|
+
setIsLoading(false);
|
|
536
|
+
}
|
|
537
|
+
}, [siteId, apiKey, baseUrl, opts?.builderTenantId]);
|
|
538
|
+
react.useEffect(() => {
|
|
539
|
+
if (!siteId || !apiKey) return;
|
|
540
|
+
fetchStatus();
|
|
541
|
+
intervalRef.current = setInterval(fetchStatus, POLL_INTERVAL_MS3);
|
|
542
|
+
return () => {
|
|
543
|
+
if (intervalRef.current) clearInterval(intervalRef.current);
|
|
544
|
+
};
|
|
545
|
+
}, [fetchStatus, siteId, apiKey]);
|
|
546
|
+
const checkout = react.useCallback(
|
|
547
|
+
async (checkoutOpts) => {
|
|
548
|
+
if (!apiKey || !siteId) return;
|
|
549
|
+
try {
|
|
550
|
+
const successUrl = checkoutOpts?.successUrl || (typeof window !== "undefined" ? window.location.href : "");
|
|
551
|
+
const cancelUrl = checkoutOpts?.cancelUrl || (typeof window !== "undefined" ? window.location.href : "");
|
|
552
|
+
const resp = await fetch(`${baseUrl}/billing/app-access/checkout`, {
|
|
553
|
+
method: "POST",
|
|
554
|
+
headers: {
|
|
555
|
+
Authorization: `Bearer ${apiKey}`,
|
|
556
|
+
"Content-Type": "application/json"
|
|
557
|
+
},
|
|
558
|
+
body: JSON.stringify({
|
|
559
|
+
site_id: siteId,
|
|
560
|
+
success_url: successUrl,
|
|
561
|
+
cancel_url: cancelUrl
|
|
562
|
+
})
|
|
563
|
+
});
|
|
564
|
+
if (!resp.ok) {
|
|
565
|
+
const body = await resp.json().catch(() => ({}));
|
|
566
|
+
throw new Error(body.detail || `Checkout failed: ${resp.status}`);
|
|
567
|
+
}
|
|
568
|
+
const data = await resp.json();
|
|
569
|
+
if (data.url && typeof window !== "undefined") {
|
|
570
|
+
window.location.href = data.url;
|
|
571
|
+
}
|
|
572
|
+
} catch (err) {
|
|
573
|
+
setError(err.message || "Checkout failed");
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
[apiKey, siteId, baseUrl]
|
|
577
|
+
);
|
|
578
|
+
return {
|
|
579
|
+
hasAccess,
|
|
580
|
+
queriesUsed,
|
|
581
|
+
queriesRemaining,
|
|
582
|
+
paymentMode,
|
|
583
|
+
unlockPriceCents,
|
|
584
|
+
unlockPriceLabel,
|
|
585
|
+
isLoading,
|
|
586
|
+
error,
|
|
587
|
+
checkout,
|
|
588
|
+
refetch: fetchStatus
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
var WalletCtx = react.createContext({
|
|
592
|
+
privyReady: false,
|
|
593
|
+
baseUrl: "https://sage-api.flowstack.fun"
|
|
594
|
+
});
|
|
595
|
+
function WalletProvider({
|
|
596
|
+
children,
|
|
597
|
+
privyAppId,
|
|
598
|
+
chain = "arbitrum",
|
|
599
|
+
baseUrl = "https://sage-api.flowstack.fun",
|
|
600
|
+
walletConnectProjectId
|
|
601
|
+
}) {
|
|
602
|
+
const [queryClient] = react.useState(() => new reactQuery.QueryClient());
|
|
603
|
+
const selectedChain = chain === "arbitrum-sepolia" ? chains.arbitrumSepolia : chains.arbitrum;
|
|
604
|
+
const wagmiConfig = react.useMemo(() => {
|
|
605
|
+
const config = wagmi$1.createConfig({
|
|
606
|
+
chains: [selectedChain],
|
|
607
|
+
transports: { [selectedChain.id]: wagmi.http() },
|
|
608
|
+
// Explicit connectors so wagmi always has something to work with.
|
|
609
|
+
// Without these, useConnect().connectors is empty → connectAsync({ connector: undefined })
|
|
610
|
+
// → "undefined is not an object (evaluating 'n.uid')" crash.
|
|
611
|
+
connectors: [
|
|
612
|
+
wagmi.injected()
|
|
613
|
+
// MetaMask, Rabby, Brave, any browser extension wallet
|
|
614
|
+
]
|
|
615
|
+
});
|
|
616
|
+
if (typeof window !== "undefined") {
|
|
617
|
+
window.__wagmiConfig = config;
|
|
618
|
+
}
|
|
619
|
+
return config;
|
|
620
|
+
}, [selectedChain]);
|
|
621
|
+
if (!privyAppId) {
|
|
622
|
+
return /* @__PURE__ */ jsxRuntime.jsx(WalletCtx.Provider, { value: { privyReady: false, baseUrl }, children: /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: queryClient, children }) });
|
|
623
|
+
}
|
|
624
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
625
|
+
reactAuth.PrivyProvider,
|
|
626
|
+
{
|
|
627
|
+
appId: privyAppId,
|
|
628
|
+
config: {
|
|
629
|
+
// Embedded signer — the EOA keypair that controls the smart wallet.
|
|
630
|
+
// Must be created for all users so the smart wallet has a signer.
|
|
631
|
+
// NOTE: @privy-io/react-auth v3 nests createOnLogin per chain
|
|
632
|
+
// (embeddedWallets.ethereum.createOnLogin). The old v2 flat shape
|
|
633
|
+
// (embeddedWallets.createOnLogin) is silently ignored on v3, so NO
|
|
634
|
+
// wallet gets created on login — the user authenticates but has no
|
|
635
|
+
// wallet/address. Use the v3 shape.
|
|
636
|
+
embeddedWallets: {
|
|
637
|
+
ethereum: {
|
|
638
|
+
createOnLogin: "all-users"
|
|
639
|
+
},
|
|
640
|
+
showWalletUIs: false
|
|
641
|
+
},
|
|
642
|
+
// EVM smart wallet (ERC-4337) — Kernel/ZeroDev implementation.
|
|
643
|
+
// Must be enabled in the Privy dashboard (Smart wallets → Kernel → Arbitrum).
|
|
644
|
+
// The smart wallet address is what Privy returns as type='smart_wallet'
|
|
645
|
+
// in linked_accounts, and is the canonical address for the token economy.
|
|
646
|
+
// @ts-ignore — 'smart_wallets' may not be in older @privy-io/react-auth types
|
|
647
|
+
smart_wallets: {
|
|
648
|
+
type: "kernel"
|
|
649
|
+
},
|
|
650
|
+
loginMethods: ["email", "google", "apple", "twitter", "github", "linkedin", "spotify", "wallet"],
|
|
651
|
+
appearance: { theme: "dark" },
|
|
652
|
+
defaultChain: selectedChain,
|
|
653
|
+
...walletConnectProjectId ? { walletConnectCloudProjectId: walletConnectProjectId } : {}
|
|
654
|
+
},
|
|
655
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsxRuntime.jsx(wagmi$1.WagmiProvider, { config: wagmiConfig, children: /* @__PURE__ */ jsxRuntime.jsx(WalletCtx.Provider, { value: { privyReady: true, baseUrl }, children }) }) })
|
|
656
|
+
}
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
function LoginButton({
|
|
660
|
+
onSuccess,
|
|
661
|
+
onError,
|
|
662
|
+
showWalletOption = true,
|
|
663
|
+
className
|
|
664
|
+
}) {
|
|
665
|
+
const [mode, setMode] = react.useState("signup");
|
|
666
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
667
|
+
const handlePrivyLogin = react.useCallback(async () => {
|
|
668
|
+
setIsLoading(true);
|
|
669
|
+
try {
|
|
670
|
+
const { usePrivy } = await import('@privy-io/react-auth');
|
|
671
|
+
onError?.("Use this component inside WalletProvider");
|
|
672
|
+
} catch (e) {
|
|
673
|
+
onError?.(e instanceof Error ? e.message : "Login failed");
|
|
674
|
+
} finally {
|
|
675
|
+
setIsLoading(false);
|
|
676
|
+
}
|
|
677
|
+
}, [onSuccess, onError]);
|
|
678
|
+
const handleWalletConnect = react.useCallback(async () => {
|
|
679
|
+
setIsLoading(true);
|
|
680
|
+
try {
|
|
681
|
+
onError?.("Wallet connect requires WalletProvider context");
|
|
682
|
+
} catch (e) {
|
|
683
|
+
onError?.(e instanceof Error ? e.message : "Wallet connect failed");
|
|
684
|
+
} finally {
|
|
685
|
+
setIsLoading(false);
|
|
686
|
+
}
|
|
687
|
+
}, [onError]);
|
|
688
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, children: [
|
|
689
|
+
mode === "signup" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
690
|
+
"button",
|
|
691
|
+
{
|
|
692
|
+
onClick: handlePrivyLogin,
|
|
693
|
+
disabled: isLoading,
|
|
694
|
+
style: {
|
|
695
|
+
padding: "12px 24px",
|
|
696
|
+
borderRadius: "8px",
|
|
697
|
+
border: "none",
|
|
698
|
+
backgroundColor: "#d4a843",
|
|
699
|
+
color: "#0a0a0a",
|
|
700
|
+
fontWeight: 600,
|
|
701
|
+
cursor: isLoading ? "not-allowed" : "pointer",
|
|
702
|
+
opacity: isLoading ? 0.7 : 1,
|
|
703
|
+
width: "100%"
|
|
704
|
+
},
|
|
705
|
+
children: isLoading ? "Signing in..." : "Sign up with Google"
|
|
706
|
+
}
|
|
707
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
708
|
+
"button",
|
|
709
|
+
{
|
|
710
|
+
onClick: handleWalletConnect,
|
|
711
|
+
disabled: isLoading,
|
|
712
|
+
style: {
|
|
713
|
+
padding: "12px 24px",
|
|
714
|
+
borderRadius: "8px",
|
|
715
|
+
border: "1px solid #c4bdb3",
|
|
716
|
+
backgroundColor: "transparent",
|
|
717
|
+
color: "#c4bdb3",
|
|
718
|
+
fontWeight: 600,
|
|
719
|
+
cursor: isLoading ? "not-allowed" : "pointer",
|
|
720
|
+
opacity: isLoading ? 0.7 : 1,
|
|
721
|
+
width: "100%"
|
|
722
|
+
},
|
|
723
|
+
children: isLoading ? "Connecting..." : "Connect Wallet"
|
|
724
|
+
}
|
|
725
|
+
),
|
|
726
|
+
showWalletOption && /* @__PURE__ */ jsxRuntime.jsx(
|
|
727
|
+
"button",
|
|
728
|
+
{
|
|
729
|
+
onClick: () => setMode(mode === "signup" ? "wallet" : "signup"),
|
|
730
|
+
style: {
|
|
731
|
+
marginTop: "8px",
|
|
732
|
+
padding: "8px",
|
|
733
|
+
background: "none",
|
|
734
|
+
border: "none",
|
|
735
|
+
color: "#c4bdb3",
|
|
736
|
+
fontSize: "13px",
|
|
737
|
+
cursor: "pointer",
|
|
738
|
+
width: "100%",
|
|
739
|
+
textAlign: "center"
|
|
740
|
+
},
|
|
741
|
+
children: mode === "signup" ? "Already have INFER? Connect wallet" : "Sign up with email instead"
|
|
742
|
+
}
|
|
743
|
+
)
|
|
744
|
+
] });
|
|
745
|
+
}
|
|
746
|
+
function InferBalanceBadge({
|
|
747
|
+
lowBalanceThreshold = 5,
|
|
748
|
+
onBuyMore,
|
|
749
|
+
className
|
|
750
|
+
}) {
|
|
751
|
+
const { data, isLoading } = useInferBalance();
|
|
752
|
+
if (isLoading && !data) {
|
|
753
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className, style: { ...badgeStyle, opacity: 0.5 }, children: "..." });
|
|
754
|
+
}
|
|
755
|
+
if (!data) return null;
|
|
756
|
+
const isLow = data.queryCredits <= lowBalanceThreshold;
|
|
757
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
758
|
+
"span",
|
|
759
|
+
{
|
|
760
|
+
className,
|
|
761
|
+
style: {
|
|
762
|
+
...badgeStyle,
|
|
763
|
+
backgroundColor: isLow ? "#3a1c1c" : "#1a1a1a",
|
|
764
|
+
borderColor: isLow ? "#d44343" : "#333"
|
|
765
|
+
},
|
|
766
|
+
children: [
|
|
767
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: isLow ? "#d44343" : "#d4a843", fontWeight: 600 }, children: data.queryCredits }),
|
|
768
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#c4bdb3", marginLeft: "4px", fontSize: "12px" }, children: "queries" }),
|
|
769
|
+
isLow && onBuyMore && /* @__PURE__ */ jsxRuntime.jsx(
|
|
770
|
+
"button",
|
|
771
|
+
{
|
|
772
|
+
onClick: onBuyMore,
|
|
773
|
+
style: {
|
|
774
|
+
marginLeft: "8px",
|
|
775
|
+
padding: "2px 8px",
|
|
776
|
+
borderRadius: "4px",
|
|
777
|
+
border: "1px solid #d4a843",
|
|
778
|
+
backgroundColor: "transparent",
|
|
779
|
+
color: "#d4a843",
|
|
780
|
+
fontSize: "11px",
|
|
781
|
+
cursor: "pointer"
|
|
782
|
+
},
|
|
783
|
+
children: "Buy More"
|
|
784
|
+
}
|
|
785
|
+
)
|
|
786
|
+
]
|
|
787
|
+
}
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
var badgeStyle = {
|
|
791
|
+
display: "inline-flex",
|
|
792
|
+
alignItems: "center",
|
|
793
|
+
padding: "4px 12px",
|
|
794
|
+
borderRadius: "20px",
|
|
795
|
+
border: "1px solid #333",
|
|
796
|
+
fontSize: "13px",
|
|
797
|
+
fontFamily: "var(--font-body, Inter, sans-serif)"
|
|
798
|
+
};
|
|
799
|
+
function AgentBalanceBadge({
|
|
800
|
+
lowBalanceThreshold = 2,
|
|
801
|
+
onBuyMore,
|
|
802
|
+
className
|
|
803
|
+
}) {
|
|
804
|
+
const { data, isLoading } = useAgentBalance();
|
|
805
|
+
if (isLoading && !data) {
|
|
806
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className, style: { ...badgeStyle2, opacity: 0.5 }, children: "..." });
|
|
807
|
+
}
|
|
808
|
+
if (!data) return null;
|
|
809
|
+
const isLow = data.buildCredits <= lowBalanceThreshold;
|
|
810
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
811
|
+
"span",
|
|
812
|
+
{
|
|
813
|
+
className,
|
|
814
|
+
style: {
|
|
815
|
+
...badgeStyle2,
|
|
816
|
+
backgroundColor: isLow ? "#3a1c1c" : "#1a1a1a",
|
|
817
|
+
borderColor: isLow ? "#d44343" : "#333"
|
|
818
|
+
},
|
|
819
|
+
children: [
|
|
820
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: isLow ? "#d44343" : "#43d4a8", fontWeight: 600 }, children: data.buildCredits }),
|
|
821
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#c4bdb3", marginLeft: "4px", fontSize: "12px" }, children: "builds" }),
|
|
822
|
+
isLow && onBuyMore && /* @__PURE__ */ jsxRuntime.jsx(
|
|
823
|
+
"button",
|
|
824
|
+
{
|
|
825
|
+
onClick: onBuyMore,
|
|
826
|
+
style: {
|
|
827
|
+
marginLeft: "8px",
|
|
828
|
+
padding: "2px 8px",
|
|
829
|
+
borderRadius: "4px",
|
|
830
|
+
border: "1px solid #43d4a8",
|
|
831
|
+
backgroundColor: "transparent",
|
|
832
|
+
color: "#43d4a8",
|
|
833
|
+
fontSize: "11px",
|
|
834
|
+
cursor: "pointer"
|
|
835
|
+
},
|
|
836
|
+
children: "Buy More"
|
|
837
|
+
}
|
|
838
|
+
)
|
|
839
|
+
]
|
|
840
|
+
}
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
var badgeStyle2 = {
|
|
844
|
+
display: "inline-flex",
|
|
845
|
+
alignItems: "center",
|
|
846
|
+
padding: "4px 12px",
|
|
847
|
+
borderRadius: "20px",
|
|
848
|
+
border: "1px solid #333",
|
|
849
|
+
fontSize: "13px",
|
|
850
|
+
fontFamily: "var(--font-body, Inter, sans-serif)"
|
|
851
|
+
};
|
|
852
|
+
var DEFAULT_OIF_BASE = "https://openinferencefoundation.org";
|
|
853
|
+
function NeedsAgent({
|
|
854
|
+
amountNeeded,
|
|
855
|
+
onProceed,
|
|
856
|
+
returnUrl,
|
|
857
|
+
children,
|
|
858
|
+
oifBaseUrl = DEFAULT_OIF_BASE
|
|
859
|
+
}) {
|
|
860
|
+
const { data, isLoading } = useAgentBalance();
|
|
861
|
+
const available = data?.available ?? 0;
|
|
862
|
+
const hasSufficient = available >= amountNeeded;
|
|
863
|
+
const handleGetAgent = () => {
|
|
864
|
+
const base = oifBaseUrl.replace(/\/$/, "");
|
|
865
|
+
const returnTo = returnUrl ?? (typeof window !== "undefined" ? window.location.href : "");
|
|
866
|
+
const url = `${base}/buy?returnTo=${encodeURIComponent(returnTo)}&need=${amountNeeded}`;
|
|
867
|
+
if (typeof window !== "undefined") {
|
|
868
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
if (isLoading) {
|
|
872
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { opacity: 0.6, pointerEvents: "none" }, children });
|
|
873
|
+
}
|
|
874
|
+
if (hasSufficient) {
|
|
875
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { onClick: onProceed, style: { cursor: "pointer" }, children });
|
|
876
|
+
}
|
|
877
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
878
|
+
children && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { opacity: 0.5, pointerEvents: "none" }, children }),
|
|
879
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-start", gap: 6 }, children: [
|
|
880
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { style: { margin: 0, fontSize: "0.85em", color: "#888" }, children: [
|
|
881
|
+
"You need ",
|
|
882
|
+
amountNeeded,
|
|
883
|
+
" AGENT to use this feature.",
|
|
884
|
+
data !== null && ` You have ${available.toFixed(2)}.`
|
|
885
|
+
] }),
|
|
886
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
887
|
+
"button",
|
|
888
|
+
{
|
|
889
|
+
onClick: handleGetAgent,
|
|
890
|
+
style: {
|
|
891
|
+
background: "#6366f1",
|
|
892
|
+
color: "#fff",
|
|
893
|
+
border: "none",
|
|
894
|
+
borderRadius: 6,
|
|
895
|
+
padding: "6px 14px",
|
|
896
|
+
fontSize: "0.875em",
|
|
897
|
+
fontWeight: 600,
|
|
898
|
+
cursor: "pointer"
|
|
899
|
+
},
|
|
900
|
+
children: "Get AGENT \u2192"
|
|
901
|
+
}
|
|
902
|
+
)
|
|
903
|
+
] })
|
|
904
|
+
] });
|
|
905
|
+
}
|
|
906
|
+
function PaymentRequired({
|
|
907
|
+
queryCredits = 0,
|
|
908
|
+
inferPerQuery = 50,
|
|
909
|
+
onBuy,
|
|
910
|
+
onDismiss,
|
|
911
|
+
className
|
|
912
|
+
}) {
|
|
913
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: containerStyle, children: [
|
|
914
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: "24px", marginBottom: "8px" }, children: "0 queries remaining" }),
|
|
915
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { style: { color: "#c4bdb3", marginBottom: "16px", lineHeight: 1.5 }, children: [
|
|
916
|
+
"Each query costs ",
|
|
917
|
+
inferPerQuery,
|
|
918
|
+
" INFER. Buy more to continue using Casino."
|
|
919
|
+
] }),
|
|
920
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onBuy, style: buyButtonStyle, children: "Buy INFER" }),
|
|
921
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: "#666", fontSize: "12px", marginTop: "12px" }, children: "Pay with credit card. No crypto knowledge needed." }),
|
|
922
|
+
onDismiss && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onDismiss, style: dismissStyle, children: "Maybe later" })
|
|
923
|
+
] });
|
|
924
|
+
}
|
|
925
|
+
var containerStyle = {
|
|
926
|
+
textAlign: "center",
|
|
927
|
+
padding: "32px 24px",
|
|
928
|
+
backgroundColor: "#111111",
|
|
929
|
+
borderRadius: "12px",
|
|
930
|
+
border: "1px solid #333",
|
|
931
|
+
maxWidth: "400px",
|
|
932
|
+
margin: "0 auto",
|
|
933
|
+
fontFamily: "var(--font-body, Inter, sans-serif)",
|
|
934
|
+
color: "#f5f0e8"
|
|
935
|
+
};
|
|
936
|
+
var buyButtonStyle = {
|
|
937
|
+
padding: "14px 32px",
|
|
938
|
+
borderRadius: "8px",
|
|
939
|
+
border: "none",
|
|
940
|
+
backgroundColor: "#d4a843",
|
|
941
|
+
color: "#0a0a0a",
|
|
942
|
+
fontWeight: 700,
|
|
943
|
+
fontSize: "16px",
|
|
944
|
+
cursor: "pointer",
|
|
945
|
+
width: "100%"
|
|
946
|
+
};
|
|
947
|
+
var dismissStyle = {
|
|
948
|
+
marginTop: "8px",
|
|
949
|
+
padding: "8px",
|
|
950
|
+
background: "none",
|
|
951
|
+
border: "none",
|
|
952
|
+
color: "#666",
|
|
953
|
+
fontSize: "13px",
|
|
954
|
+
cursor: "pointer"
|
|
955
|
+
};
|
|
956
|
+
var TIERS = [
|
|
957
|
+
{ queries: 100, infer: 5e3, price: 5 },
|
|
958
|
+
{ queries: 500, infer: 25e3, price: 25 },
|
|
959
|
+
{ queries: 2e3, infer: 1e5, price: 95 }
|
|
960
|
+
];
|
|
961
|
+
function BuyInferModal({
|
|
962
|
+
isOpen,
|
|
963
|
+
onClose,
|
|
964
|
+
apiKey,
|
|
965
|
+
environment = "sandbox",
|
|
966
|
+
walletAddress,
|
|
967
|
+
inferPerQuery = 50,
|
|
968
|
+
className
|
|
969
|
+
}) {
|
|
970
|
+
const { buy, isBuying, status } = useBuyInfer({
|
|
971
|
+
apiKey,
|
|
972
|
+
environment,
|
|
973
|
+
walletAddress
|
|
974
|
+
});
|
|
975
|
+
const [selectedTier, setSelectedTier] = react.useState(1);
|
|
976
|
+
if (!isOpen) return null;
|
|
977
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: overlayStyle, onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
978
|
+
"div",
|
|
979
|
+
{
|
|
980
|
+
className,
|
|
981
|
+
style: modalStyle,
|
|
982
|
+
onClick: (e) => e.stopPropagation(),
|
|
983
|
+
children: [
|
|
984
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "24px" }, children: [
|
|
985
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: 0, fontSize: "20px", color: "#f5f0e8" }, children: "Buy Query Credits" }),
|
|
986
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onClose, style: closeStyle, children: "\xD7" })
|
|
987
|
+
] }),
|
|
988
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", marginBottom: "24px" }, children: TIERS.map((tier, i) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
989
|
+
"button",
|
|
990
|
+
{
|
|
991
|
+
onClick: () => setSelectedTier(i),
|
|
992
|
+
style: {
|
|
993
|
+
...tierStyle,
|
|
994
|
+
borderColor: selectedTier === i ? "#d4a843" : "#333",
|
|
995
|
+
backgroundColor: selectedTier === i ? "#1a1a0a" : "#1a1a1a"
|
|
996
|
+
},
|
|
997
|
+
children: [
|
|
998
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontWeight: 600, fontSize: "16px", color: "#f5f0e8" }, children: [
|
|
999
|
+
tier.queries,
|
|
1000
|
+
" queries"
|
|
1001
|
+
] }),
|
|
1002
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { color: "#d4a843", fontWeight: 700, fontSize: "18px" }, children: [
|
|
1003
|
+
"$",
|
|
1004
|
+
tier.price
|
|
1005
|
+
] })
|
|
1006
|
+
]
|
|
1007
|
+
},
|
|
1008
|
+
tier.queries
|
|
1009
|
+
)) }),
|
|
1010
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1011
|
+
"button",
|
|
1012
|
+
{
|
|
1013
|
+
onClick: () => buy(TIERS[selectedTier].price),
|
|
1014
|
+
disabled: isBuying,
|
|
1015
|
+
style: {
|
|
1016
|
+
...buyStyle,
|
|
1017
|
+
opacity: isBuying ? 0.7 : 1,
|
|
1018
|
+
cursor: isBuying ? "not-allowed" : "pointer"
|
|
1019
|
+
},
|
|
1020
|
+
children: isBuying ? "Processing..." : `Buy ${TIERS[selectedTier].queries} queries \u2014 $${TIERS[selectedTier].price}`
|
|
1021
|
+
}
|
|
1022
|
+
),
|
|
1023
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: "#666", fontSize: "12px", textAlign: "center", marginTop: "12px" }, children: "Powered by MoonPay. Visa, Mastercard, Apple Pay accepted." })
|
|
1024
|
+
]
|
|
1025
|
+
}
|
|
1026
|
+
) });
|
|
1027
|
+
}
|
|
1028
|
+
var overlayStyle = {
|
|
1029
|
+
position: "fixed",
|
|
1030
|
+
inset: 0,
|
|
1031
|
+
backgroundColor: "rgba(0, 0, 0, 0.7)",
|
|
1032
|
+
display: "flex",
|
|
1033
|
+
alignItems: "center",
|
|
1034
|
+
justifyContent: "center",
|
|
1035
|
+
zIndex: 9999
|
|
1036
|
+
};
|
|
1037
|
+
var modalStyle = {
|
|
1038
|
+
backgroundColor: "#111111",
|
|
1039
|
+
borderRadius: "16px",
|
|
1040
|
+
border: "1px solid #333",
|
|
1041
|
+
padding: "24px",
|
|
1042
|
+
width: "100%",
|
|
1043
|
+
maxWidth: "420px",
|
|
1044
|
+
fontFamily: "var(--font-body, Inter, sans-serif)"
|
|
1045
|
+
};
|
|
1046
|
+
var closeStyle = {
|
|
1047
|
+
background: "none",
|
|
1048
|
+
border: "none",
|
|
1049
|
+
color: "#666",
|
|
1050
|
+
fontSize: "24px",
|
|
1051
|
+
cursor: "pointer",
|
|
1052
|
+
padding: "4px 8px"
|
|
1053
|
+
};
|
|
1054
|
+
var tierStyle = {
|
|
1055
|
+
display: "flex",
|
|
1056
|
+
justifyContent: "space-between",
|
|
1057
|
+
alignItems: "center",
|
|
1058
|
+
padding: "16px 20px",
|
|
1059
|
+
borderRadius: "12px",
|
|
1060
|
+
border: "1px solid #333",
|
|
1061
|
+
cursor: "pointer",
|
|
1062
|
+
transition: "border-color 0.15s"
|
|
1063
|
+
};
|
|
1064
|
+
var buyStyle = {
|
|
1065
|
+
padding: "14px 32px",
|
|
1066
|
+
borderRadius: "8px",
|
|
1067
|
+
border: "none",
|
|
1068
|
+
backgroundColor: "#d4a843",
|
|
1069
|
+
color: "#0a0a0a",
|
|
1070
|
+
fontWeight: 700,
|
|
1071
|
+
fontSize: "16px",
|
|
1072
|
+
width: "100%"
|
|
1073
|
+
};
|
|
1074
|
+
function AppPaywall({ siteId, builderTenantId, children, onBlocked }) {
|
|
1075
|
+
const {
|
|
1076
|
+
hasAccess,
|
|
1077
|
+
queriesRemaining,
|
|
1078
|
+
paymentMode,
|
|
1079
|
+
unlockPriceCents,
|
|
1080
|
+
unlockPriceLabel,
|
|
1081
|
+
isLoading,
|
|
1082
|
+
checkout
|
|
1083
|
+
} = useAppAccess(siteId, { builderTenantId });
|
|
1084
|
+
const [serverBlocked, setServerBlocked] = react.useState(false);
|
|
1085
|
+
const [serverBlockReason, setServerBlockReason] = react.useState(null);
|
|
1086
|
+
react.useEffect(() => {
|
|
1087
|
+
function handlePaywall(e) {
|
|
1088
|
+
const detail = e.detail;
|
|
1089
|
+
if (!detail) return;
|
|
1090
|
+
if (detail.site_id && detail.site_id !== siteId) return;
|
|
1091
|
+
setServerBlocked(true);
|
|
1092
|
+
setServerBlockReason(detail.code || "payment_required");
|
|
1093
|
+
onBlocked?.(detail.code || "payment_required");
|
|
1094
|
+
}
|
|
1095
|
+
window.addEventListener("flowstack:app_paywall", handlePaywall);
|
|
1096
|
+
return () => window.removeEventListener("flowstack:app_paywall", handlePaywall);
|
|
1097
|
+
}, [siteId, onBlocked]);
|
|
1098
|
+
const showPaywall = serverBlocked || !isLoading && !hasAccess;
|
|
1099
|
+
if (!showPaywall) {
|
|
1100
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
1101
|
+
}
|
|
1102
|
+
const reason = serverBlockReason || (queriesRemaining === 0 ? "free_tier_exhausted" : "payment_required");
|
|
1103
|
+
const priceLabel = unlockPriceLabel || (unlockPriceCents ? `$${(unlockPriceCents / 100).toFixed(2)}` : "Unlock app");
|
|
1104
|
+
const showAgentOption = paymentMode === "agent" || paymentMode === "both";
|
|
1105
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1106
|
+
"div",
|
|
1107
|
+
{
|
|
1108
|
+
style: {
|
|
1109
|
+
position: "fixed",
|
|
1110
|
+
inset: 0,
|
|
1111
|
+
zIndex: 9999,
|
|
1112
|
+
display: "flex",
|
|
1113
|
+
alignItems: "center",
|
|
1114
|
+
justifyContent: "center",
|
|
1115
|
+
background: "rgba(0,0,0,0.72)",
|
|
1116
|
+
backdropFilter: "blur(4px)"
|
|
1117
|
+
},
|
|
1118
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1119
|
+
"div",
|
|
1120
|
+
{
|
|
1121
|
+
style: {
|
|
1122
|
+
background: "#111",
|
|
1123
|
+
border: "1px solid #333",
|
|
1124
|
+
borderRadius: "12px",
|
|
1125
|
+
padding: "32px",
|
|
1126
|
+
maxWidth: "360px",
|
|
1127
|
+
width: "90vw",
|
|
1128
|
+
textAlign: "center",
|
|
1129
|
+
color: "#fff"
|
|
1130
|
+
},
|
|
1131
|
+
children: [
|
|
1132
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1133
|
+
"p",
|
|
1134
|
+
{
|
|
1135
|
+
style: {
|
|
1136
|
+
fontSize: "10px",
|
|
1137
|
+
letterSpacing: "0.3em",
|
|
1138
|
+
textTransform: "uppercase",
|
|
1139
|
+
color: "#888",
|
|
1140
|
+
marginBottom: "12px"
|
|
1141
|
+
},
|
|
1142
|
+
children: reason === "free_tier_exhausted" ? "Free queries used" : "Access required"
|
|
1143
|
+
}
|
|
1144
|
+
),
|
|
1145
|
+
reason === "free_tier_exhausted" && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "13px", color: "#aaa", marginBottom: "20px" }, children: "You've used your free queries for this month. Unlock unlimited access below." }),
|
|
1146
|
+
reason === "payment_required" && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "13px", color: "#aaa", marginBottom: "20px" }, children: "This app requires payment to access." }),
|
|
1147
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1148
|
+
"button",
|
|
1149
|
+
{
|
|
1150
|
+
onClick: () => checkout(),
|
|
1151
|
+
style: {
|
|
1152
|
+
width: "100%",
|
|
1153
|
+
padding: "12px 24px",
|
|
1154
|
+
background: "#6366f1",
|
|
1155
|
+
color: "#fff",
|
|
1156
|
+
border: "none",
|
|
1157
|
+
borderRadius: "8px",
|
|
1158
|
+
fontSize: "13px",
|
|
1159
|
+
fontWeight: 600,
|
|
1160
|
+
cursor: "pointer",
|
|
1161
|
+
marginBottom: showAgentOption ? "8px" : "0"
|
|
1162
|
+
},
|
|
1163
|
+
children: priceLabel
|
|
1164
|
+
}
|
|
1165
|
+
),
|
|
1166
|
+
showAgentOption && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1167
|
+
"p",
|
|
1168
|
+
{
|
|
1169
|
+
style: {
|
|
1170
|
+
fontSize: "11px",
|
|
1171
|
+
color: "#666",
|
|
1172
|
+
cursor: "pointer",
|
|
1173
|
+
textDecoration: "underline"
|
|
1174
|
+
},
|
|
1175
|
+
onClick: () => {
|
|
1176
|
+
if (typeof window !== "undefined") {
|
|
1177
|
+
window.open(`https://openinferencefoundation.org/buy?returnTo=${encodeURIComponent(window.location.href)}`, "_blank");
|
|
1178
|
+
}
|
|
1179
|
+
},
|
|
1180
|
+
children: "Or pay with AGENT tokens"
|
|
1181
|
+
}
|
|
1182
|
+
)
|
|
1183
|
+
]
|
|
1184
|
+
}
|
|
1185
|
+
)
|
|
1186
|
+
}
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
exports.AgentBalanceBadge = AgentBalanceBadge;
|
|
1191
|
+
exports.AppPaywall = AppPaywall;
|
|
1192
|
+
exports.BuyInferModal = BuyInferModal;
|
|
1193
|
+
exports.InferBalanceBadge = InferBalanceBadge;
|
|
1194
|
+
exports.LoginButton = LoginButton;
|
|
1195
|
+
exports.NeedsAgent = NeedsAgent;
|
|
1196
|
+
exports.PaymentRequired = PaymentRequired;
|
|
1197
|
+
exports.WalletProvider = WalletProvider;
|
|
1198
|
+
exports.useAgentBalance = useAgentBalance;
|
|
1199
|
+
exports.useAppAccess = useAppAccess;
|
|
1200
|
+
exports.useBuyInfer = useBuyInfer;
|
|
1201
|
+
exports.useDeposit = useDeposit;
|
|
1202
|
+
exports.useInferBalance = useInferBalance;
|
|
1203
|
+
exports.useWalletAuth = useWalletAuth;
|
|
1204
|
+
//# sourceMappingURL=index.js.map
|
|
1205
|
+
//# sourceMappingURL=index.js.map
|