diff --git a/plugins/evm/dao/api.go b/plugins/evm/dao/api.go index 659cae0..46204a9 100644 --- a/plugins/evm/dao/api.go +++ b/plugins/evm/dao/api.go @@ -26,7 +26,7 @@ type ISrv interface { BlockByHash(ctx context.Context, hash string) *EvmBlock TransactionsCursor(ctx context.Context, limit int, before, after *uint, opts ...model.Option) ([]TransactionSampleJson, map[string]interface{}) AccountsCursor(ctx context.Context, address string, limit int, before, after *string) ([]AccountsJson, map[string]interface{}) - ContractsCursor(ctx context.Context, limit int, before, after *string) ([]ContractsJson, map[string]interface{}) + ContractsCursor(ctx context.Context, limit int, before, after *string, verifiedSourceOnly bool) ([]ContractsJson, map[string]interface{}) AccountTokens(ctx context.Context, address, category string) []AccountTokenJson CollectiblesCursor(ctx context.Context, address string, contract string, limit int, before, after *string) ([]Erc721Holders, map[string]interface{}) @@ -541,10 +541,13 @@ func (c ContractsJson) Cursor() string { return util.Base64Encode(fmt.Sprintf("%d_%s", c.TransactionCount, c.Address)) } -func (a *ApiSrv) ContractsCursor(ctx context.Context, limit int, before, after *string) ([]ContractsJson, map[string]interface{}) { +func (a *ApiSrv) ContractsCursor(ctx context.Context, limit int, before, after *string, verifiedSourceOnly bool) ([]ContractsJson, map[string]interface{}) { var list []ContractsJson fetch := limit + 1 q := sg.db.WithContext(ctx).Model(&Contract{}).Select("contract_name,address,transaction_count,verify_status") + if verifiedSourceOnly { + q = q.Where("verify_status = ? AND source_code IS NOT NULL AND source_code <> ?", "verified", "") + } if cursor := cursorDecode(after); len(cursor) == 2 { q = q.Where("(transaction_count,address) < (?,?)", cursor[0], cursor[1]).Order("transaction_count desc").Order("address desc") } else if cursor = cursorDecode(before); len(cursor) == 2 { diff --git a/plugins/evm/dao/api_cursor_test.go b/plugins/evm/dao/api_cursor_test.go index 6236dbd..f520240 100644 --- a/plugins/evm/dao/api_cursor_test.go +++ b/plugins/evm/dao/api_cursor_test.go @@ -69,3 +69,42 @@ func TestAccountsCursorBeforeUsesBeforeCursor(t *testing.T) { assert.Equal(t, false, page["has_previous_page"]) assert.Equal(t, true, page["has_next_page"]) } + +func TestContractsCursorVerifiedSourceOnly(t *testing.T) { + db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) + require.NoError(t, err) + require.NoError(t, db.AutoMigrate(&Contract{})) + + sg = &Storage{db: db} + + ctx := context.Background() + contracts := []Contract{ + { + Address: "0x0000000000000000000000000000000000000001", + ContractName: "VerifiedWithSource", + VerifyStatus: "verified", + SourceCode: "pragma solidity ^0.8.0; contract VerifiedWithSource {}", + TransactionCount: 30, + }, + { + Address: "0x0000000000000000000000000000000000000002", + ContractName: "VerifiedWithoutSource", + VerifyStatus: "verified", + TransactionCount: 20, + }, + { + Address: "0x0000000000000000000000000000000000000003", + ContractName: "UnverifiedWithSource", + SourceCode: "pragma solidity ^0.8.0; contract UnverifiedWithSource {}", + TransactionCount: 10, + }, + } + require.NoError(t, db.Create(&contracts).Error) + + list, page := (&ApiSrv{}).ContractsCursor(ctx, 10, nil, nil, true) + + require.Len(t, list, 1) + assert.Equal(t, "VerifiedWithSource", list[0].ContractName) + assert.Equal(t, "verified", list[0].VerifyStatus) + assert.Equal(t, false, page["has_next_page"]) +} diff --git a/plugins/evm/http/api_test.go b/plugins/evm/http/api_test.go index 1d7768f..f6ee275 100644 --- a/plugins/evm/http/api_test.go +++ b/plugins/evm/http/api_test.go @@ -23,7 +23,7 @@ func (m MockServer) AccountsCursor(ctx context.Context, address string, limit in return nil, nil } -func (m MockServer) ContractsCursor(ctx context.Context, limit int, before, after *string) ([]dao.ContractsJson, map[string]interface{}) { +func (m MockServer) ContractsCursor(ctx context.Context, limit int, before, after *string, verifiedSourceOnly bool) ([]dao.ContractsJson, map[string]interface{}) { return nil, nil } diff --git a/plugins/evm/http/http.go b/plugins/evm/http/http.go index ddcc41d..136b5d7 100644 --- a/plugins/evm/http/http.go +++ b/plugins/evm/http/http.go @@ -342,9 +342,10 @@ func contractHandle(w http.ResponseWriter, r *http.Request) error { } type contractsParams struct { - Limit int `json:"row" validate:"min=1,max=100"` - Before *string `json:"before" validate:"omitempty,min=0"` - After *string `json:"after" validate:"omitempty,min=0"` + Limit int `json:"row" validate:"min=1,max=100"` + Before *string `json:"before" validate:"omitempty,min=0"` + After *string `json:"after" validate:"omitempty,min=0"` + VerifiedSourceOnly bool `json:"verified_source"` } // @Summary Evm contract list @@ -360,7 +361,7 @@ func contractsHandle(w http.ResponseWriter, r *http.Request) error { toJson(w, 10001, nil, err) return nil } - list, page := srv.ContractsCursor(r.Context(), p.Limit, p.Before, p.After) + list, page := srv.ContractsCursor(r.Context(), p.Limit, p.Before, p.After, p.VerifiedSourceOnly) toJson(w, 0, map[string]interface{}{"list": list, "pagination": page}, nil) return nil } diff --git a/ui-react/src/components/contract/contractTable.tsx b/ui-react/src/components/contract/contractTable.tsx index 9bf77ab..2eef0eb 100644 --- a/ui-react/src/components/contract/contractTable.tsx +++ b/ui-react/src/components/contract/contractTable.tsx @@ -1,7 +1,7 @@ -import React, { useMemo } from 'react' +import React from 'react' import { BareProps } from '@/types/page' -import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, getKeyValue, Spinner } from '@heroui/react' +import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, getKeyValue, Spinner, Switch } from '@heroui/react' import { getPVMContractListParams, unwrap, usePVMContracts } from '@/utils/api' import { PAGE_SIZE } from '@/utils/const' import { useData } from '@/context' @@ -18,11 +18,13 @@ const Component: React.FC = ({ children, className, args }) => { const { metadata, token } = useData() const [page, setPage] = React.useState(1) const [cursor, setCursor] = React.useState<{ after?: number; before?: number }>({}) + const [verifiedSourceOnly, setVerifiedSourceOnly] = React.useState(false) const rowsPerPage = PAGE_SIZE const NEXT_PUBLIC_API_HOST = env('NEXT_PUBLIC_API_HOST') || '' const { data, isLoading } = usePVMContracts(NEXT_PUBLIC_API_HOST, { ...args, row: rowsPerPage, + verified_source: verifiedSourceOnly, ...cursor, }) const contractsData = unwrap(data) @@ -41,46 +43,51 @@ const Component: React.FC = ({ children, className, args }) => { setPage(page + 1) } } + const handleVerifiedSourceChange = (selected: boolean) => { + setVerifiedSourceOnly(selected) + setCursor({}) + setPage(1) + } return ( - - } - classNames={{ - wrapper: 'min-h-[222px]', - td: 'h-[50px]', - }}> - - Contract - Name - Transaction - Status - - } items={items || []} emptyContent={'No data'}> - {(item) => ( - - {(columnKey) => { - if (columnKey === 'address') { - return ( - - {item.address} - - ) - } else if (columnKey === 'verify_status') { - return {item.verify_status === 'verified' ? 'Verified' : 'Unverified'} - } - return {getKeyValue(item, columnKey)} - }} - - )} - -
+
+
+ + Verified source only + +
+ } + classNames={{ + wrapper: 'min-h-[222px]', + td: 'h-[50px]', + }}> + + Contract + Name + Transaction + Status + + } items={items || []} emptyContent={'No data'}> + {(item) => ( + + {(columnKey) => { + if (columnKey === 'address') { + return ( + + {item.address} + + ) + } else if (columnKey === 'verify_status') { + return {item.verify_status === 'verified' ? 'Verified' : 'Unverified'} + } + return {getKeyValue(item, columnKey)} + }} + + )} + +
+
) } diff --git a/ui-react/src/components/contract/info.tsx b/ui-react/src/components/contract/info.tsx index 91f3180..e2d86d0 100644 --- a/ui-react/src/components/contract/info.tsx +++ b/ui-react/src/components/contract/info.tsx @@ -53,7 +53,7 @@ const Component: React.FC = ({ children, className, contract }) => {
Contract Source Code
- + {contract.source_code ? :
No uploaded source code is stored for this contract.
}
diff --git a/ui-react/src/pages/contract/[id].tsx b/ui-react/src/pages/contract/[id].tsx index 6655b7f..96c8988 100644 --- a/ui-react/src/pages/contract/[id].tsx +++ b/ui-react/src/pages/contract/[id].tsx @@ -82,7 +82,12 @@ export default function Page() { {contractData.verify_status === 'verified' ? ( ) : ( - +
+
+ Source code has not been uploaded for this contract. +
+ +
)} diff --git a/ui-react/src/utils/api.ts b/ui-react/src/utils/api.ts index 42dcdac..d29befd 100644 --- a/ui-react/src/utils/api.ts +++ b/ui-react/src/utils/api.ts @@ -649,6 +649,7 @@ export type getPVMContractListParams = { row?: number after?: number before?: number + verified_source?: boolean } type getPVMContractParams = {