diff --git a/packages/extension-polkagate/src/popup/history/Detail.tsx b/packages/extension-polkagate/src/popup/history/Detail.tsx
index 5de3c1536..13c463a47 100644
--- a/packages/extension-polkagate/src/popup/history/Detail.tsx
+++ b/packages/extension-polkagate/src/popup/history/Detail.tsx
@@ -9,6 +9,7 @@ import { Divider, Grid, Typography } from '@mui/material';
import React, { useCallback, useContext, useMemo } from 'react';
import { AccountContext, PButton, Popup } from '../../components';
+import { getVoteType } from '../../fullscreen/governance/utils/util';
import { useTranslation } from '../../hooks';
import { HeaderBrand } from '../../partials';
import { accountName, amountToMachine, toShortAddress, upperCaseFirstChar } from '../../util/utils';
@@ -63,7 +64,7 @@ export default function Detail ({ chainName, decimal, info, setShowDetail, showD
showBackArrow
text={t('Transaction Detail')}
/>
-
+
{action}
@@ -79,9 +80,24 @@ export default function Detail ({ chainName, decimal, info, setShowDetail, showD
{info?.to &&
} toCopy={info?.to?.address} />
}
+ {info?.refId &&
+
+ }
+ {info?.refId &&
+
+ }
+ {info?.delegatee &&
+ } noDivider toCopy={info.delegatee} />
+ }
{info?.amount &&
}
+ {info?.conviction &&
+
+ }
+ {info?.class &&
+
+ }
{info?.fee &&
}
diff --git a/packages/extension-polkagate/src/popup/history/HistoryTabs.tsx b/packages/extension-polkagate/src/popup/history/HistoryTabs.tsx
index ee4b63107..15bb5f662 100644
--- a/packages/extension-polkagate/src/popup/history/HistoryTabs.tsx
+++ b/packages/extension-polkagate/src/popup/history/HistoryTabs.tsx
@@ -6,18 +6,26 @@
import { Box, Divider, Tab, Tabs } from '@mui/material';
import React, { useCallback } from 'react';
-import { useInfo, useTranslation } from '../../hooks';
-import { STAKING_CHAINS } from '../../util/constants';
+import { useInfo, useIsExtensionPopup, useTranslation } from '../../hooks';
+import { GOVERNANCE_CHAINS, STAKING_CHAINS } from '../../util/constants';
export enum TAB_MAP {
ALL,
TRANSFERS,
- STAKING
+ STAKING,
+ GOVERNANCE
}
-export default function HistoryTabs ({ address, setTabIndex, tabIndex }: {address: string | undefined , tabIndex: TAB_MAP, setTabIndex: React.Dispatch>}): React.ReactElement {
+interface HistoryTabsProps {
+ address: string | undefined;
+ tabIndex: TAB_MAP;
+ setTabIndex: React.Dispatch>;
+}
+
+export default function HistoryTabs ({ address, setTabIndex, tabIndex }: HistoryTabsProps): React.ReactElement {
const { t } = useTranslation();
const { chain } = useInfo(address);
+ const isExtensionMode = useIsExtensionPopup();
const handleTabChange = useCallback((_event: React.SyntheticEvent, value: number) => {
setTabIndex(value);
@@ -34,14 +42,15 @@ export default function HistoryTabs ({ address, setTabIndex, tabIndex }: {addres
fontWeight: 500
},
color: 'text.primary',
- fontSize: '18px',
+ fontSize: isExtensionMode ? '16px' : '18px',
fontWeight: 400,
- minWidth: '108px',
+ minWidth: isExtensionMode ? '70px' : '108px',
+ p: isExtensionMode ? '12px' : undefined,
textTransform: 'capitalize'
}}
value={TAB_MAP.ALL}
/>
- } label='' sx={{ minWidth: '1px', p: '0', width: '1px' }} value={4} />
+ } label='' sx={{ minWidth: '1px', p: '0', width: '1px' }} value={4} />
{STAKING_CHAINS.includes(chain?.genesisHash ?? '') &&
- } label='' sx={{ minWidth: '1px', p: '0', width: '1px' }} value={5} />
+ } label='' sx={{ minWidth: '1px', p: '0', width: '1px' }} value={5} />
}
{STAKING_CHAINS.includes(chain?.genesisHash ?? '') &&
}
+ {GOVERNANCE_CHAINS.includes(chain?.genesisHash ?? '') &&
+ } label='' sx={{ minWidth: '1px', p: '0', width: '1px' }} value={6} />
+ }
+ {GOVERNANCE_CHAINS.includes(chain?.genesisHash ?? '') &&
+
+ }
);
diff --git a/packages/extension-polkagate/src/popup/history/index.tsx b/packages/extension-polkagate/src/popup/history/index.tsx
index f4f090ff9..12eb60b14 100644
--- a/packages/extension-polkagate/src/popup/history/index.tsx
+++ b/packages/extension-polkagate/src/popup/history/index.tsx
@@ -24,9 +24,9 @@ export default function TransactionHistory (): React.ReactElement {
const [tabIndex, setTabIndex] = useState(state?.tabIndex ?? TAB_MAP.ALL);
- const { grouped, tabHistory, transfersTx } = useTransactionHistory(address, tabIndex);
+ const { governanceTx, grouped, tabHistory, transfersTx } = useTransactionHistory(address, tabIndex);
- const _onBack = useCallback(() => {
+ const onBack = useCallback(() => {
history.push({
pathname: state?.pathname ?? '/'
});
@@ -35,7 +35,7 @@ export default function TransactionHistory (): React.ReactElement {
return (
<>
@@ -63,12 +63,12 @@ export default function TransactionHistory (): React.ReactElement {
/>
));
})}
- {grouped === null && transfersTx.isFetching === false &&
+ {grouped === null && transfersTx.isFetching === false && governanceTx.isFetching === false &&
{t('Nothing to show')}
}
- {(grouped === undefined || (transfersTx.isFetching && tabHistory?.length === 0)) &&
+ {(grouped === undefined || ((transfersTx.isFetching || governanceTx.isFetching) && tabHistory?.length === 0)) &&
}
diff --git a/packages/extension-polkagate/src/popup/history/modal/HistoryDetailModal.tsx b/packages/extension-polkagate/src/popup/history/modal/HistoryDetailModal.tsx
index 9ea5a0e21..0bdfd8410 100644
--- a/packages/extension-polkagate/src/popup/history/modal/HistoryDetailModal.tsx
+++ b/packages/extension-polkagate/src/popup/history/modal/HistoryDetailModal.tsx
@@ -9,6 +9,7 @@ import { Divider, Grid, Typography } from '@mui/material';
import React, { useCallback, useContext, useMemo } from 'react';
import { AccountContext, PButton } from '../../../components';
+import { getVoteType } from '../../../fullscreen/governance/utils/util';
import { useTranslation } from '../../../hooks';
import { accountName, amountToMachine, toShortAddress, upperCaseFirstChar } from '../../../util/utils';
import Explorer from '../Explorer';
@@ -30,7 +31,7 @@ export default function HistoryDetailModal ({ chainName, decimal, info, setShowD
const { accounts } = useContext(AccountContext);
const options = { day: 'numeric', hour: 'numeric', minute: 'numeric', month: 'short', second: 'numeric', weekday: 'short', year: 'numeric' } as Intl.DateTimeFormatOptions;
- const _onBack = useCallback(() => {
+ const onBack = useCallback(() => {
setShowDetail(false);
}, [setShowDetail]);
@@ -73,9 +74,24 @@ export default function HistoryDetailModal ({ chainName, decimal, info, setShowD
{info?.to &&
} toCopy={info?.to?.address} />
}
+ {info?.refId &&
+
+ }
+ {info?.refId &&
+
+ }
+ {info?.delegatee &&
+ } noDivider toCopy={info.delegatee} />
+ }
{info?.amount &&
}
+ {info?.conviction &&
+
+ }
+ {info?.class &&
+
+ }
{info?.fee &&
}
@@ -95,9 +111,9 @@ export default function HistoryDetailModal ({ chainName, decimal, info, setShowD
('Back')}
+ text={t('Back')}
/>
>
);
diff --git a/packages/extension-polkagate/src/popup/history/modal/HistoryModal.tsx b/packages/extension-polkagate/src/popup/history/modal/HistoryModal.tsx
index 771dff20b..2cc2ad4f1 100644
--- a/packages/extension-polkagate/src/popup/history/modal/HistoryModal.tsx
+++ b/packages/extension-polkagate/src/popup/history/modal/HistoryModal.tsx
@@ -32,7 +32,7 @@ export default function HistoryModal ({ address, setDisplayPopup }: Props): Reac
const [detailInfo, setDetailInfo] = useState();
const [showDetail, setShowDetail] = useState(false);
- const { grouped, tabHistory, transfersTx } = useTransactionHistory(address, tabIndex);
+ const { governanceTx, grouped, tabHistory, transfersTx } = useTransactionHistory(address, tabIndex);
const backToAccount = useCallback(() => setDisplayPopup(undefined), [setDisplayPopup]);
@@ -71,12 +71,12 @@ export default function HistoryModal ({ address, setDisplayPopup }: Props): Reac
/>
));
})}
- {grouped === null && transfersTx.isFetching === false &&
+ {grouped === null && transfersTx.isFetching === false && governanceTx.isFetching === false &&
{t('Nothing to show')}
}
- {(grouped === undefined || (transfersTx.isFetching && tabHistory?.length === 0)) &&
+ {(grouped === undefined || ((transfersTx.isFetching || governanceTx.isFetching) && tabHistory?.length === 0)) &&
}
{grouped &&
diff --git a/packages/extension-polkagate/src/popup/history/partials/HistoryItem.tsx b/packages/extension-polkagate/src/popup/history/partials/HistoryItem.tsx
index 053edafa3..d34fc5984 100644
--- a/packages/extension-polkagate/src/popup/history/partials/HistoryItem.tsx
+++ b/packages/extension-polkagate/src/popup/history/partials/HistoryItem.tsx
@@ -84,7 +84,7 @@ export default function HistoryItem ({ anotherDay, chainName, date, decimal, for
- {info.success ? t('Completed') : t('Failed')}
+ {info.success ? t('Completed') : t('Failed')}
diff --git a/packages/extension-polkagate/src/popup/history/useTransactionHistory.tsx b/packages/extension-polkagate/src/popup/history/useTransactionHistory.tsx
index 2a37e1faa..5313f4fe3 100644
--- a/packages/extension-polkagate/src/popup/history/useTransactionHistory.tsx
+++ b/packages/extension-polkagate/src/popup/history/useTransactionHistory.tsx
@@ -3,20 +3,16 @@
/* eslint-disable react/jsx-max-props-per-line */
-import type { TransactionDetail, Transfers } from '../../util/types';
+import type { Extrinsics, TransactionDetail, Transfers } from '../../util/types';
import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';
import { useInfo } from '../../hooks';
+import { getGovHistory } from '../../util/api/getGovHistory';
import { getTxTransfers } from '../../util/api/getTransfers';
import { STAKING_ACTIONS } from '../../util/constants';
import { getHistoryFromStorage } from '../../util/utils';
-
-enum TAB_MAP {
- ALL,
- TRANSFERS,
- STAKING
-}
+import { TAB_MAP } from './HistoryTabs';
interface RecordTabStatus {
pageNum: number,
@@ -25,6 +21,13 @@ interface RecordTabStatus {
transactions?: Transfers[]
}
+interface RecordTabStatusGov {
+ pageNum: number,
+ isFetching?: boolean,
+ hasMore?: boolean,
+ transactions?: Extrinsics[]
+}
+
const SINGLE_PAGE_SIZE = 50;
const MAX_PAGE = 4;
@@ -37,14 +40,16 @@ const INITIAL_STATE = {
export interface TransactionHistoryOutput{
grouped: Record | null | undefined;
- tabHistory: TransactionDetail[] | null
- transfersTx: object & RecordTabStatus
+ tabHistory: TransactionDetail[] | null;
+ transfersTx: object & RecordTabStatus;
+ governanceTx: object & RecordTabStatusGov;
}
export default function useTransactionHistory (address: string | undefined, tabIndex: TAB_MAP): TransactionHistoryOutput {
- const { chainName, formatted } = useInfo(address);
+ const { chain, chainName, decimal, formatted } = useInfo(address);
- const [fetchedHistoriesFromSubscan, setFetchedHistoriesFromSubscan] = React.useState([]);
+ const [fetchedTransferHistoriesFromSubscan, setFetchedTransferHistoriesFromSubscan] = React.useState([]);
+ const [fetchedGovernanceHistoriesFromSubscan, setFetchedGovernanceHistoriesFromSubscan] = React.useState([]);
const [tabHistory, setTabHistory] = useState([]);
const [localHistories, setLocalHistories] = useState([]);
@@ -52,11 +57,18 @@ export default function useTransactionHistory (address: string | undefined, tabI
return Object.assign({}, state, action);
}
+ function stateReducerGov (state: object, action: RecordTabStatusGov) {
+ return Object.assign({}, state, action);
+ }
+
const [transfersTx, setTransfersTx] = useReducer(stateReducer, INITIAL_STATE);
+ const [governanceTx, setGovernanceTx] = useReducer(stateReducerGov, INITIAL_STATE);
const observerInstance = useRef();
const receivingTransfers = useRef();
+ const receivingGovernance = useRef();
receivingTransfers.current = transfersTx;
+ receivingGovernance.current = governanceTx;
const grouped = useMemo((): Record | null | undefined => {
if (!tabHistory) {
@@ -83,6 +95,35 @@ export default function useTransactionHistory (address: string | undefined, tabI
return temp;
}, [tabHistory]);
+ useEffect(() => {
+ if (!governanceTx?.transactions?.length || !decimal) {
+ return;
+ }
+
+ const govHistoryFromSubscan: TransactionDetail[] = [];
+
+ governanceTx.transactions.forEach((govTx: Extrinsics): void => {
+ govHistoryFromSubscan.push({
+ action: 'Governance',
+ amount: govTx.amount !== undefined ? (Number(govTx.amount) / (10 ** decimal)).toString() : undefined,
+ block: govTx.block_num,
+ class: govTx.class,
+ conviction: govTx.conviction,
+ date: govTx.block_timestamp * 1000, // to be consistent with the locally saved times
+ delegatee: govTx.delegatee,
+ fee: govTx.fee,
+ from: { address: govTx.account_display.address, name: '' },
+ refId: govTx.refId,
+ subAction: govTx.call_module_function,
+ success: govTx.success,
+ txHash: govTx.extrinsic_hash,
+ voteType: govTx.voteType
+ });
+ });
+
+ setFetchedGovernanceHistoriesFromSubscan(govHistoryFromSubscan);
+ }, [decimal, formatted, governanceTx.transactions, transfersTx]);
+
useEffect(() => {
if (!transfersTx?.transactions?.length) {
return;
@@ -121,16 +162,16 @@ export default function useTransactionHistory (address: string | undefined, tabI
}
});
- setFetchedHistoriesFromSubscan(historyFromSubscan);
+ setFetchedTransferHistoriesFromSubscan(historyFromSubscan);
}, [formatted, transfersTx]);
useEffect(() => {
- if (!localHistories && !fetchedHistoriesFromSubscan) {
+ if (!localHistories && !fetchedTransferHistoriesFromSubscan && !fetchedGovernanceHistoriesFromSubscan) {
return;
}
- const filteredLocalHistories = localHistories?.filter((h1) => !fetchedHistoriesFromSubscan?.find((h2) => h1.txHash === h2.txHash));
- let history = filteredLocalHistories.concat(fetchedHistoriesFromSubscan);
+ const filteredLocalHistories = localHistories?.filter((h1) => !fetchedTransferHistoriesFromSubscan?.find((h2) => h1.txHash === h2.txHash));
+ let history = filteredLocalHistories.concat(fetchedTransferHistoriesFromSubscan).concat(fetchedGovernanceHistoriesFromSubscan);
history = history.sort((a, b) => b.date - a.date);
@@ -141,12 +182,15 @@ export default function useTransactionHistory (address: string | undefined, tabI
case (TAB_MAP.STAKING):
history = history.filter((h) => STAKING_ACTIONS.includes(h.action));
break;
+ case (TAB_MAP.GOVERNANCE):
+ history = history.filter((h) => ['Governance', 'Unlock Referenda'].includes(h.action));
+ break;
default:
break;
}
setTabHistory(history);
- }, [tabIndex, fetchedHistoriesFromSubscan, localHistories]);
+ }, [tabIndex, fetchedTransferHistoriesFromSubscan, localHistories, fetchedGovernanceHistoriesFromSubscan]);
useEffect(() => {
formatted && getHistoryFromStorage(String(formatted)).then((h) => {
@@ -154,6 +198,27 @@ export default function useTransactionHistory (address: string | undefined, tabI
}).catch(console.error);
}, [formatted, chainName]);
+ const getGovExtrinsics = useCallback(async (outerState: RecordTabStatusGov): Promise => {
+ const { pageNum, transactions } = outerState;
+
+ setGovernanceTx({
+ isFetching: true,
+ pageNum
+ });
+
+ const res = await getGovHistory(chainName ?? '', String(formatted), pageNum, chain?.ss58Format);
+
+ const { count, extrinsics } = res.data || {};
+ const nextPageNum = pageNum + 1;
+
+ setGovernanceTx({
+ hasMore: !(nextPageNum * SINGLE_PAGE_SIZE >= count) && nextPageNum < MAX_PAGE,
+ isFetching: false,
+ pageNum: nextPageNum,
+ transactions: transactions?.concat(extrinsics || [])
+ });
+ }, [chainName, formatted, chain?.ss58Format]);
+
const getTransfers = useCallback(async (outerState: RecordTabStatus): Promise => {
const { pageNum, transactions } = outerState;
@@ -187,11 +252,11 @@ export default function useTransactionHistory (address: string | undefined, tabI
return; // If the observer object is not in view, do nothing
}
- if (receivingTransfers.current?.isFetching) {
+ if (receivingTransfers.current?.isFetching && receivingGovernance.current?.isFetching) {
return; // If already fetching, do nothing
}
- if (!receivingTransfers.current?.hasMore) {
+ if (!receivingTransfers.current?.hasMore && !receivingTransfers.current?.hasMore) {
observerInstance.current?.disconnect();
console.log('No more data to load, disconnecting observer.');
@@ -202,6 +267,11 @@ export default function useTransactionHistory (address: string | undefined, tabI
.catch((error) => {
console.error('Error fetching transfers:', error);
});
+
+ receivingGovernance.current && getGovExtrinsics(receivingGovernance.current) // Fetch more governance history if available
+ .catch((error) => {
+ console.error('Error fetching transfers:', error);
+ });
};
const options = {
@@ -221,7 +291,7 @@ export default function useTransactionHistory (address: string | undefined, tabI
return () => {
observerInstance.current?.disconnect();
};
- }, [chainName, formatted, getTransfers]);
+ }, [chainName, formatted, getGovExtrinsics, getTransfers, governanceTx]);
- return { grouped, tabHistory, transfersTx };
+ return { governanceTx, grouped, tabHistory, transfersTx };
}
diff --git a/packages/extension-polkagate/src/util/api/getGovHistory.ts b/packages/extension-polkagate/src/util/api/getGovHistory.ts
new file mode 100644
index 000000000..bcecd5fa6
--- /dev/null
+++ b/packages/extension-polkagate/src/util/api/getGovHistory.ts
@@ -0,0 +1,270 @@
+// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+/* eslint-disable @typescript-eslint/no-unsafe-member-access */
+/* eslint-disable no-case-declarations */
+/* eslint-disable camelcase */
+
+import type { Extrinsics, ExtrinsicsRequest } from '../types';
+
+import request from 'umi-request';
+
+import { hexToU8a } from '@polkadot/util';
+import { encodeAddress } from '@polkadot/util-crypto';
+
+// Common types
+interface AccountId {
+ Id: string;
+}
+
+interface StandardVote {
+ balance: string;
+ vote: number;
+}
+
+interface SplitAbstainVote {
+ abstain: string;
+ aye: string;
+ nay: string;
+}
+
+// Parameter types for different convictionvoting actions
+interface BaseParam {
+ value: T;
+}
+
+interface VotesType {
+ Standard: StandardVote;
+ SplitAbstain: SplitAbstainVote
+}
+
+type ClassOfParam = BaseParam;
+
+interface DelegateParams extends Array> {
+ 0: ClassOfParam; // ClassOf
+ 1: BaseParam; // delegatee AccountId
+ 2: BaseParam; // conviction Locked2x
+ 3: BaseParam; // balance
+}
+
+interface UndelegateParams extends Array> {
+ 0: ClassOfParam; // ClassOf
+}
+
+interface UnlockParams extends Array> {
+ 0: ClassOfParam; // ClassOf
+ 1: BaseParam; // delegatee AccountId
+}
+
+interface VoteParams extends Array> {
+ 0: BaseParam; // PollIndexOf
+ 1: BaseParam;
+}
+
+interface RemoveVoteParams extends Array> {
+ 0: ClassOfParam; // ClassOf
+ 1: BaseParam; // PollIndexOf
+}
+
+// Function types
+// Define a type for the param types mapping
+interface ParamTypesMapping {
+ delegate: DelegateParams;
+ undelegate: UndelegateParams;
+ vote: VoteParams;
+ remove_vote: RemoveVoteParams;
+ unlock: UnlockParams;
+}
+
+const nullObject = {
+ code: 0,
+ data: {
+ count: 0,
+ extrinsics: null
+ },
+ generated_at: Date.now(),
+ message: 'Success'
+} as unknown as ExtrinsicsRequest;
+
+const MODULE = 'convictionvoting';
+const RETRY_DELAY = 1100; // 1.1 second delay
+const MAX_RETRIES = 7;
+const BATCH_SIZE = 3;
+const PAGE_SIZE = 12;
+
+/**
+ * Sleep function to create delays between retries
+ * @param ms Milliseconds to sleep
+ */
+const sleep = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms));
+
+/**
+ * Enhanced POST request with retry logic for rate limiting
+ */
+async function postReq (
+ api: string,
+ data: Record = {},
+ option?: Record,
+ retryCount = 0
+): Promise {
+ try {
+ const response = await request.post(api, { data, ...option }) as T;
+
+ return response;
+ } catch (error) {
+ if (retryCount < MAX_RETRIES) {
+ console.log(`Rate limit hit, retrying in ${RETRY_DELAY}ms... (Attempt ${retryCount + 1}/${MAX_RETRIES})`);
+ await sleep(RETRY_DELAY);
+
+ return postReq(api, data, option, retryCount + 1);
+ }
+
+ throw error;
+ }
+}
+
+/**
+ * Processes an array in batches
+ * @param array Array to process
+ * @param batchSize Size of each batch
+ * @param processor Function to process each batch
+ */
+async function processBatch (array: T[], batchSize: number, processor: (items: T[]) => Promise): Promise {
+ const results: T[] = [];
+
+ for (let i = 0; i < array.length; i += batchSize) {
+ const batch = array.slice(i, i + batchSize);
+ const batchResults = await processor(batch);
+
+ results.push(...batchResults);
+
+ // Add delay between batches if not the last batch
+ if (i + batchSize < array.length) {
+ await sleep(RETRY_DELAY);
+ }
+ }
+
+ return results;
+}
+
+/**
+ * Process a batch of extrinsics
+ */
+async function processExtrinsicsBatch (extrinsics: Extrinsics[], network: string, prefix: number) {
+ return Promise.all(
+ extrinsics.map(async (extrinsic) => {
+ try {
+ const functionName = extrinsic.call_module_function as keyof ParamTypesMapping;
+
+ interface ResponseType {
+ data: {
+ params: ParamTypesMapping[typeof functionName];
+ };
+ }
+
+ const txDetail = await postReq(
+ `https://${network}.api.subscan.io/api/scan/extrinsic`,
+ { hash: extrinsic.extrinsic_hash }
+ );
+
+ const additionalInfo = getAdditionalInfo(functionName, txDetail, prefix);
+
+ return {
+ ...extrinsic,
+ ...additionalInfo
+ } as Extrinsics;
+ } catch (error) {
+ console.error('Failed to fetch details for extrinsic:', error);
+
+ return extrinsic;
+ }
+ })
+ );
+}
+
+/**
+ * Fetches governance history for a given address
+ * @param chainName - Name of the blockchain
+ * @param address - Account address
+ * @param pageNum - Page number for pagination
+ * @param prefix - chain prefix
+ * @returns Promise resolving to ExtrinsicsRequest
+ */
+export async function getGovHistory (chainName: string, address: string, pageNum: number, prefix: number | undefined): Promise {
+ if (!chainName || prefix === undefined) {
+ return Promise.resolve(nullObject);
+ }
+
+ const network = chainName.toLowerCase();
+
+ const extrinsics = await postReq(`https://${network}.api.subscan.io/api/v2/scan/extrinsics`, {
+ address,
+ module: MODULE,
+ page: pageNum,
+ row: PAGE_SIZE
+ });
+
+ if (!extrinsics.data.extrinsics) {
+ return extrinsics;
+ }
+
+ // Process extrinsics in batches
+ const extrinsicsInfo = await processBatch(
+ extrinsics.data.extrinsics,
+ BATCH_SIZE,
+ (batch) => processExtrinsicsBatch(batch, network, prefix)
+ );
+
+ return {
+ ...extrinsics,
+ data: {
+ ...extrinsics.data,
+ extrinsics: extrinsicsInfo
+ }
+ };
+}
+
+function getAdditionalInfo (functionName: keyof ParamTypesMapping, txDetail: { data: { params: ParamTypesMapping[typeof functionName]; } }, prefix: number) {
+ const id = (txDetail.data.params[1]?.value as AccountId).Id as string | undefined;
+ const formattedAddress = id ? encodeAddress(hexToU8a(id), prefix) : undefined;
+
+ const voteBalance = ((txDetail.data.params[1]?.value as VotesType)?.Standard?.balance ?? (txDetail.data.params[1]?.value as VotesType)?.SplitAbstain?.abstain) as string | undefined;
+ const voteType = ((txDetail.data.params[1]?.value as VotesType)?.Standard?.vote ?? null) as number | null;
+
+ switch (functionName) {
+ case 'delegate':
+ return {
+ amount: txDetail.data.params[3]?.value as string | undefined,
+ class: txDetail.data.params[0]?.value as number | undefined,
+ conviction: txDetail.data.params[2]?.value as string | undefined,
+ delegatee: formattedAddress
+ };
+
+ case 'undelegate':
+ return {
+ class: txDetail.data.params[0]?.value as number | undefined
+ };
+
+ case 'unlock':
+ return {
+ class: txDetail.data.params[0]?.value as number | undefined,
+ from: formattedAddress
+ };
+
+ case 'vote':
+ return {
+ amount: voteBalance,
+ refId: txDetail.data.params[0]?.value as number | undefined,
+ voteType
+ };
+
+ case 'remove_vote':
+ return {
+ class: txDetail.data.params[0]?.value as number | undefined,
+ refId: txDetail.data.params[1]?.value as number | undefined
+ };
+
+ default:
+ return {};
+ }
+}
diff --git a/packages/extension-polkagate/src/util/types.ts b/packages/extension-polkagate/src/util/types.ts
index c66e08260..1ec110c01 100644
--- a/packages/extension-polkagate/src/util/types.ts
+++ b/packages/extension-polkagate/src/util/types.ts
@@ -1,8 +1,6 @@
// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0
-// @ts-nocheck
-
import type { LinkOption } from '@polkagate/apps-config/endpoints/types';
import type React from 'react';
import type { ApiPromise } from '@polkadot/api';
@@ -14,6 +12,7 @@ import type { InjectedExtension } from '@polkadot/extension-inject/types';
import type { IconTheme } from '@polkadot/react-identicon/types';
import type { Balance } from '@polkadot/types/interfaces';
import type { AccountId } from '@polkadot/types/interfaces/runtime';
+// @ts-ignore
import type { PalletNominationPoolsBondedPoolInner, PalletNominationPoolsPoolMember, PalletNominationPoolsRewardPool } from '@polkadot/types/lookup';
import type { BN } from '@polkadot/util';
import type { KeypairType } from '@polkadot/util-crypto/types';
@@ -97,9 +96,7 @@ export interface ValidatorsIdentities {
eraIndex: number;
}
-export interface SavedValidatorsIdentities {
- [chainName: string]: ValidatorsIdentities;
-}
+export type SavedValidatorsIdentities = Record;
export interface AllValidatorsFromSubscan {
current: ValidatorsFromSubscan[];
@@ -113,7 +110,7 @@ export interface ValidatorsName {
export interface SavedMetaData {
chainName: string;
- metaData: any
+ metaData: unknown
}
export interface ValidatorsFromSubscan {
@@ -139,8 +136,8 @@ interface stashAccountDisplay {
address: string;
display: string;
identity: boolean;
- judgements: any;
- parent: any;
+ judgements: unknown;
+ parent: unknown;
}
export interface TxResult {
@@ -151,15 +148,20 @@ export interface TxResult {
failureText?: string;
}
export interface TransactionDetail extends TxResult {
- action: string; // send, Solo staking, pool staking ...
+ action: string; // send, Solo staking, pool staking, convictionvoting ...
amount?: string;
chain?: Chain;
date: number;
from: NameAddress;
- subAction?: string; // bond_extra, unbound, nominate
+ subAction?: string; // bond_extra, unbound, nominate, vote
to?: NameAddress;
token?: string;
throughProxy?: NameAddress;
+ refId?: number;
+ voteType?: number;
+ class?: number;
+ conviction?: string;
+ delegatee?: string;
}
export interface TxInfo extends TransactionDetail {
@@ -226,7 +228,7 @@ interface Identity {
export interface TransferRequest {
code: number;
data: {
- list: any;
+ list: unknown;
count: number;
transfers: Transfers[];
};
@@ -234,6 +236,16 @@ export interface TransferRequest {
message: string;
}
+export interface ExtrinsicsRequest {
+ code: number;
+ data: {
+ count: number;
+ extrinsics: Extrinsics[];
+ };
+ generated_at: number;
+ message: string;
+}
+
export interface TipsRequest {
code: number;
data: {
@@ -244,6 +256,32 @@ export interface TipsRequest {
message: string;
}
+export interface Extrinsics {
+ id: number,
+ block_num: number,
+ block_timestamp: number,
+ extrinsic_index: string,
+ call_module_function: string, // vote
+ call_module: string, // convictionvoting
+ nonce: number,
+ extrinsic_hash: string,
+ success: boolean,
+ fee: string,
+ fee_used: string,
+ tip: string,
+ finalized: true,
+ account_display: {
+ address: string,
+ people: Record
+ },
+ refId?: number;
+ amount?: string;
+ voteType?: number;
+ class?: number;
+ conviction?: string;
+ delegatee?: string;
+}
+
export interface Transfers {
amount: string;
asset_symbol: string;
@@ -267,7 +305,7 @@ interface AccountDisplay {
judgements: string;
account_index: string;
identity: boolean;
- parent: any;
+ parent: unknown;
}
export interface CouncilInfo extends DeriveElectionsInfo {
@@ -282,7 +320,7 @@ export interface PersonsInfo {
export interface MotionsInfo {
proposals: DeriveCollectiveProposal[];
- proposalInfo: any[];
+ proposalInfo: unknown[];
accountInfo: DeriveAccountInfo[]
}
@@ -484,7 +522,7 @@ export interface SubQueryRewardInfo {
reward: Reward
}
export interface SubQueryHistory {
- action(action: any): unknown;
+ action(action: unknown): unknown;
id: string,
blockNumber: number,
extrinsicIdx: number,