@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.
- package/README.md +14 -0
- package/host.d.mts +18 -0
- package/host.js +871 -0
- package/host.js.map +1 -0
- package/hostRuntimeConfig-BOEJo2nq.d.mts +14 -0
- package/package.json +49 -0
- package/runtime-contract.json +27 -0
- package/sdk.d.mts +24 -0
- package/sdk.js +571 -0
- package/sdk.js.map +1 -0
- package/server.d.mts +26 -0
- package/server.js +200 -0
- package/server.js.map +1 -0
- package/txError-1lPZHdqI.d.mts +216 -0
- package/types-CX7pLmiT.d.mts +270 -0
- package/ui.d.mts +99 -0
- package/ui.js +232 -0
- package/ui.js.map +1 -0
package/sdk.js
ADDED
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { erc20Abi, formatUnits, parseUnits } from 'viem';
|
|
3
|
+
export { erc20Abi } from 'viem';
|
|
4
|
+
import Decimal from 'decimal.js';
|
|
5
|
+
import * as React3 from 'react';
|
|
6
|
+
import { createContext, useState, useRef, useCallback, useEffect, useMemo, useContext } from 'react';
|
|
7
|
+
import { useAccount, useChainId, useConnect, useDisconnect, useSwitchChain, usePublicClient, useWalletClient, useBalance } from 'wagmi';
|
|
8
|
+
import { Loader2, AlertTriangle, CheckCircle2, Info } from 'lucide-react';
|
|
9
|
+
import { clsx } from 'clsx';
|
|
10
|
+
import { twMerge } from 'tailwind-merge';
|
|
11
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
12
|
+
import { cva } from 'class-variance-authority';
|
|
13
|
+
|
|
14
|
+
var standardErc20Abi = erc20Abi;
|
|
15
|
+
function shortenAddress(raw, start = 6, end = 4) {
|
|
16
|
+
if (!raw) return "";
|
|
17
|
+
if (raw.length <= start + end) return raw;
|
|
18
|
+
return `${raw.slice(0, start)}...${raw.slice(-end)}`;
|
|
19
|
+
}
|
|
20
|
+
function formatTokenAmount(value, decimals = 18, precision = 4) {
|
|
21
|
+
if (value === void 0 || value === null) return "-";
|
|
22
|
+
const raw = typeof value === "bigint" ? formatUnits(value, decimals) : String(value);
|
|
23
|
+
return new Decimal(raw).toDecimalPlaces(precision, Decimal.ROUND_DOWN).toString();
|
|
24
|
+
}
|
|
25
|
+
function parseTokenAmount(value, decimals = 18) {
|
|
26
|
+
if (!/^\d*(\.\d*)?$/.test(value.trim())) {
|
|
27
|
+
throw new Error("Invalid decimal amount.");
|
|
28
|
+
}
|
|
29
|
+
return parseUnits(value || "0", decimals);
|
|
30
|
+
}
|
|
31
|
+
function formatPercentBps(value, precision = 2) {
|
|
32
|
+
if (value === void 0 || value === null) return "-";
|
|
33
|
+
return `${new Decimal(String(value)).div(100).toDecimalPlaces(precision).toString()}%`;
|
|
34
|
+
}
|
|
35
|
+
function formatCountdown(targetTimeMs) {
|
|
36
|
+
if (!targetTimeMs) return "-";
|
|
37
|
+
const diff = Math.max(0, targetTimeMs - Date.now());
|
|
38
|
+
const totalSeconds = Math.floor(diff / 1e3);
|
|
39
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
40
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
41
|
+
const seconds = totalSeconds % 60;
|
|
42
|
+
if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;
|
|
43
|
+
return `${minutes}m ${seconds}s`;
|
|
44
|
+
}
|
|
45
|
+
function cn(...inputs) {
|
|
46
|
+
return twMerge(clsx(inputs));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/ui/Alert.tsx
|
|
50
|
+
var toneClass = {
|
|
51
|
+
info: "border-[#3f5f8f] bg-[#17243a] text-[#d8e2ef]",
|
|
52
|
+
success: "border-[#35d39d]/35 bg-[#173c39] text-[#8ff4c8]",
|
|
53
|
+
warning: "border-[#f2c94c]/35 bg-[#42381f] text-[#fff0b8]",
|
|
54
|
+
danger: "border-[#ff6b6b]/35 bg-[#4a1f27] text-[#ffc4c4]"
|
|
55
|
+
};
|
|
56
|
+
var iconMap = {
|
|
57
|
+
info: Info,
|
|
58
|
+
success: CheckCircle2,
|
|
59
|
+
warning: AlertTriangle,
|
|
60
|
+
danger: AlertTriangle
|
|
61
|
+
};
|
|
62
|
+
function Alert({ children, tone = "info", className }) {
|
|
63
|
+
const Icon = iconMap[tone];
|
|
64
|
+
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));
|
|
65
|
+
}
|
|
66
|
+
var buttonVariants = cva(
|
|
67
|
+
"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",
|
|
68
|
+
{
|
|
69
|
+
variants: {
|
|
70
|
+
variant: {
|
|
71
|
+
default: "border border-[#4a90ff]/60 bg-[#315fb5] text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.16)] hover:bg-[#3b70d6]",
|
|
72
|
+
secondary: "border border-[#3a4d66] bg-[#304057] text-white hover:bg-[#3a4c65]",
|
|
73
|
+
outline: "border border-[#3d6ba8] bg-[#17243a] text-[#d8e8ff] hover:border-[#559cff] hover:bg-[#1d3150]",
|
|
74
|
+
ghost: "text-[#a8b5c7] hover:bg-[#304057] hover:text-white",
|
|
75
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
76
|
+
},
|
|
77
|
+
size: {
|
|
78
|
+
default: "h-10 px-4",
|
|
79
|
+
sm: "h-8 px-3 text-xs",
|
|
80
|
+
lg: "h-12 px-5",
|
|
81
|
+
icon: "h-10 w-10"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
defaultVariants: {
|
|
85
|
+
variant: "default",
|
|
86
|
+
size: "default"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
var Button = React3.forwardRef(
|
|
91
|
+
({ className, variant, size, asChild = false, loading = false, disabled, children, ...props }, ref) => {
|
|
92
|
+
const Comp = asChild ? Slot : "button";
|
|
93
|
+
if (asChild) {
|
|
94
|
+
return /* @__PURE__ */ React3.createElement(Comp, { className: cn(buttonVariants({ variant, size, className })), ref, ...props }, children);
|
|
95
|
+
}
|
|
96
|
+
return /* @__PURE__ */ React3.createElement(
|
|
97
|
+
Comp,
|
|
98
|
+
{
|
|
99
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
100
|
+
ref,
|
|
101
|
+
disabled: disabled || loading,
|
|
102
|
+
...props
|
|
103
|
+
},
|
|
104
|
+
loading ? /* @__PURE__ */ React3.createElement(Loader2, { className: "h-4 w-4 animate-spin" }) : null,
|
|
105
|
+
children
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
Button.displayName = "Button";
|
|
110
|
+
var Card = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React3.createElement(
|
|
111
|
+
"div",
|
|
112
|
+
{
|
|
113
|
+
ref,
|
|
114
|
+
className: cn(
|
|
115
|
+
"rounded-lg border border-[#3a4d66] bg-[#1d2938] text-white shadow-panel shadow-black/30 backdrop-blur",
|
|
116
|
+
className
|
|
117
|
+
),
|
|
118
|
+
...props
|
|
119
|
+
}
|
|
120
|
+
));
|
|
121
|
+
Card.displayName = "Card";
|
|
122
|
+
var CardHeader = React3.forwardRef(
|
|
123
|
+
({ className, ...props }, ref) => /* @__PURE__ */ React3.createElement("div", { ref, className: cn("flex flex-col gap-1.5 p-5", className), ...props })
|
|
124
|
+
);
|
|
125
|
+
CardHeader.displayName = "CardHeader";
|
|
126
|
+
var CardTitle = React3.forwardRef(
|
|
127
|
+
({ className, ...props }, ref) => /* @__PURE__ */ React3.createElement("h3", { ref, className: cn("text-lg font-semibold leading-tight tracking-normal text-white", className), ...props })
|
|
128
|
+
);
|
|
129
|
+
CardTitle.displayName = "CardTitle";
|
|
130
|
+
var CardDescription = React3.forwardRef(
|
|
131
|
+
({ className, ...props }, ref) => /* @__PURE__ */ React3.createElement("p", { ref, className: cn("text-sm leading-6 text-[#a8b5c7]", className), ...props })
|
|
132
|
+
);
|
|
133
|
+
CardDescription.displayName = "CardDescription";
|
|
134
|
+
var CardContent = React3.forwardRef(
|
|
135
|
+
({ className, ...props }, ref) => /* @__PURE__ */ React3.createElement("div", { ref, className: cn("p-5 pt-0", className), ...props })
|
|
136
|
+
);
|
|
137
|
+
CardContent.displayName = "CardContent";
|
|
138
|
+
var Input = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React3.createElement(
|
|
139
|
+
"input",
|
|
140
|
+
{
|
|
141
|
+
ref,
|
|
142
|
+
className: cn(
|
|
143
|
+
"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",
|
|
144
|
+
className
|
|
145
|
+
),
|
|
146
|
+
...props
|
|
147
|
+
}
|
|
148
|
+
));
|
|
149
|
+
Input.displayName = "Input";
|
|
150
|
+
|
|
151
|
+
// src/sdk/taxInfo.ts
|
|
152
|
+
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
153
|
+
function isValidAddress(value) {
|
|
154
|
+
return Boolean(value && /^0x[a-fA-F0-9]{40}$/.test(value));
|
|
155
|
+
}
|
|
156
|
+
function isSameAddress(a, b) {
|
|
157
|
+
return Boolean(a && b && a.toLowerCase() === b.toLowerCase());
|
|
158
|
+
}
|
|
159
|
+
function resolveManifestBinding(manifest, input) {
|
|
160
|
+
const bindings = manifest.match.bindings;
|
|
161
|
+
if (!bindings.length) return null;
|
|
162
|
+
const resolveUnique = (candidates) => {
|
|
163
|
+
if (candidates.length === 1) return candidates[0];
|
|
164
|
+
return null;
|
|
165
|
+
};
|
|
166
|
+
if (input.chainId && input.factoryAddress) {
|
|
167
|
+
return resolveUnique(bindings.filter((binding) => binding.chainId === input.chainId && isSameAddress(binding.factoryAddress, input.factoryAddress)));
|
|
168
|
+
}
|
|
169
|
+
if (input.chainId) {
|
|
170
|
+
return resolveUnique(bindings.filter((binding) => binding.chainId === input.chainId));
|
|
171
|
+
}
|
|
172
|
+
if (input.factoryAddress) {
|
|
173
|
+
return resolveUnique(bindings.filter((binding) => isSameAddress(binding.factoryAddress, input.factoryAddress)));
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
function resolveTokenMarketPhase(tokenInfo) {
|
|
178
|
+
if (!tokenInfo?.exists) return "unknown";
|
|
179
|
+
return tokenInfo.status >= 2 ? "dex-listed" : "internal-market";
|
|
180
|
+
}
|
|
181
|
+
function isCustomVaultTaxToken(tokenInfo) {
|
|
182
|
+
return Boolean(tokenInfo?.exists && tokenInfo.isTaxToken);
|
|
183
|
+
}
|
|
184
|
+
function isActionAvailableForPhase(stage, marketPhase = "unknown") {
|
|
185
|
+
if (stage === "read-only") return false;
|
|
186
|
+
if (stage === "both") return true;
|
|
187
|
+
if (marketPhase === "unknown") return false;
|
|
188
|
+
return stage === marketPhase;
|
|
189
|
+
}
|
|
190
|
+
function readTaxVaultHostContext(host) {
|
|
191
|
+
const tokenInfo = host?.tokenInfo;
|
|
192
|
+
const marketPhase = host?.marketPhase ?? resolveTokenMarketPhase(tokenInfo);
|
|
193
|
+
const isListed = host?.isListed ?? Boolean(tokenInfo && tokenInfo.status >= 2);
|
|
194
|
+
const isTaxToken = tokenInfo?.isTaxToken === true;
|
|
195
|
+
return {
|
|
196
|
+
tokenInfo,
|
|
197
|
+
taxInfo: host?.taxInfo ?? null,
|
|
198
|
+
vaultInfo: host?.vaultInfo ?? null,
|
|
199
|
+
feeMode: host?.feeMode ?? "unknown",
|
|
200
|
+
renderSurface: host?.renderSurface ?? "unavailable",
|
|
201
|
+
vaultType: host?.vaultType,
|
|
202
|
+
copyScope: host?.copyScope ?? "tax",
|
|
203
|
+
marketPhase,
|
|
204
|
+
isListed,
|
|
205
|
+
isTaxToken,
|
|
206
|
+
isSupportedCustomVaultToken: isCustomVaultTaxToken(tokenInfo)
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/sdk/runtimeContext.ts
|
|
211
|
+
var zeroAddress = "0x0000000000000000000000000000000000000000";
|
|
212
|
+
function explorerForChain(chainId) {
|
|
213
|
+
if (chainId === 56) return "https://bscscan.com";
|
|
214
|
+
if (chainId === 97) return "https://testnet.bscscan.com";
|
|
215
|
+
return void 0;
|
|
216
|
+
}
|
|
217
|
+
function chainLabelForChain(chainId) {
|
|
218
|
+
if (chainId === 56) return "BNB Chain";
|
|
219
|
+
if (chainId === 97) return "BNB Testnet";
|
|
220
|
+
return `Chain ${chainId}`;
|
|
221
|
+
}
|
|
222
|
+
function buildRuntimeExtraConfig(input) {
|
|
223
|
+
const hostRuntimeResult = input.hostRuntimeResult;
|
|
224
|
+
const runtimeOverrides = input.runtimeOverrides;
|
|
225
|
+
return {
|
|
226
|
+
...hostRuntimeResult ? {
|
|
227
|
+
hostRuntimeStatus: hostRuntimeResult.status,
|
|
228
|
+
hostRuntimePolicy: hostRuntimeResult.policy,
|
|
229
|
+
hostRuntimeDegradeReason: hostRuntimeResult.degradeReason,
|
|
230
|
+
hostRuntimeWarnings: hostRuntimeResult.warnings
|
|
231
|
+
} : {},
|
|
232
|
+
...hostRuntimeResult?.presentation?.extraConfig ?? {},
|
|
233
|
+
...runtimeOverrides?.extraConfig ?? {}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function createVaultRuntimeContext(input) {
|
|
237
|
+
const runtimeOverrides = input.runtimeOverrides;
|
|
238
|
+
const resolvedBinding = resolveManifestBinding(input.manifest, {
|
|
239
|
+
chainId: runtimeOverrides?.chainId ?? input.connectedChainId ?? input.hostRuntimeResult?.addresses.chainId,
|
|
240
|
+
factoryAddress: runtimeOverrides?.factoryAddress ?? input.hostRuntimeResult?.addresses.factoryAddress
|
|
241
|
+
});
|
|
242
|
+
const effectiveChainId = runtimeOverrides?.chainId ?? input.hostRuntimeResult?.addresses.chainId ?? input.connectedChainId ?? resolvedBinding?.chainId ?? input.manifest.match.bindings[0]?.chainId ?? 56;
|
|
243
|
+
return {
|
|
244
|
+
chainId: effectiveChainId,
|
|
245
|
+
factoryAddress: runtimeOverrides?.factoryAddress ?? input.hostRuntimeResult?.addresses.factoryAddress ?? resolvedBinding?.factoryAddress ?? zeroAddress,
|
|
246
|
+
tokenAddress: runtimeOverrides?.tokenAddress ?? input.hostRuntimeResult?.addresses.tokenAddress ?? zeroAddress,
|
|
247
|
+
vaultAddress: runtimeOverrides?.vaultAddress ?? input.hostRuntimeResult?.addresses.vaultAddress ?? zeroAddress,
|
|
248
|
+
userAddress: runtimeOverrides?.userAddress,
|
|
249
|
+
tokenSymbol: runtimeOverrides?.tokenSymbol ?? input.hostRuntimeResult?.tokenSymbol,
|
|
250
|
+
tokenName: runtimeOverrides?.tokenName ?? input.hostRuntimeResult?.tokenName,
|
|
251
|
+
tokenImageUrl: runtimeOverrides?.tokenImageUrl ?? input.hostRuntimeResult?.tokenImageUrl,
|
|
252
|
+
explorerBaseUrl: runtimeOverrides?.explorerBaseUrl ?? explorerForChain(effectiveChainId),
|
|
253
|
+
paymentToken: runtimeOverrides?.paymentToken ?? input.hostRuntimeResult?.paymentToken,
|
|
254
|
+
host: runtimeOverrides?.host ?? input.hostRuntimeResult?.host,
|
|
255
|
+
extraConfig: buildRuntimeExtraConfig(input),
|
|
256
|
+
manifest: input.manifest
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/sdk/runtime.tsx
|
|
261
|
+
var RuntimeContext = createContext(null);
|
|
262
|
+
function applyParams(value, params) {
|
|
263
|
+
if (!params) return value;
|
|
264
|
+
return Object.entries(params).reduce((acc, [key, item]) => acc.replaceAll(`{${key}}`, String(item)), value);
|
|
265
|
+
}
|
|
266
|
+
function getPreviewOracleEndpoint(extraConfig, oracleId) {
|
|
267
|
+
const oracleEndpoints = extraConfig?.oracleEndpoints;
|
|
268
|
+
if (!oracleEndpoints || typeof oracleEndpoints !== "object" || Array.isArray(oracleEndpoints)) return void 0;
|
|
269
|
+
const endpoint = oracleEndpoints[oracleId];
|
|
270
|
+
return typeof endpoint === "string" ? endpoint : void 0;
|
|
271
|
+
}
|
|
272
|
+
function VaultRuntimeProvider({ children, manifest, i18n, runtimeContext: runtimeOverrides, hostRuntimeResult, locale = "en" }) {
|
|
273
|
+
const [version, setVersion] = useState(0);
|
|
274
|
+
const [messages, setMessages] = useState([]);
|
|
275
|
+
const toastTimersRef = useRef(/* @__PURE__ */ new Map());
|
|
276
|
+
const { address: accountAddress, isConnected } = useAccount();
|
|
277
|
+
const connectedChainId = useChainId();
|
|
278
|
+
const { connect, connectors } = useConnect();
|
|
279
|
+
const { disconnect } = useDisconnect();
|
|
280
|
+
const { switchChainAsync, isPending: isSwitchingChain } = useSwitchChain();
|
|
281
|
+
const effectiveChainId = runtimeOverrides?.chainId ?? hostRuntimeResult?.addresses.chainId ?? connectedChainId ?? manifest.match.bindings[0]?.chainId ?? 56;
|
|
282
|
+
const publicClient = usePublicClient({ chainId: effectiveChainId });
|
|
283
|
+
const { data: walletClient } = useWalletClient();
|
|
284
|
+
const { data: nativeBalance } = useBalance({ address: accountAddress, chainId: isConnected ? connectedChainId : void 0 });
|
|
285
|
+
const dismissMessage = useCallback((id) => {
|
|
286
|
+
const timerId = toastTimersRef.current.get(id);
|
|
287
|
+
if (timerId) {
|
|
288
|
+
window.clearTimeout(timerId);
|
|
289
|
+
toastTimersRef.current.delete(id);
|
|
290
|
+
}
|
|
291
|
+
setMessages((items) => items.filter((item) => item.id !== id));
|
|
292
|
+
}, []);
|
|
293
|
+
useEffect(() => {
|
|
294
|
+
const timers = toastTimersRef.current;
|
|
295
|
+
return () => {
|
|
296
|
+
for (const timerId of timers.values()) {
|
|
297
|
+
window.clearTimeout(timerId);
|
|
298
|
+
}
|
|
299
|
+
timers.clear();
|
|
300
|
+
};
|
|
301
|
+
}, []);
|
|
302
|
+
const runtimeContext = useMemo(() => {
|
|
303
|
+
return createVaultRuntimeContext({
|
|
304
|
+
manifest,
|
|
305
|
+
connectedChainId,
|
|
306
|
+
hostRuntimeResult,
|
|
307
|
+
runtimeOverrides: {
|
|
308
|
+
...runtimeOverrides,
|
|
309
|
+
userAddress: runtimeOverrides?.userAddress ?? accountAddress
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}, [accountAddress, connectedChainId, hostRuntimeResult, manifest, runtimeOverrides]);
|
|
313
|
+
const i18nApi = useMemo(
|
|
314
|
+
() => ({
|
|
315
|
+
locale,
|
|
316
|
+
t(key, fallback, params) {
|
|
317
|
+
const defaultLocale = manifest.i18n[0];
|
|
318
|
+
const resolved = i18n[locale]?.[key] ?? i18n[defaultLocale]?.[key] ?? i18n.en?.[key] ?? fallback ?? key;
|
|
319
|
+
return applyParams(resolved, params);
|
|
320
|
+
}
|
|
321
|
+
}),
|
|
322
|
+
[i18n, locale, manifest.i18n]
|
|
323
|
+
);
|
|
324
|
+
const notify = useMemo(() => {
|
|
325
|
+
const push = (level, message) => {
|
|
326
|
+
const id = Date.now() + Math.floor(Math.random() * 1e3);
|
|
327
|
+
setMessages((items) => [{ id, level, message }, ...items].slice(0, 4));
|
|
328
|
+
const timerId = window.setTimeout(() => {
|
|
329
|
+
dismissMessage(id);
|
|
330
|
+
}, 4200);
|
|
331
|
+
toastTimersRef.current.set(id, timerId);
|
|
332
|
+
};
|
|
333
|
+
return {
|
|
334
|
+
info: (message) => push("info", message),
|
|
335
|
+
success: (message) => push("success", message),
|
|
336
|
+
warning: (message) => push("warning", message),
|
|
337
|
+
error: (message) => push("error", message)
|
|
338
|
+
};
|
|
339
|
+
}, [dismissMessage]);
|
|
340
|
+
const wallet = useMemo(
|
|
341
|
+
() => ({
|
|
342
|
+
address: accountAddress,
|
|
343
|
+
chainId: isConnected ? connectedChainId : void 0,
|
|
344
|
+
chainLabel: isConnected ? chainLabelForChain(connectedChainId) : void 0,
|
|
345
|
+
requiredChainId: runtimeContext.chainId,
|
|
346
|
+
requiredChainLabel: chainLabelForChain(runtimeContext.chainId),
|
|
347
|
+
isConnected,
|
|
348
|
+
isWrongNetwork: Boolean(isConnected && connectedChainId !== runtimeContext.chainId),
|
|
349
|
+
canSwitchChain: Boolean(switchChainAsync),
|
|
350
|
+
isSwitchingChain,
|
|
351
|
+
// Real native-token balance from the connected wallet. "0" until a wallet is connected.
|
|
352
|
+
balance: nativeBalance ? formatUnits(nativeBalance.value, nativeBalance.decimals) : "0",
|
|
353
|
+
// Wallet connection is host/shell-owned. In this preview the SDK forwards to the
|
|
354
|
+
// injected wagmi connector so the surface is functional rather than a no-op stub.
|
|
355
|
+
connect: () => {
|
|
356
|
+
const connector = connectors[0];
|
|
357
|
+
if (connector) connect({ connector });
|
|
358
|
+
},
|
|
359
|
+
disconnect: () => disconnect(),
|
|
360
|
+
switchChain: async () => {
|
|
361
|
+
if (!switchChainAsync) {
|
|
362
|
+
throw new Error(`Switch wallet to ${chainLabelForChain(runtimeContext.chainId)} before continuing.`);
|
|
363
|
+
}
|
|
364
|
+
await switchChainAsync({ chainId: runtimeContext.chainId });
|
|
365
|
+
}
|
|
366
|
+
}),
|
|
367
|
+
[accountAddress, connect, connectedChainId, connectors, disconnect, isConnected, isSwitchingChain, nativeBalance, runtimeContext.chainId, switchChainAsync]
|
|
368
|
+
);
|
|
369
|
+
const assertWalletWriteReady = useCallback(
|
|
370
|
+
(actionLabel) => {
|
|
371
|
+
if (!accountAddress) throw new Error("Wallet is not connected.");
|
|
372
|
+
if (isConnected && connectedChainId !== runtimeContext.chainId) {
|
|
373
|
+
throw new Error(`Wrong network. Switch wallet to ${chainLabelForChain(runtimeContext.chainId)} before ${actionLabel}.`);
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
[accountAddress, connectedChainId, isConnected, runtimeContext.chainId]
|
|
377
|
+
);
|
|
378
|
+
const readContract = useCallback(
|
|
379
|
+
async (request) => {
|
|
380
|
+
if (!publicClient || !request.abi || !request.address) {
|
|
381
|
+
throw new Error(`Contract read ${request.functionName} requires a public client, ABI, and address.`);
|
|
382
|
+
}
|
|
383
|
+
return await publicClient.readContract({
|
|
384
|
+
address: request.address,
|
|
385
|
+
abi: request.abi,
|
|
386
|
+
functionName: request.functionName,
|
|
387
|
+
args: request.args
|
|
388
|
+
});
|
|
389
|
+
},
|
|
390
|
+
[publicClient]
|
|
391
|
+
);
|
|
392
|
+
const simulateContract = useCallback(
|
|
393
|
+
async (request) => {
|
|
394
|
+
assertWalletWriteReady(`simulating ${request.functionName}`);
|
|
395
|
+
if (!publicClient || !request.abi || !request.address) {
|
|
396
|
+
throw new Error(`Contract simulation ${request.functionName} requires a public client, ABI, and address.`);
|
|
397
|
+
}
|
|
398
|
+
const simulation = await publicClient.simulateContract({
|
|
399
|
+
account: accountAddress,
|
|
400
|
+
address: request.address,
|
|
401
|
+
abi: request.abi,
|
|
402
|
+
functionName: request.functionName,
|
|
403
|
+
args: request.args,
|
|
404
|
+
value: request.value
|
|
405
|
+
});
|
|
406
|
+
return { request, result: simulation.result };
|
|
407
|
+
},
|
|
408
|
+
[accountAddress, assertWalletWriteReady, publicClient]
|
|
409
|
+
);
|
|
410
|
+
const writeContract = useCallback(
|
|
411
|
+
async (request) => {
|
|
412
|
+
assertWalletWriteReady(`writing ${request.functionName}`);
|
|
413
|
+
if (!walletClient || !request.abi || !request.address) {
|
|
414
|
+
throw new Error(`Contract write ${request.functionName} requires a wallet client, ABI, and address.`);
|
|
415
|
+
}
|
|
416
|
+
const hash = await walletClient.writeContract({
|
|
417
|
+
address: request.address,
|
|
418
|
+
abi: request.abi,
|
|
419
|
+
functionName: request.functionName,
|
|
420
|
+
args: request.args,
|
|
421
|
+
value: request.value
|
|
422
|
+
});
|
|
423
|
+
return hash;
|
|
424
|
+
},
|
|
425
|
+
[assertWalletWriteReady, walletClient]
|
|
426
|
+
);
|
|
427
|
+
const waitForTx = useCallback(
|
|
428
|
+
async (hash) => {
|
|
429
|
+
if (!publicClient) throw new Error("Transaction receipt requires a public client.");
|
|
430
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
431
|
+
return { hash, status: receipt.status === "success" ? "success" : "reverted" };
|
|
432
|
+
},
|
|
433
|
+
[publicClient]
|
|
434
|
+
);
|
|
435
|
+
const readOracle = useCallback(
|
|
436
|
+
async (oracleId, params) => {
|
|
437
|
+
const endpoint = getPreviewOracleEndpoint(runtimeContext.extraConfig, oracleId);
|
|
438
|
+
if (!endpoint) throw new Error(`Oracle ${oracleId} is not provisioned by the preview runtime.`);
|
|
439
|
+
const url = new URL(endpoint);
|
|
440
|
+
for (const [key, value] of Object.entries(params ?? {})) {
|
|
441
|
+
url.searchParams.set(key, value);
|
|
442
|
+
}
|
|
443
|
+
const response = await fetch(url.toString(), { cache: "no-store" });
|
|
444
|
+
if (!response.ok) throw new Error(`Oracle ${oracleId} returned ${response.status}.`);
|
|
445
|
+
return await response.json();
|
|
446
|
+
},
|
|
447
|
+
[runtimeContext.extraConfig]
|
|
448
|
+
);
|
|
449
|
+
const refetch = useCallback(async () => {
|
|
450
|
+
setVersion((item) => item + 1);
|
|
451
|
+
}, []);
|
|
452
|
+
const openExplorerTx = useCallback(
|
|
453
|
+
(hash) => {
|
|
454
|
+
if (!runtimeContext.explorerBaseUrl) return;
|
|
455
|
+
window.open(`${runtimeContext.explorerBaseUrl.replace(/\/$/, "")}/tx/${hash}`, "_blank", "noreferrer");
|
|
456
|
+
},
|
|
457
|
+
[runtimeContext.explorerBaseUrl]
|
|
458
|
+
);
|
|
459
|
+
const sdk = useMemo(
|
|
460
|
+
() => ({
|
|
461
|
+
context: runtimeContext,
|
|
462
|
+
i18n: i18nApi,
|
|
463
|
+
notify,
|
|
464
|
+
wallet,
|
|
465
|
+
readContract,
|
|
466
|
+
simulateContract,
|
|
467
|
+
writeContract,
|
|
468
|
+
waitForTx,
|
|
469
|
+
readOracle,
|
|
470
|
+
refetch,
|
|
471
|
+
refetchNonce: version,
|
|
472
|
+
openExplorerTx
|
|
473
|
+
}),
|
|
474
|
+
[i18nApi, notify, openExplorerTx, readContract, readOracle, refetch, runtimeContext, simulateContract, version, waitForTx, wallet, writeContract]
|
|
475
|
+
);
|
|
476
|
+
return /* @__PURE__ */ React.createElement(RuntimeContext.Provider, { value: sdk }, children, /* @__PURE__ */ React.createElement("div", { className: "pointer-events-none fixed bottom-4 right-4 z-50 flex w-[min(360px,calc(100vw-2rem))] flex-col gap-2" }, messages.map((message) => /* @__PURE__ */ React.createElement("div", { key: message.id, className: "pointer-events-auto" }, /* @__PURE__ */ React.createElement(
|
|
477
|
+
Alert,
|
|
478
|
+
{
|
|
479
|
+
tone: message.level === "error" ? "danger" : message.level,
|
|
480
|
+
className: "cursor-pointer shadow-panel backdrop-blur-sm transition hover:translate-y-[-1px]"
|
|
481
|
+
},
|
|
482
|
+
/* @__PURE__ */ React.createElement("button", { type: "button", className: "w-full text-left", onClick: () => dismissMessage(message.id) }, message.message)
|
|
483
|
+
)))));
|
|
484
|
+
}
|
|
485
|
+
function useFlapSdk() {
|
|
486
|
+
const sdk = useContext(RuntimeContext);
|
|
487
|
+
if (!sdk) throw new Error("useFlapSdk must be used within VaultRuntimeProvider.");
|
|
488
|
+
return sdk;
|
|
489
|
+
}
|
|
490
|
+
function useVaultContext() {
|
|
491
|
+
return useFlapSdk().context;
|
|
492
|
+
}
|
|
493
|
+
function useFlapI18n() {
|
|
494
|
+
return useFlapSdk().i18n;
|
|
495
|
+
}
|
|
496
|
+
function useFlapNotify() {
|
|
497
|
+
return useFlapSdk().notify;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// src/sdk/txError.ts
|
|
501
|
+
var defaultMessages = {
|
|
502
|
+
userRejected: "You rejected the wallet request.",
|
|
503
|
+
walletDisconnected: "Connect a wallet before sending this transaction.",
|
|
504
|
+
wrongNetwork: "Switch the wallet to the required chain before sending this transaction.",
|
|
505
|
+
insufficientFunds: "Insufficient wallet balance to complete this transaction.",
|
|
506
|
+
simulationFailed: "The transaction could not pass simulation.",
|
|
507
|
+
reverted: "The contract rejected this transaction.",
|
|
508
|
+
unknown: "Transaction failed. Please check your wallet and try again."
|
|
509
|
+
};
|
|
510
|
+
function isObject(value) {
|
|
511
|
+
return Boolean(value) && typeof value === "object";
|
|
512
|
+
}
|
|
513
|
+
function readString(value) {
|
|
514
|
+
return typeof value === "string" ? value : "";
|
|
515
|
+
}
|
|
516
|
+
function collectMessages(error, depth = 0, seen = /* @__PURE__ */ new Set()) {
|
|
517
|
+
if (depth > 4 || error == null || seen.has(error)) return [];
|
|
518
|
+
seen.add(error);
|
|
519
|
+
if (typeof error === "string") return [error];
|
|
520
|
+
if (!isObject(error)) return [];
|
|
521
|
+
const messages = [
|
|
522
|
+
readString(error.shortMessage),
|
|
523
|
+
readString(error.message),
|
|
524
|
+
readString(error.details),
|
|
525
|
+
readString(error.reason)
|
|
526
|
+
].filter(Boolean);
|
|
527
|
+
return [...messages, ...collectMessages(error.cause, depth + 1, seen)];
|
|
528
|
+
}
|
|
529
|
+
function collectCodes(error, depth = 0, seen = /* @__PURE__ */ new Set()) {
|
|
530
|
+
if (depth > 4 || error == null || seen.has(error)) return [];
|
|
531
|
+
seen.add(error);
|
|
532
|
+
if (!isObject(error)) return [];
|
|
533
|
+
const codes = [error.code, error.name].filter((value) => typeof value === "string" || typeof value === "number");
|
|
534
|
+
return [...codes, ...collectCodes(error.cause, depth + 1, seen)];
|
|
535
|
+
}
|
|
536
|
+
function getTxErrorKind(error) {
|
|
537
|
+
const message = collectMessages(error).join(" ").toLowerCase();
|
|
538
|
+
const codes = collectCodes(error).map((value) => String(value).toLowerCase()).join(" ");
|
|
539
|
+
if (codes.includes("4001") || codes.includes("action_rejected") || codes.includes("userrejectedrequesterror") || /user rejected|user denied|rejected the request|denied transaction|request rejected|cancelled/i.test(message)) {
|
|
540
|
+
return "userRejected";
|
|
541
|
+
}
|
|
542
|
+
if (/wallet is not connected|connector not connected|account not found|no wallet client/i.test(message)) {
|
|
543
|
+
return "walletDisconnected";
|
|
544
|
+
}
|
|
545
|
+
if (/wrong network|switch wallet to|switch network to|required chain/i.test(message)) {
|
|
546
|
+
return "wrongNetwork";
|
|
547
|
+
}
|
|
548
|
+
if (/insufficient funds|exceeds the balance|gas \* price \+ value/i.test(message)) {
|
|
549
|
+
return "insufficientFunds";
|
|
550
|
+
}
|
|
551
|
+
if (/simulation failed|simulatecontract|execution reverted during simulation/i.test(message)) {
|
|
552
|
+
return "simulationFailed";
|
|
553
|
+
}
|
|
554
|
+
if (/execution reverted|reverted with the following reason|contract function .* reverted|call exception/i.test(message)) {
|
|
555
|
+
return "reverted";
|
|
556
|
+
}
|
|
557
|
+
return "unknown";
|
|
558
|
+
}
|
|
559
|
+
function handleTxError(error, messages) {
|
|
560
|
+
const kind = getTxErrorKind(error);
|
|
561
|
+
return messages?.[kind] ?? defaultMessages[kind];
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// src/sdk/useFlapWallet.ts
|
|
565
|
+
function useFlapWallet() {
|
|
566
|
+
return useFlapSdk().wallet;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
export { VaultRuntimeProvider, ZERO_ADDRESS, formatCountdown, formatPercentBps, formatTokenAmount, getTxErrorKind, handleTxError, isActionAvailableForPhase, isCustomVaultTaxToken, isValidAddress, parseTokenAmount, readTaxVaultHostContext, resolveTokenMarketPhase, shortenAddress, standardErc20Abi, useVaultContext as useFlapChain, useFlapI18n, useFlapNotify, useFlapSdk, useFlapWallet, useVaultContext };
|
|
570
|
+
//# sourceMappingURL=sdk.js.map
|
|
571
|
+
//# sourceMappingURL=sdk.js.map
|