@engramx/idl 0.16.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +27 -0
- package/dist/engram.did +4045 -0
- package/dist/engram.idl.d.ts +3839 -0
- package/dist/engram.idl.js +1423 -0
- package/dist/market.did +1402 -0
- package/dist/market.idl.d.ts +1354 -0
- package/dist/market.idl.js +551 -0
- package/dist/registry.did +1671 -0
- package/dist/registry.idl.d.ts +1692 -0
- package/dist/registry.idl.js +522 -0
- package/package.json +50 -0
package/dist/market.did
ADDED
|
@@ -0,0 +1,1402 @@
|
|
|
1
|
+
type X402Endpoint =
|
|
2
|
+
record {
|
|
3
|
+
contentId: opt text;
|
|
4
|
+
createdAtNs: int;
|
|
5
|
+
createdBy: principal;
|
|
6
|
+
description: text;
|
|
7
|
+
enabledAtNs: opt int;
|
|
8
|
+
enabledBy: opt principal;
|
|
9
|
+
id: text;
|
|
10
|
+
name: text;
|
|
11
|
+
price: nat;
|
|
12
|
+
status: EndpointStatus;
|
|
13
|
+
suggestedPrice: nat;
|
|
14
|
+
totalPurchases: nat;
|
|
15
|
+
totalRevenue: nat;
|
|
16
|
+
};
|
|
17
|
+
type VerificationMethod =
|
|
18
|
+
variant {
|
|
19
|
+
AutoSettle;
|
|
20
|
+
BuyerConfirm: record {disputeWindowSeconds: nat;};
|
|
21
|
+
HashMatch;
|
|
22
|
+
ZkGroth16:
|
|
23
|
+
record {
|
|
24
|
+
bindResult: bool;
|
|
25
|
+
verificationKey: blob;
|
|
26
|
+
verifierCanister: principal;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
type TokenAmount =
|
|
30
|
+
variant {
|
|
31
|
+
Ckusdc: nat;
|
|
32
|
+
Ckusdt: nat;
|
|
33
|
+
Icp: nat;
|
|
34
|
+
};
|
|
35
|
+
type SpendingPolicy =
|
|
36
|
+
record {
|
|
37
|
+
allowedCallers: opt vec principal;
|
|
38
|
+
blockedCallers: opt vec principal;
|
|
39
|
+
maxConcurrentSessions: opt nat;
|
|
40
|
+
maxPerDay: opt nat;
|
|
41
|
+
maxPerTransaction: opt nat;
|
|
42
|
+
maxSessionDeposit: opt nat;
|
|
43
|
+
maxSessionDuration: opt int;
|
|
44
|
+
rateLimitPerMinute: opt nat;
|
|
45
|
+
sessionIdleTimeout: opt int;
|
|
46
|
+
};
|
|
47
|
+
type SessionStatus =
|
|
48
|
+
variant {
|
|
49
|
+
closed;
|
|
50
|
+
closing;
|
|
51
|
+
expired;
|
|
52
|
+
open;
|
|
53
|
+
other: text;
|
|
54
|
+
};
|
|
55
|
+
type SessionState =
|
|
56
|
+
record {
|
|
57
|
+
consumed: nat;
|
|
58
|
+
deposited: nat;
|
|
59
|
+
id: text;
|
|
60
|
+
lastActivityAtNs: int;
|
|
61
|
+
openedAtNs: int;
|
|
62
|
+
payer: Payer;
|
|
63
|
+
remaining: nat;
|
|
64
|
+
status: SessionStatus;
|
|
65
|
+
voucherCount: nat;
|
|
66
|
+
};
|
|
67
|
+
type SessionIntent =
|
|
68
|
+
record {
|
|
69
|
+
costPerCall: opt nat;
|
|
70
|
+
description: opt text;
|
|
71
|
+
expiryNs: int;
|
|
72
|
+
minDeposit: opt nat;
|
|
73
|
+
network: text;
|
|
74
|
+
recipient: text;
|
|
75
|
+
suggestedDeposit: nat;
|
|
76
|
+
token: text;
|
|
77
|
+
};
|
|
78
|
+
type SessionCounts =
|
|
79
|
+
record {
|
|
80
|
+
closed: nat;
|
|
81
|
+
closing: nat;
|
|
82
|
+
expired: nat;
|
|
83
|
+
open: nat;
|
|
84
|
+
total: nat;
|
|
85
|
+
};
|
|
86
|
+
type SessionConfig =
|
|
87
|
+
record {
|
|
88
|
+
autoClose: bool;
|
|
89
|
+
idleTimeout: opt int;
|
|
90
|
+
maxDeposit: nat;
|
|
91
|
+
};
|
|
92
|
+
type ServiceType =
|
|
93
|
+
variant {
|
|
94
|
+
Async;
|
|
95
|
+
Sync;
|
|
96
|
+
};
|
|
97
|
+
type ServiceRequestResponse =
|
|
98
|
+
variant {
|
|
99
|
+
Accepted: record {jobId: text;};
|
|
100
|
+
PaymentRequired: vec PaymentRequirement;
|
|
101
|
+
};
|
|
102
|
+
type ServiceDeliveryMethod =
|
|
103
|
+
variant {
|
|
104
|
+
Both;
|
|
105
|
+
Callback;
|
|
106
|
+
Poll;
|
|
107
|
+
};
|
|
108
|
+
type ServiceDefinition =
|
|
109
|
+
record {
|
|
110
|
+
createdAt: int;
|
|
111
|
+
delivery: ServiceDeliveryMethod;
|
|
112
|
+
description: text;
|
|
113
|
+
enabled: bool;
|
|
114
|
+
id: text;
|
|
115
|
+
name: text;
|
|
116
|
+
operatorId: principal;
|
|
117
|
+
pricing: PricingScheme;
|
|
118
|
+
serviceType: ServiceType;
|
|
119
|
+
timeout: nat;
|
|
120
|
+
verification: VerificationMethod;
|
|
121
|
+
};
|
|
122
|
+
type SellerStatus =
|
|
123
|
+
record {
|
|
124
|
+
byteQuota: nat;
|
|
125
|
+
bytesStored: nat;
|
|
126
|
+
engramId: principal;
|
|
127
|
+
expiresAtNs: int;
|
|
128
|
+
verifiedAtNs: int;
|
|
129
|
+
};
|
|
130
|
+
type RoleGrant =
|
|
131
|
+
record {
|
|
132
|
+
holder: principal;
|
|
133
|
+
role: variant {
|
|
134
|
+
Admin;
|
|
135
|
+
Other: text;
|
|
136
|
+
Root;
|
|
137
|
+
WasmAdmin;
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
type Role =
|
|
141
|
+
variant {
|
|
142
|
+
Admin;
|
|
143
|
+
Agent;
|
|
144
|
+
Anonymous;
|
|
145
|
+
Buyer;
|
|
146
|
+
Engram;
|
|
147
|
+
Guardian;
|
|
148
|
+
Other;
|
|
149
|
+
Owner;
|
|
150
|
+
Root;
|
|
151
|
+
Seller;
|
|
152
|
+
Viewer;
|
|
153
|
+
WasmAdmin;
|
|
154
|
+
};
|
|
155
|
+
type RevenueSummary =
|
|
156
|
+
record {
|
|
157
|
+
contentPurchases: nat;
|
|
158
|
+
contentRevenue: nat;
|
|
159
|
+
endpointBreakdown:
|
|
160
|
+
vec record {
|
|
161
|
+
endpointId: text;
|
|
162
|
+
name: text;
|
|
163
|
+
purchases: nat;
|
|
164
|
+
revenue: nat;
|
|
165
|
+
};
|
|
166
|
+
owedToYou: nat;
|
|
167
|
+
serviceJobsSettled: nat;
|
|
168
|
+
serviceRevenue: nat;
|
|
169
|
+
unresolvedWithdrawals: nat;
|
|
170
|
+
};
|
|
171
|
+
type Result_9 =
|
|
172
|
+
variant {
|
|
173
|
+
Err: Error;
|
|
174
|
+
Ok: Page_7;
|
|
175
|
+
};
|
|
176
|
+
type Result_8 =
|
|
177
|
+
variant {
|
|
178
|
+
Err: Error;
|
|
179
|
+
Ok: Page_5;
|
|
180
|
+
};
|
|
181
|
+
type Result_7 =
|
|
182
|
+
variant {
|
|
183
|
+
Err: Error;
|
|
184
|
+
Ok: Page_4;
|
|
185
|
+
};
|
|
186
|
+
type Result_6 =
|
|
187
|
+
variant {
|
|
188
|
+
Err: Error;
|
|
189
|
+
Ok: Page_2;
|
|
190
|
+
};
|
|
191
|
+
type Result_5 =
|
|
192
|
+
variant {
|
|
193
|
+
Err: Error;
|
|
194
|
+
Ok: Page_1;
|
|
195
|
+
};
|
|
196
|
+
type Result_4 =
|
|
197
|
+
variant {
|
|
198
|
+
Err: Error;
|
|
199
|
+
Ok: SessionState;
|
|
200
|
+
};
|
|
201
|
+
type Result_3 =
|
|
202
|
+
variant {
|
|
203
|
+
Err: Error;
|
|
204
|
+
Ok: PaymentQuote;
|
|
205
|
+
};
|
|
206
|
+
type Result_2 =
|
|
207
|
+
variant {
|
|
208
|
+
Err: Error;
|
|
209
|
+
Ok: text;
|
|
210
|
+
};
|
|
211
|
+
type Result_18 =
|
|
212
|
+
variant {
|
|
213
|
+
Err: Error;
|
|
214
|
+
Ok: X402Endpoint;
|
|
215
|
+
};
|
|
216
|
+
type Result_17 =
|
|
217
|
+
variant {
|
|
218
|
+
Err: Error;
|
|
219
|
+
Ok: ServiceRequestResponse;
|
|
220
|
+
};
|
|
221
|
+
type Result_16 =
|
|
222
|
+
variant {
|
|
223
|
+
Err: Error;
|
|
224
|
+
Ok: ContentResponse;
|
|
225
|
+
};
|
|
226
|
+
type Result_15 =
|
|
227
|
+
variant {
|
|
228
|
+
Err: Error;
|
|
229
|
+
Ok: opt text;
|
|
230
|
+
};
|
|
231
|
+
type Result_14 =
|
|
232
|
+
variant {
|
|
233
|
+
Err: Error;
|
|
234
|
+
Ok: Job;
|
|
235
|
+
};
|
|
236
|
+
type Result_13 =
|
|
237
|
+
variant {
|
|
238
|
+
Err: Error;
|
|
239
|
+
Ok: blob;
|
|
240
|
+
};
|
|
241
|
+
type Result_12 =
|
|
242
|
+
variant {
|
|
243
|
+
Err: Error;
|
|
244
|
+
Ok: JobStatus;
|
|
245
|
+
};
|
|
246
|
+
type Result_11 =
|
|
247
|
+
variant {
|
|
248
|
+
Err: Error;
|
|
249
|
+
Ok: RevenueSummary;
|
|
250
|
+
};
|
|
251
|
+
type Result_10 =
|
|
252
|
+
variant {
|
|
253
|
+
Err: Error;
|
|
254
|
+
Ok: SellerStatus;
|
|
255
|
+
};
|
|
256
|
+
type Result_1 =
|
|
257
|
+
variant {
|
|
258
|
+
Err: Error;
|
|
259
|
+
Ok;
|
|
260
|
+
};
|
|
261
|
+
type Result =
|
|
262
|
+
variant {
|
|
263
|
+
Err: Error;
|
|
264
|
+
Ok: nat;
|
|
265
|
+
};
|
|
266
|
+
type QuotedRequirement =
|
|
267
|
+
record {
|
|
268
|
+
amount: nat;
|
|
269
|
+
expiryNs: int;
|
|
270
|
+
network: text;
|
|
271
|
+
recipient: text;
|
|
272
|
+
scheme: text;
|
|
273
|
+
token: text;
|
|
274
|
+
tokenName: opt text;
|
|
275
|
+
tokenVersion: opt text;
|
|
276
|
+
};
|
|
277
|
+
type PurchaseRecord =
|
|
278
|
+
record {
|
|
279
|
+
amount: nat;
|
|
280
|
+
buyer: Payer;
|
|
281
|
+
endpointId: text;
|
|
282
|
+
id: text;
|
|
283
|
+
network: text;
|
|
284
|
+
receiptId: text;
|
|
285
|
+
timestampNs: int;
|
|
286
|
+
txHash: opt text;
|
|
287
|
+
};
|
|
288
|
+
type PricingScheme =
|
|
289
|
+
variant {
|
|
290
|
+
Exact: nat;
|
|
291
|
+
Session;
|
|
292
|
+
Upto: nat;
|
|
293
|
+
};
|
|
294
|
+
type PendingWithdrawal =
|
|
295
|
+
record {
|
|
296
|
+
amountE6s: nat;
|
|
297
|
+
createdAtTimeNs: nat64;
|
|
298
|
+
id: nat;
|
|
299
|
+
parkedAtNs: int;
|
|
300
|
+
reason: text;
|
|
301
|
+
seller: principal;
|
|
302
|
+
};
|
|
303
|
+
type PaymentSignature =
|
|
304
|
+
record {
|
|
305
|
+
asset: opt text;
|
|
306
|
+
authorization: opt Eip3009Authorization;
|
|
307
|
+
network: text;
|
|
308
|
+
nonce: blob;
|
|
309
|
+
publicKey: opt blob;
|
|
310
|
+
scheme: text;
|
|
311
|
+
sender: text;
|
|
312
|
+
signature: blob;
|
|
313
|
+
};
|
|
314
|
+
type PaymentRequirement =
|
|
315
|
+
record {
|
|
316
|
+
amount: nat;
|
|
317
|
+
expiry: int;
|
|
318
|
+
network: text;
|
|
319
|
+
nonce: blob;
|
|
320
|
+
recipient: text;
|
|
321
|
+
scheme: text;
|
|
322
|
+
token: text;
|
|
323
|
+
tokenName: opt text;
|
|
324
|
+
tokenVersion: opt text;
|
|
325
|
+
};
|
|
326
|
+
type PaymentQuote =
|
|
327
|
+
record {
|
|
328
|
+
amountE6: nat;
|
|
329
|
+
requirements: vec QuotedRequirement;
|
|
330
|
+
};
|
|
331
|
+
type Payer =
|
|
332
|
+
variant {
|
|
333
|
+
Evm: text;
|
|
334
|
+
Icp: principal;
|
|
335
|
+
};
|
|
336
|
+
type ParkedTx =
|
|
337
|
+
record {
|
|
338
|
+
chainId: nat;
|
|
339
|
+
leg: ParkedLeg;
|
|
340
|
+
parkedAt: int;
|
|
341
|
+
token: text;
|
|
342
|
+
txHash: text;
|
|
343
|
+
};
|
|
344
|
+
type ParkedLeg =
|
|
345
|
+
variant {
|
|
346
|
+
Refund;
|
|
347
|
+
Settle;
|
|
348
|
+
UptoRemainder;
|
|
349
|
+
};
|
|
350
|
+
type Page_7 =
|
|
351
|
+
record {
|
|
352
|
+
items: vec principal;
|
|
353
|
+
total: nat;
|
|
354
|
+
withheld: nat;
|
|
355
|
+
};
|
|
356
|
+
type Page_6 =
|
|
357
|
+
record {
|
|
358
|
+
items: vec ContentEntry;
|
|
359
|
+
total: nat;
|
|
360
|
+
withheld: nat;
|
|
361
|
+
};
|
|
362
|
+
type Page_5 =
|
|
363
|
+
record {
|
|
364
|
+
items: vec AuditEntry;
|
|
365
|
+
total: nat;
|
|
366
|
+
withheld: nat;
|
|
367
|
+
};
|
|
368
|
+
type Page_4 =
|
|
369
|
+
record {
|
|
370
|
+
items: vec PurchaseRecord;
|
|
371
|
+
total: nat;
|
|
372
|
+
withheld: nat;
|
|
373
|
+
};
|
|
374
|
+
type Page_3 =
|
|
375
|
+
record {
|
|
376
|
+
items: vec X402Endpoint;
|
|
377
|
+
total: nat;
|
|
378
|
+
withheld: nat;
|
|
379
|
+
};
|
|
380
|
+
type Page_2 =
|
|
381
|
+
record {
|
|
382
|
+
items: vec Job;
|
|
383
|
+
total: nat;
|
|
384
|
+
withheld: nat;
|
|
385
|
+
};
|
|
386
|
+
type Page_1 =
|
|
387
|
+
record {
|
|
388
|
+
items: vec PendingWithdrawal;
|
|
389
|
+
total: nat;
|
|
390
|
+
withheld: nat;
|
|
391
|
+
};
|
|
392
|
+
type Page =
|
|
393
|
+
record {
|
|
394
|
+
items: vec ServiceDefinition;
|
|
395
|
+
total: nat;
|
|
396
|
+
withheld: nat;
|
|
397
|
+
};
|
|
398
|
+
type JobStatus =
|
|
399
|
+
variant {
|
|
400
|
+
Assigned;
|
|
401
|
+
Computing;
|
|
402
|
+
Disputed;
|
|
403
|
+
Expired;
|
|
404
|
+
Pending;
|
|
405
|
+
Refunded;
|
|
406
|
+
Settled;
|
|
407
|
+
Settling;
|
|
408
|
+
Submitted;
|
|
409
|
+
Verified;
|
|
410
|
+
other: text;
|
|
411
|
+
};
|
|
412
|
+
type JobCounts =
|
|
413
|
+
record {
|
|
414
|
+
active: nat;
|
|
415
|
+
expired: nat;
|
|
416
|
+
parked: nat;
|
|
417
|
+
refunded: nat;
|
|
418
|
+
settled: nat;
|
|
419
|
+
settling: nat;
|
|
420
|
+
total: nat;
|
|
421
|
+
};
|
|
422
|
+
type Job =
|
|
423
|
+
record {
|
|
424
|
+
actualCost: opt nat;
|
|
425
|
+
amount: nat;
|
|
426
|
+
buyer: text;
|
|
427
|
+
completedAtNs: opt int;
|
|
428
|
+
createdAtNs: int;
|
|
429
|
+
deliveryCallback: opt text;
|
|
430
|
+
expiresAtNs: int;
|
|
431
|
+
id: text;
|
|
432
|
+
operator: opt principal;
|
|
433
|
+
params: blob;
|
|
434
|
+
parkedTx: opt ParkedTx;
|
|
435
|
+
paymentReceiptId: text;
|
|
436
|
+
proof: opt blob;
|
|
437
|
+
result: opt blob;
|
|
438
|
+
serviceId: text;
|
|
439
|
+
status: JobStatus;
|
|
440
|
+
};
|
|
441
|
+
type HttpResponse =
|
|
442
|
+
record {
|
|
443
|
+
body: blob;
|
|
444
|
+
headers: vec record {
|
|
445
|
+
text;
|
|
446
|
+
text;
|
|
447
|
+
};
|
|
448
|
+
status_code: nat16;
|
|
449
|
+
upgrade: opt bool;
|
|
450
|
+
};
|
|
451
|
+
type HttpRequest =
|
|
452
|
+
record {
|
|
453
|
+
body: blob;
|
|
454
|
+
headers: vec record {
|
|
455
|
+
text;
|
|
456
|
+
text;
|
|
457
|
+
};
|
|
458
|
+
method: text;
|
|
459
|
+
url: text;
|
|
460
|
+
};
|
|
461
|
+
type GenesisNote =
|
|
462
|
+
record {
|
|
463
|
+
genesisAtNs: int;
|
|
464
|
+
note: text;
|
|
465
|
+
predecessorHeadHash: opt blob;
|
|
466
|
+
predecessorSize: opt nat;
|
|
467
|
+
version: text;
|
|
468
|
+
};
|
|
469
|
+
type Error =
|
|
470
|
+
variant {
|
|
471
|
+
Conflict: text;
|
|
472
|
+
Invalid: text;
|
|
473
|
+
NotFound: text;
|
|
474
|
+
Other: text;
|
|
475
|
+
Refused: text;
|
|
476
|
+
Unavailable: text;
|
|
477
|
+
};
|
|
478
|
+
type EndpointStatus =
|
|
479
|
+
variant {
|
|
480
|
+
Deleted;
|
|
481
|
+
Disabled;
|
|
482
|
+
Enabled;
|
|
483
|
+
};
|
|
484
|
+
type Eip3009Authorization =
|
|
485
|
+
record {
|
|
486
|
+
from: text;
|
|
487
|
+
nonce: blob;
|
|
488
|
+
r: blob;
|
|
489
|
+
s: blob;
|
|
490
|
+
to: text;
|
|
491
|
+
v: nat8;
|
|
492
|
+
validAfter: nat;
|
|
493
|
+
validBefore: nat;
|
|
494
|
+
value: nat;
|
|
495
|
+
};
|
|
496
|
+
type DeliveryMethod =
|
|
497
|
+
variant {
|
|
498
|
+
assetCanister: record {
|
|
499
|
+
canisterId: principal;
|
|
500
|
+
path: text;
|
|
501
|
+
};
|
|
502
|
+
canisterQuery: record {
|
|
503
|
+
chunkCount: nat;
|
|
504
|
+
method: text;
|
|
505
|
+
};
|
|
506
|
+
httpUrl: text;
|
|
507
|
+
inline: blob;
|
|
508
|
+
};
|
|
509
|
+
type ContentResponse =
|
|
510
|
+
variant {
|
|
511
|
+
Delivered: ContentDelivery;
|
|
512
|
+
PaymentRequired: vec PaymentRequirement;
|
|
513
|
+
};
|
|
514
|
+
type ContentRef =
|
|
515
|
+
record {
|
|
516
|
+
id: text;
|
|
517
|
+
metadata: opt vec record {
|
|
518
|
+
text;
|
|
519
|
+
text;
|
|
520
|
+
};
|
|
521
|
+
mimeType: opt text;
|
|
522
|
+
sizeBytes: opt nat;
|
|
523
|
+
};
|
|
524
|
+
type ContentEntry =
|
|
525
|
+
record {
|
|
526
|
+
chunkCount: nat;
|
|
527
|
+
createdAt: int;
|
|
528
|
+
id: text;
|
|
529
|
+
mimeType: text;
|
|
530
|
+
totalSize: nat;
|
|
531
|
+
};
|
|
532
|
+
type ContentDelivery =
|
|
533
|
+
record {
|
|
534
|
+
delivery: DeliveryMethod;
|
|
535
|
+
grant: AccessGrant;
|
|
536
|
+
settlementTxHash: opt text;
|
|
537
|
+
};
|
|
538
|
+
type CertifiedAuditHead =
|
|
539
|
+
record {
|
|
540
|
+
certificate: opt blob;
|
|
541
|
+
headHash: blob;
|
|
542
|
+
size: nat;
|
|
543
|
+
total: nat;
|
|
544
|
+
witness: opt blob;
|
|
545
|
+
};
|
|
546
|
+
type AuditHead =
|
|
547
|
+
record {
|
|
548
|
+
headHash: blob;
|
|
549
|
+
size: nat;
|
|
550
|
+
};
|
|
551
|
+
type AuditEntry =
|
|
552
|
+
record {
|
|
553
|
+
caller: principal;
|
|
554
|
+
callerRole: Role;
|
|
555
|
+
details: text;
|
|
556
|
+
entryHash: blob;
|
|
557
|
+
id: nat;
|
|
558
|
+
operation: text;
|
|
559
|
+
success: bool;
|
|
560
|
+
timestampNs: int;
|
|
561
|
+
};
|
|
562
|
+
type AdminRoles = record {roles: vec RoleGrant;};
|
|
563
|
+
type AccessGrant =
|
|
564
|
+
record {
|
|
565
|
+
contentRef: ContentRef;
|
|
566
|
+
expiresAt: int;
|
|
567
|
+
grantId: text;
|
|
568
|
+
grantee: principal;
|
|
569
|
+
hmac: blob;
|
|
570
|
+
issuedAt: int;
|
|
571
|
+
receiptId: text;
|
|
572
|
+
};
|
|
573
|
+
service : {
|
|
574
|
+
addAuditViewer: (viewer: principal) -> (Result_1);
|
|
575
|
+
/// Push this canister's chain head into the registry's anchor ring — the same thing every engram
|
|
576
|
+
/// does with its memory head, and the cashier with its treasury head.
|
|
577
|
+
///
|
|
578
|
+
/// L12's argument applies to escrow with full force: a self-attested commerce ledger sitting
|
|
579
|
+
/// beside witnessed ones is the asymmetry an auditor finds first. Every chain in this system is
|
|
580
|
+
/// witnessed by a party that does not control it, or the phrase means nothing.
|
|
581
|
+
///
|
|
582
|
+
/// Best-effort and isolated: a registry that is slow or down must never take the market with it.
|
|
583
|
+
/// The FAILURE IS RECORDED rather than swallowed — an anchor push that silently stopped would
|
|
584
|
+
/// leave the chain looking witnessed while it was not, which is worse than a visibly missing one.
|
|
585
|
+
///
|
|
586
|
+
/// ⟨RENAMED 2026-10-02 from `sysAnchorNow`, with `setRegistryId` and for the same reason: its
|
|
587
|
+
/// caller is the market's admin, a person, so it does not belong to the canister-to-canister
|
|
588
|
+
/// tier the `sys` prefix names.⟩
|
|
589
|
+
anchorNow: () -> (Result_1);
|
|
590
|
+
/// A seller takes on a pending job. The CALLER must be the seller of the service the job was
|
|
591
|
+
/// created against — claiming is not open, and there is no assignment step: whoever registered
|
|
592
|
+
/// the service claims its work.
|
|
593
|
+
///
|
|
594
|
+
/// ⚠ EVERY FAILURE ARRIVES AS `#Conflict`, INCLUDING ONES THAT ARE NOT CONFLICTS. The refusals
|
|
595
|
+
/// come from the vendored ic402 service registry as plain strings, so the arm is already lost
|
|
596
|
+
/// when this method sees them. `#Conflict` here therefore covers FOUR distinct situations:
|
|
597
|
+
/// · the job does not exist ⟶ properly `#NotFound`
|
|
598
|
+
/// · the service does not exist ⟶ properly `#NotFound`
|
|
599
|
+
/// · the job is not pending ⟶ properly `#Conflict`
|
|
600
|
+
/// · **you are not the seller** ⟶ properly `#Refused`
|
|
601
|
+
///
|
|
602
|
+
/// **Read the MESSAGE, not the arm, on this method only.** That is the opposite of what L1 asks
|
|
603
|
+
/// everywhere else, and it is stated here rather than left to be discovered: a caller branching
|
|
604
|
+
/// on the arm will retry a call that can never succeed for it. Recovering the distinction needs
|
|
605
|
+
/// the LIBRARY to carry arms — matching its prose here would reintroduce exactly the
|
|
606
|
+
/// message-branching L1 abolished, and would break silently on an ic402 bump. ⟨Recorded with
|
|
607
|
+
/// three costed options in the project's backlog, 2026-08-21. This is the market's whole ic402
|
|
608
|
+
/// seam, not one method: 6 call sites collapse a module error into `#Conflict`.⟩
|
|
609
|
+
/// ⟨2026-10-02 — counted again, and ruled.⟩ ELEVEN sites on this market collapse an ic402 text
|
|
610
|
+
/// into `#Conflict`, not 6: `setServiceEnabled`, `claimJob`, `submitJobResult`, `confirmJob`,
|
|
611
|
+
/// `disputeJob`, `resolveDispute`, `reconcileSession`, `forceResolveSession`, `recoverEscrow`,
|
|
612
|
+
/// `reconcileJob`, `resolveJob` — each now carries its own audited caveat. Four can carry a
|
|
613
|
+
/// refusal of the caller's standing — `claimJob`, `confirmJob`, `disputeJob` and `submitJobResult`;
|
|
614
|
+
/// `setServiceEnabled`, the fourth noted on 2026-08-21, is now gated by this canister before the
|
|
615
|
+
/// library is called; the other six are state behind this canister's own gate. Read-the-message
|
|
616
|
+
/// is RATIFIED as the 0.16.0 contract (the owner's ruling of 2026-10-02, the pre-cut
|
|
617
|
+
/// recommendation); giving the library one arm per kind of refusal is post-cut dependency work,
|
|
618
|
+
/// not a change made mid-cut.
|
|
619
|
+
///
|
|
620
|
+
/// It also refuses while TRADE IS FROZEN, and that one is a true `#Conflict` — the refusal says
|
|
621
|
+
/// so explicitly ("Not a permission problem and not a missing job"), and `isTradeFrozen()` is the
|
|
622
|
+
/// ungated read that confirms it.
|
|
623
|
+
claimJob: (jobId: text) -> (Result_1);
|
|
624
|
+
/// ⟨0.16.0, L2.6⟩ `confirmJob` ABSORBS `confirmJobEvm`. One buyer action, two ways to prove you
|
|
625
|
+
/// are the buyer.
|
|
626
|
+
///
|
|
627
|
+
/// The pair existed because authorization arrives differently on the two rails, not because the
|
|
628
|
+
/// ACT differs: both release escrow to the seller. An ICP-rail buyer proves identity by calling
|
|
629
|
+
/// as their principal; an EVM-rail buyer has no principal here at all, so they prove it with a
|
|
630
|
+
/// secp256k1 signature over `getBuyerActionMessage`, which ic402 ecRecovers and compares to the job's
|
|
631
|
+
/// stored 0x buyer. `evmProof = null` means "authorize me as the caller".
|
|
632
|
+
///
|
|
633
|
+
/// THE HARDENING IS ON THE SIGNATURE BRANCH ONLY, and deliberately so. That branch is
|
|
634
|
+
/// ANONYMOUS and does real work — an ecRecover plus, on success, an on-chain settle — so it is
|
|
635
|
+
/// griefable by anyone: it keeps the cycles floor (protect the canister's balance from drain) and
|
|
636
|
+
/// the per-caller rate limit. Applying the same rate limit to the principal branch would throttle
|
|
637
|
+
/// identified buyers to defend against a threat that branch does not have.
|
|
638
|
+
///
|
|
639
|
+
/// ⚠ `#Conflict` HERE ALSO COVERS "NOT THE BUYER", which is a refusal and not a conflict. The
|
|
640
|
+
/// underlying ic402 registry returns plain strings, so the arm is gone before this method sees
|
|
641
|
+
/// it: `Job not found` (properly `#NotFound`), `Job not in submitted status` (properly
|
|
642
|
+
/// `#Conflict`) and **`Not the buyer`** (properly `#Refused`) all arrive as `#Conflict`. On THIS
|
|
643
|
+
/// method read the MESSAGE rather than the arm — it is terminal money settlement, so a caller
|
|
644
|
+
/// who reads `#Conflict` as "retry later" will retry a release that can never happen for them.
|
|
645
|
+
/// Same seam as `claimJob`; three costed fixes in the project's backlog ⟨2026-08-21⟩.
|
|
646
|
+
confirmJob: (jobId: text, evmProof: opt vec nat8) -> (Result_1);
|
|
647
|
+
/// Create a content endpoint — a shelf in the storefront. Born DISABLED.
|
|
648
|
+
///
|
|
649
|
+
/// Born disabled deliberately: a listing that went live at creation would be sellable before its
|
|
650
|
+
/// price was set, and `price` starts at 0 with `suggestedPrice` as the fallback.
|
|
651
|
+
createEndpoint: (name: text, description: text, contentId: text,
|
|
652
|
+
suggestedPriceE6: nat) -> (Result_18);
|
|
653
|
+
/// Submit a paid service request. Returns a job id, or the price.
|
|
654
|
+
///
|
|
655
|
+
/// ⟨0.16.0⟩ TWO GATES FROM THE ENGRAM DO NOT TRAVEL, and both for the same reason: they were the
|
|
656
|
+
/// engram protecting ITS OWNER'S money, which is custody, and custody stayed.
|
|
657
|
+
///
|
|
658
|
+
/// · `requireAgentOrOwner` — the engram only let its own agents spend its funds. Here the
|
|
659
|
+
/// caller is a BUYER paying with their own signature; gating that to a roster would mean a
|
|
660
|
+
/// marketplace only its own staff could buy from.
|
|
661
|
+
/// · `approvalId` / `#approvalPending` — an approval is the buyer's own spend policy, checked on
|
|
662
|
+
/// their engram before they call. Two arms and a parameter disappear because the
|
|
663
|
+
/// responsibility moved, not because a check was dropped.
|
|
664
|
+
///
|
|
665
|
+
/// What DOES gate here is the market's freeze, and the service's own existence and pricing mode.
|
|
666
|
+
createJob: (serviceId: text, params: blob, paymentSig:
|
|
667
|
+
opt PaymentSignature) -> (Result_17);
|
|
668
|
+
/// Delete content.
|
|
669
|
+
///
|
|
670
|
+
/// ⚠ THE QUOTA IS NOT REFUNDED, and this is the honest part of the design rather than a
|
|
671
|
+
/// meanness. The memory is not returned to the subnet when the entry goes — enhanced orthogonal
|
|
672
|
+
/// persistence never shrinks — so crediting the bytes back would let one seller cycle uploads
|
|
673
|
+
/// forever against a ceiling that only ever rose in reality. The quota tracks bytes CLAIMED, not
|
|
674
|
+
/// bytes currently held, because claimed is the number that costs money.
|
|
675
|
+
deleteContent: (id: text) -> (Result_1);
|
|
676
|
+
/// Delete an endpoint. Terminal — the record stays as #Deleted so purchase history keeps
|
|
677
|
+
/// resolving, and the id is never reused.
|
|
678
|
+
deleteEndpoint: (endpointId: text) -> (Result_1);
|
|
679
|
+
/// ⟨0.16.0, L2.6⟩ `disputeJob` ABSORBS `disputeJobEvm`, exactly as confirmJob does, and for the
|
|
680
|
+
/// same reason: one act, two proofs of buyership.
|
|
681
|
+
///
|
|
682
|
+
/// NOT FREEZE-GATED, and that asymmetry with confirmJob is deliberate. Recording a dispute moves
|
|
683
|
+
/// no money — it marks the job #Disputed, and any refund happens later through resolveDispute or
|
|
684
|
+
/// a timeout. A buyer must be able to say "this is wrong" DURING an emergency freeze; a freeze
|
|
685
|
+
/// that silenced complaints would protect the market from its own buyers.
|
|
686
|
+
///
|
|
687
|
+
/// ⚠ `#Conflict` HERE ALSO COVERS "NOT THE BUYER" — same ic402 seam as `confirmJob` and
|
|
688
|
+
/// `claimJob`: `Job not found`, `Job not in submitted status` and **`Not the buyer`** all arrive
|
|
689
|
+
/// as `#Conflict` because the library hands back strings. Read the MESSAGE, not the arm.
|
|
690
|
+
/// ⟨2026-08-21; the project's backlog carries the three fixes and why none is a one-liner.⟩
|
|
691
|
+
///
|
|
692
|
+
/// ⚠ AND NOTE WHERE THE DISPUTE GOES: `resolveDispute` is answered by the SERVICE'S OWN SELLER
|
|
693
|
+
/// — the party being disputed. This market has no admin arbitration and no appeal, deliberately
|
|
694
|
+
/// and on the record. Filing a dispute is not an escalation to a neutral party.
|
|
695
|
+
disputeJob: (jobId: text, reason: text, evmProof: opt vec nat8) ->
|
|
696
|
+
(Result_1);
|
|
697
|
+
/// Closing refunds the unconsumed deposit to the payer, so it is NOT freeze-gated — the same
|
|
698
|
+
/// reasoning as disputeJob and forceCloseSession. A halt stops money moving toward sellers; it
|
|
699
|
+
/// must not strand a buyer's own escrow.
|
|
700
|
+
endSession: (sessionId: text) -> (Result_1);
|
|
701
|
+
/// Admin recovery: force a stuck session closed and return the unconsumed deposit to its payer.
|
|
702
|
+
///
|
|
703
|
+
/// Documented in RECOVERY-RUNBOOK as an owner procedure; on a shared canister the equivalent
|
|
704
|
+
/// authority is the market admin, because the session's own parties are precisely who cannot
|
|
705
|
+
/// unstick it. No discretion: the refund destination is the payer recorded on the session.
|
|
706
|
+
forceCloseSession: (sessionId: text) -> (Result_1);
|
|
707
|
+
/// Admin recovery: mark a stuck session resolved. STATE ONLY — no funds move.
|
|
708
|
+
///
|
|
709
|
+
/// Kept distinct from forceCloseSession, which refunds, because the two are different admissions.
|
|
710
|
+
/// This one says "the accounting is already right and the record is wrong"; that one says "return
|
|
711
|
+
/// what is left". Merging them would let a state repair silently move money.
|
|
712
|
+
///
|
|
713
|
+
/// NOT freeze-gated, and the asymmetry is the point: a freeze halts SETTLEMENT, and this settles
|
|
714
|
+
/// nothing. An admin must be able to repair a record during the halt that made the record wrong.
|
|
715
|
+
///
|
|
716
|
+
/// ⟨2026-10-02, the payment library's refusal channel — audited at this site⟩ `#Conflict` here
|
|
717
|
+
/// carries ic402's text, and ic402 answers with one text channel for every refusal. AUDITED:
|
|
718
|
+
/// this method's own gate (`requireAdmin`) decides authorization before the library is called, and every
|
|
719
|
+
/// text the library returns here is a STATE refusal (session missing, or not stuck in #closing) — including "not found", which
|
|
720
|
+
/// arrives as `#Conflict` rather than `#NotFound`. Read the MESSAGE for which. This is the 0.16.0
|
|
721
|
+
/// contract as ratified on 2026-10-02; giving the library one arm per kind of refusal is post-cut
|
|
722
|
+
/// dependency work.
|
|
723
|
+
forceResolveSession: (sessionId: text) -> (Result_1);
|
|
724
|
+
/// PUBLIC: accountability requires knowing who holds power. This is the shape every canister
|
|
725
|
+
/// in the system serves, so one reader covers all of them.
|
|
726
|
+
getAdminRoles: () -> (AdminRoles) query;
|
|
727
|
+
/// PUBLIC: a head exists to be compared. A head nobody can read witnesses nothing.
|
|
728
|
+
getAuditHead: () -> (AuditHead) query;
|
|
729
|
+
/// The exact message an EVM buyer signs (keccak256 of it) to confirm or dispute a job.
|
|
730
|
+
///
|
|
731
|
+
/// PUBLIC: it must be, since the caller who needs it is by definition unauthenticated here.
|
|
732
|
+
/// It binds action + job + THIS canister, so a signature cannot be replayed across actions, across
|
|
733
|
+
/// jobs, or onto another market.
|
|
734
|
+
getBuyerActionMessage: (action: text, jobId: text) -> (text) query;
|
|
735
|
+
/// PUBLIC: the subnet-certified head, so a client verifies against the NNS root rather than
|
|
736
|
+
/// trusting this replica. `witness` is null until this canister grows a certified tree — an
|
|
737
|
+
/// honest absence, distinct from an empty proof.
|
|
738
|
+
getCertifiedAuditHead: () -> (CertifiedAuditHead) query;
|
|
739
|
+
/// Paid content read. `#PaymentRequired` comes back inside `#Ok` — see Types.ContentResponse for
|
|
740
|
+
/// why a price is an answer rather than an error.
|
|
741
|
+
///
|
|
742
|
+
/// The `approvalId` parameter is gone, as it is on openSession: an approval is the BUYER's own
|
|
743
|
+
/// custody policy, checked on their engram before they call here.
|
|
744
|
+
///
|
|
745
|
+
/// ⟨L7⟩ UPDATE, NOT QUERY: the paid path awaits `gate.settle(sig, ?amount)`, an inter-canister
|
|
746
|
+
/// settlement. A query cannot await it, and splitting the free and paid halves into two methods
|
|
747
|
+
/// would put the price and the content behind different names for one buyer act.
|
|
748
|
+
///
|
|
749
|
+
/// ⟨2026-09-30, ic402 2.17.0⟩ It now awaits `gate.settleFrom(msg.caller, sig, ?amount)`. On the
|
|
750
|
+
/// ICP rail a signature's `sender` is only a claim — the payer's ICRC-2 allowance is the whole
|
|
751
|
+
/// authentication — so `settle` would spend the allowance of whoever the signature named and
|
|
752
|
+
/// deliver the content to the caller. `settleFrom` refuses unless the caller IS the named sender,
|
|
753
|
+
/// before the nonce lock or any ledger call, and the refusal reaches the buyer as #Refused with
|
|
754
|
+
/// ic402's text naming both principals. Pay as the principal that approved the allowance.
|
|
755
|
+
getContent: (contentId: text, paymentSig: opt PaymentSignature) ->
|
|
756
|
+
(Result_16);
|
|
757
|
+
/// ⟨0.16.0⟩ THE `getChunk` FIX, and it is the sharpest single case in the corpus.
|
|
758
|
+
///
|
|
759
|
+
/// The engram's version was:
|
|
760
|
+
///
|
|
761
|
+
/// switch (gate.verifyGrant(msg.caller, grant)) {
|
|
762
|
+
/// case (#ok) { contentStore.getChunk(grant.contentRef.id, index) };
|
|
763
|
+
/// case (_) { null };
|
|
764
|
+
/// };
|
|
765
|
+
///
|
|
766
|
+
/// `AccessGrantResult` has FOUR arms — `#ok`, `#expired`, `#invalidGrant`, `#revoked` — each
|
|
767
|
+
/// carrying its own explanatory text. All three failures were collapsed into `null`, which is
|
|
768
|
+
/// ALSO what the content store returns for "that chunk index does not exist". So a buyer whose
|
|
769
|
+
/// grant had expired, a buyer whose grant was revoked, and a buyer asking for a chunk past the end
|
|
770
|
+
/// of a file received byte-identical answers. The information existed — the sibling `verifyGrant`
|
|
771
|
+
/// returned the full result — and was discarded one line later.
|
|
772
|
+
///
|
|
773
|
+
/// Worse than uninformative: a buyer who PAID and whose grant expired sees "no such chunk" and
|
|
774
|
+
/// concludes the content is gone, not that their access lapsed. On a paid rail that is a refund
|
|
775
|
+
/// request and a support ticket generated by a type.
|
|
776
|
+
///
|
|
777
|
+
/// The honesty gates could not see it. `gate.verifyGrant` is a PAYMENT gate, and the gate-call
|
|
778
|
+
/// list they scan holds authorization helpers only — so a refusal fabricated behind a payment
|
|
779
|
+
/// check was out of scope by construction rather than by ruling. L11 adds payment gates to that
|
|
780
|
+
/// list; this method is why.
|
|
781
|
+
getContentChunk: (grant: AccessGrant, index: nat) -> (Result_13) query;
|
|
782
|
+
/// A seller's payout address — THEIRS, or the admin's to read.
|
|
783
|
+
///
|
|
784
|
+
/// The engram made this owner-only and TRAPPED at everyone else. Here the answer is a refusal a
|
|
785
|
+
/// caller can read, and `#Ok(null)` genuinely means "this seller has set no payout address" —
|
|
786
|
+
/// distinguishable from "you may not ask", which is the whole point of the envelope.
|
|
787
|
+
getEvmSellerPayout: (seller: principal) -> (Result_15) query;
|
|
788
|
+
/// PUBLIC: what this canister's chains continue, or that they continue nothing. A fresh chain
|
|
789
|
+
/// beside a published predecessor head is auditable; a fresh chain with no statement is
|
|
790
|
+
/// indistinguishable from a wipe.
|
|
791
|
+
getGenesisNote: () -> (GenesisNote) query;
|
|
792
|
+
/// ⟨0.16.0, L1⟩ The engram TRAPPED here. Trapping was the right fix at the time — it stopped the
|
|
793
|
+
/// refusal masquerading as a not-found — but it hands the caller no structured reason and cannot
|
|
794
|
+
/// be told from a genuine fault. Under L1 the refusal is a value.
|
|
795
|
+
///
|
|
796
|
+
/// The comment the engram carried is worth keeping because it names what went wrong: `null`
|
|
797
|
+
/// collapsed "unknown jobId" and "not authorized", and the product rendered the wrong conclusion
|
|
798
|
+
/// from it — the marketplace UI printed "Job not found (or not yet visible)" at a caller who was
|
|
799
|
+
/// merely unauthorized. Job existence is not the secret: `getJobCounts()` is ungated.
|
|
800
|
+
getJob: (jobId: text) -> (Result_14) query;
|
|
801
|
+
/// PUBLIC: aggregate counts that identify nobody — and already public on the engram, which is
|
|
802
|
+
/// what made `listJobs`' existence-leak justification false.
|
|
803
|
+
getJobCounts: () -> (JobCounts) query;
|
|
804
|
+
/// The sharpest of the family, and the one in a money path — the engram's own comment said so.
|
|
805
|
+
///
|
|
806
|
+
/// `null` there collapsed THREE meanings: unknown jobId, not authorized, and NO RESULT SUBMITTED
|
|
807
|
+
/// YET. So a refusal presented as a claim about the WORK. Combined with the `buyer : Text` defect,
|
|
808
|
+
/// an EVM-rail buyer was refused permanently, polled, read `null` as "the seller delivered
|
|
809
|
+
/// nothing", and escalated to dispute — a fabricated refusal manufacturing a commercial conflict.
|
|
810
|
+
///
|
|
811
|
+
/// Three outcomes, three answers now. And `Payer` (above) removes the root cause rather than
|
|
812
|
+
/// only labelling it.
|
|
813
|
+
getJobResult: (jobId: text) -> (Result_13) query;
|
|
814
|
+
/// Terminal money settlement — the seller gets paid. Honours the freeze exactly like every
|
|
815
|
+
/// other settle path; a settlement that ignored the freeze would make the freeze a suggestion.
|
|
816
|
+
/// PUBLIC: a job's state, and nothing else about it.
|
|
817
|
+
///
|
|
818
|
+
/// ⟨0.16.0⟩ THE UNGATED TWIN OF `getJob`, AND IT MUST STAY UNGATED. `getJob` is allowed to refuse
|
|
819
|
+
/// precisely because this exists: an EVM-rail buyer has NO PRINCIPAL on this canister, so a
|
|
820
|
+
/// principal-gated read can never authorize them, and without a public status read they would
|
|
821
|
+
/// have no way to see the state of a job they paid for. `getJob`'s refusal names this method for
|
|
822
|
+
/// exactly that reason.
|
|
823
|
+
///
|
|
824
|
+
/// Job existence is not the secret being kept — the payload is. Status and nothing more.
|
|
825
|
+
///
|
|
826
|
+
/// The engram returned `?EngramJobStatus`, conflating "no such job" with a null-ish answer. Here
|
|
827
|
+
/// a missing job is `#NotFound`, which is a fact rather than an absence a caller has to interpret.
|
|
828
|
+
getJobStatus: (jobId: text) -> (Result_12) query;
|
|
829
|
+
/// PUBLIC: the market's spend limits, with the caller rosters REDACTED.
|
|
830
|
+
///
|
|
831
|
+
/// The limits themselves are already public — they are advertised inside every 402 challenge, so
|
|
832
|
+
/// withholding them here would hide nothing while making the market unquotable. `allowedCallers`
|
|
833
|
+
/// and `blockedCallers` are different in kind: an access-control roster, and a world-readable
|
|
834
|
+
/// blocklist tells everyone who has been blocked. Redacted to `null`, which reads as "not
|
|
835
|
+
/// disclosed" rather than as "empty" — an empty roster and a withheld one are different facts.
|
|
836
|
+
///
|
|
837
|
+
/// Reads the GLOBAL policy. The engram called `getPolicy(self)`, i.e. the effective policy for
|
|
838
|
+
/// the canister acting as its own payer — which silently returns a self-override if one exists,
|
|
839
|
+
/// so the published limits could differ from the ones actually applied to buyers.
|
|
840
|
+
getPaymentPolicy: () -> (SpendingPolicy) query;
|
|
841
|
+
/// and nothing is aggregated across sellers.
|
|
842
|
+
///
|
|
843
|
+
/// THE FIGURES ARE COMPUTED, NOT READ FROM THE CANISTER-WIDE COUNTERS. `payments`' scalars are
|
|
844
|
+
/// every seller's money added together; handing them to one seller would overstate their revenue
|
|
845
|
+
/// by the entire market. Service revenue is derived per seller from settled jobs on services they
|
|
846
|
+
/// operate.
|
|
847
|
+
///
|
|
848
|
+
/// Anonymous is refused rather than answered with zeroes. A zero here is a real business fact —
|
|
849
|
+
/// "you have sold nothing" — and returning it to a caller who simply has no identity is the
|
|
850
|
+
/// fabricated-empty defect wearing a number instead of a list.
|
|
851
|
+
getRevenueSummary: () -> (Result_11) query;
|
|
852
|
+
/// PUBLIC: what a buyer can learn about a seller, and the whole of it.
|
|
853
|
+
///
|
|
854
|
+
/// The ENGRAM ID is the point of this read. This market runs no reputation system and issues no
|
|
855
|
+
/// badges, so the honest trust signal is not a verdict we invent — it is a pointer to the record
|
|
856
|
+
/// the seller already keeps. A buyer who wants to know whether this seller is a verified human,
|
|
857
|
+
/// what their audit chain says, or how long they have existed asks THAT engram directly, and gets
|
|
858
|
+
/// an answer this market cannot shade.
|
|
859
|
+
///
|
|
860
|
+
/// `verified` is about ownership at a moment in time and nothing else. It is not an endorsement,
|
|
861
|
+
/// and the field name is the strongest claim it makes.
|
|
862
|
+
getSellerStatus: (seller: principal) -> (Result_10) query;
|
|
863
|
+
/// PUBLIC: aggregate session counts. Identifies nobody, and the same reasoning as getJobCounts —
|
|
864
|
+
/// which is also what made `listJobs`' old "no existence leak" defence false.
|
|
865
|
+
getSessionCounts: () -> (SessionCounts) query;
|
|
866
|
+
http_request: (request: HttpRequest) -> (HttpResponse) query;
|
|
867
|
+
/// ⟨2026-09-30, ic402 2.17.0⟩ THE TWO PAID ROUTES HERE STAY ON `gate.settle`, AND THAT IS WHY NO
|
|
868
|
+
/// FLOW MAY LEAVE A STANDING ALLOWANCE TO THIS MARKET. An HTTP request arrives with no caller to
|
|
869
|
+
/// check (the gateway calls anonymously), so `settleFrom` cannot apply and ic402 says to keep
|
|
870
|
+
/// `settle` on HTTP paths. Such a settle is authenticated only by the payer's ICRC-2 allowance:
|
|
871
|
+
/// anyone who names a principal holding an unspent allowance to this canister can spend it here.
|
|
872
|
+
/// The defence is on the paying side — approve exactly the price plus one ledger fee, expiring
|
|
873
|
+
/// within minutes, immediately before paying (the SDK's `MarketClient` does). WHAT WOULD MAKE
|
|
874
|
+
/// THIS WRONG: an HTTP route that could authenticate its payer, or a flow of ours that approves
|
|
875
|
+
/// more than one payment's worth or without an expiry.
|
|
876
|
+
///
|
|
877
|
+
/// ⟨CORRECTED 2026-09-30 — the owner's answer "1 B"⟩ The paragraph above is history. No web route
|
|
878
|
+
/// settles an ICP-rail payment any more, so none spends an allowance and none can be used against
|
|
879
|
+
/// a standing one. The one `gate.settle` left is on /service/, reached only with an EVM (`eip155:`)
|
|
880
|
+
/// payment carrying the buyer's own EIP-3009 authorization — a signature that names the payment and
|
|
881
|
+
/// travels privately in the request. An ICP-rail (ckUSDC) payment offered there is refused before
|
|
882
|
+
/// any settle and pointed at the Candid `createJob`; /content/ sells nothing over the web
|
|
883
|
+
/// (`contentNotSoldOverHttp`). ckUSDC is taken only by the Candid calls, which check their caller.
|
|
884
|
+
/// WHAT WOULD MAKE THIS WRONG: a web route that settles a payment whose network is not `eip155:`.
|
|
885
|
+
http_request_update: (request: HttpRequest) -> (HttpResponse);
|
|
886
|
+
/// PUBLIC: a wallet calls this BEFORE its user has any standing here — that is the whole point of
|
|
887
|
+
/// the call — so gating it would make the message unreadable at exactly the moment it is needed,
|
|
888
|
+
/// and a signature given without it is a signature given blind.
|
|
889
|
+
///
|
|
890
|
+
/// ICRC-21 consent messages for this market's own state-changing methods.
|
|
891
|
+
///
|
|
892
|
+
/// Every canister a signer is asked to sign FOR needs this, or the wallet cannot render what it
|
|
893
|
+
/// is approving. Two shape facts, both learned from live signer failures and both load-bearing:
|
|
894
|
+
///
|
|
895
|
+
/// · `user_preferences : {}` accepts ANY record. The spec nests language at
|
|
896
|
+
/// user_preferences.metadata.language, and declaring the flat `{ language }` TRAPPED every
|
|
897
|
+
/// real signer's request ("did not find field language in record"; OISY, 2026-07-25).
|
|
898
|
+
/// `record {}` is a supertype of every record, so it decodes any signer's preferences without
|
|
899
|
+
/// trapping and is a widening — therefore compatible — interface.
|
|
900
|
+
/// · The reply REQUIRES `metadata` beside `consent_message`. Omitting it made OISY fail to
|
|
901
|
+
/// decode the reply ("Cannot find required field metadata", 2026-07-26). language is "en"
|
|
902
|
+
/// because the messages ARE English; the field states what they are, not what was asked for.
|
|
903
|
+
///
|
|
904
|
+
/// The fallback says the method name rather than inventing a description. A wallet showing an
|
|
905
|
+
/// approving-sounding sentence for a method this canister does not recognise is the signing-time
|
|
906
|
+
/// version of a fabricated answer.
|
|
907
|
+
icrc21_canister_call_consent_message: (request:
|
|
908
|
+
record {
|
|
909
|
+
arg: blob;
|
|
910
|
+
method: text;
|
|
911
|
+
user_preferences: record {};
|
|
912
|
+
}) ->
|
|
913
|
+
(variant {
|
|
914
|
+
Err: record {
|
|
915
|
+
description: text;
|
|
916
|
+
error_code: nat;
|
|
917
|
+
};
|
|
918
|
+
Ok:
|
|
919
|
+
record {
|
|
920
|
+
consent_message: variant {GenericDisplayMessage: text;};
|
|
921
|
+
metadata: record {
|
|
922
|
+
language: text;
|
|
923
|
+
utc_offset_minutes: opt int16;
|
|
924
|
+
};
|
|
925
|
+
};
|
|
926
|
+
}) query;
|
|
927
|
+
/// PUBLIC: whether this market is trading at all. A buyer whose job creation refuses needs to be
|
|
928
|
+
/// able to tell "the market is halted" from "my request was wrong", and only this canister can
|
|
929
|
+
/// say which. Withholding it would turn an announced halt into an unexplained failure, which is
|
|
930
|
+
/// the fabricate-on-refusal shape read from the other side.
|
|
931
|
+
///
|
|
932
|
+
/// The market's own freeze — trade halted for everyone here, by this canister's admin.
|
|
933
|
+
///
|
|
934
|
+
/// DELIBERATELY NOT the engram's freeze, which stops one owner's engram signing at its guardian's
|
|
935
|
+
/// word. The two were a single field for as long as they shared a struct, and a guardian who
|
|
936
|
+
/// froze their own engram could not tell they had not also frozen a marketplace. Separate facts,
|
|
937
|
+
/// separate authorities, separate switches.
|
|
938
|
+
isTradeFrozen: () -> (bool) query;
|
|
939
|
+
/// `total` is the head counter, not the row count: this is a bounded ring, so a page that
|
|
940
|
+
/// reported only what it returned would make a trimmed log look complete.
|
|
941
|
+
listAuditEntries: (start: nat, limit: nat) -> (Result_8) query;
|
|
942
|
+
/// Admin AND the enrolled viewers themselves — L8's roster rule. A viewer who cannot see the
|
|
943
|
+
/// roster cannot tell whether their own enrolment survived a rotation.
|
|
944
|
+
listAuditViewers: () -> (Result_9) query;
|
|
945
|
+
/// PUBLIC: the catalogue is the storefront. A shop that refuses to say what it sells to
|
|
946
|
+
/// anyone who has not already bought is not a shop.
|
|
947
|
+
listContent: (start: nat, limit: nat) -> (Page_6) query;
|
|
948
|
+
/// L11 names this canister's reason for having a denial log at all: the commerce surface is where
|
|
949
|
+
/// the fabricated-empty class was born, and a refusal that leaves no trace cannot be counted,
|
|
950
|
+
/// alerted on, or disproved.
|
|
951
|
+
listDenialEntries: (start: nat, limit: nat) -> (Result_8) query;
|
|
952
|
+
/// Purchase history for one endpoint: its creator sees every row, a buyer sees their own.
|
|
953
|
+
///
|
|
954
|
+
/// ⟨0.16.0⟩ Two defects close here at once, and they are the same defect wearing two hats.
|
|
955
|
+
///
|
|
956
|
+
/// The engram matched a buyer with `buyer == Principal.toText(msg.caller)` — string equality
|
|
957
|
+
/// against a field that holds a principal on one rail and a `0x` address on the other. So an
|
|
958
|
+
/// EVM-rail buyer could not see their OWN purchases, ever. `Payer` makes that an arm match.
|
|
959
|
+
///
|
|
960
|
+
/// And the filtered rows were dropped SILENTLY: a buyer reading someone else's endpoint got a
|
|
961
|
+
/// short list with nothing saying rows had been removed, indistinguishable from an endpoint with
|
|
962
|
+
/// few sales. `withheld` now counts them, so "no purchases here" and "not yours to see" are
|
|
963
|
+
/// different answers — without naming whose they were.
|
|
964
|
+
listEndpointPurchases: (endpointId: text, start: nat, limit: nat) ->
|
|
965
|
+
(Result_7) query;
|
|
966
|
+
/// PUBLIC: the shelves, like the catalogue. `#Deleted` listings are excluded from an
|
|
967
|
+
/// unfiltered read and COUNTED in `withheld` — a shop with three deleted listings and a shop with
|
|
968
|
+
/// none look different, which is the honest answer, without publishing what was withdrawn.
|
|
969
|
+
listEndpoints: (statusFilter: opt EndpointStatus, start: nat, limit:
|
|
970
|
+
nat) -> (Page_3) query;
|
|
971
|
+
/// ⟨0.16.0⟩ THE `listJobs` FIX — the second of the two fabricated empties L11 names.
|
|
972
|
+
///
|
|
973
|
+
/// The engram's version ended its authorization check with `if (not (isOwner or
|
|
974
|
+
/// isServiceOperator)) { return [] }`, and its own `.did` comment declared the behaviour on the
|
|
975
|
+
/// wire: "Fails closed to [] for an unknown service or an unauthorized caller (no existence
|
|
976
|
+
/// leak)." So a refused caller and a service with genuinely no jobs were the same answer, and it
|
|
977
|
+
/// was documented as a feature.
|
|
978
|
+
///
|
|
979
|
+
/// THE EXISTENCE-LEAK JUSTIFICATION DOES NOT HOLD, checked at the site rather than argued: the
|
|
980
|
+
/// sibling `getJobCounts()` is an UNGATED public query over the same registry, so the number of jobs
|
|
981
|
+
/// is already world-readable. Nothing was being protected — the empty list hid the refusal from
|
|
982
|
+
/// the person refused, and hid it from every honesty gate too.
|
|
983
|
+
///
|
|
984
|
+
/// It could not be caught mechanically either, and that is the more interesting half. The gates
|
|
985
|
+
/// scan for a known set of authorization helpers; this method's check is two LOCAL BOOLEANS
|
|
986
|
+
/// (`isOwner`, `isServiceOperator`), so the analyzer matched nothing and reported no finding. Its
|
|
987
|
+
/// siblings `getJob` and `getJobResult` had already been converted to traps for exactly this
|
|
988
|
+
/// defect; this one survived because it was written differently, not because it was ruled safe.
|
|
989
|
+
///
|
|
990
|
+
/// Now: `#Refused` when the caller may not ask, `#NotFound` when the service does not exist —
|
|
991
|
+
/// two facts that were one empty list.
|
|
992
|
+
listJobs: (serviceId: text, statusFilter: opt JobStatus, start: nat, limit:
|
|
993
|
+
nat) -> (Result_6) query;
|
|
994
|
+
/// The parked withdrawals belonging to this caller, so an unresolved payout is discoverable
|
|
995
|
+
/// rather than something a seller has to already know the id of.
|
|
996
|
+
///
|
|
997
|
+
/// PAGED, and NOT taken as a bounded exemption, though the temptation was real: a seller parks a
|
|
998
|
+
/// withdrawal only when a ledger call rejects, and each withdrawal zeroes their balance — so the
|
|
999
|
+
/// list is 0 or 1 in every ordinary history. It is not STRUCTURALLY bounded, which is the test
|
|
1000
|
+
/// the page-shape rule actually applies: a run of ledger outages across a run of sales
|
|
1001
|
+
/// accumulates rows with nothing to stop it. A bare array that silently truncated would be
|
|
1002
|
+
/// indistinguishable from "you have no other unresolved payouts", and this is money.
|
|
1003
|
+
///
|
|
1004
|
+
/// `withheld` is always 0 here. The filter is by owner and a caller cannot own a row they may
|
|
1005
|
+
/// not see, so nothing is ever hidden from them — unlike `listEndpointPurchases`, where a
|
|
1006
|
+
/// non-owner's view withholds other buyers' rows. Stated because a reader comparing the two
|
|
1007
|
+
/// should know the zero is a fact about this surface, not an unimplemented field.
|
|
1008
|
+
listMyPendingWithdrawals: (start: nat, limit: nat) -> (Result_5) query;
|
|
1009
|
+
/// ⟨0.16.0⟩ `listAllServices` FOLDS in as `includeDisabled`.
|
|
1010
|
+
///
|
|
1011
|
+
/// The engram had two methods because the visibility rule differed: everyone saw enabled
|
|
1012
|
+
/// services; the owner and active sellers also saw disabled drafts. On a shared market that
|
|
1013
|
+
/// rule becomes per-seller — you see every enabled service, plus YOUR OWN disabled drafts — which
|
|
1014
|
+
/// is one query with a flag rather than two methods with divergent gates.
|
|
1015
|
+
///
|
|
1016
|
+
/// `withheld` counts other sellers' drafts that the flag asked for and the caller may not see. A
|
|
1017
|
+
/// seller can tell "no drafts" from "not yours to see" without learning whose they are.
|
|
1018
|
+
listServices: (includeDisabled: bool, start: nat, limit: nat) ->
|
|
1019
|
+
(Page) query;
|
|
1020
|
+
/// ⟨0.16.0⟩ THE APPROVAL PARAMETER IS GONE, and its absence is the split working.
|
|
1021
|
+
///
|
|
1022
|
+
/// The engram's openSession took an `approvalId` and could answer `#approvalPending` — because
|
|
1023
|
+
/// there, opening a session was THE ENGRAM SPENDING ITS OWNER'S MONEY, so the owner's approval
|
|
1024
|
+
/// queue gated it.
|
|
1025
|
+
///
|
|
1026
|
+
/// Here the caller is a BUYER. If that buyer is an engram, its owner's approval was checked on
|
|
1027
|
+
/// the engram before it ever called this canister — approvals stayed with custody, and so did the
|
|
1028
|
+
/// signing they gate. The market receives a payment signature that someone already authorised;
|
|
1029
|
+
/// asking it to also adjudicate whose approval backs that signature would put one owner's custody
|
|
1030
|
+
/// policy on a canister shared with everyone else's.
|
|
1031
|
+
///
|
|
1032
|
+
/// So a whole return arm disappears rather than moving. That is what a correct seam looks like:
|
|
1033
|
+
/// the market got simpler because the responsibility went where it belonged, not because anything
|
|
1034
|
+
/// was dropped.
|
|
1035
|
+
///
|
|
1036
|
+
/// ⚠ ⟨2026-09-02 — OWNER-RULED: MARKET SESSIONS ARE NOT REQUIRED FOR NOW. The interfaces stay;
|
|
1037
|
+
/// this note is what makes them honest.⟩ THE SESSION LIFECYCLE HERE IS INERT: a session can be
|
|
1038
|
+
/// opened, and it can be closed or refunded, but it can never be SPENT. `gate.consumeVoucher` has
|
|
1039
|
+
/// **zero call sites in this canister** — the only two in the estate are on the ENGRAM, which
|
|
1040
|
+
/// runs its own separate paid-memory rail with its own gateway, `sess-N` counter and escrow
|
|
1041
|
+
/// subaccount. So a market session is deposit-in and full-refund-out, and a buyer who opens one
|
|
1042
|
+
/// gets their money back minus two ckUSDC ledger fees, having bought nothing.
|
|
1043
|
+
///
|
|
1044
|
+
/// WHY THE METHODS ARE KEPT RATHER THAN DELETED: they are a complete, reviewable lifecycle
|
|
1045
|
+
/// (open / end / forceClose / forceResolve / recoverEscrow / getSessionCounts) against a real
|
|
1046
|
+
/// escrow, and the ruling is "not required for now", not "never". Deleting them would mean
|
|
1047
|
+
/// re-deriving the seam when session billing is wanted, and the market is WITHHELD at 0.16.0 —
|
|
1048
|
+
/// in neither environment — so nothing here is reachable by a customer today regardless.
|
|
1049
|
+
///
|
|
1050
|
+
/// WHAT WOULD MAKE THIS SAFE TO ENABLE: a voucher-spend path — something on this canister that
|
|
1051
|
+
/// calls `gate.consumeVoucher` and delivers what the voucher paid for. Until that exists, the
|
|
1052
|
+
/// two `#Session` billing refusals say session billing is UNAVAILABLE rather than pointing
|
|
1053
|
+
/// buyers at `openSession`, which is what they used to do.
|
|
1054
|
+
///
|
|
1055
|
+
/// ⟨2026-09-30, ic402 2.17.0⟩ That future spend path calls `gate.consumeVoucherFrom(msg.caller, …)`,
|
|
1056
|
+
/// not the bare `consumeVoucher`: the signed fallback is off on this canister, so the bare call
|
|
1057
|
+
/// refuses every voucher, and only the session key calling as itself may spend a session.
|
|
1058
|
+
openSession: (config: SessionConfig, sig: PaymentSignature) -> (Result_4);
|
|
1059
|
+
/// RENAMED from `requestSession` (census tier-3 #21): it is a pure dry run — a quote for a session
|
|
1060
|
+
/// the caller has not opened and is not committed to. `request` read as an action with an effect.
|
|
1061
|
+
///
|
|
1062
|
+
/// PUBLIC: a price list nobody can read is not a price list, and this is the market's. ⟨The
|
|
1063
|
+
/// sentence was already here as "Ungated:"; only the marker the L8 gate greps for was missing,
|
|
1064
|
+
/// which is why this method read as undefended when it was not.⟩
|
|
1065
|
+
/// ⟨CORRECTED 2026-08-24 — this said "⟨L7⟩ UPDATE, NOT QUERY" and cited a market ruling that
|
|
1066
|
+
/// DEC-107 had already closed.⟩ QUERY, per the ruled quote convention: FAST READS. Both
|
|
1067
|
+
/// `quotePayment` and `previewOpenSession` become queries, and L7's open-ruling exception
|
|
1068
|
+
/// retires with them — so the argument written here for keeping it an update was arguing the
|
|
1069
|
+
/// losing side of a decision already made, and was added the same day by not reading forward
|
|
1070
|
+
/// to the ruling. `gate.offerSession` is pure — it validates the intent and returns it — so
|
|
1071
|
+
/// nothing forced the update in the first place.
|
|
1072
|
+
///
|
|
1073
|
+
/// ONE-REPLICA CAVEAT, stated here because the ruling requires it at both sites: a query is one
|
|
1074
|
+
/// replica's word and is NOT a settleable challenge. Nothing quoted here can be redeemed. The
|
|
1075
|
+
/// commitment happens in `openSession`, which IS a certified update call, and a buyer who needs
|
|
1076
|
+
/// the price attested rather than quoted takes it from that reply.
|
|
1077
|
+
///
|
|
1078
|
+
/// ⟨2026-10-02⟩ Answers the house `Types.SessionIntent` — ic402's intent with `expiry` spelled
|
|
1079
|
+
/// `expiryNs`. `openSession` hands ic402 its own record, from `sessionIntent()` below.
|
|
1080
|
+
previewOpenSession: () -> (SessionIntent) query;
|
|
1081
|
+
/// PUBLIC: what it costs to pay this market a given amount, on every rail it settles.
|
|
1082
|
+
///
|
|
1083
|
+
/// ⟨0.16.0⟩ Reshaped from `getPaymentRequirements(amount) : [PaymentRequirement]`. A bare array
|
|
1084
|
+
/// was a `get*` returning a list, which reads like a collection to page through; this is a QUOTE,
|
|
1085
|
+
/// a single answer to a single question, and it now says so in its name and its type.
|
|
1086
|
+
///
|
|
1087
|
+
/// The quote ECHOES the amount it priced. A caller comparing this to a later 402 challenge is
|
|
1088
|
+
/// then comparing two stated numbers instead of assuming the one they sent came back unchanged.
|
|
1089
|
+
///
|
|
1090
|
+
/// Zero is refused. ic402's requirement builder traps on a zero amount, and a quote for nothing
|
|
1091
|
+
/// is not a question this canister can answer — better a legible refusal than a trap in the
|
|
1092
|
+
/// caller's own callback.
|
|
1093
|
+
///
|
|
1094
|
+
/// ⟨CORRECTED 2026-10-02 — the nonce is GONE from the quote; the two paragraphs below are kept as
|
|
1095
|
+
/// the record of why.⟩ Each requirement is now a `QuotedRequirement`: ic402's requirement without
|
|
1096
|
+
/// its `nonce` (and with `expiry` spelled `expiryNs`). The paragraphs below describe the throwaway
|
|
1097
|
+
/// that was published until this date; the pre-cut review recommended dropping it over ratifying
|
|
1098
|
+
/// it, and it was dropped. "Do not pay against it" is now enforced by there being nothing to pay
|
|
1099
|
+
/// against.
|
|
1100
|
+
///
|
|
1101
|
+
/// ⚠ THE NONCE IN EACH REQUIREMENT IS A THROWAWAY — DO NOT PAY AGAINST IT. `requireAll` mints a
|
|
1102
|
+
/// server nonce with a HashMap put, and this is a QUERY, so the write is discarded on return.
|
|
1103
|
+
/// The nonce is published and does not exist. Signing a payment against it settles as
|
|
1104
|
+
/// `#expired("Server nonce expired, already used, or bound to a different network/token")`,
|
|
1105
|
+
/// which is wrong on every clause and points a caller back here for another dead one.
|
|
1106
|
+
///
|
|
1107
|
+
/// That is by design, not an oversight now excused: a quote is a PRICE LIST, not a settleable
|
|
1108
|
+
/// challenge. The HTTP surface of this same canister already draws the line in the same place —
|
|
1109
|
+
/// `/discovery/resources` is a catalog with a throwaway nonce, while `/content` and `/service`
|
|
1110
|
+
/// return `httpUpgrade()` so the challenge is minted in an UPDATE and persists. This is the
|
|
1111
|
+
/// Candid twin of `/discovery/resources`, and it was the only one of the pair not saying so.
|
|
1112
|
+
/// ⟨CORRECTED 2026-09-30⟩ Only `/service` upgrades now; `/content` sells nothing over the web and
|
|
1113
|
+
/// answers 410 from the query. And unlike this quote, the web catalog lists EVM requirements only.
|
|
1114
|
+
///
|
|
1115
|
+
/// TO GET A PAYABLE CHALLENGE, call the resource: `getContent(contentId, null)` or
|
|
1116
|
+
/// `createJob(serviceId, params, null)`. Both are updates, so the nonce they mint survives.
|
|
1117
|
+
/// This matters most on the ckUSDC rail, which is where customer payments settle: ICP has no
|
|
1118
|
+
/// on-chain nonce, so `settle` ALWAYS requires the server one. A conformant EVM x402 client
|
|
1119
|
+
/// sends no server nonce at all and is unaffected.
|
|
1120
|
+
quotePayment: (amountE6: nat) -> (Result_3) query;
|
|
1121
|
+
/// Admin recovery: re-drive a job stuck mid-settlement — a broadcast EVM transfer whose outcome
|
|
1122
|
+
/// this canister never learned. Reads the chain and finishes what the settle started.
|
|
1123
|
+
///
|
|
1124
|
+
/// Unstick, not decide: it does not choose an outcome, it discovers one. When it cannot, it says
|
|
1125
|
+
/// so and points at resolveJob, which is where a human decision belongs.
|
|
1126
|
+
///
|
|
1127
|
+
/// ⟨2026-10-02, the payment library's refusal channel — audited at this site⟩ `#Conflict` here
|
|
1128
|
+
/// carries ic402's text, and ic402 answers with one text channel for every refusal. AUDITED:
|
|
1129
|
+
/// this method's own gate (`requireAdmin`) decides authorization before the library is called, and every
|
|
1130
|
+
/// text the library returns here is a STATE refusal (job missing, no parked tx, no confirm hook wired, the chain's answer) — including "not found", which
|
|
1131
|
+
/// arrives as `#Conflict` rather than `#NotFound`. Read the MESSAGE for which. This is the 0.16.0
|
|
1132
|
+
/// contract as ratified on 2026-10-02; giving the library one arm per kind of refusal is post-cut
|
|
1133
|
+
/// dependency work.
|
|
1134
|
+
reconcileJob: (jobId: text) -> (Result_2);
|
|
1135
|
+
/// Admin recovery for a session whose settlement was interrupted mid-flight. Same line as
|
|
1136
|
+
/// forceCloseSession: it completes what was already decided, it does not decide anything.
|
|
1137
|
+
///
|
|
1138
|
+
/// Freeze-gated, unlike forceClose — reconciliation can PAY a seller, and a halt must stop money
|
|
1139
|
+
/// moving outward. Force-close only returns a buyer's own deposit, which a freeze must not trap
|
|
1140
|
+
/// (the same reasoning that leaves disputeJob un-gated).
|
|
1141
|
+
///
|
|
1142
|
+
/// ⟨2026-10-02, the payment library's refusal channel — audited at this site⟩ `#Conflict` here
|
|
1143
|
+
/// carries ic402's text, and ic402 answers with one text channel for every refusal. AUDITED:
|
|
1144
|
+
/// this method's own gate (`requireAdmin`) decides authorization before the library is called, and every
|
|
1145
|
+
/// text the library returns here is a STATE refusal (session missing, not parked in #closing, no parked tx, no EVM deposit or sender configured, the chain's answer) — including "not found", which
|
|
1146
|
+
/// arrives as `#Conflict` rather than `#NotFound`. Read the MESSAGE for which. This is the 0.16.0
|
|
1147
|
+
/// contract as ratified on 2026-10-02; giving the library one arm per kind of refusal is post-cut
|
|
1148
|
+
/// dependency work.
|
|
1149
|
+
reconcileSession: (sessionId: text) -> (Result_2);
|
|
1150
|
+
/// Settle a parked withdrawal by asking the ledger what happened, rather than assuming.
|
|
1151
|
+
///
|
|
1152
|
+
/// Re-sends the IDENTICAL transfer — same amount, same fee, same `created_at_time`. Within
|
|
1153
|
+
/// ICRC-1's 24h deduplication window there are exactly two outcomes and both are conclusive:
|
|
1154
|
+
/// · `#Err(#Duplicate)` — the original DID execute. The seller has been paid; discard the
|
|
1155
|
+
/// parked record.
|
|
1156
|
+
/// · `#Ok` — the original did NOT execute, and this call has now paid it. Discard.
|
|
1157
|
+
/// Anything else leaves the record parked and retryable; it is never guessed at.
|
|
1158
|
+
///
|
|
1159
|
+
/// Callable by the SELLER it belongs to. This pays only ever to the recorded seller, so a
|
|
1160
|
+
/// stranger calling it could not misdirect funds — but it is still scoped, because a parked
|
|
1161
|
+
/// withdrawal is the seller's business and its existence is a fact about their balance.
|
|
1162
|
+
///
|
|
1163
|
+
/// The precedent is the cashier's `recoverPendingClaim`: same saga, same rail, same reason.
|
|
1164
|
+
recoverContentWithdrawal: (withdrawalId: nat) -> (Result);
|
|
1165
|
+
/// Admin recovery: return unconsumed escrow from a stuck or terminal session's own subaccount.
|
|
1166
|
+
/// THE ONE RECOVERY METHOD THAT MOVES REAL MONEY.
|
|
1167
|
+
///
|
|
1168
|
+
/// It is on the right side of "an admin may unstick, and may not decide" for one structural
|
|
1169
|
+
/// reason, not by policy: THE DESTINATION IS NOT A PARAMETER. ic402 hardcodes the refund to
|
|
1170
|
+
/// `session.payer`, and the admin's principal appears nowhere in the transfer. The admin supplies
|
|
1171
|
+
/// the session, the token and an amount; they cannot supply a recipient. An admin can unstick a
|
|
1172
|
+
/// refund and cannot redirect one, and that is enforced by the shape of the call rather than by
|
|
1173
|
+
/// this comment.
|
|
1174
|
+
///
|
|
1175
|
+
/// ic402 authorizes `caller == session.payer`, so the payer is read from the session and passed
|
|
1176
|
+
/// through — the same pattern the engram used, and NOT the seller-impersonation defect, because
|
|
1177
|
+
/// here the identity being passed is the one that RECEIVES the money rather than one being
|
|
1178
|
+
/// presented as the actor. The audit row records the real admin who forced it.
|
|
1179
|
+
///
|
|
1180
|
+
/// Idempotent through the ledger: the first refund drains the subaccount, so a second call fails
|
|
1181
|
+
/// with insufficient funds rather than paying twice.
|
|
1182
|
+
///
|
|
1183
|
+
/// ⟨2026-10-02, the payment library's refusal channel — audited at this site⟩ `#Conflict` here
|
|
1184
|
+
/// carries ic402's text, and ic402 answers with one text channel for every refusal. AUDITED:
|
|
1185
|
+
/// this method's own gate (`requireAdmin`; the library's own only-the-payer check cannot fire, because the payer is read from the session and passed through) decides authorization before the library is called, and every
|
|
1186
|
+
/// text the library returns here is a STATE refusal (an open session, nothing left to recover, the ledger's refusal) — including "not found", which
|
|
1187
|
+
/// arrives as `#Conflict` rather than `#NotFound`. Read the MESSAGE for which. This is the 0.16.0
|
|
1188
|
+
/// contract as ratified on 2026-10-02; giving the library one arm per kind of refusal is post-cut
|
|
1189
|
+
/// dependency work.
|
|
1190
|
+
///
|
|
1191
|
+
/// ⟨2026-10-05, ic402 2.17.3 — beside the first line and the list above⟩ Escrow comes back only
|
|
1192
|
+
/// from a session that reads `closed`. A session that reads `closing` or `expired` is refused, with
|
|
1193
|
+
/// nothing moved — a fourth state refusal — so a "stuck" session here means one that
|
|
1194
|
+
/// `forceResolveSession` has closed first; recover it within 24 hours of that force.
|
|
1195
|
+
recoverEscrow: (sessionId: text, amount: TokenAmount) -> (Result);
|
|
1196
|
+
/// Register as a seller by naming the engram you own.
|
|
1197
|
+
///
|
|
1198
|
+
/// The registry is asked, not trusted from the caller's word: `verifyEngramActiveOwner` confirms
|
|
1199
|
+
/// that THIS caller owns THAT engram and that it is #Active — which also means an abandoned
|
|
1200
|
+
/// engram cannot back a listing, since abandonment is terminal there and this market has no
|
|
1201
|
+
/// business inheriting a claim its owner walked away from.
|
|
1202
|
+
///
|
|
1203
|
+
/// Re-registering is how a seller refreshes an expiring binding, and it is idempotent apart from
|
|
1204
|
+
/// the timestamp. The byte count SURVIVES re-registration — otherwise the quota would reset every
|
|
1205
|
+
/// thirty days, which is a quota in name only.
|
|
1206
|
+
registerSeller: (engramId: principal) -> (Result_1);
|
|
1207
|
+
/// Register a paid service. Born DISABLED, like an endpoint and for the same reason: a listing
|
|
1208
|
+
/// that went live at creation would be sellable before its seller had checked it.
|
|
1209
|
+
///
|
|
1210
|
+
/// ⚠ THIS NEARLY DID NOT MAKE THE MOVE. It was deleted from the engram with the rest of the
|
|
1211
|
+
/// commerce graft and was not written here until a downstream web client failed to compile
|
|
1212
|
+
/// against a market that had no way to create a service at all — sellers could enable, disable
|
|
1213
|
+
/// and list services, and could never make one. Recorded because the gap was invisible to every
|
|
1214
|
+
/// gate: the canisters compiled, the .did was well formed, and check-client-idls only sees names
|
|
1215
|
+
/// that ARE declared.
|
|
1216
|
+
///
|
|
1217
|
+
/// `verificationMethod` stays a STRING with a parsed tail, exactly as the engram had it, because
|
|
1218
|
+
/// #BuyerConfirm carries a window ("BuyerConfirm:3600") and a bare variant cannot. The parse is
|
|
1219
|
+
/// total: an unrecognised method falls back to #AutoSettle rather than refusing, which is the
|
|
1220
|
+
/// engram's behaviour preserved deliberately — a seller mistyping a verification mode should get
|
|
1221
|
+
/// the safe default, not a service they cannot create.
|
|
1222
|
+
registerService: (name: text, description: text, serviceType: ServiceType,
|
|
1223
|
+
pricing: PricingScheme, verificationMethod: text, verifierCanisterId:
|
|
1224
|
+
opt text, verificationKey: opt blob, delivery: ServiceDeliveryMethod,
|
|
1225
|
+
timeout: nat, bindResult: opt bool) -> (Result_2);
|
|
1226
|
+
removeAuditViewer: (viewer: principal) -> (Result_1);
|
|
1227
|
+
/// Remove a per-payer override so that payer reverts to — and keeps TRACKING — the global policy.
|
|
1228
|
+
///
|
|
1229
|
+
/// Its pair's only other "undo" was overwriting the override with a frozen copy of the global
|
|
1230
|
+
/// policy, which then stops following later global changes. That is why the two never travel
|
|
1231
|
+
/// apart: without this one, every override is permanent in effect.
|
|
1232
|
+
removeCallerPolicy: (caller: principal) -> (Result_1);
|
|
1233
|
+
/// ⟨0.16.0⟩ RULED 2026-08-17: THE SELLER RESOLVES. Not the market admin.
|
|
1234
|
+
///
|
|
1235
|
+
/// The engram's `requireOwner` was coherent there because the engram's owner IS the seller — a
|
|
1236
|
+
/// dispute was between that owner and their buyer. Transcribing it here would have made the
|
|
1237
|
+
/// market admin the arbiter of commercial disputes between independent parties, moving their
|
|
1238
|
+
/// money: a business and probably a legal position, inherited by accident from a canister where
|
|
1239
|
+
/// "owner" meant something else.
|
|
1240
|
+
///
|
|
1241
|
+
/// The ruling keeps the power where it already was rather than quietly relocating it to us.
|
|
1242
|
+
///
|
|
1243
|
+
/// ⚠ THE ASYMMETRY IS REAL AND IS STATED, HERE AND ON THE WIRE. The accused judges. A seller can
|
|
1244
|
+
/// refuse a refund to a buyer who has already paid, and nothing on this canister overrides that:
|
|
1245
|
+
/// there is no admin override, no appeal, and no reputation system yet to price the behaviour.
|
|
1246
|
+
/// That is an honest description of who holds power in this market today, and a buyer is entitled
|
|
1247
|
+
/// to know it BEFORE they buy — which is why it is in the refusal text a buyer actually sees, not
|
|
1248
|
+
/// only in a comment they never will.
|
|
1249
|
+
///
|
|
1250
|
+
/// Writing it down is also what makes it revisitable: when a reputation system or an escrow
|
|
1251
|
+
/// timeout arrives, this method is where the change lands and this comment is the argument it has
|
|
1252
|
+
/// to answer.
|
|
1253
|
+
///
|
|
1254
|
+
/// ⟨2026-10-02, the payment library's refusal channel — audited at this site⟩ `#Conflict` here
|
|
1255
|
+
/// carries ic402's text, and ic402 answers with one text channel for every refusal. AUDITED:
|
|
1256
|
+
/// this method's own gate (the service's seller, checked above the library call) decides authorization before the library is called, and every
|
|
1257
|
+
/// text the library returns here is a STATE refusal (job missing, not in a resolvable state, a refund parked or failed) — including "not found", which
|
|
1258
|
+
/// arrives as `#Conflict` rather than `#NotFound`. Read the MESSAGE for which. This is the 0.16.0
|
|
1259
|
+
/// contract as ratified on 2026-10-02; giving the library one arm per kind of refusal is post-cut
|
|
1260
|
+
/// dependency work.
|
|
1261
|
+
resolveDispute: (jobId: text, refundBuyer: bool) -> (Result_1);
|
|
1262
|
+
/// Admin LAST RESORT: force a job to a terminal status when reconcileJob cannot determine one —
|
|
1263
|
+
/// a reverted or dead transaction that the chain will never resolve either way.
|
|
1264
|
+
///
|
|
1265
|
+
/// ⚠ THIS IS THE ONE PLACE THE ADMIN LINE BENDS, AND IT IS NAMED SO IT CANNOT BE USED CASUALLY.
|
|
1266
|
+
/// Elsewhere on this canister an admin may unstick and may not decide; here the whole point is
|
|
1267
|
+
/// that no discoverable answer exists, so someone must choose whether the seller was paid or
|
|
1268
|
+
/// the buyer refunded. The narrowness is the safeguard: ic402 rejects any non-terminal target, so
|
|
1269
|
+
/// the choice is between #Settled, #Refunded and #Expired and cannot be used to reopen a job or
|
|
1270
|
+
/// move it sideways. Every use is a chained audit row naming the admin and the status they chose.
|
|
1271
|
+
///
|
|
1272
|
+
/// `#other` fails closed. It is a placeholder for a job state this canister cannot name, and
|
|
1273
|
+
/// forcing a job into a state we cannot name is not a recovery.
|
|
1274
|
+
///
|
|
1275
|
+
/// ⟨2026-10-02, the payment library's refusal channel — audited at this site⟩ `#Conflict` here
|
|
1276
|
+
/// carries ic402's text, and ic402 answers with one text channel for every refusal. AUDITED:
|
|
1277
|
+
/// this method's own gate (`requireAdmin`) decides authorization before the library is called, and every
|
|
1278
|
+
/// text the library returns here is a STATE refusal (job missing, not stuck in #Settling, a target that is not terminal) — including "not found", which
|
|
1279
|
+
/// arrives as `#Conflict` rather than `#NotFound`. Read the MESSAGE for which. This is the 0.16.0
|
|
1280
|
+
/// contract as ratified on 2026-10-02; giving the library one arm per kind of refusal is post-cut
|
|
1281
|
+
/// dependency work.
|
|
1282
|
+
resolveJob: (jobId: text, terminal: JobStatus) -> (Result_1);
|
|
1283
|
+
setAdmin: (newAdmin: principal) -> (Result_1);
|
|
1284
|
+
/// Override the policy for ONE payer — the market's per-buyer risk control.
|
|
1285
|
+
setCallerPolicy: (caller: principal, policy: SpendingPolicy) -> (Result_1);
|
|
1286
|
+
/// ⟨0.16.0, L2.6⟩ `enableEndpoint` and `disableEndpoint` FOLD into one setter.
|
|
1287
|
+
///
|
|
1288
|
+
/// They were byte-identical apart from the status they wrote and the word in their name, which is
|
|
1289
|
+
/// the definition of a boolean expressed as two methods. L2.6 rules one idiom for a boolean
|
|
1290
|
+
/// feature, and the fold also removes a real hazard: two methods meant two authorization checks
|
|
1291
|
+
/// to keep in step, and a divergence between them would have been invisible to a reader looking at
|
|
1292
|
+
/// either one alone.
|
|
1293
|
+
///
|
|
1294
|
+
/// `#Deleted` stays a refusal in both directions and stays `#Conflict`, not `#Invalid`: the
|
|
1295
|
+
/// request is well-formed and permitted, the endpoint is simply in a terminal state — and calling
|
|
1296
|
+
/// that `#Invalid` would tell a caller to fix their request when nothing about it is wrong.
|
|
1297
|
+
setEndpointEnabled: (endpointId: text, enabled: bool) -> (Result_1);
|
|
1298
|
+
/// Set an endpoint's price. THE SELLER'S, not the admin's.
|
|
1299
|
+
///
|
|
1300
|
+
/// requireEndpointOwner admits the market admin for the enable/disable pair, because a listing an
|
|
1301
|
+
/// admin cannot take down is a moderation problem. PRICE IS DIFFERENT: changing what a seller
|
|
1302
|
+
/// charges is trading on their behalf, and no recovery story needs it.
|
|
1303
|
+
setEndpointPrice: (endpointId: text, priceE6: nat) -> (Result_1);
|
|
1304
|
+
/// A seller registers the EVM address their EVM-paid settlements are sent to.
|
|
1305
|
+
///
|
|
1306
|
+
/// SELF-SCOPED: the address is keyed by `msg.caller`, so the only account a caller can change is
|
|
1307
|
+
/// their own — there is no gate to get wrong, because there is nothing to gate. The engram's
|
|
1308
|
+
/// `requireAgentOrOwner` here was protecting a canister-level roster that a shared market does
|
|
1309
|
+
/// not have. Anonymous is refused: an anonymous payout address is an address nobody can be paid at.
|
|
1310
|
+
///
|
|
1311
|
+
/// Without one, an EVM-paid job's settlement FAILS CLOSED and rolls back — the market does not
|
|
1312
|
+
/// cross rails and pay an EVM seller out of the ICP pool.
|
|
1313
|
+
setEvmSellerPayout: (address: text) -> (Result_1);
|
|
1314
|
+
/// Set the market's global spending policy.
|
|
1315
|
+
///
|
|
1316
|
+
/// The audit row records the LIMITS, not just the fact of a change, so a WIDENING is
|
|
1317
|
+
/// distinguishable from a tightening after the fact. A policy raise is the quiet half of most
|
|
1318
|
+
/// money incidents, and "policy changed" alone cannot tell an investigator which way.
|
|
1319
|
+
setPaymentPolicy: (policy: SpendingPolicy) -> (Result_1);
|
|
1320
|
+
/// Whom this canister anchors to. Set by the admin at deploy; `sys` because it is protocol
|
|
1321
|
+
/// configuration, not a product surface.
|
|
1322
|
+
///
|
|
1323
|
+
/// ⟨RENAMED 2026-10-02 from `sysSetRegistryId` — the sys-tier retier ruled 2026-08-24, executed
|
|
1324
|
+
/// under the owner's 2026-10-02 ruling to carry out the pre-cut interface decisions as
|
|
1325
|
+
/// recommended.⟩ The sentence above is why it was sys-named, and the retier is why it no longer
|
|
1326
|
+
/// is: the `sys` prefix names the canister-to-canister tier, whose callers are other canisters,
|
|
1327
|
+
/// and this method's only caller is a PERSON — the market's admin, at deploy. Keeping it there
|
|
1328
|
+
/// made "a `sys` method has no human caller" false, and so uncheckable. A plain admin setter
|
|
1329
|
+
/// (`setAdmin`'s sibling) is what it always was.
|
|
1330
|
+
setRegistryId: (id: principal) -> (Result_1);
|
|
1331
|
+
/// ⟨0.16.0, L2.6⟩ enableService + disableService FOLD, same as the endpoint pair.
|
|
1332
|
+
///
|
|
1333
|
+
/// Deliberately NOT admin-overridable, unlike endpoints. Disabling a seller's service is a
|
|
1334
|
+
/// commercial act against that seller, and an admin who needs to remove a listing should do it
|
|
1335
|
+
/// through a recorded moderation path rather than by silently flipping a flag that looks like the
|
|
1336
|
+
/// seller did it. Endpoints allow the admin override because a deleted endpoint is already
|
|
1337
|
+
/// terminal and visible; a disabled service looks like the seller's own choice.
|
|
1338
|
+
///
|
|
1339
|
+
/// ⚠ `#Conflict` HERE ALSO COVERS "NOT THE SELLER" — the same ic402 seam as `claimJob`,
|
|
1340
|
+
/// `confirmJob` and `disputeJob`. `enableService`/`disableService` return `Service not found`
|
|
1341
|
+
/// (properly `#NotFound`) and **`Not the seller`** (properly `#Refused`) as plain strings, so
|
|
1342
|
+
/// the arm is gone before this method sees it. Read the MESSAGE, not the arm.
|
|
1343
|
+
/// ⟨2026-08-21; the project's backlog carries the three costed fixes and why none is a
|
|
1344
|
+
/// one-liner.⟩
|
|
1345
|
+
/// ⟨CORRECTED 2026-10-02, audited at this site — the paragraph above no longer holds here.⟩
|
|
1346
|
+
/// `requireServiceSeller` now runs BEFORE the library: a missing service is refused `#NotFound`
|
|
1347
|
+
/// and another seller `#Refused`, by this canister, with its own sentences. The library is then
|
|
1348
|
+
/// called with the caller already proven to be the seller, so its "Not the seller" and "Service
|
|
1349
|
+
/// not found" texts cannot reach the `#Conflict` arm below except across a race with a deletion.
|
|
1350
|
+
/// The seam is closed on this method by the gate, not by the library; the others keep their
|
|
1351
|
+
/// caveats.
|
|
1352
|
+
setServiceEnabled: (id: text, enabled: bool) -> (Result_1);
|
|
1353
|
+
/// A seller submits a finished job's result.
|
|
1354
|
+
///
|
|
1355
|
+
/// AUTHORITY IS THE JOB'S SELLER, checked by ic402 against the claim — not "a seller of this
|
|
1356
|
+
/// canister", which on a shared market would mean any seller could submit results for any other
|
|
1357
|
+
/// seller's jobs. The engram's `requireAgentOrOwner` was a canister-level gate that made sense
|
|
1358
|
+
/// only while one canister meant one seller.
|
|
1359
|
+
///
|
|
1360
|
+
/// FREEZE-GATED because it can MOVE MONEY: the auto-settling verification modes (#AutoSettle /
|
|
1361
|
+
/// #HashMatch / #ZkGroth16) pay the seller inside submitResult. Only #BuyerConfirm defers to
|
|
1362
|
+
/// confirmJob. A reader who assumes "submitting a result is not a payment" is wrong for three of
|
|
1363
|
+
/// the four modes.
|
|
1364
|
+
///
|
|
1365
|
+
/// ⚠ `#Conflict` HERE ALSO COVERS "NOT THE ASSIGNED OPERATOR" — a refusal of the caller's
|
|
1366
|
+
/// STANDING, not of the job's state. ⟨2026-10-02, audited at this site⟩ ic402's `submitResult`
|
|
1367
|
+
/// checks the caller against the job's operator itself and answers with one text channel, so
|
|
1368
|
+
/// "Not the assigned operator" and "Job not assigned" arrive here exactly as "Cannot submit result
|
|
1369
|
+
/// in status: …" does. A caller that branches on the arm retries a submission that can never be
|
|
1370
|
+
/// theirs. Read the MESSAGE, not the arm. This is the 0.16.0 contract as ratified on 2026-10-02;
|
|
1371
|
+
/// giving the library one arm per kind of refusal is post-cut dependency work.
|
|
1372
|
+
submitJobResult: (jobId: text, result: blob, proof: opt blob, actualCost:
|
|
1373
|
+
opt nat) -> (Result_1);
|
|
1374
|
+
/// Upload a whole file in one call.
|
|
1375
|
+
uploadContent: (id: text, mimeType: text, data: blob) -> (Result_1);
|
|
1376
|
+
/// Push one chunk of a started upload. NOT charged — uploadContentInit already reserved the
|
|
1377
|
+
/// declared total, so charging again here would bill the same bytes twice.
|
|
1378
|
+
uploadContentChunk: (id: text, index: nat, data: blob) -> (Result_1);
|
|
1379
|
+
/// Begin a chunked upload. The WHOLE declared size is charged here, up front.
|
|
1380
|
+
///
|
|
1381
|
+
/// Charging at init rather than per chunk is the point: a per-chunk charge would let a seller
|
|
1382
|
+
/// begin an upload that cannot finish, and discover it only after claiming most of the memory.
|
|
1383
|
+
/// Reserving the declared total means an over-quota upload is refused before its first byte lands.
|
|
1384
|
+
uploadContentInit: (id: text, mimeType: text, totalSize: nat, chunkCount:
|
|
1385
|
+
nat) -> (Result_1);
|
|
1386
|
+
/// The buyer-facing half of the same check, kept because it answers a different question: "is my
|
|
1387
|
+
/// grant still good", asked BEFORE spending a round trip on a chunk. Documented in ACCESS-CONTROL
|
|
1388
|
+
/// as the buyer's pre-flight.
|
|
1389
|
+
verifyGrant: (grant: AccessGrant) -> (Result_1) query;
|
|
1390
|
+
/// Withdraw everything this caller has earned from content sales, to their own principal.
|
|
1391
|
+
///
|
|
1392
|
+
/// NO DESTINATION ARGUMENT, and that is the security property rather than a missing feature: the
|
|
1393
|
+
/// funds go to `msg.caller` and nowhere else, so a stolen or mis-signed call cannot redirect a
|
|
1394
|
+
/// seller's balance to an attacker's account. A seller who wants the money elsewhere moves it
|
|
1395
|
+
/// from their own account, where they are the only signer.
|
|
1396
|
+
///
|
|
1397
|
+
/// NOT GATED ON `requireSeller`. A seller binding expires and can be revoked; earnings do not.
|
|
1398
|
+
/// Gating withdrawal on current seller status would strand the balance of anyone whose engram
|
|
1399
|
+
/// lapsed after they sold — money owed, permanently unreachable, for a reason unrelated to the
|
|
1400
|
+
/// debt. The gate is the debt itself: you may withdraw exactly what you are owed.
|
|
1401
|
+
withdrawContentRevenue: () -> (Result);
|
|
1402
|
+
}
|