@purplesquirrel/robinhood-chain-mcp 0.1.0 → 0.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/dist/assets.d.ts +73 -0
- package/dist/assets.js +89 -0
- package/dist/assets.js.map +1 -0
- package/dist/blockscout.d.ts +42 -0
- package/dist/blockscout.js +85 -0
- package/dist/blockscout.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +92 -0
- package/dist/index.js.map +1 -0
- package/dist/networks.d.ts +24 -0
- package/dist/networks.js +43 -0
- package/dist/networks.js.map +1 -0
- package/dist/rpc.d.ts +27 -0
- package/dist/rpc.js +94 -0
- package/dist/rpc.js.map +1 -0
- package/dist/tools/account.d.ts +4 -0
- package/dist/tools/account.js +79 -0
- package/dist/tools/account.js.map +1 -0
- package/dist/tools/arbitrum.d.ts +23 -0
- package/dist/tools/arbitrum.js +213 -0
- package/dist/tools/arbitrum.js.map +1 -0
- package/dist/tools/chain.d.ts +3 -0
- package/dist/tools/chain.js +72 -0
- package/dist/tools/chain.js.map +1 -0
- package/dist/tools/contract.d.ts +3 -0
- package/dist/tools/contract.js +93 -0
- package/dist/tools/contract.js.map +1 -0
- package/dist/tools/eip3009.d.ts +9 -0
- package/dist/tools/eip3009.js +292 -0
- package/dist/tools/eip3009.js.map +1 -0
- package/dist/tools/erc20.d.ts +3 -0
- package/dist/tools/erc20.js +197 -0
- package/dist/tools/erc20.js.map +1 -0
- package/dist/tools/explorer.d.ts +5 -0
- package/dist/tools/explorer.js +160 -0
- package/dist/tools/explorer.js.map +1 -0
- package/dist/tools/nft.d.ts +4 -0
- package/dist/tools/nft.js +106 -0
- package/dist/tools/nft.js.map +1 -0
- package/dist/tools/oracle.d.ts +29 -0
- package/dist/tools/oracle.js +320 -0
- package/dist/tools/oracle.js.map +1 -0
- package/dist/tools/stocktokens.d.ts +27 -0
- package/dist/tools/stocktokens.js +322 -0
- package/dist/tools/stocktokens.js.map +1 -0
- package/dist/tools/token.d.ts +4 -0
- package/dist/tools/token.js +44 -0
- package/dist/tools/token.js.map +1 -0
- package/dist/tools/transaction.d.ts +4 -0
- package/dist/tools/transaction.js +47 -0
- package/dist/tools/transaction.js.map +1 -0
- package/dist/tools/uniswap.d.ts +34 -0
- package/dist/tools/uniswap.js +207 -0
- package/dist/tools/uniswap.js.map +1 -0
- package/dist/tools/usdg.d.ts +3 -0
- package/dist/tools/usdg.js +178 -0
- package/dist/tools/usdg.js.map +1 -0
- package/dist/tools/utils.d.ts +7 -0
- package/dist/tools/utils.js +116 -0
- package/dist/tools/utils.js.map +1 -0
- package/dist/tools/x402.d.ts +3 -0
- package/dist/tools/x402.js +128 -0
- package/dist/tools/x402.js.map +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +2 -1
package/dist/assets.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical Robinhood Chain assets and the EIP-3009 surface.
|
|
3
|
+
*
|
|
4
|
+
* USDG is the chain's native stablecoin (Paxos Global Dollar). It matters more than
|
|
5
|
+
* a typical ERC-20 here because Robinhood Chain has no canonical USDC — USDC bridged
|
|
6
|
+
* in from any of the 13 supported source chains arrives as USDG.
|
|
7
|
+
*/
|
|
8
|
+
export interface Eip712Domain {
|
|
9
|
+
name: string;
|
|
10
|
+
version: string;
|
|
11
|
+
chainId: number;
|
|
12
|
+
verifyingContract: `0x${string}`;
|
|
13
|
+
}
|
|
14
|
+
export interface KnownAsset {
|
|
15
|
+
symbol: string;
|
|
16
|
+
name: string;
|
|
17
|
+
address: `0x${string}`;
|
|
18
|
+
decimals: number;
|
|
19
|
+
/** Supports EIP-3009 transferWithAuthorization (gasless, third-party submitted). */
|
|
20
|
+
eip3009: boolean;
|
|
21
|
+
/** Supports EIP-2612 permit. */
|
|
22
|
+
eip2612: boolean;
|
|
23
|
+
domain?: Eip712Domain;
|
|
24
|
+
/** Expected DOMAIN_SEPARATOR, for drift detection against the live contract. */
|
|
25
|
+
expectedDomainSeparator?: `0x${string}`;
|
|
26
|
+
note?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare const KNOWN_ASSETS: Record<string, KnownAsset>;
|
|
29
|
+
export declare function resolveAsset(symbolOrAddress: string): KnownAsset | undefined;
|
|
30
|
+
/** Function selectors, for probing an unknown token's capabilities. */
|
|
31
|
+
export declare const SELECTORS: {
|
|
32
|
+
readonly name: "0x06fdde03";
|
|
33
|
+
readonly symbol: "0x95d89b41";
|
|
34
|
+
readonly decimals: "0x313ce567";
|
|
35
|
+
readonly totalSupply: "0x18160ddd";
|
|
36
|
+
readonly balanceOf: "0x70a08231";
|
|
37
|
+
readonly permit: "0xd505accf";
|
|
38
|
+
readonly nonces: "0x7ecebe00";
|
|
39
|
+
readonly DOMAIN_SEPARATOR: "0x3644e515";
|
|
40
|
+
readonly transferWithAuthorization: "0xe3ee160e";
|
|
41
|
+
readonly receiveWithAuthorization: "0xef55bec6";
|
|
42
|
+
readonly cancelAuthorization: "0x5a049a70";
|
|
43
|
+
readonly authorizationState: "0xe94a0102";
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Revert selectors that mean "this function does not exist here", as opposed to
|
|
47
|
+
* "this function exists and rejected your arguments". Distinguishing the two is the
|
|
48
|
+
* only reliable way to detect capabilities on a Diamond, whose facet code is not
|
|
49
|
+
* reachable from the proxy's bytecode.
|
|
50
|
+
*/
|
|
51
|
+
export declare const UNKNOWN_FUNCTION_REVERTS: Record<string, string>;
|
|
52
|
+
/** EIP-712 type for TransferWithAuthorization, per EIP-3009. */
|
|
53
|
+
export declare const TRANSFER_WITH_AUTHORIZATION_TYPES: {
|
|
54
|
+
readonly TransferWithAuthorization: readonly [{
|
|
55
|
+
readonly name: "from";
|
|
56
|
+
readonly type: "address";
|
|
57
|
+
}, {
|
|
58
|
+
readonly name: "to";
|
|
59
|
+
readonly type: "address";
|
|
60
|
+
}, {
|
|
61
|
+
readonly name: "value";
|
|
62
|
+
readonly type: "uint256";
|
|
63
|
+
}, {
|
|
64
|
+
readonly name: "validAfter";
|
|
65
|
+
readonly type: "uint256";
|
|
66
|
+
}, {
|
|
67
|
+
readonly name: "validBefore";
|
|
68
|
+
readonly type: "uint256";
|
|
69
|
+
}, {
|
|
70
|
+
readonly name: "nonce";
|
|
71
|
+
readonly type: "bytes32";
|
|
72
|
+
}];
|
|
73
|
+
};
|
package/dist/assets.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical Robinhood Chain assets and the EIP-3009 surface.
|
|
3
|
+
*
|
|
4
|
+
* USDG is the chain's native stablecoin (Paxos Global Dollar). It matters more than
|
|
5
|
+
* a typical ERC-20 here because Robinhood Chain has no canonical USDC — USDC bridged
|
|
6
|
+
* in from any of the 13 supported source chains arrives as USDG.
|
|
7
|
+
*/
|
|
8
|
+
export const KNOWN_ASSETS = {
|
|
9
|
+
USDG: {
|
|
10
|
+
symbol: "USDG",
|
|
11
|
+
name: "Global Dollar",
|
|
12
|
+
address: "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168",
|
|
13
|
+
decimals: 6,
|
|
14
|
+
eip3009: true,
|
|
15
|
+
eip2612: true,
|
|
16
|
+
// USDG does not expose version(), so the domain was derived by matching the
|
|
17
|
+
// on-chain DOMAIN_SEPARATOR rather than assumed:
|
|
18
|
+
// keccak256(abi.encode(EIP712Domain_typehash, keccak("Global Dollar"),
|
|
19
|
+
// keccak("1"), 4663, 0x5fc5...d168)) == the value below, exactly.
|
|
20
|
+
domain: {
|
|
21
|
+
name: "Global Dollar",
|
|
22
|
+
version: "1",
|
|
23
|
+
chainId: 4663,
|
|
24
|
+
verifyingContract: "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168",
|
|
25
|
+
},
|
|
26
|
+
expectedDomainSeparator: "0x7a3d7400b27830f4f91c2c16a082486d67c1befecaec2f53b33f1f35d5b62036",
|
|
27
|
+
note: "Native stablecoin. USDC bridged from other chains arrives as USDG. EIP-2535 Diamond — its facets do not appear in the proxy bytecode, so scanning bytecode for selectors gives a false negative.",
|
|
28
|
+
},
|
|
29
|
+
WETH: {
|
|
30
|
+
symbol: "WETH",
|
|
31
|
+
name: "Wrapped Ether",
|
|
32
|
+
address: "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73",
|
|
33
|
+
decimals: 18,
|
|
34
|
+
eip3009: false,
|
|
35
|
+
eip2612: false,
|
|
36
|
+
},
|
|
37
|
+
USDE: {
|
|
38
|
+
symbol: "USDE",
|
|
39
|
+
name: "Ethena USDe",
|
|
40
|
+
address: "0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34",
|
|
41
|
+
decimals: 18,
|
|
42
|
+
eip3009: false,
|
|
43
|
+
eip2612: false,
|
|
44
|
+
note: "eip3009/eip2612 flags not probed; treat as unverified.",
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
export function resolveAsset(symbolOrAddress) {
|
|
48
|
+
const needle = symbolOrAddress.trim().toLowerCase();
|
|
49
|
+
return Object.values(KNOWN_ASSETS).find((a) => a.symbol.toLowerCase() === needle || a.address.toLowerCase() === needle);
|
|
50
|
+
}
|
|
51
|
+
/** Function selectors, for probing an unknown token's capabilities. */
|
|
52
|
+
export const SELECTORS = {
|
|
53
|
+
// ERC-20
|
|
54
|
+
name: "0x06fdde03",
|
|
55
|
+
symbol: "0x95d89b41",
|
|
56
|
+
decimals: "0x313ce567",
|
|
57
|
+
totalSupply: "0x18160ddd",
|
|
58
|
+
balanceOf: "0x70a08231",
|
|
59
|
+
// EIP-2612
|
|
60
|
+
permit: "0xd505accf",
|
|
61
|
+
nonces: "0x7ecebe00",
|
|
62
|
+
DOMAIN_SEPARATOR: "0x3644e515",
|
|
63
|
+
// EIP-3009
|
|
64
|
+
transferWithAuthorization: "0xe3ee160e",
|
|
65
|
+
receiveWithAuthorization: "0xef55bec6",
|
|
66
|
+
cancelAuthorization: "0x5a049a70",
|
|
67
|
+
authorizationState: "0xe94a0102",
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Revert selectors that mean "this function does not exist here", as opposed to
|
|
71
|
+
* "this function exists and rejected your arguments". Distinguishing the two is the
|
|
72
|
+
* only reliable way to detect capabilities on a Diamond, whose facet code is not
|
|
73
|
+
* reachable from the proxy's bytecode.
|
|
74
|
+
*/
|
|
75
|
+
export const UNKNOWN_FUNCTION_REVERTS = {
|
|
76
|
+
"0x800ab12c": "FacetNotFound() — EIP-2535 Diamond, no facet for this selector",
|
|
77
|
+
};
|
|
78
|
+
/** EIP-712 type for TransferWithAuthorization, per EIP-3009. */
|
|
79
|
+
export const TRANSFER_WITH_AUTHORIZATION_TYPES = {
|
|
80
|
+
TransferWithAuthorization: [
|
|
81
|
+
{ name: "from", type: "address" },
|
|
82
|
+
{ name: "to", type: "address" },
|
|
83
|
+
{ name: "value", type: "uint256" },
|
|
84
|
+
{ name: "validAfter", type: "uint256" },
|
|
85
|
+
{ name: "validBefore", type: "uint256" },
|
|
86
|
+
{ name: "nonce", type: "bytes32" },
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=assets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets.js","sourceRoot":"","sources":["../src/assets.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAwBH,MAAM,CAAC,MAAM,YAAY,GAA+B;IACtD,IAAI,EAAE;QACJ,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,eAAe;QACrB,OAAO,EAAE,4CAA4C;QACrD,QAAQ,EAAE,CAAC;QACX,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,IAAI;QACb,4EAA4E;QAC5E,iDAAiD;QACjD,yEAAyE;QACzE,sEAAsE;QACtE,MAAM,EAAE;YACN,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,GAAG;YACZ,OAAO,EAAE,IAAI;YACb,iBAAiB,EAAE,4CAA4C;SAChE;QACD,uBAAuB,EACrB,oEAAoE;QACtE,IAAI,EAAE,kMAAkM;KACzM;IACD,IAAI,EAAE;QACJ,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,eAAe;QACrB,OAAO,EAAE,4CAA4C;QACrD,QAAQ,EAAE,EAAE;QACZ,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,KAAK;KACf;IACD,IAAI,EAAE;QACJ,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,4CAA4C;QACrD,QAAQ,EAAE,EAAE;QACZ,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,KAAK;QACd,IAAI,EAAE,wDAAwD;KAC/D;CACF,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,eAAuB;IAClD,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACpD,OAAO,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,IAAI,CACrC,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,MAAM,IAAI,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,MAAM,CAC1E,CAAC;AACJ,CAAC;AAED,uEAAuE;AACvE,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,SAAS;IACT,IAAI,EAAE,YAAY;IAClB,MAAM,EAAE,YAAY;IACpB,QAAQ,EAAE,YAAY;IACtB,WAAW,EAAE,YAAY;IACzB,SAAS,EAAE,YAAY;IACvB,WAAW;IACX,MAAM,EAAE,YAAY;IACpB,MAAM,EAAE,YAAY;IACpB,gBAAgB,EAAE,YAAY;IAC9B,WAAW;IACX,yBAAyB,EAAE,YAAY;IACvC,wBAAwB,EAAE,YAAY;IACtC,mBAAmB,EAAE,YAAY;IACjC,kBAAkB,EAAE,YAAY;CACxB,CAAC;AAEX;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAA2B;IAC9D,YAAY,EAAE,gEAAgE;CAC/E,CAAC;AAEF,gEAAgE;AAChE,MAAM,CAAC,MAAM,iCAAiC,GAAG;IAC/C,yBAAyB,EAAE;QACzB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE;QACjC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE;QAC/B,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE;QAClC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE;QACvC,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE;QACxC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE;KACnC;CACO,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blockscout REST v2 client.
|
|
3
|
+
*
|
|
4
|
+
* Robinhood Chain's explorer is a Blockscout instance, which gives indexed access the
|
|
5
|
+
* raw JSON-RPC cannot: token enumeration, holder counts, address transaction history.
|
|
6
|
+
* Only mainnet has one — testnet calls fail with a clear message rather than a
|
|
7
|
+
* confusing network error.
|
|
8
|
+
*/
|
|
9
|
+
import type { NetworkConfig } from "./networks.js";
|
|
10
|
+
/**
|
|
11
|
+
* Cloudflare (or another edge control) refused the request before Blockscout saw it.
|
|
12
|
+
* Distinct from a chain or contract problem: the explorer is up, we were turned away.
|
|
13
|
+
* The live canary skips on this rather than reporting a false chain change.
|
|
14
|
+
*/
|
|
15
|
+
export declare class ExplorerBlockedError extends Error {
|
|
16
|
+
constructor(path: string);
|
|
17
|
+
}
|
|
18
|
+
export declare class NoExplorerError extends Error {
|
|
19
|
+
constructor(network: string);
|
|
20
|
+
}
|
|
21
|
+
export declare class BlockscoutClient {
|
|
22
|
+
private network;
|
|
23
|
+
constructor(network: NetworkConfig);
|
|
24
|
+
get available(): boolean;
|
|
25
|
+
get<T = unknown>(path: string, params?: Record<string, string | number | undefined>): Promise<T>;
|
|
26
|
+
}
|
|
27
|
+
export interface TokenItem {
|
|
28
|
+
address?: string;
|
|
29
|
+
address_hash?: string;
|
|
30
|
+
symbol?: string;
|
|
31
|
+
name?: string;
|
|
32
|
+
decimals?: string;
|
|
33
|
+
type?: string;
|
|
34
|
+
holders?: string;
|
|
35
|
+
holders_count?: string;
|
|
36
|
+
total_supply?: string;
|
|
37
|
+
exchange_rate?: string | null;
|
|
38
|
+
circulating_market_cap?: string | null;
|
|
39
|
+
}
|
|
40
|
+
/** Blockscout has moved this field name between versions; accept either. */
|
|
41
|
+
export declare const tokenAddress: (t: TokenItem) => string;
|
|
42
|
+
export declare const tokenHolders: (t: TokenItem) => string;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blockscout REST v2 client.
|
|
3
|
+
*
|
|
4
|
+
* Robinhood Chain's explorer is a Blockscout instance, which gives indexed access the
|
|
5
|
+
* raw JSON-RPC cannot: token enumeration, holder counts, address transaction history.
|
|
6
|
+
* Only mainnet has one — testnet calls fail with a clear message rather than a
|
|
7
|
+
* confusing network error.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Cloudflare (or another edge control) refused the request before Blockscout saw it.
|
|
11
|
+
* Distinct from a chain or contract problem: the explorer is up, we were turned away.
|
|
12
|
+
* The live canary skips on this rather than reporting a false chain change.
|
|
13
|
+
*/
|
|
14
|
+
export class ExplorerBlockedError extends Error {
|
|
15
|
+
constructor(path) {
|
|
16
|
+
super(`Blockscout returned HTTP 403 for ${path}. The explorer is behind bot protection ` +
|
|
17
|
+
`that rejects non-browser clients; this is an access problem, not a chain change.`);
|
|
18
|
+
this.name = "ExplorerBlockedError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Full browser User-Agent. The instance's edge rejects every non-browser UA tried,
|
|
23
|
+
* including the conventional "Mozilla/5.0 (compatible; name/1.0; +url)" bot form.
|
|
24
|
+
*/
|
|
25
|
+
const BROWSER_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 " +
|
|
26
|
+
"(KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36";
|
|
27
|
+
export class NoExplorerError extends Error {
|
|
28
|
+
constructor(network) {
|
|
29
|
+
super(`No block explorer is published for ${network}. Explorer-backed tools are mainnet-only; JSON-RPC tools work on both networks.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export class BlockscoutClient {
|
|
33
|
+
network;
|
|
34
|
+
constructor(network) {
|
|
35
|
+
this.network = network;
|
|
36
|
+
}
|
|
37
|
+
get available() {
|
|
38
|
+
return Boolean(this.network.explorerApiUrl);
|
|
39
|
+
}
|
|
40
|
+
async get(path, params = {}) {
|
|
41
|
+
if (!this.network.explorerApiUrl) {
|
|
42
|
+
throw new NoExplorerError(this.network.name);
|
|
43
|
+
}
|
|
44
|
+
const url = new URL(this.network.explorerApiUrl + path);
|
|
45
|
+
for (const [k, v] of Object.entries(params)) {
|
|
46
|
+
if (v !== undefined)
|
|
47
|
+
url.searchParams.set(k, String(v));
|
|
48
|
+
}
|
|
49
|
+
// Blockscout is a public, rate-limited service that returns intermittent 5xx
|
|
50
|
+
// under bursts. Retry server errors with backoff; never retry 4xx, which are our
|
|
51
|
+
// fault and will fail identically every time.
|
|
52
|
+
//
|
|
53
|
+
// The one 4xx that is NOT our fault is 403. This instance sits behind Cloudflare
|
|
54
|
+
// bot management, which serves a "Just a moment..." interstitial to any client
|
|
55
|
+
// that does not present a browser User-Agent. Verified 2026-09-02: the UA below
|
|
56
|
+
// returns 200 while "robinhood-chain-mcp", "curl/8.7.1", a bare "node", and even
|
|
57
|
+
// the polite-bot form "Mozilla/5.0 (compatible; name/1.0; +url)" all return 403.
|
|
58
|
+
// A 403 therefore means "the explorer refused us", never "the chain changed" -
|
|
59
|
+
// callers distinguish the two via ExplorerBlockedError.
|
|
60
|
+
let lastStatus = 0;
|
|
61
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
62
|
+
const res = await fetch(url, {
|
|
63
|
+
headers: { accept: "application/json", "user-agent": BROWSER_UA },
|
|
64
|
+
signal: AbortSignal.timeout(30_000),
|
|
65
|
+
});
|
|
66
|
+
if (res.ok)
|
|
67
|
+
return (await res.json());
|
|
68
|
+
lastStatus = res.status;
|
|
69
|
+
if (res.status < 500)
|
|
70
|
+
break;
|
|
71
|
+
if (attempt < 2) {
|
|
72
|
+
await new Promise((r) => setTimeout(r, 400 * 2 ** attempt));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (lastStatus === 403) {
|
|
76
|
+
throw new ExplorerBlockedError(path);
|
|
77
|
+
}
|
|
78
|
+
throw new Error(`Blockscout HTTP ${lastStatus} for ${path}` +
|
|
79
|
+
(lastStatus >= 500 ? " (retried 3x — the explorer is likely rate-limiting or down)" : ""));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Blockscout has moved this field name between versions; accept either. */
|
|
83
|
+
export const tokenAddress = (t) => t.address ?? t.address_hash ?? "";
|
|
84
|
+
export const tokenHolders = (t) => t.holders ?? t.holders_count ?? "0";
|
|
85
|
+
//# sourceMappingURL=blockscout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blockscout.js","sourceRoot":"","sources":["../src/blockscout.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Robinhood Chain MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Robinhood Chain is an Arbitrum Orbit L2 settling on Ethereum (mainnet chain ID
|
|
6
|
+
* 4663, testnet 46630), fully EVM-compatible with ETH as the gas token.
|
|
7
|
+
*
|
|
8
|
+
* Read and build only: this server never holds keys, never signs, and never
|
|
9
|
+
* broadcasts. Transaction and EIP-3009 authorization tools return unsigned payloads
|
|
10
|
+
* for external signing.
|
|
11
|
+
*
|
|
12
|
+
* Environment:
|
|
13
|
+
* ROBINHOOD_NETWORK mainnet (default) | testnet
|
|
14
|
+
* ROBINHOOD_RPC_URL override the RPC endpoint (e.g. a private node)
|
|
15
|
+
*/
|
|
16
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
17
|
+
export declare const server: McpServer;
|
|
18
|
+
export declare const toolNames: string[];
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Robinhood Chain MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Robinhood Chain is an Arbitrum Orbit L2 settling on Ethereum (mainnet chain ID
|
|
6
|
+
* 4663, testnet 46630), fully EVM-compatible with ETH as the gas token.
|
|
7
|
+
*
|
|
8
|
+
* Read and build only: this server never holds keys, never signs, and never
|
|
9
|
+
* broadcasts. Transaction and EIP-3009 authorization tools return unsigned payloads
|
|
10
|
+
* for external signing.
|
|
11
|
+
*
|
|
12
|
+
* Environment:
|
|
13
|
+
* ROBINHOOD_NETWORK mainnet (default) | testnet
|
|
14
|
+
* ROBINHOOD_RPC_URL override the RPC endpoint (e.g. a private node)
|
|
15
|
+
*/
|
|
16
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
17
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
18
|
+
import { resolveNetwork } from "./networks.js";
|
|
19
|
+
import { RpcClient } from "./rpc.js";
|
|
20
|
+
import { BlockscoutClient } from "./blockscout.js";
|
|
21
|
+
import { registerChainTools } from "./tools/chain.js";
|
|
22
|
+
import { registerAccountTools } from "./tools/account.js";
|
|
23
|
+
import { registerContractTools } from "./tools/contract.js";
|
|
24
|
+
import { registerTransactionTools } from "./tools/transaction.js";
|
|
25
|
+
import { registerTokenTools } from "./tools/token.js";
|
|
26
|
+
import { registerUsdgTools } from "./tools/usdg.js";
|
|
27
|
+
import { registerArbitrumTools } from "./tools/arbitrum.js";
|
|
28
|
+
import { registerUtilTools } from "./tools/utils.js";
|
|
29
|
+
import { registerErc20Tools } from "./tools/erc20.js";
|
|
30
|
+
import { registerNftTools } from "./tools/nft.js";
|
|
31
|
+
import { registerEip3009Tools } from "./tools/eip3009.js";
|
|
32
|
+
import { registerExplorerTools } from "./tools/explorer.js";
|
|
33
|
+
import { registerX402Tools } from "./tools/x402.js";
|
|
34
|
+
import { registerStockTokenTools } from "./tools/stocktokens.js";
|
|
35
|
+
import { registerUniswapTools } from "./tools/uniswap.js";
|
|
36
|
+
import { registerOracleTools } from "./tools/oracle.js";
|
|
37
|
+
const network = resolveNetwork();
|
|
38
|
+
const rpc = new RpcClient(network);
|
|
39
|
+
const scout = new BlockscoutClient(network);
|
|
40
|
+
export const server = new McpServer({
|
|
41
|
+
name: "robinhood-chain-mcp",
|
|
42
|
+
version: "0.1.0",
|
|
43
|
+
});
|
|
44
|
+
export const toolNames = [];
|
|
45
|
+
const register = (name, description, shape,
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
+
handler) => {
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
49
|
+
server.tool(name, description, shape, async (args) => {
|
|
50
|
+
try {
|
|
51
|
+
return { content: [{ type: "text", text: await handler(args) }] };
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
56
|
+
isError: true,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
toolNames.push(name);
|
|
61
|
+
};
|
|
62
|
+
registerChainTools(register, rpc);
|
|
63
|
+
registerAccountTools(register, rpc, scout);
|
|
64
|
+
registerContractTools(register, rpc);
|
|
65
|
+
registerTransactionTools(register, rpc, scout);
|
|
66
|
+
registerTokenTools(register, rpc, scout);
|
|
67
|
+
registerUsdgTools(register, rpc);
|
|
68
|
+
registerArbitrumTools(register, rpc);
|
|
69
|
+
registerUtilTools(register);
|
|
70
|
+
registerErc20Tools(register, rpc);
|
|
71
|
+
registerNftTools(register, rpc, scout);
|
|
72
|
+
registerEip3009Tools(register, rpc);
|
|
73
|
+
registerExplorerTools(register, rpc, scout);
|
|
74
|
+
registerX402Tools(register, rpc);
|
|
75
|
+
registerStockTokenTools(register, rpc, scout);
|
|
76
|
+
registerUniswapTools(register, rpc);
|
|
77
|
+
registerOracleTools(register, rpc, scout);
|
|
78
|
+
async function main() {
|
|
79
|
+
const transport = new StdioServerTransport();
|
|
80
|
+
await server.connect(transport);
|
|
81
|
+
// stdout is the MCP transport — diagnostics must go to stderr or they corrupt it.
|
|
82
|
+
console.error(`robinhood-chain-mcp: ${toolNames.length} tools on ${network.name} (chainId ${network.chainId})` +
|
|
83
|
+
(scout.available ? "" : " — explorer-backed tools unavailable on this network"));
|
|
84
|
+
}
|
|
85
|
+
// Only start the transport when run as a binary, so tests can import the module.
|
|
86
|
+
if (process.env.ROBINHOOD_MCP_NO_START !== "1") {
|
|
87
|
+
main().catch((err) => {
|
|
88
|
+
console.error("Fatal:", err);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Robinhood Chain networks.
|
|
3
|
+
*
|
|
4
|
+
* Robinhood Chain is an Arbitrum Orbit L2 settling on Ethereum, fully EVM-compatible,
|
|
5
|
+
* with ETH as the gas token — there is no custom gas currency to account for.
|
|
6
|
+
*
|
|
7
|
+
* Both chain IDs below were confirmed against the live RPCs via eth_chainId
|
|
8
|
+
* (mainnet 0x1237 = 4663, testnet 0xb626 = 46630) rather than taken from docs.
|
|
9
|
+
*/
|
|
10
|
+
export interface NetworkConfig {
|
|
11
|
+
name: string;
|
|
12
|
+
chainId: number;
|
|
13
|
+
chainIdHex: string;
|
|
14
|
+
rpcUrl: string;
|
|
15
|
+
/** Blockscout instance, or undefined where none is published. */
|
|
16
|
+
explorerUrl?: string;
|
|
17
|
+
/** Blockscout REST v2 base, derived from explorerUrl. */
|
|
18
|
+
explorerApiUrl?: string;
|
|
19
|
+
nativeSymbol: string;
|
|
20
|
+
nativeDecimals: number;
|
|
21
|
+
testnet: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare const NETWORKS: Record<string, NetworkConfig>;
|
|
24
|
+
export declare function resolveNetwork(): NetworkConfig;
|
package/dist/networks.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Robinhood Chain networks.
|
|
3
|
+
*
|
|
4
|
+
* Robinhood Chain is an Arbitrum Orbit L2 settling on Ethereum, fully EVM-compatible,
|
|
5
|
+
* with ETH as the gas token — there is no custom gas currency to account for.
|
|
6
|
+
*
|
|
7
|
+
* Both chain IDs below were confirmed against the live RPCs via eth_chainId
|
|
8
|
+
* (mainnet 0x1237 = 4663, testnet 0xb626 = 46630) rather than taken from docs.
|
|
9
|
+
*/
|
|
10
|
+
export const NETWORKS = {
|
|
11
|
+
mainnet: {
|
|
12
|
+
name: "Robinhood Chain",
|
|
13
|
+
chainId: 4663,
|
|
14
|
+
chainIdHex: "0x1237",
|
|
15
|
+
rpcUrl: "https://rpc.mainnet.chain.robinhood.com/",
|
|
16
|
+
explorerUrl: "https://robinhoodchain.blockscout.com",
|
|
17
|
+
explorerApiUrl: "https://robinhoodchain.blockscout.com/api/v2",
|
|
18
|
+
nativeSymbol: "ETH",
|
|
19
|
+
nativeDecimals: 18,
|
|
20
|
+
testnet: false,
|
|
21
|
+
},
|
|
22
|
+
testnet: {
|
|
23
|
+
name: "Robinhood Chain Testnet",
|
|
24
|
+
chainId: 46630,
|
|
25
|
+
chainIdHex: "0xb626",
|
|
26
|
+
rpcUrl: "https://rpc.testnet.chain.robinhood.com/",
|
|
27
|
+
// Found in the docs-site JS bundle, not the documentation itself. An earlier
|
|
28
|
+
// guess at a robinhoodchain-testnet.blockscout.com host 404s, which is why this
|
|
29
|
+
// was briefly recorded as "no explorer" — verified live before being restored.
|
|
30
|
+
explorerUrl: "https://explorer.testnet.chain.robinhood.com",
|
|
31
|
+
explorerApiUrl: "https://explorer.testnet.chain.robinhood.com/api/v2",
|
|
32
|
+
nativeSymbol: "ETH",
|
|
33
|
+
nativeDecimals: 18,
|
|
34
|
+
testnet: true,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
export function resolveNetwork() {
|
|
38
|
+
const requested = (process.env.ROBINHOOD_NETWORK ?? "mainnet").toLowerCase();
|
|
39
|
+
const base = NETWORKS[requested] ?? NETWORKS.mainnet;
|
|
40
|
+
const rpcOverride = process.env.ROBINHOOD_RPC_URL;
|
|
41
|
+
return rpcOverride ? { ...base, rpcUrl: rpcOverride } : base;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=networks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"networks.js","sourceRoot":"","sources":["../src/networks.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgBH,MAAM,CAAC,MAAM,QAAQ,GAAkC;IACrD,OAAO,EAAE;QACP,IAAI,EAAE,iBAAiB;QACvB,OAAO,EAAE,IAAI;QACb,UAAU,EAAE,QAAQ;QACpB,MAAM,EAAE,0CAA0C;QAClD,WAAW,EAAE,uCAAuC;QACpD,cAAc,EAAE,8CAA8C;QAC9D,YAAY,EAAE,KAAK;QACnB,cAAc,EAAE,EAAE;QAClB,OAAO,EAAE,KAAK;KACf;IACD,OAAO,EAAE;QACP,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,KAAK;QACd,UAAU,EAAE,QAAQ;QACpB,MAAM,EAAE,0CAA0C;QAClD,6EAA6E;QAC7E,gFAAgF;QAChF,+EAA+E;QAC/E,WAAW,EAAE,8CAA8C;QAC3D,cAAc,EAAE,qDAAqD;QACrE,YAAY,EAAE,KAAK;QACnB,cAAc,EAAE,EAAE;QAClB,OAAO,EAAE,IAAI;KACd;CACF,CAAC;AAEF,MAAM,UAAU,cAAc;IAC5B,MAAM,SAAS,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC;IAC7E,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,CAAC,IAAI,QAAQ,CAAC,OAAO,CAAC;IACrD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IAClD,OAAO,WAAW,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/D,CAAC"}
|
package/dist/rpc.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { NetworkConfig } from "./networks.js";
|
|
2
|
+
/**
|
|
3
|
+
* A transport-level failure (HTTP status), as opposed to an RpcError, which is the
|
|
4
|
+
* chain answering. Callers that interpret errors as negative answers -- "this
|
|
5
|
+
* function does not exist", "this is not a stock token" -- MUST NOT swallow this:
|
|
6
|
+
* a rate-limit is not a fact about a contract.
|
|
7
|
+
*/
|
|
8
|
+
export declare class TransportError extends Error {
|
|
9
|
+
status: number;
|
|
10
|
+
constructor(status: number, message: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class RpcError extends Error {
|
|
13
|
+
code: number;
|
|
14
|
+
data?: unknown | undefined;
|
|
15
|
+
constructor(code: number, message: string, data?: unknown | undefined);
|
|
16
|
+
}
|
|
17
|
+
export declare class RpcClient {
|
|
18
|
+
private network;
|
|
19
|
+
constructor(network: NetworkConfig);
|
|
20
|
+
get config(): NetworkConfig;
|
|
21
|
+
call<T = unknown>(method: string, params?: unknown[]): Promise<T>;
|
|
22
|
+
}
|
|
23
|
+
export declare function toHex(value: number | bigint | string): string;
|
|
24
|
+
export declare function fromHex(hex: string): bigint;
|
|
25
|
+
/** Format a base-unit amount as a decimal string with its symbol. */
|
|
26
|
+
export declare function formatUnits(raw: bigint | string, decimals: number, symbol: string): string;
|
|
27
|
+
export declare function json(value: unknown): string;
|
package/dist/rpc.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A transport-level failure (HTTP status), as opposed to an RpcError, which is the
|
|
3
|
+
* chain answering. Callers that interpret errors as negative answers -- "this
|
|
4
|
+
* function does not exist", "this is not a stock token" -- MUST NOT swallow this:
|
|
5
|
+
* a rate-limit is not a fact about a contract.
|
|
6
|
+
*/
|
|
7
|
+
export class TransportError extends Error {
|
|
8
|
+
status;
|
|
9
|
+
constructor(status, message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.status = status;
|
|
12
|
+
this.name = "TransportError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class RpcError extends Error {
|
|
16
|
+
code;
|
|
17
|
+
data;
|
|
18
|
+
constructor(code, message, data) {
|
|
19
|
+
super(`RPC ${code}: ${message}`);
|
|
20
|
+
this.code = code;
|
|
21
|
+
this.data = data;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
let id = 0;
|
|
25
|
+
export class RpcClient {
|
|
26
|
+
network;
|
|
27
|
+
constructor(network) {
|
|
28
|
+
this.network = network;
|
|
29
|
+
}
|
|
30
|
+
get config() {
|
|
31
|
+
return this.network;
|
|
32
|
+
}
|
|
33
|
+
async call(method, params = []) {
|
|
34
|
+
// The public RPC rate-limits bursts with HTTP 429. Retry those (and 5xx) with
|
|
35
|
+
// backoff; never retry 4xx, which are our fault and will fail identically.
|
|
36
|
+
// This matters beyond convenience: callers that treat any thrown error as a
|
|
37
|
+
// negative answer would otherwise report a rate-limit as fact about a contract.
|
|
38
|
+
let lastStatus = 0;
|
|
39
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
40
|
+
const res = await fetch(this.network.rpcUrl, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: {
|
|
43
|
+
"content-type": "application/json",
|
|
44
|
+
// The Robinhood RPC rejects some default client User-Agents with HTTP 403
|
|
45
|
+
// (Node's urllib UA is refused outright), so set an explicit one.
|
|
46
|
+
"user-agent": "robinhood-chain-mcp",
|
|
47
|
+
},
|
|
48
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: ++id, method, params }),
|
|
49
|
+
signal: AbortSignal.timeout(30_000),
|
|
50
|
+
});
|
|
51
|
+
if (res.ok) {
|
|
52
|
+
const body = (await res.json());
|
|
53
|
+
if (body.error) {
|
|
54
|
+
throw new RpcError(body.error.code, body.error.message, body.error.data);
|
|
55
|
+
}
|
|
56
|
+
return body.result;
|
|
57
|
+
}
|
|
58
|
+
lastStatus = res.status;
|
|
59
|
+
const retryable = res.status === 429 || res.status >= 500;
|
|
60
|
+
if (!retryable || attempt === 3)
|
|
61
|
+
break;
|
|
62
|
+
const retryAfter = Number(res.headers.get("retry-after"));
|
|
63
|
+
const delay = Number.isFinite(retryAfter) && retryAfter > 0
|
|
64
|
+
? retryAfter * 1000
|
|
65
|
+
: 300 * 2 ** attempt;
|
|
66
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
67
|
+
}
|
|
68
|
+
throw new TransportError(lastStatus, `HTTP ${lastStatus} from ${this.network.rpcUrl}` +
|
|
69
|
+
(lastStatus === 429 ? " (rate limited; retried 4x)" : ""));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function toHex(value) {
|
|
73
|
+
if (typeof value === "string") {
|
|
74
|
+
return value.startsWith("0x") ? value : "0x" + BigInt(value).toString(16);
|
|
75
|
+
}
|
|
76
|
+
return "0x" + BigInt(value).toString(16);
|
|
77
|
+
}
|
|
78
|
+
export function fromHex(hex) {
|
|
79
|
+
return BigInt(hex);
|
|
80
|
+
}
|
|
81
|
+
/** Format a base-unit amount as a decimal string with its symbol. */
|
|
82
|
+
export function formatUnits(raw, decimals, symbol) {
|
|
83
|
+
const value = typeof raw === "string" ? BigInt(raw) : raw;
|
|
84
|
+
const base = 10n ** BigInt(decimals);
|
|
85
|
+
const whole = value / base;
|
|
86
|
+
const frac = value % base;
|
|
87
|
+
const fracStr = frac.toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
88
|
+
const decimal = fracStr.length > 0 ? `${whole}.${fracStr}` : whole.toString();
|
|
89
|
+
return `${decimal} ${symbol} (${value.toString()} base units)`;
|
|
90
|
+
}
|
|
91
|
+
export function json(value) {
|
|
92
|
+
return JSON.stringify(value, (_k, v) => (typeof v === "bigint" ? v.toString() : v), 2);
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=rpc.js.map
|
package/dist/rpc.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc.js","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"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"}
|