@oddmaki-protocol/sdk 1.10.0 → 1.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/index.d.mts +47 -3
- package/dist/index.d.ts +47 -3
- package/dist/index.js +136 -27
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +136 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -11574,15 +11574,18 @@ var PriceMarketModule = class extends BaseModule {
|
|
|
11574
11574
|
);
|
|
11575
11575
|
}
|
|
11576
11576
|
const isDeferred = pm.strikePrice === BigInt(0);
|
|
11577
|
+
const windowSeconds = Number(pm.resolutionWindow);
|
|
11577
11578
|
const closeVAA = await this.fetchPythHistoricalData(
|
|
11578
11579
|
pm.feedId,
|
|
11579
|
-
Number(pm.closeTime)
|
|
11580
|
+
Number(pm.closeTime),
|
|
11581
|
+
windowSeconds
|
|
11580
11582
|
);
|
|
11581
11583
|
let pythUpdateData;
|
|
11582
11584
|
if (isDeferred) {
|
|
11583
11585
|
const openVAA = await this.fetchPythHistoricalData(
|
|
11584
11586
|
pm.feedId,
|
|
11585
|
-
Number(pm.openTime)
|
|
11587
|
+
Number(pm.openTime),
|
|
11588
|
+
windowSeconds
|
|
11586
11589
|
);
|
|
11587
11590
|
pythUpdateData = [...openVAA, ...closeVAA];
|
|
11588
11591
|
} else {
|
|
@@ -11661,6 +11664,29 @@ var PriceMarketModule = class extends BaseModule {
|
|
|
11661
11664
|
functionName: "getPythContract"
|
|
11662
11665
|
});
|
|
11663
11666
|
}
|
|
11667
|
+
/**
|
|
11668
|
+
* Mark a stuck price market as Invalid and refund holders 50/50 via the CTF.
|
|
11669
|
+
* Permissionless: anyone can call this once `closeTime + 7 days` has elapsed.
|
|
11670
|
+
*
|
|
11671
|
+
* Use this for markets that never resolve via Pyth — e.g. the feed was
|
|
11672
|
+
* deprecated by the publisher, or Hermes had no in-window VAA at closeTime.
|
|
11673
|
+
* Reports payouts `[1, 1]` to the CTF, so every YES and every NO token
|
|
11674
|
+
* redeems for half the underlying collateral.
|
|
11675
|
+
*
|
|
11676
|
+
* Reverts on chain with `GracePeriodNotElapsed` if called too early.
|
|
11677
|
+
*/
|
|
11678
|
+
async markInvalid(marketId) {
|
|
11679
|
+
const wallet = this.walletClient;
|
|
11680
|
+
const account = await this.getSignerAccount();
|
|
11681
|
+
const { request } = await this.publicClient.simulateContract({
|
|
11682
|
+
address: this.config.diamondAddress,
|
|
11683
|
+
abi: PythResolutionFacet_default,
|
|
11684
|
+
functionName: "markPriceMarketInvalid",
|
|
11685
|
+
args: [marketId],
|
|
11686
|
+
account
|
|
11687
|
+
});
|
|
11688
|
+
return wallet.writeContract(request);
|
|
11689
|
+
}
|
|
11664
11690
|
/**
|
|
11665
11691
|
* Set the Pyth oracle contract address. Diamond owner only.
|
|
11666
11692
|
*/
|
|
@@ -11800,18 +11826,12 @@ var PriceMarketModule = class extends BaseModule {
|
|
|
11800
11826
|
if (pm.strikePrice !== BigInt(0)) return null;
|
|
11801
11827
|
const now = BigInt(Math.floor(Date.now() / 1e3));
|
|
11802
11828
|
if (now < pm.openTime) return null;
|
|
11803
|
-
const
|
|
11804
|
-
|
|
11805
|
-
|
|
11806
|
-
|
|
11807
|
-
|
|
11808
|
-
|
|
11809
|
-
}
|
|
11810
|
-
const data = await response.json();
|
|
11811
|
-
const parsed = data.parsed?.[0];
|
|
11812
|
-
if (!parsed?.price) {
|
|
11813
|
-
throw new Error("Pyth Hermes response missing parsed.price for open VAA");
|
|
11814
|
-
}
|
|
11829
|
+
const parsed = await this.fetchPythHistoricalParsed(
|
|
11830
|
+
pm.feedId,
|
|
11831
|
+
Number(pm.openTime),
|
|
11832
|
+
Number(pm.resolutionWindow)
|
|
11833
|
+
);
|
|
11834
|
+
if (!parsed) return null;
|
|
11815
11835
|
return {
|
|
11816
11836
|
price: BigInt(parsed.price.price),
|
|
11817
11837
|
publishTime: BigInt(parsed.price.publish_time),
|
|
@@ -11821,22 +11841,111 @@ var PriceMarketModule = class extends BaseModule {
|
|
|
11821
11841
|
};
|
|
11822
11842
|
}
|
|
11823
11843
|
/**
|
|
11824
|
-
* Fetch historical Pyth price update data
|
|
11825
|
-
*
|
|
11826
|
-
*
|
|
11844
|
+
* Fetch historical Pyth price update data covering the window
|
|
11845
|
+
* `[publishTime, publishTime + windowSeconds]`.
|
|
11846
|
+
*
|
|
11847
|
+
* Hermes serves a VAA AT the exact requested second when one exists, and
|
|
11848
|
+
* 404s when no publish landed in that second. For low-volume feeds or
|
|
11849
|
+
* markets resolving deep in the past this means a single request at
|
|
11850
|
+
* `publishTime` is fragile. This helper walks a few sample points across
|
|
11851
|
+
* the window until it finds a VAA, transparently retrying with exponential
|
|
11852
|
+
* backoff on 429.
|
|
11853
|
+
*
|
|
11854
|
+
* The on-chain `pickEarliestInWindow` accepts any VAA whose `publishTime`
|
|
11855
|
+
* falls in the same window, so returning an offset VAA is correct as long
|
|
11856
|
+
* as it's still in range.
|
|
11857
|
+
*/
|
|
11858
|
+
async fetchPythHistoricalData(feedId, publishTime, windowSeconds) {
|
|
11859
|
+
const result = await this.fetchPythHistoricalRaw(
|
|
11860
|
+
feedId,
|
|
11861
|
+
publishTime,
|
|
11862
|
+
windowSeconds
|
|
11863
|
+
);
|
|
11864
|
+
if (!result) {
|
|
11865
|
+
throw new Error(
|
|
11866
|
+
`No Pyth VAA available in window [${publishTime}, ${publishTime + windowSeconds}] for feed ${feedId}`
|
|
11867
|
+
);
|
|
11868
|
+
}
|
|
11869
|
+
return result.updateData;
|
|
11870
|
+
}
|
|
11871
|
+
/**
|
|
11872
|
+
* Hermes parsed+binary fetch shared by {@link fetchPythHistoricalData} and
|
|
11873
|
+
* {@link fetchProjectedOpenPrice}. Returns the first in-window VAA whose
|
|
11874
|
+
* Hermes response includes a `parsed[0].price` entry, walking a few sample
|
|
11875
|
+
* timestamps across the window before giving up. Returns `null` on
|
|
11876
|
+
* complete miss so the caller can decide how to surface the failure
|
|
11877
|
+
* (resolution throws; UI projection returns null gracefully).
|
|
11827
11878
|
*/
|
|
11828
|
-
async
|
|
11829
|
-
const
|
|
11830
|
-
|
|
11831
|
-
|
|
11832
|
-
|
|
11879
|
+
async fetchPythHistoricalParsed(feedId, publishTime, windowSeconds) {
|
|
11880
|
+
const result = await this.fetchPythHistoricalRaw(
|
|
11881
|
+
feedId,
|
|
11882
|
+
publishTime,
|
|
11883
|
+
windowSeconds
|
|
11884
|
+
);
|
|
11885
|
+
return result?.parsed ?? null;
|
|
11886
|
+
}
|
|
11887
|
+
async fetchPythHistoricalRaw(feedId, publishTime, windowSeconds) {
|
|
11888
|
+
const window = Math.max(0, Math.floor(windowSeconds));
|
|
11889
|
+
const offsets = window === 0 ? [0] : Array.from(/* @__PURE__ */ new Set([
|
|
11890
|
+
0,
|
|
11891
|
+
Math.floor(window / 4),
|
|
11892
|
+
Math.floor(window / 2),
|
|
11893
|
+
Math.floor(3 * window / 4),
|
|
11894
|
+
window
|
|
11895
|
+
])).sort((a, b) => a - b);
|
|
11896
|
+
for (const offset of offsets) {
|
|
11897
|
+
const t = publishTime + offset;
|
|
11898
|
+
const url = `${PYTH_HERMES_BASE}/v2/updates/price/${t}?ids[]=${feedId}`;
|
|
11899
|
+
const response = await this.hermesFetchWithBackoff(url);
|
|
11900
|
+
if (response === null) continue;
|
|
11901
|
+
const data = await response.json();
|
|
11902
|
+
const updateDataRaw = data.binary?.data;
|
|
11903
|
+
const parsed = data.parsed?.[0];
|
|
11904
|
+
if (!updateDataRaw || updateDataRaw.length === 0) continue;
|
|
11905
|
+
return {
|
|
11906
|
+
updateData: updateDataRaw.map(
|
|
11907
|
+
(d) => `0x${d}`
|
|
11908
|
+
),
|
|
11909
|
+
parsed
|
|
11910
|
+
};
|
|
11833
11911
|
}
|
|
11834
|
-
|
|
11835
|
-
|
|
11836
|
-
|
|
11837
|
-
|
|
11912
|
+
return null;
|
|
11913
|
+
}
|
|
11914
|
+
/**
|
|
11915
|
+
* Fetch a Hermes URL with a short backoff on 429. Returns the response for
|
|
11916
|
+
* 2xx, `null` for 404 (treated as "no VAA at this timestamp, try another"),
|
|
11917
|
+
* and throws on persistent 429 or other non-OK statuses.
|
|
11918
|
+
*
|
|
11919
|
+
* Budget kept intentionally small (2 attempts) so callers that are already
|
|
11920
|
+
* iterating (e.g. multiple sample timestamps in `fetchPythHistoricalRaw`)
|
|
11921
|
+
* don't compound the rate-limit pressure. If Hermes is genuinely rate-
|
|
11922
|
+
* limiting us, retrying within the same request cycle won't help — better
|
|
11923
|
+
* to fail fast and let the caller back off until the next tick.
|
|
11924
|
+
*/
|
|
11925
|
+
async hermesFetchWithBackoff(url, options = {}) {
|
|
11926
|
+
const maxAttempts = options.maxAttempts ?? 2;
|
|
11927
|
+
const initialDelayMs = options.initialDelayMs ?? 750;
|
|
11928
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
11929
|
+
const response = await fetch(url);
|
|
11930
|
+
if (response.status === 429) {
|
|
11931
|
+
if (attempt === maxAttempts - 1) {
|
|
11932
|
+
throw new Error(
|
|
11933
|
+
`Pyth Hermes API error: 429 Too Many Requests (gave up after ${maxAttempts} attempts)`
|
|
11934
|
+
);
|
|
11935
|
+
}
|
|
11936
|
+
const delay = initialDelayMs * Math.pow(2, attempt);
|
|
11937
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
11938
|
+
continue;
|
|
11939
|
+
}
|
|
11940
|
+
if (response.status === 404) return null;
|
|
11941
|
+
if (!response.ok) {
|
|
11942
|
+
throw new Error(
|
|
11943
|
+
`Pyth Hermes API error: ${response.status} ${response.statusText}`
|
|
11944
|
+
);
|
|
11945
|
+
}
|
|
11946
|
+
return response;
|
|
11838
11947
|
}
|
|
11839
|
-
return
|
|
11948
|
+
return null;
|
|
11840
11949
|
}
|
|
11841
11950
|
};
|
|
11842
11951
|
|