@sundaeswap/sprinkles 0.6.1 → 0.7.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.
Files changed (153) hide show
  1. package/dist/cjs/Sprinkle/__tests__/action-integration.test.js +590 -0
  2. package/dist/cjs/Sprinkle/__tests__/action-integration.test.js.map +1 -0
  3. package/dist/cjs/Sprinkle/__tests__/action-registry.test.js +193 -0
  4. package/dist/cjs/Sprinkle/__tests__/action-registry.test.js.map +1 -0
  5. package/dist/cjs/Sprinkle/__tests__/action-runner.test.js +304 -0
  6. package/dist/cjs/Sprinkle/__tests__/action-runner.test.js.map +1 -0
  7. package/dist/cjs/Sprinkle/__tests__/builtin-actions.test.js +1110 -0
  8. package/dist/cjs/Sprinkle/__tests__/builtin-actions.test.js.map +1 -0
  9. package/dist/cjs/Sprinkle/__tests__/cli-adapter.test.js +722 -0
  10. package/dist/cjs/Sprinkle/__tests__/cli-adapter.test.js.map +1 -0
  11. package/dist/cjs/Sprinkle/__tests__/mcp-adapter.test.js +713 -0
  12. package/dist/cjs/Sprinkle/__tests__/mcp-adapter.test.js.map +1 -0
  13. package/dist/cjs/Sprinkle/__tests__/tui-helpers.test.js +334 -0
  14. package/dist/cjs/Sprinkle/__tests__/tui-helpers.test.js.map +1 -0
  15. package/dist/cjs/Sprinkle/__tests__/wallet-transaction-actions.test.js +749 -0
  16. package/dist/cjs/Sprinkle/__tests__/wallet-transaction-actions.test.js.map +1 -0
  17. package/dist/cjs/Sprinkle/actions/builtin/blaze-helper.js +61 -0
  18. package/dist/cjs/Sprinkle/actions/builtin/blaze-helper.js.map +1 -0
  19. package/dist/cjs/Sprinkle/actions/builtin/index.js +117 -0
  20. package/dist/cjs/Sprinkle/actions/builtin/index.js.map +1 -0
  21. package/dist/cjs/Sprinkle/actions/builtin/profile-actions.js +202 -0
  22. package/dist/cjs/Sprinkle/actions/builtin/profile-actions.js.map +1 -0
  23. package/dist/cjs/Sprinkle/actions/builtin/settings-actions.js +87 -0
  24. package/dist/cjs/Sprinkle/actions/builtin/settings-actions.js.map +1 -0
  25. package/dist/cjs/Sprinkle/actions/builtin/transaction-actions.js +345 -0
  26. package/dist/cjs/Sprinkle/actions/builtin/transaction-actions.js.map +1 -0
  27. package/dist/cjs/Sprinkle/actions/builtin/wallet-actions.js +212 -0
  28. package/dist/cjs/Sprinkle/actions/builtin/wallet-actions.js.map +1 -0
  29. package/dist/cjs/Sprinkle/actions/cli-adapter.js +372 -0
  30. package/dist/cjs/Sprinkle/actions/cli-adapter.js.map +1 -0
  31. package/dist/cjs/Sprinkle/actions/index.js +127 -0
  32. package/dist/cjs/Sprinkle/actions/index.js.map +1 -0
  33. package/dist/cjs/Sprinkle/actions/mcp-adapter.js +415 -0
  34. package/dist/cjs/Sprinkle/actions/mcp-adapter.js.map +1 -0
  35. package/dist/cjs/Sprinkle/actions/registry.js +92 -0
  36. package/dist/cjs/Sprinkle/actions/registry.js.map +1 -0
  37. package/dist/cjs/Sprinkle/actions/runner.js +190 -0
  38. package/dist/cjs/Sprinkle/actions/runner.js.map +1 -0
  39. package/dist/cjs/Sprinkle/actions/tui-helpers.js +96 -0
  40. package/dist/cjs/Sprinkle/actions/tui-helpers.js.map +1 -0
  41. package/dist/cjs/Sprinkle/actions/types.js +68 -0
  42. package/dist/cjs/Sprinkle/actions/types.js.map +1 -0
  43. package/dist/cjs/Sprinkle/index.js +412 -1
  44. package/dist/cjs/Sprinkle/index.js.map +1 -1
  45. package/dist/cjs/Sprinkle/prompts.js +12 -7
  46. package/dist/cjs/Sprinkle/prompts.js.map +1 -1
  47. package/dist/cjs/Sprinkle/type-guards.js +7 -1
  48. package/dist/cjs/Sprinkle/type-guards.js.map +1 -1
  49. package/dist/esm/Sprinkle/__tests__/action-integration.test.js +588 -0
  50. package/dist/esm/Sprinkle/__tests__/action-integration.test.js.map +1 -0
  51. package/dist/esm/Sprinkle/__tests__/action-registry.test.js +192 -0
  52. package/dist/esm/Sprinkle/__tests__/action-registry.test.js.map +1 -0
  53. package/dist/esm/Sprinkle/__tests__/action-runner.test.js +302 -0
  54. package/dist/esm/Sprinkle/__tests__/action-runner.test.js.map +1 -0
  55. package/dist/esm/Sprinkle/__tests__/builtin-actions.test.js +1107 -0
  56. package/dist/esm/Sprinkle/__tests__/builtin-actions.test.js.map +1 -0
  57. package/dist/esm/Sprinkle/__tests__/cli-adapter.test.js +720 -0
  58. package/dist/esm/Sprinkle/__tests__/cli-adapter.test.js.map +1 -0
  59. package/dist/esm/Sprinkle/__tests__/mcp-adapter.test.js +712 -0
  60. package/dist/esm/Sprinkle/__tests__/mcp-adapter.test.js.map +1 -0
  61. package/dist/esm/Sprinkle/__tests__/tui-helpers.test.js +332 -0
  62. package/dist/esm/Sprinkle/__tests__/tui-helpers.test.js.map +1 -0
  63. package/dist/esm/Sprinkle/__tests__/wallet-transaction-actions.test.js +747 -0
  64. package/dist/esm/Sprinkle/__tests__/wallet-transaction-actions.test.js.map +1 -0
  65. package/dist/esm/Sprinkle/actions/builtin/blaze-helper.js +55 -0
  66. package/dist/esm/Sprinkle/actions/builtin/blaze-helper.js.map +1 -0
  67. package/dist/esm/Sprinkle/actions/builtin/index.js +32 -0
  68. package/dist/esm/Sprinkle/actions/builtin/index.js.map +1 -0
  69. package/dist/esm/Sprinkle/actions/builtin/profile-actions.js +197 -0
  70. package/dist/esm/Sprinkle/actions/builtin/profile-actions.js.map +1 -0
  71. package/dist/esm/Sprinkle/actions/builtin/settings-actions.js +81 -0
  72. package/dist/esm/Sprinkle/actions/builtin/settings-actions.js.map +1 -0
  73. package/dist/esm/Sprinkle/actions/builtin/transaction-actions.js +340 -0
  74. package/dist/esm/Sprinkle/actions/builtin/transaction-actions.js.map +1 -0
  75. package/dist/esm/Sprinkle/actions/builtin/wallet-actions.js +207 -0
  76. package/dist/esm/Sprinkle/actions/builtin/wallet-actions.js.map +1 -0
  77. package/dist/esm/Sprinkle/actions/cli-adapter.js +361 -0
  78. package/dist/esm/Sprinkle/actions/cli-adapter.js.map +1 -0
  79. package/dist/esm/Sprinkle/actions/index.js +12 -0
  80. package/dist/esm/Sprinkle/actions/index.js.map +1 -0
  81. package/dist/esm/Sprinkle/actions/mcp-adapter.js +407 -0
  82. package/dist/esm/Sprinkle/actions/mcp-adapter.js.map +1 -0
  83. package/dist/esm/Sprinkle/actions/registry.js +85 -0
  84. package/dist/esm/Sprinkle/actions/registry.js.map +1 -0
  85. package/dist/esm/Sprinkle/actions/runner.js +182 -0
  86. package/dist/esm/Sprinkle/actions/runner.js.map +1 -0
  87. package/dist/esm/Sprinkle/actions/tui-helpers.js +91 -0
  88. package/dist/esm/Sprinkle/actions/tui-helpers.js.map +1 -0
  89. package/dist/esm/Sprinkle/actions/types.js +61 -0
  90. package/dist/esm/Sprinkle/actions/types.js.map +1 -0
  91. package/dist/esm/Sprinkle/index.js +260 -1
  92. package/dist/esm/Sprinkle/index.js.map +1 -1
  93. package/dist/esm/Sprinkle/prompts.js +12 -7
  94. package/dist/esm/Sprinkle/prompts.js.map +1 -1
  95. package/dist/esm/Sprinkle/type-guards.js +3 -0
  96. package/dist/esm/Sprinkle/type-guards.js.map +1 -1
  97. package/dist/types/Sprinkle/actions/builtin/blaze-helper.d.ts +39 -0
  98. package/dist/types/Sprinkle/actions/builtin/blaze-helper.d.ts.map +1 -0
  99. package/dist/types/Sprinkle/actions/builtin/index.d.ts +26 -0
  100. package/dist/types/Sprinkle/actions/builtin/index.d.ts.map +1 -0
  101. package/dist/types/Sprinkle/actions/builtin/profile-actions.d.ts +55 -0
  102. package/dist/types/Sprinkle/actions/builtin/profile-actions.d.ts.map +1 -0
  103. package/dist/types/Sprinkle/actions/builtin/settings-actions.d.ts +32 -0
  104. package/dist/types/Sprinkle/actions/builtin/settings-actions.d.ts.map +1 -0
  105. package/dist/types/Sprinkle/actions/builtin/transaction-actions.d.ts +70 -0
  106. package/dist/types/Sprinkle/actions/builtin/transaction-actions.d.ts.map +1 -0
  107. package/dist/types/Sprinkle/actions/builtin/wallet-actions.d.ts +50 -0
  108. package/dist/types/Sprinkle/actions/builtin/wallet-actions.d.ts.map +1 -0
  109. package/dist/types/Sprinkle/actions/cli-adapter.d.ts +104 -0
  110. package/dist/types/Sprinkle/actions/cli-adapter.d.ts.map +1 -0
  111. package/dist/types/Sprinkle/actions/index.d.ts +12 -0
  112. package/dist/types/Sprinkle/actions/index.d.ts.map +1 -0
  113. package/dist/types/Sprinkle/actions/mcp-adapter.d.ts +92 -0
  114. package/dist/types/Sprinkle/actions/mcp-adapter.d.ts.map +1 -0
  115. package/dist/types/Sprinkle/actions/registry.d.ts +42 -0
  116. package/dist/types/Sprinkle/actions/registry.d.ts.map +1 -0
  117. package/dist/types/Sprinkle/actions/runner.d.ts +45 -0
  118. package/dist/types/Sprinkle/actions/runner.d.ts.map +1 -0
  119. package/dist/types/Sprinkle/actions/tui-helpers.d.ts +53 -0
  120. package/dist/types/Sprinkle/actions/tui-helpers.d.ts.map +1 -0
  121. package/dist/types/Sprinkle/actions/types.d.ts +76 -0
  122. package/dist/types/Sprinkle/actions/types.d.ts.map +1 -0
  123. package/dist/types/Sprinkle/index.d.ts +81 -1
  124. package/dist/types/Sprinkle/index.d.ts.map +1 -1
  125. package/dist/types/Sprinkle/prompts.d.ts.map +1 -1
  126. package/dist/types/Sprinkle/type-guards.d.ts +4 -1
  127. package/dist/types/Sprinkle/type-guards.d.ts.map +1 -1
  128. package/dist/types/tsconfig.build.tsbuildinfo +1 -1
  129. package/package.json +9 -2
  130. package/src/Sprinkle/__tests__/action-integration.test.ts +558 -0
  131. package/src/Sprinkle/__tests__/action-registry.test.ts +187 -0
  132. package/src/Sprinkle/__tests__/action-runner.test.ts +324 -0
  133. package/src/Sprinkle/__tests__/builtin-actions.test.ts +1022 -0
  134. package/src/Sprinkle/__tests__/cli-adapter.test.ts +715 -0
  135. package/src/Sprinkle/__tests__/mcp-adapter.test.ts +718 -0
  136. package/src/Sprinkle/__tests__/tui-helpers.test.ts +325 -0
  137. package/src/Sprinkle/__tests__/wallet-transaction-actions.test.ts +695 -0
  138. package/src/Sprinkle/actions/builtin/blaze-helper.ts +89 -0
  139. package/src/Sprinkle/actions/builtin/index.ts +86 -0
  140. package/src/Sprinkle/actions/builtin/profile-actions.ts +229 -0
  141. package/src/Sprinkle/actions/builtin/settings-actions.ts +99 -0
  142. package/src/Sprinkle/actions/builtin/transaction-actions.ts +381 -0
  143. package/src/Sprinkle/actions/builtin/wallet-actions.ts +233 -0
  144. package/src/Sprinkle/actions/cli-adapter.ts +430 -0
  145. package/src/Sprinkle/actions/index.ts +32 -0
  146. package/src/Sprinkle/actions/mcp-adapter.ts +463 -0
  147. package/src/Sprinkle/actions/registry.ts +97 -0
  148. package/src/Sprinkle/actions/runner.ts +200 -0
  149. package/src/Sprinkle/actions/tui-helpers.ts +114 -0
  150. package/src/Sprinkle/actions/types.ts +91 -0
  151. package/src/Sprinkle/index.ts +351 -0
  152. package/src/Sprinkle/prompts.ts +118 -72
  153. package/src/Sprinkle/type-guards.ts +9 -0
@@ -0,0 +1,381 @@
1
+ /**
2
+ * Built-in transaction actions for the Sprinkle action system.
3
+ * These actions expose transaction signing, submission, and decoding
4
+ * as non-interactive actions available in CLI and MCP modes.
5
+ *
6
+ * Sign/submit actions use category "wallet".
7
+ * The decode action uses category "transaction" (no wallet required).
8
+ */
9
+
10
+ import { Type } from "@sinclair/typebox";
11
+ import type { TSchema } from "@sinclair/typebox";
12
+ import { Core } from "@blaze-cardano/sdk";
13
+ import { ActionError } from "../types.js";
14
+ import type { IAction } from "../types.js";
15
+ import { getBlazeFromContext, isHotWallet } from "./blaze-helper.js";
16
+ import { countSignatures, getRequiredSigners } from "../../tx-dialog.js";
17
+
18
+ /** Shared token entry schema (also used in wallet-actions) */
19
+ const TokenEntrySchema = Type.Object({
20
+ policyId: Type.String({ description: "Policy ID of the token" }),
21
+ assetName: Type.String({ description: "Asset name of the token (hex)" }),
22
+ quantity: Type.String({ description: "Token quantity as string (BigInt-safe)" }),
23
+ });
24
+
25
+ /**
26
+ * Parses a hex CBOR string into a `Core.Transaction`.
27
+ * Throws `ActionError("INVALID_CBOR")` if the string is not valid transaction CBOR.
28
+ */
29
+ function parseTxCbor(txCbor: string): Core.Transaction {
30
+ try {
31
+ return Core.Transaction.fromCbor(Core.TxCBOR(txCbor));
32
+ } catch (err) {
33
+ throw new ActionError(
34
+ `Invalid transaction CBOR: ${err instanceof Error ? err.message : String(err)}`,
35
+ "INVALID_CBOR",
36
+ { error: err instanceof Error ? err.message : String(err) },
37
+ );
38
+ }
39
+ }
40
+
41
+ /**
42
+ * `sign-transaction` -- Signs a transaction with the configured hot wallet.
43
+ * Cold wallets cannot sign; use an external signing tool and submit the result.
44
+ */
45
+ export const signTransaction: IAction<
46
+ { txCbor: string },
47
+ { signedTxCbor: string; txHash: string; signatureCount: number },
48
+ TSchema
49
+ > = {
50
+ name: "sign-transaction",
51
+ description:
52
+ "Sign a transaction with the configured hot wallet. Requires a hot wallet; cold wallets cannot sign.",
53
+ category: "wallet",
54
+ inputSchema: Type.Object({
55
+ txCbor: Type.String({ description: "Transaction CBOR hex string to sign" }),
56
+ }),
57
+ outputSchema: Type.Object({
58
+ signedTxCbor: Type.String({ description: "Signed transaction CBOR hex" }),
59
+ txHash: Type.String({ description: "Transaction hash" }),
60
+ signatureCount: Type.Number({ description: "Number of VKey witnesses after signing" }),
61
+ }),
62
+ execute: async (input, context) => {
63
+ const blaze = await getBlazeFromContext(context);
64
+
65
+ if (!isHotWallet(blaze)) {
66
+ throw new ActionError(
67
+ "Cold wallets cannot sign transactions. Export the CBOR and sign externally, then use submit-transaction.",
68
+ "COLD_WALLET",
69
+ );
70
+ }
71
+
72
+ const tx = parseTxCbor(input.txCbor);
73
+
74
+ let signedTx: Core.Transaction;
75
+ try {
76
+ signedTx = await blaze.signTransaction(tx);
77
+ } catch (err) {
78
+ throw new ActionError(
79
+ `Failed to sign transaction: ${err instanceof Error ? err.message : String(err)}`,
80
+ "SIGN_ERROR",
81
+ { error: err instanceof Error ? err.message : String(err) },
82
+ );
83
+ }
84
+
85
+ return {
86
+ signedTxCbor: signedTx.toCbor(),
87
+ txHash: signedTx.body().hash().toString(),
88
+ signatureCount: countSignatures(signedTx),
89
+ };
90
+ },
91
+ };
92
+
93
+ /**
94
+ * `submit-transaction` -- Submits a signed transaction to the blockchain.
95
+ */
96
+ export const submitTransaction: IAction<
97
+ { txCbor: string },
98
+ { txHash: string; submitted: boolean },
99
+ TSchema
100
+ > = {
101
+ name: "submit-transaction",
102
+ description: "Submit a signed transaction to the blockchain.",
103
+ category: "wallet",
104
+ inputSchema: Type.Object({
105
+ txCbor: Type.String({ description: "Signed transaction CBOR hex string to submit" }),
106
+ }),
107
+ outputSchema: Type.Object({
108
+ txHash: Type.String({ description: "Submitted transaction hash" }),
109
+ submitted: Type.Boolean(),
110
+ }),
111
+ execute: async (input, context) => {
112
+ const blaze = await getBlazeFromContext(context);
113
+ const tx = parseTxCbor(input.txCbor);
114
+
115
+ let txHash: Core.TransactionId;
116
+ try {
117
+ txHash = await blaze.submitTransaction(tx);
118
+ } catch (err) {
119
+ throw new ActionError(
120
+ `Transaction submission failed: ${err instanceof Error ? err.message : String(err)}`,
121
+ "SUBMISSION_ERROR",
122
+ { error: err instanceof Error ? err.message : String(err) },
123
+ );
124
+ }
125
+
126
+ return {
127
+ txHash: txHash.toString(),
128
+ submitted: true,
129
+ };
130
+ },
131
+ };
132
+
133
+ /**
134
+ * `sign-and-submit` -- Signs a transaction and immediately submits it.
135
+ * Optionally waits for on-chain confirmation with a configurable timeout.
136
+ */
137
+ export const signAndSubmit: IAction<
138
+ {
139
+ txCbor: string;
140
+ waitForConfirmation?: boolean;
141
+ confirmationTimeout?: number;
142
+ },
143
+ {
144
+ txHash: string;
145
+ submitted: boolean;
146
+ confirmed?: boolean;
147
+ signedTxCbor: string;
148
+ },
149
+ TSchema
150
+ > = {
151
+ name: "sign-and-submit",
152
+ description:
153
+ "Sign and submit a transaction. Optionally wait for on-chain confirmation.",
154
+ category: "wallet",
155
+ inputSchema: Type.Object({
156
+ txCbor: Type.String({ description: "Transaction CBOR hex string to sign and submit" }),
157
+ waitForConfirmation: Type.Optional(
158
+ Type.Boolean({
159
+ default: false,
160
+ description: "Whether to wait for on-chain confirmation before returning",
161
+ }),
162
+ ),
163
+ confirmationTimeout: Type.Optional(
164
+ Type.Number({
165
+ default: 60,
166
+ description: "Timeout in seconds to wait for confirmation",
167
+ }),
168
+ ),
169
+ }),
170
+ outputSchema: Type.Object({
171
+ txHash: Type.String({ description: "Transaction hash" }),
172
+ submitted: Type.Boolean(),
173
+ confirmed: Type.Optional(Type.Boolean({ description: "Whether tx was confirmed on-chain" })),
174
+ signedTxCbor: Type.String({ description: "Signed transaction CBOR hex" }),
175
+ }),
176
+ execute: async (input, context) => {
177
+ const blaze = await getBlazeFromContext(context);
178
+
179
+ if (!isHotWallet(blaze)) {
180
+ throw new ActionError(
181
+ "Cold wallets cannot sign transactions. Export the CBOR and sign externally, then use submit-transaction.",
182
+ "COLD_WALLET",
183
+ );
184
+ }
185
+
186
+ const tx = parseTxCbor(input.txCbor);
187
+
188
+ // Sign
189
+ let signedTx: Core.Transaction;
190
+ try {
191
+ signedTx = await blaze.signTransaction(tx);
192
+ } catch (err) {
193
+ throw new ActionError(
194
+ `Failed to sign transaction: ${err instanceof Error ? err.message : String(err)}`,
195
+ "SIGN_ERROR",
196
+ { error: err instanceof Error ? err.message : String(err) },
197
+ );
198
+ }
199
+
200
+ const signedTxCbor = signedTx.toCbor();
201
+
202
+ // Submit
203
+ let txHash: Core.TransactionId;
204
+ try {
205
+ txHash = await blaze.submitTransaction(signedTx);
206
+ } catch (err) {
207
+ throw new ActionError(
208
+ `Transaction submission failed: ${err instanceof Error ? err.message : String(err)}`,
209
+ "SUBMISSION_ERROR",
210
+ { error: err instanceof Error ? err.message : String(err), signedTxCbor },
211
+ );
212
+ }
213
+
214
+ const txHashStr = txHash.toString();
215
+
216
+ // If confirmation not requested, return immediately
217
+ if (!input.waitForConfirmation) {
218
+ return {
219
+ txHash: txHashStr,
220
+ submitted: true,
221
+ signedTxCbor,
222
+ };
223
+ }
224
+
225
+ // Poll for confirmation
226
+ const timeoutSeconds = input.confirmationTimeout ?? 60;
227
+ const deadline = Date.now() + timeoutSeconds * 1000;
228
+ const pollIntervalMs = 5000; // 5 seconds between polls
229
+
230
+ let confirmed = false;
231
+ while (Date.now() < deadline) {
232
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
233
+ try {
234
+ // Query UTxOs and check if any reference this tx hash
235
+ // This is a lightweight confirmation check using the provider
236
+ const utxos = await blaze.provider.getUnspentOutputs(
237
+ await blaze.wallet.getChangeAddress(),
238
+ );
239
+ // If provider returns without error and tx hash appears in a UTxO input or
240
+ // the query succeeds after submission, assume confirmed.
241
+ // A more precise check would be to look up the tx directly.
242
+ // Use resolveUnspentOutputs if available, otherwise accept the provider response.
243
+ const anyMatch = utxos.some(
244
+ (u) => u.input().transactionId().toString() === txHashStr,
245
+ );
246
+ // If we get a fresh UTxO referencing our tx, confirmed
247
+ if (anyMatch) {
248
+ confirmed = true;
249
+ break;
250
+ }
251
+ // Check if we got back any UTxOs at all - for a send transaction the
252
+ // change output should be visible once confirmed
253
+ // Fall through and keep polling
254
+ } catch {
255
+ // Provider error during poll -- keep trying until timeout
256
+ }
257
+ }
258
+
259
+ if (!confirmed && input.waitForConfirmation) {
260
+ // Return partial success rather than throwing -- tx was submitted successfully
261
+ // but we couldn't confirm within the timeout
262
+ return {
263
+ txHash: txHashStr,
264
+ submitted: true,
265
+ confirmed: false,
266
+ signedTxCbor,
267
+ };
268
+ }
269
+
270
+ return {
271
+ txHash: txHashStr,
272
+ submitted: true,
273
+ confirmed,
274
+ signedTxCbor,
275
+ };
276
+ },
277
+ };
278
+
279
+ /**
280
+ * `decode-transaction` -- Decodes a transaction CBOR hex without requiring a wallet.
281
+ * Extracts inputs, outputs, fee, witness count, and required signers from the tx body.
282
+ */
283
+ export const decodeTransaction: IAction<
284
+ { txCbor: string },
285
+ {
286
+ txHash: string;
287
+ inputs: Array<{ txHash: string; outputIndex: number }>;
288
+ outputs: Array<{
289
+ address: string;
290
+ lovelace: string;
291
+ tokens: Array<{ policyId: string; assetName: string; quantity: string }>;
292
+ }>;
293
+ fee: string;
294
+ signatureCount: number;
295
+ requiredSigners: string[];
296
+ },
297
+ TSchema
298
+ > = {
299
+ name: "decode-transaction",
300
+ description:
301
+ "Decode a transaction CBOR hex and return its inputs, outputs, fee, and signers. Does not require a wallet.",
302
+ category: "transaction",
303
+ inputSchema: Type.Object({
304
+ txCbor: Type.String({ description: "Transaction CBOR hex string to decode" }),
305
+ }),
306
+ outputSchema: Type.Object({
307
+ txHash: Type.String({ description: "Transaction body hash" }),
308
+ inputs: Type.Array(
309
+ Type.Object({
310
+ txHash: Type.String({ description: "Input transaction hash" }),
311
+ outputIndex: Type.Number({ description: "Input output index" }),
312
+ }),
313
+ ),
314
+ outputs: Type.Array(
315
+ Type.Object({
316
+ address: Type.String({ description: "Output address (bech32 or hex)" }),
317
+ lovelace: Type.String({ description: "Output lovelace amount as string" }),
318
+ tokens: Type.Array(TokenEntrySchema),
319
+ }),
320
+ ),
321
+ fee: Type.String({ description: "Transaction fee in lovelace as string" }),
322
+ signatureCount: Type.Number({ description: "Number of VKey witnesses" }),
323
+ requiredSigners: Type.Array(
324
+ Type.String({ description: "Required signer key hashes" }),
325
+ ),
326
+ }),
327
+ execute: async (input, _context) => {
328
+ // No Blaze instance needed -- pure CBOR parsing
329
+ const tx = parseTxCbor(input.txCbor);
330
+
331
+ const body = tx.body();
332
+ const txHash = body.hash().toString();
333
+
334
+ // Extract inputs
335
+ const inputSet = body.inputs();
336
+ const inputs = Array.from(inputSet.values()).map((txInput) => ({
337
+ txHash: txInput.transactionId().toString(),
338
+ outputIndex: Number(txInput.index()),
339
+ }));
340
+
341
+ // Extract outputs
342
+ const outputs = body.outputs().map((txOutput) => {
343
+ const value = txOutput.amount();
344
+ const tokens: Array<{ policyId: string; assetName: string; quantity: string }> = [];
345
+
346
+ const multiasset = value.multiasset();
347
+ if (multiasset) {
348
+ for (const [assetId, quantity] of multiasset.entries()) {
349
+ tokens.push({
350
+ policyId: Core.AssetId.getPolicyId(assetId),
351
+ assetName: Core.AssetId.getAssetName(assetId),
352
+ quantity: quantity.toString(),
353
+ });
354
+ }
355
+ }
356
+
357
+ // Attempt bech32 address, fall back to hex if conversion fails
358
+ let address: string;
359
+ try {
360
+ address = txOutput.address().toBech32();
361
+ } catch {
362
+ address = txOutput.address().toBytes();
363
+ }
364
+
365
+ return {
366
+ address,
367
+ lovelace: value.coin().toString(),
368
+ tokens,
369
+ };
370
+ });
371
+
372
+ return {
373
+ txHash,
374
+ inputs,
375
+ outputs,
376
+ fee: body.fee().toString(),
377
+ signatureCount: countSignatures(tx),
378
+ requiredSigners: getRequiredSigners(tx),
379
+ };
380
+ },
381
+ };
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Built-in wallet query actions for the Sprinkle action system.
3
+ * These actions expose wallet address, balance, and UTxO queries as
4
+ * non-interactive actions available in CLI and MCP modes.
5
+ *
6
+ * All actions use category "wallet".
7
+ */
8
+
9
+ import { Type } from "@sinclair/typebox";
10
+ import type { TSchema } from "@sinclair/typebox";
11
+ import { Core } from "@blaze-cardano/sdk";
12
+ import { ActionError } from "../types.js";
13
+ import type { IAction } from "../types.js";
14
+ import { getBlazeFromContext } from "./blaze-helper.js";
15
+
16
+ /** Shared token entry shape for balance and UTxO output */
17
+ const TokenEntrySchema = Type.Object({
18
+ policyId: Type.String({ description: "Policy ID of the token" }),
19
+ assetName: Type.String({ description: "Asset name of the token (hex)" }),
20
+ quantity: Type.String({ description: "Token quantity as string (BigInt-safe)" }),
21
+ });
22
+
23
+ /**
24
+ * `get-wallet-address` -- Returns the primary change address of the configured wallet.
25
+ */
26
+ export const getWalletAddress: IAction<
27
+ Record<string, never>,
28
+ { address: string; network: string },
29
+ TSchema
30
+ > = {
31
+ name: "get-wallet-address",
32
+ description: "Get the primary address of the configured wallet.",
33
+ category: "wallet",
34
+ inputSchema: Type.Object({}),
35
+ outputSchema: Type.Object({
36
+ address: Type.String({ description: "Bech32-encoded payment/change address" }),
37
+ network: Type.String({ description: "Network name (mainnet, preview, preprod)" }),
38
+ }),
39
+ execute: async (_input, context) => {
40
+ const blaze = await getBlazeFromContext(context);
41
+
42
+ let address: Core.Address;
43
+ try {
44
+ address = await blaze.wallet.getChangeAddress();
45
+ } catch (err) {
46
+ throw new ActionError(
47
+ `Failed to get wallet address: ${err instanceof Error ? err.message : String(err)}`,
48
+ "NO_ADDRESS",
49
+ { error: err instanceof Error ? err.message : String(err) },
50
+ );
51
+ }
52
+
53
+ if (!address) {
54
+ throw new ActionError(
55
+ "The wallet returned no address. Ensure the wallet is properly configured.",
56
+ "NO_ADDRESS",
57
+ );
58
+ }
59
+
60
+ const settings = context.settings as unknown as { network?: string };
61
+ const network = settings.network ?? "unknown";
62
+
63
+ return {
64
+ address: address.toBech32(),
65
+ network,
66
+ };
67
+ },
68
+ };
69
+
70
+ /**
71
+ * `get-wallet-balance` -- Returns the ADA and token balance of the configured wallet.
72
+ * Aggregates lovelace and multi-asset amounts from all UTxOs.
73
+ * All quantities are returned as strings for BigInt-safe JSON serialization.
74
+ */
75
+ export const getWalletBalance: IAction<
76
+ Record<string, never>,
77
+ {
78
+ lovelace: string;
79
+ ada: string;
80
+ tokens: Array<{ policyId: string; assetName: string; quantity: string }>;
81
+ },
82
+ TSchema
83
+ > = {
84
+ name: "get-wallet-balance",
85
+ description:
86
+ "Get the ADA and token balance of the configured wallet. Quantities returned as strings.",
87
+ category: "wallet",
88
+ inputSchema: Type.Object({}),
89
+ outputSchema: Type.Object({
90
+ lovelace: Type.String({
91
+ description: "Total ADA balance in lovelace (as string for BigInt safety)",
92
+ }),
93
+ ada: Type.String({ description: "Total ADA balance as decimal string" }),
94
+ tokens: Type.Array(TokenEntrySchema),
95
+ }),
96
+ execute: async (_input, context) => {
97
+ const blaze = await getBlazeFromContext(context);
98
+
99
+ let utxos: Awaited<ReturnType<typeof blaze.wallet.getUnspentOutputs>>;
100
+ try {
101
+ utxos = await blaze.wallet.getUnspentOutputs();
102
+ } catch (err) {
103
+ throw new ActionError(
104
+ `Provider error while fetching UTxOs: ${err instanceof Error ? err.message : String(err)}`,
105
+ "PROVIDER_ERROR",
106
+ { error: err instanceof Error ? err.message : String(err) },
107
+ );
108
+ }
109
+
110
+ // Aggregate lovelace and token amounts
111
+ let totalLovelace = 0n;
112
+ const tokenMap = new Map<string, bigint>(); // key: `${policyId}.${assetName}`
113
+
114
+ for (const utxo of utxos) {
115
+ const value = utxo.output().amount();
116
+ totalLovelace += value.coin();
117
+
118
+ const multiasset = value.multiasset();
119
+ if (multiasset) {
120
+ for (const [assetId, quantity] of multiasset.entries()) {
121
+ const policyId = Core.AssetId.getPolicyId(assetId);
122
+ const assetName = Core.AssetId.getAssetName(assetId);
123
+ const key = `${policyId}.${assetName}`;
124
+ tokenMap.set(key, (tokenMap.get(key) ?? 0n) + quantity);
125
+ }
126
+ }
127
+ }
128
+
129
+ // Convert tokens map to array, serializing BigInt quantities as strings
130
+ const tokens = Array.from(tokenMap.entries()).map(([key, quantity]) => {
131
+ const [policyId, assetName] = key.split(".");
132
+ return {
133
+ policyId: policyId ?? "",
134
+ assetName: assetName ?? "",
135
+ quantity: quantity.toString(),
136
+ };
137
+ });
138
+
139
+ // ADA = lovelace / 1_000_000, with 6 decimal places
140
+ const adaWhole = totalLovelace / 1_000_000n;
141
+ const adaFraction = totalLovelace % 1_000_000n;
142
+ const ada = `${adaWhole}.${adaFraction.toString().padStart(6, "0")}`;
143
+
144
+ return {
145
+ lovelace: totalLovelace.toString(),
146
+ ada,
147
+ tokens,
148
+ };
149
+ },
150
+ };
151
+
152
+ /**
153
+ * `get-wallet-utxos` -- Returns raw UTxO set for the configured wallet.
154
+ * Accepts an optional limit (default 100). Returns UTxOs and the total count.
155
+ */
156
+ export const getWalletUtxos: IAction<
157
+ { limit?: number },
158
+ {
159
+ utxos: Array<{
160
+ txHash: string;
161
+ outputIndex: number;
162
+ lovelace: string;
163
+ tokens: Array<{ policyId: string; assetName: string; quantity: string }>;
164
+ }>;
165
+ total: number;
166
+ },
167
+ TSchema
168
+ > = {
169
+ name: "get-wallet-utxos",
170
+ description: "Get the UTxO set for the configured wallet.",
171
+ category: "wallet",
172
+ inputSchema: Type.Object({
173
+ limit: Type.Optional(
174
+ Type.Number({ default: 100, description: "Maximum number of UTxOs to return" }),
175
+ ),
176
+ }),
177
+ outputSchema: Type.Object({
178
+ utxos: Type.Array(
179
+ Type.Object({
180
+ txHash: Type.String({ description: "Transaction hash of the UTxO" }),
181
+ outputIndex: Type.Number({ description: "Output index within the transaction" }),
182
+ lovelace: Type.String({ description: "Lovelace amount as string (BigInt-safe)" }),
183
+ tokens: Type.Array(TokenEntrySchema),
184
+ }),
185
+ ),
186
+ total: Type.Number({ description: "Total number of UTxOs (before limit)" }),
187
+ }),
188
+ execute: async (input, context) => {
189
+ const blaze = await getBlazeFromContext(context);
190
+ const limit = input.limit ?? 100;
191
+
192
+ let allUtxos: Awaited<ReturnType<typeof blaze.wallet.getUnspentOutputs>>;
193
+ try {
194
+ allUtxos = await blaze.wallet.getUnspentOutputs();
195
+ } catch (err) {
196
+ throw new ActionError(
197
+ `Provider error while fetching UTxOs: ${err instanceof Error ? err.message : String(err)}`,
198
+ "PROVIDER_ERROR",
199
+ { error: err instanceof Error ? err.message : String(err) },
200
+ );
201
+ }
202
+
203
+ const total = allUtxos.length;
204
+ const limited = allUtxos.slice(0, limit);
205
+
206
+ const utxos = limited.map((utxo) => {
207
+ const input = utxo.input();
208
+ const output = utxo.output();
209
+ const value = output.amount();
210
+
211
+ const tokens: Array<{ policyId: string; assetName: string; quantity: string }> = [];
212
+ const multiasset = value.multiasset();
213
+ if (multiasset) {
214
+ for (const [assetId, quantity] of multiasset.entries()) {
215
+ tokens.push({
216
+ policyId: Core.AssetId.getPolicyId(assetId),
217
+ assetName: Core.AssetId.getAssetName(assetId),
218
+ quantity: quantity.toString(),
219
+ });
220
+ }
221
+ }
222
+
223
+ return {
224
+ txHash: input.transactionId().toString(),
225
+ outputIndex: Number(input.index()),
226
+ lovelace: value.coin().toString(),
227
+ tokens,
228
+ };
229
+ });
230
+
231
+ return { utxos, total };
232
+ },
233
+ };