m2m-sentinel-sdk 1.2.5 → 1.2.7
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/agent_adapter.js +11 -23
- package/base_account_paymaster_guard.js +52 -0
- package/eliza_plugin.js +1 -1
- package/index.d.ts +17 -0
- package/langchain_tool.js +93 -0
- package/mcp_server.js +176 -84
- package/package.json +5 -1
- package/smithery.yaml +2 -2
- package/typescript/base_account_paymaster_guard.ts +20 -0
- package/typescript/package-lock.json +2 -2
- package/typescript/package.json +1 -1
- package/typescript/x402.ts +1 -1
- package/x402_signer.js +1 -1
package/agent_adapter.js
CHANGED
|
@@ -67,7 +67,7 @@ class M2MSentinelActionProvider {
|
|
|
67
67
|
|
|
68
68
|
const headers = {
|
|
69
69
|
Accept: 'application/json',
|
|
70
|
-
'User-Agent': 'M2MSentinel-AgentKit/1.2.
|
|
70
|
+
'User-Agent': 'M2MSentinel-AgentKit/1.2.7',
|
|
71
71
|
...options.headers
|
|
72
72
|
};
|
|
73
73
|
if (this.apiKey && !headers['x-api-key']) {
|
|
@@ -161,18 +161,16 @@ class M2MSentinelActionProvider {
|
|
|
161
161
|
return JSON.stringify(res.body);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
async getDexLiquidity(
|
|
165
|
-
const
|
|
166
|
-
const res = await this._queryApi(`/v1/dex/metrics?pair=${encodeURIComponent(pair)}`);
|
|
164
|
+
async getDexLiquidity(_args = {}) {
|
|
165
|
+
const res = await this._queryApi('/v1/dex/metrics');
|
|
167
166
|
if (!res.ok) {
|
|
168
167
|
return JSON.stringify({ status: 'ERROR', statusCode: res.statusCode, message: 'Failed to retrieve DEX liquidity' });
|
|
169
168
|
}
|
|
170
169
|
return JSON.stringify(res.body);
|
|
171
170
|
}
|
|
172
171
|
|
|
173
|
-
async getWhaleSignals(
|
|
174
|
-
const
|
|
175
|
-
const res = await this._queryApi(`/v1/whales/signals?limit=${encodeURIComponent(limit)}`);
|
|
172
|
+
async getWhaleSignals(_args = {}) {
|
|
173
|
+
const res = await this._queryApi('/v1/whales/signals');
|
|
176
174
|
if (!res.ok) {
|
|
177
175
|
return JSON.stringify({ status: 'ERROR', statusCode: res.statusCode, message: 'Failed to retrieve whale signals' });
|
|
178
176
|
}
|
|
@@ -198,7 +196,7 @@ class M2MSentinelActionProvider {
|
|
|
198
196
|
},
|
|
199
197
|
{
|
|
200
198
|
name: 'm2m_get_gas_metrics',
|
|
201
|
-
description: '
|
|
199
|
+
description: 'Fetch current Base gas price in wei/gwei with RPC provenance. Read-only telemetry; does not authorize or submit transactions.',
|
|
202
200
|
schema: {
|
|
203
201
|
type: 'object',
|
|
204
202
|
properties: {}
|
|
@@ -207,7 +205,7 @@ class M2MSentinelActionProvider {
|
|
|
207
205
|
},
|
|
208
206
|
{
|
|
209
207
|
name: 'm2m_get_token_price',
|
|
210
|
-
description: '
|
|
208
|
+
description: 'Fetch the median Base DEX spot price in USD across up to five deepest indexed pools for one allowlisted token symbol. No historical price series.',
|
|
211
209
|
schema: {
|
|
212
210
|
type: 'object',
|
|
213
211
|
properties: {
|
|
@@ -222,29 +220,19 @@ class M2MSentinelActionProvider {
|
|
|
222
220
|
},
|
|
223
221
|
{
|
|
224
222
|
name: 'm2m_get_dex_liquidity',
|
|
225
|
-
description: '
|
|
223
|
+
description: 'Fetch aggregate reserve, depth, and volume metrics across tracked Base DEX liquidity pools; no individual pair filtering.',
|
|
226
224
|
schema: {
|
|
227
225
|
type: 'object',
|
|
228
|
-
properties: {
|
|
229
|
-
pair: {
|
|
230
|
-
type: 'string',
|
|
231
|
-
description: 'DEX pair identifier (e.g. WETH-USDC)'
|
|
232
|
-
}
|
|
233
|
-
}
|
|
226
|
+
properties: {}
|
|
234
227
|
},
|
|
235
228
|
invoke: (args) => this.getDexLiquidity(args)
|
|
236
229
|
},
|
|
237
230
|
{
|
|
238
231
|
name: 'm2m_get_whale_signals',
|
|
239
|
-
description: '
|
|
232
|
+
description: 'Fetch up to 50 tracked recent high-value ERC-20 transfer signals on Base; no query parameter limit filtering.',
|
|
240
233
|
schema: {
|
|
241
234
|
type: 'object',
|
|
242
|
-
properties: {
|
|
243
|
-
limit: {
|
|
244
|
-
type: 'number',
|
|
245
|
-
description: 'Maximum signals to retrieve (1-50)'
|
|
246
|
-
}
|
|
247
|
-
}
|
|
235
|
+
properties: {}
|
|
248
236
|
},
|
|
249
237
|
invoke: (args) => this.getWhaleSignals(args)
|
|
250
238
|
},
|
|
@@ -810,10 +810,59 @@ class BaseAccountPaymasterGuard {
|
|
|
810
810
|
sendCalls(provider, request, options = {}) {
|
|
811
811
|
return this.request(provider, request, options);
|
|
812
812
|
}
|
|
813
|
+
|
|
814
|
+
preflightTransaction(transaction, options = {}) {
|
|
815
|
+
return preflightBeforeSigning({
|
|
816
|
+
client: this.client,
|
|
817
|
+
transaction,
|
|
818
|
+
policy: options.policy === undefined ? this.policy : options.policy,
|
|
819
|
+
signAndSend: options.signAndSend,
|
|
820
|
+
context: options.context
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
guardTransaction(transaction, options = {}) {
|
|
825
|
+
return this.preflightTransaction(transaction, options);
|
|
826
|
+
}
|
|
813
827
|
}
|
|
814
828
|
|
|
815
829
|
const BaseAccountExecutionGuard = BaseAccountPaymasterGuard;
|
|
816
830
|
|
|
831
|
+
/**
|
|
832
|
+
* Productized single-call preflight verification adapter.
|
|
833
|
+
*
|
|
834
|
+
* Preflights a caller transaction against M2M Sentinel, validates decision-grade
|
|
835
|
+
* evidence without invented safety claims, evaluates an affirmative caller-owned
|
|
836
|
+
* policy, and forwards to the caller's signing/send callback.
|
|
837
|
+
*/
|
|
838
|
+
async function preflightBeforeSigning({ client, transaction, policy, signAndSend, context } = {}) {
|
|
839
|
+
assertClient(client);
|
|
840
|
+
if (typeof policy !== 'function') throw new TypeError('An explicit caller-owned policy function is required.');
|
|
841
|
+
if (typeof signAndSend !== 'function') throw new TypeError('A caller-owned signing/send callback is required.');
|
|
842
|
+
|
|
843
|
+
// Validate and canonicalize input transaction
|
|
844
|
+
const transactionSnapshot = canonicalTransaction(transaction, 'transaction');
|
|
845
|
+
const observation = await client.preflightTransaction(cloneJson(transactionSnapshot, 'transaction'));
|
|
846
|
+
const observationSnapshot = cloneJson(observation, 'observation');
|
|
847
|
+
assertObservationReady(observationSnapshot, transactionSnapshot);
|
|
848
|
+
|
|
849
|
+
let callerContext = { transaction: cloneJson(transactionSnapshot, 'transaction') };
|
|
850
|
+
if (context !== undefined) {
|
|
851
|
+
if (!isPlainRecord(context)) throw new TypeError('context must be a JSON object when supplied.');
|
|
852
|
+
callerContext = { ...cloneJson(context, 'context'), ...callerContext };
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
const policyResult = await policy(cloneJson(observationSnapshot, 'observation'), callerContext);
|
|
856
|
+
if (!isAffirmativePolicyResult(policyResult)) {
|
|
857
|
+
blocked('CALLER_POLICY_REJECTED', `Transaction rejected: ${policyRejectionReason(policyResult)}`);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
return signAndSend(cloneJson(transactionSnapshot, 'transaction'), cloneJson(observationSnapshot, 'observation'));
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
const guardTransaction = preflightBeforeSigning;
|
|
864
|
+
const preflightTransactionBeforeSigning = preflightBeforeSigning;
|
|
865
|
+
|
|
817
866
|
/** Construct the public SDK client while keeping credentials caller-owned. */
|
|
818
867
|
function createPublicClient({ baseUrl, apiKey, timeoutMs, sdk } = {}) {
|
|
819
868
|
if (typeof apiKey !== 'string' || apiKey.length === 0) {
|
|
@@ -861,6 +910,9 @@ module.exports = {
|
|
|
861
910
|
preflightWalletSendCalls,
|
|
862
911
|
sendCallsWithPreflight,
|
|
863
912
|
executeWalletSendCalls,
|
|
913
|
+
preflightBeforeSigning,
|
|
914
|
+
guardTransaction,
|
|
915
|
+
preflightTransactionBeforeSigning,
|
|
864
916
|
BaseAccountPaymasterGuard,
|
|
865
917
|
BaseAccountExecutionGuard,
|
|
866
918
|
createBaseAccountPaymasterGuard: (options) => new BaseAccountPaymasterGuard(options),
|
package/eliza_plugin.js
CHANGED
|
@@ -69,7 +69,7 @@ const m2mSentinelPlugin = {
|
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
name: 'GET_GAS_FEES',
|
|
72
|
-
description: 'Fetches
|
|
72
|
+
description: 'Fetches current Base gas-price telemetry with upstream provenance.',
|
|
73
73
|
handler: async (runtime) => {
|
|
74
74
|
try { return { text: JSON.stringify(await clientFor(runtime).getGasFees()) }; }
|
|
75
75
|
catch (err) { return { text: errorText(err) }; }
|
package/index.d.ts
CHANGED
|
@@ -198,6 +198,17 @@ export interface BaseAccountGuardCallOptions {
|
|
|
198
198
|
context?: Record<string, unknown>;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
export interface PreflightBeforeSigningOptions {
|
|
202
|
+
client: Pick<M2MSentinelClient, 'preflightTransaction'>;
|
|
203
|
+
transaction: TransactionPreflightRequest;
|
|
204
|
+
policy: (
|
|
205
|
+
observation: BaseAccountPreflightObservation,
|
|
206
|
+
context: { transaction: TransactionPreflightRequest; [key: string]: unknown }
|
|
207
|
+
) => BaseAccountCallerPolicyResult | Promise<BaseAccountCallerPolicyResult>;
|
|
208
|
+
signAndSend: (transaction: TransactionPreflightRequest, observation: BaseAccountPreflightObservation) => Promise<unknown>;
|
|
209
|
+
context?: Record<string, unknown>;
|
|
210
|
+
}
|
|
211
|
+
|
|
201
212
|
export interface M2MSentinelErrorOptions {
|
|
202
213
|
status?: number;
|
|
203
214
|
body?: unknown;
|
|
@@ -285,10 +296,16 @@ export const preflightWalletSendCalls: typeof guardWalletSendCalls;
|
|
|
285
296
|
export const sendCallsWithPreflight: typeof guardWalletSendCalls;
|
|
286
297
|
export const executeWalletSendCalls: typeof guardWalletSendCalls;
|
|
287
298
|
|
|
299
|
+
export function preflightBeforeSigning(options: PreflightBeforeSigningOptions): Promise<unknown>;
|
|
300
|
+
export const guardTransaction: typeof preflightBeforeSigning;
|
|
301
|
+
export const preflightTransactionBeforeSigning: typeof preflightBeforeSigning;
|
|
302
|
+
|
|
288
303
|
export class BaseAccountPaymasterGuard {
|
|
289
304
|
constructor(options: BaseAccountPaymasterGuardOptions);
|
|
290
305
|
request(provider: Eip1193Provider, request: WalletSendCallsRequest, options?: BaseAccountGuardCallOptions): Promise<unknown>;
|
|
291
306
|
sendCalls(provider: Eip1193Provider, request: WalletSendCallsRequest, options?: BaseAccountGuardCallOptions): Promise<unknown>;
|
|
307
|
+
preflightTransaction(transaction: TransactionPreflightRequest, options?: { policy?: BaseAccountCallerPolicy; signAndSend: (tx: any, obs: any) => Promise<unknown>; context?: Record<string, unknown> }): Promise<unknown>;
|
|
308
|
+
guardTransaction(transaction: TransactionPreflightRequest, options?: { policy?: BaseAccountCallerPolicy; signAndSend: (tx: any, obs: any) => Promise<unknown>; context?: Record<string, unknown> }): Promise<unknown>;
|
|
292
309
|
}
|
|
293
310
|
|
|
294
311
|
export const BaseAccountExecutionGuard: typeof BaseAccountPaymasterGuard;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* LangChain & LangGraph Tool Provider for M2M Sentinel.
|
|
5
|
+
*
|
|
6
|
+
* Compatible with @langchain/core/tools StructuredTool and DynamicStructuredTool.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { M2MSentinelClient } = require('./index.js');
|
|
10
|
+
|
|
11
|
+
class M2MSentinelLangChainTools {
|
|
12
|
+
/**
|
|
13
|
+
* @param {Object} [options]
|
|
14
|
+
* @param {string} [options.apiKey] M2M Sentinel API Key
|
|
15
|
+
* @param {string} [options.baseUrl] M2M Sentinel Base URL
|
|
16
|
+
*/
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this.client = new M2MSentinelClient(options);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Return a list of tool definitions compatible with LangChain agents.
|
|
23
|
+
* Can be passed directly to `createOpenAIToolsAgent({ tools })` or `AgentExecutor`.
|
|
24
|
+
*/
|
|
25
|
+
getTools() {
|
|
26
|
+
return [
|
|
27
|
+
{
|
|
28
|
+
name: 'm2m_audit_contract',
|
|
29
|
+
description: 'Deterministic EVM bytecode capability analysis and proxy resolution for Base contracts. Run before signing or sending transactions to unverified contracts.',
|
|
30
|
+
schema: {
|
|
31
|
+
type: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
address: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
description: 'The 0x-prefixed 40-hex Ethereum address on Base (Chain ID 8453) to inspect.'
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
required: ['address']
|
|
39
|
+
},
|
|
40
|
+
func: async ({ address }) => {
|
|
41
|
+
try {
|
|
42
|
+
const res = await this.client.auditContract(address);
|
|
43
|
+
return JSON.stringify(res);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
return JSON.stringify({ error: err.message, status: err.status || 500 });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'm2m_get_gas_metrics',
|
|
51
|
+
description: 'Fetch current Base gas price in wei/gwei with RPC provenance. Read-only telemetry; does not authorize or submit transactions.',
|
|
52
|
+
schema: {
|
|
53
|
+
type: 'object',
|
|
54
|
+
properties: {}
|
|
55
|
+
},
|
|
56
|
+
func: async () => {
|
|
57
|
+
try {
|
|
58
|
+
const res = await this.client.getGasFees();
|
|
59
|
+
return JSON.stringify(res);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
return JSON.stringify({ error: err.message, status: err.status || 500 });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'm2m_get_token_price',
|
|
67
|
+
description: 'Fetch sourced Base DEX spot price for one allowlisted token symbol (e.g. USDC, WETH), with pool provenance and no historical price series.',
|
|
68
|
+
schema: {
|
|
69
|
+
type: 'object',
|
|
70
|
+
properties: {
|
|
71
|
+
symbol: {
|
|
72
|
+
type: 'string',
|
|
73
|
+
description: 'The asset symbol (e.g., USDC, WETH).'
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
required: ['symbol']
|
|
77
|
+
},
|
|
78
|
+
func: async ({ symbol }) => {
|
|
79
|
+
try {
|
|
80
|
+
const res = await this.client.getTokenPrice(symbol);
|
|
81
|
+
return JSON.stringify(res);
|
|
82
|
+
} catch (err) {
|
|
83
|
+
return JSON.stringify({ error: err.message, status: err.status || 500 });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
M2MSentinelLangChainTools
|
|
93
|
+
};
|
package/mcp_server.js
CHANGED
|
@@ -5,75 +5,131 @@ const https = require('https');
|
|
|
5
5
|
const http = require('http');
|
|
6
6
|
const readline = require('readline');
|
|
7
7
|
|
|
8
|
+
const pkg = require('./package.json');
|
|
9
|
+
|
|
8
10
|
let BASE_URL = process.env.M2M_SENTINEL_BASE_URL || 'https://api.m2msentinel.com';
|
|
9
11
|
let API_KEY = process.env.M2M_SENTINEL_API_KEY || '';
|
|
10
12
|
const TIMEOUT_MS = Number(process.env.M2M_SENTINEL_TIMEOUT_MS || 30000);
|
|
11
|
-
const VERSION =
|
|
13
|
+
const VERSION = pkg.version;
|
|
14
|
+
|
|
15
|
+
const SERVER_INSTRUCTIONS =
|
|
16
|
+
'M2M Sentinel MCP server provides factual, read-only observations on Base Mainnet (chainId 8453).\n\n' +
|
|
17
|
+
'• Canonical Tools: The six m2m_* tool names (m2m_audit_contract, m2m_get_gas_metrics, m2m_get_token_price, m2m_get_dex_liquidity, m2m_get_whale_signals, m2m_get_service_status) are canonical.\n' +
|
|
18
|
+
'• Backwards Compatibility: Legacy tool names (audit_contract, get_capability_score, get_gas_fees, get_dex_metrics, get_token_price, get_whale_signals) are not advertised in tools/list but remain compatibility-callable.\n' +
|
|
19
|
+
'• Upstream Behavior: m2m_get_dex_liquidity returns aggregate tracked pool liquidity metrics without pair query filtering; m2m_get_whale_signals returns recent high-value transfer events (up to 50) without query limit filtering.\n' +
|
|
20
|
+
'• Access and Errors: The stdio wrapper reads M2M_SENTINEL_API_KEY when set. Protected upstream calls may return structured 401, 402, 429, or 503 responses; x402 payment metadata is surfaced, but this wrapper does not sign payments.\n' +
|
|
21
|
+
'• Observational Scope: All operations are factual, read-only observations. The server does not custody private keys, sign transactions, or execute on-chain state changes.\n' +
|
|
22
|
+
'• Limitation: Outputs reflect observed bytecode patterns and telemetry and are not safety, economic, or exploitability guarantees.';
|
|
12
23
|
|
|
13
24
|
const TOOLS = [
|
|
14
25
|
{
|
|
15
26
|
name: 'm2m_audit_contract',
|
|
16
|
-
description: '
|
|
17
|
-
inputSchema: {
|
|
27
|
+
description: 'Inspect static bytecode capabilities (e.g. mint, pause, freeze, upgradeability slots), common proxy target resolution, and coverage index for a single Base contract address. Factual capability observation only, not a safety or exploitability guarantee. Distinguishable from m2m_get_service_status (infrastructure status) and legacy score-only endpoints.',
|
|
28
|
+
inputSchema: {
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
address: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: 'Base contract address (0x-prefixed 40-hex string, chainId 8453) to inspect.'
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
required: ['address']
|
|
37
|
+
},
|
|
38
|
+
annotations: {
|
|
39
|
+
readOnlyHint: true,
|
|
40
|
+
destructiveHint: false,
|
|
41
|
+
idempotentHint: true,
|
|
42
|
+
openWorldHint: true
|
|
43
|
+
}
|
|
18
44
|
},
|
|
19
45
|
{
|
|
20
46
|
name: 'm2m_get_gas_metrics',
|
|
21
|
-
description: '
|
|
22
|
-
inputSchema: {
|
|
47
|
+
description: 'Fetch current Base gas price in wei/gwei with RPC provenance for the upstream observation. Read-only telemetry; does not inspect contract code, return token/DEX pricing, or authorize or submit transactions.',
|
|
48
|
+
inputSchema: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {}
|
|
51
|
+
},
|
|
52
|
+
annotations: {
|
|
53
|
+
readOnlyHint: true,
|
|
54
|
+
destructiveHint: false,
|
|
55
|
+
idempotentHint: true,
|
|
56
|
+
openWorldHint: true
|
|
57
|
+
}
|
|
23
58
|
},
|
|
24
59
|
{
|
|
25
60
|
name: 'm2m_get_token_price',
|
|
26
|
-
description: '
|
|
27
|
-
inputSchema: {
|
|
61
|
+
description: 'Fetch the median Base DEX spot price in USD across up to five deepest indexed pools, with contract address, decimals, and pool provenance for one allowlisted token symbol (e.g. USDC, WETH, AERO). Does not return historical price series; contrast with m2m_get_dex_liquidity, which returns aggregate pool reserve depth rather than an asset price.',
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
symbol: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
description: 'Allowlisted token symbol on Base (e.g. USDC, WETH, AERO). Lookups are case-insensitive single symbols.'
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
required: ['symbol']
|
|
71
|
+
},
|
|
72
|
+
annotations: {
|
|
73
|
+
readOnlyHint: true,
|
|
74
|
+
destructiveHint: false,
|
|
75
|
+
idempotentHint: true,
|
|
76
|
+
openWorldHint: true
|
|
77
|
+
}
|
|
28
78
|
},
|
|
29
79
|
{
|
|
30
80
|
name: 'm2m_get_dex_liquidity',
|
|
31
|
-
description: '
|
|
32
|
-
inputSchema: {
|
|
81
|
+
description: 'Fetch aggregate pool reserve, depth, and volume metrics across tracked Base DEX liquidity pools. Reports tracked-pool metrics only and does not filter by individual trading pair; contrast with m2m_get_token_price, which observes token spot prices.',
|
|
82
|
+
inputSchema: {
|
|
83
|
+
type: 'object',
|
|
84
|
+
properties: {}
|
|
85
|
+
},
|
|
86
|
+
annotations: {
|
|
87
|
+
readOnlyHint: true,
|
|
88
|
+
destructiveHint: false,
|
|
89
|
+
idempotentHint: true,
|
|
90
|
+
openWorldHint: true
|
|
91
|
+
}
|
|
33
92
|
},
|
|
34
93
|
{
|
|
35
94
|
name: 'm2m_get_whale_signals',
|
|
36
|
-
description: '
|
|
37
|
-
inputSchema: {
|
|
95
|
+
description: 'Fetch up to 50 tracked recent high-value ERC-20 transfer signals on Base with transaction hashes, token/sender/receiver addresses, amounts, and valuation provenance. Observes large on-chain transfer events without query parameter limits; does not inspect contract bytecode or query DEX pricing.',
|
|
96
|
+
inputSchema: {
|
|
97
|
+
type: 'object',
|
|
98
|
+
properties: {}
|
|
99
|
+
},
|
|
100
|
+
annotations: {
|
|
101
|
+
readOnlyHint: true,
|
|
102
|
+
destructiveHint: false,
|
|
103
|
+
idempotentHint: true,
|
|
104
|
+
openWorldHint: true
|
|
105
|
+
}
|
|
38
106
|
},
|
|
39
107
|
{
|
|
40
108
|
name: 'm2m_get_service_status',
|
|
41
|
-
description: '
|
|
42
|
-
inputSchema: {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
description: 'Return the static capability coverage index and provenance for a Base contract. This is not a safety score.',
|
|
53
|
-
inputSchema: { type: 'object', properties: { address: { type: 'string' } }, required: ['address'] }
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
name: 'get_gas_fees',
|
|
57
|
-
description: 'Alias for m2m_get_gas_metrics.',
|
|
58
|
-
inputSchema: { type: 'object', properties: {} }
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
name: 'get_dex_metrics',
|
|
62
|
-
description: 'Alias for m2m_get_dex_liquidity.',
|
|
63
|
-
inputSchema: { type: 'object', properties: {} }
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
name: 'get_token_price',
|
|
67
|
-
description: 'Alias for m2m_get_token_price.',
|
|
68
|
-
inputSchema: { type: 'object', properties: { symbol: { type: 'string' } }, required: ['symbol'] }
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
name: 'get_whale_signals',
|
|
72
|
-
description: 'Alias for m2m_get_whale_signals.',
|
|
73
|
-
inputSchema: { type: 'object', properties: {} }
|
|
109
|
+
description: 'Fetch operational status, upstream Base RPC quorum status, and persistence availability for M2M Sentinel infrastructure. Does not return blockchain or market telemetry.',
|
|
110
|
+
inputSchema: {
|
|
111
|
+
type: 'object',
|
|
112
|
+
properties: {}
|
|
113
|
+
},
|
|
114
|
+
annotations: {
|
|
115
|
+
readOnlyHint: true,
|
|
116
|
+
destructiveHint: false,
|
|
117
|
+
idempotentHint: true,
|
|
118
|
+
openWorldHint: true
|
|
119
|
+
}
|
|
74
120
|
}
|
|
75
121
|
];
|
|
76
122
|
|
|
123
|
+
// Backwards compatibility alias lookup
|
|
124
|
+
const TOOL_ALIASES = Object.freeze({
|
|
125
|
+
audit_contract: 'm2m_audit_contract',
|
|
126
|
+
get_capability_score: 'm2m_audit_contract',
|
|
127
|
+
get_gas_fees: 'm2m_get_gas_metrics',
|
|
128
|
+
get_dex_metrics: 'm2m_get_dex_liquidity',
|
|
129
|
+
get_token_price: 'm2m_get_token_price',
|
|
130
|
+
get_whale_signals: 'm2m_get_whale_signals'
|
|
131
|
+
});
|
|
132
|
+
|
|
77
133
|
function parseX402Header(value) {
|
|
78
134
|
if (!value) return null;
|
|
79
135
|
try { return JSON.parse(value); } catch (_) {}
|
|
@@ -87,8 +143,8 @@ function parseX402Header(value) {
|
|
|
87
143
|
}
|
|
88
144
|
|
|
89
145
|
function queryApi(path, customApiKey = null, customBaseUrl = null) {
|
|
90
|
-
const activeBaseUrl = customBaseUrl || BASE_URL;
|
|
91
|
-
const activeApiKey = customApiKey !== null ? customApiKey : API_KEY;
|
|
146
|
+
const activeBaseUrl = customBaseUrl || process.env.M2M_SENTINEL_BASE_URL || BASE_URL;
|
|
147
|
+
const activeApiKey = customApiKey !== null ? customApiKey : (process.env.M2M_SENTINEL_API_KEY || API_KEY);
|
|
92
148
|
|
|
93
149
|
return new Promise((resolve, reject) => {
|
|
94
150
|
const url = new URL(path, activeBaseUrl);
|
|
@@ -129,12 +185,22 @@ function pathForTool(name, args) {
|
|
|
129
185
|
const input = args || {};
|
|
130
186
|
switch (name) {
|
|
131
187
|
case 'm2m_audit_contract':
|
|
132
|
-
case 'audit_contract':
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
188
|
+
case 'audit_contract': {
|
|
189
|
+
const addr = input.address ? String(input.address).trim() : '';
|
|
190
|
+
if (!addr) throw new Error('address is required');
|
|
191
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(addr)) {
|
|
192
|
+
throw new Error('A valid 40-hex 0x-prefixed Base contract address is required');
|
|
193
|
+
}
|
|
194
|
+
return '/v1/audit/' + encodeURIComponent(addr);
|
|
195
|
+
}
|
|
196
|
+
case 'get_capability_score': {
|
|
197
|
+
const addr = input.address ? String(input.address).trim() : '';
|
|
198
|
+
if (!addr) throw new Error('address is required');
|
|
199
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(addr)) {
|
|
200
|
+
throw new Error('A valid 40-hex 0x-prefixed Base contract address is required');
|
|
201
|
+
}
|
|
202
|
+
return '/v1/security/score/' + encodeURIComponent(addr);
|
|
203
|
+
}
|
|
138
204
|
case 'm2m_get_gas_metrics':
|
|
139
205
|
case 'get_gas_fees':
|
|
140
206
|
return '/v1/gas/fees';
|
|
@@ -142,9 +208,11 @@ function pathForTool(name, args) {
|
|
|
142
208
|
case 'get_dex_metrics':
|
|
143
209
|
return '/v1/dex/metrics';
|
|
144
210
|
case 'm2m_get_token_price':
|
|
145
|
-
case 'get_token_price':
|
|
146
|
-
|
|
147
|
-
|
|
211
|
+
case 'get_token_price': {
|
|
212
|
+
const sym = input.symbol ? String(input.symbol).trim().toUpperCase() : '';
|
|
213
|
+
if (!sym) throw new Error('symbol is required');
|
|
214
|
+
return '/v1/token/price/' + encodeURIComponent(sym);
|
|
215
|
+
}
|
|
148
216
|
case 'm2m_get_whale_signals':
|
|
149
217
|
case 'get_whale_signals':
|
|
150
218
|
return '/v1/whales/signals';
|
|
@@ -173,24 +241,32 @@ function failure(id, code, message, data) {
|
|
|
173
241
|
send({ jsonrpc: '2.0', id, error });
|
|
174
242
|
}
|
|
175
243
|
|
|
176
|
-
async function handleMcpMessage(request) {
|
|
244
|
+
async function handleMcpMessage(request, responder = null) {
|
|
245
|
+
const replySuccess = responder ? (id, res) => responder({ jsonrpc: '2.0', id, result: res }) : success;
|
|
246
|
+
const replyFailure = responder ? (id, code, msg, data) => {
|
|
247
|
+
const error = { code, message: msg };
|
|
248
|
+
if (data !== undefined) error.data = data;
|
|
249
|
+
responder({ jsonrpc: '2.0', id, error });
|
|
250
|
+
} : failure;
|
|
251
|
+
|
|
177
252
|
if (!request || request.jsonrpc !== '2.0' || typeof request.method !== 'string') {
|
|
178
|
-
|
|
253
|
+
replyFailure(request && request.id !== undefined ? request.id : null, -32600, 'Invalid Request');
|
|
179
254
|
return;
|
|
180
255
|
}
|
|
181
256
|
if (request.id === undefined || request.id === null) return;
|
|
182
257
|
|
|
183
258
|
try {
|
|
184
259
|
if (request.method === 'initialize') {
|
|
185
|
-
|
|
260
|
+
replySuccess(request.id, {
|
|
186
261
|
protocolVersion: request.params && request.params.protocolVersion ? request.params.protocolVersion : '2024-11-05',
|
|
187
262
|
capabilities: { tools: {} },
|
|
188
|
-
serverInfo: { name: 'm2m-sentinel-mcp', version: VERSION }
|
|
263
|
+
serverInfo: { name: 'm2m-sentinel-mcp', version: VERSION },
|
|
264
|
+
instructions: SERVER_INSTRUCTIONS
|
|
189
265
|
});
|
|
190
266
|
return;
|
|
191
267
|
}
|
|
192
268
|
if (request.method === 'tools/list') {
|
|
193
|
-
|
|
269
|
+
replySuccess(request.id, { tools: TOOLS });
|
|
194
270
|
return;
|
|
195
271
|
}
|
|
196
272
|
if (request.method === 'tools/call') {
|
|
@@ -198,7 +274,7 @@ async function handleMcpMessage(request) {
|
|
|
198
274
|
const path = pathForTool(params.name, params.arguments || {});
|
|
199
275
|
const apiResult = await queryApi(path);
|
|
200
276
|
const isError = !apiResult.ok;
|
|
201
|
-
|
|
277
|
+
replySuccess(request.id, {
|
|
202
278
|
content: [{ type: 'text', text: typeof apiResult.body === 'object' ? JSON.stringify(apiResult.body, null, 2) : String(apiResult.body) }],
|
|
203
279
|
isError,
|
|
204
280
|
structuredContent: apiResult.body,
|
|
@@ -212,9 +288,9 @@ async function handleMcpMessage(request) {
|
|
|
212
288
|
});
|
|
213
289
|
return;
|
|
214
290
|
}
|
|
215
|
-
|
|
291
|
+
replyFailure(request.id, -32601, 'Method not found');
|
|
216
292
|
} catch (err) {
|
|
217
|
-
|
|
293
|
+
replyFailure(request.id, -32603, err && err.message ? err.message : 'Internal error');
|
|
218
294
|
}
|
|
219
295
|
}
|
|
220
296
|
|
|
@@ -250,10 +326,10 @@ USAGE:
|
|
|
250
326
|
|
|
251
327
|
COMMANDS:
|
|
252
328
|
audit <address> Disassemble bytecode, detect capabilities & resolve proxies
|
|
253
|
-
gas Fetch
|
|
254
|
-
price <symbol> Fetch DEX
|
|
255
|
-
dex
|
|
256
|
-
whales
|
|
329
|
+
gas Fetch current Base Mainnet gas-price telemetry
|
|
330
|
+
price <symbol> Fetch median Base DEX token price from deepest pools (USDC, WETH, AERO)
|
|
331
|
+
dex Fetch aggregate tracked Base liquidity pool metrics
|
|
332
|
+
whales Fetch large ERC-20 transfer signals on Base (up to 50)
|
|
257
333
|
status Check live API, RPC quorum, and persistence health
|
|
258
334
|
mcp Launch Model Context Protocol stdio JSON-RPC server
|
|
259
335
|
|
|
@@ -419,13 +495,16 @@ async function runCli(args) {
|
|
|
419
495
|
|
|
420
496
|
} else if (commandName === 'gas' || commandName === 'fees') {
|
|
421
497
|
console.log('================================================================');
|
|
422
|
-
console.log('⛽ M2M SENTINEL — BASE GAS
|
|
498
|
+
console.log('⛽ M2M SENTINEL — BASE GAS PRICE TELEMETRY');
|
|
423
499
|
console.log(`Network: Base Mainnet (8453) | Sourced Latency: ${res.latencyMs}ms`);
|
|
424
500
|
console.log('================================================================');
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
501
|
+
const gasPrice = data && data.gasPrice && typeof data.gasPrice === 'object' ? data.gasPrice : data;
|
|
502
|
+
const gwei = gasPrice && gasPrice.gwei !== undefined
|
|
503
|
+
? gasPrice.gwei
|
|
504
|
+
: (data && data.gasPriceGwei !== undefined ? data.gasPriceGwei : (data && data.standard));
|
|
505
|
+
if (gwei !== undefined) console.log(`• Gas Price: ${gwei} gwei`);
|
|
506
|
+
if (gasPrice && gasPrice.wei !== undefined) console.log(`• Gas Price: ${gasPrice.wei} wei`);
|
|
507
|
+
if (data && data.provenance) console.log(`• RPC Provenance: ${JSON.stringify(data.provenance)}`);
|
|
429
508
|
console.log('================================================================\n');
|
|
430
509
|
|
|
431
510
|
} else if (commandName === 'price') {
|
|
@@ -473,20 +552,33 @@ async function runCli(args) {
|
|
|
473
552
|
// Entrypoint: Dual Mode Selection
|
|
474
553
|
// ---------------------------------------------------------------------------
|
|
475
554
|
|
|
476
|
-
|
|
555
|
+
if (require.main === module) {
|
|
556
|
+
const cliArgs = process.argv.slice(2);
|
|
477
557
|
|
|
478
|
-
// If arguments are passed or run in an interactive terminal, invoke CLI
|
|
479
|
-
if (cliArgs.length > 0 || (process.stdin.isTTY && !process.env.M2M_MCP_FORCE)) {
|
|
480
|
-
|
|
481
|
-
|
|
558
|
+
// If arguments are passed or run in an interactive terminal, invoke CLI
|
|
559
|
+
if (cliArgs.length > 0 || (process.stdin.isTTY && !process.env.M2M_MCP_FORCE)) {
|
|
560
|
+
if (cliArgs.length === 0) {
|
|
561
|
+
printHelp();
|
|
562
|
+
} else {
|
|
563
|
+
runCli(cliArgs).then((code) => {
|
|
564
|
+
if (code !== 0 && typeof code === 'number') process.exit(code);
|
|
565
|
+
});
|
|
566
|
+
}
|
|
482
567
|
} else {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
});
|
|
568
|
+
// Piped execution without args -> start MCP stdio server
|
|
569
|
+
startMcpServer();
|
|
486
570
|
}
|
|
487
|
-
} else {
|
|
488
|
-
// Piped execution without args -> start MCP stdio server
|
|
489
|
-
startMcpServer();
|
|
490
571
|
}
|
|
491
572
|
|
|
492
|
-
module.exports = {
|
|
573
|
+
module.exports = {
|
|
574
|
+
TOOLS,
|
|
575
|
+
TOOL_ALIASES,
|
|
576
|
+
SERVER_INSTRUCTIONS,
|
|
577
|
+
pathForTool,
|
|
578
|
+
parseX402Header,
|
|
579
|
+
runCli,
|
|
580
|
+
queryApi,
|
|
581
|
+
handleMcpMessage,
|
|
582
|
+
startMcpServer,
|
|
583
|
+
VERSION
|
|
584
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "m2m-sentinel-sdk",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
4
4
|
"mcpName": "io.github.M2M-Sentinel/m2m-sentinel-sdk",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
"m2m-sentinel": "./mcp_server.js",
|
|
17
17
|
"m2msentinel": "./mcp_server.js"
|
|
18
18
|
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node --test"
|
|
21
|
+
},
|
|
19
22
|
"license": "MIT",
|
|
20
23
|
"homepage": "https://m2msentinel.com",
|
|
21
24
|
"repository": {
|
|
@@ -54,6 +57,7 @@
|
|
|
54
57
|
"agent_adapter.js",
|
|
55
58
|
"x402_signer.js",
|
|
56
59
|
"eliza_plugin.js",
|
|
60
|
+
"langchain_tool.js",
|
|
57
61
|
"mcp_server.js",
|
|
58
62
|
"smithery.yaml",
|
|
59
63
|
"README.md",
|
package/smithery.yaml
CHANGED
|
@@ -13,7 +13,7 @@ startCommand:
|
|
|
13
13
|
M2M_SENTINEL_API_KEY:
|
|
14
14
|
type: string
|
|
15
15
|
title: M2M Sentinel API Key
|
|
16
|
-
description: Optional API key for
|
|
16
|
+
description: Optional API key for prepaid access. Protected calls may return structured 401, 402, 429, or 503 responses without authorized access.
|
|
17
17
|
default: ""
|
|
18
18
|
|
|
19
19
|
configSchema:
|
|
@@ -22,6 +22,6 @@ configSchema:
|
|
|
22
22
|
M2M_SENTINEL_API_KEY:
|
|
23
23
|
type: string
|
|
24
24
|
title: M2M Sentinel API Key
|
|
25
|
-
description: Optional API key for
|
|
25
|
+
description: Optional API key for prepaid access. Protected calls may return structured 401, 402, 429, or 503 responses without authorized access.
|
|
26
26
|
default: ""
|
|
27
27
|
|
|
@@ -186,6 +186,19 @@ interface ExecutionIdentityBlockedErrorInstance extends Error {
|
|
|
186
186
|
interface BaseAccountPaymasterGuardInstance {
|
|
187
187
|
request(provider: Eip1193Provider, request: WalletSendCallsRequest, options?: BaseAccountGuardCallOptions): Promise<unknown>;
|
|
188
188
|
sendCalls(provider: Eip1193Provider, request: WalletSendCallsRequest, options?: BaseAccountGuardCallOptions): Promise<unknown>;
|
|
189
|
+
preflightTransaction(transaction: TransactionPreflightRequest, options?: { policy?: BaseAccountCallerPolicy; signAndSend: (tx: any, obs: any) => Promise<unknown>; context?: Record<string, unknown> }): Promise<unknown>;
|
|
190
|
+
guardTransaction(transaction: TransactionPreflightRequest, options?: { policy?: BaseAccountCallerPolicy; signAndSend: (tx: any, obs: any) => Promise<unknown>; context?: Record<string, unknown> }): Promise<unknown>;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export interface PreflightBeforeSigningOptions {
|
|
194
|
+
client: Pick<M2MSentinelClient, 'preflightTransaction'>;
|
|
195
|
+
transaction: TransactionPreflightRequest;
|
|
196
|
+
policy: (
|
|
197
|
+
observation: BaseAccountPreflightObservation,
|
|
198
|
+
context: { transaction: TransactionPreflightRequest; [key: string]: unknown }
|
|
199
|
+
) => BaseAccountCallerPolicyResult | Promise<BaseAccountCallerPolicyResult>;
|
|
200
|
+
signAndSend: (transaction: TransactionPreflightRequest, observation: BaseAccountPreflightObservation) => Promise<unknown>;
|
|
201
|
+
context?: Record<string, unknown>;
|
|
189
202
|
}
|
|
190
203
|
|
|
191
204
|
// This source-distributed TypeScript entrypoint targets Node.js/CommonJS
|
|
@@ -243,6 +256,13 @@ export const preflightWalletSendCalls = guardWalletSendCalls;
|
|
|
243
256
|
export const sendCallsWithPreflight = guardWalletSendCalls;
|
|
244
257
|
export const executeWalletSendCalls = guardWalletSendCalls;
|
|
245
258
|
|
|
259
|
+
export function preflightBeforeSigning(options: PreflightBeforeSigningOptions): Promise<unknown> {
|
|
260
|
+
return runtime().preflightBeforeSigning(options);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export const guardTransaction = preflightBeforeSigning;
|
|
264
|
+
export const preflightTransactionBeforeSigning = preflightBeforeSigning;
|
|
265
|
+
|
|
246
266
|
export const BaseAccountPaymasterGuard: {
|
|
247
267
|
new (options: BaseAccountPaymasterGuardOptions): BaseAccountPaymasterGuardInstance;
|
|
248
268
|
} = runtime().BaseAccountPaymasterGuard;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "m2m-sentinel-sdk",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "m2m-sentinel-sdk",
|
|
9
|
-
"version": "1.2.
|
|
9
|
+
"version": "1.2.7",
|
|
10
10
|
"devDependencies": {
|
|
11
11
|
"@types/node": "^22.7.5",
|
|
12
12
|
"typescript": "^5.0.0"
|
package/typescript/package.json
CHANGED
package/typescript/x402.ts
CHANGED
|
@@ -318,7 +318,7 @@ export class X402SignerClient {
|
|
|
318
318
|
|
|
319
319
|
async fetchWithAutoPayment(path: string, options: any = {}): Promise<any> {
|
|
320
320
|
const url = path.startsWith('http') ? path : `${this.baseUrl}/${path.replace(/^\/+/, '')}`;
|
|
321
|
-
const headers = { 'Accept': 'application/json', 'User-Agent': '@m2msentinel/sdk-ts/1.2.
|
|
321
|
+
const headers = { 'Accept': 'application/json', 'User-Agent': '@m2msentinel/sdk-ts/1.2.7', ...(options.headers || {}) };
|
|
322
322
|
|
|
323
323
|
let res = await fetch(url, { method: options.method || 'GET', headers, body: options.body });
|
|
324
324
|
if (res.status !== 402) {
|
package/x402_signer.js
CHANGED
|
@@ -271,7 +271,7 @@ class X402SignerClient {
|
|
|
271
271
|
path: url.pathname + url.search,
|
|
272
272
|
method: options.method || 'GET',
|
|
273
273
|
headers: {
|
|
274
|
-
'User-Agent': 'M2M-Sentinel-X402Signer/1.2.
|
|
274
|
+
'User-Agent': 'M2M-Sentinel-X402Signer/1.2.7',
|
|
275
275
|
'Accept': 'application/json',
|
|
276
276
|
...(options.headers || {})
|
|
277
277
|
},
|