fix: read USD prices from Chainlink price feeds through the RPC endpoint (closes #525)
CoinDesk now refuses requests without an API key, so mainnet showed no USD prices. Mainnet prices now come from Chainlink's USD price feeds, read with eth_call through the user's RPC endpoint; no other service is asked. The bundled list pairs each of 53 tokens with its feed by contract address, and ETH and WETH use ETH/USD. The popup reads the feeds of ETH and the tokens the user holds or tracks in one batched request, at most every 5 minutes. A reading older than its heartbeat plus 30 minutes, or not positive, is no price. A token with no feed shows no USD value. CoinDesk is gone. RULES.md still names the CoinDesk price API. Model: opus-5-5
This commit was merged in pull request #681.
This commit is contained in:
+13
-1
@@ -43,12 +43,15 @@ const GAS_PRICE_WEI = BASE_FEE_WEI + PRIORITY_FEE_WEI;
|
||||
|
||||
const STUB_BLOCK_NUMBER = 21000000;
|
||||
|
||||
// A 32-byte zero word, returned for every eth_call. It is what makes ethers'
|
||||
// A 32-byte zero word, returned for every eth_call but a price feed's
|
||||
// latestRoundData() (see rpcResult() below). It is what makes ethers'
|
||||
// ENS reverse lookup resolve to "no resolver set" instead of throwing, and a
|
||||
// throw there reaches the console through src/shared/log.js, which fails the
|
||||
// run on its own.
|
||||
const ZERO_WORD = "0x" + "0".repeat(64);
|
||||
|
||||
const SELECTOR_LATEST_ROUND_DATA = "0xfeaf968c";
|
||||
|
||||
// One ETH, so the popup's balance lines render something and the wallet does
|
||||
// not look empty on the approval screen.
|
||||
const STUB_BALANCE_WEI = 10n ** 18n;
|
||||
@@ -111,6 +114,15 @@ function rpcResult(req, state) {
|
||||
return latestBlock();
|
||||
}
|
||||
|
||||
// A price feed's latestRoundData() gets five zero words, as in network.js:
|
||||
// a price of zero, which the wallet counts as no price.
|
||||
const data = String(
|
||||
(req.params && req.params[0] && req.params[0].data) || "",
|
||||
);
|
||||
if (method === "eth_call" && data.startsWith(SELECTOR_LATEST_ROUND_DATA)) {
|
||||
return "0x" + "0".repeat(64 * 5);
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(RPC_RESULTS, method)) {
|
||||
return RPC_RESULTS[method];
|
||||
}
|
||||
|
||||
@@ -251,8 +251,13 @@ const SELECTOR_DECIMALS = "0x313ce567";
|
||||
const SELECTOR_SYMBOL = "0x95d89b41";
|
||||
const SELECTOR_NAME = "0x06fdde03";
|
||||
const SELECTOR_BALANCE_OF = "0x70a08231";
|
||||
const SELECTOR_LATEST_ROUND_DATA = "0xfeaf968c";
|
||||
|
||||
// Every eth_call still answers with a zero word except decimals(), symbol(),
|
||||
// On any contract, a latestRoundData() call is answered with five zero words.
|
||||
// That is a round with a price of zero, which the wallet counts as no
|
||||
// price. So no screen shows a USD value beside the amounts the tests compare.
|
||||
//
|
||||
// Every other eth_call answers with a zero word except decimals(), symbol(),
|
||||
// name() and balanceOf() on the stub token. The wallet reads decimals() back
|
||||
// at signing time to compare with the scale the confirmation screen rendered
|
||||
// (issue #305). Adding the token by its contract address reads the first three
|
||||
@@ -273,6 +278,9 @@ function ethCallResult(req, opts) {
|
||||
if (!call || typeof call !== "object") return ZERO_WORD;
|
||||
const data = String(call.data || call.input || "").toLowerCase();
|
||||
const to = String(call.to || "").toLowerCase();
|
||||
if (data.startsWith(SELECTOR_LATEST_ROUND_DATA)) {
|
||||
return "0x" + "0".repeat(64 * 5);
|
||||
}
|
||||
if (to !== STUB_TOKEN.address) return ZERO_WORD;
|
||||
if (data.startsWith(SELECTOR_DECIMALS)) {
|
||||
return word(opts.tokenDecimalsOverride ?? STUB_TOKEN.decimals);
|
||||
@@ -764,11 +772,6 @@ async function installNetworkStubs(ctx, opts) {
|
||||
}
|
||||
}
|
||||
|
||||
// CoinDesk price tick
|
||||
if (url.hostname.endsWith("coindesk.com")) {
|
||||
return jsonResponse(route, { Data: {} });
|
||||
}
|
||||
|
||||
// The interception canary's own request. Answered with nothing: what
|
||||
// is being observed is that it arrived here at all.
|
||||
if (url.href === WORKER_PROBE_URL) {
|
||||
|
||||
Reference in New Issue
Block a user