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