@hopivia/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/README.md +78 -0
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -1
- package/dist/confirm.d.ts +8 -0
- package/dist/confirm.js +34 -0
- package/dist/confirm.js.map +1 -0
- package/dist/http.d.ts +5 -0
- package/dist/http.js +26 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/keychain.d.ts +5 -0
- package/dist/keychain.js +29 -0
- package/dist/keychain.js.map +1 -0
- package/dist/messages.d.ts +46 -0
- package/dist/messages.js +82 -0
- package/dist/messages.js.map +1 -0
- package/dist/pinned.d.ts +6 -0
- package/dist/pinned.js +23 -0
- package/dist/pinned.js.map +1 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.js +393 -49
- package/dist/server.js.map +1 -1
- package/dist/setup.d.ts +11 -0
- package/dist/setup.js +176 -0
- package/dist/setup.js.map +1 -0
- package/dist/signer.d.ts +72 -0
- package/dist/signer.js +83 -0
- package/dist/signer.js.map +1 -0
- package/dist/transferLink.d.ts +29 -0
- package/dist/transferLink.js +116 -0
- package/dist/transferLink.js.map +1 -0
- package/dist/verify.d.ts +81 -0
- package/dist/verify.js +164 -0
- package/dist/verify.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/wallets.d.ts +47 -0
- package/dist/wallets.js +172 -0
- package/dist/wallets.js.map +1 -0
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -43,6 +43,7 @@ Claude Desktop, Cursor, Windsurf — add to the client's MCP config (`claude_des
|
|
|
43
43
|
| `get_balances` | native + token balances of an address |
|
|
44
44
|
| `get_delegation` | delegated access: tokens, daily caps, what is left today, expiry and allowance an owner granted the agent on the Hopivia delegation contract (BNB Chain) |
|
|
45
45
|
| `api_key_info` | tier, limit and usage of the configured key |
|
|
46
|
+
| `list_wallets`, `send_tokens`, `swap_tokens` | sign and send from local agent wallets — see below |
|
|
46
47
|
|
|
47
48
|
Resource `hopivia://docs` — the full API guide as Markdown (same as <https://hopivia.com/docs.md>).
|
|
48
49
|
|
|
@@ -52,6 +53,79 @@ Recipients: `build_swap_transactions`, `get_bridge_quote` and `build_bridge_tran
|
|
|
52
53
|
|
|
53
54
|
Refusals of the API (HTTP 422: `simulation_reverted`, `price_impact_too_high`, `token_risk_high`, `insufficient_balance`, `delegation_not_allowed`, `delegation_cap_exceeded`, `delegation_expired`, `delegation_token_out_not_allowed`, `delegation_allowance_low`) come back as tool errors with the API's `error` and `message`, so the agent can explain and fix the request.
|
|
54
55
|
|
|
56
|
+
## Local wallets: transfers and swaps the agent sends itself
|
|
57
|
+
|
|
58
|
+
Besides building unsigned transactions, the server can sign with **a separate wallet you fund for the agent** ("wallet B"): put a few dollars and some gas on it, give it a name, and ask in plain language: *"send 100 USDT from work to mom on BSC"*.
|
|
59
|
+
|
|
60
|
+
Easiest: run the setup and answer the questions — it creates the wallet, the limits and contacts, writes the file with owner-only permissions and connects Claude Desktop:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npx -y @hopivia/mcp setup
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Or create `~/.hopivia/wallets.json` (or point `HOPIVIA_WALLETS_FILE` elsewhere) by hand and make it readable only by you (`chmod 600`):
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"wallets": { "work": "0x<private key of the agent wallet>" },
|
|
71
|
+
"contacts": { "mom": "0x…", "exchange": "0x…" },
|
|
72
|
+
"limits": { "perTransferUsd": 100, "dailyUsd": 200 },
|
|
73
|
+
"confirm": "new",
|
|
74
|
+
"language": "en",
|
|
75
|
+
"defaultChain": "bsc"
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
On macOS `setup` can keep the key in the **Keychain** instead of the file (`"work": { "address": "0x…", "keychain": true }`): the item trusts no application, so every transfer shows the macOS prompt and nothing reads the key silently. The key is loaded only when the wallet signs.
|
|
80
|
+
|
|
81
|
+
| Tool | What it does |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `list_wallets` | wallet names and addresses (never keys), contacts, limits, USD spent in the last 24 h; balances with `chain` |
|
|
84
|
+
| `send_tokens` | sends a token from a named wallet to a contact name, the name of another of your wallets (*"send 50 USDT from 1 to a"*) or a 0x address, signs and waits for confirmation on-chain |
|
|
85
|
+
| `send_tokens_batch` | pays up to 50 recipients from one wallet (e.g. a list from a file): the whole list is validated first (addresses, duplicates, balance with fees, limits), one approval window shows every row, then payments go out one by one and stop at the first failure |
|
|
86
|
+
| `swap_tokens` | swaps inside a named wallet at the best price; the output stays in the wallet (e.g. to get the chain coin for gas) |
|
|
87
|
+
|
|
88
|
+
Rules the server enforces, whatever the model is told:
|
|
89
|
+
|
|
90
|
+
- **Fees included or on top.** By default the recipient gets exactly the amount and the fees come on top. Say "including fees" and the wallet spends exactly the amount; the recipient gets it minus the fees. Every result and confirmation states the total fees (network + service) in USD.
|
|
91
|
+
- **New addresses need you.** Your contacts and your own wallets count as known (a contact wins when a contact and a wallet share a name). A recipient that is not one of them is confirmed by you directly: the MCP client shows a form (MCP elicitation) or, on macOS, a system dialog. Decline, or no way to ask, and nothing is sent. `"confirm"`: `"always"` asks for every transfer, `"new"` (default) only for addresses outside the contacts, `"never"` lets the agent act alone within the limits (a token without a USD price is still confirmed). The window shows the wallet, the recipient, what the recipient gets, what leaves the wallet and the total fees, in `"language"` (`ru` or `en`).
|
|
92
|
+
- **Limits.** Per transfer and per rolling 24 hours, in USD, checked before signing.
|
|
93
|
+
- **Fee.** Every transfer pays a visible Hopivia fee of **$0.01**. On chains with the Hopivia transfer contract the amount and the fee go in **one transaction**: the wallet signs a Permit2 permit that names the recipient, the amount and a fee ceiling, and the contract sends both. The first transfer of each token adds a one-time approval to Permit2. Elsewhere the fee is a second, separate transfer. Swaps pay the usual 0.15 % aggregator fee.
|
|
94
|
+
- **The API is not trusted with your funds.** Before signing, the server decodes every transaction the API prepared and checks the recipient, the amount (decimals read from the chain), the fee wallet, a fee ceiling of 2 % and the contract addresses pinned in this package. Anything else is refused with `plan_rejected` and nothing is signed. Swaps must go through the pinned Hopivia aggregator and pay out to the same wallet.
|
|
95
|
+
- **No gas, no transfer.** If the wallet lacks the chain coin, the tool says exactly how much to send. Swapping a token into the coin also needs gas, so it only helps while some is left.
|
|
96
|
+
- **Keys stay local.** They are read from the file, used to sign, and never printed, sent to the API or returned by a tool.
|
|
97
|
+
|
|
98
|
+
What can still go wrong: anyone or anything that can read the file (malware on the machine) can use the wallet, so keep on it only what you are ready to lose; and a new address you confirm without checking is still sent.
|
|
99
|
+
|
|
100
|
+
## Claude on the phone (remote connector)
|
|
101
|
+
|
|
102
|
+
The Claude mobile app cannot start a local MCP server; it uses connectors that run on a server. Hopivia hosts one:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
https://hopivia.com/api/v1/mcp
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Streamable HTTP, stateless (every POST is answered with plain JSON; GET and DELETE return 405), no sign-in. Add it once on claude.ai in a browser: **Settings → Connectors → Add custom connector**, name `Hopivia`, the URL above. The connector then shows up in the Claude apps on the phone and the desktop under the same account. Claude Code can use it too: `claude mcp add --transport http hopivia https://hopivia.com/api/v1/mcp`.
|
|
109
|
+
|
|
110
|
+
No keys ever reach this server, so it has no wallet tools. Tools in remote mode: `list_chains`, `list_tokens`, `get_quote`, `get_bridge_quote`, `get_bridge_status`, `check_token`, `get_balances`, `get_delegation` and `transfer_link`. The unsigned-transaction builders (`build_swap_transactions`, `build_bridge_transactions`) are left out: a chat on the phone has nothing to sign them with.
|
|
111
|
+
|
|
112
|
+
How a transfer works:
|
|
113
|
+
|
|
114
|
+
1. The user writes *"send 50 USDT to mom on BSC, her address is 0x…"*.
|
|
115
|
+
2. Claude calls `transfer_link` with the network, the token, the address the user typed, the amount and, if the user said so, "including fees". The tool checks the address (not zero, not the token contract, a mixed-case address must match its checksum), the network, the token and the amount, and asks the API for the fees: `GET /api/v1/transfer/estimate`.
|
|
116
|
+
3. Claude answers with a link and what will happen, in the user's language:
|
|
117
|
+
```
|
|
118
|
+
Ссылка для перевода: https://hopivia.com/pay?chain=bsc&token=USDT&to=0x…&amount=50&fees=ontop&lang=ru&label=%D0%9C%D0%B0%D0%BC%D0%B0
|
|
119
|
+
Получатель получит: 50 USDT
|
|
120
|
+
С кошелька уйдёт: 50.01 USDT
|
|
121
|
+
Комиссия: ≈ $0.015
|
|
122
|
+
```
|
|
123
|
+
4. The user opens the link on hopivia.com, connects their own wallet (MetaMask, Trust Wallet, WalletConnect), checks the recipient, the amount and the fees, and signs. Nothing moves before that signature; Claude cannot see whether the user signed.
|
|
124
|
+
|
|
125
|
+
The link: `<site>/pay?chain=<bsc|ethereum|polygon|arbitrum|base|optimism|avalanche>&token=<symbol or 0x address>&to=<checksummed 0x address>&amount=<decimal>&fees=<ontop|included>&lang=<ru|en>[&label=<recipient name, at most 40 characters>]`. The fee figure is an estimate without the sender's wallet (typical gas for the network + the $0.01 service fee); the page shows the exact one. Lists of recipients (a batch link) are not supported yet.
|
|
126
|
+
|
|
127
|
+
Self-hosting: the endpoint is part of the API (`services/api`, route `/api/v1/mcp`); it runs `createServer({ mode: 'remote' })` in process and calls the API's own handlers. Its rate limit is per address or key, `RATE_MCP` requests per minute (default 300), since connector traffic arrives from the client provider's servers.
|
|
128
|
+
|
|
55
129
|
## Environment
|
|
56
130
|
|
|
57
131
|
| Variable | Default | Meaning |
|
|
@@ -59,6 +133,8 @@ Refusals of the API (HTTP 422: `simulation_reverted`, `price_impact_too_high`, `
|
|
|
59
133
|
| `HOPIVIA_API_KEY` | — | `X-API-Key` for higher limits |
|
|
60
134
|
| `HOPIVIA_API_URL` | `https://hopivia.com/api/v1` | API base (point it at a local API for development) |
|
|
61
135
|
| `HOPIVIA_SITE_URL` | `https://hopivia.com` | site base for docs links and the docs resource |
|
|
136
|
+
| `HOPIVIA_WALLETS_FILE` | `~/.hopivia/wallets.json` | local wallets, contacts and limits |
|
|
137
|
+
| `RPC_<chainId>` | public nodes | RPC used to send from local wallets, e.g. `RPC_56` |
|
|
62
138
|
|
|
63
139
|
## Programmatic use
|
|
64
140
|
|
|
@@ -70,6 +146,8 @@ const server = createServer({ apiKey: process.env.HOPIVIA_API_KEY });
|
|
|
70
146
|
await server.connect(new StdioServerTransport());
|
|
71
147
|
```
|
|
72
148
|
|
|
149
|
+
Remote (HTTP) mode: `handleRemoteRequest(request, { baseUrl, siteUrl, parsedBody })` takes a web `Request` and returns a `Response`; it builds a fresh `createServer({ mode: 'remote' })` per request.
|
|
150
|
+
|
|
73
151
|
## Development
|
|
74
152
|
|
|
75
153
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,11 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
6
6
|
import { createServer } from './server.js';
|
|
7
7
|
import { VERSION } from './version.js';
|
|
8
8
|
const arg = process.argv[2];
|
|
9
|
+
if (arg === 'setup') {
|
|
10
|
+
const { runSetup } = await import('./setup.js');
|
|
11
|
+
await runSetup();
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
9
14
|
if (arg === '--version' || arg === '-v') {
|
|
10
15
|
console.log(VERSION);
|
|
11
16
|
process.exit(0);
|
|
@@ -14,12 +19,14 @@ if (arg === '--help' || arg === '-h') {
|
|
|
14
19
|
console.log(`@hopivia/mcp ${VERSION} — MCP server for the Hopivia API (https://hopivia.com/docs)
|
|
15
20
|
|
|
16
21
|
Usage: hopivia-mcp start the stdio server (what MCP clients run)
|
|
22
|
+
hopivia-mcp setup create the local agent wallet, limits and contacts, connect Claude Desktop
|
|
17
23
|
hopivia-mcp --version
|
|
18
24
|
|
|
19
25
|
Environment:
|
|
20
26
|
HOPIVIA_API_KEY optional X-API-Key; free keys at https://hopivia.com/agents
|
|
21
27
|
HOPIVIA_API_URL API base, default https://hopivia.com/api/v1
|
|
22
28
|
HOPIVIA_SITE_URL site base for docs links, default https://hopivia.com
|
|
29
|
+
HOPIVIA_WALLETS_FILE local agent wallets, default ~/.hopivia/wallets.json
|
|
23
30
|
|
|
24
31
|
Claude Code: claude mcp add hopivia -- npx -y @hopivia/mcp
|
|
25
32
|
Claude Desktop / Cursor: {"mcpServers":{"hopivia":{"command":"npx","args":["-y","@hopivia/mcp"]}}}`);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,0GAA0G;AAC1G,uGAAuG;AACvG,kDAAkD;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC5B,IAAI,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;IACxC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AACD,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;IACrC,OAAO,CAAC,GAAG,CAAC,gBAAgB,OAAO
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,0GAA0G;AAC1G,uGAAuG;AACvG,kDAAkD;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC5B,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;IACpB,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;IAChD,MAAM,QAAQ,EAAE,CAAC;IACjB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AACD,IAAI,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;IACxC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AACD,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;IACrC,OAAO,CAAC,GAAG,CAAC,gBAAgB,OAAO;;;;;;;;;;;;;mGAa8D,CAAC,CAAC;IACnG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,MAAM,GAAG,YAAY,CAAC;IAC1B,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe;IACnC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe;IACpC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB;IACrC,SAAS,EAAE,eAAe,OAAO,EAAE;CACpC,CAAC,CAAC;AACH,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
export type ConfirmResult = {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
via: 'elicitation' | 'dialog' | 'none';
|
|
5
|
+
reason?: string;
|
|
6
|
+
};
|
|
7
|
+
export type Confirmer = (server: McpServer, text: string) => Promise<ConfirmResult>;
|
|
8
|
+
export declare const defaultConfirmer: Confirmer;
|
package/dist/confirm.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Asking the USER (not the model) to approve a transfer. The model can be talked into a wrong
|
|
2
|
+
// recipient by text it reads, so for addresses outside the contacts the approval must come from a
|
|
3
|
+
// channel the model cannot answer: MCP elicitation (the client shows a form to the user) or, on
|
|
4
|
+
// macOS when the client lacks it, a native dialog. Without either, the transfer is refused.
|
|
5
|
+
import { execFile } from 'node:child_process';
|
|
6
|
+
function macDialog(text) {
|
|
7
|
+
// AppleScript string literal: escape backslashes and quotes; no other interpolation
|
|
8
|
+
const esc = text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
9
|
+
const script = `display dialog "${esc}" with title "Hopivia — confirm transfer" buttons {"Cancel", "Send"} default button "Cancel" cancel button "Cancel" with icon caution giving up after 120`;
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
execFile('osascript', ['-e', script], { timeout: 130_000 }, (err, stdout) => resolve(!err && /button returned:Send/.test(stdout) && !/gave up:true/.test(stdout)));
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export const defaultConfirmer = async (server, text) => {
|
|
15
|
+
const caps = server.server.getClientCapabilities();
|
|
16
|
+
if (caps?.elicitation) {
|
|
17
|
+
try {
|
|
18
|
+
const r = await server.server.elicitInput({
|
|
19
|
+
mode: 'form',
|
|
20
|
+
message: text,
|
|
21
|
+
requestedSchema: { type: 'object', properties: { send: { type: 'boolean', title: 'Send this transfer', default: false } }, required: ['send'] },
|
|
22
|
+
});
|
|
23
|
+
return { ok: r.action === 'accept' && r.content?.send === true, via: 'elicitation', ...(r.action !== 'accept' ? { reason: `user chose ${r.action}` } : {}) };
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
if (process.platform !== 'darwin')
|
|
27
|
+
return { ok: false, via: 'none', reason: `confirmation failed: ${e.message}` };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (process.platform === 'darwin')
|
|
31
|
+
return { ok: await macDialog(text), via: 'dialog' };
|
|
32
|
+
return { ok: false, via: 'none', reason: 'this MCP client cannot ask the user for confirmation' };
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=confirm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confirm.js","sourceRoot":"","sources":["../src/confirm.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,kGAAkG;AAClG,gGAAgG;AAChG,4FAA4F;AAC5F,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAM9C,SAAS,SAAS,CAAC,IAAY;IAC7B,oFAAoF;IACpF,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC7D,MAAM,MAAM,GAAG,mBAAmB,GAAG,2JAA2J,CAAC;IACjM,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,sBAAsB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACrK,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAc,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE;IAChE,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,qBAAqB,EAAE,CAAC;IACnD,IAAI,IAAI,EAAE,WAAW,EAAE,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC;gBACxC,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,IAAI;gBACb,eAAe,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,oBAAoB,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE;aAChJ,CAAC,CAAC;YACH,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAK,CAAC,CAAC,OAA0C,EAAE,IAAI,KAAK,IAAI,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QACnM,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ;gBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,wBAAyB,CAAW,CAAC,OAAO,EAAE,EAAE,CAAC;QAC/H,CAAC;IACH,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;IACvF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,sDAAsD,EAAE,CAAC;AACpG,CAAC,CAAC"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type ServerOptions } from './server.js';
|
|
2
|
+
export type RemoteOptions = Omit<ServerOptions, 'mode' | 'wallets' | 'signer' | 'confirmer' | 'spendLog'>;
|
|
3
|
+
export declare function handleRemoteRequest(req: Request, opts: RemoteOptions & {
|
|
4
|
+
parsedBody?: unknown;
|
|
5
|
+
}): Promise<Response>;
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// The hosted (remote) MCP endpoint over Streamable HTTP, stateless: every POST gets a fresh server
|
|
2
|
+
// and transport and the answer is plain JSON, so any number of API instances can serve it and
|
|
3
|
+
// nothing outlives the request. Framework-neutral (web Request in, Response out); the API mounts
|
|
4
|
+
// it at POST /api/v1/mcp.
|
|
5
|
+
import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
6
|
+
import { createServer } from './server.js';
|
|
7
|
+
export async function handleRemoteRequest(req, opts) {
|
|
8
|
+
// Stateless: no session to resume (GET would open a server-to-client stream) or to end (DELETE)
|
|
9
|
+
if (req.method !== 'POST') {
|
|
10
|
+
return new Response(JSON.stringify({ jsonrpc: '2.0', error: { code: -32000, message: 'Method not allowed: this MCP endpoint is stateless, send JSON-RPC with POST' }, id: null }), {
|
|
11
|
+
status: 405, headers: { 'content-type': 'application/json', allow: 'POST' },
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
const { parsedBody, ...serverOpts } = opts;
|
|
15
|
+
const server = createServer({ ...serverOpts, mode: 'remote' });
|
|
16
|
+
const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true });
|
|
17
|
+
await server.connect(transport);
|
|
18
|
+
try {
|
|
19
|
+
return await transport.handleRequest(req, parsedBody !== undefined ? { parsedBody } : undefined);
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
await transport.close().catch(() => undefined);
|
|
23
|
+
await server.close().catch(() => undefined);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,8FAA8F;AAC9F,iGAAiG;AACjG,0BAA0B;AAC1B,OAAO,EAAE,wCAAwC,EAAE,MAAM,+DAA+D,CAAC;AACzH,OAAO,EAAE,YAAY,EAAsB,MAAM,aAAa,CAAC;AAI/D,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,GAAY,EAAE,IAA8C;IACpG,gGAAgG;IAChG,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QAC1B,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,6EAA6E,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE;YACjL,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,EAAE;SAC5E,CAAC,CAAC;IACL,CAAC;IACD,MAAM,EAAE,UAAU,EAAE,GAAG,UAAU,EAAE,GAAG,IAAI,CAAC;IAC3C,MAAM,MAAM,GAAG,YAAY,CAAC,EAAE,GAAG,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC/D,MAAM,SAAS,GAAG,IAAI,wCAAwC,CAAC,EAAE,kBAAkB,EAAE,SAAS,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5H,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,IAAI,CAAC;QACH,OAAO,MAAM,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACnG,CAAC;YAAS,CAAC;QACT,MAAM,SAAS,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC/C,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC9C,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { createServer, type ServerOptions } from './server.js';
|
|
2
|
+
export { handleRemoteRequest, type RemoteOptions } from './http.js';
|
|
3
|
+
export { payLink, PAY_CHAINS } from './transferLink.js';
|
|
2
4
|
export { HopiviaClient, ApiError, type ClientOptions } from './client.js';
|
|
3
5
|
export { VERSION } from './version.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { createServer } from './server.js';
|
|
2
|
+
export { handleRemoteRequest } from './http.js';
|
|
3
|
+
export { payLink, PAY_CHAINS } from './transferLink.js';
|
|
2
4
|
export { HopiviaClient, ApiError } from './client.js';
|
|
3
5
|
export { VERSION } from './version.js';
|
|
4
6
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAsB,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAsB,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAsB,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAsB,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAsB,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const KEYCHAIN_SERVICE = "hopivia-agent-wallet";
|
|
2
|
+
export declare const keychainAvailable: () => boolean;
|
|
3
|
+
export declare function keychainStore(account: string, privateKey: string, keychain?: string): void;
|
|
4
|
+
export declare function keychainRead(account: string, keychain?: string): Promise<string>;
|
|
5
|
+
export declare function keychainDelete(account: string, keychain?: string): void;
|
package/dist/keychain.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// macOS Keychain storage for agent wallet keys (the `security` tool that ships with macOS).
|
|
2
|
+
// Items are created with an EMPTY list of trusted applications (-T ""), so every read shows the
|
|
3
|
+
// system dialog "… wants to use your confidential information" and needs the user's approval:
|
|
4
|
+
// a program that merely runs as the user cannot read the key silently, unlike a plain file.
|
|
5
|
+
import { execFile, execFileSync } from 'node:child_process';
|
|
6
|
+
export const KEYCHAIN_SERVICE = 'hopivia-agent-wallet';
|
|
7
|
+
export const keychainAvailable = () => process.platform === 'darwin';
|
|
8
|
+
export function keychainStore(account, privateKey, keychain) {
|
|
9
|
+
// -U updates an existing item. The key passes through argv for the duration of this call only.
|
|
10
|
+
execFileSync('security', ['add-generic-password', '-U', '-s', KEYCHAIN_SERVICE, '-a', account, '-T', '', '-w', privateKey, ...(keychain ? [keychain] : [])], { stdio: 'ignore' });
|
|
11
|
+
}
|
|
12
|
+
// Asynchronous: the approval dialog may stay open while the user decides
|
|
13
|
+
export function keychainRead(account, keychain) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
execFile('security', ['find-generic-password', '-s', KEYCHAIN_SERVICE, '-a', account, '-w', ...(keychain ? [keychain] : [])], { timeout: 180_000 }, (err, stdout) => {
|
|
16
|
+
if (err)
|
|
17
|
+
reject(new Error(`the key of "${account}" was not released by the macOS Keychain (denied, timed out or missing)`));
|
|
18
|
+
else
|
|
19
|
+
resolve(stdout.trim());
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export function keychainDelete(account, keychain) {
|
|
24
|
+
try {
|
|
25
|
+
execFileSync('security', ['delete-generic-password', '-s', KEYCHAIN_SERVICE, '-a', account, ...(keychain ? [keychain] : [])], { stdio: 'ignore' });
|
|
26
|
+
}
|
|
27
|
+
catch { /* not there */ }
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=keychain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keychain.js","sourceRoot":"","sources":["../src/keychain.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,gGAAgG;AAChG,8FAA8F;AAC9F,4FAA4F;AAC5F,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE5D,MAAM,CAAC,MAAM,gBAAgB,GAAG,sBAAsB,CAAC;AACvD,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC;AAErE,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,UAAkB,EAAE,QAAiB;IAClF,+FAA+F;IAC/F,YAAY,CAAC,UAAU,EAAE,CAAC,sBAAsB,EAAE,IAAI,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;AACpL,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,QAAiB;IAC7D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CAAC,UAAU,EAAE,CAAC,uBAAuB,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;YAClK,IAAI,GAAG;gBAAE,MAAM,CAAC,IAAI,KAAK,CAAC,eAAe,OAAO,yEAAyE,CAAC,CAAC,CAAC;;gBACvH,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,QAAiB;IAC/D,IAAI,CAAC;QAAC,YAAY,CAAC,UAAU,EAAE,CAAC,yBAAyB,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC;AACvL,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type Lang = 'ru' | 'en';
|
|
2
|
+
export declare function detectLang(configured?: unknown): Lang;
|
|
3
|
+
export type ConfirmFacts = {
|
|
4
|
+
wallet: string;
|
|
5
|
+
walletAddress: string;
|
|
6
|
+
recipient: string;
|
|
7
|
+
recipientName?: string;
|
|
8
|
+
knownRecipient: boolean;
|
|
9
|
+
chain: string;
|
|
10
|
+
symbol: string;
|
|
11
|
+
received: string;
|
|
12
|
+
spent?: string;
|
|
13
|
+
feesUsd?: number;
|
|
14
|
+
gasSymbol: string;
|
|
15
|
+
gasMax: string;
|
|
16
|
+
feesIncluded: boolean;
|
|
17
|
+
};
|
|
18
|
+
export declare function confirmText(f: ConfirmFacts, lang: Lang): string;
|
|
19
|
+
export type BatchFacts = {
|
|
20
|
+
wallet: string;
|
|
21
|
+
walletAddress: string;
|
|
22
|
+
chain: string;
|
|
23
|
+
symbol: string;
|
|
24
|
+
rows: {
|
|
25
|
+
name?: string;
|
|
26
|
+
address: string;
|
|
27
|
+
amount: string;
|
|
28
|
+
known: boolean;
|
|
29
|
+
}[];
|
|
30
|
+
total: string;
|
|
31
|
+
feesUsd?: number;
|
|
32
|
+
feesIncluded: boolean;
|
|
33
|
+
};
|
|
34
|
+
export declare function batchConfirmText(f: BatchFacts, lang: Lang): string;
|
|
35
|
+
export type LinkFacts = {
|
|
36
|
+
link: string;
|
|
37
|
+
chain: string;
|
|
38
|
+
symbol: string;
|
|
39
|
+
recipient: string;
|
|
40
|
+
label?: string;
|
|
41
|
+
received?: string;
|
|
42
|
+
spent?: string;
|
|
43
|
+
feesUsd?: number;
|
|
44
|
+
serviceUsd: number;
|
|
45
|
+
};
|
|
46
|
+
export declare function transferLinkText(f: LinkFacts, lang: Lang): string;
|
package/dist/messages.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export function detectLang(configured) {
|
|
2
|
+
if (configured === 'ru' || configured === 'en')
|
|
3
|
+
return configured;
|
|
4
|
+
return /^ru/i.test(process.env.LANG ?? process.env.LC_ALL ?? '') ? 'ru' : 'en';
|
|
5
|
+
}
|
|
6
|
+
const usd = (x) => (x >= 0.1 ? x.toFixed(2) : x >= 0.01 ? x.toFixed(3) : x.toFixed(4));
|
|
7
|
+
export function confirmText(f, lang) {
|
|
8
|
+
const who = f.recipientName ? `«${f.recipientName}» ${f.recipient}` : f.recipient;
|
|
9
|
+
if (lang === 'ru') {
|
|
10
|
+
return [
|
|
11
|
+
`Перевести ${f.symbol} в сети ${f.chain}`,
|
|
12
|
+
`С кошелька: «${f.wallet}» ${f.walletAddress}`,
|
|
13
|
+
`Кому: ${who}`,
|
|
14
|
+
f.knownRecipient ? '' : '⚠ Этого адреса нет в ваших контактах. Сверьте его до последнего символа.',
|
|
15
|
+
`Получатель получит: ${f.received} ${f.symbol}`,
|
|
16
|
+
f.spent ? `С кошелька уйдёт: ${f.spent} ${f.symbol}${f.feesIncluded ? ' (комиссия включена)' : ''}` : '',
|
|
17
|
+
f.feesUsd !== undefined ? `Комиссия: ≈ $${usd(f.feesUsd)}` : `Комиссия: газ до ${f.gasMax} ${f.gasSymbol} и $0.01`,
|
|
18
|
+
'Подтвердить перевод?',
|
|
19
|
+
].filter(Boolean).join('\n');
|
|
20
|
+
}
|
|
21
|
+
return [
|
|
22
|
+
`Send ${f.symbol} on ${f.chain}`,
|
|
23
|
+
`From: "${f.wallet}" ${f.walletAddress}`,
|
|
24
|
+
`To: ${who}`,
|
|
25
|
+
f.knownRecipient ? '' : '⚠ This address is NOT in your contacts. Check it character by character.',
|
|
26
|
+
`Recipient gets: ${f.received} ${f.symbol}`,
|
|
27
|
+
f.spent ? `Leaves the wallet: ${f.spent} ${f.symbol}${f.feesIncluded ? ' (fees included)' : ''}` : '',
|
|
28
|
+
f.feesUsd !== undefined ? `Fees: ≈ $${usd(f.feesUsd)}` : `Fees: gas up to ${f.gasMax} ${f.gasSymbol} and $0.01`,
|
|
29
|
+
'Confirm this transfer?',
|
|
30
|
+
].filter(Boolean).join('\n');
|
|
31
|
+
}
|
|
32
|
+
const SHOWN = 20;
|
|
33
|
+
export function batchConfirmText(f, lang) {
|
|
34
|
+
const unknown = f.rows.filter((r) => !r.known).length;
|
|
35
|
+
const row = (r) => `${r.known ? '' : '⚠ '}${r.name ? `«${r.name}» ` : ''}${r.address} — ${r.amount} ${f.symbol}`;
|
|
36
|
+
const rows = f.rows.slice(0, SHOWN).map(row);
|
|
37
|
+
const more = f.rows.length - SHOWN;
|
|
38
|
+
if (lang === 'ru') {
|
|
39
|
+
return [
|
|
40
|
+
`Перевести ${f.symbol} ${f.rows.length} получателям в сети ${f.chain}`,
|
|
41
|
+
`С кошелька: «${f.wallet}» ${f.walletAddress}`,
|
|
42
|
+
...rows, more > 0 ? `…и ещё ${more}` : '',
|
|
43
|
+
unknown ? `⚠ ${unknown} адрес(ов) нет в ваших контактах. Сверьте их.` : '',
|
|
44
|
+
`Всего получателям: ${f.total} ${f.symbol}${f.feesIncluded ? ' (комиссия включена)' : ''}`,
|
|
45
|
+
f.feesUsd !== undefined ? `Комиссия: ≈ $${usd(f.feesUsd)}` : '',
|
|
46
|
+
'Подтвердить все переводы?',
|
|
47
|
+
].filter(Boolean).join('\n');
|
|
48
|
+
}
|
|
49
|
+
return [
|
|
50
|
+
`Send ${f.symbol} to ${f.rows.length} recipients on ${f.chain}`,
|
|
51
|
+
`From: "${f.wallet}" ${f.walletAddress}`,
|
|
52
|
+
...rows, more > 0 ? `…and ${more} more` : '',
|
|
53
|
+
unknown ? `⚠ ${unknown} address(es) are NOT in your contacts. Check them.` : '',
|
|
54
|
+
`Total to recipients: ${f.total} ${f.symbol}${f.feesIncluded ? ' (fees included)' : ''}`,
|
|
55
|
+
f.feesUsd !== undefined ? `Fees: ≈ $${usd(f.feesUsd)}` : '',
|
|
56
|
+
'Confirm all transfers?',
|
|
57
|
+
].filter(Boolean).join('\n');
|
|
58
|
+
}
|
|
59
|
+
export function transferLinkText(f, lang) {
|
|
60
|
+
const who = f.label ? `«${f.label}» ${f.recipient}` : f.recipient;
|
|
61
|
+
if (lang === 'ru') {
|
|
62
|
+
return [
|
|
63
|
+
`Ссылка для перевода: ${f.link}`,
|
|
64
|
+
`Сеть: ${f.chain}`,
|
|
65
|
+
`Кому: ${who}`,
|
|
66
|
+
f.received ? `Получатель получит: ${f.received} ${f.symbol}` : '',
|
|
67
|
+
f.spent ? `С кошелька уйдёт: ${f.spent} ${f.symbol}` : '',
|
|
68
|
+
f.feesUsd !== undefined ? `Комиссия: ≈ $${usd(f.feesUsd)}` : `Комиссия: газ сети и $${f.serviceUsd}, точная сумма будет на странице`,
|
|
69
|
+
'Откройте ссылку, подключите свой кошелёк, сверьте адрес получателя на странице и в кошельке и подпишите перевод. Без вашей подписи ничего не отправится.',
|
|
70
|
+
].filter(Boolean).join('\n');
|
|
71
|
+
}
|
|
72
|
+
return [
|
|
73
|
+
`Transfer link: ${f.link}`,
|
|
74
|
+
`Network: ${f.chain}`,
|
|
75
|
+
`To: ${who}`,
|
|
76
|
+
f.received ? `Recipient gets: ${f.received} ${f.symbol}` : '',
|
|
77
|
+
f.spent ? `Leaves your wallet: ${f.spent} ${f.symbol}` : '',
|
|
78
|
+
f.feesUsd !== undefined ? `Fees: ≈ $${usd(f.feesUsd)}` : `Fees: network gas and $${f.serviceUsd}, the exact amount is shown on the page`,
|
|
79
|
+
'Open the link, connect your own wallet, check the recipient address on the page and in your wallet, then sign. Nothing is sent without your signature.',
|
|
80
|
+
].filter(Boolean).join('\n');
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"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"}
|
package/dist/pinned.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const PERMIT2: "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
2
|
+
export declare const FEE_WALLET: "0x0A6934D941ee8EaeB41fa68c0245cd959F930ee3";
|
|
3
|
+
export declare const AGGREGATORS: Record<number, `0x${string}`>;
|
|
4
|
+
export declare const TRANSFER_CONTRACTS: Record<number, `0x${string}`>;
|
|
5
|
+
export declare const transferContractPinned: (chainId: number) => string | undefined;
|
|
6
|
+
export declare const feeWalletPinned: () => string;
|
package/dist/pinned.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Addresses the MCP server trusts on its own, without asking the API. Everything the API returns
|
|
2
|
+
// for a local wallet is checked against these and against what the user asked for before the
|
|
3
|
+
// wallet signs: a compromised or spoofed API must not be able to redirect funds.
|
|
4
|
+
// Update together with the site repo's deployed*.json (npm run deploy:* writes them).
|
|
5
|
+
export const PERMIT2 = '0x000000000022D473030F116dDEE9F6B43aC78BA3';
|
|
6
|
+
// Hopivia fee wallet (aggregator feeRecipient on every chain)
|
|
7
|
+
export const FEE_WALLET = '0x0A6934D941ee8EaeB41fa68c0245cd959F930ee3';
|
|
8
|
+
// HopiviaAggregator per chain (deployed.json)
|
|
9
|
+
export const AGGREGATORS = {
|
|
10
|
+
56: '0x1914264e39A80B32c39BE63e0Ae0808bbD837245',
|
|
11
|
+
1: '0x68F831FFDc96029f04A6E0C1704bCef7fAD0F961',
|
|
12
|
+
10: '0x68F831FFDc96029f04A6E0C1704bCef7fAD0F961',
|
|
13
|
+
137: '0x68F831FFDc96029f04A6E0C1704bCef7fAD0F961',
|
|
14
|
+
8453: '0x68F831FFDc96029f04A6E0C1704bCef7fAD0F961',
|
|
15
|
+
42161: '0x68F831FFDc96029f04A6E0C1704bCef7fAD0F961',
|
|
16
|
+
43114: '0x68F831FFDc96029f04A6E0C1704bCef7fAD0F961',
|
|
17
|
+
};
|
|
18
|
+
// HopiviaTransfer per chain (deployed-transfer.json); empty until deployed
|
|
19
|
+
export const TRANSFER_CONTRACTS = {};
|
|
20
|
+
// Tests and forks can pin other addresses: HOPIVIA_PIN_TRANSFER_<chainId>, HOPIVIA_PIN_FEE_WALLET
|
|
21
|
+
export const transferContractPinned = (chainId) => process.env[`HOPIVIA_PIN_TRANSFER_${chainId}`] ?? TRANSFER_CONTRACTS[chainId];
|
|
22
|
+
export const feeWalletPinned = () => process.env.HOPIVIA_PIN_FEE_WALLET ?? FEE_WALLET;
|
|
23
|
+
//# sourceMappingURL=pinned.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pinned.js","sourceRoot":"","sources":["../src/pinned.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,6FAA6F;AAC7F,iFAAiF;AACjF,sFAAsF;AACtF,MAAM,CAAC,MAAM,OAAO,GAAG,4CAAqD,CAAC;AAC7E,8DAA8D;AAC9D,MAAM,CAAC,MAAM,UAAU,GAAG,4CAAqD,CAAC;AAChF,8CAA8C;AAC9C,MAAM,CAAC,MAAM,WAAW,GAAkC;IACxD,EAAE,EAAE,4CAA4C;IAChD,CAAC,EAAE,4CAA4C;IAC/C,EAAE,EAAE,4CAA4C;IAChD,GAAG,EAAE,4CAA4C;IACjD,IAAI,EAAE,4CAA4C;IAClD,KAAK,EAAE,4CAA4C;IACnD,KAAK,EAAE,4CAA4C;CACpD,CAAC;AACF,2EAA2E;AAC3E,MAAM,CAAC,MAAM,kBAAkB,GAAkC,EAAE,CAAC;AAEpE,kGAAkG;AAClG,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,OAAe,EAAsB,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,OAAO,EAAE,CAAC,IAAI,kBAAkB,CAAC,OAAO,CAAC,CAAC;AAC7J,MAAM,CAAC,MAAM,eAAe,GAAG,GAAW,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,UAAU,CAAC"}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { type ClientOptions } from './client.js';
|
|
3
|
+
import { type Confirmer } from './confirm.js';
|
|
4
|
+
import { type Signer } from './signer.js';
|
|
5
|
+
import { type SpendLog, type WalletConfig } from './wallets.js';
|
|
3
6
|
export declare function recipientNotice(p: {
|
|
4
7
|
recipient?: string;
|
|
5
8
|
sender?: string;
|
|
@@ -8,5 +11,10 @@ export declare function recipientNotice(p: {
|
|
|
8
11
|
}): string | undefined;
|
|
9
12
|
export type ServerOptions = ClientOptions & {
|
|
10
13
|
siteUrl?: string;
|
|
14
|
+
mode?: 'local' | 'remote';
|
|
15
|
+
wallets?: WalletConfig;
|
|
16
|
+
signer?: Signer;
|
|
17
|
+
confirmer?: Confirmer;
|
|
18
|
+
spendLog?: SpendLog;
|
|
11
19
|
};
|
|
12
20
|
export declare function createServer(opts?: ServerOptions): McpServer;
|