fix: read USD prices from Chainlink price feeds through the RPC endpoint (closes #525)
CoinDesk now refuses every request without an API key, so mainnet showed no USD prices. On mainnet the wallet now reads latestRoundData() from Chainlink's USD price feeds through the configured RPC endpoint. ETH and the held or tracked tokens with a feed are read as one batched request, at most every 5 minutes. src/shared/tokenList.js lists the feeds of 53 bundled tokens, keyed by contract address, and the ETH/USD feed. A reading older than its feed's heartbeat plus 30 minutes, or not positive, is no price. A failed read leaves no price and is logged. getPrice() takes a contract address instead of a symbol. The CoinDesk request is removed. Model: opus-5-5
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