@ember-finance/sdk 2.11.0-pre.7 → 2.11.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/dist/src/abis/EmberDepositParkingLot.json +175 -43
- package/dist/src/api/v2/apis/vaults-api.d.ts +0 -42
- package/dist/src/api/v2/apis/vaults-api.js +0 -87
- package/dist/src/api/v2/models/chain.d.ts +0 -6
- package/dist/src/api/v2/models/index.d.ts +0 -3
- package/dist/src/api/v2/models/index.js +0 -3
- package/dist/src/api/v2/models/instant-withdraw-config.d.ts +2 -21
- package/dist/src/api/v2/models/poc-report-protocol.d.ts +0 -13
- package/dist/src/api/v2/models/transparency-row.d.ts +1 -1
- package/dist/src/evm-vaults/interfaces/index.d.ts +5 -0
- package/dist/src/evm-vaults/on-chain-calls/operator.d.ts +16 -0
- package/dist/src/evm-vaults/on-chain-calls/operator.js +20 -0
- package/dist/src/evm-vaults/on-chain-calls/tx-builder.d.ts +21 -0
- package/dist/src/evm-vaults/on-chain-calls/tx-builder.js +39 -0
- package/dist/src/evm-vaults/on-chain-calls/vault-admin.d.ts +16 -0
- package/dist/src/evm-vaults/on-chain-calls/vault-admin.js +20 -0
- package/dist/src/evm-vaults/on-chain-calls/vault-reader.js +3 -1
- package/package.json +1 -6
- package/dist/src/api/v2/models/coin-placement.d.ts +0 -113
- package/dist/src/api/v2/models/coin-placement.js +0 -17
- package/dist/src/api/v2/models/coin-placements-response.d.ts +0 -25
- package/dist/src/api/v2/models/coin-placements-response.js +0 -14
- package/dist/src/api/v2/models/poc-report-protocol-asset.d.ts +0 -60
- package/dist/src/api/v2/models/poc-report-protocol-asset.js +0 -18
- package/dist/src/placements/adapters/alphalend.d.ts +0 -1
- package/dist/src/placements/adapters/alphalend.js +0 -58
- package/dist/src/placements/adapters/bluefin-spot.d.ts +0 -1
- package/dist/src/placements/adapters/bluefin-spot.js +0 -78
- package/dist/src/placements/adapters/current.d.ts +0 -1
- package/dist/src/placements/adapters/current.js +0 -70
- package/dist/src/placements/adapters/evm-protocols.d.ts +0 -1
- package/dist/src/placements/adapters/evm-protocols.js +0 -8
- package/dist/src/placements/adapters/factory.d.ts +0 -15
- package/dist/src/placements/adapters/factory.js +0 -62
- package/dist/src/placements/adapters/index.d.ts +0 -14
- package/dist/src/placements/adapters/index.js +0 -25
- package/dist/src/placements/adapters/navi.d.ts +0 -1
- package/dist/src/placements/adapters/navi.js +0 -53
- package/dist/src/placements/adapters/pendle.d.ts +0 -9
- package/dist/src/placements/adapters/pendle.js +0 -108
- package/dist/src/placements/adapters/royco.d.ts +0 -2
- package/dist/src/placements/adapters/royco.js +0 -49
- package/dist/src/placements/adapters/suilend.d.ts +0 -1
- package/dist/src/placements/adapters/suilend.js +0 -57
- package/dist/src/placements/batch.d.ts +0 -70
- package/dist/src/placements/batch.js +0 -197
- package/dist/src/placements/client.d.ts +0 -17
- package/dist/src/placements/client.js +0 -194
- package/dist/src/placements/evm.d.ts +0 -29
- package/dist/src/placements/evm.js +0 -280
- package/dist/src/placements/index.d.ts +0 -9
- package/dist/src/placements/index.js +0 -9
- package/dist/src/placements/pointers.d.ts +0 -24
- package/dist/src/placements/pointers.js +0 -126
- package/dist/src/placements/registry.d.ts +0 -11
- package/dist/src/placements/registry.js +0 -19
- package/dist/src/placements/sui.d.ts +0 -47
- package/dist/src/placements/sui.js +0 -302
- package/dist/src/placements/types.d.ts +0 -147
- package/dist/src/placements/types.js +0 -1
- package/dist/src/placements/vaults.d.ts +0 -15
- package/dist/src/placements/vaults.js +0 -97
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import { Batcher, ChunkTooLargeError, TransientError, isAbortError, isTransientStatus, retryAfterMs } from "./batch.js";
|
|
2
|
-
/** `balanceOf(address)` */
|
|
3
|
-
const BALANCE_OF_SELECTOR = "0x70a08231";
|
|
4
|
-
export function encodeBalanceOf(owner) {
|
|
5
|
-
const address = owner.toLowerCase().replace(/^0x/, "").padStart(64, "0");
|
|
6
|
-
return `${BALANCE_OF_SELECTOR}${address}`;
|
|
7
|
-
}
|
|
8
|
-
export function asContractAddress(address) {
|
|
9
|
-
// An EVM node rejects an unprefixed `to` outright rather than coercing it,
|
|
10
|
-
// so a pointer carrying a bare address would fail the whole read.
|
|
11
|
-
const trimmed = address.trim();
|
|
12
|
-
return trimmed.startsWith("0x") ? trimmed : `0x${trimmed}`;
|
|
13
|
-
}
|
|
14
|
-
export function decodeUint256(hex) {
|
|
15
|
-
if (!hex || hex === "0x")
|
|
16
|
-
return 0n;
|
|
17
|
-
return BigInt(hex);
|
|
18
|
-
}
|
|
19
|
-
export const MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";
|
|
20
|
-
const AGGREGATE3_SELECTOR = "0x82ad56cb";
|
|
21
|
-
const RATE_LIMITED = /too many requests|rate ?limit/i;
|
|
22
|
-
const HEX = /^(0x)?[0-9a-fA-F]*$/;
|
|
23
|
-
class HttpError extends Error {
|
|
24
|
-
constructor(status) {
|
|
25
|
-
super(`evm rpc ${status}`);
|
|
26
|
-
this.name = "HttpError";
|
|
27
|
-
this.status = status;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
function stripHex(value) {
|
|
31
|
-
return value.trim().replace(/^0x/i, "");
|
|
32
|
-
}
|
|
33
|
-
function word(value) {
|
|
34
|
-
return BigInt(value).toString(16).padStart(64, "0");
|
|
35
|
-
}
|
|
36
|
-
function addressWord(address) {
|
|
37
|
-
return stripHex(address).toLowerCase().padStart(64, "0");
|
|
38
|
-
}
|
|
39
|
-
function bytesWords(data) {
|
|
40
|
-
const raw = stripHex(data);
|
|
41
|
-
const length = raw.length / 2;
|
|
42
|
-
const padded = raw.padEnd(Math.ceil(length / 32) * 64, "0");
|
|
43
|
-
return word(length) + padded;
|
|
44
|
-
}
|
|
45
|
-
export function encodeAggregate3(calls) {
|
|
46
|
-
const tuples = calls.map(call => addressWord(call.to) + word(1) + word(0x60) + bytesWords(call.data));
|
|
47
|
-
let offset = calls.length * 32;
|
|
48
|
-
const offsets = tuples.map(tuple => {
|
|
49
|
-
const current = offset;
|
|
50
|
-
offset += tuple.length / 2;
|
|
51
|
-
return word(current);
|
|
52
|
-
});
|
|
53
|
-
return (AGGREGATE3_SELECTOR +
|
|
54
|
-
word(0x20) +
|
|
55
|
-
word(calls.length) +
|
|
56
|
-
offsets.join("") +
|
|
57
|
-
tuples.join(""));
|
|
58
|
-
}
|
|
59
|
-
export function decodeAggregate3(data, count) {
|
|
60
|
-
const raw = stripHex(data);
|
|
61
|
-
const read = (byteOffset) => {
|
|
62
|
-
const slice = raw.slice(byteOffset * 2, byteOffset * 2 + 64);
|
|
63
|
-
if (slice.length !== 64)
|
|
64
|
-
throw new Error("multicall: truncated response");
|
|
65
|
-
return Number(BigInt(`0x${slice}`));
|
|
66
|
-
};
|
|
67
|
-
const arrayOffset = read(0);
|
|
68
|
-
const length = read(arrayOffset);
|
|
69
|
-
if (length !== count)
|
|
70
|
-
throw new Error("multicall: result count mismatch");
|
|
71
|
-
const base = arrayOffset + 32;
|
|
72
|
-
const results = [];
|
|
73
|
-
for (let index = 0; index < length; index += 1) {
|
|
74
|
-
const tupleOffset = base + read(base + index * 32);
|
|
75
|
-
const success = read(tupleOffset) === 1;
|
|
76
|
-
const bytesOffset = tupleOffset + read(tupleOffset + 32);
|
|
77
|
-
const bytesLength = read(bytesOffset);
|
|
78
|
-
const start = (bytesOffset + 32) * 2;
|
|
79
|
-
const body = raw.slice(start, start + bytesLength * 2);
|
|
80
|
-
if (body.length !== bytesLength * 2)
|
|
81
|
-
throw new Error("multicall: truncated response");
|
|
82
|
-
results.push({ success, data: `0x${body}` });
|
|
83
|
-
}
|
|
84
|
-
return results;
|
|
85
|
-
}
|
|
86
|
-
function jsonRpcRequest(method, params, id) {
|
|
87
|
-
return { jsonrpc: "2.0", id, method, params };
|
|
88
|
-
}
|
|
89
|
-
function callRequest(call, id) {
|
|
90
|
-
return jsonRpcRequest("eth_call", [{ to: call.to, data: call.data }, "latest"], id);
|
|
91
|
-
}
|
|
92
|
-
function isRateLimited(entry) {
|
|
93
|
-
const error = entry?.error;
|
|
94
|
-
if (!error)
|
|
95
|
-
return false;
|
|
96
|
-
return error.code === -32005 || RATE_LIMITED.test(error.message ?? "");
|
|
97
|
-
}
|
|
98
|
-
function toResult(entry) {
|
|
99
|
-
if (!entry)
|
|
100
|
-
return { ok: false, error: new Error("evm rpc: no result for call") };
|
|
101
|
-
if (isRateLimited(entry)) {
|
|
102
|
-
return {
|
|
103
|
-
ok: false,
|
|
104
|
-
error: new TransientError(`evm rpc: ${entry.error?.message}`)
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
if (entry.error)
|
|
108
|
-
return { ok: false, error: new Error(`evm rpc: ${entry.error.message}`) };
|
|
109
|
-
return { ok: true, value: entry.result ?? "0x" };
|
|
110
|
-
}
|
|
111
|
-
function validateCall(to, data) {
|
|
112
|
-
const address = stripHex(to);
|
|
113
|
-
if (!HEX.test(to) || address.length !== 40) {
|
|
114
|
-
throw new Error(`evm rpc: ${to} is not a contract address`);
|
|
115
|
-
}
|
|
116
|
-
const calldata = stripHex(data);
|
|
117
|
-
if (!HEX.test(data) || calldata.length % 2 !== 0) {
|
|
118
|
-
throw new Error(`evm rpc: calldata ${data} is not even-length hex`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
export function jsonRpcEvmReader(endpoints = {}, options = {}) {
|
|
122
|
-
const fetchImpl = options.fetch ??
|
|
123
|
-
((input, init) => globalThis.fetch(input, init));
|
|
124
|
-
const onRequest = options.onRequest;
|
|
125
|
-
const useMulticall = options.multicall ?? false;
|
|
126
|
-
const batchers = new Map();
|
|
127
|
-
const modes = new Map();
|
|
128
|
-
const probes = new Map();
|
|
129
|
-
const post = async (endpoint, body, items, attempt) => {
|
|
130
|
-
let response;
|
|
131
|
-
try {
|
|
132
|
-
response = await fetchImpl(endpoint, {
|
|
133
|
-
method: "POST",
|
|
134
|
-
headers: { "Content-Type": "application/json" },
|
|
135
|
-
body: JSON.stringify(body),
|
|
136
|
-
signal: options.signal
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
catch (error) {
|
|
140
|
-
if (isAbortError(error))
|
|
141
|
-
throw error;
|
|
142
|
-
onRequest?.({ items, attempt, status: "network-error" });
|
|
143
|
-
throw new TransientError(`evm rpc: ${error instanceof Error ? error.message : String(error)}`);
|
|
144
|
-
}
|
|
145
|
-
onRequest?.({ items, attempt, status: response.status });
|
|
146
|
-
if (isTransientStatus(response.status)) {
|
|
147
|
-
throw new TransientError(`evm rpc ${response.status}`, retryAfterMs(response.headers?.get("retry-after")));
|
|
148
|
-
}
|
|
149
|
-
if (!response.ok)
|
|
150
|
-
throw new HttpError(response.status);
|
|
151
|
-
return response;
|
|
152
|
-
};
|
|
153
|
-
const rpc = async (endpoint, method, params, items, attempt) => {
|
|
154
|
-
const response = await post(endpoint, jsonRpcRequest(method, params, 1), items, attempt);
|
|
155
|
-
const parsed = (await response.json());
|
|
156
|
-
const entry = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
157
|
-
if (isRateLimited(entry))
|
|
158
|
-
throw new TransientError(`evm rpc: ${entry?.error?.message}`);
|
|
159
|
-
return entry ?? {};
|
|
160
|
-
};
|
|
161
|
-
const detectMode = (endpoint, attempt) => {
|
|
162
|
-
const known = modes.get(endpoint);
|
|
163
|
-
if (known)
|
|
164
|
-
return Promise.resolve(known);
|
|
165
|
-
if (!useMulticall) {
|
|
166
|
-
modes.set(endpoint, "batch");
|
|
167
|
-
return Promise.resolve("batch");
|
|
168
|
-
}
|
|
169
|
-
const pending = probes.get(endpoint);
|
|
170
|
-
if (pending)
|
|
171
|
-
return pending;
|
|
172
|
-
const probe = (async () => {
|
|
173
|
-
let mode = "batch";
|
|
174
|
-
try {
|
|
175
|
-
const code = await rpc(endpoint, "eth_getCode", [MULTICALL3_ADDRESS, "latest"], 1, attempt);
|
|
176
|
-
if (!code.error && stripHex(code.result ?? "").length > 2)
|
|
177
|
-
mode = "multicall";
|
|
178
|
-
}
|
|
179
|
-
catch (error) {
|
|
180
|
-
if (error instanceof TransientError || isAbortError(error))
|
|
181
|
-
throw error;
|
|
182
|
-
}
|
|
183
|
-
modes.set(endpoint, mode);
|
|
184
|
-
return mode;
|
|
185
|
-
})();
|
|
186
|
-
probes.set(endpoint, probe);
|
|
187
|
-
const clear = () => probes.delete(endpoint);
|
|
188
|
-
probe.then(clear, clear);
|
|
189
|
-
return probe;
|
|
190
|
-
};
|
|
191
|
-
const sendMulticall = async (endpoint, calls, attempt) => {
|
|
192
|
-
let entry;
|
|
193
|
-
try {
|
|
194
|
-
entry = await rpc(endpoint, "eth_call", [{ to: MULTICALL3_ADDRESS, data: encodeAggregate3(calls) }, "latest"], calls.length, attempt);
|
|
195
|
-
}
|
|
196
|
-
catch (error) {
|
|
197
|
-
if (error instanceof TransientError || isAbortError(error))
|
|
198
|
-
throw error;
|
|
199
|
-
return undefined;
|
|
200
|
-
}
|
|
201
|
-
if (entry.error || !entry.result)
|
|
202
|
-
return undefined;
|
|
203
|
-
let decoded;
|
|
204
|
-
try {
|
|
205
|
-
decoded = decodeAggregate3(entry.result, calls.length);
|
|
206
|
-
}
|
|
207
|
-
catch {
|
|
208
|
-
return undefined;
|
|
209
|
-
}
|
|
210
|
-
return decoded.map(result => result.success
|
|
211
|
-
? { ok: true, value: result.data }
|
|
212
|
-
: { ok: false, error: new Error("evm rpc: execution reverted") });
|
|
213
|
-
};
|
|
214
|
-
const sendBatch = async (endpoint, calls, attempt) => {
|
|
215
|
-
let response;
|
|
216
|
-
try {
|
|
217
|
-
response = await post(endpoint, calls.map((call, id) => callRequest(call, id)), calls.length, attempt);
|
|
218
|
-
}
|
|
219
|
-
catch (error) {
|
|
220
|
-
if (error instanceof HttpError) {
|
|
221
|
-
modes.set(endpoint, "single");
|
|
222
|
-
throw new ChunkTooLargeError(error.message);
|
|
223
|
-
}
|
|
224
|
-
throw error;
|
|
225
|
-
}
|
|
226
|
-
const parsed = (await response.json());
|
|
227
|
-
if (!Array.isArray(parsed)) {
|
|
228
|
-
modes.set(endpoint, "single");
|
|
229
|
-
throw new ChunkTooLargeError("evm rpc: batch unsupported");
|
|
230
|
-
}
|
|
231
|
-
const byId = new Map(parsed.map(entry => [Number(entry.id), entry]));
|
|
232
|
-
return calls.map((_, id) => toResult(byId.get(id)));
|
|
233
|
-
};
|
|
234
|
-
const sendSingle = async (endpoint, call, attempt) => {
|
|
235
|
-
const response = await post(endpoint, callRequest(call, 1), 1, attempt);
|
|
236
|
-
const parsed = (await response.json());
|
|
237
|
-
return [toResult(Array.isArray(parsed) ? parsed[0] : parsed)];
|
|
238
|
-
};
|
|
239
|
-
const send = async (endpoint, calls, attempt) => {
|
|
240
|
-
if (calls.length === 1)
|
|
241
|
-
return sendSingle(endpoint, calls[0], attempt);
|
|
242
|
-
const mode = await detectMode(endpoint, attempt);
|
|
243
|
-
if (mode === "single")
|
|
244
|
-
throw new ChunkTooLargeError("evm rpc: batch unsupported");
|
|
245
|
-
if (mode === "multicall") {
|
|
246
|
-
const aggregated = await sendMulticall(endpoint, calls, attempt);
|
|
247
|
-
if (aggregated)
|
|
248
|
-
return aggregated;
|
|
249
|
-
modes.set(endpoint, "batch");
|
|
250
|
-
}
|
|
251
|
-
return sendBatch(endpoint, calls, attempt);
|
|
252
|
-
};
|
|
253
|
-
const batcherFor = (endpoint) => {
|
|
254
|
-
const existing = batchers.get(endpoint);
|
|
255
|
-
if (existing)
|
|
256
|
-
return existing;
|
|
257
|
-
const batcher = new Batcher({
|
|
258
|
-
send: (calls, attempt) => send(endpoint, calls, attempt),
|
|
259
|
-
key: call => `${call.to.toLowerCase()}:${call.data.toLowerCase()}`,
|
|
260
|
-
maxWeight: options.maxCalls ?? 20,
|
|
261
|
-
concurrency: options.concurrency,
|
|
262
|
-
retries: options.retries,
|
|
263
|
-
baseDelayMs: options.baseDelayMs,
|
|
264
|
-
maxDelayMs: options.maxDelayMs,
|
|
265
|
-
sleep: options.sleep,
|
|
266
|
-
signal: options.signal
|
|
267
|
-
});
|
|
268
|
-
batchers.set(endpoint, batcher);
|
|
269
|
-
return batcher;
|
|
270
|
-
};
|
|
271
|
-
return {
|
|
272
|
-
async call(chainId, to, data, override) {
|
|
273
|
-
const endpoint = override ?? endpoints[chainId];
|
|
274
|
-
if (!endpoint)
|
|
275
|
-
throw new Error(`no evm endpoint for chain ${chainId}`);
|
|
276
|
-
validateCall(to, data);
|
|
277
|
-
return batcherFor(endpoint).enqueue({ to, data });
|
|
278
|
-
}
|
|
279
|
-
};
|
|
280
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export * from "./types.js";
|
|
2
|
-
export { AbortedError, isAbortError } from "./batch.js";
|
|
3
|
-
export * from "./registry.js";
|
|
4
|
-
export * from "./client.js";
|
|
5
|
-
export * from "./pointers.js";
|
|
6
|
-
export * from "./vaults.js";
|
|
7
|
-
export * from "./sui.js";
|
|
8
|
-
export * from "./evm.js";
|
|
9
|
-
export * from "./adapters/index.js";
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export * from "./types.js";
|
|
2
|
-
export { AbortedError, isAbortError } from "./batch.js";
|
|
3
|
-
export * from "./registry.js";
|
|
4
|
-
export * from "./client.js";
|
|
5
|
-
export * from "./pointers.js";
|
|
6
|
-
export * from "./vaults.js";
|
|
7
|
-
export * from "./sui.js";
|
|
8
|
-
export * from "./evm.js";
|
|
9
|
-
export * from "./adapters/index.js";
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { PlatformEnv } from "../common/constants.js";
|
|
2
|
-
import { PlacementRef, PointerSource } from "./types.js";
|
|
3
|
-
/**
|
|
4
|
-
* Pointers held in memory. Useful for tests, and for pinning a known venue set
|
|
5
|
-
* without a round-trip.
|
|
6
|
-
*/
|
|
7
|
-
export declare function staticPointerSource(refs: PlacementRef[]): PointerSource;
|
|
8
|
-
export interface HttpPointerSourceOptions {
|
|
9
|
-
/** Vaults API environment. Defaults to prod. */
|
|
10
|
-
env?: PlatformEnv;
|
|
11
|
-
/** Overrides `env` entirely. For a local deployment, or a proxy. */
|
|
12
|
-
baseUrl?: string;
|
|
13
|
-
path?: string;
|
|
14
|
-
/** How long a coin's rows may be reused, in milliseconds. Default 30s. */
|
|
15
|
-
cacheMs?: number;
|
|
16
|
-
}
|
|
17
|
-
/** Pointers from the vaults API. Defaults to prod. */
|
|
18
|
-
export declare function httpPointerSource(options?: HttpPointerSourceOptions): PointerSource;
|
|
19
|
-
/**
|
|
20
|
-
* Merges what every source returns, de-duplicated by venue. Earlier sources win,
|
|
21
|
-
* so a caller can pin rows ahead of a remote set. A source that throws is
|
|
22
|
-
* skipped rather than failing the set.
|
|
23
|
-
*/
|
|
24
|
-
export declare function mergePointerSources(...sources: PointerSource[]): PointerSource;
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { environmentConfig } from "../common/constants.js";
|
|
2
|
-
import { coinTypesMatch } from "./sui.js";
|
|
3
|
-
/**
|
|
4
|
-
* Pointers held in memory. Useful for tests, and for pinning a known venue set
|
|
5
|
-
* without a round-trip.
|
|
6
|
-
*/
|
|
7
|
-
export function staticPointerSource(refs) {
|
|
8
|
-
return {
|
|
9
|
-
// Coins are compared with `coinTypesMatch` rather than by string
|
|
10
|
-
// equality, so a row pinned in one spelling still matches a caller
|
|
11
|
-
// passing another — a short Sui address against its padded form, say.
|
|
12
|
-
// Exact matching would fail by returning nothing, which reads as
|
|
13
|
-
// "listed nowhere".
|
|
14
|
-
async placements(protocol, coin) {
|
|
15
|
-
return refs.filter(ref => ref.protocol === protocol && coinTypesMatch(ref.coin, coin.coin));
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Decimals scale every amount an adapter returns, so a missing or malformed
|
|
21
|
-
* value is a silent power-of-ten error rather than a visible failure.
|
|
22
|
-
*/
|
|
23
|
-
function requireDecimals(row) {
|
|
24
|
-
if (!Number.isInteger(row.decimals)) {
|
|
25
|
-
throw new Error(`placement ${row.protocol}/${row.venue} has no decimals`);
|
|
26
|
-
}
|
|
27
|
-
return row.decimals;
|
|
28
|
-
}
|
|
29
|
-
/** Pointers from the vaults API. Defaults to prod. */
|
|
30
|
-
export function httpPointerSource(options = {}) {
|
|
31
|
-
// The cache window exists to collapse the burst a single `forAccount`
|
|
32
|
-
// makes, not to cache across a session: pointers change when a venue is
|
|
33
|
-
// added, and a long window would keep a new one invisible.
|
|
34
|
-
const baseUrl = options.baseUrl ?? environmentConfig[options.env ?? "prod"].apiHost;
|
|
35
|
-
const path = options.path ?? "/api/v2/vaults/placements";
|
|
36
|
-
const cacheMs = options.cacheMs ?? 30000;
|
|
37
|
-
/**
|
|
38
|
-
* One request per coin, not per (coin, protocol).
|
|
39
|
-
*
|
|
40
|
-
* `?coin=` already returns every protocol's rows, so asking once per adapter
|
|
41
|
-
* fetched the same list five times and threw four of them away. Reading one
|
|
42
|
-
* vault meant eight requests where two would do, and a caller sweeping every
|
|
43
|
-
* vault made hundreds — enough to be rate-limited, and a rejected discover
|
|
44
|
-
* drops that venue from the answer.
|
|
45
|
-
*
|
|
46
|
-
* In-flight requests are shared rather than merely cached, so the adapters
|
|
47
|
-
* racing each other at the start of a `forAccount` collapse into one call.
|
|
48
|
-
*/
|
|
49
|
-
const inFlight = new Map();
|
|
50
|
-
const rowsForCoin = (coin) => {
|
|
51
|
-
const key = coin.coin.toLowerCase();
|
|
52
|
-
const hit = inFlight.get(key);
|
|
53
|
-
if (hit && Date.now() - hit.at < cacheMs)
|
|
54
|
-
return hit.rows;
|
|
55
|
-
const rows = (async () => {
|
|
56
|
-
const url = new URL(`${baseUrl.replace(/\/$/, "")}${path}`);
|
|
57
|
-
url.searchParams.set("coin", coin.coin);
|
|
58
|
-
const response = await fetch(url.toString(), {
|
|
59
|
-
headers: { Accept: "application/json" }
|
|
60
|
-
});
|
|
61
|
-
if (!response.ok) {
|
|
62
|
-
throw new Error(`pointer source ${response.status}`);
|
|
63
|
-
}
|
|
64
|
-
const body = (await response.json());
|
|
65
|
-
// The chain type comes from the coin, not the wire: an adapter only
|
|
66
|
-
// ever runs on its own chain, so a server that named it again could
|
|
67
|
-
// only ever disagree.
|
|
68
|
-
return (body.placements ?? []).map(placement => ({
|
|
69
|
-
id: placement.id,
|
|
70
|
-
protocol: placement.protocol,
|
|
71
|
-
chain: {
|
|
72
|
-
type: coin.chain.type,
|
|
73
|
-
chainId: placement.chainId,
|
|
74
|
-
identifier: placement.chainIdentifier
|
|
75
|
-
},
|
|
76
|
-
venue: placement.venue,
|
|
77
|
-
coin: placement.coin,
|
|
78
|
-
decimals: requireDecimals(placement),
|
|
79
|
-
protocolName: placement.protocolName,
|
|
80
|
-
protocolLogoUrl: placement.protocolLogoUrl,
|
|
81
|
-
source: placement.source,
|
|
82
|
-
label: placement.label,
|
|
83
|
-
shareToken: placement.shareToken,
|
|
84
|
-
url: placement.url,
|
|
85
|
-
rpcUrl: placement.rpcUrl
|
|
86
|
-
}));
|
|
87
|
-
})();
|
|
88
|
-
// A failure is not cached: the next adapter should retry rather than
|
|
89
|
-
// inherit a rejection it never caused.
|
|
90
|
-
rows.catch(() => inFlight.delete(key));
|
|
91
|
-
inFlight.set(key, { at: Date.now(), rows });
|
|
92
|
-
return rows;
|
|
93
|
-
};
|
|
94
|
-
return {
|
|
95
|
-
async placements(protocol, coin) {
|
|
96
|
-
const rows = await rowsForCoin(coin);
|
|
97
|
-
return rows.filter(ref => ref.protocol === protocol);
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Merges what every source returns, de-duplicated by venue. Earlier sources win,
|
|
103
|
-
* so a caller can pin rows ahead of a remote set. A source that throws is
|
|
104
|
-
* skipped rather than failing the set.
|
|
105
|
-
*/
|
|
106
|
-
export function mergePointerSources(...sources) {
|
|
107
|
-
return {
|
|
108
|
-
async placements(protocol, coin) {
|
|
109
|
-
const results = await Promise.allSettled(sources.map(source => source.placements(protocol, coin)));
|
|
110
|
-
const byVenue = new Map();
|
|
111
|
-
for (const result of results) {
|
|
112
|
-
if (result.status !== "fulfilled")
|
|
113
|
-
continue;
|
|
114
|
-
for (const ref of result.value) {
|
|
115
|
-
// Key on the chain type as well as its id: an evm row whose
|
|
116
|
-
// chainId is absent would otherwise land in the same slot as
|
|
117
|
-
// a sui row at the same venue string and silently replace it.
|
|
118
|
-
const key = `${ref.chain.type}:${ref.chain.chainId ?? ""}:${ref.venue.toLowerCase()}`;
|
|
119
|
-
if (!byVenue.has(key))
|
|
120
|
-
byVenue.set(key, ref);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return [...byVenue.values()];
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { ChainType } from "../common/constants.js";
|
|
2
|
-
import { ProtocolAdapter, ProtocolId } from "./types.js";
|
|
3
|
-
/**
|
|
4
|
-
* Add a protocol. Re-registering an id replaces it, so a consumer can override
|
|
5
|
-
* a bundled adapter without forking the module.
|
|
6
|
-
*/
|
|
7
|
-
export declare function registerAdapter(adapter: ProtocolAdapter): void;
|
|
8
|
-
export declare function getAdapter(id: ProtocolId): ProtocolAdapter | undefined;
|
|
9
|
-
export declare function listAdapters(chain?: ChainType): ProtocolAdapter[];
|
|
10
|
-
/** Drops every registration. Intended for tests. */
|
|
11
|
-
export declare function resetAdapters(): void;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
const adapters = new Map();
|
|
2
|
-
/**
|
|
3
|
-
* Add a protocol. Re-registering an id replaces it, so a consumer can override
|
|
4
|
-
* a bundled adapter without forking the module.
|
|
5
|
-
*/
|
|
6
|
-
export function registerAdapter(adapter) {
|
|
7
|
-
adapters.set(adapter.id, adapter);
|
|
8
|
-
}
|
|
9
|
-
export function getAdapter(id) {
|
|
10
|
-
return adapters.get(id);
|
|
11
|
-
}
|
|
12
|
-
export function listAdapters(chain) {
|
|
13
|
-
const all = [...adapters.values()];
|
|
14
|
-
return chain ? all.filter(adapter => adapter.chain === chain) : all;
|
|
15
|
-
}
|
|
16
|
-
/** Drops every registration. Intended for tests. */
|
|
17
|
-
export function resetAdapters() {
|
|
18
|
-
adapters.clear();
|
|
19
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { SuiReader } from "./types.js";
|
|
2
|
-
import { TransportRequest } from "./batch.js";
|
|
3
|
-
export type { TransportRequest } from "./batch.js";
|
|
4
|
-
/**
|
|
5
|
-
* Canonical form for a Sui coin type: `0x` stripped from the address segment,
|
|
6
|
-
* lowercased and left-padded to 64 hex chars.
|
|
7
|
-
*
|
|
8
|
-
* Object JSON returns unprefixed padded types ("0000..02::sui::SUI") while
|
|
9
|
-
* config and RPC use the short prefixed form ("0x2::sui::SUI"). Comparing the
|
|
10
|
-
* two without normalising silently never matches.
|
|
11
|
-
*
|
|
12
|
-
* Only the address is folded. It is hex, so case is not significant there, but
|
|
13
|
-
* the module and struct names are Move identifiers and two that differ only in
|
|
14
|
-
* case are two different types.
|
|
15
|
-
*/
|
|
16
|
-
export declare function normalizeCoinType(coinType: string): string;
|
|
17
|
-
export declare function coinTypesMatch(a: string, b: string): boolean;
|
|
18
|
-
/**
|
|
19
|
-
* A Sui address as GraphQL wants a dynamic-field key: base64 of its BCS
|
|
20
|
-
* encoding, which for an address is simply its 32 raw bytes.
|
|
21
|
-
*/
|
|
22
|
-
export declare function bcsAddress(address: string): string;
|
|
23
|
-
/** Type arguments of a Move type, e.g. `Pool<A, B>` -> ["A", "B"]. */
|
|
24
|
-
export declare function typeArguments(type: string): string[];
|
|
25
|
-
/** The object id a Move table's entries actually hang off. */
|
|
26
|
-
export declare function tableId(field: unknown): string | undefined;
|
|
27
|
-
/**
|
|
28
|
-
* Some protocols wrap fixed-point numbers as `{ value }` scaled by 1e18.
|
|
29
|
-
* Returns base units, truncated — these feed balances, not accounting.
|
|
30
|
-
*/
|
|
31
|
-
export declare function decimalToBaseUnits(field: unknown): bigint;
|
|
32
|
-
export declare function toBigInt(field: unknown): bigint;
|
|
33
|
-
export declare const DEFAULT_SUI_GRAPHQL_ENDPOINT = "https://graphql.mainnet.sui.io/graphql";
|
|
34
|
-
export interface GraphqlSuiReaderOptions {
|
|
35
|
-
endpoint?: string;
|
|
36
|
-
maxRootFields?: number;
|
|
37
|
-
maxBytes?: number;
|
|
38
|
-
concurrency?: number;
|
|
39
|
-
retries?: number;
|
|
40
|
-
baseDelayMs?: number;
|
|
41
|
-
maxDelayMs?: number;
|
|
42
|
-
sleep?: (ms: number) => Promise<void>;
|
|
43
|
-
fetch?: typeof globalThis.fetch;
|
|
44
|
-
signal?: AbortSignal;
|
|
45
|
-
onRequest?: (request: TransportRequest) => void;
|
|
46
|
-
}
|
|
47
|
-
export declare function graphqlSuiReader(options?: string | GraphqlSuiReaderOptions): SuiReader;
|