diff --git a/.changelog/828.feature.md b/.changelog/828.feature.md new file mode 100644 index 000000000..4a8b7f391 --- /dev/null +++ b/.changelog/828.feature.md @@ -0,0 +1,4 @@ +Add `sort_by` parameter to `/{runtime}/evm_tokens` endpoint. + +The parameter can be used to configure if results should be sorted by number +of total holders, or by the calculated market cap. diff --git a/api/spec/v1.yaml b/api/spec/v1.yaml index a0be00dc9..dcc164540 100644 --- a/api/spec/v1.yaml +++ b/api/spec/v1.yaml @@ -1082,6 +1082,14 @@ paths: schema: type: string description: A filter on the name, the name or symbol must contain this value as a substring. + - in: query + name: sort_by + schema: + type: string + enum: [total_holders, market_cap] + description: | + The field to sort the tokens by. + If unset, the tokens will be sorted by number of holders. responses: '200': description: The requested tokens. diff --git a/storage/client/client.go b/storage/client/client.go index 3e9017953..008c65149 100644 --- a/storage/client/client.go +++ b/storage/client/client.go @@ -2005,6 +2005,7 @@ func (c *StorageClient) RuntimeTokens(ctx context.Context, p apiTypes.GetRuntime p.Name, refSwapFactoryAddr, refSwapTokenAddr, + p.SortBy, p.Limit, p.Offset, ) diff --git a/storage/client/queries/queries.go b/storage/client/queries/queries.go index 5acb58c56..f6fb8e7ed 100644 --- a/storage/client/queries/queries.go +++ b/storage/client/queries/queries.go @@ -704,37 +704,40 @@ const ( tokens.token_type IS NOT NULL AND -- exclude token _candidates_ that we haven't inspected yet tokens.token_type != 0 -- exclude unknown-type tokens; they're often just contracts that emitted Transfer events but don't expose the token ticker, name, balance etc. ORDER BY - ( - CASE - -- For the reference token itself, it is 1:1 in value with, you know, itself. - WHEN - tokens.token_address = $5 - THEN 1.0 - -- The pool keeps a proportion of reserves so that reserve0 of token0 is worth about as much as reserve1 of token1. - -- When token0 is the reference token, more reserve0 means token1 is worth more than the reference token. - WHEN - ref_swap_pair_creations.token0_address = $5 AND - ref_swap_pairs.reserve0 IS NOT NULL AND - ref_swap_pairs.reserve0 > 0 AND - ref_swap_pairs.reserve1 IS NOT NULL AND - ref_swap_pairs.reserve1 > 0 - THEN ref_swap_pairs.reserve0::REAL / ref_swap_pairs.reserve1::REAL - -- When token1 is the reference token, more reserve1 means token0 is worth more than the reference token. - WHEN - ref_swap_pair_creations.token1_address = $5 AND - ref_swap_pairs.reserve0 IS NOT NULL AND - ref_swap_pairs.reserve0 > 0 AND - ref_swap_pairs.reserve1 IS NOT NULL AND - ref_swap_pairs.reserve1 > 0 - THEN ref_swap_pairs.reserve1::REAL / ref_swap_pairs.reserve0::REAL - ELSE 0.0 - END * - COALESCE(tokens.total_supply, 0) - ) DESC, + CASE + -- If sort_by is not "market_cap" then we sort by num_holders (below). + WHEN $6::text IS NULL OR $6::text != 'market_cap' THEN NULL + ELSE + -- Otherwise, sort by market cap. + ( + CASE + -- For the reference token itself, it is 1:1 in value with, you know, itself. + WHEN tokens.token_address = $5 THEN 1.0 + -- The pool keeps a proportion of reserves so that reserve0 of token0 is worth about as much as reserve1 of token1. + -- When token0 is the reference token, more reserve0 means token1 is worth more than the reference token. + WHEN + ref_swap_pair_creations.token0_address = $5 AND + ref_swap_pairs.reserve0 IS NOT NULL AND + ref_swap_pairs.reserve0 > 0 AND + ref_swap_pairs.reserve1 IS NOT NULL AND + ref_swap_pairs.reserve1 > 0 + THEN ref_swap_pairs.reserve0::REAL / ref_swap_pairs.reserve1::REAL + -- When token1 is the reference token, more reserve1 means token0 is worth more than the reference token. + WHEN + ref_swap_pair_creations.token1_address = $5 AND + ref_swap_pairs.reserve0 IS NOT NULL AND + ref_swap_pairs.reserve0 > 0 AND + ref_swap_pairs.reserve1 IS NOT NULL AND + ref_swap_pairs.reserve1 > 0 + THEN ref_swap_pairs.reserve1::REAL / ref_swap_pairs.reserve0::REAL + ELSE 0.0 + END * COALESCE(tokens.total_supply, 0) + ) + END DESC, num_holders DESC, contract_addr - LIMIT $6::bigint - OFFSET $7::bigint` + LIMIT $7::bigint + OFFSET $8::bigint` //nolint:gosec // Linter suspects a hardcoded credentials token. EvmTokenHolders = ` diff --git a/tests/e2e_regression/common_test_cases.sh b/tests/e2e_regression/common_test_cases.sh index 0521c3fc9..ed74aecc5 100644 --- a/tests/e2e_regression/common_test_cases.sh +++ b/tests/e2e_regression/common_test_cases.sh @@ -67,6 +67,7 @@ commonTestCases=( 'emerald_events /v1/emerald/events' 'emerald_events_by_type /v1/emerald/events?type=accounts.transfer' 'emerald_tokens /v1/emerald/evm_tokens' + 'emerald_tokens_sort_market_cap /v1/emerald/evm_tokens?sort_by=market_cap' 'emerald_status /v1/emerald/status' 'emerald_tx_volume /v1/emerald/stats/tx_volume' 'emerald_contract_account /v1/emerald/accounts/oasis1qz2rynvcmrkwd57v00298uc2vtzgatde3cjpy72f' diff --git a/tests/e2e_regression/damask/expected/emerald_tokens_sort_market_cap.body b/tests/e2e_regression/damask/expected/emerald_tokens_sort_market_cap.body new file mode 100644 index 000000000..c059de7a6 --- /dev/null +++ b/tests/e2e_regression/damask/expected/emerald_tokens_sort_market_cap.body @@ -0,0 +1,5 @@ +{ + "evm_tokens": [], + "is_total_count_clipped": false, + "total_count": 0 +} diff --git a/tests/e2e_regression/damask/expected/emerald_tokens_sort_market_cap.headers b/tests/e2e_regression/damask/expected/emerald_tokens_sort_market_cap.headers new file mode 100644 index 000000000..1cce01c2d --- /dev/null +++ b/tests/e2e_regression/damask/expected/emerald_tokens_sort_market_cap.headers @@ -0,0 +1,6 @@ +HTTP/1.1 200 OK +Content-Type: application/json +Vary: Origin +Date: UNINTERESTING +Content-Length: UNINTERESTING + diff --git a/tests/e2e_regression/eden/expected/emerald_tokens_sort_market_cap.body b/tests/e2e_regression/eden/expected/emerald_tokens_sort_market_cap.body new file mode 100644 index 000000000..63b47f137 --- /dev/null +++ b/tests/e2e_regression/eden/expected/emerald_tokens_sort_market_cap.body @@ -0,0 +1,167 @@ +{ + "evm_tokens": [ + { + "contract_addr": "oasis1qqz8706pmf38wmptl6dkcaec8yykw0rvfv7ql6fc", + "decimals": 18, + "eth_contract_addr": "0xf02b3e437304892105992512539F769423a515Cb", + "is_verified": true, + "name": "YUZUToken", + "num_holders": 15, + "num_transfers": 108, + "symbol": "YUZU", + "total_supply": "324996573618707482993195360", + "type": "ERC20", + "verification_level": "partial" + }, + { + "contract_addr": "oasis1qpgcp5jzlgk4hcenaj2x82rqk8rrve2keyuc8aaf", + "decimals": 18, + "eth_contract_addr": "0x21C718C22D52d0F3a789b752D4c2fD5908a8A733", + "is_verified": true, + "name": "Wrapped ROSE", + "num_holders": 14, + "num_transfers": 48, + "symbol": "wROSE", + "total_supply": "11110528749907675559457292", + "type": "ERC20", + "verification_level": "partial" + }, + { + "contract_addr": "oasis1qp2hssandc7dekjdr6ygmtzt783k3gn38uupdeys", + "decimals": 6, + "eth_contract_addr": "0xdC19A122e268128B5eE20366299fc7b5b199C8e3", + "is_verified": false, + "name": "Tether USD", + "num_holders": 9, + "num_transfers": 33, + "symbol": "USDT", + "total_supply": "690015441212", + "type": "ERC20" + }, + { + "contract_addr": "oasis1qzj2hwfs3yjm20jz5h70yk3etsst0k8s3cnl6l08", + "decimals": 18, + "eth_contract_addr": "0x941494A56164eA04d79f9867ddDB0Dd754A625cC", + "is_verified": true, + "name": "YuzuSwap LP Token", + "num_holders": 4, + "num_transfers": 2, + "symbol": "LPT", + "total_supply": "5515821744343857173275622", + "type": "ERC20", + "verification_level": "partial" + }, + { + "contract_addr": "oasis1qpuqs65me58n6mt05ezd7cnttx7k57w28v36l4ca", + "decimals": 6, + "eth_contract_addr": "0x81ECac0D6Be0550A00FF064a4f9dd2400585FE9c", + "is_verified": false, + "name": "USD Coin (Celer)", + "num_holders": 3, + "num_transfers": 6, + "symbol": "ceUSDC", + "total_supply": "160671058631", + "type": "ERC20" + }, + { + "contract_addr": "oasis1qqehgt8jfnczmku5hazxkkprw5nqqk4nvunx9zf4", + "decimals": 18, + "eth_contract_addr": "0x28c9D3e689B5d3629aFC2D69ef6a2799578574e0", + "is_verified": true, + "name": "YuzuSwap LP Token", + "num_holders": 2, + "num_transfers": 1, + "symbol": "LPT", + "total_supply": "8543198732792740426556", + "type": "ERC20", + "verification_level": "partial" + }, + { + "contract_addr": "oasis1qzel2watfs6g3cm765dwlukymjs5k3rnhvua4z78", + "decimals": 18, + "eth_contract_addr": "0x3223f17957Ba502cbe71401D55A0DB26E5F7c68F", + "is_verified": false, + "name": "Wrapped Ether", + "num_holders": 2, + "num_transfers": 2, + "symbol": "WETH", + "total_supply": "153212215450000000000", + "type": "ERC20" + }, + { + "contract_addr": "oasis1qpgvjkelzwd0yucjqyfpfzalrp3x74se7y922a7s", + "decimals": 18, + "eth_contract_addr": "0x485E6D145E958347EFDe7A43538879DCcD868f40", + "is_verified": false, + "name": "YuzuParkExtDummyToken", + "num_holders": 1, + "num_transfers": 2, + "symbol": "YPEDT", + "total_supply": "10000", + "type": "ERC20" + }, + { + "contract_addr": "oasis1qqjv74rz8e6v9q6yn7y76zvf0yv5299ejsaxnjqu", + "decimals": 18, + "eth_contract_addr": "0xf5493ea940d12cE8594f81BaB2bB7d4ed81d49e8", + "is_verified": false, + "name": "xYUZU", + "num_holders": 1, + "num_transfers": 1, + "symbol": "xYUZU", + "total_supply": "72707317251766251839614070", + "type": "ERC20" + }, + { + "contract_addr": "oasis1qrsl7tgujttvscfe2k268s7q2s7kdu9tyv9qv2tx", + "decimals": 8, + "eth_contract_addr": "0x5D9ab5522c64E1F6ef5e3627ECCc093f56167818", + "is_verified": false, + "name": "Wrapped BTC", + "num_holders": 1, + "num_transfers": 2, + "symbol": "WBTC", + "total_supply": "175579512", + "type": "ERC20" + }, + { + "contract_addr": "oasis1qz29t7nxkwfqgfk36uqqs9pzuzdt8zmrjud5mehx", + "decimals": 0, + "eth_contract_addr": "0x1108A83b867c8b720fEa7261AE7A64DAB17B4159", + "is_verified": false, + "name": "MyNFT", + "num_holders": 1, + "num_transfers": 2, + "symbol": "MNFT", + "total_supply": "2", + "type": "ERC721" + }, + { + "contract_addr": "oasis1qzpxtzc3xphccjq94wxffchfc5sua5t24vc6gvt6", + "decimals": 18, + "eth_contract_addr": "0x5C78A65AD6D0eC6618788b6E8e211F31729111Ca", + "is_verified": true, + "name": "Wrapped ROSE", + "num_holders": 1, + "num_transfers": 1, + "symbol": "WROSE", + "total_supply": "370854109276538025377920", + "type": "ERC20", + "verification_level": "partial" + }, + { + "contract_addr": "oasis1qzxx6htc6cceayd492mqt6lqc8l3eda29cnscx67", + "decimals": 18, + "eth_contract_addr": "0x0487F746B01A663108E7c4E5739F282491a45799", + "is_verified": false, + "name": "YuzuSwap LP Token", + "num_holders": 1, + "num_transfers": 4, + "symbol": "LPT", + "total_supply": "3454145239972177", + "type": "ERC20" + } + ], + "is_total_count_clipped": false, + "total_count": 13 +} diff --git a/tests/e2e_regression/eden/expected/emerald_tokens_sort_market_cap.headers b/tests/e2e_regression/eden/expected/emerald_tokens_sort_market_cap.headers new file mode 100644 index 000000000..06ce86c1c --- /dev/null +++ b/tests/e2e_regression/eden/expected/emerald_tokens_sort_market_cap.headers @@ -0,0 +1,6 @@ +HTTP/1.1 200 OK +Content-Type: application/json +Vary: Origin +Date: UNINTERESTING +Transfer-Encoding: chunked +