@flapsdk/vault-runtime 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.
@@ -0,0 +1,270 @@
1
+ import { Abi, PublicClient } from 'viem';
2
+
3
+ type Address = `0x${string}`;
4
+ interface PaymentToken {
5
+ address: Address;
6
+ symbol: string;
7
+ decimals: number;
8
+ isNative?: boolean;
9
+ }
10
+ type FeeMode = "creator" | "holder" | "gift" | "unknown";
11
+ type VaultRenderSurface = "standard-taxinfo" | "vault-taxinfo" | "feeinfo" | "unavailable";
12
+ type TokenMarketPhase = "unknown" | "internal-market" | "dex-listed";
13
+ type ActionAvailabilityStage = "internal-market" | "dex-listed" | "both" | "read-only";
14
+ interface FlapTokenInfo {
15
+ exists: boolean;
16
+ isTaxToken: boolean;
17
+ taxRate: number;
18
+ taxRateRaw?: bigint;
19
+ quoteTokenAddress?: Address;
20
+ status: number;
21
+ tokenVersion: number;
22
+ }
23
+ interface FlapFeeVaultInfo {
24
+ addr?: Address | null;
25
+ factory?: Address | null;
26
+ riskLevel?: number | null;
27
+ isOfficialVault?: boolean | null;
28
+ isVault?: boolean | null;
29
+ isAIConsumer?: boolean | null;
30
+ }
31
+ interface FlapTaxInfo {
32
+ marketBps: number;
33
+ deflationBps: number;
34
+ lpBps: number;
35
+ dividendBps: number;
36
+ feeRate: number;
37
+ buyTaxRate?: number;
38
+ sellTaxRate?: number;
39
+ burntTokenAmount?: bigint;
40
+ totalQuoteSentToDividend?: bigint;
41
+ totalQuoteAddedToLiquidity?: bigint;
42
+ totalTokenAddedToLiquidity?: bigint;
43
+ totalQuoteSentToMarketing?: bigint;
44
+ marketingWallet?: Address;
45
+ dividendToken?: Address;
46
+ quoteToken?: Address;
47
+ minimumShareBalance?: bigint;
48
+ vaultInfo?: FlapFeeVaultInfo | null;
49
+ }
50
+ interface FlapVaultPortalInfo {
51
+ found: boolean;
52
+ vault?: Address;
53
+ vaultFactory?: Address;
54
+ description?: string;
55
+ isOfficial?: boolean;
56
+ riskLevel?: number;
57
+ }
58
+ interface VaultHostContext {
59
+ tokenInfo?: FlapTokenInfo;
60
+ taxInfo?: FlapTaxInfo | null;
61
+ vaultInfo?: FlapVaultPortalInfo | null;
62
+ feeMode?: FeeMode;
63
+ renderSurface?: VaultRenderSurface;
64
+ vaultType?: string;
65
+ copyScope?: "tax" | "fee";
66
+ isListed?: boolean;
67
+ marketPhase?: TokenMarketPhase;
68
+ }
69
+ interface TokenMetadataSnapshot {
70
+ tokenSymbol?: string;
71
+ tokenName?: string;
72
+ }
73
+ interface TokenRuntimeSnapshot extends TokenMetadataSnapshot {
74
+ tokenInfo?: FlapTokenInfo | null;
75
+ taxInfo?: FlapTaxInfo | null;
76
+ vaultInfo?: FlapVaultPortalInfo | null;
77
+ host?: VaultHostContext;
78
+ giftVaultFactory?: Address;
79
+ paymentToken?: PaymentToken;
80
+ copyScope: "tax";
81
+ hasTaxVaults: boolean;
82
+ hostReadSupported: boolean;
83
+ hostReadFromChain: boolean;
84
+ }
85
+ type HostRuntimeStatus = "full-host" | "onchain" | "unavailable";
86
+ type HostRuntimePolicy = "prefer-full-host" | "require-full-host";
87
+ type HostRuntimeDegradeReason = "invalid-token-address" | "unsupported-chain" | "chain-read-unavailable" | "token-not-found" | "token-not-tax" | "host-presentation-not-configured" | "host-presentation-unavailable" | "host-presentation-required";
88
+ type HostRuntimeWarning = "tax-info-unavailable" | "payment-token-unavailable" | "vault-address-unavailable" | "factory-address-unavailable";
89
+ type HostRuntimeDataSource = "host-proxy" | "onchain" | "unavailable";
90
+ interface HostRuntimeSources {
91
+ tokenMetadata: HostRuntimeDataSource;
92
+ taxState: HostRuntimeDataSource;
93
+ vaultState: HostRuntimeDataSource;
94
+ presentation: HostRuntimeDataSource;
95
+ }
96
+ interface HostTokenPresentation {
97
+ tokenSymbol?: string;
98
+ tokenName?: string;
99
+ tokenImageUrl?: string;
100
+ tokenDetailHref?: string;
101
+ chainHref?: string;
102
+ extraConfig?: Record<string, unknown>;
103
+ }
104
+ interface HostRuntimeAddresses {
105
+ chainId: number;
106
+ tokenAddress: Address;
107
+ factoryAddress?: Address;
108
+ vaultAddress?: Address;
109
+ factoryAddressHint?: Address;
110
+ vaultAddressHint?: Address;
111
+ }
112
+ interface HostRuntimePresentationRequest {
113
+ chainId: number;
114
+ tokenAddress: Address;
115
+ factoryAddress?: Address;
116
+ vaultAddress?: Address;
117
+ snapshot: TokenRuntimeSnapshot;
118
+ }
119
+ type HostRuntimePresentationFetcher = (request: HostRuntimePresentationRequest) => Promise<HostTokenPresentation | null>;
120
+ interface HostRuntimeInput {
121
+ publicClient: PublicClient;
122
+ chainId: number;
123
+ tokenAddress: Address;
124
+ factoryAddressHint?: Address;
125
+ vaultAddressHint?: Address;
126
+ policy?: HostRuntimePolicy;
127
+ presentationFetcher?: HostRuntimePresentationFetcher;
128
+ }
129
+ interface HostRuntimeResult {
130
+ status: HostRuntimeStatus;
131
+ policy: HostRuntimePolicy;
132
+ degradeReason?: HostRuntimeDegradeReason;
133
+ warnings: HostRuntimeWarning[];
134
+ addresses: HostRuntimeAddresses;
135
+ snapshot: TokenRuntimeSnapshot | null;
136
+ host?: VaultHostContext;
137
+ paymentToken?: PaymentToken;
138
+ tokenSymbol?: string;
139
+ tokenName?: string;
140
+ tokenImageUrl?: string;
141
+ tokenDetailHref?: string;
142
+ chainHref?: string;
143
+ presentation?: HostTokenPresentation | null;
144
+ sources: HostRuntimeSources;
145
+ }
146
+ interface VaultRuntimeExtraConfig extends Record<string, unknown> {
147
+ previewFixture?: boolean;
148
+ tokenUnavailable?: boolean;
149
+ tokenDetailHref?: string;
150
+ chainHref?: string;
151
+ oracleEndpoints?: Record<string, string>;
152
+ hostRuntimeStatus?: HostRuntimeStatus;
153
+ hostRuntimePolicy?: HostRuntimePolicy;
154
+ hostRuntimeDegradeReason?: HostRuntimeDegradeReason;
155
+ hostRuntimeWarnings?: HostRuntimeWarning[];
156
+ }
157
+ interface ManifestBindingEntry {
158
+ chainId: number;
159
+ factoryAddress: Address;
160
+ vaultAddresses?: Address[];
161
+ tokenAddresses?: Address[];
162
+ }
163
+ interface VaultManifest {
164
+ artifactId: string;
165
+ name: string;
166
+ match: {
167
+ bindings: ManifestBindingEntry[];
168
+ };
169
+ endpoints?: EndpointPolicy;
170
+ i18n: string[];
171
+ }
172
+ type EndpointPolicy = string | string[];
173
+ interface VaultRuntimeContext {
174
+ chainId: number;
175
+ factoryAddress: Address;
176
+ tokenAddress: Address;
177
+ vaultAddress: Address;
178
+ userAddress?: Address;
179
+ tokenSymbol?: string;
180
+ tokenName?: string;
181
+ tokenImageUrl?: string;
182
+ explorerBaseUrl?: string;
183
+ paymentToken?: PaymentToken;
184
+ host?: VaultHostContext;
185
+ extraConfig?: VaultRuntimeExtraConfig;
186
+ manifest: VaultManifest;
187
+ }
188
+ interface VaultRuntimeContextOverrides extends Partial<Omit<VaultRuntimeContext, "manifest" | "extraConfig">> {
189
+ extraConfig?: VaultRuntimeExtraConfig;
190
+ }
191
+ interface CreateVaultRuntimeContextInput {
192
+ manifest: VaultManifest;
193
+ connectedChainId?: number;
194
+ hostRuntimeResult?: HostRuntimeResult | null;
195
+ runtimeOverrides?: VaultRuntimeContextOverrides;
196
+ }
197
+ interface ContractReadRequest {
198
+ /** Optional human-readable label for the target contract (e.g. "vault", "token"). Advisory only; the runtime keys off `address` + `abi`. */
199
+ contract?: string;
200
+ address?: Address;
201
+ abi?: Abi;
202
+ functionName: string;
203
+ args?: unknown[];
204
+ }
205
+ interface ContractWriteRequest extends ContractReadRequest {
206
+ value?: bigint;
207
+ }
208
+ interface SimulateResult {
209
+ request: ContractWriteRequest;
210
+ result?: unknown;
211
+ }
212
+ interface TxReceipt {
213
+ hash: Address;
214
+ status: "success" | "reverted";
215
+ }
216
+ interface FlapNotify {
217
+ info(message: string): void;
218
+ success(message: string): void;
219
+ warning(message: string): void;
220
+ error(message: string): void;
221
+ }
222
+ interface FlapI18n {
223
+ locale: string;
224
+ t(key: string, fallback?: string, params?: Record<string, string | number>): string;
225
+ }
226
+ interface FlapWallet {
227
+ address?: Address;
228
+ chainId?: number;
229
+ chainLabel?: string;
230
+ requiredChainId: number;
231
+ requiredChainLabel: string;
232
+ isConnected: boolean;
233
+ isWrongNetwork: boolean;
234
+ canSwitchChain: boolean;
235
+ isSwitchingChain: boolean;
236
+ balance: string;
237
+ connect(): void;
238
+ disconnect(): void;
239
+ switchChain(): Promise<void>;
240
+ }
241
+ interface FlapVaultSdk {
242
+ context: VaultRuntimeContext;
243
+ i18n: FlapI18n;
244
+ notify: FlapNotify;
245
+ wallet: FlapWallet;
246
+ readContract<T = unknown>(request: ContractReadRequest): Promise<T>;
247
+ simulateContract(request: ContractWriteRequest): Promise<SimulateResult>;
248
+ writeContract(request: ContractWriteRequest): Promise<Address>;
249
+ waitForTx(hash: Address): Promise<TxReceipt>;
250
+ readOracle<T = unknown>(oracleId: string, params?: Record<string, string>): Promise<T>;
251
+ /**
252
+ * Triggers a reload by incrementing `refetchNonce`. Components that want
253
+ * automatic reloads should include `sdk.refetchNonce` in their effect deps.
254
+ * The optional `keys` argument is reserved for future scoped invalidation and
255
+ * is currently advisory only.
256
+ */
257
+ refetch(keys?: string[]): Promise<void>;
258
+ /**
259
+ * Monotonic counter that changes every time `refetch()` is called. Add it to a
260
+ * `useEffect` dependency array to re-run reads after `refetch()`.
261
+ */
262
+ refetchNonce: number;
263
+ openExplorerTx(hash: Address): void;
264
+ }
265
+ interface VaultComponentProps {
266
+ sdk?: FlapVaultSdk;
267
+ context?: VaultRuntimeContext;
268
+ }
269
+
270
+ export type { ActionAvailabilityStage as A, VaultManifest as B, ContractReadRequest as C, VaultRenderSurface as D, EndpointPolicy as E, FeeMode as F, VaultRuntimeContext as G, HostRuntimeAddresses as H, VaultRuntimeContextOverrides as I, VaultRuntimeExtraConfig as J, ManifestBindingEntry as M, PaymentToken as P, SimulateResult as S, TokenMarketPhase as T, VaultComponentProps as V, Address as a, ContractWriteRequest as b, CreateVaultRuntimeContextInput as c, FlapFeeVaultInfo as d, FlapI18n as e, FlapNotify as f, FlapTaxInfo as g, FlapTokenInfo as h, FlapVaultPortalInfo as i, FlapVaultSdk as j, FlapWallet as k, HostRuntimeDataSource as l, HostRuntimeDegradeReason as m, HostRuntimeInput as n, HostRuntimePolicy as o, HostRuntimePresentationFetcher as p, HostRuntimePresentationRequest as q, HostRuntimeResult as r, HostRuntimeSources as s, HostRuntimeStatus as t, HostRuntimeWarning as u, HostTokenPresentation as v, TokenMetadataSnapshot as w, TokenRuntimeSnapshot as x, TxReceipt as y, VaultHostContext as z };
package/ui.d.mts ADDED
@@ -0,0 +1,99 @@
1
+ import * as React from 'react';
2
+ import { ReactNode } from 'react';
3
+ import * as class_variance_authority_types from 'class-variance-authority/types';
4
+ import { VariantProps } from 'class-variance-authority';
5
+
6
+ interface AddressLinkProps {
7
+ address?: string;
8
+ explorerBaseUrl?: string;
9
+ label?: string;
10
+ }
11
+ declare function AddressLink({ address, explorerBaseUrl, label }: AddressLinkProps): React.JSX.Element;
12
+
13
+ type AlertTone = "info" | "success" | "warning" | "danger";
14
+ declare function Alert({ children, tone, className }: {
15
+ children: ReactNode;
16
+ tone?: AlertTone;
17
+ className?: string;
18
+ }): React.JSX.Element;
19
+
20
+ declare const buttonVariants: (props?: ({
21
+ variant?: "default" | "secondary" | "outline" | "ghost" | "destructive" | null | undefined;
22
+ size?: "default" | "sm" | "lg" | "icon" | null | undefined;
23
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
24
+ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
25
+ asChild?: boolean;
26
+ loading?: boolean;
27
+ }
28
+ declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
29
+
30
+ declare const Card: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
31
+ declare const CardHeader: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
32
+ declare const CardTitle: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLHeadingElement> & React.RefAttributes<HTMLHeadingElement>>;
33
+ declare const CardDescription: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLParagraphElement> & React.RefAttributes<HTMLParagraphElement>>;
34
+ declare const CardContent: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
35
+
36
+ declare function Countdown({ targetTimeMs, fallback }: {
37
+ targetTimeMs?: number;
38
+ fallback?: string;
39
+ }): React.JSX.Element;
40
+
41
+ interface DataRowProps {
42
+ label: ReactNode;
43
+ value: ReactNode;
44
+ detail?: ReactNode;
45
+ className?: string;
46
+ }
47
+ declare function DataRow({ label, value, detail, className }: DataRowProps): React.JSX.Element;
48
+
49
+ type DetailTileTone = "default" | "primary" | "success" | "warning" | "muted";
50
+ interface DetailTileProps {
51
+ label: ReactNode;
52
+ value: ReactNode;
53
+ detail?: ReactNode;
54
+ icon?: ReactNode;
55
+ tone?: DetailTileTone;
56
+ className?: string;
57
+ valueClassName?: string;
58
+ }
59
+ declare function DetailTile({ label, value, detail, icon, tone, className, valueClassName }: DetailTileProps): React.JSX.Element;
60
+
61
+ interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
62
+ }
63
+ declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
64
+
65
+ type MetricTone = "default" | "primary" | "success" | "warning" | "muted";
66
+ interface MetricProps {
67
+ label: ReactNode;
68
+ value: ReactNode;
69
+ hint?: ReactNode;
70
+ tone?: MetricTone;
71
+ className?: string;
72
+ }
73
+ declare function Metric({ label, value, hint, tone, className }: MetricProps): React.JSX.Element;
74
+
75
+ type StatusTone = "neutral" | "success" | "warning" | "danger";
76
+ declare function StatusBadge({ children, tone }: {
77
+ children: ReactNode;
78
+ tone?: StatusTone;
79
+ }): React.JSX.Element;
80
+
81
+ type TxButtonState = "idle" | "validating" | "approving" | "approval_confirming" | "simulating" | "writing" | "confirming" | "success" | "failed";
82
+ interface TxButtonProps extends ButtonProps {
83
+ state?: TxButtonState;
84
+ idleLabel: string;
85
+ }
86
+ declare function TxButton({ state, idleLabel, children, ...props }: TxButtonProps): React.JSX.Element;
87
+
88
+ interface VaultBannerProps {
89
+ title: ReactNode;
90
+ description: ReactNode;
91
+ badges?: ReactNode;
92
+ meta?: ReactNode;
93
+ action?: ReactNode;
94
+ icon?: ReactNode;
95
+ className?: string;
96
+ }
97
+ declare function VaultBanner({ title, description, badges, meta, action, icon, className }: VaultBannerProps): React.JSX.Element;
98
+
99
+ export { AddressLink, Alert, Button, type ButtonProps, Card, CardContent, CardDescription, CardHeader, CardTitle, Countdown, DataRow, DetailTile, Input, type InputProps, Metric, StatusBadge, type StatusTone, TxButton, type TxButtonState, VaultBanner, buttonVariants };
package/ui.js ADDED
@@ -0,0 +1,232 @@
1
+ "use client";
2
+ import { Loader2, ExternalLink, AlertTriangle, CheckCircle2, Info, ShieldCheck } from 'lucide-react';
3
+ import 'decimal.js';
4
+ import 'viem';
5
+ import { clsx } from 'clsx';
6
+ import { twMerge } from 'tailwind-merge';
7
+ import * as React3 from 'react';
8
+ import { useState, useEffect, useMemo } from 'react';
9
+ import { Slot } from '@radix-ui/react-slot';
10
+ import { cva } from 'class-variance-authority';
11
+
12
+ function shortenAddress(raw, start = 6, end = 4) {
13
+ if (!raw) return "";
14
+ if (raw.length <= start + end) return raw;
15
+ return `${raw.slice(0, start)}...${raw.slice(-end)}`;
16
+ }
17
+
18
+ // src/ui/AddressLink.tsx
19
+ function AddressLink({ address, explorerBaseUrl, label }) {
20
+ if (!address) return /* @__PURE__ */ React.createElement("span", null, "-");
21
+ const href = explorerBaseUrl ? `${explorerBaseUrl.replace(/\/$/, "")}/address/${address}` : void 0;
22
+ const content = /* @__PURE__ */ React.createElement("span", { className: "inline-flex min-w-0 items-center gap-1 text-primary" }, /* @__PURE__ */ React.createElement("span", { className: "truncate" }, label || shortenAddress(address)), href ? /* @__PURE__ */ React.createElement(ExternalLink, { className: "h-3.5 w-3.5 shrink-0" }) : null);
23
+ return href ? /* @__PURE__ */ React.createElement("a", { href, target: "_blank", rel: "noreferrer" }, content) : content;
24
+ }
25
+ function cn(...inputs) {
26
+ return twMerge(clsx(inputs));
27
+ }
28
+
29
+ // src/ui/Alert.tsx
30
+ var toneClass = {
31
+ info: "border-[#3f5f8f] bg-[#17243a] text-[#d8e2ef]",
32
+ success: "border-[#35d39d]/35 bg-[#173c39] text-[#8ff4c8]",
33
+ warning: "border-[#f2c94c]/35 bg-[#42381f] text-[#fff0b8]",
34
+ danger: "border-[#ff6b6b]/35 bg-[#4a1f27] text-[#ffc4c4]"
35
+ };
36
+ var iconMap = {
37
+ info: Info,
38
+ success: CheckCircle2,
39
+ warning: AlertTriangle,
40
+ danger: AlertTriangle
41
+ };
42
+ function Alert({ children, tone = "info", className }) {
43
+ const Icon = iconMap[tone];
44
+ return /* @__PURE__ */ React.createElement("div", { className: cn("flex gap-3 rounded-md border p-3 text-sm font-medium leading-6", toneClass[tone], className) }, /* @__PURE__ */ React.createElement(Icon, { className: "mt-0.5 h-4 w-4 shrink-0 opacity-90" }), /* @__PURE__ */ React.createElement("div", { className: "min-w-0 break-words" }, children));
45
+ }
46
+ var buttonVariants = cva(
47
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#4a90ff]/60 disabled:pointer-events-none disabled:opacity-50",
48
+ {
49
+ variants: {
50
+ variant: {
51
+ default: "border border-[#4a90ff]/60 bg-[#315fb5] text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.16)] hover:bg-[#3b70d6]",
52
+ secondary: "border border-[#3a4d66] bg-[#304057] text-white hover:bg-[#3a4c65]",
53
+ outline: "border border-[#3d6ba8] bg-[#17243a] text-[#d8e8ff] hover:border-[#559cff] hover:bg-[#1d3150]",
54
+ ghost: "text-[#a8b5c7] hover:bg-[#304057] hover:text-white",
55
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90"
56
+ },
57
+ size: {
58
+ default: "h-10 px-4",
59
+ sm: "h-8 px-3 text-xs",
60
+ lg: "h-12 px-5",
61
+ icon: "h-10 w-10"
62
+ }
63
+ },
64
+ defaultVariants: {
65
+ variant: "default",
66
+ size: "default"
67
+ }
68
+ }
69
+ );
70
+ var Button = React3.forwardRef(
71
+ ({ className, variant, size, asChild = false, loading = false, disabled, children, ...props }, ref) => {
72
+ const Comp = asChild ? Slot : "button";
73
+ if (asChild) {
74
+ return /* @__PURE__ */ React3.createElement(Comp, { className: cn(buttonVariants({ variant, size, className })), ref, ...props }, children);
75
+ }
76
+ return /* @__PURE__ */ React3.createElement(
77
+ Comp,
78
+ {
79
+ className: cn(buttonVariants({ variant, size, className })),
80
+ ref,
81
+ disabled: disabled || loading,
82
+ ...props
83
+ },
84
+ loading ? /* @__PURE__ */ React3.createElement(Loader2, { className: "h-4 w-4 animate-spin" }) : null,
85
+ children
86
+ );
87
+ }
88
+ );
89
+ Button.displayName = "Button";
90
+ var Card = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React3.createElement(
91
+ "div",
92
+ {
93
+ ref,
94
+ className: cn(
95
+ "rounded-lg border border-[#3a4d66] bg-[#1d2938] text-white shadow-panel shadow-black/30 backdrop-blur",
96
+ className
97
+ ),
98
+ ...props
99
+ }
100
+ ));
101
+ Card.displayName = "Card";
102
+ var CardHeader = React3.forwardRef(
103
+ ({ className, ...props }, ref) => /* @__PURE__ */ React3.createElement("div", { ref, className: cn("flex flex-col gap-1.5 p-5", className), ...props })
104
+ );
105
+ CardHeader.displayName = "CardHeader";
106
+ var CardTitle = React3.forwardRef(
107
+ ({ className, ...props }, ref) => /* @__PURE__ */ React3.createElement("h3", { ref, className: cn("text-lg font-semibold leading-tight tracking-normal text-white", className), ...props })
108
+ );
109
+ CardTitle.displayName = "CardTitle";
110
+ var CardDescription = React3.forwardRef(
111
+ ({ className, ...props }, ref) => /* @__PURE__ */ React3.createElement("p", { ref, className: cn("text-sm leading-6 text-[#a8b5c7]", className), ...props })
112
+ );
113
+ CardDescription.displayName = "CardDescription";
114
+ var CardContent = React3.forwardRef(
115
+ ({ className, ...props }, ref) => /* @__PURE__ */ React3.createElement("div", { ref, className: cn("p-5 pt-0", className), ...props })
116
+ );
117
+ CardContent.displayName = "CardContent";
118
+ function formatMs(ms) {
119
+ const safe = Math.max(0, ms);
120
+ const totalSeconds = Math.floor(safe / 1e3);
121
+ const days = Math.floor(totalSeconds / 86400);
122
+ const hours = Math.floor(totalSeconds % 86400 / 3600);
123
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
124
+ const seconds = totalSeconds % 60;
125
+ if (days > 0) return `${days}d ${hours}h ${minutes}m`;
126
+ if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;
127
+ return `${minutes}m ${seconds}s`;
128
+ }
129
+ function Countdown({ targetTimeMs, fallback = "-" }) {
130
+ const [now, setNow] = useState(() => Date.now());
131
+ useEffect(() => {
132
+ const timer = window.setInterval(() => setNow(Date.now()), 1e3);
133
+ return () => window.clearInterval(timer);
134
+ }, []);
135
+ const text = useMemo(() => {
136
+ if (!targetTimeMs) return fallback;
137
+ return formatMs(targetTimeMs - now);
138
+ }, [fallback, now, targetTimeMs]);
139
+ return /* @__PURE__ */ React.createElement("span", null, text);
140
+ }
141
+
142
+ // src/ui/DataRow.tsx
143
+ function DataRow({ label, value, detail, className }) {
144
+ return /* @__PURE__ */ React.createElement("div", { className: cn("flex items-start justify-between gap-4 border-b border-white/8 py-3 last:border-0", className) }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0 text-sm text-white/54" }, label), /* @__PURE__ */ React.createElement("div", { className: "min-w-0 text-right" }, /* @__PURE__ */ React.createElement("div", { className: "break-words text-sm font-semibold text-white" }, value), detail ? /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs text-white/42" }, detail) : null));
145
+ }
146
+
147
+ // src/ui/DetailTile.tsx
148
+ var toneClass2 = {
149
+ default: "border-[#40536c] bg-[#304057]",
150
+ primary: "border-[#3b82f6]/60 bg-[#17325a]",
151
+ success: "border-[#35d39d]/40 bg-[#173c39]",
152
+ warning: "border-[#f2c94c]/40 bg-[#42381f]",
153
+ muted: "border-[#33445b] bg-[#172131]"
154
+ };
155
+ function DetailTile({ label, value, detail, icon, tone = "default", className, valueClassName }) {
156
+ return /* @__PURE__ */ React.createElement("div", { className: cn("min-w-0 rounded-md border p-3", toneClass2[tone], className) }, /* @__PURE__ */ React.createElement("div", { className: "flex min-w-0 items-center gap-2 text-xs font-medium text-[#a9b6c8]" }, icon ? /* @__PURE__ */ React.createElement("span", { className: "shrink-0 text-[#8fb7ff]" }, icon) : null, /* @__PURE__ */ React.createElement("span", { className: "min-w-0 truncate" }, label)), /* @__PURE__ */ React.createElement("div", { className: cn("mt-2 min-w-0 break-words text-base font-semibold leading-snug text-white", valueClassName) }, value), detail ? /* @__PURE__ */ React.createElement("div", { className: "mt-1 min-w-0 break-words text-xs leading-5 text-[#8d9caf]" }, detail) : null);
157
+ }
158
+ var Input = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React3.createElement(
159
+ "input",
160
+ {
161
+ ref,
162
+ className: cn(
163
+ "flex h-11 w-full rounded-md border border-[#aebdd0] bg-[#101827] px-3 py-2 text-sm font-semibold text-white caret-white shadow-[inset_0_1px_0_rgba(255,255,255,0.05)] outline-none transition-colors selection:bg-[#4a90ff]/45 selection:text-white placeholder:text-[#7f8ca0] focus:border-[#64a7ff] focus:bg-[#132033] focus:ring-2 focus:ring-[#4a90ff]/30 disabled:cursor-not-allowed disabled:border-[#40536c] disabled:bg-[#1d2938] disabled:text-[#8d9caf] disabled:opacity-80",
164
+ className
165
+ ),
166
+ ...props
167
+ }
168
+ ));
169
+ Input.displayName = "Input";
170
+
171
+ // src/ui/Metric.tsx
172
+ var toneClass3 = {
173
+ default: "border-[#40536c] bg-[#304057]",
174
+ primary: "border-[#3b82f6]/55 bg-[#17325a]",
175
+ success: "border-[#35d39d]/40 bg-[#173c39]",
176
+ warning: "border-[#f2c94c]/40 bg-[#42381f]",
177
+ muted: "border-[#33445b] bg-[#172131]"
178
+ };
179
+ var valueToneClass = {
180
+ default: "text-white",
181
+ primary: "text-[#8fb7ff]",
182
+ success: "text-[#6ff0bc]",
183
+ warning: "text-[#ffe08a]",
184
+ muted: "text-white"
185
+ };
186
+ function Metric({ label, value, hint, tone = "default", className }) {
187
+ return /* @__PURE__ */ React.createElement("div", { className: cn("rounded-md border p-3", toneClass3[tone], className) }, /* @__PURE__ */ React.createElement("div", { className: "text-xs font-medium text-[#a9b6c8]" }, label), /* @__PURE__ */ React.createElement("div", { className: cn("mt-2 break-words text-xl font-semibold tracking-normal", valueToneClass[tone]) }, value), hint ? /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs text-[#d8e2ef]" }, hint) : null);
188
+ }
189
+
190
+ // src/ui/StatusBadge.tsx
191
+ var toneClass4 = {
192
+ neutral: "border-[#5d6f86] bg-[#2d3b50] text-[#d8e2ef]",
193
+ success: "border-[#35d39d]/45 bg-[#176755]/55 text-[#78f2bf]",
194
+ warning: "border-[#f2c94c]/45 bg-[#59451f]/55 text-[#ffe08a]",
195
+ danger: "border-[#ff6b6b]/45 bg-[#5f2027]/55 text-[#ffb2b2]"
196
+ };
197
+ function StatusBadge({ children, tone = "neutral" }) {
198
+ return /* @__PURE__ */ React.createElement("span", { className: cn("inline-flex items-center rounded-full border px-3 py-1 text-xs font-semibold leading-none", toneClass4[tone]) }, children);
199
+ }
200
+
201
+ // src/ui/TxButton.tsx
202
+ var labels = {
203
+ idle: "",
204
+ validating: "Validating",
205
+ approving: "Approving",
206
+ approval_confirming: "Confirming approval",
207
+ simulating: "Simulating",
208
+ writing: "Sending",
209
+ confirming: "Confirming",
210
+ success: "Done",
211
+ failed: "Retry"
212
+ };
213
+ function TxButton({ state = "idle", idleLabel, children, ...props }) {
214
+ const loading = ["validating", "approving", "approval_confirming", "simulating", "writing", "confirming"].includes(state);
215
+ return /* @__PURE__ */ React.createElement(Button, { loading, ...props }, children || labels[state] || idleLabel);
216
+ }
217
+ function VaultBanner({ title, description, badges, meta, action, icon, className }) {
218
+ return /* @__PURE__ */ React.createElement(
219
+ "section",
220
+ {
221
+ className: cn(
222
+ "rounded-lg border border-[#3b82f6] bg-[#172a5a] p-4 shadow-[inset_0_1px_0_rgba(255,255,255,0.08)]",
223
+ className
224
+ )
225
+ },
226
+ /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "inline-flex h-4 w-4 shrink-0 items-center justify-center text-[#8fc8ff]" }, icon ?? /* @__PURE__ */ React.createElement(ShieldCheck, { className: "h-4 w-4" })), /* @__PURE__ */ React.createElement("h3", { className: "min-w-0 text-base font-semibold leading-tight text-white" }, title), badges), /* @__PURE__ */ React.createElement("p", { className: "mt-3 max-w-3xl text-sm font-medium leading-6 text-[#d8e2ef]" }, description), meta ? /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-xs font-medium leading-5 text-[#92a4bd]" }, meta) : null), action ? /* @__PURE__ */ React.createElement("div", { className: "shrink-0" }, action) : null)
227
+ );
228
+ }
229
+
230
+ export { AddressLink, Alert, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Countdown, DataRow, DetailTile, Input, Metric, StatusBadge, TxButton, VaultBanner, buttonVariants };
231
+ //# sourceMappingURL=ui.js.map
232
+ //# sourceMappingURL=ui.js.map
package/ui.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/sdk/format.ts","../../src/ui/AddressLink.tsx","../../src/ui/utils.ts","../../src/ui/Alert.tsx","../../src/ui/Button.tsx","../../src/ui/Card.tsx","../../src/ui/Countdown.tsx","../../src/ui/DataRow.tsx","../../src/ui/DetailTile.tsx","../../src/ui/Input.tsx","../../src/ui/Metric.tsx","../../src/ui/StatusBadge.tsx","../../src/ui/TxButton.tsx","../../src/ui/VaultBanner.tsx"],"names":["React2","toneClass","React4"],"mappings":"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","file":"ui.js","sourcesContent":["import Decimal from \"decimal.js\";\nimport { formatUnits, parseUnits } from \"viem\";\n\nexport function shortenAddress(raw?: string, start = 6, end = 4) {\n if (!raw) return \"\";\n if (raw.length <= start + end) return raw;\n return `${raw.slice(0, start)}...${raw.slice(-end)}`;\n}\n\nexport function formatTokenAmount(value?: bigint | string | number | null, decimals = 18, precision = 4) {\n if (value === undefined || value === null) return \"-\";\n const raw = typeof value === \"bigint\" ? formatUnits(value, decimals) : String(value);\n return new Decimal(raw).toDecimalPlaces(precision, Decimal.ROUND_DOWN).toString();\n}\n\nexport function parseTokenAmount(value: string, decimals = 18) {\n if (!/^\\d*(\\.\\d*)?$/.test(value.trim())) {\n throw new Error(\"Invalid decimal amount.\");\n }\n return parseUnits(value || \"0\", decimals);\n}\n\nexport function formatPercentBps(value?: bigint | number | null, precision = 2) {\n if (value === undefined || value === null) return \"-\";\n return `${new Decimal(String(value)).div(100).toDecimalPlaces(precision).toString()}%`;\n}\n\nexport function formatCountdown(targetTimeMs?: number) {\n if (!targetTimeMs) return \"-\";\n const diff = Math.max(0, targetTimeMs - Date.now());\n const totalSeconds = Math.floor(diff / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;\n return `${minutes}m ${seconds}s`;\n}\n","import { ExternalLink } from \"lucide-react\";\nimport { shortenAddress } from \"@/src/sdk/format\";\n\ninterface AddressLinkProps {\n address?: string;\n explorerBaseUrl?: string;\n label?: string;\n}\n\nexport function AddressLink({ address, explorerBaseUrl, label }: AddressLinkProps) {\n if (!address) return <span>-</span>;\n const href = explorerBaseUrl ? `${explorerBaseUrl.replace(/\\/$/, \"\")}/address/${address}` : undefined;\n const content = (\n <span className=\"inline-flex min-w-0 items-center gap-1 text-primary\">\n <span className=\"truncate\">{label || shortenAddress(address)}</span>\n {href ? <ExternalLink className=\"h-3.5 w-3.5 shrink-0\" /> : null}\n </span>\n );\n return href ? (\n <a href={href} target=\"_blank\" rel=\"noreferrer\">\n {content}\n </a>\n ) : (\n content\n );\n}\n","import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import { AlertTriangle, CheckCircle2, Info } from \"lucide-react\";\nimport { ReactNode } from \"react\";\nimport { cn } from \"./utils\";\n\ntype AlertTone = \"info\" | \"success\" | \"warning\" | \"danger\";\n\nconst toneClass: Record<AlertTone, string> = {\n info: \"border-[#3f5f8f] bg-[#17243a] text-[#d8e2ef]\",\n success: \"border-[#35d39d]/35 bg-[#173c39] text-[#8ff4c8]\",\n warning: \"border-[#f2c94c]/35 bg-[#42381f] text-[#fff0b8]\",\n danger: \"border-[#ff6b6b]/35 bg-[#4a1f27] text-[#ffc4c4]\",\n};\n\nconst iconMap = {\n info: Info,\n success: CheckCircle2,\n warning: AlertTriangle,\n danger: AlertTriangle,\n};\n\nexport function Alert({ children, tone = \"info\", className }: { children: ReactNode; tone?: AlertTone; className?: string }) {\n const Icon = iconMap[tone];\n return (\n <div className={cn(\"flex gap-3 rounded-md border p-3 text-sm font-medium leading-6\", toneClass[tone], className)}>\n <Icon className=\"mt-0.5 h-4 w-4 shrink-0 opacity-90\" />\n <div className=\"min-w-0 break-words\">{children}</div>\n </div>\n );\n}\n","import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Loader2 } from \"lucide-react\";\nimport { cn } from \"./utils\";\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#4a90ff]/60 disabled:pointer-events-none disabled:opacity-50\",\n {\n variants: {\n variant: {\n default: \"border border-[#4a90ff]/60 bg-[#315fb5] text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.16)] hover:bg-[#3b70d6]\",\n secondary: \"border border-[#3a4d66] bg-[#304057] text-white hover:bg-[#3a4c65]\",\n outline: \"border border-[#3d6ba8] bg-[#17243a] text-[#d8e8ff] hover:border-[#559cff] hover:bg-[#1d3150]\",\n ghost: \"text-[#a8b5c7] hover:bg-[#304057] hover:text-white\",\n destructive: \"bg-destructive text-destructive-foreground hover:bg-destructive/90\",\n },\n size: {\n default: \"h-10 px-4\",\n sm: \"h-8 px-3 text-xs\",\n lg: \"h-12 px-5\",\n icon: \"h-10 w-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nexport interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {\n asChild?: boolean;\n loading?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, loading = false, disabled, children, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n if (asChild) {\n return (\n <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props}>\n {children}\n </Comp>\n );\n }\n return (\n <Comp\n className={cn(buttonVariants({ variant, size, className }))}\n ref={ref}\n disabled={disabled || loading}\n {...props}\n >\n {loading ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : null}\n {children}\n </Comp>\n );\n },\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n","import * as React from \"react\";\nimport { cn } from \"./utils\";\n\nconst Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n \"rounded-lg border border-[#3a4d66] bg-[#1d2938] text-white shadow-panel shadow-black/30 backdrop-blur\",\n className,\n )}\n {...props}\n />\n));\nCard.displayName = \"Card\";\n\nconst CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex flex-col gap-1.5 p-5\", className)} {...props} />\n ),\n);\nCardHeader.displayName = \"CardHeader\";\n\nconst CardTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(\n ({ className, ...props }, ref) => (\n <h3 ref={ref} className={cn(\"text-lg font-semibold leading-tight tracking-normal text-white\", className)} {...props} />\n ),\n);\nCardTitle.displayName = \"CardTitle\";\n\nconst CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(\n ({ className, ...props }, ref) => (\n <p ref={ref} className={cn(\"text-sm leading-6 text-[#a8b5c7]\", className)} {...props} />\n ),\n);\nCardDescription.displayName = \"CardDescription\";\n\nconst CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => <div ref={ref} className={cn(\"p-5 pt-0\", className)} {...props} />,\n);\nCardContent.displayName = \"CardContent\";\n\nexport { Card, CardContent, CardDescription, CardHeader, CardTitle };\n","import { useEffect, useMemo, useState } from \"react\";\n\nfunction formatMs(ms: number) {\n const safe = Math.max(0, ms);\n const totalSeconds = Math.floor(safe / 1000);\n const days = Math.floor(totalSeconds / 86400);\n const hours = Math.floor((totalSeconds % 86400) / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n if (days > 0) return `${days}d ${hours}h ${minutes}m`;\n if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;\n return `${minutes}m ${seconds}s`;\n}\n\nexport function Countdown({ targetTimeMs, fallback = \"-\" }: { targetTimeMs?: number; fallback?: string }) {\n const [now, setNow] = useState(() => Date.now());\n\n useEffect(() => {\n const timer = window.setInterval(() => setNow(Date.now()), 1000);\n return () => window.clearInterval(timer);\n }, []);\n\n const text = useMemo(() => {\n if (!targetTimeMs) return fallback;\n return formatMs(targetTimeMs - now);\n }, [fallback, now, targetTimeMs]);\n\n return <span>{text}</span>;\n}\n","import { ReactNode } from \"react\";\nimport { cn } from \"./utils\";\n\ninterface DataRowProps {\n label: ReactNode;\n value: ReactNode;\n detail?: ReactNode;\n className?: string;\n}\n\nexport function DataRow({ label, value, detail, className }: DataRowProps) {\n return (\n <div className={cn(\"flex items-start justify-between gap-4 border-b border-white/8 py-3 last:border-0\", className)}>\n <div className=\"min-w-0 text-sm text-white/54\">{label}</div>\n <div className=\"min-w-0 text-right\">\n <div className=\"break-words text-sm font-semibold text-white\">{value}</div>\n {detail ? <div className=\"mt-1 text-xs text-white/42\">{detail}</div> : null}\n </div>\n </div>\n );\n}\n","import { ReactNode } from \"react\";\nimport { cn } from \"./utils\";\n\ntype DetailTileTone = \"default\" | \"primary\" | \"success\" | \"warning\" | \"muted\";\n\nconst toneClass: Record<DetailTileTone, string> = {\n default: \"border-[#40536c] bg-[#304057]\",\n primary: \"border-[#3b82f6]/60 bg-[#17325a]\",\n success: \"border-[#35d39d]/40 bg-[#173c39]\",\n warning: \"border-[#f2c94c]/40 bg-[#42381f]\",\n muted: \"border-[#33445b] bg-[#172131]\",\n};\n\ninterface DetailTileProps {\n label: ReactNode;\n value: ReactNode;\n detail?: ReactNode;\n icon?: ReactNode;\n tone?: DetailTileTone;\n className?: string;\n valueClassName?: string;\n}\n\nexport function DetailTile({ label, value, detail, icon, tone = \"default\", className, valueClassName }: DetailTileProps) {\n return (\n <div className={cn(\"min-w-0 rounded-md border p-3\", toneClass[tone], className)}>\n <div className=\"flex min-w-0 items-center gap-2 text-xs font-medium text-[#a9b6c8]\">\n {icon ? <span className=\"shrink-0 text-[#8fb7ff]\">{icon}</span> : null}\n <span className=\"min-w-0 truncate\">{label}</span>\n </div>\n <div className={cn(\"mt-2 min-w-0 break-words text-base font-semibold leading-snug text-white\", valueClassName)}>\n {value}\n </div>\n {detail ? <div className=\"mt-1 min-w-0 break-words text-xs leading-5 text-[#8d9caf]\">{detail}</div> : null}\n </div>\n );\n}\n","import * as React from \"react\";\nimport { cn } from \"./utils\";\n\nexport interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, ...props }, ref) => (\n <input\n ref={ref}\n className={cn(\n \"flex h-11 w-full rounded-md border border-[#aebdd0] bg-[#101827] px-3 py-2 text-sm font-semibold text-white caret-white shadow-[inset_0_1px_0_rgba(255,255,255,0.05)] outline-none transition-colors selection:bg-[#4a90ff]/45 selection:text-white placeholder:text-[#7f8ca0] focus:border-[#64a7ff] focus:bg-[#132033] focus:ring-2 focus:ring-[#4a90ff]/30 disabled:cursor-not-allowed disabled:border-[#40536c] disabled:bg-[#1d2938] disabled:text-[#8d9caf] disabled:opacity-80\",\n className,\n )}\n {...props}\n />\n));\nInput.displayName = \"Input\";\n\nexport { Input };\n","import { ReactNode } from \"react\";\nimport { cn } from \"./utils\";\n\ntype MetricTone = \"default\" | \"primary\" | \"success\" | \"warning\" | \"muted\";\n\nconst toneClass: Record<MetricTone, string> = {\n default: \"border-[#40536c] bg-[#304057]\",\n primary: \"border-[#3b82f6]/55 bg-[#17325a]\",\n success: \"border-[#35d39d]/40 bg-[#173c39]\",\n warning: \"border-[#f2c94c]/40 bg-[#42381f]\",\n muted: \"border-[#33445b] bg-[#172131]\",\n};\n\nconst valueToneClass: Record<MetricTone, string> = {\n default: \"text-white\",\n primary: \"text-[#8fb7ff]\",\n success: \"text-[#6ff0bc]\",\n warning: \"text-[#ffe08a]\",\n muted: \"text-white\",\n};\n\ninterface MetricProps {\n label: ReactNode;\n value: ReactNode;\n hint?: ReactNode;\n tone?: MetricTone;\n className?: string;\n}\n\nexport function Metric({ label, value, hint, tone = \"default\", className }: MetricProps) {\n return (\n <div className={cn(\"rounded-md border p-3\", toneClass[tone], className)}>\n <div className=\"text-xs font-medium text-[#a9b6c8]\">{label}</div>\n <div className={cn(\"mt-2 break-words text-xl font-semibold tracking-normal\", valueToneClass[tone])}>{value}</div>\n {hint ? <div className=\"mt-1 text-xs text-[#d8e2ef]\">{hint}</div> : null}\n </div>\n );\n}\n","import { ReactNode } from \"react\";\nimport { cn } from \"./utils\";\n\nexport type StatusTone = \"neutral\" | \"success\" | \"warning\" | \"danger\";\n\nconst toneClass: Record<StatusTone, string> = {\n neutral: \"border-[#5d6f86] bg-[#2d3b50] text-[#d8e2ef]\",\n success: \"border-[#35d39d]/45 bg-[#176755]/55 text-[#78f2bf]\",\n warning: \"border-[#f2c94c]/45 bg-[#59451f]/55 text-[#ffe08a]\",\n danger: \"border-[#ff6b6b]/45 bg-[#5f2027]/55 text-[#ffb2b2]\",\n};\n\nexport function StatusBadge({ children, tone = \"neutral\" }: { children: ReactNode; tone?: StatusTone }) {\n return (\n <span className={cn(\"inline-flex items-center rounded-full border px-3 py-1 text-xs font-semibold leading-none\", toneClass[tone])}>\n {children}\n </span>\n );\n}\n","import { Button, type ButtonProps } from \"./Button\";\n\nexport type TxButtonState = \"idle\" | \"validating\" | \"approving\" | \"approval_confirming\" | \"simulating\" | \"writing\" | \"confirming\" | \"success\" | \"failed\";\n\nconst labels: Record<TxButtonState, string> = {\n idle: \"\",\n validating: \"Validating\",\n approving: \"Approving\",\n approval_confirming: \"Confirming approval\",\n simulating: \"Simulating\",\n writing: \"Sending\",\n confirming: \"Confirming\",\n success: \"Done\",\n failed: \"Retry\",\n};\n\ninterface TxButtonProps extends ButtonProps {\n state?: TxButtonState;\n idleLabel: string;\n}\n\nexport function TxButton({ state = \"idle\", idleLabel, children, ...props }: TxButtonProps) {\n const loading = [\"validating\", \"approving\", \"approval_confirming\", \"simulating\", \"writing\", \"confirming\"].includes(state);\n return (\n <Button loading={loading} {...props}>\n {children || labels[state] || idleLabel}\n </Button>\n );\n}\n","import { ShieldCheck } from \"lucide-react\";\nimport { ReactNode } from \"react\";\nimport { cn } from \"./utils\";\n\ninterface VaultBannerProps {\n title: ReactNode;\n description: ReactNode;\n badges?: ReactNode;\n meta?: ReactNode;\n action?: ReactNode;\n icon?: ReactNode;\n className?: string;\n}\n\nexport function VaultBanner({ title, description, badges, meta, action, icon, className }: VaultBannerProps) {\n return (\n <section\n className={cn(\n \"rounded-lg border border-[#3b82f6] bg-[#172a5a] p-4 shadow-[inset_0_1px_0_rgba(255,255,255,0.08)]\",\n className,\n )}\n >\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <span className=\"inline-flex h-4 w-4 shrink-0 items-center justify-center text-[#8fc8ff]\">\n {icon ?? <ShieldCheck className=\"h-4 w-4\" />}\n </span>\n <h3 className=\"min-w-0 text-base font-semibold leading-tight text-white\">{title}</h3>\n {badges}\n </div>\n <p className=\"mt-3 max-w-3xl text-sm font-medium leading-6 text-[#d8e2ef]\">{description}</p>\n {meta ? <div className=\"mt-2 text-xs font-medium leading-5 text-[#92a4bd]\">{meta}</div> : null}\n </div>\n {action ? <div className=\"shrink-0\">{action}</div> : null}\n </div>\n </section>\n );\n}\n"]}