@ember-finance/sdk 2.18.0 → 2.19.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.
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { VaultsApi } from "../api/v2/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* The generated vaults client, on the fetch adapter so this module keeps one
|
|
4
|
+
* transport in the browser and in Node.
|
|
5
|
+
*/
|
|
6
|
+
export declare function vaultsApi(baseUrl: string): VaultsApi;
|
|
7
|
+
/** Status and Retry-After of a failed call, or undefined when no response came back. */
|
|
8
|
+
export declare function httpFailure(error: unknown): {
|
|
9
|
+
status: number;
|
|
10
|
+
retryAfter?: string;
|
|
11
|
+
} | undefined;
|
|
12
|
+
/** True for a transport failure (no response), which is worth retrying. */
|
|
13
|
+
export declare function isNetworkError(error: unknown): boolean;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { Configuration, VaultsApi } from "../api/v2/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* The generated vaults client, on the fetch adapter so this module keeps one
|
|
5
|
+
* transport in the browser and in Node.
|
|
6
|
+
*/
|
|
7
|
+
export function vaultsApi(baseUrl) {
|
|
8
|
+
return new VaultsApi(new Configuration({
|
|
9
|
+
basePath: baseUrl.replace(/\/$/, ""),
|
|
10
|
+
baseOptions: { adapter: "fetch" }
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
13
|
+
/** Status and Retry-After of a failed call, or undefined when no response came back. */
|
|
14
|
+
export function httpFailure(error) {
|
|
15
|
+
if (!axios.isAxiosError(error) || !error.response)
|
|
16
|
+
return undefined;
|
|
17
|
+
const retryAfter = error.response.headers?.["retry-after"];
|
|
18
|
+
return {
|
|
19
|
+
status: error.response.status,
|
|
20
|
+
retryAfter: typeof retryAfter === "string" ? retryAfter : undefined
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** True for a transport failure (no response), which is worth retrying. */
|
|
24
|
+
export function isNetworkError(error) {
|
|
25
|
+
return axios.isAxiosError(error) && !error.response && !axios.isCancel(error);
|
|
26
|
+
}
|
|
@@ -10,7 +10,6 @@ export interface HttpPointerSourceOptions {
|
|
|
10
10
|
env?: PlatformEnv;
|
|
11
11
|
/** Overrides `env` entirely. For a local deployment, or a proxy. */
|
|
12
12
|
baseUrl?: string;
|
|
13
|
-
path?: string;
|
|
14
13
|
/** How long a coin's rows may be reused, in milliseconds. Default 30s. */
|
|
15
14
|
cacheMs?: number;
|
|
16
15
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { environmentConfig } from "../common/constants.js";
|
|
2
|
-
import {
|
|
2
|
+
import { httpFailure, isNetworkError, vaultsApi } from "./api.js";
|
|
3
|
+
import { isTransientStatus, retryAfterMs } from "./batch.js";
|
|
3
4
|
import { coinTypesMatch, normalizeCoinType } from "./sui.js";
|
|
4
5
|
/**
|
|
5
6
|
* Pointers held in memory. Useful for tests, and for pinning a known venue set
|
|
@@ -33,7 +34,7 @@ export function httpPointerSource(options = {}) {
|
|
|
33
34
|
// makes, not to cache across a session: pointers change when a venue is
|
|
34
35
|
// added, and a long window would keep a new one invisible.
|
|
35
36
|
const baseUrl = options.baseUrl ?? environmentConfig[options.env ?? "prod"].apiHost;
|
|
36
|
-
const
|
|
37
|
+
const api = vaultsApi(baseUrl);
|
|
37
38
|
const cacheMs = options.cacheMs ?? 30000;
|
|
38
39
|
/**
|
|
39
40
|
* One request for every coin. The endpoint answers with the whole registry
|
|
@@ -59,12 +60,7 @@ export function httpPointerSource(options = {}) {
|
|
|
59
60
|
inFlight.set(key, { at: Date.now(), rows });
|
|
60
61
|
return rows;
|
|
61
62
|
};
|
|
62
|
-
const request = (coin) =>
|
|
63
|
-
const url = new URL(`${baseUrl.replace(/\/$/, "")}${path}`);
|
|
64
|
-
if (coin !== undefined)
|
|
65
|
-
url.searchParams.set("coin", coin);
|
|
66
|
-
return fetchPlacements(url.toString());
|
|
67
|
-
};
|
|
63
|
+
const request = (coin) => fetchPlacements(api, coin);
|
|
68
64
|
const rowsFor = async (coin) => {
|
|
69
65
|
if (Date.now() >= perCoinUntil) {
|
|
70
66
|
try {
|
|
@@ -139,34 +135,31 @@ const FALLBACK_RECHECK_MS = 5 * 60000;
|
|
|
139
135
|
* Retries transient failures. Every vault's venues now ride on this one
|
|
140
136
|
* request, and callers keep a failed discover rather than retrying it.
|
|
141
137
|
*/
|
|
142
|
-
async function fetchPlacements(
|
|
138
|
+
async function fetchPlacements(api, coin) {
|
|
143
139
|
for (let attempt = 1;; attempt++) {
|
|
144
140
|
let waitMs;
|
|
145
141
|
try {
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
});
|
|
149
|
-
if (response.ok) {
|
|
150
|
-
const body = (await response.json());
|
|
151
|
-
return body.placements ?? [];
|
|
152
|
-
}
|
|
153
|
-
if (!isTransientStatus(response.status) || attempt >= FETCH_ATTEMPTS) {
|
|
154
|
-
throw new PointerSourceError(response.status);
|
|
155
|
-
}
|
|
156
|
-
waitMs = retryAfterMs(response.headers?.get("retry-after"));
|
|
157
|
-
// Retrying before the server asked would only add to the load it
|
|
158
|
-
// is shedding and fail again.
|
|
159
|
-
if (waitMs !== undefined && waitMs > RETRY_MAX_MS) {
|
|
160
|
-
throw new PointerSourceError(response.status);
|
|
161
|
-
}
|
|
142
|
+
const { data } = await api.getCoinPlacements(coin);
|
|
143
|
+
return data.placements ?? [];
|
|
162
144
|
}
|
|
163
145
|
catch (error) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
attempt >= FETCH_ATTEMPTS)
|
|
169
|
-
|
|
146
|
+
const failure = httpFailure(error);
|
|
147
|
+
if (!failure) {
|
|
148
|
+
// Only a transport failure is worth retrying, not an abort or a
|
|
149
|
+
// bug of our own.
|
|
150
|
+
if (!isNetworkError(error) || attempt >= FETCH_ATTEMPTS)
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
if (!isTransientStatus(failure.status) || attempt >= FETCH_ATTEMPTS) {
|
|
155
|
+
throw new PointerSourceError(failure.status);
|
|
156
|
+
}
|
|
157
|
+
waitMs = retryAfterMs(failure.retryAfter);
|
|
158
|
+
// Retrying before the server asked would only add to the load it
|
|
159
|
+
// is shedding and fail again.
|
|
160
|
+
if (waitMs !== undefined && waitMs > RETRY_MAX_MS) {
|
|
161
|
+
throw new PointerSourceError(failure.status);
|
|
162
|
+
}
|
|
170
163
|
}
|
|
171
164
|
}
|
|
172
165
|
const backoff = Math.min(RETRY_BASE_MS * 2 ** (attempt - 1), RETRY_MAX_MS);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { environmentConfig } from "../common/constants.js";
|
|
2
|
+
import { httpFailure, vaultsApi } from "./api.js";
|
|
2
3
|
/**
|
|
3
4
|
* Resolves a vault to the receipt coins it issues, from the vaults API.
|
|
4
5
|
*
|
|
@@ -6,14 +7,17 @@ import { environmentConfig } from "../common/constants.js";
|
|
|
6
7
|
* each, so this returns one entry per chain rather than assuming a single coin.
|
|
7
8
|
*/
|
|
8
9
|
export function httpVaultCoinsResolver(options = {}) {
|
|
9
|
-
const
|
|
10
|
-
const get = async (path) => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
const api = vaultsApi(options.baseUrl ?? environmentConfig[options.env ?? "prod"].apiHost);
|
|
11
|
+
const get = async (path, call) => {
|
|
12
|
+
try {
|
|
13
|
+
return (await call()).data;
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
const failure = httpFailure(error);
|
|
17
|
+
if (failure)
|
|
18
|
+
throw new Error(`vaults api ${failure.status} for ${path}`);
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
17
21
|
};
|
|
18
22
|
// The chain catalogue is what carries the EVM network id. Without it an EVM
|
|
19
23
|
// coin has no chainId, and every read for it fails rather than returning a
|
|
@@ -22,7 +26,7 @@ export function httpVaultCoinsResolver(options = {}) {
|
|
|
22
26
|
const chainRefs = () => {
|
|
23
27
|
if (chains)
|
|
24
28
|
return chains;
|
|
25
|
-
chains = get("/api/v2/vaults/supported-chains").then(supported => {
|
|
29
|
+
chains = get("/api/v2/vaults/supported-chains", () => api.getSupportedChains()).then(supported => {
|
|
26
30
|
const byIdentifier = new Map();
|
|
27
31
|
for (const chain of supported) {
|
|
28
32
|
if (!chain.identifier)
|
|
@@ -51,10 +55,10 @@ export function httpVaultCoinsResolver(options = {}) {
|
|
|
51
55
|
return {
|
|
52
56
|
async coinsForVault(vaultId) {
|
|
53
57
|
const [vaults, catalogue] = await Promise.all([
|
|
54
|
-
get(`/api/v2/vaults?vaultId=${
|
|
58
|
+
get(`/api/v2/vaults?vaultId=${vaultId}`, () => api.getVaults(vaultId)),
|
|
55
59
|
chainRefs()
|
|
56
60
|
]);
|
|
57
|
-
const list =
|
|
61
|
+
const list = vaults;
|
|
58
62
|
const vault = list.find(v => v.id === vaultId) ?? list[0];
|
|
59
63
|
if (!vault)
|
|
60
64
|
throw new Error(`vault ${vaultId} not found`);
|