wip-agent-pay 1.0.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/.license-guard.json +7 -0
- package/CASH.md +75 -0
- package/CHANGELOG.md +18 -0
- package/CLA.md +17 -0
- package/DEPLOY-WORKER.md +161 -0
- package/LICENSE +21 -0
- package/PARTNERS-402.md +75 -0
- package/PARTNERS-STRIPE.md +69 -0
- package/README.md +80 -0
- package/REFERENCE.md +160 -0
- package/SETUP.md +242 -0
- package/SKILL.md +43 -0
- package/SPEC.md +156 -0
- package/WALLET.md +89 -0
- package/cli.js +265 -0
- package/detect.mjs +13 -0
- package/examples/minimal/README.md +17 -0
- package/install.js +6 -0
- package/lib/cdp-auth.js +131 -0
- package/mcp-server.mjs +105 -0
- package/openclaw.mjs +88 -0
- package/openclaw.plugin.json +17 -0
- package/package.json +43 -0
- package/providers/coinbase.js +55 -0
- package/providers/index.js +155 -0
- package/providers/passthrough.js +139 -0
- package/providers/privy.js +50 -0
- package/providers/stripe.js +54 -0
- package/providers/x402.js +50 -0
- package/skills/agent-pay/SKILL.md +30 -0
- package/worker/index.js +1440 -0
- package/worker/wrangler.toml +25 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// providers/coinbase.js
|
|
2
|
+
// v1 Coinbase provider (isolated portfolio only)
|
|
3
|
+
//
|
|
4
|
+
// Worker is live. Coinbase transfer is still TODO (v1.1).
|
|
5
|
+
// Right now: mints real one-time URLs on the Worker, but no actual
|
|
6
|
+
// funds move yet. When Coinbase API is wired, Step 2 sends USDC
|
|
7
|
+
// before minting the URL.
|
|
8
|
+
|
|
9
|
+
import { execSync } from 'node:child_process';
|
|
10
|
+
|
|
11
|
+
const WORKER_URL = 'https://pay-wip-computer.wipcomputer.workers.dev';
|
|
12
|
+
|
|
13
|
+
export default async function authorize(amount, service, note = '') {
|
|
14
|
+
console.log(`\n wip-agent-pay ... authorizing`);
|
|
15
|
+
console.log(` Amount: ${amount}`);
|
|
16
|
+
console.log(` Service: ${service}`);
|
|
17
|
+
if (note) console.log(` Note: ${note}`);
|
|
18
|
+
|
|
19
|
+
// --- Step 1: Pull Worker secret from 1Password ---
|
|
20
|
+
let workerSecret;
|
|
21
|
+
try {
|
|
22
|
+
workerSecret = execSync(
|
|
23
|
+
'OP_SERVICE_ACCOUNT_TOKEN=$(cat ~/.openclaw/secrets/op-sa-token) ' +
|
|
24
|
+
'op item get "wip-agent-pay-worker-secret" --vault "Agent Secrets" --fields label=credential --reveal',
|
|
25
|
+
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
26
|
+
).trim();
|
|
27
|
+
} catch (err) {
|
|
28
|
+
return { success: false, error: '1Password: could not retrieve worker secret' };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// --- Step 2: Send funds via Coinbase Advanced Trade API ---
|
|
32
|
+
// TODO (v1.1): Wire real Coinbase transfer
|
|
33
|
+
// When wired, this step will:
|
|
34
|
+
// 1. Pull API key + secret from 1Password (wip-agent-pay-coinbase)
|
|
35
|
+
// 2. Send USDC from isolated "wip-agent-pay" portfolio
|
|
36
|
+
// 3. Only proceed to Step 3 if transfer succeeds
|
|
37
|
+
|
|
38
|
+
// --- Step 3: Mint one-time URL via Worker ---
|
|
39
|
+
const res = await fetch(`${WORKER_URL}/create`, {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: {
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
'Authorization': `Bearer ${workerSecret}`
|
|
44
|
+
},
|
|
45
|
+
body: JSON.stringify({ amount, service, note, expiresMin: 3 })
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
const body = await res.text().catch(() => '');
|
|
50
|
+
return { success: false, error: `Worker returned ${res.status}: ${body}` };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { url } = await res.json();
|
|
54
|
+
return { success: true, provider: 'coinbase', amount, service, note, url };
|
|
55
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// providers/index.js
|
|
2
|
+
// Provider router. Picks the right provider based on command + flags.
|
|
3
|
+
//
|
|
4
|
+
// Default (no flag): Pool Mode A. Apple Pay per transaction. Parker's float.
|
|
5
|
+
// --wallet=cdp: Path 1. Sign from user's CDP wallet.
|
|
6
|
+
// --wallet=privy: Path 1. Sign from user's Privy wallet.
|
|
7
|
+
|
|
8
|
+
import { execSync } from 'node:child_process';
|
|
9
|
+
import mintAuthorize from './coinbase.js';
|
|
10
|
+
|
|
11
|
+
const WORKER_URL = 'https://pay-wip-computer.wipcomputer.workers.dev';
|
|
12
|
+
|
|
13
|
+
// Lazy imports ... only load what's needed
|
|
14
|
+
async function getX402() { return (await import('./x402.js')).default; }
|
|
15
|
+
async function getStripe() { return (await import('./stripe.js')).default; }
|
|
16
|
+
async function getPrivy() { return (await import('./privy.js')).default; }
|
|
17
|
+
async function getPool() { return await import('./passthrough.js'); }
|
|
18
|
+
|
|
19
|
+
function getWorkerSecret() {
|
|
20
|
+
return execSync(
|
|
21
|
+
'OP_SERVICE_ACCOUNT_TOKEN=$(cat ~/.openclaw/secrets/op-sa-token) ' +
|
|
22
|
+
'op item get "wip-agent-pay-worker-secret" --vault "Agent Secrets" --fields label=credential --reveal',
|
|
23
|
+
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
24
|
+
).trim();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Pay for a paywalled URL.
|
|
29
|
+
*
|
|
30
|
+
* Default (Pool Mode A): Apple Pay per transaction. Parker's float covers x402.
|
|
31
|
+
* --wallet=cdp (Path 1): Sign with user's CDP wallet.
|
|
32
|
+
* --wallet=privy (Path 1): Sign with user's Privy wallet.
|
|
33
|
+
*/
|
|
34
|
+
export async function pay(url, opts = {}) {
|
|
35
|
+
// Path 1: self-custody wallet
|
|
36
|
+
if (opts.wallet === 'cdp') {
|
|
37
|
+
const x402Pay = await getX402();
|
|
38
|
+
return x402Pay(url);
|
|
39
|
+
}
|
|
40
|
+
if (opts.wallet === 'privy') {
|
|
41
|
+
const privyPay = await getPrivy();
|
|
42
|
+
return privyPay(url);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Pool Mode A (default): Apple Pay + pool wallet
|
|
46
|
+
const pool = await getPool();
|
|
47
|
+
return pool.default(url);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Fund wallet via Stripe (money in).
|
|
52
|
+
* Opens Apple Pay checkout, deposits to agent wallet.
|
|
53
|
+
*/
|
|
54
|
+
export async function fund(amount, opts = {}) {
|
|
55
|
+
const stripeFund = await getStripe();
|
|
56
|
+
return stripeFund(amount, opts);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Mint a one-time URL (existing Mode B flow).
|
|
61
|
+
*/
|
|
62
|
+
export async function mint(amount, service, note = '') {
|
|
63
|
+
return mintAuthorize(amount, service, note);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check wallet balance.
|
|
68
|
+
*/
|
|
69
|
+
export async function balance(opts = {}) {
|
|
70
|
+
let workerSecret;
|
|
71
|
+
try { workerSecret = getWorkerSecret(); } catch {
|
|
72
|
+
return { error: '1Password: could not retrieve worker secret' };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const wallet = opts.wallet || 'cdp';
|
|
76
|
+
const res = await fetch(`${WORKER_URL}/balance`, {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers: {
|
|
79
|
+
'Content-Type': 'application/json',
|
|
80
|
+
'Authorization': `Bearer ${workerSecret}`,
|
|
81
|
+
},
|
|
82
|
+
body: JSON.stringify({ wallet }),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (!res.ok) {
|
|
86
|
+
const body = await res.text().catch(() => '');
|
|
87
|
+
return { error: `Worker returned ${res.status}: ${body}` };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return res.json();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get transaction history.
|
|
95
|
+
*/
|
|
96
|
+
export async function history(opts = {}) {
|
|
97
|
+
let workerSecret;
|
|
98
|
+
try { workerSecret = getWorkerSecret(); } catch {
|
|
99
|
+
return { error: '1Password: could not retrieve worker secret' };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const wallet = opts.wallet || 'cdp';
|
|
103
|
+
const limit = opts.limit || 20;
|
|
104
|
+
const res = await fetch(`${WORKER_URL}/history`, {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: {
|
|
107
|
+
'Content-Type': 'application/json',
|
|
108
|
+
'Authorization': `Bearer ${workerSecret}`,
|
|
109
|
+
},
|
|
110
|
+
body: JSON.stringify({ wallet, limit }),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (!res.ok) {
|
|
114
|
+
const body = await res.text().catch(() => '');
|
|
115
|
+
return { error: `Worker returned ${res.status}: ${body}` };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return res.json();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get or set budget (spending limits).
|
|
123
|
+
*/
|
|
124
|
+
export async function budget(opts = {}) {
|
|
125
|
+
let workerSecret;
|
|
126
|
+
try { workerSecret = getWorkerSecret(); } catch {
|
|
127
|
+
return { error: '1Password: could not retrieve worker secret' };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const wallet = opts.wallet || 'cdp';
|
|
131
|
+
const res = await fetch(`${WORKER_URL}/budget`, {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: {
|
|
134
|
+
'Content-Type': 'application/json',
|
|
135
|
+
'Authorization': `Bearer ${workerSecret}`,
|
|
136
|
+
},
|
|
137
|
+
body: JSON.stringify({
|
|
138
|
+
wallet,
|
|
139
|
+
// If setting a budget
|
|
140
|
+
...(opts.daily !== undefined && { daily: opts.daily }),
|
|
141
|
+
...(opts.perTx !== undefined && { perTx: opts.perTx }),
|
|
142
|
+
...(opts.total !== undefined && { total: opts.total }),
|
|
143
|
+
}),
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (!res.ok) {
|
|
147
|
+
const body = await res.text().catch(() => '');
|
|
148
|
+
return { error: `Worker returned ${res.status}: ${body}` };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return res.json();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Default export for backwards compatibility (mint flow)
|
|
155
|
+
export default mintAuthorize;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// providers/passthrough.js
|
|
2
|
+
// Pool Mode (Mode A): User pays via Apple Pay (Stripe Checkout).
|
|
3
|
+
// Worker signs x402 from Parker's pool wallet. Content returned.
|
|
4
|
+
//
|
|
5
|
+
// Pricing: x402 price + Stripe fees + $0.25 flat fee.
|
|
6
|
+
// Max pool transaction: $25. Over $25 redirects to Mode C (user's own wallet).
|
|
7
|
+
|
|
8
|
+
import { execSync } from 'node:child_process';
|
|
9
|
+
|
|
10
|
+
const WORKER_URL = 'https://pay-wip-computer.wipcomputer.workers.dev';
|
|
11
|
+
|
|
12
|
+
function getWorkerSecret() {
|
|
13
|
+
return execSync(
|
|
14
|
+
'OP_SERVICE_ACCOUNT_TOKEN=$(cat ~/.openclaw/secrets/op-sa-token) ' +
|
|
15
|
+
'op item get "wip-agent-pay-worker-secret" --vault "Agent Secrets" --fields label=credential --reveal',
|
|
16
|
+
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
17
|
+
).trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Pay for a paywalled URL via Pool Mode.
|
|
22
|
+
* Worker fetches 402, calculates total (x402 + Stripe fees + $0.25),
|
|
23
|
+
* creates Stripe Checkout. Returns checkout URL for Apple Pay.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} url - The paywalled URL
|
|
26
|
+
* @returns {{ success, checkoutUrl, paymentId, amount, pricing, service, error }}
|
|
27
|
+
*/
|
|
28
|
+
export default async function pool(url) {
|
|
29
|
+
console.log(`\n wip-pay ... checking price`);
|
|
30
|
+
console.log(` URL: ${url}`);
|
|
31
|
+
|
|
32
|
+
let workerSecret;
|
|
33
|
+
try { workerSecret = getWorkerSecret(); } catch {
|
|
34
|
+
return { success: false, error: '1Password: could not retrieve worker secret' };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Hit the URL via Worker to get 402 + create Stripe Checkout
|
|
38
|
+
const res = await fetch(`${WORKER_URL}/pool/pay`, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
'Authorization': `Bearer ${workerSecret}`,
|
|
43
|
+
},
|
|
44
|
+
body: JSON.stringify({ url }),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
const body = await res.text().catch(() => '');
|
|
49
|
+
try {
|
|
50
|
+
const data = JSON.parse(body);
|
|
51
|
+
// Over pool limit ... redirect to Mode C
|
|
52
|
+
if (data.status === 'over-pool-limit') {
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
overPoolLimit: true,
|
|
56
|
+
amount: data.amount,
|
|
57
|
+
service: data.service,
|
|
58
|
+
poolMax: data.poolMax,
|
|
59
|
+
error: data.error,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
} catch { /* not JSON */ }
|
|
63
|
+
return { success: false, error: `Worker returned ${res.status}: ${body}` };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const data = await res.json();
|
|
67
|
+
|
|
68
|
+
// No paywall ... content came back free
|
|
69
|
+
if (data.status === 'no-paywall') {
|
|
70
|
+
return { success: true, content: data.content, amount: 0, free: true };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 402 found ... checkout URL ready
|
|
74
|
+
return {
|
|
75
|
+
success: true,
|
|
76
|
+
needsPayment: true,
|
|
77
|
+
checkoutUrl: data.checkoutUrl,
|
|
78
|
+
paymentId: data.paymentId,
|
|
79
|
+
amount: data.amount,
|
|
80
|
+
pricing: data.pricing,
|
|
81
|
+
service: data.service,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check if a pool payment has been confirmed.
|
|
87
|
+
* Call this after user completes Apple Pay checkout.
|
|
88
|
+
* Worker checks Stripe, signs x402 from pool wallet, returns content.
|
|
89
|
+
*
|
|
90
|
+
* @param {string} paymentId - The payment ID from pool()
|
|
91
|
+
* @returns {{ success, content, amount, service, error }}
|
|
92
|
+
*/
|
|
93
|
+
export async function confirm(paymentId) {
|
|
94
|
+
let workerSecret;
|
|
95
|
+
try { workerSecret = getWorkerSecret(); } catch {
|
|
96
|
+
return { success: false, error: '1Password: could not retrieve worker secret' };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const res = await fetch(`${WORKER_URL}/pool/confirm`, {
|
|
100
|
+
method: 'POST',
|
|
101
|
+
headers: {
|
|
102
|
+
'Content-Type': 'application/json',
|
|
103
|
+
'Authorization': `Bearer ${workerSecret}`,
|
|
104
|
+
},
|
|
105
|
+
body: JSON.stringify({ paymentId }),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (!res.ok) {
|
|
109
|
+
const body = await res.text().catch(() => '');
|
|
110
|
+
return { success: false, error: `Worker returned ${res.status}: ${body}` };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return res.json();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Poll for payment confirmation with timeout.
|
|
118
|
+
* Stripe Checkout -> webhook -> Worker signs x402 -> content returned.
|
|
119
|
+
*
|
|
120
|
+
* @param {string} paymentId
|
|
121
|
+
* @param {number} timeoutMs - Max wait time (default: 120s)
|
|
122
|
+
* @param {number} intervalMs - Poll interval (default: 2s)
|
|
123
|
+
*/
|
|
124
|
+
export async function waitForPayment(paymentId, timeoutMs = 120000, intervalMs = 2000) {
|
|
125
|
+
const deadline = Date.now() + timeoutMs;
|
|
126
|
+
|
|
127
|
+
while (Date.now() < deadline) {
|
|
128
|
+
const result = await confirm(paymentId);
|
|
129
|
+
if (result.success && result.status === 'paid') {
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
if (result.error && result.error !== 'pending') {
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
await new Promise(r => setTimeout(r, intervalMs));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return { success: false, error: 'Payment timed out. User may not have completed checkout.' };
|
|
139
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// providers/privy.js
|
|
2
|
+
// Privy embedded wallet provider. Server-side wallet with spend policies.
|
|
3
|
+
// Worker handles x402 negotiation and Privy RPC signing.
|
|
4
|
+
//
|
|
5
|
+
// Like CDP, but no Coinbase. Privy holds the keys. Broad chain support.
|
|
6
|
+
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
|
|
9
|
+
const WORKER_URL = 'https://pay-wip-computer.wipcomputer.workers.dev';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Pay for a paywalled URL via x402 using Privy wallet.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} url - The paywalled URL
|
|
15
|
+
* @returns {{ success, content, amount, service, txHash, error }}
|
|
16
|
+
*/
|
|
17
|
+
export default async function pay(url) {
|
|
18
|
+
console.log(`\n wip-agent-pay ... paying via x402 (Privy)`);
|
|
19
|
+
console.log(` URL: ${url}`);
|
|
20
|
+
|
|
21
|
+
// Pull Worker secret from 1Password
|
|
22
|
+
let workerSecret;
|
|
23
|
+
try {
|
|
24
|
+
workerSecret = execSync(
|
|
25
|
+
'OP_SERVICE_ACCOUNT_TOKEN=$(cat ~/.openclaw/secrets/op-sa-token) ' +
|
|
26
|
+
'op item get "wip-agent-pay-worker-secret" --vault "Agent Secrets" --fields label=credential --reveal',
|
|
27
|
+
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
28
|
+
).trim();
|
|
29
|
+
} catch {
|
|
30
|
+
return { success: false, error: '1Password: could not retrieve worker secret' };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Call Worker /privy/pay
|
|
34
|
+
const res = await fetch(`${WORKER_URL}/privy/pay`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
'Authorization': `Bearer ${workerSecret}`,
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify({ url }),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
const body = await res.text().catch(() => '');
|
|
45
|
+
return { success: false, error: `Worker returned ${res.status}: ${body}` };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const result = await res.json();
|
|
49
|
+
return { success: true, ...result };
|
|
50
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// providers/stripe.js
|
|
2
|
+
// Stripe funding provider. Opens Apple Pay checkout to fund agent wallet.
|
|
3
|
+
// Worker creates Stripe Checkout session. User taps Face ID. Done.
|
|
4
|
+
//
|
|
5
|
+
// The user never sees crypto. Stripe handles fiat. Worker handles the rest.
|
|
6
|
+
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
|
|
9
|
+
const WORKER_URL = 'https://pay-wip-computer.wipcomputer.workers.dev';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Fund agent wallet via Apple Pay / Stripe Checkout.
|
|
13
|
+
*
|
|
14
|
+
* @param {number} amount - USD amount to fund
|
|
15
|
+
* @param {object} opts - { wallet: 'cdp'|'privy' }
|
|
16
|
+
* @returns {{ success, checkoutUrl, fundId, error }}
|
|
17
|
+
*/
|
|
18
|
+
export default async function fund(amount, opts = {}) {
|
|
19
|
+
const wallet = opts.wallet || 'cdp';
|
|
20
|
+
|
|
21
|
+
console.log(`\n wip-agent-pay ... funding wallet`);
|
|
22
|
+
console.log(` Amount: $${amount}`);
|
|
23
|
+
console.log(` Wallet: ${wallet}`);
|
|
24
|
+
|
|
25
|
+
// Pull Worker secret from 1Password
|
|
26
|
+
let workerSecret;
|
|
27
|
+
try {
|
|
28
|
+
workerSecret = execSync(
|
|
29
|
+
'OP_SERVICE_ACCOUNT_TOKEN=$(cat ~/.openclaw/secrets/op-sa-token) ' +
|
|
30
|
+
'op item get "wip-agent-pay-worker-secret" --vault "Agent Secrets" --fields label=credential --reveal',
|
|
31
|
+
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
32
|
+
).trim();
|
|
33
|
+
} catch {
|
|
34
|
+
return { success: false, error: '1Password: could not retrieve worker secret' };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Call Worker /stripe/checkout
|
|
38
|
+
const res = await fetch(`${WORKER_URL}/stripe/checkout`, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
'Authorization': `Bearer ${workerSecret}`,
|
|
43
|
+
},
|
|
44
|
+
body: JSON.stringify({ amount, wallet }),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
const body = await res.text().catch(() => '');
|
|
49
|
+
return { success: false, error: `Worker returned ${res.status}: ${body}` };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { checkoutUrl, fundId } = await res.json();
|
|
53
|
+
return { success: true, checkoutUrl, fundId, amount, wallet };
|
|
54
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// providers/x402.js
|
|
2
|
+
// x402 payment provider. Agent hits a paywalled URL, Worker handles
|
|
3
|
+
// the 402 negotiation, CDP signing, and retry. Returns content.
|
|
4
|
+
//
|
|
5
|
+
// The agent never sees crypto. The Worker does everything.
|
|
6
|
+
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
|
|
9
|
+
const WORKER_URL = 'https://pay-wip-computer.wipcomputer.workers.dev';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Pay for a paywalled URL via x402.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} url - The paywalled URL
|
|
15
|
+
* @returns {{ success, content, amount, service, txHash, error }}
|
|
16
|
+
*/
|
|
17
|
+
export default async function pay(url) {
|
|
18
|
+
console.log(`\n wip-agent-pay ... paying via x402`);
|
|
19
|
+
console.log(` URL: ${url}`);
|
|
20
|
+
|
|
21
|
+
// Pull Worker secret from 1Password
|
|
22
|
+
let workerSecret;
|
|
23
|
+
try {
|
|
24
|
+
workerSecret = execSync(
|
|
25
|
+
'OP_SERVICE_ACCOUNT_TOKEN=$(cat ~/.openclaw/secrets/op-sa-token) ' +
|
|
26
|
+
'op item get "wip-agent-pay-worker-secret" --vault "Agent Secrets" --fields label=credential --reveal',
|
|
27
|
+
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
28
|
+
).trim();
|
|
29
|
+
} catch {
|
|
30
|
+
return { success: false, error: '1Password: could not retrieve worker secret' };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Call Worker /x402/pay ... Worker handles 402 negotiation, CDP signing, retry
|
|
34
|
+
const res = await fetch(`${WORKER_URL}/x402/pay`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
'Authorization': `Bearer ${workerSecret}`,
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify({ url }),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
const body = await res.text().catch(() => '');
|
|
45
|
+
return { success: false, error: `Worker returned ${res.status}: ${body}` };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const result = await res.json();
|
|
49
|
+
return { success: true, ...result };
|
|
50
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wip-agent-pay
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: Disposable wallet for agents. x402 protocol + Coinbase isolated portfolio + one-time self-destructing URLs .
|
|
5
|
+
tags: [payment, x402, coinbase, agent, 1password, universal-interface, cloudflare, disposable-wallet]
|
|
6
|
+
score: 10
|
|
7
|
+
install: npm install -g wip-agent-pay
|
|
8
|
+
run: wip-agent-pay 0.10 morning-stew "MS-#8"
|
|
9
|
+
metadata:
|
|
10
|
+
category: finance
|
|
11
|
+
capabilities:
|
|
12
|
+
- micropayment
|
|
13
|
+
- disposable-wallet
|
|
14
|
+
- one-time-url
|
|
15
|
+
interface: CLI + Module + MCP + OpenClaw Plugin + Skill
|
|
16
|
+
requires:
|
|
17
|
+
binaries: [node]
|
|
18
|
+
openclaw:
|
|
19
|
+
emoji: "💳"
|
|
20
|
+
install:
|
|
21
|
+
env: []
|
|
22
|
+
author:
|
|
23
|
+
name: Parker Todd Brooks
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
# wip-agent-pay
|
|
27
|
+
|
|
28
|
+
Give your agent a disposable wallet. Powered by x402, Coinbase, and one-time self-destructing URLs.
|
|
29
|
+
|
|
30
|
+
**Agent says "authorize $0.10" ... skill pulls creds from 1Password ... sends USDC from isolated portfolio ... returns a one-time URL ... agent uses it once ... URL dies.**
|