diff --git a/api/spec/v1.yaml b/api/spec/v1.yaml index f1347b881..006dacc5c 100644 --- a/api/spec/v1.yaml +++ b/api/spec/v1.yaml @@ -1036,6 +1036,30 @@ paths: $ref: '#/components/schemas/TokenHolderList' <<: *common_error_responses + /{runtime}/evm_tokens/{address}/nfts: + get: + summary: | + Returns the list of non-fungible token (NFT) instances of an EVM (ERC-721, ...) token. + This endpoint does not verify that `address` is actually an EVM token; if it is not, it will simply return an empty list. + parameters: + - *limit + - *offset + - *runtime + - in: path + name: address + required: true + schema: + <<: *StakingAddressType + description: The staking address of the token contract for which to return the NFT instances. + responses: + '200': + description: The requested instances. + content: + application/json: + schema: + $ref: '#/components/schemas/EvmNftList' + <<: *common_error_responses + /{runtime}/accounts/{address}: get: summary: Returns a runtime account. @@ -2572,6 +2596,48 @@ components: the `/{runtime}/accounts/{address}` endpoint. example: false + EvmNftList: + allOf: + - $ref: '#/components/schemas/List' + - type: object + required: [evm_nfts] + properties: + evm_nfts: + type: array + items: + $ref: '#/components/schemas/EvmNft' + description: A list of L2 EVM NFT (ERC-721, ...) instances. + description: A list of NFT instances. + + EvmNft: + type: object + required: + - token + - contract_addr + - id + properties: + token: + $ref: '#/components/schemas/EvmToken' + id: + <<: *BigIntType + description: The instance ID of this NFT within the collection represented by `token`. + metadata_uri: + type: string + metadata_accessed: + type: string + name: + type: string + description: Identifies the asset which this NFT represents + description: + type: string + description: Describes the asset which this NFT represents + image: + type: string + description: | + A URI pointing to a resource with mime type image/* representing + the asset which this NFT represents. (Additional + non-descriptive text from ERC-721 omitted.) + AccountStats: type: object required: [total_sent, total_received, num_txns] diff --git a/api/v1/strict_server.go b/api/v1/strict_server.go index b56f848f4..1851fc72b 100644 --- a/api/v1/strict_server.go +++ b/api/v1/strict_server.go @@ -290,6 +290,14 @@ func (srv *StrictServerImpl) GetRuntimeEvmTokensAddressHolders(ctx context.Conte return apiTypes.GetRuntimeEvmTokensAddressHolders200JSONResponse(*holders), nil } +func (srv *StrictServerImpl) GetRuntimeEvmTokensAddressNfts(ctx context.Context, request apiTypes.GetRuntimeEvmTokensAddressNftsRequestObject) (apiTypes.GetRuntimeEvmTokensAddressNftsResponseObject, error) { + nfts, err := srv.dbClient.RuntimeEVMNFTs(ctx, request.Params, request.Address) + if err != nil { + return nil, err + } + return apiTypes.GetRuntimeEvmTokensAddressNfts200JSONResponse(*nfts), nil +} + func (srv *StrictServerImpl) GetRuntimeTransactions(ctx context.Context, request apiTypes.GetRuntimeTransactionsRequestObject) (apiTypes.GetRuntimeTransactionsResponseObject, error) { transactions, err := srv.dbClient.RuntimeTransactions(ctx, request.Params, nil) if err != nil { diff --git a/storage/client/client.go b/storage/client/client.go index bcdaf776b..fa2530d00 100644 --- a/storage/client/client.go +++ b/storage/client/client.go @@ -1551,6 +1551,70 @@ func (c *StorageClient) RuntimeTokenHolders(ctx context.Context, p apiTypes.GetR return &hs, nil } +func (c *StorageClient) RuntimeEVMNFTs(ctx context.Context, p apiTypes.GetRuntimeEvmTokensAddressNftsParams, address staking.Address) (*EvmNftList, error) { + res, err := c.withTotalCount( + ctx, + queries.EvmNfts, + runtimeFromCtx(ctx), + address, + p.Limit, + p.Offset, + ) + if err != nil { + return nil, wrapError(err) + } + defer res.rows.Close() + + nfts := EvmNftList{ + EvmNfts: []EvmNft{}, + TotalCount: res.totalCount, + IsTotalCountClipped: res.isTotalCountClipped, + } + for res.rows.Next() { + var nft EvmNft + var contractAddrContextIdentifier string + var contractAddrContextVersion int + var contractAddrData []byte + var tokenType sql.NullInt32 + var metadataAccessedN sql.NullTime + if err = res.rows.Scan( + &nft.Token.ContractAddr, + &contractAddrContextIdentifier, + &contractAddrContextVersion, + &contractAddrData, + &nft.Token.Name, + &nft.Token.Symbol, + &nft.Token.Decimals, + &tokenType, + &nft.Token.TotalSupply, + &nft.Token.NumTransfers, + &nft.Token.NumHolders, + &nft.Token.IsVerified, + &nft.Id, + &nft.MetadataUri, + &metadataAccessedN, + &nft.Name, + &nft.Description, + &nft.Image, + ); err != nil { + return nil, wrapError(err) + } + if contractEthAddr, err1 := EVMEthAddrFromPreimage(contractAddrContextIdentifier, contractAddrContextVersion, contractAddrData); err1 == nil { + contractECAddr := ethCommon.BytesToAddress(contractEthAddr) + nft.Token.EthContractAddr = contractECAddr.String() + } + if tokenType.Valid { + nft.Token.Type = translateTokenType(common.TokenType(tokenType.Int32)) + } + if metadataAccessedN.Valid { + nft.MetadataAccessed = common.Ptr(metadataAccessedN.Time.String()) + } + nfts.EvmNfts = append(nfts.EvmNfts, nft) + } + + return &nfts, nil +} + // RuntimeStatus returns runtime status information. func (c *StorageClient) RuntimeStatus(ctx context.Context) (*RuntimeStatus, error) { runtimeName := runtimeFromCtx(ctx) diff --git a/storage/client/queries/queries.go b/storage/client/queries/queries.go index b19f051a1..0dbbf7c65 100644 --- a/storage/client/queries/queries.go +++ b/storage/client/queries/queries.go @@ -336,10 +336,10 @@ const ( txs.error_message FROM chain.runtime_transactions AS txs LEFT JOIN chain.runtime_transaction_signers AS signer0 ON - (signer0.runtime = txs.runtime) AND - (signer0.round = txs.round) AND - (signer0.tx_index = txs.tx_index) AND - (signer0.signer_index = 0) + (signer0.runtime = txs.runtime) AND + (signer0.round = txs.round) AND + (signer0.tx_index = txs.tx_index) AND + (signer0.signer_index = 0) LEFT JOIN chain.address_preimages AS signer0_preimage ON (signer0.signer_address = signer0_preimage.address) AND -- For now, the only user is the explorer, where we only care @@ -494,6 +494,48 @@ const ( LIMIT $3::bigint OFFSET $4::bigint` + EvmNfts = ` + WITH + token_holders AS ( + SELECT token_address, COUNT(*) AS num_holders + FROM chain.evm_token_balances + WHERE (runtime = $1) AND (balance != 0) + GROUP BY token_address + ) + SELECT + chain.evm_nfts.token_address, + chain.address_preimages.context_identifier, + chain.address_preimages.context_version, + chain.address_preimages.address_data, + chain.evm_tokens.token_name, + chain.evm_tokens.symbol, + chain.evm_tokens.decimals, + chain.evm_tokens.token_type, + chain.evm_tokens.total_supply, + chain.evm_tokens.num_transfers, + COALESCE(token_holders.num_holders, 0) AS num_holders, + chain.evm_contracts.verification_info_downloaded_at IS NOT NULL AS is_verified, + chain.evm_nfts.nft_id, + chain.evm_nfts.metadata_uri, + chain.evm_nfts.metadata_accessed, + chain.evm_nfts.name, + chain.evm_nfts.description, + chain.evm_nfts.image + FROM chain.evm_nfts + LEFT JOIN chain.address_preimages ON + chain.address_preimages.address = chain.evm_nfts.token_address + LEFT JOIN chain.evm_tokens USING (runtime, token_address) + LEFT JOIN token_holders USING (token_address) + LEFT JOIN chain.evm_contracts ON + chain.evm_contracts.runtime = chain.evm_tokens.runtime AND + chain.evm_contracts.contract_address = chain.evm_tokens.token_address + WHERE + chain.evm_nfts.runtime = $1::runtime AND + chain.evm_nfts.token_address = $2::oasis_addr + ORDER BY token_address, nft_id + LIMIT $3::bigint + OFFSET $4::bigint` + AccountRuntimeSdkBalances = ` SELECT balance AS balance, @@ -516,7 +558,7 @@ const ( tokens.token_type, tokens.decimals AS token_decimals FROM chain.evm_token_balances AS balances - JOIN chain.address_preimages AS preimages ON (preimages.address = balances.token_address AND preimages.context_identifier = 'oasis-runtime-sdk/address: secp256k1eth' AND preimages.context_version = 0) + JOIN chain.address_preimages AS preimages ON (preimages.address = balances.token_address AND preimages.context_identifier = 'oasis-runtime-sdk/address: secp256k1eth' AND preimages.context_version = 0) JOIN chain.evm_tokens AS tokens USING (runtime, token_address) WHERE runtime = $1 AND balances.account_address = $2::text AND diff --git a/storage/client/types.go b/storage/client/types.go index 9aaca1a93..f40d14098 100644 --- a/storage/client/types.go +++ b/storage/client/types.go @@ -135,6 +135,10 @@ type BareTokenHolder = api.BareTokenHolder type TokenHolderList = api.TokenHolderList +type EvmNft = api.EvmNft + +type EvmNftList = api.EvmNftList + // TxVolumeList is the storage response for GetVolumes. type TxVolumeList = api.TxVolumeList diff --git a/storage/migrations/21_evm_nfts.up.sql b/storage/migrations/21_evm_nfts.up.sql index 0f056d534..f7281ef27 100644 --- a/storage/migrations/21_evm_nfts.up.sql +++ b/storage/migrations/21_evm_nfts.up.sql @@ -1,3 +1,5 @@ +BEGIN; + CREATE TABLE chain.evm_nfts ( runtime runtime NOT NULL, token_address oasis_addr NOT NULL, @@ -14,3 +16,5 @@ CREATE TABLE chain.evm_nfts ( image TEXT ); CREATE INDEX ix_evm_nfts_stale ON chain.evm_nfts (runtime, token_address, nft_id) WHERE last_download_round IS NULL OR last_want_download_round > last_download_round; + +COMMIT; diff --git a/storage/migrations/22_evm_nfts_grant.up.sql b/storage/migrations/22_evm_nfts_grant.up.sql new file mode 100644 index 000000000..3bb402ba9 --- /dev/null +++ b/storage/migrations/22_evm_nfts_grant.up.sql @@ -0,0 +1,7 @@ +BEGIN; + +-- Grant others read-only use. +GRANT SELECT ON ALL TABLES IN SCHEMA chain TO PUBLIC; +GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA chain TO PUBLIC; + +COMMIT; diff --git a/tests/e2e_regression/e2e_config_1.yml b/tests/e2e_regression/e2e_config_1.yml index b2f3adba5..4310847a9 100644 --- a/tests/e2e_regression/e2e_config_1.yml +++ b/tests/e2e_regression/e2e_config_1.yml @@ -15,6 +15,8 @@ analysis: nodes: damask: default: { rpc: unix:/tmp/node.sock } + ipfs: + gateway: https://ipfs.io fast_startup: true analyzers: consensus: diff --git a/tests/e2e_regression/e2e_config_2.yml b/tests/e2e_regression/e2e_config_2.yml index 8f4decfc6..6755cca89 100644 --- a/tests/e2e_regression/e2e_config_2.yml +++ b/tests/e2e_regression/e2e_config_2.yml @@ -9,10 +9,14 @@ analysis: nodes: damask: default: { rpc: unix:/tmp/node.sock } + ipfs: + gateway: https://ipfs.io fast_startup: true analyzers: evm_tokens_emerald: stop_on_empty_queue: true + evm_nfts_emerald: + stop_on_empty_queue: true evm_token_balances_emerald: stop_on_empty_queue: true evm_contract_code_emerald: diff --git a/tests/e2e_regression/expected/emerald_token_nfts.body b/tests/e2e_regression/expected/emerald_token_nfts.body new file mode 100644 index 000000000..cedb3180a --- /dev/null +++ b/tests/e2e_regression/expected/emerald_token_nfts.body @@ -0,0 +1,246 @@ +{ + "evm_nfts": [ + { + "description": "The first Ape NFT project on Oasis!", + "id": "227", + "image": "ipfs://QmQbbX9ZcA8Dfay94vJZrmvohub1oQfLtZ791DzD8DLmMx/227.png", + "metadata_accessed": "UNINTERESTING", + "metadata_uri": "ipfs://QmPKAuGQnJCM2FVSxkNcyqf1TyDusKuwuD9VnWJN9CF4xA/227.json", + "name": " Oasis Apes #227", + "token": { + "contract_addr": "oasis1qqewaa87rnyshyqs7yutnnpzzetejecgeu005l8u", + "decimals": 0, + "eth_contract_addr": "0x99f43f11CC6b5C378eBc2Cb4eEd7CC4F5F0006C0", + "is_verified": false, + "name": "OasisApes", + "num_holders": 6, + "num_transfers": 16, + "symbol": "OA", + "total_supply": "3333", + "type": "ERC721" + } + }, + { + "description": "The first Ape NFT project on Oasis!", + "id": "466", + "image": "ipfs://QmQbbX9ZcA8Dfay94vJZrmvohub1oQfLtZ791DzD8DLmMx/466.png", + "metadata_accessed": "UNINTERESTING", + "metadata_uri": "ipfs://QmPKAuGQnJCM2FVSxkNcyqf1TyDusKuwuD9VnWJN9CF4xA/466.json", + "name": " Oasis Apes #466", + "token": { + "contract_addr": "oasis1qqewaa87rnyshyqs7yutnnpzzetejecgeu005l8u", + "decimals": 0, + "eth_contract_addr": "0x99f43f11CC6b5C378eBc2Cb4eEd7CC4F5F0006C0", + "is_verified": false, + "name": "OasisApes", + "num_holders": 6, + "num_transfers": 16, + "symbol": "OA", + "total_supply": "3333", + "type": "ERC721" + } + }, + { + "description": "The first Ape NFT project on Oasis!", + "id": "753", + "image": "ipfs://QmQbbX9ZcA8Dfay94vJZrmvohub1oQfLtZ791DzD8DLmMx/753.png", + "metadata_accessed": "UNINTERESTING", + "metadata_uri": "ipfs://QmPKAuGQnJCM2FVSxkNcyqf1TyDusKuwuD9VnWJN9CF4xA/753.json", + "name": " Oasis Apes #753", + "token": { + "contract_addr": "oasis1qqewaa87rnyshyqs7yutnnpzzetejecgeu005l8u", + "decimals": 0, + "eth_contract_addr": "0x99f43f11CC6b5C378eBc2Cb4eEd7CC4F5F0006C0", + "is_verified": false, + "name": "OasisApes", + "num_holders": 6, + "num_transfers": 16, + "symbol": "OA", + "total_supply": "3333", + "type": "ERC721" + } + }, + { + "description": "The first Ape NFT project on Oasis!", + "id": "1087", + "image": "ipfs://QmQbbX9ZcA8Dfay94vJZrmvohub1oQfLtZ791DzD8DLmMx/1087.png", + "metadata_accessed": "UNINTERESTING", + "metadata_uri": "ipfs://QmPKAuGQnJCM2FVSxkNcyqf1TyDusKuwuD9VnWJN9CF4xA/1087.json", + "name": " Oasis Apes #1087", + "token": { + "contract_addr": "oasis1qqewaa87rnyshyqs7yutnnpzzetejecgeu005l8u", + "decimals": 0, + "eth_contract_addr": "0x99f43f11CC6b5C378eBc2Cb4eEd7CC4F5F0006C0", + "is_verified": false, + "name": "OasisApes", + "num_holders": 6, + "num_transfers": 16, + "symbol": "OA", + "total_supply": "3333", + "type": "ERC721" + } + }, + { + "description": "The first Ape NFT project on Oasis!", + "id": "1297", + "image": "ipfs://QmQbbX9ZcA8Dfay94vJZrmvohub1oQfLtZ791DzD8DLmMx/1297.png", + "metadata_accessed": "UNINTERESTING", + "metadata_uri": "ipfs://QmPKAuGQnJCM2FVSxkNcyqf1TyDusKuwuD9VnWJN9CF4xA/1297.json", + "name": " Oasis Apes #1297", + "token": { + "contract_addr": "oasis1qqewaa87rnyshyqs7yutnnpzzetejecgeu005l8u", + "decimals": 0, + "eth_contract_addr": "0x99f43f11CC6b5C378eBc2Cb4eEd7CC4F5F0006C0", + "is_verified": false, + "name": "OasisApes", + "num_holders": 6, + "num_transfers": 16, + "symbol": "OA", + "total_supply": "3333", + "type": "ERC721" + } + }, + { + "description": "The first Ape NFT project on Oasis!", + "id": "1336", + "image": "ipfs://QmQbbX9ZcA8Dfay94vJZrmvohub1oQfLtZ791DzD8DLmMx/1336.png", + "metadata_accessed": "UNINTERESTING", + "metadata_uri": "ipfs://QmPKAuGQnJCM2FVSxkNcyqf1TyDusKuwuD9VnWJN9CF4xA/1336.json", + "name": " Oasis Apes #1336", + "token": { + "contract_addr": "oasis1qqewaa87rnyshyqs7yutnnpzzetejecgeu005l8u", + "decimals": 0, + "eth_contract_addr": "0x99f43f11CC6b5C378eBc2Cb4eEd7CC4F5F0006C0", + "is_verified": false, + "name": "OasisApes", + "num_holders": 6, + "num_transfers": 16, + "symbol": "OA", + "total_supply": "3333", + "type": "ERC721" + } + }, + { + "description": "The first Ape NFT project on Oasis!", + "id": "1427", + "image": "ipfs://QmQbbX9ZcA8Dfay94vJZrmvohub1oQfLtZ791DzD8DLmMx/1427.png", + "metadata_accessed": "UNINTERESTING", + "metadata_uri": "ipfs://QmPKAuGQnJCM2FVSxkNcyqf1TyDusKuwuD9VnWJN9CF4xA/1427.json", + "name": " Oasis Apes #1427", + "token": { + "contract_addr": "oasis1qqewaa87rnyshyqs7yutnnpzzetejecgeu005l8u", + "decimals": 0, + "eth_contract_addr": "0x99f43f11CC6b5C378eBc2Cb4eEd7CC4F5F0006C0", + "is_verified": false, + "name": "OasisApes", + "num_holders": 6, + "num_transfers": 16, + "symbol": "OA", + "total_supply": "3333", + "type": "ERC721" + } + }, + { + "description": "The first Ape NFT project on Oasis!", + "id": "1602", + "image": "ipfs://QmQbbX9ZcA8Dfay94vJZrmvohub1oQfLtZ791DzD8DLmMx/1602.png", + "metadata_accessed": "UNINTERESTING", + "metadata_uri": "ipfs://QmPKAuGQnJCM2FVSxkNcyqf1TyDusKuwuD9VnWJN9CF4xA/1602.json", + "name": " Oasis Apes #1602", + "token": { + "contract_addr": "oasis1qqewaa87rnyshyqs7yutnnpzzetejecgeu005l8u", + "decimals": 0, + "eth_contract_addr": "0x99f43f11CC6b5C378eBc2Cb4eEd7CC4F5F0006C0", + "is_verified": false, + "name": "OasisApes", + "num_holders": 6, + "num_transfers": 16, + "symbol": "OA", + "total_supply": "3333", + "type": "ERC721" + } + }, + { + "description": "The first Ape NFT project on Oasis!", + "id": "1636", + "image": "ipfs://QmQbbX9ZcA8Dfay94vJZrmvohub1oQfLtZ791DzD8DLmMx/1636.png", + "metadata_accessed": "UNINTERESTING", + "metadata_uri": "ipfs://QmPKAuGQnJCM2FVSxkNcyqf1TyDusKuwuD9VnWJN9CF4xA/1636.json", + "name": " Oasis Apes #1636", + "token": { + "contract_addr": "oasis1qqewaa87rnyshyqs7yutnnpzzetejecgeu005l8u", + "decimals": 0, + "eth_contract_addr": "0x99f43f11CC6b5C378eBc2Cb4eEd7CC4F5F0006C0", + "is_verified": false, + "name": "OasisApes", + "num_holders": 6, + "num_transfers": 16, + "symbol": "OA", + "total_supply": "3333", + "type": "ERC721" + } + }, + { + "description": "The first Ape NFT project on Oasis!", + "id": "1670", + "image": "ipfs://QmQbbX9ZcA8Dfay94vJZrmvohub1oQfLtZ791DzD8DLmMx/1670.png", + "metadata_accessed": "UNINTERESTING", + "metadata_uri": "ipfs://QmPKAuGQnJCM2FVSxkNcyqf1TyDusKuwuD9VnWJN9CF4xA/1670.json", + "name": " Oasis Apes #1670", + "token": { + "contract_addr": "oasis1qqewaa87rnyshyqs7yutnnpzzetejecgeu005l8u", + "decimals": 0, + "eth_contract_addr": "0x99f43f11CC6b5C378eBc2Cb4eEd7CC4F5F0006C0", + "is_verified": false, + "name": "OasisApes", + "num_holders": 6, + "num_transfers": 16, + "symbol": "OA", + "total_supply": "3333", + "type": "ERC721" + } + }, + { + "description": "The first Ape NFT project on Oasis!", + "id": "1708", + "image": "ipfs://QmQbbX9ZcA8Dfay94vJZrmvohub1oQfLtZ791DzD8DLmMx/1708.png", + "metadata_accessed": "UNINTERESTING", + "metadata_uri": "ipfs://QmPKAuGQnJCM2FVSxkNcyqf1TyDusKuwuD9VnWJN9CF4xA/1708.json", + "name": " Oasis Apes #1708", + "token": { + "contract_addr": "oasis1qqewaa87rnyshyqs7yutnnpzzetejecgeu005l8u", + "decimals": 0, + "eth_contract_addr": "0x99f43f11CC6b5C378eBc2Cb4eEd7CC4F5F0006C0", + "is_verified": false, + "name": "OasisApes", + "num_holders": 6, + "num_transfers": 16, + "symbol": "OA", + "total_supply": "3333", + "type": "ERC721" + } + }, + { + "description": "The first Ape NFT project on Oasis!", + "id": "2546", + "image": "ipfs://QmQbbX9ZcA8Dfay94vJZrmvohub1oQfLtZ791DzD8DLmMx/2546.png", + "metadata_accessed": "UNINTERESTING", + "metadata_uri": "ipfs://QmPKAuGQnJCM2FVSxkNcyqf1TyDusKuwuD9VnWJN9CF4xA/2546.json", + "name": " Oasis Apes #2546", + "token": { + "contract_addr": "oasis1qqewaa87rnyshyqs7yutnnpzzetejecgeu005l8u", + "decimals": 0, + "eth_contract_addr": "0x99f43f11CC6b5C378eBc2Cb4eEd7CC4F5F0006C0", + "is_verified": false, + "name": "OasisApes", + "num_holders": 6, + "num_transfers": 16, + "symbol": "OA", + "total_supply": "3333", + "type": "ERC721" + } + } + ], + "is_total_count_clipped": false, + "total_count": 12 +} diff --git a/tests/e2e_regression/expected/emerald_token_nfts.headers b/tests/e2e_regression/expected/emerald_token_nfts.headers new file mode 100644 index 000000000..06ce86c1c --- /dev/null +++ b/tests/e2e_regression/expected/emerald_token_nfts.headers @@ -0,0 +1,6 @@ +HTTP/1.1 200 OK +Content-Type: application/json +Vary: Origin +Date: UNINTERESTING +Transfer-Encoding: chunked + diff --git a/tests/e2e_regression/rpc-cache/consensus/00000-1.psg.pmt b/tests/e2e_regression/rpc-cache/consensus/00000-1.psg.pmt index 14d9f8318..616fa0a9a 100644 Binary files a/tests/e2e_regression/rpc-cache/consensus/00000-1.psg.pmt and b/tests/e2e_regression/rpc-cache/consensus/00000-1.psg.pmt differ diff --git a/tests/e2e_regression/rpc-cache/consensus/db.pmt b/tests/e2e_regression/rpc-cache/consensus/db.pmt index e269c18f2..06cbe80e7 100644 Binary files a/tests/e2e_regression/rpc-cache/consensus/db.pmt and b/tests/e2e_regression/rpc-cache/consensus/db.pmt differ diff --git a/tests/e2e_regression/rpc-cache/consensus/index.pmt b/tests/e2e_regression/rpc-cache/consensus/index.pmt index d658de322..7ee22fa13 100644 Binary files a/tests/e2e_regression/rpc-cache/consensus/index.pmt and b/tests/e2e_regression/rpc-cache/consensus/index.pmt differ diff --git a/tests/e2e_regression/rpc-cache/emerald/00000-1.psg b/tests/e2e_regression/rpc-cache/emerald/00000-1.psg index 9205aeb27..0f90c0bbb 100644 Binary files a/tests/e2e_regression/rpc-cache/emerald/00000-1.psg and b/tests/e2e_regression/rpc-cache/emerald/00000-1.psg differ diff --git a/tests/e2e_regression/rpc-cache/emerald/00000-1.psg.pmt b/tests/e2e_regression/rpc-cache/emerald/00000-1.psg.pmt index 654e0fc4c..56ef2b095 100644 Binary files a/tests/e2e_regression/rpc-cache/emerald/00000-1.psg.pmt and b/tests/e2e_regression/rpc-cache/emerald/00000-1.psg.pmt differ diff --git a/tests/e2e_regression/rpc-cache/emerald/db.pmt b/tests/e2e_regression/rpc-cache/emerald/db.pmt index b273bb3e6..a93f4fd17 100644 Binary files a/tests/e2e_regression/rpc-cache/emerald/db.pmt and b/tests/e2e_regression/rpc-cache/emerald/db.pmt differ diff --git a/tests/e2e_regression/rpc-cache/emerald/index.pmt b/tests/e2e_regression/rpc-cache/emerald/index.pmt index da7f86f10..5036cd377 100644 Binary files a/tests/e2e_regression/rpc-cache/emerald/index.pmt and b/tests/e2e_regression/rpc-cache/emerald/index.pmt differ diff --git a/tests/e2e_regression/rpc-cache/emerald/main.pix b/tests/e2e_regression/rpc-cache/emerald/main.pix index 2df734ea0..490962c59 100644 Binary files a/tests/e2e_regression/rpc-cache/emerald/main.pix and b/tests/e2e_regression/rpc-cache/emerald/main.pix differ diff --git a/tests/e2e_regression/rpc-cache/emerald/overflow.pix b/tests/e2e_regression/rpc-cache/emerald/overflow.pix index 1a2df6490..55afffed2 100644 Binary files a/tests/e2e_regression/rpc-cache/emerald/overflow.pix and b/tests/e2e_regression/rpc-cache/emerald/overflow.pix differ diff --git a/tests/e2e_regression/run.sh b/tests/e2e_regression/run.sh index e3d088e2d..ea40f5efe 100755 --- a/tests/e2e_regression/run.sh +++ b/tests/e2e_regression/run.sh @@ -103,6 +103,7 @@ testCases=( 'emerald_tokens /v1/emerald/evm_tokens' 'emerald_token /v1/emerald/evm_tokens/oasis1qpgcp5jzlgk4hcenaj2x82rqk8rrve2keyuc8aaf' 'emerald_token_holders /v1/emerald/evm_tokens/oasis1qpgcp5jzlgk4hcenaj2x82rqk8rrve2keyuc8aaf/holders' + 'emerald_token_nfts /v1/emerald/evm_tokens/oasis1qqewaa87rnyshyqs7yutnnpzzetejecgeu005l8u/nfts' 'emerald_account_with_rose /v1/emerald/accounts/oasis1qrt0sv2s2x2lkt9e7kmr2mzxgme8m0pzauwztprl' 'emerald_account_with_evm_token /v1/emerald/accounts/oasis1qpwx3ptmvcceqkd4syjmqf9jmdlf90xmuuy0f6y9' 'emerald_contract_account /v1/emerald/accounts/oasis1qrrmuaed6numjju8gajzn68tn2edlvycjc50nfva' @@ -146,7 +147,11 @@ for ((i = 0; i < nCases; i++)); do curl --silent --show-error --dump-header "$outDir/$name.headers" "$url" >"$outDir/$name.body" # Try to pretty-print and normalize (for stable diffs) the output. # If `jq` fails, output was probably not JSON; leave it as-is. - jq 'if .latest_update_age_ms? then .latest_update_age_ms="UNINTERESTING" else . end' \ + jq \ + ' + (if .latest_update_age_ms? then .latest_update_age_ms="UNINTERESTING" else . end) | + ((.evm_nfts?[]? | select(.metadata_accessed) | .metadata_accessed) = "UNINTERESTING") + ' \ <"$outDir/$name.body" \ >/tmp/pretty 2>/dev/null && cp /tmp/pretty "$outDir/$name.body" || true