@sundaeswap/sprinkles 0.7.0 → 0.8.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.
- 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 +282 -6
- 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 +260 -9
- 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 +5 -2
- 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 +264 -3
- package/src/Sprinkle/schemas.ts +20 -0
package/package.json
CHANGED
|
@@ -51,14 +51,14 @@ async function makeSprinkle(
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
describe("getBuiltinActions", () => {
|
|
54
|
-
test("returns an array of
|
|
54
|
+
test("returns an array of 21 actions", () => {
|
|
55
55
|
const actions = getBuiltinActions();
|
|
56
|
-
expect(actions).toHaveLength(
|
|
56
|
+
expect(actions).toHaveLength(21);
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
test("all actions have a category", () => {
|
|
60
60
|
const actions = getBuiltinActions();
|
|
61
|
-
const validCategories = ["sprinkles", "wallet", "transaction"];
|
|
61
|
+
const validCategories = ["sprinkles", "wallet", "transaction", "utility", "addressbook"];
|
|
62
62
|
for (const action of actions) {
|
|
63
63
|
expect(validCategories).toContain((action as any).category);
|
|
64
64
|
}
|
|
@@ -89,7 +89,7 @@ describe("getBuiltinActions", () => {
|
|
|
89
89
|
for (const action of actions) {
|
|
90
90
|
sprinkle.registerAction(action as any);
|
|
91
91
|
}
|
|
92
|
-
expect(sprinkle.listActions()).toHaveLength(
|
|
92
|
+
expect(sprinkle.listActions()).toHaveLength(21);
|
|
93
93
|
});
|
|
94
94
|
});
|
|
95
95
|
|
|
@@ -205,9 +205,30 @@ describe("coerceValue", () => {
|
|
|
205
205
|
expect(coerceValue("not-json", Type.Object({ x: Type.String() }))).toBe("not-json");
|
|
206
206
|
});
|
|
207
207
|
|
|
208
|
-
test("
|
|
209
|
-
const obj = { x: 1 };
|
|
210
|
-
expect(coerceValue(obj, Type.Object({ x: Type.Number() }))).
|
|
208
|
+
test("coerces properties of already-parsed object for Object schema", () => {
|
|
209
|
+
const obj = { x: "1" };
|
|
210
|
+
expect(coerceValue(obj, Type.Object({ x: Type.Number() }))).toEqual({ x: 1 });
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("recursively coerces nested BigInt values inside parsed JSON", () => {
|
|
214
|
+
const schema = Type.Object({
|
|
215
|
+
name: Type.String(),
|
|
216
|
+
reserve_assets: Type.Array(Type.Object({
|
|
217
|
+
asset: Type.Array(Type.String()),
|
|
218
|
+
numerator: Type.BigInt(),
|
|
219
|
+
denominator: Type.BigInt(),
|
|
220
|
+
})),
|
|
221
|
+
});
|
|
222
|
+
const raw = '{"name":"test","reserve_assets":[{"asset":["policy","name"],"numerator":"1","denominator":"2"}]}';
|
|
223
|
+
const result = coerceValue(raw, schema);
|
|
224
|
+
expect(result).toEqual({
|
|
225
|
+
name: "test",
|
|
226
|
+
reserve_assets: [{
|
|
227
|
+
asset: ["policy", "name"],
|
|
228
|
+
numerator: BigInt(1),
|
|
229
|
+
denominator: BigInt(2),
|
|
230
|
+
}],
|
|
231
|
+
});
|
|
211
232
|
});
|
|
212
233
|
|
|
213
234
|
test("returns malformed JSON string as-is for Object schema", () => {
|
|
@@ -91,8 +91,30 @@ describe("FillInStruct", () => {
|
|
|
91
91
|
expect(mockInputCancellable.mock.calls[0][0].default).toBe("99");
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
-
test("
|
|
94
|
+
test("fills a boolean field with select", async () => {
|
|
95
|
+
mockSelectCancellable.mockResolvedValueOnce(true);
|
|
96
|
+
|
|
97
|
+
const result = await sprinkle.FillInStruct(Type.Boolean() as any);
|
|
98
|
+
expect(result).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("boolean field respects default value", async () => {
|
|
102
|
+
mockSelectCancellable.mockResolvedValueOnce(false);
|
|
103
|
+
|
|
104
|
+
await sprinkle.FillInStruct(Type.Boolean() as any, false as any);
|
|
105
|
+
expect(mockSelectCancellable.mock.calls[0][0].default).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("boolean field throws on cancel", async () => {
|
|
109
|
+
mockSelectCancellable.mockResolvedValueOnce(null);
|
|
110
|
+
|
|
95
111
|
expect(sprinkle.FillInStruct(Type.Boolean() as any)).rejects.toThrow(
|
|
112
|
+
UserCancelledError,
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("throws for unsupported types", async () => {
|
|
117
|
+
expect(sprinkle.FillInStruct(Type.Null() as any)).rejects.toThrow(
|
|
96
118
|
"Unable to fill in struct",
|
|
97
119
|
);
|
|
98
120
|
});
|
|
@@ -543,7 +543,7 @@ describe("createMcpServer", () => {
|
|
|
543
543
|
|
|
544
544
|
// --- Tool input schema ---
|
|
545
545
|
|
|
546
|
-
test("tool input schema is
|
|
546
|
+
test("tool input schema is a Zod raw shape matching the action inputSchema", async () => {
|
|
547
547
|
const sprinkle = makeMockSprinkle([
|
|
548
548
|
{
|
|
549
549
|
name: "typed-action",
|
|
@@ -560,10 +560,12 @@ describe("createMcpServer", () => {
|
|
|
560
560
|
await createMcpServer(sprinkle, "test-server");
|
|
561
561
|
|
|
562
562
|
const schema = registeredTools[0]!.schema;
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
expect(
|
|
566
|
-
|
|
563
|
+
// Schema is now a Zod raw shape (Record<string, ZodType>)
|
|
564
|
+
expect(schema).toHaveProperty("name");
|
|
565
|
+
expect(schema).toHaveProperty("count");
|
|
566
|
+
// Verify they are Zod schema instances (have _zod internal property)
|
|
567
|
+
expect((schema as Record<string, unknown>).name).toHaveProperty("_zod");
|
|
568
|
+
expect((schema as Record<string, unknown>).count).toHaveProperty("_zod");
|
|
567
569
|
});
|
|
568
570
|
|
|
569
571
|
// --- Tool handler: successful execution ---
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { describe, expect, test, mock } from "bun:test";
|
|
2
|
+
import { ActionError } from "../actions/types.js";
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Mocks — mirror the Blaze SDK surface used by native-script.ts
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
let provideScriptCalls: any[] = [];
|
|
9
|
+
const mockCompletedTx = { toCbor: () => "mock-tx-cbor" };
|
|
10
|
+
|
|
11
|
+
mock.module("@blaze-cardano/sdk", () => ({
|
|
12
|
+
Core: {
|
|
13
|
+
Ed25519KeyHashHex: (h: string) => h,
|
|
14
|
+
Slot: (n: number) => n,
|
|
15
|
+
HexBlob: (s: string) => s,
|
|
16
|
+
ScriptPubkey: class {
|
|
17
|
+
hash: string;
|
|
18
|
+
constructor(h: string) {
|
|
19
|
+
this.hash = h;
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
ScriptAll: class {
|
|
23
|
+
scripts: any[];
|
|
24
|
+
constructor(s: any[]) {
|
|
25
|
+
this.scripts = s;
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
ScriptAny: class {
|
|
29
|
+
scripts: any[];
|
|
30
|
+
constructor(s: any[]) {
|
|
31
|
+
this.scripts = s;
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
ScriptNOfK: class {
|
|
35
|
+
scripts: any[];
|
|
36
|
+
n: number;
|
|
37
|
+
constructor(s: any[], n: number) {
|
|
38
|
+
this.scripts = s;
|
|
39
|
+
this.n = n;
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
TimelockExpiry: class {
|
|
43
|
+
slot: number;
|
|
44
|
+
constructor(s: number) {
|
|
45
|
+
this.slot = s;
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
TimelockStart: class {
|
|
49
|
+
slot: number;
|
|
50
|
+
constructor(s: number) {
|
|
51
|
+
this.slot = s;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
NativeScript: {
|
|
55
|
+
fromCbor: (hex: string) => {
|
|
56
|
+
if (hex === "invalid") throw new Error("Invalid CBOR");
|
|
57
|
+
return { __fromCbor: hex };
|
|
58
|
+
},
|
|
59
|
+
newScriptPubkey: (sp: any) => ({ __type: "pubkey", value: sp }),
|
|
60
|
+
newScriptAll: (sa: any) => ({ __type: "all", value: sa }),
|
|
61
|
+
newScriptAny: (sa: any) => ({ __type: "any", value: sa }),
|
|
62
|
+
newScriptNOfK: (sn: any) => ({ __type: "nofk", value: sn }),
|
|
63
|
+
newTimelockExpiry: (te: any) => ({ __type: "timelockExpiry", value: te }),
|
|
64
|
+
newTimelockStart: (ts: any) => ({ __type: "timelockStart", value: ts }),
|
|
65
|
+
},
|
|
66
|
+
Script: {
|
|
67
|
+
newNativeScript: (ns: any) => ({
|
|
68
|
+
__nativeScript: ns,
|
|
69
|
+
hash: () => {
|
|
70
|
+
// Produce a deterministic hex hash from the native script content
|
|
71
|
+
if (ns?.__type === "pubkey") return ns.value.hash;
|
|
72
|
+
if (ns?.__fromCbor) return "cbor" + ns.__fromCbor;
|
|
73
|
+
return "0".repeat(56);
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
const { toNativeScript, completeWithScripts } = await import(
|
|
81
|
+
"../actions/builtin/native-script.js"
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// toNativeScript — CBOR hex input
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
describe("toNativeScript — CBOR hex", () => {
|
|
89
|
+
test("converts valid CBOR hex string", () => {
|
|
90
|
+
const result = toNativeScript("82018200");
|
|
91
|
+
expect(result.__nativeScript).toEqual({ __fromCbor: "82018200" });
|
|
92
|
+
expect(typeof result.hash).toBe("function");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("throws INVALID_NATIVE_SCRIPT for invalid CBOR", () => {
|
|
96
|
+
expect(() => toNativeScript("invalid")).toThrow(ActionError);
|
|
97
|
+
try {
|
|
98
|
+
toNativeScript("invalid");
|
|
99
|
+
} catch (err: any) {
|
|
100
|
+
expect(err.code).toBe("INVALID_NATIVE_SCRIPT");
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// toNativeScript — MultisigScript JSON input
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
describe("toNativeScript — MultisigScript JSON", () => {
|
|
110
|
+
const keyHash = "a".repeat(56);
|
|
111
|
+
|
|
112
|
+
test("converts Signature", () => {
|
|
113
|
+
const result = toNativeScript({ Signature: { key_hash: keyHash } });
|
|
114
|
+
expect(result.__nativeScript.__type).toBe("pubkey");
|
|
115
|
+
expect(result.__nativeScript.value.hash).toBe(keyHash);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("converts AllOf with nested scripts", () => {
|
|
119
|
+
const result = toNativeScript({
|
|
120
|
+
AllOf: {
|
|
121
|
+
scripts: [
|
|
122
|
+
{ Signature: { key_hash: keyHash } },
|
|
123
|
+
{ Signature: { key_hash: "b".repeat(56) } },
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
expect(result.__nativeScript.__type).toBe("all");
|
|
128
|
+
expect(result.__nativeScript.value.scripts).toHaveLength(2);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("converts AnyOf", () => {
|
|
132
|
+
const result = toNativeScript({
|
|
133
|
+
AnyOf: {
|
|
134
|
+
scripts: [{ Signature: { key_hash: keyHash } }],
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
expect(result.__nativeScript.__type).toBe("any");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("converts AtLeast", () => {
|
|
141
|
+
const result = toNativeScript({
|
|
142
|
+
AtLeast: {
|
|
143
|
+
required: 2n,
|
|
144
|
+
scripts: [
|
|
145
|
+
{ Signature: { key_hash: keyHash } },
|
|
146
|
+
{ Signature: { key_hash: "b".repeat(56) } },
|
|
147
|
+
{ Signature: { key_hash: "c".repeat(56) } },
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
expect(result.__nativeScript.__type).toBe("nofk");
|
|
152
|
+
expect(result.__nativeScript.value.n).toBe(2);
|
|
153
|
+
expect(result.__nativeScript.value.scripts).toHaveLength(3);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("converts Before (TimelockExpiry)", () => {
|
|
157
|
+
const result = toNativeScript({ Before: { time: 1000n } });
|
|
158
|
+
expect(result.__nativeScript.__type).toBe("timelockExpiry");
|
|
159
|
+
expect(result.__nativeScript.value.slot).toBe(1000);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("converts After (TimelockStart)", () => {
|
|
163
|
+
const result = toNativeScript({ After: { time: 500n } });
|
|
164
|
+
expect(result.__nativeScript.__type).toBe("timelockStart");
|
|
165
|
+
expect(result.__nativeScript.value.slot).toBe(500);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("converts Script (script hash reference)", () => {
|
|
169
|
+
const result = toNativeScript({ Script: { script_hash: keyHash } });
|
|
170
|
+
expect(result.__nativeScript.__type).toBe("pubkey");
|
|
171
|
+
expect(result.__nativeScript.value.hash).toBe(keyHash);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("handles deeply nested structures", () => {
|
|
175
|
+
const result = toNativeScript({
|
|
176
|
+
AllOf: {
|
|
177
|
+
scripts: [
|
|
178
|
+
{
|
|
179
|
+
AtLeast: {
|
|
180
|
+
required: 1n,
|
|
181
|
+
scripts: [
|
|
182
|
+
{ Signature: { key_hash: keyHash } },
|
|
183
|
+
{
|
|
184
|
+
AnyOf: {
|
|
185
|
+
scripts: [{ Signature: { key_hash: "b".repeat(56) } }],
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
{ After: { time: 100n } },
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
expect(result.__nativeScript.__type).toBe("all");
|
|
196
|
+
expect(result.__nativeScript.value.scripts).toHaveLength(2);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// completeWithScripts
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
describe("completeWithScripts", () => {
|
|
205
|
+
function makeTxBuilder() {
|
|
206
|
+
provideScriptCalls = [];
|
|
207
|
+
const builder: any = {
|
|
208
|
+
provideScript: (script: any) => {
|
|
209
|
+
provideScriptCalls.push(script);
|
|
210
|
+
return builder;
|
|
211
|
+
},
|
|
212
|
+
complete: async () => mockCompletedTx,
|
|
213
|
+
};
|
|
214
|
+
return builder;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
test("completes without scripts when none provided", async () => {
|
|
218
|
+
const builder = makeTxBuilder();
|
|
219
|
+
const tx = await completeWithScripts(builder);
|
|
220
|
+
expect(tx).toBe(mockCompletedTx);
|
|
221
|
+
expect(provideScriptCalls).toHaveLength(0);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("completes without scripts when empty array provided", async () => {
|
|
225
|
+
const builder = makeTxBuilder();
|
|
226
|
+
const tx = await completeWithScripts(builder, []);
|
|
227
|
+
expect(tx).toBe(mockCompletedTx);
|
|
228
|
+
expect(provideScriptCalls).toHaveLength(0);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("attaches scripts before completing", async () => {
|
|
232
|
+
const builder = makeTxBuilder();
|
|
233
|
+
const keyHash = "a".repeat(56);
|
|
234
|
+
const tx = await completeWithScripts(builder, [
|
|
235
|
+
{ Signature: { key_hash: keyHash } },
|
|
236
|
+
"82018200",
|
|
237
|
+
]);
|
|
238
|
+
expect(tx).toBe(mockCompletedTx);
|
|
239
|
+
expect(provideScriptCalls).toHaveLength(2);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("propagates ActionError from invalid script input", async () => {
|
|
243
|
+
const builder = makeTxBuilder();
|
|
244
|
+
await expect(
|
|
245
|
+
completeWithScripts(builder, ["invalid"]),
|
|
246
|
+
).rejects.toMatchObject({ code: "INVALID_NATIVE_SCRIPT" });
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("retries with addressbook script on missing script hash error", async () => {
|
|
250
|
+
const keyHash = "a".repeat(56);
|
|
251
|
+
const expectedScriptHash = keyHash;
|
|
252
|
+
let attempt = 0;
|
|
253
|
+
provideScriptCalls = [];
|
|
254
|
+
const builder: any = {
|
|
255
|
+
provideScript: (script: any) => {
|
|
256
|
+
provideScriptCalls.push(script);
|
|
257
|
+
return builder;
|
|
258
|
+
},
|
|
259
|
+
complete: async () => {
|
|
260
|
+
attempt++;
|
|
261
|
+
if (attempt === 1) {
|
|
262
|
+
throw new Error(
|
|
263
|
+
`complete: Could not resolve script hash ${expectedScriptHash}`,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
return mockCompletedTx;
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const context = {
|
|
271
|
+
sprinkle: {
|
|
272
|
+
addressbook: {
|
|
273
|
+
"my-multisig": { Signature: { key_hash: keyHash } },
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
settings: {},
|
|
277
|
+
} as any;
|
|
278
|
+
|
|
279
|
+
const tx = await completeWithScripts(builder, undefined, context);
|
|
280
|
+
expect(tx).toBe(mockCompletedTx);
|
|
281
|
+
expect(attempt).toBe(2);
|
|
282
|
+
expect(provideScriptCalls).toHaveLength(1);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("throws original error when script hash not in addressbook", async () => {
|
|
286
|
+
const builder: any = {
|
|
287
|
+
provideScript: () => builder,
|
|
288
|
+
complete: async () => {
|
|
289
|
+
throw new Error("complete: Could not resolve script hash deadbeef1234");
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const context = {
|
|
294
|
+
sprinkle: {
|
|
295
|
+
addressbook: {
|
|
296
|
+
"unrelated": { Signature: { key_hash: "b".repeat(56) } },
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
settings: {},
|
|
300
|
+
} as any;
|
|
301
|
+
|
|
302
|
+
await expect(
|
|
303
|
+
completeWithScripts(builder, undefined, context),
|
|
304
|
+
).rejects.toThrow("Could not resolve script hash deadbeef1234");
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("throws original error when no addressbook in context", async () => {
|
|
308
|
+
const builder: any = {
|
|
309
|
+
provideScript: () => builder,
|
|
310
|
+
complete: async () => {
|
|
311
|
+
throw new Error("complete: Could not resolve script hash deadbeef1234");
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
await expect(
|
|
316
|
+
completeWithScripts(builder, undefined, undefined),
|
|
317
|
+
).rejects.toThrow("Could not resolve script hash deadbeef1234");
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test("throws non-script errors without retry", async () => {
|
|
321
|
+
const builder: any = {
|
|
322
|
+
provideScript: () => builder,
|
|
323
|
+
complete: async () => {
|
|
324
|
+
throw new Error("some other error");
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const context = {
|
|
329
|
+
sprinkle: {
|
|
330
|
+
addressbook: {
|
|
331
|
+
"my-sig": { Signature: { key_hash: "a".repeat(56) } },
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
settings: {},
|
|
335
|
+
} as any;
|
|
336
|
+
|
|
337
|
+
await expect(
|
|
338
|
+
completeWithScripts(builder, undefined, context),
|
|
339
|
+
).rejects.toThrow("some other error");
|
|
340
|
+
});
|
|
341
|
+
});
|