@dipansrimany/mlink-sdk 0.1.0 → 0.3.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,580 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var zod = require('zod');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ // src/constants.ts
8
+ var MANTLE_SEPOLIA = {
9
+ chainId: 5003,
10
+ name: "Mantle Sepolia",
11
+ rpcUrl: "https://rpc.sepolia.mantle.xyz",
12
+ explorerUrl: "https://sepolia.mantlescan.xyz",
13
+ nativeCurrency: {
14
+ name: "MNT",
15
+ symbol: "MNT",
16
+ decimals: 18
17
+ }
18
+ };
19
+ var DEFAULT_CHAIN = MANTLE_SEPOLIA;
20
+ var ACTION_QUERY_PARAM = "action";
21
+
22
+ // src/utils.ts
23
+ function parseBlinkUrl(blinkUrl) {
24
+ try {
25
+ const url = new URL(blinkUrl);
26
+ const actionParam = url.searchParams.get(ACTION_QUERY_PARAM);
27
+ if (!actionParam) return null;
28
+ const actionUrl = decodeURIComponent(actionParam);
29
+ new URL(actionUrl);
30
+ return actionUrl;
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+ var addressRegex = /^0x[a-fA-F0-9]{40}$/;
36
+ var hexRegex = /^0x[a-fA-F0-9]*$/;
37
+ var ActionButtonSchema = zod.z.object({
38
+ label: zod.z.string().min(1).max(50),
39
+ value: zod.z.string().min(1),
40
+ type: zod.z.enum(["button", "input"]),
41
+ placeholder: zod.z.string().optional(),
42
+ disabled: zod.z.boolean().optional()
43
+ });
44
+ var ActionMetadataSchema = zod.z.object({
45
+ title: zod.z.string().min(1).max(100),
46
+ icon: zod.z.string().url(),
47
+ description: zod.z.string().min(1).max(500),
48
+ actions: zod.z.array(ActionButtonSchema).min(1),
49
+ disabled: zod.z.boolean().optional(),
50
+ error: zod.z.object({ message: zod.z.string() }).optional()
51
+ });
52
+ zod.z.object({
53
+ account: zod.z.string().regex(addressRegex, "Invalid Ethereum address"),
54
+ action: zod.z.string().min(1),
55
+ input: zod.z.string().optional()
56
+ });
57
+ var EVMTransactionSchema = zod.z.object({
58
+ to: zod.z.string().regex(addressRegex, "Invalid to address"),
59
+ value: zod.z.string(),
60
+ data: zod.z.string().regex(hexRegex, "Invalid hex data"),
61
+ chainId: zod.z.number().positive()
62
+ });
63
+ var TransactionResponseSchema = zod.z.object({
64
+ transaction: EVMTransactionSchema,
65
+ message: zod.z.string().optional()
66
+ });
67
+ function validateActionMetadata(data) {
68
+ const result = ActionMetadataSchema.safeParse(data);
69
+ if (result.success) {
70
+ return { success: true, data: result.data };
71
+ }
72
+ return { success: false, error: result.error.message };
73
+ }
74
+ function validateTransactionResponse(data) {
75
+ const result = TransactionResponseSchema.safeParse(data);
76
+ if (result.success) {
77
+ return { success: true, data: result.data };
78
+ }
79
+ return { success: false, error: result.error.message };
80
+ }
81
+
82
+ // src/react/useMlink.ts
83
+ var DEFAULT_REFRESH_INTERVAL = 10 * 60 * 1e3;
84
+ function useMlink(url, options = {}) {
85
+ const { refreshInterval = DEFAULT_REFRESH_INTERVAL, enabled = true } = options;
86
+ const [status, setStatus] = react.useState("idle");
87
+ const [metadata, setMetadata] = react.useState(null);
88
+ const [error, setError] = react.useState(null);
89
+ const abortControllerRef = react.useRef(null);
90
+ const intervalRef = react.useRef(null);
91
+ const actionUrl = parseBlinkUrl(url) || url;
92
+ const fetchMetadata = react.useCallback(async () => {
93
+ if (!actionUrl || !enabled) return;
94
+ if (abortControllerRef.current) {
95
+ abortControllerRef.current.abort();
96
+ }
97
+ abortControllerRef.current = new AbortController();
98
+ setStatus("loading");
99
+ setError(null);
100
+ try {
101
+ const response = await fetch(actionUrl, {
102
+ method: "GET",
103
+ headers: {
104
+ Accept: "application/json"
105
+ },
106
+ signal: abortControllerRef.current.signal
107
+ });
108
+ if (!response.ok) {
109
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
110
+ }
111
+ const data = await response.json();
112
+ const validation = validateActionMetadata(data);
113
+ if (!validation.success) {
114
+ throw new Error(`Invalid metadata: ${validation.error}`);
115
+ }
116
+ setMetadata(validation.data);
117
+ setStatus("ready");
118
+ } catch (err) {
119
+ if (err instanceof Error && err.name === "AbortError") {
120
+ return;
121
+ }
122
+ const errorMessage = err instanceof Error ? err.message : "Failed to fetch mlink data";
123
+ setError(errorMessage);
124
+ setStatus("error");
125
+ }
126
+ }, [actionUrl, enabled]);
127
+ react.useEffect(() => {
128
+ if (enabled) {
129
+ fetchMetadata();
130
+ }
131
+ return () => {
132
+ if (abortControllerRef.current) {
133
+ abortControllerRef.current.abort();
134
+ }
135
+ };
136
+ }, [fetchMetadata, enabled]);
137
+ react.useEffect(() => {
138
+ if (!enabled || !refreshInterval) return;
139
+ intervalRef.current = setInterval(() => {
140
+ fetchMetadata();
141
+ }, refreshInterval);
142
+ return () => {
143
+ if (intervalRef.current) {
144
+ clearInterval(intervalRef.current);
145
+ }
146
+ };
147
+ }, [fetchMetadata, refreshInterval, enabled]);
148
+ return {
149
+ status,
150
+ metadata,
151
+ error,
152
+ url: actionUrl,
153
+ refresh: fetchMetadata
154
+ };
155
+ }
156
+ function useExecuteMlink(options) {
157
+ const { adapter, actionUrl, onSuccess, onError } = options;
158
+ const [status, setStatus] = react.useState("ready");
159
+ const [txHash, setTxHash] = react.useState(null);
160
+ const [error, setError] = react.useState(null);
161
+ const reset = react.useCallback(() => {
162
+ setStatus("ready");
163
+ setTxHash(null);
164
+ setError(null);
165
+ }, []);
166
+ const execute = react.useCallback(
167
+ async (action, input) => {
168
+ setStatus("executing");
169
+ setError(null);
170
+ setTxHash(null);
171
+ try {
172
+ let account = adapter.getAddress();
173
+ if (!adapter.isConnected() || !account) {
174
+ account = await adapter.connect();
175
+ }
176
+ const response = await fetch(actionUrl, {
177
+ method: "POST",
178
+ headers: {
179
+ "Content-Type": "application/json"
180
+ },
181
+ body: JSON.stringify({
182
+ account,
183
+ action,
184
+ input
185
+ })
186
+ });
187
+ if (!response.ok) {
188
+ const errorData = await response.json().catch(() => ({}));
189
+ throw new Error(
190
+ errorData.message || `HTTP ${response.status}: ${response.statusText}`
191
+ );
192
+ }
193
+ const data = await response.json();
194
+ const validation = validateTransactionResponse(data);
195
+ if (!validation.success) {
196
+ throw new Error(`Invalid response: ${validation.error}`);
197
+ }
198
+ const hash = await adapter.signAndSendTransaction(data.transaction);
199
+ setTxHash(hash);
200
+ setStatus("success");
201
+ onSuccess?.(hash, action);
202
+ return {
203
+ success: true,
204
+ txHash: hash,
205
+ message: data.message
206
+ };
207
+ } catch (err) {
208
+ const errorMessage = err instanceof Error ? err.message : "Transaction failed";
209
+ setError(errorMessage);
210
+ setStatus("error");
211
+ onError?.(errorMessage);
212
+ return {
213
+ success: false,
214
+ error: errorMessage
215
+ };
216
+ }
217
+ },
218
+ [adapter, actionUrl, onSuccess, onError]
219
+ );
220
+ return {
221
+ execute,
222
+ status,
223
+ txHash,
224
+ error,
225
+ reset
226
+ };
227
+ }
228
+
229
+ // src/react/themes.ts
230
+ var lightTheme = {
231
+ // Colors
232
+ "--mlink-bg-primary": "#ffffff",
233
+ "--mlink-bg-secondary": "#f8f9fa",
234
+ "--mlink-border-color": "#e9ecef",
235
+ "--mlink-text-primary": "#212529",
236
+ "--mlink-text-secondary": "#6c757d",
237
+ "--mlink-text-link": "#0d6efd",
238
+ "--mlink-button-bg": "#212529",
239
+ "--mlink-button-text": "#ffffff",
240
+ "--mlink-button-hover": "#343a40",
241
+ "--mlink-button-disabled": "#adb5bd",
242
+ "--mlink-input-bg": "#ffffff",
243
+ "--mlink-input-border": "#ced4da",
244
+ "--mlink-input-text": "#212529",
245
+ "--mlink-input-placeholder": "#adb5bd",
246
+ "--mlink-success": "#198754",
247
+ "--mlink-error": "#dc3545",
248
+ "--mlink-warning": "#ffc107",
249
+ // Sizing
250
+ "--mlink-border-radius": "12px",
251
+ "--mlink-button-radius": "8px",
252
+ "--mlink-input-radius": "8px",
253
+ // Shadows
254
+ "--mlink-shadow": "0 2px 8px rgba(0, 0, 0, 0.08)"
255
+ };
256
+ var darkTheme = {
257
+ // Colors
258
+ "--mlink-bg-primary": "#1a1a1a",
259
+ "--mlink-bg-secondary": "#2d2d2d",
260
+ "--mlink-border-color": "#404040",
261
+ "--mlink-text-primary": "#ffffff",
262
+ "--mlink-text-secondary": "#a0a0a0",
263
+ "--mlink-text-link": "#60a5fa",
264
+ "--mlink-button-bg": "#ffffff",
265
+ "--mlink-button-text": "#1a1a1a",
266
+ "--mlink-button-hover": "#e5e5e5",
267
+ "--mlink-button-disabled": "#525252",
268
+ "--mlink-input-bg": "#2d2d2d",
269
+ "--mlink-input-border": "#404040",
270
+ "--mlink-input-text": "#ffffff",
271
+ "--mlink-input-placeholder": "#6b7280",
272
+ "--mlink-success": "#22c55e",
273
+ "--mlink-error": "#ef4444",
274
+ "--mlink-warning": "#f59e0b",
275
+ // Sizing
276
+ "--mlink-border-radius": "12px",
277
+ "--mlink-button-radius": "8px",
278
+ "--mlink-input-radius": "8px",
279
+ // Shadows
280
+ "--mlink-shadow": "0 2px 8px rgba(0, 0, 0, 0.3)"
281
+ };
282
+ var mantleTheme = {
283
+ // Colors - Mantle brand colors
284
+ "--mlink-bg-primary": "#0a0a0a",
285
+ "--mlink-bg-secondary": "#141414",
286
+ "--mlink-border-color": "#2a2a2a",
287
+ "--mlink-text-primary": "#ffffff",
288
+ "--mlink-text-secondary": "#9ca3af",
289
+ "--mlink-text-link": "#65d9e4",
290
+ "--mlink-button-bg": "#65d9e4",
291
+ "--mlink-button-text": "#000000",
292
+ "--mlink-button-hover": "#85e3ec",
293
+ "--mlink-button-disabled": "#374151",
294
+ "--mlink-input-bg": "#1f1f1f",
295
+ "--mlink-input-border": "#374151",
296
+ "--mlink-input-text": "#ffffff",
297
+ "--mlink-input-placeholder": "#6b7280",
298
+ "--mlink-success": "#34d399",
299
+ "--mlink-error": "#f87171",
300
+ "--mlink-warning": "#fbbf24",
301
+ // Sizing
302
+ "--mlink-border-radius": "16px",
303
+ "--mlink-button-radius": "10px",
304
+ "--mlink-input-radius": "10px",
305
+ // Shadows
306
+ "--mlink-shadow": "0 4px 16px rgba(101, 217, 228, 0.1)"
307
+ };
308
+ var themePresets = {
309
+ light: lightTheme,
310
+ dark: darkTheme,
311
+ mantle: mantleTheme
312
+ };
313
+ function resolveTheme(theme) {
314
+ if (!theme) {
315
+ return darkTheme;
316
+ }
317
+ if (typeof theme === "string") {
318
+ return themePresets[theme] || darkTheme;
319
+ }
320
+ return { ...darkTheme, ...theme };
321
+ }
322
+ function themeToCSS(theme) {
323
+ return theme;
324
+ }
325
+ var MlinkContext = react.createContext(null);
326
+ function MlinkProvider({
327
+ children,
328
+ theme,
329
+ defaultChain = DEFAULT_CHAIN
330
+ }) {
331
+ const resolvedTheme = react.useMemo(() => resolveTheme(theme), [theme]);
332
+ const value = react.useMemo(
333
+ () => ({
334
+ theme: resolvedTheme,
335
+ defaultChain
336
+ }),
337
+ [resolvedTheme, defaultChain]
338
+ );
339
+ return /* @__PURE__ */ jsxRuntime.jsx(MlinkContext.Provider, { value, children });
340
+ }
341
+ function useMlinkContext() {
342
+ const context = react.useContext(MlinkContext);
343
+ if (!context) {
344
+ return {
345
+ theme: resolveTheme("dark"),
346
+ defaultChain: DEFAULT_CHAIN
347
+ };
348
+ }
349
+ return context;
350
+ }
351
+ function Mlink({
352
+ url,
353
+ adapter,
354
+ theme: themeProp,
355
+ onSuccess,
356
+ onError,
357
+ className = "",
358
+ stylePreset = "default"
359
+ }) {
360
+ const context = useMlinkContext();
361
+ const resolvedTheme = themeProp ? resolveTheme(themeProp) : context.theme;
362
+ const { status: fetchStatus, metadata, error: fetchError } = useMlink(url);
363
+ const {
364
+ execute,
365
+ status: execStatus,
366
+ txHash,
367
+ error: execError,
368
+ reset
369
+ } = useExecuteMlink({
370
+ adapter,
371
+ actionUrl: url,
372
+ onSuccess,
373
+ onError
374
+ });
375
+ const [inputValues, setInputValues] = react.useState({});
376
+ const handleInputChange = react.useCallback((actionValue, value) => {
377
+ setInputValues((prev) => ({ ...prev, [actionValue]: value }));
378
+ }, []);
379
+ const handleAction = react.useCallback(
380
+ async (action) => {
381
+ const input = action.type === "input" ? inputValues[action.value] : void 0;
382
+ await execute(action.value, input);
383
+ },
384
+ [execute, inputValues]
385
+ );
386
+ if (fetchStatus === "loading") {
387
+ return /* @__PURE__ */ jsxRuntime.jsx(MlinkContainer, { theme: resolvedTheme, className, preset: stylePreset, children: /* @__PURE__ */ jsxRuntime.jsx(MlinkSkeleton, {}) });
388
+ }
389
+ if (fetchStatus === "error" || !metadata) {
390
+ return /* @__PURE__ */ jsxRuntime.jsx(MlinkContainer, { theme: resolvedTheme, className, preset: stylePreset, children: /* @__PURE__ */ jsxRuntime.jsx(MlinkError, { message: fetchError || "Failed to load action" }) });
391
+ }
392
+ if (execStatus === "success" && txHash) {
393
+ return /* @__PURE__ */ jsxRuntime.jsx(MlinkContainer, { theme: resolvedTheme, className, preset: stylePreset, children: /* @__PURE__ */ jsxRuntime.jsx(
394
+ MlinkSuccess,
395
+ {
396
+ message: metadata.title,
397
+ txHash,
398
+ onReset: reset
399
+ }
400
+ ) });
401
+ }
402
+ return /* @__PURE__ */ jsxRuntime.jsxs(MlinkContainer, { theme: resolvedTheme, className, preset: stylePreset, children: [
403
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mlink-icon", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: metadata.icon, alt: metadata.title }) }),
404
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mlink-content", children: [
405
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mlink-title", children: metadata.title }),
406
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mlink-description", children: metadata.description })
407
+ ] }),
408
+ execError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mlink-error-banner", children: execError }),
409
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mlink-actions", children: metadata.actions.map((action, index) => /* @__PURE__ */ jsxRuntime.jsx(
410
+ ActionButtonComponent,
411
+ {
412
+ action,
413
+ inputValue: inputValues[action.value] || "",
414
+ onInputChange: (value) => handleInputChange(action.value, value),
415
+ onExecute: () => handleAction(action),
416
+ loading: execStatus === "executing",
417
+ disabled: metadata.disabled === true || action.disabled === true
418
+ },
419
+ `${action.value}-${index}`
420
+ )) })
421
+ ] });
422
+ }
423
+ function MlinkContainer({ theme, className, preset, children }) {
424
+ return /* @__PURE__ */ jsxRuntime.jsx(
425
+ "div",
426
+ {
427
+ className: `mlink-container mlink-${preset} ${className}`,
428
+ style: themeToCSS(theme),
429
+ children
430
+ }
431
+ );
432
+ }
433
+ function MlinkSkeleton() {
434
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mlink-skeleton", children: [
435
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mlink-skeleton-icon" }),
436
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mlink-skeleton-content", children: [
437
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mlink-skeleton-title" }),
438
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mlink-skeleton-description" })
439
+ ] }),
440
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mlink-skeleton-button" })
441
+ ] });
442
+ }
443
+ function MlinkError({ message }) {
444
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mlink-error", children: [
445
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mlink-error-icon", children: "!" }),
446
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mlink-error-message", children: message })
447
+ ] });
448
+ }
449
+ function MlinkSuccess({ message, txHash, onReset }) {
450
+ const shortHash = `${txHash.slice(0, 10)}...${txHash.slice(-8)}`;
451
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mlink-success", children: [
452
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mlink-success-icon", children: "\u2713" }),
453
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mlink-success-title", children: "Transaction Sent" }),
454
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mlink-success-message", children: message }),
455
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: "mlink-success-hash", children: shortHash }),
456
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "mlink-button mlink-button-secondary", onClick: onReset, children: "Done" })
457
+ ] });
458
+ }
459
+ function ActionButtonComponent({
460
+ action,
461
+ inputValue,
462
+ onInputChange,
463
+ onExecute,
464
+ loading,
465
+ disabled
466
+ }) {
467
+ if (action.type === "input") {
468
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mlink-input-group", children: [
469
+ /* @__PURE__ */ jsxRuntime.jsx(
470
+ "input",
471
+ {
472
+ type: "text",
473
+ className: "mlink-input",
474
+ placeholder: action.placeholder || action.label,
475
+ value: inputValue,
476
+ onChange: (e) => onInputChange(e.target.value),
477
+ disabled: disabled || loading
478
+ }
479
+ ),
480
+ /* @__PURE__ */ jsxRuntime.jsx(
481
+ "button",
482
+ {
483
+ className: "mlink-button",
484
+ onClick: onExecute,
485
+ disabled: disabled || loading || !inputValue.trim(),
486
+ children: loading ? /* @__PURE__ */ jsxRuntime.jsx(MlinkSpinner, {}) : action.label
487
+ }
488
+ )
489
+ ] });
490
+ }
491
+ return /* @__PURE__ */ jsxRuntime.jsx(
492
+ "button",
493
+ {
494
+ className: "mlink-button",
495
+ onClick: onExecute,
496
+ disabled: disabled || loading,
497
+ children: loading ? /* @__PURE__ */ jsxRuntime.jsx(MlinkSpinner, {}) : action.label
498
+ }
499
+ );
500
+ }
501
+ function MlinkSpinner() {
502
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mlink-spinner" });
503
+ }
504
+ var Mlink_default = Mlink;
505
+ function useMlinkWagmiAdapter(config) {
506
+ const { address, isConnected, connect, sendTransaction } = config;
507
+ return react.useMemo(
508
+ () => ({
509
+ connect: async () => {
510
+ await connect();
511
+ if (!address) {
512
+ throw new Error("Failed to connect wallet");
513
+ }
514
+ return address;
515
+ },
516
+ signAndSendTransaction: async (transaction) => {
517
+ const txHash = await sendTransaction({
518
+ to: transaction.to,
519
+ value: BigInt(transaction.value),
520
+ data: transaction.data,
521
+ chainId: transaction.chainId
522
+ });
523
+ return txHash;
524
+ },
525
+ isConnected: () => isConnected,
526
+ getAddress: () => address || null
527
+ }),
528
+ [address, isConnected, connect, sendTransaction]
529
+ );
530
+ }
531
+ function useMlinkEthersAdapter(config) {
532
+ const { signer, connect } = config;
533
+ return react.useMemo(
534
+ () => ({
535
+ connect: async () => {
536
+ await connect();
537
+ if (!signer) {
538
+ throw new Error("Failed to connect wallet");
539
+ }
540
+ return await signer.getAddress();
541
+ },
542
+ signAndSendTransaction: async (transaction) => {
543
+ if (!signer) {
544
+ throw new Error("Wallet not connected");
545
+ }
546
+ const tx = await signer.sendTransaction({
547
+ to: transaction.to,
548
+ value: BigInt(transaction.value),
549
+ data: transaction.data,
550
+ chainId: transaction.chainId
551
+ });
552
+ return tx.hash;
553
+ },
554
+ isConnected: () => signer !== null,
555
+ getAddress: () => null
556
+ // Ethers requires async call
557
+ }),
558
+ [signer, connect]
559
+ );
560
+ }
561
+ function createMlinkAdapter(adapter) {
562
+ return adapter;
563
+ }
564
+
565
+ exports.Mlink = Mlink;
566
+ exports.MlinkComponent = Mlink_default;
567
+ exports.MlinkProvider = MlinkProvider;
568
+ exports.createMlinkAdapter = createMlinkAdapter;
569
+ exports.darkTheme = darkTheme;
570
+ exports.lightTheme = lightTheme;
571
+ exports.mantleTheme = mantleTheme;
572
+ exports.resolveTheme = resolveTheme;
573
+ exports.themePresets = themePresets;
574
+ exports.useExecuteMlink = useExecuteMlink;
575
+ exports.useMlink = useMlink;
576
+ exports.useMlinkContext = useMlinkContext;
577
+ exports.useMlinkEthersAdapter = useMlinkEthersAdapter;
578
+ exports.useMlinkWagmiAdapter = useMlinkWagmiAdapter;
579
+ //# sourceMappingURL=index.js.map
580
+ //# sourceMappingURL=index.js.map