arc-builder-kit 0.2.0__py3-none-any.whl
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.
- arc_builder_kit/__init__.py +4 -0
- arc_builder_kit/__main__.py +6 -0
- arc_builder_kit/_paths.py +47 -0
- arc_builder_kit/cli.py +277 -0
- arc_builder_kit/config/arc_testnet.facts.json +31 -0
- arc_builder_kit/doctor.py +936 -0
- arc_builder_kit/examples/agent-commerce-components/components.js +200 -0
- arc_builder_kit/examples/agent-commerce-components/index.html +120 -0
- arc_builder_kit/examples/agent-commerce-flows/flows.js +271 -0
- arc_builder_kit/examples/agent-commerce-flows/index.html +114 -0
- arc_builder_kit/examples/agent-commerce-live/commerce-live.js +190 -0
- arc_builder_kit/examples/agent-commerce-live/index.html +105 -0
- arc_builder_kit/examples/agent-commerce-review-packet/index.html +96 -0
- arc_builder_kit/examples/agent-commerce-review-packet/packet.js +125 -0
- arc_builder_kit/examples/agent-identity-profile-preview/identity.js +126 -0
- arc_builder_kit/examples/agent-identity-profile-preview/index.html +104 -0
- arc_builder_kit/examples/arc-agent-treasury-lab/index.html +152 -0
- arc_builder_kit/examples/arc-agent-treasury-lab/treasury.js +532 -0
- arc_builder_kit/examples/arc-testnet-operator-evidence/evidence.example.json +47 -0
- arc_builder_kit/examples/arc-testnet-wallet-send-gate/index.html +233 -0
- arc_builder_kit/examples/arc-testnet-wallet-send-gate/live-infrastructure-policy.example.json +59 -0
- arc_builder_kit/examples/arc-testnet-wallet-send-gate/wallet-send-gate.js +472 -0
- arc_builder_kit/examples/circle-wallet-integration/index.html +155 -0
- arc_builder_kit/examples/circle-wallet-integration/wallet-lab.js +91 -0
- arc_builder_kit/examples/job-escrow-simulator/index.html +121 -0
- arc_builder_kit/examples/job-escrow-simulator/simulator.js +162 -0
- arc_builder_kit/examples/payment-intent-demo/index.html +132 -0
- arc_builder_kit/examples/payment-intent-playground/index.html +301 -0
- arc_builder_kit/examples/payment-intent-playground/playground.js +835 -0
- arc_builder_kit/examples/payment-intent-receipt-matcher/index.html +157 -0
- arc_builder_kit/examples/payment-intent-receipt-matcher/matcher.js +877 -0
- arc_builder_kit/examples/receipt-verifier-playground/index.html +120 -0
- arc_builder_kit/examples/receipt-verifier-playground/verifier.js +226 -0
- arc_builder_kit/examples/receipt-viewer/index.html +138 -0
- arc_builder_kit/examples/receipt-viewer/receipt-viewer.js +472 -0
- arc_builder_kit/examples/transaction-status-playground/index.html +135 -0
- arc_builder_kit/examples/transaction-status-playground/status.js +518 -0
- arc_builder_kit/examples/x402-local-challenge-server/.env.example +25 -0
- arc_builder_kit/examples/x402-local-challenge-server/README.md +111 -0
- arc_builder_kit/examples/x402-local-challenge-server/server.py +711 -0
- arc_builder_kit/mcp_server.py +463 -0
- arc_builder_kit/release_packet.py +469 -0
- arc_builder_kit/templates/README.md +25 -0
- arc_builder_kit/templates/job-escrow-starter/README.md +25 -0
- arc_builder_kit/templates/job-escrow-starter/index.html +41 -0
- arc_builder_kit/templates/job-escrow-starter/index.js +14 -0
- arc_builder_kit/templates/payment-intent-starter/README.md +25 -0
- arc_builder_kit/templates/payment-intent-starter/index.html +42 -0
- arc_builder_kit/templates/payment-intent-starter/index.js +7 -0
- arc_builder_kit/templates/x402-agent-starter/README.md +29 -0
- arc_builder_kit/templates/x402-agent-starter/server.py +201 -0
- arc_builder_kit/validate_repo.py +2212 -0
- arc_builder_kit-0.2.0.dist-info/METADATA +543 -0
- arc_builder_kit-0.2.0.dist-info/RECORD +58 -0
- arc_builder_kit-0.2.0.dist-info/WHEEL +5 -0
- arc_builder_kit-0.2.0.dist-info/entry_points.txt +3 -0
- arc_builder_kit-0.2.0.dist-info/licenses/LICENSE +21 -0
- arc_builder_kit-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
const ARC_AGENT_TREASURY = (() => {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const MICRO_USDC = 1_000_000;
|
|
5
|
+
const MAX_SAFE_USDC = 1_000_000;
|
|
6
|
+
const SCENARIOS = Object.freeze({
|
|
7
|
+
pass_first: ['passed'],
|
|
8
|
+
fail_then_pass: ['failed', 'passed'],
|
|
9
|
+
exhaust_retries: ['failed', 'failed', 'failed', 'failed', 'failed'],
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
function parseUsdc(value, label = 'amount') {
|
|
13
|
+
const text = String(value ?? '').trim();
|
|
14
|
+
if (!/^(?:0|[1-9]\d*)(?:\.\d{1,6})?$/.test(text)) {
|
|
15
|
+
throw new Error(`${label} must be a non-negative USDC decimal with at most 6 places.`);
|
|
16
|
+
}
|
|
17
|
+
const [whole, fraction = ''] = text.split('.');
|
|
18
|
+
const micro = (Number(whole) * MICRO_USDC) + Number(fraction.padEnd(6, '0'));
|
|
19
|
+
if (!Number.isSafeInteger(micro) || micro > MAX_SAFE_USDC * MICRO_USDC) {
|
|
20
|
+
throw new Error(`${label} exceeds the local simulation limit.`);
|
|
21
|
+
}
|
|
22
|
+
return micro;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function formatUsdc(micro) {
|
|
26
|
+
if (!Number.isSafeInteger(micro)) {
|
|
27
|
+
throw new Error('USDC micro-unit value must be a safe integer.');
|
|
28
|
+
}
|
|
29
|
+
const sign = micro < 0 ? '-' : '';
|
|
30
|
+
const absolute = Math.abs(micro);
|
|
31
|
+
const whole = Math.floor(absolute / MICRO_USDC);
|
|
32
|
+
const fraction = String(absolute % MICRO_USDC).padStart(6, '0').replace(/0+$/, '');
|
|
33
|
+
return `${sign}${whole}${fraction ? `.${fraction}` : ''}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function clone(value) {
|
|
37
|
+
return JSON.parse(JSON.stringify(value));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function nowIso() {
|
|
41
|
+
return new Date().toISOString();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function requireIdentifier(value, label) {
|
|
45
|
+
const text = String(value ?? '').trim();
|
|
46
|
+
if (!/^[A-Za-z0-9._-]{3,64}$/.test(text)) {
|
|
47
|
+
throw new Error(`${label} must use 3-64 letters, numbers, dots, underscores, or hyphens.`);
|
|
48
|
+
}
|
|
49
|
+
return text;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function normalizePolicy(input = {}) {
|
|
53
|
+
const policy = {
|
|
54
|
+
openingBalanceMicro: parseUsdc(input.openingBalance ?? '5', 'opening balance'),
|
|
55
|
+
reserveMicro: parseUsdc(input.reserve ?? '2', 'reserve'),
|
|
56
|
+
dailySpendCapMicro: parseUsdc(input.dailySpendCap ?? '2', 'daily spend cap'),
|
|
57
|
+
singleTaskCapMicro: parseUsdc(input.singleTaskCap ?? '1', 'single-task cap'),
|
|
58
|
+
minProfitMicro: parseUsdc(input.minProfit ?? '0.10', 'minimum profit'),
|
|
59
|
+
maxAttempts: Number(input.maxAttempts ?? 5),
|
|
60
|
+
};
|
|
61
|
+
if (!Number.isInteger(policy.maxAttempts) || policy.maxAttempts < 1 || policy.maxAttempts > 5) {
|
|
62
|
+
throw new Error('policy max attempts must be an integer from 1 to 5.');
|
|
63
|
+
}
|
|
64
|
+
if (policy.openingBalanceMicro < policy.reserveMicro) {
|
|
65
|
+
throw new Error('opening balance must be at least the protected reserve.');
|
|
66
|
+
}
|
|
67
|
+
if (policy.dailySpendCapMicro <= 0 || policy.singleTaskCapMicro <= 0) {
|
|
68
|
+
throw new Error('spend caps must be greater than zero.');
|
|
69
|
+
}
|
|
70
|
+
return policy;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function safetyControls() {
|
|
74
|
+
return {
|
|
75
|
+
network: 'arc-testnet',
|
|
76
|
+
chainId: 5042002,
|
|
77
|
+
chainIdHex: '0x4cef52',
|
|
78
|
+
asset: 'USDC',
|
|
79
|
+
assetDecimals: 6,
|
|
80
|
+
localOnly: true,
|
|
81
|
+
simulatedX402ReceiptsOnly: true,
|
|
82
|
+
walletConnected: false,
|
|
83
|
+
signingEnabled: false,
|
|
84
|
+
custodyEnabled: false,
|
|
85
|
+
mainnetEnabled: false,
|
|
86
|
+
autonomousSpendingEnabled: false,
|
|
87
|
+
transactionBroadcast: false,
|
|
88
|
+
talksToBackend: false,
|
|
89
|
+
humanApprovalRequiredForLiveExtension: true,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function createState(policyInput = {}) {
|
|
94
|
+
const policy = normalizePolicy(policyInput);
|
|
95
|
+
return {
|
|
96
|
+
schema: 'arc-mcp-builder-assistant.agentTreasury.local.v1',
|
|
97
|
+
status: 'ready',
|
|
98
|
+
policy,
|
|
99
|
+
treasury: {
|
|
100
|
+
balanceMicro: policy.openingBalanceMicro,
|
|
101
|
+
earnedMicro: 0,
|
|
102
|
+
spentMicro: 0,
|
|
103
|
+
dailySpendMicro: 0,
|
|
104
|
+
},
|
|
105
|
+
counters: {
|
|
106
|
+
accepted: 0,
|
|
107
|
+
denied: 0,
|
|
108
|
+
completed: 0,
|
|
109
|
+
failed: 0,
|
|
110
|
+
},
|
|
111
|
+
processedRequestIds: [],
|
|
112
|
+
acceptedReceiptIds: [],
|
|
113
|
+
receipts: [],
|
|
114
|
+
events: [{
|
|
115
|
+
at: nowIso(),
|
|
116
|
+
type: 'lab_reset',
|
|
117
|
+
message: 'Local Arc Agent Treasury policy initialized.',
|
|
118
|
+
}],
|
|
119
|
+
currentTask: null,
|
|
120
|
+
lastDecision: null,
|
|
121
|
+
controls: safetyControls(),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function normalizeTask(input, policy) {
|
|
126
|
+
const task = {
|
|
127
|
+
requestId: requireIdentifier(input.requestId, 'request ID'),
|
|
128
|
+
receiptId: requireIdentifier(input.receiptId, 'receipt ID'),
|
|
129
|
+
service: String(input.service ?? '').trim(),
|
|
130
|
+
quotedRevenueMicro: parseUsdc(input.quotedRevenue, 'quoted revenue'),
|
|
131
|
+
computeCostPerAttemptMicro: parseUsdc(input.computeCostPerAttempt, 'compute cost per attempt'),
|
|
132
|
+
maxAttempts: Number(input.maxAttempts),
|
|
133
|
+
scenario: String(input.scenario ?? ''),
|
|
134
|
+
};
|
|
135
|
+
if (!task.service || task.service.length > 120) {
|
|
136
|
+
throw new Error('service must contain 1-120 characters.');
|
|
137
|
+
}
|
|
138
|
+
if (task.quotedRevenueMicro <= 0 || task.computeCostPerAttemptMicro <= 0) {
|
|
139
|
+
throw new Error('quoted revenue and compute cost must be greater than zero.');
|
|
140
|
+
}
|
|
141
|
+
if (!Number.isInteger(task.maxAttempts) || task.maxAttempts < 1 || task.maxAttempts > policy.maxAttempts) {
|
|
142
|
+
throw new Error(`task max attempts must be an integer from 1 to ${policy.maxAttempts}.`);
|
|
143
|
+
}
|
|
144
|
+
if (!Object.hasOwn(SCENARIOS, task.scenario)) {
|
|
145
|
+
throw new Error('scenario is not supported.');
|
|
146
|
+
}
|
|
147
|
+
task.worstCaseCostMicro = task.computeCostPerAttemptMicro * task.maxAttempts;
|
|
148
|
+
task.expectedProfitMicro = task.quotedRevenueMicro - task.worstCaseCostMicro;
|
|
149
|
+
return task;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function decisionFor(state, task) {
|
|
153
|
+
const reasons = [];
|
|
154
|
+
const projectedBalanceMicro = state.treasury.balanceMicro
|
|
155
|
+
+ task.quotedRevenueMicro
|
|
156
|
+
- task.worstCaseCostMicro;
|
|
157
|
+
const projectedDailySpendMicro = state.treasury.dailySpendMicro + task.worstCaseCostMicro;
|
|
158
|
+
|
|
159
|
+
if (state.processedRequestIds.includes(task.requestId)) reasons.push('request_replay_detected');
|
|
160
|
+
if (state.acceptedReceiptIds.includes(task.receiptId)) reasons.push('receipt_replay_detected');
|
|
161
|
+
if (task.worstCaseCostMicro > state.policy.singleTaskCapMicro) reasons.push('single_task_cap_exceeded');
|
|
162
|
+
if (projectedDailySpendMicro > state.policy.dailySpendCapMicro) reasons.push('daily_spend_cap_exceeded');
|
|
163
|
+
if (projectedBalanceMicro < state.policy.reserveMicro) reasons.push('protected_reserve_would_be_breached');
|
|
164
|
+
if (task.expectedProfitMicro < state.policy.minProfitMicro) reasons.push('minimum_profit_not_met');
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
approved: reasons.length === 0,
|
|
168
|
+
reasons,
|
|
169
|
+
requestId: task.requestId,
|
|
170
|
+
receiptId: task.receiptId,
|
|
171
|
+
quotedRevenueMicro: task.quotedRevenueMicro,
|
|
172
|
+
worstCaseCostMicro: task.worstCaseCostMicro,
|
|
173
|
+
expectedProfitMicro: task.expectedProfitMicro,
|
|
174
|
+
projectedBalanceMicro,
|
|
175
|
+
projectedDailySpendMicro,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function reviewPaidTask(currentState, input) {
|
|
180
|
+
const state = clone(currentState);
|
|
181
|
+
if (state.currentTask && state.currentTask.status === 'reviewed') {
|
|
182
|
+
throw new Error('finish or clear the reviewed task before reviewing another.');
|
|
183
|
+
}
|
|
184
|
+
let task;
|
|
185
|
+
try {
|
|
186
|
+
task = normalizeTask(input, state.policy);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
state.counters.denied += 1;
|
|
189
|
+
state.status = 'denied';
|
|
190
|
+
state.lastDecision = { approved: false, reasons: ['invalid_task'], detail: error.message };
|
|
191
|
+
state.events.push({ at: nowIso(), type: 'task_denied', message: error.message });
|
|
192
|
+
return state;
|
|
193
|
+
}
|
|
194
|
+
const decision = decisionFor(state, task);
|
|
195
|
+
state.lastDecision = decision;
|
|
196
|
+
if (!decision.approved) {
|
|
197
|
+
state.counters.denied += 1;
|
|
198
|
+
state.status = 'denied';
|
|
199
|
+
state.events.push({
|
|
200
|
+
at: nowIso(),
|
|
201
|
+
type: 'task_denied',
|
|
202
|
+
requestId: task.requestId,
|
|
203
|
+
message: `Policy denied task: ${decision.reasons.join(', ')}.`,
|
|
204
|
+
});
|
|
205
|
+
return state;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
state.treasury.balanceMicro += task.quotedRevenueMicro;
|
|
209
|
+
state.treasury.earnedMicro += task.quotedRevenueMicro;
|
|
210
|
+
state.processedRequestIds.push(task.requestId);
|
|
211
|
+
state.acceptedReceiptIds.push(task.receiptId);
|
|
212
|
+
state.receipts.push({
|
|
213
|
+
receiptId: task.receiptId,
|
|
214
|
+
requestId: task.requestId,
|
|
215
|
+
amountMicro: task.quotedRevenueMicro,
|
|
216
|
+
asset: 'USDC',
|
|
217
|
+
network: 'arc-testnet',
|
|
218
|
+
verifierMode: 'local-simulation',
|
|
219
|
+
settled: false,
|
|
220
|
+
transactionBroadcast: false,
|
|
221
|
+
});
|
|
222
|
+
state.currentTask = {
|
|
223
|
+
...task,
|
|
224
|
+
status: 'reviewed',
|
|
225
|
+
attempts: [],
|
|
226
|
+
actualCostMicro: 0,
|
|
227
|
+
actualProfitMicro: 0,
|
|
228
|
+
outputVerified: false,
|
|
229
|
+
manualRefundReviewRequired: false,
|
|
230
|
+
};
|
|
231
|
+
state.counters.accepted += 1;
|
|
232
|
+
state.status = 'reviewed';
|
|
233
|
+
state.events.push({
|
|
234
|
+
at: nowIso(),
|
|
235
|
+
type: 'task_reviewed',
|
|
236
|
+
requestId: task.requestId,
|
|
237
|
+
message: 'Local x402 receipt accepted and worst-case compute budget reserved by policy.',
|
|
238
|
+
});
|
|
239
|
+
return state;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function canSpendAttempt(state, task) {
|
|
243
|
+
return task.actualCostMicro + task.computeCostPerAttemptMicro <= task.worstCaseCostMicro
|
|
244
|
+
&& state.treasury.dailySpendMicro + task.computeCostPerAttemptMicro <= state.policy.dailySpendCapMicro
|
|
245
|
+
&& state.treasury.balanceMicro - task.computeCostPerAttemptMicro >= state.policy.reserveMicro;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function runVerifiedLoop(currentState) {
|
|
249
|
+
const state = clone(currentState);
|
|
250
|
+
const task = state.currentTask;
|
|
251
|
+
if (!task || task.status !== 'reviewed') {
|
|
252
|
+
throw new Error('a policy-approved reviewed task is required before running the loop.');
|
|
253
|
+
}
|
|
254
|
+
const outcomes = SCENARIOS[task.scenario];
|
|
255
|
+
task.status = 'running';
|
|
256
|
+
state.status = 'running';
|
|
257
|
+
|
|
258
|
+
for (let index = 0; index < task.maxAttempts; index += 1) {
|
|
259
|
+
if (!canSpendAttempt(state, task)) {
|
|
260
|
+
task.status = 'policy_blocked';
|
|
261
|
+
state.status = 'policy_blocked';
|
|
262
|
+
task.manualRefundReviewRequired = true;
|
|
263
|
+
state.counters.failed += 1;
|
|
264
|
+
state.events.push({
|
|
265
|
+
at: nowIso(),
|
|
266
|
+
type: 'loop_blocked',
|
|
267
|
+
requestId: task.requestId,
|
|
268
|
+
message: 'Runtime spend preflight failed closed before the next compute attempt.',
|
|
269
|
+
});
|
|
270
|
+
return state;
|
|
271
|
+
}
|
|
272
|
+
state.treasury.balanceMicro -= task.computeCostPerAttemptMicro;
|
|
273
|
+
state.treasury.spentMicro += task.computeCostPerAttemptMicro;
|
|
274
|
+
state.treasury.dailySpendMicro += task.computeCostPerAttemptMicro;
|
|
275
|
+
task.actualCostMicro += task.computeCostPerAttemptMicro;
|
|
276
|
+
const verification = outcomes[Math.min(index, outcomes.length - 1)];
|
|
277
|
+
const attempt = {
|
|
278
|
+
attempt: index + 1,
|
|
279
|
+
stages: ['reproduce', 'execute', 'verify'],
|
|
280
|
+
verification,
|
|
281
|
+
costMicro: task.computeCostPerAttemptMicro,
|
|
282
|
+
};
|
|
283
|
+
if (verification === 'failed' && index + 1 < task.maxAttempts) {
|
|
284
|
+
attempt.stages.push('repair');
|
|
285
|
+
}
|
|
286
|
+
task.attempts.push(attempt);
|
|
287
|
+
state.events.push({
|
|
288
|
+
at: nowIso(),
|
|
289
|
+
type: verification === 'passed' ? 'verification_passed' : 'verification_failed',
|
|
290
|
+
requestId: task.requestId,
|
|
291
|
+
message: `Attempt ${index + 1} verification ${verification}.`,
|
|
292
|
+
});
|
|
293
|
+
if (verification === 'passed') {
|
|
294
|
+
task.status = 'completed';
|
|
295
|
+
task.outputVerified = true;
|
|
296
|
+
task.actualProfitMicro = task.quotedRevenueMicro - task.actualCostMicro;
|
|
297
|
+
state.status = 'completed';
|
|
298
|
+
state.counters.completed += 1;
|
|
299
|
+
return state;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
task.status = 'failed_manual_review';
|
|
303
|
+
task.actualProfitMicro = task.quotedRevenueMicro - task.actualCostMicro;
|
|
304
|
+
task.manualRefundReviewRequired = true;
|
|
305
|
+
state.status = 'failed_manual_review';
|
|
306
|
+
state.counters.failed += 1;
|
|
307
|
+
state.events.push({
|
|
308
|
+
at: nowIso(),
|
|
309
|
+
type: 'manual_review_required',
|
|
310
|
+
requestId: task.requestId,
|
|
311
|
+
message: 'Verification attempts exhausted; no success claim emitted.',
|
|
312
|
+
});
|
|
313
|
+
return state;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function clearFinishedTask(currentState) {
|
|
317
|
+
const state = clone(currentState);
|
|
318
|
+
if (state.currentTask && ['reviewed', 'running'].includes(state.currentTask.status)) {
|
|
319
|
+
throw new Error('cannot clear an active reviewed task.');
|
|
320
|
+
}
|
|
321
|
+
state.currentTask = null;
|
|
322
|
+
state.status = 'ready';
|
|
323
|
+
state.lastDecision = null;
|
|
324
|
+
state.events.push({ at: nowIso(), type: 'next_task_ready', message: 'Treasury ready for another request.' });
|
|
325
|
+
return state;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function publicSnapshot(state) {
|
|
329
|
+
const snapshot = clone(state);
|
|
330
|
+
snapshot.treasury = {
|
|
331
|
+
balance: formatUsdc(state.treasury.balanceMicro),
|
|
332
|
+
earned: formatUsdc(state.treasury.earnedMicro),
|
|
333
|
+
spent: formatUsdc(state.treasury.spentMicro),
|
|
334
|
+
dailySpend: formatUsdc(state.treasury.dailySpendMicro),
|
|
335
|
+
netChange: formatUsdc(state.treasury.earnedMicro - state.treasury.spentMicro),
|
|
336
|
+
};
|
|
337
|
+
snapshot.policy = {
|
|
338
|
+
reserve: formatUsdc(state.policy.reserveMicro),
|
|
339
|
+
dailySpendCap: formatUsdc(state.policy.dailySpendCapMicro),
|
|
340
|
+
singleTaskCap: formatUsdc(state.policy.singleTaskCapMicro),
|
|
341
|
+
minProfit: formatUsdc(state.policy.minProfitMicro),
|
|
342
|
+
maxAttempts: state.policy.maxAttempts,
|
|
343
|
+
};
|
|
344
|
+
delete snapshot.policy.openingBalanceMicro;
|
|
345
|
+
if (snapshot.currentTask) {
|
|
346
|
+
for (const key of [
|
|
347
|
+
'quotedRevenueMicro',
|
|
348
|
+
'computeCostPerAttemptMicro',
|
|
349
|
+
'worstCaseCostMicro',
|
|
350
|
+
'expectedProfitMicro',
|
|
351
|
+
'actualCostMicro',
|
|
352
|
+
'actualProfitMicro',
|
|
353
|
+
]) {
|
|
354
|
+
snapshot.currentTask[key.replace('Micro', '')] = formatUsdc(snapshot.currentTask[key]);
|
|
355
|
+
delete snapshot.currentTask[key];
|
|
356
|
+
}
|
|
357
|
+
for (const attempt of snapshot.currentTask.attempts) {
|
|
358
|
+
attempt.cost = formatUsdc(attempt.costMicro);
|
|
359
|
+
delete attempt.costMicro;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
snapshot.receipts = snapshot.receipts.map((receipt) => {
|
|
363
|
+
const { amountMicro, ...publicReceipt } = receipt;
|
|
364
|
+
return { ...publicReceipt, amount: formatUsdc(amountMicro) };
|
|
365
|
+
});
|
|
366
|
+
return snapshot;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return Object.freeze({
|
|
370
|
+
MICRO_USDC,
|
|
371
|
+
SCENARIOS,
|
|
372
|
+
parseUsdc,
|
|
373
|
+
formatUsdc,
|
|
374
|
+
createState,
|
|
375
|
+
reviewPaidTask,
|
|
376
|
+
runVerifiedLoop,
|
|
377
|
+
clearFinishedTask,
|
|
378
|
+
publicSnapshot,
|
|
379
|
+
});
|
|
380
|
+
})();
|
|
381
|
+
|
|
382
|
+
globalThis.ArcAgentTreasuryDomain = ARC_AGENT_TREASURY;
|
|
383
|
+
|
|
384
|
+
if (typeof document !== 'undefined') {
|
|
385
|
+
const elements = {
|
|
386
|
+
openingBalance: document.querySelector('#opening-balance'),
|
|
387
|
+
reserve: document.querySelector('#reserve'),
|
|
388
|
+
dailyCap: document.querySelector('#daily-cap'),
|
|
389
|
+
singleTaskCap: document.querySelector('#single-task-cap'),
|
|
390
|
+
minProfit: document.querySelector('#min-profit'),
|
|
391
|
+
policyAttempts: document.querySelector('#policy-attempts'),
|
|
392
|
+
requestId: document.querySelector('#request-id'),
|
|
393
|
+
receiptId: document.querySelector('#receipt-id'),
|
|
394
|
+
service: document.querySelector('#service'),
|
|
395
|
+
revenue: document.querySelector('#quoted-revenue'),
|
|
396
|
+
computeCost: document.querySelector('#compute-cost'),
|
|
397
|
+
taskAttempts: document.querySelector('#task-attempts'),
|
|
398
|
+
scenario: document.querySelector('#scenario'),
|
|
399
|
+
review: document.querySelector('#review-task'),
|
|
400
|
+
run: document.querySelector('#run-loop'),
|
|
401
|
+
next: document.querySelector('#next-task'),
|
|
402
|
+
reset: document.querySelector('#reset-lab'),
|
|
403
|
+
status: document.querySelector('#status'),
|
|
404
|
+
balance: document.querySelector('#metric-balance'),
|
|
405
|
+
earned: document.querySelector('#metric-earned'),
|
|
406
|
+
spent: document.querySelector('#metric-spent'),
|
|
407
|
+
net: document.querySelector('#metric-net'),
|
|
408
|
+
decision: document.querySelector('#decision'),
|
|
409
|
+
ledger: document.querySelector('#ledger'),
|
|
410
|
+
snapshot: document.querySelector('#snapshot'),
|
|
411
|
+
};
|
|
412
|
+
let state;
|
|
413
|
+
|
|
414
|
+
function policyInput() {
|
|
415
|
+
return {
|
|
416
|
+
openingBalance: elements.openingBalance.value,
|
|
417
|
+
reserve: elements.reserve.value,
|
|
418
|
+
dailySpendCap: elements.dailyCap.value,
|
|
419
|
+
singleTaskCap: elements.singleTaskCap.value,
|
|
420
|
+
minProfit: elements.minProfit.value,
|
|
421
|
+
maxAttempts: elements.policyAttempts.value,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function taskInput() {
|
|
426
|
+
return {
|
|
427
|
+
requestId: elements.requestId.value,
|
|
428
|
+
receiptId: elements.receiptId.value,
|
|
429
|
+
service: elements.service.value,
|
|
430
|
+
quotedRevenue: elements.revenue.value,
|
|
431
|
+
computeCostPerAttempt: elements.computeCost.value,
|
|
432
|
+
maxAttempts: elements.taskAttempts.value,
|
|
433
|
+
scenario: elements.scenario.value,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function setError(error) {
|
|
438
|
+
elements.decision.textContent = error.message;
|
|
439
|
+
elements.decision.dataset.verdict = 'error';
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function render() {
|
|
443
|
+
const snapshot = ARC_AGENT_TREASURY.publicSnapshot(state);
|
|
444
|
+
elements.status.textContent = state.status.replaceAll('_', ' ');
|
|
445
|
+
elements.status.dataset.state = state.status;
|
|
446
|
+
elements.balance.textContent = `${snapshot.treasury.balance} USDC`;
|
|
447
|
+
elements.earned.textContent = `${snapshot.treasury.earned} USDC`;
|
|
448
|
+
elements.spent.textContent = `${snapshot.treasury.spent} USDC`;
|
|
449
|
+
elements.net.textContent = `${snapshot.treasury.netChange} USDC`;
|
|
450
|
+
elements.snapshot.textContent = JSON.stringify(snapshot, null, 2);
|
|
451
|
+
elements.decision.textContent = state.lastDecision
|
|
452
|
+
? state.lastDecision.approved
|
|
453
|
+
? `APPROVED · worst-case cost ${ARC_AGENT_TREASURY.formatUsdc(state.lastDecision.worstCaseCostMicro)} USDC`
|
|
454
|
+
: `DENIED · ${state.lastDecision.reasons.join(', ')}`
|
|
455
|
+
: 'No task reviewed yet.';
|
|
456
|
+
elements.decision.dataset.verdict = state.lastDecision?.approved ? 'approved' : state.lastDecision ? 'denied' : 'idle';
|
|
457
|
+
elements.ledger.replaceChildren(...state.events.slice().reverse().map((event) => {
|
|
458
|
+
const row = document.createElement('li');
|
|
459
|
+
const strong = document.createElement('strong');
|
|
460
|
+
const span = document.createElement('span');
|
|
461
|
+
strong.textContent = event.type.replaceAll('_', ' ');
|
|
462
|
+
span.textContent = event.message;
|
|
463
|
+
row.append(strong, span);
|
|
464
|
+
return row;
|
|
465
|
+
}));
|
|
466
|
+
|
|
467
|
+
const active = state.currentTask && ['reviewed', 'running'].includes(state.currentTask.status);
|
|
468
|
+
const finished = state.currentTask && !active;
|
|
469
|
+
const policyFrozen = state.counters.accepted > 0 || state.counters.denied > 0;
|
|
470
|
+
for (const input of [
|
|
471
|
+
elements.openingBalance,
|
|
472
|
+
elements.reserve,
|
|
473
|
+
elements.dailyCap,
|
|
474
|
+
elements.singleTaskCap,
|
|
475
|
+
elements.minProfit,
|
|
476
|
+
elements.policyAttempts,
|
|
477
|
+
]) input.disabled = policyFrozen;
|
|
478
|
+
for (const input of [
|
|
479
|
+
elements.requestId,
|
|
480
|
+
elements.receiptId,
|
|
481
|
+
elements.service,
|
|
482
|
+
elements.revenue,
|
|
483
|
+
elements.computeCost,
|
|
484
|
+
elements.taskAttempts,
|
|
485
|
+
elements.scenario,
|
|
486
|
+
]) input.disabled = active;
|
|
487
|
+
elements.review.disabled = Boolean(active);
|
|
488
|
+
elements.run.disabled = !state.currentTask || state.currentTask.status !== 'reviewed';
|
|
489
|
+
elements.next.disabled = !finished;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function reset() {
|
|
493
|
+
try {
|
|
494
|
+
state = ARC_AGENT_TREASURY.createState(policyInput());
|
|
495
|
+
render();
|
|
496
|
+
} catch (error) {
|
|
497
|
+
setError(error);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
elements.review.addEventListener('click', () => {
|
|
502
|
+
try {
|
|
503
|
+
if (state.counters.accepted === 0 && state.counters.denied === 0) {
|
|
504
|
+
state = ARC_AGENT_TREASURY.createState(policyInput());
|
|
505
|
+
}
|
|
506
|
+
state = ARC_AGENT_TREASURY.reviewPaidTask(state, taskInput());
|
|
507
|
+
render();
|
|
508
|
+
} catch (error) {
|
|
509
|
+
setError(error);
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
elements.run.addEventListener('click', () => {
|
|
513
|
+
try {
|
|
514
|
+
state = ARC_AGENT_TREASURY.runVerifiedLoop(state);
|
|
515
|
+
render();
|
|
516
|
+
} catch (error) {
|
|
517
|
+
setError(error);
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
elements.next.addEventListener('click', () => {
|
|
521
|
+
try {
|
|
522
|
+
state = ARC_AGENT_TREASURY.clearFinishedTask(state);
|
|
523
|
+
elements.requestId.value = `req-${state.processedRequestIds.length + 1}`;
|
|
524
|
+
elements.receiptId.value = `receipt-${state.acceptedReceiptIds.length + 1}`;
|
|
525
|
+
render();
|
|
526
|
+
} catch (error) {
|
|
527
|
+
setError(error);
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
elements.reset.addEventListener('click', reset);
|
|
531
|
+
reset();
|
|
532
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "arc-mcp-builder-assistant.arcTestnet.operatorEvidence.v1",
|
|
3
|
+
"packetStatus": "local_operator_evidence",
|
|
4
|
+
"network": {
|
|
5
|
+
"name": "arc-testnet",
|
|
6
|
+
"chainId": 5042002,
|
|
7
|
+
"chainIdHex": "0x4cef52"
|
|
8
|
+
},
|
|
9
|
+
"review": {
|
|
10
|
+
"reviewedCommit": "6540eb3cc56a8d3ee3824f8de7995316754a36bf",
|
|
11
|
+
"reviewedSurface": "pre_send_readiness_baseline",
|
|
12
|
+
"manualReviewRequired": true
|
|
13
|
+
},
|
|
14
|
+
"evidence": {
|
|
15
|
+
"frozenIntentReviewed": true,
|
|
16
|
+
"unsignedDraftMatchesIntent": true,
|
|
17
|
+
"finalLocalConfirmationReviewed": true,
|
|
18
|
+
"walletRequestSpyResult": "not_applicable_no_wallet_surface",
|
|
19
|
+
"testsPassed": true,
|
|
20
|
+
"browserSmokePassed": true,
|
|
21
|
+
"arcStatusCheckPassed": true,
|
|
22
|
+
"references": [
|
|
23
|
+
"docs/arc-testnet-send-readiness-gate.md",
|
|
24
|
+
"docs/arc-testnet-operator-runbook.md",
|
|
25
|
+
"scripts/test_all.py",
|
|
26
|
+
"scripts/check_arc_testnet_status.py"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"controls": {
|
|
30
|
+
"walletConnected": false,
|
|
31
|
+
"signingEnabled": false,
|
|
32
|
+
"transactionBroadcast": false,
|
|
33
|
+
"backendSignerEnabled": false,
|
|
34
|
+
"mainnetEnabled": false,
|
|
35
|
+
"autonomousSpendingEnabled": false,
|
|
36
|
+
"ethSendTransactionForbidden": true,
|
|
37
|
+
"noSecretsObserved": true,
|
|
38
|
+
"humanApprovalRequired": true,
|
|
39
|
+
"separateGuardedPrRequired": true
|
|
40
|
+
},
|
|
41
|
+
"decision": {
|
|
42
|
+
"status": "blocked_pending_separate_guarded_pr",
|
|
43
|
+
"reason": "Current evidence documents readiness only; it does not approve a live-send implementation.",
|
|
44
|
+
"rollbackOwner": "repository maintainer",
|
|
45
|
+
"rollbackAction": "Keep wallet and transaction surfaces disabled or revert the separate guarded PR."
|
|
46
|
+
}
|
|
47
|
+
}
|