@haven-fi/solauto-sdk 1.0.833 → 1.0.835
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.
|
@@ -40,11 +40,11 @@ const tokens = __importStar(require("./tokenConstants"));
|
|
|
40
40
|
exports.SWITCHBOARD_PRICE_FEED_IDS = {
|
|
41
41
|
[spl_token_1.NATIVE_MINT.toString()]: {
|
|
42
42
|
feedId: "4Hmd6PdjVA9auCoScE12iaBogfwS4ZXQ6VZoBeqanwWW",
|
|
43
|
-
feedHash: "
|
|
43
|
+
feedHash: "0x19089ab8a78c2d6e1b0b17ed50db7d99d7a05260160dfdf727746f656e879539",
|
|
44
44
|
},
|
|
45
45
|
[tokens.JUP_SOL]: {
|
|
46
|
-
feedId: "
|
|
47
|
-
feedHash: "
|
|
46
|
+
feedId: "BRCWKZ1PevwTFzBL2MLZM5hMwCNMWE8NYuS6zFPaXZ6y",
|
|
47
|
+
feedHash: "0xd126e3a968e310748ad925d7d728f3e779121b0c26b7d1a3d061cc2da3093005",
|
|
48
48
|
},
|
|
49
49
|
[tokens.JITO_SOL]: {
|
|
50
50
|
feedId: "5htZ4vPKPjAEg8EJv6JHcaCetMM4XehZo8znQvrp6Ur3",
|
|
@@ -55,12 +55,12 @@ exports.SWITCHBOARD_PRICE_FEED_IDS = {
|
|
|
55
55
|
feedHash: "0x74e140c452fe29cced80898a51961ed1410c1fe0efbdc970fe5aac986427419e"
|
|
56
56
|
},
|
|
57
57
|
[tokens.JTO]: {
|
|
58
|
-
feedId: "
|
|
59
|
-
feedHash: "
|
|
58
|
+
feedId: "V9o7SWeCFpGpKbWwVQD9x5RnpUEMh8sUAwqi1XnCReu",
|
|
59
|
+
feedHash: "0x1ac1e88ed90c78e2514aa346ba1c90807d12524089920348f29b725832f4e070"
|
|
60
60
|
},
|
|
61
61
|
[tokens.H_SOL]: {
|
|
62
|
-
feedId: "
|
|
63
|
-
feedHash: "
|
|
62
|
+
feedId: "3zCUYzDhuyZREWgngzJfUt2UzQ5SzKCUK5AdwiB2HYmU",
|
|
63
|
+
feedHash: "0x1b8b079fe96e1749f6418405e91407790689623dee8dc123ae26eb833c9d9bd5",
|
|
64
64
|
},
|
|
65
65
|
[tokens.POPCAT]: {
|
|
66
66
|
feedId: "FTcFqwCjAgv2VMaowQ9XSBcesVzFzZkaju25nKio6bki",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"priceUtils.d.ts","sourceRoot":"","sources":["../../src/utils/priceUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,IAAI,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAiBrE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAErD,UAAU,WAAW;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,SAAS,EAAE,EAClB,SAAS,GAAE,SAA8B,EACzC,SAAS,CAAC,EAAE,SAAS,GACpB,OAAO,CAAC,MAAM,EAAE,CAAC,CA8CnB;AAED,wBAAsB,aAAa,CACjC,KAAK,EAAE,SAAS,EAAE,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"priceUtils.d.ts","sourceRoot":"","sources":["../../src/utils/priceUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,IAAI,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAiBrE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAErD,UAAU,WAAW;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,SAAS,EAAE,EAClB,SAAS,GAAE,SAA8B,EACzC,SAAS,CAAC,EAAE,SAAS,GACpB,OAAO,CAAC,MAAM,EAAE,CAAC,CA8CnB;AAED,wBAAsB,aAAa,CACjC,KAAK,EAAE,SAAS,EAAE,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CA0EtC;AAkBD,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,SAAS,EAAE,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAsDtC;AAED,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,SAAS,EAAE,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAsBtC;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,SAAS,GAAG,YAAY,GAAG,MAAM,GAAG,SAAS,EACnD,SAAS,GAAE,SAA8B,EACzC,SAAS,CAAC,EAAE,SAAS,GACpB,MAAM,GAAG,SAAS,CAyBpB"}
|
package/dist/utils/priceUtils.js
CHANGED
|
@@ -80,9 +80,16 @@ async function getPythPrices(mints) {
|
|
|
80
80
|
return {};
|
|
81
81
|
}
|
|
82
82
|
const priceFeedIds = mints.map((mint) => constants_1.PYTH_PRICE_FEED_IDS[mint.toString()]);
|
|
83
|
-
const
|
|
83
|
+
const pythApiKey = process.env.PYTH_API_KEY;
|
|
84
|
+
// Hermes requires authentication as of August 26, 2026. Jupiter keeps price
|
|
85
|
+
// fetching available for browser clients and services without a Pyth API key.
|
|
86
|
+
if (!pythApiKey) {
|
|
87
|
+
(0, generalUtils_1.consoleLog)("PYTH_API_KEY is not configured; using Jupiter prices");
|
|
88
|
+
return await getJupTokenPrices(mints);
|
|
89
|
+
}
|
|
90
|
+
const getReq = async () => await fetch(`https://pyth.dourolabs.app/hermes/v2/updates/price/latest?${priceFeedIds
|
|
84
91
|
.map((x) => `ids%5B%5D=${x}`)
|
|
85
|
-
.join("&")}`);
|
|
92
|
+
.join("&")}`, { headers: { Authorization: `Bearer ${pythApiKey}` } });
|
|
86
93
|
const deriveValue = (price, exponent) => {
|
|
87
94
|
if (exponent > 0) {
|
|
88
95
|
return Number((0, numberUtils_1.toBaseUnit)(Number(price), exponent));
|
|
@@ -94,23 +101,30 @@ async function getPythPrices(mints) {
|
|
|
94
101
|
return Number(price);
|
|
95
102
|
}
|
|
96
103
|
};
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
104
|
+
let prices;
|
|
105
|
+
try {
|
|
106
|
+
prices = await (0, generalUtils_1.retryWithExponentialBackoff)(async () => {
|
|
107
|
+
const resp = await getReq();
|
|
108
|
+
if (!resp.ok) {
|
|
109
|
+
const responseBody = await resp.text();
|
|
110
|
+
throw new Error(`Pyth Hermes request failed (${resp.status} ${resp.statusText}): ${responseBody}`);
|
|
111
|
+
}
|
|
112
|
+
const json = await resp.json();
|
|
113
|
+
const parsedPrices = json.parsed.map((x) => {
|
|
114
|
+
return {
|
|
115
|
+
realtimePrice: deriveValue(x.price.price, x.price.expo),
|
|
116
|
+
confInterval: deriveValue(x.price.conf, x.price.expo) * 2.12,
|
|
117
|
+
emaPrice: deriveValue(x.ema_price.price, x.ema_price.expo),
|
|
118
|
+
emaConfInterval: deriveValue(x.ema_price.conf, x.ema_price.expo) * 2.12,
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
return parsedPrices;
|
|
122
|
+
}, 5, 250);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
(0, generalUtils_1.consoleLog)(`Failed to fetch Pyth prices; using Jupiter prices: ${error?.message ?? error}`);
|
|
126
|
+
return await getJupTokenPrices(mints);
|
|
127
|
+
}
|
|
114
128
|
return (0, generalUtils_1.createRecord)(mints.map((x) => x.toString()), prices);
|
|
115
129
|
}
|
|
116
130
|
function getSortedPriceData(prices, mints) {
|
package/package.json
CHANGED
|
@@ -10,12 +10,12 @@ interface SwitchboardFeed {
|
|
|
10
10
|
export const SWITCHBOARD_PRICE_FEED_IDS: { [key: string]: SwitchboardFeed } = {
|
|
11
11
|
[NATIVE_MINT.toString()]: {
|
|
12
12
|
feedId: "4Hmd6PdjVA9auCoScE12iaBogfwS4ZXQ6VZoBeqanwWW",
|
|
13
|
-
feedHash: "
|
|
13
|
+
feedHash: "0x19089ab8a78c2d6e1b0b17ed50db7d99d7a05260160dfdf727746f656e879539",
|
|
14
14
|
},
|
|
15
15
|
[tokens.JUP_SOL]: {
|
|
16
|
-
feedId: "
|
|
16
|
+
feedId: "BRCWKZ1PevwTFzBL2MLZM5hMwCNMWE8NYuS6zFPaXZ6y",
|
|
17
17
|
feedHash:
|
|
18
|
-
"
|
|
18
|
+
"0xd126e3a968e310748ad925d7d728f3e779121b0c26b7d1a3d061cc2da3093005",
|
|
19
19
|
},
|
|
20
20
|
[tokens.JITO_SOL]: {
|
|
21
21
|
feedId: "5htZ4vPKPjAEg8EJv6JHcaCetMM4XehZo8znQvrp6Ur3",
|
|
@@ -26,13 +26,13 @@ export const SWITCHBOARD_PRICE_FEED_IDS: { [key: string]: SwitchboardFeed } = {
|
|
|
26
26
|
feedHash: "0x74e140c452fe29cced80898a51961ed1410c1fe0efbdc970fe5aac986427419e"
|
|
27
27
|
},
|
|
28
28
|
[tokens.JTO]: {
|
|
29
|
-
feedId: "
|
|
30
|
-
feedHash: "
|
|
29
|
+
feedId: "V9o7SWeCFpGpKbWwVQD9x5RnpUEMh8sUAwqi1XnCReu",
|
|
30
|
+
feedHash: "0x1ac1e88ed90c78e2514aa346ba1c90807d12524089920348f29b725832f4e070"
|
|
31
31
|
},
|
|
32
32
|
[tokens.H_SOL]: {
|
|
33
|
-
feedId: "
|
|
33
|
+
feedId: "3zCUYzDhuyZREWgngzJfUt2UzQ5SzKCUK5AdwiB2HYmU",
|
|
34
34
|
feedHash:
|
|
35
|
-
"
|
|
35
|
+
"0x1b8b079fe96e1749f6418405e91407790689623dee8dc123ae26eb833c9d9bd5",
|
|
36
36
|
},
|
|
37
37
|
[tokens.POPCAT]: {
|
|
38
38
|
feedId: "FTcFqwCjAgv2VMaowQ9XSBcesVzFzZkaju25nKio6bki",
|
package/src/utils/priceUtils.ts
CHANGED
|
@@ -88,12 +88,21 @@ export async function getPythPrices(
|
|
|
88
88
|
const priceFeedIds = mints.map(
|
|
89
89
|
(mint) => PYTH_PRICE_FEED_IDS[mint.toString()]
|
|
90
90
|
);
|
|
91
|
+
const pythApiKey = process.env.PYTH_API_KEY;
|
|
92
|
+
|
|
93
|
+
// Hermes requires authentication as of August 26, 2026. Jupiter keeps price
|
|
94
|
+
// fetching available for browser clients and services without a Pyth API key.
|
|
95
|
+
if (!pythApiKey) {
|
|
96
|
+
consoleLog("PYTH_API_KEY is not configured; using Jupiter prices");
|
|
97
|
+
return await getJupTokenPrices(mints);
|
|
98
|
+
}
|
|
91
99
|
|
|
92
100
|
const getReq = async () =>
|
|
93
101
|
await fetch(
|
|
94
|
-
`https://
|
|
102
|
+
`https://pyth.dourolabs.app/hermes/v2/updates/price/latest?${priceFeedIds
|
|
95
103
|
.map((x) => `ids%5B%5D=${x}`)
|
|
96
|
-
.join("&")}
|
|
104
|
+
.join("&")}`,
|
|
105
|
+
{ headers: { Authorization: `Bearer ${pythApiKey}` } }
|
|
97
106
|
);
|
|
98
107
|
|
|
99
108
|
const deriveValue = (price: number, exponent: number) => {
|
|
@@ -106,30 +115,40 @@ export async function getPythPrices(
|
|
|
106
115
|
}
|
|
107
116
|
};
|
|
108
117
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
let prices: PriceResult[];
|
|
119
|
+
try {
|
|
120
|
+
prices = await retryWithExponentialBackoff(
|
|
121
|
+
async () => {
|
|
122
|
+
const resp = await getReq();
|
|
123
|
+
if (!resp.ok) {
|
|
124
|
+
const responseBody = await resp.text();
|
|
125
|
+
throw new Error(
|
|
126
|
+
`Pyth Hermes request failed (${resp.status} ${resp.statusText}): ${responseBody}`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
116
129
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
const json = await resp.json();
|
|
131
|
+
const parsedPrices = json.parsed.map((x: any) => {
|
|
132
|
+
return {
|
|
133
|
+
realtimePrice: deriveValue(x.price.price, x.price.expo),
|
|
134
|
+
confInterval: deriveValue(x.price.conf, x.price.expo) * 2.12,
|
|
135
|
+
emaPrice: deriveValue(x.ema_price.price, x.ema_price.expo),
|
|
136
|
+
emaConfInterval:
|
|
137
|
+
deriveValue(x.ema_price.conf, x.ema_price.expo) * 2.12,
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return parsedPrices;
|
|
142
|
+
},
|
|
143
|
+
5,
|
|
144
|
+
250
|
|
145
|
+
);
|
|
146
|
+
} catch (error: any) {
|
|
147
|
+
consoleLog(
|
|
148
|
+
`Failed to fetch Pyth prices; using Jupiter prices: ${error?.message ?? error}`
|
|
149
|
+
);
|
|
150
|
+
return await getJupTokenPrices(mints);
|
|
151
|
+
}
|
|
133
152
|
|
|
134
153
|
return createRecord(
|
|
135
154
|
mints.map((x) => x.toString()),
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { describe, it } from "mocha";
|
|
3
|
+
import { PublicKey } from "@solana/web3.js";
|
|
4
|
+
import { getPythPrices, USDC } from "../../src";
|
|
5
|
+
|
|
6
|
+
describe("Pyth price fetching", () => {
|
|
7
|
+
const originalFetch = global.fetch;
|
|
8
|
+
const originalPythApiKey = process.env.PYTH_API_KEY;
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
global.fetch = originalFetch;
|
|
12
|
+
if (originalPythApiKey === undefined) {
|
|
13
|
+
delete process.env.PYTH_API_KEY;
|
|
14
|
+
} else {
|
|
15
|
+
process.env.PYTH_API_KEY = originalPythApiKey;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("uses Jupiter when a Pyth API key is not configured", async () => {
|
|
20
|
+
delete process.env.PYTH_API_KEY;
|
|
21
|
+
let requestUrl = "";
|
|
22
|
+
global.fetch = (async (input: string | URL | Request) => {
|
|
23
|
+
requestUrl = input.toString();
|
|
24
|
+
return {
|
|
25
|
+
json: async () => ({ [USDC]: { usdPrice: 1 } }),
|
|
26
|
+
} as Response;
|
|
27
|
+
}) as typeof fetch;
|
|
28
|
+
|
|
29
|
+
const prices = await getPythPrices([new PublicKey(USDC)]);
|
|
30
|
+
|
|
31
|
+
expect(requestUrl).to.contain("lite-api.jup.ag/price/v3");
|
|
32
|
+
expect(prices[USDC].realtimePrice).to.equal(1);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("uses authenticated upgraded Hermes when a Pyth API key is configured", async () => {
|
|
36
|
+
process.env.PYTH_API_KEY = "test-api-key";
|
|
37
|
+
let requestUrl = "";
|
|
38
|
+
let authorization = "";
|
|
39
|
+
global.fetch = (async (
|
|
40
|
+
input: string | URL | Request,
|
|
41
|
+
init?: RequestInit
|
|
42
|
+
) => {
|
|
43
|
+
requestUrl = input.toString();
|
|
44
|
+
authorization = (init?.headers as Record<string, string>).Authorization;
|
|
45
|
+
return {
|
|
46
|
+
ok: true,
|
|
47
|
+
json: async () => ({
|
|
48
|
+
parsed: [
|
|
49
|
+
{
|
|
50
|
+
price: { price: "100000000", conf: "1000", expo: -8 },
|
|
51
|
+
ema_price: { price: "100000000", conf: "1000", expo: -8 },
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
}),
|
|
55
|
+
} as Response;
|
|
56
|
+
}) as typeof fetch;
|
|
57
|
+
|
|
58
|
+
const prices = await getPythPrices([new PublicKey(USDC)]);
|
|
59
|
+
|
|
60
|
+
expect(requestUrl).to.contain("pyth.dourolabs.app/hermes");
|
|
61
|
+
expect(authorization).to.equal("Bearer test-api-key");
|
|
62
|
+
expect(prices[USDC].realtimePrice).to.equal(1);
|
|
63
|
+
});
|
|
64
|
+
});
|