@multiagency/sdk 0.3.1 → 0.4.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.
@@ -0,0 +1,193 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Treasury proposals (docs/enforcement.md, "Treasury proposals"): an app proposes swaps now; the owner approves their
4
+ * exact terms later by signing `proposalText` (NEP-413, recipient the platform host); the platform executes them once,
5
+ * through the same plan execution as any saved plan, under the owner's policy. Pure schemas and the text builder,
6
+ * shared by the platform and apps, so an app can show and check the text it relays.
7
+ */
8
+ /** At most this long between proposing and expiry: Sputnik's default `proposal_period`. */
9
+ export declare const MAX_PROPOSAL_MS: number;
10
+ /** Proposals per agent neither claimed nor closed; another is refused. */
11
+ export declare const MAX_OPEN_PROPOSALS = 32;
12
+ export declare const MAX_PROPOSAL_LEGS = 8;
13
+ /** The platform's assets as the owner's text names them. */
14
+ export declare const PROPOSAL_TOKENS: Record<string, {
15
+ symbol: string;
16
+ decimals: number;
17
+ }>;
18
+ export declare const proposalId: z.ZodString;
19
+ /** A swap an app chooses: what it spends, buys, and optionally the least it accepts (raised to the quote's floor). */
20
+ export declare const proposalSwap: z.ZodObject<{
21
+ from: z.ZodEnum<{
22
+ [x: string]: string;
23
+ }>;
24
+ to: z.ZodEnum<{
25
+ [x: string]: string;
26
+ }>;
27
+ amount: z.ZodString;
28
+ minAmountOut: z.ZodOptional<z.ZodString>;
29
+ }, z.core.$strict>;
30
+ /**
31
+ * `legs`: the app's chosen swaps, in order, or `"strategy"` for the legs a rebalance toward the owner's strategy picks.
32
+ * `expiresAt`: at most `MAX_PROPOSAL_MS` ahead; the proposal may be approved and executed until then.
33
+ */
34
+ export declare const proposalCreateInput: z.ZodObject<{
35
+ requestId: z.ZodString;
36
+ legs: z.ZodUnion<readonly [z.ZodArray<z.ZodObject<{
37
+ from: z.ZodEnum<{
38
+ [x: string]: string;
39
+ }>;
40
+ to: z.ZodEnum<{
41
+ [x: string]: string;
42
+ }>;
43
+ amount: z.ZodString;
44
+ minAmountOut: z.ZodOptional<z.ZodString>;
45
+ }, z.core.$strict>>, z.ZodLiteral<"strategy">]>;
46
+ expiresAt: z.ZodISODateTime;
47
+ note: z.ZodOptional<z.ZodString>;
48
+ }, z.core.$strict>;
49
+ /** The owner's NEP-413 signature over the proposal's text, as the app relays it. */
50
+ export declare const proposalSignature: z.ZodObject<{
51
+ message: z.ZodString;
52
+ nonce: z.ZodString;
53
+ publicKey: z.ZodString;
54
+ signature: z.ZodString;
55
+ }, z.core.$strict>;
56
+ export declare const proposalSigned: z.ZodObject<{
57
+ id: z.ZodString;
58
+ signed: z.ZodObject<{
59
+ message: z.ZodString;
60
+ nonce: z.ZodString;
61
+ publicKey: z.ZodString;
62
+ signature: z.ZodString;
63
+ }, z.core.$strict>;
64
+ }, z.core.$strict>;
65
+ export declare const proposalRef: z.ZodObject<{
66
+ id: z.ZodString;
67
+ }, z.core.$strict>;
68
+ export declare const ProposalState: z.ZodEnum<{
69
+ approved: "approved";
70
+ executed: "executed";
71
+ executing: "executing";
72
+ expired: "expired";
73
+ invalidated: "invalidated";
74
+ proposed: "proposed";
75
+ rejected: "rejected";
76
+ stopped: "stopped";
77
+ unknown: "unknown";
78
+ withdrawn: "withdrawn";
79
+ }>;
80
+ export type ProposalState = z.infer<typeof ProposalState>;
81
+ /** A proposal as the platform shows it: its signed terms, its state, and what the journal holds for its execution. */
82
+ export declare const proposalView: z.ZodObject<{
83
+ id: z.ZodString;
84
+ requestId: z.ZodString;
85
+ agentId: z.ZodString;
86
+ deployment: z.ZodString;
87
+ organization: z.ZodString;
88
+ source: z.ZodEnum<{
89
+ external: "external";
90
+ rebalance: "rebalance";
91
+ }>;
92
+ legs: z.ZodArray<z.ZodObject<{
93
+ from: z.ZodString;
94
+ to: z.ZodString;
95
+ amount: z.ZodString;
96
+ minAmountOut: z.ZodString;
97
+ }, z.core.$loose>>;
98
+ quoted: z.ZodArray<z.ZodObject<{
99
+ amountOut: z.ZodString;
100
+ costBps: z.ZodNumber;
101
+ usdIn: z.ZodNumber;
102
+ }, z.core.$loose>>;
103
+ policyRevision: z.ZodNullable<z.ZodNumber>;
104
+ policyDigest: z.ZodNullable<z.ZodString>;
105
+ strategyDigest: z.ZodString;
106
+ createdAt: z.ZodString;
107
+ expiresAt: z.ZodString;
108
+ note: z.ZodNullable<z.ZodString>;
109
+ sha256: z.ZodString;
110
+ state: z.ZodEnum<{
111
+ approved: "approved";
112
+ executed: "executed";
113
+ executing: "executing";
114
+ expired: "expired";
115
+ invalidated: "invalidated";
116
+ proposed: "proposed";
117
+ rejected: "rejected";
118
+ stopped: "stopped";
119
+ unknown: "unknown";
120
+ withdrawn: "withdrawn";
121
+ }>;
122
+ reason: z.ZodNullable<z.ZodString>;
123
+ approvedAt: z.ZodNullable<z.ZodString>;
124
+ planId: z.ZodNullable<z.ZodString>;
125
+ operations: z.ZodArray<z.ZodObject<{
126
+ method: z.ZodOptional<z.ZodString>;
127
+ status: z.ZodNullable<z.ZodString>;
128
+ correlationId: z.ZodNullable<z.ZodString>;
129
+ refused: z.ZodNullable<z.ZodString>;
130
+ }, z.core.$loose>>;
131
+ }, z.core.$loose>;
132
+ export type ProposalView = z.infer<typeof proposalView>;
133
+ export declare const proposalList: z.ZodObject<{
134
+ proposals: z.ZodArray<z.ZodObject<{
135
+ id: z.ZodString;
136
+ requestId: z.ZodString;
137
+ agentId: z.ZodString;
138
+ deployment: z.ZodString;
139
+ organization: z.ZodString;
140
+ source: z.ZodEnum<{
141
+ external: "external";
142
+ rebalance: "rebalance";
143
+ }>;
144
+ legs: z.ZodArray<z.ZodObject<{
145
+ from: z.ZodString;
146
+ to: z.ZodString;
147
+ amount: z.ZodString;
148
+ minAmountOut: z.ZodString;
149
+ }, z.core.$loose>>;
150
+ quoted: z.ZodArray<z.ZodObject<{
151
+ amountOut: z.ZodString;
152
+ costBps: z.ZodNumber;
153
+ usdIn: z.ZodNumber;
154
+ }, z.core.$loose>>;
155
+ policyRevision: z.ZodNullable<z.ZodNumber>;
156
+ policyDigest: z.ZodNullable<z.ZodString>;
157
+ strategyDigest: z.ZodString;
158
+ createdAt: z.ZodString;
159
+ expiresAt: z.ZodString;
160
+ note: z.ZodNullable<z.ZodString>;
161
+ sha256: z.ZodString;
162
+ state: z.ZodEnum<{
163
+ approved: "approved";
164
+ executed: "executed";
165
+ executing: "executing";
166
+ expired: "expired";
167
+ invalidated: "invalidated";
168
+ proposed: "proposed";
169
+ rejected: "rejected";
170
+ stopped: "stopped";
171
+ unknown: "unknown";
172
+ withdrawn: "withdrawn";
173
+ }>;
174
+ reason: z.ZodNullable<z.ZodString>;
175
+ approvedAt: z.ZodNullable<z.ZodString>;
176
+ planId: z.ZodNullable<z.ZodString>;
177
+ operations: z.ZodArray<z.ZodObject<{
178
+ method: z.ZodOptional<z.ZodString>;
179
+ status: z.ZodNullable<z.ZodString>;
180
+ correlationId: z.ZodNullable<z.ZodString>;
181
+ refused: z.ZodNullable<z.ZodString>;
182
+ }, z.core.$loose>>;
183
+ }, z.core.$loose>>;
184
+ }, z.core.$loose>;
185
+ /** A raw amount in whole units, exact, without trailing zeros. */
186
+ export declare function tokenAmount(raw: string, decimals: number): string;
187
+ type Terms = Pick<ProposalView, "id" | "agentId" | "deployment" | "organization" | "legs" | "policyRevision" | "policyDigest" | "strategyDigest" | "expiresAt" | "sha256">;
188
+ /**
189
+ * The exact text the owner signs to approve (or reject) a proposal: the platform accepts only these bytes. The host is
190
+ * the NEP-413 recipient, so it isn't repeated here.
191
+ */
192
+ export declare function proposalText(proposal: Terms, verdict: "approve" | "reject"): string;
193
+ export {};
@@ -0,0 +1,102 @@
1
+ import { z } from "zod";
2
+ import { ASSETS } from "./assets.js";
3
+ import { payoutRequestId } from "./inputs.js";
4
+ /**
5
+ * Treasury proposals (docs/enforcement.md, "Treasury proposals"): an app proposes swaps now; the owner approves their
6
+ * exact terms later by signing `proposalText` (NEP-413, recipient the platform host); the platform executes them once,
7
+ * through the same plan execution as any saved plan, under the owner's policy. Pure schemas and the text builder,
8
+ * shared by the platform and apps, so an app can show and check the text it relays.
9
+ */
10
+ /** At most this long between proposing and expiry: Sputnik's default `proposal_period`. */
11
+ export const MAX_PROPOSAL_MS = 7 * 86_400_000;
12
+ /** Proposals per agent neither claimed nor closed; another is refused. */
13
+ export const MAX_OPEN_PROPOSALS = 32;
14
+ export const MAX_PROPOSAL_LEGS = 8;
15
+ /** The platform's assets as the owner's text names them. */
16
+ export const PROPOSAL_TOKENS = {
17
+ [ASSETS.USDC]: { symbol: "USDC", decimals: 6 },
18
+ [ASSETS.wNEAR]: { symbol: "wNEAR", decimals: 24 },
19
+ [ASSETS.ETH]: { symbol: "ETH", decimals: 18 },
20
+ };
21
+ const asset = z.enum(Object.values(ASSETS));
22
+ const amount = z.string().regex(/^[0-9]{1,40}$/).refine(value => BigInt(value) > 0n, "Amount must be positive");
23
+ export const proposalId = z.string().regex(/^[a-f0-9]{32}$/);
24
+ /** A swap an app chooses: what it spends, buys, and optionally the least it accepts (raised to the quote's floor). */
25
+ export const proposalSwap = z.strictObject({ from: asset, to: asset, amount, minAmountOut: amount.optional() })
26
+ .refine(swap => swap.from !== swap.to, "Swap assets must differ");
27
+ /**
28
+ * `legs`: the app's chosen swaps, in order, or `"strategy"` for the legs a rebalance toward the owner's strategy picks.
29
+ * `expiresAt`: at most `MAX_PROPOSAL_MS` ahead; the proposal may be approved and executed until then.
30
+ */
31
+ export const proposalCreateInput = z.strictObject({
32
+ requestId: payoutRequestId,
33
+ legs: z.union([z.array(proposalSwap).min(1).max(MAX_PROPOSAL_LEGS), z.literal("strategy")]),
34
+ expiresAt: z.iso.datetime(),
35
+ note: z.string().max(280).optional(),
36
+ });
37
+ /** The owner's NEP-413 signature over the proposal's text, as the app relays it. */
38
+ export const proposalSignature = z.strictObject({
39
+ message: z.string().min(1).max(4_096),
40
+ nonce: z.string().min(1).max(64),
41
+ publicKey: z.string().regex(/^ed25519:[1-9A-HJ-NP-Za-km-z]{32,64}$/),
42
+ signature: z.string().min(1).max(200),
43
+ });
44
+ export const proposalSigned = z.strictObject({ id: proposalId, signed: proposalSignature });
45
+ export const proposalRef = z.strictObject({ id: proposalId });
46
+ export const ProposalState = z.enum(["proposed", "approved", "executing", "executed", "stopped", "unknown", "rejected", "withdrawn", "expired", "invalidated"]);
47
+ const leg = z.looseObject({ from: z.string(), to: z.string(), amount: z.string(), minAmountOut: z.string() });
48
+ /** A proposal as the platform shows it: its signed terms, its state, and what the journal holds for its execution. */
49
+ export const proposalView = z.looseObject({
50
+ id: z.string(),
51
+ requestId: z.string(),
52
+ agentId: z.string(),
53
+ deployment: z.string(),
54
+ organization: z.string(),
55
+ source: z.enum(["external", "rebalance"]),
56
+ /** The terms the owner signs: each leg's floor is the least it may return. */
57
+ legs: z.array(leg),
58
+ /** Quotes when proposed: shown, never relied on. */
59
+ quoted: z.array(z.looseObject({ amountOut: z.string(), costBps: z.number(), usdIn: z.number() })),
60
+ policyRevision: z.number().nullable(),
61
+ policyDigest: z.string().nullable(),
62
+ strategyDigest: z.string(),
63
+ createdAt: z.string(),
64
+ expiresAt: z.string(),
65
+ note: z.string().nullable(),
66
+ /** sha256 of the canonical record: named in the owner's text. */
67
+ sha256: z.string(),
68
+ state: ProposalState,
69
+ /** Why it was closed (rejected, withdrawn, expired, invalidated), or null. */
70
+ reason: z.string().nullable(),
71
+ approvedAt: z.string().nullable(),
72
+ /** The plan its execution ran, once claimed. */
73
+ planId: z.string().nullable(),
74
+ /** Its plan's operations in the journal, oldest first. */
75
+ operations: z.array(z.looseObject({ method: z.string().optional(), status: z.string().nullable(), correlationId: z.string().nullable(), refused: z.string().nullable() })),
76
+ });
77
+ export const proposalList = z.looseObject({ proposals: z.array(proposalView) });
78
+ /** A raw amount in whole units, exact, without trailing zeros. */
79
+ export function tokenAmount(raw, decimals) {
80
+ const digits = raw.padStart(decimals + 1, "0");
81
+ const whole = digits.slice(0, digits.length - decimals);
82
+ const fraction = digits.slice(digits.length - decimals).replace(/0+$/, "");
83
+ return fraction ? `${whole}.${fraction}` : whole;
84
+ }
85
+ const shown = (asset, raw) => {
86
+ const token = PROPOSAL_TOKENS[asset];
87
+ return token ? `${tokenAmount(raw, token.decimals)} ${token.symbol} (${raw})` : `${raw} of ${asset}`;
88
+ };
89
+ /**
90
+ * The exact text the owner signs to approve (or reject) a proposal: the platform accepts only these bytes. The host is
91
+ * the NEP-413 recipient, so it isn't repeated here.
92
+ */
93
+ export function proposalText(proposal, verdict) {
94
+ return [
95
+ `${verdict === "approve" ? "Approve" : "Reject"} Treasury proposal ${proposal.id}`,
96
+ `Agent ${proposal.agentId}`,
97
+ `Proposed by ${proposal.deployment} for ${proposal.organization}`,
98
+ ...proposal.legs.map((leg, i) => `${i + 1}. Swap ${shown(leg.from, leg.amount)} for at least ${shown(leg.to, leg.minAmountOut)}`),
99
+ `Under policy revision ${proposal.policyRevision ?? "none"} (${proposal.policyDigest ?? "none"}), strategy ${proposal.strategyDigest}`,
100
+ `Valid until ${proposal.expiresAt}. Proposal sha256 ${proposal.sha256}.`,
101
+ ].join("\n");
102
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiagency/sdk",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "MultiAgency SDK: payee, compensation and owner-app schemas, typed payee and owner clients, and the shared release asset server.",
package/provenance.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiagency/sdk",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "wire": {
5
5
  "payeeManifestVersion": 1,
6
6
  "capabilities": [
@@ -12,50 +12,52 @@
12
12
  "supplier-agent-connections-v1"
13
13
  ]
14
14
  },
15
- "sourceHead": "766b3f3a25b6ae90ac4d4f1d8bb65ef5597f6f66",
15
+ "sourceHead": "1a7fe9818e2a83c548e1ba8d587f10acac37716a",
16
16
  "dirtyPaths": [],
17
17
  "snapshot": {
18
- "runDigest": "106ed6be37bc8f6b133006b49b74264cb9519807f20c3b70ad17cb2cbbf12136",
18
+ "runDigest": "c8dafe626d05552a1d101d8119f41441f67bb4c25dec60331dc98e2e62589de8",
19
19
  "overlayDiffSha256": null
20
20
  },
21
21
  "sources": {
22
22
  "src/compensation-contract.ts": "3443b0690db7921d6dec04375d9281a2e1df3f4ffbe48c2b821e89af177f14c1",
23
23
  "src/contract/agent-connection.ts": "cfe3ec3b3cdc4da2773b20de008e1a004f2862499d0df5831923637f8b39a746",
24
- "src/contract/app-access.ts": "f5c792723dcaf870bf000265de9816b819035883cf79227e7de0cccc25681363",
25
- "src/contract/app-client.ts": "d1ae48532320e8bfa5337f68b915f699566eea77d59d836ccd55e00676ea82b3",
26
- "src/contract/app-consent.ts": "671d5904b448af50bc06aee19bdefa84359494b6cfe38cc0725042703ca6418a",
24
+ "src/contract/app-access.ts": "9385fc9ef5fbb26734b70ed9ddba916113815ebbaf6f2fb8e6fbc3809bb78f59",
25
+ "src/contract/app-client.ts": "a505db256012feb3659c02f001444b4bddc90fc7a091200c36b32c88d63832d5",
26
+ "src/contract/app-consent.ts": "6649a7d27f19ab84f23433deeefb877b4e386934d64f3ac2040d04b9d3309d3d",
27
27
  "src/contract/assets.ts": "b6cc3a160e488bfecf273b50e747e4640c8f4ba4d000bd6898db4f805ae9f310",
28
28
  "src/contract/client.ts": "1eabd53e3119078d3c01a18850c180fab45385eec6ad210b92aa7fa158a03996",
29
29
  "src/contract/gateway.ts": "e66bb199ab504b007e5a04617e0cf53b5b3d9641328a799c7df33f8901fa473b",
30
- "src/contract/index.ts": "7c2daca088c9841b4e5e986825cd0501ef9f271fb4b9bc2804e83c93b5f938c5",
30
+ "src/contract/index.ts": "e1b0f7dc2618543de5e98c6a94c9ed979c43a17e625445daa395d4be42a0396c",
31
31
  "src/contract/inputs.ts": "a7b16ff1ff05a65bc2bd6b8bdaa978296d912e45098d105e0a578a8174a029d3",
32
32
  "src/contract/intents-account.ts": "8e88f6f41de28fdb48fa49243ccb4e0035dc7823586931658922927cf92f8fea",
33
33
  "src/contract/orpc-client.ts": "94cbd34bc64bd06152f2dbf17d2ff6b270d1d3b703e3bb7019483bcce93eeb04",
34
- "src/contract/owner-capabilities.ts": "1cee82270d4c9bf2984e71297515da0763b2e6b3c2d8052c96efed89813fb326",
34
+ "src/contract/owner-capabilities.ts": "31aadb5a44b776ea34dc5f7b5a4306a765c6deec0b4a802337fe6fefd4373f69",
35
35
  "src/contract/owner-client.ts": "468a2e06784e95242087ecf45af805092de28af4e59ad0d445b29999b145b9f6",
36
36
  "src/contract/owner-contract.ts": "339f63d7b585d652f1fd2175ebc94a5d8ef5e31a1f4730e554c2b0b89b24ab11",
37
- "src/contract/owner-inputs.ts": "4328b6734a955fe524cf04852a0e4d9c20cf3c618416c5e91d857c98ffa1b837",
38
- "src/contract/owner-outputs.ts": "59e9d8a712c4e6dc60075edaef86fee3f50a6b6c99273cd992431efbdefee75a",
39
- "src/contract/package-version.ts": "a1495bd2343727a79d50e56af598990d3efce9e3253538b0d8b09feddc9e0736",
37
+ "src/contract/owner-inputs.ts": "e8dab47bbbffcf1dbe268fae4e0ffa703b8c46bb79b2cc5659bb1cc6ecd9c2e2",
38
+ "src/contract/owner-outputs.ts": "1e88bf152ddfdf2f7ee74ae53d3a05dafc3f994b01a6e22b720856dfa616f4aa",
39
+ "src/contract/package-version.ts": "e97007b462544c8e245b29c002930918e2f9a357be0b3346fc02a68ecdb8a1a6",
40
40
  "src/contract/payee-entry.ts": "9557ec0f53dd4ab3411d3a81ccd82667f83766386332fb84e93a50caa8287bef",
41
41
  "src/contract/payee.ts": "ec6e0c628be8592b997a5ceccfcd1ca18f891bb969731214fd4205b7e3f07a66",
42
- "src/contract/permissions.ts": "fd0280c696cc070390360f20ff941f4ef2cf8143d81d55856103de43a935c93d",
42
+ "src/contract/permissions.ts": "03171e6e382cf70f264760c6d9734e06d3bfd371c3bc12238335f763cd0b4242",
43
43
  "src/contract/proposals.ts": "8a9abb34f1ee09fcfb1de2cc2ced2773d3fa189f20faa0bc1a31ed9fae82beef",
44
44
  "src/contract/run-log-schema.ts": "2ef522a589f6a70cff58e4ded63ddf41a6d3998b2fde96a1bf9176b363da3847",
45
45
  "src/contract/run-progress-schema.ts": "5eead5bde373e8f058f7f42cf588c4a71e1960d5d7097e0381384c03f6a4bbfb",
46
+ "src/contract/treasury-proposals.ts": "8b0e9cfca940e706689a3da92fd781f0e8279753bbd8f866452a8ad249d6cae3",
46
47
  "src/payee-contract.ts": "800260c35c1edbc6e8361f9213fe5f4ccdc22d8d7b1510db08a590f83a152351",
47
48
  "src/contract/release-server.mjs": "a2177a0fc28bc19edefae2e5d5e8e11dffa3c4f099f604cee82457cb8eca51c7",
48
49
  "src/contract/release-server.d.mts": "05fb535f8d1f3ff4aac62395b717144aecf1f4b0c5b388ca83d8b4cb677d2764",
49
- "docs/sdk-changes.md": "969bd9a4fcc3774660dea0717798e5e5e300f54d40dc88b104fa75122a0ce567",
50
+ "docs/sdk-changes.md": "5cd4a1fab6ab8ae15c27bd99a587bbcae9ee393d00214e38dea7321d4189a154",
50
51
  "src/contract/README.md": "e80e5cbbab40fef5d4a8deadb90b02133b2998df7fc17ff031910e818e6909a9",
51
52
  "LICENSE": "0f90c37abe95e72f99ddb9b9dfc5e54b663d8bfed929d4c261669059443ba822",
52
- "src/contract/skills/app-server/SKILL.md": "0bef6c331bc86eeae588c0c54ce2932c25cb5d2d07107912c728213bcf943071",
53
- "src/contract/skills/getting-started/SKILL.md": "c8b2bce01aa946c8a6c3540266208e93e3ae08ddb7cf5deff7a6ccae2b92f622",
54
- "src/contract/skills/owner-workflows/SKILL.md": "e6b88d764aa8b6cafd1a26ad7a21cbbd842625a9b145c96dd922a6ef6ae080ed",
55
- "src/contract/skills/payee-workflows/SKILL.md": "efa1bd5e07d4b06405d54c5fc424d9357ab336883bdd55e66bbd7094bb01441a"
53
+ "src/contract/skills/app-server/SKILL.md": "fe120aaec480521947ea4cef71ea50e810db7619aa8a8b2ca7cf04e598de63b1",
54
+ "src/contract/skills/getting-started/SKILL.md": "f46e923f1c5b69746f99f3fc7809248c2c7b6a036d45e53fbaa27040d18d05c1",
55
+ "src/contract/skills/owner-workflows/SKILL.md": "f4d8585663430808e89c9f527b010c76519ad21592e7281f443d4c7d675cd34d",
56
+ "src/contract/skills/payee-workflows/SKILL.md": "6839aef4532fcfd6c5a4fdf1bb34faff1477a879c06f0ede2bd514961aa24680",
57
+ "src/contract/skills/treasury-app/SKILL.md": "d53226515032de733f75a34c91f5287241df99be67d2ebd248d35ed69258d7ab"
56
58
  },
57
59
  "sourcesDirty": [],
58
60
  "node": "v24.22.1",
59
61
  "typescript": "Version 7.0.2",
60
- "builtAt": "2026-10-31T04:39:35.856Z"
62
+ "builtAt": "2026-11-03T01:24:16.514Z"
61
63
  }
@@ -10,7 +10,7 @@ metadata:
10
10
  purpose: 'An app server''s access to agents: credential, consents, relayed owner actions, and payout requests by requestId with recovery by state.'
11
11
  type: core
12
12
  library: '@multiagency/sdk'
13
- library_version: '0.3.1'
13
+ library_version: '0.4.0'
14
14
  requires:
15
15
  - getting-started
16
16
  - owner-workflows
@@ -25,8 +25,8 @@ sources:
25
25
 
26
26
  Read getting-started and owner-workflows first. `appClient` runs on your server with the deployment's service credential
27
27
  (`tap_…`), issued by the platform operator: a server secret, never sent to a browser. A call reaches an agent only
28
- under a live owner consent for that Organization and agent, and only within the consent's scopes: `read`, `owner`, and
29
- `payout`. No production MultiAgency host serves the `/apps/v1` routes yet; the operator runs them on a staging host for
28
+ under a live owner consent for that Organization and agent, and only within the consent's scopes: `read`, `owner`,
29
+ `payout`, and for Treasury `treasury` and `treasury-execute` (see treasury-app). No production MultiAgency host serves the `/apps/v1` routes yet; the operator runs them on a staging host for
30
30
  its own tests. Until a production host does, build against the SDK repository's local stack
31
31
  (`pnpm test:e2e:stack --app <deployment>`). Use only `/apps/v1` and the confirm page on the platform's host: it serves
32
32
  nothing else (no sign-in or owner page), and setup there is for Payables agents only.
@@ -9,7 +9,7 @@ metadata:
9
9
  purpose: 'Orients an app or agent author: the two clients, the four identities and their server-side limits, recovery kinds, and the supported surface of this package version.'
10
10
  type: lifecycle
11
11
  library: '@multiagency/sdk'
12
- library_version: '0.3.1'
12
+ library_version: '0.4.0'
13
13
  sources:
14
14
  - 'MultiAgency/platform:src/contract/index.ts'
15
15
  - 'MultiAgency/platform:src/contract/payee-entry.ts'
@@ -62,11 +62,12 @@ it may retry.
62
62
 
63
63
  ## What this package version supports
64
64
 
65
- `@multiagency/sdk` 0.3.1, entries:
65
+ `@multiagency/sdk` 0.4.0, entries:
66
66
  - `@multiagency/sdk`: `ownerClient` with `OwnerResult`/`OwnerFailure`, `appClient` with `AppRouteResult` and the
67
67
  consent and setup texts (`consentMessage`, `setupMessage`), `ownerOutputs` and `OwnerInputs`/`OwnerOutput`
68
68
  per procedure, `ownerCapabilities` and `mayCall` (display only), gateway discovery (`gatewayDiscovery`,
69
- `gatewayDenialScope`), schemas for runs (`RecordingSchema`) and proposals, `ASSETS` and account helpers. It also
69
+ `gatewayDenialScope`), schemas for runs (`RecordingSchema`), DAO proposals and Treasury proposals (`proposalView`,
70
+ `proposalText`; see treasury-app), `ASSETS` and account helpers. It also
70
71
  re-exports the payee entry. Peer: zod ^4.6.5. It bundles `@orpc/client` 2.0.0-beta.47 and loads no other oRPC
71
72
  package, so it runs beside a host that shares its own oRPC. It depends on `@ag-ui/core` 1.0.3.
72
73
  - `@multiagency/sdk/contract`: `ownerContract`, `ownerErrors` and `OwnerContract`, for an app implementing a server
@@ -9,7 +9,7 @@ metadata:
9
9
  purpose: 'Owner reads, owner-signed actions and money streams with ownerClient, with recovery by result kind and retained identities.'
10
10
  type: core
11
11
  library: '@multiagency/sdk'
12
- library_version: '0.3.1'
12
+ library_version: '0.4.0'
13
13
  requires:
14
14
  - getting-started
15
15
  sources:
@@ -120,6 +120,8 @@ export function machineClient(origin: string, agent: string, token: string) {
120
120
  Result: `await machineClient(origin, "treasury", token).call("overview")` answers like the browser client. A procedure
121
121
  outside the credential's scopes, or an expired or revoked credential, answers `forbidden` or `session`. Results, streams
122
122
  and recovery are the same as above: a lost `executePlan` or `sweep` is `unknown`, and nothing is sent again.
123
+ `executePlan` runs only while the owner's live grant binds the saved strategy (`treasury:<suffix>`, signed once per
124
+ saved strategy); otherwise it answers one `rejected` event naming the grant, and nothing is claimed.
123
125
 
124
126
  ## Recover an action that returned no identity
125
127
 
@@ -9,7 +9,7 @@ metadata:
9
9
  purpose: 'Payee reports and their payment status with payeeClient, from a browser session or an agent connection, with recovery by kept identities.'
10
10
  type: core
11
11
  library: '@multiagency/sdk'
12
- library_version: '0.3.1'
12
+ library_version: '0.4.0'
13
13
  requires:
14
14
  - getting-started
15
15
  sources:
@@ -0,0 +1,117 @@
1
+ ---
2
+ name: treasury-app
3
+ description: >
4
+ Use when an app's server proposes Treasury swaps for an Organization's agent through @multiagency/sdk and has them
5
+ executed once the owner approves later: proposals by requestId, the exact text the owner signs, one execution per
6
+ proposal with recovery by state, and the two Treasury consent scopes. Server side only. Read app-server first.
7
+ metadata:
8
+ purpose: 'Treasury proposals through an app: propose by requestId, the owner signs the exact terms, execute once, recover by state.'
9
+ type: core
10
+ library: '@multiagency/sdk'
11
+ library_version: '0.4.0'
12
+ requires:
13
+ - getting-started
14
+ - app-server
15
+ sources:
16
+ - 'MultiAgency/platform:src/contract/treasury-proposals.ts'
17
+ - 'MultiAgency/platform:src/contract/app-access.ts'
18
+ - 'MultiAgency/platform:src/proposals/service.ts'
19
+ - 'MultiAgency/platform:docs/enforcement.md'
20
+ ---
21
+
22
+ # Treasury proposals from an app's server
23
+
24
+ Read app-server first: the same credential, consent and result kinds apply. An agent runtime (your CLI, an assistant)
25
+ calls your server, never the platform. A proposal is a list of swaps your app proposes now; the agent's owner approves
26
+ its exact terms later (hours or days), by signing a text in their own wallet; then your server asks for its execution,
27
+ which runs once, under the owner's signed policy. No production host serves this yet; build against the SDK
28
+ repository's local stack.
29
+
30
+ Two consent scopes, each named in the text the owner signs:
31
+ - `treasury`: Treasury reads (`strategy/get`, `autopilot/get`, `autopilot/evidence`, `autopilot/log`, `treasury/get`),
32
+ strategy settings (`strategy/save`, which act only once the owner signs a grant binding them), `autopilot/enable` and
33
+ `autopilot/stop`, `sweep`, and every `proposals/*` procedure.
34
+ - `treasury-execute`, only beside `treasury`: `rebalance`, `externalPlan` and `executePlan`, five-minute plans executed
35
+ within the policy without a signature per swap, and only while the owner's live grant binds the strategy they were
36
+ planned under. `sweep`, `rebalance` and `executePlan` are streams: read them with `app.agent(...).stream(path, input)`.
37
+
38
+ ## Propose, once per requestId
39
+
40
+ Store the `requestId` (1–64 of `[A-Za-z0-9_-]`) and the request before calling. `legs` is your chosen swaps in raw units
41
+ (at most 8; a `minAmountOut` you require is kept if it is above the quote's floor), or `"strategy"` for the legs a
42
+ rebalance toward the owner's strategy picks now. `expiresAt` is at most 7 days ahead.
43
+
44
+ ```ts
45
+ import { appClient, proposalText, type ProposalView } from "@multiagency/sdk";
46
+
47
+ type Request = { requestId: string; legs: { from: string; to: string; amount: string; minAmountOut?: string }[] | "strategy"; expiresAt: string };
48
+ type SignNep413 = (message: string, recipient: string, nonce: Uint8Array) => Promise<{ publicKey: string; signature: string }>;
49
+
50
+ export async function propose(app: ReturnType<typeof appClient>, organization: string, agent: string, request: Request) {
51
+ const calls = app.agent(organization, agent);
52
+ const made = await calls.call("proposals/create", request);
53
+ if (made.ok || (made.kind !== "unknown" && made.kind !== "unavailable")) return made;
54
+ // The answer was lost: the same requestId and request answer the stored proposal and store nothing new.
55
+ return calls.call("proposals/create", request);
56
+ }
57
+
58
+ export async function approve(app: ReturnType<typeof appClient>, organization: string, agent: string, host: string,
59
+ proposal: ProposalView, sign: SignNep413) {
60
+ // The platform accepts only this exact text; show the owner this text, which their wallet shows as it signs.
61
+ const message = proposalText(proposal, "approve");
62
+ const nonce = crypto.getRandomValues(new Uint8Array(32));
63
+ const { publicKey, signature } = await sign(message, host, nonce);
64
+ return app.agent(organization, agent).call("proposals/approve", { id: proposal.id,
65
+ signed: { message, nonce: btoa(String.fromCharCode(...nonce)), publicKey, signature } });
66
+ }
67
+ ```
68
+
69
+ Result: `{ ok: true, value: proposal }` with `state` `proposed`, then `approved`. The text names each swap with its
70
+ floor (the least it may return), the policy revision and strategy it was proposed under, the expiry and the
71
+ proposal's sha256; the host is the NEP-413 recipient. Approving records the signature and sends nothing. Refusals carry
72
+ `code`: `request_conflict` (another request under that `requestId`), `expiry_invalid`, `too_many_proposals`,
73
+ `not_proposable` (a provider warning, an output the policy forbids, a floor above the quote, cost over the strategy's
74
+ limit, or a balance short: the message says which), `signature_invalid`, `invalidated` (the policy, strategy or agent
75
+ changed since: propose again), `not_open`, `expired`, `withdrawn` (made under a consent that has since ended). The owner
76
+ rejects with `proposalText(proposal, "reject")` and `proposals/reject`; your app withdraws with `proposals/withdraw`.
77
+
78
+ ## Execute once, and follow it
79
+
80
+ ```ts
81
+ import { appClient } from "@multiagency/sdk";
82
+
83
+ export async function execute(app: ReturnType<typeof appClient>, organization: string, agent: string, id: string) {
84
+ const calls = app.agent(organization, agent);
85
+ const ran = await calls.call("proposals/execute", { id });
86
+ if (ran.ok || (ran.kind !== "unknown" && ran.kind !== "unavailable")) return ran;
87
+ // The answer was lost: read the proposal. Never propose the same swaps again because of a lost answer.
88
+ return calls.call("proposals/get", { id });
89
+ }
90
+ ```
91
+
92
+ Result: the proposal, `state`:
93
+ - `executed`: every swap succeeded; `operations` lists them.
94
+ - `stopped`: claimed, then a swap didn't succeed or was refused, or nothing was sent. It never runs again: propose the
95
+ rest anew.
96
+ - `unknown`: a swap's outcome isn't proven. It blocks the agent's sends until resolved and is never resent: read later.
97
+ - `executing`: running now; read later.
98
+ - `invalidated`, `expired`, `rejected`, `withdrawn`: it ended without running.
99
+
100
+ A refusal `not_executable` means the attempt was refused before anything was claimed (price below a signed floor, a
101
+ provider warning, a short balance, no live treasury grant, the policy not in force): nothing was sent, the proposal
102
+ stays `approved`, and asking again later is safe. `not_approved`: the owner hasn't approved it. Execution re-quotes
103
+ every swap and sends each with a floor at least the signed one.
104
+
105
+ ## Common mistakes
106
+
107
+ - **Building your own approval text** (CRITICAL): sign only `proposalText(proposal, "approve")` from the proposal the
108
+ platform returned; any other text is refused, and a summary of yours is not what the owner signs.
109
+ - **A new requestId after `unknown`** (CRITICAL): the swaps may have run. Read `proposals/get`.
110
+ - **Treating `stopped` as nothing sent** (HIGH): read `operations`; a swap may have succeeded before it stopped.
111
+ - **Asking for `treasury-execute` when proposals suffice** (HIGH): it lets your app swap within the policy without a
112
+ signature per swap. Ask for it only when the owner wants unattended execution.
113
+
114
+ ## Completion
115
+
116
+ Every proposal you created has a read state; none reads `unknown` or `executing` without a later `proposals/get`.
117
+ Report anything you could not read; do not act again.