diff --git a/docs/api/tokens/get-non-fungible-token-holdings.example.schema.json b/docs/api/tokens/get-non-fungible-token-holdings.example.schema.json new file mode 100644 index 00000000..2d2b51d2 --- /dev/null +++ b/docs/api/tokens/get-non-fungible-token-holdings.example.schema.json @@ -0,0 +1,31 @@ +{ + "limit": 50, + "offset": 0, + "total": 3, + "results": [ + { + "asset_identifier": "SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173.the-explorer-guild::The-Explorer-Guild", + "value": { + "hex": "0x0100000000000000000000000000000803", + "repr": "u2051" + }, + "tx_id": "0x12e6f88724d0e630de26c376f172cf1781e25435e2b4ee54d36a949b244e429c" + }, + { + "asset_identifier": "SP2BE8TZATXEVPGZ8HAFZYE5GKZ02X0YDKAN7ZTGW.arties::arties", + "value": { + "hex": "0x01000000000000000000000000000004f3", + "repr": "u1267" + }, + "tx_id": "0xddc464c5e20a78dc5ac351913e0d9b9cce76fc955cc4599e0cccad960998f130" + }, + { + "asset_identifier": "SP000000000000000000002Q6VF78.bns::names", + "value": { + "hex": "0x0c00000002046e616d65020000000672616661656c096e616d6573706163650200000003627463", + "repr": "(tuple (name 0x72616661656c) (namespace 0x627463))" + }, + "tx_id": "0x0153a41ed24a0e1d32f66ea98338df09f942571ca66359e28bdca79ccd0305cf" + } + ] +} diff --git a/docs/api/tokens/get-non-fungible-token-holdings.schema.json b/docs/api/tokens/get-non-fungible-token-holdings.schema.json new file mode 100644 index 00000000..34d82e5e --- /dev/null +++ b/docs/api/tokens/get-non-fungible-token-holdings.schema.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "List of Non-Fungible Token holdings", + "title": "NonFungibleTokenHoldingsList", + "type": "object", + "required": [ + "results", + "limit", + "offset", + "total" + ], + "properties": { + "limit": { + "type": "integer", + "maximum": 200, + "description": "The number of Non-Fungible Token holdings to return" + }, + "offset": { + "type": "integer", + "description": "The number to Non-Fungible Token holdings to skip (starting at `0`)" + }, + "total": { + "type": "integer", + "description": "The number of Non-Fungible Token holdings available" + }, + "results": { + "type": "array", + "items": { + "$ref": "../../entities/tokens/non-fungible-token-holding.schema.json" + } + } + } +} diff --git a/docs/entities/tokens/non-fungible-token-holding-0.example.schema.json b/docs/entities/tokens/non-fungible-token-holding-0.example.schema.json new file mode 100644 index 00000000..77facc53 --- /dev/null +++ b/docs/entities/tokens/non-fungible-token-holding-0.example.schema.json @@ -0,0 +1,8 @@ +{ + "asset_identifier": "SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173.the-explorer-guild::The-Explorer-Guild", + "value": { + "hex": "0x0100000000000000000000000000000803", + "repr": "u2051" + }, + "tx_id": "0x12e6f88724d0e630de26c376f172cf1781e25435e2b4ee54d36a949b244e429c" +} diff --git a/docs/entities/tokens/non-fungible-token-holding-0.schema.json b/docs/entities/tokens/non-fungible-token-holding-0.schema.json new file mode 100644 index 00000000..aa84b34c --- /dev/null +++ b/docs/entities/tokens/non-fungible-token-holding-0.schema.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "NonFungibleTokenHolding", + "description": "Ownership of a Non-Fungible Token", + "required": ["asset_identifier", "value", "tx_id"], + "additionalProperties": false, + "properties": { + "asset_identifier": { + "type": "string" + }, + "value": { + "type": "object", + "required": ["hex", "repr"], + "description": "Non-Fungible Token value", + "additionalProperties": false, + "properties": { + "hex": { + "type": "string", + "description": "Hex string representing the identifier of the Non-Fungible Token" + }, + "repr": { + "type": "string", + "description": "Readable string of the Non-Fungible Token identifier" + } + } + }, + "tx_id": { + "type": "string" + } + } +} diff --git a/docs/entities/tokens/non-fungible-token-holding-1-tx-metadata.example.schema.json b/docs/entities/tokens/non-fungible-token-holding-1-tx-metadata.example.schema.json new file mode 100644 index 00000000..b5c403ac --- /dev/null +++ b/docs/entities/tokens/non-fungible-token-holding-1-tx-metadata.example.schema.json @@ -0,0 +1,58 @@ +{ + "asset_identifier": "SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173.the-explorer-guild::The-Explorer-Guild", + "value": { + "hex": "0x0100000000000000000000000000000803", + "repr": "u2051" + }, + "tx": { + "tx_id": "0x12e6f88724d0e630de26c376f172cf1781e25435e2b4ee54d36a949b244e429c", + "nonce": 7, + "fee_rate": "812000", + "sender_address": "SP3BK1NNSWN719Z6KDW05RBGVS940YCN6X84STYPR", + "sponsored": false, + "post_condition_mode": "deny", + "post_conditions": [ + { + "type": "stx", + "condition_code": "sent_equal_to", + "amount": "100000000", + "principal": { + "type_id": "principal_standard", + "address": "SP3BK1NNSWN719Z6KDW05RBGVS940YCN6X84STYPR" + } + } + ], + "anchor_mode": "any", + "is_unanchored": false, + "block_hash": "0x8a809916e35577337c3f9812a57d61666fde63932b1bdd1c4d1e2f24fb72d46c", + "parent_block_hash": "0xacf8dd56e08bd7986a5bae5e0f13d8dca8cb3a2dc5a1c4d739fda3b3b378b01d", + "block_height": 36442, + "burn_block_time": 1635994340, + "burn_block_time_iso": "2021-11-04T02:52:20.000Z", + "parent_burn_block_time": 1635994054, + "parent_burn_block_time_iso": "2021-11-04T02:47:34.000Z", + "canonical": true, + "tx_index": 4, + "tx_status": "success", + "tx_result": { + "hex": "0x070100000000000000000000000000000803", + "repr": "(ok u2051)" + }, + "microblock_hash": "", + "microblock_sequence": 2147483647, + "microblock_canonical": true, + "event_count": 2, + "events": [], + "execution_cost_read_count": 14, + "execution_cost_read_length": 91190, + "execution_cost_runtime": 92297000, + "execution_cost_write_count": 3, + "execution_cost_write_length": 19, + "tx_type": "contract_call", + "contract_call": { + "contract_id": "SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173.the-explorer-guild-mint", + "function_name": "claim", + "function_signature": "(define-public (claim ))" + } + } +} diff --git a/docs/entities/tokens/non-fungible-token-holding-1-tx-metadata.schema.json b/docs/entities/tokens/non-fungible-token-holding-1-tx-metadata.schema.json new file mode 100644 index 00000000..f2f67aeb --- /dev/null +++ b/docs/entities/tokens/non-fungible-token-holding-1-tx-metadata.schema.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "NonFungibleTokenHolding", + "description": "Ownership of a Non-Fungible Token with transaction metadata", + "required": ["asset_identifier", "value", "tx"], + "additionalProperties": false, + "properties": { + "asset_identifier": { + "type": "string" + }, + "value": { + "type": "object", + "required": ["hex", "repr"], + "description": "Non-Fungible Token value", + "additionalProperties": false, + "properties": { + "hex": { + "type": "string", + "description": "Hex string representing the identifier of the Non-Fungible Token" + }, + "repr": { + "type": "string", + "description": "Readable string of the Non-Fungible Token identifier" + } + } + }, + "tx": { + "$ref": "../entities/transactions/transaction.schema.json" + } + } +} diff --git a/docs/entities/tokens/non-fungible-token-holding.schema.json b/docs/entities/tokens/non-fungible-token-holding.schema.json new file mode 100644 index 00000000..fca0eea1 --- /dev/null +++ b/docs/entities/tokens/non-fungible-token-holding.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Non-Fungible Token holding", + "description": "Describes the ownership of a Non-Fungible Token", + "anyOf": [ + { + "$ref": "./non-fungible-token-holding-0.schema.json" + }, + { + "$ref": "./non-fungible-token-holding-1-tx-metadata.schema.json" + } + ] +} diff --git a/docs/openapi.yaml b/docs/openapi.yaml index a87edd31..ca71f336 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1525,8 +1525,8 @@ paths: summary: Get nft events deprecated: true description: | - Retrieves a list of all nfts owned by an address, contains the clarity value of the identifier of the nft. This endpoint is deprecated in favor of `/extended/v1/tokens/nft/balance`. + Retrieves a list of all nfts owned by an address, contains the clarity value of the identifier of the nft. tags: - Accounts operationId: get_account_nft @@ -2853,6 +2853,57 @@ paths: example: $ref: ./api/tokens/get-fungible-tokens-metadata-list.example.schema.json + /extended/v1/tokens/nft/holdings: + get: + operationId: get_nft_holdings + summary: Non-Fungible Token holdings + description: | + Retrieves the list of Non-Fungible Tokens owned by the given principal (STX address or Smart Contract ID). + Results can be filtered by asset identifier and can include metadata about the transaction that made the principal own each token. + More information on Non-Fungible Tokens on the Stacks blockchain can be found [here](https://docs.stacks.co/write-smart-contracts/tokens#non-fungible-tokens-nfts). + tags: + - Non-Fungible Tokens + parameters: + - name: principal + in: query + description: STX address or smart contract id that owns the tokens + required: true + schema: + type: string + - name: limit + in: query + description: max number of tokens to fetch + required: false + schema: + type: integer + - name: offset + in: query + description: index of first tokens to fetch + required: false + schema: + type: integer + - name: unanchored + in: query + description: whether or not to include tokens from unconfirmed transactions + required: false + schema: + type: boolean + - name: tx_metadata + in: query + description: whether or not to include complete transaction metadata. Enabling this option can affect endpoint response times. + required: false + schema: + type: boolean + responses: + 200: + description: List of Non-Fungible Token holdings + content: + application/json: + schema: + $ref: ./api/tokens/get-non-fungible-token-holdings.schema.json + example: + $ref: ./api/tokens/get-non-fungible-token-holdings.example.schema.json + /extended/v1/tokens/nft/metadata: get: operationId: get_nft_metadata_list diff --git a/src/api/routes/tokens/tokens.ts b/src/api/routes/tokens/tokens.ts index b44dd946..8c39bb2e 100644 --- a/src/api/routes/tokens/tokens.ts +++ b/src/api/routes/tokens/tokens.ts @@ -43,7 +43,6 @@ export function createTokenRouter(db: DataStore): express.Router { res.status(400).json({ error: `Invalid asset_identifier` }); return; } - const limit = parseTokenQueryLimit(req.query.limit ?? 50); const offset = parsePagingQueryInput(req.query.offset ?? 0); const includeUnanchored = isUnanchoredRequest(req, res, next); @@ -80,7 +79,6 @@ export function createTokenRouter(db: DataStore): express.Router { total: total, results: parsedResults, }; - res.status(200).json(response); }) ); diff --git a/src/datastore/memory-store.ts b/src/datastore/memory-store.ts index 1fcaf0b3..83f47b83 100644 --- a/src/datastore/memory-store.ts +++ b/src/datastore/memory-store.ts @@ -640,6 +640,7 @@ export class MemoryDataStore getNftHoldings(args: { principal: string; + assetIdentifier?: string; limit: number; offset: number; includeUnanchored: boolean;