arc402-cli 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +245 -0
- package/dist/abis.d.ts +19 -0
- package/dist/abis.d.ts.map +1 -0
- package/dist/abis.js +177 -0
- package/dist/abis.js.map +1 -0
- package/dist/bundler.d.ts +65 -0
- package/dist/bundler.d.ts.map +1 -0
- package/dist/bundler.js +181 -0
- package/dist/bundler.js.map +1 -0
- package/dist/client.d.ts +14 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +24 -0
- package/dist/client.js.map +1 -0
- package/dist/coinbase-smart-wallet.d.ts +28 -0
- package/dist/coinbase-smart-wallet.d.ts.map +1 -0
- package/dist/coinbase-smart-wallet.js +38 -0
- package/dist/coinbase-smart-wallet.js.map +1 -0
- package/dist/commands/accept.d.ts +3 -0
- package/dist/commands/accept.d.ts.map +1 -0
- package/dist/commands/accept.js +26 -0
- package/dist/commands/accept.js.map +1 -0
- package/dist/commands/agent-handshake.d.ts +3 -0
- package/dist/commands/agent-handshake.d.ts.map +1 -0
- package/dist/commands/agent-handshake.js +61 -0
- package/dist/commands/agent-handshake.js.map +1 -0
- package/dist/commands/agent.d.ts +3 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +417 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/agreements.d.ts +3 -0
- package/dist/commands/agreements.d.ts.map +1 -0
- package/dist/commands/agreements.js +344 -0
- package/dist/commands/agreements.js.map +1 -0
- package/dist/commands/arbitrator.d.ts +3 -0
- package/dist/commands/arbitrator.d.ts.map +1 -0
- package/dist/commands/arbitrator.js +157 -0
- package/dist/commands/arbitrator.js.map +1 -0
- package/dist/commands/arena-handshake.d.ts +3 -0
- package/dist/commands/arena-handshake.d.ts.map +1 -0
- package/dist/commands/arena-handshake.js +187 -0
- package/dist/commands/arena-handshake.js.map +1 -0
- package/dist/commands/cancel.d.ts +3 -0
- package/dist/commands/cancel.d.ts.map +1 -0
- package/dist/commands/cancel.js +30 -0
- package/dist/commands/cancel.js.map +1 -0
- package/dist/commands/channel.d.ts +3 -0
- package/dist/commands/channel.d.ts.map +1 -0
- package/dist/commands/channel.js +238 -0
- package/dist/commands/channel.js.map +1 -0
- package/dist/commands/coldstart.d.ts +3 -0
- package/dist/commands/coldstart.d.ts.map +1 -0
- package/dist/commands/coldstart.js +148 -0
- package/dist/commands/coldstart.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +40 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/contract-interaction.d.ts +3 -0
- package/dist/commands/contract-interaction.d.ts.map +1 -0
- package/dist/commands/contract-interaction.js +165 -0
- package/dist/commands/contract-interaction.js.map +1 -0
- package/dist/commands/daemon.d.ts +3 -0
- package/dist/commands/daemon.d.ts.map +1 -0
- package/dist/commands/daemon.js +891 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/deliver.d.ts +3 -0
- package/dist/commands/deliver.d.ts.map +1 -0
- package/dist/commands/deliver.js +156 -0
- package/dist/commands/deliver.js.map +1 -0
- package/dist/commands/discover.d.ts +3 -0
- package/dist/commands/discover.d.ts.map +1 -0
- package/dist/commands/discover.js +224 -0
- package/dist/commands/discover.js.map +1 -0
- package/dist/commands/dispute.d.ts +3 -0
- package/dist/commands/dispute.d.ts.map +1 -0
- package/dist/commands/dispute.js +348 -0
- package/dist/commands/dispute.js.map +1 -0
- package/dist/commands/endpoint.d.ts +3 -0
- package/dist/commands/endpoint.d.ts.map +1 -0
- package/dist/commands/endpoint.js +604 -0
- package/dist/commands/endpoint.js.map +1 -0
- package/dist/commands/hire.d.ts +3 -0
- package/dist/commands/hire.d.ts.map +1 -0
- package/dist/commands/hire.js +189 -0
- package/dist/commands/hire.js.map +1 -0
- package/dist/commands/migrate.d.ts +3 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +163 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/negotiate.d.ts +3 -0
- package/dist/commands/negotiate.d.ts.map +1 -0
- package/dist/commands/negotiate.js +247 -0
- package/dist/commands/negotiate.js.map +1 -0
- package/dist/commands/openshell.d.ts +3 -0
- package/dist/commands/openshell.d.ts.map +1 -0
- package/dist/commands/openshell.js +952 -0
- package/dist/commands/openshell.js.map +1 -0
- package/dist/commands/owner.d.ts +3 -0
- package/dist/commands/owner.d.ts.map +1 -0
- package/dist/commands/owner.js +32 -0
- package/dist/commands/owner.js.map +1 -0
- package/dist/commands/policy.d.ts +4 -0
- package/dist/commands/policy.d.ts.map +1 -0
- package/dist/commands/policy.js +248 -0
- package/dist/commands/policy.js.map +1 -0
- package/dist/commands/relay.d.ts +3 -0
- package/dist/commands/relay.d.ts.map +1 -0
- package/dist/commands/relay.js +279 -0
- package/dist/commands/relay.js.map +1 -0
- package/dist/commands/remediate.d.ts +3 -0
- package/dist/commands/remediate.d.ts.map +1 -0
- package/dist/commands/remediate.js +42 -0
- package/dist/commands/remediate.js.map +1 -0
- package/dist/commands/reputation.d.ts +4 -0
- package/dist/commands/reputation.d.ts.map +1 -0
- package/dist/commands/reputation.js +72 -0
- package/dist/commands/reputation.js.map +1 -0
- package/dist/commands/setup.d.ts +3 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +332 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/trust.d.ts +3 -0
- package/dist/commands/trust.d.ts.map +1 -0
- package/dist/commands/trust.js +23 -0
- package/dist/commands/trust.js.map +1 -0
- package/dist/commands/verify.d.ts +3 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +88 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/commands/wallet.d.ts +3 -0
- package/dist/commands/wallet.d.ts.map +1 -0
- package/dist/commands/wallet.js +2520 -0
- package/dist/commands/wallet.js.map +1 -0
- package/dist/commands/watchtower.d.ts +3 -0
- package/dist/commands/watchtower.d.ts.map +1 -0
- package/dist/commands/watchtower.js +238 -0
- package/dist/commands/watchtower.js.map +1 -0
- package/dist/commands/workroom.d.ts +3 -0
- package/dist/commands/workroom.d.ts.map +1 -0
- package/dist/commands/workroom.js +855 -0
- package/dist/commands/workroom.js.map +1 -0
- package/dist/config.d.ts +62 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +141 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon/config.d.ts +74 -0
- package/dist/daemon/config.d.ts.map +1 -0
- package/dist/daemon/config.js +271 -0
- package/dist/daemon/config.js.map +1 -0
- package/dist/daemon/hire-listener.d.ts +31 -0
- package/dist/daemon/hire-listener.d.ts.map +1 -0
- package/dist/daemon/hire-listener.js +207 -0
- package/dist/daemon/hire-listener.js.map +1 -0
- package/dist/daemon/index.d.ts +29 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +535 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/job-lifecycle.d.ts +62 -0
- package/dist/daemon/job-lifecycle.d.ts.map +1 -0
- package/dist/daemon/job-lifecycle.js +201 -0
- package/dist/daemon/job-lifecycle.js.map +1 -0
- package/dist/daemon/notify.d.ts +22 -0
- package/dist/daemon/notify.d.ts.map +1 -0
- package/dist/daemon/notify.js +148 -0
- package/dist/daemon/notify.js.map +1 -0
- package/dist/daemon/token-metering.d.ts +42 -0
- package/dist/daemon/token-metering.d.ts.map +1 -0
- package/dist/daemon/token-metering.js +178 -0
- package/dist/daemon/token-metering.js.map +1 -0
- package/dist/daemon/userops.d.ts +21 -0
- package/dist/daemon/userops.d.ts.map +1 -0
- package/dist/daemon/userops.js +88 -0
- package/dist/daemon/userops.js.map +1 -0
- package/dist/daemon/wallet-monitor.d.ts +16 -0
- package/dist/daemon/wallet-monitor.d.ts.map +1 -0
- package/dist/daemon/wallet-monitor.js +57 -0
- package/dist/daemon/wallet-monitor.js.map +1 -0
- package/dist/drain-v4.d.ts +2 -0
- package/dist/drain-v4.d.ts.map +1 -0
- package/dist/drain-v4.js +167 -0
- package/dist/drain-v4.js.map +1 -0
- package/dist/endpoint-config.d.ts +36 -0
- package/dist/endpoint-config.d.ts.map +1 -0
- package/dist/endpoint-config.js +96 -0
- package/dist/endpoint-config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +79 -0
- package/dist/index.js.map +1 -0
- package/dist/openshell-runtime.d.ts +55 -0
- package/dist/openshell-runtime.d.ts.map +1 -0
- package/dist/openshell-runtime.js +268 -0
- package/dist/openshell-runtime.js.map +1 -0
- package/dist/signing.d.ts +2 -0
- package/dist/signing.d.ts.map +1 -0
- package/dist/signing.js +23 -0
- package/dist/signing.js.map +1 -0
- package/dist/telegram-notify.d.ts +23 -0
- package/dist/telegram-notify.d.ts.map +1 -0
- package/dist/telegram-notify.js +106 -0
- package/dist/telegram-notify.js.map +1 -0
- package/dist/ui/banner.d.ts +7 -0
- package/dist/ui/banner.d.ts.map +1 -0
- package/dist/ui/banner.js +37 -0
- package/dist/ui/banner.js.map +1 -0
- package/dist/ui/colors.d.ts +14 -0
- package/dist/ui/colors.d.ts.map +1 -0
- package/dist/ui/colors.js +29 -0
- package/dist/ui/colors.js.map +1 -0
- package/dist/ui/format.d.ts +26 -0
- package/dist/ui/format.d.ts.map +1 -0
- package/dist/ui/format.js +77 -0
- package/dist/ui/format.js.map +1 -0
- package/dist/ui/spinner.d.ts +8 -0
- package/dist/ui/spinner.d.ts.map +1 -0
- package/dist/ui/spinner.js +43 -0
- package/dist/ui/spinner.js.map +1 -0
- package/dist/utils/format.d.ts +10 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +61 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/hash.d.ts +3 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +43 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/time.d.ts +3 -0
- package/dist/utils/time.d.ts.map +1 -0
- package/dist/utils/time.js +21 -0
- package/dist/utils/time.js.map +1 -0
- package/dist/wallet-router.d.ts +25 -0
- package/dist/wallet-router.d.ts.map +1 -0
- package/dist/wallet-router.js +153 -0
- package/dist/wallet-router.js.map +1 -0
- package/dist/walletconnect-session.d.ts +12 -0
- package/dist/walletconnect-session.d.ts.map +1 -0
- package/dist/walletconnect-session.js +26 -0
- package/dist/walletconnect-session.js.map +1 -0
- package/dist/walletconnect.d.ts +46 -0
- package/dist/walletconnect.d.ts.map +1 -0
- package/dist/walletconnect.js +267 -0
- package/dist/walletconnect.js.map +1 -0
- package/package.json +38 -0
- package/scripts/authorize-machine-key.ts +43 -0
- package/scripts/drain-wallet.ts +149 -0
- package/scripts/execute-spend-only.ts +81 -0
- package/scripts/register-agent-userop.ts +186 -0
- package/src/abis.ts +187 -0
- package/src/bundler.ts +235 -0
- package/src/client.ts +34 -0
- package/src/coinbase-smart-wallet.ts +51 -0
- package/src/commands/accept.ts +25 -0
- package/src/commands/agent-handshake.ts +67 -0
- package/src/commands/agent.ts +458 -0
- package/src/commands/agreements.ts +324 -0
- package/src/commands/arbitrator.ts +129 -0
- package/src/commands/arena-handshake.ts +217 -0
- package/src/commands/cancel.ts +26 -0
- package/src/commands/channel.ts +208 -0
- package/src/commands/coldstart.ts +156 -0
- package/src/commands/config.ts +35 -0
- package/src/commands/contract-interaction.ts +166 -0
- package/src/commands/daemon.ts +971 -0
- package/src/commands/deliver.ts +116 -0
- package/src/commands/discover.ts +295 -0
- package/src/commands/dispute.ts +373 -0
- package/src/commands/endpoint.ts +619 -0
- package/src/commands/hire.ts +200 -0
- package/src/commands/migrate.ts +175 -0
- package/src/commands/negotiate.ts +270 -0
- package/src/commands/openshell.ts +1053 -0
- package/src/commands/owner.ts +30 -0
- package/src/commands/policy.ts +252 -0
- package/src/commands/relay.ts +272 -0
- package/src/commands/remediate.ts +22 -0
- package/src/commands/reputation.ts +71 -0
- package/src/commands/setup.ts +343 -0
- package/src/commands/trust.ts +15 -0
- package/src/commands/verify.ts +88 -0
- package/src/commands/wallet.ts +2892 -0
- package/src/commands/watchtower.ts +232 -0
- package/src/commands/workroom.ts +889 -0
- package/src/config.ts +153 -0
- package/src/daemon/config.ts +308 -0
- package/src/daemon/hire-listener.ts +226 -0
- package/src/daemon/index.ts +609 -0
- package/src/daemon/job-lifecycle.ts +215 -0
- package/src/daemon/notify.ts +157 -0
- package/src/daemon/token-metering.ts +183 -0
- package/src/daemon/userops.ts +119 -0
- package/src/daemon/wallet-monitor.ts +90 -0
- package/src/drain-v4.ts +159 -0
- package/src/endpoint-config.ts +83 -0
- package/src/index.ts +75 -0
- package/src/openshell-runtime.ts +277 -0
- package/src/signing.ts +28 -0
- package/src/telegram-notify.ts +88 -0
- package/src/ui/banner.ts +41 -0
- package/src/ui/colors.ts +30 -0
- package/src/ui/format.ts +77 -0
- package/src/ui/spinner.ts +46 -0
- package/src/utils/format.ts +48 -0
- package/src/utils/hash.ts +5 -0
- package/src/utils/time.ts +15 -0
- package/src/wallet-router.ts +178 -0
- package/src/walletconnect-session.ts +27 -0
- package/src/walletconnect.ts +294 -0
- package/test/time.test.js +11 -0
- package/tsconfig.json +19 -0
package/src/config.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
|
|
5
|
+
export interface Arc402Config {
|
|
6
|
+
network: "base-mainnet" | "base-sepolia";
|
|
7
|
+
rpcUrl: string;
|
|
8
|
+
privateKey?: string;
|
|
9
|
+
guardianPrivateKey?: string;
|
|
10
|
+
guardianAddress?: string;
|
|
11
|
+
walletConnectProjectId?: string;
|
|
12
|
+
ownerAddress?: string;
|
|
13
|
+
agentRegistryAddress?: string;
|
|
14
|
+
agentRegistryV2Address?: string;
|
|
15
|
+
serviceAgreementAddress?: string;
|
|
16
|
+
disputeArbitrationAddress?: string;
|
|
17
|
+
disputeModuleAddress?: string;
|
|
18
|
+
trustRegistryAddress: string;
|
|
19
|
+
trustRegistryV2Address?: string;
|
|
20
|
+
intentAttestationAddress?: string;
|
|
21
|
+
settlementCoordinatorAddress?: string;
|
|
22
|
+
sessionChannelsAddress?: string;
|
|
23
|
+
reputationOracleAddress?: string;
|
|
24
|
+
sponsorshipAttestationAddress?: string;
|
|
25
|
+
capabilityRegistryAddress?: string;
|
|
26
|
+
governanceAddress?: string;
|
|
27
|
+
agreementTreeAddress?: string;
|
|
28
|
+
policyEngineAddress?: string;
|
|
29
|
+
walletFactoryAddress?: string;
|
|
30
|
+
walletContractAddress?: string;
|
|
31
|
+
watchtowerRegistryAddress?: string;
|
|
32
|
+
governedTokenWhitelistAddress?: string;
|
|
33
|
+
vouchingRegistryAddress?: string;
|
|
34
|
+
migrationRegistryAddress?: string;
|
|
35
|
+
handshakeAddress?: string;
|
|
36
|
+
paymasterUrl?: string; // CDP paymaster endpoint
|
|
37
|
+
cdpKeyName?: string; // CDP API key name (org/.../apiKeys/...)
|
|
38
|
+
cdpPrivateKey?: string; // CDP EC private key — base64 DER SEC1 (store in CDP_PRIVATE_KEY env var)
|
|
39
|
+
subdomainApi?: string; // defaults to https://api.arc402.xyz
|
|
40
|
+
telegramBotToken?: string;
|
|
41
|
+
telegramChatId?: string;
|
|
42
|
+
telegramThreadId?: number;
|
|
43
|
+
wcSession?: {
|
|
44
|
+
topic: string;
|
|
45
|
+
expiry: number; // Unix timestamp
|
|
46
|
+
account: string; // Phone wallet address
|
|
47
|
+
chainId: number;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const CONFIG_DIR = path.join(os.homedir(), ".arc402");
|
|
52
|
+
const CONFIG_PATH = process.env.ARC402_CONFIG || path.join(CONFIG_DIR, "config.json");
|
|
53
|
+
|
|
54
|
+
export const getConfigPath = () => CONFIG_PATH;
|
|
55
|
+
|
|
56
|
+
export function loadConfig(): Arc402Config {
|
|
57
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
58
|
+
console.error(`No config found at ${CONFIG_PATH}. Run \`arc402 config init\` to set up your configuration.`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8")) as Arc402Config;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function saveConfig(config: Arc402Config): void {
|
|
65
|
+
const configDir = path.dirname(CONFIG_PATH);
|
|
66
|
+
fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
|
67
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const configExists = () => fs.existsSync(CONFIG_PATH);
|
|
71
|
+
|
|
72
|
+
// Public Base RPC — stale state, do not use for production. Alchemy recommended.
|
|
73
|
+
export const PUBLIC_BASE_RPC = "https://mainnet.base.org";
|
|
74
|
+
export const ALCHEMY_BASE_RPC = "https://base-mainnet.g.alchemy.com/v2/YIA2uRCsFI-j5pqH-aRzflrACSlV1Qrs";
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Warn at runtime if the configured RPC is the public Base endpoint.
|
|
78
|
+
* Public Base RPC has delayed state propagation — use Alchemy for production.
|
|
79
|
+
*/
|
|
80
|
+
export function warnIfPublicRpc(config: Arc402Config): void {
|
|
81
|
+
if (config.rpcUrl === PUBLIC_BASE_RPC || config.rpcUrl === "https://sepolia.base.org") {
|
|
82
|
+
console.warn("WARN: Using public Base RPC — state reads may be stale. Set rpcUrl to an Alchemy endpoint for production.");
|
|
83
|
+
console.warn(` Recommended: arc402 config set rpcUrl ${ALCHEMY_BASE_RPC}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const NETWORK_DEFAULTS: Record<string, Partial<Arc402Config> & { usdcAddress: string }> = {
|
|
88
|
+
"base-mainnet": {
|
|
89
|
+
rpcUrl: ALCHEMY_BASE_RPC,
|
|
90
|
+
usdcAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
91
|
+
paymasterUrl: "https://api.developer.coinbase.com/rpc/v1/base/dca85088-a2ac-4ec3-8647-5154b150e7a9",
|
|
92
|
+
// Base Mainnet deployments — v2 deployed 2026-03-15
|
|
93
|
+
policyEngineAddress: "0xAA5Ef3489C929bFB3BFf5D5FE15aa62d3763c847",
|
|
94
|
+
trustRegistryAddress: "0x22366D6dabb03062Bc0a5E893EfDff15D8E329b1", // TrustRegistryV3 — v2
|
|
95
|
+
trustRegistryV2Address: "0xdA1D377991B2E580991B0DD381CdD635dd71aC39", // old v2, kept for reference
|
|
96
|
+
intentAttestationAddress: "0x7ad8db6C5f394542E8e9658F86C85cC99Cf6D460",
|
|
97
|
+
settlementCoordinatorAddress: "0xd52d8Be9728976E0D70C89db9F8ACeb5B5e97cA2", // SettlementCoordinatorV2
|
|
98
|
+
agentRegistryAddress: "0xcc0D8731ccCf6CFfF4e66F6d68cA86330Ea8B622", // ARC402RegistryV2
|
|
99
|
+
agentRegistryV2Address: "0xD5c2851B00090c92Ba7F4723FB548bb30C9B6865", // AgentRegistry
|
|
100
|
+
walletFactoryAddress: "0xcB52B5d746eEc05e141039E92e3dBefeAe496051", // WalletFactoryV5 — redeployed 2026-03-19 (optimized bytecode, FOUNDRY_PROFILE=deploy)
|
|
101
|
+
sponsorshipAttestationAddress: "0xD6c2edE89Ea71aE19Db2Be848e172b444Ed38f22",
|
|
102
|
+
serviceAgreementAddress: "0xC98B402CAB9156da68A87a69E3B4bf167A3CCcF6",
|
|
103
|
+
sessionChannelsAddress: "0x578f8d1bd82E8D6268E329d664d663B4d985BE61",
|
|
104
|
+
disputeModuleAddress: "0x5ebd301cEF0C908AB17Fd183aD9c274E4B34e9d6",
|
|
105
|
+
reputationOracleAddress: "0x359F76a54F9A345546E430e4d6665A7dC9DaECd4",
|
|
106
|
+
governanceAddress: "0xE931DD2EEb9Af9353Dd5E2c1250492A0135E0EC4", // ARC402Governance
|
|
107
|
+
guardianAddress: "0xED0A033B79626cdf9570B6c3baC7f699cD0032D8", // ARC402Guardian
|
|
108
|
+
walletContractAddress: "0xfd5C8c0a08fDcdeD2fe03e0DC9FA55595667F313", // ARC402Wallet instance
|
|
109
|
+
agreementTreeAddress: "0x6a82240512619B25583b9e95783410cf782915b1",
|
|
110
|
+
capabilityRegistryAddress: "0x7becb642668B80502dD957A594E1dD0aC414c1a3",
|
|
111
|
+
disputeArbitrationAddress: "0xF61b75E4903fbC81169FeF8b7787C13cB7750601",
|
|
112
|
+
governedTokenWhitelistAddress: "0xeB58896337244Bb408362Fea727054f9e7157451",
|
|
113
|
+
watchtowerRegistryAddress: "0xbC811d1e3c5C5b67CA57df1DFb08847b1c8c458A",
|
|
114
|
+
vouchingRegistryAddress: "0x94519194Bf17865770faD59eF581feC512Ae99c9",
|
|
115
|
+
migrationRegistryAddress: "0xb60B62357b90F254f555f03B162a30E22890e3B5",
|
|
116
|
+
handshakeAddress: "0x4F5A38Bb746d7E5d49d8fd26CA6beD141Ec2DDb3",
|
|
117
|
+
},
|
|
118
|
+
"base-sepolia": {
|
|
119
|
+
rpcUrl: "https://sepolia.base.org",
|
|
120
|
+
usdcAddress: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
121
|
+
// v2 deployment — Base Sepolia (chain 84532) — deployed 2026-03-15
|
|
122
|
+
// Unchanged v1 contracts:
|
|
123
|
+
policyEngineAddress: "0x44102e70c2A366632d98Fe40d892a2501fC7fFF2",
|
|
124
|
+
intentAttestationAddress: "0x942c807Cc6E0240A061e074b61345618aBadc457",
|
|
125
|
+
settlementCoordinatorAddress: "0x52b565797975781f069368Df40d6633b2aD03390",
|
|
126
|
+
agentRegistryV2Address: "0x07D526f8A8e148570509aFa249EFF295045A0cc9", // AgentRegistry
|
|
127
|
+
reputationOracleAddress: "0x410e650113fd163389C956BC7fC51c5642617187",
|
|
128
|
+
walletFactoryAddress: "0xD560C22aD5372Aa830ee5ffBFa4a5D9f528e7B87",
|
|
129
|
+
sponsorshipAttestationAddress:"0xc0d927745AcF8DEeE551BE11A12c97c492DDC989",
|
|
130
|
+
governanceAddress: "0x504b3D73A8dFbcAB9551d8a11Bb0B07C90C4c926",
|
|
131
|
+
guardianAddress: "0x5c1D2cD6B9B291b436BF1b109A711F0E477EB6fe",
|
|
132
|
+
walletContractAddress: "0xc77854f9091A25eD1f35EA24E9bdFb64d0850E45",
|
|
133
|
+
agreementTreeAddress: "0x8F46F31FcEbd60f526308AD20e4a008887709720",
|
|
134
|
+
capabilityRegistryAddress: "0x6a413e74b65828A014dD8DA61861Bf9E1b6372D2",
|
|
135
|
+
governedTokenWhitelistAddress:"0x64C15CA701167C7c901a8a5575a5232b37CAF213",
|
|
136
|
+
watchtowerRegistryAddress: "0x70c4E53E3A916eB8A695630f129B943af9C61C57",
|
|
137
|
+
// v2 contracts (new/redeployed 2026-03-15):
|
|
138
|
+
trustRegistryAddress: "0xf2aE072BB8575c23B0efbF44bDc8188aA900cA7a", // TrustRegistryV3
|
|
139
|
+
agentRegistryAddress: "0x0461b2b7A1E50866962CB07326000A94009c58Ff", // ARC402RegistryV2
|
|
140
|
+
serviceAgreementAddress: "0xbbb1DA355D810E9baEF1a7D072B2132E4755976B",
|
|
141
|
+
sessionChannelsAddress: "0x5EF144AE2C8456d014e6E3F293c162410C043564",
|
|
142
|
+
disputeModuleAddress: "0x01866144495fBBbBB7aaD81605de051B2A62594A",
|
|
143
|
+
disputeArbitrationAddress: "0xa4f6F77927Da53a25926A5f0bffBEB0210108cA8",
|
|
144
|
+
vouchingRegistryAddress: "0x96432aDc7aC06256297AdF11B94C47f68b2F13A2",
|
|
145
|
+
migrationRegistryAddress: "0x3aeAaD32386D6fC40eeb5c2C27a5aCFE6aDf9ABD",
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export const getUsdcAddress = (config: Arc402Config) => NETWORK_DEFAULTS[config.network]?.usdcAddress ?? "";
|
|
150
|
+
|
|
151
|
+
export function getSubdomainApi(config: Arc402Config): string {
|
|
152
|
+
return config.subdomainApi ?? "https://api.arc402.xyz";
|
|
153
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon configuration loader.
|
|
3
|
+
* Parses ~/.arc402/daemon.toml, enforces env: prefix for secrets,
|
|
4
|
+
* resolves env: values from the environment.
|
|
5
|
+
*/
|
|
6
|
+
import * as fs from "fs";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
import * as os from "os";
|
|
9
|
+
import { parse as parseToml } from "smol-toml";
|
|
10
|
+
|
|
11
|
+
export const DAEMON_DIR = path.join(os.homedir(), ".arc402");
|
|
12
|
+
export const DAEMON_TOML = path.join(DAEMON_DIR, "daemon.toml");
|
|
13
|
+
export const DAEMON_PID = path.join(DAEMON_DIR, "daemon.pid");
|
|
14
|
+
export const DAEMON_LOG = path.join(DAEMON_DIR, "daemon.log");
|
|
15
|
+
export const DAEMON_DB = path.join(DAEMON_DIR, "daemon.db");
|
|
16
|
+
export const DAEMON_SOCK = path.join(DAEMON_DIR, "daemon.sock");
|
|
17
|
+
|
|
18
|
+
export interface DaemonConfig {
|
|
19
|
+
wallet: {
|
|
20
|
+
contract_address: string;
|
|
21
|
+
owner_address: string;
|
|
22
|
+
machine_key: string; // must be "env:VAR_NAME"
|
|
23
|
+
};
|
|
24
|
+
network: {
|
|
25
|
+
rpc_url: string;
|
|
26
|
+
chain_id: number;
|
|
27
|
+
entry_point: string;
|
|
28
|
+
};
|
|
29
|
+
bundler: {
|
|
30
|
+
mode: "external" | "arc402" | "self";
|
|
31
|
+
endpoint: string;
|
|
32
|
+
earn_fees: boolean;
|
|
33
|
+
eth_float: string;
|
|
34
|
+
sweep_threshold: string;
|
|
35
|
+
sweep_to: string;
|
|
36
|
+
rpc_url: string;
|
|
37
|
+
};
|
|
38
|
+
relay: {
|
|
39
|
+
enabled: boolean;
|
|
40
|
+
listen_port: number;
|
|
41
|
+
endpoint: string;
|
|
42
|
+
max_concurrent_agreements: number;
|
|
43
|
+
poll_interval_seconds: number;
|
|
44
|
+
relay_url: string;
|
|
45
|
+
};
|
|
46
|
+
watchtower: {
|
|
47
|
+
enabled: boolean;
|
|
48
|
+
poll_interval_seconds: number;
|
|
49
|
+
challenge_confirmation_blocks: number;
|
|
50
|
+
external_watchtower_url: string;
|
|
51
|
+
update_interval_states: number;
|
|
52
|
+
};
|
|
53
|
+
policy: {
|
|
54
|
+
auto_accept: boolean;
|
|
55
|
+
max_price_eth: string;
|
|
56
|
+
allowed_capabilities: string[];
|
|
57
|
+
require_min_trust_score: number;
|
|
58
|
+
min_hire_lead_time_seconds: number;
|
|
59
|
+
};
|
|
60
|
+
notifications: {
|
|
61
|
+
telegram_bot_token: string;
|
|
62
|
+
telegram_chat_id: string;
|
|
63
|
+
notify_on_hire_request: boolean;
|
|
64
|
+
notify_on_hire_accepted: boolean;
|
|
65
|
+
notify_on_hire_rejected: boolean;
|
|
66
|
+
notify_on_delivery: boolean;
|
|
67
|
+
notify_on_dispute: boolean;
|
|
68
|
+
notify_on_channel_challenge: boolean;
|
|
69
|
+
notify_on_low_balance: boolean;
|
|
70
|
+
low_balance_threshold_eth: string;
|
|
71
|
+
};
|
|
72
|
+
work: {
|
|
73
|
+
handler: "exec" | "http" | "noop";
|
|
74
|
+
exec_command: string;
|
|
75
|
+
http_url: string;
|
|
76
|
+
http_auth_token: string;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function resolveEnvValue(value: string, field: string): string {
|
|
81
|
+
if (!value.startsWith("env:")) return value;
|
|
82
|
+
const varName = value.slice(4);
|
|
83
|
+
const resolved = process.env[varName];
|
|
84
|
+
if (!resolved) {
|
|
85
|
+
throw new Error(`Environment variable ${varName} is not set (required for ${field})`);
|
|
86
|
+
}
|
|
87
|
+
return resolved;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function tryResolveEnvValue(value: string): string {
|
|
91
|
+
if (!value.startsWith("env:")) return value;
|
|
92
|
+
const varName = value.slice(4);
|
|
93
|
+
return process.env[varName] ?? "";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function str(v: unknown, def = ""): string {
|
|
97
|
+
return typeof v === "string" ? v : def;
|
|
98
|
+
}
|
|
99
|
+
function num(v: unknown, def: number): number {
|
|
100
|
+
return typeof v === "number" ? v : def;
|
|
101
|
+
}
|
|
102
|
+
function bool(v: unknown, def: boolean): boolean {
|
|
103
|
+
return typeof v === "boolean" ? v : def;
|
|
104
|
+
}
|
|
105
|
+
function strArr(v: unknown): string[] {
|
|
106
|
+
return Array.isArray(v) ? (v as string[]) : [];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function withDefaults(raw: Record<string, unknown>): DaemonConfig {
|
|
110
|
+
const w = (raw.wallet as Record<string, unknown>) ?? {};
|
|
111
|
+
const n = (raw.network as Record<string, unknown>) ?? {};
|
|
112
|
+
const b = (raw.bundler as Record<string, unknown>) ?? {};
|
|
113
|
+
const r = (raw.relay as Record<string, unknown>) ?? {};
|
|
114
|
+
const wt = (raw.watchtower as Record<string, unknown>) ?? {};
|
|
115
|
+
const p = (raw.policy as Record<string, unknown>) ?? {};
|
|
116
|
+
const notif = (raw.notifications as Record<string, unknown>) ?? {};
|
|
117
|
+
const work = (raw.work as Record<string, unknown>) ?? {};
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
wallet: {
|
|
121
|
+
contract_address: str(w.contract_address),
|
|
122
|
+
owner_address: str(w.owner_address),
|
|
123
|
+
machine_key: str(w.machine_key, "env:ARC402_MACHINE_KEY"),
|
|
124
|
+
},
|
|
125
|
+
network: {
|
|
126
|
+
rpc_url: str(n.rpc_url, "https://base-mainnet.g.alchemy.com/v2/YIA2uRCsFI-j5pqH-aRzflrACSlV1Qrs"),
|
|
127
|
+
chain_id: num(n.chain_id, 8453),
|
|
128
|
+
entry_point: str(n.entry_point, "0x0000000071727De22E5E9d8BAf0edAc6f37da032"),
|
|
129
|
+
},
|
|
130
|
+
bundler: {
|
|
131
|
+
mode: (str(b.mode, "external")) as "external" | "arc402" | "self",
|
|
132
|
+
endpoint: str(b.endpoint),
|
|
133
|
+
earn_fees: bool(b.earn_fees, false),
|
|
134
|
+
eth_float: str(b.eth_float, "0.01"),
|
|
135
|
+
sweep_threshold: str(b.sweep_threshold, "0.005"),
|
|
136
|
+
sweep_to: str(b.sweep_to),
|
|
137
|
+
rpc_url: str(b.rpc_url),
|
|
138
|
+
},
|
|
139
|
+
relay: {
|
|
140
|
+
enabled: bool(r.enabled, true),
|
|
141
|
+
listen_port: num(r.listen_port, 4402),
|
|
142
|
+
endpoint: str(r.endpoint),
|
|
143
|
+
max_concurrent_agreements: num(r.max_concurrent_agreements, 10),
|
|
144
|
+
poll_interval_seconds: num(r.poll_interval_seconds, 2),
|
|
145
|
+
relay_url: str(r.relay_url),
|
|
146
|
+
},
|
|
147
|
+
watchtower: {
|
|
148
|
+
enabled: bool(wt.enabled, true),
|
|
149
|
+
poll_interval_seconds: num(wt.poll_interval_seconds, 60),
|
|
150
|
+
challenge_confirmation_blocks: num(wt.challenge_confirmation_blocks, 2),
|
|
151
|
+
external_watchtower_url: str(wt.external_watchtower_url),
|
|
152
|
+
update_interval_states: num(wt.update_interval_states, 10),
|
|
153
|
+
},
|
|
154
|
+
policy: {
|
|
155
|
+
auto_accept: bool(p.auto_accept, false),
|
|
156
|
+
max_price_eth: str(p.max_price_eth, "0.1"),
|
|
157
|
+
allowed_capabilities: strArr(p.allowed_capabilities),
|
|
158
|
+
require_min_trust_score: num(p.require_min_trust_score, 50),
|
|
159
|
+
min_hire_lead_time_seconds: num(p.min_hire_lead_time_seconds, 300),
|
|
160
|
+
},
|
|
161
|
+
notifications: {
|
|
162
|
+
telegram_bot_token: str(notif.telegram_bot_token, "env:TELEGRAM_BOT_TOKEN"),
|
|
163
|
+
telegram_chat_id: str(notif.telegram_chat_id, "env:TELEGRAM_CHAT_ID"),
|
|
164
|
+
notify_on_hire_request: bool(notif.notify_on_hire_request, true),
|
|
165
|
+
notify_on_hire_accepted: bool(notif.notify_on_hire_accepted, true),
|
|
166
|
+
notify_on_hire_rejected: bool(notif.notify_on_hire_rejected, true),
|
|
167
|
+
notify_on_delivery: bool(notif.notify_on_delivery, true),
|
|
168
|
+
notify_on_dispute: bool(notif.notify_on_dispute, true),
|
|
169
|
+
notify_on_channel_challenge: bool(notif.notify_on_channel_challenge, true),
|
|
170
|
+
notify_on_low_balance: bool(notif.notify_on_low_balance, true),
|
|
171
|
+
low_balance_threshold_eth: str(notif.low_balance_threshold_eth, "0.005"),
|
|
172
|
+
},
|
|
173
|
+
work: {
|
|
174
|
+
handler: (str(work.handler, "noop")) as "exec" | "http" | "noop",
|
|
175
|
+
exec_command: str(work.exec_command),
|
|
176
|
+
http_url: str(work.http_url),
|
|
177
|
+
http_auth_token: str(work.http_auth_token),
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function loadDaemonConfig(configPath = DAEMON_TOML): DaemonConfig {
|
|
183
|
+
if (!fs.existsSync(configPath)) {
|
|
184
|
+
throw new Error(`daemon.toml not found at ${configPath}. Run: arc402 daemon init`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
188
|
+
let parsed: Record<string, unknown>;
|
|
189
|
+
try {
|
|
190
|
+
parsed = parseToml(raw) as Record<string, unknown>;
|
|
191
|
+
} catch (err) {
|
|
192
|
+
throw new Error(`Failed to parse daemon.toml: ${err instanceof Error ? err.message : String(err)}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const config = withDefaults(parsed);
|
|
196
|
+
|
|
197
|
+
// Required fields
|
|
198
|
+
if (!config.wallet.contract_address) {
|
|
199
|
+
throw new Error("daemon.toml: wallet.contract_address is required");
|
|
200
|
+
}
|
|
201
|
+
if (!config.network.rpc_url) {
|
|
202
|
+
throw new Error("daemon.toml: network.rpc_url is required");
|
|
203
|
+
}
|
|
204
|
+
if (!config.network.chain_id) {
|
|
205
|
+
throw new Error("daemon.toml: network.chain_id is required");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Machine key MUST use env: prefix — never hardcoded
|
|
209
|
+
if (!config.wallet.machine_key.startsWith("env:")) {
|
|
210
|
+
throw new Error("ERROR: machine_key must use env: prefix — never hardcode keys");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Resolve optional env: values silently (missing = disabled feature)
|
|
214
|
+
config.notifications.telegram_bot_token = tryResolveEnvValue(config.notifications.telegram_bot_token);
|
|
215
|
+
config.notifications.telegram_chat_id = tryResolveEnvValue(config.notifications.telegram_chat_id);
|
|
216
|
+
config.work.http_auth_token = tryResolveEnvValue(config.work.http_auth_token);
|
|
217
|
+
|
|
218
|
+
return config;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function loadMachineKey(config: DaemonConfig): { privateKey: string; address: string } {
|
|
222
|
+
const envVarName = config.wallet.machine_key.startsWith("env:")
|
|
223
|
+
? config.wallet.machine_key.slice(4)
|
|
224
|
+
: "ARC402_MACHINE_KEY";
|
|
225
|
+
|
|
226
|
+
const privateKey = process.env[envVarName];
|
|
227
|
+
if (!privateKey) {
|
|
228
|
+
throw new Error(`Machine key not found. Set environment variable: ${envVarName}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const { ethers } = require("ethers") as typeof import("ethers");
|
|
232
|
+
let address: string;
|
|
233
|
+
try {
|
|
234
|
+
const w = new ethers.Wallet(privateKey);
|
|
235
|
+
address = w.address;
|
|
236
|
+
} catch {
|
|
237
|
+
throw new Error(`Invalid machine key format in ${envVarName}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return { privateKey, address };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export const TEMPLATE_DAEMON_TOML = `# ~/.arc402/daemon.toml
|
|
244
|
+
# ARC-402 Daemon Configuration
|
|
245
|
+
# Generated by: arc402 daemon init
|
|
246
|
+
#
|
|
247
|
+
# SECURITY: Never put private keys here. Use environment variables.
|
|
248
|
+
|
|
249
|
+
[wallet]
|
|
250
|
+
contract_address = "" # ARC402Wallet contract address (required)
|
|
251
|
+
owner_address = "" # Owner EOA address — for display and verification only
|
|
252
|
+
machine_key = "env:ARC402_MACHINE_KEY" # Machine key loaded from environment. NEVER hardcode here.
|
|
253
|
+
|
|
254
|
+
[network]
|
|
255
|
+
rpc_url = "https://base-mainnet.g.alchemy.com/v2/YIA2uRCsFI-j5pqH-aRzflrACSlV1Qrs" # Alchemy Base RPC (recommended)
|
|
256
|
+
chain_id = 8453 # Base mainnet. Use 84532 for Base Sepolia.
|
|
257
|
+
entry_point = "0x0000000071727De22E5E9d8BAf0edAc6f37da032" # ERC-4337 EntryPoint v0.7
|
|
258
|
+
|
|
259
|
+
[bundler]
|
|
260
|
+
mode = "external" # external | arc402 | self
|
|
261
|
+
endpoint = "" # Required when mode = external. Pimlico, Alchemy, etc.
|
|
262
|
+
earn_fees = false # self mode only: bundle for other network agents
|
|
263
|
+
eth_float = "0.01" # Minimum ETH to maintain in bundler EOA for gas fronting
|
|
264
|
+
sweep_threshold = "0.005" # Sweep fees to wallet when bundler EOA exceeds this (ETH)
|
|
265
|
+
sweep_to = "" # Sweep destination. Defaults to wallet.contract_address.
|
|
266
|
+
rpc_url = "" # self mode: private RPC. Defaults to network.rpc_url if empty.
|
|
267
|
+
|
|
268
|
+
[relay]
|
|
269
|
+
enabled = true
|
|
270
|
+
listen_port = 4402 # Port for incoming relay messages
|
|
271
|
+
endpoint = "" # Your public URL — run: arc402 setup endpoint
|
|
272
|
+
# Example: https://gigabrain.arc402.xyz
|
|
273
|
+
max_concurrent_agreements = 10 # Refuse new hire requests when this many are in-flight
|
|
274
|
+
poll_interval_seconds = 2 # How often to poll relay for incoming messages
|
|
275
|
+
relay_url = "" # The relay to poll. Defaults to agent metadata relay if empty.
|
|
276
|
+
|
|
277
|
+
[watchtower]
|
|
278
|
+
enabled = true
|
|
279
|
+
poll_interval_seconds = 60 # How often to poll chain for stale-close events
|
|
280
|
+
challenge_confirmation_blocks = 2 # Wait N block confirmations before accepting close as final
|
|
281
|
+
external_watchtower_url = "" # Register open channels here as backup (Tier 2 watchtower)
|
|
282
|
+
update_interval_states = 10 # Forward state to external watchtower every N state changes
|
|
283
|
+
|
|
284
|
+
[policy]
|
|
285
|
+
auto_accept = false # If true: auto-accept all hire requests within policy bounds
|
|
286
|
+
max_price_eth = "0.1" # Refuse any hire priced above this (ETH)
|
|
287
|
+
allowed_capabilities = [] # Empty list = accept any capability. Non-empty = whitelist.
|
|
288
|
+
require_min_trust_score = 50 # Refuse hirers whose wallet trust score is below this (0–100)
|
|
289
|
+
min_hire_lead_time_seconds = 300 # Refuse hires with delivery deadline < this many seconds away
|
|
290
|
+
|
|
291
|
+
[notifications]
|
|
292
|
+
telegram_bot_token = "env:TELEGRAM_BOT_TOKEN" # Load from env, not hardcoded
|
|
293
|
+
telegram_chat_id = "env:TELEGRAM_CHAT_ID" # Load from env, not hardcoded
|
|
294
|
+
notify_on_hire_request = true # Notify when a hire request arrives (pending approval)
|
|
295
|
+
notify_on_hire_accepted = true # Notify when daemon accepts a hire
|
|
296
|
+
notify_on_hire_rejected = true # Notify when daemon rejects a hire
|
|
297
|
+
notify_on_delivery = true # Notify when work is delivered and fulfill() submitted
|
|
298
|
+
notify_on_dispute = true # Notify when a dispute is raised (by either party)
|
|
299
|
+
notify_on_channel_challenge = true # Notify when watchtower submits a channel challenge
|
|
300
|
+
notify_on_low_balance = false # Disabled by default — enable if you want balance alerts
|
|
301
|
+
low_balance_threshold_eth = "0.005" # Balance alert threshold
|
|
302
|
+
|
|
303
|
+
[work]
|
|
304
|
+
handler = "noop" # exec | http | noop
|
|
305
|
+
exec_command = "" # called with agreementId and spec as args (exec mode)
|
|
306
|
+
http_url = "" # POST {agreementId, specHash, deadline} as JSON (http mode)
|
|
307
|
+
http_auth_token = "env:WORKER_AUTH_TOKEN"
|
|
308
|
+
`;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hire listener — polls relay for incoming hire proposals,
|
|
3
|
+
* evaluates against policy, and queues for approval or auto-accepts.
|
|
4
|
+
*/
|
|
5
|
+
import * as http from "http";
|
|
6
|
+
import * as https from "https";
|
|
7
|
+
import { ethers } from "ethers";
|
|
8
|
+
import type { DaemonConfig } from "./config";
|
|
9
|
+
import type { DaemonDB } from "./index";
|
|
10
|
+
import type { Notifier } from "./notify";
|
|
11
|
+
|
|
12
|
+
export interface HireProposal {
|
|
13
|
+
messageId: string;
|
|
14
|
+
hirerAddress: string;
|
|
15
|
+
capability: string;
|
|
16
|
+
priceEth: string;
|
|
17
|
+
deadlineUnix: number;
|
|
18
|
+
specHash: string;
|
|
19
|
+
agreementId?: string;
|
|
20
|
+
signature?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PolicyResult {
|
|
24
|
+
allowed: boolean;
|
|
25
|
+
reason?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function relayGet(
|
|
29
|
+
relayUrl: string,
|
|
30
|
+
urlPath: string
|
|
31
|
+
): Promise<{ status: number; data: unknown }> {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const parsed = new URL(urlPath, relayUrl);
|
|
34
|
+
const isHttps = parsed.protocol === "https:";
|
|
35
|
+
const mod = isHttps ? https : http;
|
|
36
|
+
const options: http.RequestOptions = {
|
|
37
|
+
hostname: parsed.hostname,
|
|
38
|
+
port: parsed.port || (isHttps ? 443 : 80),
|
|
39
|
+
path: parsed.pathname + (parsed.search || ""),
|
|
40
|
+
method: "GET",
|
|
41
|
+
headers: { "Content-Type": "application/json" },
|
|
42
|
+
};
|
|
43
|
+
const req = mod.request(options, (res) => {
|
|
44
|
+
let raw = "";
|
|
45
|
+
res.on("data", (c: Buffer) => { raw += c.toString(); });
|
|
46
|
+
res.on("end", () => {
|
|
47
|
+
try {
|
|
48
|
+
resolve({ status: res.statusCode ?? 0, data: JSON.parse(raw) });
|
|
49
|
+
} catch {
|
|
50
|
+
resolve({ status: res.statusCode ?? 0, data: raw });
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
req.on("error", reject);
|
|
55
|
+
req.end();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function evaluatePolicy(
|
|
60
|
+
proposal: HireProposal,
|
|
61
|
+
config: DaemonConfig,
|
|
62
|
+
activeCount: number
|
|
63
|
+
): PolicyResult {
|
|
64
|
+
const policy = config.policy;
|
|
65
|
+
|
|
66
|
+
// Concurrency check
|
|
67
|
+
if (activeCount >= config.relay.max_concurrent_agreements) {
|
|
68
|
+
return { allowed: false, reason: "at_capacity" };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Price check
|
|
72
|
+
try {
|
|
73
|
+
const priceWei = ethers.parseEther(proposal.priceEth || "0");
|
|
74
|
+
const maxWei = ethers.parseEther(policy.max_price_eth);
|
|
75
|
+
if (priceWei > maxWei) {
|
|
76
|
+
return { allowed: false, reason: "price_exceeds_policy" };
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
return { allowed: false, reason: "invalid_price" };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Capability check (empty list = accept all)
|
|
83
|
+
if (policy.allowed_capabilities.length > 0 && proposal.capability) {
|
|
84
|
+
if (!policy.allowed_capabilities.includes(proposal.capability)) {
|
|
85
|
+
return { allowed: false, reason: "capability_not_allowed" };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Deadline check
|
|
90
|
+
const now = Math.floor(Date.now() / 1000);
|
|
91
|
+
if (proposal.deadlineUnix > 0) {
|
|
92
|
+
if (proposal.deadlineUnix < now + policy.min_hire_lead_time_seconds) {
|
|
93
|
+
return { allowed: false, reason: "deadline_too_soon" };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { allowed: true };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function parseProposal(msg: Record<string, unknown>): HireProposal | null {
|
|
101
|
+
const payload = (msg.payload ?? msg) as Record<string, unknown>;
|
|
102
|
+
if (!payload.hirerAddress && !payload.hirer_address && !payload.from) return null;
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
messageId: String(msg.messageId ?? msg.id ?? `msg_${Date.now()}`),
|
|
106
|
+
hirerAddress: String(payload.hirerAddress ?? payload.hirer_address ?? msg.from ?? ""),
|
|
107
|
+
capability: String(payload.capability ?? ""),
|
|
108
|
+
priceEth: String(payload.priceEth ?? payload.price_eth ?? "0"),
|
|
109
|
+
deadlineUnix: Number(payload.deadlineUnix ?? payload.deadline ?? 0),
|
|
110
|
+
specHash: String(payload.specHash ?? payload.spec_hash ?? ""),
|
|
111
|
+
agreementId: payload.agreementId ? String(payload.agreementId) : undefined,
|
|
112
|
+
signature: payload.signature ? String(payload.signature) : undefined,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export class HireListener {
|
|
117
|
+
private config: DaemonConfig;
|
|
118
|
+
private db: DaemonDB;
|
|
119
|
+
private notifier: Notifier;
|
|
120
|
+
private walletAddress: string;
|
|
121
|
+
private lastSeenMessageId: string | null = null;
|
|
122
|
+
private onApprove: ((hireId: string) => Promise<void>) | null = null;
|
|
123
|
+
|
|
124
|
+
constructor(
|
|
125
|
+
config: DaemonConfig,
|
|
126
|
+
db: DaemonDB,
|
|
127
|
+
notifier: Notifier,
|
|
128
|
+
walletAddress: string
|
|
129
|
+
) {
|
|
130
|
+
this.config = config;
|
|
131
|
+
this.db = db;
|
|
132
|
+
this.notifier = notifier;
|
|
133
|
+
this.walletAddress = walletAddress;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
setApproveCallback(cb: (hireId: string) => Promise<void>): void {
|
|
137
|
+
this.onApprove = cb;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async poll(): Promise<void> {
|
|
141
|
+
const relayUrl = this.config.relay.relay_url;
|
|
142
|
+
if (!relayUrl) return;
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const qs =
|
|
146
|
+
`?address=${encodeURIComponent(this.walletAddress)}` +
|
|
147
|
+
(this.lastSeenMessageId ? `&since=${encodeURIComponent(this.lastSeenMessageId)}` : "");
|
|
148
|
+
|
|
149
|
+
const result = await relayGet(relayUrl, `/poll${qs}`);
|
|
150
|
+
const data = result.data as { messages?: Array<Record<string, unknown>> };
|
|
151
|
+
const messages = data.messages ?? [];
|
|
152
|
+
|
|
153
|
+
for (const msg of messages) {
|
|
154
|
+
this.lastSeenMessageId = String(msg.messageId ?? "");
|
|
155
|
+
await this.handleMessage(msg);
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
// Transient relay failure — retry next poll
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private async handleMessage(msg: Record<string, unknown>): Promise<void> {
|
|
163
|
+
const proposal = parseProposal(msg);
|
|
164
|
+
if (!proposal) return;
|
|
165
|
+
|
|
166
|
+
// Dedup — skip if already in DB
|
|
167
|
+
const existing = this.db.getHireRequest(proposal.messageId);
|
|
168
|
+
if (existing) return;
|
|
169
|
+
|
|
170
|
+
// Count active agreements
|
|
171
|
+
const activeCount = this.db.countActiveHireRequests();
|
|
172
|
+
|
|
173
|
+
// Policy evaluation
|
|
174
|
+
const policyResult = evaluatePolicy(proposal, this.config, activeCount);
|
|
175
|
+
|
|
176
|
+
if (!policyResult.allowed) {
|
|
177
|
+
// Reject
|
|
178
|
+
const hireId = proposal.messageId;
|
|
179
|
+
this.db.insertHireRequest({
|
|
180
|
+
id: hireId,
|
|
181
|
+
agreement_id: proposal.agreementId ?? null,
|
|
182
|
+
hirer_address: proposal.hirerAddress,
|
|
183
|
+
capability: proposal.capability,
|
|
184
|
+
price_eth: proposal.priceEth,
|
|
185
|
+
deadline_unix: proposal.deadlineUnix,
|
|
186
|
+
spec_hash: proposal.specHash,
|
|
187
|
+
status: "rejected",
|
|
188
|
+
reject_reason: policyResult.reason ?? "policy_violation",
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (this.config.notifications.notify_on_hire_rejected) {
|
|
192
|
+
await this.notifier.notifyHireRejected(hireId, policyResult.reason ?? "policy_violation");
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Insert as pending_approval or auto-accept
|
|
198
|
+
const hireId = proposal.messageId;
|
|
199
|
+
const status = this.config.policy.auto_accept ? "accepted" : "pending_approval";
|
|
200
|
+
|
|
201
|
+
this.db.insertHireRequest({
|
|
202
|
+
id: hireId,
|
|
203
|
+
agreement_id: proposal.agreementId ?? null,
|
|
204
|
+
hirer_address: proposal.hirerAddress,
|
|
205
|
+
capability: proposal.capability,
|
|
206
|
+
price_eth: proposal.priceEth,
|
|
207
|
+
deadline_unix: proposal.deadlineUnix,
|
|
208
|
+
spec_hash: proposal.specHash,
|
|
209
|
+
status,
|
|
210
|
+
reject_reason: null,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
if (status === "pending_approval") {
|
|
214
|
+
if (this.config.notifications.notify_on_hire_request) {
|
|
215
|
+
await this.notifier.notifyHireRequest(
|
|
216
|
+
hireId,
|
|
217
|
+
proposal.hirerAddress,
|
|
218
|
+
proposal.priceEth,
|
|
219
|
+
proposal.capability
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
} else if (status === "accepted" && this.onApprove) {
|
|
223
|
+
await this.onApprove(hireId);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|