Skip to content

Commit

Permalink
Merge pull request #514 from oasisprotocol/pro-wh/feature/holders13
Browse files Browse the repository at this point in the history
NFT APIs
  • Loading branch information
pro-wh authored Oct 11, 2023
2 parents 5881904 + c8e416e commit 59e9f1f
Show file tree
Hide file tree
Showing 21 changed files with 464 additions and 6 deletions.
66 changes: 66 additions & 0 deletions api/spec/v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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]
Expand Down
8 changes: 8 additions & 0 deletions api/v1/strict_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
64 changes: 64 additions & 0 deletions storage/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
52 changes: 47 additions & 5 deletions storage/client/queries/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions storage/client/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions storage/migrations/21_evm_nfts.up.sql
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
BEGIN;

CREATE TABLE chain.evm_nfts (
runtime runtime NOT NULL,
token_address oasis_addr NOT NULL,
Expand All @@ -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;
7 changes: 7 additions & 0 deletions storage/migrations/22_evm_nfts_grant.up.sql
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 2 additions & 0 deletions tests/e2e_regression/e2e_config_1.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ analysis:
nodes:
damask:
default: { rpc: unix:/tmp/node.sock }
ipfs:
gateway: https://ipfs.io
fast_startup: true
analyzers:
consensus:
Expand Down
4 changes: 4 additions & 0 deletions tests/e2e_regression/e2e_config_2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading

0 comments on commit 59e9f1f

Please sign in to comment.