@jaw.id/ui 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/cjs-error.cjs +3 -0
  2. package/dist/{ccip-BYa9WTP9.js → ccip-nTJDT8tV.js} +1 -1
  3. package/dist/{index-DBYNWeek.js → index-C2yN5Jh0.js} +20536 -20246
  4. package/dist/index.js +1 -1
  5. package/package.json +8 -2
  6. package/.babelrc +0 -12
  7. package/CHANGELOG.md +0 -23
  8. package/components.json +0 -22
  9. package/postcss.config.cjs +0 -6
  10. package/src/components/ConnectDialog/index.tsx +0 -270
  11. package/src/components/ConnectDialog/types.ts +0 -34
  12. package/src/components/DefaultDialog/index.tsx +0 -99
  13. package/src/components/Eip712Dialog/index.tsx +0 -525
  14. package/src/components/Eip712Dialog/types.ts +0 -27
  15. package/src/components/FeeTokenSelector/index.tsx +0 -308
  16. package/src/components/OnboardingDialog/index.tsx +0 -317
  17. package/src/components/OnboardingDialog/types.ts +0 -43
  18. package/src/components/OrSeparator/index.tsx +0 -13
  19. package/src/components/PermissionDialog/index.tsx +0 -598
  20. package/src/components/PermissionDialog/types.ts +0 -77
  21. package/src/components/SignatureDialog/index.tsx +0 -180
  22. package/src/components/SignatureDialog/types.ts +0 -27
  23. package/src/components/SiweDialog/index.tsx +0 -231
  24. package/src/components/SiweDialog/types.ts +0 -34
  25. package/src/components/TransactionDialog/DecodedCalldata.tsx +0 -79
  26. package/src/components/TransactionDialog/index.tsx +0 -663
  27. package/src/components/TransactionDialog/types.ts +0 -54
  28. package/src/components/ui/accordion.tsx +0 -66
  29. package/src/components/ui/avatar.tsx +0 -53
  30. package/src/components/ui/button.tsx +0 -59
  31. package/src/components/ui/card.tsx +0 -92
  32. package/src/components/ui/checkbox.tsx +0 -32
  33. package/src/components/ui/dialog.tsx +0 -183
  34. package/src/components/ui/dropdown-menu.tsx +0 -258
  35. package/src/components/ui/form.tsx +0 -167
  36. package/src/components/ui/input.tsx +0 -60
  37. package/src/components/ui/label.tsx +0 -24
  38. package/src/components/ui/popover.tsx +0 -48
  39. package/src/components/ui/scroll-area.tsx +0 -58
  40. package/src/components/ui/select.tsx +0 -160
  41. package/src/components/ui/separator.tsx +0 -28
  42. package/src/components/ui/sheet.tsx +0 -169
  43. package/src/components/ui/spinner.tsx +0 -18
  44. package/src/components/ui/tabs.tsx +0 -69
  45. package/src/components/ui/tooltip.tsx +0 -61
  46. package/src/hooks/index.ts +0 -5
  47. package/src/hooks/useChainIconURI.tsx +0 -117
  48. package/src/hooks/useDecodedCalldata.ts +0 -128
  49. package/src/hooks/useFeeTokenPrice.tsx +0 -36
  50. package/src/hooks/useGasEstimation.ts +0 -474
  51. package/src/hooks/useIsMobile.ts +0 -36
  52. package/src/icons/index.tsx +0 -114
  53. package/src/index.ts +0 -19
  54. package/src/lib/utils.ts +0 -6
  55. package/src/react/ReactUIHandler.tsx +0 -3004
  56. package/src/react/index.ts +0 -2
  57. package/src/styles.css +0 -210
  58. package/src/utils/formatAddress.ts +0 -24
  59. package/src/utils/index.ts +0 -4
  60. package/src/utils/justaNameInstance.ts +0 -25
  61. package/src/utils/tokenBalance.ts +0 -41
  62. package/src/utils/tokenPrice.ts +0 -58
  63. package/tailwind.config.js +0 -130
  64. package/tsconfig.json +0 -19
  65. package/tsconfig.lib.json +0 -43
  66. package/vite.config.ts +0 -45
@@ -1,598 +0,0 @@
1
- 'use client'
2
-
3
- import { ANY_TARGET, ANY_FN_SEL } from "@jaw.id/core";
4
- import { isNativeToken } from "../../utils/tokenBalance";
5
- import { Button } from "../ui/button";
6
- import { DefaultDialog } from "../DefaultDialog";
7
- import { FeeTokenSelector } from "../FeeTokenSelector";
8
- import { PermissionDialogProps } from "./types";
9
- import { useIsMobile, useChainIconURI, useFeeTokenPrice } from "../../hooks";
10
- import {CopiedIcon, CopyIcon, WalletIcon} from "../../icons";
11
- import { useState, useEffect, useRef } from "react";
12
- import { getJustaNameInstance } from "../../utils/justaNameInstance";
13
-
14
- export const PermissionDialog = ({
15
- open,
16
- onOpenChange,
17
- mode,
18
- permissionId,
19
- spenderAddress,
20
- origin,
21
- spends = [],
22
- calls = [],
23
- expiryDate,
24
- networkName,
25
- chainId,
26
- chainIcon,
27
- apiKey,
28
- onConfirm,
29
- onCancel,
30
- isProcessing,
31
- status,
32
- isLoadingTokenInfo = false,
33
- timestamp = new Date(),
34
- warningMessage,
35
- gasFee,
36
- gasFeeLoading = false,
37
- gasEstimationError,
38
- sponsored = false,
39
- // Fee token props
40
- feeTokens,
41
- feeTokensLoading,
42
- selectedFeeToken,
43
- onFeeTokenSelect,
44
- showFeeTokenSelector,
45
- isPayingWithErc20,
46
- // RPC configuration
47
- mainnetRpcUrl,
48
- }: PermissionDialogProps) => {
49
- // Ref for scrollable container
50
- const scrollableRef = useRef<HTMLDivElement>(null);
51
-
52
- const isMobile = useIsMobile();
53
-
54
- // Get native token symbol from feeTokens (defaults to ETH if not found)
55
- const nativeToken = feeTokens?.find(t => t.isNative);
56
- const nativeSymbol = nativeToken?.symbol || 'ETH';
57
-
58
- // Fetch native token price dynamically based on the chain's native token symbol
59
- const nativeTokenPrice = useFeeTokenPrice(nativeSymbol);
60
-
61
- const [isPermissionIdCopied, setIsPermissionIdCopied] = useState(false);
62
- const [resolvedAddresses, setResolvedAddresses] = useState<Record<string, string>>({});
63
- const [isResolvingAddresses, setIsResolvingAddresses] = useState(true); // Start true to prevent early clicks
64
-
65
- // Resolve addresses to human-readable names
66
- useEffect(() => {
67
- if (!chainId) {
68
- setIsResolvingAddresses(false);
69
- return;
70
- }
71
-
72
- const justaName = getJustaNameInstance(mainnetRpcUrl);
73
- const addressesToResolve: string[] = [];
74
-
75
- if (spenderAddress) {
76
- addressesToResolve.push(spenderAddress);
77
- }
78
-
79
- calls.forEach((call) => {
80
- if (call.target && !addressesToResolve.includes(call.target)
81
- && call.target.toLowerCase() !== ANY_TARGET.toLowerCase()) {
82
- addressesToResolve.push(call.target);
83
- }
84
- });
85
-
86
- if (addressesToResolve.length === 0) {
87
- setIsResolvingAddresses(false);
88
- return;
89
- }
90
-
91
- setIsResolvingAddresses(true);
92
-
93
- const resolvePromises = addressesToResolve.map(async (address) => {
94
- try {
95
- const result = await justaName.subnames.reverseResolve({
96
- address: address as `0x${string}`,
97
- chainId: chainId,
98
- });
99
- if (result) {
100
- return { address, name: result };
101
- }
102
- } catch {
103
- // Silently fail if resolution fails
104
- }
105
- return null;
106
- });
107
-
108
- Promise.all(resolvePromises).then((results) => {
109
- const newResolved: Record<string, string> = {};
110
- results.forEach((result) => {
111
- if (result) {
112
- newResolved[result.address] = result.name;
113
- }
114
- });
115
- setResolvedAddresses(prev => ({ ...prev, ...newResolved }));
116
- setIsResolvingAddresses(false);
117
- });
118
- }, [spenderAddress, calls, chainId]);
119
-
120
- // Handle wheel events for smooth scrolling over content
121
- useEffect(() => {
122
- const scrollable = scrollableRef.current;
123
- if (!scrollable) return;
124
-
125
- const handleWheel = (e: WheelEvent) => {
126
- // Prevent default to handle scroll manually
127
- e.preventDefault();
128
- // Smooth scroll
129
- scrollable.scrollTop += e.deltaY;
130
- };
131
-
132
- // Add event listener with passive: false to allow preventDefault
133
- scrollable.addEventListener('wheel', handleWheel, { passive: false });
134
-
135
- return () => {
136
- scrollable.removeEventListener('wheel', handleWheel);
137
- };
138
- }, []);
139
-
140
- // Get chain icon using the hook - fetch from capabilities chainMetadata
141
- const defaultChainIcon = useChainIconURI(chainId || 1, apiKey, 24);
142
- const displayChainIcon = chainIcon || defaultChainIcon;
143
-
144
- // Map known sentinel addresses to friendly labels
145
- const getContractDisplayName = (target: string): string | null => {
146
- if (target.toLowerCase() === ANY_TARGET.toLowerCase()) {
147
- return 'Any Contract';
148
- }
149
- return null;
150
- };
151
-
152
- const getFunctionDisplayName = (signature: string, selector?: string): string => {
153
- if (selector?.toLowerCase() === ANY_FN_SEL.toLowerCase()) {
154
- return 'Any Function';
155
- }
156
- return signature;
157
- };
158
-
159
- // Truncate address for display (e.g., 0x43e...ead3)
160
- const truncateAddress = (address: string) => {
161
- if (!address) return '';
162
- return `${address.slice(0, 5)}...${address.slice(-4)}`;
163
- };
164
-
165
- // Copy to clipboard helper
166
- const copyToClipboard = (text: string, setCopied: (value: boolean) => void) => {
167
- if (typeof window !== 'undefined' && navigator?.clipboard) {
168
- navigator.clipboard.writeText(text);
169
- setCopied(true);
170
- setTimeout(() => setCopied(false), 3000);
171
- }
172
- };
173
-
174
- const hasGasPaymentOption = !gasEstimationError || sponsored;
175
- const canConfirm = !isProcessing && !isLoadingTokenInfo && !isResolvingAddresses && !gasFeeLoading && hasGasPaymentOption;
176
-
177
- // Count total permissions
178
- const totalSpends = spends.length;
179
- const totalCalls = calls.length;
180
- const totalPermissions = totalSpends + totalCalls;
181
-
182
- return (
183
- <DefaultDialog
184
- open={open}
185
- onOpenChange={isProcessing ? undefined : onOpenChange}
186
- header={
187
- <div className="flex flex-col gap-2.5 p-3.5">
188
- <p className="text-xs font-bold text-muted-foreground leading-[100%]">
189
- {timestamp.toLocaleDateString('en-US', {
190
- weekday: 'long',
191
- day: 'numeric',
192
- month: 'long'
193
- })} at {timestamp.toLocaleTimeString('en-US', {
194
- hour: '2-digit',
195
- minute: '2-digit',
196
- second: '2-digit',
197
- timeZoneName: 'short'
198
- })}
199
- </p>
200
- <p className="text-[30px] font-normal leading-[100%] text-foreground">
201
- {mode === 'grant' ? 'Permission Request' : 'Revoke Permission'}
202
- </p>
203
- </div>
204
- }
205
- contentStyle={isMobile ? {
206
- width: '100%',
207
- height: '100%',
208
- maxWidth: 'none',
209
- maxHeight: 'none',
210
- } : {
211
- width: '500px',
212
- minWidth: '500px',
213
- maxHeight: '90vh',
214
- }}
215
- >
216
- <div className="flex flex-col gap-6 justify-between max-md:h-full h-full overflow-hidden">
217
- {/* Scrollable Content Area */}
218
- <div ref={scrollableRef} className="flex flex-col gap-3 flex-1 overflow-y-auto min-h-0 max-h-[60vh] max-md:pb-2">
219
- {/* Permission ID Card - Only for revoke mode */}
220
- {mode === 'revoke' && permissionId && (
221
- <div className="flex flex-col gap-2.5 p-3.5 border border-border rounded-[6px]">
222
- <div className="flex flex-row items-center justify-between">
223
- <p className="text-xs font-bold leading-[133%] text-foreground">Permission ID</p>
224
- {isPermissionIdCopied ? (
225
- <CopiedIcon width={16} height={16} />
226
- ) : (
227
- <CopyIcon
228
- width={16}
229
- height={16}
230
- onClick={() => copyToClipboard(permissionId, setIsPermissionIdCopied)}
231
- className="cursor-pointer"
232
- />
233
- )}
234
- </div>
235
- <p className="text-base font-normal leading-[150%] text-foreground break-all">
236
- {permissionId}
237
- </p>
238
- </div>
239
- )}
240
-
241
- {/* Requesting dApp + Spender Address */}
242
- <div className="flex flex-row justify-between items-center gap-2.5 p-3.5 border border-border rounded-[6px]">
243
- <div className="flex flex-col text-foreground gap-0.5 min-w-0 flex-1">
244
- <p className="text-xs font-bold leading-[133%]">Requesting dApp</p>
245
- <p className="text-base font-normal leading-[150%] truncate overflow-hidden">
246
- {origin}
247
- </p>
248
- </div>
249
- <div className="w-[1px] rounded-full bg-border h-full flex-shrink-0 min-h-[50px]" />
250
- <div className="flex flex-col text-foreground gap-0.5 min-w-0 flex-1">
251
- <p className="text-xs font-bold leading-[133%]">Spender Address</p>
252
- <div className="flex flex-row items-center gap-1">
253
- <WalletIcon className="w-3 h-3 flex-shrink-0" stroke="black" />
254
- <p className="text-base font-normal leading-[150%] truncate overflow-hidden">
255
- {resolvedAddresses[spenderAddress] || truncateAddress(spenderAddress)}
256
- </p>
257
- </div>
258
- </div>
259
- </div>
260
-
261
- {/* Permissions Summary */}
262
- <div className="flex flex-row justify-between items-center gap-2.5 p-3.5 border border-border rounded-[6px]">
263
- <div className="flex flex-col text-foreground gap-0.5 flex-1">
264
- <p className="text-xs font-bold leading-[133%]">Network</p>
265
- <div className="flex flex-row items-center gap-1">
266
- {displayChainIcon}
267
- <p className="text-base font-normal leading-[150%]">{networkName}</p>
268
- </div>
269
- </div>
270
- <div className="w-[1px] rounded-full bg-border h-full flex-shrink-0 min-h-[50px]" />
271
- <div className="flex flex-col text-foreground gap-0.5 flex-1">
272
- <p className="text-xs font-bold leading-[133%]">Expiry Date</p>
273
- <p className="text-base font-normal leading-[150%]">{expiryDate}</p>
274
- </div>
275
- </div>
276
-
277
- {/* Spend Permissions Section */}
278
- {totalSpends > 0 && (
279
- <div className="flex flex-col gap-2">
280
- <p className="text-sm font-bold text-foreground px-1">
281
- Spend Permissions ({totalSpends})
282
- </p>
283
- <div className="flex flex-col gap-2">
284
- {spends.map((spend, index) => (
285
- <div
286
- key={index}
287
- className="flex flex-col gap-3 p-3.5 border border-border rounded-[6px] bg-background"
288
- >
289
- {/* Amount */}
290
- <div className="flex flex-col gap-0.5">
291
- <p className="text-xs font-bold leading-[133%] text-muted-foreground">Amount</p>
292
- {isLoadingTokenInfo ? (
293
- <div className="h-[30px] w-32 bg-muted animate-pulse rounded" />
294
- ) : (
295
- <div className="flex items-center gap-2">
296
- <p className="text-xl font-normal leading-[150%] text-foreground">
297
- {spend.amount}
298
- </p>
299
- {spend.amountUsd && (
300
- <p className="text-sm font-bold text-muted-foreground">${spend.amountUsd}</p>
301
- )}
302
- </div>
303
- )}
304
- </div>
305
-
306
- {/* Duration and Token */}
307
- <div className="flex flex-row justify-between items-center gap-2.5">
308
- <div className="flex flex-col gap-0.5 flex-1">
309
- <p className="text-xs font-bold leading-[133%] text-muted-foreground">Duration</p>
310
- <p className="text-base font-normal leading-[150%] text-foreground">{spend.duration}</p>
311
- </div>
312
- <div className="w-[1px] rounded-full bg-border h-full flex-shrink-0 min-h-[40px]" />
313
- <div className="flex flex-col gap-0.5 flex-1">
314
- <p className="text-xs font-bold leading-[133%] text-muted-foreground">Token</p>
315
- <p className="text-base font-normal leading-[150%] text-foreground">{isNativeToken(spend.tokenAddress) ? `Native (${nativeSymbol})` : spend.token}</p>
316
- </div>
317
- </div>
318
- </div>
319
- ))}
320
- </div>
321
- </div>
322
- )}
323
-
324
- {/* Call Permissions Section */}
325
- {totalCalls > 0 && (
326
- <div className="flex flex-col gap-2">
327
- <p className="text-sm font-bold text-foreground px-1">
328
- Call Permissions ({totalCalls})
329
- </p>
330
- <div className="flex flex-col gap-2">
331
- {calls.map((call, index) => (
332
- <div
333
- key={index}
334
- className="flex flex-col gap-2.5 p-3.5 border border-border rounded-[6px] bg-background"
335
- >
336
- <div className="flex flex-col gap-0.5">
337
- <p className="text-xs font-bold leading-[133%] text-muted-foreground">Function</p>
338
- <code className="text-sm font-mono leading-[150%] text-foreground break-all">
339
- {getFunctionDisplayName(call.functionSignature, call.selector)}
340
- </code>
341
- </div>
342
- <div className="flex flex-col gap-0.5">
343
- <p className="text-xs font-bold leading-[133%] text-muted-foreground">Contract</p>
344
- <p className="text-sm font-mono leading-[150%] text-foreground break-all">
345
- {getContractDisplayName(call.target) || resolvedAddresses[call.target] || call.target}
346
- </p>
347
- </div>
348
- </div>
349
- ))}
350
- </div>
351
- </div>
352
- )}
353
-
354
- {/* Warning (Grant) / Info (Revoke) Card */}
355
- {mode === 'grant' ? (
356
- <div className="flex items-start gap-2.5 p-3.5 border border-border rounded-[6px] bg-yellow-50">
357
- <svg
358
- width="16"
359
- height="16"
360
- viewBox="0 0 16 16"
361
- fill="none"
362
- xmlns="http://www.w3.org/2000/svg"
363
- className="flex-shrink-0 mt-0.5"
364
- >
365
- <path
366
- d="M8 1.5L1 14.5H15L8 1.5Z"
367
- stroke="#F59E0B"
368
- strokeWidth="1.5"
369
- strokeLinecap="round"
370
- strokeLinejoin="round"
371
- />
372
- <path
373
- d="M8 6V9"
374
- stroke="#F59E0B"
375
- strokeWidth="1.5"
376
- strokeLinecap="round"
377
- />
378
- <circle
379
- cx="8"
380
- cy="11.5"
381
- r="0.5"
382
- fill="#F59E0B"
383
- />
384
- </svg>
385
- <div className="flex flex-col gap-1">
386
- <p className="text-xs font-bold leading-[133%] text-yellow-800">Warning</p>
387
- <p className="text-xs font-normal leading-[150%] text-yellow-900">
388
- {warningMessage || `You are granting ${totalPermissions} permission${totalPermissions > 1 ? 's' : ''} to this dApp until ${expiryDate}. Only approve if you trust this dApp.`}
389
- </p>
390
- </div>
391
- </div>
392
- ) : (
393
- <div className="flex items-start gap-2.5 p-3.5 border border-border rounded-[6px] bg-blue-50">
394
- <svg
395
- width="16"
396
- height="16"
397
- viewBox="0 0 16 16"
398
- fill="none"
399
- xmlns="http://www.w3.org/2000/svg"
400
- className="flex-shrink-0 mt-0.5"
401
- >
402
- <circle
403
- cx="8"
404
- cy="8"
405
- r="6.5"
406
- stroke="#3B82F6"
407
- strokeWidth="1.5"
408
- />
409
- <path
410
- d="M8 7V11"
411
- stroke="#3B82F6"
412
- strokeWidth="1.5"
413
- strokeLinecap="round"
414
- />
415
- <circle
416
- cx="8"
417
- cy="5"
418
- r="0.5"
419
- fill="#3B82F6"
420
- />
421
- </svg>
422
- <div className="flex flex-col gap-1">
423
- <p className="text-xs font-bold leading-[133%] text-blue-800">Info</p>
424
- <p className="text-xs font-normal leading-[150%] text-blue-900">
425
- This will revoke all permissions and prevent the spender from making any further transactions on your behalf.
426
- </p>
427
- </div>
428
- </div>
429
- )}
430
-
431
- {/* Status Message */}
432
- {status && (
433
- <div className={`text-sm p-3 rounded-lg ${
434
- status.includes('Error') ? 'bg-red-50 text-red-600' :
435
- status.includes('successfully') ? 'bg-green-50 text-green-600' :
436
- 'bg-blue-50 text-blue-600'
437
- }`}>
438
- {status}
439
- </div>
440
- )}
441
- </div>
442
-
443
- {/* Fixed Bottom Section - Gas Estimation + Action Buttons */}
444
- <div className="flex-shrink-0 space-y-3">
445
- {/* Gas Estimation Section - Shown for both grant and revoke modes */}
446
- <div className="flex flex-row justify-between items-center gap-2.5 p-3.5 border border-border rounded-[6px]">
447
- <div className="flex flex-col text-foreground flex-1 gap-0.5">
448
- <p className="text-xs font-bold leading-[133%]">Network</p>
449
- <div className="flex flex-row items-center gap-1">
450
- {displayChainIcon}
451
- <p className="text-base font-normal text-ellipsis leading-[150%] truncate">{networkName}</p>
452
- </div>
453
- </div>
454
- <div className="w-[1px] rounded-full bg-border h-full min-h-[50px]" />
455
- <div className="flex flex-col text-foreground flex-1 gap-0.5">
456
- <p className="text-xs font-bold leading-[133%]">Network Fees</p>
457
- <div className="flex flex-row items-center w-full justify-between gap-1">
458
- {gasFeeLoading && !isPayingWithErc20 ? (
459
- <p className="text-base font-normal text-muted-foreground">Estimating...</p>
460
- ) : gasEstimationError && !sponsored ? (
461
- <div className="flex flex-col gap-0.5 w-full">
462
- <div className="flex items-center justify-between w-full">
463
- <div className="flex flex-col">
464
- <p className="text-sm text-red-600 font-medium">Gas Estimation Failed</p>
465
- <p className="text-xs text-red-500">{gasEstimationError}</p>
466
- </div>
467
- {showFeeTokenSelector && feeTokens && onFeeTokenSelect && (
468
- <FeeTokenSelector
469
- tokens={feeTokens}
470
- selectedToken={selectedFeeToken ?? null}
471
- onSelect={onFeeTokenSelect}
472
- isLoading={feeTokensLoading ?? false}
473
- disabled={isProcessing}
474
- nativeTokenPrice={nativeTokenPrice}
475
- estimatedGasEth={gasFee || '0'}
476
- />
477
- )}
478
- </div>
479
- </div>
480
- ) : sponsored || gasFee === 'sponsored' ? (
481
- <div className="flex flex-col">
482
- <div className="flex items-center gap-2">
483
- {sponsored && gasFee && gasFee !== 'sponsored' && nativeTokenPrice > 0 && (
484
- <div className="flex flex-col line-through text-muted-foreground">
485
- <p className="text-base font-normal">
486
- ${(nativeTokenPrice * Number(gasFee)).toFixed(4)}
487
- </p>
488
- </div>
489
- )}
490
- <span className="text-xs font-semibold text-green-600 bg-green-100 px-2 py-0.5 rounded">
491
- Sponsored
492
- </span>
493
- </div>
494
- <p className="text-xs font-normal text-muted-foreground">
495
- {sponsored && gasFee && gasFee !== 'sponsored' ? (() => {
496
- const gasValue = Number(gasFee);
497
- if (gasValue > 0 && gasValue < 0.0001) {
498
- return '> 0.0001 ETH';
499
- }
500
- return gasValue.toFixed(4) + ' ETH';
501
- })() : 'Gas fees covered'}
502
- </p>
503
- </div>
504
- ) : isPayingWithErc20 && selectedFeeToken ? (
505
- <div className="flex flex-col gap-0.5 w-full">
506
- <div className="flex items-center justify-between w-full">
507
- <p className="text-base font-normal text-foreground">
508
- {/* Show estimated cost from paymaster quote - don't fallback to ETH calculation */}
509
- {selectedFeeToken.gasCostFormatted ? (
510
- // For stablecoins like USDC/USDT, the value is approximately USD
511
- `$${selectedFeeToken.gasCostFormatted}`
512
- ) : (
513
- <span className="text-muted-foreground">Estimating...</span>
514
- )}
515
- </p>
516
- {/* Inline Fee Token Selector */}
517
- {showFeeTokenSelector && feeTokens && onFeeTokenSelect && (
518
- <FeeTokenSelector
519
- tokens={feeTokens}
520
- selectedToken={selectedFeeToken}
521
- onSelect={onFeeTokenSelect}
522
- isLoading={feeTokensLoading ?? false}
523
- disabled={isProcessing}
524
- nativeTokenPrice={nativeTokenPrice}
525
- estimatedGasEth={gasFee || '0'}
526
- />
527
- )}
528
- </div>
529
- {selectedFeeToken.gasCostFormatted && (
530
- <p className="text-xs font-normal text-muted-foreground">
531
- Up to {selectedFeeToken.gasCostFormatted} {selectedFeeToken.symbol}
532
- </p>
533
- )}
534
- </div>
535
- ) : gasFee && gasFee !== 'sponsored' ? (
536
- <div className="flex flex-col gap-0.5 w-full">
537
- <div className="flex items-center justify-between w-full">
538
- {nativeTokenPrice > 0 && (
539
- <p className="text-base font-normal text-foreground">
540
- ${(nativeTokenPrice * Number(gasFee)).toFixed(4)}
541
- </p>
542
- )}
543
- {/* Inline Fee Token Selector (when paying with ETH but selector is available) */}
544
- {showFeeTokenSelector && feeTokens && onFeeTokenSelect && selectedFeeToken && (
545
- <FeeTokenSelector
546
- tokens={feeTokens}
547
- selectedToken={selectedFeeToken}
548
- onSelect={onFeeTokenSelect}
549
- isLoading={feeTokensLoading ?? false}
550
- disabled={isProcessing}
551
- nativeTokenPrice={nativeTokenPrice}
552
- estimatedGasEth={gasFee || '0'}
553
- />
554
- )}
555
- </div>
556
- <p className="text-xs font-normal text-muted-foreground">
557
- {(() => {
558
- const gasValue = Number(gasFee);
559
- if (gasValue > 0 && gasValue < 0.0001) {
560
- return '> 0.0001 ETH';
561
- }
562
- return gasValue.toFixed(4) + ' ETH';
563
- })()}
564
- </p>
565
- </div>
566
- ) : (
567
- <p className="text-base font-normal text-muted-foreground">-</p>
568
- )}
569
- </div>
570
- </div>
571
- </div>
572
-
573
- {/* Action Buttons */}
574
- <div className="flex gap-3 p-3.5 max-md:mt-auto">
575
- <Button
576
- variant="outline"
577
- onClick={onCancel}
578
- disabled={isProcessing}
579
- className="flex-1"
580
- >
581
- Cancel
582
- </Button>
583
- <Button
584
- variant={mode === 'revoke' ? 'destructive' : 'default'}
585
- onClick={onConfirm}
586
- disabled={!canConfirm}
587
- className="flex-1"
588
- >
589
- {isProcessing ? 'Processing...' : (isLoadingTokenInfo || isResolvingAddresses || gasFeeLoading) ? 'Loading...' : mode === 'grant' ? 'Accept' : 'Revoke'}
590
- </Button>
591
- </div>
592
- </div>
593
- </div>
594
- </DefaultDialog>
595
- );
596
- };
597
-
598
- export * from './types';
@@ -1,77 +0,0 @@
1
- import { ReactElement } from 'react';
2
- import { FeeTokenOption } from '../FeeTokenSelector';
3
-
4
- export interface SpendPermission {
5
- amount: string;
6
- amountUsd?: string;
7
- token: string;
8
- tokenAddress: string;
9
- duration: string;
10
- limit: string;
11
- }
12
-
13
- export interface CallPermission {
14
- target: string;
15
- selector?: string;
16
- functionSignature: string;
17
- }
18
-
19
- export interface PermissionDialogProps {
20
- open: boolean;
21
- onOpenChange: (open: boolean) => void;
22
-
23
- // Mode: grant for new permissions, revoke for existing
24
- mode: 'grant' | 'revoke';
25
-
26
- // Permission details
27
- permissionId?: string; // Only for revoke mode
28
- spenderAddress: string;
29
- origin: string; // Requesting dApp origin
30
-
31
- // Arrays of permissions
32
- spends?: SpendPermission[];
33
- calls?: CallPermission[];
34
-
35
- // Period and expiry
36
- expiryDate: string; // Formatted expiry date
37
-
38
- // Network info
39
- networkName: string;
40
- chainId?: number;
41
- chainIcon?: ReactElement;
42
- apiKey?: string;
43
-
44
- // Actions
45
- onConfirm: () => void;
46
- onCancel: () => void;
47
-
48
- // Status
49
- isProcessing: boolean;
50
- status?: string;
51
- isLoadingTokenInfo?: boolean;
52
-
53
- // Timestamp
54
- timestamp?: Date;
55
-
56
- // Custom warning message for grant mode
57
- warningMessage?: string;
58
-
59
- // Gas estimation props
60
- gasFee?: string;
61
- gasFeeLoading?: boolean;
62
- gasEstimationError?: string;
63
- sponsored?: boolean;
64
-
65
- // Fee token selection (for ERC-20 paymaster)
66
- feeTokens?: FeeTokenOption[];
67
- feeTokensLoading?: boolean;
68
- selectedFeeToken?: FeeTokenOption | null;
69
- onFeeTokenSelect?: (token: FeeTokenOption) => void;
70
- showFeeTokenSelector?: boolean;
71
-
72
- // ERC-20 payment indicator (when user selected non-native token)
73
- isPayingWithErc20?: boolean;
74
-
75
- // RPC configuration
76
- mainnetRpcUrl: string;
77
- }