@ironwallet/mcp-server 1.0.0-preprod.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.
Files changed (110) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -0
  3. package/assets/knight.png +0 -0
  4. package/assets/wordmark.svg +20 -0
  5. package/dist/api/auth.d.ts +20 -0
  6. package/dist/api/auth.js +92 -0
  7. package/dist/api/auth.js.map +1 -0
  8. package/dist/api/balances.d.ts +25 -0
  9. package/dist/api/balances.js +565 -0
  10. package/dist/api/balances.js.map +1 -0
  11. package/dist/api/http.d.ts +34 -0
  12. package/dist/api/http.js +277 -0
  13. package/dist/api/http.js.map +1 -0
  14. package/dist/api/relay.d.ts +49 -0
  15. package/dist/api/relay.js +194 -0
  16. package/dist/api/relay.js.map +1 -0
  17. package/dist/api/rpc.d.ts +10 -0
  18. package/dist/api/rpc.js +26 -0
  19. package/dist/api/rpc.js.map +1 -0
  20. package/dist/api/swap.d.ts +134 -0
  21. package/dist/api/swap.js +554 -0
  22. package/dist/api/swap.js.map +1 -0
  23. package/dist/cli.d.ts +6 -0
  24. package/dist/cli.js +20 -0
  25. package/dist/cli.js.map +1 -0
  26. package/dist/config.d.ts +91 -0
  27. package/dist/config.js +175 -0
  28. package/dist/config.js.map +1 -0
  29. package/dist/generated/env-config.d.ts +5 -0
  30. package/dist/generated/env-config.js +23 -0
  31. package/dist/generated/env-config.js.map +1 -0
  32. package/dist/generated/env-profile.d.ts +15 -0
  33. package/dist/generated/env-profile.js +2 -0
  34. package/dist/generated/env-profile.js.map +1 -0
  35. package/dist/keystore/crypto.d.ts +17 -0
  36. package/dist/keystore/crypto.js +57 -0
  37. package/dist/keystore/crypto.js.map +1 -0
  38. package/dist/keystore/store.d.ts +45 -0
  39. package/dist/keystore/store.js +224 -0
  40. package/dist/keystore/store.js.map +1 -0
  41. package/dist/keystore/types.d.ts +31 -0
  42. package/dist/keystore/types.js +2 -0
  43. package/dist/keystore/types.js.map +1 -0
  44. package/dist/local-secrets.d.ts +16 -0
  45. package/dist/local-secrets.js +73 -0
  46. package/dist/local-secrets.js.map +1 -0
  47. package/dist/log.d.ts +30 -0
  48. package/dist/log.js +323 -0
  49. package/dist/log.js.map +1 -0
  50. package/dist/passphrase.d.ts +6 -0
  51. package/dist/passphrase.js +10 -0
  52. package/dist/passphrase.js.map +1 -0
  53. package/dist/policy.d.ts +22 -0
  54. package/dist/policy.js +108 -0
  55. package/dist/policy.js.map +1 -0
  56. package/dist/server.d.ts +6 -0
  57. package/dist/server.js +38 -0
  58. package/dist/server.js.map +1 -0
  59. package/dist/signing/bitcoin.d.ts +18 -0
  60. package/dist/signing/bitcoin.js +84 -0
  61. package/dist/signing/bitcoin.js.map +1 -0
  62. package/dist/signing/dispatch.d.ts +7 -0
  63. package/dist/signing/dispatch.js +35 -0
  64. package/dist/signing/dispatch.js.map +1 -0
  65. package/dist/signing/evm.d.ts +16 -0
  66. package/dist/signing/evm.js +59 -0
  67. package/dist/signing/evm.js.map +1 -0
  68. package/dist/signing/index.d.ts +10 -0
  69. package/dist/signing/index.js +57 -0
  70. package/dist/signing/index.js.map +1 -0
  71. package/dist/signing/solana.d.ts +18 -0
  72. package/dist/signing/solana.js +106 -0
  73. package/dist/signing/solana.js.map +1 -0
  74. package/dist/signing/swap.d.ts +10 -0
  75. package/dist/signing/swap.js +114 -0
  76. package/dist/signing/swap.js.map +1 -0
  77. package/dist/signing/ton.d.ts +33 -0
  78. package/dist/signing/ton.js +166 -0
  79. package/dist/signing/ton.js.map +1 -0
  80. package/dist/signing/tron.d.ts +11 -0
  81. package/dist/signing/tron.js +39 -0
  82. package/dist/signing/tron.js.map +1 -0
  83. package/dist/signing/types.d.ts +23 -0
  84. package/dist/signing/types.js +2 -0
  85. package/dist/signing/types.js.map +1 -0
  86. package/dist/signing/xrp.d.ts +11 -0
  87. package/dist/signing/xrp.js +122 -0
  88. package/dist/signing/xrp.js.map +1 -0
  89. package/dist/tools/helpers.d.ts +42 -0
  90. package/dist/tools/helpers.js +80 -0
  91. package/dist/tools/helpers.js.map +1 -0
  92. package/dist/tools/index.d.ts +7 -0
  93. package/dist/tools/index.js +16 -0
  94. package/dist/tools/index.js.map +1 -0
  95. package/dist/tools/swap.d.ts +6 -0
  96. package/dist/tools/swap.js +343 -0
  97. package/dist/tools/swap.js.map +1 -0
  98. package/dist/tools/transfer.d.ts +6 -0
  99. package/dist/tools/transfer.js +284 -0
  100. package/dist/tools/transfer.js.map +1 -0
  101. package/dist/tools/wallets.d.ts +6 -0
  102. package/dist/tools/wallets.js +79 -0
  103. package/dist/tools/wallets.js.map +1 -0
  104. package/dist/wallet/derive.d.ts +53 -0
  105. package/dist/wallet/derive.js +233 -0
  106. package/dist/wallet/derive.js.map +1 -0
  107. package/dist/web/manager.d.ts +15 -0
  108. package/dist/web/manager.js +444 -0
  109. package/dist/web/manager.js.map +1 -0
  110. package/package.json +54 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 INWAY AG
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # @ironwallet/mcp-server
2
+
3
+ Local [MCP](https://modelcontextprotocol.io) server for **Ironwallet** (`ironwallet-mcp` bin).
4
+
5
+ Give an agent a **self-custody hot wallet** on this machine, seed-compatible with the [Ironwallet](https://ironwallet.io) app. Signing never leaves the machine. Recovery phrases stay encrypted on disk and **never pass through the agent**.
6
+
7
+ There is no per-transaction confirmation UI.
8
+
9
+ > **Hot-wallet only.** Use a dedicated wallet with limited balance. See [Security](#security).
10
+
11
+ Product page: [ironwallet.io/ai](https://ironwallet.io/ai)
12
+
13
+ **Requirements:** Node.js 20+ (`npx`).
14
+
15
+ ---
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npx -y @ironwallet/mcp-server
21
+ ```
22
+
23
+ Or add to MCP config (Cursor: `.cursor/mcp.json` or global):
24
+
25
+ ```json
26
+ {
27
+ "mcpServers": {
28
+ "ironwallet": {
29
+ "command": "npx",
30
+ "args": ["-y", "@ironwallet/mcp-server"]
31
+ }
32
+ }
33
+ }
34
+ ```
35
+
36
+ `npx` pulls the latest published build. First launch can take ~30s while dependencies install. If the MCP client times out, run the same command once in a terminal to warm the cache, then reconnect.
37
+
38
+ The plugin install on [ironwallet.io/ai](https://ironwallet.io/ai) wires this up automatically.
39
+
40
+ ---
41
+
42
+ ## How it works
43
+
44
+ ```
45
+ MCP client → ironwallet-mcp (stdio)
46
+ ├── encrypted keystore on disk
47
+ ├── signs on this machine
48
+ └── HTTPS to Ironwallet backends
49
+ ```
50
+
51
+ The mnemonic never appears in tool inputs/outputs, logs meant for the agent, or requests to backends / the LLM / the MCP client’s cloud.
52
+
53
+ ---
54
+
55
+ ## Features
56
+
57
+ | Area | Capability |
58
+ |------|------------|
59
+ | **Networks** | Ethereum, BSC, Polygon, Base, Arbitrum, Optimism, Avalanche, Tron, Bitcoin, Litecoin, Dogecoin, Solana, XRP, TON |
60
+ | **Wallets** | Create, import, list, and back up (local browser for secrets) |
61
+ | **Balances** | Native coins and tokens |
62
+ | **Transfers** | Fee estimate and send through Ironwallet’s transfer relay |
63
+ | **Swaps** | Quotes and execution through Ironwallet **Swap Proxy** |
64
+ | **Policy** | Optional per-wallet limits (`readOnly`, `maxPerTx`, transfer recipient allow-list). Applies to `send_transfer` and `execute_swap`. |
65
+
66
+ ---
67
+
68
+ ## Tools
69
+
70
+ | Tool | Purpose | Moves funds? |
71
+ |------|---------|:------------:|
72
+ | `list_wallets` | Names and addresses | no |
73
+ | `create_wallets` | New wallets; returns a browser `backup_url` | no |
74
+ | `open_wallet_manager` | Local browser UI to import / create / back up | no |
75
+ | `get_balance` | Native or token balance | no |
76
+ | `estimate_transfer` | Fee estimate, no broadcast | no |
77
+ | `send_transfer` | Sign locally and send | **yes** |
78
+ | `get_operation_status` | Poll a transfer | no |
79
+ | `list_swap_networks` | Networks available for swap | no |
80
+ | `list_swap_assets` | Sell / buy catalog | no |
81
+ | `estimate_swap` | Quote (may expire) | no |
82
+ | `execute_swap` | Fresh quote → sign → swap | **yes** |
83
+ | `get_swap_status` | Poll a swap | no |
84
+
85
+ No tool accepts or returns a seed. Import and backup only in the local browser (`open_wallet_manager` / `backup_url`).
86
+
87
+ ### Wallets
88
+
89
+ - **Create:** `create_wallets` — the agent gets names and addresses; open `backup_url` in a browser to view and back up recovery phrases.
90
+ - **Import / back up:** `open_wallet_manager` — loopback-only page; the phrase is typed or shown only in the browser.
91
+ - **List:** `list_wallets`
92
+
93
+ The browser page binds to `127.0.0.1` under an unguessable path and shuts down after 15 minutes of inactivity.
94
+
95
+ ### Transfers
96
+
97
+ 1. `list_wallets` / `get_balance`
98
+ 2. `estimate_transfer` (optional)
99
+ 3. `send_transfer`
100
+ 4. `get_operation_status` when you need to wait on the operation
101
+
102
+ `send_transfer` may reduce the amount slightly so the fee still fits the balance; the response reports when that happened.
103
+
104
+ ### Swaps
105
+
106
+ Swaps use **Swap Proxy**, not the transfer relay.
107
+
108
+ 1. `list_swap_networks`
109
+ 2. `list_swap_assets` (`direction=from`, then `direction=to` with the chosen sell asset)
110
+ 3. Copy **network, symbol, address, decimals** from the catalog into `estimate_swap` / `execute_swap` (especially for tokens)
111
+ 4. `estimate_swap` for a preview, or go straight to `execute_swap`
112
+ 5. Poll with `get_swap_status` using `operationId`
113
+
114
+ **Useful options**
115
+
116
+ - `maxMode: true` — sell as much of the balance as the service allows (fees are accounted for server-side). `amount` can be omitted when `maxMode` is set.
117
+ - Omit `address` only for native coins. For tokens, always pass `address` (and ideally `decimals`) from `list_swap_assets`.
118
+
119
+ **Operational notes**
120
+
121
+ - Quotes expire. Prefer `execute_swap` (fresh quote). If execute times out, **poll status** before retrying — do not blindly re-run execute.
122
+ - Wallet policy: `readOnly` blocks sends and swaps. `maxPerTx` applies to the transfer amount or the corrected swap sell amount. A transfer recipient allow-list also blocks swaps (the swap router is not a whitelisted destination).
123
+
124
+ ---
125
+
126
+ ## Configuration
127
+
128
+ Nothing to paste into MCP config for normal use. On first launch the server writes a relay API key, keystore wrapping secret, and device id under `~/.ironwallet-mcp/` (`keystore-passphrase`, `relay-api-key`, `device-id`, mode `0600`). Set the env vars only to override.
129
+
130
+ The user-facing backup is the **recovery phrase** in the wallet manager, not those files.
131
+
132
+ | Variable | Default | Notes |
133
+ |----------|---------|-------|
134
+ | `IW_PASSPHRASE` | generated locally | Override keystore wrapping secret |
135
+ | `IW_RELAY_API_KEY` | generated UUID | Override `x-api-key` |
136
+ | `IW_DEVICE_ID` | generated UUID | Override `X-Device-Id` (stable per keystore directory) |
137
+ | `IW_KEYSTORE_DIR` | `~/.ironwallet-mcp` | Keystore directory |
138
+ | `IW_HTTP_TIMEOUT_MS` | `15000` | General HTTP timeout (1s–120s) |
139
+ | `IW_HTTP_FORWARD_TIMEOUT_MS` | `60000` | Longer timeout for broadcast-style calls. A client timeout does not always mean the operation failed — check status |
140
+ | `IW_HTTP_RETRIES` | `2` | Retries for safe/idempotent calls; broadcasts are not auto-retried |
141
+ | `IW_LOG_ENABLED` | `1` | JSONL diagnostics to a log file (`0` to disable) |
142
+ | `IW_LOG_FILE` | `{keystoreDir}/logs/iw-mcp-YYYY-MM-DD.jsonl` | Log path |
143
+ | `IW_LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error` |
144
+ | `IW_LOG_STDERR` | `0` | Mirror logs to stderr (stdout is reserved for MCP) |
145
+
146
+ ---
147
+
148
+ ## Security
149
+
150
+ - Seeds are encrypted at rest. They never appear in tool results, agent chat, or backend requests.
151
+ - The agent **can move funds without asking again**. Optional wallet policy (`readOnly`, `maxPerTx`, transfer recipient allow-list) is off by default and applies to both `send_transfer` and `execute_swap`.
152
+ - Anyone with the keystore **and** the wrapping secret controls the funds. A leaked seed cannot be revoked.
153
+ - Timeout is not always failure: poll status before retrying a send or swap.
154
+ - Desktop / stdio only. A phone or remote agent would need a design where signing stays on a trusted device.
155
+ - **Do not** put a main wallet here. Use a small hot wallet.
156
+
157
+ Details and private disclosure: [SECURITY.md](../../SECURITY.md).
158
+
159
+ ## License
160
+
161
+ [MIT](LICENSE)
Binary file
@@ -0,0 +1,20 @@
1
+ <svg width="111" height="27" viewBox="0 0 111 27" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <linearGradient id="iwblue" x1="0" y1="0" x2="111" y2="27" gradientUnits="userSpaceOnUse">
4
+ <stop stop-color="#2C6FE0"/>
5
+ <stop offset="1" stop-color="#12B4EA"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <path d="M46.3652 14.0577V0.594684H50.6898V14.0577H46.3652Z" fill="url(#iwblue)"/>
9
+ <path d="M70.4671 4.72361C70.4671 5.54548 70.2453 6.28908 69.8017 6.95441C69.3582 7.61973 68.7124 8.10894 67.8645 8.42203L70.8584 14.0577H66.0055L63.6181 9.14606H62.0135V14.0577H57.6889V0.594684H65.9272C66.8926 0.594684 67.7144 0.783845 68.3928 1.16217C69.0842 1.52744 69.5995 2.0297 69.9387 2.66893C70.2909 3.29512 70.4671 3.98001 70.4671 4.72361ZM66.0642 4.89973C66.0642 4.5475 65.9468 4.25397 65.7119 4.01915C65.4771 3.78433 65.1901 3.66692 64.8509 3.66692H62.0135V6.1521H64.8509C65.1901 6.1521 65.4771 6.03469 65.7119 5.79987C65.9468 5.55201 66.0642 5.25196 66.0642 4.89973Z" fill="url(#iwblue)"/>
10
+ <path d="M83.2006 0.359863C85.5227 0.359863 87.3164 0.953438 88.5819 2.14059C89.8473 3.32773 90.48 5.05627 90.48 7.32621C90.48 9.59614 89.8473 11.3247 88.5819 12.5118C87.3164 13.699 85.5227 14.2925 83.2006 14.2925C80.8785 14.2925 79.0847 13.7055 77.8193 12.5314C76.5669 11.3442 75.9407 9.60918 75.9407 7.32621C75.9407 5.04323 76.5669 3.31469 77.8193 2.14059C79.0847 0.953438 80.8785 0.359863 83.2006 0.359863ZM83.2006 3.58865C82.2743 3.58865 81.5699 3.86913 81.0872 4.43009C80.6045 4.99105 80.3632 5.74769 80.3632 6.70002V7.95239C80.3632 8.90472 80.6045 9.66137 81.0872 10.2223C81.5699 10.7833 82.2743 11.0638 83.2006 11.0638C84.1268 11.0638 84.8313 10.7833 85.3139 10.2223C85.8097 9.66137 86.0575 8.90472 86.0575 7.95239V6.70002C86.0575 5.74769 85.8097 4.99105 85.3139 4.43009C84.8313 3.86913 84.1268 3.58865 83.2006 3.58865Z" fill="url(#iwblue)"/>
11
+ <path d="M106.35 14.0577L100.695 7.52189V14.0577H96.7225V0.594684H100.499L106.154 7.22836V0.594684H110.127V14.0577H106.35Z" fill="url(#iwblue)"/>
12
+ <path d="M49.0385 26.2881L46.2676 18.0693H47.1599L49.7547 25.8067H49.3085L51.9855 18.0693H52.7838L55.4256 25.8067H55.0029L57.6211 18.0693H58.4548L55.6839 26.2881H54.7681L52.2438 18.9499H52.4786L49.9425 26.2881H49.0385Z" fill="url(#iwblue)"/>
13
+ <path d="M61.5136 26.2881L65.2708 18.0693H66.1279L69.885 26.2881H68.9692L65.5173 18.5742H65.8696L62.4177 26.2881H61.5136ZM62.993 24.0925L63.2513 23.388H68.0299L68.2882 24.0925H62.993Z" fill="url(#iwblue)"/>
14
+ <path d="M74.2777 26.2881V18.0693H75.1465V25.5366H79.749V26.2881H74.2777Z" fill="url(#iwblue)"/>
15
+ <path d="M84.2247 26.2881V18.0693H85.0935V25.5366H89.696V26.2881H84.2247Z" fill="url(#iwblue)"/>
16
+ <path d="M94.9466 21.7443H99.2907V22.484H94.9466V21.7443ZM95.0405 25.5366H99.9835V26.2881H94.1717V18.0693H99.8074V18.8208H95.0405V25.5366Z" fill="url(#iwblue)"/>
17
+ <path d="M106.656 26.2881V18.8208H103.767V18.0693H110.413V18.8208H107.524V26.2881H106.656Z" fill="url(#iwblue)"/>
18
+ <path d="M34.9699 0.321167C36.4223 0.835034 37.1485 1.52019 37.1485 2.37664C37.1485 2.65498 37.0184 3.17955 36.7583 3.95035L27.9868 22.9408C27.9435 23.0907 27.8676 23.2727 27.7592 23.4868C27.6508 23.7009 27.3582 23.99 26.8813 24.3539C26.426 24.6965 25.8733 24.8678 25.223 24.8678C24.5943 24.8678 24.0415 24.6965 23.5647 24.3539C23.1094 23.99 22.8059 23.6367 22.6542 23.2941L18.8498 12.2138C16.4437 19.2367 15.2189 22.8123 15.1755 22.9408C15.1322 23.0693 15.0346 23.2513 14.8829 23.4868C14.7312 23.7223 14.5577 23.9364 14.3627 24.1291C13.8641 24.6216 13.2679 24.8678 12.5743 24.8678C11.8806 24.8678 11.3062 24.7072 10.8509 24.3861C10.4174 24.0649 10.1247 23.7437 9.973 23.4226L9.74539 22.9408L3.66432 11.1387C3.42587 10.4321 3.30664 9.90756 3.30664 9.56499C3.30664 8.70854 4.03283 8.03409 5.48521 7.54163C6.11385 7.32752 6.67746 7.22047 7.17603 7.22047C7.69629 7.22047 8.08648 7.39176 8.34661 7.73433C8.62841 8.07691 9.23868 9.15618 9.23868 9.15618L12.1561 14.4989L16.021 5.9716C16.3244 5.07233 16.8772 4.45141 17.6793 4.10883C18.0261 3.95895 18.4705 3.88402 19.0124 3.88402C19.576 3.88402 20.0963 4.06601 20.5732 4.43C21.0501 4.77258 21.3536 5.11516 21.4836 5.45773L25.223 14.0766L31.2164 2.16211C31.2164 2.16211 31.5734 1.47207 31.7999 1.09357C31.9832 0.787288 32.0552 0.57374 32.3361 0.353283C32.6926 0.0735416 33.0081 0 33.4417 0C33.8752 0 34.3846 0.107056 34.9699 0.321167Z" fill="url(#iwblue)"/>
19
+ <rect width="5.8342" height="5.82899" rx="2.91449" transform="matrix(0.952501 -0.304536 0.305143 0.952307 0 1.93018)" fill="url(#iwblue)"/>
20
+ </svg>
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Challenge-signature login, matching the mobile app:
3
+ * 1. POST {authUrl}/api/v1/login/start { wallets: [{ network, publicKey }] }
4
+ * 2. Sign the returned challenge with the Ethereum key (EIP-191 personal_sign)
5
+ * 3. POST {authUrl}/api/v1/login/confirm { challenge, signature }
6
+ * -> { accessToken, expiryIn }
7
+ *
8
+ * Tokens are cached per wallet (keyed by EVM address) until shortly before
9
+ * expiry, then a fresh challenge login runs (the app does not use refresh).
10
+ */
11
+ export interface AuthOpts {
12
+ correlationId?: string;
13
+ }
14
+ /**
15
+ * Return a valid Bearer token for the wallet identified by its mnemonic.
16
+ * Uses the cache when the token is still fresh (10s safety margin).
17
+ */
18
+ export declare function getAccessToken(mnemonic: string, opts?: AuthOpts): Promise<string>;
19
+ /** Authorization + device headers for authenticated backend calls. */
20
+ export declare function authHeaders(token: string): Record<string, string>;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Challenge-signature login, matching the mobile app:
3
+ * 1. POST {authUrl}/api/v1/login/start { wallets: [{ network, publicKey }] }
4
+ * 2. Sign the returned challenge with the Ethereum key (EIP-191 personal_sign)
5
+ * 3. POST {authUrl}/api/v1/login/confirm { challenge, signature }
6
+ * -> { accessToken, expiryIn }
7
+ *
8
+ * Tokens are cached per wallet (keyed by EVM address) until shortly before
9
+ * expiry, then a fresh challenge login runs (the app does not use refresh).
10
+ */
11
+ import { Wallet } from "ethers";
12
+ import { getConfig, commonHeaders, AUTH_NETWORKS } from "../config.js";
13
+ import { deriveAddresses, deriveEvm } from "../wallet/derive.js";
14
+ import { httpJson } from "./http.js";
15
+ import { logError, logInfo, newCorrelationId } from "../log.js";
16
+ const tokenCache = new Map();
17
+ /**
18
+ * Return a valid Bearer token for the wallet identified by its mnemonic.
19
+ * Uses the cache when the token is still fresh (10s safety margin).
20
+ */
21
+ export async function getAccessToken(mnemonic, opts = {}) {
22
+ const correlationId = opts.correlationId ?? newCorrelationId("auth");
23
+ const cfg = getConfig();
24
+ const addresses = deriveAddresses(mnemonic);
25
+ const cacheKey = addresses.ethereum.toLowerCase();
26
+ const cached = tokenCache.get(cacheKey);
27
+ if (cached && Date.now() < cached.expiresAt) {
28
+ logInfo("auth.token.cache_hit", {
29
+ correlationId,
30
+ ethereum: addresses.ethereum,
31
+ expiresInMs: cached.expiresAt - Date.now(),
32
+ });
33
+ return cached.token;
34
+ }
35
+ logInfo("auth.login.start", {
36
+ correlationId,
37
+ ethereum: addresses.ethereum,
38
+ authUrl: cfg.authUrl,
39
+ networks: AUTH_NETWORKS,
40
+ });
41
+ const started = Date.now();
42
+ try {
43
+ const wallets = AUTH_NETWORKS.map((network) => ({
44
+ network,
45
+ publicKey: addresses[network],
46
+ }));
47
+ const start = await httpJson(`${cfg.authUrl}/api/v1/login/start`, {
48
+ method: "POST",
49
+ headers: { ...commonHeaders(cfg), "Content-Type": "application/json" },
50
+ body: JSON.stringify({ wallets }),
51
+ }, { retry: true, correlationId, label: "auth.login.start" });
52
+ if (!start?.challenge)
53
+ throw new Error("login/start returned no challenge.");
54
+ logInfo("auth.login.challenge", {
55
+ correlationId,
56
+ challengeLen: start.challenge.length,
57
+ });
58
+ const evm = deriveEvm(mnemonic);
59
+ const signature = await new Wallet(evm.privateKey).signMessage(start.challenge);
60
+ const confirm = await httpJson(`${cfg.authUrl}/api/v1/login/confirm`, {
61
+ method: "POST",
62
+ headers: { ...commonHeaders(cfg), "Content-Type": "application/json" },
63
+ body: JSON.stringify({ challenge: start.challenge, signature }),
64
+ }, { retry: true, correlationId, label: "auth.login.confirm" });
65
+ if (!confirm?.accessToken)
66
+ throw new Error("login/confirm returned no token.");
67
+ const expiresAt = Date.now() + Math.max(0, confirm.expiryIn - 10) * 1000;
68
+ tokenCache.set(cacheKey, { token: confirm.accessToken, expiresAt });
69
+ logInfo("auth.login.ok", {
70
+ correlationId,
71
+ ethereum: addresses.ethereum,
72
+ elapsedMs: Date.now() - started,
73
+ expiryIn: confirm.expiryIn,
74
+ tokenLen: confirm.accessToken.length,
75
+ });
76
+ return confirm.accessToken;
77
+ }
78
+ catch (e) {
79
+ logError("auth.login.fail", e, {
80
+ correlationId,
81
+ ethereum: addresses.ethereum,
82
+ elapsedMs: Date.now() - started,
83
+ });
84
+ throw e;
85
+ }
86
+ }
87
+ /** Authorization + device headers for authenticated backend calls. */
88
+ export function authHeaders(token) {
89
+ const cfg = getConfig();
90
+ return { ...commonHeaders(cfg), Authorization: `Bearer ${token}` };
91
+ }
92
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/api/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * On-chain balances read directly from RPC.
3
+ * - EVM: ethers JsonRpcProvider. Native via getBalance; ERC-20 via
4
+ * balanceOf/decimals/symbol calls.
5
+ * - Tron: TronGrid HTTP API. Native TRX via /wallet/getaccount; TRC-20 via
6
+ * /wallet/triggerconstantcontract (balanceOf) plus on-chain decimals/symbol.
7
+ * - Solana: native SOL via getBalance; SPL via getTokenAccountsByOwner + mint.
8
+ * - TON: native via getAddressBalance; jettons via TonCenter v3 indexer,
9
+ * with on-chain get_wallet_address + get_wallet_data as fallback.
10
+ */
11
+ import { type NetworkId } from "../config.js";
12
+ export interface BalanceResult {
13
+ network: NetworkId;
14
+ address: string;
15
+ /** Token contract address for non-native balances; null for the native coin. */
16
+ contractAddress: string | null;
17
+ symbol: string;
18
+ decimals: number;
19
+ /** Balance in the smallest unit (wei / sun / token base units) as a string. */
20
+ raw: string;
21
+ /** Human-readable balance formatted with `decimals`. */
22
+ formatted: string;
23
+ }
24
+ /** Read the on-chain balance for a wallet address on the given network. */
25
+ export declare function getBalance(network: NetworkId, address: string, contractAddress?: string): Promise<BalanceResult>;