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.
@@ -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.**