@suilend/sdk 11.0.4 → 11.1.0-next.gd01dd81
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/lib/pyth.d.ts +32 -6
- package/lib/pyth.js +87 -8
- package/package.json +1 -1
package/lib/pyth.d.ts
CHANGED
|
@@ -81,20 +81,46 @@ export declare const DEFAULT_HERMES_TIMEOUT_MS: number;
|
|
|
81
81
|
* silently inheriting hermes-client's.
|
|
82
82
|
*/
|
|
83
83
|
export declare const hermesConnectionConfig: (hermesConfig?: HermesClientConfig) => HermesClientConfig;
|
|
84
|
+
/**
|
|
85
|
+
* Public Hermes has required a Pyth API key since 2026-08-26 16:00 UTC, and
|
|
86
|
+
* nothing in this module can supply one — so an unconfigured caller gets a 401
|
|
87
|
+
* on every price read. Said once, at construction, because the alternative is
|
|
88
|
+
* discovering it per-request from inside whatever swallows the rejection.
|
|
89
|
+
*
|
|
90
|
+
* Two ways to be authenticated, and both are honoured: `accessToken`, which
|
|
91
|
+
* hermes-client turns into a Bearer header itself, or an `Authorization`
|
|
92
|
+
* header passed as a plain object. A header in either other `HeadersInit` form
|
|
93
|
+
* gets its own warning, because it is silently dropped in transit rather than
|
|
94
|
+
* rejected — the caller believes they configured auth and did not.
|
|
95
|
+
*
|
|
96
|
+
* Exported for tests only.
|
|
97
|
+
*/
|
|
98
|
+
export declare const warnIfPublicHermesIsUnauthenticated: (endpoint: string, hermesConfig?: HermesClientConfig) => void;
|
|
99
|
+
/** Tests only: forget that the unauthenticated warning was already emitted. */
|
|
100
|
+
export declare const resetPublicHermesAuthWarning: () => void;
|
|
84
101
|
/**
|
|
85
102
|
* Pick the price source `initializeSuilend` will use.
|
|
86
103
|
*
|
|
87
104
|
* Lives here rather than in initialize.ts because it belongs beside
|
|
88
105
|
* getWorkingPythEndpoint, the thing it decides whether to call.
|
|
89
106
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
107
|
+
* The `/live` probe now runs ONLY when a `fallbackPythEndpoint` was supplied,
|
|
108
|
+
* which is the only case where its answer can change anything: with no
|
|
109
|
+
* fallback, `getWorkingPythEndpoint` returns the primary whether the probe
|
|
110
|
+
* passes or fails, so it was a request and up to 5s spent on a decision already
|
|
111
|
+
* made. Post-cutover it was worse than wasted — it reports Hermes healthy
|
|
112
|
+
* (`/live` needs no key) for an endpoint that 401s every real read, so it
|
|
113
|
+
* certified the broken path. Skipping it for an injected source is the same
|
|
114
|
+
* argument taken further: that probe answers for whether Hermes is up, not for
|
|
115
|
+
* whether it still serves the caller's feeds, nor whether the caller is even
|
|
116
|
+
* pointed at Hermes.
|
|
94
117
|
*
|
|
95
118
|
* `hermesConfig` exists so the commonest reason to reach for an injected source
|
|
96
|
-
* — an
|
|
97
|
-
*
|
|
119
|
+
* — an API key, which public Hermes now requires — does not cost the caller a
|
|
120
|
+
* hand-rolled `PriceFeedSource`. Pass it as `accessToken`, which hermes-client
|
|
121
|
+
* turns into a Bearer header on every request; a plain-object
|
|
122
|
+
* `headers: { Authorization: ... }` also works, but the other two `HeadersInit`
|
|
123
|
+
* forms do not survive its object-spread merge. It configures only the
|
|
98
124
|
* connection built here; an injected source is returned untouched, config or
|
|
99
125
|
* not, because there is nothing left to build.
|
|
100
126
|
*
|
package/lib/pyth.js
CHANGED
|
@@ -149,20 +149,97 @@ export const hermesConnectionConfig = (hermesConfig) => ({
|
|
|
149
149
|
// quietly hand back hermes-client's 5s.
|
|
150
150
|
timeout: hermesConfig?.timeout ?? DEFAULT_HERMES_TIMEOUT_MS,
|
|
151
151
|
});
|
|
152
|
+
let _warnedPublicHermesIsUnauthenticated = false;
|
|
153
|
+
/**
|
|
154
|
+
* Whether `headers` will actually reach Hermes carrying an Authorization.
|
|
155
|
+
*
|
|
156
|
+
* `HeadersInit` allows three forms, but hermes-client merges with an OBJECT
|
|
157
|
+
* SPREAD (`{ ...authHeaders, ...this.headers }`), not `new Headers()`, so only
|
|
158
|
+
* a plain object survives:
|
|
159
|
+
*
|
|
160
|
+
* [["Authorization","k"]] -> { "0": ["Authorization","k"] } header named "0"
|
|
161
|
+
* new Headers({Authorization}) -> {} dropped
|
|
162
|
+
* { Authorization: "Bearer k" } -> { Authorization: "Bearer k" } sent
|
|
163
|
+
*
|
|
164
|
+
* So this deliberately does NOT use `new Headers(init).has(...)`, which answers
|
|
165
|
+
* true for all three and would go quiet for the two that cannot authenticate.
|
|
166
|
+
* Constructing one would also throw on an invalid header name, which is not a
|
|
167
|
+
* thing a diagnostic should do to its caller.
|
|
168
|
+
*/
|
|
169
|
+
const headersCarryAuthorization = (headers) => {
|
|
170
|
+
if (headers === undefined)
|
|
171
|
+
return false;
|
|
172
|
+
if (Array.isArray(headers))
|
|
173
|
+
return false;
|
|
174
|
+
if (typeof Headers !== "undefined" && headers instanceof Headers)
|
|
175
|
+
return false;
|
|
176
|
+
return Object.keys(headers).some((name) => name.toLowerCase() === "authorization");
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Public Hermes has required a Pyth API key since 2026-08-26 16:00 UTC, and
|
|
180
|
+
* nothing in this module can supply one — so an unconfigured caller gets a 401
|
|
181
|
+
* on every price read. Said once, at construction, because the alternative is
|
|
182
|
+
* discovering it per-request from inside whatever swallows the rejection.
|
|
183
|
+
*
|
|
184
|
+
* Two ways to be authenticated, and both are honoured: `accessToken`, which
|
|
185
|
+
* hermes-client turns into a Bearer header itself, or an `Authorization`
|
|
186
|
+
* header passed as a plain object. A header in either other `HeadersInit` form
|
|
187
|
+
* gets its own warning, because it is silently dropped in transit rather than
|
|
188
|
+
* rejected — the caller believes they configured auth and did not.
|
|
189
|
+
*
|
|
190
|
+
* Exported for tests only.
|
|
191
|
+
*/
|
|
192
|
+
export const warnIfPublicHermesIsUnauthenticated = (endpoint, hermesConfig) => {
|
|
193
|
+
if (_warnedPublicHermesIsUnauthenticated)
|
|
194
|
+
return;
|
|
195
|
+
if (endpoint !== PRIMARY_PYTH_ENDPOINT)
|
|
196
|
+
return;
|
|
197
|
+
if (hermesConfig?.accessToken !== undefined)
|
|
198
|
+
return;
|
|
199
|
+
if (headersCarryAuthorization(hermesConfig?.headers))
|
|
200
|
+
return;
|
|
201
|
+
const headers = hermesConfig?.headers;
|
|
202
|
+
const droppedForm = headers !== undefined &&
|
|
203
|
+
(Array.isArray(headers) ||
|
|
204
|
+
(typeof Headers !== "undefined" && headers instanceof Headers));
|
|
205
|
+
_warnedPublicHermesIsUnauthenticated = true;
|
|
206
|
+
console.warn(droppedForm
|
|
207
|
+
? `[resolvePythConnection] hermesConfig.headers was passed as ${Array.isArray(headers) ? "an array of tuples" : "a Headers instance"}, which hermes-client drops: it merges headers with an object spread, ` +
|
|
208
|
+
`so only a plain object is sent. ${PRIMARY_PYTH_ENDPOINT} has required ` +
|
|
209
|
+
`a Pyth API key since 2026-08-26 16:00 UTC, so every price read will ` +
|
|
210
|
+
`fail with 401. Pass hermesConfig.accessToken instead.`
|
|
211
|
+
: `[resolvePythConnection] ${PRIMARY_PYTH_ENDPOINT} has required a Pyth ` +
|
|
212
|
+
`API key since 2026-08-26 16:00 UTC and none was configured, so every ` +
|
|
213
|
+
`price read will fail with 401. Pass hermesConfig.accessToken, or ` +
|
|
214
|
+
`inject your own pythConnection.`);
|
|
215
|
+
};
|
|
216
|
+
/** Tests only: forget that the unauthenticated warning was already emitted. */
|
|
217
|
+
export const resetPublicHermesAuthWarning = () => {
|
|
218
|
+
_warnedPublicHermesIsUnauthenticated = false;
|
|
219
|
+
};
|
|
152
220
|
/**
|
|
153
221
|
* Pick the price source `initializeSuilend` will use.
|
|
154
222
|
*
|
|
155
223
|
* Lives here rather than in initialize.ts because it belongs beside
|
|
156
224
|
* getWorkingPythEndpoint, the thing it decides whether to call.
|
|
157
225
|
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
226
|
+
* The `/live` probe now runs ONLY when a `fallbackPythEndpoint` was supplied,
|
|
227
|
+
* which is the only case where its answer can change anything: with no
|
|
228
|
+
* fallback, `getWorkingPythEndpoint` returns the primary whether the probe
|
|
229
|
+
* passes or fails, so it was a request and up to 5s spent on a decision already
|
|
230
|
+
* made. Post-cutover it was worse than wasted — it reports Hermes healthy
|
|
231
|
+
* (`/live` needs no key) for an endpoint that 401s every real read, so it
|
|
232
|
+
* certified the broken path. Skipping it for an injected source is the same
|
|
233
|
+
* argument taken further: that probe answers for whether Hermes is up, not for
|
|
234
|
+
* whether it still serves the caller's feeds, nor whether the caller is even
|
|
235
|
+
* pointed at Hermes.
|
|
162
236
|
*
|
|
163
237
|
* `hermesConfig` exists so the commonest reason to reach for an injected source
|
|
164
|
-
* — an
|
|
165
|
-
*
|
|
238
|
+
* — an API key, which public Hermes now requires — does not cost the caller a
|
|
239
|
+
* hand-rolled `PriceFeedSource`. Pass it as `accessToken`, which hermes-client
|
|
240
|
+
* turns into a Bearer header on every request; a plain-object
|
|
241
|
+
* `headers: { Authorization: ... }` also works, but the other two `HeadersInit`
|
|
242
|
+
* forms do not survive its object-spread merge. It configures only the
|
|
166
243
|
* connection built here; an injected source is returned untouched, config or
|
|
167
244
|
* not, because there is nothing left to build.
|
|
168
245
|
*
|
|
@@ -176,8 +253,10 @@ export const hermesConnectionConfig = (hermesConfig) => ({
|
|
|
176
253
|
export const resolvePythConnection = async (pythConnectionOverride, fallbackPythEndpoint, hermesConfig) => {
|
|
177
254
|
if (pythConnectionOverride)
|
|
178
255
|
return pythConnectionOverride;
|
|
179
|
-
|
|
180
|
-
|
|
256
|
+
const pythEndpoint = fallbackPythEndpoint
|
|
257
|
+
? await getWorkingPythEndpoint(fallbackPythEndpoint)
|
|
258
|
+
: PRIMARY_PYTH_ENDPOINT;
|
|
259
|
+
warnIfPublicHermesIsUnauthenticated(pythEndpoint, hermesConfig);
|
|
181
260
|
return createHermesPriceFeedSource(new HermesClient(pythEndpoint, hermesConnectionConfig(hermesConfig)));
|
|
182
261
|
};
|
|
183
262
|
/**
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@suilend/sdk","version":"11.0.
|
|
1
|
+
{"name":"@suilend/sdk","version":"11.1.0-next.gd01dd81","private":false,"description":"A TypeScript SDK for interacting with the Suilend program","author":"Suilend","license":"MIT","main":"./index.js","exports":{".":"./index.js","./client":"./client.js","./mmt":"./mmt.js","./strategies":"./strategies.js","./api/events":"./api/events.js","./api":"./api/index.js","./lib/constants":"./lib/constants.js","./lib":"./lib/index.js","./lib/initialize":"./lib/initialize.js","./lib/liquidityMining":"./lib/liquidityMining.js","./lib/proCompatible":"./lib/proCompatible.js","./lib/pyth":"./lib/pyth.js","./lib/strategyOwnerCap":"./lib/strategyOwnerCap.js","./lib/transactions":"./lib/transactions.js","./lib/types":"./lib/types.js","./margin":"./margin/index.js","./parsers/apiReserveAssetDataEvent":"./parsers/apiReserveAssetDataEvent.js","./parsers":"./parsers/index.js","./parsers/lendingMarket":"./parsers/lendingMarket.js","./parsers/obligation":"./parsers/obligation.js","./parsers/rateLimiter":"./parsers/rateLimiter.js","./parsers/reserve":"./parsers/reserve.js","./swap":"./swap/index.js","./swap/quote":"./swap/quote.js","./swap/transaction":"./swap/transaction.js","./utils/events":"./utils/events.js","./utils/feedId":"./utils/feedId.js","./utils":"./utils/index.js","./utils/obligation":"./utils/obligation.js","./utils/simulate":"./utils/simulate.js","./_generated/_framework/reified":"./_generated/_framework/reified.js","./_generated/_framework/util":"./_generated/_framework/util.js","./_generated/_framework/vector":"./_generated/_framework/vector.js","./_generated/suilend":"./_generated/suilend/index.js","./margin/margin/admin_cap":"./margin/margin/admin_cap.js","./margin/margin/market":"./margin/margin/market.js","./margin/margin/permissions":"./margin/margin/permissions.js","./margin/margin/position":"./margin/margin/position.js","./margin/margin/router":"./margin/margin/router.js","./margin/margin/version":"./margin/margin/version.js","./margin/utils":"./margin/utils/index.js","./_generated/suilend/cell/structs":"./_generated/suilend/cell/structs.js","./_generated/suilend/decimal/structs":"./_generated/suilend/decimal/structs.js","./_generated/suilend/lending-market/functions":"./_generated/suilend/lending-market/functions.js","./_generated/suilend/lending-market/structs":"./_generated/suilend/lending-market/structs.js","./_generated/suilend/lending-market-registry/functions":"./_generated/suilend/lending-market-registry/functions.js","./_generated/suilend/liquidity-mining/structs":"./_generated/suilend/liquidity-mining/structs.js","./_generated/suilend/obligation/structs":"./_generated/suilend/obligation/structs.js","./_generated/suilend/rate-limiter/functions":"./_generated/suilend/rate-limiter/functions.js","./_generated/suilend/rate-limiter/structs":"./_generated/suilend/rate-limiter/structs.js","./_generated/suilend/reserve/structs":"./_generated/suilend/reserve/structs.js","./_generated/suilend/reserve-config/functions":"./_generated/suilend/reserve-config/functions.js","./_generated/suilend/reserve-config/structs":"./_generated/suilend/reserve-config/structs.js","./_generated/_dependencies/source/0x1":"./_generated/_dependencies/source/0x1/index.js","./_generated/_dependencies/source/0x2":"./_generated/_dependencies/source/0x2/index.js","./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e":"./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/index.js","./margin/margin/deps/std/type_name":"./margin/margin/deps/std/type_name.js","./margin/margin/deps/sui/vec_set":"./margin/margin/deps/sui/vec_set.js","./margin/margin/deps/suilend/lending_market":"./margin/margin/deps/suilend/lending_market.js","./_generated/_dependencies/source/0x1/ascii/structs":"./_generated/_dependencies/source/0x1/ascii/structs.js","./_generated/_dependencies/source/0x1/option/structs":"./_generated/_dependencies/source/0x1/option/structs.js","./_generated/_dependencies/source/0x1/type-name/structs":"./_generated/_dependencies/source/0x1/type-name/structs.js","./_generated/_dependencies/source/0x2/bag/structs":"./_generated/_dependencies/source/0x2/bag/structs.js","./_generated/_dependencies/source/0x2/balance/structs":"./_generated/_dependencies/source/0x2/balance/structs.js","./_generated/_dependencies/source/0x2/object/structs":"./_generated/_dependencies/source/0x2/object/structs.js","./_generated/_dependencies/source/0x2/object-table/structs":"./_generated/_dependencies/source/0x2/object-table/structs.js","./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price-identifier/structs":"./_generated/_dependencies/source/0x8d97f1cd6ac663735be08d1d2b6d02a159e711586461306ce60a2b7a6a565a9e/price-identifier/structs.js"},"types":"./index.d.ts","scripts":{"build":"rm -rf ./dist && tsc && node ./fix-esm-imports.js","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.test.json","test":"vitest run","lint:ci":"yarn run typecheck","prettier":"prettier --write src/ tests/","release":"yarn run build && node ./release.js && cd ./dist && npm publish --access public"},"repository":{"type":"git","url":"git+https://github.com/fireflyprotocol/lending-mono.git","directory":"ts/sdks/sdk"},"dependencies":{"@bluefin-exchange/bluefin7k-aggregator-sdk":"^7.5.0","@cetusprotocol/aggregator-sdk":"^1.5.7","@flowx-finance/sdk":"^2.1.0","@pythnetwork/hermes-client":"3.1.0","@pythnetwork/pyth-sui-js":"4.0.0","@suilend/springsui-sdk":"^4.0.0","bignumber.js":"^11.1.5","bn.js":"^5.2.2","crypto-js":"^4.2.0","lodash":"^4.17.21","p-limit":"7.3.1","uuid":"^14.0.1"},"devDependencies":{"@mysten/bcs":"^2.0.5","@mysten/sui":"2.23.2","@suilend/sui-core":"^1.0.0","@tsconfig/recommended":"^1.0.8","@types/bn.js":"^5.2.0","@types/lodash":"^4.17.25","@types/node":"^26.2.0","fast-check":"^4.9.0","prettier":"^3.3.3","ts-node":"^10.9.2","typescript":"^6.0.3","vitest":"4.1.10"},"peerDependencies":{"@mysten/bcs":"^2.0.5","@mysten/sui":">=2.22.1 <3","@suilend/sui-core":"^1.0.0"},"type":"module"}
|