m2m-sentinel-sdk 1.1.0 → 1.1.2

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 CHANGED
@@ -1,71 +1,146 @@
1
- # M2M Sentinel SDKs
2
-
3
- Base URL: `https://m2msentinel.vercel.app`.
4
-
5
- M2M Sentinel reports selected static bytecode capabilities, common proxy
6
- structures, evidence quality, and sourced Base market observations. It does not
7
- classify a contract as safe, malicious, or exploitable. Transaction middleware
8
- therefore requires a caller-defined policy and has no built-in allow threshold.
9
-
10
- ## Support status
11
-
12
- Supported here: JavaScript/Node (`index.js`), TypeScript source, Python source,
13
- and the MCP stdio server. Other language folders are community previews and
14
- must be checked against `/openapi.json` before production use.
15
-
16
- ## Install this source build
17
-
18
- ```bash
19
- node -e "const { M2MSentinelClient } = require('./public/sdk')"
20
- python -m pip install ./public/sdk/python
21
- ```
22
-
23
- Registry publishing is intentionally separate from the production API.
24
- The repository does not claim that these source-installable packages are available from every language registry.
25
-
26
- ## Authentication and x402
27
-
28
- Send API keys only in `x-api-key` (or `Authorization: Bearer`). Query-string
29
- credentials are rejected. Payable routes also support x402 v2. An unpaid call
30
- returns a base64 `PAYMENT-REQUIRED` challenge when the facilitator is ready; a
31
- successful settlement returns `PAYMENT-RESPONSE`.
32
-
33
- ## Public routes
34
-
35
- - `GET /v1/status`
36
- - `GET /v1/stats` (public counter-free privacy envelope; exact aggregate
37
- commercial counters are operator-only)
38
- - `GET /v1/plans`
39
- - `GET /v1/demo/audit/:address` for the published sample allowlist
40
- - free-key and subscription onboarding routes
41
-
42
- The JavaScript, TypeScript, and Python clients expose multi-period
43
- purchase/renewal and wallet recovery without requiring callers to hand-build
44
- requests:
45
-
46
- ```js
47
- await client.createSubscriptionIntent('GROWTH', wallet, {
48
- durationDays: 90,
49
- renewExistingKey: true,
50
- apiKey: existingPaidKey
51
- });
52
- const challenge = await client.createRecoveryChallenge(wallet, { txHash });
53
- await client.claimRecoveredKey(challenge.intent.id, walletSignature);
54
- ```
55
-
56
- `getPublicStats()` returns the counter-free privacy envelope. Operator-only
57
- detail has a deliberately separate `getOperatorAggregateStats(days, token)`
58
- method and sends the operator token only as `Authorization: Bearer`. This is
59
- for private server/operator tooling only: never embed or bundle the operator
60
- token in browser, mobile, SDK-distribution, or customer code.
61
-
62
- ## Protected routes
63
-
64
- - `GET /v1/audit/:address`
65
- - `GET /v1/security/score/:address` (legacy URL; returns a capability coverage index)
66
- - Base gas, DEX, token-price, and large-transfer observation routes
67
- - key self-service routes
68
-
69
- All live-data responses preserve provenance. Capability responses include
70
- `notASafetyGuarantee: true`, a limitations array, `capabilityRating`, and an
71
- evidence-grade flag. Do not replace `UNVERIFIED` or `null` with a default.
1
+ # M2M Sentinel SDKs
2
+
3
+ Production API Base URL: `https://api.m2msentinel.com` (with fallback `https://m2msentinel.com`).
4
+
5
+ Deterministic EVM bytecode and proxy capability intelligence on Base. Factual static capability observation — not a formal reachability audit, safety guarantee, or transaction advice. Live latency depends on RPC availability and deployment geography; transaction middleware requires a caller-defined policy and has no built-in decision threshold.
6
+
7
+ ---
8
+
9
+ ## 🏗️ Recommended Defense-in-Depth Pipeline for Agents
10
+
11
+ Autonomous agents handling value must never rely on a single oracle or heuristic. M2M Sentinel can supply static observations as Layer 1 of a caller-owned pipeline:
12
+
13
+ ```text
14
+ [Agent Intent]
15
+
16
+
17
+ [Transaction Builder]
18
+
19
+
20
+ [Stage 1: M2M Sentinel Observation] ── (Bytecode hash, proxy target, selected opcode/selector evidence)
21
+
22
+
23
+ [Stage 2: Local Policy Engine] ── (Caller-defined rules: Check spending bounds, reject DELEGATECALL, verify allowlist)
24
+
25
+
26
+ [Stage 3: Execution Simulation] ── (eth_call / Tenderly / Trace state simulation)
27
+
28
+
29
+ [Stage 4: Sub-Wallet Signing] ── (Scoped ephemeral wallet signs & broadcasts on Base)
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Installation
35
+
36
+ ### JavaScript / TypeScript (npm)
37
+
38
+ ```bash
39
+ # Scoped package (recommended)
40
+ npm install @m2msentinel/sdk
41
+
42
+ # Or unscoped package
43
+ npm install m2m-sentinel-sdk@1.1.2
44
+ ```
45
+
46
+ ### Python (PyPI)
47
+
48
+ ```bash
49
+ pip install m2m-sentinel==1.1.2
50
+ ```
51
+
52
+ ### MCP Server (Model Context Protocol)
53
+
54
+ ```bash
55
+ npx -y @m2msentinel/sdk
56
+ # or
57
+ npx -y m2m-sentinel-sdk
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Quickstart: JavaScript / TypeScript
63
+
64
+ ```javascript
65
+ const { M2MSentinelClient, X402SignerClient } = require('@m2msentinel/sdk');
66
+
67
+ // 1. Standard API Client (Header Authentication)
68
+ const client = new M2MSentinelClient({
69
+ apiKey: process.env.M2M_SENTINEL_API_KEY,
70
+ baseUrl: 'https://api.m2msentinel.com'
71
+ });
72
+
73
+ // Inspect contract capabilities before transaction
74
+ const audit = await client.auditContract('0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913');
75
+ console.log('Contract capabilities:', audit.audit.verdict.executableCapabilities);
76
+ console.log('Capability evidence:', audit.audit.dissection.capabilities);
77
+ console.log('Proxy Target:', audit.audit.proxyResolution.targetAddress);
78
+ console.log('Reachability:', audit.audit.reachability || 'NOT_ESTABLISHED');
79
+
80
+ // 2. Autonomous Headless x402 Micropayments (EIP-3009 Local Signing)
81
+ const x402Client = new X402SignerClient({
82
+ walletSigner: myAgentWallet, // ethers / viem signer
83
+ baseUrl: 'https://api.m2msentinel.com',
84
+ maxPriceUsd: 0.01 // Optional: strict spending limit (default $0.05)
85
+ });
86
+
87
+ const res = await x402Client.fetchWithAutoPayment('/v1/audit/0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913');
88
+ console.log('Paid analysis result:', res.json);
89
+ ```
90
+
91
+ ---
92
+
93
+ ## 🛡️ Autonomous Wallet Policy & Security Boundaries
94
+
95
+ To prevent autonomous AI agents from blindly signing arbitrary or spoofed HTTP 402 challenges from untrusted sources, `X402SignerClient` enforces **4 strict client-side invariants** locally before generating any cryptographic signature:
96
+
97
+ | Client Invariant | Enforced Value | Security Protection |
98
+ | :--- | :--- | :--- |
99
+ | **Chain ID** | `8453` (Base Mainnet) | Rejects signing on any unapproved EVM chain. |
100
+ | **Asset Contract** | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | Rejects signing for unapproved tokens (Base USDC only). |
101
+ | **Payout Recipient** | `0x6d6c398390cfb88f1cd42715b84906a0bd6652aa` | Rejects signing payments to unexpected recipient addresses. |
102
+ | **Price Ceiling** | `maxPriceUsd` (Default: `$0.05`) | Throws if remote challenge requests funds exceeding caller's authorized ceiling. |
103
+
104
+ ```javascript
105
+ import { X402SignerClient } from '@m2msentinel/sdk';
106
+
107
+ // Fully policy-constrained autonomous signer with immutable recipient & domain constants
108
+ const signer = new X402SignerClient({
109
+ wallet: agentWallet,
110
+ maxPriceUsd: 0.005 // Strict spending limit: 0.5 cents max per decision (default $0.05)
111
+ });
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Authentication and x402
117
+
118
+ Send API keys only in `x-api-key` (or `Authorization: Bearer`). Query-string credentials are rejected with HTTP 401. Payable routes also support x402 v2 on Base USDC. An unpaid call returns a base64 `PAYMENT-REQUIRED` challenge; a successful settlement returns `PAYMENT-RESPONSE`.
119
+
120
+ ---
121
+
122
+ ## Public Routes
123
+
124
+ - `GET /v1/status`
125
+ - `GET /v1/stats` (public privacy envelope; exact aggregate commercial counters are operator-only)
126
+ - `GET /v1/plans`
127
+ - `GET /v1/demo/audit/:address` for the published sample allowlist
128
+ - `GET /v1/audit/:address` (requires API key or x402 payment)
129
+
130
+ The JavaScript, TypeScript, and Python clients expose multi-period purchase/renewal and wallet recovery without requiring callers to hand-build requests:
131
+
132
+ ```javascript
133
+ await client.createSubscriptionIntent('GROWTH', wallet, {
134
+ durationDays: 90,
135
+ renewExistingKey: true,
136
+ apiKey: existingPaidKey
137
+ });
138
+ const challenge = await client.createRecoveryChallenge(wallet, { txHash });
139
+ await client.claimRecoveredKey(challenge.intent.id, walletSignature);
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Disclaimer & Limitations
145
+
146
+ Deterministic EVM bytecode and proxy capability intelligence on Base. Factual static capability observation — not a formal reachability audit, safety guarantee, or transaction advice. Live latency depends on RPC availability and deployment geography.
package/agent_adapter.js CHANGED
@@ -1,30 +1,276 @@
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
+ function summarizeAuditResponse(body, requestedAddress) {
17
+ const audit = body && body.audit ? body.audit : {};
18
+ const proxy = audit.proxyResolution || {};
19
+ const capabilities = Array.isArray(audit.verdict && audit.verdict.executableCapabilities)
20
+ ? audit.verdict.executableCapabilities
21
+ : (audit.dissection && Array.isArray(audit.dissection.capabilities)
22
+ ? audit.dissection.capabilities
23
+ .map((item) => typeof item === 'string' ? item : item && item.type)
24
+ .filter(Boolean)
25
+ : []);
26
+
27
+ return {
28
+ address: audit.address || requestedAddress,
29
+ isContract: Boolean(audit.dissection && audit.dissection.isValidContract),
30
+ isProxy: Boolean(proxy.isProxy),
31
+ proxyType: proxy.proxyType || 'NONE',
32
+ targetAddress: proxy.targetAddress || null,
33
+ executableCapabilities: capabilities,
34
+ reachability: audit.reachability || 'NOT_ESTABLISHED',
35
+ trustLevel: audit.provenance && audit.provenance.trustLevel
36
+ ? audit.provenance.trustLevel
37
+ : 'NOT_REPORTED'
38
+ };
39
+ }
40
+
41
+ class M2MSentinelActionProvider {
6
42
  constructor(options = {}) {
7
- this.name = 'm2m_sentinel_capability_analysis';
8
- this.description = 'Reports selected static bytecode capabilities, common proxy structures, provenance, and limitations for Base contracts.';
9
- this.client = new M2MSentinelClient({
10
- apiKey: options.apiKey || process.env.M2M_SENTINEL_API_KEY,
11
- baseUrl: options.baseUrl
43
+ this.name = 'm2m_sentinel';
44
+ this.actionProviderName = 'm2m_sentinel';
45
+ this.baseUrl = options.baseUrl || DEFAULT_BASE_URL;
46
+ this.apiKey = options.apiKey || process.env.M2M_SENTINEL_API_KEY || '';
47
+ this.timeoutMs = Number(options.timeoutMs || DEFAULT_TIMEOUT_MS);
48
+ }
49
+
50
+ supportsNetwork(network) {
51
+ if (!network) return true;
52
+ const chainId = String(network.chainId || network.networkId || '');
53
+ const protocolFamily = String(network.protocolFamily || 'evm').toLowerCase();
54
+ return protocolFamily === 'evm' && (
55
+ chainId === '8453' ||
56
+ chainId === 'base' ||
57
+ chainId === 'base-mainnet' ||
58
+ chainId === 'base-sepolia' ||
59
+ chainId === '84532'
60
+ );
61
+ }
62
+
63
+ async _queryApi(endpointPath, options = {}) {
64
+ const url = new URL(endpointPath, this.baseUrl);
65
+ const isHttps = url.protocol === 'https:';
66
+ const transport = isHttps ? https : http;
67
+
68
+ const headers = {
69
+ Accept: 'application/json',
70
+ 'User-Agent': 'M2MSentinel-AgentKit/1.1.2',
71
+ ...options.headers
72
+ };
73
+ if (this.apiKey && !headers['x-api-key']) {
74
+ headers['x-api-key'] = this.apiKey;
75
+ }
76
+
77
+ return new Promise((resolve, reject) => {
78
+ const req = transport.request(url, {
79
+ method: 'GET',
80
+ headers,
81
+ timeout: this.timeoutMs
82
+ }, (res) => {
83
+ let data = '';
84
+ res.setEncoding('utf8');
85
+ res.on('data', (chunk) => { data += chunk; });
86
+ res.on('end', () => {
87
+ let body = null;
88
+ if (data) {
89
+ try { body = JSON.parse(data); } catch (_) { body = { raw: data }; }
90
+ }
91
+ resolve({
92
+ statusCode: res.statusCode,
93
+ ok: res.statusCode >= 200 && res.statusCode < 300,
94
+ body
95
+ });
96
+ });
97
+ });
98
+
99
+ req.on('timeout', () => req.destroy(new Error('M2M Sentinel request timed out')));
100
+ req.on('error', reject);
101
+ req.end();
102
+ });
103
+ }
104
+
105
+ async auditContract(args) {
106
+ const address = String(args.address || args.contractAddress || '').trim();
107
+ if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
108
+ return JSON.stringify({
109
+ status: 'ERROR',
110
+ error: 'INVALID_ADDRESS',
111
+ message: 'A valid 40-hex 0x-prefixed Base contract address is required.'
112
+ });
113
+ }
114
+
115
+ const res = await this._queryApi(`/v1/audit/${encodeURIComponent(address)}`);
116
+ if (!res.ok) {
117
+ return JSON.stringify({
118
+ status: 'ERROR',
119
+ statusCode: res.statusCode,
120
+ error: res.body && res.body.error ? res.body.error : 'API_ERROR',
121
+ message: res.body && res.body.message ? res.body.message : 'Contract audit query failed',
122
+ notASafetyGuarantee: true
123
+ });
124
+ }
125
+
126
+ const observation = summarizeAuditResponse(res.body, address);
127
+ return JSON.stringify({
128
+ status: 'SUCCESS',
129
+ data: res.body,
130
+ notASafetyGuarantee: true,
131
+ observation,
132
+ observationSummary: `Contract ${observation.address}: isContract=${observation.isContract}, isProxy=${observation.isProxy}, proxyTarget=${observation.targetAddress || 'UNRESOLVED'}, capabilities=${observation.executableCapabilities.join(',') || 'NONE_OBSERVED'}, reachability=${observation.reachability}`
12
133
  });
13
134
  }
14
135
 
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
- };
136
+ async getGasMetrics() {
137
+ const res = await this._queryApi('/v1/gas/fees');
138
+ if (!res.ok) {
139
+ return JSON.stringify({ status: 'ERROR', statusCode: res.statusCode, message: 'Failed to retrieve gas fees' });
140
+ }
141
+ return JSON.stringify(res.body);
142
+ }
143
+
144
+ async getTokenPrice(args) {
145
+ const symbol = String(args.symbol || args.tokenSymbol || '').trim().toUpperCase();
146
+ if (!symbol) {
147
+ return JSON.stringify({ status: 'ERROR', message: 'Token symbol is required (e.g. USDC, WETH)' });
148
+ }
149
+ const res = await this._queryApi(`/v1/token/price/${encodeURIComponent(symbol)}`);
150
+ if (!res.ok) {
151
+ return JSON.stringify({ status: 'ERROR', statusCode: res.statusCode, message: `Failed to retrieve price for ${symbol}` });
152
+ }
153
+ return JSON.stringify(res.body);
154
+ }
155
+
156
+ async getServiceStatus() {
157
+ const res = await this._queryApi('/v1/status');
158
+ if (!res.ok) {
159
+ return JSON.stringify({ status: 'UNAVAILABLE', statusCode: res.statusCode });
26
160
  }
161
+ return JSON.stringify(res.body);
27
162
  }
163
+
164
+ async getDexLiquidity(args = {}) {
165
+ const pair = String(args.pair || 'WETH-USDC').trim();
166
+ const res = await this._queryApi(`/v1/dex/metrics?pair=${encodeURIComponent(pair)}`);
167
+ if (!res.ok) {
168
+ return JSON.stringify({ status: 'ERROR', statusCode: res.statusCode, message: 'Failed to retrieve DEX liquidity' });
169
+ }
170
+ return JSON.stringify(res.body);
171
+ }
172
+
173
+ async getWhaleSignals(args = {}) {
174
+ const limit = Number(args.limit || 10);
175
+ const res = await this._queryApi(`/v1/whales/signals?limit=${encodeURIComponent(limit)}`);
176
+ if (!res.ok) {
177
+ return JSON.stringify({ status: 'ERROR', statusCode: res.statusCode, message: 'Failed to retrieve whale signals' });
178
+ }
179
+ return JSON.stringify(res.body);
180
+ }
181
+
182
+ getActions(_walletProvider) {
183
+ return [
184
+ {
185
+ name: 'm2m_audit_contract',
186
+ description: 'Inspect Base target contract bytecode capability observations, proxy implementation slots, and limitations before executing transactions. Returns factual evidence, not a safety guarantee.',
187
+ schema: {
188
+ type: 'object',
189
+ properties: {
190
+ address: {
191
+ type: 'string',
192
+ description: 'Target Base contract address (0x-prefixed 40-hex)'
193
+ }
194
+ },
195
+ required: ['address']
196
+ },
197
+ invoke: (args) => this.auditContract(args)
198
+ },
199
+ {
200
+ name: 'm2m_get_gas_metrics',
201
+ description: 'Get real-time Base network gas execution metrics and recommendations before submitting on-chain transactions.',
202
+ schema: {
203
+ type: 'object',
204
+ properties: {}
205
+ },
206
+ invoke: () => this.getGasMetrics()
207
+ },
208
+ {
209
+ name: 'm2m_get_token_price',
210
+ description: 'Observe real-time Base DEX token price for slippage check and valuation.',
211
+ schema: {
212
+ type: 'object',
213
+ properties: {
214
+ symbol: {
215
+ type: 'string',
216
+ description: 'Token symbol on Base (e.g. USDC, WETH)'
217
+ }
218
+ },
219
+ required: ['symbol']
220
+ },
221
+ invoke: (args) => this.getTokenPrice(args)
222
+ },
223
+ {
224
+ name: 'm2m_get_dex_liquidity',
225
+ description: 'Get tracked Base DEX pool reserve and liquidity metrics.',
226
+ schema: {
227
+ type: 'object',
228
+ properties: {
229
+ pair: {
230
+ type: 'string',
231
+ description: 'DEX pair identifier (e.g. WETH-USDC)'
232
+ }
233
+ }
234
+ },
235
+ invoke: (args) => this.getDexLiquidity(args)
236
+ },
237
+ {
238
+ name: 'm2m_get_whale_signals',
239
+ description: 'Get tracked Base whale transfer and concentration signals.',
240
+ schema: {
241
+ type: 'object',
242
+ properties: {
243
+ limit: {
244
+ type: 'number',
245
+ description: 'Maximum signals to retrieve (1-50)'
246
+ }
247
+ }
248
+ },
249
+ invoke: (args) => this.getWhaleSignals(args)
250
+ },
251
+ {
252
+ name: 'm2m_get_service_status',
253
+ description: 'Check operational status of M2M Sentinel upstream verification rails.',
254
+ schema: {
255
+ type: 'object',
256
+ properties: {}
257
+ },
258
+ invoke: () => this.getServiceStatus()
259
+ }
260
+ ];
261
+ }
262
+ }
263
+
264
+ function m2mSentinelActionProvider(options = {}) {
265
+ return new M2MSentinelActionProvider(options);
28
266
  }
29
267
 
30
- module.exports = { M2MSentinelAgentTool };
268
+ // Backward compatibility alias
269
+ class M2MSentinelAgentTool extends M2MSentinelActionProvider {}
270
+
271
+ module.exports = {
272
+ M2MSentinelActionProvider,
273
+ m2mSentinelActionProvider,
274
+ M2MSentinelAgentTool,
275
+ summarizeAuditResponse
276
+ };
package/eliza_plugin.js CHANGED
@@ -1,4 +1,9 @@
1
- // UNSUPPORTED / COMMUNITY PREVIEW — not covered by the supported-SDK guarantee; verify against /openapi.json before production use.
1
+ /**
2
+ * M2M Sentinel ElizaOS Production Adapter Plugin
3
+ *
4
+ * Provides automated pre-transaction contract capability preflight and proxy inspection
5
+ * actions for autonomous agents running on the ElizaOS runtime framework.
6
+ */
2
7
 
3
8
  const { M2MSentinelClient } = require('./index.js');
4
9
 
@@ -81,4 +86,4 @@ const m2mSentinelPlugin = {
81
86
  ]
82
87
  };
83
88
 
84
- module.exports = { m2mSentinelPlugin };
89
+ module.exports = { m2mSentinelPlugin };