@financedistrict/fdx 0.1.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.
@@ -0,0 +1,148 @@
1
+ # Architecture
2
+
3
+ FDX is a three-layer system that gives AI agents secure access to blockchain wallets without managing private keys.
4
+
5
+ ## System Overview
6
+
7
+ ```
8
+ ┌─────────────────────────────────────────────────────────────────────┐
9
+ │ AI Agent │
10
+ │ (Asks: "What's my wallet balance?") │
11
+ └────────────────────────────────┬────────────────────────────────────┘
12
+
13
+ │ invokes
14
+
15
+ ┌─────────────────────────────────────────────────────────────────────┐
16
+ │ FDX npm Package │
17
+ │ (@financedistrict/fdx) │
18
+ │ │
19
+ │ ┌─────────────────┐ │
20
+ │ │ CLI Commands │ │
21
+ │ │ │ │
22
+ │ │ • fdx setup │ │
23
+ │ │ • fdx status │ │
24
+ │ │ • fdx call │ │
25
+ │ │ <method> │ │
26
+ │ └─────────────────┘ │
27
+ │ │
28
+ │ ┌──────────────────────────────────────────────────────────────┐ │
29
+ │ │ WalletClient (SDK) │ │
30
+ │ │ • OAuth 2.1 + DCR + PKCE authentication │ │
31
+ │ │ • JSON-RPC 2.0 MCP protocol client │ │
32
+ │ │ • High-level methods for wallet/DeFi operations │ │
33
+ │ └──────────────────────────────────────────────────────────────┘ │
34
+ └────────────────────────────────┬────────────────────────────────────┘
35
+
36
+ │ HTTPS + OAuth 2.1
37
+
38
+ ┌─────────────────────────────────────────────────────────────────────┐
39
+ │ Finance District MCP Server │
40
+ │ (https://mcp.fd.xyz) │
41
+ │ │
42
+ │ • User authentication via OAuth 2.1 │
43
+ │ • Smart Account management (EVM + Solana) │
44
+ │ • Multi-chain token transfers │
45
+ │ • DEX aggregation for swaps │
46
+ │ • DeFi yield strategy integration (Aave, Compound, Yearn) │
47
+ │ • X-402 payment protocol support │
48
+ │ │
49
+ │ Supported Chains: │
50
+ │ • Ethereum (1) │
51
+ │ • BNB Smart Chain (56) │
52
+ │ • Arbitrum One (42161) │
53
+ │ • Base (8453) │
54
+ │ • Solana (solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp) │
55
+ └─────────────────────────────────────────────────────────────────────┘
56
+ ```
57
+
58
+ ## Key Components
59
+
60
+ ### 1. npm Package (`@financedistrict/fdx`)
61
+
62
+ The npm package includes:
63
+
64
+ - **CLI Tool** (`bin/fdx.js`): Command-line interface for setup, status checks, and method invocation
65
+ - **SDK** (`src/wallet-client.js`): High-level JavaScript API with typed methods for each MCP tool
66
+ - **OAuth Client** (`src/mcp-auth.js`): RFC 7591 Dynamic Client Registration with PKCE
67
+ - **MCP Client** (`src/mcp-client.js`): JSON-RPC 2.0 protocol handler with SSE response format
68
+
69
+ ### 2. Finance District MCP Server
70
+
71
+ The remote server (hosted at fd.xyz) provides:
72
+
73
+ - **Authentication**: OAuth 2.1 with Microsoft Entra ID (no local keys)
74
+ - **Smart Accounts**: EVM account abstraction via ERC-4337, deterministic Solana addresses
75
+ - **Multi-Chain Support**: Single interface for ETH, BSC, ARB, BASE, SOL
76
+ - **DeFi Integration**: Swap tokens via DEX aggregators, earn yield via Aave/Compound/Yearn
77
+ - **Payment Protocol**: X-402 payment authorization for premium API access
78
+
79
+ ## Data Flow
80
+
81
+ ### Example: Agent Checks Wallet Balance
82
+
83
+ 1. **User asks agent**: _"What's my ETH balance?"_
84
+ 2. **Agent invokes CLI**: `fdx call getWalletOverview --chainKey ethereum`
85
+ 3. **CLI loads SDK**: `bin/commands/call.js` → `src/wallet-client.js` → `getWalletOverview()`
86
+ 4. **SDK authenticates**: Reads OAuth tokens from `~/.fdx/auth.json`
87
+ 5. **SDK calls MCP**: HTTPS request to `mcp.fd.xyz` with JSON-RPC 2.0 payload
88
+ 6. **Server queries chain**: Fetches balances from Ethereum RPC nodes
89
+ 7. **Response flows back**: JSON → SDK → CLI → stdout → Agent reads JSON
90
+ 8. **Agent formats answer**: _"You have 0.42 ETH in your Ethereum wallet (0xABC...)"_
91
+
92
+ ## Authentication Flow
93
+
94
+ ### First-Time Setup (`fdx setup`)
95
+
96
+ 1. **Generate PKCE challenge**: `S256(random_verifier)` → code_challenge
97
+ 2. **Register OAuth client**: POST to `/oauth/register` (RFC 7591 DCR)
98
+ 3. **Open browser**: User grants consent via Microsoft Entra ID
99
+ 4. **Exchange code**: Authorization code + verifier → access_token + refresh_token
100
+ 5. **Store tokens**: Save to `~/.fdx/auth.json`
101
+
102
+ ### Subsequent Requests
103
+
104
+ 1. **Load tokens**: Read from `~/.fdx/auth.json`
105
+ 2. **Check expiry**: If `access_token` expired, use `refresh_token` to get new token
106
+ 3. **Attach header**: `Authorization: Bearer <access_token>`
107
+ 4. **Make request**: HTTPS + JSON-RPC to MCP server
108
+
109
+ No private keys, no seed phrases. All wallet operations are server-side with user consent via OAuth.
110
+
111
+ ## Security Model
112
+
113
+ - **No Local Keys**: Agent never touches private keys. Wallets are managed server-side.
114
+ - **OAuth 2.1**: Industry-standard authentication with PKCE protection.
115
+ - **Consent-Based**: User authorizes agent via web browser on first setup.
116
+ - **Token Refresh**: Long-lived refresh tokens minimize re-authentication.
117
+ - **Smart Accounts**: ERC-4337 account abstraction allows multi-signature, recovery, upgradability.
118
+ - **Audit Trail**: All transactions are recorded on-chain with transparent history.
119
+
120
+ ## Multi-Chain Support
121
+
122
+ FDX abstracts chain differences behind a single API:
123
+
124
+ | Chain | Chain ID | Network Key | Address Format |
125
+ | -------- | -------- | ----------- | -------------- |
126
+ | Ethereum | 1 | ethereum | 0x... |
127
+ | BSC | 56 | bsc | 0x... |
128
+ | Arbitrum | 42161 | arbitrum | 0x... |
129
+ | Base | 8453 | base | 0x... |
130
+ | Solana | (CAIP-2) | solana | base58 |
131
+
132
+ ## DeFi Integration
133
+
134
+ The MCP server integrates with DeFi protocols:
135
+
136
+ - **DEX Aggregation**: 1inch, 0x, Jupiter (Solana) for best swap routes
137
+ - **Yield Strategies**: Aave (lending), Compound (lending), Yearn (vaults)
138
+ - **Smart Routing**: Policy-driven swap execution (BestExecution, LowGas, MevProtected)
139
+
140
+ Agents can discover strategies, deposit tokens, and withdraw yield — all through `fdx call`.
141
+
142
+ ## Development & Testing
143
+
144
+ See [DEVELOPMENT.md](DEVELOPMENT.md) for running from source.
145
+
146
+ ## License
147
+
148
+ MIT
@@ -0,0 +1,147 @@
1
+ # Development Guide
2
+
3
+ Step-by-step instructions to set up FDX for local development on any machine.
4
+
5
+ ---
6
+
7
+ ## Prerequisites
8
+
9
+ - Node.js >= 18
10
+ - Git
11
+ - A browser for the OAuth consent flow
12
+
13
+ ---
14
+
15
+ ## 1. Clone the repo
16
+
17
+ ```bash
18
+ git clone https://github.com/financedistrict-platform/fd-agent-wallet-cli.git
19
+ cd fd-agent-wallet-cli
20
+ ```
21
+
22
+ ## 2. Install dependencies and link the CLI
23
+
24
+ ```bash
25
+ npm install
26
+ npm link
27
+ ```
28
+
29
+ Verify the `fdx` command is available:
30
+
31
+ ```bash
32
+ fdx
33
+ ```
34
+
35
+ Should print usage info with `setup`, `status`, `call` commands.
36
+
37
+ ## 3. Run setup
38
+
39
+ ```bash
40
+ fdx setup
41
+ ```
42
+
43
+ This will:
44
+
45
+ - Discover the OAuth server
46
+ - Register a client dynamically (first time only)
47
+ - Print an authorization URL — open it in your browser
48
+ - Wait for the OAuth callback on `localhost:6260`
49
+ - Exchange the code for tokens
50
+ - Write tokens to `~/.fdx/auth.json`
51
+
52
+ If the machine is headless and you can't open a browser, run `fdx setup` on your local machine instead, then copy the token file:
53
+
54
+ ```bash
55
+ # From local machine
56
+ scp ~/.fdx/auth.json user@server:~/.fdx/auth.json
57
+ ssh user@server "chmod 600 ~/.fdx/auth.json"
58
+ ```
59
+
60
+ ## 4. Verify the CLI works
61
+
62
+ ```bash
63
+ fdx status
64
+ fdx call getMyInfo
65
+ fdx call getAppVersion
66
+ ```
67
+
68
+ All three should return data. If `fdx call` fails with auth errors, run `fdx setup` again.
69
+
70
+ ## 5. Environment Variables
71
+
72
+ | Variable | Description | Default |
73
+ | ------------------ | ------------------ | -------------------------------------- |
74
+ | `FDX_MCP_SERVER` | MCP server URL | `https://mcp.fd.xyz` |
75
+ | `FDX_REDIRECT_URI` | OAuth callback URI | `http://localhost:6260/oauth/callback` |
76
+ | `FDX_STORE_PATH` | Token store path | `~/.fdx/auth.json` |
77
+ | `FDX_LOG_PATH` | Log file path | `~/.fdx/fdx.log` |
78
+ | `FDX_LOG_LEVEL` | Log verbosity (`debug`\|`info`\|`warn`\|`error`\|`off`) | `info` |
79
+
80
+ You can set these inline before a command, as persistent shell exports, or via a `.env` file in the working directory (see `.env.example`). The `.env` file is gitignored so values never end up in the repository.
81
+
82
+ ## 6. Testing against a non-production environment
83
+
84
+ The CLI defaults to the production server. To point it at a different environment, override `FDX_MCP_SERVER` — the test server address is shared privately with the test team and must never be committed to the repository.
85
+
86
+ **Option A — inline (one command):**
87
+
88
+ ```bash
89
+ FDX_MCP_SERVER=https://... fdx setup --device
90
+ FDX_MCP_SERVER=https://... fdx call getMyInfo
91
+ ```
92
+
93
+ **Option B — shell export (current session):**
94
+
95
+ ```bash
96
+ export FDX_MCP_SERVER=https://...
97
+ fdx setup --device
98
+ fdx call getMyInfo
99
+ ```
100
+
101
+ **Option C — `.env` file (persistent, local only):**
102
+
103
+ ```bash
104
+ cp .env.example .env
105
+ # edit .env and uncomment FDX_MCP_SERVER=https://...
106
+ fdx setup --device
107
+ ```
108
+
109
+ The `.env` file is loaded automatically when the `fdx` binary starts. It is gitignored — do not commit it.
110
+
111
+ ---
112
+
113
+ ## Updating after code changes
114
+
115
+ When changes are pushed to the repo:
116
+
117
+ ```bash
118
+ cd fd-agent-wallet-cli
119
+ git pull
120
+ npm install
121
+ npm link
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Running Tests
127
+
128
+ ```bash
129
+ npm test
130
+ ```
131
+
132
+ ## Linting
133
+
134
+ ```bash
135
+ npm run lint
136
+ npm run lint:fix # auto-fix
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Troubleshooting
142
+
143
+ | Problem | Fix |
144
+ | ------------------------- | ----------------------------------------------------------------------------- |
145
+ | `fdx: command not found` | Run `npm link` in the repo directory |
146
+ | Auth errors on `fdx call` | Run `fdx setup` to re-authenticate |
147
+ | Token expired | Auto-refreshes via refresh token. If that also expired, run `fdx setup` again |
@@ -0,0 +1,57 @@
1
+ # Uninstall
2
+
3
+ Remove FDX and all associated data.
4
+
5
+ ## Steps
6
+
7
+ ### For npm install users
8
+
9
+ ```bash
10
+ # 1. Remove auth tokens and config
11
+ rm -rf ~/.fdx
12
+
13
+ # 2. Uninstall the package globally
14
+ npm uninstall -g @financedistrict/fdx
15
+ ```
16
+
17
+ ### For development users (git clone)
18
+
19
+ If you installed from source:
20
+
21
+ ```bash
22
+ # 1. Remove auth tokens and config
23
+ rm -rf ~/.fdx
24
+
25
+ # 2. Unlink the CLI
26
+ npm unlink -g @financedistrict/fdx
27
+
28
+ # 3. Remove the repo
29
+ rm -rf fd-agent-wallet-cli # or wherever you cloned it
30
+ ```
31
+
32
+ ### OS Credential Store Cleanup
33
+
34
+ If you used FDX with an OS credential store, secrets may persist in the
35
+ system keychain after removing `~/.fdx`. Clean them up manually:
36
+
37
+ **macOS (Keychain):**
38
+
39
+ ```bash
40
+ security delete-generic-password -s fdx-wallet
41
+ ```
42
+
43
+ **Linux (libsecret):**
44
+
45
+ ```bash
46
+ secret-tool clear service fdx-wallet
47
+ ```
48
+
49
+ **Windows:** Credentials are stored via DPAPI in `~/.fdx/.cred_*` files and
50
+ are removed when you delete the `~/.fdx` directory. No additional cleanup
51
+ is needed.
52
+
53
+ ## Verify
54
+
55
+ ```bash
56
+ which fdx # should return nothing
57
+ ```
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@financedistrict/fdx",
3
+ "version": "0.1.0",
4
+ "description": "Agent wallet CLI — a command-line interface to the Finance District MCP wallet server",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "fdx": "./bin/fdx.js"
8
+ },
9
+ "scripts": {
10
+ "test": "node --test test/**/*.test.js",
11
+ "lint": "eslint .",
12
+ "lint:fix": "eslint . --fix",
13
+ "format": "prettier --write ."
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/financedistrict-platform/fd-agent-wallet-cli.git"
18
+ },
19
+ "keywords": [
20
+ "fdx",
21
+ "finance-district",
22
+ "wallet",
23
+ "mcp",
24
+ "crypto",
25
+ "defi",
26
+ "agent",
27
+ "cli"
28
+ ],
29
+ "author": "Finance District",
30
+ "license": "MIT",
31
+ "bugs": {
32
+ "url": "https://github.com/financedistrict-platform/fd-agent-wallet-cli/issues"
33
+ },
34
+ "homepage": "https://github.com/financedistrict-platform/fd-agent-wallet-cli#readme",
35
+ "dependencies": {
36
+ "@modelcontextprotocol/sdk": "^1.26.0",
37
+ "axios": "^1.13.5",
38
+ "commander": "^14.0.3",
39
+ "dotenv": "^16.6.1",
40
+ "nanospinner": "^1.2.2",
41
+ "picocolors": "^1.1.1"
42
+ },
43
+ "devDependencies": {
44
+ "eslint": "^8.57.1",
45
+ "eslint-config-prettier": "^10.1.8",
46
+ "eslint-plugin-import": "^2.32.0",
47
+ "eslint-plugin-prettier": "^5.5.5",
48
+ "prettier": "^3.8.1"
49
+ },
50
+ "type": "commonjs",
51
+ "engines": {
52
+ "node": ">=18.0.0"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public"
56
+ }
57
+ }
@@ -0,0 +1,226 @@
1
+ 'use strict';
2
+
3
+ const { execFileSync } = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const SERVICE = 'fdx-wallet';
8
+ const TIMEOUT = 10000;
9
+
10
+ let _supported;
11
+
12
+ /**
13
+ * Check if the OS credential store is available on this platform.
14
+ * - macOS: Keychain via `security` CLI
15
+ * - Linux: libsecret via `secret-tool` CLI
16
+ * - Windows: DPAPI via PowerShell
17
+ */
18
+ function isSupported() {
19
+ if (_supported !== undefined) return _supported;
20
+
21
+ try {
22
+ switch (process.platform) {
23
+ case 'darwin':
24
+ execFileSync('security', ['help'], { stdio: 'pipe', timeout: TIMEOUT });
25
+ _supported = true;
26
+ break;
27
+ case 'linux':
28
+ execFileSync('secret-tool', ['--help'], { stdio: 'pipe', timeout: TIMEOUT });
29
+ _supported = true;
30
+ break;
31
+ case 'win32':
32
+ execFileSync('powershell', ['-NoProfile', '-NonInteractive', '-Command', '$null'], {
33
+ stdio: 'pipe',
34
+ timeout: TIMEOUT,
35
+ });
36
+ _supported = true;
37
+ break;
38
+ default:
39
+ _supported = false;
40
+ }
41
+ } catch {
42
+ _supported = false;
43
+ }
44
+
45
+ return _supported;
46
+ }
47
+
48
+ /**
49
+ * Retrieve a secret from the OS credential store.
50
+ * @param {string} account - Unique account identifier (e.g. MCP server host).
51
+ * @returns {string|null} The secret string, or null if not found / not supported.
52
+ */
53
+ function getSecret(account) {
54
+ if (!isSupported()) return null;
55
+
56
+ try {
57
+ switch (process.platform) {
58
+ case 'darwin':
59
+ return execFileSync(
60
+ 'security',
61
+ ['find-generic-password', '-a', account, '-s', SERVICE, '-w'],
62
+ { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: TIMEOUT },
63
+ ).trim();
64
+
65
+ case 'linux':
66
+ return execFileSync('secret-tool', ['lookup', 'service', SERVICE, 'account', account], {
67
+ encoding: 'utf8',
68
+ stdio: ['pipe', 'pipe', 'pipe'],
69
+ timeout: TIMEOUT,
70
+ }).trim();
71
+
72
+ case 'win32':
73
+ return winDecrypt(account);
74
+
75
+ default:
76
+ return null;
77
+ }
78
+ } catch {
79
+ return null;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Store a secret in the OS credential store.
85
+ * @param {string} account - Unique account identifier.
86
+ * @param {string} secret - The secret value to store.
87
+ * @returns {boolean} True if stored successfully, false otherwise.
88
+ */
89
+ function setSecret(account, secret) {
90
+ if (!isSupported()) return false;
91
+
92
+ try {
93
+ switch (process.platform) {
94
+ case 'darwin':
95
+ // Delete existing entry first (add-generic-password -U can be unreliable)
96
+ try {
97
+ execFileSync('security', ['delete-generic-password', '-a', account, '-s', SERVICE], {
98
+ stdio: 'pipe',
99
+ timeout: TIMEOUT,
100
+ });
101
+ } catch {
102
+ // Entry may not exist — ignore
103
+ }
104
+ // Use -w with stdin to avoid leaking the secret in process arguments
105
+ execFileSync('security', ['add-generic-password', '-a', account, '-s', SERVICE, '-w'], {
106
+ input: secret,
107
+ stdio: ['pipe', 'pipe', 'pipe'],
108
+ timeout: TIMEOUT,
109
+ });
110
+ return true;
111
+
112
+ case 'linux':
113
+ execFileSync(
114
+ 'secret-tool',
115
+ ['store', '--label', `FDX (${account})`, 'service', SERVICE, 'account', account],
116
+ { input: secret, stdio: ['pipe', 'pipe', 'pipe'], timeout: TIMEOUT },
117
+ );
118
+ return true;
119
+
120
+ case 'win32':
121
+ winEncrypt(account, secret);
122
+ return true;
123
+
124
+ default:
125
+ return false;
126
+ }
127
+ } catch {
128
+ return false;
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Delete a secret from the OS credential store.
134
+ * @param {string} account - Unique account identifier.
135
+ */
136
+ function deleteSecret(account) {
137
+ if (!isSupported()) return;
138
+
139
+ try {
140
+ switch (process.platform) {
141
+ case 'darwin':
142
+ execFileSync('security', ['delete-generic-password', '-a', account, '-s', SERVICE], {
143
+ stdio: 'pipe',
144
+ timeout: TIMEOUT,
145
+ });
146
+ break;
147
+
148
+ case 'linux':
149
+ execFileSync('secret-tool', ['clear', 'service', SERVICE, 'account', account], {
150
+ stdio: 'pipe',
151
+ timeout: TIMEOUT,
152
+ });
153
+ break;
154
+
155
+ case 'win32':
156
+ winDeleteFile(account);
157
+ break;
158
+ }
159
+ } catch {
160
+ // Credential may not exist — ignore
161
+ }
162
+ }
163
+
164
+ // --- Windows DPAPI helpers ---
165
+ // Tokens are encrypted with the current user's Windows login credentials
166
+ // and stored in a file under ~/.fdx/
167
+
168
+ function dpapiPath(account) {
169
+ const crypto = require('crypto');
170
+ const os = require('os');
171
+ const hash = crypto.createHash('sha256').update(account).digest('hex').slice(0, 16);
172
+ return path.join(os.homedir(), '.fdx', `.cred_${hash}`);
173
+ }
174
+
175
+ function winEncrypt(account, plaintext) {
176
+ const script = [
177
+ 'Add-Type -AssemblyName System.Security',
178
+ '$bytes = [Text.Encoding]::UTF8.GetBytes([Console]::In.ReadToEnd())',
179
+ '$enc = [Security.Cryptography.ProtectedData]::Protect(' +
180
+ '$bytes, $null, [Security.Cryptography.DataProtectionScope]::CurrentUser)',
181
+ '[Convert]::ToBase64String($enc)',
182
+ ].join('; ');
183
+
184
+ const encrypted = execFileSync(
185
+ 'powershell',
186
+ ['-NoProfile', '-NonInteractive', '-Command', script],
187
+ { input: plaintext, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: TIMEOUT },
188
+ ).trim();
189
+
190
+ const filePath = dpapiPath(account);
191
+ fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
192
+ fs.writeFileSync(filePath, encrypted, { mode: 0o600 });
193
+ }
194
+
195
+ function winDecrypt(account) {
196
+ const filePath = dpapiPath(account);
197
+ if (!fs.existsSync(filePath)) return null;
198
+
199
+ const encrypted = fs.readFileSync(filePath, 'utf8').trim();
200
+ if (!encrypted) return null;
201
+
202
+ const script = [
203
+ 'Add-Type -AssemblyName System.Security',
204
+ '$bytes = [Convert]::FromBase64String([Console]::In.ReadToEnd())',
205
+ '$dec = [Security.Cryptography.ProtectedData]::Unprotect(' +
206
+ '$bytes, $null, [Security.Cryptography.DataProtectionScope]::CurrentUser)',
207
+ '[Text.Encoding]::UTF8.GetString($dec)',
208
+ ].join('; ');
209
+
210
+ return execFileSync('powershell', ['-NoProfile', '-NonInteractive', '-Command', script], {
211
+ input: encrypted,
212
+ encoding: 'utf8',
213
+ stdio: ['pipe', 'pipe', 'pipe'],
214
+ timeout: TIMEOUT,
215
+ }).trim();
216
+ }
217
+
218
+ function winDeleteFile(account) {
219
+ try {
220
+ fs.unlinkSync(dpapiPath(account));
221
+ } catch {
222
+ // File may not exist — ignore
223
+ }
224
+ }
225
+
226
+ module.exports = { isSupported, getSecret, setSecret, deleteSecret };
package/src/factory.js ADDED
@@ -0,0 +1,29 @@
1
+ const os = require('os');
2
+ const path = require('path');
3
+
4
+ const { WalletClient } = require('./wallet-client');
5
+
6
+ const DEFAULT_MCP_SERVER = 'https://mcp.fd.xyz';
7
+
8
+ function createClientFromEnv() {
9
+ const mcpServerUrl = process.env.FDX_MCP_SERVER || DEFAULT_MCP_SERVER;
10
+ const redirectUri = process.env.FDX_REDIRECT_URI;
11
+ const storePath = process.env.FDX_STORE_PATH;
12
+
13
+ const parsed = new URL(mcpServerUrl);
14
+ if (
15
+ parsed.protocol !== 'https:' &&
16
+ parsed.hostname !== 'localhost' &&
17
+ parsed.hostname !== '127.0.0.1'
18
+ ) {
19
+ throw new Error('FDX_MCP_SERVER must use HTTPS (HTTP is only allowed for localhost)');
20
+ }
21
+
22
+ return new WalletClient({
23
+ mcpServerUrl,
24
+ redirectUri: redirectUri || `http://localhost:6260/oauth/callback`,
25
+ storePath: storePath || path.join(os.homedir(), '.fdx', 'auth.json'),
26
+ });
27
+ }
28
+
29
+ module.exports = { createClientFromEnv };
package/src/index.js ADDED
@@ -0,0 +1,11 @@
1
+ const { createClientFromEnv } = require('./factory');
2
+ const { MCPAuthClient } = require('./mcp-auth');
3
+ const { MCPClient } = require('./mcp-client');
4
+ const { WalletClient } = require('./wallet-client');
5
+
6
+ module.exports = {
7
+ MCPAuthClient,
8
+ MCPClient,
9
+ WalletClient,
10
+ createClientFromEnv,
11
+ };