@ember-finance/sdk 2.15.0 → 2.17.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.
@@ -48,13 +48,13 @@ export declare const VaultsApiAxiosParamCreator: (configuration?: Configuration)
48
48
  /**
49
49
  * Pointers only: which protocol venues list the coin. No amounts are returned — clients read those from chain, so a stale row here can drop a venue but can never report a wrong balance.
50
50
  * @summary Get venues that list an Ember receipt coin
51
- * @param {string} coin Sui coin type or EVM token address of the receipt coin
51
+ * @param {string} [coin] Sui coin type or EVM token address of the receipt coin. Omit it to get every active placement, sorted by coin. A blank value is rejected.
52
52
  * @param {string} [protocol] Restrict to one protocol adapter id, e.g. \"bluefin-spot\"
53
53
  * @param {string} [chainIdentifier] Narrow to one chain, e.g. \"sui-mainnet\".
54
54
  * @param {*} [options] Override http request option.
55
55
  * @throws {RequiredError}
56
56
  */
57
- getCoinPlacements: (coin: string, protocol?: string, chainIdentifier?: string, options?: RawAxiosRequestConfig) => Promise<RequestArgs>;
57
+ getCoinPlacements: (coin?: string, protocol?: string, chainIdentifier?: string, options?: RawAxiosRequestConfig) => Promise<RequestArgs>;
58
58
  /**
59
59
  *
60
60
  * @summary Get the price of the coins
@@ -215,13 +215,13 @@ export declare const VaultsApiFp: (configuration?: Configuration) => {
215
215
  /**
216
216
  * Pointers only: which protocol venues list the coin. No amounts are returned — clients read those from chain, so a stale row here can drop a venue but can never report a wrong balance.
217
217
  * @summary Get venues that list an Ember receipt coin
218
- * @param {string} coin Sui coin type or EVM token address of the receipt coin
218
+ * @param {string} [coin] Sui coin type or EVM token address of the receipt coin. Omit it to get every active placement, sorted by coin. A blank value is rejected.
219
219
  * @param {string} [protocol] Restrict to one protocol adapter id, e.g. \&quot;bluefin-spot\&quot;
220
220
  * @param {string} [chainIdentifier] Narrow to one chain, e.g. \&quot;sui-mainnet\&quot;.
221
221
  * @param {*} [options] Override http request option.
222
222
  * @throws {RequiredError}
223
223
  */
224
- getCoinPlacements(coin: string, protocol?: string, chainIdentifier?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<CoinPlacementsResponse>>;
224
+ getCoinPlacements(coin?: string, protocol?: string, chainIdentifier?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<CoinPlacementsResponse>>;
225
225
  /**
226
226
  *
227
227
  * @summary Get the price of the coins
@@ -382,13 +382,13 @@ export declare const VaultsApiFactory: (configuration?: Configuration, basePath?
382
382
  /**
383
383
  * Pointers only: which protocol venues list the coin. No amounts are returned — clients read those from chain, so a stale row here can drop a venue but can never report a wrong balance.
384
384
  * @summary Get venues that list an Ember receipt coin
385
- * @param {string} coin Sui coin type or EVM token address of the receipt coin
385
+ * @param {string} [coin] Sui coin type or EVM token address of the receipt coin. Omit it to get every active placement, sorted by coin. A blank value is rejected.
386
386
  * @param {string} [protocol] Restrict to one protocol adapter id, e.g. \&quot;bluefin-spot\&quot;
387
387
  * @param {string} [chainIdentifier] Narrow to one chain, e.g. \&quot;sui-mainnet\&quot;.
388
388
  * @param {*} [options] Override http request option.
389
389
  * @throws {RequiredError}
390
390
  */
391
- getCoinPlacements(coin: string, protocol?: string, chainIdentifier?: string, options?: RawAxiosRequestConfig): AxiosPromise<CoinPlacementsResponse>;
391
+ getCoinPlacements(coin?: string, protocol?: string, chainIdentifier?: string, options?: RawAxiosRequestConfig): AxiosPromise<CoinPlacementsResponse>;
392
392
  /**
393
393
  *
394
394
  * @summary Get the price of the coins
@@ -552,14 +552,14 @@ export declare class VaultsApi extends BaseAPI {
552
552
  /**
553
553
  * Pointers only: which protocol venues list the coin. No amounts are returned — clients read those from chain, so a stale row here can drop a venue but can never report a wrong balance.
554
554
  * @summary Get venues that list an Ember receipt coin
555
- * @param {string} coin Sui coin type or EVM token address of the receipt coin
555
+ * @param {string} [coin] Sui coin type or EVM token address of the receipt coin. Omit it to get every active placement, sorted by coin. A blank value is rejected.
556
556
  * @param {string} [protocol] Restrict to one protocol adapter id, e.g. \&quot;bluefin-spot\&quot;
557
557
  * @param {string} [chainIdentifier] Narrow to one chain, e.g. \&quot;sui-mainnet\&quot;.
558
558
  * @param {*} [options] Override http request option.
559
559
  * @throws {RequiredError}
560
560
  * @memberof VaultsApi
561
561
  */
562
- getCoinPlacements(coin: string, protocol?: string, chainIdentifier?: string, options?: RawAxiosRequestConfig): Promise<import("axios").AxiosResponse<CoinPlacementsResponse, any, {}>>;
562
+ getCoinPlacements(coin?: string, protocol?: string, chainIdentifier?: string, options?: RawAxiosRequestConfig): Promise<import("axios").AxiosResponse<CoinPlacementsResponse, any, {}>>;
563
563
  /**
564
564
  *
565
565
  * @summary Get the price of the coins
@@ -55,15 +55,13 @@ export const VaultsApiAxiosParamCreator = function (configuration) {
55
55
  /**
56
56
  * Pointers only: which protocol venues list the coin. No amounts are returned — clients read those from chain, so a stale row here can drop a venue but can never report a wrong balance.
57
57
  * @summary Get venues that list an Ember receipt coin
58
- * @param {string} coin Sui coin type or EVM token address of the receipt coin
58
+ * @param {string} [coin] Sui coin type or EVM token address of the receipt coin. Omit it to get every active placement, sorted by coin. A blank value is rejected.
59
59
  * @param {string} [protocol] Restrict to one protocol adapter id, e.g. \&quot;bluefin-spot\&quot;
60
60
  * @param {string} [chainIdentifier] Narrow to one chain, e.g. \&quot;sui-mainnet\&quot;.
61
61
  * @param {*} [options] Override http request option.
62
62
  * @throws {RequiredError}
63
63
  */
64
64
  getCoinPlacements: async (coin, protocol, chainIdentifier, options = {}) => {
65
- // verify required parameter 'coin' is not null or undefined
66
- assertParamExists("getCoinPlacements", "coin", coin);
67
65
  const localVarPath = `/api/v2/vaults/placements`;
68
66
  // use dummy base URL string because the URL constructor only accepts absolute URLs.
69
67
  const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
@@ -713,7 +711,7 @@ export const VaultsApiFp = function (configuration) {
713
711
  /**
714
712
  * Pointers only: which protocol venues list the coin. No amounts are returned — clients read those from chain, so a stale row here can drop a venue but can never report a wrong balance.
715
713
  * @summary Get venues that list an Ember receipt coin
716
- * @param {string} coin Sui coin type or EVM token address of the receipt coin
714
+ * @param {string} [coin] Sui coin type or EVM token address of the receipt coin. Omit it to get every active placement, sorted by coin. A blank value is rejected.
717
715
  * @param {string} [protocol] Restrict to one protocol adapter id, e.g. \&quot;bluefin-spot\&quot;
718
716
  * @param {string} [chainIdentifier] Narrow to one chain, e.g. \&quot;sui-mainnet\&quot;.
719
717
  * @param {*} [options] Override http request option.
@@ -974,7 +972,7 @@ export const VaultsApiFactory = function (configuration, basePath, axios) {
974
972
  /**
975
973
  * Pointers only: which protocol venues list the coin. No amounts are returned — clients read those from chain, so a stale row here can drop a venue but can never report a wrong balance.
976
974
  * @summary Get venues that list an Ember receipt coin
977
- * @param {string} coin Sui coin type or EVM token address of the receipt coin
975
+ * @param {string} [coin] Sui coin type or EVM token address of the receipt coin. Omit it to get every active placement, sorted by coin. A blank value is rejected.
978
976
  * @param {string} [protocol] Restrict to one protocol adapter id, e.g. \&quot;bluefin-spot\&quot;
979
977
  * @param {string} [chainIdentifier] Narrow to one chain, e.g. \&quot;sui-mainnet\&quot;.
980
978
  * @param {*} [options] Override http request option.
@@ -1217,7 +1215,7 @@ export class VaultsApi extends BaseAPI {
1217
1215
  /**
1218
1216
  * Pointers only: which protocol venues list the coin. No amounts are returned — clients read those from chain, so a stale row here can drop a venue but can never report a wrong balance.
1219
1217
  * @summary Get venues that list an Ember receipt coin
1220
- * @param {string} coin Sui coin type or EVM token address of the receipt coin
1218
+ * @param {string} [coin] Sui coin type or EVM token address of the receipt coin. Omit it to get every active placement, sorted by coin. A blank value is rejected.
1221
1219
  * @param {string} [protocol] Restrict to one protocol adapter id, e.g. \&quot;bluefin-spot\&quot;
1222
1220
  * @param {string} [chainIdentifier] Narrow to one chain, e.g. \&quot;sui-mainnet\&quot;.
1223
1221
  * @param {*} [options] Override http request option.
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Ember Protocol Vaults API
3
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
4
+ *
5
+ * The version of the OpenAPI document: 2.0.0
6
+ *
7
+ *
8
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
9
+ * https://openapi-generator.tech
10
+ * Do not edit the class manually.
11
+ */
12
+ /**
13
+ * An Atomic Liquidity Vault on Sui. Not a VaultInfo with extra fields: an ALV mints no receipt coin, so it has no ReceiptCoinType, and it carries a rate manager because its rate is set externally rather than derived from a receipt supply.
14
+ * @export
15
+ * @interface AtomicVaultInfo
16
+ */
17
+ export interface AtomicVaultInfo {
18
+ /**
19
+ * The name of the vault
20
+ * @type {string}
21
+ * @memberof AtomicVaultInfo
22
+ */
23
+ Name?: string;
24
+ /**
25
+ * The object identifier of the vault
26
+ * @type {string}
27
+ * @memberof AtomicVaultInfo
28
+ */
29
+ ObjectId?: string;
30
+ /**
31
+ * The coin type the vault holds and pays out in
32
+ * @type {string}
33
+ * @memberof AtomicVaultInfo
34
+ */
35
+ CollateralCoinType?: string;
36
+ /**
37
+ * The number of decimals for the collateral coin
38
+ * @type {number}
39
+ * @memberof AtomicVaultInfo
40
+ */
41
+ CollateralCoinDecimals?: number;
42
+ /**
43
+ * The admin identifier for the vault
44
+ * @type {string}
45
+ * @memberof AtomicVaultInfo
46
+ */
47
+ Admin?: string;
48
+ /**
49
+ * The operator identifier for the vault
50
+ * @type {string}
51
+ * @memberof AtomicVaultInfo
52
+ */
53
+ Operator?: string;
54
+ /**
55
+ * The address allowed to update the vault\'s rate
56
+ * @type {string}
57
+ * @memberof AtomicVaultInfo
58
+ */
59
+ RateManager?: string;
60
+ /**
61
+ * The maximum rate change per update
62
+ * @type {string}
63
+ * @memberof AtomicVaultInfo
64
+ */
65
+ MaxRateChangePerUpdate?: string;
66
+ /**
67
+ * The fee percentage for the vault
68
+ * @type {string}
69
+ * @memberof AtomicVaultInfo
70
+ */
71
+ FeePercentage?: string;
72
+ /**
73
+ * The minimum withdrawal shares
74
+ * @type {string}
75
+ * @memberof AtomicVaultInfo
76
+ */
77
+ MinWithdrawalShares?: string;
78
+ /**
79
+ * List of sub-account identifiers
80
+ * @type {Array<string>}
81
+ * @memberof AtomicVaultInfo
82
+ */
83
+ SubAccounts?: Array<string>;
84
+ }
@@ -0,0 +1,14 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /**
4
+ * Ember Protocol Vaults API
5
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
6
+ *
7
+ * The version of the OpenAPI document: 2.0.0
8
+ *
9
+ *
10
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
11
+ * https://openapi-generator.tech
12
+ * Do not edit the class manually.
13
+ */
14
+ export {};
@@ -12,6 +12,7 @@ export * from "./apy-history.js";
12
12
  export * from "./apy-history-response.js";
13
13
  export * from "./apy-period-average.js";
14
14
  export * from "./asset.js";
15
+ export * from "./atomic-vault-info.js";
15
16
  export * from "./borrowed.js";
16
17
  export * from "./chain.js";
17
18
  export * from "./chain-vault-details.js";
@@ -12,6 +12,7 @@ export * from "./apy-history.js";
12
12
  export * from "./apy-history-response.js";
13
13
  export * from "./apy-period-average.js";
14
14
  export * from "./asset.js";
15
+ export * from "./atomic-vault-info.js";
15
16
  export * from "./borrowed.js";
16
17
  export * from "./chain.js";
17
18
  export * from "./chain-vault-details.js";
@@ -9,6 +9,7 @@
9
9
  * https://openapi-generator.tech
10
10
  * Do not edit the class manually.
11
11
  */
12
+ import type { AtomicVaultInfo } from "./atomic-vault-info.js";
12
13
  import type { UltraCoinInfo } from "./ultra-coin-info.js";
13
14
  import type { VaultInfo } from "./vault-info.js";
14
15
  import type { VaultProtocol } from "./vault-protocol.js";
@@ -38,4 +39,12 @@ export interface SuiProtocolInfo {
38
39
  Vaults?: {
39
40
  [key: string]: VaultInfo;
40
41
  };
42
+ /**
43
+ * Map of Atomic Liquidity Vaults by id
44
+ * @type {{ [key: string]: AtomicVaultInfo; }}
45
+ * @memberof SuiProtocolInfo
46
+ */
47
+ AtomicVaults?: {
48
+ [key: string]: AtomicVaultInfo;
49
+ };
41
50
  }
@@ -1,5 +1,6 @@
1
1
  import { environmentConfig } from "../common/constants.js";
2
- import { coinTypesMatch } from "./sui.js";
2
+ import { isAbortError, isTransientStatus, retryAfterMs } from "./batch.js";
3
+ import { coinTypesMatch, normalizeCoinType } from "./sui.js";
3
4
  /**
4
5
  * Pointers held in memory. Useful for tests, and for pinning a known venue set
5
6
  * without a round-trip.
@@ -35,69 +36,152 @@ export function httpPointerSource(options = {}) {
35
36
  const path = options.path ?? "/api/v2/vaults/placements";
36
37
  const cacheMs = options.cacheMs ?? 30000;
37
38
  /**
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.
39
+ * One request for every coin. The endpoint answers with the whole registry
40
+ * when `coin` is omitted, so reading every vault costs one call rather than
41
+ * one per receipt coin.
45
42
  *
46
43
  * In-flight requests are shared rather than merely cached, so the adapters
47
44
  * racing each other at the start of a `forAccount` collapse into one call.
48
45
  */
49
46
  const inFlight = new Map();
50
- const rowsForCoin = (coin) => {
51
- const key = coin.coin.toLowerCase();
47
+ // Set when the server rejects the unfiltered call, i.e. it predates it.
48
+ // Rechecked after a while, so a long-lived source picks up a server that
49
+ // has since been upgraded.
50
+ let perCoinUntil = 0;
51
+ const shared = (key, load) => {
52
52
  const hit = inFlight.get(key);
53
53
  if (hit && Date.now() - hit.at < cacheMs)
54
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
- })();
55
+ const rows = load();
88
56
  // A failure is not cached: the next adapter should retry rather than
89
57
  // inherit a rejection it never caused.
90
58
  rows.catch(() => inFlight.delete(key));
91
59
  inFlight.set(key, { at: Date.now(), rows });
92
60
  return rows;
93
61
  };
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
+ };
68
+ const rowsFor = async (coin) => {
69
+ if (Date.now() >= perCoinUntil) {
70
+ try {
71
+ return await shared("*", () => request());
72
+ }
73
+ catch (error) {
74
+ if (!(error instanceof PointerSourceError) || error.status !== 400) {
75
+ throw error;
76
+ }
77
+ perCoinUntil = Date.now() + FALLBACK_RECHECK_MS;
78
+ }
79
+ }
80
+ return shared(coin.coin.toLowerCase(), () => request(coin.coin));
81
+ };
94
82
  return {
95
83
  async placements(protocol, coin) {
96
- const rows = await rowsForCoin(coin);
97
- return rows.filter(ref => ref.protocol === protocol);
84
+ const rows = await rowsFor(coin);
85
+ const wanted = coinKey(coin.coin);
86
+ // One venue stored under two spellings of the coin would otherwise
87
+ // be read twice and double-count the position.
88
+ const seen = new Set();
89
+ return (rows
90
+ .filter(row => {
91
+ if (row.protocol !== protocol || coinKey(row.coin) !== wanted) {
92
+ return false;
93
+ }
94
+ const key = [row.chainIdentifier, row.venue, row.shareToken ?? ""]
95
+ .join(":")
96
+ .toLowerCase();
97
+ if (seen.has(key))
98
+ return false;
99
+ seen.add(key);
100
+ return true;
101
+ })
102
+ // The chain type comes from the coin, not the wire: an adapter
103
+ // only ever runs on its own chain, so a server that named it
104
+ // again could only ever disagree.
105
+ .map(row => ({
106
+ id: row.id,
107
+ protocol: row.protocol,
108
+ chain: {
109
+ type: coin.chain.type,
110
+ chainId: row.chainId,
111
+ identifier: row.chainIdentifier
112
+ },
113
+ venue: row.venue,
114
+ coin: row.coin,
115
+ decimals: requireDecimals(row),
116
+ protocolName: row.protocolName,
117
+ protocolLogoUrl: row.protocolLogoUrl,
118
+ source: row.source,
119
+ label: row.label,
120
+ shareToken: row.shareToken,
121
+ url: row.url,
122
+ rpcUrl: row.rpcUrl
123
+ })));
98
124
  }
99
125
  };
100
126
  }
127
+ class PointerSourceError extends Error {
128
+ constructor(status) {
129
+ super(`pointer source ${status}`);
130
+ this.status = status;
131
+ this.name = "PointerSourceError";
132
+ }
133
+ }
134
+ const FETCH_ATTEMPTS = 3;
135
+ const RETRY_BASE_MS = 250;
136
+ const RETRY_MAX_MS = 2000;
137
+ const FALLBACK_RECHECK_MS = 5 * 60000;
138
+ /**
139
+ * Retries transient failures. Every vault's venues now ride on this one
140
+ * request, and callers keep a failed discover rather than retrying it.
141
+ */
142
+ async function fetchPlacements(url) {
143
+ for (let attempt = 1;; attempt++) {
144
+ let waitMs;
145
+ try {
146
+ const response = await fetch(url, {
147
+ headers: { Accept: "application/json" }
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
+ }
162
+ }
163
+ catch (error) {
164
+ // fetch rejects only on network failure; retry that, not our own
165
+ // errors or an abort.
166
+ if (error instanceof PointerSourceError ||
167
+ isAbortError(error) ||
168
+ attempt >= FETCH_ATTEMPTS) {
169
+ throw error;
170
+ }
171
+ }
172
+ const backoff = Math.min(RETRY_BASE_MS * 2 ** (attempt - 1), RETRY_MAX_MS);
173
+ await new Promise(resolve => setTimeout(resolve, waitMs ?? backoff));
174
+ }
175
+ }
176
+ /**
177
+ * Coin identity across spellings. `coinTypesMatch` folds a Sui address; EVM
178
+ * addresses are compared case-insensitively and with or without `0x`, since
179
+ * the API returns them lowercased and the vaults list checksums them.
180
+ */
181
+ function coinKey(coin) {
182
+ const normalized = normalizeCoinType(coin);
183
+ return normalized.includes("::") ? normalized : normalized.replace(/^0x/, "");
184
+ }
101
185
  /**
102
186
  * Merges what every source returns, de-duplicated by venue. Earlier sources win,
103
187
  * so a caller can pin rows ahead of a remote set. A source that throws is
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ember-finance/sdk",
3
3
  "description": "Ember Protocol SDK",
4
- "version": "2.15.0",
4
+ "version": "2.17.0",
5
5
  "type": "module",
6
6
  "module": "./dist/index.js",
7
7
  "main": "./dist/index.js",