@jaw.id/ui 0.0.3 → 0.0.4
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/cjs-error.cjs +3 -0
- package/dist/{ccip-BYa9WTP9.js → ccip-yRQNp5zr.js} +1 -1
- package/dist/{index-DBYNWeek.js → index-BcDVfcdq.js} +20832 -20511
- package/dist/index.js +1 -1
- package/package.json +8 -2
- package/.babelrc +0 -12
- package/CHANGELOG.md +0 -23
- package/components.json +0 -22
- package/postcss.config.cjs +0 -6
- package/src/components/ConnectDialog/index.tsx +0 -270
- package/src/components/ConnectDialog/types.ts +0 -34
- package/src/components/DefaultDialog/index.tsx +0 -99
- package/src/components/Eip712Dialog/index.tsx +0 -525
- package/src/components/Eip712Dialog/types.ts +0 -27
- package/src/components/FeeTokenSelector/index.tsx +0 -308
- package/src/components/OnboardingDialog/index.tsx +0 -317
- package/src/components/OnboardingDialog/types.ts +0 -43
- package/src/components/OrSeparator/index.tsx +0 -13
- package/src/components/PermissionDialog/index.tsx +0 -598
- package/src/components/PermissionDialog/types.ts +0 -77
- package/src/components/SignatureDialog/index.tsx +0 -180
- package/src/components/SignatureDialog/types.ts +0 -27
- package/src/components/SiweDialog/index.tsx +0 -231
- package/src/components/SiweDialog/types.ts +0 -34
- package/src/components/TransactionDialog/DecodedCalldata.tsx +0 -79
- package/src/components/TransactionDialog/index.tsx +0 -663
- package/src/components/TransactionDialog/types.ts +0 -54
- package/src/components/ui/accordion.tsx +0 -66
- package/src/components/ui/avatar.tsx +0 -53
- package/src/components/ui/button.tsx +0 -59
- package/src/components/ui/card.tsx +0 -92
- package/src/components/ui/checkbox.tsx +0 -32
- package/src/components/ui/dialog.tsx +0 -183
- package/src/components/ui/dropdown-menu.tsx +0 -258
- package/src/components/ui/form.tsx +0 -167
- package/src/components/ui/input.tsx +0 -60
- package/src/components/ui/label.tsx +0 -24
- package/src/components/ui/popover.tsx +0 -48
- package/src/components/ui/scroll-area.tsx +0 -58
- package/src/components/ui/select.tsx +0 -160
- package/src/components/ui/separator.tsx +0 -28
- package/src/components/ui/sheet.tsx +0 -169
- package/src/components/ui/spinner.tsx +0 -18
- package/src/components/ui/tabs.tsx +0 -69
- package/src/components/ui/tooltip.tsx +0 -61
- package/src/hooks/index.ts +0 -5
- package/src/hooks/useChainIconURI.tsx +0 -117
- package/src/hooks/useDecodedCalldata.ts +0 -128
- package/src/hooks/useFeeTokenPrice.tsx +0 -36
- package/src/hooks/useGasEstimation.ts +0 -474
- package/src/hooks/useIsMobile.ts +0 -36
- package/src/icons/index.tsx +0 -114
- package/src/index.ts +0 -19
- package/src/lib/utils.ts +0 -6
- package/src/react/ReactUIHandler.tsx +0 -3004
- package/src/react/index.ts +0 -2
- package/src/styles.css +0 -210
- package/src/utils/formatAddress.ts +0 -24
- package/src/utils/index.ts +0 -4
- package/src/utils/justaNameInstance.ts +0 -25
- package/src/utils/tokenBalance.ts +0 -41
- package/src/utils/tokenPrice.ts +0 -58
- package/tailwind.config.js +0 -130
- package/tsconfig.json +0 -19
- package/tsconfig.lib.json +0 -43
- 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
|
-
}
|