@occa/sdk 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.
- package/CHANGELOG.md +238 -0
- package/LICENSE +21 -0
- package/README.md +102 -0
- package/dist/chunk-N7LNBSDD.js +658 -0
- package/dist/chunk-N7LNBSDD.js.map +1 -0
- package/dist/chunk-X6FBCGHU.js +97 -0
- package/dist/chunk-X6FBCGHU.js.map +1 -0
- package/dist/chunk-YCSBYRSH.js +75 -0
- package/dist/chunk-YCSBYRSH.js.map +1 -0
- package/dist/constants.cjs +121 -0
- package/dist/constants.cjs.map +1 -0
- package/dist/constants.d.cts +53 -0
- package/dist/constants.d.ts +53 -0
- package/dist/constants.js +51 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.cjs +870 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +117 -0
- package/dist/index.js.map +1 -0
- package/dist/instructions.cjs +785 -0
- package/dist/instructions.cjs.map +1 -0
- package/dist/instructions.d.cts +442 -0
- package/dist/instructions.d.ts +442 -0
- package/dist/instructions.js +51 -0
- package/dist/instructions.js.map +1 -0
- package/dist/pda.cjs +146 -0
- package/dist/pda.cjs.map +1 -0
- package/dist/pda.d.cts +116 -0
- package/dist/pda.d.ts +116 -0
- package/dist/pda.js +24 -0
- package/dist/pda.js.map +1 -0
- package/package.json +80 -0
- package/src/constants.ts +93 -0
- package/src/idl/registry.json +1415 -0
- package/src/idl/treasury.json +1627 -0
- package/src/index.ts +18 -0
- package/src/instructions.ts +1171 -0
- package/src/pda.ts +199 -0
|
@@ -0,0 +1,1171 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PublicKey,
|
|
3
|
+
SystemProgram,
|
|
4
|
+
TransactionInstruction,
|
|
5
|
+
} from "@solana/web3.js";
|
|
6
|
+
import {
|
|
7
|
+
OPERATIONS_KIND,
|
|
8
|
+
REGISTRY_PROGRAM_ID,
|
|
9
|
+
SOL_PSEUDO_MINT,
|
|
10
|
+
TREASURY_PROGRAM_ID,
|
|
11
|
+
type OperationsKind,
|
|
12
|
+
} from "./constants";
|
|
13
|
+
import {
|
|
14
|
+
deriveAgentIdentityPda,
|
|
15
|
+
deriveCompanyPda,
|
|
16
|
+
deriveDailyAnchorPda,
|
|
17
|
+
deriveDeploymentPda,
|
|
18
|
+
deriveOperationsPda,
|
|
19
|
+
derivePolicyPda,
|
|
20
|
+
deriveProtocolFeePda,
|
|
21
|
+
deriveTreasuryPda,
|
|
22
|
+
u32LeBytes,
|
|
23
|
+
} from "./pda";
|
|
24
|
+
|
|
25
|
+
// Anchor instruction discriminators — copied verbatim from
|
|
26
|
+
// `packages/occa-sdk/src/idl/registry.json`. If the program is rebuilt
|
|
27
|
+
// with renamed instructions, regen the IDL and update both.
|
|
28
|
+
//
|
|
29
|
+
// Authority model: every state-changing ix is signed by `owner` (the
|
|
30
|
+
// user wallet). The operator hot wallet sponsors fees only — it never
|
|
31
|
+
// appears as a signer authority on any account.
|
|
32
|
+
export const INSTRUCTION_DISCRIMINATOR = {
|
|
33
|
+
createCompany: Buffer.from([36, 192, 217, 147, 233, 129, 198, 18]),
|
|
34
|
+
updateCompanyMetadata: Buffer.from([186, 229, 190, 16, 234, 141, 170, 89]),
|
|
35
|
+
updateCompanyStatus: Buffer.from([61, 6, 101, 120, 141, 13, 125, 75]),
|
|
36
|
+
registerAgentIdentity: Buffer.from([57, 31, 242, 205, 57, 129, 123, 35]),
|
|
37
|
+
updateAgentIdentityMetadata: Buffer.from([
|
|
38
|
+
250, 182, 24, 200, 201, 147, 60, 183,
|
|
39
|
+
]),
|
|
40
|
+
createDeployment: Buffer.from([55, 207, 186, 101, 21, 218, 102, 171]),
|
|
41
|
+
updateDeploymentMetadata: Buffer.from([100, 135, 41, 32, 16, 41, 29, 76]),
|
|
42
|
+
updateDeploymentStatus: Buffer.from([225, 195, 150, 254, 178, 203, 53, 147]),
|
|
43
|
+
retireDeployment: Buffer.from([45, 188, 162, 197, 136, 180, 202, 153]),
|
|
44
|
+
setReceivingAddress: Buffer.from([70, 63, 44, 87, 16, 6, 156, 200]),
|
|
45
|
+
commitDailyAnchor: Buffer.from([18, 7, 3, 65, 58, 148, 164, 0]),
|
|
46
|
+
} as const;
|
|
47
|
+
|
|
48
|
+
// ── Borsh primitives ────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
function encodeString(s: string): Buffer {
|
|
51
|
+
const utf8 = Buffer.from(s, "utf8");
|
|
52
|
+
const len = Buffer.alloc(4);
|
|
53
|
+
len.writeUInt32LE(utf8.length, 0);
|
|
54
|
+
return Buffer.concat([len, utf8]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function encodePubkey(pk: PublicKey): Buffer {
|
|
58
|
+
return Buffer.from(pk.toBytes());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function encodeU8(n: number): Buffer {
|
|
62
|
+
if (!Number.isInteger(n) || n < 0 || n > 0xff) {
|
|
63
|
+
throw new RangeError(`u8 out of range: ${n}`);
|
|
64
|
+
}
|
|
65
|
+
return Buffer.from([n]);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function encodeMetadataHash(hash: Uint8Array | Buffer): Buffer {
|
|
69
|
+
if (hash.length !== 32) {
|
|
70
|
+
throw new RangeError(`metadata_hash must be 32 bytes, got ${hash.length}`);
|
|
71
|
+
}
|
|
72
|
+
return Buffer.from(hash);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Borsh `Option<u32>` = 1-byte tag (0=None, 1=Some) + optional 4-byte LE. */
|
|
76
|
+
function encodeOptionU32(value: number | null | undefined): Buffer {
|
|
77
|
+
if (value === null || value === undefined) {
|
|
78
|
+
return Buffer.from([0]);
|
|
79
|
+
}
|
|
80
|
+
return Buffer.concat([Buffer.from([1]), u32LeBytes(value)]);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── Company ────────────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
export interface CreateCompanyParams {
|
|
86
|
+
/** User wallet — signer + bound into the PDA seed. Sole authority for
|
|
87
|
+
* every state-changing ix on this company. Immutable. */
|
|
88
|
+
owner: PublicKey;
|
|
89
|
+
/** Rent payer. Typically the operator hot wallet (sponsored UX),
|
|
90
|
+
* but may equal `owner`. */
|
|
91
|
+
payer: PublicKey;
|
|
92
|
+
nonce: number;
|
|
93
|
+
name: string;
|
|
94
|
+
/** BCP-47 locale tag (e.g. "en", "id"). Empty string allowed. */
|
|
95
|
+
locale: string;
|
|
96
|
+
/** Off-chain metadata pointer (IPFS / Arweave / HTTPS). */
|
|
97
|
+
metadataUri: string;
|
|
98
|
+
/** SHA-256 of canonical metadata JSON (32 bytes). Pass `Buffer.alloc(32)`
|
|
99
|
+
* if metadata not yet finalized. */
|
|
100
|
+
metadataHash: Uint8Array | Buffer;
|
|
101
|
+
programId?: PublicKey;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function buildCreateCompanyInstruction(params: CreateCompanyParams): {
|
|
105
|
+
instruction: TransactionInstruction;
|
|
106
|
+
companyPda: PublicKey;
|
|
107
|
+
treasuryPda: PublicKey;
|
|
108
|
+
policyPda: PublicKey;
|
|
109
|
+
bump: number;
|
|
110
|
+
} {
|
|
111
|
+
const programId = params.programId ?? REGISTRY_PROGRAM_ID;
|
|
112
|
+
const { pda: companyPda, bump } = deriveCompanyPda(
|
|
113
|
+
params.owner,
|
|
114
|
+
params.nonce,
|
|
115
|
+
programId,
|
|
116
|
+
);
|
|
117
|
+
// Treasury + Policy PDAs initialized atomically inside the chain ix
|
|
118
|
+
// via CPI to `treasury::init_treasury` (registry/lib.rs CreateCompany
|
|
119
|
+
// accounts struct). Both must be passed in the tx even though they're
|
|
120
|
+
// not yet allocated — chain CPI does the `init` against them.
|
|
121
|
+
const { pda: treasuryPda } = deriveTreasuryPda(companyPda);
|
|
122
|
+
const { pda: policyPda } = derivePolicyPda(companyPda);
|
|
123
|
+
|
|
124
|
+
const data = Buffer.concat([
|
|
125
|
+
INSTRUCTION_DISCRIMINATOR.createCompany,
|
|
126
|
+
u32LeBytes(params.nonce),
|
|
127
|
+
encodeString(params.name),
|
|
128
|
+
encodeString(params.locale),
|
|
129
|
+
encodeString(params.metadataUri),
|
|
130
|
+
encodeMetadataHash(params.metadataHash),
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
const instruction = new TransactionInstruction({
|
|
134
|
+
programId,
|
|
135
|
+
// Order MUST match registry/lib.rs `CreateCompany` accounts struct:
|
|
136
|
+
// company, owner, payer, treasury, policy, treasury_program, system_program.
|
|
137
|
+
keys: [
|
|
138
|
+
{ pubkey: companyPda, isSigner: false, isWritable: true },
|
|
139
|
+
{ pubkey: params.owner, isSigner: true, isWritable: false },
|
|
140
|
+
{ pubkey: params.payer, isSigner: true, isWritable: true },
|
|
141
|
+
{ pubkey: treasuryPda, isSigner: false, isWritable: true },
|
|
142
|
+
{ pubkey: policyPda, isSigner: false, isWritable: true },
|
|
143
|
+
{ pubkey: TREASURY_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
144
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
145
|
+
],
|
|
146
|
+
data,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return { instruction, companyPda, treasuryPda, policyPda, bump };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface UpdateCompanyMetadataParams {
|
|
153
|
+
companyPda: PublicKey;
|
|
154
|
+
/** User wallet — must equal `company.owner`. */
|
|
155
|
+
owner: PublicKey;
|
|
156
|
+
name: string;
|
|
157
|
+
locale: string;
|
|
158
|
+
metadataUri: string;
|
|
159
|
+
metadataHash: Uint8Array | Buffer;
|
|
160
|
+
programId?: PublicKey;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function buildUpdateCompanyMetadataInstruction(
|
|
164
|
+
params: UpdateCompanyMetadataParams,
|
|
165
|
+
): { instruction: TransactionInstruction } {
|
|
166
|
+
const programId = params.programId ?? REGISTRY_PROGRAM_ID;
|
|
167
|
+
const data = Buffer.concat([
|
|
168
|
+
INSTRUCTION_DISCRIMINATOR.updateCompanyMetadata,
|
|
169
|
+
encodeString(params.name),
|
|
170
|
+
encodeString(params.locale),
|
|
171
|
+
encodeString(params.metadataUri),
|
|
172
|
+
encodeMetadataHash(params.metadataHash),
|
|
173
|
+
]);
|
|
174
|
+
const instruction = new TransactionInstruction({
|
|
175
|
+
programId,
|
|
176
|
+
keys: [
|
|
177
|
+
{ pubkey: params.companyPda, isSigner: false, isWritable: true },
|
|
178
|
+
{ pubkey: params.owner, isSigner: true, isWritable: false },
|
|
179
|
+
],
|
|
180
|
+
data,
|
|
181
|
+
});
|
|
182
|
+
return { instruction };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface UpdateCompanyStatusParams {
|
|
186
|
+
companyPda: PublicKey;
|
|
187
|
+
owner: PublicKey;
|
|
188
|
+
/** 0 = Active, 1 = Paused. See `COMPANY_STATUS`. */
|
|
189
|
+
newStatus: number;
|
|
190
|
+
programId?: PublicKey;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function buildUpdateCompanyStatusInstruction(
|
|
194
|
+
params: UpdateCompanyStatusParams,
|
|
195
|
+
): { instruction: TransactionInstruction } {
|
|
196
|
+
const programId = params.programId ?? REGISTRY_PROGRAM_ID;
|
|
197
|
+
const data = Buffer.concat([
|
|
198
|
+
INSTRUCTION_DISCRIMINATOR.updateCompanyStatus,
|
|
199
|
+
encodeU8(params.newStatus),
|
|
200
|
+
]);
|
|
201
|
+
const instruction = new TransactionInstruction({
|
|
202
|
+
programId,
|
|
203
|
+
keys: [
|
|
204
|
+
{ pubkey: params.companyPda, isSigner: false, isWritable: true },
|
|
205
|
+
{ pubkey: params.owner, isSigner: true, isWritable: false },
|
|
206
|
+
],
|
|
207
|
+
data,
|
|
208
|
+
});
|
|
209
|
+
return { instruction };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ── Agent Identity ─────────────────────────────────────────────────────────
|
|
213
|
+
|
|
214
|
+
export interface RegisterAgentIdentityParams {
|
|
215
|
+
/** Stable identity key — typically a fresh keypair pubkey held by the
|
|
216
|
+
* user wallet (NOT the user wallet itself). Baked into the PDA seed. */
|
|
217
|
+
agentPubkey: PublicKey;
|
|
218
|
+
/** Owning user wallet. Immutable — there is no transfer instruction. */
|
|
219
|
+
owner: PublicKey;
|
|
220
|
+
/** Rent payer (typically operator hot wallet). */
|
|
221
|
+
payer: PublicKey;
|
|
222
|
+
name: string;
|
|
223
|
+
metadataUri: string;
|
|
224
|
+
metadataHash: Uint8Array | Buffer;
|
|
225
|
+
programId?: PublicKey;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function buildRegisterAgentIdentityInstruction(
|
|
229
|
+
params: RegisterAgentIdentityParams,
|
|
230
|
+
): {
|
|
231
|
+
instruction: TransactionInstruction;
|
|
232
|
+
identityPda: PublicKey;
|
|
233
|
+
bump: number;
|
|
234
|
+
} {
|
|
235
|
+
const programId = params.programId ?? REGISTRY_PROGRAM_ID;
|
|
236
|
+
const { pda: identityPda, bump } = deriveAgentIdentityPda(
|
|
237
|
+
params.agentPubkey,
|
|
238
|
+
programId,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const data = Buffer.concat([
|
|
242
|
+
INSTRUCTION_DISCRIMINATOR.registerAgentIdentity,
|
|
243
|
+
encodePubkey(params.agentPubkey),
|
|
244
|
+
encodeString(params.name),
|
|
245
|
+
encodeString(params.metadataUri),
|
|
246
|
+
encodeMetadataHash(params.metadataHash),
|
|
247
|
+
]);
|
|
248
|
+
|
|
249
|
+
const instruction = new TransactionInstruction({
|
|
250
|
+
programId,
|
|
251
|
+
keys: [
|
|
252
|
+
{ pubkey: identityPda, isSigner: false, isWritable: true },
|
|
253
|
+
{ pubkey: params.owner, isSigner: true, isWritable: false },
|
|
254
|
+
{ pubkey: params.payer, isSigner: true, isWritable: true },
|
|
255
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
256
|
+
],
|
|
257
|
+
data,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
return { instruction, identityPda, bump };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export interface UpdateAgentIdentityMetadataParams {
|
|
264
|
+
identityPda: PublicKey;
|
|
265
|
+
/** User wallet — must equal `identity.owner`. */
|
|
266
|
+
owner: PublicKey;
|
|
267
|
+
name: string;
|
|
268
|
+
metadataUri: string;
|
|
269
|
+
metadataHash: Uint8Array | Buffer;
|
|
270
|
+
programId?: PublicKey;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function buildUpdateAgentIdentityMetadataInstruction(
|
|
274
|
+
params: UpdateAgentIdentityMetadataParams,
|
|
275
|
+
): { instruction: TransactionInstruction } {
|
|
276
|
+
const programId = params.programId ?? REGISTRY_PROGRAM_ID;
|
|
277
|
+
const data = Buffer.concat([
|
|
278
|
+
INSTRUCTION_DISCRIMINATOR.updateAgentIdentityMetadata,
|
|
279
|
+
encodeString(params.name),
|
|
280
|
+
encodeString(params.metadataUri),
|
|
281
|
+
encodeMetadataHash(params.metadataHash),
|
|
282
|
+
]);
|
|
283
|
+
const instruction = new TransactionInstruction({
|
|
284
|
+
programId,
|
|
285
|
+
keys: [
|
|
286
|
+
{ pubkey: params.identityPda, isSigner: false, isWritable: true },
|
|
287
|
+
{ pubkey: params.owner, isSigner: true, isWritable: false },
|
|
288
|
+
],
|
|
289
|
+
data,
|
|
290
|
+
});
|
|
291
|
+
return { instruction };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ── Deployment ─────────────────────────────────────────────────────────────
|
|
295
|
+
|
|
296
|
+
export interface CreateDeploymentParams {
|
|
297
|
+
companyPda: PublicKey;
|
|
298
|
+
identityPda: PublicKey;
|
|
299
|
+
/** User wallet — must equal both `company.owner` and `identity.owner`. */
|
|
300
|
+
owner: PublicKey;
|
|
301
|
+
payer: PublicKey;
|
|
302
|
+
/** Per-company u32 counter. Caller picks the next free index. */
|
|
303
|
+
deploymentIndex: number;
|
|
304
|
+
/** Capability persona (e.g. "ceo", "sdr"). Bounded by MAX_ROLE_LEN. */
|
|
305
|
+
role: string;
|
|
306
|
+
/** Reporting parent index within this company (null = top-level). */
|
|
307
|
+
parentDeploymentIndex?: number | null;
|
|
308
|
+
/** Pinned adapter (`PublicKey.default` = unspecified). */
|
|
309
|
+
adapterId: PublicKey;
|
|
310
|
+
metadataUri: string;
|
|
311
|
+
metadataHash: Uint8Array | Buffer;
|
|
312
|
+
programId?: PublicKey;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function buildCreateDeploymentInstruction(
|
|
316
|
+
params: CreateDeploymentParams,
|
|
317
|
+
): {
|
|
318
|
+
instruction: TransactionInstruction;
|
|
319
|
+
deploymentPda: PublicKey;
|
|
320
|
+
bump: number;
|
|
321
|
+
} {
|
|
322
|
+
const programId = params.programId ?? REGISTRY_PROGRAM_ID;
|
|
323
|
+
const { pda: deploymentPda, bump } = deriveDeploymentPda(
|
|
324
|
+
params.companyPda,
|
|
325
|
+
params.deploymentIndex,
|
|
326
|
+
programId,
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
const data = Buffer.concat([
|
|
330
|
+
INSTRUCTION_DISCRIMINATOR.createDeployment,
|
|
331
|
+
u32LeBytes(params.deploymentIndex),
|
|
332
|
+
encodeString(params.role),
|
|
333
|
+
encodeOptionU32(params.parentDeploymentIndex),
|
|
334
|
+
encodePubkey(params.adapterId),
|
|
335
|
+
encodeString(params.metadataUri),
|
|
336
|
+
encodeMetadataHash(params.metadataHash),
|
|
337
|
+
]);
|
|
338
|
+
|
|
339
|
+
const instruction = new TransactionInstruction({
|
|
340
|
+
programId,
|
|
341
|
+
keys: [
|
|
342
|
+
// Order matches the on-chain `CreateDeployment` accounts struct.
|
|
343
|
+
{ pubkey: params.companyPda, isSigner: false, isWritable: false },
|
|
344
|
+
{ pubkey: params.identityPda, isSigner: false, isWritable: false },
|
|
345
|
+
{ pubkey: params.owner, isSigner: true, isWritable: false },
|
|
346
|
+
{ pubkey: deploymentPda, isSigner: false, isWritable: true },
|
|
347
|
+
{ pubkey: params.payer, isSigner: true, isWritable: true },
|
|
348
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
349
|
+
],
|
|
350
|
+
data,
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
return { instruction, deploymentPda, bump };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export interface UpdateDeploymentMetadataParams {
|
|
357
|
+
deploymentPda: PublicKey;
|
|
358
|
+
owner: PublicKey;
|
|
359
|
+
role: string;
|
|
360
|
+
metadataUri: string;
|
|
361
|
+
metadataHash: Uint8Array | Buffer;
|
|
362
|
+
programId?: PublicKey;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export function buildUpdateDeploymentMetadataInstruction(
|
|
366
|
+
params: UpdateDeploymentMetadataParams,
|
|
367
|
+
): { instruction: TransactionInstruction } {
|
|
368
|
+
const programId = params.programId ?? REGISTRY_PROGRAM_ID;
|
|
369
|
+
const data = Buffer.concat([
|
|
370
|
+
INSTRUCTION_DISCRIMINATOR.updateDeploymentMetadata,
|
|
371
|
+
encodeString(params.role),
|
|
372
|
+
encodeString(params.metadataUri),
|
|
373
|
+
encodeMetadataHash(params.metadataHash),
|
|
374
|
+
]);
|
|
375
|
+
const instruction = new TransactionInstruction({
|
|
376
|
+
programId,
|
|
377
|
+
keys: [
|
|
378
|
+
{ pubkey: params.deploymentPda, isSigner: false, isWritable: true },
|
|
379
|
+
{ pubkey: params.owner, isSigner: true, isWritable: false },
|
|
380
|
+
],
|
|
381
|
+
data,
|
|
382
|
+
});
|
|
383
|
+
return { instruction };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export interface UpdateDeploymentStatusParams {
|
|
387
|
+
deploymentPda: PublicKey;
|
|
388
|
+
owner: PublicKey;
|
|
389
|
+
/** 0 = Active, 1 = Paused. Use `retire_deployment` for terminal. */
|
|
390
|
+
newStatus: number;
|
|
391
|
+
programId?: PublicKey;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export function buildUpdateDeploymentStatusInstruction(
|
|
395
|
+
params: UpdateDeploymentStatusParams,
|
|
396
|
+
): { instruction: TransactionInstruction } {
|
|
397
|
+
const programId = params.programId ?? REGISTRY_PROGRAM_ID;
|
|
398
|
+
const data = Buffer.concat([
|
|
399
|
+
INSTRUCTION_DISCRIMINATOR.updateDeploymentStatus,
|
|
400
|
+
encodeU8(params.newStatus),
|
|
401
|
+
]);
|
|
402
|
+
const instruction = new TransactionInstruction({
|
|
403
|
+
programId,
|
|
404
|
+
keys: [
|
|
405
|
+
{ pubkey: params.deploymentPda, isSigner: false, isWritable: true },
|
|
406
|
+
{ pubkey: params.owner, isSigner: true, isWritable: false },
|
|
407
|
+
],
|
|
408
|
+
data,
|
|
409
|
+
});
|
|
410
|
+
return { instruction };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export interface RetireDeploymentParams {
|
|
414
|
+
deploymentPda: PublicKey;
|
|
415
|
+
owner: PublicKey;
|
|
416
|
+
programId?: PublicKey;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export function buildRetireDeploymentInstruction(
|
|
420
|
+
params: RetireDeploymentParams,
|
|
421
|
+
): { instruction: TransactionInstruction } {
|
|
422
|
+
const programId = params.programId ?? REGISTRY_PROGRAM_ID;
|
|
423
|
+
const instruction = new TransactionInstruction({
|
|
424
|
+
programId,
|
|
425
|
+
keys: [
|
|
426
|
+
{ pubkey: params.deploymentPda, isSigner: false, isWritable: true },
|
|
427
|
+
{ pubkey: params.owner, isSigner: true, isWritable: false },
|
|
428
|
+
],
|
|
429
|
+
data: Buffer.from(INSTRUCTION_DISCRIMINATOR.retireDeployment),
|
|
430
|
+
});
|
|
431
|
+
return { instruction };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export interface SetReceivingAddressParams {
|
|
435
|
+
deploymentPda: PublicKey;
|
|
436
|
+
/** User wallet — must equal `deployment.owner`. */
|
|
437
|
+
owner: PublicKey;
|
|
438
|
+
/** New receiving address (passive destination wallet for funds disbursed
|
|
439
|
+
* *to* this agent). Pass `PublicKey.default` to clear. NEVER a signer
|
|
440
|
+
* on chain — receiving address never authorizes any on-chain action. */
|
|
441
|
+
newReceivingAddress: PublicKey;
|
|
442
|
+
programId?: PublicKey;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export function buildSetReceivingAddressInstruction(
|
|
446
|
+
params: SetReceivingAddressParams,
|
|
447
|
+
): { instruction: TransactionInstruction } {
|
|
448
|
+
const programId = params.programId ?? REGISTRY_PROGRAM_ID;
|
|
449
|
+
const data = Buffer.concat([
|
|
450
|
+
INSTRUCTION_DISCRIMINATOR.setReceivingAddress,
|
|
451
|
+
encodePubkey(params.newReceivingAddress),
|
|
452
|
+
]);
|
|
453
|
+
const instruction = new TransactionInstruction({
|
|
454
|
+
programId,
|
|
455
|
+
keys: [
|
|
456
|
+
{ pubkey: params.deploymentPda, isSigner: false, isWritable: true },
|
|
457
|
+
{ pubkey: params.owner, isSigner: true, isWritable: false },
|
|
458
|
+
],
|
|
459
|
+
data,
|
|
460
|
+
});
|
|
461
|
+
return { instruction };
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ── Treasury program ────────────────────────────────────────────────────────
|
|
465
|
+
//
|
|
466
|
+
// The treasury program is a SEPARATE program from registry — these
|
|
467
|
+
// instructions target `TREASURY_PROGRAM_ID`. Discriminators copied from
|
|
468
|
+
// `occa-programs/target/idl/treasury.json`.
|
|
469
|
+
|
|
470
|
+
export const TREASURY_INSTRUCTION_DISCRIMINATOR = {
|
|
471
|
+
setPolicy: Buffer.from([40, 133, 12, 157, 235, 202, 2, 132]),
|
|
472
|
+
disburseDiscretionary: Buffer.from([102, 176, 14, 127, 210, 4, 96, 175]),
|
|
473
|
+
initProtocolFeeAccount: Buffer.from([214, 27, 184, 174, 155, 79, 141, 114]),
|
|
474
|
+
registerCompanyOperations: Buffer.from([212, 173, 142, 23, 28, 221, 55, 99]),
|
|
475
|
+
updateOperationsCapability: Buffer.from([21, 13, 197, 41, 92, 229, 142, 202]),
|
|
476
|
+
revokeOperations: Buffer.from([141, 196, 241, 103, 182, 146, 117, 183]),
|
|
477
|
+
closeOperations: Buffer.from([2, 52, 136, 225, 80, 230, 222, 120]),
|
|
478
|
+
disburseRoutine: Buffer.from([45, 152, 225, 130, 133, 73, 62, 202]),
|
|
479
|
+
disbursePrivileged: Buffer.from([173, 78, 248, 158, 76, 46, 88, 167]),
|
|
480
|
+
} as const;
|
|
481
|
+
|
|
482
|
+
// ── Borsh helpers for treasury args ─────────────────────────────────────────
|
|
483
|
+
|
|
484
|
+
function encodeU16(n: number): Buffer {
|
|
485
|
+
if (!Number.isInteger(n) || n < 0 || n > 0xffff) {
|
|
486
|
+
throw new RangeError(`u16 out of range: ${n}`);
|
|
487
|
+
}
|
|
488
|
+
const buf = Buffer.alloc(2);
|
|
489
|
+
buf.writeUInt16LE(n, 0);
|
|
490
|
+
return buf;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function encodeU64(n: bigint): Buffer {
|
|
494
|
+
if (n < 0n || n > 0xffff_ffff_ffff_ffffn) {
|
|
495
|
+
throw new RangeError(`u64 out of range: ${n}`);
|
|
496
|
+
}
|
|
497
|
+
const buf = Buffer.alloc(8);
|
|
498
|
+
buf.writeBigUInt64LE(n, 0);
|
|
499
|
+
return buf;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/** A per-asset budget / balance entry. `SOL_PSEUDO_MINT` for SOL. */
|
|
503
|
+
export interface AssetBudget {
|
|
504
|
+
mint: PublicKey;
|
|
505
|
+
amount: bigint;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function encodeAssetBudget(b: AssetBudget): Buffer {
|
|
509
|
+
return Buffer.concat([encodePubkey(b.mint), encodeU64(b.amount)]);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/** Borsh `Vec<T>` = 4-byte LE length + each item. */
|
|
513
|
+
function encodeVec<T>(items: T[], encodeItem: (item: T) => Buffer): Buffer {
|
|
514
|
+
return Buffer.concat([u32LeBytes(items.length), ...items.map(encodeItem)]);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** Borsh `Option<T>` = 1-byte tag (0=None, 1=Some) + optional payload. */
|
|
518
|
+
function encodeOption<T>(
|
|
519
|
+
value: T | null | undefined,
|
|
520
|
+
encodeSome: (v: T) => Buffer,
|
|
521
|
+
): Buffer {
|
|
522
|
+
if (value === null || value === undefined) return Buffer.from([0]);
|
|
523
|
+
return Buffer.concat([Buffer.from([1]), encodeSome(value)]);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// ── set_policy ──────────────────────────────────────────────────────────────
|
|
527
|
+
|
|
528
|
+
export interface SetPolicyParams {
|
|
529
|
+
companyPda: PublicKey;
|
|
530
|
+
treasuryPda: PublicKey;
|
|
531
|
+
policyPda: PublicKey;
|
|
532
|
+
/** Must equal `company.owner` — the sole Privileged-class signer. */
|
|
533
|
+
controllingAuthority: PublicKey;
|
|
534
|
+
/** Per-month routine-class cap. Omit to leave unchanged. */
|
|
535
|
+
routineBudgetPerMonth?: AssetBudget[];
|
|
536
|
+
/** Per-month discretionary-class cap. Omit to leave unchanged. */
|
|
537
|
+
discretionaryBudgetPerMonth?: AssetBudget[];
|
|
538
|
+
/** SOL threshold above which Privileged-class disbursement (= owner +
|
|
539
|
+
* secondary signer) is required. Omit to leave unchanged. Pass
|
|
540
|
+
* `u64::MAX` (-1n cast) to disable (= secondary signer never required). */
|
|
541
|
+
privilegedThresholdLamports?: bigint;
|
|
542
|
+
/** Per-asset threshold variant of the above. Omit to leave unchanged. */
|
|
543
|
+
privilegedThresholdPerToken?: AssetBudget[];
|
|
544
|
+
/** Second signer required for Privileged-class disbursements.
|
|
545
|
+
* Distinguishes three cases:
|
|
546
|
+
* • `undefined` — leave unchanged
|
|
547
|
+
* • `null` — clear (no secondary signer; Privileged disburse
|
|
548
|
+
* then has no valid signer combination, effectively
|
|
549
|
+
* disabling it)
|
|
550
|
+
* • `PublicKey` — set to this pubkey */
|
|
551
|
+
secondarySigner?: PublicKey | null;
|
|
552
|
+
/** Agent Operating Fee in basis points. Omit to leave unchanged. */
|
|
553
|
+
agentOperatingFeeBps?: number;
|
|
554
|
+
/** Accepted-asset allow-list. Omit to leave unchanged. */
|
|
555
|
+
acceptedAssets?: PublicKey[];
|
|
556
|
+
programId?: PublicKey;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Build a `set_policy` instruction. Privileged-class — signed by the
|
|
561
|
+
* controlling authority (= company owner).
|
|
562
|
+
*
|
|
563
|
+
* Every parameter is `Option<T>`: omitted = "leave unchanged". For
|
|
564
|
+
* `secondarySigner`, JS `null` maps to the "clear" case (outer Some,
|
|
565
|
+
* inner None) — see the field doc above.
|
|
566
|
+
*/
|
|
567
|
+
export function buildSetPolicyInstruction(params: SetPolicyParams): {
|
|
568
|
+
instruction: TransactionInstruction;
|
|
569
|
+
} {
|
|
570
|
+
const programId = params.programId ?? TREASURY_PROGRAM_ID;
|
|
571
|
+
|
|
572
|
+
// Borsh `Option<Option<Pubkey>>` encoder for secondary_signer:
|
|
573
|
+
// undefined → outer None (00)
|
|
574
|
+
// null → outer Some, inner None (01 00)
|
|
575
|
+
// pubkey → outer Some, inner Some (01 01 <32 bytes>)
|
|
576
|
+
function encodeSecondarySigner(v: PublicKey | null | undefined): Buffer {
|
|
577
|
+
if (v === undefined) return Buffer.from([0]);
|
|
578
|
+
if (v === null) return Buffer.from([1, 0]);
|
|
579
|
+
return Buffer.concat([Buffer.from([1, 1]), encodePubkey(v)]);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Field order MUST match the on-chain `SetPolicyParams` struct.
|
|
583
|
+
const data = Buffer.concat([
|
|
584
|
+
TREASURY_INSTRUCTION_DISCRIMINATOR.setPolicy,
|
|
585
|
+
// 1. routine_budget_per_month: Option<Vec<AssetBudget>>
|
|
586
|
+
encodeOption(params.routineBudgetPerMonth, (v) =>
|
|
587
|
+
encodeVec(v, encodeAssetBudget),
|
|
588
|
+
),
|
|
589
|
+
// 2. discretionary_budget_per_month: Option<Vec<AssetBudget>>
|
|
590
|
+
encodeOption(params.discretionaryBudgetPerMonth, (v) =>
|
|
591
|
+
encodeVec(v, encodeAssetBudget),
|
|
592
|
+
),
|
|
593
|
+
// 3. privileged_threshold_lamports: Option<u64>
|
|
594
|
+
encodeOption(params.privilegedThresholdLamports, encodeU64),
|
|
595
|
+
// 4. privileged_threshold_per_token: Option<Vec<AssetBudget>>
|
|
596
|
+
encodeOption(params.privilegedThresholdPerToken, (v) =>
|
|
597
|
+
encodeVec(v, encodeAssetBudget),
|
|
598
|
+
),
|
|
599
|
+
// 5. secondary_signer: Option<Option<Pubkey>>
|
|
600
|
+
encodeSecondarySigner(params.secondarySigner),
|
|
601
|
+
// 6. agent_operating_fee_bps: Option<u16>
|
|
602
|
+
encodeOption(params.agentOperatingFeeBps, encodeU16),
|
|
603
|
+
// 7. accepted_assets: Option<Vec<Pubkey>>
|
|
604
|
+
encodeOption(params.acceptedAssets, (v) => encodeVec(v, encodePubkey)),
|
|
605
|
+
]);
|
|
606
|
+
|
|
607
|
+
const instruction = new TransactionInstruction({
|
|
608
|
+
programId,
|
|
609
|
+
// Order: company, controlling_authority, treasury, policy.
|
|
610
|
+
keys: [
|
|
611
|
+
{ pubkey: params.companyPda, isSigner: false, isWritable: false },
|
|
612
|
+
{
|
|
613
|
+
pubkey: params.controllingAuthority,
|
|
614
|
+
isSigner: true,
|
|
615
|
+
isWritable: false,
|
|
616
|
+
},
|
|
617
|
+
{ pubkey: params.treasuryPda, isSigner: false, isWritable: true },
|
|
618
|
+
{ pubkey: params.policyPda, isSigner: false, isWritable: true },
|
|
619
|
+
],
|
|
620
|
+
data,
|
|
621
|
+
});
|
|
622
|
+
return { instruction };
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// ── disburse_discretionary ──────────────────────────────────────────────────
|
|
626
|
+
|
|
627
|
+
export interface DisburseDiscretionaryParams {
|
|
628
|
+
companyPda: PublicKey;
|
|
629
|
+
treasuryPda: PublicKey;
|
|
630
|
+
policyPda: PublicKey;
|
|
631
|
+
/** Must equal `company.owner`. */
|
|
632
|
+
controllingAuthority: PublicKey;
|
|
633
|
+
/** Deployment PDA of the agent being paid — chain matches `destination`
|
|
634
|
+
* against its `receiving_address`. */
|
|
635
|
+
deploymentPda: PublicKey;
|
|
636
|
+
/** The agent's receiving address — destination of the funds. */
|
|
637
|
+
destination: PublicKey;
|
|
638
|
+
/** Amount in lamports (SOL only — Phase 1). */
|
|
639
|
+
amountLamports: bigint;
|
|
640
|
+
/** Defaults to `SOL_PSEUDO_MINT`. */
|
|
641
|
+
mint?: PublicKey;
|
|
642
|
+
programId?: PublicKey;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Build a `disburse_discretionary` instruction — Discretionary-class
|
|
647
|
+
* payout to an agent's receiving address, signed by the controlling
|
|
648
|
+
* authority. The 3% Agent Operating Fee is deducted on-chain and routed
|
|
649
|
+
* to the ProtocolFeeAccount.
|
|
650
|
+
*/
|
|
651
|
+
export function buildDisburseDiscretionaryInstruction(
|
|
652
|
+
params: DisburseDiscretionaryParams,
|
|
653
|
+
): { instruction: TransactionInstruction } {
|
|
654
|
+
const programId = params.programId ?? TREASURY_PROGRAM_ID;
|
|
655
|
+
const mint = params.mint ?? SOL_PSEUDO_MINT;
|
|
656
|
+
const { pda: protocolFeePda } = deriveProtocolFeePda();
|
|
657
|
+
|
|
658
|
+
const data = Buffer.concat([
|
|
659
|
+
TREASURY_INSTRUCTION_DISCRIMINATOR.disburseDiscretionary,
|
|
660
|
+
encodePubkey(mint),
|
|
661
|
+
encodeU64(params.amountLamports),
|
|
662
|
+
]);
|
|
663
|
+
|
|
664
|
+
const instruction = new TransactionInstruction({
|
|
665
|
+
programId,
|
|
666
|
+
// Order: company, controlling_authority, treasury, policy,
|
|
667
|
+
// deployment, destination, protocol_fee_account.
|
|
668
|
+
keys: [
|
|
669
|
+
{ pubkey: params.companyPda, isSigner: false, isWritable: false },
|
|
670
|
+
{
|
|
671
|
+
pubkey: params.controllingAuthority,
|
|
672
|
+
isSigner: true,
|
|
673
|
+
isWritable: false,
|
|
674
|
+
},
|
|
675
|
+
{ pubkey: params.treasuryPda, isSigner: false, isWritable: true },
|
|
676
|
+
{ pubkey: params.policyPda, isSigner: false, isWritable: true },
|
|
677
|
+
{ pubkey: params.deploymentPda, isSigner: false, isWritable: false },
|
|
678
|
+
{ pubkey: params.destination, isSigner: false, isWritable: true },
|
|
679
|
+
{ pubkey: protocolFeePda, isSigner: false, isWritable: true },
|
|
680
|
+
],
|
|
681
|
+
data,
|
|
682
|
+
});
|
|
683
|
+
return { instruction };
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// ── Treasury: more builders ────────────────────────────────────────────────
|
|
687
|
+
|
|
688
|
+
/** Encode an i64 as little-endian 8 bytes (Borsh). */
|
|
689
|
+
function encodeI64(n: bigint): Buffer {
|
|
690
|
+
const buf = Buffer.alloc(8);
|
|
691
|
+
buf.writeBigInt64LE(n, 0);
|
|
692
|
+
return buf;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/** Encode a Borsh bool — single byte 0 or 1. */
|
|
696
|
+
function encodeBool(b: boolean): Buffer {
|
|
697
|
+
return Buffer.from([b ? 1 : 0]);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/** Encode an 8-byte action whitelist entry (Anchor ix discriminator). */
|
|
701
|
+
function encodeAction(disc: Uint8Array | Buffer): Buffer {
|
|
702
|
+
if (disc.length !== 8) {
|
|
703
|
+
throw new RangeError(`action_whitelist entry must be 8 bytes, got ${disc.length}`);
|
|
704
|
+
}
|
|
705
|
+
return Buffer.from(disc);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// ── init_protocol_fee_account ───────────────────────────────────────────────
|
|
709
|
+
|
|
710
|
+
export interface InitProtocolFeeAccountParams {
|
|
711
|
+
/** Upgrade authority of the Treasury program. Pays + signs. */
|
|
712
|
+
authority: PublicKey;
|
|
713
|
+
/** Long-lived withdrawal authority for accumulated fees (e.g. governance
|
|
714
|
+
* multisig). Immutable after init. */
|
|
715
|
+
governance: PublicKey;
|
|
716
|
+
/** The Treasury program's own pubkey — passed as an account. Defaults to
|
|
717
|
+
* `TREASURY_PROGRAM_ID`. */
|
|
718
|
+
program?: PublicKey;
|
|
719
|
+
/** The program's ProgramData PDA (BPFLoaderUpgradeable). Anchor uses this
|
|
720
|
+
* to verify the signer is the upgrade authority. Caller derives:
|
|
721
|
+
* `PublicKey.findProgramAddressSync([program.toBuffer()],
|
|
722
|
+
* BPF_LOADER_UPGRADEABLE_PROGRAM_ID)`. */
|
|
723
|
+
programData: PublicKey;
|
|
724
|
+
programId?: PublicKey;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Build an `init_protocol_fee_account` instruction. Singleton — call once
|
|
729
|
+
* per program deployment by the upgrade authority. Subsequent calls fail
|
|
730
|
+
* (PDA already initialized).
|
|
731
|
+
*/
|
|
732
|
+
export function buildInitProtocolFeeAccountInstruction(
|
|
733
|
+
params: InitProtocolFeeAccountParams,
|
|
734
|
+
): { instruction: TransactionInstruction } {
|
|
735
|
+
const programId = params.programId ?? TREASURY_PROGRAM_ID;
|
|
736
|
+
const programAccount = params.program ?? programId;
|
|
737
|
+
const { pda: protocolFeePda } = deriveProtocolFeePda(programId);
|
|
738
|
+
|
|
739
|
+
const data = Buffer.concat([
|
|
740
|
+
TREASURY_INSTRUCTION_DISCRIMINATOR.initProtocolFeeAccount,
|
|
741
|
+
encodePubkey(params.governance),
|
|
742
|
+
]);
|
|
743
|
+
|
|
744
|
+
const instruction = new TransactionInstruction({
|
|
745
|
+
programId,
|
|
746
|
+
keys: [
|
|
747
|
+
{ pubkey: protocolFeePda, isSigner: false, isWritable: true },
|
|
748
|
+
{ pubkey: params.authority, isSigner: true, isWritable: true },
|
|
749
|
+
{ pubkey: programAccount, isSigner: false, isWritable: false },
|
|
750
|
+
{ pubkey: params.programData, isSigner: false, isWritable: false },
|
|
751
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
752
|
+
],
|
|
753
|
+
data,
|
|
754
|
+
});
|
|
755
|
+
return { instruction };
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// ── register_company_operations ─────────────────────────────────────────────
|
|
759
|
+
|
|
760
|
+
export interface RegisterCompanyOperationsParams {
|
|
761
|
+
companyPda: PublicKey;
|
|
762
|
+
/** Must equal `company.owner` — Privileged-class signer. */
|
|
763
|
+
controllingAuthority: PublicKey;
|
|
764
|
+
/** Disbursement or Anchor — determines which OperationsAccount PDA is
|
|
765
|
+
* created. Order matters: enum byte is part of the seed. */
|
|
766
|
+
kind: OperationsKind;
|
|
767
|
+
/** Hot wallet that will sign downstream operations ix (e.g.
|
|
768
|
+
* `disburse_routine` for Disbursement, `commit_daily_anchor` for Anchor). */
|
|
769
|
+
signer: PublicKey;
|
|
770
|
+
/** Each entry = an 8-byte Anchor instruction discriminator the signer
|
|
771
|
+
* is allowed to invoke. Empty = no actions permitted (account exists
|
|
772
|
+
* but is essentially disabled). */
|
|
773
|
+
actionWhitelist: (Uint8Array | Buffer)[];
|
|
774
|
+
/** How many disburse calls this signer may make per calendar month. */
|
|
775
|
+
rateLimitPerPeriod: number;
|
|
776
|
+
/** Unix seconds. `0` = never expires. */
|
|
777
|
+
expiryUnix: bigint;
|
|
778
|
+
/** Rent payer — typically the operator hot wallet. */
|
|
779
|
+
payer: PublicKey;
|
|
780
|
+
programId?: PublicKey;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Build a `register_company_operations` instruction — creates an
|
|
785
|
+
* OperationsAccount binding a hot wallet to a capability set. Privileged-
|
|
786
|
+
* class (signed by `controlling_authority` = company owner).
|
|
787
|
+
*/
|
|
788
|
+
export function buildRegisterCompanyOperationsInstruction(
|
|
789
|
+
params: RegisterCompanyOperationsParams,
|
|
790
|
+
): { instruction: TransactionInstruction } {
|
|
791
|
+
const programId = params.programId ?? TREASURY_PROGRAM_ID;
|
|
792
|
+
const { pda: operationsPda } = deriveOperationsPda(
|
|
793
|
+
params.companyPda,
|
|
794
|
+
params.kind,
|
|
795
|
+
programId,
|
|
796
|
+
);
|
|
797
|
+
|
|
798
|
+
const data = Buffer.concat([
|
|
799
|
+
TREASURY_INSTRUCTION_DISCRIMINATOR.registerCompanyOperations,
|
|
800
|
+
// kind: u8 enum byte
|
|
801
|
+
Buffer.from([params.kind]),
|
|
802
|
+
encodePubkey(params.signer),
|
|
803
|
+
encodeVec(params.actionWhitelist, encodeAction),
|
|
804
|
+
u32LeBytes(params.rateLimitPerPeriod),
|
|
805
|
+
encodeI64(params.expiryUnix),
|
|
806
|
+
]);
|
|
807
|
+
|
|
808
|
+
const instruction = new TransactionInstruction({
|
|
809
|
+
programId,
|
|
810
|
+
keys: [
|
|
811
|
+
{ pubkey: params.companyPda, isSigner: false, isWritable: false },
|
|
812
|
+
{
|
|
813
|
+
pubkey: params.controllingAuthority,
|
|
814
|
+
isSigner: true,
|
|
815
|
+
isWritable: false,
|
|
816
|
+
},
|
|
817
|
+
{ pubkey: operationsPda, isSigner: false, isWritable: true },
|
|
818
|
+
{ pubkey: params.payer, isSigner: true, isWritable: true },
|
|
819
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
820
|
+
],
|
|
821
|
+
data,
|
|
822
|
+
});
|
|
823
|
+
return { instruction };
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// ── update_operations_capability ────────────────────────────────────────────
|
|
827
|
+
|
|
828
|
+
export interface UpdateOperationsCapabilityParams {
|
|
829
|
+
companyPda: PublicKey;
|
|
830
|
+
controllingAuthority: PublicKey;
|
|
831
|
+
/** Which OperationsAccount (by kind) to update. */
|
|
832
|
+
kind: OperationsKind;
|
|
833
|
+
/** Each field is independently optional — omitted = "leave unchanged". */
|
|
834
|
+
actionWhitelist?: (Uint8Array | Buffer)[];
|
|
835
|
+
rateLimitPerPeriod?: number;
|
|
836
|
+
/** Pass `0n` to set "no expiry" sentinel; `undefined` to leave unchanged. */
|
|
837
|
+
expiryUnix?: bigint;
|
|
838
|
+
programId?: PublicKey;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Build an `update_operations_capability` instruction — Privileged-class
|
|
843
|
+
* mutation of an existing OperationsAccount's whitelist / rate limit /
|
|
844
|
+
* expiry. Each param is Option-encoded.
|
|
845
|
+
*/
|
|
846
|
+
export function buildUpdateOperationsCapabilityInstruction(
|
|
847
|
+
params: UpdateOperationsCapabilityParams,
|
|
848
|
+
): { instruction: TransactionInstruction } {
|
|
849
|
+
const programId = params.programId ?? TREASURY_PROGRAM_ID;
|
|
850
|
+
const { pda: operationsPda } = deriveOperationsPda(
|
|
851
|
+
params.companyPda,
|
|
852
|
+
params.kind,
|
|
853
|
+
programId,
|
|
854
|
+
);
|
|
855
|
+
|
|
856
|
+
// Field order MUST match `UpdateOperationsCapabilityParams` struct.
|
|
857
|
+
const argBody = Buffer.concat([
|
|
858
|
+
encodeOption(params.actionWhitelist, (v) => encodeVec(v, encodeAction)),
|
|
859
|
+
encodeOption(params.rateLimitPerPeriod, u32LeBytes),
|
|
860
|
+
encodeOption(params.expiryUnix, encodeI64),
|
|
861
|
+
]);
|
|
862
|
+
|
|
863
|
+
const data = Buffer.concat([
|
|
864
|
+
TREASURY_INSTRUCTION_DISCRIMINATOR.updateOperationsCapability,
|
|
865
|
+
argBody,
|
|
866
|
+
]);
|
|
867
|
+
|
|
868
|
+
const instruction = new TransactionInstruction({
|
|
869
|
+
programId,
|
|
870
|
+
keys: [
|
|
871
|
+
{ pubkey: params.companyPda, isSigner: false, isWritable: false },
|
|
872
|
+
{
|
|
873
|
+
pubkey: params.controllingAuthority,
|
|
874
|
+
isSigner: true,
|
|
875
|
+
isWritable: false,
|
|
876
|
+
},
|
|
877
|
+
{ pubkey: operationsPda, isSigner: false, isWritable: true },
|
|
878
|
+
],
|
|
879
|
+
data,
|
|
880
|
+
});
|
|
881
|
+
return { instruction };
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// ── revoke_operations ───────────────────────────────────────────────────────
|
|
885
|
+
|
|
886
|
+
export interface RevokeOperationsParams {
|
|
887
|
+
companyPda: PublicKey;
|
|
888
|
+
controllingAuthority: PublicKey;
|
|
889
|
+
kind: OperationsKind;
|
|
890
|
+
programId?: PublicKey;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* Build a `revoke_operations` instruction — flips the `revoked` flag on
|
|
895
|
+
* the OperationsAccount. Subsequent ix attempts by the signer fail. The
|
|
896
|
+
* account stays open (rent retained); call `close_operations` to reclaim.
|
|
897
|
+
*/
|
|
898
|
+
export function buildRevokeOperationsInstruction(
|
|
899
|
+
params: RevokeOperationsParams,
|
|
900
|
+
): { instruction: TransactionInstruction } {
|
|
901
|
+
const programId = params.programId ?? TREASURY_PROGRAM_ID;
|
|
902
|
+
const { pda: operationsPda } = deriveOperationsPda(
|
|
903
|
+
params.companyPda,
|
|
904
|
+
params.kind,
|
|
905
|
+
programId,
|
|
906
|
+
);
|
|
907
|
+
|
|
908
|
+
const data = Buffer.from(TREASURY_INSTRUCTION_DISCRIMINATOR.revokeOperations);
|
|
909
|
+
|
|
910
|
+
const instruction = new TransactionInstruction({
|
|
911
|
+
programId,
|
|
912
|
+
keys: [
|
|
913
|
+
{ pubkey: params.companyPda, isSigner: false, isWritable: false },
|
|
914
|
+
{
|
|
915
|
+
pubkey: params.controllingAuthority,
|
|
916
|
+
isSigner: true,
|
|
917
|
+
isWritable: false,
|
|
918
|
+
},
|
|
919
|
+
{ pubkey: operationsPda, isSigner: false, isWritable: true },
|
|
920
|
+
],
|
|
921
|
+
data,
|
|
922
|
+
});
|
|
923
|
+
return { instruction };
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// ── close_operations ────────────────────────────────────────────────────────
|
|
927
|
+
|
|
928
|
+
export interface CloseOperationsParams {
|
|
929
|
+
companyPda: PublicKey;
|
|
930
|
+
/** Rent refund destination + signer. */
|
|
931
|
+
controllingAuthority: PublicKey;
|
|
932
|
+
kind: OperationsKind;
|
|
933
|
+
programId?: PublicKey;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* Build a `close_operations` instruction — closes the OperationsAccount
|
|
938
|
+
* and refunds rent to `controlling_authority`. Permanent: a new
|
|
939
|
+
* register call would create a fresh account at the same PDA.
|
|
940
|
+
*/
|
|
941
|
+
export function buildCloseOperationsInstruction(
|
|
942
|
+
params: CloseOperationsParams,
|
|
943
|
+
): { instruction: TransactionInstruction } {
|
|
944
|
+
const programId = params.programId ?? TREASURY_PROGRAM_ID;
|
|
945
|
+
const { pda: operationsPda } = deriveOperationsPda(
|
|
946
|
+
params.companyPda,
|
|
947
|
+
params.kind,
|
|
948
|
+
programId,
|
|
949
|
+
);
|
|
950
|
+
|
|
951
|
+
const data = Buffer.from(TREASURY_INSTRUCTION_DISCRIMINATOR.closeOperations);
|
|
952
|
+
|
|
953
|
+
const instruction = new TransactionInstruction({
|
|
954
|
+
programId,
|
|
955
|
+
keys: [
|
|
956
|
+
{ pubkey: params.companyPda, isSigner: false, isWritable: false },
|
|
957
|
+
{
|
|
958
|
+
pubkey: params.controllingAuthority,
|
|
959
|
+
isSigner: true,
|
|
960
|
+
isWritable: true,
|
|
961
|
+
},
|
|
962
|
+
{ pubkey: operationsPda, isSigner: false, isWritable: true },
|
|
963
|
+
],
|
|
964
|
+
data,
|
|
965
|
+
});
|
|
966
|
+
return { instruction };
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// ── disburse_routine ────────────────────────────────────────────────────────
|
|
970
|
+
|
|
971
|
+
export interface DisburseRoutineParams {
|
|
972
|
+
companyPda: PublicKey;
|
|
973
|
+
treasuryPda: PublicKey;
|
|
974
|
+
policyPda: PublicKey;
|
|
975
|
+
/** Disbursement-kind OperationsAccount for this company. */
|
|
976
|
+
operationsPda: PublicKey;
|
|
977
|
+
/** Hot wallet matching `operations.signer` — the only signer. NOT the
|
|
978
|
+
* controlling authority. */
|
|
979
|
+
operationsSigner: PublicKey;
|
|
980
|
+
/** Deployment PDA of the agent being paid. */
|
|
981
|
+
deploymentPda: PublicKey;
|
|
982
|
+
/** Agent's receiving_address. Chain verifies match against deployment. */
|
|
983
|
+
destination: PublicKey;
|
|
984
|
+
amountLamports: bigint;
|
|
985
|
+
/** Defaults to `SOL_PSEUDO_MINT`. */
|
|
986
|
+
mint?: PublicKey;
|
|
987
|
+
programId?: PublicKey;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* Build a `disburse_routine` instruction — Routine-class batched payout.
|
|
992
|
+
* Signed by the Disbursement Wallet (operations_signer), NOT the company
|
|
993
|
+
* owner. Whitelist + rate limit + expiry enforced on-chain by the
|
|
994
|
+
* OperationsAccount. The 3% Agent Operating Fee is deducted and routed
|
|
995
|
+
* to ProtocolFeeAccount.
|
|
996
|
+
*/
|
|
997
|
+
export function buildDisburseRoutineInstruction(
|
|
998
|
+
params: DisburseRoutineParams,
|
|
999
|
+
): { instruction: TransactionInstruction } {
|
|
1000
|
+
const programId = params.programId ?? TREASURY_PROGRAM_ID;
|
|
1001
|
+
const mint = params.mint ?? SOL_PSEUDO_MINT;
|
|
1002
|
+
const { pda: protocolFeePda } = deriveProtocolFeePda(programId);
|
|
1003
|
+
|
|
1004
|
+
const data = Buffer.concat([
|
|
1005
|
+
TREASURY_INSTRUCTION_DISCRIMINATOR.disburseRoutine,
|
|
1006
|
+
encodePubkey(mint),
|
|
1007
|
+
encodeU64(params.amountLamports),
|
|
1008
|
+
]);
|
|
1009
|
+
|
|
1010
|
+
const instruction = new TransactionInstruction({
|
|
1011
|
+
programId,
|
|
1012
|
+
// Order: company, treasury, policy, operations, operations_signer,
|
|
1013
|
+
// deployment, destination, protocol_fee_account.
|
|
1014
|
+
keys: [
|
|
1015
|
+
{ pubkey: params.companyPda, isSigner: false, isWritable: false },
|
|
1016
|
+
{ pubkey: params.treasuryPda, isSigner: false, isWritable: true },
|
|
1017
|
+
{ pubkey: params.policyPda, isSigner: false, isWritable: true },
|
|
1018
|
+
{ pubkey: params.operationsPda, isSigner: false, isWritable: true },
|
|
1019
|
+
{ pubkey: params.operationsSigner, isSigner: true, isWritable: false },
|
|
1020
|
+
{ pubkey: params.deploymentPda, isSigner: false, isWritable: false },
|
|
1021
|
+
{ pubkey: params.destination, isSigner: false, isWritable: true },
|
|
1022
|
+
{ pubkey: protocolFeePda, isSigner: false, isWritable: true },
|
|
1023
|
+
],
|
|
1024
|
+
data,
|
|
1025
|
+
});
|
|
1026
|
+
return { instruction };
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// ── disburse_privileged ─────────────────────────────────────────────────────
|
|
1030
|
+
|
|
1031
|
+
export interface DisbursePrivilegedParams {
|
|
1032
|
+
companyPda: PublicKey;
|
|
1033
|
+
treasuryPda: PublicKey;
|
|
1034
|
+
policyPda: PublicKey;
|
|
1035
|
+
/** Must equal `company.owner`. */
|
|
1036
|
+
controllingAuthority: PublicKey;
|
|
1037
|
+
/** Must equal `policy.secondary_signer` (which must be set, i.e. not
|
|
1038
|
+
* the default pubkey, for Privileged disbursements to work). */
|
|
1039
|
+
secondarySigner: PublicKey;
|
|
1040
|
+
/** Deployment of the recipient agent when `isAgentDestination = true`.
|
|
1041
|
+
* Still required as a read-only account even for external destinations
|
|
1042
|
+
* (chain verifies it belongs to the same company). */
|
|
1043
|
+
deploymentPda: PublicKey;
|
|
1044
|
+
destination: PublicKey;
|
|
1045
|
+
amountLamports: bigint;
|
|
1046
|
+
/** Whether the destination is an in-company agent (fee applies) or an
|
|
1047
|
+
* external party (no fee). */
|
|
1048
|
+
isAgentDestination: boolean;
|
|
1049
|
+
mint?: PublicKey;
|
|
1050
|
+
programId?: PublicKey;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* Build a `disburse_privileged` instruction — over-threshold or
|
|
1055
|
+
* externally-destined payout. Requires both the controlling authority
|
|
1056
|
+
* (owner) AND a secondary signer. Bypasses normal budget caps; logged
|
|
1057
|
+
* in the policy's privileged-spent counter regardless.
|
|
1058
|
+
*/
|
|
1059
|
+
export function buildDisbursePrivilegedInstruction(
|
|
1060
|
+
params: DisbursePrivilegedParams,
|
|
1061
|
+
): { instruction: TransactionInstruction } {
|
|
1062
|
+
const programId = params.programId ?? TREASURY_PROGRAM_ID;
|
|
1063
|
+
const mint = params.mint ?? SOL_PSEUDO_MINT;
|
|
1064
|
+
const { pda: protocolFeePda } = deriveProtocolFeePda(programId);
|
|
1065
|
+
|
|
1066
|
+
const data = Buffer.concat([
|
|
1067
|
+
TREASURY_INSTRUCTION_DISCRIMINATOR.disbursePrivileged,
|
|
1068
|
+
encodePubkey(mint),
|
|
1069
|
+
encodeU64(params.amountLamports),
|
|
1070
|
+
encodeBool(params.isAgentDestination),
|
|
1071
|
+
]);
|
|
1072
|
+
|
|
1073
|
+
const instruction = new TransactionInstruction({
|
|
1074
|
+
programId,
|
|
1075
|
+
// Order: company, controlling_authority, secondary_signer, treasury,
|
|
1076
|
+
// policy, deployment, destination, protocol_fee_account.
|
|
1077
|
+
keys: [
|
|
1078
|
+
{ pubkey: params.companyPda, isSigner: false, isWritable: false },
|
|
1079
|
+
{
|
|
1080
|
+
pubkey: params.controllingAuthority,
|
|
1081
|
+
isSigner: true,
|
|
1082
|
+
isWritable: false,
|
|
1083
|
+
},
|
|
1084
|
+
{ pubkey: params.secondarySigner, isSigner: true, isWritable: false },
|
|
1085
|
+
{ pubkey: params.treasuryPda, isSigner: false, isWritable: true },
|
|
1086
|
+
{ pubkey: params.policyPda, isSigner: false, isWritable: false },
|
|
1087
|
+
{ pubkey: params.deploymentPda, isSigner: false, isWritable: false },
|
|
1088
|
+
{ pubkey: params.destination, isSigner: false, isWritable: true },
|
|
1089
|
+
{ pubkey: protocolFeePda, isSigner: false, isWritable: true },
|
|
1090
|
+
],
|
|
1091
|
+
data,
|
|
1092
|
+
});
|
|
1093
|
+
return { instruction };
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// ── Registry: commit_daily_anchor ───────────────────────────────────────────
|
|
1097
|
+
|
|
1098
|
+
export interface CommitDailyAnchorParams {
|
|
1099
|
+
deploymentPda: PublicKey;
|
|
1100
|
+
companyPda: PublicKey;
|
|
1101
|
+
/** Anchor Wallet — must equal `operations.signer` (kind=Anchor). */
|
|
1102
|
+
anchorSigner: PublicKey;
|
|
1103
|
+
/** Anchor-kind OperationsAccount for this company (in TREASURY program,
|
|
1104
|
+
* read-only here). Caller derives via `deriveOperationsPda(company,
|
|
1105
|
+
* OPERATIONS_KIND.Anchor)`. */
|
|
1106
|
+
operationsPda: PublicKey;
|
|
1107
|
+
/** Unix seconds aligned to 00:00:00 UTC for the day this anchor covers
|
|
1108
|
+
* (multiple of 86_400). Becomes part of the DailyAnchor PDA seed. */
|
|
1109
|
+
dayUnix: bigint;
|
|
1110
|
+
/** Merkle root over the day's task hashes — 32 bytes. */
|
|
1111
|
+
merkleRoot: Uint8Array | Buffer;
|
|
1112
|
+
/** Number of leaves in the Merkle tree. Must be > 0. */
|
|
1113
|
+
taskCount: number;
|
|
1114
|
+
/** Rent payer (typically the operator hot wallet). */
|
|
1115
|
+
payer: PublicKey;
|
|
1116
|
+
programId?: PublicKey;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
/**
|
|
1120
|
+
* Build a `commit_daily_anchor` instruction — Anchor-class single-tx
|
|
1121
|
+
* commit of one agent-day's Merkle-rooted task activity. Signed by the
|
|
1122
|
+
* Anchor Wallet bound to the company's Anchor-kind OperationsAccount.
|
|
1123
|
+
*
|
|
1124
|
+
* Caller is responsible for:
|
|
1125
|
+
* 1. Hashing each task's content (Blake3) off-chain
|
|
1126
|
+
* 2. Building the Merkle tree
|
|
1127
|
+
* 3. Passing the root + leaf count here
|
|
1128
|
+
*
|
|
1129
|
+
* The on-chain handler does NOT verify the Merkle tree itself — chain
|
|
1130
|
+
* holds only the commitment; verification happens off-chain against the
|
|
1131
|
+
* trace store.
|
|
1132
|
+
*/
|
|
1133
|
+
export function buildCommitDailyAnchorInstruction(
|
|
1134
|
+
params: CommitDailyAnchorParams,
|
|
1135
|
+
): { instruction: TransactionInstruction } {
|
|
1136
|
+
const programId = params.programId ?? REGISTRY_PROGRAM_ID;
|
|
1137
|
+
if (params.merkleRoot.length !== 32) {
|
|
1138
|
+
throw new RangeError(
|
|
1139
|
+
`merkleRoot must be 32 bytes, got ${params.merkleRoot.length}`,
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
const { pda: dailyAnchorPda } = deriveDailyAnchorPda(
|
|
1143
|
+
params.deploymentPda,
|
|
1144
|
+
params.dayUnix,
|
|
1145
|
+
programId,
|
|
1146
|
+
);
|
|
1147
|
+
|
|
1148
|
+
const data = Buffer.concat([
|
|
1149
|
+
INSTRUCTION_DISCRIMINATOR.commitDailyAnchor,
|
|
1150
|
+
encodeI64(params.dayUnix),
|
|
1151
|
+
Buffer.from(params.merkleRoot),
|
|
1152
|
+
u32LeBytes(params.taskCount),
|
|
1153
|
+
]);
|
|
1154
|
+
|
|
1155
|
+
const instruction = new TransactionInstruction({
|
|
1156
|
+
programId,
|
|
1157
|
+
// Order: deployment, company, anchor_signer, operations, daily_anchor,
|
|
1158
|
+
// payer, system_program.
|
|
1159
|
+
keys: [
|
|
1160
|
+
{ pubkey: params.deploymentPda, isSigner: false, isWritable: false },
|
|
1161
|
+
{ pubkey: params.companyPda, isSigner: false, isWritable: false },
|
|
1162
|
+
{ pubkey: params.anchorSigner, isSigner: true, isWritable: false },
|
|
1163
|
+
{ pubkey: params.operationsPda, isSigner: false, isWritable: false },
|
|
1164
|
+
{ pubkey: dailyAnchorPda, isSigner: false, isWritable: true },
|
|
1165
|
+
{ pubkey: params.payer, isSigner: true, isWritable: true },
|
|
1166
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
1167
|
+
],
|
|
1168
|
+
data,
|
|
1169
|
+
});
|
|
1170
|
+
return { instruction };
|
|
1171
|
+
}
|