indelible-mcp 2.9.0 → 3.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "indelible-mcp",
3
- "version": "2.9.0",
3
+ "version": "3.0.0",
4
4
  "description": "Blockchain-backed memory and code storage for Claude Code. Save AI conversations and source code permanently on BSV.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.js CHANGED
@@ -223,7 +223,7 @@ Commands:
223
223
 
224
224
  function printHelp() {
225
225
  console.log(`
226
- Indelible MCP — Blockchain memory for Claude Code (v2.9.0)
226
+ Indelible MCP — Blockchain memory for Claude Code (v3.0.0)
227
227
 
228
228
  Setup:
229
229
  indelible-mcp setup --wif=KEY --pin=PIN Import and encrypt your private key
@@ -324,7 +324,7 @@ function readStdin() {
324
324
 
325
325
  const SERVER_INFO = {
326
326
  name: 'indelible',
327
- version: '2.9.0',
327
+ version: '3.0.0',
328
328
  description: 'Blockchain-backed memory and code storage for Claude Code'
329
329
  }
330
330
 
@@ -116,6 +116,37 @@ export async function checkProTier(wif) {
116
116
  return { ok: true, plan: 'unknown' }
117
117
  }
118
118
 
119
+ /**
120
+ * Auto-generate relay API key for SPV bridge access
121
+ * Logs in with address, then calls generate-key endpoint
122
+ * @param {string} address - BSV address
123
+ * @param {string} apiUrl - Optional API URL override
124
+ * @returns {Promise<{key: string, tier: string}|null>}
125
+ */
126
+ export async function ensureRelayKey(address, apiUrl) {
127
+ if (!apiUrl) apiUrl = getApiUrl()
128
+ try {
129
+ const authRes = await fetch(`${apiUrl}/api/auth/wif`, {
130
+ method: 'POST',
131
+ headers: { 'Content-Type': 'application/json' },
132
+ body: JSON.stringify({ address }),
133
+ signal: AbortSignal.timeout(10000)
134
+ })
135
+ if (!authRes.ok) return null
136
+ const { token } = await authRes.json()
137
+ if (!token) return null
138
+ const keyRes = await fetch(`${apiUrl}/api/relay/generate-key`, {
139
+ method: 'POST',
140
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
141
+ body: JSON.stringify({}),
142
+ signal: AbortSignal.timeout(10000)
143
+ })
144
+ if (!keyRes.ok) return null
145
+ const keyData = await keyRes.json()
146
+ return { key: keyData.key, tier: keyData.tier }
147
+ } catch { return null }
148
+ }
149
+
119
150
  /**
120
151
  * Check if server is reachable
121
152
  */
package/src/lib/spv.js CHANGED
@@ -8,33 +8,66 @@
8
8
  */
9
9
 
10
10
  import { Transaction, P2PKH, PrivateKey, SatoshisPerKilobyte, LockingScript, OP } from '@bsv/sdk'
11
- import { loadConfig } from './config.js'
12
-
13
- const DEFAULT_SPV_BRIDGES = [
14
- { url: 'http://45.76.19.199:8080', name: 'SPV Relay 1' },
15
- { url: 'http://144.202.50.135:8080', name: 'SPV Relay 2' },
16
- { url: 'http://155.138.254.224:8080', name: 'SPV Relay 3' },
17
- { url: 'http://107.191.49.18:8080', name: 'SPV Relay 4' },
18
- { url: 'http://155.138.216.126:8080', name: 'SPV Relay 5' },
11
+ import { loadConfig, saveConfig } from './config.js'
12
+
13
+ const GATEWAY_URL = 'http://155.138.238.167:8080'
14
+ const GATEWAY_BRIDGE = { url: GATEWAY_URL, name: 'relay-gateway' }
15
+
16
+ // Old individual bridge IPs used to detect configs that need migration
17
+ const OLD_BRIDGE_IPS = [
18
+ '45.76.19.199', '107.191.49.18', '155.138.254.224',
19
+ '144.202.50.135', '155.138.216.126'
19
20
  ]
20
21
 
21
- function getBridges() {
22
+ let migrationDone = false
23
+
24
+ async function getBridges() {
22
25
  const config = loadConfig()
23
- const apiKey = config?.api_key || ''
26
+ let apiKey = config?.spv_api_key || ''
27
+ let bridges = config?.spv_bridges || []
28
+
29
+ // Auto-migrate: detect old individual bridge IPs → swap to gateway
30
+ if (!migrationDone && bridges.length > 0) {
31
+ const hasOldIps = bridges.some(b => {
32
+ const url = typeof b === 'string' ? b : b.url
33
+ return OLD_BRIDGE_IPS.some(ip => url.includes(ip))
34
+ })
35
+ if (hasOldIps) {
36
+ bridges = [GATEWAY_BRIDGE]
37
+ config.spv_bridges = bridges
38
+ // Auto-generate relay key if missing
39
+ if (!apiKey || !apiKey.startsWith('relay_sk_')) {
40
+ try {
41
+ const { ensureRelayKey } = await import('./api-client.js')
42
+ const address = config?.address
43
+ if (address) {
44
+ const result = await ensureRelayKey(address)
45
+ if (result) {
46
+ apiKey = result.key
47
+ config.spv_api_key = apiKey
48
+ }
49
+ }
50
+ } catch { /* backend unreachable — save bridge migration, skip key */ }
51
+ }
52
+ saveConfig(config)
53
+ migrationDone = true
54
+ }
55
+ }
24
56
 
25
- if (config?.spv_bridges && config.spv_bridges.length > 0) {
26
- return config.spv_bridges.map(b => ({
57
+ if (bridges.length > 0) {
58
+ return bridges.map(b => ({
27
59
  url: typeof b === 'string' ? b : b.url,
28
60
  name: typeof b === 'string' ? b : (b.name || b.url),
29
61
  apiKey
30
62
  }))
31
63
  }
32
64
 
33
- return DEFAULT_SPV_BRIDGES.map(b => ({ ...b, apiKey }))
65
+ // No config use gateway default
66
+ return [{ ...GATEWAY_BRIDGE, apiKey }]
34
67
  }
35
68
 
36
69
  async function spvFetch(path, options = {}) {
37
- const bridges = getBridges()
70
+ const bridges = await getBridges()
38
71
  let lastError = null
39
72
 
40
73
  for (const bridge of bridges) {
@@ -73,9 +106,16 @@ export async function checkHealth() {
73
106
  }
74
107
 
75
108
  export async function getUtxos(address) {
76
- const res = await spvFetch(`/api/address/${address}/unspent`)
77
- if (!res.ok) throw new Error(`Failed to get UTXOs: ${res.status}`)
78
- return res.json()
109
+ try {
110
+ const res = await spvFetch(`/api/address/${address}/unspent`)
111
+ if (res.ok) return res.json()
112
+ } catch { /* bridge unavailable */ }
113
+
114
+ // WoC lookup for address-based indexing
115
+ const wocRes = await fetch(`https://api.whatsonchain.com/v1/bsv/main/address/${address}/unspent`)
116
+ if (wocRes.ok) return wocRes.json()
117
+
118
+ throw new Error(`Failed to get UTXOs for ${address}`)
79
119
  }
80
120
 
81
121
  export async function getAddressHistory(address) {
@@ -104,7 +144,7 @@ export async function getRawTxHex(txid) {
104
144
  }
105
145
 
106
146
  export async function broadcastTx(rawTxHex) {
107
- const bridges = getBridges()
147
+ const bridges = await getBridges()
108
148
  let firstSuccess = null
109
149
  const errors = []
110
150
 
@@ -10,7 +10,7 @@
10
10
  import { PrivateKey } from '@bsv/sdk'
11
11
  import { loadConfig, saveConfig, getConfigPath } from '../lib/config.js'
12
12
  import { sha256, encryptWif } from '../lib/crypto.js'
13
- import { register, checkConnection } from '../lib/api-client.js'
13
+ import { register, checkConnection, ensureRelayKey } from '../lib/api-client.js'
14
14
  import { setPassword } from 'cross-keychain'
15
15
 
16
16
  const DEFAULT_API_URL = 'https://indelible.one'
@@ -84,6 +84,8 @@ export async function setupWallet(apiUrl = DEFAULT_API_URL, importWif, pin) {
84
84
  address,
85
85
  api_key: apiKey,
86
86
  api_url: apiUrl,
87
+ spv_bridges: [{ url: 'http://155.138.238.167:8080', name: 'relay-gateway' }],
88
+ spv_api_key: '',
87
89
  created_at: new Date().toISOString()
88
90
  }
89
91
 
@@ -105,7 +107,16 @@ export async function setupWallet(apiUrl = DEFAULT_API_URL, importWif, pin) {
105
107
  registered = true
106
108
  } catch { /* will register on first use */ }
107
109
 
110
+ // Auto-generate relay API key
111
+ let relayKey = null
108
112
  const serverConnected = await checkConnection()
113
+ if (serverConnected) {
114
+ relayKey = await ensureRelayKey(address, apiUrl)
115
+ if (relayKey) {
116
+ config.spv_api_key = relayKey.key
117
+ saveConfig(config)
118
+ }
119
+ }
109
120
 
110
121
  return {
111
122
  success: true,
@@ -114,6 +125,7 @@ export async function setupWallet(apiUrl = DEFAULT_API_URL, importWif, pin) {
114
125
  keychainStored,
115
126
  registered,
116
127
  serverConnected,
117
- message: `Wallet imported and encrypted! Address: ${address}.${keychainStored ? ' PIN stored in OS keychain.' : ' Set INDELIBLE_PIN env var for MCP mode.'}${registered ? ' Registered with Indelible.' : ' Will register on first save.'}`
128
+ relayTier: relayKey?.tier || null,
129
+ message: `Wallet imported and encrypted! Address: ${address}.${keychainStored ? ' PIN stored in OS keychain.' : ' Set INDELIBLE_PIN env var for MCP mode.'}${registered ? ' Registered with Indelible.' : ' Will register on first save.'}${relayKey ? ` Relay key generated (${relayKey.tier}).` : ''}`
118
130
  }
119
131
  }