@sundaeswap/sprinkles 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -181
- package/dist/cjs/Sprinkle/__tests__/builtin-actions.test.js +4 -4
- package/dist/cjs/Sprinkle/__tests__/builtin-actions.test.js.map +1 -1
- package/dist/cjs/Sprinkle/__tests__/cli-adapter.test.js +25 -3
- package/dist/cjs/Sprinkle/__tests__/cli-adapter.test.js.map +1 -1
- package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js +15 -1
- package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
- package/dist/cjs/Sprinkle/__tests__/mcp-adapter.test.js +7 -9
- package/dist/cjs/Sprinkle/__tests__/mcp-adapter.test.js.map +1 -1
- package/dist/cjs/Sprinkle/__tests__/native-script.test.js +390 -0
- package/dist/cjs/Sprinkle/__tests__/native-script.test.js.map +1 -0
- package/dist/cjs/Sprinkle/__tests__/utility-actions.test.js +367 -0
- package/dist/cjs/Sprinkle/__tests__/utility-actions.test.js.map +1 -0
- package/dist/cjs/Sprinkle/actions/builtin/addressbook-actions.js +164 -0
- package/dist/cjs/Sprinkle/actions/builtin/addressbook-actions.js.map +1 -0
- package/dist/cjs/Sprinkle/actions/builtin/index.js +60 -3
- package/dist/cjs/Sprinkle/actions/builtin/index.js.map +1 -1
- package/dist/cjs/Sprinkle/actions/builtin/native-script.js +139 -0
- package/dist/cjs/Sprinkle/actions/builtin/native-script.js.map +1 -0
- package/dist/cjs/Sprinkle/actions/builtin/utility-actions.js +218 -0
- package/dist/cjs/Sprinkle/actions/builtin/utility-actions.js.map +1 -0
- package/dist/cjs/Sprinkle/actions/cli-adapter.js +20 -2
- package/dist/cjs/Sprinkle/actions/cli-adapter.js.map +1 -1
- package/dist/cjs/Sprinkle/actions/index.js +12 -0
- package/dist/cjs/Sprinkle/actions/index.js.map +1 -1
- package/dist/cjs/Sprinkle/actions/mcp-adapter.js +146 -4
- package/dist/cjs/Sprinkle/actions/mcp-adapter.js.map +1 -1
- package/dist/cjs/Sprinkle/index.js +267 -5
- package/dist/cjs/Sprinkle/index.js.map +1 -1
- package/dist/cjs/Sprinkle/schemas.js +17 -1
- package/dist/cjs/Sprinkle/schemas.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/builtin-actions.test.js +4 -4
- package/dist/esm/Sprinkle/__tests__/builtin-actions.test.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/cli-adapter.test.js +25 -3
- package/dist/esm/Sprinkle/__tests__/cli-adapter.test.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js +15 -1
- package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/mcp-adapter.test.js +7 -9
- package/dist/esm/Sprinkle/__tests__/mcp-adapter.test.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/native-script.test.js +388 -0
- package/dist/esm/Sprinkle/__tests__/native-script.test.js.map +1 -0
- package/dist/esm/Sprinkle/__tests__/utility-actions.test.js +365 -0
- package/dist/esm/Sprinkle/__tests__/utility-actions.test.js.map +1 -0
- package/dist/esm/Sprinkle/actions/builtin/addressbook-actions.js +159 -0
- package/dist/esm/Sprinkle/actions/builtin/addressbook-actions.js.map +1 -0
- package/dist/esm/Sprinkle/actions/builtin/index.js +8 -3
- package/dist/esm/Sprinkle/actions/builtin/index.js.map +1 -1
- package/dist/esm/Sprinkle/actions/builtin/native-script.js +133 -0
- package/dist/esm/Sprinkle/actions/builtin/native-script.js.map +1 -0
- package/dist/esm/Sprinkle/actions/builtin/utility-actions.js +213 -0
- package/dist/esm/Sprinkle/actions/builtin/utility-actions.js.map +1 -0
- package/dist/esm/Sprinkle/actions/cli-adapter.js +20 -2
- package/dist/esm/Sprinkle/actions/cli-adapter.js.map +1 -1
- package/dist/esm/Sprinkle/actions/index.js +1 -1
- package/dist/esm/Sprinkle/actions/index.js.map +1 -1
- package/dist/esm/Sprinkle/actions/mcp-adapter.js +145 -5
- package/dist/esm/Sprinkle/actions/mcp-adapter.js.map +1 -1
- package/dist/esm/Sprinkle/index.js +259 -8
- package/dist/esm/Sprinkle/index.js.map +1 -1
- package/dist/esm/Sprinkle/schemas.js +16 -0
- package/dist/esm/Sprinkle/schemas.js.map +1 -1
- package/dist/types/Sprinkle/actions/builtin/addressbook-actions.d.ts +50 -0
- package/dist/types/Sprinkle/actions/builtin/addressbook-actions.d.ts.map +1 -0
- package/dist/types/Sprinkle/actions/builtin/index.d.ts +6 -2
- package/dist/types/Sprinkle/actions/builtin/index.d.ts.map +1 -1
- package/dist/types/Sprinkle/actions/builtin/native-script.d.ts +27 -0
- package/dist/types/Sprinkle/actions/builtin/native-script.d.ts.map +1 -0
- package/dist/types/Sprinkle/actions/builtin/utility-actions.d.ts +48 -0
- package/dist/types/Sprinkle/actions/builtin/utility-actions.d.ts.map +1 -0
- package/dist/types/Sprinkle/actions/cli-adapter.d.ts.map +1 -1
- package/dist/types/Sprinkle/actions/index.d.ts +2 -1
- package/dist/types/Sprinkle/actions/index.d.ts.map +1 -1
- package/dist/types/Sprinkle/actions/mcp-adapter.d.ts +24 -0
- package/dist/types/Sprinkle/actions/mcp-adapter.d.ts.map +1 -1
- package/dist/types/Sprinkle/index.d.ts +3 -1
- package/dist/types/Sprinkle/index.d.ts.map +1 -1
- package/dist/types/Sprinkle/schemas.d.ts +72 -0
- package/dist/types/Sprinkle/schemas.d.ts.map +1 -1
- package/dist/types/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/Sprinkle/__tests__/builtin-actions.test.ts +4 -4
- package/src/Sprinkle/__tests__/cli-adapter.test.ts +24 -3
- package/src/Sprinkle/__tests__/fill-in-struct.test.ts +23 -1
- package/src/Sprinkle/__tests__/mcp-adapter.test.ts +7 -5
- package/src/Sprinkle/__tests__/native-script.test.ts +341 -0
- package/src/Sprinkle/__tests__/utility-actions.test.ts +348 -0
- package/src/Sprinkle/actions/builtin/addressbook-actions.ts +168 -0
- package/src/Sprinkle/actions/builtin/index.ts +41 -2
- package/src/Sprinkle/actions/builtin/native-script.ts +165 -0
- package/src/Sprinkle/actions/builtin/utility-actions.ts +285 -0
- package/src/Sprinkle/actions/cli-adapter.ts +18 -2
- package/src/Sprinkle/actions/index.ts +2 -1
- package/src/Sprinkle/actions/mcp-adapter.ts +179 -4
- package/src/Sprinkle/index.ts +261 -3
- package/src/Sprinkle/schemas.ts +20 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { describe, expect, test, mock, beforeEach } from "bun:test";
|
|
2
|
+
import { ActionError } from "../actions/types.js";
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Mocks
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
// Controllable mock state
|
|
9
|
+
let mockBlaze: any = {};
|
|
10
|
+
let mockIsHot = true;
|
|
11
|
+
let mockBlazeError: Error | null = null;
|
|
12
|
+
|
|
13
|
+
mock.module("../actions/builtin/blaze-helper.js", () => ({
|
|
14
|
+
getBlazeFromContext: async () => {
|
|
15
|
+
if (mockBlazeError) throw mockBlazeError;
|
|
16
|
+
return mockBlaze;
|
|
17
|
+
},
|
|
18
|
+
isHotWallet: () => mockIsHot,
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
// Track calls to blaze transaction builder
|
|
22
|
+
let mintCalls: Array<{ policyId: string; mints: Map<string, bigint> }> = [];
|
|
23
|
+
let provideScriptCalls: any[] = [];
|
|
24
|
+
let payAssetsCalls: Array<{ address: any; value: any }> = [];
|
|
25
|
+
let registerStakeCalls: any[] = [];
|
|
26
|
+
|
|
27
|
+
// Mock transaction returned by complete()
|
|
28
|
+
const mockCompletedTx = {
|
|
29
|
+
toCbor: () => "mock-tx-cbor-hex",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Mock transaction builder chain
|
|
33
|
+
function makeTxBuilder() {
|
|
34
|
+
return {
|
|
35
|
+
addMint: (policyId: string, mints: Map<string, bigint>) => {
|
|
36
|
+
mintCalls.push({ policyId, mints });
|
|
37
|
+
return makeTxBuilder();
|
|
38
|
+
},
|
|
39
|
+
provideScript: (script: any) => {
|
|
40
|
+
provideScriptCalls.push(script);
|
|
41
|
+
return makeTxBuilder();
|
|
42
|
+
},
|
|
43
|
+
payAssets: (address: any, value: any) => {
|
|
44
|
+
payAssetsCalls.push({ address, value });
|
|
45
|
+
return makeTxBuilder();
|
|
46
|
+
},
|
|
47
|
+
addRegisterStake: (credential: any) => {
|
|
48
|
+
registerStakeCalls.push(credential);
|
|
49
|
+
return makeTxBuilder();
|
|
50
|
+
},
|
|
51
|
+
complete: async () => mockCompletedTx,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Mock address with payment credential
|
|
56
|
+
function makeMockAddress(hash = "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234") {
|
|
57
|
+
return {
|
|
58
|
+
toBech32: () => "addr_test1qzmock",
|
|
59
|
+
asBase: () => ({
|
|
60
|
+
getPaymentCredential: () => ({
|
|
61
|
+
hash: { toString: () => hash },
|
|
62
|
+
}),
|
|
63
|
+
}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
mock.module("@blaze-cardano/sdk", () => ({
|
|
68
|
+
Core: {
|
|
69
|
+
Ed25519KeyHashHex: (h: string) => h,
|
|
70
|
+
ScriptPubkey: class {
|
|
71
|
+
hash: string;
|
|
72
|
+
constructor(h: string) { this.hash = h; }
|
|
73
|
+
},
|
|
74
|
+
Script: {
|
|
75
|
+
newNativeScript: (ns: any) => ({
|
|
76
|
+
hash: () => "mock-policy-id-hash",
|
|
77
|
+
}),
|
|
78
|
+
},
|
|
79
|
+
NativeScript: {
|
|
80
|
+
newScriptPubkey: (sp: any) => sp,
|
|
81
|
+
},
|
|
82
|
+
PolicyId: (s: string) => s,
|
|
83
|
+
AssetName: (s: string) => s,
|
|
84
|
+
toHex: (buf: Buffer) => buf.toString("hex"),
|
|
85
|
+
Address: {
|
|
86
|
+
fromBech32: (addr: string) => {
|
|
87
|
+
if (addr === "invalid-address") throw new Error("Invalid bech32");
|
|
88
|
+
return { toBech32: () => addr };
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
TxCBOR: (s: string) => s,
|
|
92
|
+
Hash28ByteBase16: (s: string) => s,
|
|
93
|
+
Credential: {
|
|
94
|
+
fromCore: (c: any) => c,
|
|
95
|
+
},
|
|
96
|
+
CredentialType: {
|
|
97
|
+
ScriptHash: 1,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
makeValue: (...args: any[]) => ({ __makeValueArgs: args }),
|
|
101
|
+
Blaze: class {},
|
|
102
|
+
HotWallet: class {},
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
// Import actions after mocks are set up
|
|
106
|
+
const { mintToken, simpleSend, registerStakeScript } = await import(
|
|
107
|
+
"../actions/builtin/utility-actions.js"
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Minimal context factory
|
|
111
|
+
function makeContext(settingsOverrides: Record<string, unknown> = {}) {
|
|
112
|
+
return {
|
|
113
|
+
sprinkle: {} as any,
|
|
114
|
+
settings: {
|
|
115
|
+
network: "preview",
|
|
116
|
+
provider: { type: "blockfrost", apiKey: "test" },
|
|
117
|
+
wallet: { type: "hot", mnemonic: "test words" },
|
|
118
|
+
...settingsOverrides,
|
|
119
|
+
} as any,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// mint-token
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
describe("mint-token", () => {
|
|
128
|
+
beforeEach(() => {
|
|
129
|
+
mockBlazeError = null;
|
|
130
|
+
mockIsHot = true;
|
|
131
|
+
mintCalls = [];
|
|
132
|
+
provideScriptCalls = [];
|
|
133
|
+
mockBlaze = {
|
|
134
|
+
wallet: {
|
|
135
|
+
getChangeAddress: async () => makeMockAddress(),
|
|
136
|
+
},
|
|
137
|
+
newTransaction: () => makeTxBuilder(),
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("has correct metadata", () => {
|
|
142
|
+
expect(mintToken.name).toBe("mint-token");
|
|
143
|
+
expect(mintToken.category).toBe("utility");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("builds a mint transaction and returns policy info", async () => {
|
|
147
|
+
const ctx = makeContext();
|
|
148
|
+
const result = await mintToken.execute(
|
|
149
|
+
{ tokenName: "MyToken", amount: "1000" },
|
|
150
|
+
ctx,
|
|
151
|
+
);
|
|
152
|
+
expect(result.policyId).toBe("mock-policy-id-hash");
|
|
153
|
+
expect(result.tokenName).toBe("MyToken");
|
|
154
|
+
expect(result.amount).toBe("1000");
|
|
155
|
+
expect(result.txCbor).toBe("mock-tx-cbor-hex");
|
|
156
|
+
expect(mintCalls).toHaveLength(1);
|
|
157
|
+
expect(provideScriptCalls).toHaveLength(1);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("throws COLD_WALLET when wallet is not hot", async () => {
|
|
161
|
+
mockIsHot = false;
|
|
162
|
+
const ctx = makeContext();
|
|
163
|
+
await expect(
|
|
164
|
+
mintToken.execute({ tokenName: "T", amount: "1" }, ctx),
|
|
165
|
+
).rejects.toMatchObject({ code: "COLD_WALLET" });
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("throws WALLET_NOT_CONFIGURED when blaze init fails", async () => {
|
|
169
|
+
mockBlazeError = new ActionError(
|
|
170
|
+
"Missing settings",
|
|
171
|
+
"WALLET_NOT_CONFIGURED",
|
|
172
|
+
{ missingFields: ["wallet"] },
|
|
173
|
+
);
|
|
174
|
+
const ctx = makeContext();
|
|
175
|
+
await expect(
|
|
176
|
+
mintToken.execute({ tokenName: "T", amount: "1" }, ctx),
|
|
177
|
+
).rejects.toMatchObject({ code: "WALLET_NOT_CONFIGURED" });
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("throws NO_ADDRESS when getChangeAddress fails", async () => {
|
|
181
|
+
mockBlaze = {
|
|
182
|
+
wallet: {
|
|
183
|
+
getChangeAddress: async () => {
|
|
184
|
+
throw new Error("no address");
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
newTransaction: () => makeTxBuilder(),
|
|
188
|
+
};
|
|
189
|
+
const ctx = makeContext();
|
|
190
|
+
await expect(
|
|
191
|
+
mintToken.execute({ tokenName: "T", amount: "1" }, ctx),
|
|
192
|
+
).rejects.toMatchObject({ code: "NO_ADDRESS" });
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("throws INVALID_ADDRESS when base address is null", async () => {
|
|
196
|
+
mockBlaze = {
|
|
197
|
+
wallet: {
|
|
198
|
+
getChangeAddress: async () => ({
|
|
199
|
+
toBech32: () => "addr_test1...",
|
|
200
|
+
asBase: () => null,
|
|
201
|
+
}),
|
|
202
|
+
},
|
|
203
|
+
newTransaction: () => makeTxBuilder(),
|
|
204
|
+
};
|
|
205
|
+
const ctx = makeContext();
|
|
206
|
+
await expect(
|
|
207
|
+
mintToken.execute({ tokenName: "T", amount: "1" }, ctx),
|
|
208
|
+
).rejects.toMatchObject({ code: "INVALID_ADDRESS" });
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// simple-send
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
describe("simple-send", () => {
|
|
217
|
+
beforeEach(() => {
|
|
218
|
+
mockBlazeError = null;
|
|
219
|
+
mockIsHot = true;
|
|
220
|
+
payAssetsCalls = [];
|
|
221
|
+
mockBlaze = {
|
|
222
|
+
newTransaction: () => makeTxBuilder(),
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("has correct metadata", () => {
|
|
227
|
+
expect(simpleSend.name).toBe("simple-send");
|
|
228
|
+
expect(simpleSend.category).toBe("wallet");
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("builds a lovelace-only send transaction", async () => {
|
|
232
|
+
const ctx = makeContext();
|
|
233
|
+
const result = await simpleSend.execute(
|
|
234
|
+
{ recipientAddress: "addr_test1qzrecipient", lovelace: "5000000" },
|
|
235
|
+
ctx,
|
|
236
|
+
);
|
|
237
|
+
expect(result.txCbor).toBe("mock-tx-cbor-hex");
|
|
238
|
+
expect(payAssetsCalls).toHaveLength(1);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("builds a token send transaction", async () => {
|
|
242
|
+
const ctx = makeContext();
|
|
243
|
+
const result = await simpleSend.execute(
|
|
244
|
+
{
|
|
245
|
+
recipientAddress: "addr_test1qzrecipient",
|
|
246
|
+
policyId: "a".repeat(56),
|
|
247
|
+
assetName: "4d79546f6b656e",
|
|
248
|
+
tokenAmount: "100",
|
|
249
|
+
},
|
|
250
|
+
ctx,
|
|
251
|
+
);
|
|
252
|
+
expect(result.txCbor).toBe("mock-tx-cbor-hex");
|
|
253
|
+
expect(payAssetsCalls).toHaveLength(1);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("throws INVALID_INPUT when neither lovelace nor token provided", async () => {
|
|
257
|
+
const ctx = makeContext();
|
|
258
|
+
await expect(
|
|
259
|
+
simpleSend.execute({ recipientAddress: "addr_test1qzrecipient" }, ctx),
|
|
260
|
+
).rejects.toMatchObject({ code: "INVALID_INPUT" });
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("throws INVALID_ADDRESS for bad recipient", async () => {
|
|
264
|
+
const ctx = makeContext();
|
|
265
|
+
await expect(
|
|
266
|
+
simpleSend.execute(
|
|
267
|
+
{ recipientAddress: "invalid-address", lovelace: "1000000" },
|
|
268
|
+
ctx,
|
|
269
|
+
),
|
|
270
|
+
).rejects.toMatchObject({ code: "INVALID_ADDRESS" });
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("throws WALLET_NOT_CONFIGURED when blaze init fails", async () => {
|
|
274
|
+
mockBlazeError = new ActionError(
|
|
275
|
+
"Missing settings",
|
|
276
|
+
"WALLET_NOT_CONFIGURED",
|
|
277
|
+
{ missingFields: ["wallet"] },
|
|
278
|
+
);
|
|
279
|
+
const ctx = makeContext();
|
|
280
|
+
await expect(
|
|
281
|
+
simpleSend.execute(
|
|
282
|
+
{ recipientAddress: "addr_test1qz", lovelace: "1000000" },
|
|
283
|
+
ctx,
|
|
284
|
+
),
|
|
285
|
+
).rejects.toMatchObject({ code: "WALLET_NOT_CONFIGURED" });
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
// register-stake-script
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
|
|
293
|
+
describe("register-stake-script", () => {
|
|
294
|
+
beforeEach(() => {
|
|
295
|
+
mockBlazeError = null;
|
|
296
|
+
mockIsHot = true;
|
|
297
|
+
registerStakeCalls = [];
|
|
298
|
+
mockBlaze = {
|
|
299
|
+
newTransaction: () => makeTxBuilder(),
|
|
300
|
+
};
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("has correct metadata", () => {
|
|
304
|
+
expect(registerStakeScript.name).toBe("register-stake-script");
|
|
305
|
+
expect(registerStakeScript.category).toBe("utility");
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test("builds a stake registration transaction", async () => {
|
|
309
|
+
const validHash = "a".repeat(56);
|
|
310
|
+
const ctx = makeContext();
|
|
311
|
+
const result = await registerStakeScript.execute(
|
|
312
|
+
{ scriptHash: validHash },
|
|
313
|
+
ctx,
|
|
314
|
+
);
|
|
315
|
+
expect(result.txCbor).toBe("mock-tx-cbor-hex");
|
|
316
|
+
expect(registerStakeCalls).toHaveLength(1);
|
|
317
|
+
expect(registerStakeCalls[0]).toMatchObject({
|
|
318
|
+
hash: validHash,
|
|
319
|
+
type: 1, // ScriptHash
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test("throws INVALID_SCRIPT_HASH for wrong length", async () => {
|
|
324
|
+
const ctx = makeContext();
|
|
325
|
+
await expect(
|
|
326
|
+
registerStakeScript.execute({ scriptHash: "tooshort" }, ctx),
|
|
327
|
+
).rejects.toMatchObject({ code: "INVALID_SCRIPT_HASH" });
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test("throws INVALID_SCRIPT_HASH for non-hex characters", async () => {
|
|
331
|
+
const ctx = makeContext();
|
|
332
|
+
await expect(
|
|
333
|
+
registerStakeScript.execute({ scriptHash: "g".repeat(56) }, ctx),
|
|
334
|
+
).rejects.toMatchObject({ code: "INVALID_SCRIPT_HASH" });
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test("throws WALLET_NOT_CONFIGURED when blaze init fails", async () => {
|
|
338
|
+
mockBlazeError = new ActionError(
|
|
339
|
+
"Missing settings",
|
|
340
|
+
"WALLET_NOT_CONFIGURED",
|
|
341
|
+
{ missingFields: ["provider"] },
|
|
342
|
+
);
|
|
343
|
+
const ctx = makeContext();
|
|
344
|
+
await expect(
|
|
345
|
+
registerStakeScript.execute({ scriptHash: "a".repeat(56) }, ctx),
|
|
346
|
+
).rejects.toMatchObject({ code: "WALLET_NOT_CONFIGURED" });
|
|
347
|
+
});
|
|
348
|
+
});
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in addressbook actions for managing named native scripts.
|
|
3
|
+
*
|
|
4
|
+
* The addressbook stores MultisigScript entries by name in the profile,
|
|
5
|
+
* allowing them to be reused across actions and automatically resolved
|
|
6
|
+
* by script hash during transaction building.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Type } from "@sinclair/typebox";
|
|
10
|
+
import type { TSchema } from "@sinclair/typebox";
|
|
11
|
+
import { MultisigScript } from "../../schemas.js";
|
|
12
|
+
import type { TMultisigScript } from "../../schemas.js";
|
|
13
|
+
import { ActionError } from "../types.js";
|
|
14
|
+
import type { IAction } from "../types.js";
|
|
15
|
+
import { toNativeScript } from "./native-script.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* `list-addressbook` -- List all entries in the addressbook.
|
|
19
|
+
*/
|
|
20
|
+
export const listAddressbook: IAction<
|
|
21
|
+
Record<string, never>,
|
|
22
|
+
{ entries: Array<{ name: string; scriptHash: string; script: TMultisigScript }> },
|
|
23
|
+
TSchema
|
|
24
|
+
> = {
|
|
25
|
+
name: "list-addressbook",
|
|
26
|
+
description: "List all named native scripts in the addressbook.",
|
|
27
|
+
category: "addressbook",
|
|
28
|
+
inputSchema: Type.Object({}),
|
|
29
|
+
outputSchema: Type.Object({
|
|
30
|
+
entries: Type.Array(
|
|
31
|
+
Type.Object({
|
|
32
|
+
name: Type.String(),
|
|
33
|
+
scriptHash: Type.String(),
|
|
34
|
+
script: MultisigScript,
|
|
35
|
+
}),
|
|
36
|
+
),
|
|
37
|
+
}),
|
|
38
|
+
execute: async (_input, context) => {
|
|
39
|
+
const addressbook = context.sprinkle.addressbook ?? {};
|
|
40
|
+
const entries = [];
|
|
41
|
+
for (const [name, ms] of Object.entries(addressbook)) {
|
|
42
|
+
try {
|
|
43
|
+
const script = toNativeScript(ms);
|
|
44
|
+
entries.push({ name, scriptHash: script.hash(), script: ms });
|
|
45
|
+
} catch {
|
|
46
|
+
// Include entries even if hash computation fails
|
|
47
|
+
entries.push({ name, scriptHash: "unknown", script: ms });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { entries };
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* `set-addressbook-entry` -- Add or update a named native script in the addressbook.
|
|
56
|
+
*/
|
|
57
|
+
export const setAddressbookEntry: IAction<
|
|
58
|
+
{ name: string; script: TMultisigScript },
|
|
59
|
+
{ name: string; scriptHash: string },
|
|
60
|
+
TSchema
|
|
61
|
+
> = {
|
|
62
|
+
name: "set-addressbook-entry",
|
|
63
|
+
description:
|
|
64
|
+
"Add or update a named native script in the addressbook. Accepts a MultisigScript JSON structure.",
|
|
65
|
+
category: "addressbook",
|
|
66
|
+
inputSchema: Type.Object({
|
|
67
|
+
name: Type.String({
|
|
68
|
+
minLength: 1,
|
|
69
|
+
description: "Name for this addressbook entry",
|
|
70
|
+
}),
|
|
71
|
+
script: MultisigScript,
|
|
72
|
+
}),
|
|
73
|
+
outputSchema: Type.Object({
|
|
74
|
+
name: Type.String(),
|
|
75
|
+
scriptHash: Type.String({ description: "Computed script hash" }),
|
|
76
|
+
}),
|
|
77
|
+
execute: async (input, context) => {
|
|
78
|
+
// Validate the script can be converted
|
|
79
|
+
const blazeScript = toNativeScript(input.script);
|
|
80
|
+
const scriptHash = blazeScript.hash();
|
|
81
|
+
|
|
82
|
+
context.sprinkle.addressbook[input.name] = input.script;
|
|
83
|
+
context.sprinkle.saveSettings();
|
|
84
|
+
|
|
85
|
+
return { name: input.name, scriptHash };
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* `delete-addressbook-entry` -- Remove a named native script from the addressbook.
|
|
91
|
+
*/
|
|
92
|
+
export const deleteAddressbookEntry: IAction<
|
|
93
|
+
{ name: string },
|
|
94
|
+
{ deleted: boolean; name: string },
|
|
95
|
+
TSchema
|
|
96
|
+
> = {
|
|
97
|
+
name: "delete-addressbook-entry",
|
|
98
|
+
description: "Remove a named native script from the addressbook.",
|
|
99
|
+
category: "addressbook",
|
|
100
|
+
inputSchema: Type.Object({
|
|
101
|
+
name: Type.String({
|
|
102
|
+
minLength: 1,
|
|
103
|
+
description: "Name of the addressbook entry to delete",
|
|
104
|
+
}),
|
|
105
|
+
}),
|
|
106
|
+
outputSchema: Type.Object({
|
|
107
|
+
deleted: Type.Boolean(),
|
|
108
|
+
name: Type.String(),
|
|
109
|
+
}),
|
|
110
|
+
execute: async (input, context) => {
|
|
111
|
+
if (!(input.name in context.sprinkle.addressbook)) {
|
|
112
|
+
throw new ActionError(
|
|
113
|
+
`Addressbook entry "${input.name}" not found.`,
|
|
114
|
+
"NOT_FOUND",
|
|
115
|
+
{ name: input.name },
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
delete context.sprinkle.addressbook[input.name];
|
|
120
|
+
context.sprinkle.saveSettings();
|
|
121
|
+
|
|
122
|
+
return { deleted: true, name: input.name };
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* `get-addressbook-entry` -- Get a specific named native script from the addressbook.
|
|
128
|
+
*/
|
|
129
|
+
export const getAddressbookEntry: IAction<
|
|
130
|
+
{ name: string },
|
|
131
|
+
{ name: string; scriptHash: string; script: TMultisigScript },
|
|
132
|
+
TSchema
|
|
133
|
+
> = {
|
|
134
|
+
name: "get-addressbook-entry",
|
|
135
|
+
description: "Get a specific named native script from the addressbook.",
|
|
136
|
+
category: "addressbook",
|
|
137
|
+
inputSchema: Type.Object({
|
|
138
|
+
name: Type.String({
|
|
139
|
+
minLength: 1,
|
|
140
|
+
description: "Name of the addressbook entry to retrieve",
|
|
141
|
+
}),
|
|
142
|
+
}),
|
|
143
|
+
outputSchema: Type.Object({
|
|
144
|
+
name: Type.String(),
|
|
145
|
+
scriptHash: Type.String(),
|
|
146
|
+
script: MultisigScript,
|
|
147
|
+
}),
|
|
148
|
+
execute: async (input, context) => {
|
|
149
|
+
const ms = context.sprinkle.addressbook[input.name];
|
|
150
|
+
if (!ms) {
|
|
151
|
+
throw new ActionError(
|
|
152
|
+
`Addressbook entry "${input.name}" not found.`,
|
|
153
|
+
"NOT_FOUND",
|
|
154
|
+
{ name: input.name },
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let scriptHash = "unknown";
|
|
159
|
+
try {
|
|
160
|
+
const blazeScript = toNativeScript(ms);
|
|
161
|
+
scriptHash = blazeScript.hash();
|
|
162
|
+
} catch {
|
|
163
|
+
// Return entry even if hash computation fails
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { name: input.name, scriptHash, script: ms };
|
|
167
|
+
},
|
|
168
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Barrel export for built-in Sprinkle actions.
|
|
3
3
|
*
|
|
4
|
-
* Provides a `getBuiltinActions()` factory that returns all
|
|
4
|
+
* Provides a `getBuiltinActions()` factory that returns all 17 built-in actions
|
|
5
5
|
* ready for registration on an ActionRegistry.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -31,6 +31,25 @@ export {
|
|
|
31
31
|
decodeTransaction,
|
|
32
32
|
} from "./transaction-actions.js";
|
|
33
33
|
|
|
34
|
+
export {
|
|
35
|
+
mintToken,
|
|
36
|
+
simpleSend,
|
|
37
|
+
registerStakeScript,
|
|
38
|
+
} from "./utility-actions.js";
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
toNativeScript,
|
|
42
|
+
completeWithScripts,
|
|
43
|
+
} from "./native-script.js";
|
|
44
|
+
export type { NativeScriptInput } from "./native-script.js";
|
|
45
|
+
|
|
46
|
+
export {
|
|
47
|
+
listAddressbook,
|
|
48
|
+
getAddressbookEntry,
|
|
49
|
+
setAddressbookEntry,
|
|
50
|
+
deleteAddressbookEntry,
|
|
51
|
+
} from "./addressbook-actions.js";
|
|
52
|
+
|
|
34
53
|
import {
|
|
35
54
|
listProfiles,
|
|
36
55
|
getProfile,
|
|
@@ -54,8 +73,21 @@ import {
|
|
|
54
73
|
decodeTransaction,
|
|
55
74
|
} from "./transaction-actions.js";
|
|
56
75
|
|
|
76
|
+
import {
|
|
77
|
+
mintToken,
|
|
78
|
+
simpleSend,
|
|
79
|
+
registerStakeScript,
|
|
80
|
+
} from "./utility-actions.js";
|
|
81
|
+
|
|
82
|
+
import {
|
|
83
|
+
listAddressbook,
|
|
84
|
+
getAddressbookEntry,
|
|
85
|
+
setAddressbookEntry,
|
|
86
|
+
deleteAddressbookEntry,
|
|
87
|
+
} from "./addressbook-actions.js";
|
|
88
|
+
|
|
57
89
|
/**
|
|
58
|
-
* Returns
|
|
90
|
+
* Returns all built-in Sprinkle actions.
|
|
59
91
|
* Register them on your ActionRegistry or pass them to `Sprinkle.run()`.
|
|
60
92
|
*
|
|
61
93
|
* @example
|
|
@@ -82,5 +114,12 @@ export function getBuiltinActions<S extends TSchema>(): AnyAction<S>[] {
|
|
|
82
114
|
submitTransaction,
|
|
83
115
|
signAndSubmit,
|
|
84
116
|
decodeTransaction,
|
|
117
|
+
mintToken,
|
|
118
|
+
simpleSend,
|
|
119
|
+
registerStakeScript,
|
|
120
|
+
listAddressbook,
|
|
121
|
+
getAddressbookEntry,
|
|
122
|
+
setAddressbookEntry,
|
|
123
|
+
deleteAddressbookEntry,
|
|
85
124
|
] as unknown as AnyAction<S>[];
|
|
86
125
|
}
|