@cicero-zcash/react 0.0.0-stage → 0.1.0
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/README.md +241 -2
- package/dist/index.d.mts +484 -0
- package/dist/index.d.ts +484 -0
- package/dist/index.js +2602 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2581 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +67 -4
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2581 @@
|
|
|
1
|
+
// src/context/CiceroContext.tsx
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
useContext,
|
|
5
|
+
useState as useState4,
|
|
6
|
+
useEffect as useEffect4,
|
|
7
|
+
useCallback as useCallback2,
|
|
8
|
+
useMemo,
|
|
9
|
+
useRef
|
|
10
|
+
} from "react";
|
|
11
|
+
|
|
12
|
+
// src/utils/zip321.ts
|
|
13
|
+
function encodeBase64Url(str) {
|
|
14
|
+
try {
|
|
15
|
+
const bytes = new TextEncoder().encode(str);
|
|
16
|
+
let binary = "";
|
|
17
|
+
const len = bytes.byteLength;
|
|
18
|
+
for (let i = 0; i < len; i++) {
|
|
19
|
+
binary += String.fromCharCode(bytes[i]);
|
|
20
|
+
}
|
|
21
|
+
const base64 = btoa(binary);
|
|
22
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
23
|
+
} catch {
|
|
24
|
+
return "";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function decodeBase64Url(base64url) {
|
|
28
|
+
try {
|
|
29
|
+
let base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
|
|
30
|
+
while (base64.length % 4) {
|
|
31
|
+
base64 += "=";
|
|
32
|
+
}
|
|
33
|
+
const binary = atob(base64);
|
|
34
|
+
const bytes = new Uint8Array(binary.length);
|
|
35
|
+
for (let i = 0; i < binary.length; i++) {
|
|
36
|
+
bytes[i] = binary.charCodeAt(i);
|
|
37
|
+
}
|
|
38
|
+
return new TextDecoder().decode(bytes);
|
|
39
|
+
} catch {
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function generateZip321Uri({
|
|
44
|
+
address,
|
|
45
|
+
amount,
|
|
46
|
+
memo,
|
|
47
|
+
message
|
|
48
|
+
}) {
|
|
49
|
+
const cleanAddress = address.trim();
|
|
50
|
+
const params = new URLSearchParams();
|
|
51
|
+
if (amount !== void 0 && amount !== null && amount !== "") {
|
|
52
|
+
const numAmount = typeof amount === "number" ? amount : parseFloat(amount);
|
|
53
|
+
if (!isNaN(numAmount) && numAmount > 0) {
|
|
54
|
+
params.append("amount", numAmount.toString());
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (memo && memo.trim().length > 0) {
|
|
58
|
+
const encodedMemo = encodeBase64Url(memo.trim());
|
|
59
|
+
if (encodedMemo) {
|
|
60
|
+
params.append("memo", encodedMemo);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (message && message.trim().length > 0) {
|
|
64
|
+
params.append("message", message.trim());
|
|
65
|
+
}
|
|
66
|
+
const queryString = params.toString();
|
|
67
|
+
return queryString ? `zcash:${cleanAddress}?${queryString}` : `zcash:${cleanAddress}`;
|
|
68
|
+
}
|
|
69
|
+
function parseZip321Uri(rawUri) {
|
|
70
|
+
const trimmed = rawUri.trim();
|
|
71
|
+
const withoutScheme = trimmed.startsWith("zcash:") ? trimmed.slice(6) : trimmed;
|
|
72
|
+
const [addressPart, queryPart] = withoutScheme.split("?");
|
|
73
|
+
const result = {
|
|
74
|
+
address: addressPart || "",
|
|
75
|
+
rawUri: trimmed
|
|
76
|
+
};
|
|
77
|
+
if (!queryPart) {
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
const searchParams = new URLSearchParams(queryPart);
|
|
81
|
+
const amount = searchParams.get("amount");
|
|
82
|
+
const memo = searchParams.get("memo");
|
|
83
|
+
const message = searchParams.get("message");
|
|
84
|
+
if (amount) result.amount = amount;
|
|
85
|
+
if (memo) result.memo = decodeBase64Url(memo);
|
|
86
|
+
if (message) result.message = message;
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
function getWalletDeepLinks(zip321Uri) {
|
|
90
|
+
return {
|
|
91
|
+
generic: zip321Uri,
|
|
92
|
+
zashi: zip321Uri,
|
|
93
|
+
// Zashi registers the native zcash: URI handler on iOS/Android
|
|
94
|
+
ywallet: zip321Uri
|
|
95
|
+
// Ywallet also natively handles zcash: protocol
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/wallet/zucchini.ts
|
|
100
|
+
var ZucchiniWalletAdapter = class _ZucchiniWalletAdapter {
|
|
101
|
+
client = null;
|
|
102
|
+
provider = null;
|
|
103
|
+
_connected = false;
|
|
104
|
+
_address = null;
|
|
105
|
+
_lastAccounts = [];
|
|
106
|
+
/**
|
|
107
|
+
* Checks whether the Zucchini Wallet extension is installed and its
|
|
108
|
+
* provider is available in the current browser environment.
|
|
109
|
+
*/
|
|
110
|
+
static isAvailable() {
|
|
111
|
+
if (typeof window === "undefined") return false;
|
|
112
|
+
const win = window;
|
|
113
|
+
if (win.zucchini && typeof win.zucchini.request === "function") {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
if (win.zcash) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
return !!win.zucchini;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Subscribes to wallet readiness. Fires callback immediately if already available,
|
|
123
|
+
* or listens for injection events / polls briefly for late extension initialization.
|
|
124
|
+
*/
|
|
125
|
+
static onReady(callback) {
|
|
126
|
+
if (typeof window === "undefined") return () => {
|
|
127
|
+
};
|
|
128
|
+
if (_ZucchiniWalletAdapter.isAvailable()) {
|
|
129
|
+
callback();
|
|
130
|
+
return () => {
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const handler = () => callback();
|
|
134
|
+
window.addEventListener("zucchini#initialized", handler);
|
|
135
|
+
window.addEventListener("zcash#initialized", handler);
|
|
136
|
+
const interval = setInterval(() => {
|
|
137
|
+
if (_ZucchiniWalletAdapter.isAvailable()) {
|
|
138
|
+
clearInterval(interval);
|
|
139
|
+
callback();
|
|
140
|
+
}
|
|
141
|
+
}, 150);
|
|
142
|
+
const timeout = setTimeout(() => {
|
|
143
|
+
clearInterval(interval);
|
|
144
|
+
}, 3500);
|
|
145
|
+
return () => {
|
|
146
|
+
window.removeEventListener("zucchini#initialized", handler);
|
|
147
|
+
window.removeEventListener("zcash#initialized", handler);
|
|
148
|
+
clearInterval(interval);
|
|
149
|
+
clearTimeout(timeout);
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Discovers and initializes the Zucchini Client using @zucchinifi/dapp-sdk/zcash
|
|
154
|
+
* with graceful fallback to raw window.zucchini / window.zcash globals.
|
|
155
|
+
*/
|
|
156
|
+
async getClient() {
|
|
157
|
+
if (this.client) return this.client;
|
|
158
|
+
if (typeof window === "undefined") {
|
|
159
|
+
throw new Error("Zucchini Wallet is only available in browser environments.");
|
|
160
|
+
}
|
|
161
|
+
const win = window;
|
|
162
|
+
let rawProvider = win.zucchini || win.zcash;
|
|
163
|
+
try {
|
|
164
|
+
const { discoverZucchiniProvider, createZucchiniClient } = await import("@zucchinifi/dapp-sdk/zcash");
|
|
165
|
+
const discovered = discoverZucchiniProvider() || rawProvider;
|
|
166
|
+
if (discovered) {
|
|
167
|
+
this.provider = discovered;
|
|
168
|
+
this.client = createZucchiniClient(discovered);
|
|
169
|
+
return this.client;
|
|
170
|
+
}
|
|
171
|
+
} catch {
|
|
172
|
+
}
|
|
173
|
+
if (rawProvider) {
|
|
174
|
+
this.provider = rawProvider;
|
|
175
|
+
this.client = {
|
|
176
|
+
async connect(req) {
|
|
177
|
+
if (typeof rawProvider.connect === "function") {
|
|
178
|
+
return rawProvider.connect(req?.permissions || ["view_addresses", "view_balance", "send_transaction"]);
|
|
179
|
+
}
|
|
180
|
+
if (typeof rawProvider.request === "function") {
|
|
181
|
+
return rawProvider.request({ method: "connect", params: req });
|
|
182
|
+
}
|
|
183
|
+
return { connected: true };
|
|
184
|
+
},
|
|
185
|
+
async disconnect() {
|
|
186
|
+
if (typeof rawProvider.disconnect === "function") {
|
|
187
|
+
return rawProvider.disconnect();
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
async requestPayment(uri) {
|
|
191
|
+
if (typeof rawProvider.request === "function") {
|
|
192
|
+
const res = await rawProvider.request({
|
|
193
|
+
method: "zcash_requestPayment",
|
|
194
|
+
params: { uri }
|
|
195
|
+
});
|
|
196
|
+
return { txid: res?.txid || res?.txId || res };
|
|
197
|
+
}
|
|
198
|
+
throw new Error("Direct ZIP-321 send not supported by provider");
|
|
199
|
+
},
|
|
200
|
+
async getAddresses() {
|
|
201
|
+
if (typeof rawProvider.request === "function") {
|
|
202
|
+
return rawProvider.request({ method: "getAddresses" });
|
|
203
|
+
}
|
|
204
|
+
return {};
|
|
205
|
+
},
|
|
206
|
+
on(event, listener) {
|
|
207
|
+
rawProvider.on?.(event, listener);
|
|
208
|
+
return () => rawProvider.off?.(event, listener);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
return this.client;
|
|
212
|
+
}
|
|
213
|
+
throw new Error(
|
|
214
|
+
"Zucchini Wallet extension not detected. Please install it from the Chrome Web Store or ensure the side-panel is open."
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Requests connection with view_addresses and send_transaction permissions.
|
|
219
|
+
* Resolves the user's active Shielded Unified Address.
|
|
220
|
+
*/
|
|
221
|
+
async connect() {
|
|
222
|
+
const client = await this.getClient();
|
|
223
|
+
const connection = await client.connect({
|
|
224
|
+
permissions: ["view_addresses", "view_balance", "send_transaction"]
|
|
225
|
+
});
|
|
226
|
+
let address = void 0;
|
|
227
|
+
if (connection?.accounts && connection.accounts.length > 0) {
|
|
228
|
+
address = connection.accounts[0].address;
|
|
229
|
+
}
|
|
230
|
+
if (!address && typeof client.getAddresses === "function") {
|
|
231
|
+
try {
|
|
232
|
+
const addresses = await client.getAddresses();
|
|
233
|
+
address = addresses?.unified || addresses?.orchard || addresses?.sapling || addresses?.transparent;
|
|
234
|
+
} catch {
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
this._lastAccounts = connection?.accounts || [];
|
|
238
|
+
const balance = await this.getBalance({ address });
|
|
239
|
+
this._connected = true;
|
|
240
|
+
this._address = address || null;
|
|
241
|
+
return {
|
|
242
|
+
address,
|
|
243
|
+
connected: true,
|
|
244
|
+
balance: balance ?? void 0
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Helper to normalize raw balance formats (e.g. number, string, zatoshis, or pool totals).
|
|
249
|
+
*/
|
|
250
|
+
parseBalancePayload(payload) {
|
|
251
|
+
if (payload === void 0 || payload === null) return null;
|
|
252
|
+
if (typeof payload === "number") {
|
|
253
|
+
return payload > 1e6 ? payload / 1e8 : payload;
|
|
254
|
+
}
|
|
255
|
+
if (typeof payload === "string") {
|
|
256
|
+
const parsed = parseFloat(payload);
|
|
257
|
+
if (!isNaN(parsed)) {
|
|
258
|
+
return parsed > 1e6 ? parsed / 1e8 : parsed;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (typeof payload === "object") {
|
|
262
|
+
const cand = payload.total ?? payload.balance ?? payload.amount ?? payload.zec ?? payload.value ?? payload.zatoshis;
|
|
263
|
+
if (cand !== void 0 && cand !== null) {
|
|
264
|
+
return this.parseBalancePayload(cand);
|
|
265
|
+
}
|
|
266
|
+
let sum = 0;
|
|
267
|
+
let hasPool = false;
|
|
268
|
+
for (const pool of ["orchard", "sapling", "transparent", "shielded", "unshielded"]) {
|
|
269
|
+
if (payload[pool] !== void 0) {
|
|
270
|
+
const v = this.parseBalancePayload(payload[pool]);
|
|
271
|
+
if (v !== null) {
|
|
272
|
+
sum += v;
|
|
273
|
+
hasPool = true;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (hasPool) return sum;
|
|
278
|
+
}
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Reads real-time balance from Zucchini provider.
|
|
283
|
+
*/
|
|
284
|
+
async getBalance(options) {
|
|
285
|
+
const raw = this.provider || (typeof window !== "undefined" ? window.zucchini || window.zcash : null);
|
|
286
|
+
const methods = [
|
|
287
|
+
"getBalance",
|
|
288
|
+
"getBalances",
|
|
289
|
+
"zcash_getBalance",
|
|
290
|
+
"zcash_getBalances",
|
|
291
|
+
"view_balance",
|
|
292
|
+
"getBalanceSummary"
|
|
293
|
+
];
|
|
294
|
+
if (raw && typeof raw.request === "function") {
|
|
295
|
+
for (const method of methods) {
|
|
296
|
+
try {
|
|
297
|
+
const res = await raw.request({ method, params: options });
|
|
298
|
+
const parsed = this.parseBalancePayload(res);
|
|
299
|
+
if (parsed !== null) return parsed;
|
|
300
|
+
} catch {
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (this._lastAccounts && this._lastAccounts.length > 0) {
|
|
305
|
+
for (const acc of this._lastAccounts) {
|
|
306
|
+
const parsed = this.parseBalancePayload(acc);
|
|
307
|
+
if (parsed !== null) return parsed;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Sends payment via standard ZIP-321 URI requestPayment or transaction fallback.
|
|
314
|
+
*/
|
|
315
|
+
async requestPayment(params) {
|
|
316
|
+
if (!this._connected) {
|
|
317
|
+
await this.connect();
|
|
318
|
+
}
|
|
319
|
+
const client = await this.getClient();
|
|
320
|
+
const numAmount = typeof params.amount === "number" ? params.amount : parseFloat(params.amount);
|
|
321
|
+
if (isNaN(numAmount) || numAmount <= 0) {
|
|
322
|
+
throw new Error("Payment amount must be a positive number.");
|
|
323
|
+
}
|
|
324
|
+
const uri = generateZip321Uri({
|
|
325
|
+
address: params.recipient.trim(),
|
|
326
|
+
amount: numAmount,
|
|
327
|
+
memo: params.memo?.trim()
|
|
328
|
+
});
|
|
329
|
+
try {
|
|
330
|
+
if (typeof client.requestPayment === "function") {
|
|
331
|
+
const res = await client.requestPayment(uri);
|
|
332
|
+
const txId = res?.txid || res?.txId || "";
|
|
333
|
+
return { txId, submitted: true };
|
|
334
|
+
}
|
|
335
|
+
} catch (e) {
|
|
336
|
+
}
|
|
337
|
+
if (typeof client.requestTransaction === "function") {
|
|
338
|
+
const zatoshis = BigInt(Math.round(numAmount * 1e8));
|
|
339
|
+
const res = await client.requestTransaction({
|
|
340
|
+
amountZatoshis: zatoshis,
|
|
341
|
+
recipient: params.recipient.trim(),
|
|
342
|
+
memo: params.memo?.trim()
|
|
343
|
+
});
|
|
344
|
+
return { txId: res?.txid || "", submitted: true };
|
|
345
|
+
}
|
|
346
|
+
throw new Error("Zucchini Wallet does not support payment dispatch.");
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Subscribes to accountsChanged events.
|
|
350
|
+
*/
|
|
351
|
+
onAccountsChanged(listener) {
|
|
352
|
+
if (this.client?.on) {
|
|
353
|
+
return this.client.on("accountsChanged", listener);
|
|
354
|
+
}
|
|
355
|
+
if (this.provider?.on) {
|
|
356
|
+
this.provider.on("accountsChanged", listener);
|
|
357
|
+
return () => this.provider?.off?.("accountsChanged", listener);
|
|
358
|
+
}
|
|
359
|
+
return () => {
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Subscribes to disconnect events.
|
|
364
|
+
*/
|
|
365
|
+
onDisconnect(listener) {
|
|
366
|
+
if (this.client?.on) {
|
|
367
|
+
return this.client.on("disconnect", listener);
|
|
368
|
+
}
|
|
369
|
+
if (this.provider?.on) {
|
|
370
|
+
this.provider.on("disconnect", listener);
|
|
371
|
+
return () => this.provider?.off?.("disconnect", listener);
|
|
372
|
+
}
|
|
373
|
+
return () => {
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Disconnects the current session.
|
|
378
|
+
*/
|
|
379
|
+
async disconnect() {
|
|
380
|
+
try {
|
|
381
|
+
if (this.client?.disconnect) {
|
|
382
|
+
await this.client.disconnect();
|
|
383
|
+
} else if (this.provider?.disconnect) {
|
|
384
|
+
await this.provider.disconnect();
|
|
385
|
+
}
|
|
386
|
+
} catch {
|
|
387
|
+
}
|
|
388
|
+
this._connected = false;
|
|
389
|
+
this._address = null;
|
|
390
|
+
}
|
|
391
|
+
get isConnected() {
|
|
392
|
+
return this._connected;
|
|
393
|
+
}
|
|
394
|
+
get address() {
|
|
395
|
+
return this._address;
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// src/wallets/zucchini.ts
|
|
400
|
+
var adapter = new ZucchiniWalletAdapter();
|
|
401
|
+
var zucchiniWallet = {
|
|
402
|
+
id: "zucchini",
|
|
403
|
+
name: "Zucchini Wallet",
|
|
404
|
+
icon: "\u{1F952}",
|
|
405
|
+
description: "In-browser Chrome extension with 1-click side-panel approvals",
|
|
406
|
+
category: "extension",
|
|
407
|
+
downloadUrl: "https://chromewebstore.google.com/detail/zucchini-wallet",
|
|
408
|
+
isAvailable: () => ZucchiniWalletAdapter.isAvailable(),
|
|
409
|
+
connect: async (options) => {
|
|
410
|
+
const session = await adapter.connect();
|
|
411
|
+
return {
|
|
412
|
+
address: session.address,
|
|
413
|
+
connected: true,
|
|
414
|
+
network: options?.network || "mainnet",
|
|
415
|
+
walletId: "zucchini",
|
|
416
|
+
balance: session.balance
|
|
417
|
+
};
|
|
418
|
+
},
|
|
419
|
+
disconnect: async () => {
|
|
420
|
+
await adapter.disconnect();
|
|
421
|
+
},
|
|
422
|
+
getBalance: async (options) => {
|
|
423
|
+
return adapter.getBalance(options);
|
|
424
|
+
},
|
|
425
|
+
sendPayment: async (params) => {
|
|
426
|
+
const res = await adapter.requestPayment({
|
|
427
|
+
recipient: params.recipient,
|
|
428
|
+
amount: params.amount,
|
|
429
|
+
memo: params.memo
|
|
430
|
+
});
|
|
431
|
+
return {
|
|
432
|
+
txId: res.txId,
|
|
433
|
+
submitted: res.submitted
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
// src/wallets/zashi.ts
|
|
439
|
+
var zashiWallet = {
|
|
440
|
+
id: "zashi",
|
|
441
|
+
name: "Zashi by ECC",
|
|
442
|
+
icon: "\u{1F6E1}\uFE0F",
|
|
443
|
+
description: "Official zero-knowledge mobile wallet by Electric Coin Co.",
|
|
444
|
+
category: "mobile",
|
|
445
|
+
downloadUrl: "https://electriccoin.co/zashi/",
|
|
446
|
+
isAvailable: () => true,
|
|
447
|
+
// Available via mobile QR or deep link
|
|
448
|
+
connect: async (options) => {
|
|
449
|
+
return {
|
|
450
|
+
connected: true,
|
|
451
|
+
network: options?.network || "mainnet",
|
|
452
|
+
walletId: "zashi"
|
|
453
|
+
};
|
|
454
|
+
},
|
|
455
|
+
sendPayment: async (params) => {
|
|
456
|
+
const rawUri = generateZip321Uri({
|
|
457
|
+
address: params.recipient,
|
|
458
|
+
amount: params.amount,
|
|
459
|
+
memo: params.memo,
|
|
460
|
+
message: params.message
|
|
461
|
+
});
|
|
462
|
+
if (typeof window !== "undefined") {
|
|
463
|
+
window.location.href = rawUri;
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
txId: "",
|
|
467
|
+
submitted: false,
|
|
468
|
+
rawUri
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// src/wallets/ywallet.ts
|
|
474
|
+
var ywallet = {
|
|
475
|
+
id: "ywallet",
|
|
476
|
+
name: "Ywallet",
|
|
477
|
+
icon: "\u26A1",
|
|
478
|
+
description: "Fast-syncing multi-platform shielded wallet for mobile & desktop",
|
|
479
|
+
category: "mobile",
|
|
480
|
+
downloadUrl: "https://ywallet.app/",
|
|
481
|
+
isAvailable: () => true,
|
|
482
|
+
connect: async (options) => {
|
|
483
|
+
return {
|
|
484
|
+
connected: true,
|
|
485
|
+
network: options?.network || "mainnet",
|
|
486
|
+
walletId: "ywallet"
|
|
487
|
+
};
|
|
488
|
+
},
|
|
489
|
+
sendPayment: async (params) => {
|
|
490
|
+
const rawUri = generateZip321Uri({
|
|
491
|
+
address: params.recipient,
|
|
492
|
+
amount: params.amount,
|
|
493
|
+
memo: params.memo,
|
|
494
|
+
message: params.message
|
|
495
|
+
});
|
|
496
|
+
if (typeof window !== "undefined") {
|
|
497
|
+
window.location.href = rawUri;
|
|
498
|
+
}
|
|
499
|
+
return {
|
|
500
|
+
txId: "",
|
|
501
|
+
submitted: false,
|
|
502
|
+
rawUri
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
// src/wallets/viewingKey.ts
|
|
508
|
+
var viewingKeyWallet = {
|
|
509
|
+
id: "viewing-key",
|
|
510
|
+
name: "Viewing Key / Auditor",
|
|
511
|
+
icon: "\u{1F441}\uFE0F",
|
|
512
|
+
description: "Connect with a Unified Address or Viewing Key for read-only audit mode",
|
|
513
|
+
category: "read-only",
|
|
514
|
+
isAvailable: () => true,
|
|
515
|
+
connect: async (options) => {
|
|
516
|
+
return {
|
|
517
|
+
connected: true,
|
|
518
|
+
network: options?.network || "mainnet",
|
|
519
|
+
walletId: "viewing-key"
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
// src/wallets/burner.ts
|
|
525
|
+
var MOCK_BURNER_ADDR = "u1qwe829xkps9482mzsll049281kz8471hzkas093847zkd09472619as8361048293710293847102938471029384710";
|
|
526
|
+
var burnerWallet = {
|
|
527
|
+
id: "burner",
|
|
528
|
+
name: "Dev Session Wallet",
|
|
529
|
+
icon: "\u{1F9EA}",
|
|
530
|
+
description: "Instant ephemeral shielded test wallet for rapid local testing",
|
|
531
|
+
category: "developer",
|
|
532
|
+
isAvailable: () => true,
|
|
533
|
+
connect: async (options) => {
|
|
534
|
+
return {
|
|
535
|
+
address: MOCK_BURNER_ADDR,
|
|
536
|
+
connected: true,
|
|
537
|
+
network: options?.network || "testnet",
|
|
538
|
+
walletId: "burner"
|
|
539
|
+
};
|
|
540
|
+
},
|
|
541
|
+
sendPayment: async (_params) => {
|
|
542
|
+
const mockTxId = `0x${Array.from(
|
|
543
|
+
{ length: 64 },
|
|
544
|
+
() => Math.floor(Math.random() * 16).toString(16)
|
|
545
|
+
).join("")}`;
|
|
546
|
+
return {
|
|
547
|
+
txId: mockTxId,
|
|
548
|
+
submitted: true
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
// src/wallets/index.ts
|
|
554
|
+
function getDefaultWallets() {
|
|
555
|
+
return [
|
|
556
|
+
zucchiniWallet,
|
|
557
|
+
zashiWallet,
|
|
558
|
+
ywallet,
|
|
559
|
+
viewingKeyWallet,
|
|
560
|
+
burnerWallet
|
|
561
|
+
];
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// src/hooks/useZcashPrice.ts
|
|
565
|
+
import { useState, useEffect, useCallback } from "react";
|
|
566
|
+
var cachedPrice = null;
|
|
567
|
+
var lastFetchTime = 0;
|
|
568
|
+
var CACHE_DURATION_MS = 6e4;
|
|
569
|
+
function useZcashPrice(enabled = true) {
|
|
570
|
+
const [state, setState] = useState(() => ({
|
|
571
|
+
usdPrice: cachedPrice,
|
|
572
|
+
loading: !cachedPrice && enabled,
|
|
573
|
+
error: null,
|
|
574
|
+
lastUpdated: lastFetchTime || null
|
|
575
|
+
}));
|
|
576
|
+
useEffect(() => {
|
|
577
|
+
if (!enabled) return;
|
|
578
|
+
let isMounted = true;
|
|
579
|
+
const now = Date.now();
|
|
580
|
+
if (cachedPrice && now - lastFetchTime < CACHE_DURATION_MS) {
|
|
581
|
+
setState((prev) => {
|
|
582
|
+
if (prev.usdPrice === cachedPrice && !prev.loading) return prev;
|
|
583
|
+
return {
|
|
584
|
+
usdPrice: cachedPrice,
|
|
585
|
+
loading: false,
|
|
586
|
+
error: null,
|
|
587
|
+
lastUpdated: lastFetchTime
|
|
588
|
+
};
|
|
589
|
+
});
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
async function fetchPrice() {
|
|
593
|
+
try {
|
|
594
|
+
const res = await fetch(
|
|
595
|
+
"https://api.coingecko.com/api/v3/simple/price?ids=zcash&vs_currencies=usd",
|
|
596
|
+
{ signal: AbortSignal.timeout(4e3) }
|
|
597
|
+
);
|
|
598
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
599
|
+
const data = await res.json();
|
|
600
|
+
const price = data?.zcash?.usd;
|
|
601
|
+
if (typeof price === "number") {
|
|
602
|
+
cachedPrice = price;
|
|
603
|
+
lastFetchTime = Date.now();
|
|
604
|
+
if (isMounted) {
|
|
605
|
+
setState({
|
|
606
|
+
usdPrice: price,
|
|
607
|
+
loading: false,
|
|
608
|
+
error: null,
|
|
609
|
+
lastUpdated: lastFetchTime
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
throw new Error("Invalid price payload");
|
|
615
|
+
} catch (err) {
|
|
616
|
+
try {
|
|
617
|
+
const binanceRes = await fetch(
|
|
618
|
+
"https://api.binance.com/api/v3/ticker/price?symbol=ZECUSDT",
|
|
619
|
+
{ signal: AbortSignal.timeout(3e3) }
|
|
620
|
+
);
|
|
621
|
+
if (binanceRes.ok) {
|
|
622
|
+
const bData = await binanceRes.json();
|
|
623
|
+
const bPrice = parseFloat(bData.price);
|
|
624
|
+
if (!isNaN(bPrice) && bPrice > 0) {
|
|
625
|
+
cachedPrice = bPrice;
|
|
626
|
+
lastFetchTime = Date.now();
|
|
627
|
+
if (isMounted) {
|
|
628
|
+
setState({
|
|
629
|
+
usdPrice: bPrice,
|
|
630
|
+
loading: false,
|
|
631
|
+
error: null,
|
|
632
|
+
lastUpdated: lastFetchTime
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
} catch {
|
|
639
|
+
}
|
|
640
|
+
if (isMounted) {
|
|
641
|
+
const fallback = cachedPrice ?? 38.5;
|
|
642
|
+
setState({
|
|
643
|
+
usdPrice: fallback,
|
|
644
|
+
loading: false,
|
|
645
|
+
error: err instanceof Error ? err.message : "Price fetch error",
|
|
646
|
+
lastUpdated: Date.now()
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
fetchPrice();
|
|
652
|
+
return () => {
|
|
653
|
+
isMounted = false;
|
|
654
|
+
};
|
|
655
|
+
}, [enabled]);
|
|
656
|
+
const convertZecToUsd = useCallback(
|
|
657
|
+
(zecAmount) => {
|
|
658
|
+
const num = typeof zecAmount === "number" ? zecAmount : parseFloat(zecAmount);
|
|
659
|
+
if (isNaN(num) || state.usdPrice === null) return null;
|
|
660
|
+
return (num * state.usdPrice).toFixed(2);
|
|
661
|
+
},
|
|
662
|
+
[state.usdPrice]
|
|
663
|
+
);
|
|
664
|
+
return {
|
|
665
|
+
...state,
|
|
666
|
+
convertZecToUsd
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/components/CiceroConnectModal.tsx
|
|
671
|
+
import { useState as useState2, useEffect as useEffect2 } from "react";
|
|
672
|
+
import { createPortal } from "react-dom";
|
|
673
|
+
import {
|
|
674
|
+
X,
|
|
675
|
+
ExternalLink,
|
|
676
|
+
Shield,
|
|
677
|
+
Sparkles,
|
|
678
|
+
AlertCircle,
|
|
679
|
+
ArrowRight
|
|
680
|
+
} from "lucide-react";
|
|
681
|
+
|
|
682
|
+
// src/components/CiceroQRCode.tsx
|
|
683
|
+
import { QRCodeSVG } from "qrcode.react";
|
|
684
|
+
import { jsx } from "react/jsx-runtime";
|
|
685
|
+
var CiceroQRCode = ({
|
|
686
|
+
value,
|
|
687
|
+
size = 200,
|
|
688
|
+
className = "",
|
|
689
|
+
level = "M",
|
|
690
|
+
includeMargin = true
|
|
691
|
+
}) => {
|
|
692
|
+
return /* @__PURE__ */ jsx(
|
|
693
|
+
"div",
|
|
694
|
+
{
|
|
695
|
+
className: `inline-flex items-center justify-center p-3 bg-white rounded-lg border border-[#ededed] shadow-sm ${className}`,
|
|
696
|
+
children: /* @__PURE__ */ jsx(
|
|
697
|
+
QRCodeSVG,
|
|
698
|
+
{
|
|
699
|
+
value,
|
|
700
|
+
size,
|
|
701
|
+
level,
|
|
702
|
+
includeMargin,
|
|
703
|
+
imageSettings: {
|
|
704
|
+
src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23171717'%3E%3Cpath d='M12 2L3 7v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V7l-9-5zm0 4.5l5 2.5v4.5c0 3.3-2.1 6.3-5 7.2-2.9-.9-5-3.9-5-7.2V9l5-2.5z'/%3E%3Cpath d='M10 11h4l-3 4h3' stroke='%233ecf8e' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E",
|
|
705
|
+
height: 28,
|
|
706
|
+
width: 28,
|
|
707
|
+
excavate: true
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
)
|
|
711
|
+
}
|
|
712
|
+
);
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
// src/components/CiceroConnectModal.tsx
|
|
716
|
+
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
717
|
+
var CiceroConnectModal = ({
|
|
718
|
+
isOpen: propIsOpen,
|
|
719
|
+
onClose: propOnClose
|
|
720
|
+
}) => {
|
|
721
|
+
const {
|
|
722
|
+
isConnectModalOpen,
|
|
723
|
+
closeConnectModal,
|
|
724
|
+
wallets,
|
|
725
|
+
connect,
|
|
726
|
+
network,
|
|
727
|
+
appName
|
|
728
|
+
} = useCicero();
|
|
729
|
+
const isOpen = propIsOpen !== void 0 ? propIsOpen : isConnectModalOpen;
|
|
730
|
+
const handleClose = propOnClose || closeConnectModal;
|
|
731
|
+
const [selectedWalletId, setSelectedWalletId] = useState2("zucchini");
|
|
732
|
+
const [customKeyInput, setCustomKeyInput] = useState2("");
|
|
733
|
+
const [error, setError] = useState2(null);
|
|
734
|
+
const [isSubmitting, setIsSubmitting] = useState2(false);
|
|
735
|
+
const [mounted, setMounted] = useState2(false);
|
|
736
|
+
const [, setAvailabilityTick] = useState2(0);
|
|
737
|
+
useEffect2(() => {
|
|
738
|
+
setMounted(true);
|
|
739
|
+
}, []);
|
|
740
|
+
useEffect2(() => {
|
|
741
|
+
if (!isOpen) return;
|
|
742
|
+
const cleanupReady = ZucchiniWalletAdapter.onReady(() => {
|
|
743
|
+
setAvailabilityTick((t) => t + 1);
|
|
744
|
+
});
|
|
745
|
+
const interval = setInterval(() => {
|
|
746
|
+
setAvailabilityTick((t) => t + 1);
|
|
747
|
+
}, 600);
|
|
748
|
+
return () => {
|
|
749
|
+
cleanupReady();
|
|
750
|
+
clearInterval(interval);
|
|
751
|
+
};
|
|
752
|
+
}, [isOpen]);
|
|
753
|
+
useEffect2(() => {
|
|
754
|
+
const handleKeyDown = (e) => {
|
|
755
|
+
if (e.key === "Escape" && isOpen) {
|
|
756
|
+
handleClose();
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
760
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
761
|
+
}, [isOpen, handleClose]);
|
|
762
|
+
if (!isOpen || !mounted) return null;
|
|
763
|
+
const selectedWallet = wallets.find((w) => w.id === selectedWalletId) || wallets[0];
|
|
764
|
+
const isAvailable = selectedWallet?.isAvailable();
|
|
765
|
+
const handleWalletSelect = (walletId) => {
|
|
766
|
+
setSelectedWalletId(walletId);
|
|
767
|
+
setError(null);
|
|
768
|
+
};
|
|
769
|
+
const handleConnectAction = async () => {
|
|
770
|
+
if (!selectedWallet) return;
|
|
771
|
+
setError(null);
|
|
772
|
+
setIsSubmitting(true);
|
|
773
|
+
try {
|
|
774
|
+
if (selectedWallet.id === "viewing-key") {
|
|
775
|
+
if (!customKeyInput.trim()) {
|
|
776
|
+
throw new Error("Please enter a valid Unified Address or Viewing Key.");
|
|
777
|
+
}
|
|
778
|
+
await connect(selectedWallet.id, customKeyInput.trim());
|
|
779
|
+
} else {
|
|
780
|
+
await connect(selectedWallet.id);
|
|
781
|
+
}
|
|
782
|
+
} catch (err) {
|
|
783
|
+
setError(err instanceof Error ? err.message : "Connection failed");
|
|
784
|
+
} finally {
|
|
785
|
+
setIsSubmitting(false);
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
return createPortal(
|
|
789
|
+
/* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-[99999] flex items-center justify-center p-4 sm:p-6 overflow-y-auto font-sans", children: [
|
|
790
|
+
/* @__PURE__ */ jsx2(
|
|
791
|
+
"div",
|
|
792
|
+
{
|
|
793
|
+
className: "fixed inset-0 bg-[#171717]/60 backdrop-blur-sm transition-opacity z-[99999]",
|
|
794
|
+
onClick: handleClose
|
|
795
|
+
}
|
|
796
|
+
),
|
|
797
|
+
/* @__PURE__ */ jsxs("div", { className: "relative w-full max-w-2xl bg-white rounded-xl border border-[#ededed] shadow-2xl overflow-hidden z-[100000] text-[#171717] animate-in fade-in zoom-in-95 duration-150", children: [
|
|
798
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4 border-b border-[#ededed] bg-[#fafafa]", children: [
|
|
799
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
|
|
800
|
+
/* @__PURE__ */ jsx2("div", { className: "w-8 h-8 rounded-md bg-[#171717] flex items-center justify-center text-[#3ecf8e]", children: /* @__PURE__ */ jsx2(Shield, { className: "w-4 h-4 fill-[#3ecf8e]" }) }),
|
|
801
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
802
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
803
|
+
/* @__PURE__ */ jsx2("span", { className: "font-semibold text-sm text-[#171717]", children: "Connect Zcash Wallet" }),
|
|
804
|
+
/* @__PURE__ */ jsx2("span", { className: "text-[10px] uppercase tracking-wider font-semibold px-2 py-0.5 rounded-full bg-[#3ecf8e]/15 text-[#24b47e] border border-[#3ecf8e]/30", children: network })
|
|
805
|
+
] }),
|
|
806
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-[#707070] mt-0.5", children: [
|
|
807
|
+
"Connect to ",
|
|
808
|
+
/* @__PURE__ */ jsx2("strong", { className: "text-[#171717]", children: appName }),
|
|
809
|
+
" with your preferred zero-knowledge wallet."
|
|
810
|
+
] })
|
|
811
|
+
] })
|
|
812
|
+
] }),
|
|
813
|
+
/* @__PURE__ */ jsx2(
|
|
814
|
+
"button",
|
|
815
|
+
{
|
|
816
|
+
onClick: handleClose,
|
|
817
|
+
className: "p-1 rounded-md text-[#707070] hover:text-[#171717] hover:bg-[#ededed] transition-colors",
|
|
818
|
+
"aria-label": "Close modal",
|
|
819
|
+
children: /* @__PURE__ */ jsx2(X, { className: "w-4 h-4" })
|
|
820
|
+
}
|
|
821
|
+
)
|
|
822
|
+
] }),
|
|
823
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-12 min-h-[380px]", children: [
|
|
824
|
+
/* @__PURE__ */ jsxs("div", { className: "md:col-span-5 p-3 bg-[#fafafa] border-r border-[#ededed] space-y-1", children: [
|
|
825
|
+
/* @__PURE__ */ jsx2("div", { className: "px-3 py-2 text-[11px] font-mono uppercase tracking-wider text-[#707070]", children: "Available Wallets" }),
|
|
826
|
+
wallets.map((w) => {
|
|
827
|
+
const active = w.id === selectedWalletId;
|
|
828
|
+
const available = w.isAvailable();
|
|
829
|
+
return /* @__PURE__ */ jsx2(
|
|
830
|
+
"button",
|
|
831
|
+
{
|
|
832
|
+
onClick: () => handleWalletSelect(w.id),
|
|
833
|
+
className: `w-full flex items-center justify-between p-3 rounded-lg text-left text-xs transition-all ${active ? "bg-white shadow-sm border border-[#dfdfdf] text-[#171717] font-medium" : "hover:bg-white/60 text-[#707070] hover:text-[#171717] border border-transparent"}`,
|
|
834
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
|
|
835
|
+
/* @__PURE__ */ jsx2("span", { className: "text-base", children: w.icon }),
|
|
836
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
837
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
838
|
+
/* @__PURE__ */ jsx2("span", { className: "text-sm text-[#171717] font-medium", children: w.name }),
|
|
839
|
+
w.id === "zucchini" && available && /* @__PURE__ */ jsx2("span", { className: "text-[9px] uppercase font-mono px-1.5 py-0.2 rounded bg-emerald-100 text-emerald-800 font-bold", children: "Detected" })
|
|
840
|
+
] }),
|
|
841
|
+
/* @__PURE__ */ jsx2("span", { className: "text-[11px] text-[#9a9a9a] line-clamp-1", children: w.category === "extension" ? "Browser Extension" : w.category === "mobile" ? "Mobile / QR" : w.category === "read-only" ? "Auditor View" : "Dev Session" })
|
|
842
|
+
] })
|
|
843
|
+
] })
|
|
844
|
+
},
|
|
845
|
+
w.id
|
|
846
|
+
);
|
|
847
|
+
})
|
|
848
|
+
] }),
|
|
849
|
+
/* @__PURE__ */ jsxs("div", { className: "md:col-span-7 p-6 flex flex-col justify-between bg-white", children: [
|
|
850
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
851
|
+
/* @__PURE__ */ jsx2("div", { className: "flex items-start justify-between pb-4 border-b border-[#ededed]", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
|
|
852
|
+
/* @__PURE__ */ jsx2("span", { className: "text-2xl", children: selectedWallet.icon }),
|
|
853
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
854
|
+
/* @__PURE__ */ jsx2("h4", { className: "text-base font-semibold text-[#171717]", children: selectedWallet.name }),
|
|
855
|
+
/* @__PURE__ */ jsx2("p", { className: "text-xs text-[#707070] mt-0.5", children: selectedWallet.description })
|
|
856
|
+
] })
|
|
857
|
+
] }) }),
|
|
858
|
+
error && /* @__PURE__ */ jsxs("div", { className: "p-3 rounded-md bg-rose-50 border border-rose-200 text-rose-800 text-xs flex items-start gap-2", children: [
|
|
859
|
+
/* @__PURE__ */ jsx2(AlertCircle, { className: "w-4 h-4 text-rose-600 flex-shrink-0 mt-0.5" }),
|
|
860
|
+
/* @__PURE__ */ jsx2("span", { children: error })
|
|
861
|
+
] }),
|
|
862
|
+
selectedWallet.id === "zucchini" && /* @__PURE__ */ jsx2("div", { className: "space-y-4 py-2", children: isAvailable ? /* @__PURE__ */ jsxs("div", { className: "p-4 rounded-lg bg-emerald-50 border border-emerald-200 text-xs text-emerald-950 space-y-2", children: [
|
|
863
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 font-semibold", children: [
|
|
864
|
+
/* @__PURE__ */ jsx2(Sparkles, { className: "w-4 h-4 text-[#24b47e]" }),
|
|
865
|
+
/* @__PURE__ */ jsx2("span", { children: "Extension Detected & Ready" })
|
|
866
|
+
] }),
|
|
867
|
+
/* @__PURE__ */ jsx2("p", { className: "text-emerald-800 leading-relaxed", children: "Clicking connect will prompt your Zucchini Wallet side-panel to authorize access." })
|
|
868
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "p-4 rounded-lg bg-[#fafafa] border border-[#ededed] text-xs text-[#707070] space-y-3", children: [
|
|
869
|
+
/* @__PURE__ */ jsx2("p", { children: "Zucchini Wallet extension was not detected in this browser. Install it to enable 1-click in-browser approvals." }),
|
|
870
|
+
selectedWallet.downloadUrl && /* @__PURE__ */ jsxs(
|
|
871
|
+
"a",
|
|
872
|
+
{
|
|
873
|
+
href: selectedWallet.downloadUrl,
|
|
874
|
+
target: "_blank",
|
|
875
|
+
rel: "noopener noreferrer",
|
|
876
|
+
className: "inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md bg-[#171717] text-white text-xs font-medium hover:bg-[#212121]",
|
|
877
|
+
children: [
|
|
878
|
+
/* @__PURE__ */ jsx2("span", { children: "Install from Chrome Web Store" }),
|
|
879
|
+
/* @__PURE__ */ jsx2(ExternalLink, { className: "w-3.5 h-3.5" })
|
|
880
|
+
]
|
|
881
|
+
}
|
|
882
|
+
)
|
|
883
|
+
] }) }),
|
|
884
|
+
(selectedWallet.id === "zashi" || selectedWallet.id === "ywallet") && /* @__PURE__ */ jsxs("div", { className: "text-center space-y-3 py-1", children: [
|
|
885
|
+
/* @__PURE__ */ jsx2("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx2(
|
|
886
|
+
CiceroQRCode,
|
|
887
|
+
{
|
|
888
|
+
value: `zcash:u1demo?message=Connect%20to%20${encodeURIComponent(appName || "Cicero App")}`,
|
|
889
|
+
size: 150
|
|
890
|
+
}
|
|
891
|
+
) }),
|
|
892
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
893
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs font-medium text-[#171717]", children: [
|
|
894
|
+
"Scan with ",
|
|
895
|
+
selectedWallet.name,
|
|
896
|
+
" app"
|
|
897
|
+
] }),
|
|
898
|
+
/* @__PURE__ */ jsxs("p", { className: "text-[11px] text-[#707070] mt-0.5", children: [
|
|
899
|
+
"Open ",
|
|
900
|
+
selectedWallet.name,
|
|
901
|
+
" on iOS/Android and scan this QR code to authenticate."
|
|
902
|
+
] })
|
|
903
|
+
] })
|
|
904
|
+
] }),
|
|
905
|
+
selectedWallet.id === "viewing-key" && /* @__PURE__ */ jsxs("div", { className: "space-y-3 py-2 text-xs", children: [
|
|
906
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
907
|
+
/* @__PURE__ */ jsx2("label", { className: "font-medium text-[#171717] block mb-1", children: "Unified Address or Viewing Key" }),
|
|
908
|
+
/* @__PURE__ */ jsx2(
|
|
909
|
+
"textarea",
|
|
910
|
+
{
|
|
911
|
+
rows: 3,
|
|
912
|
+
value: customKeyInput,
|
|
913
|
+
onChange: (e) => setCustomKeyInput(e.target.value),
|
|
914
|
+
placeholder: "Paste u1... address, zxviews... or uview... key",
|
|
915
|
+
className: "w-full p-2.5 rounded-md bg-white border border-[#dfdfdf] font-mono text-xs text-[#171717] focus:outline-none focus:border-[#3ecf8e]"
|
|
916
|
+
}
|
|
917
|
+
)
|
|
918
|
+
] }),
|
|
919
|
+
/* @__PURE__ */ jsx2("p", { className: "text-[11px] text-[#707070]", children: "Read-only connection for tax calculation, accounting reconciliation, or proof-of-reserves." })
|
|
920
|
+
] }),
|
|
921
|
+
selectedWallet.id === "burner" && /* @__PURE__ */ jsx2("div", { className: "space-y-3 py-2 text-xs", children: /* @__PURE__ */ jsxs("div", { className: "p-3.5 rounded-lg bg-[#fafafa] border border-[#ededed] space-y-1.5", children: [
|
|
922
|
+
/* @__PURE__ */ jsx2("span", { className: "font-semibold text-[#171717]", children: "Ephemeral Shielded Test Wallet" }),
|
|
923
|
+
/* @__PURE__ */ jsx2("p", { className: "text-[#707070] leading-relaxed", children: "Instantly creates an in-memory session wallet pre-funded with simulated testnet ZEC. Perfect for testing dApps without needing external extensions." })
|
|
924
|
+
] }) })
|
|
925
|
+
] }),
|
|
926
|
+
/* @__PURE__ */ jsxs("div", { className: "pt-4 border-t border-[#ededed] mt-4 flex items-center justify-between", children: [
|
|
927
|
+
/* @__PURE__ */ jsx2("span", { className: "text-[11px] text-[#9a9a9a]", children: "End-to-End Encrypted" }),
|
|
928
|
+
/* @__PURE__ */ jsx2(
|
|
929
|
+
"button",
|
|
930
|
+
{
|
|
931
|
+
onClick: handleConnectAction,
|
|
932
|
+
disabled: isSubmitting,
|
|
933
|
+
className: "inline-flex items-center gap-2 px-5 py-2.5 rounded-md bg-[#3ecf8e] text-[#171717] font-semibold text-xs hover:bg-[#24b47e] transition-colors disabled:opacity-50 shadow-sm",
|
|
934
|
+
children: isSubmitting ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
935
|
+
/* @__PURE__ */ jsx2("span", { className: "w-3.5 h-3.5 border-2 border-[#171717]/30 border-t-[#171717] rounded-full animate-spin" }),
|
|
936
|
+
/* @__PURE__ */ jsx2("span", { children: "Connecting..." })
|
|
937
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
938
|
+
/* @__PURE__ */ jsx2("span", { children: selectedWallet.id === "viewing-key" ? "Connect Read-Only" : selectedWallet.id === "burner" ? "Launch Dev Session" : `Connect ${selectedWallet.name}` }),
|
|
939
|
+
/* @__PURE__ */ jsx2(ArrowRight, { className: "w-3.5 h-3.5" })
|
|
940
|
+
] })
|
|
941
|
+
}
|
|
942
|
+
)
|
|
943
|
+
] })
|
|
944
|
+
] })
|
|
945
|
+
] })
|
|
946
|
+
] })
|
|
947
|
+
] }),
|
|
948
|
+
document.body
|
|
949
|
+
);
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
// src/components/CiceroAccountModal.tsx
|
|
953
|
+
import { useState as useState3, useEffect as useEffect3 } from "react";
|
|
954
|
+
import { createPortal as createPortal2 } from "react-dom";
|
|
955
|
+
import {
|
|
956
|
+
X as X2,
|
|
957
|
+
Copy,
|
|
958
|
+
Check,
|
|
959
|
+
LogOut,
|
|
960
|
+
ExternalLink as ExternalLink2,
|
|
961
|
+
Shield as Shield2,
|
|
962
|
+
RefreshCw,
|
|
963
|
+
QrCode,
|
|
964
|
+
ArrowRightLeft
|
|
965
|
+
} from "lucide-react";
|
|
966
|
+
|
|
967
|
+
// src/utils/address.ts
|
|
968
|
+
function getAddressType(address) {
|
|
969
|
+
if (!address || typeof address !== "string") {
|
|
970
|
+
return "unknown";
|
|
971
|
+
}
|
|
972
|
+
const clean = address.trim();
|
|
973
|
+
if (clean.startsWith("u1") || clean.startsWith("utest1")) {
|
|
974
|
+
return "unified";
|
|
975
|
+
}
|
|
976
|
+
if (clean.startsWith("zs1") || clean.startsWith("ztestsapling1")) {
|
|
977
|
+
return "sapling";
|
|
978
|
+
}
|
|
979
|
+
if (clean.startsWith("t1") || clean.startsWith("t3") || clean.startsWith("tm")) {
|
|
980
|
+
return "transparent";
|
|
981
|
+
}
|
|
982
|
+
return "unknown";
|
|
983
|
+
}
|
|
984
|
+
function isShieldedAddress(address) {
|
|
985
|
+
const type = getAddressType(address);
|
|
986
|
+
return type === "unified" || type === "sapling";
|
|
987
|
+
}
|
|
988
|
+
function truncateAddress(address, start = 8, end = 8) {
|
|
989
|
+
if (!address) return "";
|
|
990
|
+
const clean = address.trim();
|
|
991
|
+
if (clean.length <= start + end + 3) {
|
|
992
|
+
return clean;
|
|
993
|
+
}
|
|
994
|
+
return `${clean.slice(0, start)}...${clean.slice(-end)}`;
|
|
995
|
+
}
|
|
996
|
+
function validateMemoSupport(address, memo) {
|
|
997
|
+
const byteLength = memo ? new TextEncoder().encode(memo).length : 0;
|
|
998
|
+
const isAddressShielded = isShieldedAddress(address);
|
|
999
|
+
if (byteLength > 512) {
|
|
1000
|
+
return {
|
|
1001
|
+
valid: false,
|
|
1002
|
+
warning: `Memo exceeds Zcash protocol limit of 512 bytes (current: ${byteLength} bytes).`,
|
|
1003
|
+
byteLength
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
if (memo && !isAddressShielded) {
|
|
1007
|
+
return {
|
|
1008
|
+
valid: true,
|
|
1009
|
+
warning: "Warning: Transparent addresses (t-addr) do not support shielded encrypted memos. Use a Unified (u1) or Sapling (zs1) address to protect your memo.",
|
|
1010
|
+
byteLength
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
return {
|
|
1014
|
+
valid: true,
|
|
1015
|
+
byteLength
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/components/CiceroAccountModal.tsx
|
|
1020
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1021
|
+
var CiceroAccountModal = ({
|
|
1022
|
+
isOpen: propIsOpen,
|
|
1023
|
+
onClose: propOnClose
|
|
1024
|
+
}) => {
|
|
1025
|
+
const {
|
|
1026
|
+
isAccountModalOpen,
|
|
1027
|
+
closeAccountModal,
|
|
1028
|
+
address,
|
|
1029
|
+
wallet,
|
|
1030
|
+
network,
|
|
1031
|
+
switchNetwork,
|
|
1032
|
+
balance,
|
|
1033
|
+
refetchBalance,
|
|
1034
|
+
disconnect
|
|
1035
|
+
} = useCicero();
|
|
1036
|
+
const isOpen = propIsOpen !== void 0 ? propIsOpen : isAccountModalOpen;
|
|
1037
|
+
const handleClose = propOnClose || closeAccountModal;
|
|
1038
|
+
const [copied, setCopied] = useState3(false);
|
|
1039
|
+
const [showQr, setShowQr] = useState3(false);
|
|
1040
|
+
const [mounted, setMounted] = useState3(false);
|
|
1041
|
+
useEffect3(() => {
|
|
1042
|
+
setMounted(true);
|
|
1043
|
+
}, []);
|
|
1044
|
+
useEffect3(() => {
|
|
1045
|
+
const handleKeyDown = (e) => {
|
|
1046
|
+
if (e.key === "Escape" && isOpen) {
|
|
1047
|
+
handleClose();
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
1051
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1052
|
+
}, [isOpen, handleClose]);
|
|
1053
|
+
if (!isOpen || !mounted) return null;
|
|
1054
|
+
const copyAddress = async () => {
|
|
1055
|
+
if (!address) return;
|
|
1056
|
+
try {
|
|
1057
|
+
await navigator.clipboard.writeText(address);
|
|
1058
|
+
setCopied(true);
|
|
1059
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
1060
|
+
} catch {
|
|
1061
|
+
}
|
|
1062
|
+
};
|
|
1063
|
+
return createPortal2(
|
|
1064
|
+
/* @__PURE__ */ jsxs2("div", { className: "fixed inset-0 z-[99999] flex items-center justify-center p-4 sm:p-6 overflow-y-auto font-sans", children: [
|
|
1065
|
+
/* @__PURE__ */ jsx3(
|
|
1066
|
+
"div",
|
|
1067
|
+
{
|
|
1068
|
+
className: "fixed inset-0 bg-[#171717]/60 backdrop-blur-sm transition-opacity z-[99999]",
|
|
1069
|
+
onClick: handleClose
|
|
1070
|
+
}
|
|
1071
|
+
),
|
|
1072
|
+
/* @__PURE__ */ jsxs2("div", { className: "relative w-full max-w-md bg-white rounded-xl border border-[#ededed] shadow-2xl overflow-hidden z-[100000] text-[#171717] animate-in fade-in zoom-in-95 duration-150", children: [
|
|
1073
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between px-6 py-4 border-b border-[#ededed] bg-[#fafafa]", children: [
|
|
1074
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
|
|
1075
|
+
/* @__PURE__ */ jsx3("span", { className: "text-xl", children: wallet?.icon || "\u{1F6E1}\uFE0F" }),
|
|
1076
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
1077
|
+
/* @__PURE__ */ jsx3("span", { className: "font-semibold text-sm text-[#171717]", children: wallet?.name || "Zcash Wallet" }),
|
|
1078
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1.5 text-[11px] text-[#707070]", children: [
|
|
1079
|
+
/* @__PURE__ */ jsx3("span", { className: "w-1.5 h-1.5 rounded-full bg-[#24b47e]" }),
|
|
1080
|
+
/* @__PURE__ */ jsx3("span", { children: "Connected" })
|
|
1081
|
+
] })
|
|
1082
|
+
] })
|
|
1083
|
+
] }),
|
|
1084
|
+
/* @__PURE__ */ jsx3(
|
|
1085
|
+
"button",
|
|
1086
|
+
{
|
|
1087
|
+
onClick: handleClose,
|
|
1088
|
+
className: "p-1 rounded-md text-[#707070] hover:text-[#171717] hover:bg-[#ededed] transition-colors",
|
|
1089
|
+
children: /* @__PURE__ */ jsx3(X2, { className: "w-4 h-4" })
|
|
1090
|
+
}
|
|
1091
|
+
)
|
|
1092
|
+
] }),
|
|
1093
|
+
/* @__PURE__ */ jsxs2("div", { className: "p-6 space-y-5", children: [
|
|
1094
|
+
/* @__PURE__ */ jsxs2("div", { className: "p-4 rounded-lg bg-[#fafafa] border border-[#ededed] text-center space-y-1", children: [
|
|
1095
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-center gap-1.5 text-xs text-[#707070]", children: [
|
|
1096
|
+
/* @__PURE__ */ jsx3("span", { children: "Shielded Balance" }),
|
|
1097
|
+
/* @__PURE__ */ jsx3(
|
|
1098
|
+
"button",
|
|
1099
|
+
{
|
|
1100
|
+
onClick: () => refetchBalance(),
|
|
1101
|
+
className: "hover:text-[#171717] p-0.5",
|
|
1102
|
+
title: "Refresh balance",
|
|
1103
|
+
children: /* @__PURE__ */ jsx3(
|
|
1104
|
+
RefreshCw,
|
|
1105
|
+
{
|
|
1106
|
+
className: `w-3 h-3 ${balance.isLoading ? "animate-spin text-[#3ecf8e]" : ""}`
|
|
1107
|
+
}
|
|
1108
|
+
)
|
|
1109
|
+
}
|
|
1110
|
+
)
|
|
1111
|
+
] }),
|
|
1112
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-baseline justify-center gap-1.5", children: [
|
|
1113
|
+
/* @__PURE__ */ jsx3("span", { className: "text-3xl font-bold font-mono text-[#171717] tracking-tight", children: balance.zec }),
|
|
1114
|
+
/* @__PURE__ */ jsx3("span", { className: "text-sm font-medium text-[#707070]", children: "ZEC" })
|
|
1115
|
+
] }),
|
|
1116
|
+
balance.usd && /* @__PURE__ */ jsxs2("p", { className: "text-xs text-[#707070] font-mono", children: [
|
|
1117
|
+
"\u2248 $",
|
|
1118
|
+
balance.usd,
|
|
1119
|
+
" USD"
|
|
1120
|
+
] })
|
|
1121
|
+
] }),
|
|
1122
|
+
/* @__PURE__ */ jsxs2("div", { className: "space-y-2 text-xs", children: [
|
|
1123
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between text-[#707070]", children: [
|
|
1124
|
+
/* @__PURE__ */ jsx3("span", { children: "Your Shielded Address" }),
|
|
1125
|
+
/* @__PURE__ */ jsxs2(
|
|
1126
|
+
"button",
|
|
1127
|
+
{
|
|
1128
|
+
onClick: () => setShowQr(!showQr),
|
|
1129
|
+
className: "flex items-center gap-1 text-[#24b47e] hover:underline",
|
|
1130
|
+
children: [
|
|
1131
|
+
/* @__PURE__ */ jsx3(QrCode, { className: "w-3.5 h-3.5" }),
|
|
1132
|
+
/* @__PURE__ */ jsx3("span", { children: showQr ? "Hide QR" : "Show QR" })
|
|
1133
|
+
]
|
|
1134
|
+
}
|
|
1135
|
+
)
|
|
1136
|
+
] }),
|
|
1137
|
+
address ? /* @__PURE__ */ jsxs2("div", { className: "p-3 bg-[#fafafa] rounded-md border border-[#ededed] font-mono text-xs text-[#171717] break-all select-all flex items-center justify-between gap-2", children: [
|
|
1138
|
+
/* @__PURE__ */ jsx3("span", { className: "truncate", children: truncateAddress(address, 16, 12) }),
|
|
1139
|
+
/* @__PURE__ */ jsx3(
|
|
1140
|
+
"button",
|
|
1141
|
+
{
|
|
1142
|
+
onClick: copyAddress,
|
|
1143
|
+
className: "p-1 rounded hover:bg-white text-[#707070] hover:text-[#171717] transition-colors flex-shrink-0",
|
|
1144
|
+
title: "Copy full address",
|
|
1145
|
+
children: copied ? /* @__PURE__ */ jsx3(Check, { className: "w-3.5 h-3.5 text-[#24b47e]" }) : /* @__PURE__ */ jsx3(Copy, { className: "w-3.5 h-3.5" })
|
|
1146
|
+
}
|
|
1147
|
+
)
|
|
1148
|
+
] }) : /* @__PURE__ */ jsx3("div", { className: "p-3 bg-[#fafafa] rounded-md border border-[#ededed] text-xs text-[#707070]", children: "Connected in Read-Only Mode" }),
|
|
1149
|
+
showQr && address && /* @__PURE__ */ jsx3("div", { className: "pt-2 flex justify-center", children: /* @__PURE__ */ jsx3(CiceroQRCode, { value: `zcash:${address}`, size: 160 }) }),
|
|
1150
|
+
address && /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between text-[11px] pt-1", children: [
|
|
1151
|
+
/* @__PURE__ */ jsxs2("span", { className: "flex items-center gap-1 text-[#707070]", children: [
|
|
1152
|
+
/* @__PURE__ */ jsx3(Shield2, { className: "w-3 h-3 text-[#3ecf8e]" }),
|
|
1153
|
+
/* @__PURE__ */ jsx3("span", { children: "zk-Shielded Address" })
|
|
1154
|
+
] }),
|
|
1155
|
+
/* @__PURE__ */ jsxs2(
|
|
1156
|
+
"a",
|
|
1157
|
+
{
|
|
1158
|
+
href: network === "testnet" ? `https://blockexplorer.one/zcash/testnet/address/${address}` : `https://blockchair.com/zcash/address/${address}`,
|
|
1159
|
+
target: "_blank",
|
|
1160
|
+
rel: "noopener noreferrer",
|
|
1161
|
+
className: "flex items-center gap-1 text-[#24b47e] hover:underline",
|
|
1162
|
+
children: [
|
|
1163
|
+
/* @__PURE__ */ jsx3("span", { children: "View on Explorer" }),
|
|
1164
|
+
/* @__PURE__ */ jsx3(ExternalLink2, { className: "w-3 h-3" })
|
|
1165
|
+
]
|
|
1166
|
+
}
|
|
1167
|
+
)
|
|
1168
|
+
] })
|
|
1169
|
+
] }),
|
|
1170
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between p-3 rounded-lg border border-[#ededed] bg-white text-xs", children: [
|
|
1171
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
|
|
1172
|
+
/* @__PURE__ */ jsx3(ArrowRightLeft, { className: "w-4 h-4 text-[#707070]" }),
|
|
1173
|
+
/* @__PURE__ */ jsx3("span", { className: "text-[#171717] font-medium", children: "Network" })
|
|
1174
|
+
] }),
|
|
1175
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1 p-0.5 rounded bg-[#fafafa] border border-[#dfdfdf]", children: [
|
|
1176
|
+
/* @__PURE__ */ jsx3(
|
|
1177
|
+
"button",
|
|
1178
|
+
{
|
|
1179
|
+
onClick: () => switchNetwork("mainnet"),
|
|
1180
|
+
className: `px-2 py-1 rounded text-[11px] font-medium transition-colors ${network === "mainnet" ? "bg-white shadow-sm text-[#24b47e] font-semibold" : "text-[#707070]"}`,
|
|
1181
|
+
children: "Mainnet"
|
|
1182
|
+
}
|
|
1183
|
+
),
|
|
1184
|
+
/* @__PURE__ */ jsx3(
|
|
1185
|
+
"button",
|
|
1186
|
+
{
|
|
1187
|
+
onClick: () => switchNetwork("testnet"),
|
|
1188
|
+
className: `px-2 py-1 rounded text-[11px] font-medium transition-colors ${network === "testnet" ? "bg-white shadow-sm text-amber-700 font-semibold" : "text-[#707070]"}`,
|
|
1189
|
+
children: "Testnet"
|
|
1190
|
+
}
|
|
1191
|
+
)
|
|
1192
|
+
] })
|
|
1193
|
+
] })
|
|
1194
|
+
] }),
|
|
1195
|
+
/* @__PURE__ */ jsxs2("div", { className: "px-6 py-3.5 bg-[#fafafa] border-t border-[#ededed] flex items-center justify-between", children: [
|
|
1196
|
+
/* @__PURE__ */ jsxs2(
|
|
1197
|
+
"button",
|
|
1198
|
+
{
|
|
1199
|
+
onClick: disconnect,
|
|
1200
|
+
className: "flex items-center gap-1.5 text-xs text-rose-600 hover:text-rose-700 font-medium transition-colors",
|
|
1201
|
+
children: [
|
|
1202
|
+
/* @__PURE__ */ jsx3(LogOut, { className: "w-3.5 h-3.5" }),
|
|
1203
|
+
/* @__PURE__ */ jsx3("span", { children: "Disconnect Wallet" })
|
|
1204
|
+
]
|
|
1205
|
+
}
|
|
1206
|
+
),
|
|
1207
|
+
/* @__PURE__ */ jsx3(
|
|
1208
|
+
"button",
|
|
1209
|
+
{
|
|
1210
|
+
onClick: handleClose,
|
|
1211
|
+
className: "px-3.5 py-1.5 rounded-md border border-[#dfdfdf] bg-white hover:bg-[#fafafa] text-xs font-medium text-[#171717] transition-colors",
|
|
1212
|
+
children: "Done"
|
|
1213
|
+
}
|
|
1214
|
+
)
|
|
1215
|
+
] })
|
|
1216
|
+
] })
|
|
1217
|
+
] }),
|
|
1218
|
+
document.body
|
|
1219
|
+
);
|
|
1220
|
+
};
|
|
1221
|
+
|
|
1222
|
+
// src/context/CiceroContext.tsx
|
|
1223
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1224
|
+
var CiceroContext = createContext(null);
|
|
1225
|
+
var CiceroProvider = ({
|
|
1226
|
+
children,
|
|
1227
|
+
initialNetwork = "mainnet",
|
|
1228
|
+
wallets = getDefaultWallets(),
|
|
1229
|
+
appName = "Zcash App",
|
|
1230
|
+
appLogo,
|
|
1231
|
+
autoConnect = true
|
|
1232
|
+
}) => {
|
|
1233
|
+
const [network, setNetwork] = useState4(initialNetwork);
|
|
1234
|
+
const [account, setAccount] = useState4({
|
|
1235
|
+
address: null,
|
|
1236
|
+
viewingKey: null,
|
|
1237
|
+
isConnected: false,
|
|
1238
|
+
isConnecting: false,
|
|
1239
|
+
status: "disconnected",
|
|
1240
|
+
wallet: null,
|
|
1241
|
+
error: null
|
|
1242
|
+
});
|
|
1243
|
+
const [balance, setBalance] = useState4({
|
|
1244
|
+
zec: 0,
|
|
1245
|
+
usd: null,
|
|
1246
|
+
isLoading: false
|
|
1247
|
+
});
|
|
1248
|
+
const [isConnectModalOpen, setIsConnectModalOpen] = useState4(false);
|
|
1249
|
+
const [isAccountModalOpen, setIsAccountModalOpen] = useState4(false);
|
|
1250
|
+
const { convertZecToUsd } = useZcashPrice(true);
|
|
1251
|
+
const convertZecToUsdRef = useRef(convertZecToUsd);
|
|
1252
|
+
convertZecToUsdRef.current = convertZecToUsd;
|
|
1253
|
+
const walletsRef = useRef(wallets);
|
|
1254
|
+
walletsRef.current = wallets;
|
|
1255
|
+
useEffect4(() => {
|
|
1256
|
+
if (typeof window === "undefined") return;
|
|
1257
|
+
const savedWalletId = localStorage.getItem("cicero_wallet_id");
|
|
1258
|
+
const savedAddress = localStorage.getItem("cicero_address");
|
|
1259
|
+
const savedNetwork = localStorage.getItem("cicero_network");
|
|
1260
|
+
if (savedNetwork) {
|
|
1261
|
+
setNetwork(savedNetwork);
|
|
1262
|
+
}
|
|
1263
|
+
if (autoConnect && savedWalletId) {
|
|
1264
|
+
const targetWallet = walletsRef.current.find((w) => w.id === savedWalletId);
|
|
1265
|
+
if (targetWallet && (savedAddress || targetWallet.isAvailable())) {
|
|
1266
|
+
const savedBal = localStorage.getItem("cicero_balance");
|
|
1267
|
+
const parsedBal = savedBal ? parseFloat(savedBal) : null;
|
|
1268
|
+
const activeZec = parsedBal !== null && !isNaN(parsedBal) ? parsedBal : 0;
|
|
1269
|
+
const initialUsd = convertZecToUsdRef.current(activeZec);
|
|
1270
|
+
setAccount({
|
|
1271
|
+
address: savedAddress || null,
|
|
1272
|
+
viewingKey: null,
|
|
1273
|
+
isConnected: true,
|
|
1274
|
+
isConnecting: false,
|
|
1275
|
+
status: "connected",
|
|
1276
|
+
wallet: targetWallet,
|
|
1277
|
+
error: null
|
|
1278
|
+
});
|
|
1279
|
+
setBalance({
|
|
1280
|
+
zec: activeZec,
|
|
1281
|
+
usd: initialUsd,
|
|
1282
|
+
isLoading: false
|
|
1283
|
+
});
|
|
1284
|
+
if (targetWallet.getBalance) {
|
|
1285
|
+
targetWallet.getBalance({ address: savedAddress || void 0, network }).then((freshBal) => {
|
|
1286
|
+
if (freshBal !== null && !isNaN(freshBal)) {
|
|
1287
|
+
setBalance({
|
|
1288
|
+
zec: freshBal,
|
|
1289
|
+
usd: convertZecToUsdRef.current(freshBal),
|
|
1290
|
+
isLoading: false
|
|
1291
|
+
});
|
|
1292
|
+
localStorage.setItem("cicero_balance", freshBal.toString());
|
|
1293
|
+
}
|
|
1294
|
+
}).catch(() => {
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}, [autoConnect]);
|
|
1300
|
+
useEffect4(() => {
|
|
1301
|
+
if (typeof window === "undefined") return;
|
|
1302
|
+
const cleanupReady = ZucchiniWalletAdapter.onReady(() => {
|
|
1303
|
+
const savedWalletId = localStorage.getItem("cicero_wallet_id");
|
|
1304
|
+
const savedAddress = localStorage.getItem("cicero_address");
|
|
1305
|
+
if (savedWalletId === "zucchini" && autoConnect) {
|
|
1306
|
+
const zucchini = walletsRef.current.find((w) => w.id === "zucchini");
|
|
1307
|
+
if (zucchini) {
|
|
1308
|
+
setAccount((prev) => {
|
|
1309
|
+
if (prev.isConnected && prev.wallet?.id === "zucchini") return prev;
|
|
1310
|
+
return {
|
|
1311
|
+
address: savedAddress || null,
|
|
1312
|
+
viewingKey: null,
|
|
1313
|
+
isConnected: true,
|
|
1314
|
+
isConnecting: false,
|
|
1315
|
+
status: "connected",
|
|
1316
|
+
wallet: zucchini,
|
|
1317
|
+
error: null
|
|
1318
|
+
};
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
});
|
|
1323
|
+
return () => {
|
|
1324
|
+
cleanupReady();
|
|
1325
|
+
};
|
|
1326
|
+
}, [autoConnect]);
|
|
1327
|
+
useEffect4(() => {
|
|
1328
|
+
if (account.isConnected) {
|
|
1329
|
+
setBalance((prev) => {
|
|
1330
|
+
const newUsd = convertZecToUsd(prev.zec);
|
|
1331
|
+
if (prev.usd === newUsd) return prev;
|
|
1332
|
+
return {
|
|
1333
|
+
...prev,
|
|
1334
|
+
usd: newUsd
|
|
1335
|
+
};
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
}, [account.isConnected, balance.zec, convertZecToUsd]);
|
|
1339
|
+
const switchNetwork = useCallback2((newNet) => {
|
|
1340
|
+
setNetwork(newNet);
|
|
1341
|
+
if (typeof window !== "undefined") {
|
|
1342
|
+
localStorage.setItem("cicero_network", newNet);
|
|
1343
|
+
}
|
|
1344
|
+
}, []);
|
|
1345
|
+
const connect = useCallback2(
|
|
1346
|
+
async (walletId, customAddressOrKey) => {
|
|
1347
|
+
const targetWallet = wallets.find((w) => w.id === walletId);
|
|
1348
|
+
if (!targetWallet) {
|
|
1349
|
+
throw new Error(`Wallet ${walletId} not found in registered wallets`);
|
|
1350
|
+
}
|
|
1351
|
+
setAccount((prev) => ({
|
|
1352
|
+
...prev,
|
|
1353
|
+
isConnecting: true,
|
|
1354
|
+
status: "connecting",
|
|
1355
|
+
error: null
|
|
1356
|
+
}));
|
|
1357
|
+
try {
|
|
1358
|
+
let session;
|
|
1359
|
+
if (walletId === "viewing-key" && customAddressOrKey) {
|
|
1360
|
+
session = {
|
|
1361
|
+
address: customAddressOrKey,
|
|
1362
|
+
viewingKey: customAddressOrKey.startsWith("zx") || customAddressOrKey.startsWith("uview") ? customAddressOrKey : void 0,
|
|
1363
|
+
connected: true,
|
|
1364
|
+
network,
|
|
1365
|
+
walletId: "viewing-key"
|
|
1366
|
+
};
|
|
1367
|
+
} else {
|
|
1368
|
+
session = await targetWallet.connect({ network });
|
|
1369
|
+
}
|
|
1370
|
+
const resolvedAddress = session.address || customAddressOrKey || null;
|
|
1371
|
+
setAccount({
|
|
1372
|
+
address: resolvedAddress,
|
|
1373
|
+
viewingKey: session.viewingKey || null,
|
|
1374
|
+
isConnected: true,
|
|
1375
|
+
isConnecting: false,
|
|
1376
|
+
status: "connected",
|
|
1377
|
+
wallet: targetWallet,
|
|
1378
|
+
error: null
|
|
1379
|
+
});
|
|
1380
|
+
let resolvedZec = session.balance ?? null;
|
|
1381
|
+
if (resolvedZec === null && targetWallet.getBalance) {
|
|
1382
|
+
try {
|
|
1383
|
+
resolvedZec = await targetWallet.getBalance({
|
|
1384
|
+
address: resolvedAddress || void 0,
|
|
1385
|
+
network
|
|
1386
|
+
});
|
|
1387
|
+
} catch {
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
if (resolvedZec === null && typeof window !== "undefined") {
|
|
1391
|
+
const cached = localStorage.getItem("cicero_balance");
|
|
1392
|
+
if (cached && !isNaN(parseFloat(cached))) {
|
|
1393
|
+
resolvedZec = parseFloat(cached);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
const activeZec = resolvedZec !== null && !isNaN(resolvedZec) ? resolvedZec : 0;
|
|
1397
|
+
setBalance({
|
|
1398
|
+
zec: activeZec,
|
|
1399
|
+
usd: convertZecToUsd(activeZec),
|
|
1400
|
+
isLoading: false
|
|
1401
|
+
});
|
|
1402
|
+
if (typeof window !== "undefined" && resolvedZec !== null) {
|
|
1403
|
+
localStorage.setItem("cicero_balance", resolvedZec.toString());
|
|
1404
|
+
}
|
|
1405
|
+
if (typeof window !== "undefined") {
|
|
1406
|
+
localStorage.setItem("cicero_wallet_id", walletId);
|
|
1407
|
+
if (resolvedAddress) {
|
|
1408
|
+
localStorage.setItem("cicero_address", resolvedAddress);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
setIsConnectModalOpen(false);
|
|
1412
|
+
} catch (err) {
|
|
1413
|
+
const msg = err instanceof Error ? err.message : "Connection failed";
|
|
1414
|
+
setAccount((prev) => ({
|
|
1415
|
+
...prev,
|
|
1416
|
+
isConnecting: false,
|
|
1417
|
+
status: "error",
|
|
1418
|
+
error: msg
|
|
1419
|
+
}));
|
|
1420
|
+
throw err;
|
|
1421
|
+
}
|
|
1422
|
+
},
|
|
1423
|
+
[wallets, network, convertZecToUsd]
|
|
1424
|
+
);
|
|
1425
|
+
const disconnect = useCallback2(async () => {
|
|
1426
|
+
if (account.wallet?.disconnect) {
|
|
1427
|
+
try {
|
|
1428
|
+
await account.wallet.disconnect();
|
|
1429
|
+
} catch {
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
setAccount({
|
|
1433
|
+
address: null,
|
|
1434
|
+
viewingKey: null,
|
|
1435
|
+
isConnected: false,
|
|
1436
|
+
isConnecting: false,
|
|
1437
|
+
status: "disconnected",
|
|
1438
|
+
wallet: null,
|
|
1439
|
+
error: null
|
|
1440
|
+
});
|
|
1441
|
+
setBalance({
|
|
1442
|
+
zec: 0,
|
|
1443
|
+
usd: null,
|
|
1444
|
+
isLoading: false
|
|
1445
|
+
});
|
|
1446
|
+
if (typeof window !== "undefined") {
|
|
1447
|
+
localStorage.removeItem("cicero_wallet_id");
|
|
1448
|
+
localStorage.removeItem("cicero_address");
|
|
1449
|
+
localStorage.removeItem("cicero_balance");
|
|
1450
|
+
}
|
|
1451
|
+
setIsAccountModalOpen(false);
|
|
1452
|
+
}, [account.wallet]);
|
|
1453
|
+
const sendPayment = useCallback2(
|
|
1454
|
+
async (params) => {
|
|
1455
|
+
if (!account.wallet) {
|
|
1456
|
+
throw new Error("No wallet connected. Please connect a Zcash wallet first.");
|
|
1457
|
+
}
|
|
1458
|
+
if (account.wallet.sendPayment) {
|
|
1459
|
+
return account.wallet.sendPayment(params);
|
|
1460
|
+
}
|
|
1461
|
+
throw new Error(`Wallet ${account.wallet.name} does not support programmatic send.`);
|
|
1462
|
+
},
|
|
1463
|
+
[account.wallet]
|
|
1464
|
+
);
|
|
1465
|
+
const refetchBalance = useCallback2(async () => {
|
|
1466
|
+
if (!account.wallet) return;
|
|
1467
|
+
setBalance((prev) => ({ ...prev, isLoading: true }));
|
|
1468
|
+
try {
|
|
1469
|
+
let freshZec = null;
|
|
1470
|
+
if (account.wallet.getBalance) {
|
|
1471
|
+
freshZec = await account.wallet.getBalance({
|
|
1472
|
+
address: account.address || void 0,
|
|
1473
|
+
network
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
if (freshZec !== null && !isNaN(freshZec)) {
|
|
1477
|
+
setBalance({
|
|
1478
|
+
zec: freshZec,
|
|
1479
|
+
usd: convertZecToUsd(freshZec),
|
|
1480
|
+
isLoading: false
|
|
1481
|
+
});
|
|
1482
|
+
if (typeof window !== "undefined") {
|
|
1483
|
+
localStorage.setItem("cicero_balance", freshZec.toString());
|
|
1484
|
+
}
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
} catch {
|
|
1488
|
+
}
|
|
1489
|
+
setBalance((prev) => ({
|
|
1490
|
+
...prev,
|
|
1491
|
+
isLoading: false,
|
|
1492
|
+
usd: convertZecToUsd(prev.zec)
|
|
1493
|
+
}));
|
|
1494
|
+
}, [account.wallet, account.address, network, convertZecToUsd]);
|
|
1495
|
+
const openConnectModal = useCallback2(() => setIsConnectModalOpen(true), []);
|
|
1496
|
+
const closeConnectModal = useCallback2(() => setIsConnectModalOpen(false), []);
|
|
1497
|
+
const openAccountModal = useCallback2(() => setIsAccountModalOpen(true), []);
|
|
1498
|
+
const closeAccountModal = useCallback2(() => setIsAccountModalOpen(false), []);
|
|
1499
|
+
const value = useMemo(
|
|
1500
|
+
() => ({
|
|
1501
|
+
account,
|
|
1502
|
+
address: account.address,
|
|
1503
|
+
isConnected: account.isConnected,
|
|
1504
|
+
isConnecting: account.isConnecting,
|
|
1505
|
+
wallet: account.wallet,
|
|
1506
|
+
network,
|
|
1507
|
+
switchNetwork,
|
|
1508
|
+
balance,
|
|
1509
|
+
refetchBalance,
|
|
1510
|
+
connect,
|
|
1511
|
+
disconnect,
|
|
1512
|
+
sendPayment,
|
|
1513
|
+
isConnectModalOpen,
|
|
1514
|
+
openConnectModal,
|
|
1515
|
+
closeConnectModal,
|
|
1516
|
+
isAccountModalOpen,
|
|
1517
|
+
openAccountModal,
|
|
1518
|
+
closeAccountModal,
|
|
1519
|
+
wallets,
|
|
1520
|
+
appName,
|
|
1521
|
+
appLogo
|
|
1522
|
+
}),
|
|
1523
|
+
[
|
|
1524
|
+
account,
|
|
1525
|
+
network,
|
|
1526
|
+
switchNetwork,
|
|
1527
|
+
balance,
|
|
1528
|
+
refetchBalance,
|
|
1529
|
+
connect,
|
|
1530
|
+
disconnect,
|
|
1531
|
+
sendPayment,
|
|
1532
|
+
isConnectModalOpen,
|
|
1533
|
+
openConnectModal,
|
|
1534
|
+
closeConnectModal,
|
|
1535
|
+
isAccountModalOpen,
|
|
1536
|
+
openAccountModal,
|
|
1537
|
+
closeAccountModal,
|
|
1538
|
+
wallets,
|
|
1539
|
+
appName,
|
|
1540
|
+
appLogo
|
|
1541
|
+
]
|
|
1542
|
+
);
|
|
1543
|
+
return /* @__PURE__ */ jsxs3(CiceroContext.Provider, { value, children: [
|
|
1544
|
+
children,
|
|
1545
|
+
/* @__PURE__ */ jsx4(CiceroConnectModal, {}),
|
|
1546
|
+
/* @__PURE__ */ jsx4(CiceroAccountModal, {})
|
|
1547
|
+
] });
|
|
1548
|
+
};
|
|
1549
|
+
function useCicero() {
|
|
1550
|
+
const ctx = useContext(CiceroContext);
|
|
1551
|
+
if (!ctx) {
|
|
1552
|
+
throw new Error("useCicero must be used within a <CiceroProvider>");
|
|
1553
|
+
}
|
|
1554
|
+
return ctx;
|
|
1555
|
+
}
|
|
1556
|
+
var useAccount = () => {
|
|
1557
|
+
const { account, address, isConnected, isConnecting, wallet, disconnect } = useCicero();
|
|
1558
|
+
return { ...account, address, isConnected, isConnecting, wallet, disconnect };
|
|
1559
|
+
};
|
|
1560
|
+
var useBalance = () => {
|
|
1561
|
+
const { balance, refetchBalance } = useCicero();
|
|
1562
|
+
return { ...balance, refetch: refetchBalance };
|
|
1563
|
+
};
|
|
1564
|
+
var useConnectModal = () => {
|
|
1565
|
+
const { isConnectModalOpen, openConnectModal, closeConnectModal } = useCicero();
|
|
1566
|
+
return {
|
|
1567
|
+
isOpen: isConnectModalOpen,
|
|
1568
|
+
openConnectModal,
|
|
1569
|
+
closeConnectModal
|
|
1570
|
+
};
|
|
1571
|
+
};
|
|
1572
|
+
var useAccountModal = () => {
|
|
1573
|
+
const { isAccountModalOpen, openAccountModal, closeAccountModal } = useCicero();
|
|
1574
|
+
return {
|
|
1575
|
+
isOpen: isAccountModalOpen,
|
|
1576
|
+
openAccountModal,
|
|
1577
|
+
closeAccountModal
|
|
1578
|
+
};
|
|
1579
|
+
};
|
|
1580
|
+
|
|
1581
|
+
// src/components/CiceroConnectButton.tsx
|
|
1582
|
+
import { Shield as Shield3, ChevronDown } from "lucide-react";
|
|
1583
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1584
|
+
var CiceroConnectButton = ({
|
|
1585
|
+
label = "Connect Zcash Wallet",
|
|
1586
|
+
variant = "primary",
|
|
1587
|
+
size = "md",
|
|
1588
|
+
showBalance = true,
|
|
1589
|
+
showNetwork = true,
|
|
1590
|
+
className = ""
|
|
1591
|
+
}) => {
|
|
1592
|
+
const {
|
|
1593
|
+
account,
|
|
1594
|
+
address,
|
|
1595
|
+
isConnected,
|
|
1596
|
+
isConnecting,
|
|
1597
|
+
network,
|
|
1598
|
+
balance,
|
|
1599
|
+
openConnectModal,
|
|
1600
|
+
openAccountModal
|
|
1601
|
+
} = useCicero();
|
|
1602
|
+
const sizeClasses = {
|
|
1603
|
+
sm: "text-xs px-2.5 py-1.5 gap-1.5",
|
|
1604
|
+
md: "text-sm px-3.5 py-2 gap-2",
|
|
1605
|
+
lg: "text-base px-4.5 py-2.5 gap-2.5"
|
|
1606
|
+
}[size];
|
|
1607
|
+
const variantClasses = {
|
|
1608
|
+
primary: "bg-[#3ecf8e] text-[#171717] hover:bg-[#24b47e] font-semibold shadow-sm",
|
|
1609
|
+
outline: "bg-white text-[#171717] border border-[#dfdfdf] hover:border-[#c7c7c7] hover:bg-[#fafafa] font-medium shadow-sm",
|
|
1610
|
+
dark: "bg-[#1c1c1c] text-white hover:bg-[#242424] font-medium shadow-sm"
|
|
1611
|
+
}[variant];
|
|
1612
|
+
return /* @__PURE__ */ jsx5(Fragment2, { children: !isConnected ? /* @__PURE__ */ jsxs4(
|
|
1613
|
+
"button",
|
|
1614
|
+
{
|
|
1615
|
+
type: "button",
|
|
1616
|
+
onClick: openConnectModal,
|
|
1617
|
+
disabled: isConnecting,
|
|
1618
|
+
className: `inline-flex items-center justify-center rounded-[6px] transition-all select-none ${sizeClasses} ${variantClasses} ${className}`,
|
|
1619
|
+
children: [
|
|
1620
|
+
/* @__PURE__ */ jsx5(
|
|
1621
|
+
Shield3,
|
|
1622
|
+
{
|
|
1623
|
+
className: `flex-shrink-0 ${size === "sm" ? "w-3.5 h-3.5" : size === "lg" ? "w-5 h-5" : "w-4 h-4"} ${variant === "primary" ? "fill-[#171717]/20" : "text-[#3ecf8e]"}`
|
|
1624
|
+
}
|
|
1625
|
+
),
|
|
1626
|
+
/* @__PURE__ */ jsx5("span", { children: isConnecting ? "Connecting..." : label })
|
|
1627
|
+
]
|
|
1628
|
+
}
|
|
1629
|
+
) : (
|
|
1630
|
+
/* Connected State Pill (RainbowKit style) */
|
|
1631
|
+
/* @__PURE__ */ jsxs4("div", { className: `inline-flex items-center rounded-[6px] border border-[#dfdfdf] bg-white p-0.5 shadow-sm text-xs ${className}`, children: [
|
|
1632
|
+
showNetwork && /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1.5 px-2.5 py-1 text-[11px] font-medium text-[#707070] border-r border-[#ededed]", children: [
|
|
1633
|
+
/* @__PURE__ */ jsx5(
|
|
1634
|
+
"span",
|
|
1635
|
+
{
|
|
1636
|
+
className: `w-2 h-2 rounded-full ${network === "mainnet" ? "bg-[#3ecf8e]" : "bg-amber-400"}`
|
|
1637
|
+
}
|
|
1638
|
+
),
|
|
1639
|
+
/* @__PURE__ */ jsx5("span", { className: "capitalize", children: network })
|
|
1640
|
+
] }),
|
|
1641
|
+
showBalance && /* @__PURE__ */ jsxs4("div", { className: "px-2.5 py-1 font-mono font-medium text-[#171717] border-r border-[#ededed]", children: [
|
|
1642
|
+
/* @__PURE__ */ jsx5("span", { children: balance.zec }),
|
|
1643
|
+
" ",
|
|
1644
|
+
/* @__PURE__ */ jsx5("span", { className: "text-[#707070] font-normal", children: "ZEC" })
|
|
1645
|
+
] }),
|
|
1646
|
+
/* @__PURE__ */ jsxs4(
|
|
1647
|
+
"button",
|
|
1648
|
+
{
|
|
1649
|
+
type: "button",
|
|
1650
|
+
onClick: openAccountModal,
|
|
1651
|
+
className: "flex items-center gap-1.5 px-2.5 py-1 rounded-[4px] hover:bg-[#fafafa] text-[#171717] font-medium transition-colors",
|
|
1652
|
+
children: [
|
|
1653
|
+
/* @__PURE__ */ jsx5("span", { className: "text-sm", children: account.wallet?.icon || "\u{1F6E1}\uFE0F" }),
|
|
1654
|
+
/* @__PURE__ */ jsx5("span", { className: "font-mono text-[11px]", children: address ? truncateAddress(address, 6, 4) : "Connected" }),
|
|
1655
|
+
/* @__PURE__ */ jsx5(ChevronDown, { className: "w-3 h-3 text-[#707070]" })
|
|
1656
|
+
]
|
|
1657
|
+
}
|
|
1658
|
+
)
|
|
1659
|
+
] })
|
|
1660
|
+
) });
|
|
1661
|
+
};
|
|
1662
|
+
|
|
1663
|
+
// src/components/CiceroPayButton.tsx
|
|
1664
|
+
import { Shield as Shield5 } from "lucide-react";
|
|
1665
|
+
|
|
1666
|
+
// src/hooks/useCiceroPayment.ts
|
|
1667
|
+
import { useState as useState5, useEffect as useEffect5, useCallback as useCallback3, useMemo as useMemo2, useRef as useRef2 } from "react";
|
|
1668
|
+
function useCiceroPayment({
|
|
1669
|
+
recipient,
|
|
1670
|
+
amount,
|
|
1671
|
+
memo,
|
|
1672
|
+
message,
|
|
1673
|
+
network = "mainnet",
|
|
1674
|
+
expiresInSeconds = 900,
|
|
1675
|
+
// 15 mins
|
|
1676
|
+
invoiceId,
|
|
1677
|
+
customRpcUrl,
|
|
1678
|
+
onSuccess,
|
|
1679
|
+
onError,
|
|
1680
|
+
onStatusChange
|
|
1681
|
+
}) {
|
|
1682
|
+
const onSuccessRef = useRef2(onSuccess);
|
|
1683
|
+
onSuccessRef.current = onSuccess;
|
|
1684
|
+
const onErrorRef = useRef2(onError);
|
|
1685
|
+
onErrorRef.current = onError;
|
|
1686
|
+
const onStatusChangeRef = useRef2(onStatusChange);
|
|
1687
|
+
onStatusChangeRef.current = onStatusChange;
|
|
1688
|
+
const [status, setStatus] = useState5("pending");
|
|
1689
|
+
const [txId, setTxId] = useState5(null);
|
|
1690
|
+
const [secondsRemaining, setSecondsRemaining] = useState5(expiresInSeconds);
|
|
1691
|
+
const [isModalOpen, setIsModalOpen] = useState5(false);
|
|
1692
|
+
const [zucchiniAvailable, setZucchiniAvailable] = useState5(false);
|
|
1693
|
+
const [zucchiniAdapter] = useState5(() => new ZucchiniWalletAdapter());
|
|
1694
|
+
const [zucchiniPaying, setZucchiniPaying] = useState5(false);
|
|
1695
|
+
useEffect5(() => {
|
|
1696
|
+
setZucchiniAvailable(ZucchiniWalletAdapter.isAvailable());
|
|
1697
|
+
}, []);
|
|
1698
|
+
const addressType = useMemo2(() => getAddressType(recipient), [recipient]);
|
|
1699
|
+
const isShielded = useMemo2(() => isShieldedAddress(recipient), [recipient]);
|
|
1700
|
+
const memoValidation = useMemo2(
|
|
1701
|
+
() => validateMemoSupport(recipient, memo),
|
|
1702
|
+
[recipient, memo]
|
|
1703
|
+
);
|
|
1704
|
+
const lastWarningRef = useRef2(null);
|
|
1705
|
+
useEffect5(() => {
|
|
1706
|
+
if (!memoValidation.valid && memoValidation.warning) {
|
|
1707
|
+
if (lastWarningRef.current !== memoValidation.warning) {
|
|
1708
|
+
lastWarningRef.current = memoValidation.warning;
|
|
1709
|
+
onErrorRef.current?.(new Error(memoValidation.warning));
|
|
1710
|
+
}
|
|
1711
|
+
} else {
|
|
1712
|
+
lastWarningRef.current = null;
|
|
1713
|
+
}
|
|
1714
|
+
}, [memoValidation.valid, memoValidation.warning]);
|
|
1715
|
+
const zip321Uri = useMemo2(() => {
|
|
1716
|
+
return generateZip321Uri({
|
|
1717
|
+
address: recipient,
|
|
1718
|
+
amount,
|
|
1719
|
+
memo,
|
|
1720
|
+
message
|
|
1721
|
+
});
|
|
1722
|
+
}, [recipient, amount, memo, message]);
|
|
1723
|
+
const deepLinks = useMemo2(() => getWalletDeepLinks(zip321Uri), [zip321Uri]);
|
|
1724
|
+
const updateStatus = useCallback3((newStatus) => {
|
|
1725
|
+
setStatus(newStatus);
|
|
1726
|
+
onStatusChangeRef.current?.(newStatus);
|
|
1727
|
+
}, []);
|
|
1728
|
+
useEffect5(() => {
|
|
1729
|
+
if (status !== "pending") return;
|
|
1730
|
+
const timer = setInterval(() => {
|
|
1731
|
+
setSecondsRemaining((prev) => {
|
|
1732
|
+
if (prev <= 1) {
|
|
1733
|
+
clearInterval(timer);
|
|
1734
|
+
updateStatus("expired");
|
|
1735
|
+
return 0;
|
|
1736
|
+
}
|
|
1737
|
+
return prev - 1;
|
|
1738
|
+
});
|
|
1739
|
+
}, 1e3);
|
|
1740
|
+
return () => clearInterval(timer);
|
|
1741
|
+
}, [status, updateStatus]);
|
|
1742
|
+
const resetPayment = useCallback3(() => {
|
|
1743
|
+
setStatus("pending");
|
|
1744
|
+
setTxId(null);
|
|
1745
|
+
setSecondsRemaining(expiresInSeconds);
|
|
1746
|
+
setZucchiniPaying(false);
|
|
1747
|
+
}, [expiresInSeconds]);
|
|
1748
|
+
const openModal = useCallback3(() => {
|
|
1749
|
+
resetPayment();
|
|
1750
|
+
setIsModalOpen(true);
|
|
1751
|
+
}, [resetPayment]);
|
|
1752
|
+
const closeModal = useCallback3(() => {
|
|
1753
|
+
setIsModalOpen(false);
|
|
1754
|
+
}, []);
|
|
1755
|
+
const payWithZucchini = useCallback3(async () => {
|
|
1756
|
+
setZucchiniPaying(true);
|
|
1757
|
+
try {
|
|
1758
|
+
await zucchiniAdapter.connect();
|
|
1759
|
+
const numAmount = typeof amount === "number" ? amount : parseFloat(amount);
|
|
1760
|
+
const result = await zucchiniAdapter.requestPayment({
|
|
1761
|
+
recipient,
|
|
1762
|
+
amount: numAmount,
|
|
1763
|
+
memo
|
|
1764
|
+
});
|
|
1765
|
+
updateStatus("detected");
|
|
1766
|
+
setTxId(result.txId);
|
|
1767
|
+
setTimeout(() => {
|
|
1768
|
+
updateStatus("confirmed");
|
|
1769
|
+
onSuccessRef.current?.({
|
|
1770
|
+
txId: result.txId,
|
|
1771
|
+
amount: isNaN(numAmount) ? 0 : numAmount,
|
|
1772
|
+
memo
|
|
1773
|
+
});
|
|
1774
|
+
setZucchiniPaying(false);
|
|
1775
|
+
}, 1500);
|
|
1776
|
+
} catch (err) {
|
|
1777
|
+
const error = err instanceof Error ? err : new Error("Zucchini payment failed");
|
|
1778
|
+
onErrorRef.current?.(error);
|
|
1779
|
+
setZucchiniPaying(false);
|
|
1780
|
+
}
|
|
1781
|
+
}, [recipient, amount, memo, zucchiniAdapter, updateStatus]);
|
|
1782
|
+
const simulateDetection = useCallback3(() => {
|
|
1783
|
+
updateStatus("detected");
|
|
1784
|
+
setTimeout(() => {
|
|
1785
|
+
const mockTxId = `0x${Array.from(
|
|
1786
|
+
{ length: 64 },
|
|
1787
|
+
() => Math.floor(Math.random() * 16).toString(16)
|
|
1788
|
+
).join("")}`;
|
|
1789
|
+
setTxId(mockTxId);
|
|
1790
|
+
updateStatus("confirmed");
|
|
1791
|
+
const numAmount = typeof amount === "number" ? amount : parseFloat(amount);
|
|
1792
|
+
onSuccessRef.current?.({
|
|
1793
|
+
txId: mockTxId,
|
|
1794
|
+
amount: isNaN(numAmount) ? 0 : numAmount,
|
|
1795
|
+
memo
|
|
1796
|
+
});
|
|
1797
|
+
}, 2e3);
|
|
1798
|
+
}, [amount, memo, updateStatus]);
|
|
1799
|
+
return {
|
|
1800
|
+
status,
|
|
1801
|
+
txId,
|
|
1802
|
+
secondsRemaining,
|
|
1803
|
+
isModalOpen,
|
|
1804
|
+
openModal,
|
|
1805
|
+
closeModal,
|
|
1806
|
+
resetPayment,
|
|
1807
|
+
zip321Uri,
|
|
1808
|
+
deepLinks,
|
|
1809
|
+
addressType,
|
|
1810
|
+
isShielded,
|
|
1811
|
+
memoValidation,
|
|
1812
|
+
simulateDetection,
|
|
1813
|
+
invoiceId,
|
|
1814
|
+
network,
|
|
1815
|
+
customRpcUrl,
|
|
1816
|
+
// Zucchini Wallet
|
|
1817
|
+
zucchiniAvailable,
|
|
1818
|
+
zucchiniPaying,
|
|
1819
|
+
payWithZucchini
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
// src/components/CiceroModal.tsx
|
|
1824
|
+
import { useState as useState6, useEffect as useEffect6 } from "react";
|
|
1825
|
+
import { createPortal as createPortal3 } from "react-dom";
|
|
1826
|
+
import {
|
|
1827
|
+
Shield as Shield4,
|
|
1828
|
+
Copy as Copy2,
|
|
1829
|
+
Check as Check2,
|
|
1830
|
+
ExternalLink as ExternalLink3,
|
|
1831
|
+
Clock,
|
|
1832
|
+
CheckCircle2,
|
|
1833
|
+
AlertCircle as AlertCircle2,
|
|
1834
|
+
X as X3,
|
|
1835
|
+
Lock,
|
|
1836
|
+
Sparkles as Sparkles2,
|
|
1837
|
+
Zap
|
|
1838
|
+
} from "lucide-react";
|
|
1839
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1840
|
+
var CiceroModal = ({
|
|
1841
|
+
isOpen,
|
|
1842
|
+
onClose,
|
|
1843
|
+
recipient,
|
|
1844
|
+
amount,
|
|
1845
|
+
memo,
|
|
1846
|
+
message,
|
|
1847
|
+
network = "mainnet",
|
|
1848
|
+
status,
|
|
1849
|
+
txId,
|
|
1850
|
+
secondsRemaining,
|
|
1851
|
+
zip321Uri,
|
|
1852
|
+
addressType,
|
|
1853
|
+
isShielded,
|
|
1854
|
+
memoValidation,
|
|
1855
|
+
onSimulateDetection,
|
|
1856
|
+
onResetPayment,
|
|
1857
|
+
title = "Pay with Shielded Zcash",
|
|
1858
|
+
subtitle = "Zero-Knowledge, Confidential Checkout",
|
|
1859
|
+
showUsd = true,
|
|
1860
|
+
zucchiniAvailable = false,
|
|
1861
|
+
zucchiniPaying = false,
|
|
1862
|
+
onPayWithZucchini
|
|
1863
|
+
}) => {
|
|
1864
|
+
const [copiedType, setCopiedType] = useState6(null);
|
|
1865
|
+
const { convertZecToUsd } = useZcashPrice(showUsd);
|
|
1866
|
+
useEffect6(() => {
|
|
1867
|
+
const handleKeyDown = (e) => {
|
|
1868
|
+
if (e.key === "Escape" && isOpen) {
|
|
1869
|
+
onClose();
|
|
1870
|
+
}
|
|
1871
|
+
};
|
|
1872
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
1873
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1874
|
+
}, [isOpen, onClose]);
|
|
1875
|
+
const [mounted, setMounted] = useState6(false);
|
|
1876
|
+
useEffect6(() => {
|
|
1877
|
+
setMounted(true);
|
|
1878
|
+
}, []);
|
|
1879
|
+
if (!isOpen || !mounted) return null;
|
|
1880
|
+
const copyToClipboard = async (text, type) => {
|
|
1881
|
+
try {
|
|
1882
|
+
await navigator.clipboard.writeText(text);
|
|
1883
|
+
setCopiedType(type);
|
|
1884
|
+
setTimeout(() => setCopiedType(null), 2e3);
|
|
1885
|
+
} catch {
|
|
1886
|
+
}
|
|
1887
|
+
};
|
|
1888
|
+
const formatTime = (secs) => {
|
|
1889
|
+
const m = Math.floor(secs / 60);
|
|
1890
|
+
const s = secs % 60;
|
|
1891
|
+
return `${m}:${s < 10 ? "0" : ""}${s}`;
|
|
1892
|
+
};
|
|
1893
|
+
const usdValue = convertZecToUsd(amount);
|
|
1894
|
+
return createPortal3(
|
|
1895
|
+
/* @__PURE__ */ jsxs5("div", { className: "fixed inset-0 z-[99999] flex items-center justify-center p-4 sm:p-6 overflow-y-auto", children: [
|
|
1896
|
+
/* @__PURE__ */ jsx6(
|
|
1897
|
+
"div",
|
|
1898
|
+
{
|
|
1899
|
+
className: "fixed inset-0 bg-[#171717]/60 backdrop-blur-sm transition-opacity z-[99999]",
|
|
1900
|
+
onClick: onClose
|
|
1901
|
+
}
|
|
1902
|
+
),
|
|
1903
|
+
/* @__PURE__ */ jsxs5("div", { className: "relative w-full max-w-lg bg-white rounded-xl border border-[#ededed] shadow-2xl overflow-hidden z-[100000] text-[#171717] font-sans animate-in fade-in zoom-in-95 duration-150", children: [
|
|
1904
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between px-6 py-4 border-b border-[#ededed] bg-[#fafafa]", children: [
|
|
1905
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2.5", children: [
|
|
1906
|
+
/* @__PURE__ */ jsx6("div", { className: "w-8 h-8 rounded-md bg-[#171717] flex items-center justify-center text-[#3ecf8e]", children: /* @__PURE__ */ jsx6(Shield4, { className: "w-4 h-4 fill-[#3ecf8e]" }) }),
|
|
1907
|
+
/* @__PURE__ */ jsxs5("div", { children: [
|
|
1908
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
|
|
1909
|
+
/* @__PURE__ */ jsx6("span", { className: "font-medium text-sm text-[#171717]", children: title }),
|
|
1910
|
+
/* @__PURE__ */ jsx6("span", { className: "text-[10px] uppercase tracking-wider font-semibold px-2 py-0.5 rounded-full bg-[#3ecf8e]/15 text-[#24b47e] border border-[#3ecf8e]/30", children: isShielded ? "zk-Shielded" : "Transparent" }),
|
|
1911
|
+
network === "testnet" && /* @__PURE__ */ jsx6("span", { className: "text-[10px] uppercase tracking-wider font-semibold px-2 py-0.5 rounded-full bg-amber-100 text-amber-800 border border-amber-200", children: "Testnet" })
|
|
1912
|
+
] }),
|
|
1913
|
+
/* @__PURE__ */ jsx6("p", { className: "text-xs text-[#707070] mt-0.5", children: subtitle })
|
|
1914
|
+
] })
|
|
1915
|
+
] }),
|
|
1916
|
+
/* @__PURE__ */ jsx6(
|
|
1917
|
+
"button",
|
|
1918
|
+
{
|
|
1919
|
+
onClick: onClose,
|
|
1920
|
+
className: "p-1 rounded-md text-[#707070] hover:text-[#171717] hover:bg-[#ededed] transition-colors",
|
|
1921
|
+
"aria-label": "Close modal",
|
|
1922
|
+
children: /* @__PURE__ */ jsx6(X3, { className: "w-4 h-4" })
|
|
1923
|
+
}
|
|
1924
|
+
)
|
|
1925
|
+
] }),
|
|
1926
|
+
/* @__PURE__ */ jsxs5("div", { className: "px-6 py-2.5 bg-white border-b border-[#ededed] flex items-center justify-between text-xs", children: [
|
|
1927
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
|
|
1928
|
+
status === "pending" && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
1929
|
+
/* @__PURE__ */ jsxs5("span", { className: "relative flex h-2 w-2", children: [
|
|
1930
|
+
/* @__PURE__ */ jsx6("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-[#3ecf8e] opacity-75" }),
|
|
1931
|
+
/* @__PURE__ */ jsx6("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-[#24b47e]" })
|
|
1932
|
+
] }),
|
|
1933
|
+
/* @__PURE__ */ jsx6("span", { className: "text-[#707070]", children: "Listening for transaction..." })
|
|
1934
|
+
] }),
|
|
1935
|
+
status === "detected" && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
1936
|
+
/* @__PURE__ */ jsxs5("span", { className: "relative flex h-2 w-2", children: [
|
|
1937
|
+
/* @__PURE__ */ jsx6("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-400 opacity-75" }),
|
|
1938
|
+
/* @__PURE__ */ jsx6("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-amber-500" })
|
|
1939
|
+
] }),
|
|
1940
|
+
/* @__PURE__ */ jsx6("span", { className: "text-amber-800 font-medium", children: "Transaction spotted in mempool! Confirming..." })
|
|
1941
|
+
] }),
|
|
1942
|
+
status === "confirmed" && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
1943
|
+
/* @__PURE__ */ jsx6(CheckCircle2, { className: "w-4 h-4 text-[#24b47e]" }),
|
|
1944
|
+
/* @__PURE__ */ jsx6("span", { className: "text-[#24b47e] font-medium", children: "Payment Confirmed On-Chain!" })
|
|
1945
|
+
] }),
|
|
1946
|
+
status === "expired" && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
1947
|
+
/* @__PURE__ */ jsx6(AlertCircle2, { className: "w-4 h-4 text-rose-500" }),
|
|
1948
|
+
/* @__PURE__ */ jsx6("span", { className: "text-rose-600 font-medium", children: "Payment window expired" })
|
|
1949
|
+
] })
|
|
1950
|
+
] }),
|
|
1951
|
+
status === "pending" && /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1 text-[#707070] font-mono text-[11px]", children: [
|
|
1952
|
+
/* @__PURE__ */ jsx6(Clock, { className: "w-3 h-3" }),
|
|
1953
|
+
/* @__PURE__ */ jsx6("span", { children: formatTime(secondsRemaining) })
|
|
1954
|
+
] })
|
|
1955
|
+
] }),
|
|
1956
|
+
/* @__PURE__ */ jsx6("div", { className: "p-6 space-y-5", children: status === "confirmed" ? /* @__PURE__ */ jsxs5("div", { className: "py-6 text-center space-y-4", children: [
|
|
1957
|
+
/* @__PURE__ */ jsx6("div", { className: "w-16 h-16 mx-auto rounded-full bg-[#3ecf8e]/15 border border-[#3ecf8e]/40 flex items-center justify-center text-[#24b47e] animate-bounce", children: /* @__PURE__ */ jsx6(CheckCircle2, { className: "w-8 h-8" }) }),
|
|
1958
|
+
/* @__PURE__ */ jsxs5("div", { children: [
|
|
1959
|
+
/* @__PURE__ */ jsx6("h3", { className: "text-lg font-medium text-[#171717]", children: "Payment Received & Shielded!" }),
|
|
1960
|
+
/* @__PURE__ */ jsx6("p", { className: "text-xs text-[#707070] max-w-sm mx-auto mt-1", children: "Your transaction has been verified using zero-knowledge proofs. No sender or recipient address was leaked." })
|
|
1961
|
+
] }),
|
|
1962
|
+
txId && /* @__PURE__ */ jsxs5("div", { className: "p-3 bg-[#fafafa] rounded-md border border-[#ededed] text-left", children: [
|
|
1963
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between text-[11px] text-[#707070] mb-1", children: [
|
|
1964
|
+
/* @__PURE__ */ jsx6("span", { children: "Transaction Hash" }),
|
|
1965
|
+
/* @__PURE__ */ jsx6("span", { className: "text-[#24b47e] font-mono", children: "Shielded Note" })
|
|
1966
|
+
] }),
|
|
1967
|
+
/* @__PURE__ */ jsx6("div", { className: "font-mono text-xs text-[#171717] break-all", children: txId })
|
|
1968
|
+
] }),
|
|
1969
|
+
/* @__PURE__ */ jsx6(
|
|
1970
|
+
"button",
|
|
1971
|
+
{
|
|
1972
|
+
onClick: onClose,
|
|
1973
|
+
className: "w-full py-2.5 px-4 rounded-md bg-[#3ecf8e] text-[#171717] font-medium text-sm hover:bg-[#24b47e] transition-colors",
|
|
1974
|
+
children: "Close & Return"
|
|
1975
|
+
}
|
|
1976
|
+
)
|
|
1977
|
+
] }) : /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
1978
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between p-4 bg-[#fafafa] rounded-lg border border-[#ededed]", children: [
|
|
1979
|
+
/* @__PURE__ */ jsxs5("div", { children: [
|
|
1980
|
+
/* @__PURE__ */ jsx6("span", { className: "text-xs text-[#707070] uppercase font-mono tracking-wider", children: "Total Amount" }),
|
|
1981
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-baseline gap-2 mt-0.5", children: [
|
|
1982
|
+
/* @__PURE__ */ jsxs5("span", { className: "text-2xl font-semibold tracking-tight text-[#171717] font-mono", children: [
|
|
1983
|
+
amount,
|
|
1984
|
+
" ",
|
|
1985
|
+
/* @__PURE__ */ jsx6("span", { className: "text-sm font-normal text-[#707070]", children: "ZEC" })
|
|
1986
|
+
] }),
|
|
1987
|
+
usdValue && /* @__PURE__ */ jsxs5("span", { className: "text-xs text-[#707070] font-mono", children: [
|
|
1988
|
+
"\u2248 $",
|
|
1989
|
+
usdValue,
|
|
1990
|
+
" USD"
|
|
1991
|
+
] })
|
|
1992
|
+
] })
|
|
1993
|
+
] }),
|
|
1994
|
+
/* @__PURE__ */ jsxs5("div", { className: "text-right", children: [
|
|
1995
|
+
/* @__PURE__ */ jsx6("span", { className: "text-[11px] text-[#707070] block font-mono", children: "Address Standard" }),
|
|
1996
|
+
/* @__PURE__ */ jsx6("span", { className: "inline-block mt-0.5 px-2 py-0.5 text-xs font-mono font-medium rounded bg-white border border-[#dfdfdf] text-[#171717]", children: addressType === "unified" ? "Unified (ZIP-316)" : addressType === "sapling" ? "Sapling (Shielded)" : "Transparent" })
|
|
1997
|
+
] })
|
|
1998
|
+
] }),
|
|
1999
|
+
message && /* @__PURE__ */ jsxs5("div", { className: "px-3.5 py-2 bg-[#fafafa] rounded-md border border-[#ededed] text-xs text-[#707070]", children: [
|
|
2000
|
+
/* @__PURE__ */ jsx6("span", { className: "font-medium text-[#171717]", children: "Order Note: " }),
|
|
2001
|
+
/* @__PURE__ */ jsx6("span", { children: message })
|
|
2002
|
+
] }),
|
|
2003
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex flex-col sm:flex-row items-center gap-5 p-4 rounded-lg border border-[#ededed] bg-white", children: [
|
|
2004
|
+
/* @__PURE__ */ jsx6("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx6(CiceroQRCode, { value: zip321Uri, size: 150 }) }),
|
|
2005
|
+
/* @__PURE__ */ jsxs5("div", { className: "space-y-3 w-full text-center sm:text-left", children: [
|
|
2006
|
+
/* @__PURE__ */ jsxs5("div", { children: [
|
|
2007
|
+
/* @__PURE__ */ jsxs5("h4", { className: "text-sm font-medium text-[#171717] flex items-center justify-center sm:justify-start gap-1.5", children: [
|
|
2008
|
+
/* @__PURE__ */ jsx6("span", { children: "Scan with Shielded Wallet" }),
|
|
2009
|
+
/* @__PURE__ */ jsx6(Sparkles2, { className: "w-3.5 h-3.5 text-[#3ecf8e]" })
|
|
2010
|
+
] }),
|
|
2011
|
+
/* @__PURE__ */ jsxs5("p", { className: "text-xs text-[#707070] mt-0.5 leading-relaxed", children: [
|
|
2012
|
+
"Compatible with ",
|
|
2013
|
+
/* @__PURE__ */ jsx6("strong", { className: "text-[#171717]", children: "Zashi" }),
|
|
2014
|
+
", ",
|
|
2015
|
+
/* @__PURE__ */ jsx6("strong", { className: "text-[#171717]", children: "Ywallet" }),
|
|
2016
|
+
", or any ZIP-321 compliant Zcash wallet."
|
|
2017
|
+
] })
|
|
2018
|
+
] }),
|
|
2019
|
+
/* @__PURE__ */ jsxs5(
|
|
2020
|
+
"a",
|
|
2021
|
+
{
|
|
2022
|
+
href: zip321Uri,
|
|
2023
|
+
className: "inline-flex items-center justify-center gap-1.5 w-full py-2 px-3 rounded-md bg-[#171717] text-white text-xs font-medium hover:bg-[#212121] transition-colors",
|
|
2024
|
+
children: [
|
|
2025
|
+
/* @__PURE__ */ jsx6("span", { children: "Open in Mobile / Desktop Wallet" }),
|
|
2026
|
+
/* @__PURE__ */ jsx6(ExternalLink3, { className: "w-3.5 h-3.5 text-[#3ecf8e]" })
|
|
2027
|
+
]
|
|
2028
|
+
}
|
|
2029
|
+
),
|
|
2030
|
+
zucchiniAvailable && onPayWithZucchini && /* @__PURE__ */ jsx6(
|
|
2031
|
+
"button",
|
|
2032
|
+
{
|
|
2033
|
+
onClick: onPayWithZucchini,
|
|
2034
|
+
disabled: zucchiniPaying,
|
|
2035
|
+
className: "inline-flex items-center justify-center gap-2 w-full py-2.5 px-3 rounded-md bg-[#3ecf8e] text-[#171717] text-xs font-semibold hover:bg-[#24b47e] transition-colors disabled:opacity-60 disabled:cursor-wait shadow-sm",
|
|
2036
|
+
children: zucchiniPaying ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
2037
|
+
/* @__PURE__ */ jsx6("span", { className: "w-3.5 h-3.5 border-2 border-[#171717]/30 border-t-[#171717] rounded-full animate-spin" }),
|
|
2038
|
+
/* @__PURE__ */ jsx6("span", { children: "Confirming in Zucchini..." })
|
|
2039
|
+
] }) : /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
2040
|
+
/* @__PURE__ */ jsx6("span", { className: "text-sm", children: "\u{1F952}" }),
|
|
2041
|
+
/* @__PURE__ */ jsx6("span", { children: "Pay with Zucchini Wallet" })
|
|
2042
|
+
] })
|
|
2043
|
+
}
|
|
2044
|
+
),
|
|
2045
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
|
|
2046
|
+
/* @__PURE__ */ jsx6(
|
|
2047
|
+
"button",
|
|
2048
|
+
{
|
|
2049
|
+
onClick: () => copyToClipboard(zip321Uri, "uri"),
|
|
2050
|
+
className: "flex-1 py-1.5 px-2.5 rounded-md border border-[#ededed] hover:border-[#dfdfdf] hover:bg-[#fafafa] text-xs font-medium text-[#171717] transition-colors flex items-center justify-center gap-1",
|
|
2051
|
+
children: copiedType === "uri" ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
2052
|
+
/* @__PURE__ */ jsx6(Check2, { className: "w-3 h-3 text-[#24b47e]" }),
|
|
2053
|
+
/* @__PURE__ */ jsx6("span", { className: "text-[#24b47e]", children: "Copied URI" })
|
|
2054
|
+
] }) : /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
2055
|
+
/* @__PURE__ */ jsx6(Copy2, { className: "w-3 h-3 text-[#707070]" }),
|
|
2056
|
+
/* @__PURE__ */ jsx6("span", { children: "Copy ZIP-321 URI" })
|
|
2057
|
+
] })
|
|
2058
|
+
}
|
|
2059
|
+
),
|
|
2060
|
+
/* @__PURE__ */ jsx6(
|
|
2061
|
+
"button",
|
|
2062
|
+
{
|
|
2063
|
+
onClick: () => copyToClipboard(recipient, "address"),
|
|
2064
|
+
className: "flex-1 py-1.5 px-2.5 rounded-md border border-[#ededed] hover:border-[#dfdfdf] hover:bg-[#fafafa] text-xs font-medium text-[#171717] transition-colors flex items-center justify-center gap-1",
|
|
2065
|
+
children: copiedType === "address" ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
2066
|
+
/* @__PURE__ */ jsx6(Check2, { className: "w-3 h-3 text-[#24b47e]" }),
|
|
2067
|
+
/* @__PURE__ */ jsx6("span", { className: "text-[#24b47e]", children: "Copied Address" })
|
|
2068
|
+
] }) : /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
2069
|
+
/* @__PURE__ */ jsx6(Copy2, { className: "w-3 h-3 text-[#707070]" }),
|
|
2070
|
+
/* @__PURE__ */ jsx6("span", { children: "Copy Address" })
|
|
2071
|
+
] })
|
|
2072
|
+
}
|
|
2073
|
+
)
|
|
2074
|
+
] })
|
|
2075
|
+
] })
|
|
2076
|
+
] }),
|
|
2077
|
+
/* @__PURE__ */ jsxs5("div", { className: "space-y-1.5 text-xs", children: [
|
|
2078
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between text-[#707070]", children: [
|
|
2079
|
+
/* @__PURE__ */ jsx6("span", { children: "Recipient Address" }),
|
|
2080
|
+
/* @__PURE__ */ jsx6("span", { className: "font-mono text-[11px]", children: truncateAddress(recipient, 12, 10) })
|
|
2081
|
+
] }),
|
|
2082
|
+
/* @__PURE__ */ jsx6("div", { className: "p-2 bg-[#fafafa] rounded-md border border-[#ededed] font-mono text-[11px] text-[#171717] break-all select-all", children: recipient })
|
|
2083
|
+
] }),
|
|
2084
|
+
memo && /* @__PURE__ */ jsxs5("div", { className: "space-y-1.5 text-xs p-3 rounded-md bg-[#fafafa] border border-[#ededed]", children: [
|
|
2085
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
|
|
2086
|
+
/* @__PURE__ */ jsxs5("span", { className: "flex items-center gap-1.5 font-medium text-[#171717]", children: [
|
|
2087
|
+
/* @__PURE__ */ jsx6(Lock, { className: "w-3 h-3 text-[#3ecf8e]" }),
|
|
2088
|
+
/* @__PURE__ */ jsx6("span", { children: "Encrypted Memo" })
|
|
2089
|
+
] }),
|
|
2090
|
+
/* @__PURE__ */ jsxs5("span", { className: "text-[10px] font-mono text-[#707070]", children: [
|
|
2091
|
+
memoValidation.byteLength,
|
|
2092
|
+
"/512 bytes"
|
|
2093
|
+
] })
|
|
2094
|
+
] }),
|
|
2095
|
+
/* @__PURE__ */ jsx6("p", { className: "font-mono text-xs text-[#171717] bg-white p-2 rounded border border-[#dfdfdf]", children: memo }),
|
|
2096
|
+
/* @__PURE__ */ jsx6("p", { className: "text-[11px] text-[#707070]", children: "Only the recipient holding the viewing/spending key can decrypt this message on-chain." })
|
|
2097
|
+
] }),
|
|
2098
|
+
memoValidation.warning && /* @__PURE__ */ jsxs5("div", { className: "p-2.5 rounded-md bg-amber-50 border border-amber-200 text-amber-800 text-xs flex items-start gap-2", children: [
|
|
2099
|
+
/* @__PURE__ */ jsx6(AlertCircle2, { className: "w-4 h-4 flex-shrink-0 mt-0.5 text-amber-600" }),
|
|
2100
|
+
/* @__PURE__ */ jsx6("span", { children: memoValidation.warning })
|
|
2101
|
+
] }),
|
|
2102
|
+
/* @__PURE__ */ jsxs5("div", { className: "pt-2 border-t border-[#ededed] flex items-center justify-between", children: [
|
|
2103
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1.5 text-[11px] text-[#707070]", children: [
|
|
2104
|
+
/* @__PURE__ */ jsx6(Zap, { className: "w-3 h-3 text-[#3ecf8e]" }),
|
|
2105
|
+
/* @__PURE__ */ jsx6("span", { children: "Demo Controls:" })
|
|
2106
|
+
] }),
|
|
2107
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
|
|
2108
|
+
/* @__PURE__ */ jsx6(
|
|
2109
|
+
"button",
|
|
2110
|
+
{
|
|
2111
|
+
onClick: onSimulateDetection,
|
|
2112
|
+
className: "px-2.5 py-1 text-[11px] font-medium rounded border border-[#dfdfdf] hover:border-[#3ecf8e] hover:text-[#24b47e] bg-white text-[#171717] transition-colors",
|
|
2113
|
+
children: "Simulate Payment"
|
|
2114
|
+
}
|
|
2115
|
+
),
|
|
2116
|
+
status === "expired" && /* @__PURE__ */ jsx6(
|
|
2117
|
+
"button",
|
|
2118
|
+
{
|
|
2119
|
+
onClick: onResetPayment,
|
|
2120
|
+
className: "px-2.5 py-1 text-[11px] font-medium rounded bg-[#171717] text-white hover:bg-[#212121] transition-colors",
|
|
2121
|
+
children: "Refresh"
|
|
2122
|
+
}
|
|
2123
|
+
)
|
|
2124
|
+
] })
|
|
2125
|
+
] })
|
|
2126
|
+
] }) })
|
|
2127
|
+
] })
|
|
2128
|
+
] }),
|
|
2129
|
+
document.body
|
|
2130
|
+
);
|
|
2131
|
+
};
|
|
2132
|
+
|
|
2133
|
+
// src/components/CiceroPayButton.tsx
|
|
2134
|
+
import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2135
|
+
var CiceroPayButton = ({
|
|
2136
|
+
recipient,
|
|
2137
|
+
amount,
|
|
2138
|
+
memo,
|
|
2139
|
+
message,
|
|
2140
|
+
label = "Pay with Zcash",
|
|
2141
|
+
network = "mainnet",
|
|
2142
|
+
variant = "primary",
|
|
2143
|
+
size = "md",
|
|
2144
|
+
showUsd = true,
|
|
2145
|
+
modalTitle,
|
|
2146
|
+
modalSubtitle,
|
|
2147
|
+
expiresInSeconds = 900,
|
|
2148
|
+
customRpcUrl,
|
|
2149
|
+
invoiceId,
|
|
2150
|
+
onSuccess,
|
|
2151
|
+
onError,
|
|
2152
|
+
onStatusChange,
|
|
2153
|
+
className = "",
|
|
2154
|
+
disabled = false
|
|
2155
|
+
}) => {
|
|
2156
|
+
const {
|
|
2157
|
+
status,
|
|
2158
|
+
txId,
|
|
2159
|
+
secondsRemaining,
|
|
2160
|
+
isModalOpen,
|
|
2161
|
+
openModal,
|
|
2162
|
+
closeModal,
|
|
2163
|
+
resetPayment,
|
|
2164
|
+
zip321Uri,
|
|
2165
|
+
addressType,
|
|
2166
|
+
isShielded,
|
|
2167
|
+
memoValidation,
|
|
2168
|
+
simulateDetection,
|
|
2169
|
+
zucchiniAvailable,
|
|
2170
|
+
zucchiniPaying,
|
|
2171
|
+
payWithZucchini
|
|
2172
|
+
} = useCiceroPayment({
|
|
2173
|
+
recipient,
|
|
2174
|
+
amount,
|
|
2175
|
+
memo,
|
|
2176
|
+
message,
|
|
2177
|
+
network,
|
|
2178
|
+
expiresInSeconds,
|
|
2179
|
+
invoiceId,
|
|
2180
|
+
customRpcUrl,
|
|
2181
|
+
onSuccess,
|
|
2182
|
+
onError,
|
|
2183
|
+
onStatusChange
|
|
2184
|
+
});
|
|
2185
|
+
const sizeClasses = {
|
|
2186
|
+
sm: "text-xs px-3 py-1.5 gap-1.5",
|
|
2187
|
+
md: "text-sm px-4 py-2 gap-2",
|
|
2188
|
+
lg: "text-base px-5 py-2.5 gap-2.5"
|
|
2189
|
+
}[size];
|
|
2190
|
+
const variantClasses = {
|
|
2191
|
+
// Signature Supabase emerald CTA: #3ecf8e background, #171717 text, 6px radius
|
|
2192
|
+
primary: "bg-[#3ecf8e] text-[#171717] hover:bg-[#24b47e] active:bg-[#24b47e] shadow-sm font-medium",
|
|
2193
|
+
// Secondary outline on white
|
|
2194
|
+
outline: "bg-white text-[#171717] border border-[#dfdfdf] hover:border-[#c7c7c7] hover:bg-[#fafafa] font-medium shadow-sm",
|
|
2195
|
+
// Dark canvas-night
|
|
2196
|
+
dark: "bg-[#1c1c1c] text-white hover:bg-[#242424] font-medium shadow-sm"
|
|
2197
|
+
}[variant];
|
|
2198
|
+
return /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
2199
|
+
/* @__PURE__ */ jsxs6(
|
|
2200
|
+
"button",
|
|
2201
|
+
{
|
|
2202
|
+
type: "button",
|
|
2203
|
+
onClick: openModal,
|
|
2204
|
+
disabled,
|
|
2205
|
+
className: `inline-flex items-center justify-center rounded-[6px] transition-all duration-150 select-none disabled:opacity-50 disabled:cursor-not-allowed ${sizeClasses} ${variantClasses} ${className}`,
|
|
2206
|
+
children: [
|
|
2207
|
+
/* @__PURE__ */ jsx7(
|
|
2208
|
+
Shield5,
|
|
2209
|
+
{
|
|
2210
|
+
className: `flex-shrink-0 ${size === "sm" ? "w-3.5 h-3.5" : size === "lg" ? "w-5 h-5" : "w-4 h-4"} ${variant === "primary" ? "fill-[#171717]/20" : "text-[#3ecf8e]"}`
|
|
2211
|
+
}
|
|
2212
|
+
),
|
|
2213
|
+
/* @__PURE__ */ jsx7("span", { children: label })
|
|
2214
|
+
]
|
|
2215
|
+
}
|
|
2216
|
+
),
|
|
2217
|
+
/* @__PURE__ */ jsx7(
|
|
2218
|
+
CiceroModal,
|
|
2219
|
+
{
|
|
2220
|
+
isOpen: isModalOpen,
|
|
2221
|
+
onClose: closeModal,
|
|
2222
|
+
recipient,
|
|
2223
|
+
amount,
|
|
2224
|
+
memo,
|
|
2225
|
+
message,
|
|
2226
|
+
network,
|
|
2227
|
+
status,
|
|
2228
|
+
txId,
|
|
2229
|
+
secondsRemaining,
|
|
2230
|
+
zip321Uri,
|
|
2231
|
+
addressType,
|
|
2232
|
+
isShielded,
|
|
2233
|
+
memoValidation,
|
|
2234
|
+
onSimulateDetection: simulateDetection,
|
|
2235
|
+
onResetPayment: resetPayment,
|
|
2236
|
+
title: modalTitle,
|
|
2237
|
+
subtitle: modalSubtitle,
|
|
2238
|
+
showUsd,
|
|
2239
|
+
zucchiniAvailable,
|
|
2240
|
+
zucchiniPaying,
|
|
2241
|
+
onPayWithZucchini: payWithZucchini
|
|
2242
|
+
}
|
|
2243
|
+
)
|
|
2244
|
+
] });
|
|
2245
|
+
};
|
|
2246
|
+
|
|
2247
|
+
// src/components/CiceroInvoice.tsx
|
|
2248
|
+
import { useState as useState7 } from "react";
|
|
2249
|
+
import {
|
|
2250
|
+
Shield as Shield6,
|
|
2251
|
+
Copy as Copy3,
|
|
2252
|
+
Check as Check3,
|
|
2253
|
+
ExternalLink as ExternalLink4,
|
|
2254
|
+
Clock as Clock2,
|
|
2255
|
+
CheckCircle2 as CheckCircle22,
|
|
2256
|
+
AlertCircle as AlertCircle3,
|
|
2257
|
+
Lock as Lock2,
|
|
2258
|
+
Sparkles as Sparkles3
|
|
2259
|
+
} from "lucide-react";
|
|
2260
|
+
import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2261
|
+
var CiceroInvoice = ({
|
|
2262
|
+
recipient,
|
|
2263
|
+
amount,
|
|
2264
|
+
memo,
|
|
2265
|
+
message,
|
|
2266
|
+
network = "mainnet",
|
|
2267
|
+
expiresInSeconds = 900,
|
|
2268
|
+
showUsd = true,
|
|
2269
|
+
invoiceId,
|
|
2270
|
+
customRpcUrl,
|
|
2271
|
+
onSuccess,
|
|
2272
|
+
onError,
|
|
2273
|
+
onStatusChange,
|
|
2274
|
+
className = ""
|
|
2275
|
+
}) => {
|
|
2276
|
+
const [copiedType, setCopiedType] = useState7(null);
|
|
2277
|
+
const { convertZecToUsd } = useZcashPrice(showUsd);
|
|
2278
|
+
const {
|
|
2279
|
+
status,
|
|
2280
|
+
txId,
|
|
2281
|
+
secondsRemaining,
|
|
2282
|
+
zip321Uri,
|
|
2283
|
+
addressType,
|
|
2284
|
+
isShielded,
|
|
2285
|
+
memoValidation,
|
|
2286
|
+
simulateDetection,
|
|
2287
|
+
resetPayment,
|
|
2288
|
+
zucchiniAvailable,
|
|
2289
|
+
zucchiniPaying,
|
|
2290
|
+
payWithZucchini
|
|
2291
|
+
} = useCiceroPayment({
|
|
2292
|
+
recipient,
|
|
2293
|
+
amount,
|
|
2294
|
+
memo,
|
|
2295
|
+
message,
|
|
2296
|
+
network,
|
|
2297
|
+
expiresInSeconds,
|
|
2298
|
+
invoiceId,
|
|
2299
|
+
customRpcUrl,
|
|
2300
|
+
onSuccess,
|
|
2301
|
+
onError,
|
|
2302
|
+
onStatusChange
|
|
2303
|
+
});
|
|
2304
|
+
const copyToClipboard = async (text, type) => {
|
|
2305
|
+
try {
|
|
2306
|
+
await navigator.clipboard.writeText(text);
|
|
2307
|
+
setCopiedType(type);
|
|
2308
|
+
setTimeout(() => setCopiedType(null), 2e3);
|
|
2309
|
+
} catch {
|
|
2310
|
+
}
|
|
2311
|
+
};
|
|
2312
|
+
const formatTime = (secs) => {
|
|
2313
|
+
const m = Math.floor(secs / 60);
|
|
2314
|
+
const s = secs % 60;
|
|
2315
|
+
return `${m}:${s < 10 ? "0" : ""}${s}`;
|
|
2316
|
+
};
|
|
2317
|
+
const usdValue = convertZecToUsd(amount);
|
|
2318
|
+
return /* @__PURE__ */ jsxs7(
|
|
2319
|
+
"div",
|
|
2320
|
+
{
|
|
2321
|
+
className: `bg-white rounded-xl border border-[#ededed] shadow-sm overflow-hidden text-[#171717] font-sans ${className}`,
|
|
2322
|
+
children: [
|
|
2323
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between px-5 py-3.5 border-b border-[#ededed] bg-[#fafafa]", children: [
|
|
2324
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
|
|
2325
|
+
/* @__PURE__ */ jsx8("div", { className: "w-7 h-7 rounded-md bg-[#171717] flex items-center justify-center text-[#3ecf8e]", children: /* @__PURE__ */ jsx8(Shield6, { className: "w-3.5 h-3.5 fill-[#3ecf8e]" }) }),
|
|
2326
|
+
/* @__PURE__ */ jsxs7("div", { children: [
|
|
2327
|
+
/* @__PURE__ */ jsx8("span", { className: "font-medium text-xs text-[#171717]", children: "Shielded Zcash Invoice" }),
|
|
2328
|
+
invoiceId && /* @__PURE__ */ jsxs7("span", { className: "text-[10px] text-[#707070] ml-2 font-mono", children: [
|
|
2329
|
+
"#",
|
|
2330
|
+
invoiceId
|
|
2331
|
+
] })
|
|
2332
|
+
] })
|
|
2333
|
+
] }),
|
|
2334
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
|
|
2335
|
+
/* @__PURE__ */ jsx8("span", { className: "text-[10px] font-mono px-1.5 py-0.5 rounded bg-white border border-[#dfdfdf] text-[#171717]", children: addressType === "unified" ? "Unified" : addressType === "sapling" ? "Sapling" : "Transparent" }),
|
|
2336
|
+
/* @__PURE__ */ jsx8("span", { className: "text-[10px] uppercase tracking-wider font-semibold px-2 py-0.5 rounded-full bg-[#3ecf8e]/15 text-[#24b47e] border border-[#3ecf8e]/30", children: isShielded ? "zk-Shielded" : "Transparent" }),
|
|
2337
|
+
network === "testnet" && /* @__PURE__ */ jsx8("span", { className: "text-[10px] uppercase tracking-wider font-semibold px-2 py-0.5 rounded-full bg-amber-100 text-amber-800 border border-amber-200", children: "Testnet" })
|
|
2338
|
+
] })
|
|
2339
|
+
] }),
|
|
2340
|
+
/* @__PURE__ */ jsx8("div", { className: "p-5 space-y-4", children: status === "confirmed" ? /* @__PURE__ */ jsxs7("div", { className: "py-6 text-center space-y-3", children: [
|
|
2341
|
+
/* @__PURE__ */ jsx8("div", { className: "w-12 h-12 mx-auto rounded-full bg-[#3ecf8e]/15 border border-[#3ecf8e]/40 flex items-center justify-center text-[#24b47e]", children: /* @__PURE__ */ jsx8(CheckCircle22, { className: "w-6 h-6" }) }),
|
|
2342
|
+
/* @__PURE__ */ jsxs7("div", { children: [
|
|
2343
|
+
/* @__PURE__ */ jsx8("h4", { className: "font-medium text-sm text-[#171717]", children: "Payment Verified!" }),
|
|
2344
|
+
/* @__PURE__ */ jsx8("p", { className: "text-xs text-[#707070] mt-0.5", children: "Shielded transaction detected and settled on-chain." })
|
|
2345
|
+
] }),
|
|
2346
|
+
txId && /* @__PURE__ */ jsx8("div", { className: "font-mono text-[11px] p-2 bg-[#fafafa] rounded border border-[#ededed] text-[#707070] break-all", children: txId })
|
|
2347
|
+
] }) : /* @__PURE__ */ jsxs7(Fragment5, { children: [
|
|
2348
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between p-3.5 bg-[#fafafa] rounded-lg border border-[#ededed]", children: [
|
|
2349
|
+
/* @__PURE__ */ jsxs7("div", { children: [
|
|
2350
|
+
/* @__PURE__ */ jsx8("span", { className: "text-[10px] text-[#707070] uppercase font-mono tracking-wider", children: "Amount Due" }),
|
|
2351
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-baseline gap-2 mt-0.5", children: [
|
|
2352
|
+
/* @__PURE__ */ jsxs7("span", { className: "text-xl font-semibold tracking-tight text-[#171717] font-mono", children: [
|
|
2353
|
+
amount,
|
|
2354
|
+
" ",
|
|
2355
|
+
/* @__PURE__ */ jsx8("span", { className: "text-xs font-normal text-[#707070]", children: "ZEC" })
|
|
2356
|
+
] }),
|
|
2357
|
+
usdValue && /* @__PURE__ */ jsxs7("span", { className: "text-xs text-[#707070] font-mono", children: [
|
|
2358
|
+
"\u2248 $",
|
|
2359
|
+
usdValue,
|
|
2360
|
+
" USD"
|
|
2361
|
+
] })
|
|
2362
|
+
] })
|
|
2363
|
+
] }),
|
|
2364
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1.5 text-[#707070] font-mono text-xs", children: [
|
|
2365
|
+
/* @__PURE__ */ jsx8(Clock2, { className: "w-3.5 h-3.5" }),
|
|
2366
|
+
/* @__PURE__ */ jsx8("span", { children: formatTime(secondsRemaining) })
|
|
2367
|
+
] })
|
|
2368
|
+
] }),
|
|
2369
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex flex-col sm:flex-row items-center gap-4", children: [
|
|
2370
|
+
/* @__PURE__ */ jsx8(CiceroQRCode, { value: zip321Uri, size: 140 }),
|
|
2371
|
+
/* @__PURE__ */ jsxs7("div", { className: "space-y-2.5 w-full", children: [
|
|
2372
|
+
zucchiniAvailable && /* @__PURE__ */ jsx8(
|
|
2373
|
+
"button",
|
|
2374
|
+
{
|
|
2375
|
+
onClick: payWithZucchini,
|
|
2376
|
+
disabled: zucchiniPaying,
|
|
2377
|
+
className: "inline-flex items-center justify-center gap-1.5 w-full py-2 px-3 rounded-md bg-[#3ecf8e] text-[#171717] text-xs font-semibold hover:bg-[#24b47e] transition-colors disabled:opacity-60 shadow-sm",
|
|
2378
|
+
children: zucchiniPaying ? /* @__PURE__ */ jsxs7(Fragment5, { children: [
|
|
2379
|
+
/* @__PURE__ */ jsx8("span", { className: "w-3.5 h-3.5 border-2 border-[#171717]/30 border-t-[#171717] rounded-full animate-spin" }),
|
|
2380
|
+
/* @__PURE__ */ jsx8("span", { children: "Confirming in Zucchini..." })
|
|
2381
|
+
] }) : /* @__PURE__ */ jsxs7(Fragment5, { children: [
|
|
2382
|
+
/* @__PURE__ */ jsx8("span", { children: "\u{1F952}" }),
|
|
2383
|
+
/* @__PURE__ */ jsx8("span", { children: "Pay with Zucchini Wallet" })
|
|
2384
|
+
] })
|
|
2385
|
+
}
|
|
2386
|
+
),
|
|
2387
|
+
/* @__PURE__ */ jsxs7(
|
|
2388
|
+
"a",
|
|
2389
|
+
{
|
|
2390
|
+
href: zip321Uri,
|
|
2391
|
+
className: "inline-flex items-center justify-center gap-1.5 w-full py-2 px-3 rounded-md bg-[#171717] text-white text-xs font-medium hover:bg-[#212121] transition-colors",
|
|
2392
|
+
children: [
|
|
2393
|
+
/* @__PURE__ */ jsx8("span", { children: "Pay with Wallet App" }),
|
|
2394
|
+
/* @__PURE__ */ jsx8(ExternalLink4, { className: "w-3.5 h-3.5 text-[#3ecf8e]" })
|
|
2395
|
+
]
|
|
2396
|
+
}
|
|
2397
|
+
),
|
|
2398
|
+
/* @__PURE__ */ jsxs7("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
2399
|
+
/* @__PURE__ */ jsxs7(
|
|
2400
|
+
"button",
|
|
2401
|
+
{
|
|
2402
|
+
onClick: () => copyToClipboard(zip321Uri, "uri"),
|
|
2403
|
+
className: "py-1.5 px-2 rounded-md border border-[#ededed] hover:bg-[#fafafa] text-[11px] font-medium text-[#171717] transition-colors flex items-center justify-center gap-1",
|
|
2404
|
+
children: [
|
|
2405
|
+
copiedType === "uri" ? /* @__PURE__ */ jsx8(Check3, { className: "w-3 h-3 text-[#24b47e]" }) : /* @__PURE__ */ jsx8(Copy3, { className: "w-3 h-3 text-[#707070]" }),
|
|
2406
|
+
/* @__PURE__ */ jsx8("span", { children: copiedType === "uri" ? "Copied" : "Copy URI" })
|
|
2407
|
+
]
|
|
2408
|
+
}
|
|
2409
|
+
),
|
|
2410
|
+
/* @__PURE__ */ jsxs7(
|
|
2411
|
+
"button",
|
|
2412
|
+
{
|
|
2413
|
+
onClick: () => copyToClipboard(recipient, "address"),
|
|
2414
|
+
className: "py-1.5 px-2 rounded-md border border-[#ededed] hover:bg-[#fafafa] text-[11px] font-medium text-[#171717] transition-colors flex items-center justify-center gap-1",
|
|
2415
|
+
children: [
|
|
2416
|
+
copiedType === "address" ? /* @__PURE__ */ jsx8(Check3, { className: "w-3 h-3 text-[#24b47e]" }) : /* @__PURE__ */ jsx8(Copy3, { className: "w-3 h-3 text-[#707070]" }),
|
|
2417
|
+
/* @__PURE__ */ jsx8("span", { children: copiedType === "address" ? "Copied" : "Copy Addr" })
|
|
2418
|
+
]
|
|
2419
|
+
}
|
|
2420
|
+
)
|
|
2421
|
+
] }),
|
|
2422
|
+
/* @__PURE__ */ jsxs7("div", { className: "text-[11px] text-[#707070] font-mono truncate", children: [
|
|
2423
|
+
"To: ",
|
|
2424
|
+
truncateAddress(recipient, 10, 8)
|
|
2425
|
+
] })
|
|
2426
|
+
] })
|
|
2427
|
+
] }),
|
|
2428
|
+
memo && /* @__PURE__ */ jsxs7("div", { className: "p-2.5 bg-[#fafafa] rounded-md border border-[#ededed] text-xs", children: [
|
|
2429
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1 text-[11px] font-medium text-[#171717] mb-0.5", children: [
|
|
2430
|
+
/* @__PURE__ */ jsx8(Lock2, { className: "w-3 h-3 text-[#3ecf8e]" }),
|
|
2431
|
+
/* @__PURE__ */ jsx8("span", { children: "Encrypted Memo" })
|
|
2432
|
+
] }),
|
|
2433
|
+
/* @__PURE__ */ jsx8("div", { className: "font-mono text-[11px] text-[#171717]", children: memo })
|
|
2434
|
+
] }),
|
|
2435
|
+
memoValidation.warning && /* @__PURE__ */ jsxs7("div", { className: "p-2 rounded bg-amber-50 border border-amber-200 text-amber-800 text-[11px] flex items-start gap-1.5", children: [
|
|
2436
|
+
/* @__PURE__ */ jsx8(AlertCircle3, { className: "w-3.5 h-3.5 flex-shrink-0 mt-0.5 text-amber-600" }),
|
|
2437
|
+
/* @__PURE__ */ jsx8("span", { children: memoValidation.warning })
|
|
2438
|
+
] }),
|
|
2439
|
+
status === "expired" && /* @__PURE__ */ jsxs7("div", { className: "p-2 rounded bg-rose-50 border border-rose-200 text-rose-800 text-xs flex items-center justify-between", children: [
|
|
2440
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1.5", children: [
|
|
2441
|
+
/* @__PURE__ */ jsx8(AlertCircle3, { className: "w-3.5 h-3.5 text-rose-600" }),
|
|
2442
|
+
/* @__PURE__ */ jsx8("span", { children: "Invoice has expired" })
|
|
2443
|
+
] }),
|
|
2444
|
+
/* @__PURE__ */ jsx8(
|
|
2445
|
+
"button",
|
|
2446
|
+
{
|
|
2447
|
+
onClick: resetPayment,
|
|
2448
|
+
className: "px-2 py-0.5 text-[11px] font-medium rounded bg-[#171717] text-white hover:bg-[#212121]",
|
|
2449
|
+
children: "Reset"
|
|
2450
|
+
}
|
|
2451
|
+
)
|
|
2452
|
+
] }),
|
|
2453
|
+
/* @__PURE__ */ jsxs7("div", { className: "pt-2 border-t border-[#ededed] flex items-center justify-between text-[11px] text-[#707070]", children: [
|
|
2454
|
+
/* @__PURE__ */ jsxs7("span", { className: "flex items-center gap-1", children: [
|
|
2455
|
+
/* @__PURE__ */ jsx8(Sparkles3, { className: "w-3 h-3 text-[#3ecf8e]" }),
|
|
2456
|
+
"Demo test"
|
|
2457
|
+
] }),
|
|
2458
|
+
/* @__PURE__ */ jsx8(
|
|
2459
|
+
"button",
|
|
2460
|
+
{
|
|
2461
|
+
onClick: simulateDetection,
|
|
2462
|
+
className: "px-2 py-0.5 rounded border border-[#dfdfdf] hover:border-[#3ecf8e] text-[#171717] bg-white transition-colors",
|
|
2463
|
+
children: "Simulate Success"
|
|
2464
|
+
}
|
|
2465
|
+
)
|
|
2466
|
+
] })
|
|
2467
|
+
] }) })
|
|
2468
|
+
]
|
|
2469
|
+
}
|
|
2470
|
+
);
|
|
2471
|
+
};
|
|
2472
|
+
|
|
2473
|
+
// src/hooks/useZucchiniWallet.ts
|
|
2474
|
+
import { useState as useState8, useEffect as useEffect7, useCallback as useCallback4 } from "react";
|
|
2475
|
+
function useZucchiniWallet(options = {}) {
|
|
2476
|
+
const { autoConnect = false } = options;
|
|
2477
|
+
const [adapter2] = useState8(() => new ZucchiniWalletAdapter());
|
|
2478
|
+
const [isAvailable, setIsAvailable] = useState8(false);
|
|
2479
|
+
const [connectionStatus, setConnectionStatus] = useState8("disconnected");
|
|
2480
|
+
const [paymentStatus, setPaymentStatus] = useState8("idle");
|
|
2481
|
+
const [session, setSession] = useState8(null);
|
|
2482
|
+
const [error, setError] = useState8(null);
|
|
2483
|
+
useEffect7(() => {
|
|
2484
|
+
const detected = ZucchiniWalletAdapter.isAvailable();
|
|
2485
|
+
setIsAvailable(detected);
|
|
2486
|
+
if (detected && autoConnect) {
|
|
2487
|
+
connect();
|
|
2488
|
+
}
|
|
2489
|
+
}, []);
|
|
2490
|
+
const connect = useCallback4(async () => {
|
|
2491
|
+
setConnectionStatus("connecting");
|
|
2492
|
+
setError(null);
|
|
2493
|
+
try {
|
|
2494
|
+
const sess = await adapter2.connect();
|
|
2495
|
+
setSession(sess);
|
|
2496
|
+
setConnectionStatus("connected");
|
|
2497
|
+
return sess;
|
|
2498
|
+
} catch (err) {
|
|
2499
|
+
const msg = err instanceof Error ? err.message : "Failed to connect to Zucchini Wallet";
|
|
2500
|
+
setError(msg);
|
|
2501
|
+
setConnectionStatus("error");
|
|
2502
|
+
throw err;
|
|
2503
|
+
}
|
|
2504
|
+
}, [adapter2]);
|
|
2505
|
+
const requestPayment = useCallback4(
|
|
2506
|
+
async (params) => {
|
|
2507
|
+
setPaymentStatus("requesting");
|
|
2508
|
+
setError(null);
|
|
2509
|
+
try {
|
|
2510
|
+
const result = await adapter2.requestPayment(params);
|
|
2511
|
+
setPaymentStatus("submitted");
|
|
2512
|
+
return result;
|
|
2513
|
+
} catch (err) {
|
|
2514
|
+
const msg = err instanceof Error ? err.message : "Payment request failed";
|
|
2515
|
+
setError(msg);
|
|
2516
|
+
setPaymentStatus("error");
|
|
2517
|
+
throw err;
|
|
2518
|
+
}
|
|
2519
|
+
},
|
|
2520
|
+
[adapter2]
|
|
2521
|
+
);
|
|
2522
|
+
const disconnect = useCallback4(async () => {
|
|
2523
|
+
await adapter2.disconnect();
|
|
2524
|
+
setSession(null);
|
|
2525
|
+
setConnectionStatus("disconnected");
|
|
2526
|
+
setPaymentStatus("idle");
|
|
2527
|
+
}, [adapter2]);
|
|
2528
|
+
return {
|
|
2529
|
+
/** Whether the Zucchini extension is installed in this browser */
|
|
2530
|
+
isAvailable,
|
|
2531
|
+
/** Current wallet connection lifecycle state */
|
|
2532
|
+
connectionStatus,
|
|
2533
|
+
/** Current payment request lifecycle state */
|
|
2534
|
+
paymentStatus,
|
|
2535
|
+
/** Active session info (address, etc.) */
|
|
2536
|
+
session,
|
|
2537
|
+
/** Last error message (if any) */
|
|
2538
|
+
error,
|
|
2539
|
+
/** Prompt the user to connect their Zucchini Wallet */
|
|
2540
|
+
connect,
|
|
2541
|
+
/** Send a ZIP-321 payment request for user review & approval */
|
|
2542
|
+
requestPayment,
|
|
2543
|
+
/** Disconnect the current session */
|
|
2544
|
+
disconnect
|
|
2545
|
+
};
|
|
2546
|
+
}
|
|
2547
|
+
export {
|
|
2548
|
+
CiceroAccountModal,
|
|
2549
|
+
CiceroConnectButton,
|
|
2550
|
+
CiceroConnectModal,
|
|
2551
|
+
CiceroInvoice,
|
|
2552
|
+
CiceroModal,
|
|
2553
|
+
CiceroPayButton,
|
|
2554
|
+
CiceroProvider,
|
|
2555
|
+
CiceroQRCode,
|
|
2556
|
+
ZucchiniWalletAdapter,
|
|
2557
|
+
burnerWallet,
|
|
2558
|
+
decodeBase64Url,
|
|
2559
|
+
encodeBase64Url,
|
|
2560
|
+
generateZip321Uri,
|
|
2561
|
+
getAddressType,
|
|
2562
|
+
getDefaultWallets,
|
|
2563
|
+
getWalletDeepLinks,
|
|
2564
|
+
isShieldedAddress,
|
|
2565
|
+
parseZip321Uri,
|
|
2566
|
+
truncateAddress,
|
|
2567
|
+
useAccount,
|
|
2568
|
+
useAccountModal,
|
|
2569
|
+
useBalance,
|
|
2570
|
+
useCicero,
|
|
2571
|
+
useCiceroPayment,
|
|
2572
|
+
useConnectModal,
|
|
2573
|
+
useZcashPrice,
|
|
2574
|
+
useZucchiniWallet,
|
|
2575
|
+
validateMemoSupport,
|
|
2576
|
+
viewingKeyWallet,
|
|
2577
|
+
ywallet,
|
|
2578
|
+
zashiWallet,
|
|
2579
|
+
zucchiniWallet
|
|
2580
|
+
};
|
|
2581
|
+
//# sourceMappingURL=index.mjs.map
|