Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions src/js/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ export const CONFIG = {
// Ethereum mainnet providers for ENS resolution
ensProviderUrls: [
'https://ethereum-rpc.publicnode.com',
'https://eth.meowrpc.com',
'https://eth.llamarpc.com',
'https://eth.drpc.org',
'https://cloudflare-eth.com'
],
Expand Down Expand Up @@ -442,7 +440,29 @@ export function loadRpcSelection() {
} catch (e) {
saved = null;
}
return normalizeRpcSelection(saved && saved.v === 2 ? saved : migrateRpcPreference(saved));
if (saved && saved.v === 3) return normalizeRpcSelection(saved);
if (saved && saved.v === 2) return normalizeRpcSelection(leavePreviousDefault(saved));
return normalizeRpcSelection(migrateRpcPreference(saved));
}

const PREVIOUS_DEFAULT = ['drpc', 'publicnode', 'tenderly'];

/**
* A selection saved before the default changed whose enabled set is exactly
* the old default never chose anything: it moves to the current default,
* keeping a custom URL as an off row. Any other v2 selection stays as it was,
* and saving writes v3, so a later choice is never moved.
*/
function leavePreviousDefault(sel) {
const on = (Array.isArray(sel.rows) ? sel.rows : []).filter(r => r && r.on).map(r => r.key);
const isPrevious = on.length === PREVIOUS_DEFAULT.length && PREVIOUS_DEFAULT.every(k => on.includes(k));
if (!isPrevious) return sel;
return {
v: 3,
rows: RPC_ENDPOINTS.map(e => ({ key: e.key, on: false }))
.concat([{ key: RPC_CUSTOM_KEY, on: false }]),
customUrl: typeof sel.customUrl === 'string' ? sel.customUrl : ''
};
}

/**
Expand Down Expand Up @@ -498,7 +518,7 @@ function normalizeRpcSelection(sel) {
/** @param {{rows: Array<{key: string, on: boolean}>, customUrl: string}} sel */
export function saveRpcSelection(sel) {
localStorage.setItem(CONFIG.storageKeys.rpcPreference, JSON.stringify({
v: 2,
v: 3,
rows: sel.rows.map(r => ({ key: r.key, on: !!r.on })),
customUrl: (sel.customUrl || '').trim()
}));
Expand Down
15 changes: 9 additions & 6 deletions src/js/ui/GasEstimator.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
*/

import { Logger } from '../logger.js';
import { CONFIG } from '../config.js';
import { CONFIG, getRpcEndpoints } from '../config.js';

export const GasEstimator = {
// RPC endpoints from centralized config
RPC_URLS: CONFIG.network.rpcEndpoints,
// The user's RPC selection, read per call so a change in Settings applies at once
get RPC_URLS() {
return getRpcEndpoints().map(e => e.url);
},
currentRpcIndex: 0,

// Approximate gas units for Streamr operations (based on real tx data)
Expand All @@ -31,11 +33,12 @@ export const GasEstimator = {
async rpcCall(method, params = []) {
let lastError = null;
const startIndex = this.currentRpcIndex;
const urls = this.RPC_URLS;

// Try each RPC in sequence, starting from the last successful one
for (let i = 0; i < this.RPC_URLS.length; i++) {
const index = (startIndex + i) % this.RPC_URLS.length;
const rpcUrl = this.RPC_URLS[index];
for (let i = 0; i < urls.length; i++) {
const index = (startIndex + i) % urls.length;
const rpcUrl = urls[index];

try {
const controller = new AbortController();
Expand Down
10 changes: 10 additions & 0 deletions tests/unit/GasEstimator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ describe('GasEstimator', () => {
expect(isPolygonUrl).toBe(true);
});
});

it('should follow the RPC selection in Settings', () => {
localStorage.setItem('pombo_rpc_preference',
JSON.stringify({ v: 3, rows: [{ key: 'sentio', on: true }], customUrl: '' }));
try {
expect(GasEstimator.RPC_URLS).toEqual(['https://matic.rpc.sentio.xyz']);
} finally {
localStorage.removeItem('pombo_rpc_preference');
}
});
});

describe('CACHE_DURATION', () => {
Expand Down
59 changes: 59 additions & 0 deletions tests/unit/config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,65 @@ describe('config', () => {
});
});

describe('a selection saved with the previous default', () => {
const STORAGE_KEY = 'pombo_rpc_preference';
const OLD_DEFAULT = ['drpc', 'publicnode', 'tenderly'];
const saveV2 = (rows, customUrl = '') =>
localStorage.setItem(STORAGE_KEY, JSON.stringify({ v: 2, rows, customUrl }));
const enabled = (sel) => sel.rows.filter(r => r.on).map(r => r.key);
const defaultUrls = () =>
RPC_DEFAULT_ENABLED.map(k => RPC_ENDPOINTS.find(e => e.key === k).url);

beforeEach(() => localStorage.removeItem(STORAGE_KEY));
afterEach(() => localStorage.removeItem(STORAGE_KEY));

it('should move to the current default, whatever the saved order', () => {
saveV2([
{ key: 'tenderly', on: true }, { key: 'drpc', on: true },
{ key: 'publicnode', on: true }, { key: '1rpc', on: false }
]);
expect(rpcSelectionUrls(loadRpcSelection())).toEqual(defaultUrls());
});

it('should keep a custom url as an off row when it moves', () => {
saveV2(
OLD_DEFAULT.map(k => ({ key: k, on: true })).concat([{ key: RPC_CUSTOM_KEY, on: false }]),
'https://my-own-node.example'
);
const sel = loadRpcSelection();
expect(rpcSelectionUrls(sel)).toEqual(defaultUrls());
expect(sel.customUrl).toBe('https://my-own-node.example');
expect(sel.rows.find(r => r.key === RPC_CUSTOM_KEY)).toEqual({ key: RPC_CUSTOM_KEY, on: false });
});

it('should leave any other saved choice as it was', () => {
const choices = [
{ rows: [{ key: 'drpc', on: true }, { key: 'tenderly', on: true }], customUrl: '' },
{ rows: OLD_DEFAULT.concat(['1rpc']).map(k => ({ key: k, on: true })), customUrl: '' },
{
rows: OLD_DEFAULT.map(k => ({ key: k, on: true })).concat([{ key: RPC_CUSTOM_KEY, on: true }]),
customUrl: 'https://my-own-node.example'
}
];
for (const { rows, customUrl } of choices) {
saveV2(rows, customUrl);
expect(enabled(loadRpcSelection())).toEqual(rows.filter(r => r.on).map(r => r.key));
}
});

it('should never move a choice saved after the change, even the old default', () => {
saveRpcSelection({ rows: OLD_DEFAULT.map(k => ({ key: k, on: true })), customUrl: '' });
expect(enabled(loadRpcSelection())).toEqual(OLD_DEFAULT);
});

it('should stay put once moved and saved', () => {
saveV2(OLD_DEFAULT.map(k => ({ key: k, on: true })));
const moved = loadRpcSelection();
saveRpcSelection(moved);
expect(loadRpcSelection()).toEqual(moved);
});
});

describe('migration from the preset setting', () => {
const STORAGE_KEY = 'pombo_rpc_preference';

Expand Down
Loading