@intentlayer/sdk 0.1.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/LICENSE +21 -0
- package/README.md +259 -0
- package/dist/agentConstraintBuilder-CK56UDUI.mjs +9 -0
- package/dist/chunk-ABTKEYXQ.mjs +227 -0
- package/dist/chunk-E4GOMRS5.mjs +205 -0
- package/dist/chunk-GRBEB2EV.mjs +1112 -0
- package/dist/chunk-XP4I75K7.mjs +21 -0
- package/dist/index.d.mts +8317 -0
- package/dist/index.d.ts +8317 -0
- package/dist/index.js +10353 -0
- package/dist/index.mjs +8613 -0
- package/dist/policyBootstrapBuilder-RJOUS67N.mjs +25 -0
- package/dist/registryBootstrapBuilder-AUWE7EWJ.mjs +29 -0
- package/package.json +61 -0
|
@@ -0,0 +1,1112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__esm
|
|
3
|
+
} from "./chunk-XP4I75K7.mjs";
|
|
4
|
+
|
|
5
|
+
// src/constraints.ts
|
|
6
|
+
import { keccak256, encodePacked, decodeAbiParameters } from "viem";
|
|
7
|
+
function toBigEndianUint128(value) {
|
|
8
|
+
if (value >= 1n << 128n) {
|
|
9
|
+
throw new Error("Value exceeds uint128 maximum");
|
|
10
|
+
}
|
|
11
|
+
const shifted = value << 128n;
|
|
12
|
+
const hex = shifted.toString(16).padStart(64, "0");
|
|
13
|
+
const high16Bytes = hex.slice(0, 32);
|
|
14
|
+
return `0x${high16Bytes}`;
|
|
15
|
+
}
|
|
16
|
+
function toBigEndianUint48(value) {
|
|
17
|
+
if (value >= 281474976710656) {
|
|
18
|
+
throw new Error("Value exceeds uint48 maximum");
|
|
19
|
+
}
|
|
20
|
+
const shifted = BigInt(value) << 208n;
|
|
21
|
+
const hex = shifted.toString(16).padStart(64, "0");
|
|
22
|
+
const high6Bytes = hex.slice(0, 12);
|
|
23
|
+
return `0x${high6Bytes}`;
|
|
24
|
+
}
|
|
25
|
+
function toBigEndianUint32(value) {
|
|
26
|
+
if (value >= 4294967296) {
|
|
27
|
+
throw new Error("Value exceeds uint32 maximum");
|
|
28
|
+
}
|
|
29
|
+
const shifted = BigInt(value) << 224n;
|
|
30
|
+
const hex = shifted.toString(16).padStart(64, "0");
|
|
31
|
+
const high4Bytes = hex.slice(0, 8);
|
|
32
|
+
return `0x${high4Bytes}`;
|
|
33
|
+
}
|
|
34
|
+
function toBigEndianInt16(value) {
|
|
35
|
+
if (value < -32768 || value > 32767) {
|
|
36
|
+
throw new Error("Value exceeds int16 range");
|
|
37
|
+
}
|
|
38
|
+
const unsignedValue = value < 0 ? 65536 + value : value;
|
|
39
|
+
const shifted = BigInt(unsignedValue) << 240n;
|
|
40
|
+
const hex = shifted.toString(16).padStart(64, "0");
|
|
41
|
+
const high2Bytes = hex.slice(0, 4);
|
|
42
|
+
return `0x${high2Bytes}`;
|
|
43
|
+
}
|
|
44
|
+
function toBigEndianUint16(value) {
|
|
45
|
+
if (value >= 65536) {
|
|
46
|
+
throw new Error("Value exceeds uint16 maximum");
|
|
47
|
+
}
|
|
48
|
+
const shifted = BigInt(value) << 240n;
|
|
49
|
+
const hex = shifted.toString(16).padStart(64, "0");
|
|
50
|
+
const high2Bytes = hex.slice(0, 4);
|
|
51
|
+
return `0x${high2Bytes}`;
|
|
52
|
+
}
|
|
53
|
+
function createConstraintBuilder() {
|
|
54
|
+
return new CompoundLeafBuilder();
|
|
55
|
+
}
|
|
56
|
+
function getLeafAndProofForRecipient(policy, recipient, asset) {
|
|
57
|
+
const assetKey = asset || "0x0000000000000000000000000000000000000000";
|
|
58
|
+
const key = `${recipient.toLowerCase()}-${assetKey.toLowerCase()}`;
|
|
59
|
+
const proof = policy.leafProofs[key];
|
|
60
|
+
return proof || null;
|
|
61
|
+
}
|
|
62
|
+
function isRecipientAllowed(policy, recipient, asset) {
|
|
63
|
+
const assetKey = asset || "0x0000000000000000000000000000000000000000";
|
|
64
|
+
const key = `${recipient.toLowerCase()}-${assetKey.toLowerCase()}`;
|
|
65
|
+
return key in policy.leafProofs;
|
|
66
|
+
}
|
|
67
|
+
function buildConstraintCalldata(wallet, asset, recipient, amount) {
|
|
68
|
+
if (amount >= 1n << 128n) {
|
|
69
|
+
throw new Error("Amount exceeds uint128 maximum");
|
|
70
|
+
}
|
|
71
|
+
return encodePacked(
|
|
72
|
+
["address", "address", "address", "uint128"],
|
|
73
|
+
[wallet, asset, recipient, amount]
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
function buildConstraintCalldataFromIntent(intent, wallet) {
|
|
77
|
+
return buildConstraintCalldata(
|
|
78
|
+
wallet,
|
|
79
|
+
intent.asset,
|
|
80
|
+
intent.recipient,
|
|
81
|
+
intent.amount
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
function extractConstraintDataFromOperations(operationsBundle, owner) {
|
|
85
|
+
try {
|
|
86
|
+
const operations = decodeAbiParameters(
|
|
87
|
+
[{
|
|
88
|
+
type: "tuple[]",
|
|
89
|
+
components: [
|
|
90
|
+
{ type: "uint8", name: "opType" },
|
|
91
|
+
{ type: "address", name: "target" },
|
|
92
|
+
{ type: "uint256", name: "value" },
|
|
93
|
+
{ type: "bytes", name: "data" }
|
|
94
|
+
]
|
|
95
|
+
}],
|
|
96
|
+
operationsBundle
|
|
97
|
+
)[0];
|
|
98
|
+
const transferOp = operations.find((op) => op.opType === 2);
|
|
99
|
+
if (!transferOp) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const [recipient, amount] = decodeAbiParameters(
|
|
103
|
+
[{ type: "address" }, { type: "uint256" }],
|
|
104
|
+
transferOp.data
|
|
105
|
+
);
|
|
106
|
+
const token = transferOp.target;
|
|
107
|
+
const constraintCalldata = buildConstraintCalldataFromIntent(
|
|
108
|
+
{ asset: token, recipient, amount },
|
|
109
|
+
owner
|
|
110
|
+
);
|
|
111
|
+
return {
|
|
112
|
+
constraintCalldata,
|
|
113
|
+
token,
|
|
114
|
+
recipient,
|
|
115
|
+
amount
|
|
116
|
+
};
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error("Failed to extract constraint data from operations:", error);
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
var DOMAIN_PREFIX2, TAG_COMPOUND_EMPLOYEE2, COMPOUND_LEAF_VERSION, FLAG_RECIPIENT, FLAG_CAP, FLAG_DEADLINE, FLAG_ASSET, FLAG_BUCKET, FLAG_PERIOD, FLAG_VENDOR_GROUP, DOMAIN_V3, VERSION_V3, TAG_COMPOUND_V3, FLAG_TARGET, FLAG_SELECTOR_LIST, FLAG_NO_BRIDGE, FLAG_OPS_HASH, PERIOD_NONE2, PERIOD_DAILY2, PERIOD_WEEKLY2, PERIOD_MONTH302, PERIOD_CUSTOM2, CompoundLeafBuilder, CompoundLeafV3Builder;
|
|
123
|
+
var init_constraints = __esm({
|
|
124
|
+
"src/constraints.ts"() {
|
|
125
|
+
"use strict";
|
|
126
|
+
DOMAIN_PREFIX2 = keccak256("IEL_CONSTRAINT_LEAF_V1").slice(0, 10);
|
|
127
|
+
TAG_COMPOUND_EMPLOYEE2 = 16;
|
|
128
|
+
COMPOUND_LEAF_VERSION = 1;
|
|
129
|
+
FLAG_RECIPIENT = 1;
|
|
130
|
+
FLAG_CAP = 2;
|
|
131
|
+
FLAG_DEADLINE = 4;
|
|
132
|
+
FLAG_ASSET = 8;
|
|
133
|
+
FLAG_BUCKET = 16;
|
|
134
|
+
FLAG_PERIOD = 32;
|
|
135
|
+
FLAG_VENDOR_GROUP = 64;
|
|
136
|
+
DOMAIN_V3 = "0x49454C33";
|
|
137
|
+
VERSION_V3 = 3;
|
|
138
|
+
TAG_COMPOUND_V3 = 32;
|
|
139
|
+
FLAG_TARGET = 2048;
|
|
140
|
+
FLAG_SELECTOR_LIST = 4096;
|
|
141
|
+
FLAG_NO_BRIDGE = 8192;
|
|
142
|
+
FLAG_OPS_HASH = 16384;
|
|
143
|
+
PERIOD_NONE2 = 0;
|
|
144
|
+
PERIOD_DAILY2 = 1;
|
|
145
|
+
PERIOD_WEEKLY2 = 2;
|
|
146
|
+
PERIOD_MONTH302 = 3;
|
|
147
|
+
PERIOD_CUSTOM2 = 4;
|
|
148
|
+
CompoundLeafBuilder = class {
|
|
149
|
+
/**
|
|
150
|
+
* Build compound constraint leaf with TLV encoding
|
|
151
|
+
*
|
|
152
|
+
* Format: [4B domain_prefix][1B tag][1B version][1B flags][conditional_fields...]
|
|
153
|
+
* Matches foundry test _buildCompoundLeaf exactly
|
|
154
|
+
*/
|
|
155
|
+
buildCompoundLeaf(constraints) {
|
|
156
|
+
let flags = 0;
|
|
157
|
+
if (constraints.allowedAsset) {
|
|
158
|
+
if (!constraints.allowedAsset.match(/^0x[a-fA-F0-9]{40}$/)) {
|
|
159
|
+
throw new Error(`Invalid asset address: ${constraints.allowedAsset}`);
|
|
160
|
+
}
|
|
161
|
+
flags |= FLAG_ASSET;
|
|
162
|
+
}
|
|
163
|
+
if (constraints.allowedRecipient) {
|
|
164
|
+
if (!constraints.allowedRecipient.match(/^0x[a-fA-F0-9]{40}$/)) {
|
|
165
|
+
throw new Error(`Invalid recipient address: ${constraints.allowedRecipient}`);
|
|
166
|
+
}
|
|
167
|
+
flags |= FLAG_RECIPIENT;
|
|
168
|
+
}
|
|
169
|
+
if (constraints.maxAmount !== void 0) {
|
|
170
|
+
if (constraints.maxAmount <= 0n) {
|
|
171
|
+
throw new Error("Amount must be positive");
|
|
172
|
+
}
|
|
173
|
+
if (constraints.maxAmount >= 1n << 128n) {
|
|
174
|
+
throw new Error("Amount exceeds uint128 maximum");
|
|
175
|
+
}
|
|
176
|
+
flags |= FLAG_CAP;
|
|
177
|
+
}
|
|
178
|
+
if (constraints.deadline !== void 0) {
|
|
179
|
+
const currentTimestamp = Math.floor(Date.now() / 1e3);
|
|
180
|
+
if (constraints.deadline <= currentTimestamp) {
|
|
181
|
+
throw new Error("Deadline must be in the future");
|
|
182
|
+
}
|
|
183
|
+
const maxUint48 = 2n ** 48n - 1n;
|
|
184
|
+
if (BigInt(constraints.deadline) >= maxUint48) {
|
|
185
|
+
throw new Error("Deadline exceeds uint48 maximum");
|
|
186
|
+
}
|
|
187
|
+
flags |= FLAG_DEADLINE;
|
|
188
|
+
}
|
|
189
|
+
if (constraints.bucketId) {
|
|
190
|
+
if (!constraints.bucketId.match(/^0x[a-fA-F0-9]{64}$/)) {
|
|
191
|
+
throw new Error(`Invalid bucketId format: ${constraints.bucketId}`);
|
|
192
|
+
}
|
|
193
|
+
flags |= FLAG_BUCKET;
|
|
194
|
+
}
|
|
195
|
+
if (constraints.periodCode !== void 0) {
|
|
196
|
+
console.log("\u{1F50D} SDK DEBUG: Period constraint validation triggered");
|
|
197
|
+
console.log("\u{1F50D} SDK DEBUG: periodCode:", constraints.periodCode);
|
|
198
|
+
console.log("\u{1F50D} SDK DEBUG: FLAG_PERIOD value:", FLAG_PERIOD);
|
|
199
|
+
if (constraints.periodCode < 0 || constraints.periodCode > 4) {
|
|
200
|
+
throw new Error(`Invalid period code: ${constraints.periodCode}`);
|
|
201
|
+
}
|
|
202
|
+
flags |= FLAG_PERIOD;
|
|
203
|
+
console.log("\u{1F50D} SDK DEBUG: flags after adding FLAG_PERIOD:", flags);
|
|
204
|
+
if (constraints.offsetMinutes !== void 0) {
|
|
205
|
+
if (constraints.offsetMinutes < -720 || constraints.offsetMinutes > 840) {
|
|
206
|
+
throw new Error(`Invalid UTC offset: ${constraints.offsetMinutes} (must be -720 to +840)`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (constraints.periodCode === PERIOD_CUSTOM2 && !constraints.customSeconds) {
|
|
210
|
+
throw new Error("Custom period requires customSeconds");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (constraints.vendorGroupId) {
|
|
214
|
+
if (!constraints.vendorGroupId.match(/^0x[a-fA-F0-9]{64}$/)) {
|
|
215
|
+
throw new Error(`Invalid vendorGroupId format: ${constraints.vendorGroupId}`);
|
|
216
|
+
}
|
|
217
|
+
flags |= FLAG_VENDOR_GROUP;
|
|
218
|
+
}
|
|
219
|
+
let leaf = encodePacked(
|
|
220
|
+
["bytes4", "uint8", "uint8", "uint8"],
|
|
221
|
+
[DOMAIN_PREFIX2, TAG_COMPOUND_EMPLOYEE2, COMPOUND_LEAF_VERSION, flags]
|
|
222
|
+
);
|
|
223
|
+
if (flags & FLAG_ASSET) {
|
|
224
|
+
leaf = encodePacked(
|
|
225
|
+
["bytes", "address"],
|
|
226
|
+
[leaf, constraints.allowedAsset]
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
if (flags & FLAG_RECIPIENT) {
|
|
230
|
+
leaf = encodePacked(
|
|
231
|
+
["bytes", "address"],
|
|
232
|
+
[leaf, constraints.allowedRecipient]
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
if (flags & FLAG_CAP) {
|
|
236
|
+
const capBigEndian = toBigEndianUint128(constraints.maxAmount);
|
|
237
|
+
leaf = encodePacked(
|
|
238
|
+
["bytes", "bytes"],
|
|
239
|
+
[leaf, capBigEndian]
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
if (flags & FLAG_DEADLINE) {
|
|
243
|
+
const deadlineBigEndian = toBigEndianUint48(constraints.deadline);
|
|
244
|
+
leaf = encodePacked(
|
|
245
|
+
["bytes", "bytes"],
|
|
246
|
+
[leaf, deadlineBigEndian]
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
if (flags & FLAG_BUCKET) {
|
|
250
|
+
leaf = encodePacked(
|
|
251
|
+
["bytes", "bytes32"],
|
|
252
|
+
[leaf, constraints.bucketId]
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (flags & FLAG_PERIOD) {
|
|
256
|
+
leaf = encodePacked(
|
|
257
|
+
["bytes", "uint8"],
|
|
258
|
+
[leaf, constraints.periodCode]
|
|
259
|
+
);
|
|
260
|
+
const offset = constraints.offsetMinutes ?? 0;
|
|
261
|
+
const offsetBigEndian = toBigEndianInt16(offset);
|
|
262
|
+
leaf = encodePacked(
|
|
263
|
+
["bytes", "bytes"],
|
|
264
|
+
[leaf, offsetBigEndian]
|
|
265
|
+
);
|
|
266
|
+
if (constraints.periodCode === PERIOD_CUSTOM2) {
|
|
267
|
+
const customSecondsBigEndian = toBigEndianUint32(constraints.customSeconds);
|
|
268
|
+
leaf = encodePacked(
|
|
269
|
+
["bytes", "bytes"],
|
|
270
|
+
[leaf, customSecondsBigEndian]
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (flags & FLAG_VENDOR_GROUP) {
|
|
275
|
+
leaf = encodePacked(
|
|
276
|
+
["bytes", "bytes32"],
|
|
277
|
+
[leaf, constraints.vendorGroupId]
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
flags,
|
|
282
|
+
asset: constraints.allowedAsset,
|
|
283
|
+
recipient: constraints.allowedRecipient,
|
|
284
|
+
cap: constraints.maxAmount,
|
|
285
|
+
deadline: constraints.deadline,
|
|
286
|
+
bucketId: constraints.bucketId,
|
|
287
|
+
periodCode: constraints.periodCode,
|
|
288
|
+
offsetMinutes: constraints.offsetMinutes,
|
|
289
|
+
customSeconds: constraints.customSeconds,
|
|
290
|
+
vendorGroupId: constraints.vendorGroupId,
|
|
291
|
+
encoded: leaf
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Create compound constraint policy
|
|
296
|
+
*
|
|
297
|
+
* High-level helper that builds compound leaf and returns everything needed
|
|
298
|
+
* for policy registration. Handles both single and multiple recipients.
|
|
299
|
+
*/
|
|
300
|
+
createConstrainedPolicy(walletAddress, constraints) {
|
|
301
|
+
if (constraints.allowedRecipients && constraints.allowedRecipients.length > 1) {
|
|
302
|
+
throw new Error("Use createMultiRecipientPolicy() for multiple recipients");
|
|
303
|
+
}
|
|
304
|
+
const recipient = constraints.allowedRecipients?.[0] || constraints.allowedRecipient;
|
|
305
|
+
const normalizedConstraints = {
|
|
306
|
+
allowedAsset: constraints.allowedAsset,
|
|
307
|
+
allowedRecipient: recipient,
|
|
308
|
+
maxAmount: constraints.maxAmount,
|
|
309
|
+
deadline: constraints.deadline,
|
|
310
|
+
bucketId: constraints.bucketId,
|
|
311
|
+
// Week 1 additions
|
|
312
|
+
periodCode: constraints.periodCode,
|
|
313
|
+
offsetMinutes: constraints.offsetMinutes,
|
|
314
|
+
customSeconds: constraints.customSeconds,
|
|
315
|
+
vendorGroupId: constraints.vendorGroupId
|
|
316
|
+
};
|
|
317
|
+
const hasConstraints = normalizedConstraints.allowedAsset || normalizedConstraints.allowedRecipient || normalizedConstraints.maxAmount !== void 0 || normalizedConstraints.deadline !== void 0 || normalizedConstraints.bucketId || normalizedConstraints.periodCode !== void 0 || normalizedConstraints.vendorGroupId;
|
|
318
|
+
if (!hasConstraints) {
|
|
319
|
+
return {
|
|
320
|
+
policyId: keccak256(walletAddress),
|
|
321
|
+
merkleRoot: "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
322
|
+
leafAndProof: "0x",
|
|
323
|
+
// Empty bytes for no constraints
|
|
324
|
+
policyRootVersion: 1,
|
|
325
|
+
// Compound leaf version
|
|
326
|
+
leaf: {
|
|
327
|
+
flags: 0,
|
|
328
|
+
encoded: "0x"
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
const leaf = this.buildCompoundLeaf(normalizedConstraints);
|
|
333
|
+
const merkleRoot = keccak256(leaf.encoded);
|
|
334
|
+
return {
|
|
335
|
+
policyId: keccak256(walletAddress),
|
|
336
|
+
merkleRoot,
|
|
337
|
+
leafAndProof: leaf.encoded,
|
|
338
|
+
// Single leaf, no separate proof needed
|
|
339
|
+
policyRootVersion: 1,
|
|
340
|
+
// Compound leaf version
|
|
341
|
+
leaf
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Build asset-only constraint (common case)
|
|
346
|
+
*/
|
|
347
|
+
buildAssetConstraint(asset) {
|
|
348
|
+
return this.buildCompoundLeaf({ allowedAsset: asset });
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Build recipient-only constraint (common case)
|
|
352
|
+
*/
|
|
353
|
+
buildRecipientConstraint(recipient) {
|
|
354
|
+
return this.buildCompoundLeaf({ allowedRecipient: recipient });
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Build cap-only constraint (common case)
|
|
358
|
+
*/
|
|
359
|
+
buildCapConstraint(maxAmount) {
|
|
360
|
+
return this.buildCompoundLeaf({ maxAmount });
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Build asset + recipient constraint (common case)
|
|
364
|
+
*/
|
|
365
|
+
buildAssetRecipientConstraint(asset, recipient) {
|
|
366
|
+
return this.buildCompoundLeaf({
|
|
367
|
+
allowedAsset: asset,
|
|
368
|
+
allowedRecipient: recipient
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Build recipient + cap constraint (common case)
|
|
373
|
+
*/
|
|
374
|
+
buildRecipientCapConstraint(recipient, maxAmount) {
|
|
375
|
+
return this.buildCompoundLeaf({
|
|
376
|
+
allowedRecipient: recipient,
|
|
377
|
+
maxAmount
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Build full constraint with all fields (common case)
|
|
382
|
+
*/
|
|
383
|
+
buildFullConstraint(asset, recipient, maxAmount, deadline, bucketId) {
|
|
384
|
+
return this.buildCompoundLeaf({
|
|
385
|
+
allowedAsset: asset,
|
|
386
|
+
allowedRecipient: recipient,
|
|
387
|
+
maxAmount,
|
|
388
|
+
deadline,
|
|
389
|
+
bucketId
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Create compound leaf with proper validation (alternative to buildCompoundLeaf)
|
|
394
|
+
*
|
|
395
|
+
* Provides a cleaner interface for creating individual compound leaves
|
|
396
|
+
*/
|
|
397
|
+
createCompoundLeaf(constraints) {
|
|
398
|
+
if (!constraints.asset && !constraints.recipient && constraints.maxAmount === void 0 && constraints.deadline === void 0 && !constraints.bucketId) {
|
|
399
|
+
throw new Error("At least one constraint must be specified");
|
|
400
|
+
}
|
|
401
|
+
const policyConstraints = {
|
|
402
|
+
allowedAsset: constraints.asset,
|
|
403
|
+
allowedRecipient: constraints.recipient,
|
|
404
|
+
maxAmount: constraints.maxAmount,
|
|
405
|
+
deadline: constraints.deadline,
|
|
406
|
+
bucketId: constraints.bucketId
|
|
407
|
+
};
|
|
408
|
+
return this.buildCompoundLeaf(policyConstraints);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Build period constraint
|
|
412
|
+
*/
|
|
413
|
+
buildPeriodConstraint(periodCode, offsetMinutes = 0, customSeconds) {
|
|
414
|
+
return this.buildCompoundLeaf({
|
|
415
|
+
periodCode,
|
|
416
|
+
offsetMinutes,
|
|
417
|
+
customSeconds
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Build vendor group constraint
|
|
422
|
+
*/
|
|
423
|
+
buildVendorGroupConstraint(vendorGroupId) {
|
|
424
|
+
return this.buildCompoundLeaf({ vendorGroupId });
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Build period + cap constraint (common pattern)
|
|
428
|
+
*/
|
|
429
|
+
buildPeriodCapConstraint(periodCode, maxAmount, offsetMinutes = 0) {
|
|
430
|
+
return this.buildCompoundLeaf({
|
|
431
|
+
periodCode,
|
|
432
|
+
offsetMinutes,
|
|
433
|
+
maxAmount
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Build vendor group + cap + period constraint (enterprise pattern)
|
|
438
|
+
*/
|
|
439
|
+
buildVendorGroupCapPeriodConstraint(vendorGroupId, maxAmount, periodCode, offsetMinutes = 0) {
|
|
440
|
+
return this.buildCompoundLeaf({
|
|
441
|
+
vendorGroupId,
|
|
442
|
+
maxAmount,
|
|
443
|
+
periodCode,
|
|
444
|
+
offsetMinutes
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Generate vendor group ID from string (helper)
|
|
449
|
+
*/
|
|
450
|
+
static generateVendorGroupId(name) {
|
|
451
|
+
return keccak256(encodePacked(["string"], [`VENDOR_GROUP_${name}`]));
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Create multi-recipient policy with Merkle tree
|
|
455
|
+
*
|
|
456
|
+
* This creates a policy that supports multiple recipients by building a Merkle tree
|
|
457
|
+
* where each leaf represents one recipient constraint.
|
|
458
|
+
*
|
|
459
|
+
* Supports two call patterns:
|
|
460
|
+
* 1. Same constraints for all recipients: createMultiRecipientPolicy(wallet, PolicyConstraints)
|
|
461
|
+
* 2. Different constraints per recipient: createMultiRecipientPolicy(wallet, RecipientConstraint[])
|
|
462
|
+
*/
|
|
463
|
+
createMultiRecipientPolicy(walletAddress, constraintsOrRecipients) {
|
|
464
|
+
let recipients;
|
|
465
|
+
let leaves;
|
|
466
|
+
if (Array.isArray(constraintsOrRecipients)) {
|
|
467
|
+
if (constraintsOrRecipients.length === 0) {
|
|
468
|
+
throw new Error("At least one recipient must be specified for multi-recipient policy");
|
|
469
|
+
}
|
|
470
|
+
const recipientAssetSet = /* @__PURE__ */ new Set();
|
|
471
|
+
for (const item of constraintsOrRecipients) {
|
|
472
|
+
const key = `${item.recipient.toLowerCase()}-${item.asset || "none"}`;
|
|
473
|
+
if (recipientAssetSet.has(key)) {
|
|
474
|
+
throw new Error(`Duplicate recipient-asset combination: ${item.recipient} with asset ${item.asset || "none"}`);
|
|
475
|
+
}
|
|
476
|
+
recipientAssetSet.add(key);
|
|
477
|
+
}
|
|
478
|
+
recipients = constraintsOrRecipients.map((item) => item.recipient);
|
|
479
|
+
leaves = constraintsOrRecipients.map((item) => {
|
|
480
|
+
const leafConstraints = {
|
|
481
|
+
allowedRecipient: item.recipient,
|
|
482
|
+
maxAmount: item.maxAmount,
|
|
483
|
+
deadline: item.deadline,
|
|
484
|
+
allowedAsset: item.asset
|
|
485
|
+
};
|
|
486
|
+
return this.buildCompoundLeaf(leafConstraints);
|
|
487
|
+
});
|
|
488
|
+
} else {
|
|
489
|
+
const constraints = constraintsOrRecipients;
|
|
490
|
+
recipients = constraints.allowedRecipients || (constraints.allowedRecipient ? [constraints.allowedRecipient] : []);
|
|
491
|
+
if (recipients.length === 0) {
|
|
492
|
+
throw new Error("At least one recipient must be specified for multi-recipient policy");
|
|
493
|
+
}
|
|
494
|
+
leaves = recipients.map((recipient) => {
|
|
495
|
+
const leafConstraints = {
|
|
496
|
+
allowedAsset: constraints.allowedAsset,
|
|
497
|
+
allowedRecipient: recipient,
|
|
498
|
+
maxAmount: constraints.maxAmount,
|
|
499
|
+
deadline: constraints.deadline,
|
|
500
|
+
bucketId: constraints.bucketId
|
|
501
|
+
};
|
|
502
|
+
return this.buildCompoundLeaf(leafConstraints);
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
const { merkleRoot, leafProofs } = this.buildMerkleTree(leaves, recipients);
|
|
506
|
+
return {
|
|
507
|
+
policyId: keccak256(walletAddress),
|
|
508
|
+
merkleRoot,
|
|
509
|
+
leaves,
|
|
510
|
+
leafProofs,
|
|
511
|
+
policyRootVersion: 1
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Build Merkle tree from compound leaves
|
|
516
|
+
*
|
|
517
|
+
* Uses sorted pair hashing to match ConstraintLib.sol implementation
|
|
518
|
+
*/
|
|
519
|
+
buildMerkleTree(leaves, recipients) {
|
|
520
|
+
if (leaves.length === 0) {
|
|
521
|
+
throw new Error("Cannot build Merkle tree with no leaves");
|
|
522
|
+
}
|
|
523
|
+
if (leaves.length === 1) {
|
|
524
|
+
const recipient = recipients?.[0] || leaves[0].recipient;
|
|
525
|
+
if (!recipient) {
|
|
526
|
+
throw new Error("Recipient must be specified for single leaf tree");
|
|
527
|
+
}
|
|
528
|
+
const asset = leaves[0].asset ? leaves[0].asset.toLowerCase() : "*";
|
|
529
|
+
const key = `${recipient.toLowerCase()}-${asset}`;
|
|
530
|
+
return {
|
|
531
|
+
merkleRoot: keccak256(leaves[0].encoded),
|
|
532
|
+
leafProofs: {
|
|
533
|
+
[key]: leaves[0].encoded
|
|
534
|
+
// No proof needed for single leaf
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
const leafHashes = leaves.map((leaf, index) => ({
|
|
539
|
+
hash: keccak256(leaf.encoded),
|
|
540
|
+
encoded: leaf.encoded,
|
|
541
|
+
originalIndex: index
|
|
542
|
+
}));
|
|
543
|
+
leafHashes.sort((a, b) => a.hash.localeCompare(b.hash));
|
|
544
|
+
const tree = [leafHashes.map((lh) => lh.hash)];
|
|
545
|
+
while (tree[tree.length - 1].length > 1) {
|
|
546
|
+
const currentLevel = tree[tree.length - 1];
|
|
547
|
+
const nextLevel = [];
|
|
548
|
+
for (let i = 0; i < currentLevel.length; i += 2) {
|
|
549
|
+
const left = currentLevel[i];
|
|
550
|
+
const right = i + 1 < currentLevel.length ? currentLevel[i + 1] : left;
|
|
551
|
+
const hash = left <= right ? keccak256(encodePacked(["bytes32", "bytes32"], [left, right])) : keccak256(encodePacked(["bytes32", "bytes32"], [right, left]));
|
|
552
|
+
nextLevel.push(hash);
|
|
553
|
+
}
|
|
554
|
+
tree.push(nextLevel);
|
|
555
|
+
}
|
|
556
|
+
const merkleRoot = tree[tree.length - 1][0];
|
|
557
|
+
const leafProofs = {};
|
|
558
|
+
for (let sortedIndex = 0; sortedIndex < leafHashes.length; sortedIndex++) {
|
|
559
|
+
const leafHash = leafHashes[sortedIndex];
|
|
560
|
+
const originalIndex = leafHash.originalIndex;
|
|
561
|
+
const proof = this.generateMerkleProof(tree, sortedIndex);
|
|
562
|
+
const leafAndProof = encodePacked(
|
|
563
|
+
["bytes", "bytes"],
|
|
564
|
+
[leafHash.encoded, proof]
|
|
565
|
+
);
|
|
566
|
+
const recipient = recipients?.[originalIndex] || leaves[originalIndex].recipient;
|
|
567
|
+
if (!recipient) {
|
|
568
|
+
throw new Error(`Recipient must be specified for leaf ${originalIndex}`);
|
|
569
|
+
}
|
|
570
|
+
const asset = leaves[originalIndex].asset ? leaves[originalIndex].asset.toLowerCase() : "*";
|
|
571
|
+
const key = `${recipient.toLowerCase()}-${asset}`;
|
|
572
|
+
leafProofs[key] = leafAndProof;
|
|
573
|
+
}
|
|
574
|
+
return {
|
|
575
|
+
merkleRoot,
|
|
576
|
+
leafProofs,
|
|
577
|
+
tree: { root: merkleRoot, leaves: leafHashes }
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Generate Merkle proof for a leaf at given index
|
|
582
|
+
*/
|
|
583
|
+
generateMerkleProof(tree, leafIndex) {
|
|
584
|
+
let proof = "0x";
|
|
585
|
+
let currentIndex = leafIndex;
|
|
586
|
+
for (let level = 0; level < tree.length - 1; level++) {
|
|
587
|
+
const currentLevel = tree[level];
|
|
588
|
+
const isRightNode = currentIndex % 2 === 1;
|
|
589
|
+
let siblingIndex;
|
|
590
|
+
if (isRightNode) {
|
|
591
|
+
siblingIndex = currentIndex - 1;
|
|
592
|
+
} else {
|
|
593
|
+
siblingIndex = currentIndex + 1;
|
|
594
|
+
if (siblingIndex >= currentLevel.length) {
|
|
595
|
+
siblingIndex = currentIndex;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const sibling = currentLevel[siblingIndex];
|
|
599
|
+
proof = encodePacked(["bytes", "bytes32"], [proof, sibling]);
|
|
600
|
+
currentIndex = Math.floor(currentIndex / 2);
|
|
601
|
+
}
|
|
602
|
+
return proof;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Generate proof for a leaf at specific index (alternative interface)
|
|
606
|
+
*/
|
|
607
|
+
generateProof(tree, leafIndex) {
|
|
608
|
+
const hashes = tree.leaves.map((l) => keccak256(l.encoded));
|
|
609
|
+
const treeStructure = [hashes];
|
|
610
|
+
while (treeStructure[treeStructure.length - 1].length > 1) {
|
|
611
|
+
const currentLevel = treeStructure[treeStructure.length - 1];
|
|
612
|
+
const nextLevel = [];
|
|
613
|
+
for (let i = 0; i < currentLevel.length; i += 2) {
|
|
614
|
+
const left = currentLevel[i];
|
|
615
|
+
const right = i + 1 < currentLevel.length ? currentLevel[i + 1] : left;
|
|
616
|
+
const hash = left <= right ? keccak256(encodePacked(["bytes32", "bytes32"], [left, right])) : keccak256(encodePacked(["bytes32", "bytes32"], [right, left]));
|
|
617
|
+
nextLevel.push(hash);
|
|
618
|
+
}
|
|
619
|
+
treeStructure.push(nextLevel);
|
|
620
|
+
}
|
|
621
|
+
const proof = [];
|
|
622
|
+
let currentIndex = leafIndex;
|
|
623
|
+
for (let level = 0; level < treeStructure.length - 1; level++) {
|
|
624
|
+
const currentLevel = treeStructure[level];
|
|
625
|
+
const isRightNode = currentIndex % 2 === 1;
|
|
626
|
+
let siblingIndex;
|
|
627
|
+
if (isRightNode) {
|
|
628
|
+
siblingIndex = currentIndex - 1;
|
|
629
|
+
} else {
|
|
630
|
+
siblingIndex = currentIndex + 1;
|
|
631
|
+
if (siblingIndex >= currentLevel.length) {
|
|
632
|
+
siblingIndex = currentIndex;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
proof.push(currentLevel[siblingIndex]);
|
|
636
|
+
currentIndex = Math.floor(currentIndex / 2);
|
|
637
|
+
}
|
|
638
|
+
return proof;
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Verify a Merkle proof
|
|
642
|
+
*/
|
|
643
|
+
verifyProof(root, leafData, proof) {
|
|
644
|
+
let computedHash = keccak256(leafData);
|
|
645
|
+
for (const sibling of proof) {
|
|
646
|
+
computedHash = computedHash <= sibling ? keccak256(encodePacked(["bytes32", "bytes32"], [computedHash, sibling])) : keccak256(encodePacked(["bytes32", "bytes32"], [sibling, computedHash]));
|
|
647
|
+
}
|
|
648
|
+
return computedHash === root;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Build vendor group with period budget constraint (common pattern)
|
|
652
|
+
*
|
|
653
|
+
* Creates a vendor group with shared period budget across authorized members.
|
|
654
|
+
*
|
|
655
|
+
* @param vendorGroupId Vendor group identifier
|
|
656
|
+
* @param maxAmount Shared budget amount for the period
|
|
657
|
+
* @param periodCode Period type (PERIOD_DAILY, PERIOD_WEEKLY, etc.)
|
|
658
|
+
* @param offsetMinutes UTC timezone offset (default: 0)
|
|
659
|
+
* @returns Compound leaf with vendor + period constraints
|
|
660
|
+
*/
|
|
661
|
+
buildVendorGroupPeriodConstraint(vendorGroupId, maxAmount, periodCode, offsetMinutes = 0) {
|
|
662
|
+
return this.buildCompoundLeaf({
|
|
663
|
+
vendorGroupId,
|
|
664
|
+
maxAmount,
|
|
665
|
+
periodCode,
|
|
666
|
+
offsetMinutes
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
CompoundLeafV3Builder = class {
|
|
671
|
+
/**
|
|
672
|
+
* Build V3 compound constraint leaf with venue controls
|
|
673
|
+
*
|
|
674
|
+
* Format: [4B domain_v3][1B tag][1B version][2B flags][conditional_fields...]
|
|
675
|
+
*/
|
|
676
|
+
buildVenueConstraintLeaf(constraints) {
|
|
677
|
+
let flags = 0;
|
|
678
|
+
const parts = [];
|
|
679
|
+
parts.push(DOMAIN_V3);
|
|
680
|
+
parts.push(encodePacked(["uint8"], [TAG_COMPOUND_V3]));
|
|
681
|
+
parts.push(encodePacked(["uint8"], [VERSION_V3]));
|
|
682
|
+
if (constraints.allowedTarget) {
|
|
683
|
+
if (!constraints.allowedTarget.match(/^0x[a-fA-F0-9]{40}$/)) {
|
|
684
|
+
throw new Error(`Invalid target address: ${constraints.allowedTarget}`);
|
|
685
|
+
}
|
|
686
|
+
flags |= FLAG_TARGET;
|
|
687
|
+
}
|
|
688
|
+
if (constraints.allowedSelectors && constraints.allowedSelectors.length > 0) {
|
|
689
|
+
if (constraints.allowedSelectors.length > 8) {
|
|
690
|
+
throw new Error("Too many selectors (max 8)");
|
|
691
|
+
}
|
|
692
|
+
for (const selector of constraints.allowedSelectors) {
|
|
693
|
+
if (!selector.match(/^0x[a-fA-F0-9]{8}$/)) {
|
|
694
|
+
throw new Error(`Invalid selector format: ${selector}`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
flags |= FLAG_SELECTOR_LIST;
|
|
698
|
+
}
|
|
699
|
+
if (constraints.bridgeBlocked) {
|
|
700
|
+
flags |= FLAG_NO_BRIDGE;
|
|
701
|
+
}
|
|
702
|
+
parts.push(toBigEndianUint16(flags));
|
|
703
|
+
if (flags & FLAG_TARGET) {
|
|
704
|
+
parts.push(constraints.allowedTarget);
|
|
705
|
+
}
|
|
706
|
+
if (flags & FLAG_SELECTOR_LIST) {
|
|
707
|
+
parts.push(encodePacked(["uint8"], [constraints.allowedSelectors.length]));
|
|
708
|
+
for (const selector of constraints.allowedSelectors) {
|
|
709
|
+
parts.push(selector);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
const encoded = encodePacked(
|
|
713
|
+
parts.map(() => "bytes"),
|
|
714
|
+
parts
|
|
715
|
+
);
|
|
716
|
+
return {
|
|
717
|
+
flags,
|
|
718
|
+
venueConstraints: {
|
|
719
|
+
allowedTarget: constraints.allowedTarget,
|
|
720
|
+
allowedSelectors: constraints.allowedSelectors,
|
|
721
|
+
bridgeBlocked: constraints.bridgeBlocked
|
|
722
|
+
},
|
|
723
|
+
asset: constraints.allowedAsset,
|
|
724
|
+
recipient: constraints.allowedRecipient,
|
|
725
|
+
cap: constraints.maxAmount,
|
|
726
|
+
deadline: constraints.deadline,
|
|
727
|
+
bucketId: constraints.bucketId,
|
|
728
|
+
periodCode: constraints.periodCode,
|
|
729
|
+
offsetMinutes: constraints.offsetMinutes,
|
|
730
|
+
customSeconds: constraints.customSeconds,
|
|
731
|
+
vendorGroupId: constraints.vendorGroupId,
|
|
732
|
+
encoded
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Create a simple V3 policy with venue controls
|
|
737
|
+
*/
|
|
738
|
+
createVenueConstrainedPolicy(walletAddress, constraints) {
|
|
739
|
+
const leaf = this.buildVenueConstraintLeaf(constraints);
|
|
740
|
+
const merkleRoot = keccak256(leaf.encoded);
|
|
741
|
+
const leafAndProof = leaf.encoded;
|
|
742
|
+
const recipient = constraints.allowedRecipient || "0x0000000000000000000000000000000000000000";
|
|
743
|
+
const recipientKey = recipient.toLowerCase();
|
|
744
|
+
const policy = {
|
|
745
|
+
policyId: keccak256(encodePacked(["string"], [`V3_VENUE_POLICY_${Date.now()}_${Math.random()}`])),
|
|
746
|
+
merkleRoot,
|
|
747
|
+
leaves: [leaf],
|
|
748
|
+
leafProofs: {
|
|
749
|
+
[recipientKey]: leafAndProof
|
|
750
|
+
},
|
|
751
|
+
policyRootVersion: 1
|
|
752
|
+
};
|
|
753
|
+
return Object.assign(policy, {
|
|
754
|
+
getLeafAndProofForRecipient: (targetRecipient) => {
|
|
755
|
+
if (targetRecipient) {
|
|
756
|
+
const key = targetRecipient.toLowerCase();
|
|
757
|
+
return policy.leafProofs[key] || leafAndProof;
|
|
758
|
+
}
|
|
759
|
+
return leafAndProof;
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
// src/constraints/agentConstraintBuilder.ts
|
|
768
|
+
import { keccak256 as keccak2562, parseEther } from "viem";
|
|
769
|
+
|
|
770
|
+
// src/policy/types.ts
|
|
771
|
+
var V2_FLAG_RECIPIENT = 1;
|
|
772
|
+
var V2_FLAG_CAP = 2;
|
|
773
|
+
var V2_FLAG_DEADLINE = 4;
|
|
774
|
+
var V2_FLAG_ASSET = 8;
|
|
775
|
+
var V2_FLAG_BUCKET = 16;
|
|
776
|
+
var V2_FLAG_PERIOD = 32;
|
|
777
|
+
var V2_FLAG_VENDOR_GROUP = 64;
|
|
778
|
+
var PERIOD_NONE = 0;
|
|
779
|
+
var PERIOD_DAILY = 1;
|
|
780
|
+
var PERIOD_WEEKLY = 2;
|
|
781
|
+
var PERIOD_MONTH30 = 3;
|
|
782
|
+
var PERIOD_CUSTOM = 4;
|
|
783
|
+
var GLOBAL_FLAG_NO_BRIDGE = 1n << 0n;
|
|
784
|
+
var GLOBAL_FLAG_POST_CONDITIONS = 1n << 1n;
|
|
785
|
+
var GLOBAL_FLAG_FX_SAFETY = 1n << 2n;
|
|
786
|
+
var GLOBAL_FLAG_SOLVER_ENVELOPE = 1n << 3n;
|
|
787
|
+
|
|
788
|
+
// src/policy/v2LeafBuilder.ts
|
|
789
|
+
var DOMAIN_PREFIX = "0x1a13d438";
|
|
790
|
+
var TAG_COMPOUND_EMPLOYEE = 16;
|
|
791
|
+
var VERSION = 1;
|
|
792
|
+
function toBigEndianHex(value, byteLength) {
|
|
793
|
+
const hex = BigInt(value).toString(16).padStart(byteLength * 2, "0");
|
|
794
|
+
if (hex.length > byteLength * 2) {
|
|
795
|
+
throw new Error(`Value ${value} exceeds ${byteLength} bytes`);
|
|
796
|
+
}
|
|
797
|
+
return hex;
|
|
798
|
+
}
|
|
799
|
+
function validateAddress(address, fieldName) {
|
|
800
|
+
if (!address.startsWith("0x") || address.length !== 42) {
|
|
801
|
+
throw new Error(`${fieldName} must be a valid 20-byte address, got: ${address}`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
function validateBytes32(value, fieldName) {
|
|
805
|
+
if (!value.startsWith("0x") || value.length !== 66) {
|
|
806
|
+
throw new Error(`${fieldName} must be a valid 32-byte hex, got length ${value.length}`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
function buildV2CompoundLeaf(params) {
|
|
810
|
+
const chunks = [];
|
|
811
|
+
chunks.push(DOMAIN_PREFIX.slice(2));
|
|
812
|
+
chunks.push(TAG_COMPOUND_EMPLOYEE.toString(16).padStart(2, "0"));
|
|
813
|
+
chunks.push(VERSION.toString(16).padStart(2, "0"));
|
|
814
|
+
if (params.flags < 0 || params.flags > 255) {
|
|
815
|
+
throw new Error(`Flags must be 0-255, got: ${params.flags}`);
|
|
816
|
+
}
|
|
817
|
+
chunks.push(params.flags.toString(16).padStart(2, "0"));
|
|
818
|
+
if (params.flags & V2_FLAG_ASSET) {
|
|
819
|
+
if (!params.asset) {
|
|
820
|
+
throw new Error("asset is required when FLAG_ASSET is set");
|
|
821
|
+
}
|
|
822
|
+
validateAddress(params.asset, "asset");
|
|
823
|
+
chunks.push(params.asset.slice(2).toLowerCase());
|
|
824
|
+
}
|
|
825
|
+
if (params.flags & V2_FLAG_RECIPIENT) {
|
|
826
|
+
if (!params.allowedRecipient) {
|
|
827
|
+
throw new Error("allowedRecipient is required when FLAG_RECIPIENT is set");
|
|
828
|
+
}
|
|
829
|
+
validateAddress(params.allowedRecipient, "allowedRecipient");
|
|
830
|
+
chunks.push(params.allowedRecipient.slice(2).toLowerCase());
|
|
831
|
+
}
|
|
832
|
+
if (params.flags & V2_FLAG_CAP) {
|
|
833
|
+
if (params.cap === void 0) {
|
|
834
|
+
throw new Error("cap is required when FLAG_CAP is set");
|
|
835
|
+
}
|
|
836
|
+
const maxUint128 = (1n << 128n) - 1n;
|
|
837
|
+
if (params.cap < 0n || params.cap > maxUint128) {
|
|
838
|
+
throw new Error(`cap must be 0 to ${maxUint128}, got: ${params.cap}`);
|
|
839
|
+
}
|
|
840
|
+
chunks.push(toBigEndianHex(params.cap, 16));
|
|
841
|
+
}
|
|
842
|
+
if (params.flags & V2_FLAG_DEADLINE) {
|
|
843
|
+
if (params.deadline === void 0) {
|
|
844
|
+
throw new Error("deadline is required when FLAG_DEADLINE is set");
|
|
845
|
+
}
|
|
846
|
+
const maxUint48 = (1n << 48n) - 1n;
|
|
847
|
+
if (BigInt(params.deadline) < 0n || BigInt(params.deadline) > maxUint48) {
|
|
848
|
+
throw new Error(`deadline must be 0 to ${maxUint48}, got: ${params.deadline}`);
|
|
849
|
+
}
|
|
850
|
+
chunks.push(toBigEndianHex(params.deadline, 6));
|
|
851
|
+
}
|
|
852
|
+
if (params.flags & V2_FLAG_BUCKET) {
|
|
853
|
+
if (!params.bucketId) {
|
|
854
|
+
throw new Error("bucketId is required when FLAG_BUCKET is set");
|
|
855
|
+
}
|
|
856
|
+
validateBytes32(params.bucketId, "bucketId");
|
|
857
|
+
chunks.push(params.bucketId.slice(2).toLowerCase());
|
|
858
|
+
}
|
|
859
|
+
if (params.flags & V2_FLAG_PERIOD) {
|
|
860
|
+
if (params.periodCode === void 0) {
|
|
861
|
+
throw new Error("periodCode is required when FLAG_PERIOD is set");
|
|
862
|
+
}
|
|
863
|
+
if (params.periodCode < 0 || params.periodCode > 255) {
|
|
864
|
+
throw new Error(`periodCode must be 0-255, got: ${params.periodCode}`);
|
|
865
|
+
}
|
|
866
|
+
chunks.push(params.periodCode.toString(16).padStart(2, "0"));
|
|
867
|
+
const offsetMinutes = params.offsetMinutes ?? 0;
|
|
868
|
+
if (offsetMinutes < -32768 || offsetMinutes > 32767) {
|
|
869
|
+
throw new Error(`offsetMinutes must be -32768 to 32767, got: ${offsetMinutes}`);
|
|
870
|
+
}
|
|
871
|
+
const unsignedOffset = offsetMinutes < 0 ? 65536 + offsetMinutes : offsetMinutes;
|
|
872
|
+
chunks.push(unsignedOffset.toString(16).padStart(4, "0"));
|
|
873
|
+
if (params.periodCode === PERIOD_CUSTOM) {
|
|
874
|
+
if (params.customSeconds === void 0) {
|
|
875
|
+
throw new Error("customSeconds is required when periodCode is PERIOD_CUSTOM");
|
|
876
|
+
}
|
|
877
|
+
const maxUint32 = 4294967295;
|
|
878
|
+
if (params.customSeconds < 0 || params.customSeconds > maxUint32) {
|
|
879
|
+
throw new Error(`customSeconds must be 0 to ${maxUint32}, got: ${params.customSeconds}`);
|
|
880
|
+
}
|
|
881
|
+
chunks.push(params.customSeconds.toString(16).padStart(8, "0"));
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
if (params.flags & V2_FLAG_VENDOR_GROUP) {
|
|
885
|
+
if (!params.vendorGroupId) {
|
|
886
|
+
throw new Error("vendorGroupId is required when FLAG_VENDOR_GROUP is set");
|
|
887
|
+
}
|
|
888
|
+
validateBytes32(params.vendorGroupId, "vendorGroupId");
|
|
889
|
+
chunks.push(params.vendorGroupId.slice(2).toLowerCase());
|
|
890
|
+
}
|
|
891
|
+
return `0x${chunks.join("")}`;
|
|
892
|
+
}
|
|
893
|
+
function v2ConstraintsToLeafParams(params) {
|
|
894
|
+
let flags = 0;
|
|
895
|
+
if (params.allowedRecipient) {
|
|
896
|
+
flags |= V2_FLAG_RECIPIENT;
|
|
897
|
+
}
|
|
898
|
+
if (params.cap !== void 0) {
|
|
899
|
+
flags |= V2_FLAG_CAP;
|
|
900
|
+
}
|
|
901
|
+
if (params.deadline !== void 0) {
|
|
902
|
+
flags |= V2_FLAG_DEADLINE;
|
|
903
|
+
}
|
|
904
|
+
if (params.asset) {
|
|
905
|
+
flags |= V2_FLAG_ASSET;
|
|
906
|
+
}
|
|
907
|
+
if (params.periodType && params.periodType !== "none") {
|
|
908
|
+
flags |= V2_FLAG_PERIOD;
|
|
909
|
+
}
|
|
910
|
+
if (params.vendorGroupId) {
|
|
911
|
+
flags |= V2_FLAG_VENDOR_GROUP;
|
|
912
|
+
}
|
|
913
|
+
let periodCode;
|
|
914
|
+
if (params.periodType) {
|
|
915
|
+
switch (params.periodType) {
|
|
916
|
+
case "none":
|
|
917
|
+
periodCode = 0;
|
|
918
|
+
break;
|
|
919
|
+
case "daily":
|
|
920
|
+
periodCode = 1;
|
|
921
|
+
break;
|
|
922
|
+
case "weekly":
|
|
923
|
+
periodCode = 2;
|
|
924
|
+
break;
|
|
925
|
+
case "monthly":
|
|
926
|
+
periodCode = 3;
|
|
927
|
+
break;
|
|
928
|
+
case "custom":
|
|
929
|
+
periodCode = 4;
|
|
930
|
+
break;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return {
|
|
934
|
+
flags,
|
|
935
|
+
allowedRecipient: params.allowedRecipient,
|
|
936
|
+
cap: params.cap,
|
|
937
|
+
deadline: params.deadline,
|
|
938
|
+
asset: params.asset,
|
|
939
|
+
periodCode,
|
|
940
|
+
offsetMinutes: params.utcOffsetMinutes,
|
|
941
|
+
customSeconds: params.customPeriodSeconds,
|
|
942
|
+
vendorGroupId: params.vendorGroupId
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
function buildV2LeafFromConstraints(params) {
|
|
946
|
+
const leafParams = v2ConstraintsToLeafParams(params);
|
|
947
|
+
return buildV2CompoundLeaf(leafParams);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// src/constraints/agentConstraintBuilder.ts
|
|
951
|
+
init_constraints();
|
|
952
|
+
function buildV2ConstraintsFromAgent(constraints) {
|
|
953
|
+
if (constraints.allowedRecipients && constraints.allowedRecipients.length > 1) {
|
|
954
|
+
return buildMultiRecipientV2Constraints(constraints);
|
|
955
|
+
}
|
|
956
|
+
const v2Params = {};
|
|
957
|
+
if (constraints.allowedRecipients && constraints.allowedRecipients.length === 1) {
|
|
958
|
+
v2Params.allowedRecipient = constraints.allowedRecipients[0];
|
|
959
|
+
}
|
|
960
|
+
if (constraints.maxDailyUsd) {
|
|
961
|
+
v2Params.cap = parseEther(String(constraints.maxDailyUsd));
|
|
962
|
+
v2Params.periodType = "daily";
|
|
963
|
+
} else if (constraints.maxPerTxUsd) {
|
|
964
|
+
v2Params.cap = parseEther(String(constraints.maxPerTxUsd));
|
|
965
|
+
}
|
|
966
|
+
if (constraints.deadline) {
|
|
967
|
+
v2Params.deadline = constraints.deadline;
|
|
968
|
+
}
|
|
969
|
+
if (constraints.allowedAssets && constraints.allowedAssets.length > 0) {
|
|
970
|
+
v2Params.asset = constraints.allowedAssets[0];
|
|
971
|
+
}
|
|
972
|
+
if (constraints.vendorGroupId !== void 0) {
|
|
973
|
+
v2Params.vendorGroupId = bigintToBytes32(constraints.vendorGroupId);
|
|
974
|
+
}
|
|
975
|
+
const leaf = buildV2LeafFromConstraints(v2Params);
|
|
976
|
+
const root = keccak2562(leaf);
|
|
977
|
+
let recipientLeaves;
|
|
978
|
+
if (constraints.allowedRecipients && constraints.allowedRecipients.length === 1) {
|
|
979
|
+
const recipient = constraints.allowedRecipients[0].toLowerCase();
|
|
980
|
+
const asset = constraints.allowedAssets?.[0] ? constraints.allowedAssets[0].toLowerCase() : "*";
|
|
981
|
+
const key = `${recipient}-${asset}`;
|
|
982
|
+
recipientLeaves = { [key]: leaf };
|
|
983
|
+
}
|
|
984
|
+
return { leaf, root, recipientLeaves };
|
|
985
|
+
}
|
|
986
|
+
function buildMultiRecipientV2Constraints(constraints) {
|
|
987
|
+
const recipients = constraints.allowedRecipients;
|
|
988
|
+
const builder = new CompoundLeafBuilder();
|
|
989
|
+
const cap = constraints.maxDailyUsd ? parseEther(String(constraints.maxDailyUsd)) : constraints.maxPerTxUsd ? parseEther(String(constraints.maxPerTxUsd)) : void 0;
|
|
990
|
+
const dummyWallet = "0x0000000000000000000000000000000000000001";
|
|
991
|
+
const assets = constraints.allowedAssets;
|
|
992
|
+
const hasMultipleAssets = assets && assets.length > 1;
|
|
993
|
+
let multiPolicy;
|
|
994
|
+
if (hasMultipleAssets) {
|
|
995
|
+
const recipientAssetCombinations = [];
|
|
996
|
+
for (const recipient of recipients) {
|
|
997
|
+
for (const asset of assets) {
|
|
998
|
+
recipientAssetCombinations.push({
|
|
999
|
+
recipient,
|
|
1000
|
+
asset,
|
|
1001
|
+
maxAmount: cap,
|
|
1002
|
+
deadline: constraints.deadline
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
multiPolicy = builder.createMultiRecipientPolicy(dummyWallet, recipientAssetCombinations);
|
|
1007
|
+
} else {
|
|
1008
|
+
const asset = assets?.[0];
|
|
1009
|
+
multiPolicy = builder.createMultiRecipientPolicy(dummyWallet, {
|
|
1010
|
+
allowedRecipients: recipients,
|
|
1011
|
+
allowedAsset: asset,
|
|
1012
|
+
maxAmount: cap,
|
|
1013
|
+
deadline: constraints.deadline,
|
|
1014
|
+
periodCode: constraints.maxDailyUsd ? PERIOD_DAILY2 : void 0
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
const defaultAsset = assets?.[0] ? assets[0].toLowerCase() : "*";
|
|
1018
|
+
const defaultKey = `${recipients[0].toLowerCase()}-${defaultAsset}`;
|
|
1019
|
+
const defaultLeafAndProof = multiPolicy.leafProofs[defaultKey];
|
|
1020
|
+
if (!defaultLeafAndProof) {
|
|
1021
|
+
throw new Error(`Failed to get default leaf+proof for multi-recipient policy`);
|
|
1022
|
+
}
|
|
1023
|
+
return {
|
|
1024
|
+
leaf: defaultLeafAndProof,
|
|
1025
|
+
// This is actually leafAndProof (leaf + merkle proof)
|
|
1026
|
+
root: multiPolicy.merkleRoot,
|
|
1027
|
+
recipientLeaves: multiPolicy.leafProofs
|
|
1028
|
+
// Map of recipient-asset -> leafAndProof
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
function constraintsToV2Params(constraints, recipient) {
|
|
1032
|
+
const params = {};
|
|
1033
|
+
if (recipient) {
|
|
1034
|
+
params.allowedRecipient = recipient;
|
|
1035
|
+
} else if (constraints.allowedRecipients && constraints.allowedRecipients.length === 1) {
|
|
1036
|
+
params.allowedRecipient = constraints.allowedRecipients[0];
|
|
1037
|
+
}
|
|
1038
|
+
if (constraints.maxDailyUsd) {
|
|
1039
|
+
params.cap = parseEther(String(constraints.maxDailyUsd));
|
|
1040
|
+
params.periodType = "daily";
|
|
1041
|
+
} else if (constraints.maxPerTxUsd) {
|
|
1042
|
+
params.cap = parseEther(String(constraints.maxPerTxUsd));
|
|
1043
|
+
}
|
|
1044
|
+
if (constraints.deadline) {
|
|
1045
|
+
params.deadline = constraints.deadline;
|
|
1046
|
+
}
|
|
1047
|
+
if (constraints.allowedAssets && constraints.allowedAssets.length > 0) {
|
|
1048
|
+
params.asset = constraints.allowedAssets[0];
|
|
1049
|
+
}
|
|
1050
|
+
if (constraints.vendorGroupId !== void 0) {
|
|
1051
|
+
params.vendorGroupId = bigintToBytes32(constraints.vendorGroupId);
|
|
1052
|
+
}
|
|
1053
|
+
return params;
|
|
1054
|
+
}
|
|
1055
|
+
function bigintToBytes32(value) {
|
|
1056
|
+
return `0x${value.toString(16).padStart(64, "0")}`;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
export {
|
|
1060
|
+
FLAG_RECIPIENT,
|
|
1061
|
+
FLAG_CAP,
|
|
1062
|
+
FLAG_DEADLINE,
|
|
1063
|
+
FLAG_ASSET,
|
|
1064
|
+
FLAG_BUCKET,
|
|
1065
|
+
FLAG_PERIOD,
|
|
1066
|
+
FLAG_VENDOR_GROUP,
|
|
1067
|
+
DOMAIN_V3,
|
|
1068
|
+
VERSION_V3,
|
|
1069
|
+
TAG_COMPOUND_V3,
|
|
1070
|
+
FLAG_TARGET,
|
|
1071
|
+
FLAG_SELECTOR_LIST,
|
|
1072
|
+
FLAG_NO_BRIDGE,
|
|
1073
|
+
FLAG_OPS_HASH,
|
|
1074
|
+
PERIOD_NONE2 as PERIOD_NONE,
|
|
1075
|
+
PERIOD_DAILY2 as PERIOD_DAILY,
|
|
1076
|
+
PERIOD_WEEKLY2 as PERIOD_WEEKLY,
|
|
1077
|
+
PERIOD_MONTH302 as PERIOD_MONTH30,
|
|
1078
|
+
PERIOD_CUSTOM2 as PERIOD_CUSTOM,
|
|
1079
|
+
CompoundLeafBuilder,
|
|
1080
|
+
createConstraintBuilder,
|
|
1081
|
+
getLeafAndProofForRecipient,
|
|
1082
|
+
isRecipientAllowed,
|
|
1083
|
+
buildConstraintCalldata,
|
|
1084
|
+
buildConstraintCalldataFromIntent,
|
|
1085
|
+
extractConstraintDataFromOperations,
|
|
1086
|
+
CompoundLeafV3Builder,
|
|
1087
|
+
init_constraints,
|
|
1088
|
+
V2_FLAG_RECIPIENT,
|
|
1089
|
+
V2_FLAG_CAP,
|
|
1090
|
+
V2_FLAG_DEADLINE,
|
|
1091
|
+
V2_FLAG_ASSET,
|
|
1092
|
+
V2_FLAG_BUCKET,
|
|
1093
|
+
V2_FLAG_PERIOD,
|
|
1094
|
+
V2_FLAG_VENDOR_GROUP,
|
|
1095
|
+
PERIOD_NONE as PERIOD_NONE2,
|
|
1096
|
+
PERIOD_DAILY as PERIOD_DAILY2,
|
|
1097
|
+
PERIOD_WEEKLY as PERIOD_WEEKLY2,
|
|
1098
|
+
PERIOD_MONTH30 as PERIOD_MONTH302,
|
|
1099
|
+
PERIOD_CUSTOM as PERIOD_CUSTOM2,
|
|
1100
|
+
GLOBAL_FLAG_NO_BRIDGE,
|
|
1101
|
+
GLOBAL_FLAG_POST_CONDITIONS,
|
|
1102
|
+
GLOBAL_FLAG_FX_SAFETY,
|
|
1103
|
+
GLOBAL_FLAG_SOLVER_ENVELOPE,
|
|
1104
|
+
DOMAIN_PREFIX,
|
|
1105
|
+
TAG_COMPOUND_EMPLOYEE,
|
|
1106
|
+
VERSION,
|
|
1107
|
+
buildV2CompoundLeaf,
|
|
1108
|
+
v2ConstraintsToLeafParams,
|
|
1109
|
+
buildV2LeafFromConstraints,
|
|
1110
|
+
buildV2ConstraintsFromAgent,
|
|
1111
|
+
constraintsToV2Params
|
|
1112
|
+
};
|