openzoo 0.50.27 → 0.50.28
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/lib/moonpayOnramp.js +80 -0
- package/lib/proxy.js +1 -1
- package/lib/stripeOnramp.js +24 -4
- package/package.json +1 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MoonPay-hosted fiat→USDC onramp for the x402 402 path.
|
|
3
|
+
*
|
|
4
|
+
* Unlike Stripe, there is no session POST: we build
|
|
5
|
+
* https://buy.moonpay.com/?apiKey=pk_…¤cyCode=usdc_sol&walletAddress=…
|
|
6
|
+
* and HMAC-SHA256 sign the query string with the secret. The dest is locked
|
|
7
|
+
* because walletAddress + currencyCode + signature are required together —
|
|
8
|
+
* the widget does not prompt for another address.
|
|
9
|
+
*
|
|
10
|
+
* Keys are NEVER in the repo: MOONPAY_PUBLISHABLE_KEY + MOONPAY_SECRET_KEY,
|
|
11
|
+
* else ~/moonpay.json `{publishableKey,secretKey}`, else ~/moonpay.pk +
|
|
12
|
+
* ~/moonpay.key. No keys → caller keeps the send-to-address copy.
|
|
13
|
+
*
|
|
14
|
+
* Do NOT set allowedIpAddress. Live IP matching would bind the URL to the
|
|
15
|
+
* shim/gateway IP; the 402 is opened on the user's machine (or phone).
|
|
16
|
+
*/
|
|
17
|
+
import crypto from 'node:crypto';
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import os from 'node:os';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
|
|
22
|
+
const HOME = os.homedir();
|
|
23
|
+
|
|
24
|
+
function readTrim(p) {
|
|
25
|
+
try { return fs.readFileSync(p, 'utf8').trim(); } catch { return ''; }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function moonpayKeys() {
|
|
29
|
+
const jsonPath = process.env.MOONPAY_KEY_FILE || path.join(HOME, 'moonpay.json');
|
|
30
|
+
let filePk = '';
|
|
31
|
+
let fileSk = '';
|
|
32
|
+
try {
|
|
33
|
+
const j = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
|
34
|
+
filePk = String(j.publishableKey || j.pk || j.apiKey || '').trim();
|
|
35
|
+
fileSk = String(j.secretKey || j.sk || j.secret || '').trim();
|
|
36
|
+
} catch { /* no json */ }
|
|
37
|
+
const pk = String(process.env.MOONPAY_PUBLISHABLE_KEY || process.env.MOONPAY_API_KEY || '').trim()
|
|
38
|
+
|| filePk
|
|
39
|
+
|| readTrim(process.env.MOONPAY_PK_FILE || path.join(HOME, 'moonpay.pk'));
|
|
40
|
+
const sk = String(process.env.MOONPAY_SECRET_KEY || '').trim()
|
|
41
|
+
|| fileSk
|
|
42
|
+
|| readTrim(process.env.MOONPAY_SECRET_FILE || path.join(HOME, 'moonpay.key'));
|
|
43
|
+
if (!pk || !sk) return null;
|
|
44
|
+
return { pk, sk };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function dollars(usd) {
|
|
48
|
+
const n = Number(usd);
|
|
49
|
+
// MoonPay USDC min is typically ~$20–30, not Stripe's $5.
|
|
50
|
+
if (!Number.isFinite(n) || n <= 0) return 30;
|
|
51
|
+
return Math.max(30, Math.ceil(n));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** HMAC-SHA256 of `url.search` (includes leading `?`), then URL-encode. */
|
|
55
|
+
export function signMoonPayUrl(unsignedUrl, secret) {
|
|
56
|
+
const u = new URL(unsignedUrl);
|
|
57
|
+
const signature = crypto.createHmac('sha256', secret).update(u.search).digest('base64');
|
|
58
|
+
return `${unsignedUrl}${u.search ? '&' : '?'}signature=${encodeURIComponent(signature)}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Signed widget URL that buys USDC on Solana into `solana`. Sync — no network.
|
|
63
|
+
* Returns null if keys are missing.
|
|
64
|
+
*/
|
|
65
|
+
export function moonpayUsdcOnrampLink({ solana, usd } = {}) {
|
|
66
|
+
const addr = String(solana || '').trim();
|
|
67
|
+
if (!addr) return null;
|
|
68
|
+
const keys = moonpayKeys();
|
|
69
|
+
if (!keys) return null;
|
|
70
|
+
const live = keys.pk.startsWith('pk_live_');
|
|
71
|
+
const host = live ? 'https://buy.moonpay.com' : 'https://buy-sandbox.moonpay.com';
|
|
72
|
+
const params = new URLSearchParams();
|
|
73
|
+
params.set('apiKey', keys.pk);
|
|
74
|
+
params.set('currencyCode', 'usdc_sol');
|
|
75
|
+
params.set('walletAddress', addr);
|
|
76
|
+
params.set('baseCurrencyCode', 'usd');
|
|
77
|
+
params.set('baseCurrencyAmount', String(dollars(usd)));
|
|
78
|
+
const unsigned = `${host}/?${params.toString()}`;
|
|
79
|
+
return signMoonPayUrl(unsigned, keys.sk);
|
|
80
|
+
}
|
package/lib/proxy.js
CHANGED
|
@@ -773,7 +773,7 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
|
|
|
773
773
|
+ `or USDC to ${client.evmAddress} on Base. Check with: openzoo balance`,
|
|
774
774
|
{ solana: client.address, usd },
|
|
775
775
|
);
|
|
776
|
-
log(
|
|
776
|
+
log(/ties to your account/i.test(msg) ? 'onramp: whop + copy-paste solana' : 'onramp: no fund blurb');
|
|
777
777
|
jsonErr(res, 402, msg);
|
|
778
778
|
return;
|
|
779
779
|
}
|
package/lib/stripeOnramp.js
CHANGED
|
@@ -12,6 +12,7 @@ import fs from 'node:fs';
|
|
|
12
12
|
import os from 'node:os';
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
|
|
15
|
+
|
|
15
16
|
const STRIPE_VERSION = '2026-06-24.dahlia';
|
|
16
17
|
const TTL_MS = 10 * 60 * 1000;
|
|
17
18
|
const cache = new Map(); // key -> { url, at }
|
|
@@ -83,10 +84,29 @@ export async function stripeUsdcOnrampLink({ solana, usd } = {}) {
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
const DEFAULT_WHOP_CHECKOUT = 'https://whop.com/staccoverflow/openzoo';
|
|
88
|
+
|
|
89
|
+
/** Whop product / checkout. OPENZOO_WHOP_CHECKOUT overrides the default product URL. */
|
|
90
|
+
export function whopBuyUrl() {
|
|
91
|
+
return String(process.env.OPENZOO_WHOP_CHECKOUT || process.env.OPENZOO_WHOP_URL || DEFAULT_WHOP_CHECKOUT).trim();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function whopFundBlurb(solana) {
|
|
95
|
+
const addr = String(solana || '').trim();
|
|
96
|
+
if (!addr) return '';
|
|
97
|
+
const url = whopBuyUrl();
|
|
98
|
+
return [
|
|
99
|
+
`Hey — buy this: ${url}`,
|
|
100
|
+
'',
|
|
101
|
+
'Copy-paste THIS Solana address into "what is your Solana address?" so it ties to your account:',
|
|
102
|
+
addr,
|
|
103
|
+
].join('\n');
|
|
104
|
+
}
|
|
105
|
+
|
|
86
106
|
export async function withOnrampLink(text, dest) {
|
|
87
|
-
const url = await stripeUsdcOnrampLink(dest);
|
|
88
107
|
const body = String(text || '').trim();
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
|
|
108
|
+
const blurb = whopFundBlurb(dest?.solana);
|
|
109
|
+
if (!blurb) return body;
|
|
110
|
+
if (/ties to your account/i.test(body) && body.includes(String(dest.solana))) return body;
|
|
111
|
+
return `${blurb}\n\n${body}`;
|
|
92
112
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.50.
|
|
3
|
+
"version": "0.50.28",
|
|
4
4
|
"description": "Local x402-paying proxy + MCP server for openzoo.fun — point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana and Base rails live; Robinhood experimental.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|