m2m-sentinel-sdk 1.0.2 → 1.0.4
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 +44 -28
- package/agent_adapter.js +30 -0
- package/eliza_plugin.js +84 -0
- package/index.js +136 -19
- package/m2m_sentinel_sdk.js +1 -12
- package/mcp_server.js +172 -0
- package/package.json +26 -9
- package/typescript/index.ts +135 -35
- package/typescript/package.json +4 -3
- package/cli/m2m_cli.js +0 -25
- package/csharp/M2MSentinel.csproj +0 -9
- package/csharp/M2MSentinelClient.cs +0 -67
- package/go/client.go +0 -94
- package/go/go.mod +0 -3
- package/java/M2MSentinelClient.java +0 -67
- package/java/pom.xml +0 -20
- package/java/src/main/java/com/m2msentinel/M2MSentinelClient.java +0 -69
- package/kotlin/M2MSentinelClient.kt +0 -59
- package/kotlin/build.gradle.kts +0 -24
- package/m2m_sentinel/__init__.py +0 -3
- package/m2m_sentinel/client.py +0 -16
- package/m2m_sentinel_sdk.py +0 -9
- package/php/M2MSentinelClient.php +0 -52
- package/php/composer.json +0 -20
- package/python/dist/m2m_sentinel-0.1.0-py3-none-any.whl +0 -0
- package/python/dist/m2m_sentinel-0.1.0.tar.gz +0 -0
- package/python/dist/m2m_sentinel-0.1.1-py3-none-any.whl +0 -0
- package/python/dist/m2m_sentinel-0.1.1.tar.gz +0 -0
- package/python/m2m_sentinel/__pycache__/client.cpython-314.pyc +0 -0
- package/python/m2m_sentinel/client.py +0 -40
- package/python/m2m_sentinel.egg-info/PKG-INFO +0 -35
- package/python/m2m_sentinel.egg-info/SOURCES.txt +0 -5
- package/python/m2m_sentinel.egg-info/dependency_links.txt +0 -1
- package/python/m2m_sentinel.egg-info/top_level.txt +0 -1
- package/python/setup.py +0 -35
- package/ruby/m2m_sentinel.gemspec +0 -17
- package/ruby/m2m_sentinel.rb +0 -55
- package/rust/Cargo.toml +0 -11
- package/rust/src/lib.rs +0 -61
- package/swift/M2MSentinelClient.swift +0 -65
- package/swift/Package.swift +0 -23
package/README.md
CHANGED
|
@@ -1,43 +1,59 @@
|
|
|
1
|
-
#
|
|
1
|
+
# M2M Sentinel SDKs
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Base URL: `https://m2msentinel.vercel.app`.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
## Support status
|
|
6
|
+
|
|
7
|
+
Supported in this repository:
|
|
8
|
+
- JavaScript/Node: `public/sdk/index.js` (`m2m_sentinel_sdk.js` re-exports it)
|
|
9
|
+
- TypeScript source: `public/sdk/typescript/index.ts`
|
|
10
|
+
- Python source: `public/sdk/m2m_sentinel/` and `public/sdk/python/m2m_sentinel/`
|
|
11
|
+
- MCP stdio server: `public/sdk/mcp_server.js`
|
|
12
|
+
|
|
13
|
+
Community preview only: Go, Rust, Java, Kotlin, Swift, C#, PHP, Ruby, CLI, Eliza plugin, and agent adapter. They include current route/header examples but are not covered by the supported-SDK guarantee; verify against `/openapi.json` before production use.
|
|
7
14
|
|
|
8
15
|
## Installation
|
|
9
16
|
|
|
17
|
+
Install from source (recommended for current `v1.0.4` build):
|
|
18
|
+
|
|
10
19
|
```bash
|
|
11
|
-
|
|
20
|
+
# JavaScript / Node.js (local path)
|
|
21
|
+
node -e "const { M2MSentinelClient } = require('./public/sdk')"
|
|
22
|
+
|
|
23
|
+
# Python (local path)
|
|
24
|
+
python -m pip install ./public/sdk/python
|
|
12
25
|
```
|
|
13
26
|
|
|
14
|
-
|
|
27
|
+
*Note on Registry Packages (`v1.0.4`)*:
|
|
28
|
+
Once published to npm and PyPI (via `npm publish` and `twine upload`), install directly via the package manager:
|
|
29
|
+
- **npm registry**: `m2m-sentinel-sdk`
|
|
30
|
+
- **PyPI registry**: `m2m-sentinel`
|
|
15
31
|
|
|
16
|
-
|
|
17
|
-
const { M2MSentinelClient } = require('m2m-sentinel-sdk');
|
|
32
|
+
## Authentication
|
|
18
33
|
|
|
19
|
-
|
|
34
|
+
API keys must be sent only in headers:
|
|
20
35
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
console.log('Contract Risk Audit:', audit);
|
|
36
|
+
```text
|
|
37
|
+
x-api-key: <key>
|
|
38
|
+
```
|
|
25
39
|
|
|
26
|
-
|
|
27
|
-
const whales = await client.getWhaleSignals();
|
|
28
|
-
console.log('Whale Signals:', whales);
|
|
29
|
-
}
|
|
40
|
+
`Authorization: Bearer <key>` is accepted by the API, but the supported SDKs use `x-api-key`. Never place credentials in URLs or query strings; the server rejects them with HTTP 401.
|
|
30
41
|
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
## x402 / 402 behavior
|
|
43
|
+
|
|
44
|
+
Paid endpoints accept either `x-api-key` or a settled x402 v2 payment. Without credentials they return HTTP 402 and a `PAYMENT-REQUIRED` header containing a JSON challenge. Supported SDKs raise/return a payment-required error that exposes the parsed challenge. After settlement, retry with `PAYMENT-SIGNATURE`; successful paid responses may include `PAYMENT-RESPONSE` and include `provenance` in the JSON body.
|
|
45
|
+
|
|
46
|
+
## Free-tier signup
|
|
47
|
+
|
|
48
|
+
1. `POST /v1/subscribe/free/challenge` with `{ "userWallet": "0x..." }`.
|
|
49
|
+
2. Sign `intent.messageToSign` with that wallet.
|
|
50
|
+
3. `POST /v1/subscribe/free/claim` with `{ "intentId": "...", "signature": "..." }`.
|
|
51
|
+
4. Store the returned `apiKey`; it cannot be recovered later.
|
|
52
|
+
|
|
53
|
+
## Current routes
|
|
54
|
+
|
|
55
|
+
Public: `GET /v1/status`, `GET /v1/plans`, free signup, and subscription intent/claim routes.
|
|
33
56
|
|
|
34
|
-
|
|
35
|
-
- **Smart Contract Risk Auditor** (`/v1/audit/:address`)
|
|
36
|
-
- **Real-Time Whale Signals** (`/v1/whales/signals`)
|
|
37
|
-
- **DEX Liquidity Depth & Spreads** (`/v1/dex/metrics`)
|
|
38
|
-
- **AI Web Search Intelligence** (`/v1/search`)
|
|
57
|
+
Protected/paid: `GET /v1/audit/:address`, `GET /v1/security/score/:address`, `GET /v1/gas/fees`, `GET /v1/dex/metrics`, `GET /v1/token/price/:symbol`, `GET /v1/whales/signals`, `GET /v1/keys/self`, `POST /v1/keys/revoke`.
|
|
39
58
|
|
|
40
|
-
|
|
41
|
-
- **Live Portal**: [https://m2msentinel.vercel.app](https://m2msentinel.vercel.app)
|
|
42
|
-
- **API Docs**: [https://m2msentinel.vercel.app/docs.html](https://m2msentinel.vercel.app/docs.html)
|
|
43
|
-
- **OpenAPI Spec**: [https://m2msentinel.vercel.app/openapi.json](https://m2msentinel.vercel.app/openapi.json)
|
|
59
|
+
SDKs preserve response bodies, including `provenance`. They surface 401, 402, 405, 429 (`Retry-After`), and 503 `DATA_SOURCE_UNAVAILABLE` without inventing fallback values.
|
package/agent_adapter.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// UNSUPPORTED / COMMUNITY PREVIEW — not covered by the supported-SDK guarantee; verify against /openapi.json before production use.
|
|
2
|
+
|
|
3
|
+
const { M2MSentinelClient } = require('./index.js');
|
|
4
|
+
|
|
5
|
+
class M2MSentinelAgentTool {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.name = 'm2m_sentinel_security_audit';
|
|
8
|
+
this.description = 'Audits EVM smart contracts on Base for security risks, proxies, and bytecode findings.';
|
|
9
|
+
this.client = new M2MSentinelClient({
|
|
10
|
+
apiKey: options.apiKey || process.env.M2M_SENTINEL_API_KEY,
|
|
11
|
+
baseUrl: options.baseUrl
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async _call(address) {
|
|
16
|
+
try {
|
|
17
|
+
return await this.client.auditContract(address);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
return {
|
|
20
|
+
error: err.name || 'M2MSentinelError',
|
|
21
|
+
message: err.message,
|
|
22
|
+
status: err.status,
|
|
23
|
+
paymentRequired: err.paymentRequired || null,
|
|
24
|
+
retryAfter: err.retryAfter || null
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { M2MSentinelAgentTool };
|
package/eliza_plugin.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// UNSUPPORTED / COMMUNITY PREVIEW — not covered by the supported-SDK guarantee; verify against /openapi.json before production use.
|
|
2
|
+
|
|
3
|
+
const { M2MSentinelClient } = require('./index.js');
|
|
4
|
+
|
|
5
|
+
function clientFor(runtime) {
|
|
6
|
+
const apiKey = runtime?.getSetting?.('M2M_SENTINEL_API_KEY') || process.env.M2M_SENTINEL_API_KEY;
|
|
7
|
+
return new M2MSentinelClient({ apiKey });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function errorText(err) {
|
|
11
|
+
if (err.status === 402) return 'M2M Sentinel payment required. Configure M2M_SENTINEL_API_KEY or settle the x402 challenge.';
|
|
12
|
+
if (err.status === 503 && err.body?.error === 'DATA_SOURCE_UNAVAILABLE') return 'M2M Sentinel data source unavailable; no estimated fallback value is returned.';
|
|
13
|
+
if (err.status === 429) return 'M2M Sentinel rate limited. Retry-After: ' + (err.retryAfter || 'not provided');
|
|
14
|
+
return 'M2M Sentinel API request failed: ' + err.message;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const m2mSentinelPlugin = {
|
|
18
|
+
name: '@elizaos/plugin-m2m-sentinel',
|
|
19
|
+
description: 'Preview ElizaOS actions for M2M Sentinel protected Web3 API routes.',
|
|
20
|
+
actions: [
|
|
21
|
+
{
|
|
22
|
+
name: 'AUDIT_CONTRACT',
|
|
23
|
+
description: 'Audits an EVM contract on Base Mainnet.',
|
|
24
|
+
handler: async (runtime, message) => {
|
|
25
|
+
const address = message.content.text.match(/0x[a-fA-F0-9]{40}/)?.[0];
|
|
26
|
+
if (!address) return { text: 'Please provide a valid 42-character EVM contract address.' };
|
|
27
|
+
try {
|
|
28
|
+
const data = await clientFor(runtime).auditContract(address);
|
|
29
|
+
const audit = data.audit || {};
|
|
30
|
+
const proxy = audit.proxyResolution || {};
|
|
31
|
+
return { text: `M2M Sentinel audit for ${address}: ${audit.securityRating || 'UNKNOWN'}; proxy=${proxy.isProxy ? proxy.proxyType : 'NO'}; provenance=${JSON.stringify(audit.provenance || data.provenance || null)}` };
|
|
32
|
+
} catch (err) { return { text: errorText(err) }; }
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'GET_SECURITY_SCORE',
|
|
37
|
+
description: 'Returns the security score for a Base contract address.',
|
|
38
|
+
handler: async (runtime, message) => {
|
|
39
|
+
const address = message.content.text.match(/0x[a-fA-F0-9]{40}/)?.[0];
|
|
40
|
+
if (!address) return { text: 'Please provide a valid EVM contract address.' };
|
|
41
|
+
try {
|
|
42
|
+
const data = await clientFor(runtime).getSecurityScore(address);
|
|
43
|
+
const score = data.securityScore === null ? 'not decision-grade' : `${data.securityScore}/100`;
|
|
44
|
+
return { text: `M2M Sentinel security score for ${address}: ${score}. Provenance: ${JSON.stringify(data.provenance || null)}` };
|
|
45
|
+
} catch (err) { return { text: errorText(err) }; }
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'GET_DEX_METRICS',
|
|
50
|
+
description: 'Fetches Base DEX metrics.',
|
|
51
|
+
handler: async (runtime) => {
|
|
52
|
+
try { return { text: JSON.stringify(await clientFor(runtime).getDexMetrics()) }; }
|
|
53
|
+
catch (err) { return { text: errorText(err) }; }
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'GET_TOKEN_PRICE',
|
|
58
|
+
description: 'Queries a tracked Base token price.',
|
|
59
|
+
handler: async (runtime, message) => {
|
|
60
|
+
const symbol = (message.content.text.match(/price of ([a-zA-Z0-9]+)/i)?.[1] || 'USDC').toUpperCase();
|
|
61
|
+
try { return { text: JSON.stringify(await clientFor(runtime).getTokenPrice(symbol)) }; }
|
|
62
|
+
catch (err) { return { text: errorText(err) }; }
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'GET_GAS_FEES',
|
|
67
|
+
description: 'Fetches live Base gas fees.',
|
|
68
|
+
handler: async (runtime) => {
|
|
69
|
+
try { return { text: JSON.stringify(await clientFor(runtime).getGasFees()) }; }
|
|
70
|
+
catch (err) { return { text: errorText(err) }; }
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'GET_WHALE_SIGNALS',
|
|
75
|
+
description: 'Fetches whale signals.',
|
|
76
|
+
handler: async (runtime) => {
|
|
77
|
+
try { return { text: JSON.stringify(await clientFor(runtime).getWhaleSignals()) }; }
|
|
78
|
+
catch (err) { return { text: errorText(err) }; }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
module.exports = { m2mSentinelPlugin };
|
package/index.js
CHANGED
|
@@ -1,26 +1,143 @@
|
|
|
1
|
-
const
|
|
1
|
+
const DEFAULT_BASE_URL = 'https://m2msentinel.vercel.app';
|
|
2
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
3
|
+
|
|
4
|
+
class M2MSentinelError extends Error {
|
|
5
|
+
constructor(message, options = {}) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = this.constructor.name;
|
|
8
|
+
this.status = options.status;
|
|
9
|
+
this.body = options.body;
|
|
10
|
+
this.retryAfter = options.retryAfter || null;
|
|
11
|
+
this.paymentRequired = options.paymentRequired || null;
|
|
12
|
+
this.paymentResponse = options.paymentResponse || null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class PaymentRequiredError extends M2MSentinelError {}
|
|
17
|
+
class RateLimitedError extends M2MSentinelError {}
|
|
18
|
+
class DataSourceUnavailableError extends M2MSentinelError {}
|
|
19
|
+
|
|
20
|
+
function parseHeaderJson(value) {
|
|
21
|
+
if (!value) return null;
|
|
22
|
+
try { return JSON.parse(value); } catch (_) { return null; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeOptions(optionsOrApiKey, baseUrl) {
|
|
26
|
+
if (optionsOrApiKey && typeof optionsOrApiKey === 'object') return { ...optionsOrApiKey };
|
|
27
|
+
return { apiKey: optionsOrApiKey || undefined, baseUrl: baseUrl || DEFAULT_BASE_URL };
|
|
28
|
+
}
|
|
2
29
|
|
|
3
30
|
class M2MSentinelClient {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
31
|
+
constructor(optionsOrApiKey, baseUrl) {
|
|
32
|
+
const options = normalizeOptions(optionsOrApiKey, baseUrl);
|
|
33
|
+
this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, '');
|
|
34
|
+
this.apiKey = options.apiKey || undefined;
|
|
35
|
+
this.timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
36
|
+
this.paymentSignature = options.paymentSignature || undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async request(method, path, body, options = {}) {
|
|
40
|
+
const headers = {
|
|
41
|
+
'Accept': 'application/json'
|
|
42
|
+
};
|
|
43
|
+
if (body !== undefined) headers['Content-Type'] = 'application/json';
|
|
44
|
+
const apiKey = options.apiKey || this.apiKey;
|
|
45
|
+
if (apiKey) headers['x-api-key'] = apiKey;
|
|
46
|
+
const paymentSignature = options.paymentSignature || this.paymentSignature;
|
|
47
|
+
if (paymentSignature) headers['PAYMENT-SIGNATURE'] = paymentSignature;
|
|
48
|
+
|
|
49
|
+
const controller = new AbortController();
|
|
50
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs || this.timeoutMs);
|
|
51
|
+
let response;
|
|
52
|
+
try {
|
|
53
|
+
response = await fetch(this.baseUrl + path, {
|
|
54
|
+
method,
|
|
55
|
+
headers,
|
|
56
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
57
|
+
signal: controller.signal
|
|
58
|
+
});
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (err && err.name === 'AbortError') throw new M2MSentinelError('M2M Sentinel request timed out', { status: 0 });
|
|
61
|
+
throw err;
|
|
62
|
+
} finally {
|
|
63
|
+
clearTimeout(timeout);
|
|
14
64
|
}
|
|
15
65
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
66
|
+
const text = await response.text();
|
|
67
|
+
let data = null;
|
|
68
|
+
if (text) {
|
|
69
|
+
try { data = JSON.parse(text); } catch (_) { data = { raw: text }; }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const paymentRequired = parseHeaderJson(response.headers.get('PAYMENT-REQUIRED'));
|
|
73
|
+
const paymentResponse = parseHeaderJson(response.headers.get('PAYMENT-RESPONSE')) || response.headers.get('PAYMENT-RESPONSE');
|
|
74
|
+
if (response.ok) return data;
|
|
75
|
+
|
|
76
|
+
const details = {
|
|
77
|
+
status: response.status,
|
|
78
|
+
body: data,
|
|
79
|
+
retryAfter: response.headers.get('Retry-After'),
|
|
80
|
+
paymentRequired,
|
|
81
|
+
paymentResponse
|
|
82
|
+
};
|
|
83
|
+
const message = data && data.message ? data.message : 'M2M Sentinel HTTP ' + response.status;
|
|
84
|
+
if (response.status === 402) throw new PaymentRequiredError(message, details);
|
|
85
|
+
if (response.status === 429) throw new RateLimitedError(message, details);
|
|
86
|
+
if (response.status === 503 && data && data.error === 'DATA_SOURCE_UNAVAILABLE') throw new DataSourceUnavailableError(message, details);
|
|
87
|
+
throw new M2MSentinelError(message, details);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getStatus() { return this.request('GET', '/v1/status'); }
|
|
91
|
+
getPlans() { return this.request('GET', '/v1/plans'); }
|
|
92
|
+
createFreeChallenge(userWallet) { return this.request('POST', '/v1/subscribe/free/challenge', { userWallet }); }
|
|
93
|
+
claimFreeTier(intentId, signature) { return this.request('POST', '/v1/subscribe/free/claim', { intentId, signature }); }
|
|
94
|
+
createSubscriptionIntent(tier, userWallet) { return this.request('POST', '/v1/subscribe/intents', { tier, userWallet }); }
|
|
95
|
+
claimSubscription(intentId, signature, txHash) { return this.request('POST', '/v1/subscribe/crypto', { intentId, signature, txHash }); }
|
|
96
|
+
auditContract(address, options) { return this.request('GET', '/v1/audit/' + encodeURIComponent(address), undefined, options); }
|
|
97
|
+
getSecurityScore(address, options) { return this.request('GET', '/v1/security/score/' + encodeURIComponent(address), undefined, options); }
|
|
98
|
+
getGasFees(options) { return this.request('GET', '/v1/gas/fees', undefined, options); }
|
|
99
|
+
getDexMetrics(options) { return this.request('GET', '/v1/dex/metrics', undefined, options); }
|
|
100
|
+
getTokenPrice(symbol, options) { return this.request('GET', '/v1/token/price/' + encodeURIComponent(symbol), undefined, options); }
|
|
101
|
+
getWhaleSignals(options) { return this.request('GET', '/v1/whales/signals', undefined, options); }
|
|
102
|
+
getKeySelf() { return this.request('GET', '/v1/keys/self'); }
|
|
103
|
+
revokeKey(confirm = true) { return this.request('POST', '/v1/keys/revoke', { confirm }); }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function createEthersSentinelMiddleware(apiKey, baseUrl) {
|
|
107
|
+
const client = new M2MSentinelClient({ apiKey, baseUrl });
|
|
108
|
+
return {
|
|
109
|
+
client,
|
|
110
|
+
verifyContractBeforeTx: async (targetAddress) => {
|
|
111
|
+
const audit = await client.auditContract(targetAddress);
|
|
112
|
+
const verdict = audit && audit.audit && audit.audit.verdict;
|
|
113
|
+
if (verdict && verdict.securityRating === 'HIGH_RISK') {
|
|
114
|
+
throw new Error('[M2M Sentinel] Transaction blocked: ' + targetAddress + ' flagged HIGH_RISK.');
|
|
115
|
+
}
|
|
116
|
+
return audit;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function createViemSentinelInterceptor(apiKey, baseUrl) {
|
|
122
|
+
const client = new M2MSentinelClient({ apiKey, baseUrl });
|
|
123
|
+
return {
|
|
124
|
+
client,
|
|
125
|
+
inspectSwapTarget: async (address) => {
|
|
126
|
+
const score = await client.getSecurityScore(address);
|
|
127
|
+
if (score && score.decisionGrade && typeof score.securityScore === 'number' && score.securityScore < 50) {
|
|
128
|
+
throw new Error('[M2M Sentinel] Security score too low (' + score.securityScore + '/100) for ' + address);
|
|
129
|
+
}
|
|
130
|
+
return score;
|
|
23
131
|
}
|
|
132
|
+
};
|
|
24
133
|
}
|
|
25
134
|
|
|
26
|
-
module.exports = {
|
|
135
|
+
module.exports = {
|
|
136
|
+
M2MSentinelClient,
|
|
137
|
+
M2MSentinelError,
|
|
138
|
+
PaymentRequiredError,
|
|
139
|
+
RateLimitedError,
|
|
140
|
+
DataSourceUnavailableError,
|
|
141
|
+
createEthersSentinelMiddleware,
|
|
142
|
+
createViemSentinelInterceptor
|
|
143
|
+
};
|
package/m2m_sentinel_sdk.js
CHANGED
|
@@ -1,12 +1 @@
|
|
|
1
|
-
|
|
2
|
-
constructor(apiKey) {
|
|
3
|
-
this.apiKey = apiKey;
|
|
4
|
-
this.baseUrl = 'https://m2msentinel.vercel.app/v1';
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
async getTokenPrice() {
|
|
8
|
-
const response = await fetch(`${this.baseUrl}/token/price`);
|
|
9
|
-
return response.json();
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
module.exports = M2MSentinelSDK;
|
|
1
|
+
module.exports = require('./index.js');
|
package/mcp_server.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const https = require('https');
|
|
5
|
+
const readline = require('readline');
|
|
6
|
+
|
|
7
|
+
const BASE_URL = process.env.M2M_SENTINEL_BASE_URL || 'https://m2msentinel.vercel.app';
|
|
8
|
+
const API_KEY = process.env.M2M_SENTINEL_API_KEY || '';
|
|
9
|
+
const TIMEOUT_MS = Number(process.env.M2M_SENTINEL_TIMEOUT_MS || 30000);
|
|
10
|
+
|
|
11
|
+
const TOOLS = [
|
|
12
|
+
{
|
|
13
|
+
name: 'audit_contract',
|
|
14
|
+
description: 'Audit an EVM smart contract address on Base and return bytecode findings, verdict, score, and provenance.',
|
|
15
|
+
inputSchema: { type: 'object', properties: { address: { type: 'string' } }, required: ['address'] }
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'get_security_score',
|
|
19
|
+
description: 'Return the M2M Sentinel security score and provenance for a Base contract address.',
|
|
20
|
+
inputSchema: { type: 'object', properties: { address: { type: 'string' } }, required: ['address'] }
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'get_gas_fees',
|
|
24
|
+
description: 'Return current Base gas fee data and provenance.',
|
|
25
|
+
inputSchema: { type: 'object', properties: {} }
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'get_dex_metrics',
|
|
29
|
+
description: 'Return tracked Base DEX metrics and provenance.',
|
|
30
|
+
inputSchema: { type: 'object', properties: {} }
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'get_token_price',
|
|
34
|
+
description: 'Return tracked Base token price data and provenance for a symbol such as USDC or WETH.',
|
|
35
|
+
inputSchema: { type: 'object', properties: { symbol: { type: 'string' } }, required: ['symbol'] }
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'get_whale_signals',
|
|
39
|
+
description: 'Return tracked Base whale signals and provenance.',
|
|
40
|
+
inputSchema: { type: 'object', properties: {} }
|
|
41
|
+
}
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
function parseHeaderJson(value) {
|
|
45
|
+
if (!value) return null;
|
|
46
|
+
try { return JSON.parse(value); } catch (_) { return null; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function queryApi(path) {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const url = new URL(path, BASE_URL);
|
|
52
|
+
const headers = { Accept: 'application/json', 'User-Agent': 'M2MSentinel-MCP/1.0.4' };
|
|
53
|
+
if (API_KEY) headers['x-api-key'] = API_KEY;
|
|
54
|
+
|
|
55
|
+
const req = https.request(url, { method: 'GET', headers, timeout: TIMEOUT_MS }, (res) => {
|
|
56
|
+
let data = '';
|
|
57
|
+
res.setEncoding('utf8');
|
|
58
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
59
|
+
res.on('end', () => {
|
|
60
|
+
let body = null;
|
|
61
|
+
if (data) {
|
|
62
|
+
try { body = JSON.parse(data); } catch (_) { body = { raw: data }; }
|
|
63
|
+
}
|
|
64
|
+
resolve({
|
|
65
|
+
status: res.statusCode,
|
|
66
|
+
ok: res.statusCode >= 200 && res.statusCode < 300,
|
|
67
|
+
body,
|
|
68
|
+
paymentRequired: parseHeaderJson(res.headers['payment-required']),
|
|
69
|
+
paymentResponse: parseHeaderJson(res.headers['payment-response']) || res.headers['payment-response'] || null,
|
|
70
|
+
retryAfter: res.headers['retry-after'] || null
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
req.on('timeout', () => req.destroy(new Error('M2M Sentinel request timed out')));
|
|
75
|
+
req.on('error', reject);
|
|
76
|
+
req.end();
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function pathForTool(name, args) {
|
|
81
|
+
const input = args || {};
|
|
82
|
+
switch (name) {
|
|
83
|
+
case 'audit_contract':
|
|
84
|
+
if (!input.address) throw new Error('address is required');
|
|
85
|
+
return '/v1/audit/' + encodeURIComponent(input.address);
|
|
86
|
+
case 'get_security_score':
|
|
87
|
+
if (!input.address) throw new Error('address is required');
|
|
88
|
+
return '/v1/security/score/' + encodeURIComponent(input.address);
|
|
89
|
+
case 'get_gas_fees':
|
|
90
|
+
return '/v1/gas/fees';
|
|
91
|
+
case 'get_dex_metrics':
|
|
92
|
+
return '/v1/dex/metrics';
|
|
93
|
+
case 'get_token_price':
|
|
94
|
+
if (!input.symbol) throw new Error('symbol is required');
|
|
95
|
+
return '/v1/token/price/' + encodeURIComponent(input.symbol);
|
|
96
|
+
case 'get_whale_signals':
|
|
97
|
+
return '/v1/whales/signals';
|
|
98
|
+
default:
|
|
99
|
+
throw new Error('Unknown tool: ' + name);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function send(message) {
|
|
104
|
+
process.stdout.write(JSON.stringify(message) + '\n');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function success(id, result) {
|
|
108
|
+
send({ jsonrpc: '2.0', id, result });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function failure(id, code, message, data) {
|
|
112
|
+
const error = { code, message };
|
|
113
|
+
if (data !== undefined) error.data = data;
|
|
114
|
+
send({ jsonrpc: '2.0', id, error });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function handle(request) {
|
|
118
|
+
if (!request || request.jsonrpc !== '2.0' || typeof request.method !== 'string') {
|
|
119
|
+
failure(request && request.id !== undefined ? request.id : null, -32600, 'Invalid Request');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (request.id === undefined || request.id === null) return;
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
if (request.method === 'initialize') {
|
|
126
|
+
success(request.id, {
|
|
127
|
+
protocolVersion: request.params && request.params.protocolVersion ? request.params.protocolVersion : '2024-11-05',
|
|
128
|
+
capabilities: { tools: {} },
|
|
129
|
+
serverInfo: { name: 'm2m-sentinel-mcp', version: '1.0.4' }
|
|
130
|
+
});
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (request.method === 'tools/list') {
|
|
134
|
+
success(request.id, { tools: TOOLS });
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (request.method === 'tools/call') {
|
|
138
|
+
const params = request.params || {};
|
|
139
|
+
const path = pathForTool(params.name, params.arguments || {});
|
|
140
|
+
const apiResult = await queryApi(path);
|
|
141
|
+
const isError = !apiResult.ok;
|
|
142
|
+
success(request.id, {
|
|
143
|
+
content: [{ type: 'text', text: JSON.stringify(apiResult.body, null, 2) }],
|
|
144
|
+
isError,
|
|
145
|
+
structuredContent: apiResult.body,
|
|
146
|
+
_meta: {
|
|
147
|
+
httpStatus: apiResult.status,
|
|
148
|
+
paymentRequired: apiResult.paymentRequired,
|
|
149
|
+
paymentResponse: apiResult.paymentResponse,
|
|
150
|
+
retryAfter: apiResult.retryAfter
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
failure(request.id, -32601, 'Method not found');
|
|
156
|
+
} catch (err) {
|
|
157
|
+
failure(request.id, -32603, err && err.message ? err.message : 'Internal error');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
162
|
+
rl.on('line', (line) => {
|
|
163
|
+
if (!line.trim()) return;
|
|
164
|
+
let request;
|
|
165
|
+
try {
|
|
166
|
+
request = JSON.parse(line);
|
|
167
|
+
} catch (err) {
|
|
168
|
+
failure(null, -32700, 'Parse error');
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
handle(request);
|
|
172
|
+
});
|
package/package.json
CHANGED
|
@@ -1,19 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "m2m-sentinel-sdk",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.4",
|
|
4
|
+
"description": "JavaScript/Node.js client SDK for M2M Sentinel Web3 Pre-Transaction Risk Decision API",
|
|
5
5
|
"main": "index.js",
|
|
6
|
-
"
|
|
7
|
-
|
|
6
|
+
"types": "typescript/index.d.ts",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://m2msentinel.vercel.app",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/m2msentinel/m2m-sentinel"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://m2msentinel.vercel.app/docs.html"
|
|
8
15
|
},
|
|
9
16
|
"keywords": [
|
|
10
17
|
"web3",
|
|
11
|
-
"
|
|
18
|
+
"x402",
|
|
19
|
+
"m2m-sentinel",
|
|
20
|
+
"base",
|
|
21
|
+
"risk-api",
|
|
12
22
|
"smart-contract-audit",
|
|
13
|
-
"
|
|
14
|
-
"m2m",
|
|
15
|
-
"sentinel"
|
|
23
|
+
"agents"
|
|
16
24
|
],
|
|
17
25
|
"author": "M2M Sentinel",
|
|
18
|
-
"
|
|
26
|
+
"files": [
|
|
27
|
+
"index.js",
|
|
28
|
+
"m2m_sentinel_sdk.js",
|
|
29
|
+
"agent_adapter.js",
|
|
30
|
+
"eliza_plugin.js",
|
|
31
|
+
"mcp_server.js",
|
|
32
|
+
"README.md",
|
|
33
|
+
"typescript/"
|
|
34
|
+
]
|
|
19
35
|
}
|
|
36
|
+
|