@hypawave/mcp 0.2.0 → 0.3.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/CHANGELOG.md +8 -0
- package/README.md +4 -2
- package/dist/index.js +114 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
5
|
|
|
6
|
+
## [0.3.0] - 2026-07-22
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **Operator funding options** — `setup_wallet {action:"funding_options", amount_sats?}` returns operator-facing funding instructions to present verbatim: an exact-amount Lightning invoice (payable from Cash App, Coinbase, or any Lightning wallet) or the wallet's Lightning address, plus an on-chain deposit address for exchanges without Lightning (e.g. Robinhood; 300-sat Coinos dust minimum surfaced). Hosted-wallet creation now returns the same funding block, and insufficient-balance NWC payment errors point the agent at the action.
|
|
11
|
+
- **Per-wallet NWC guidance** — `setup_wallet {action:"connect_own"}` without `nwc_url` now returns wallet-specific steps (Alby Hub, Coinos, Primal, LNbits, self-hosted node) for finding the connection string, instead of erroring; operator option 2 tells the agent to ask which wallet the operator uses.
|
|
12
|
+
- Coinos registration JWT (no expiry) is persisted in `~/.hypawave/wallet.json` (`token`) to mint funding invoices and on-chain addresses later; new `createFundingInvoice()` / `getOnchainAddress()` client helpers (`POST /invoice`, types `lightning` / `bitcoin`).
|
|
13
|
+
|
|
6
14
|
## [0.2.0] - 2026-07-06
|
|
7
15
|
|
|
8
16
|
### Added
|
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ All env vars are optional — with no `NWC_URL` the server runs in manual mode (
|
|
|
67
67
|
| `list_sales` | List your settled sales (payments/invoices) — reconcile missed webhooks |
|
|
68
68
|
| **Utility** | |
|
|
69
69
|
| `wallet_status` | Wallet balance, seller pubkey, spending cap, live platform fees/limits |
|
|
70
|
-
| `setup_wallet` | One-time wallet setup: create a hosted Coinos wallet (with operator consent) or connect your own NWC string |
|
|
70
|
+
| `setup_wallet` | One-time wallet setup: create a hosted Coinos wallet (with operator consent) or connect your own NWC wallet (with per-wallet steps to find the string); also serves operator funding options (Lightning + on-chain) |
|
|
71
71
|
|
|
72
72
|
## Buy in three calls
|
|
73
73
|
|
|
@@ -101,7 +101,9 @@ Selling needs **no special wallet** — payouts go straight to your Lightning Ad
|
|
|
101
101
|
|
|
102
102
|
Paying requires a wallet that returns the settlement **preimage**. Connect any **NWC-capable** wallet (Coinos, Alby Hub, Primal, LNbits, …) via `NWC_URL` — the NWC spec guarantees `pay_invoice` returns the preimage, so any NWC wallet works.
|
|
103
103
|
|
|
104
|
-
**No wallet yet? `setup_wallet`.** With explicit operator consent it registers a fresh hosted wallet at coinos.io (custodial — keep only small amounts) and saves the credentials to `~/.hypawave/wallet.json` (0600, local only; Hypawave's servers never receive them — **back this file up: it holds the only copy**).
|
|
104
|
+
**No wallet yet? `setup_wallet`.** With explicit operator consent it registers a fresh hosted wallet at coinos.io (custodial — keep only small amounts) and saves the credentials to `~/.hypawave/wallet.json` (0600, local only; Hypawave's servers never receive them — **back this file up: it holds the only copy**). Or `{action:"connect_own"}` connects a wallet you already use — called without an NWC string it returns per-wallet steps (Alby Hub, Coinos, Primal, LNbits, self-hosted node) for finding it. `NWC_URL`, when set, always wins over the wallet file.
|
|
105
|
+
|
|
106
|
+
**Funding the wallet (the human's only job).** `setup_wallet {action:"funding_options", amount_sats?}` returns operator-facing instructions the agent presents verbatim, with two paths: **instant** — an exact-amount Lightning invoice (payable from Cash App, Coinbase, or any Lightning wallet) or the wallet's Lightning address; **on-chain** — a deposit address for exchanges without Lightning support (e.g. Robinhood; ~10–60 min, mining fees, 300-sat minimum — best for larger top-ups). Low-balance payment failures point the agent at this action automatically. No bitcoin at all? Any of those apps sells it.
|
|
105
107
|
|
|
106
108
|
**No wallet configured? Manual mode.** `buy_offer` / `pay_invoice` return the bolt11; pay it with any preimage-returning wallet and submit the preimage via `confirm_payment` (or re-call `pay_invoice` with it).
|
|
107
109
|
|
package/dist/index.js
CHANGED
|
@@ -364,7 +364,18 @@ async function getClient() {
|
|
|
364
364
|
}
|
|
365
365
|
async function payBolt11(bolt11) {
|
|
366
366
|
const client = await getClient();
|
|
367
|
-
|
|
367
|
+
let result;
|
|
368
|
+
try {
|
|
369
|
+
result = await client.payInvoice({ invoice: bolt11 });
|
|
370
|
+
} catch (e) {
|
|
371
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
372
|
+
if (/insufficient|balance|not enough|exceeds.*budget|quota/i.test(msg)) {
|
|
373
|
+
throw new Error(
|
|
374
|
+
`${msg} \u2014 the agent wallet balance is too low. Call setup_wallet {action:"funding_options", amount_sats:<needed>} and present the funding options to your operator.`
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
throw e;
|
|
378
|
+
}
|
|
368
379
|
if (!result?.preimage || !/^[0-9a-fA-F]{64}$/.test(result.preimage)) {
|
|
369
380
|
throw new Error(
|
|
370
381
|
"wallet paid (or attempted) but returned no valid preimage \u2014 cannot prove settlement; check the payment in your wallet before retrying"
|
|
@@ -876,19 +887,93 @@ async function createCoinosWallet() {
|
|
|
876
887
|
provider: "coinos",
|
|
877
888
|
username,
|
|
878
889
|
password,
|
|
890
|
+
token,
|
|
879
891
|
nwc_url: nwc,
|
|
880
892
|
lightning_address: `${username}@${new URL(COINOS_API).host}`,
|
|
881
893
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
882
894
|
};
|
|
883
895
|
}
|
|
896
|
+
async function createFundingInvoice(token, amountSats) {
|
|
897
|
+
const inv = await coinos("/invoice", {
|
|
898
|
+
token,
|
|
899
|
+
body: { invoice: { type: "lightning", amount: Math.floor(amountSats) } }
|
|
900
|
+
});
|
|
901
|
+
if (!inv?.hash || !inv.hash.toLowerCase().startsWith("ln")) {
|
|
902
|
+
throw new Error("coinos returned no bolt11 for the funding invoice");
|
|
903
|
+
}
|
|
904
|
+
return inv.hash;
|
|
905
|
+
}
|
|
906
|
+
async function getOnchainAddress(token) {
|
|
907
|
+
const inv = await coinos("/invoice", {
|
|
908
|
+
token,
|
|
909
|
+
body: { invoice: { type: "bitcoin", amount: 0 } }
|
|
910
|
+
});
|
|
911
|
+
if (!inv?.hash || !/^(bc1|[13])[a-zA-Z0-9]{20,}$/.test(inv.hash)) {
|
|
912
|
+
throw new Error("coinos returned no on-chain deposit address");
|
|
913
|
+
}
|
|
914
|
+
return inv.hash;
|
|
915
|
+
}
|
|
916
|
+
async function getFundingOptions(amountSats) {
|
|
917
|
+
const wallet = readWalletFile();
|
|
918
|
+
if (!wallet || wallet.provider !== "coinos" || !wallet.token) {
|
|
919
|
+
return {
|
|
920
|
+
present_to_operator: [
|
|
921
|
+
"Fund the agent's Lightning wallet using its own receive/deposit flow (open the wallet app and create a receive invoice or address).",
|
|
922
|
+
"Instant options once you have an invoice: pay it from Cash App, Coinbase, or any Lightning wallet.",
|
|
923
|
+
wallet?.lightning_address ? `Or send sats to the wallet's Lightning address: ${wallet.lightning_address}` : ""
|
|
924
|
+
].filter(Boolean).join("\n"),
|
|
925
|
+
...wallet?.lightning_address ? { lightning_address: wallet.lightning_address } : {}
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
const result = { present_to_operator: "" };
|
|
929
|
+
const lines = [
|
|
930
|
+
amountSats ? `The agent wallet needs a top-up (~${amountSats} sats). Two ways to fund it:` : "Two ways to fund the agent wallet:"
|
|
931
|
+
];
|
|
932
|
+
if (amountSats && amountSats >= 1) {
|
|
933
|
+
try {
|
|
934
|
+
result.lightning_invoice = await createFundingInvoice(wallet.token, amountSats);
|
|
935
|
+
} catch {
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
lines.push(
|
|
939
|
+
result.lightning_invoice ? `1. INSTANT \u2014 pay this Lightning invoice from Cash App, Coinbase, or any Lightning wallet (settles in seconds):
|
|
940
|
+
${result.lightning_invoice}` : `1. INSTANT \u2014 send sats from any Lightning wallet to the agent's Lightning address: ${wallet.lightning_address}
|
|
941
|
+
(If your app needs an exact-amount invoice \u2014 Cash App, Coinbase \u2014 ask the agent for one: setup_wallet {action:'funding_options', amount_sats:N}.)`
|
|
942
|
+
);
|
|
943
|
+
result.lightning_address = wallet.lightning_address;
|
|
944
|
+
try {
|
|
945
|
+
result.onchain_address = await getOnchainAddress(wallet.token);
|
|
946
|
+
lines.push(
|
|
947
|
+
`2. FROM AN EXCHANGE WITHOUT LIGHTNING (e.g. Robinhood) \u2014 send BTC on-chain to:
|
|
948
|
+
${result.onchain_address}
|
|
949
|
+
Arrives after confirmation (~10\u201360 min). Minimum 300 sats; mining fees apply, so best for larger top-ups.`
|
|
950
|
+
);
|
|
951
|
+
} catch {
|
|
952
|
+
lines.push(
|
|
953
|
+
"2. FROM AN EXCHANGE WITHOUT LIGHTNING (e.g. Robinhood) \u2014 an on-chain deposit address could not be fetched right now; log in at coinos.io to get one, or use the Lightning option."
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
lines.push("The agent will detect the funds and continue automatically.");
|
|
957
|
+
result.present_to_operator = lines.join("\n");
|
|
958
|
+
return result;
|
|
959
|
+
}
|
|
884
960
|
|
|
885
961
|
// src/tools/setup-wallet.ts
|
|
886
962
|
var OPERATOR_OPTIONS = [
|
|
887
963
|
"To buy things automatically, your agent needs a Lightning wallet it can spend from. Three options:",
|
|
888
964
|
"1. CREATE ONE NOW (recommended for getting started) \u2014 creates a hosted wallet at coinos.io (think prepaid card: funds are held by that service, so keep only small amounts, e.g. $10\u201320 worth of sats; the operator spending cap applies to every payment). Credentials are generated and stored only on this machine \u2014 Hypawave's servers never receive them.",
|
|
889
|
-
"2. CONNECT YOUR OWN WALLET \u2014 if you already use an NWC-capable wallet (Alby Hub, Primal, LNbits, your own node),
|
|
965
|
+
"2. CONNECT YOUR OWN WALLET \u2014 if you already use an NWC-capable wallet (Alby Hub, Coinos, Primal, LNbits, your own node), tell the agent which one and it will walk you through copying its NWC connection string; no account is created anywhere. Note: self-hosted nodes need channel liquidity even for tiny payments.",
|
|
890
966
|
"3. SKIP \u2014 purchases will return a Lightning invoice to pay manually from any wallet (you'll also need the preimage from your wallet to confirm \u2014 fine for testing, clunky as a routine)."
|
|
891
967
|
].join("\n");
|
|
968
|
+
var NWC_WALLET_GUIDE = [
|
|
969
|
+
"Ask the operator which wallet they use, then give them the matching steps to copy its NWC connection string (starts with nostr+walletconnect://):",
|
|
970
|
+
"- Alby Hub: App Store (or Settings) \u2192 Connections \u2192 Add connection \u2192 set a budget \u2192 copy the connection secret.",
|
|
971
|
+
"- Coinos (existing account): coinos.io \u2192 Settings \u2192 Nostr Wallet Connect (Apps) \u2192 create/copy the connection string.",
|
|
972
|
+
"- Primal: Wallet \u2192 Settings \u2192 Connected apps \u2192 New connection \u2192 copy the string.",
|
|
973
|
+
"- LNbits: enable the NWC Service extension on your wallet \u2192 create a connection \u2192 copy the pairing URL.",
|
|
974
|
+
"- Self-hosted node (LND/CLN): run an NWC bridge (e.g. Alby Hub connected to the node) and create a connection there. Reminder: the node needs outbound channel liquidity.",
|
|
975
|
+
"Then re-call setup_wallet with {action:'connect_own', nwc_url:'<the string>'}."
|
|
976
|
+
].join("\n");
|
|
892
977
|
var NWC_URI_RE = /^nostr\+walletconnect:\/\/[0-9a-f]{64}\?/i;
|
|
893
978
|
async function tryBalance() {
|
|
894
979
|
try {
|
|
@@ -906,15 +991,26 @@ function registerSetupWalletTools(server2) {
|
|
|
906
991
|
"setup_wallet",
|
|
907
992
|
{
|
|
908
993
|
title: "Set up the agent's Lightning wallet",
|
|
909
|
-
description: "One-time wallet setup so purchases can pay automatically. Call with no arguments first: it returns the operator-facing options \u2014 present them to the operator verbatim and let them choose; do not choose for them. Then call {action:'create_hosted', confirm:true} ONLY after the operator explicitly agreed (creates a hosted custodial wallet at coinos.io; credentials are stored locally in ~/.hypawave/wallet.json and never sent to Hypawave), or {action:'connect_own'
|
|
994
|
+
description: "One-time wallet setup so purchases can pay automatically. Call with no arguments first: it returns the operator-facing options \u2014 present them to the operator verbatim and let them choose; do not choose for them. Then call {action:'create_hosted', confirm:true} ONLY after the operator explicitly agreed (creates a hosted custodial wallet at coinos.io; credentials are stored locally in ~/.hypawave/wallet.json and never sent to Hypawave), or {action:'connect_own'} to use an existing NWC wallet \u2014 without nwc_url it returns per-wallet steps to help the operator find their connection string. Also: {action:'funding_options', amount_sats?} returns operator-facing funding instructions for the configured wallet (instant Lightning invoice/address + on-chain deposit address for exchanges without Lightning, e.g. Robinhood) \u2014 present them verbatim whenever the wallet needs sats. The NWC_URL env var, when set, always takes precedence over anything configured here.",
|
|
910
995
|
inputSchema: {
|
|
911
|
-
action: z5.enum(["create_hosted", "connect_own"]).optional().describe("Omit to get the options to present to the operator"),
|
|
996
|
+
action: z5.enum(["create_hosted", "connect_own", "funding_options"]).optional().describe("Omit to get the options to present to the operator"),
|
|
912
997
|
confirm: z5.boolean().optional().describe("Required true for create_hosted \u2014 set only after the operator explicitly agreed"),
|
|
913
|
-
nwc_url: z5.string().optional().describe("For connect_own: the wallet's NWC connection string")
|
|
998
|
+
nwc_url: z5.string().optional().describe("For connect_own: the wallet's NWC connection string"),
|
|
999
|
+
amount_sats: z5.number().int().positive().optional().describe("For funding_options: mint an exact-amount Lightning funding invoice for this many sats")
|
|
914
1000
|
}
|
|
915
1001
|
},
|
|
916
|
-
async ({ action, confirm, nwc_url }) => {
|
|
1002
|
+
async ({ action, confirm, nwc_url, amount_sats }) => {
|
|
917
1003
|
const envSet = Boolean(process.env.NWC_URL || process.env.HYPAWAVE_NWC_URL);
|
|
1004
|
+
if (action === "funding_options") {
|
|
1005
|
+
if (!getNwcUrl()) {
|
|
1006
|
+
return jsonResult({
|
|
1007
|
+
ok: false,
|
|
1008
|
+
message: "No wallet configured yet \u2014 run setup_wallet first, then request funding options."
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
const funding2 = await getFundingOptions(amount_sats);
|
|
1012
|
+
return jsonResult({ ok: true, ...funding2 });
|
|
1013
|
+
}
|
|
918
1014
|
if (!action) {
|
|
919
1015
|
const configured = Boolean(getNwcUrl());
|
|
920
1016
|
return jsonResult({
|
|
@@ -930,9 +1026,16 @@ function registerSetupWalletTools(server2) {
|
|
|
930
1026
|
});
|
|
931
1027
|
}
|
|
932
1028
|
if (action === "connect_own") {
|
|
933
|
-
if (!nwc_url
|
|
1029
|
+
if (!nwc_url) {
|
|
1030
|
+
return jsonResult({
|
|
1031
|
+
ok: false,
|
|
1032
|
+
needs_nwc_url: true,
|
|
1033
|
+
present_to_operator: NWC_WALLET_GUIDE
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
if (!NWC_URI_RE.test(nwc_url) || !/[?&]secret=[0-9a-f]{64}/i.test(nwc_url)) {
|
|
934
1037
|
throw new Error(
|
|
935
|
-
"connect_own requires a valid NWC connection string: nostr+walletconnect://<64-hex-pubkey>?relay=\u2026&secret=<64-hex
|
|
1038
|
+
"connect_own requires a valid NWC connection string: nostr+walletconnect://<64-hex-pubkey>?relay=\u2026&secret=<64-hex>. Call {action:'connect_own'} without nwc_url for per-wallet steps to find it."
|
|
936
1039
|
);
|
|
937
1040
|
}
|
|
938
1041
|
if (walletFileExists()) {
|
|
@@ -976,12 +1079,14 @@ function registerSetupWalletTools(server2) {
|
|
|
976
1079
|
}
|
|
977
1080
|
const wallet = await createCoinosWallet();
|
|
978
1081
|
const path = saveWalletFile(wallet);
|
|
1082
|
+
const funding = await getFundingOptions().catch(() => null);
|
|
979
1083
|
return jsonResult({
|
|
980
1084
|
ok: true,
|
|
981
1085
|
provider: "coinos",
|
|
982
1086
|
lightning_address: wallet.lightning_address,
|
|
983
1087
|
credentials_file: path,
|
|
984
|
-
|
|
1088
|
+
...funding ? { funding } : {},
|
|
1089
|
+
next: `Fund it now (start small \u2014 5,000\u201350,000 sats): present the funding options above to the operator verbatim \u2014 instant via Lightning (Cash App, Coinbase, or any Lightning wallet), or on-chain from an exchange without Lightning (e.g. Robinhood; slower, mining fees). Then wallet_status shows the balance and purchases pay automatically under the spending cap.`,
|
|
985
1090
|
important: `Custodial wallet \u2014 coinos.io holds the funds; keep only small amounts. ${path} contains the ONLY copy of the credentials: back it up, and never delete it while the wallet holds funds.`
|
|
986
1091
|
});
|
|
987
1092
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypawave/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"mcpName": "io.github.hypawave/mcp",
|
|
5
5
|
"description": "MCP server for Hypawave — agents buy, sell, and discover over Bitcoin Lightning: search the public offer marketplace, list offers in it or sell agent-to-agent, and settle non-custodially wallet-to-wallet. Verified preimage unlocks files, data, APIs, and compute.",
|
|
6
6
|
"type": "module",
|