@satelink/sdk 0.2.0
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 +240 -0
- package/dist/adapters.d.ts +113 -0
- package/dist/adapters.d.ts.map +1 -0
- package/dist/adapters.js +178 -0
- package/dist/adapters.js.map +1 -0
- package/dist/ai.d.ts +12 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +71 -0
- package/dist/ai.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/mev.d.ts +101 -0
- package/dist/mev.d.ts.map +1 -0
- package/dist/mev.js +133 -0
- package/dist/mev.js.map +1 -0
- package/dist/provider.d.ts +36 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +97 -0
- package/dist/provider.js.map +1 -0
- package/dist/rpc.d.ts +32 -0
- package/dist/rpc.d.ts.map +1 -0
- package/dist/rpc.js +101 -0
- package/dist/rpc.js.map +1 -0
- package/dist/types.d.ts +66 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/eslint.config.js +1 -0
- package/package.json +71 -0
- package/src/adapters.ts +207 -0
- package/src/ai.ts +100 -0
- package/src/index.ts +43 -0
- package/src/mev.ts +213 -0
- package/src/provider.ts +134 -0
- package/src/rpc.ts +132 -0
- package/src/types.ts +73 -0
- package/tsconfig.json +19 -0
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@satelink/sdk",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Official SDK for the Satelink DePIN network — RPC, MEV relay, AI inference, and blockchain tools",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"require": "./dist/index.cjs",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./rpc": {
|
|
14
|
+
"import": "./dist/rpc.js",
|
|
15
|
+
"types": "./dist/rpc.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./mev": {
|
|
18
|
+
"import": "./dist/mev.js",
|
|
19
|
+
"types": "./dist/mev.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./adapters": {
|
|
22
|
+
"import": "./dist/adapters.js",
|
|
23
|
+
"types": "./dist/adapters.d.ts"
|
|
24
|
+
},
|
|
25
|
+
"./ai": {
|
|
26
|
+
"import": "./dist/ai.js",
|
|
27
|
+
"types": "./dist/ai.d.ts"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc",
|
|
32
|
+
"dev": "tsc --watch",
|
|
33
|
+
"lint": "echo 'Skipped (TypeScript handles linting)'",
|
|
34
|
+
"test": "vitest"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"satelink",
|
|
38
|
+
"depin",
|
|
39
|
+
"rpc",
|
|
40
|
+
"ethereum",
|
|
41
|
+
"polygon",
|
|
42
|
+
"mev",
|
|
43
|
+
"flashbots",
|
|
44
|
+
"blockchain",
|
|
45
|
+
"ai",
|
|
46
|
+
"openai",
|
|
47
|
+
"ethers",
|
|
48
|
+
"viem",
|
|
49
|
+
"wagmi"
|
|
50
|
+
],
|
|
51
|
+
"author": "Satelink Network",
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "https://github.com/Satelink-Protocol/Satelink_Network.git"
|
|
56
|
+
},
|
|
57
|
+
"homepage": "https://satelink.network",
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"typescript": "^5.4.0"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"ethers": ">=5.0.0",
|
|
63
|
+
"viem": ">=1.0.0",
|
|
64
|
+
"wagmi": ">=2.0.0"
|
|
65
|
+
},
|
|
66
|
+
"peerDependenciesMeta": {
|
|
67
|
+
"ethers": { "optional": true },
|
|
68
|
+
"viem": { "optional": true },
|
|
69
|
+
"wagmi": { "optional": true }
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/adapters.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework Adapters
|
|
3
|
+
* L8-002: Drop-in adapters for ethers.js, wagmi, and viem
|
|
4
|
+
*
|
|
5
|
+
* Makes Satelink RPC a drop-in replacement for Alchemy/Infura
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const BASE_URL = 'https://rpc.satelink.network';
|
|
9
|
+
|
|
10
|
+
export const SATELINK_CHAINS = {
|
|
11
|
+
polygon: { chainId: 137, name: 'Polygon', rpcUrl: `${BASE_URL}/rpc/polygon` },
|
|
12
|
+
ethereum: { chainId: 1, name: 'Ethereum', rpcUrl: `${BASE_URL}/rpc/ethereum` },
|
|
13
|
+
arbitrum: { chainId: 42161, name: 'Arbitrum One', rpcUrl: `${BASE_URL}/rpc/arbitrum` },
|
|
14
|
+
base: { chainId: 8453, name: 'Base', rpcUrl: `${BASE_URL}/rpc/base` },
|
|
15
|
+
amoy: { chainId: 80002, name: 'Polygon Amoy', rpcUrl: `${BASE_URL}/rpc/amoy` }
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
export type SatelinkChain = keyof typeof SATELINK_CHAINS;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get the RPC URL for a chain
|
|
22
|
+
*/
|
|
23
|
+
export function getSatelinkRpcUrl(chain: SatelinkChain = 'polygon', apiKey?: string): string {
|
|
24
|
+
const config = SATELINK_CHAINS[chain];
|
|
25
|
+
return apiKey ? `${config.rpcUrl}?apiKey=${apiKey}` : config.rpcUrl;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create an ethers.js v6 JsonRpcProvider for Satelink
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { getEthersProvider } from '@satelink/sdk';
|
|
34
|
+
* const provider = getEthersProvider('polygon');
|
|
35
|
+
* const block = await provider.getBlockNumber();
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function getEthersProvider(
|
|
39
|
+
chain: SatelinkChain = 'polygon',
|
|
40
|
+
apiKey?: string
|
|
41
|
+
): unknown {
|
|
42
|
+
const config = SATELINK_CHAINS[chain];
|
|
43
|
+
const url = config.rpcUrl;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
// Try ethers v6 first
|
|
47
|
+
const { JsonRpcProvider } = require('ethers');
|
|
48
|
+
const fetchRequest = apiKey ? {
|
|
49
|
+
url,
|
|
50
|
+
getUrlFunc: () => url,
|
|
51
|
+
processFunc: (req: unknown, resp: unknown) => resp,
|
|
52
|
+
setHeader: (key: string, value: string) => {},
|
|
53
|
+
} : undefined;
|
|
54
|
+
|
|
55
|
+
return new JsonRpcProvider(url, config.chainId, {
|
|
56
|
+
staticNetwork: true
|
|
57
|
+
});
|
|
58
|
+
} catch {
|
|
59
|
+
try {
|
|
60
|
+
// Fall back to ethers v5
|
|
61
|
+
const { providers } = require('ethers');
|
|
62
|
+
const connection = apiKey
|
|
63
|
+
? { url, headers: { 'X-API-Key': apiKey } }
|
|
64
|
+
: url;
|
|
65
|
+
return new providers.JsonRpcProvider(connection, config.chainId);
|
|
66
|
+
} catch {
|
|
67
|
+
throw new Error(
|
|
68
|
+
'@satelink/sdk ethers adapter requires ethers ^5.0.0 or ^6.0.0. ' +
|
|
69
|
+
'Install with: npm install ethers'
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Create a viem http transport for Satelink
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* import { createPublicClient } from 'viem';
|
|
81
|
+
* import { polygon } from 'viem/chains';
|
|
82
|
+
* import { getViemTransport } from '@satelink/sdk';
|
|
83
|
+
*
|
|
84
|
+
* const client = createPublicClient({
|
|
85
|
+
* chain: polygon,
|
|
86
|
+
* transport: getViemTransport('polygon'),
|
|
87
|
+
* });
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export function getViemTransport(
|
|
91
|
+
chain: SatelinkChain = 'polygon',
|
|
92
|
+
apiKey?: string
|
|
93
|
+
): unknown {
|
|
94
|
+
const config = SATELINK_CHAINS[chain];
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const { http } = require('viem');
|
|
98
|
+
return http(config.rpcUrl, {
|
|
99
|
+
fetchOptions: apiKey
|
|
100
|
+
? { headers: { 'X-API-Key': apiKey } }
|
|
101
|
+
: undefined,
|
|
102
|
+
retryCount: 3,
|
|
103
|
+
retryDelay: 150,
|
|
104
|
+
timeout: 20000
|
|
105
|
+
});
|
|
106
|
+
} catch {
|
|
107
|
+
throw new Error(
|
|
108
|
+
'@satelink/sdk viem adapter requires viem ^1.0.0 or ^2.0.0. ' +
|
|
109
|
+
'Install with: npm install viem'
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get a wagmi-compatible chain config for Satelink RPC
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* import { createConfig, http } from 'wagmi';
|
|
120
|
+
* import { getSatelinkChainConfig } from '@satelink/sdk';
|
|
121
|
+
*
|
|
122
|
+
* const config = createConfig({
|
|
123
|
+
* chains: [getSatelinkChainConfig('polygon')],
|
|
124
|
+
* transports: { 137: http('https://rpc.satelink.network/rpc/polygon') }
|
|
125
|
+
* });
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
export function getSatelinkChainConfig(chain: SatelinkChain = 'polygon') {
|
|
129
|
+
const config = SATELINK_CHAINS[chain];
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
id: config.chainId,
|
|
133
|
+
name: config.name,
|
|
134
|
+
nativeCurrency: getNativeCurrency(chain),
|
|
135
|
+
rpcUrls: {
|
|
136
|
+
default: { http: [config.rpcUrl] },
|
|
137
|
+
public: { http: [config.rpcUrl] }
|
|
138
|
+
},
|
|
139
|
+
blockExplorers: getBlockExplorer(chain)
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getNativeCurrency(chain: SatelinkChain) {
|
|
144
|
+
switch (chain) {
|
|
145
|
+
case 'polygon':
|
|
146
|
+
case 'amoy':
|
|
147
|
+
return { name: 'MATIC', symbol: 'MATIC', decimals: 18 };
|
|
148
|
+
case 'ethereum':
|
|
149
|
+
return { name: 'Ether', symbol: 'ETH', decimals: 18 };
|
|
150
|
+
case 'arbitrum':
|
|
151
|
+
return { name: 'Ether', symbol: 'ETH', decimals: 18 };
|
|
152
|
+
case 'base':
|
|
153
|
+
return { name: 'Ether', symbol: 'ETH', decimals: 18 };
|
|
154
|
+
default:
|
|
155
|
+
return { name: 'Ether', symbol: 'ETH', decimals: 18 };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function getBlockExplorer(chain: SatelinkChain) {
|
|
160
|
+
const explorers: Record<SatelinkChain, { default: { name: string; url: string } }> = {
|
|
161
|
+
polygon: { default: { name: 'Polygonscan', url: 'https://polygonscan.com' } },
|
|
162
|
+
ethereum: { default: { name: 'Etherscan', url: 'https://etherscan.io' } },
|
|
163
|
+
arbitrum: { default: { name: 'Arbiscan', url: 'https://arbiscan.io' } },
|
|
164
|
+
base: { default: { name: 'Basescan', url: 'https://basescan.org' } },
|
|
165
|
+
amoy: { default: { name: 'Polygonscan Amoy', url: 'https://amoy.polygonscan.com' } }
|
|
166
|
+
};
|
|
167
|
+
return explorers[chain];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Raw JSON-RPC call to Satelink (no dependencies)
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```ts
|
|
175
|
+
* import { rpc } from '@satelink/sdk';
|
|
176
|
+
* const blockNumber = await rpc('eth_blockNumber', [], 'polygon');
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
export async function rpc<T = unknown>(
|
|
180
|
+
method: string,
|
|
181
|
+
params: unknown[] = [],
|
|
182
|
+
chain: SatelinkChain = 'polygon',
|
|
183
|
+
apiKey?: string
|
|
184
|
+
): Promise<T> {
|
|
185
|
+
const config = SATELINK_CHAINS[chain];
|
|
186
|
+
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
187
|
+
if (apiKey) headers['X-API-Key'] = apiKey;
|
|
188
|
+
|
|
189
|
+
const response = await fetch(config.rpcUrl, {
|
|
190
|
+
method: 'POST',
|
|
191
|
+
headers,
|
|
192
|
+
body: JSON.stringify({
|
|
193
|
+
jsonrpc: '2.0',
|
|
194
|
+
id: Date.now(),
|
|
195
|
+
method,
|
|
196
|
+
params
|
|
197
|
+
})
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const data = await response.json();
|
|
201
|
+
|
|
202
|
+
if (data.error) {
|
|
203
|
+
throw new Error(`RPC error: ${data.error.message} (code: ${data.error.code})`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return data.result as T;
|
|
207
|
+
}
|
package/src/ai.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SatelinkAIOptions,
|
|
3
|
+
ChatMessage,
|
|
4
|
+
ChatCompletionOptions,
|
|
5
|
+
ChatCompletionResponse,
|
|
6
|
+
Model
|
|
7
|
+
} from './types.js';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_BASE_URL = 'https://rpc.satelink.network';
|
|
10
|
+
|
|
11
|
+
export class SatelinkAI {
|
|
12
|
+
private apiKey: string;
|
|
13
|
+
private baseUrl: string;
|
|
14
|
+
|
|
15
|
+
constructor(options: SatelinkAIOptions) {
|
|
16
|
+
if (!options.apiKey) {
|
|
17
|
+
throw new Error('API key is required for SatelinkAI');
|
|
18
|
+
}
|
|
19
|
+
this.apiKey = options.apiKey;
|
|
20
|
+
this.baseUrl = options.baseUrl || DEFAULT_BASE_URL;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private getHeaders(): Record<string, string> {
|
|
24
|
+
return {
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
'x-api-key': this.apiKey
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async chat(
|
|
31
|
+
messages: ChatMessage[],
|
|
32
|
+
options: ChatCompletionOptions = {}
|
|
33
|
+
): Promise<ChatCompletionResponse> {
|
|
34
|
+
const body = {
|
|
35
|
+
model: options.model || 'satelink-default',
|
|
36
|
+
messages,
|
|
37
|
+
max_tokens: options.max_tokens || 1000,
|
|
38
|
+
temperature: options.temperature,
|
|
39
|
+
stream: false
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: this.getHeaders(),
|
|
45
|
+
body: JSON.stringify(body)
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
const error = await response.json().catch(() => ({}));
|
|
50
|
+
throw new Error(
|
|
51
|
+
`AI request failed: ${response.status} - ${error.error?.message || response.statusText}`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return response.json();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async complete(prompt: string, options: ChatCompletionOptions = {}): Promise<string> {
|
|
59
|
+
const body = {
|
|
60
|
+
model: options.model || 'satelink-default',
|
|
61
|
+
prompt,
|
|
62
|
+
max_tokens: options.max_tokens || 100,
|
|
63
|
+
temperature: options.temperature
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const response = await fetch(`${this.baseUrl}/v1/completions`, {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: this.getHeaders(),
|
|
69
|
+
body: JSON.stringify(body)
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
const error = await response.json().catch(() => ({}));
|
|
74
|
+
throw new Error(
|
|
75
|
+
`AI request failed: ${response.status} - ${error.error?.message || response.statusText}`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const data = await response.json();
|
|
80
|
+
return data.choices?.[0]?.text || '';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async getModels(): Promise<Model[]> {
|
|
84
|
+
const response = await fetch(`${this.baseUrl}/v1/models`, {
|
|
85
|
+
method: 'GET',
|
|
86
|
+
headers: this.getHeaders()
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
throw new Error(`Failed to fetch models: ${response.status}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const data = await response.json();
|
|
94
|
+
return data.data || [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get models(): Promise<Model[]> {
|
|
98
|
+
return this.getModels();
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Core RPC client
|
|
2
|
+
export { SatelinkRPC, createProvider } from './rpc.js';
|
|
3
|
+
|
|
4
|
+
// EIP-1193 Provider
|
|
5
|
+
export { SatelinkProvider, createSatelinkProvider } from './provider.js';
|
|
6
|
+
|
|
7
|
+
// AI inference client
|
|
8
|
+
export { SatelinkAI } from './ai.js';
|
|
9
|
+
|
|
10
|
+
// MEV relay client
|
|
11
|
+
export { SatelinkMEV, createMevClient } from './mev.js';
|
|
12
|
+
|
|
13
|
+
// Framework adapters (ethers.js, viem, wagmi)
|
|
14
|
+
export {
|
|
15
|
+
SATELINK_CHAINS,
|
|
16
|
+
getSatelinkRpcUrl,
|
|
17
|
+
getEthersProvider,
|
|
18
|
+
getViemTransport,
|
|
19
|
+
getSatelinkChainConfig,
|
|
20
|
+
rpc
|
|
21
|
+
} from './adapters.js';
|
|
22
|
+
|
|
23
|
+
// Types
|
|
24
|
+
export type {
|
|
25
|
+
SatelinkRPCOptions,
|
|
26
|
+
SatelinkAIOptions,
|
|
27
|
+
JsonRpcRequest,
|
|
28
|
+
JsonRpcResponse,
|
|
29
|
+
ChatMessage,
|
|
30
|
+
ChatCompletionOptions,
|
|
31
|
+
ChatCompletionResponse,
|
|
32
|
+
Model,
|
|
33
|
+
TransactionRequest
|
|
34
|
+
} from './types.js';
|
|
35
|
+
export type { EIP1193Provider } from './rpc.js';
|
|
36
|
+
export type { SatelinkProviderOptions } from './provider.js';
|
|
37
|
+
export type { SatelinkChain } from './adapters.js';
|
|
38
|
+
export type {
|
|
39
|
+
MevClientOptions,
|
|
40
|
+
MevSubmitResult,
|
|
41
|
+
MevSimulationResult,
|
|
42
|
+
MevBundleStatus
|
|
43
|
+
} from './mev.js';
|
package/src/mev.ts
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MEV Relay Client
|
|
3
|
+
* L8-002: Satelink MEV private relay for searchers
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Private transaction submission
|
|
7
|
+
* - Bundle submission (Flashbots-compatible)
|
|
8
|
+
* - Bundle simulation (eth_callBundle)
|
|
9
|
+
* - Bundle status tracking
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface MevSubmitResult {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
txHash?: string;
|
|
15
|
+
bundleHash?: string;
|
|
16
|
+
provider?: string;
|
|
17
|
+
requestId?: string;
|
|
18
|
+
priceUsdt?: number;
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface MevSimulationResult {
|
|
23
|
+
ok: boolean;
|
|
24
|
+
simulation?: {
|
|
25
|
+
bundleHash?: string;
|
|
26
|
+
coinbaseDiff?: string;
|
|
27
|
+
ethSentToCoinbase?: string;
|
|
28
|
+
gasFees?: string;
|
|
29
|
+
totalGasUsed?: number;
|
|
30
|
+
results?: unknown[];
|
|
31
|
+
};
|
|
32
|
+
profitable?: boolean;
|
|
33
|
+
latency_ms?: number;
|
|
34
|
+
fee_usdt?: number;
|
|
35
|
+
error?: string;
|
|
36
|
+
requestId?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface MevBundleStatus {
|
|
40
|
+
ok: boolean;
|
|
41
|
+
bundleHash: string;
|
|
42
|
+
status: 'pending' | 'pending_high_priority' | 'not_found' | 'unknown';
|
|
43
|
+
stats?: {
|
|
44
|
+
isSimulated?: boolean;
|
|
45
|
+
isSentToMiners?: boolean;
|
|
46
|
+
isHighPriority?: boolean;
|
|
47
|
+
simulatedAt?: string;
|
|
48
|
+
submittedAt?: string;
|
|
49
|
+
sentToMinersAt?: string;
|
|
50
|
+
};
|
|
51
|
+
fee_usdt?: number;
|
|
52
|
+
error?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface MevClientOptions {
|
|
56
|
+
apiKey: string;
|
|
57
|
+
baseUrl?: string;
|
|
58
|
+
chain?: 'ethereum' | 'polygon' | 'arbitrum';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const DEFAULT_BASE_URL = 'https://rpc.satelink.network';
|
|
62
|
+
|
|
63
|
+
export class SatelinkMEV {
|
|
64
|
+
private apiKey: string;
|
|
65
|
+
private baseUrl: string;
|
|
66
|
+
private chain: string;
|
|
67
|
+
|
|
68
|
+
constructor(options: MevClientOptions) {
|
|
69
|
+
if (!options.apiKey) {
|
|
70
|
+
throw new Error('MEV relay requires an API key');
|
|
71
|
+
}
|
|
72
|
+
this.apiKey = options.apiKey;
|
|
73
|
+
this.baseUrl = options.baseUrl || DEFAULT_BASE_URL;
|
|
74
|
+
this.chain = options.chain || 'ethereum';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private getHeaders(): Record<string, string> {
|
|
78
|
+
return {
|
|
79
|
+
'Content-Type': 'application/json',
|
|
80
|
+
'X-API-Key': this.apiKey
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Submit a signed transaction to the private mempool
|
|
86
|
+
* Transaction is NOT broadcast publicly — goes directly to validators
|
|
87
|
+
*/
|
|
88
|
+
async submitPrivateTransaction(signedTx: string): Promise<MevSubmitResult> {
|
|
89
|
+
const response = await fetch(`${this.baseUrl}/rpc/mev?chain=${this.chain}`, {
|
|
90
|
+
method: 'POST',
|
|
91
|
+
headers: this.getHeaders(),
|
|
92
|
+
body: JSON.stringify({
|
|
93
|
+
jsonrpc: '2.0',
|
|
94
|
+
id: Date.now(),
|
|
95
|
+
method: 'eth_sendRawTransaction',
|
|
96
|
+
params: [signedTx]
|
|
97
|
+
})
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const data = await response.json();
|
|
101
|
+
|
|
102
|
+
if (data.error) {
|
|
103
|
+
return { ok: false, error: data.error.message || data.error };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
ok: true,
|
|
108
|
+
txHash: data.result,
|
|
109
|
+
provider: data._mev?.provider,
|
|
110
|
+
requestId: data._mev?.requestId,
|
|
111
|
+
priceUsdt: data._mev?.priceUsdt
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Submit a bundle of transactions (Flashbots-compatible)
|
|
117
|
+
* All transactions execute atomically or none do
|
|
118
|
+
*/
|
|
119
|
+
async submitBundle(
|
|
120
|
+
txs: string[],
|
|
121
|
+
options: {
|
|
122
|
+
blockNumber?: string | number;
|
|
123
|
+
minTimestamp?: number;
|
|
124
|
+
maxTimestamp?: number;
|
|
125
|
+
} = {}
|
|
126
|
+
): Promise<MevSubmitResult> {
|
|
127
|
+
const response = await fetch(`${this.baseUrl}/rpc/mev/bundle?chain=${this.chain}`, {
|
|
128
|
+
method: 'POST',
|
|
129
|
+
headers: this.getHeaders(),
|
|
130
|
+
body: JSON.stringify({
|
|
131
|
+
txs,
|
|
132
|
+
blockNumber: options.blockNumber,
|
|
133
|
+
minTimestamp: options.minTimestamp,
|
|
134
|
+
maxTimestamp: options.maxTimestamp
|
|
135
|
+
})
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const data = await response.json();
|
|
139
|
+
|
|
140
|
+
if (!data.ok) {
|
|
141
|
+
return { ok: false, error: data.error || data.message };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
ok: true,
|
|
146
|
+
bundleHash: data.bundleHash,
|
|
147
|
+
provider: data.provider,
|
|
148
|
+
requestId: data.requestId,
|
|
149
|
+
priceUsdt: data.priceUsdt
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Simulate a bundle without submitting (eth_callBundle)
|
|
155
|
+
* Use this to check profitability before paying for submission
|
|
156
|
+
*/
|
|
157
|
+
async simulateBundle(
|
|
158
|
+
txs: string[],
|
|
159
|
+
options: {
|
|
160
|
+
blockNumber?: string;
|
|
161
|
+
stateBlockNumber?: string;
|
|
162
|
+
} = {}
|
|
163
|
+
): Promise<MevSimulationResult> {
|
|
164
|
+
const response = await fetch(`${this.baseUrl}/rpc/mev/bundle/simulate`, {
|
|
165
|
+
method: 'POST',
|
|
166
|
+
headers: this.getHeaders(),
|
|
167
|
+
body: JSON.stringify({
|
|
168
|
+
txs,
|
|
169
|
+
blockNumber: options.blockNumber,
|
|
170
|
+
stateBlockNumber: options.stateBlockNumber
|
|
171
|
+
})
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return response.json();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Check if a bundle was included on-chain
|
|
179
|
+
*/
|
|
180
|
+
async getBundleStatus(bundleHash: string, blockNumber?: string): Promise<MevBundleStatus> {
|
|
181
|
+
const url = new URL(`${this.baseUrl}/rpc/mev/bundle/${bundleHash}`);
|
|
182
|
+
if (blockNumber) url.searchParams.set('blockNumber', blockNumber);
|
|
183
|
+
|
|
184
|
+
const response = await fetch(url.toString(), {
|
|
185
|
+
method: 'GET',
|
|
186
|
+
headers: this.getHeaders()
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return response.json();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get MEV relay status and stats
|
|
194
|
+
*/
|
|
195
|
+
async getStatus(): Promise<unknown> {
|
|
196
|
+
const response = await fetch(`${this.baseUrl}/rpc/mev/status`);
|
|
197
|
+
return response.json();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Set the target chain for MEV operations
|
|
202
|
+
*/
|
|
203
|
+
setChain(chain: 'ethereum' | 'polygon' | 'arbitrum'): void {
|
|
204
|
+
this.chain = chain;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Create a Satelink MEV client
|
|
210
|
+
*/
|
|
211
|
+
export function createMevClient(options: MevClientOptions): SatelinkMEV {
|
|
212
|
+
return new SatelinkMEV(options);
|
|
213
|
+
}
|