@dipansrimany/mlink-sdk 0.3.0 → 0.4.1

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.
@@ -41,18 +41,113 @@ var ActionButtonSchema = zod.z.object({
41
41
  placeholder: zod.z.string().optional(),
42
42
  disabled: zod.z.boolean().optional()
43
43
  });
44
+ zod.z.enum([
45
+ "text",
46
+ "number",
47
+ "email",
48
+ "url",
49
+ "date",
50
+ "datetime-local",
51
+ "textarea",
52
+ "select",
53
+ "radio",
54
+ "checkbox",
55
+ "address",
56
+ "token",
57
+ "amount"
58
+ ]);
59
+ var ActionParameterOptionSchema = zod.z.object({
60
+ label: zod.z.string().min(1),
61
+ value: zod.z.string(),
62
+ selected: zod.z.boolean().optional()
63
+ });
64
+ var ActionParameterBaseSchema = zod.z.object({
65
+ name: zod.z.string().min(1).max(50),
66
+ label: zod.z.string().max(100).optional(),
67
+ required: zod.z.boolean().optional(),
68
+ pattern: zod.z.string().optional(),
69
+ patternDescription: zod.z.string().optional(),
70
+ min: zod.z.union([zod.z.string(), zod.z.number()]).optional(),
71
+ max: zod.z.union([zod.z.string(), zod.z.number()]).optional()
72
+ });
73
+ var ActionParameterSchema = ActionParameterBaseSchema.extend({
74
+ type: zod.z.enum([
75
+ "text",
76
+ "number",
77
+ "email",
78
+ "url",
79
+ "date",
80
+ "datetime-local",
81
+ "textarea",
82
+ "address",
83
+ "token",
84
+ "amount"
85
+ ]).optional()
86
+ });
87
+ var ActionParameterSelectableSchema = ActionParameterBaseSchema.extend({
88
+ type: zod.z.enum(["select", "radio", "checkbox"]),
89
+ options: zod.z.array(ActionParameterOptionSchema).min(1)
90
+ });
91
+ var TypedActionParameterSchema = zod.z.union([
92
+ ActionParameterSelectableSchema,
93
+ ActionParameterSchema
94
+ ]);
95
+ var LinkedActionTypeSchema = zod.z.enum(["transaction", "post", "external-link"]);
96
+ var LinkedActionSchema = zod.z.object({
97
+ type: LinkedActionTypeSchema.optional(),
98
+ href: zod.z.string().min(1),
99
+ label: zod.z.string().min(1).max(50),
100
+ disabled: zod.z.boolean().optional(),
101
+ parameters: zod.z.array(TypedActionParameterSchema).optional()
102
+ });
103
+ var ActionLinksSchema = zod.z.object({
104
+ actions: zod.z.array(LinkedActionSchema).min(1)
105
+ });
106
+ var PostNextActionLinkSchema = zod.z.object({
107
+ type: zod.z.literal("post"),
108
+ href: zod.z.string().min(1)
109
+ });
110
+ var InlineNextActionLinkSchema = zod.z.object({
111
+ type: zod.z.literal("inline"),
112
+ action: zod.z.lazy(() => ActionMetadataSchema)
113
+ });
114
+ var NextActionLinkSchema = zod.z.union([
115
+ PostNextActionLinkSchema,
116
+ InlineNextActionLinkSchema
117
+ ]);
118
+ var iconSchema = zod.z.string().refine(
119
+ (val) => {
120
+ if (val.startsWith("http://") || val.startsWith("https://")) return true;
121
+ if (val.startsWith("data:image/")) return true;
122
+ if (val.startsWith("/")) return true;
123
+ return false;
124
+ },
125
+ { message: "Icon must be a valid URL, base64 data URI, or relative path" }
126
+ );
44
127
  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),
128
+ type: zod.z.enum(["action", "completed"]).optional(),
129
+ title: zod.z.string().min(1).max(200),
130
+ icon: iconSchema,
131
+ description: zod.z.string().min(1).max(1e3),
132
+ label: zod.z.string().max(50).optional(),
133
+ // Legacy actions (optional)
134
+ actions: zod.z.array(ActionButtonSchema).optional(),
135
+ // Solana-style linked actions (optional)
136
+ links: ActionLinksSchema.optional(),
49
137
  disabled: zod.z.boolean().optional(),
50
138
  error: zod.z.object({ message: zod.z.string() }).optional()
51
- });
139
+ }).refine(
140
+ (data) => {
141
+ return data.actions && data.actions.length > 0 || data.links?.actions && data.links.actions.length > 0;
142
+ },
143
+ { message: "Must have at least one action or linked action" }
144
+ );
52
145
  zod.z.object({
53
146
  account: zod.z.string().regex(addressRegex, "Invalid Ethereum address"),
54
147
  action: zod.z.string().min(1),
55
- input: zod.z.string().optional()
148
+ input: zod.z.string().optional(),
149
+ // Solana-style parameter data
150
+ data: zod.z.record(zod.z.union([zod.z.string(), zod.z.array(zod.z.string())])).optional()
56
151
  });
57
152
  var EVMTransactionSchema = zod.z.object({
58
153
  to: zod.z.string().regex(addressRegex, "Invalid to address"),
@@ -60,10 +155,22 @@ var EVMTransactionSchema = zod.z.object({
60
155
  data: zod.z.string().regex(hexRegex, "Invalid hex data"),
61
156
  chainId: zod.z.number().positive()
62
157
  });
63
- var TransactionResponseSchema = zod.z.object({
64
- transaction: EVMTransactionSchema,
65
- message: zod.z.string().optional()
66
- });
158
+ zod.z.object({
159
+ // Single transaction (legacy)
160
+ transaction: EVMTransactionSchema.optional(),
161
+ // Multiple transactions (batch)
162
+ transactions: zod.z.array(EVMTransactionSchema).optional(),
163
+ message: zod.z.string().optional(),
164
+ // Action chaining
165
+ links: zod.z.object({
166
+ next: NextActionLinkSchema.optional()
167
+ }).optional()
168
+ }).refine(
169
+ (data) => {
170
+ return data.transaction !== void 0 || data.transactions && data.transactions.length > 0;
171
+ },
172
+ { message: "Must have either transaction or transactions" }
173
+ );
67
174
  function validateActionMetadata(data) {
68
175
  const result = ActionMetadataSchema.safeParse(data);
69
176
  if (result.success) {
@@ -71,13 +178,6 @@ function validateActionMetadata(data) {
71
178
  }
72
179
  return { success: false, error: result.error.message };
73
180
  }
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
181
 
82
182
  // src/react/useMlink.ts
83
183
  var DEFAULT_REFRESH_INTERVAL = 10 * 60 * 1e3;
@@ -164,7 +264,7 @@ function useExecuteMlink(options) {
164
264
  setError(null);
165
265
  }, []);
166
266
  const execute = react.useCallback(
167
- async (action, input) => {
267
+ async (action, input, data) => {
168
268
  setStatus("executing");
169
269
  setError(null);
170
270
  setTxHash(null);
@@ -181,28 +281,33 @@ function useExecuteMlink(options) {
181
281
  body: JSON.stringify({
182
282
  account,
183
283
  action,
184
- input
284
+ input,
285
+ data
286
+ // Include parameter data
185
287
  })
186
288
  });
187
289
  if (!response.ok) {
188
290
  const errorData = await response.json().catch(() => ({}));
189
291
  throw new Error(
190
- errorData.message || `HTTP ${response.status}: ${response.statusText}`
292
+ errorData.error?.message || errorData.message || `HTTP ${response.status}: ${response.statusText}`
191
293
  );
192
294
  }
193
- const data = await response.json();
194
- const validation = validateTransactionResponse(data);
195
- if (!validation.success) {
196
- throw new Error(`Invalid response: ${validation.error}`);
295
+ const responseData = await response.json();
296
+ const transactions = responseData.transactions ? responseData.transactions : responseData.transaction ? [responseData.transaction] : [];
297
+ if (transactions.length === 0) {
298
+ throw new Error("No transaction returned from action");
299
+ }
300
+ let lastHash = "";
301
+ for (const tx of transactions) {
302
+ lastHash = await adapter.signAndSendTransaction(tx);
197
303
  }
198
- const hash = await adapter.signAndSendTransaction(data.transaction);
199
- setTxHash(hash);
304
+ setTxHash(lastHash);
200
305
  setStatus("success");
201
- onSuccess?.(hash, action);
306
+ onSuccess?.(lastHash, action);
202
307
  return {
203
308
  success: true,
204
- txHash: hash,
205
- message: data.message
309
+ txHash: lastHash,
310
+ message: responseData.message
206
311
  };
207
312
  } catch (err) {
208
313
  const errorMessage = err instanceof Error ? err.message : "Transaction failed";
@@ -348,6 +453,23 @@ function useMlinkContext() {
348
453
  }
349
454
  return context;
350
455
  }
456
+
457
+ // src/builders.ts
458
+ function buildHref(template, params) {
459
+ let result = template;
460
+ for (const [key, value] of Object.entries(params)) {
461
+ const placeholder = `{${key}}`;
462
+ const replacement = Array.isArray(value) ? value.join(",") : value;
463
+ result = result.replace(new RegExp(placeholder, "g"), encodeURIComponent(replacement));
464
+ }
465
+ return result;
466
+ }
467
+ function hasParameters(action) {
468
+ return Boolean(action.parameters && action.parameters.length > 0);
469
+ }
470
+ function isSelectableParam(param) {
471
+ return param.type === "select" || param.type === "radio" || param.type === "checkbox";
472
+ }
351
473
  function Mlink({
352
474
  url,
353
475
  adapter,
@@ -373,9 +495,14 @@ function Mlink({
373
495
  onError
374
496
  });
375
497
  const [inputValues, setInputValues] = react.useState({});
498
+ const [paramValues, setParamValues] = react.useState({});
499
+ const [selectedLinkedAction, setSelectedLinkedAction] = react.useState(null);
376
500
  const handleInputChange = react.useCallback((actionValue, value) => {
377
501
  setInputValues((prev) => ({ ...prev, [actionValue]: value }));
378
502
  }, []);
503
+ const handleParamChange = react.useCallback((name, value) => {
504
+ setParamValues((prev) => ({ ...prev, [name]: value }));
505
+ }, []);
379
506
  const handleAction = react.useCallback(
380
507
  async (action) => {
381
508
  const input = action.type === "input" ? inputValues[action.value] : void 0;
@@ -383,6 +510,40 @@ function Mlink({
383
510
  },
384
511
  [execute, inputValues]
385
512
  );
513
+ const handleLinkedAction = react.useCallback(
514
+ async (action) => {
515
+ if (hasParameters(action)) {
516
+ setSelectedLinkedAction(action);
517
+ const defaults = {};
518
+ action.parameters?.forEach((param) => {
519
+ if (isSelectableParam(param)) {
520
+ const selectedOption = param.options.find((o) => o.selected);
521
+ if (selectedOption) {
522
+ defaults[param.name] = param.type === "checkbox" ? [selectedOption.value] : selectedOption.value;
523
+ }
524
+ }
525
+ });
526
+ setParamValues(defaults);
527
+ } else {
528
+ await execute(action.href, void 0, {});
529
+ }
530
+ },
531
+ [execute]
532
+ );
533
+ const handleExecuteWithParams = react.useCallback(async () => {
534
+ if (!selectedLinkedAction) return;
535
+ const finalHref = buildHref(selectedLinkedAction.href, paramValues);
536
+ await execute(finalHref, void 0, paramValues);
537
+ setSelectedLinkedAction(null);
538
+ setParamValues({});
539
+ }, [selectedLinkedAction, paramValues, execute]);
540
+ const handleCancelParams = react.useCallback(() => {
541
+ setSelectedLinkedAction(null);
542
+ setParamValues({});
543
+ }, []);
544
+ const hasLinkedActions = react.useMemo(() => {
545
+ return metadata?.links?.actions && metadata.links.actions.length > 0;
546
+ }, [metadata]);
386
547
  if (fetchStatus === "loading") {
387
548
  return /* @__PURE__ */ jsxRuntime.jsx(MlinkContainer, { theme: resolvedTheme, className, preset: stylePreset, children: /* @__PURE__ */ jsxRuntime.jsx(MlinkSkeleton, {}) });
388
549
  }
@@ -399,6 +560,45 @@ function Mlink({
399
560
  }
400
561
  ) });
401
562
  }
563
+ if (selectedLinkedAction && hasParameters(selectedLinkedAction)) {
564
+ return /* @__PURE__ */ jsxRuntime.jsxs(MlinkContainer, { theme: resolvedTheme, className, preset: stylePreset, children: [
565
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mlink-content", children: [
566
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mlink-title", children: selectedLinkedAction.label }),
567
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mlink-description", children: "Fill in the details below" })
568
+ ] }),
569
+ execError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mlink-error-banner", children: execError }),
570
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mlink-params", children: selectedLinkedAction.parameters?.map((param) => /* @__PURE__ */ jsxRuntime.jsx(
571
+ ParameterInput,
572
+ {
573
+ param,
574
+ value: paramValues[param.name] || "",
575
+ onChange: (value) => handleParamChange(param.name, value),
576
+ disabled: execStatus === "executing"
577
+ },
578
+ param.name
579
+ )) }),
580
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mlink-actions", children: [
581
+ /* @__PURE__ */ jsxRuntime.jsx(
582
+ "button",
583
+ {
584
+ className: "mlink-button",
585
+ onClick: handleExecuteWithParams,
586
+ disabled: execStatus === "executing",
587
+ children: execStatus === "executing" ? /* @__PURE__ */ jsxRuntime.jsx(MlinkSpinner, {}) : selectedLinkedAction.label
588
+ }
589
+ ),
590
+ /* @__PURE__ */ jsxRuntime.jsx(
591
+ "button",
592
+ {
593
+ className: "mlink-button mlink-button-secondary",
594
+ onClick: handleCancelParams,
595
+ disabled: execStatus === "executing",
596
+ children: "Back"
597
+ }
598
+ )
599
+ ] })
600
+ ] });
601
+ }
402
602
  return /* @__PURE__ */ jsxRuntime.jsxs(MlinkContainer, { theme: resolvedTheme, className, preset: stylePreset, children: [
403
603
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mlink-icon", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: metadata.icon, alt: metadata.title }) }),
404
604
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mlink-content", children: [
@@ -406,7 +606,29 @@ function Mlink({
406
606
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mlink-description", children: metadata.description })
407
607
  ] }),
408
608
  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(
609
+ hasLinkedActions && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mlink-actions", children: [
610
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mlink-quick-actions", children: metadata.links.actions.filter((a) => !hasParameters(a)).map((action, index) => /* @__PURE__ */ jsxRuntime.jsx(
611
+ "button",
612
+ {
613
+ className: "mlink-button mlink-button-quick",
614
+ onClick: () => handleLinkedAction(action),
615
+ disabled: metadata.disabled || action.disabled || execStatus === "executing",
616
+ children: execStatus === "executing" ? /* @__PURE__ */ jsxRuntime.jsx(MlinkSpinner, {}) : action.label
617
+ },
618
+ `${action.href}-${index}`
619
+ )) }),
620
+ metadata.links.actions.filter((a) => hasParameters(a)).map((action, index) => /* @__PURE__ */ jsxRuntime.jsx(
621
+ "button",
622
+ {
623
+ className: "mlink-button mlink-button-custom",
624
+ onClick: () => handleLinkedAction(action),
625
+ disabled: metadata.disabled || action.disabled || execStatus === "executing",
626
+ children: action.label
627
+ },
628
+ `param-${action.href}-${index}`
629
+ ))
630
+ ] }),
631
+ !hasLinkedActions && metadata.actions && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mlink-actions", children: metadata.actions.map((action, index) => /* @__PURE__ */ jsxRuntime.jsx(
410
632
  ActionButtonComponent,
411
633
  {
412
634
  action,
@@ -420,6 +642,161 @@ function Mlink({
420
642
  )) })
421
643
  ] });
422
644
  }
645
+ function ParameterInput({ param, value, onChange, disabled }) {
646
+ const strValue = Array.isArray(value) ? value.join(",") : value;
647
+ if (isSelectableParam(param)) {
648
+ return /* @__PURE__ */ jsxRuntime.jsx(
649
+ SelectableParamInput,
650
+ {
651
+ param,
652
+ value,
653
+ onChange,
654
+ disabled
655
+ }
656
+ );
657
+ }
658
+ const inputType = getInputType(param.type);
659
+ if (param.type === "textarea") {
660
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mlink-param-group", children: [
661
+ param.label && /* @__PURE__ */ jsxRuntime.jsx("label", { className: "mlink-param-label", children: param.label }),
662
+ /* @__PURE__ */ jsxRuntime.jsx(
663
+ "textarea",
664
+ {
665
+ className: "mlink-textarea",
666
+ value: strValue,
667
+ onChange: (e) => onChange(e.target.value),
668
+ disabled,
669
+ required: param.required
670
+ }
671
+ )
672
+ ] });
673
+ }
674
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mlink-param-group", children: [
675
+ param.label && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "mlink-param-label", children: [
676
+ param.label,
677
+ param.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mlink-required", children: "*" })
678
+ ] }),
679
+ /* @__PURE__ */ jsxRuntime.jsx(
680
+ "input",
681
+ {
682
+ type: inputType,
683
+ className: "mlink-input",
684
+ value: strValue,
685
+ onChange: (e) => onChange(e.target.value),
686
+ disabled,
687
+ required: param.required,
688
+ pattern: param.pattern,
689
+ min: param.min?.toString(),
690
+ max: param.max?.toString(),
691
+ placeholder: param.label || param.name
692
+ }
693
+ ),
694
+ param.patternDescription && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mlink-param-hint", children: param.patternDescription })
695
+ ] });
696
+ }
697
+ function SelectableParamInput({
698
+ param,
699
+ value,
700
+ onChange,
701
+ disabled
702
+ }) {
703
+ const arrayValue = Array.isArray(value) ? value : value ? [value] : [];
704
+ if (param.type === "select") {
705
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mlink-param-group", children: [
706
+ param.label && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "mlink-param-label", children: [
707
+ param.label,
708
+ param.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mlink-required", children: "*" })
709
+ ] }),
710
+ /* @__PURE__ */ jsxRuntime.jsxs(
711
+ "select",
712
+ {
713
+ className: "mlink-select",
714
+ value: arrayValue[0] || "",
715
+ onChange: (e) => onChange(e.target.value),
716
+ disabled,
717
+ required: param.required,
718
+ children: [
719
+ /* @__PURE__ */ jsxRuntime.jsxs("option", { value: "", children: [
720
+ "Select ",
721
+ param.label || param.name
722
+ ] }),
723
+ param.options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt.value, children: opt.label }, opt.value))
724
+ ]
725
+ }
726
+ )
727
+ ] });
728
+ }
729
+ if (param.type === "radio") {
730
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mlink-param-group", children: [
731
+ param.label && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "mlink-param-label", children: [
732
+ param.label,
733
+ param.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mlink-required", children: "*" })
734
+ ] }),
735
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mlink-radio-group", children: param.options.map((opt) => /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "mlink-radio-label", children: [
736
+ /* @__PURE__ */ jsxRuntime.jsx(
737
+ "input",
738
+ {
739
+ type: "radio",
740
+ name: param.name,
741
+ value: opt.value,
742
+ checked: arrayValue[0] === opt.value,
743
+ onChange: (e) => onChange(e.target.value),
744
+ disabled
745
+ }
746
+ ),
747
+ opt.label
748
+ ] }, opt.value)) })
749
+ ] });
750
+ }
751
+ if (param.type === "checkbox") {
752
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mlink-param-group", children: [
753
+ param.label && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "mlink-param-label", children: [
754
+ param.label,
755
+ param.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mlink-required", children: "*" })
756
+ ] }),
757
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mlink-checkbox-group", children: param.options.map((opt) => /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "mlink-checkbox-label", children: [
758
+ /* @__PURE__ */ jsxRuntime.jsx(
759
+ "input",
760
+ {
761
+ type: "checkbox",
762
+ value: opt.value,
763
+ checked: arrayValue.includes(opt.value),
764
+ onChange: (e) => {
765
+ if (e.target.checked) {
766
+ onChange([...arrayValue, opt.value]);
767
+ } else {
768
+ onChange(arrayValue.filter((v) => v !== opt.value));
769
+ }
770
+ },
771
+ disabled
772
+ }
773
+ ),
774
+ opt.label
775
+ ] }, opt.value)) })
776
+ ] });
777
+ }
778
+ return null;
779
+ }
780
+ function getInputType(paramType) {
781
+ switch (paramType) {
782
+ case "number":
783
+ case "amount":
784
+ return "number";
785
+ case "email":
786
+ return "email";
787
+ case "url":
788
+ return "url";
789
+ case "date":
790
+ return "date";
791
+ case "datetime-local":
792
+ return "datetime-local";
793
+ case "address":
794
+ case "token":
795
+ case "text":
796
+ default:
797
+ return "text";
798
+ }
799
+ }
423
800
  function MlinkContainer({ theme, className, preset, children }) {
424
801
  return /* @__PURE__ */ jsxRuntime.jsx(
425
802
  "div",