@agentlayer.tech/wallet 0.1.12 → 0.1.14

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.
Files changed (31) hide show
  1. package/.openclaw/AGENTS.md +10 -1
  2. package/.openclaw/extensions/agent-wallet/index.ts +454 -18
  3. package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +96 -0
  4. package/.openclaw/extensions/agent-wallet/skills/wallet-operator/SKILL.md +2 -0
  5. package/.openclaw/extensions/pay-bridge/README.md +32 -0
  6. package/.openclaw/extensions/pay-bridge/core.mjs +287 -0
  7. package/.openclaw/extensions/pay-bridge/index.ts +196 -0
  8. package/.openclaw/extensions/pay-bridge/openclaw.plugin.json +34 -0
  9. package/.openclaw/extensions/pay-bridge/package.json +11 -0
  10. package/.openclaw/extensions/pay-bridge/skills/pay-operator/SKILL.md +20 -0
  11. package/.openclaw/extensions/pay-bridge/smoke_pay_bridge.mjs +38 -0
  12. package/CHANGELOG.md +10 -0
  13. package/README.md +16 -2
  14. package/agent-wallet/.env.example +11 -0
  15. package/agent-wallet/README.md +29 -0
  16. package/agent-wallet/agent_wallet/approval.py +4 -0
  17. package/agent-wallet/agent_wallet/config.py +6 -0
  18. package/agent-wallet/agent_wallet/exceptions.py +2 -1
  19. package/agent-wallet/agent_wallet/openclaw_adapter.py +361 -2
  20. package/agent-wallet/agent_wallet/openclaw_cli.py +13 -1
  21. package/agent-wallet/agent_wallet/openclaw_runtime.py +2 -5
  22. package/agent-wallet/agent_wallet/providers/houdini.py +539 -0
  23. package/agent-wallet/agent_wallet/transaction_policy.py +251 -0
  24. package/agent-wallet/agent_wallet/user_wallets.py +83 -0
  25. package/agent-wallet/agent_wallet/wallet_layer/base.py +40 -0
  26. package/agent-wallet/agent_wallet/wallet_layer/solana.py +885 -16
  27. package/agent-wallet/pyproject.toml +1 -1
  28. package/agent-wallet/scripts/install_agent_wallet.py +54 -2
  29. package/agent-wallet/scripts/install_openclaw_local_config.py +128 -6
  30. package/hermes/plugins/agent_wallet/tools.py +93 -9
  31. package/package.json +2 -1
@@ -3,6 +3,72 @@
3
3
  "name": "Agent Wallet",
4
4
  "description": "Official OpenClaw plugin bridge for the agent-wallet backends, including Solana, local BTC, and local EVM.",
5
5
  "version": "0.1.0",
6
+ "contracts": {
7
+ "tools": [
8
+ "claim_bags_fees",
9
+ "close_empty_token_accounts",
10
+ "continue_solana_private_swap",
11
+ "get_active_wallet_backend",
12
+ "get_bags_claimable_positions",
13
+ "get_bags_fee_analytics",
14
+ "get_btc_fee_rates",
15
+ "get_btc_max_spendable",
16
+ "get_btc_transfer_history",
17
+ "get_evm_aave_account",
18
+ "get_evm_aave_positions",
19
+ "get_evm_aave_reserves",
20
+ "get_evm_fee_rates",
21
+ "get_evm_lido_overview",
22
+ "get_evm_lido_positions",
23
+ "get_evm_lido_withdrawal_requests",
24
+ "get_evm_network",
25
+ "get_evm_swap_quote",
26
+ "get_evm_token_balance",
27
+ "get_evm_token_metadata",
28
+ "get_evm_transaction_receipt",
29
+ "get_jupiter_earn_earnings",
30
+ "get_jupiter_earn_positions",
31
+ "get_jupiter_earn_tokens",
32
+ "get_kamino_lend_market_reserves",
33
+ "get_kamino_lend_markets",
34
+ "get_kamino_lend_user_obligations",
35
+ "get_kamino_lend_user_rewards",
36
+ "get_lifi_quote",
37
+ "get_lifi_supported_chains",
38
+ "get_lifi_transfer_status",
39
+ "get_solana_private_swap_status",
40
+ "get_solana_token_prices",
41
+ "get_wallet_address",
42
+ "get_wallet_balance",
43
+ "get_wallet_capabilities",
44
+ "get_wallet_portfolio",
45
+ "jupiter_earn_deposit",
46
+ "jupiter_earn_withdraw",
47
+ "kamino_lend_borrow",
48
+ "kamino_lend_deposit",
49
+ "kamino_lend_repay",
50
+ "kamino_lend_withdraw",
51
+ "launch_bags_token",
52
+ "list_pending_solana_private_swaps",
53
+ "manage_evm_aave_position",
54
+ "manage_evm_lido_position",
55
+ "manage_evm_lido_withdrawal",
56
+ "request_devnet_airdrop",
57
+ "set_evm_network",
58
+ "set_wallet_backend",
59
+ "sign_wallet_message",
60
+ "swap_evm_lifi_cross_chain_tokens",
61
+ "swap_evm_tokens",
62
+ "swap_solana_lifi_cross_chain_tokens",
63
+ "swap_solana_privately",
64
+ "swap_solana_tokens",
65
+ "transfer_btc",
66
+ "transfer_evm_native",
67
+ "transfer_evm_token",
68
+ "transfer_sol",
69
+ "transfer_spl_token"
70
+ ]
71
+ },
6
72
  "skills": ["skills/wallet-operator"],
7
73
  "configSchema": {
8
74
  "type": "object",
@@ -171,6 +237,36 @@
171
237
  "sensitive": true
172
238
  }
173
239
  },
240
+ "houdiniBaseUrl": {
241
+ "type": "string",
242
+ "description": "Optional Houdini Partner API base URL for private Solana payouts."
243
+ },
244
+ "houdiniApiKey": {
245
+ "type": "string",
246
+ "description": "Optional Houdini Partner API key.",
247
+ "uiHints": {
248
+ "sensitive": true
249
+ }
250
+ },
251
+ "houdiniApiSecret": {
252
+ "type": "string",
253
+ "description": "Optional Houdini Partner API secret.",
254
+ "uiHints": {
255
+ "sensitive": true
256
+ }
257
+ },
258
+ "houdiniUserIp": {
259
+ "type": "string",
260
+ "description": "Required Houdini compliance header: end-user IP address."
261
+ },
262
+ "houdiniUserAgent": {
263
+ "type": "string",
264
+ "description": "Required Houdini compliance header: end-user agent string."
265
+ },
266
+ "houdiniUserTimezone": {
267
+ "type": "string",
268
+ "description": "Required Houdini compliance header: end-user timezone, for example Europe/Moscow."
269
+ },
174
270
  "kaminoBaseUrl": {
175
271
  "type": "string",
176
272
  "description": "Optional Kamino REST API base URL."
@@ -5,11 +5,13 @@ Use wallet tools only when the user explicitly asks for wallet information, sign
5
5
  Safety rules:
6
6
 
7
7
  - Prefer read-only tools first.
8
+ - When a wallet tool exists for the task, do not fall back to `exec`, `solana`, `spl-token`, `bitcoin-cli`, `curl`, or any shell-based wallet workflow. If the wallet tool fails, report the tool error and stop.
8
9
  - Jupiter Portfolio tools are temporarily disabled. Do not suggest or call them until they are re-enabled.
9
10
  - Use Jupiter Earn read tools before Jupiter Earn writes when the user needs lending/yield context.
10
11
  - Use Kamino market/reserve reads before Kamino writes when the user needs lending context.
11
12
  - Use Aave account reads before Aave writes when the user needs EVM lending context.
12
13
  - For transfers, native staking, swaps, Aave writes, Jupiter Earn writes, and Kamino writes, use `preview` before `prepare` or `execute`.
14
+ - For `swap_solana_privately`, use `preview` and then `execute` after explicit user approval. Do not use `prepare` for this tool.
13
15
  - Use `prepare` only when the user clearly intends to produce an execution plan.
14
16
  - Use `execute` only after the host issues an `approval_token` bound to the exact previewed operation.
15
17
  - On `mainnet`, require an approval token that includes explicit mainnet confirmation before any execution.
@@ -0,0 +1,32 @@
1
+ # pay-bridge
2
+
3
+ Thin OpenClaw bridge to the locally installed `pay` CLI.
4
+
5
+ This plugin is intentionally separate from `agent-wallet`:
6
+
7
+ - `agent-wallet` remains the execution wallet stack for Solana/EVM/BTC
8
+ - `pay-bridge` only discovers and calls paid APIs through `pay`
9
+ - the `pay` wallet stays separate from the AgentLayer wallet runtime
10
+
11
+ ## Exposed tools
12
+
13
+ - `pay_status`
14
+ - `pay_wallet_info`
15
+ - `pay_search_services`
16
+ - `pay_get_service_endpoints`
17
+ - `pay_api_request`
18
+
19
+ ## Intended workflow
20
+
21
+ 1. `pay_status`
22
+ 2. `pay_search_services`
23
+ 3. `pay_get_service_endpoints`
24
+ 4. `pay_api_request`
25
+
26
+ `pay_api_request` is deliberately narrow:
27
+
28
+ - it requires a `service_fqn`, `resource`, and `url`
29
+ - it validates the URL against `pay skills endpoints`
30
+ - it requires `purpose` and `user_confirmed=true`
31
+
32
+ This keeps the bridge thin and prevents it from becoming a generic arbitrary paid-curl launcher.
@@ -0,0 +1,287 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+
4
+ const execFileAsync = promisify(execFile);
5
+ const ANSI_RE = /\u001b\[[0-9;]*m/g;
6
+
7
+ function stripAnsi(text) {
8
+ return String(text || "").replace(ANSI_RE, "");
9
+ }
10
+
11
+ function nonEmptyLines(text) {
12
+ return stripAnsi(text)
13
+ .split(/\r?\n/)
14
+ .map((line) => line.trim())
15
+ .filter(Boolean);
16
+ }
17
+
18
+ function extractLastJsonValue(text) {
19
+ const lines = nonEmptyLines(text);
20
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
21
+ const candidate = lines[i];
22
+ if (!candidate.startsWith("{") && !candidate.startsWith("[")) continue;
23
+ try {
24
+ return JSON.parse(candidate);
25
+ } catch {
26
+ continue;
27
+ }
28
+ }
29
+ return null;
30
+ }
31
+
32
+ function collectStringLeaves(value, acc = new Set()) {
33
+ if (typeof value === "string") {
34
+ acc.add(value);
35
+ return acc;
36
+ }
37
+ if (Array.isArray(value)) {
38
+ for (const item of value) collectStringLeaves(item, acc);
39
+ return acc;
40
+ }
41
+ if (value && typeof value === "object") {
42
+ for (const item of Object.values(value)) collectStringLeaves(item, acc);
43
+ }
44
+ return acc;
45
+ }
46
+
47
+ function withAccountArgs(args, account) {
48
+ if (!account) return args;
49
+ return [...args, "--account", account];
50
+ }
51
+
52
+ export function resolvePayBinary(config = {}) {
53
+ return (
54
+ config.payBinary ||
55
+ process.env.OPENCLAW_PAY_BINARY ||
56
+ "pay"
57
+ );
58
+ }
59
+
60
+ export async function runPayCommand(payBinary, args, options = {}) {
61
+ const { cwd, input = null } = options;
62
+ let stdout = "";
63
+ let stderr = "";
64
+ try {
65
+ const result = await execFileAsync(payBinary, args, {
66
+ cwd,
67
+ env: { ...process.env },
68
+ input,
69
+ maxBuffer: 1024 * 1024 * 8,
70
+ });
71
+ stdout = result.stdout ?? "";
72
+ stderr = result.stderr ?? "";
73
+ } catch (error) {
74
+ stdout = typeof error?.stdout === "string" ? error.stdout : "";
75
+ stderr = typeof error?.stderr === "string" ? error.stderr : "";
76
+ const payload = extractLastJsonValue(stdout) || extractLastJsonValue(stderr);
77
+ const message =
78
+ payload?.error?.message ||
79
+ payload?.message ||
80
+ stripAnsi(stderr || stdout || error?.message || "pay command failed").trim() ||
81
+ "pay command failed";
82
+ const wrapped = new Error(message);
83
+ wrapped.stdout = stdout;
84
+ wrapped.stderr = stderr;
85
+ wrapped.details = payload && typeof payload === "object" ? payload : null;
86
+ throw wrapped;
87
+ }
88
+ return { stdout, stderr };
89
+ }
90
+
91
+ export function parseWhoamiOutput(stdout) {
92
+ const lines = nonEmptyLines(stdout);
93
+ const systemUser = lines[0] || null;
94
+ const noAccount = lines.some((line) => /no mainnet account/i.test(line));
95
+ return {
96
+ system_user: systemUser,
97
+ has_mainnet_account: !noAccount,
98
+ raw_lines: lines,
99
+ };
100
+ }
101
+
102
+ export function parseAccountListOutput(stdout) {
103
+ const lines = nonEmptyLines(stdout);
104
+ const noAccounts = lines.some((line) => /no accounts found/i.test(line));
105
+ return {
106
+ has_accounts: !noAccounts,
107
+ raw_lines: lines,
108
+ };
109
+ }
110
+
111
+ export async function getPayStatus(config = {}, options = {}) {
112
+ const payBinary = resolvePayBinary(config);
113
+ const versionResult = await runPayCommand(payBinary, ["--version"], options);
114
+ const whoamiResult = await runPayCommand(
115
+ payBinary,
116
+ withAccountArgs(["whoami"], config.defaultAccount),
117
+ options
118
+ );
119
+ const accountListResult = await runPayCommand(payBinary, ["account", "list"], options);
120
+ return {
121
+ installed: true,
122
+ pay_binary: payBinary,
123
+ version: stripAnsi(versionResult.stdout).trim() || null,
124
+ account_configured: parseWhoamiOutput(whoamiResult.stdout).has_mainnet_account,
125
+ has_any_accounts: parseAccountListOutput(accountListResult.stdout).has_accounts,
126
+ whoami: parseWhoamiOutput(whoamiResult.stdout),
127
+ accounts: parseAccountListOutput(accountListResult.stdout),
128
+ };
129
+ }
130
+
131
+ export async function getPayWalletInfo(config = {}, options = {}) {
132
+ const payBinary = resolvePayBinary(config);
133
+ const whoamiResult = await runPayCommand(
134
+ payBinary,
135
+ withAccountArgs(["whoami"], config.defaultAccount),
136
+ options
137
+ );
138
+ const accountListResult = await runPayCommand(payBinary, ["account", "list"], options);
139
+ return {
140
+ pay_binary: payBinary,
141
+ default_account: config.defaultAccount || null,
142
+ whoami: parseWhoamiOutput(whoamiResult.stdout),
143
+ accounts: parseAccountListOutput(accountListResult.stdout),
144
+ notes: [
145
+ "This wallet is managed by pay.sh and is separate from the AgentLayer execution wallet.",
146
+ ],
147
+ };
148
+ }
149
+
150
+ export async function searchPayServices(config = {}, params = {}, options = {}) {
151
+ const payBinary = resolvePayBinary(config);
152
+ const args = ["skills", "search"];
153
+ if (params.query) args.push(String(params.query));
154
+ if (params.category) args.push("--category", String(params.category));
155
+ args.push("--json");
156
+ const { stdout, stderr } = await runPayCommand(
157
+ payBinary,
158
+ withAccountArgs(args, params.account || config.defaultAccount),
159
+ options
160
+ );
161
+ const parsed = JSON.parse(stdout.trim() || "{}");
162
+ return {
163
+ query: params.query || "",
164
+ category: params.category || null,
165
+ results: parsed,
166
+ warnings: nonEmptyLines(stderr),
167
+ };
168
+ }
169
+
170
+ export async function getPayServiceEndpoints(config = {}, params = {}, options = {}) {
171
+ const payBinary = resolvePayBinary(config);
172
+ const args = [
173
+ "skills",
174
+ "endpoints",
175
+ String(params.service_fqn),
176
+ String(params.resource),
177
+ "--json",
178
+ ];
179
+ const { stdout, stderr } = await runPayCommand(
180
+ payBinary,
181
+ withAccountArgs(args, params.account || config.defaultAccount),
182
+ options
183
+ );
184
+ const parsed = JSON.parse(stdout.trim() || "{}");
185
+ return {
186
+ service_fqn: String(params.service_fqn),
187
+ resource: String(params.resource),
188
+ endpoints: parsed,
189
+ warnings: nonEmptyLines(stderr),
190
+ };
191
+ }
192
+
193
+ function ensureHttps(url, requireHttps = true) {
194
+ if (!requireHttps) return;
195
+ const parsed = new URL(url);
196
+ if (parsed.protocol !== "https:") {
197
+ throw new Error("pay_api_request only allows https URLs.");
198
+ }
199
+ }
200
+
201
+ function appendQuery(url, query) {
202
+ const parsed = new URL(url);
203
+ if (query && typeof query === "object") {
204
+ for (const [key, value] of Object.entries(query)) {
205
+ if (value === undefined || value === null) continue;
206
+ parsed.searchParams.set(key, String(value));
207
+ }
208
+ }
209
+ return parsed.toString();
210
+ }
211
+
212
+ export function endpointPayloadContainsUrl(endpointPayload, url) {
213
+ const strings = collectStringLeaves(endpointPayload);
214
+ return strings.has(url);
215
+ }
216
+
217
+ export async function executePayApiRequest(config = {}, params = {}, options = {}) {
218
+ if (params.user_confirmed !== true) {
219
+ throw new Error("pay_api_request requires user_confirmed=true.");
220
+ }
221
+ if (!params.purpose || !String(params.purpose).trim()) {
222
+ throw new Error("pay_api_request requires a non-empty purpose.");
223
+ }
224
+ if (!params.service_fqn || !params.resource || !params.url) {
225
+ throw new Error("pay_api_request requires service_fqn, resource, and url.");
226
+ }
227
+ if (params.json_body !== undefined && params.text_body !== undefined) {
228
+ throw new Error("Provide either json_body or text_body, not both.");
229
+ }
230
+
231
+ const endpointData = await getPayServiceEndpoints(config, {
232
+ account: params.account,
233
+ resource: params.resource,
234
+ service_fqn: params.service_fqn,
235
+ }, options);
236
+
237
+ const finalUrl = appendQuery(String(params.url), params.query);
238
+ ensureHttps(finalUrl, config.requireHttps !== false);
239
+ if (!endpointPayloadContainsUrl(endpointData.endpoints, String(params.url))) {
240
+ throw new Error("The requested URL is not present in pay_get_service_endpoints for this service/resource.");
241
+ }
242
+
243
+ const payBinary = resolvePayBinary(config);
244
+ const method = String(params.method || "GET").toUpperCase();
245
+ const args = ["curl"];
246
+ if (params.account || config.defaultAccount) {
247
+ args.push("--account", String(params.account || config.defaultAccount));
248
+ }
249
+ args.push("--request", method);
250
+
251
+ const headers = params.headers && typeof params.headers === "object" ? params.headers : {};
252
+ for (const [key, value] of Object.entries(headers)) {
253
+ args.push("--header", `${key}: ${String(value)}`);
254
+ }
255
+
256
+ if (params.json_body !== undefined) {
257
+ const hasContentType = Object.keys(headers).some((key) => key.toLowerCase() === "content-type");
258
+ if (!hasContentType) {
259
+ args.push("--header", "content-type: application/json");
260
+ }
261
+ args.push("--data", JSON.stringify(params.json_body));
262
+ } else if (params.text_body !== undefined) {
263
+ args.push("--data", String(params.text_body));
264
+ }
265
+
266
+ args.push(finalUrl);
267
+ const { stdout, stderr } = await runPayCommand(payBinary, args, options);
268
+ const trimmed = stdout.trim();
269
+ let responseBody = trimmed;
270
+ if (params.parse_json_response !== false) {
271
+ try {
272
+ responseBody = JSON.parse(trimmed);
273
+ } catch {
274
+ responseBody = trimmed;
275
+ }
276
+ }
277
+ return {
278
+ method,
279
+ purpose: String(params.purpose),
280
+ request_url: finalUrl,
281
+ service_fqn: String(params.service_fqn),
282
+ resource: String(params.resource),
283
+ response: responseBody,
284
+ raw_response_text: trimmed,
285
+ warnings: nonEmptyLines(stderr),
286
+ };
287
+ }
@@ -0,0 +1,196 @@
1
+ import {
2
+ executePayApiRequest,
3
+ getPayServiceEndpoints,
4
+ getPayStatus,
5
+ getPayWalletInfo,
6
+ searchPayServices,
7
+ } from "./core.mjs";
8
+
9
+ const PLUGIN_ID = "pay-bridge";
10
+
11
+ function asContent(data) {
12
+ return {
13
+ content: [
14
+ {
15
+ type: "text",
16
+ text: JSON.stringify(data, null, 2),
17
+ },
18
+ ],
19
+ };
20
+ }
21
+
22
+ function resolvePluginConfig(api) {
23
+ const globalConfig = api?.config ?? {};
24
+ const pluginEntry = globalConfig?.plugins?.entries?.[PLUGIN_ID];
25
+ return pluginEntry?.config ?? globalConfig?.config ?? {};
26
+ }
27
+
28
+ function registerTool(api, definition) {
29
+ api.registerTool({
30
+ name: definition.name,
31
+ description: definition.description,
32
+ parameters: definition.parameters,
33
+ returns: {
34
+ type: "object",
35
+ additionalProperties: true,
36
+ },
37
+ async execute(_id, params = {}) {
38
+ const config = resolvePluginConfig(api);
39
+ let result;
40
+ if (definition.name === "pay_status") {
41
+ result = await getPayStatus(config, { cwd: process.cwd() });
42
+ } else if (definition.name === "pay_wallet_info") {
43
+ result = await getPayWalletInfo(config, { cwd: process.cwd() });
44
+ } else if (definition.name === "pay_search_services") {
45
+ result = await searchPayServices(config, params, { cwd: process.cwd() });
46
+ } else if (definition.name === "pay_get_service_endpoints") {
47
+ result = await getPayServiceEndpoints(config, params, { cwd: process.cwd() });
48
+ } else if (definition.name === "pay_api_request") {
49
+ result = await executePayApiRequest(config, params, { cwd: process.cwd() });
50
+ } else {
51
+ throw new Error(`Unsupported pay-bridge tool: ${definition.name}`);
52
+ }
53
+ return asContent(result);
54
+ },
55
+ });
56
+ }
57
+
58
+ const toolDefinitions = [
59
+ {
60
+ name: "pay_status",
61
+ description:
62
+ "Check whether the local pay.sh CLI is installed and whether a pay wallet/account is configured. Use this before any paid API workflow.",
63
+ parameters: {
64
+ type: "object",
65
+ properties: {},
66
+ additionalProperties: false,
67
+ },
68
+ },
69
+ {
70
+ name: "pay_wallet_info",
71
+ description:
72
+ "Show pay wallet/account status. This wallet is separate from the AgentLayer execution wallet and is only for pay.sh API payments.",
73
+ parameters: {
74
+ type: "object",
75
+ properties: {},
76
+ additionalProperties: false,
77
+ },
78
+ },
79
+ {
80
+ name: "pay_search_services",
81
+ description:
82
+ "Search the pay.sh skills catalog for paid APIs. Prefer this instead of guessing URLs manually.",
83
+ parameters: {
84
+ type: "object",
85
+ properties: {
86
+ query: {
87
+ type: "string",
88
+ description: "Search text for service names, descriptions, or endpoint paths.",
89
+ },
90
+ category: {
91
+ type: "string",
92
+ description: "Optional category filter such as ai_ml, maps, data, compute, search, crypto_finance.",
93
+ },
94
+ account: {
95
+ type: "string",
96
+ description: "Optional pay account override.",
97
+ },
98
+ },
99
+ additionalProperties: false,
100
+ },
101
+ },
102
+ {
103
+ name: "pay_get_service_endpoints",
104
+ description:
105
+ "List the discoverable endpoints for a pay.sh service/resource pair and return their gateway URLs. Use the returned URL with pay_api_request.",
106
+ parameters: {
107
+ type: "object",
108
+ properties: {
109
+ service_fqn: {
110
+ type: "string",
111
+ description: "Fully qualified pay service name, for example solana-foundation/google/language.",
112
+ },
113
+ resource: {
114
+ type: "string",
115
+ description: "Resource name inside the service, for example entities or jobs.",
116
+ },
117
+ account: {
118
+ type: "string",
119
+ description: "Optional pay account override.",
120
+ },
121
+ },
122
+ required: ["service_fqn", "resource"],
123
+ additionalProperties: false,
124
+ },
125
+ },
126
+ {
127
+ name: "pay_api_request",
128
+ description:
129
+ "Call a paid API through the local pay.sh CLI using a URL returned by pay_get_service_endpoints. Requires explicit user_confirmed=true and keeps the pay wallet separate from AgentLayer execution wallets.",
130
+ optional: true,
131
+ parameters: {
132
+ type: "object",
133
+ properties: {
134
+ service_fqn: {
135
+ type: "string",
136
+ description: "Fully qualified pay service name used to validate the request URL.",
137
+ },
138
+ resource: {
139
+ type: "string",
140
+ description: "Resource name used to validate the request URL.",
141
+ },
142
+ url: {
143
+ type: "string",
144
+ description: "Exact HTTPS gateway URL returned by pay_get_service_endpoints.",
145
+ },
146
+ method: {
147
+ type: "string",
148
+ description: "HTTP method such as GET or POST.",
149
+ },
150
+ headers: {
151
+ type: "object",
152
+ additionalProperties: { type: "string" },
153
+ description: "Optional HTTP headers.",
154
+ },
155
+ query: {
156
+ type: "object",
157
+ additionalProperties: true,
158
+ description: "Optional query parameters to append to the URL.",
159
+ },
160
+ json_body: {
161
+ description: "Optional JSON request body.",
162
+ },
163
+ text_body: {
164
+ type: "string",
165
+ description: "Optional raw text request body. Do not provide with json_body.",
166
+ },
167
+ account: {
168
+ type: "string",
169
+ description: "Optional pay account override.",
170
+ },
171
+ parse_json_response: {
172
+ type: "boolean",
173
+ description: "If true, attempt to parse the response body as JSON.",
174
+ },
175
+ purpose: {
176
+ type: "string",
177
+ description: "Short user-facing reason for this paid API call.",
178
+ },
179
+ user_confirmed: {
180
+ type: "boolean",
181
+ description: "Must be true for paid API requests.",
182
+ },
183
+ },
184
+ required: ["service_fqn", "resource", "url", "method", "purpose", "user_confirmed"],
185
+ additionalProperties: false,
186
+ },
187
+ },
188
+ ];
189
+
190
+ export default function registerPayBridgePlugin(api) {
191
+ api?.logger?.info?.("[pay-bridge] registering pay.sh OpenClaw plugin");
192
+ for (const definition of toolDefinitions) {
193
+ registerTool(api, definition);
194
+ }
195
+ api?.logger?.info?.(`[pay-bridge] registered ${toolDefinitions.length} pay tools`);
196
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "id": "pay-bridge",
3
+ "name": "pay.sh Bridge",
4
+ "description": "Thin OpenClaw bridge to the local pay.sh CLI for paid API discovery and execution.",
5
+ "version": "0.1.0",
6
+ "contracts": {
7
+ "tools": [
8
+ "pay_status",
9
+ "pay_wallet_info",
10
+ "pay_search_services",
11
+ "pay_get_service_endpoints",
12
+ "pay_api_request"
13
+ ]
14
+ },
15
+ "skills": ["skills/pay-operator"],
16
+ "configSchema": {
17
+ "type": "object",
18
+ "additionalProperties": false,
19
+ "properties": {
20
+ "payBinary": {
21
+ "type": "string",
22
+ "description": "Absolute path or executable name for the local pay.sh binary."
23
+ },
24
+ "defaultAccount": {
25
+ "type": "string",
26
+ "description": "Optional pay account name to use when a tool call does not specify one."
27
+ },
28
+ "requireHttps": {
29
+ "type": "boolean",
30
+ "description": "If true, pay_api_request refuses non-HTTPS URLs."
31
+ }
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "pay-bridge",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "openclaw": {
7
+ "extensions": [
8
+ "./index.ts"
9
+ ]
10
+ }
11
+ }