m2m-sentinel-sdk 1.0.4 → 1.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 M2M Sentinel
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 CHANGED
@@ -1,59 +1,61 @@
1
- # M2M Sentinel SDKs
1
+ # M2M Sentinel SDK
2
2
 
3
- Base URL: `https://m2msentinel.vercel.app`.
3
+ Official multi-language client libraries for M2M Sentinel — the pre-transaction capability evidence layer for Base agents.
4
4
 
5
- ## Support status
5
+ [![npm version](https://img.shields.io/npm/v/m2m-sentinel-sdk.svg)](https://www.npmjs.com/package/m2m-sentinel-sdk)
6
+ [![PyPI version](https://img.shields.io/pypi/v/m2m-sentinel.svg)](https://pypi.org/project/m2m-sentinel/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
8
 
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.
9
+ M2M Sentinel provides proxy-aware contract capability observations, implementation slot resolution, gas metrics, DEX liquidity telemetry, and whale signals for autonomous agents operating on Base.
14
10
 
15
11
  ## Installation
16
12
 
17
- Install from source (recommended for current `v1.0.4` build):
18
-
13
+ ### JavaScript / TypeScript (Node.js)
19
14
  ```bash
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
15
+ npm install m2m-sentinel-sdk
25
16
  ```
26
17
 
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`
18
+ ### Python
19
+ ```bash
20
+ pip install m2m-sentinel
21
+ ```
31
22
 
32
- ## Authentication
23
+ ## Quick Start
33
24
 
34
- API keys must be sent only in headers:
25
+ ### Node.js / TypeScript
26
+ ```javascript
27
+ const { M2MSentinelClient } = require('m2m-sentinel-sdk');
35
28
 
36
- ```text
37
- x-api-key: <key>
38
- ```
29
+ const client = new M2MSentinelClient({
30
+ apiKey: process.env.M2M_SENTINEL_API_KEY
31
+ });
39
32
 
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.
33
+ async function main() {
34
+ const audit = await client.auditContract('0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913');
35
+ console.log('Contract Type:', audit.data.bytecodeAnalysis.contractType);
36
+ console.log('Proxy Detected:', audit.data.proxyDetection.isProxy);
37
+ console.log('Capabilities:', audit.data.bytecodeAnalysis.detectedCapabilities);
38
+ }
41
39
 
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.
40
+ main().catch(console.error);
41
+ ```
45
42
 
46
- ## Free-tier signup
43
+ ### Python
44
+ ```python
45
+ from m2m_sentinel import M2MSentinelClient
47
46
 
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.
47
+ client = M2MSentinelClient(api_key="your_api_key")
48
+ audit = client.audit_contract("0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913")
49
+ print("Contract Type:", audit["data"]["bytecodeAnalysis"]["contractType"])
50
+ ```
52
51
 
53
- ## Current routes
52
+ ## Model Context Protocol (MCP) Server
54
53
 
55
- Public: `GET /v1/status`, `GET /v1/plans`, free signup, and subscription intent/claim routes.
54
+ M2M Sentinel includes a full Model Context Protocol (MCP) server supporting stdio and HTTP/SSE streams.
56
55
 
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`.
56
+ ```bash
57
+ node mcp_server.js
58
+ ```
58
59
 
59
- SDKs preserve response bodies, including `provenance`. They surface 401, 402, 405, 429 (`Retry-After`), and 503 `DATA_SOURCE_UNAVAILABLE` without inventing fallback values.
60
+ ## License
61
+ MIT License. Copyright (c) 2026 M2M Sentinel.
package/agent_adapter.js CHANGED
@@ -1,30 +1,202 @@
1
- // UNSUPPORTED / COMMUNITY PREVIEW — not covered by the supported-SDK guarantee; verify against /openapi.json before production use.
1
+ 'use strict';
2
2
 
3
- const { M2MSentinelClient } = require('./index.js');
3
+ /**
4
+ * Coinbase AgentKit Action Provider for M2M Sentinel.
5
+ *
6
+ * Exposes pre-transaction contract bytecode capability inspection, proxy resolution,
7
+ * and market observations to autonomous Base agents using @coinbase/agentkit.
8
+ */
4
9
 
5
- class M2MSentinelAgentTool {
10
+ const https = require('https');
11
+ const http = require('http');
12
+
13
+ const DEFAULT_BASE_URL = process.env.M2M_SENTINEL_BASE_URL || 'https://api.m2msentinel.com';
14
+ const DEFAULT_TIMEOUT_MS = Number(process.env.M2M_SENTINEL_TIMEOUT_MS || 30000);
15
+
16
+ class M2MSentinelActionProvider {
6
17
  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
18
+ this.name = 'm2m_sentinel';
19
+ this.actionProviderName = 'm2m_sentinel';
20
+ this.baseUrl = options.baseUrl || DEFAULT_BASE_URL;
21
+ this.apiKey = options.apiKey || process.env.M2M_SENTINEL_API_KEY || '';
22
+ this.timeoutMs = Number(options.timeoutMs || DEFAULT_TIMEOUT_MS);
23
+ }
24
+
25
+ supportsNetwork(network) {
26
+ if (!network) return true;
27
+ const chainId = String(network.chainId || network.networkId || '');
28
+ const protocolFamily = String(network.protocolFamily || 'evm').toLowerCase();
29
+ return protocolFamily === 'evm' && (
30
+ chainId === '8453' ||
31
+ chainId === 'base' ||
32
+ chainId === 'base-mainnet' ||
33
+ chainId === 'base-sepolia' ||
34
+ chainId === '84532'
35
+ );
36
+ }
37
+
38
+ async _queryApi(endpointPath, options = {}) {
39
+ const url = new URL(endpointPath, this.baseUrl);
40
+ const isHttps = url.protocol === 'https:';
41
+ const transport = isHttps ? https : http;
42
+
43
+ const headers = {
44
+ Accept: 'application/json',
45
+ 'User-Agent': 'M2MSentinel-AgentKit/1.1.1',
46
+ ...options.headers
47
+ };
48
+ if (this.apiKey && !headers['x-api-key']) {
49
+ headers['x-api-key'] = this.apiKey;
50
+ }
51
+
52
+ return new Promise((resolve, reject) => {
53
+ const req = transport.request(url, {
54
+ method: 'GET',
55
+ headers,
56
+ timeout: this.timeoutMs
57
+ }, (res) => {
58
+ let data = '';
59
+ res.setEncoding('utf8');
60
+ res.on('data', (chunk) => { data += chunk; });
61
+ res.on('end', () => {
62
+ let body = null;
63
+ if (data) {
64
+ try { body = JSON.parse(data); } catch (_) { body = { raw: data }; }
65
+ }
66
+ resolve({
67
+ statusCode: res.statusCode,
68
+ ok: res.statusCode >= 200 && res.statusCode < 300,
69
+ body
70
+ });
71
+ });
72
+ });
73
+
74
+ req.on('timeout', () => req.destroy(new Error('M2M Sentinel request timed out')));
75
+ req.on('error', reject);
76
+ req.end();
12
77
  });
13
78
  }
14
79
 
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
- };
80
+ async auditContract(args) {
81
+ const address = String(args.address || args.contractAddress || '').trim();
82
+ if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
83
+ return JSON.stringify({
84
+ status: 'ERROR',
85
+ error: 'INVALID_ADDRESS',
86
+ message: 'A valid 40-hex 0x-prefixed Base contract address is required.'
87
+ });
88
+ }
89
+
90
+ const res = await this._queryApi(`/v1/audit/${encodeURIComponent(address)}`);
91
+ if (!res.ok) {
92
+ return JSON.stringify({
93
+ status: 'ERROR',
94
+ statusCode: res.statusCode,
95
+ error: res.body && res.body.error ? res.body.error : 'API_ERROR',
96
+ message: res.body && res.body.message ? res.body.message : 'Contract audit query failed',
97
+ notASafetyGuarantee: true
98
+ });
26
99
  }
100
+
101
+ return JSON.stringify({
102
+ status: 'SUCCESS',
103
+ data: res.body,
104
+ notASafetyGuarantee: true,
105
+ observationSummary: `Contract ${address}: Type=${res.body.bytecodeAnalysis?.contractType || 'UNKNOWN'}, isProxy=${Boolean(res.body.proxyDetection?.isProxy)}`
106
+ });
107
+ }
108
+
109
+ async getGasMetrics() {
110
+ const res = await this._queryApi('/v1/gas/fees');
111
+ if (!res.ok) {
112
+ return JSON.stringify({ status: 'ERROR', statusCode: res.statusCode, message: 'Failed to retrieve gas fees' });
113
+ }
114
+ return JSON.stringify(res.body);
115
+ }
116
+
117
+ async getTokenPrice(args) {
118
+ const symbol = String(args.symbol || args.tokenSymbol || '').trim().toUpperCase();
119
+ if (!symbol) {
120
+ return JSON.stringify({ status: 'ERROR', message: 'Token symbol is required (e.g. USDC, WETH)' });
121
+ }
122
+ const res = await this._queryApi(`/v1/token/price/${encodeURIComponent(symbol)}`);
123
+ if (!res.ok) {
124
+ return JSON.stringify({ status: 'ERROR', statusCode: res.statusCode, message: `Failed to retrieve price for ${symbol}` });
125
+ }
126
+ return JSON.stringify(res.body);
127
+ }
128
+
129
+ async getServiceStatus() {
130
+ const res = await this._queryApi('/v1/status');
131
+ if (!res.ok) {
132
+ return JSON.stringify({ status: 'UNAVAILABLE', statusCode: res.statusCode });
133
+ }
134
+ return JSON.stringify(res.body);
135
+ }
136
+
137
+ getActions(_walletProvider) {
138
+ return [
139
+ {
140
+ name: 'm2m_audit_contract',
141
+ description: 'Inspect Base target contract bytecode capability observations, proxy implementation slots, and limitations before executing transactions. Returns factual evidence, not a safety guarantee.',
142
+ schema: {
143
+ type: 'object',
144
+ properties: {
145
+ address: {
146
+ type: 'string',
147
+ description: 'Target Base contract address (0x-prefixed 40-hex)'
148
+ }
149
+ },
150
+ required: ['address']
151
+ },
152
+ invoke: (args) => this.auditContract(args)
153
+ },
154
+ {
155
+ name: 'm2m_get_gas_metrics',
156
+ description: 'Get real-time Base network gas execution metrics and recommendations before submitting on-chain transactions.',
157
+ schema: {
158
+ type: 'object',
159
+ properties: {}
160
+ },
161
+ invoke: () => this.getGasMetrics()
162
+ },
163
+ {
164
+ name: 'm2m_get_token_price',
165
+ description: 'Observe real-time Base DEX token price for slippage check and valuation.',
166
+ schema: {
167
+ type: 'object',
168
+ properties: {
169
+ symbol: {
170
+ type: 'string',
171
+ description: 'Token symbol on Base (e.g. USDC, WETH)'
172
+ }
173
+ },
174
+ required: ['symbol']
175
+ },
176
+ invoke: (args) => this.getTokenPrice(args)
177
+ },
178
+ {
179
+ name: 'm2m_get_service_status',
180
+ description: 'Check operational status of M2M Sentinel upstream verification rails.',
181
+ schema: {
182
+ type: 'object',
183
+ properties: {}
184
+ },
185
+ invoke: () => this.getServiceStatus()
186
+ }
187
+ ];
27
188
  }
28
189
  }
29
190
 
30
- module.exports = { M2MSentinelAgentTool };
191
+ function m2mSentinelActionProvider(options = {}) {
192
+ return new M2MSentinelActionProvider(options);
193
+ }
194
+
195
+ // Backward compatibility alias
196
+ class M2MSentinelAgentTool extends M2MSentinelActionProvider {}
197
+
198
+ module.exports = {
199
+ M2MSentinelActionProvider,
200
+ m2mSentinelActionProvider,
201
+ M2MSentinelAgentTool
202
+ };
package/eliza_plugin.js CHANGED
@@ -20,7 +20,7 @@ const m2mSentinelPlugin = {
20
20
  actions: [
21
21
  {
22
22
  name: 'AUDIT_CONTRACT',
23
- description: 'Audits an EVM contract on Base Mainnet.',
23
+ description: 'Reports selected static capability and proxy observations for a Base contract.',
24
24
  handler: async (runtime, message) => {
25
25
  const address = message.content.text.match(/0x[a-fA-F0-9]{40}/)?.[0];
26
26
  if (!address) return { text: 'Please provide a valid 42-character EVM contract address.' };
@@ -28,20 +28,20 @@ const m2mSentinelPlugin = {
28
28
  const data = await clientFor(runtime).auditContract(address);
29
29
  const audit = data.audit || {};
30
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)}` };
31
+ return { text: `M2M Sentinel capability analysis for ${address}: ${audit.capabilityRating || 'UNVERIFIED'}; proxy=${proxy.isProxy ? proxy.proxyType : 'NO'}; notASafetyGuarantee=true; provenance=${JSON.stringify(audit.provenance || data.provenance || null)}` };
32
32
  } catch (err) { return { text: errorText(err) }; }
33
33
  }
34
34
  },
35
35
  {
36
- name: 'GET_SECURITY_SCORE',
37
- description: 'Returns the security score for a Base contract address.',
36
+ name: 'GET_CAPABILITY_SCORE',
37
+ description: 'Returns the static capability coverage index for a Base contract. The index is not a safety score.',
38
38
  handler: async (runtime, message) => {
39
39
  const address = message.content.text.match(/0x[a-fA-F0-9]{40}/)?.[0];
40
40
  if (!address) return { text: 'Please provide a valid EVM contract address.' };
41
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)}` };
42
+ const data = await clientFor(runtime).getCapabilityScore(address);
43
+ const score = data.capabilityScore === null ? 'unverified' : `${data.capabilityScore}/100 capability coverage`;
44
+ return { text: `M2M Sentinel static index for ${address}: ${score}; not a safety score. Provenance: ${JSON.stringify(data.provenance || null)}` };
45
45
  } catch (err) { return { text: errorText(err) }; }
46
46
  }
47
47
  },
package/index.d.ts ADDED
@@ -0,0 +1,94 @@
1
+ export interface M2MSentinelClientOptions {
2
+ apiKey?: string;
3
+ baseUrl?: string;
4
+ timeoutMs?: number;
5
+ paymentSignature?: string;
6
+ }
7
+
8
+ export interface CreateSubscriptionIntentOptions extends M2MSentinelClientOptions {
9
+ durationDays?: 31 | 90 | 365;
10
+ renewExistingKey?: boolean;
11
+ }
12
+
13
+ export interface RecoveryChallengeOptions {
14
+ txHash?: string;
15
+ }
16
+
17
+ export interface M2MSentinelErrorOptions {
18
+ status?: number;
19
+ body?: unknown;
20
+ retryAfter?: string | null;
21
+ paymentRequired?: unknown;
22
+ paymentResponse?: unknown;
23
+ }
24
+
25
+ export class M2MSentinelError extends Error {
26
+ readonly status?: number;
27
+ readonly body?: unknown;
28
+ readonly retryAfter?: string | null;
29
+ readonly paymentRequired?: unknown;
30
+ readonly paymentResponse?: unknown;
31
+ constructor(message: string, options?: M2MSentinelErrorOptions);
32
+ }
33
+
34
+ export class PaymentRequiredError extends M2MSentinelError {}
35
+ export class RateLimitedError extends M2MSentinelError {}
36
+ export class DataSourceUnavailableError extends M2MSentinelError {}
37
+
38
+ export class M2MSentinelClient {
39
+ constructor(options?: M2MSentinelClientOptions);
40
+ constructor(apiKey?: string, baseUrl?: string);
41
+ request(method: string, path: string, body?: unknown, options?: M2MSentinelClientOptions): Promise<any>;
42
+ getStatus(): Promise<any>;
43
+ getPublicStats(days?: number): Promise<any>;
44
+ /** Backward-compatible alias for getPublicStats; customer keys never reveal operator counters. */
45
+ getAggregateStats(days?: number): Promise<any>;
46
+ getOperatorAggregateStats(days: number, operatorToken: string): Promise<any>;
47
+ getPlans(): Promise<any>;
48
+ demoAudit(address: string): Promise<any>;
49
+ createFreeChallenge(userWallet: string): Promise<any>;
50
+ claimFreeTier(intentId: string, signature: string): Promise<any>;
51
+ createSubscriptionIntent(tier: string, userWallet: string, options?: CreateSubscriptionIntentOptions): Promise<any>;
52
+ claimSubscription(intentId: string, signature: string, txHash: string): Promise<any>;
53
+ createRecoveryChallenge(userWallet: string, options?: RecoveryChallengeOptions): Promise<any>;
54
+ claimRecoveredKey(intentId: string, signature: string): Promise<any>;
55
+ auditContract(address: string, options?: M2MSentinelClientOptions): Promise<any>;
56
+ getCapabilityScore(address: string, options?: M2MSentinelClientOptions): Promise<any>;
57
+ /** Legacy alias. The response is a capability coverage index, not a safety score. */
58
+ getSecurityScore(address: string, options?: M2MSentinelClientOptions): Promise<any>;
59
+ getGasFees(options?: M2MSentinelClientOptions): Promise<any>;
60
+ getDexMetrics(options?: M2MSentinelClientOptions): Promise<any>;
61
+ getTokenPrice(symbol: string, options?: M2MSentinelClientOptions): Promise<any>;
62
+ getWhaleSignals(options?: M2MSentinelClientOptions): Promise<any>;
63
+ getKeySelf(): Promise<any>;
64
+ revokeKey(confirm?: boolean): Promise<any>;
65
+ }
66
+
67
+ export type SentinelPolicy = (
68
+ analysis: any,
69
+ context: { targetAddress: string; integration: string }
70
+ ) => boolean | { allow: boolean; reason?: string } | Promise<boolean | { allow: boolean; reason?: string }>;
71
+
72
+ export function enforceCallerPolicy(
73
+ policy: SentinelPolicy | undefined,
74
+ analysis: any,
75
+ context: { targetAddress: string; integration: string }
76
+ ): Promise<void>;
77
+
78
+ export function createEthersSentinelMiddleware(
79
+ apiKey?: string,
80
+ baseUrl?: string,
81
+ policy?: SentinelPolicy
82
+ ): {
83
+ client: M2MSentinelClient;
84
+ verifyContractBeforeTx(targetAddress: string, policyOverride?: SentinelPolicy): Promise<any>;
85
+ };
86
+
87
+ export function createViemSentinelInterceptor(
88
+ apiKey?: string,
89
+ baseUrl?: string,
90
+ policy?: SentinelPolicy
91
+ ): {
92
+ client: M2MSentinelClient;
93
+ inspectSwapTarget(address: string, policyOverride?: SentinelPolicy): Promise<any>;
94
+ };