@c4roa0/agentpay-user 0.0.0-stage → 0.3.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/AGENTS.md +137 -0
- package/CONTRIBUTING.md +16 -0
- package/LICENSE +373 -0
- package/NOTICE.md +20 -0
- package/README.md +85 -2
- package/SECURITY.md +16 -0
- package/doc/README.md +52 -0
- package/doc/TODO.md +107 -0
- package/doc/architecture.md +99 -0
- package/doc/design.md +60 -0
- package/doc/develope.md +68 -0
- package/doc/plans/user-wallet-distribution.md +91 -0
- package/doc/reference/harness-skill-runtime.md +120 -0
- package/doc/reference/local-cache-foundation.md +176 -0
- package/doc/reference/local-safe-owner-ui.md +25 -0
- package/doc/reference/local-update.md +18 -0
- package/doc/reference/multi-service-runtime.md +119 -0
- package/doc/reference/npm-integration-verification.md +103 -0
- package/doc/reference/onboarding-writing.md +48 -0
- package/doc/reference/original-order-recovery.md +97 -0
- package/doc/reference/private-anvil-onboarding-config.example.json +18 -0
- package/doc/reference/public-deployment-settings.md +56 -0
- package/doc/reference/random-skill-contract.md +18 -0
- package/doc/reference/result-file-cache.md +166 -0
- package/doc/reference/runtime-address-recovery.md +42 -0
- package/doc/reference/runtime-result-cache.md +157 -0
- package/doc/reference/runtime-startup-recovery.md +141 -0
- package/doc/reference/skill-input-output-cache.md +93 -0
- package/doc/reference/wallet-identity-policy-history.md +23 -0
- package/doc/runbooks/commands.md +103 -0
- package/doc/runbooks/deployment-authorization-update.md +42 -0
- package/doc/runbooks/emergency-payment-stop.md +80 -0
- package/doc/runbooks/local-autostart.md +212 -0
- package/doc/runbooks/local-cache.md +115 -0
- package/doc/runbooks/local-update.md +84 -0
- package/doc/runbooks/market-connection.md +73 -0
- package/doc/runbooks/npm-publication.md +56 -0
- package/doc/runbooks/official-test-package.md +62 -0
- package/doc/runbooks/order-cancellation.md +56 -0
- package/doc/runbooks/order-support.md +41 -0
- package/doc/runbooks/policy-renewal.md +80 -0
- package/doc/runbooks/private-anvil-terminal-setup.md +58 -0
- package/doc/runbooks/runtime-skill-client.md +155 -0
- package/doc/runbooks/safe-owner-web.md +104 -0
- package/doc/runbooks/safe-policy-migration.md +36 -0
- package/doc/runbooks/safe-policy-setup.md +52 -0
- package/doc/runbooks/terminal-authorization-preview.md +38 -0
- package/doc/runbooks/unlock-wallet.md +68 -0
- package/doc/runbooks/user-onboarding.md +87 -0
- package/doc/runbooks/wallet-identity.md +59 -0
- package/doc/security/public-source-review-2026-11-06.md +71 -0
- package/doc/usage.md +54 -0
- package/package.json +49 -4
- package/progress.md +210 -0
- package/run +29 -0
- package/scripts/agentpay-cli.mjs +11 -0
- package/scripts/authorize-existing-safe.mjs +211 -0
- package/scripts/autostart.mjs +186 -0
- package/scripts/cancel-order.mjs +76 -0
- package/scripts/check-installation.mjs +26 -0
- package/scripts/check-official-test-package.mjs +35 -0
- package/scripts/check-purchase-readiness.mjs +275 -0
- package/scripts/configure-runtime-client.mjs +16 -0
- package/scripts/guide-policy.mjs +64 -0
- package/scripts/initialize-caller-credential.mjs +11 -0
- package/scripts/install-skill-bundle.mjs +176 -0
- package/scripts/install-user.mjs +24 -0
- package/scripts/local-service.mjs +60 -0
- package/scripts/lock.mjs +11 -0
- package/scripts/login-notify.mjs +39 -0
- package/scripts/migrate-safe-policy.mjs +165 -0
- package/scripts/official-paid-test.mjs +9 -0
- package/scripts/poll-unconfirmed-notice.mjs +75 -0
- package/scripts/prepare-onboarding.mjs +33 -0
- package/scripts/preview-policy.mjs +65 -0
- package/scripts/random-model-ack-smoke.mjs +235 -0
- package/scripts/random-reject-smoke.mjs +147 -0
- package/scripts/renew.mjs +9 -0
- package/scripts/repair.mjs +10 -0
- package/scripts/revoke.mjs +9 -0
- package/scripts/select-skill-bundle.mjs +39 -0
- package/scripts/setup-local.mjs +199 -0
- package/scripts/status.mjs +13 -0
- package/scripts/test-package.mjs +35 -0
- package/scripts/unlock.mjs +13 -0
- package/scripts/update.mjs +7 -0
- package/scripts/user-setup.mjs +219 -0
- package/scripts/verify-npm-release.mjs +32 -0
- package/src/active-skill.mjs +160 -0
- package/src/anvil-onboarding-state.mjs +96 -0
- package/src/anvil-onboarding.mjs +1036 -0
- package/src/authorization-question-checkpoint.mjs +114 -0
- package/src/autostart-files.mjs +50 -0
- package/src/cache-management-cli.mjs +302 -0
- package/src/cache-management.mjs +47 -0
- package/src/caller-auth.mjs +58 -0
- package/src/client-request-lock.mjs +44 -0
- package/src/connect-market.mjs +408 -0
- package/src/demo-unlock-secret.mjs +64 -0
- package/src/deployment-status.mjs +198 -0
- package/src/file-cache-cli.mjs +83 -0
- package/src/file-cache-owner.mjs +74 -0
- package/src/file-cache-settings.mjs +50 -0
- package/src/file-cache.mjs +790 -0
- package/src/file-download.mjs +281 -0
- package/src/finalization-disclosure.mjs +16 -0
- package/src/guided-setup.mjs +194 -0
- package/src/index.mjs +1233 -0
- package/src/install-and-setup.mjs +38 -0
- package/src/install-preflight.mjs +201 -0
- package/src/install-user.mjs +279 -0
- package/src/installation-paths.mjs +31 -0
- package/src/invocation-result.mjs +148 -0
- package/src/invocation-store.mjs +385 -0
- package/src/local-data-directory.mjs +117 -0
- package/src/local-info.mjs +181 -0
- package/src/local-services.mjs +156 -0
- package/src/local-time.mjs +36 -0
- package/src/local-update.mjs +316 -0
- package/src/local-wallet-ipc.mjs +93 -0
- package/src/login-reminder.mjs +28 -0
- package/src/market-download-url.mjs +21 -0
- package/src/node-version.mjs +12 -0
- package/src/npm-update.mjs +126 -0
- package/src/official-paid-test.mjs +407 -0
- package/src/official-test-package.mjs +41 -0
- package/src/official-test-update.mjs +162 -0
- package/src/order-cancellation.mjs +241 -0
- package/src/order-closure.mjs +42 -0
- package/src/order-list-panel.mjs +110 -0
- package/src/order-review.mjs +424 -0
- package/src/order-status-panel.mjs +65 -0
- package/src/order-support.mjs +365 -0
- package/src/owner-approval-server.mjs +332 -0
- package/src/owner-signer.mjs +67 -0
- package/src/policy-info.mjs +38 -0
- package/src/policy-migration.mjs +84 -0
- package/src/policy-operations.mjs +67 -0
- package/src/policy-preview-server.mjs +236 -0
- package/src/policy-terminal-guide.mjs +377 -0
- package/src/prepare-local-services.mjs +143 -0
- package/src/prepare-onboarding.mjs +210 -0
- package/src/private-journal-files.mjs +29 -0
- package/src/protocol-package.mjs +25 -0
- package/src/readiness-profile.mjs +30 -0
- package/src/renew-policy.mjs +605 -0
- package/src/repair-market-wallet.mjs +137 -0
- package/src/restore-wallet.mjs +255 -0
- package/src/result-cache-view.mjs +276 -0
- package/src/runtime-client.mjs +1145 -0
- package/src/runtime-connection-settings.mjs +101 -0
- package/src/runtime-recovery.mjs +667 -0
- package/src/runtime-status.mjs +32 -0
- package/src/safe-owner-anvil.mjs +115 -0
- package/src/safe-policy-plan.mjs +195 -0
- package/src/service-reference.mjs +233 -0
- package/src/service-url.mjs +52 -0
- package/src/settlement-response.mjs +69 -0
- package/src/setup-autostart.mjs +197 -0
- package/src/setup-demo.mjs +152 -0
- package/src/skill-bundle.mjs +142 -0
- package/src/skill-client.mjs +198 -0
- package/src/skill-install-prompt.mjs +31 -0
- package/src/skill-package.mjs +50 -0
- package/src/skill-zip.mjs +220 -0
- package/src/software-versions.mjs +48 -0
- package/src/status-panel.mjs +603 -0
- package/src/transport.mjs +48 -0
- package/src/uninstall-local.mjs +299 -0
- package/src/user-cli.mjs +354 -0
- package/src/user-setup-guide.mjs +193 -0
- package/src/wallet-control.mjs +182 -0
- package/src/wallet-ipc-error.mjs +16 -0
- package/src/wallet-package.mjs +28 -0
- package/update-pair.json +6 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
|
3
|
+
// If a copy was not distributed with this file, see https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { displaySeconds } from "./local-time.mjs";
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
import { parseUnits, getAddress, formatUnits } from "ethers";
|
|
7
|
+
import { buildSafePolicyPlan } from "./safe-policy-plan.mjs";
|
|
8
|
+
import { authorizationFields } from "./authorization-question-checkpoint.mjs";
|
|
9
|
+
|
|
10
|
+
export function authorizationContext(deployment, delegate) {
|
|
11
|
+
return JSON.stringify(
|
|
12
|
+
Object.fromEntries(
|
|
13
|
+
[
|
|
14
|
+
"chainId",
|
|
15
|
+
"rpcUrl",
|
|
16
|
+
"module",
|
|
17
|
+
"token",
|
|
18
|
+
"factory",
|
|
19
|
+
"registry",
|
|
20
|
+
"tokenDecimals",
|
|
21
|
+
"tokenSymbol",
|
|
22
|
+
"ownerAddress",
|
|
23
|
+
]
|
|
24
|
+
.map((key) => [key, deployment[key] ?? null])
|
|
25
|
+
.concat([["delegate", delegate]]),
|
|
26
|
+
),
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
export function showAuthorizationSummary(plan, { write, currency, decimals }) {
|
|
30
|
+
write(
|
|
31
|
+
`Safe: ${plan.safe}\nNetwork: eip155:${plan.chainId}\nTotal budget: ${formatUnits(plan.totalBudget, decimals)} ${currency}\nPer-purchase limit: ${formatUnits(plan.maxPerPayment, decimals)} ${currency}\nExpires: ${displaySeconds(plan.validUntil)}`,
|
|
32
|
+
);
|
|
33
|
+
write(
|
|
34
|
+
"\nYour Agent can make purchases within these limits without asking you each time.\nIt could spend the full budget.",
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
export async function runPolicyTerminalGuide(options) {
|
|
38
|
+
let checkpoint = options.checkpoint;
|
|
39
|
+
let current = {
|
|
40
|
+
...options,
|
|
41
|
+
saveInputs: async (value) => {
|
|
42
|
+
await options.saveInputs?.(value);
|
|
43
|
+
checkpoint = value;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
for (;;) {
|
|
47
|
+
try {
|
|
48
|
+
return await runGuideOnce(current);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error.code === "AUTHORIZATION_BACK_REVIEW") {
|
|
51
|
+
const { days, ...previous } = checkpoint.fields;
|
|
52
|
+
const edited = { ...checkpoint, fields: previous };
|
|
53
|
+
await current.saveInputs(edited);
|
|
54
|
+
current = { ...current, checkpoint: edited };
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (error.code !== "AUTHORIZATION_BACK") throw error;
|
|
58
|
+
current = { ...current, checkpoint: error.checkpoint };
|
|
59
|
+
checkpoint = error.checkpoint;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function runGuideOnce({
|
|
64
|
+
deployment,
|
|
65
|
+
delegate,
|
|
66
|
+
demo = false,
|
|
67
|
+
collectOnly = false,
|
|
68
|
+
defaults = {},
|
|
69
|
+
boundSafe = null,
|
|
70
|
+
migration = false,
|
|
71
|
+
checkpoint = null,
|
|
72
|
+
saveInputs = async () => {},
|
|
73
|
+
restoreExisting = null,
|
|
74
|
+
ask,
|
|
75
|
+
write,
|
|
76
|
+
inspect,
|
|
77
|
+
}) {
|
|
78
|
+
if (
|
|
79
|
+
!Number.isInteger(deployment.tokenDecimals) ||
|
|
80
|
+
deployment.tokenDecimals < 0 ||
|
|
81
|
+
deployment.tokenDecimals > 18 ||
|
|
82
|
+
typeof deployment.source !== "string" ||
|
|
83
|
+
!deployment.source.trim()
|
|
84
|
+
)
|
|
85
|
+
throw Error("Public deployment data requires tokenDecimals and source");
|
|
86
|
+
const rpc = new URL(deployment.rpcUrl);
|
|
87
|
+
if (rpc.username || rpc.password)
|
|
88
|
+
throw Error("RPC URL must not contain credentials");
|
|
89
|
+
const publicDeployment = Object.fromEntries(
|
|
90
|
+
["chainId", "rpcUrl", "module", "token", "factory", "registry"].map(
|
|
91
|
+
(key) => [key, deployment[key]],
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
const currency = demo
|
|
95
|
+
? "demo USDC"
|
|
96
|
+
: deployment.tokenSymbol || "payment tokens";
|
|
97
|
+
const suggested = boundSafe
|
|
98
|
+
? getAddress(boundSafe)
|
|
99
|
+
: demo
|
|
100
|
+
? "0x6666666666666666666666666666666666666666"
|
|
101
|
+
: deployment.mode === "private-anvil" && deployment.chainId === 31337
|
|
102
|
+
? deployment.safe
|
|
103
|
+
: null;
|
|
104
|
+
const details = {
|
|
105
|
+
...publicDeployment,
|
|
106
|
+
...(suggested ? { safe: getAddress(suggested) } : {}),
|
|
107
|
+
delegate,
|
|
108
|
+
tokenDecimals: deployment.tokenDecimals,
|
|
109
|
+
tokenSymbol: currency,
|
|
110
|
+
};
|
|
111
|
+
const previousChoice = checkpoint?.fields?.policyChoice;
|
|
112
|
+
const editableCheckpoint =
|
|
113
|
+
previousChoice && previousChoice !== "new"
|
|
114
|
+
? {
|
|
115
|
+
...checkpoint,
|
|
116
|
+
fields: Object.fromEntries(
|
|
117
|
+
Object.entries(checkpoint.fields).filter(
|
|
118
|
+
([key]) => key !== "policyChoice",
|
|
119
|
+
),
|
|
120
|
+
),
|
|
121
|
+
}
|
|
122
|
+
: checkpoint;
|
|
123
|
+
const field = authorizationFields({
|
|
124
|
+
context: authorizationContext(deployment, delegate),
|
|
125
|
+
checkpoint: editableCheckpoint,
|
|
126
|
+
ask,
|
|
127
|
+
save: saveInputs,
|
|
128
|
+
write,
|
|
129
|
+
details,
|
|
130
|
+
});
|
|
131
|
+
write(
|
|
132
|
+
"\n------------------------------------------------------------\n2/4 Set spending permission\n------------------------------------------------------------",
|
|
133
|
+
);
|
|
134
|
+
if (demo) write("Demo only: no real authorization or payment.");
|
|
135
|
+
if (boundSafe) write(`Using your connected Safe: ${getAddress(boundSafe)}`);
|
|
136
|
+
else if (demo && suggested) write(`Demo Safe: ${getAddress(suggested)}`);
|
|
137
|
+
else if (!suggested)
|
|
138
|
+
write(
|
|
139
|
+
`Use your Safe account address on chain ${deployment.chainId}. Find it at https://app.safe.global/.`,
|
|
140
|
+
);
|
|
141
|
+
const safe = await field(
|
|
142
|
+
"safe",
|
|
143
|
+
suggested
|
|
144
|
+
? "Safe address (Enter to use the address above; type 'create' if you need a Safe)"
|
|
145
|
+
: "Enter your Safe account address, or type 'create' for guidance",
|
|
146
|
+
(value) => {
|
|
147
|
+
if (value.toLowerCase() === "create")
|
|
148
|
+
throw Error(
|
|
149
|
+
`This guide cannot create a Safe. ${deployment.chainId === 31337 ? "Ask the operator for the private Anvil (31337) test Safe; the official app does not offer this network." : `Open https://app.safe.global/ on chain ${deployment.chainId}.`} Use the Safe account address, not its owner address.`,
|
|
150
|
+
);
|
|
151
|
+
try {
|
|
152
|
+
return getAddress(value || suggested);
|
|
153
|
+
} catch {
|
|
154
|
+
throw Error("Enter a full Safe account address starting with 0x.");
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
!demo && suggested ? getAddress(suggested) : undefined,
|
|
158
|
+
);
|
|
159
|
+
if (restoreExisting && !demo) {
|
|
160
|
+
write(
|
|
161
|
+
previousChoice && previousChoice !== "new"
|
|
162
|
+
? `Original Policy: ${previousChoice}. Press Enter to retry it, or enter a corrected Policy ID.`
|
|
163
|
+
: "Press Enter to create new spending permission, or enter your original Policy ID to restore it.",
|
|
164
|
+
);
|
|
165
|
+
const choice = await field(
|
|
166
|
+
"policyChoice",
|
|
167
|
+
"New permission or original Policy ID",
|
|
168
|
+
(v) => {
|
|
169
|
+
v = v || previousChoice || "new";
|
|
170
|
+
if (v === "new") return "new";
|
|
171
|
+
if (!/^0x[0-9a-f]{64}$/i.test(v) || /^0x0+$/.test(v))
|
|
172
|
+
throw Error(
|
|
173
|
+
"Enter a full Policy ID, or press Enter for new permission.",
|
|
174
|
+
);
|
|
175
|
+
return v.toLowerCase();
|
|
176
|
+
},
|
|
177
|
+
checkpoint?.fields?.budget && !checkpoint?.fields?.policyChoice
|
|
178
|
+
? "new"
|
|
179
|
+
: undefined,
|
|
180
|
+
);
|
|
181
|
+
if (choice !== "new") {
|
|
182
|
+
const result = await restoreExisting({
|
|
183
|
+
chainId: deployment.chainId,
|
|
184
|
+
rpcUrl: deployment.rpcUrl,
|
|
185
|
+
module: deployment.module,
|
|
186
|
+
safe,
|
|
187
|
+
policyId: choice,
|
|
188
|
+
});
|
|
189
|
+
return {
|
|
190
|
+
restored: true,
|
|
191
|
+
policyConfig: {
|
|
192
|
+
...publicDeployment,
|
|
193
|
+
safe,
|
|
194
|
+
policyId: choice,
|
|
195
|
+
totalBudget: result.terms.totalBudget,
|
|
196
|
+
maxPerPayment: result.terms.maxPerPayment,
|
|
197
|
+
validAfter: result.terms.validAfter,
|
|
198
|
+
validUntil: result.terms.validUntil,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const amount = (value) => {
|
|
204
|
+
if (
|
|
205
|
+
!/^\d+(\.\d+)?$/.test(value) ||
|
|
206
|
+
parseUnits(value, deployment.tokenDecimals) <= 0n
|
|
207
|
+
)
|
|
208
|
+
throw Error(`Enter an amount greater than zero in ${currency}.`);
|
|
209
|
+
return value;
|
|
210
|
+
};
|
|
211
|
+
write(
|
|
212
|
+
migration
|
|
213
|
+
? "Your remaining allowance is the default budget for the updated permission."
|
|
214
|
+
: "Set the total amount your Agent may spend without asking you to approve each payment.",
|
|
215
|
+
);
|
|
216
|
+
if (demo || deployment.mode === "private-anvil")
|
|
217
|
+
write(`${currency} is the test currency for this rehearsal.`);
|
|
218
|
+
write(`Currency: ${currency}`);
|
|
219
|
+
const budget = await field(
|
|
220
|
+
"budget",
|
|
221
|
+
defaults.budget
|
|
222
|
+
? `Total budget · Enter to keep ${defaults.budget} ${currency}`
|
|
223
|
+
: "Enter total budget",
|
|
224
|
+
(v) => amount(v || defaults.budget || ""),
|
|
225
|
+
);
|
|
226
|
+
write(
|
|
227
|
+
"\n------------------------------------------------------------\nPer-purchase limit\n------------------------------------------------------------",
|
|
228
|
+
);
|
|
229
|
+
write("Set the most your Agent may spend on one purchase.");
|
|
230
|
+
write(`Currency: ${currency} · Maximum: ${budget}`);
|
|
231
|
+
const previousLimit =
|
|
232
|
+
defaults.perPayment &&
|
|
233
|
+
parseUnits(defaults.perPayment, deployment.tokenDecimals) <=
|
|
234
|
+
parseUnits(budget, deployment.tokenDecimals)
|
|
235
|
+
? defaults.perPayment
|
|
236
|
+
: null;
|
|
237
|
+
const perPayment = await field(
|
|
238
|
+
"perPayment",
|
|
239
|
+
previousLimit
|
|
240
|
+
? `Per-purchase limit · Enter to keep ${previousLimit} ${currency}`
|
|
241
|
+
: "Enter per-purchase limit",
|
|
242
|
+
(value) => {
|
|
243
|
+
value = value || previousLimit || "";
|
|
244
|
+
amount(value);
|
|
245
|
+
if (
|
|
246
|
+
parseUnits(value, deployment.tokenDecimals) >
|
|
247
|
+
parseUnits(budget, deployment.tokenDecimals)
|
|
248
|
+
)
|
|
249
|
+
throw Error(
|
|
250
|
+
`Enter an amount no greater than your total budget of ${budget} ${currency}.`,
|
|
251
|
+
);
|
|
252
|
+
return value;
|
|
253
|
+
},
|
|
254
|
+
);
|
|
255
|
+
write(
|
|
256
|
+
"\n------------------------------------------------------------\nAuthorization expiry\n------------------------------------------------------------",
|
|
257
|
+
);
|
|
258
|
+
write(
|
|
259
|
+
migration && defaults.validUntil
|
|
260
|
+
? `Keep the original expiry: ${displaySeconds(defaults.validUntil)}. Enter a different number of days to change it.`
|
|
261
|
+
: "Allow your Agent to make purchases for this many days, starting now.",
|
|
262
|
+
);
|
|
263
|
+
write(
|
|
264
|
+
"When this period ends, new purchases stop until you authorize them again.",
|
|
265
|
+
);
|
|
266
|
+
if (defaults.daysRounded)
|
|
267
|
+
write(
|
|
268
|
+
"The previous duration is rounded up to whole days for this default.",
|
|
269
|
+
);
|
|
270
|
+
const days = await field(
|
|
271
|
+
"days",
|
|
272
|
+
migration && defaults.validUntil
|
|
273
|
+
? `Enter to keep the original expiry, or enter new validity in days`
|
|
274
|
+
: defaults.days
|
|
275
|
+
? `Validity in days · Enter to keep ${defaults.days}`
|
|
276
|
+
: "Enter validity in days",
|
|
277
|
+
(value) => {
|
|
278
|
+
value = value || defaults.days || "";
|
|
279
|
+
const end = Math.floor(Date.now() / 1000) + Number(value) * 86400;
|
|
280
|
+
if (
|
|
281
|
+
!/^[1-9][0-9]*$/.test(value) ||
|
|
282
|
+
!Number.isSafeInteger(end) ||
|
|
283
|
+
end * 1000 > 253402300799999
|
|
284
|
+
)
|
|
285
|
+
throw Error("Enter a positive whole number of days.");
|
|
286
|
+
return value;
|
|
287
|
+
},
|
|
288
|
+
);
|
|
289
|
+
const startsAt = Math.floor(Date.now() / 1000);
|
|
290
|
+
const policyConfig = {
|
|
291
|
+
...publicDeployment,
|
|
292
|
+
safe,
|
|
293
|
+
policyId: `0x${randomBytes(32).toString("hex")}`,
|
|
294
|
+
totalBudget: parseUnits(budget, deployment.tokenDecimals).toString(),
|
|
295
|
+
maxPerPayment: parseUnits(perPayment, deployment.tokenDecimals).toString(),
|
|
296
|
+
validAfter: startsAt,
|
|
297
|
+
validUntil:
|
|
298
|
+
migration && defaults.validUntil && days === defaults.days
|
|
299
|
+
? defaults.validUntil
|
|
300
|
+
: startsAt + Number(days) * 86400,
|
|
301
|
+
};
|
|
302
|
+
const plan = buildSafePolicyPlan(policyConfig, delegate);
|
|
303
|
+
let chainState = null,
|
|
304
|
+
chainError = null;
|
|
305
|
+
if (!demo)
|
|
306
|
+
write(
|
|
307
|
+
"Checking the network, Safe and contracts… This may take a moment. No transaction is being submitted.",
|
|
308
|
+
);
|
|
309
|
+
if (!demo)
|
|
310
|
+
try {
|
|
311
|
+
chainState = await inspect(plan);
|
|
312
|
+
} catch {
|
|
313
|
+
chainError =
|
|
314
|
+
"Chain check failed. Check the RPC, network, Safe and deployment before retrying.";
|
|
315
|
+
}
|
|
316
|
+
if (chainError) {
|
|
317
|
+
write(chainError);
|
|
318
|
+
return { chainError, readyToSign: false, readyToPurchase: false };
|
|
319
|
+
}
|
|
320
|
+
if (!collectOnly) {
|
|
321
|
+
showAuthorizationSummary(plan, {
|
|
322
|
+
write,
|
|
323
|
+
currency,
|
|
324
|
+
decimals: deployment.tokenDecimals,
|
|
325
|
+
});
|
|
326
|
+
while (true) {
|
|
327
|
+
const value = (
|
|
328
|
+
await ask(
|
|
329
|
+
"Type 'confirm' to finish this preview, 'back' to change the draft, 'details' for transaction data, or 'q' to return\n> ",
|
|
330
|
+
)
|
|
331
|
+
)
|
|
332
|
+
.trim()
|
|
333
|
+
.toLowerCase();
|
|
334
|
+
if (value === "back")
|
|
335
|
+
throw Object.assign(Error("Change draft"), {
|
|
336
|
+
code: "AUTHORIZATION_BACK_REVIEW",
|
|
337
|
+
});
|
|
338
|
+
if (value === "q")
|
|
339
|
+
throw Object.assign(
|
|
340
|
+
Error("Preview cancelled; no authorization was submitted."),
|
|
341
|
+
{ code: "CANCELLED" },
|
|
342
|
+
);
|
|
343
|
+
if (value === "details") {
|
|
344
|
+
write(
|
|
345
|
+
JSON.stringify(
|
|
346
|
+
{ policyId: plan.policyId, calls: plan.calls },
|
|
347
|
+
null,
|
|
348
|
+
2,
|
|
349
|
+
),
|
|
350
|
+
);
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (value === "confirm") break;
|
|
354
|
+
}
|
|
355
|
+
write("Authorization preview complete. No permission was granted.");
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
mode: demo ? "demo" : "read_only",
|
|
359
|
+
readyToSign: false,
|
|
360
|
+
readyToPurchase: false,
|
|
361
|
+
chainState,
|
|
362
|
+
chainError,
|
|
363
|
+
policyConfig,
|
|
364
|
+
calls: plan.calls,
|
|
365
|
+
draft: {
|
|
366
|
+
safe,
|
|
367
|
+
module: plan.module,
|
|
368
|
+
chainId: plan.chainId,
|
|
369
|
+
budget,
|
|
370
|
+
perPayment,
|
|
371
|
+
currency,
|
|
372
|
+
expiry: displaySeconds(plan.validUntil),
|
|
373
|
+
delegate: plan.delegate,
|
|
374
|
+
calls: plan.calls,
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { deploymentFilePath, importSetupFiles } from "./installation-paths.mjs";
|
|
2
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
3
|
+
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
|
4
|
+
// If a copy was not distributed with this file, see https://mozilla.org/MPL/2.0/.
|
|
5
|
+
import { ensureRuntimeConnection } from "./runtime-connection-settings.mjs";
|
|
6
|
+
import { constants } from "node:fs";
|
|
7
|
+
import { open } from "node:fs/promises";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { createInterface } from "node:readline/promises";
|
|
10
|
+
import { installedUserDir } from "./local-data-directory.mjs";
|
|
11
|
+
import {
|
|
12
|
+
checkServiceDirectory,
|
|
13
|
+
readServiceSettings,
|
|
14
|
+
saveServiceSettings,
|
|
15
|
+
serviceSettings,
|
|
16
|
+
} from "./local-services.mjs";
|
|
17
|
+
|
|
18
|
+
async function privateJson(path) {
|
|
19
|
+
const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
20
|
+
try {
|
|
21
|
+
const st = await file.stat();
|
|
22
|
+
if (
|
|
23
|
+
!st.isFile() ||
|
|
24
|
+
st.uid !== process.getuid() ||
|
|
25
|
+
st.mode & 0o077 ||
|
|
26
|
+
st.size > 65536
|
|
27
|
+
)
|
|
28
|
+
throw Error(
|
|
29
|
+
"Saved User settings must be private, regular files owned by you.",
|
|
30
|
+
);
|
|
31
|
+
return JSON.parse(await file.readFile("utf8"));
|
|
32
|
+
} finally {
|
|
33
|
+
await file.close();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function optionalJson(path) {
|
|
37
|
+
try {
|
|
38
|
+
return await privateJson(path);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
if (e.code === "ENOENT") return null;
|
|
41
|
+
throw e;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export async function askServiceSetting(prompt) {
|
|
45
|
+
if (!process.stdin.isTTY)
|
|
46
|
+
throw Error(
|
|
47
|
+
"Startup settings are incomplete. Run agentpay service start in your terminal to finish configuration.",
|
|
48
|
+
);
|
|
49
|
+
const terminal = createInterface({
|
|
50
|
+
input: process.stdin,
|
|
51
|
+
output: process.stdout,
|
|
52
|
+
});
|
|
53
|
+
try {
|
|
54
|
+
return await terminal.question(`${prompt}\n> `);
|
|
55
|
+
} finally {
|
|
56
|
+
terminal.close();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Only participant-owned settings are consulted. No platform environment file,
|
|
60
|
+
// default endpoint or business-specific Skill is a source of startup authority.
|
|
61
|
+
export async function ensureLocalServices({
|
|
62
|
+
dataDir,
|
|
63
|
+
userDir = installedUserDir,
|
|
64
|
+
ask = askServiceSetting,
|
|
65
|
+
write = console.log,
|
|
66
|
+
connectionOptions = {},
|
|
67
|
+
} = {}) {
|
|
68
|
+
let saved;
|
|
69
|
+
try {
|
|
70
|
+
saved = await readServiceSettings(dataDir);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
if (e.code !== "ENOENT") throw e;
|
|
73
|
+
}
|
|
74
|
+
if (saved) {
|
|
75
|
+
await ensureRuntimeConnection({ dataDir, write, ...connectionOptions });
|
|
76
|
+
return saved;
|
|
77
|
+
}
|
|
78
|
+
await checkServiceDirectory(dataDir);
|
|
79
|
+
const key = await optionalJson(
|
|
80
|
+
join(dataDir, "wallet", "session.keystore.json"),
|
|
81
|
+
);
|
|
82
|
+
if (!key) throw Error("Wallet is not initialized. Run agentpay setup first.");
|
|
83
|
+
const connection = await optionalJson(
|
|
84
|
+
join(dataDir, "runtime", "connection.json"),
|
|
85
|
+
);
|
|
86
|
+
if (
|
|
87
|
+
connection &&
|
|
88
|
+
(connection.version !== "agentpay.runtime-connection.v1" ||
|
|
89
|
+
connection.callerTokenFile !== join(dataDir, "runtime", "caller.token"))
|
|
90
|
+
)
|
|
91
|
+
throw Error(
|
|
92
|
+
"The saved Runtime connection does not match this User directory. Review it before starting.",
|
|
93
|
+
);
|
|
94
|
+
const setup = await optionalJson(
|
|
95
|
+
await deploymentFilePath(dataDir, userDir, "anvil-onboarding.json"),
|
|
96
|
+
);
|
|
97
|
+
const policy = await optionalJson(join(dataDir, "wallet", "policy.json"));
|
|
98
|
+
if (
|
|
99
|
+
setup &&
|
|
100
|
+
policy &&
|
|
101
|
+
(["safe", "module"].some(
|
|
102
|
+
(k) => String(setup[k]).toLowerCase() !== String(policy[k]).toLowerCase(),
|
|
103
|
+
) ||
|
|
104
|
+
Number(setup.chainId) !== Number(policy.chainId))
|
|
105
|
+
)
|
|
106
|
+
throw Error(
|
|
107
|
+
"Saved setup belongs to a different authorization. Your current Wallet was kept; review its startup addresses first.",
|
|
108
|
+
);
|
|
109
|
+
let runtimeUrl = connection?.runtimeUrl || setup?.runtimeUrl;
|
|
110
|
+
if (
|
|
111
|
+
connection &&
|
|
112
|
+
setup &&
|
|
113
|
+
new URL(connection.runtimeUrl).origin !== new URL(setup.runtimeUrl).origin
|
|
114
|
+
)
|
|
115
|
+
throw Error(
|
|
116
|
+
"Saved Runtime addresses disagree. Review your local settings before starting.",
|
|
117
|
+
);
|
|
118
|
+
let settlementUrl = setup?.settlementUrl;
|
|
119
|
+
write(
|
|
120
|
+
"Preparing background services for your existing Wallet. Your key and spending permission stay unchanged.",
|
|
121
|
+
);
|
|
122
|
+
const request = async (label) => {
|
|
123
|
+
const value = (await ask(label + " (type 'q' to cancel)")).trim();
|
|
124
|
+
if (value === "q" || !value)
|
|
125
|
+
throw Error(
|
|
126
|
+
"Startup preparation cancelled. Your Wallet data is unchanged.",
|
|
127
|
+
);
|
|
128
|
+
return value;
|
|
129
|
+
};
|
|
130
|
+
if (!runtimeUrl)
|
|
131
|
+
runtimeUrl = await request(
|
|
132
|
+
"Local Runtime address, including its port (for example http://127.0.0.1:6490)",
|
|
133
|
+
);
|
|
134
|
+
if (!settlementUrl)
|
|
135
|
+
settlementUrl = await request(
|
|
136
|
+
"Settlement address supplied by your AgentPay platform",
|
|
137
|
+
);
|
|
138
|
+
const settings = serviceSettings({ version: 1, runtimeUrl, settlementUrl });
|
|
139
|
+
await saveServiceSettings({ dataDir, ...settings });
|
|
140
|
+
await ensureRuntimeConnection({ dataDir, write, ...connectionOptions });
|
|
141
|
+
write("Startup settings saved. Login startup preference was not changed.");
|
|
142
|
+
return settings;
|
|
143
|
+
}
|