-
Notifications
You must be signed in to change notification settings - Fork 64
/
sendJsonRpcPayload.ts
142 lines (129 loc) · 4.2 KB
/
sendJsonRpcPayload.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import assertNever from "assert-never";
import {
FullConfig,
JsonRpcRequest,
JsonRpcResponse,
JsonRpcSenderMiddleware,
Provider,
SingleOrBatchRequest,
SingleOrBatchResponse,
} from "../types";
import { delay, promisify } from "../util/promises";
import { AlchemySendJsonRpcFunction } from "./alchemySend";
const ALCHEMY_DISALLOWED_METHODS: string[] = [
"eth_accounts",
"eth_sendTransaction",
"eth_sign",
"eth_signTypedData_v3",
"eth_signTypedData",
];
const ALCHEMY_DISALLOWED_PREFIXES: string[] = ["personal"];
export interface JsonRpcPayloadSender {
sendJsonRpcPayload: SendJsonRpcPayloadFunction;
setWriteProvider(writeProvider: Provider | null | undefined): void;
}
export interface SendJsonRpcPayloadFunction {
(payload: JsonRpcRequest): Promise<JsonRpcResponse>;
(payload: SingleOrBatchRequest): Promise<SingleOrBatchResponse>;
}
export function makeJsonRpcPayloadSender(
alchemySendJsonRpc: AlchemySendJsonRpcFunction,
config: FullConfig,
): JsonRpcPayloadSender {
// Copy middlewares from config.
const middlewares: JsonRpcSenderMiddleware[] = [];
config.jsonRpcSenderMiddlewares.forEach((m) => middlewares.push(m));
let currentWriteProvider = config.writeProvider;
middlewares.push((payload) => {
const disallowedMethod = getDisallowedMethod(payload);
if (!disallowedMethod) {
try {
return sendJsonRpcWithRetries(payload, alchemySendJsonRpc, config);
} catch (alchemyError) {
// Fallback to write provider, but if both fail throw the error from
// Alchemy.
if (!currentWriteProvider) {
throw alchemyError;
}
try {
return sendJsonRpcWithProvider(currentWriteProvider, payload);
} catch {
throw alchemyError;
}
}
} else {
if (!currentWriteProvider) {
throw new Error(
`No provider available for method "${disallowedMethod}"`,
);
}
return sendJsonRpcWithProvider(currentWriteProvider, payload);
}
});
const sendJsonRpcPayload = (
payload: SingleOrBatchRequest,
): Promise<SingleOrBatchResponse> => {
const getNext = (i: number) => {
const middleware = middlewares[i];
return () => middleware(payload, getNext(i + 1));
};
return getNext(0)();
};
function setWriteProvider(writeProvider: Provider | null | undefined) {
currentWriteProvider = writeProvider ?? null;
}
return {
sendJsonRpcPayload: sendJsonRpcPayload as SendJsonRpcPayloadFunction,
setWriteProvider,
};
}
function sendJsonRpcWithProvider(
provider: Provider,
payload: SingleOrBatchRequest,
): Promise<SingleOrBatchResponse> {
const anyProvider: any = provider;
const sendMethod = (
anyProvider.sendAsync ? anyProvider.sendAsync : anyProvider.send
).bind(anyProvider);
return promisify((callback) => sendMethod(payload, callback));
}
function getDisallowedMethod(
payload: SingleOrBatchRequest,
): string | undefined {
const payloads = Array.isArray(payload) ? payload : [payload];
// Check if the payload method is a disallowed method or starts with a
// disallowed prefix.
const disallowedRequest =
payloads.find(
(p) =>
ALCHEMY_DISALLOWED_METHODS.indexOf(p.method) >= 0 ||
ALCHEMY_DISALLOWED_PREFIXES.some((prefix) =>
p.method.startsWith(prefix),
),
) || undefined;
return disallowedRequest && disallowedRequest.method;
}
async function sendJsonRpcWithRetries(
payload: SingleOrBatchRequest,
alchemySendJsonRpc: AlchemySendJsonRpcFunction,
{ maxRetries, retryInterval, retryJitter }: FullConfig,
): Promise<SingleOrBatchResponse> {
for (let i = 0; i < maxRetries + 1; i++) {
const result = await alchemySendJsonRpc(payload);
switch (result.type) {
case "jsonrpc":
return result.response;
case "rateLimit":
break;
case "networkError": {
const { status, message } = result;
const statusString = status !== 0 ? `(${status}) ` : "";
throw new Error(`${statusString} ${message}`);
}
default:
return assertNever(result);
}
await delay(retryInterval + ((retryJitter * Math.random()) | 0));
}
throw new Error(`Rate limited for ${maxRetries + 1} consecutive attempts.`);
}