Skip to content

Commit

Permalink
feat(refactor): persist Subscan results to class (#1942)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ross Bulat authored Feb 11, 2024
1 parent 78c8300 commit b6312e6
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 66 deletions.
6 changes: 4 additions & 2 deletions src/canvas/PoolMembers/Lists/FetchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@ export const MembersListInner = ({
if (poolId > 0 && !fetchingMemberList.current) {
fetchingMemberList.current = true;

const newMembers: PoolMember[] =
await SubscanController.handleFetchPoolMembers(poolId, page);
const newMembers = (await SubscanController.handleFetchPoolMembers(
poolId,
page
)) as PoolMember[];

fetchingMemberList.current = false;
setPoolMembersApi([...newMembers]);
Expand Down
5 changes: 0 additions & 5 deletions src/contexts/Plugins/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,6 @@ export const PluginsProvider = ({ children }: { children: ReactNode }) => {
// Check if a plugin is currently enabled.
const pluginEnabled = (key: Plugin) => pluginsRef.current.includes(key);

// Reset payouts on Subscan network on `activeAccount` switch.
useEffectIgnoreInitial(() => {
SubscanController.resetData();
}, [network, activeAccount]);

// Reset payouts on Subscan plugin not enabled. Otherwise fetch payouts.
useEffectIgnoreInitial(() => {
if (!plugins.includes('subscan')) {
Expand Down
36 changes: 22 additions & 14 deletions src/hooks/useSubscanData/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ import { isCustomEvent } from 'static/utils';
import { useEventListener } from 'usehooks-ts';
import { useErasToTimeLeft } from '../useErasToTimeLeft';
import { useApi } from 'contexts/Api';
import { useActiveAccounts } from 'contexts/ActiveAccounts';

export const useSubscanData = (keys: PayoutType[]) => {
const { activeEra } = useApi();
const { pluginEnabled } = usePlugins();
const { erasToSeconds } = useErasToTimeLeft();
const { activeAccount } = useActiveAccounts();

// Store the most up to date subscan data state.
const [data, setData] = useState<SubscanData>({});
Expand All @@ -32,14 +34,18 @@ export const useSubscanData = (keys: PayoutType[]) => {
if (isCustomEvent(e) && pluginEnabled('subscan')) {
const { keys: receivedKeys }: { keys: PayoutType[] } = e.detail;

// Filter out any keys that are not provided to the hook.
const newData: SubscanData = {};
receivedKeys
.filter((key) => keys.includes(key))
.forEach((key) => {
newData[key] = SubscanController.data?.[key] || [];
});
setStateWithRef({ ...dataRef.current, ...newData }, setData, dataRef);
// Filter out any keys that are not provided to the hook active account is still present.
if (activeAccount) {
const newData: SubscanData = {};
receivedKeys
.filter((key) => keys.includes(key))
.forEach((key) => {
newData[key] =
SubscanController.payoutData[activeAccount]?.[key] || [];
});

setStateWithRef({ ...dataRef.current, ...newData }, setData, dataRef);
}
}
};

Expand Down Expand Up @@ -79,12 +85,14 @@ export const useSubscanData = (keys: PayoutType[]) => {

// Populate state on initial render if data is already available.
useEffect(() => {
const newData: SubscanData = {};
keys.forEach((key: PayoutType) => {
newData[key] = SubscanController.data?.[key] || [];
});
setStateWithRef({ ...dataRef.current, ...newData }, setData, dataRef);
}, []);
if (activeAccount) {
const newData: SubscanData = {};
keys.forEach((key: PayoutType) => {
newData[key] = SubscanController.payoutData[activeAccount]?.[key] || [];
});
setStateWithRef({ ...dataRef.current, ...newData }, setData, dataRef);
}
}, [activeAccount]);

return { data, getData, injectBlockTimestamp };
};
4 changes: 2 additions & 2 deletions src/modals/ClaimPayouts/Forms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,13 @@ export const Forms = forwardRef(
setModalStatus('closing');
},
callbackInBlock: () => {
if (payouts) {
if (payouts && activeAccount) {
// Remove Subscan unclaimed payout record(s) if they exist.
const eraPayouts: string[] = [];
payouts.forEach(({ era }) => {
eraPayouts.push(String(era));
});
SubscanController.removeUnclaimedPayouts(eraPayouts);
SubscanController.removeUnclaimedPayouts(activeAccount, eraPayouts);

// Deduct from `unclaimedPayouts` in Payouts context.
payouts.forEach(({ era, paginatedValidators }) => {
Expand Down
124 changes: 81 additions & 43 deletions src/static/SubscanController/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
SubscanPoolDetails,
SubscanPoolMember,
SubscanRequestBody,
SubscanEraPoints,
} from './types';
import type { Locale } from 'date-fns';
import { format, fromUnixTime, getUnixTime, subDays } from 'date-fns';
Expand Down Expand Up @@ -40,8 +41,14 @@ export class SubscanController {
// The network to use for Subscan API calls.
static network: string;

// Subscan data for the current account.
static data: SubscanData;
// Subscan payout data, keyed by address.
static payoutData: Record<string, SubscanData> = {};

// Subscan pool data, keyed by `<network>-<poolId>-<key1>-<key2>...`.
static poolData: Record<string, SubscanPoolDetails | PoolMember[]> = {};

// Subscan era points data, keyed by `<network>-<address>-<era>`.
static eraPointsData: Record<string, SubscanEraPoints[]> = {};

// The timestamp of the last 5 requests made.
static _lastRequestTimes = [];
Expand All @@ -57,6 +64,37 @@ export class SubscanController {
SubscanController.network = network;
}

// ------------------------------------------------------
// Handling multiple requests.
// ------------------------------------------------------

// Handle fetching the various types of payout and set state in one render.
static handleFetchPayouts = async (address: string) => {
if (!this.payoutData[address]) {
const results = await Promise.all([
this.fetchNominatorPayouts(address),
this.fetchPoolClaims(address),
]);
const { payouts, unclaimedPayouts } = results[0];
const poolClaims = results[1];

// Persist results to class.
this.payoutData[address] = {
payouts,
unclaimedPayouts,
poolClaims,
};

document.dispatchEvent(
new CustomEvent('subscan-data-updated', {
detail: {
keys: ['payouts', 'unclaimedPayouts', 'poolClaims'],
},
})
);
}
};

// ------------------------------------------------------
// Handling requests.
// ------------------------------------------------------
Expand Down Expand Up @@ -143,7 +181,10 @@ export class SubscanController {
};

// Fetch a pool's era points from Subscan.
static fetchEraPoints = async (address: string, era: number) => {
static fetchEraPoints = async (
address: string,
era: number
): Promise<SubscanEraPoints[]> => {
const result = await this.makeRequest(this.ENDPOINTS.eraStat, {
page: 0,
row: 100,
Expand All @@ -168,49 +209,47 @@ export class SubscanController {
return list.reverse().splice(0, list.length - 1);
};

// ------------------------------------------------------
// Handling multiple requests concurrently.
// ------------------------------------------------------

// Handle fetching the various types of payout and set state in one render.
static handleFetchPayouts = async (address: string) => {
const results = await Promise.all([
this.fetchNominatorPayouts(address),
this.fetchPoolClaims(address),
]);
const { payouts, unclaimedPayouts } = results[0];
const poolClaims = results[1];

// Persist results to class.
this.data['payouts'] = payouts;
this.data['unclaimedPayouts'] = unclaimedPayouts;
this.data['poolClaims'] = poolClaims;

document.dispatchEvent(
new CustomEvent('subscan-data-updated', {
detail: {
keys: ['payouts', 'unclaimedPayouts', 'poolClaims'],
},
})
);
};

// Handle fetching pool members.
static handleFetchPoolMembers = async (poolId: number, page: number) => {
const poolMembers = await this.fetchPoolMembers(poolId, page);
return poolMembers;
const dataKey = `${this.network}-${poolId}-${page}-members}`;
const currentValue = this.poolData[dataKey];

if (currentValue) {
return currentValue;
} else {
const result = await this.fetchPoolMembers(poolId, page);
this.poolData[dataKey] = result;

return result;
}
};

// Handle fetching pool details.
static handleFetchPoolDetails = async (poolId: number) => {
const poolDetails = await this.fetchPoolDetails(poolId);
return poolDetails;
const dataKey = `${this.network}-${poolId}-details}`;
const currentValue = this.poolData[dataKey];

if (currentValue) {
return currentValue as SubscanPoolDetails;
} else {
const result = await this.fetchPoolDetails(poolId);
this.poolData[dataKey] = result;
return result;
}
};

// Handle fetching era point history.
static handleFetchEraPoints = async (address: string, era: number) => {
const eraPoints = await this.fetchEraPoints(address, era);
return eraPoints;
const dataKey = `${this.network}-${address}-${era}}`;
const currentValue = this.eraPointsData[dataKey];

if (currentValue) {
return currentValue;
} else {
const result = await this.fetchEraPoints(address, era);
this.eraPointsData[dataKey] = result;
return result;
}
};

// ------------------------------------------------------
Expand All @@ -219,19 +258,18 @@ export class SubscanController {

// Resets all received data from class.
static resetData = () => {
this.data = {};
this.payoutData = {};
};

// Remove unclaimed payouts and dispatch update event.
static removeUnclaimedPayouts = (eraPayouts: string[]) => {
const updatedUnclaimedPayouts = this.data[
'unclaimedPayouts'
] as SubscanPayout[];
static removeUnclaimedPayouts = (address: string, eraPayouts: string[]) => {
const newUnclaimedPayouts = (this.payoutData[address]?.unclaimedPayouts ||
[]) as SubscanPayout[];

eraPayouts.forEach(([era]) => {
updatedUnclaimedPayouts.filter((u) => String(u.era) !== era);
newUnclaimedPayouts.filter((u) => String(u.era) !== era);
});
this.data['unclaimedPayouts'] = updatedUnclaimedPayouts;
this.payoutData[address].unclaimedPayouts = newUnclaimedPayouts;

document.dispatchEvent(
new CustomEvent('subscan-data-updated', {
Expand Down
5 changes: 5 additions & 0 deletions src/static/SubscanController/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,8 @@ export interface SubscanPoolMember {
export interface SubscanPoolDetails {
member_count: number;
}

export interface SubscanEraPoints {
era: number;
reward_point: number;
}

0 comments on commit b6312e6

Please sign in to comment.