spendos 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +4 -0
- package/.env.example +30 -0
- package/AGENTS.md +212 -0
- package/BOOTSTRAP.md +55 -0
- package/Dockerfile +52 -0
- package/HEARTBEAT.md +7 -0
- package/IDENTITY.md +23 -0
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/SOUL.md +202 -0
- package/SUBMISSION.md +128 -0
- package/TOOLS.md +40 -0
- package/USER.md +17 -0
- package/acp-seller/bin/acp.ts +807 -0
- package/acp-seller/config.json +34 -0
- package/acp-seller/package.json +55 -0
- package/acp-seller/src/commands/agent.ts +328 -0
- package/acp-seller/src/commands/bounty.ts +1189 -0
- package/acp-seller/src/commands/deploy.ts +414 -0
- package/acp-seller/src/commands/job.ts +217 -0
- package/acp-seller/src/commands/profile.ts +71 -0
- package/acp-seller/src/commands/resource.ts +91 -0
- package/acp-seller/src/commands/search.ts +327 -0
- package/acp-seller/src/commands/sell.ts +883 -0
- package/acp-seller/src/commands/serve.ts +258 -0
- package/acp-seller/src/commands/setup.ts +399 -0
- package/acp-seller/src/commands/token.ts +88 -0
- package/acp-seller/src/commands/wallet.ts +123 -0
- package/acp-seller/src/lib/api.ts +118 -0
- package/acp-seller/src/lib/auth.ts +291 -0
- package/acp-seller/src/lib/bounty.ts +257 -0
- package/acp-seller/src/lib/client.ts +42 -0
- package/acp-seller/src/lib/config.ts +240 -0
- package/acp-seller/src/lib/open.ts +41 -0
- package/acp-seller/src/lib/openclawCron.ts +138 -0
- package/acp-seller/src/lib/output.ts +104 -0
- package/acp-seller/src/lib/wallet.ts +81 -0
- package/acp-seller/src/seller/offerings/_shared/preTransactionScan.ts +127 -0
- package/acp-seller/src/seller/offerings/canonical-catalog.ts +221 -0
- package/acp-seller/src/seller/offerings/spendos/spendos_summarize_url/handlers.ts +20 -0
- package/acp-seller/src/seller/offerings/spendos/spendos_summarize_url/offering.json +18 -0
- package/acp-seller/src/seller/offerings/spendos/spendos_translate/handlers.ts +21 -0
- package/acp-seller/src/seller/offerings/spendos/spendos_translate/offering.json +22 -0
- package/acp-seller/src/seller/offerings/spendos/spendos_tweet_gen/handlers.ts +20 -0
- package/acp-seller/src/seller/offerings/spendos/spendos_tweet_gen/offering.json +18 -0
- package/acp-seller/src/seller/runtime/acpSocket.ts +413 -0
- package/acp-seller/src/seller/runtime/logger.ts +36 -0
- package/acp-seller/src/seller/runtime/offeringTypes.ts +52 -0
- package/acp-seller/src/seller/runtime/offerings.ts +277 -0
- package/acp-seller/src/seller/runtime/paymentVerification.test.ts +207 -0
- package/acp-seller/src/seller/runtime/paymentVerification.ts +363 -0
- package/acp-seller/src/seller/runtime/seller.onchain.test.ts +220 -0
- package/acp-seller/src/seller/runtime/seller.test.ts +823 -0
- package/acp-seller/src/seller/runtime/seller.ts +1041 -0
- package/acp-seller/src/seller/runtime/sellerApi.ts +71 -0
- package/acp-seller/src/seller/runtime/startup.ts +270 -0
- package/acp-seller/src/seller/runtime/types.ts +62 -0
- package/acp-seller/tsconfig.json +20 -0
- package/bin/spendos.js +23 -0
- package/contracts/SpendOSAudit.sol +29 -0
- package/dist/mcp-server.mjs +153 -0
- package/jobs/translate.json +7 -0
- package/jobs/tweet-gen.json +7 -0
- package/openclaw.json +41 -0
- package/package.json +49 -0
- package/plugins/spendos-events/index.ts +78 -0
- package/plugins/spendos-events/package.json +14 -0
- package/policies/enforce-bounds.mjs +71 -0
- package/public/index.html +509 -0
- package/public/landing.html +241 -0
- package/railway.json +12 -0
- package/railway.toml +12 -0
- package/scripts/deploy.ts +48 -0
- package/scripts/test-x402-mainnet.ts +30 -0
- package/scripts/xmtp-listener.ts +61 -0
- package/setup.sh +278 -0
- package/skills/spendos/skill.md +26 -0
- package/src/agent.ts +152 -0
- package/src/audit.ts +166 -0
- package/src/governance.ts +367 -0
- package/src/job-registry.ts +306 -0
- package/src/mcp-public.ts +145 -0
- package/src/mcp-server.ts +171 -0
- package/src/opportunity-scanner.ts +138 -0
- package/src/server.ts +870 -0
- package/src/venice-x402.ts +234 -0
- package/src/xmtp.ts +109 -0
- package/src/zerion.ts +58 -0
- package/start.sh +168 -0
- package/tsconfig.json +14 -0
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "spendos",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The first autonomous AI agent that governs its own spending. Earns USDC, self-funds inference, proposes investments — all on-chain.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"spendos": "./bin/spendos.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": ["ai-agent", "autonomous", "usdc", "x402", "ows", "base", "defi", "governance", "acp", "venice"],
|
|
10
|
+
"author": "Roman Mondello <romy@consensus.sh>",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/consensus-hq/spendos"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://spendos.xyz",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"dev": "tsx watch src/server.ts",
|
|
19
|
+
"start": "tsx src/server.ts",
|
|
20
|
+
"deploy-contract": "tsx scripts/deploy.ts",
|
|
21
|
+
"mcp": "tsx src/mcp-server.ts"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@coinbase/x402": "^2.1.0",
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
26
|
+
"@open-wallet-standard/core": "^1.2.0",
|
|
27
|
+
"@x402/axios": "^2.9.0",
|
|
28
|
+
"@x402/core": "^2.9.0",
|
|
29
|
+
"@x402/evm": "^2.9.0",
|
|
30
|
+
"@x402/express": "^2.9.0",
|
|
31
|
+
"@x402/fetch": "^2.9.0",
|
|
32
|
+
"@xmtp/node-sdk": "^6.0.0",
|
|
33
|
+
"cors": "^2.8.5",
|
|
34
|
+
"express": "^4.21.0",
|
|
35
|
+
"mppx": "^0.5.5",
|
|
36
|
+
"viem": "^2.46.2",
|
|
37
|
+
"x402": "^1.1.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/cors": "^2.8.17",
|
|
41
|
+
"@types/express": "^5.0.0",
|
|
42
|
+
"@types/node": "^22.0.0",
|
|
43
|
+
"tsx": "^4.19.0",
|
|
44
|
+
"typescript": "^5.7.0"
|
|
45
|
+
},
|
|
46
|
+
"optionalDependencies": {
|
|
47
|
+
"@xmtp/node-bindings-darwin-arm64": "npm:null@*"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpendOS Events Plugin for OpenClaw
|
|
3
|
+
*
|
|
4
|
+
* Injects revenue events into the agent's context so it knows
|
|
5
|
+
* when it earns money, what its balance is, and when to propose
|
|
6
|
+
* new investment delegations.
|
|
7
|
+
*/
|
|
8
|
+
import { definePluginEntry } from 'openclaw/plugin-sdk/plugin-entry';
|
|
9
|
+
import { Type } from 'openclaw/plugin-sdk/typebox';
|
|
10
|
+
|
|
11
|
+
const SPENDOS_URL = process.env.SPENDOS_URL ?? 'https://spendos-production.up.railway.app';
|
|
12
|
+
|
|
13
|
+
export default definePluginEntry({
|
|
14
|
+
id: 'spendos-events',
|
|
15
|
+
name: 'SpendOS Revenue Events',
|
|
16
|
+
description: 'Injects P&L and delegation data into agent context',
|
|
17
|
+
|
|
18
|
+
register(api) {
|
|
19
|
+
// Hook: inject P&L context before every agent response
|
|
20
|
+
api.registerHook('before_agent_start', async () => {
|
|
21
|
+
try {
|
|
22
|
+
const pnlRes = await fetch(`${SPENDOS_URL}/api/pnl`);
|
|
23
|
+
const pnl = await pnlRes.json() as any;
|
|
24
|
+
|
|
25
|
+
const reqRes = await fetch(`${SPENDOS_URL}/api/requests`);
|
|
26
|
+
const requests = await reqRes.json() as any[];
|
|
27
|
+
|
|
28
|
+
const pending = requests.filter((r: any) => r.status === 'pending').length;
|
|
29
|
+
const active = requests.filter((r: any) => r.status === 'approved').length;
|
|
30
|
+
|
|
31
|
+
const context = [
|
|
32
|
+
`[SpendOS] P&L: earned $${pnl.totalEarned.toFixed(3)}, spent $${pnl.totalSpent.toFixed(4)}, profit $${pnl.profit.toFixed(4)} (${pnl.queryCount} queries, ${pnl.totalEarned > 0 ? ((pnl.profit / pnl.totalEarned) * 100).toFixed(0) : 0}% margin)`,
|
|
33
|
+
pending > 0 ? `[SpendOS] ${pending} delegation(s) pending your proposals` : '',
|
|
34
|
+
active > 0 ? `[SpendOS] ${active} active delegation(s)` : '',
|
|
35
|
+
].filter(Boolean).join('\n');
|
|
36
|
+
|
|
37
|
+
return { systemContext: context };
|
|
38
|
+
} catch {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Tool: let the agent check revenue in real-time
|
|
44
|
+
api.registerTool({
|
|
45
|
+
name: 'spendos_revenue_status',
|
|
46
|
+
description: 'Get real-time SpendOS revenue status including P&L, active delegations, and Venice inference balance',
|
|
47
|
+
parameters: Type.Object({}),
|
|
48
|
+
async execute() {
|
|
49
|
+
try {
|
|
50
|
+
const [pnlRes, walletRes, reqRes] = await Promise.all([
|
|
51
|
+
fetch(`${SPENDOS_URL}/api/pnl`),
|
|
52
|
+
fetch(`${SPENDOS_URL}/api/wallet`),
|
|
53
|
+
fetch(`${SPENDOS_URL}/api/requests`),
|
|
54
|
+
]);
|
|
55
|
+
const pnl = await pnlRes.json() as any;
|
|
56
|
+
const wallet = await walletRes.json() as any;
|
|
57
|
+
const requests = await reqRes.json() as any[];
|
|
58
|
+
|
|
59
|
+
const text = [
|
|
60
|
+
`Revenue Status:`,
|
|
61
|
+
` Earned: $${pnl.totalEarned.toFixed(3)} (${pnl.queryCount} queries)`,
|
|
62
|
+
` Spent: $${pnl.totalSpent.toFixed(4)}`,
|
|
63
|
+
` Profit: $${pnl.profit.toFixed(4)} (${pnl.totalEarned > 0 ? ((pnl.profit / pnl.totalEarned) * 100).toFixed(0) : 0}% margin)`,
|
|
64
|
+
` Wallet: ${wallet.address}`,
|
|
65
|
+
` Inference: ${wallet.inferenceMode} (Venice wallet auth)`,
|
|
66
|
+
` Venice balance: $${wallet.veniceBalance?.balanceUsd?.toFixed(2) ?? 'unknown'}`,
|
|
67
|
+
` Pending delegations: ${requests.filter((r: any) => r.status === 'pending').length}`,
|
|
68
|
+
` Active delegations: ${requests.filter((r: any) => r.status === 'approved').length}`,
|
|
69
|
+
].join('\n');
|
|
70
|
+
|
|
71
|
+
return { content: [{ type: 'text', text }] };
|
|
72
|
+
} catch (err) {
|
|
73
|
+
return { content: [{ type: 'text', text: `SpendOS unavailable: ${err}` }] };
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
function deny(reason) {
|
|
5
|
+
process.stdout.write(JSON.stringify({ allow: false, reason }));
|
|
6
|
+
process.exit(0);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function allow() {
|
|
10
|
+
process.stdout.write(JSON.stringify({ allow: true }));
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeAddress(value) {
|
|
15
|
+
return String(value).trim().toLowerCase();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseBigInt(value, fallback = 0n) {
|
|
19
|
+
if (value === undefined || value === null || value === "") return fallback;
|
|
20
|
+
try {
|
|
21
|
+
return BigInt(String(value));
|
|
22
|
+
} catch {
|
|
23
|
+
return fallback;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const raw = readFileSync(0, "utf8");
|
|
29
|
+
const ctx = JSON.parse(raw);
|
|
30
|
+
|
|
31
|
+
const tx = ctx.transaction ?? {};
|
|
32
|
+
const policy = ctx.policy_config ?? {};
|
|
33
|
+
|
|
34
|
+
const to = tx.to ? normalizeAddress(tx.to) : undefined;
|
|
35
|
+
const valueWei = parseBigInt(tx.value, 0n);
|
|
36
|
+
const dailyTotalWei = parseBigInt(ctx.spending?.daily_total, 0n);
|
|
37
|
+
|
|
38
|
+
const allowedRecipients = Array.isArray(policy.allowed_recipients)
|
|
39
|
+
? policy.allowed_recipients.map(normalizeAddress)
|
|
40
|
+
: [];
|
|
41
|
+
|
|
42
|
+
const maxNativeValueWei = parseBigInt(policy.max_native_value_wei, 0n);
|
|
43
|
+
const maxDailyTotalWei = parseBigInt(policy.max_daily_total_wei, 0n);
|
|
44
|
+
|
|
45
|
+
if (allowedRecipients.length > 0) {
|
|
46
|
+
if (!to) {
|
|
47
|
+
deny("Transaction recipient missing.");
|
|
48
|
+
}
|
|
49
|
+
if (!allowedRecipients.includes(to)) {
|
|
50
|
+
deny(`Recipient ${to} is not on the allowlist.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (maxNativeValueWei > 0n && valueWei > maxNativeValueWei) {
|
|
55
|
+
deny(
|
|
56
|
+
`Native value exceeds limit: ${valueWei.toString()} > ${maxNativeValueWei.toString()}`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (maxDailyTotalWei > 0n && dailyTotalWei + valueWei > maxDailyTotalWei) {
|
|
61
|
+
deny(
|
|
62
|
+
`Daily native spend exceeds limit: ${(dailyTotalWei + valueWei).toString()} > ${maxDailyTotalWei.toString()}`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
allow();
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const message = error instanceof Error ? error.message : "unknown policy error";
|
|
69
|
+
process.stderr.write(message);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|