mirror of
https://github.com/alexgo-io/stacks-blockchain-api.git
synced 2026-05-13 20:16:45 +08:00
feat: include entity metadata in search endpoint responses #651
This commit is contained in:
@@ -24,6 +24,14 @@
|
||||
"entity_type": {
|
||||
"type": "string",
|
||||
"enum": ["standard_address"]
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "../address/get-address-stx-balance.schema.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,15 @@
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "../../entities/blocks/block.schema.json"
|
||||
}
|
||||
],
|
||||
"additionalItems": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,18 @@
|
||||
"description": "Corresponding tx_id for smart_contract"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "../../entities/mempool-transactions/transaction.schema.json"
|
||||
},
|
||||
{
|
||||
"$ref": "../../entities/transactions/transaction.schema.json"
|
||||
}
|
||||
],
|
||||
"additionalItems": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,15 @@
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "../../entities/mempool-transactions/transaction.schema.json"
|
||||
}
|
||||
],
|
||||
"additionalItems": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,15 @@
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "../../entities/transactions/transaction.schema.json"
|
||||
}
|
||||
],
|
||||
"additionalItems": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5
docs/generated.d.ts
vendored
5
docs/generated.d.ts
vendored
@@ -2885,6 +2885,7 @@ export interface AddressSearchResult {
|
||||
*/
|
||||
entity_id: string;
|
||||
entity_type: "standard_address";
|
||||
metadata?: AddressStxBalanceResponse;
|
||||
};
|
||||
}
|
||||
/**
|
||||
@@ -2920,6 +2921,7 @@ export interface BlockSearchResult {
|
||||
burn_block_time: number;
|
||||
height: number;
|
||||
};
|
||||
metadata?: Block;
|
||||
};
|
||||
}
|
||||
/**
|
||||
@@ -2959,6 +2961,7 @@ export interface ContractSearchResult {
|
||||
*/
|
||||
tx_id?: string;
|
||||
};
|
||||
metadata?: MempoolTransaction | Transaction;
|
||||
};
|
||||
}
|
||||
/**
|
||||
@@ -3000,6 +3003,7 @@ export interface MempoolTxSearchResult {
|
||||
tx_data: {
|
||||
tx_type: string;
|
||||
};
|
||||
metadata?: MempoolTransaction;
|
||||
};
|
||||
}
|
||||
/**
|
||||
@@ -3035,6 +3039,7 @@ export interface TxSearchResult {
|
||||
block_height: number;
|
||||
tx_type: string;
|
||||
};
|
||||
metadata?: Transaction;
|
||||
};
|
||||
}
|
||||
/**
|
||||
|
||||
@@ -1677,6 +1677,11 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
description: The hex hash string for a block or transaction, account address, or contract address
|
||||
- in: query
|
||||
name: include_metadata
|
||||
schema:
|
||||
type: boolean
|
||||
description: This includes the detailed data for purticular hash in the response
|
||||
operationId: search_by_id
|
||||
responses:
|
||||
200:
|
||||
|
||||
@@ -57,6 +57,7 @@ import {
|
||||
DbTxStatus,
|
||||
DbTxTypeId,
|
||||
DbSmartContract,
|
||||
DbSearchResultWithMetadata,
|
||||
} from '../../datastore/common';
|
||||
import {
|
||||
unwrapOptional,
|
||||
@@ -1122,3 +1123,62 @@ export async function searchTx(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function searchHashWithMetadata(
|
||||
hash: string,
|
||||
db: DataStore
|
||||
): Promise<FoundOrNot<DbSearchResultWithMetadata>> {
|
||||
// checking for tx
|
||||
const txQuery = await db.getTxListDetails({ txIds: [hash], includeUnanchored: true });
|
||||
if (txQuery.length > 0) {
|
||||
// tx found
|
||||
const tx = txQuery[0];
|
||||
return {
|
||||
found: true,
|
||||
result: {
|
||||
entity_type: 'tx_id',
|
||||
entity_id: tx.tx_id,
|
||||
entity_data: tx,
|
||||
},
|
||||
};
|
||||
}
|
||||
// checking for mempool tx
|
||||
const mempoolTxQuery = await db.getMempoolTxs({
|
||||
txIds: [hash],
|
||||
includeUnanchored: true,
|
||||
includePruned: true,
|
||||
});
|
||||
if (mempoolTxQuery.length > 0) {
|
||||
// mempool tx found
|
||||
const mempoolTx = mempoolTxQuery[0];
|
||||
return {
|
||||
found: true,
|
||||
result: {
|
||||
entity_type: 'mempool_tx_id',
|
||||
entity_id: mempoolTx.tx_id,
|
||||
entity_data: mempoolTx,
|
||||
},
|
||||
};
|
||||
}
|
||||
// checking for block
|
||||
const blockQuery = await db.getBlockWithMetadata({ hash }, { txs: true, microblocks: true });
|
||||
if (blockQuery.found) {
|
||||
// block found
|
||||
const result = parseDbBlock(
|
||||
blockQuery.result.block,
|
||||
blockQuery.result.txs.map(tx => tx.tx_id),
|
||||
blockQuery.result.microblocks.accepted.map(mb => mb.microblock_hash),
|
||||
blockQuery.result.microblocks.streamed.map(mb => mb.microblock_hash)
|
||||
);
|
||||
return {
|
||||
found: true,
|
||||
result: {
|
||||
entity_type: 'block_hash',
|
||||
entity_id: result.hash,
|
||||
entity_data: result,
|
||||
},
|
||||
};
|
||||
}
|
||||
// found nothing
|
||||
return { found: false };
|
||||
}
|
||||
|
||||
@@ -9,17 +9,12 @@ function handleBadRequest(res: Response, next: NextFunction, errorMessage: strin
|
||||
throw error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the query parameters of a request are intended to include unanchored tx data.
|
||||
* If an error is encountered while parsing the query param then a 400 response with an error message
|
||||
* is sent and the function returns `void`.
|
||||
*/
|
||||
export function isUnanchoredRequest(
|
||||
export function booleanValueForParam(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
next: NextFunction,
|
||||
paramName: string
|
||||
): boolean | never {
|
||||
const paramName = 'unanchored';
|
||||
if (!(paramName in req.query)) {
|
||||
return false;
|
||||
}
|
||||
@@ -31,7 +26,7 @@ export function isUnanchoredRequest(
|
||||
case '1':
|
||||
case 'yes':
|
||||
case 'on':
|
||||
// If specified without a value, e.g. `?unanchored&thing=1` then treat it as true
|
||||
// If specified without a value, e.g. `?paramName` then treat it as true
|
||||
case '':
|
||||
return true;
|
||||
case 'false':
|
||||
@@ -48,6 +43,20 @@ export function isUnanchoredRequest(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the query parameters of a request are intended to include unanchored tx data.
|
||||
* If an error is encountered while parsing the query param then a 400 response with an error message
|
||||
* is sent and the function returns `void`.
|
||||
*/
|
||||
export function isUnanchoredRequest(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): boolean | never {
|
||||
const paramName = 'unanchored';
|
||||
return booleanValueForParam(req, res, next, paramName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the query parameters of a request are intended to include data for a specific block height,
|
||||
* or if the request intended to include unanchored tx data. If neither a block height parameter or an unanchored
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import * as express from 'express';
|
||||
import { addAsync, RouterWithAsync } from '@awaitjs/express';
|
||||
import { DataStore, DbBlock, DbTx, DbMempoolTx } from '../../datastore/common';
|
||||
import { isValidPrincipal, has0xPrefix } from '../../helpers';
|
||||
import {
|
||||
DataStore,
|
||||
DbBlock,
|
||||
DbTx,
|
||||
DbMempoolTx,
|
||||
DbSearchResult,
|
||||
DbSearchResultWithMetadata,
|
||||
} from '../../datastore/common';
|
||||
import { isValidPrincipal, has0xPrefix, FoundOrNot } from '../../helpers';
|
||||
import {
|
||||
Transaction,
|
||||
Block,
|
||||
@@ -12,9 +19,16 @@ import {
|
||||
ContractSearchResult,
|
||||
AddressSearchResult,
|
||||
SearchErrorResult,
|
||||
AddressStxBalanceResponse,
|
||||
} from '@stacks/stacks-blockchain-api-types';
|
||||
import { getTxTypeString } from '../controllers/db-controller';
|
||||
import {
|
||||
getTxTypeString,
|
||||
parseDbMempoolTx,
|
||||
parseDbTx,
|
||||
searchHashWithMetadata,
|
||||
} from '../controllers/db-controller';
|
||||
import { address } from 'bitcoinjs-lib';
|
||||
import { booleanValueForParam } from '../query-helpers';
|
||||
|
||||
const enum SearchResultType {
|
||||
TxId = 'tx_id',
|
||||
@@ -29,7 +43,7 @@ const enum SearchResultType {
|
||||
export function createSearchRouter(db: DataStore): RouterWithAsync {
|
||||
const router = addAsync(express.Router());
|
||||
|
||||
const performSearch = async (term: string): Promise<SearchResult> => {
|
||||
const performSearch = async (term: string, includeMetadata: boolean): Promise<SearchResult> => {
|
||||
// Check if term is a 32-byte hash, e.g.:
|
||||
// `0x4ac9b89ec7f2a0ca3b4399888904f171d7bdf3460b1c63ea86c28a83c2feaad8`
|
||||
// `4ac9b89ec7f2a0ca3b4399888904f171d7bdf3460b1c63ea86c28a83c2feaad8`
|
||||
@@ -41,9 +55,35 @@ export function createSearchRouter(db: DataStore): RouterWithAsync {
|
||||
}
|
||||
if (hashBuffer !== undefined && hashBuffer.length === 32) {
|
||||
const hash = '0x' + hashBuffer.toString('hex');
|
||||
const queryResult = await db.searchHash({ hash });
|
||||
let queryResult: FoundOrNot<DbSearchResult> | FoundOrNot<DbSearchResultWithMetadata> = {
|
||||
found: false,
|
||||
};
|
||||
if (!includeMetadata) {
|
||||
queryResult = await db.searchHash({ hash });
|
||||
} else {
|
||||
queryResult = await searchHashWithMetadata(hash, db);
|
||||
}
|
||||
if (queryResult.found) {
|
||||
if (queryResult.result.entity_type === 'block_hash') {
|
||||
if (queryResult.result.entity_type === 'block_hash' && queryResult.result.entity_data) {
|
||||
if (includeMetadata) {
|
||||
const blockData = queryResult.result.entity_data as Block;
|
||||
const blockResult: BlockSearchResult = {
|
||||
found: true,
|
||||
result: {
|
||||
entity_id: queryResult.result.entity_id,
|
||||
entity_type: SearchResultType.BlockHash,
|
||||
block_data: {
|
||||
canonical: blockData.canonical,
|
||||
hash: blockData.hash,
|
||||
parent_block_hash: blockData.parent_block_hash,
|
||||
burn_block_time: blockData.burn_block_time,
|
||||
height: blockData.height,
|
||||
},
|
||||
metadata: blockData,
|
||||
},
|
||||
};
|
||||
return blockResult;
|
||||
}
|
||||
const blockData = queryResult.result.entity_data as DbBlock;
|
||||
const blockResult: BlockSearchResult = {
|
||||
found: true,
|
||||
@@ -76,6 +116,9 @@ export function createSearchRouter(db: DataStore): RouterWithAsync {
|
||||
},
|
||||
},
|
||||
};
|
||||
if (includeMetadata) {
|
||||
txResult.result.metadata = parseDbTx(txData);
|
||||
}
|
||||
return txResult;
|
||||
} else if (queryResult.result.entity_type === 'mempool_tx_id') {
|
||||
const txData = queryResult.result.entity_data as DbMempoolTx;
|
||||
@@ -89,6 +132,9 @@ export function createSearchRouter(db: DataStore): RouterWithAsync {
|
||||
},
|
||||
},
|
||||
};
|
||||
if (includeMetadata) {
|
||||
txResult.result.metadata = parseDbMempoolTx(txData);
|
||||
}
|
||||
return txResult;
|
||||
} else {
|
||||
throw new Error(
|
||||
@@ -139,6 +185,9 @@ export function createSearchRouter(db: DataStore): RouterWithAsync {
|
||||
},
|
||||
},
|
||||
};
|
||||
if (includeMetadata) {
|
||||
contractResult.result.metadata = parseDbTx(txData);
|
||||
}
|
||||
return contractResult;
|
||||
} else {
|
||||
// Associated tx is a mempool tx
|
||||
@@ -154,6 +203,9 @@ export function createSearchRouter(db: DataStore): RouterWithAsync {
|
||||
},
|
||||
},
|
||||
};
|
||||
if (includeMetadata) {
|
||||
contractResult.result.metadata = parseDbMempoolTx(txData);
|
||||
}
|
||||
return contractResult;
|
||||
}
|
||||
} else if (entityType === SearchResultType.ContractAddress) {
|
||||
@@ -175,6 +227,32 @@ export function createSearchRouter(db: DataStore): RouterWithAsync {
|
||||
entity_type: entityType,
|
||||
},
|
||||
};
|
||||
if (includeMetadata) {
|
||||
const currentBlockHeight = await db.getCurrentBlockHeight();
|
||||
if (!currentBlockHeight.found) {
|
||||
throw new Error('No current block');
|
||||
}
|
||||
|
||||
const blockHeight = currentBlockHeight.result + 1;
|
||||
|
||||
const stxBalanceResult = await db.getStxBalanceAtBlock(
|
||||
principalResult.result.entity_id,
|
||||
blockHeight
|
||||
);
|
||||
const result: AddressStxBalanceResponse = {
|
||||
balance: stxBalanceResult.balance.toString(),
|
||||
total_sent: stxBalanceResult.totalSent.toString(),
|
||||
total_received: stxBalanceResult.totalReceived.toString(),
|
||||
total_fees_sent: stxBalanceResult.totalFeesSent.toString(),
|
||||
total_miner_rewards_received: stxBalanceResult.totalMinerRewardsReceived.toString(),
|
||||
lock_tx_id: stxBalanceResult.lockTxId,
|
||||
locked: stxBalanceResult.locked.toString(),
|
||||
lock_height: stxBalanceResult.lockHeight,
|
||||
burnchain_lock_height: stxBalanceResult.burnchainLockHeight,
|
||||
burnchain_unlock_height: stxBalanceResult.burnchainUnlockHeight,
|
||||
};
|
||||
addrResult.result.metadata = result;
|
||||
}
|
||||
return addrResult;
|
||||
} else {
|
||||
return {
|
||||
@@ -192,11 +270,11 @@ export function createSearchRouter(db: DataStore): RouterWithAsync {
|
||||
};
|
||||
};
|
||||
|
||||
router.getAsync('/:term', async (req, res) => {
|
||||
router.getAsync('/:term', async (req, res, next) => {
|
||||
const { term: rawTerm } = req.params;
|
||||
const includeMetadata = booleanValueForParam(req, res, next, 'include_metadata');
|
||||
const term = rawTerm.trim();
|
||||
|
||||
const searchResult = await performSearch(term);
|
||||
const searchResult = await performSearch(term, includeMetadata);
|
||||
if (!searchResult.found) {
|
||||
res.status(404);
|
||||
}
|
||||
|
||||
@@ -14,10 +14,15 @@ import {
|
||||
Transaction,
|
||||
} from '../p2p/tx';
|
||||
import { c32address } from 'c32check';
|
||||
import { AddressTokenOfferingLocked, TransactionType } from '@stacks/stacks-blockchain-api-types';
|
||||
import {
|
||||
AddressTokenOfferingLocked,
|
||||
MempoolTransaction,
|
||||
TransactionType,
|
||||
} from '@stacks/stacks-blockchain-api-types';
|
||||
import { getTxSenderAddress } from '../event-stream/reader';
|
||||
import { RawTxQueryResult } from './postgres-store';
|
||||
import { ClarityAbi } from '@stacks/transactions';
|
||||
import { Block } from '@stacks/stacks-blockchain-api-types';
|
||||
|
||||
export interface DbBlock {
|
||||
block_hash: string;
|
||||
@@ -398,6 +403,12 @@ export interface DbSearchResult {
|
||||
entity_data?: DbBlock | DbMempoolTx | DbTx;
|
||||
}
|
||||
|
||||
export interface DbSearchResultWithMetadata {
|
||||
entity_type: 'standard_address' | 'contract_address' | 'block_hash' | 'tx_id' | 'mempool_tx_id';
|
||||
entity_id: string;
|
||||
entity_data?: Block | DbMempoolTx | DbTx;
|
||||
}
|
||||
|
||||
export interface DbFtBalance {
|
||||
balance: bigint;
|
||||
totalSent: bigint;
|
||||
|
||||
@@ -88,11 +88,13 @@ import {
|
||||
DbNonFungibleTokenMetadata,
|
||||
DbFungibleTokenMetadata,
|
||||
DbTokenMetadataQueueEntry,
|
||||
DbSearchResultWithMetadata,
|
||||
} from './common';
|
||||
import {
|
||||
AddressTokenOfferingLocked,
|
||||
TransactionType,
|
||||
AddressUnlockSchedule,
|
||||
Block,
|
||||
} from '@stacks/stacks-blockchain-api-types';
|
||||
import { getTxTypeId } from '../api/controllers/db-controller';
|
||||
import { isProcessableTokenMetadata } from '../event-stream/tokens-contract-handler';
|
||||
|
||||
@@ -47,7 +47,7 @@ import { PgDataStore, cycleMigrations, runMigrations } from '../datastore/postgr
|
||||
import { PoolClient } from 'pg';
|
||||
import { bufferToHexPrefixString, I32_MAX, microStxToStx, STACKS_DECIMAL_PLACES } from '../helpers';
|
||||
import { FEE_RATE } from './../api/routes/fee-rate';
|
||||
import { FeeRateRequest } from 'docs/generated';
|
||||
import { Block, FeeRateRequest } from 'docs/generated';
|
||||
|
||||
describe('api tests', () => {
|
||||
let db: PgDataStore;
|
||||
@@ -1826,7 +1826,7 @@ describe('api tests', () => {
|
||||
|
||||
// test whitespace
|
||||
const searchResult3 = await supertest(api.server).get(
|
||||
`/extended/v1/search/ 1234000000000000000000000000000000000000000000000000000000000000 `
|
||||
`/extended/v1/search/ 1234000000000000000000000000000000000000000000000000000000000000`
|
||||
);
|
||||
expect(searchResult3.status).toBe(200);
|
||||
expect(searchResult3.type).toBe('application/json');
|
||||
@@ -1912,6 +1912,315 @@ describe('api tests', () => {
|
||||
expect(JSON.parse(searchResult7.text)).toEqual(expectedResp7);
|
||||
});
|
||||
|
||||
test('search term - hash with metadata', async () => {
|
||||
const block: DbBlock = {
|
||||
block_hash: '0x1234000000000000000000000000000000000000000000000000000000000000',
|
||||
index_block_hash: '0xdeadbeef',
|
||||
parent_index_block_hash: '0x00',
|
||||
parent_block_hash: '0xff0011',
|
||||
parent_microblock_hash: '',
|
||||
parent_microblock_sequence: 0,
|
||||
block_height: 1,
|
||||
burn_block_time: 94869286,
|
||||
burn_block_hash: '0x1234',
|
||||
burn_block_height: 123,
|
||||
miner_txid: '0x4321',
|
||||
canonical: true,
|
||||
execution_cost_read_count: 0,
|
||||
execution_cost_read_length: 0,
|
||||
execution_cost_runtime: 0,
|
||||
execution_cost_write_count: 0,
|
||||
execution_cost_write_length: 0,
|
||||
};
|
||||
|
||||
const tx: DbTx = {
|
||||
tx_id: '0x4567000000000000000000000000000000000000000000000000000000000000',
|
||||
tx_index: 4,
|
||||
anchor_mode: 3,
|
||||
nonce: 0,
|
||||
raw_tx: Buffer.alloc(0),
|
||||
index_block_hash: block.index_block_hash,
|
||||
block_hash: block.block_hash,
|
||||
block_height: 1,
|
||||
burn_block_time: 2837565,
|
||||
parent_burn_block_time: 1626122935,
|
||||
type_id: DbTxTypeId.Coinbase,
|
||||
coinbase_payload: Buffer.from('coinbase hi'),
|
||||
status: 1,
|
||||
raw_result: '0x0100000000000000000000000000000001', // u1
|
||||
canonical: true,
|
||||
microblock_canonical: true,
|
||||
microblock_sequence: I32_MAX,
|
||||
microblock_hash: '',
|
||||
parent_index_block_hash: '',
|
||||
parent_block_hash: '',
|
||||
post_conditions: Buffer.from([0x01, 0xf5]),
|
||||
fee_rate: 1234n,
|
||||
sponsored: false,
|
||||
sponsor_address: undefined,
|
||||
sender_address: 'sender-addr',
|
||||
origin_hash_mode: 1,
|
||||
event_count: 0,
|
||||
execution_cost_read_count: 0,
|
||||
execution_cost_read_length: 0,
|
||||
execution_cost_runtime: 0,
|
||||
execution_cost_write_count: 0,
|
||||
execution_cost_write_length: 0,
|
||||
};
|
||||
|
||||
const mempoolTx: DbMempoolTx = {
|
||||
pruned: false,
|
||||
tx_id: '0x8912000000000000000000000000000000000000000000000000000000000000',
|
||||
anchor_mode: 3,
|
||||
nonce: 0,
|
||||
raw_tx: Buffer.from('test-raw-tx'),
|
||||
type_id: DbTxTypeId.Coinbase,
|
||||
receipt_time: 123456,
|
||||
coinbase_payload: Buffer.from('coinbase hi'),
|
||||
status: 1,
|
||||
post_conditions: Buffer.from([0x01, 0xf5]),
|
||||
fee_rate: 1234n,
|
||||
sponsored: false,
|
||||
sponsor_address: undefined,
|
||||
sender_address: 'sender-addr',
|
||||
origin_hash_mode: 1,
|
||||
};
|
||||
await db.updateMempoolTxs({ mempoolTxs: [mempoolTx] });
|
||||
|
||||
const dataStoreUpdate: DataStoreBlockUpdateData = {
|
||||
block: block,
|
||||
microblocks: [],
|
||||
minerRewards: [],
|
||||
txs: [
|
||||
{
|
||||
tx: tx,
|
||||
stxEvents: [],
|
||||
stxLockEvents: [],
|
||||
ftEvents: [],
|
||||
nftEvents: [],
|
||||
contractLogEvents: [],
|
||||
smartContracts: [],
|
||||
names: [],
|
||||
namespaces: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
await db.update(dataStoreUpdate);
|
||||
const blockMetadata = {
|
||||
burn_block_hash: '0x1234',
|
||||
burn_block_height: 123,
|
||||
burn_block_time: 94869286,
|
||||
burn_block_time_iso: '1973-01-03T00:34:46.000Z',
|
||||
canonical: true,
|
||||
execution_cost_read_count: 0,
|
||||
execution_cost_read_length: 0,
|
||||
execution_cost_runtime: 0,
|
||||
execution_cost_write_count: 0,
|
||||
execution_cost_write_length: 0,
|
||||
hash: '0x1234000000000000000000000000000000000000000000000000000000000000',
|
||||
height: 1,
|
||||
microblocks_accepted: [],
|
||||
microblocks_streamed: [],
|
||||
miner_txid: '0x4321',
|
||||
parent_block_hash: '0xff0011',
|
||||
parent_microblock_hash: '',
|
||||
parent_microblock_sequence: 0,
|
||||
txs: ['0x4567000000000000000000000000000000000000000000000000000000000000'],
|
||||
};
|
||||
|
||||
const searchResult1 = await supertest(api.server).get(
|
||||
`/extended/v1/search/0x1234000000000000000000000000000000000000000000000000000000000000?include_metadata=true`
|
||||
);
|
||||
expect(searchResult1.status).toBe(200);
|
||||
expect(searchResult1.type).toBe('application/json');
|
||||
const expectedResp1 = {
|
||||
found: true,
|
||||
result: {
|
||||
entity_id: '0x1234000000000000000000000000000000000000000000000000000000000000',
|
||||
entity_type: 'block_hash',
|
||||
block_data: {
|
||||
canonical: true,
|
||||
hash: '0x1234000000000000000000000000000000000000000000000000000000000000',
|
||||
parent_block_hash: '0xff0011',
|
||||
burn_block_time: 94869286,
|
||||
height: 1,
|
||||
},
|
||||
metadata: blockMetadata,
|
||||
},
|
||||
};
|
||||
expect(JSON.parse(searchResult1.text)).toEqual(expectedResp1);
|
||||
|
||||
// test without 0x-prefix
|
||||
const searchResult2 = await supertest(api.server).get(
|
||||
`/extended/v1/search/1234000000000000000000000000000000000000000000000000000000000000?include_metadata=true`
|
||||
);
|
||||
expect(searchResult2.status).toBe(200);
|
||||
expect(searchResult2.type).toBe('application/json');
|
||||
const expectedResp2 = {
|
||||
found: true,
|
||||
result: {
|
||||
entity_id: '0x1234000000000000000000000000000000000000000000000000000000000000',
|
||||
entity_type: 'block_hash',
|
||||
block_data: {
|
||||
canonical: true,
|
||||
hash: '0x1234000000000000000000000000000000000000000000000000000000000000',
|
||||
parent_block_hash: '0xff0011',
|
||||
burn_block_time: 94869286,
|
||||
height: 1,
|
||||
},
|
||||
metadata: blockMetadata,
|
||||
},
|
||||
};
|
||||
expect(JSON.parse(searchResult2.text)).toEqual(expectedResp2);
|
||||
|
||||
// test whitespace
|
||||
const searchResult3 = await supertest(api.server).get(
|
||||
`/extended/v1/search/ 1234000000000000000000000000000000000000000000000000000000000000?include_metadata=true`
|
||||
);
|
||||
expect(searchResult3.status).toBe(200);
|
||||
expect(searchResult3.type).toBe('application/json');
|
||||
const expectedResp3 = {
|
||||
found: true,
|
||||
result: {
|
||||
entity_id: '0x1234000000000000000000000000000000000000000000000000000000000000',
|
||||
entity_type: 'block_hash',
|
||||
block_data: {
|
||||
canonical: true,
|
||||
hash: '0x1234000000000000000000000000000000000000000000000000000000000000',
|
||||
parent_block_hash: '0xff0011',
|
||||
burn_block_time: 94869286,
|
||||
height: 1,
|
||||
},
|
||||
metadata: blockMetadata,
|
||||
},
|
||||
};
|
||||
expect(JSON.parse(searchResult3.text)).toEqual(expectedResp3);
|
||||
|
||||
// test mempool tx search
|
||||
const searchResult4 = await supertest(api.server).get(
|
||||
`/extended/v1/search/0x8912000000000000000000000000000000000000000000000000000000000000?include_metadata=1`
|
||||
);
|
||||
expect(searchResult4.status).toBe(200);
|
||||
expect(searchResult4.type).toBe('application/json');
|
||||
const expectedResp4 = {
|
||||
found: true,
|
||||
result: {
|
||||
entity_id: '0x8912000000000000000000000000000000000000000000000000000000000000',
|
||||
entity_type: 'mempool_tx_id',
|
||||
tx_data: { tx_type: 'coinbase' },
|
||||
metadata: {
|
||||
anchor_mode: 'any',
|
||||
coinbase_payload: {
|
||||
data: '0x636f696e62617365206869',
|
||||
},
|
||||
fee_rate: '1234',
|
||||
nonce: 0,
|
||||
post_condition_mode: 'allow',
|
||||
post_conditions: [],
|
||||
receipt_time: 123456,
|
||||
receipt_time_iso: '1970-01-02T10:17:36.000Z',
|
||||
sender_address: 'sender-addr',
|
||||
sponsored: false,
|
||||
tx_id: '0x8912000000000000000000000000000000000000000000000000000000000000',
|
||||
tx_status: 'success',
|
||||
tx_type: 'coinbase',
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(JSON.parse(searchResult4.text)).toEqual(expectedResp4);
|
||||
|
||||
// test hash not found
|
||||
const searchResult5 = await supertest(api.server).get(
|
||||
`/extended/v1/search/0x1111000000000000000000000000000000000000000000000000000000000000?include_metadata=on`
|
||||
);
|
||||
expect(searchResult5.status).toBe(404);
|
||||
expect(searchResult5.type).toBe('application/json');
|
||||
const expectedResp6 = {
|
||||
found: false,
|
||||
result: { entity_type: 'unknown_hash' },
|
||||
error:
|
||||
'No block or transaction found with hash "0x1111000000000000000000000000000000000000000000000000000000000000"',
|
||||
};
|
||||
expect(JSON.parse(searchResult5.text)).toEqual(expectedResp6);
|
||||
|
||||
// test invalid hash hex
|
||||
const invalidHex = '0x1111w00000000000000000000000000000000000000000000000000000000000';
|
||||
const searchResult6 = await supertest(api.server).get(
|
||||
`/extended/v1/search/${invalidHex}?include_metadata`
|
||||
);
|
||||
expect(searchResult6.status).toBe(404);
|
||||
expect(searchResult6.type).toBe('application/json');
|
||||
const expectedResp7 = {
|
||||
found: false,
|
||||
result: { entity_type: 'invalid_term' },
|
||||
error:
|
||||
'The term "0x1111w00000000000000000000000000000000000000000000000000000000000" is not a valid block hash, transaction ID, contract principal, or account address principal',
|
||||
};
|
||||
expect(JSON.parse(searchResult6.text)).toEqual(expectedResp7);
|
||||
|
||||
// test tx search
|
||||
const searchResult8 = await supertest(api.server).get(
|
||||
`/extended/v1/search/0x4567000000000000000000000000000000000000000000000000000000000000?include_metadata`
|
||||
);
|
||||
expect(searchResult8.status).toBe(200);
|
||||
expect(searchResult8.type).toBe('application/json');
|
||||
|
||||
const expectedResp8 = {
|
||||
found: true,
|
||||
result: {
|
||||
entity_id: '0x4567000000000000000000000000000000000000000000000000000000000000',
|
||||
entity_type: 'tx_id',
|
||||
tx_data: {
|
||||
canonical: true,
|
||||
block_hash: '0x1234000000000000000000000000000000000000000000000000000000000000',
|
||||
burn_block_time: 2837565,
|
||||
block_height: 1,
|
||||
tx_type: 'coinbase',
|
||||
},
|
||||
metadata: {
|
||||
tx_id: '0x4567000000000000000000000000000000000000000000000000000000000000',
|
||||
nonce: 0,
|
||||
fee_rate: '1234',
|
||||
sender_address: 'sender-addr',
|
||||
sponsored: false,
|
||||
post_condition_mode: 'allow',
|
||||
post_conditions: [],
|
||||
anchor_mode: 'any',
|
||||
is_unanchored: false,
|
||||
block_hash: '0x1234000000000000000000000000000000000000000000000000000000000000',
|
||||
parent_block_hash: '',
|
||||
block_height: 1,
|
||||
burn_block_time: 2837565,
|
||||
burn_block_time_iso: '1970-02-02T20:12:45.000Z',
|
||||
parent_burn_block_time: 1626122935,
|
||||
parent_burn_block_time_iso: '2021-07-12T20:48:55.000Z',
|
||||
canonical: true,
|
||||
tx_index: 4,
|
||||
tx_status: 'success',
|
||||
tx_result: {
|
||||
hex: '0x0100000000000000000000000000000001',
|
||||
repr: 'u1',
|
||||
},
|
||||
microblock_hash: '',
|
||||
microblock_sequence: 2147483647,
|
||||
microblock_canonical: true,
|
||||
event_count: 0,
|
||||
execution_cost_read_count: 0,
|
||||
execution_cost_read_length: 0,
|
||||
execution_cost_runtime: 0,
|
||||
execution_cost_write_count: 0,
|
||||
execution_cost_write_length: 0,
|
||||
tx_type: 'coinbase',
|
||||
coinbase_payload: {
|
||||
data: '0x636f696e62617365206869',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(JSON.parse(searchResult8.text)).toEqual(expectedResp8);
|
||||
});
|
||||
|
||||
test('search term - principal', async () => {
|
||||
const addr1 = 'ST3J8EVYHVKH6XXPD61EE8XEHW4Y2K83861225AB1';
|
||||
const addr2 = 'ST1HB64MAJ1MBV4CQ80GF01DZS4T1DSMX20ADCRA4';
|
||||
@@ -1930,6 +2239,27 @@ describe('api tests', () => {
|
||||
const contractAddr2 = 'STSPS4JYDEYCPPCSHE3MM2NCEGR07KPBETNEZCBQ.contract-name';
|
||||
const contractAddr3 = 'STSPS4JYDEYCPPCSHE3MM2NCEGR07KPBETNEZCBQ.test-contract';
|
||||
|
||||
const block: DbBlock = {
|
||||
block_hash: '0x1234',
|
||||
index_block_hash: '0x1234',
|
||||
parent_index_block_hash: '0x2345',
|
||||
parent_block_hash: '0x5678',
|
||||
parent_microblock_hash: '',
|
||||
parent_microblock_sequence: 0,
|
||||
block_height: 100123123,
|
||||
burn_block_time: 39486,
|
||||
burn_block_hash: '0x1234',
|
||||
burn_block_height: 100123123,
|
||||
miner_txid: '0x4321',
|
||||
canonical: true,
|
||||
execution_cost_read_count: 0,
|
||||
execution_cost_read_length: 0,
|
||||
execution_cost_runtime: 0,
|
||||
execution_cost_write_count: 0,
|
||||
execution_cost_write_length: 0,
|
||||
};
|
||||
await db.updateBlock(client, block);
|
||||
|
||||
const stxTx1: DbTx = {
|
||||
tx_id: '0x1111000000000000000000000000000000000000000000000000000000000000',
|
||||
tx_index: 0,
|
||||
@@ -2071,6 +2401,7 @@ describe('api tests', () => {
|
||||
recipient: 'none',
|
||||
sender: addr4,
|
||||
};
|
||||
|
||||
await db.updateStxEvent(client, stxTx1, stxEvent2);
|
||||
|
||||
// test address as a stx event sender
|
||||
@@ -2330,6 +2661,629 @@ describe('api tests', () => {
|
||||
expect(JSON.parse(searchResult13.text)).toEqual(expectedResp13);
|
||||
});
|
||||
|
||||
test('search term - principal with metadata', async () => {
|
||||
const addr1 = 'ST3J8EVYHVKH6XXPD61EE8XEHW4Y2K83861225AB1';
|
||||
const addr2 = 'ST1HB64MAJ1MBV4CQ80GF01DZS4T1DSMX20ADCRA4';
|
||||
const addr3 = 'ST37VASHEJRMFRS91GWK1HZZKKEYQTEP85ARXCQPH';
|
||||
const addr4 = 'ST3DWSXBPYDB484QXFTR81K4AWG4ZB5XZNFF3H70C';
|
||||
const addr5 = 'ST3YKTGBCY1BNKN6J18A3QKAX7CE36SZH3A5XN9ZQ';
|
||||
const addr6 = 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR';
|
||||
const addr7 = 'SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G';
|
||||
const addr8 = 'ST3AMFNNS7KBQ28ECMJMN2G3AGJ37SSA2HSY82CMH';
|
||||
const addr9 = 'STAR26VJ4BC24SMNKRY533MAM0K3JA5ZJDVBD45A';
|
||||
const contractAddr1 = 'ST27W5M8BRKA7C5MZE2R1S1F4XTPHFWFRNHA9M04Y.hello-world';
|
||||
const contractAddr2 = 'STSPS4JYDEYCPPCSHE3MM2NCEGR07KPBETNEZCBQ.contract-name';
|
||||
const contractAddr3 = 'STSPS4JYDEYCPPCSHE3MM2NCEGR07KPBETNEZCBQ.test-contract';
|
||||
|
||||
const block: DbBlock = {
|
||||
block_hash: '0x1234',
|
||||
index_block_hash: '0x1234',
|
||||
parent_index_block_hash: '0x2345',
|
||||
parent_block_hash: '0x5678',
|
||||
parent_microblock_hash: '',
|
||||
parent_microblock_sequence: 0,
|
||||
block_height: 1,
|
||||
burn_block_time: 39486,
|
||||
burn_block_hash: '0x1234',
|
||||
burn_block_height: 100123123,
|
||||
miner_txid: '0x4321',
|
||||
canonical: true,
|
||||
execution_cost_read_count: 0,
|
||||
execution_cost_read_length: 0,
|
||||
execution_cost_runtime: 0,
|
||||
execution_cost_write_count: 0,
|
||||
execution_cost_write_length: 0,
|
||||
};
|
||||
|
||||
const stxTx1: DbTx = {
|
||||
tx_id: '0x1111000000000000000000000000000000000000000000000000000000000000',
|
||||
tx_index: 0,
|
||||
anchor_mode: 3,
|
||||
nonce: 0,
|
||||
raw_tx: Buffer.alloc(0),
|
||||
index_block_hash: '0x5432',
|
||||
block_hash: block.block_hash,
|
||||
block_height: block.block_height,
|
||||
burn_block_time: 2837565,
|
||||
parent_burn_block_time: 1626122935,
|
||||
type_id: DbTxTypeId.TokenTransfer,
|
||||
token_transfer_amount: 1n,
|
||||
token_transfer_memo: Buffer.from('hi'),
|
||||
token_transfer_recipient_address: 'none',
|
||||
status: 1,
|
||||
raw_result: '0x0100000000000000000000000000000001', // u1
|
||||
canonical: true,
|
||||
microblock_canonical: true,
|
||||
microblock_sequence: I32_MAX,
|
||||
microblock_hash: '',
|
||||
parent_index_block_hash: '',
|
||||
parent_block_hash: '',
|
||||
post_conditions: Buffer.from([0x01, 0xf5]),
|
||||
fee_rate: 1234n,
|
||||
sponsored: false,
|
||||
sponsor_address: undefined,
|
||||
sender_address: addr1,
|
||||
origin_hash_mode: 1,
|
||||
event_count: 0,
|
||||
execution_cost_read_count: 0,
|
||||
execution_cost_read_length: 0,
|
||||
execution_cost_runtime: 0,
|
||||
execution_cost_write_count: 0,
|
||||
execution_cost_write_length: 0,
|
||||
};
|
||||
|
||||
const stxTx2: DbTx = {
|
||||
tx_id: '0x2222000000000000000000000000000000000000000000000000000000000000',
|
||||
tx_index: 0,
|
||||
anchor_mode: 3,
|
||||
nonce: 0,
|
||||
raw_tx: Buffer.alloc(0),
|
||||
index_block_hash: '0x5432',
|
||||
block_hash: block.block_hash,
|
||||
block_height: block.block_height,
|
||||
burn_block_time: 2837565,
|
||||
parent_burn_block_time: 1626122935,
|
||||
type_id: DbTxTypeId.TokenTransfer,
|
||||
token_transfer_amount: 1n,
|
||||
token_transfer_memo: Buffer.from('hi'),
|
||||
token_transfer_recipient_address: addr2,
|
||||
status: 1,
|
||||
raw_result: '0x0100000000000000000000000000000001', // u1
|
||||
canonical: true,
|
||||
microblock_canonical: true,
|
||||
microblock_sequence: I32_MAX,
|
||||
microblock_hash: '',
|
||||
parent_index_block_hash: '',
|
||||
parent_block_hash: '',
|
||||
post_conditions: Buffer.from([0x01, 0xf5]),
|
||||
fee_rate: 1234n,
|
||||
sponsored: false,
|
||||
sponsor_address: undefined,
|
||||
sender_address: 'none',
|
||||
origin_hash_mode: 1,
|
||||
event_count: 0,
|
||||
execution_cost_read_count: 0,
|
||||
execution_cost_read_length: 0,
|
||||
execution_cost_runtime: 0,
|
||||
execution_cost_write_count: 0,
|
||||
execution_cost_write_length: 0,
|
||||
};
|
||||
|
||||
const stxEvent1: DbStxEvent = {
|
||||
canonical: true,
|
||||
event_type: DbEventTypeId.StxAsset,
|
||||
asset_event_type_id: DbAssetEventTypeId.Transfer,
|
||||
event_index: 0,
|
||||
tx_id: '0x1111000000000000000000000000000000000000000000000000000000000000',
|
||||
tx_index: 1,
|
||||
block_height: block.block_height,
|
||||
amount: 1n,
|
||||
recipient: addr3,
|
||||
sender: 'none',
|
||||
};
|
||||
|
||||
const stxEvent2: DbStxEvent = {
|
||||
canonical: true,
|
||||
event_type: DbEventTypeId.StxAsset,
|
||||
asset_event_type_id: DbAssetEventTypeId.Transfer,
|
||||
event_index: 0,
|
||||
tx_id: '0x1111000000000000000000000000000000000000000000000000000000000000',
|
||||
tx_index: 1,
|
||||
block_height: 1,
|
||||
amount: 1n,
|
||||
recipient: 'none',
|
||||
sender: addr4,
|
||||
};
|
||||
|
||||
const ftEvent1: DbFtEvent = {
|
||||
canonical: true,
|
||||
event_type: DbEventTypeId.FungibleTokenAsset,
|
||||
asset_event_type_id: DbAssetEventTypeId.Transfer,
|
||||
event_index: 0,
|
||||
tx_id: '0x1111000000000000000000000000000000000000000000000000000000000000',
|
||||
tx_index: 1,
|
||||
block_height: block.block_height,
|
||||
asset_identifier: 'some-asset',
|
||||
amount: 1n,
|
||||
recipient: addr5,
|
||||
sender: 'none',
|
||||
};
|
||||
|
||||
const ftEvent2: DbFtEvent = {
|
||||
canonical: true,
|
||||
event_type: DbEventTypeId.FungibleTokenAsset,
|
||||
asset_event_type_id: DbAssetEventTypeId.Transfer,
|
||||
event_index: 0,
|
||||
tx_id: '0x1111000000000000000000000000000000000000000000000000000000000000',
|
||||
tx_index: 1,
|
||||
block_height: block.block_height,
|
||||
asset_identifier: 'some-asset',
|
||||
amount: 1n,
|
||||
recipient: 'none',
|
||||
sender: addr6,
|
||||
};
|
||||
|
||||
const nftEvent1: DbNftEvent = {
|
||||
canonical: true,
|
||||
event_type: DbEventTypeId.NonFungibleTokenAsset,
|
||||
asset_event_type_id: DbAssetEventTypeId.Transfer,
|
||||
event_index: 0,
|
||||
tx_id: '0x1111000000000000000000000000000000000000000000000000000000000000',
|
||||
tx_index: 1,
|
||||
block_height: block.block_height,
|
||||
asset_identifier: 'some-asset',
|
||||
value: serializeCV(intCV(0)),
|
||||
recipient: addr7,
|
||||
sender: 'none',
|
||||
};
|
||||
|
||||
const nftEvent2: DbNftEvent = {
|
||||
canonical: true,
|
||||
event_type: DbEventTypeId.NonFungibleTokenAsset,
|
||||
asset_event_type_id: DbAssetEventTypeId.Transfer,
|
||||
event_index: 0,
|
||||
tx_id: '0x1111000000000000000000000000000000000000000000000000000000000000',
|
||||
tx_index: 1,
|
||||
block_height: block.block_height,
|
||||
asset_identifier: 'some-asset',
|
||||
value: serializeCV(intCV(0)),
|
||||
recipient: 'none',
|
||||
sender: addr8,
|
||||
};
|
||||
|
||||
const smartContractTx: DbTx = {
|
||||
type_id: DbTxTypeId.SmartContract,
|
||||
tx_id: '0x1111880000000000000000000000000000000000000000000000000000000000',
|
||||
anchor_mode: 3,
|
||||
nonce: 0,
|
||||
raw_tx: Buffer.alloc(0),
|
||||
canonical: true,
|
||||
microblock_canonical: true,
|
||||
microblock_sequence: I32_MAX,
|
||||
microblock_hash: '',
|
||||
parent_index_block_hash: '',
|
||||
parent_block_hash: '',
|
||||
smart_contract_contract_id: contractAddr1,
|
||||
smart_contract_source_code: '(some-src)',
|
||||
block_height: 1,
|
||||
tx_index: 0,
|
||||
index_block_hash: block.index_block_hash,
|
||||
block_hash: block.block_hash,
|
||||
burn_block_time: 2837565,
|
||||
parent_burn_block_time: 1626122935,
|
||||
status: 1,
|
||||
raw_result: '0x0100000000000000000000000000000001', // u1
|
||||
post_conditions: Buffer.from([0x01, 0xf5]),
|
||||
fee_rate: 1234n,
|
||||
sponsored: false,
|
||||
sponsor_address: undefined,
|
||||
sender_address: 'none',
|
||||
origin_hash_mode: 1,
|
||||
event_count: 0,
|
||||
execution_cost_read_count: 0,
|
||||
execution_cost_read_length: 0,
|
||||
execution_cost_runtime: 0,
|
||||
execution_cost_write_count: 0,
|
||||
execution_cost_write_length: 0,
|
||||
};
|
||||
|
||||
const smartContract: DbSmartContract = {
|
||||
tx_id: '0x421234',
|
||||
canonical: true,
|
||||
block_height: block.block_height,
|
||||
contract_id: contractAddr1,
|
||||
source_code: '(some-src)',
|
||||
abi: '{"some-abi":1}',
|
||||
};
|
||||
|
||||
const dataStoreUpdate: DataStoreBlockUpdateData = {
|
||||
block: block,
|
||||
microblocks: [],
|
||||
minerRewards: [],
|
||||
txs: [
|
||||
{
|
||||
tx: stxTx1,
|
||||
stxEvents: [stxEvent1, stxEvent2],
|
||||
stxLockEvents: [],
|
||||
ftEvents: [ftEvent1, ftEvent2],
|
||||
nftEvents: [nftEvent1, nftEvent2],
|
||||
contractLogEvents: [],
|
||||
smartContracts: [],
|
||||
names: [],
|
||||
namespaces: [],
|
||||
},
|
||||
{
|
||||
tx: stxTx2,
|
||||
stxEvents: [],
|
||||
stxLockEvents: [],
|
||||
ftEvents: [],
|
||||
nftEvents: [],
|
||||
contractLogEvents: [],
|
||||
smartContracts: [],
|
||||
names: [],
|
||||
namespaces: [],
|
||||
},
|
||||
{
|
||||
tx: smartContractTx,
|
||||
stxEvents: [],
|
||||
stxLockEvents: [],
|
||||
ftEvents: [],
|
||||
nftEvents: [],
|
||||
contractLogEvents: [],
|
||||
smartContracts: [smartContract],
|
||||
names: [],
|
||||
namespaces: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
await db.update(dataStoreUpdate);
|
||||
|
||||
// test address as a tx sender
|
||||
const searchResult1 = await supertest(api.server).get(
|
||||
`/extended/v1/search/${addr1}?include_metadata`
|
||||
);
|
||||
expect(searchResult1.status).toBe(200);
|
||||
expect(searchResult1.type).toBe('application/json');
|
||||
const expectedResp1 = {
|
||||
found: true,
|
||||
result: {
|
||||
entity_type: 'standard_address',
|
||||
entity_id: addr1,
|
||||
metadata: {
|
||||
balance: '-1234',
|
||||
burnchain_lock_height: 0,
|
||||
burnchain_unlock_height: 0,
|
||||
lock_height: 0,
|
||||
lock_tx_id: '',
|
||||
locked: '0',
|
||||
total_fees_sent: '1234',
|
||||
total_miner_rewards_received: '0',
|
||||
total_received: '0',
|
||||
total_sent: '0',
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(JSON.parse(searchResult1.text)).toEqual(expectedResp1);
|
||||
|
||||
// test address as a stx tx recipient
|
||||
const searchResult2 = await supertest(api.server).get(
|
||||
`/extended/v1/search/${addr2}?include_metadata`
|
||||
);
|
||||
expect(searchResult2.status).toBe(200);
|
||||
expect(searchResult2.type).toBe('application/json');
|
||||
const expectedResp2 = {
|
||||
found: true,
|
||||
result: {
|
||||
entity_type: 'standard_address',
|
||||
entity_id: addr2,
|
||||
metadata: {
|
||||
balance: '0',
|
||||
burnchain_lock_height: 0,
|
||||
burnchain_unlock_height: 0,
|
||||
lock_height: 0,
|
||||
lock_tx_id: '',
|
||||
locked: '0',
|
||||
total_fees_sent: '0',
|
||||
total_miner_rewards_received: '0',
|
||||
total_received: '0',
|
||||
total_sent: '0',
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(JSON.parse(searchResult2.text)).toEqual(expectedResp2);
|
||||
|
||||
// test address as a stx event recipient
|
||||
const searchResult3 = await supertest(api.server).get(
|
||||
`/extended/v1/search/${addr3}?include_metadata`
|
||||
);
|
||||
expect(searchResult3.status).toBe(200);
|
||||
expect(searchResult3.type).toBe('application/json');
|
||||
const expectedResp3 = {
|
||||
found: true,
|
||||
result: {
|
||||
entity_type: 'standard_address',
|
||||
entity_id: addr3,
|
||||
metadata: {
|
||||
balance: '1',
|
||||
burnchain_lock_height: 0,
|
||||
burnchain_unlock_height: 0,
|
||||
lock_height: 0,
|
||||
lock_tx_id: '',
|
||||
locked: '0',
|
||||
total_fees_sent: '0',
|
||||
total_miner_rewards_received: '0',
|
||||
total_received: '1',
|
||||
total_sent: '0',
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(JSON.parse(searchResult3.text)).toEqual(expectedResp3);
|
||||
|
||||
// test address as a stx event sender
|
||||
const searchResult4 = await supertest(api.server).get(
|
||||
`/extended/v1/search/${addr4}?include_metadata=true`
|
||||
);
|
||||
expect(searchResult4.status).toBe(200);
|
||||
expect(searchResult4.type).toBe('application/json');
|
||||
const expectedResp4 = {
|
||||
found: true,
|
||||
result: {
|
||||
entity_type: 'standard_address',
|
||||
entity_id: addr4,
|
||||
metadata: {
|
||||
balance: '-1',
|
||||
burnchain_lock_height: 0,
|
||||
burnchain_unlock_height: 0,
|
||||
lock_height: 0,
|
||||
lock_tx_id: '',
|
||||
locked: '0',
|
||||
total_fees_sent: '0',
|
||||
total_miner_rewards_received: '0',
|
||||
total_received: '0',
|
||||
total_sent: '1',
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(JSON.parse(searchResult4.text)).toEqual(expectedResp4);
|
||||
|
||||
// test address as a ft event recipient
|
||||
const searchResult5 = await supertest(api.server).get(
|
||||
`/extended/v1/search/${addr5}?include_metadata`
|
||||
);
|
||||
expect(searchResult5.status).toBe(200);
|
||||
expect(searchResult5.type).toBe('application/json');
|
||||
const emptyStandardAddressMetadata = {
|
||||
balance: '0',
|
||||
burnchain_lock_height: 0,
|
||||
burnchain_unlock_height: 0,
|
||||
lock_height: 0,
|
||||
lock_tx_id: '',
|
||||
locked: '0',
|
||||
total_fees_sent: '0',
|
||||
total_miner_rewards_received: '0',
|
||||
total_received: '0',
|
||||
total_sent: '0',
|
||||
};
|
||||
const expectedResp5 = {
|
||||
found: true,
|
||||
result: {
|
||||
entity_type: 'standard_address',
|
||||
entity_id: addr5,
|
||||
metadata: emptyStandardAddressMetadata,
|
||||
},
|
||||
};
|
||||
expect(JSON.parse(searchResult5.text)).toEqual(expectedResp5);
|
||||
|
||||
// test address as a ft event sender
|
||||
const searchResult6 = await supertest(api.server).get(
|
||||
`/extended/v1/search/${addr6}?include_metadata`
|
||||
);
|
||||
expect(searchResult6.status).toBe(200);
|
||||
expect(searchResult6.type).toBe('application/json');
|
||||
const expectedResp6 = {
|
||||
found: true,
|
||||
result: {
|
||||
entity_type: 'standard_address',
|
||||
entity_id: addr6,
|
||||
metadata: emptyStandardAddressMetadata,
|
||||
},
|
||||
};
|
||||
expect(JSON.parse(searchResult6.text)).toEqual(expectedResp6);
|
||||
|
||||
// test address as a nft event recipient
|
||||
const searchResult7 = await supertest(api.server).get(
|
||||
`/extended/v1/search/${addr7}?include_metadata`
|
||||
);
|
||||
expect(searchResult7.status).toBe(200);
|
||||
expect(searchResult7.type).toBe('application/json');
|
||||
const expectedResp7 = {
|
||||
found: true,
|
||||
result: {
|
||||
entity_type: 'standard_address',
|
||||
entity_id: addr7,
|
||||
metadata: emptyStandardAddressMetadata,
|
||||
},
|
||||
};
|
||||
expect(JSON.parse(searchResult7.text)).toEqual(expectedResp7);
|
||||
|
||||
// test address as a nft event sender
|
||||
const searchResult8 = await supertest(api.server).get(
|
||||
`/extended/v1/search/${addr8}?include_metadata`
|
||||
);
|
||||
expect(searchResult8.status).toBe(200);
|
||||
expect(searchResult8.type).toBe('application/json');
|
||||
const expectedResp8 = {
|
||||
found: true,
|
||||
result: {
|
||||
entity_type: 'standard_address',
|
||||
entity_id: addr8,
|
||||
metadata: emptyStandardAddressMetadata,
|
||||
},
|
||||
};
|
||||
expect(JSON.parse(searchResult8.text)).toEqual(expectedResp8);
|
||||
|
||||
// test contract address
|
||||
const searchResult9 = await supertest(api.server).get(
|
||||
`/extended/v1/search/${contractAddr1}?include_metadata=true`
|
||||
);
|
||||
expect(searchResult9.status).toBe(200);
|
||||
expect(searchResult9.type).toBe('application/json');
|
||||
const expectedResp9 = {
|
||||
found: true,
|
||||
result: {
|
||||
entity_id: 'ST27W5M8BRKA7C5MZE2R1S1F4XTPHFWFRNHA9M04Y.hello-world',
|
||||
entity_type: 'contract_address',
|
||||
tx_data: {
|
||||
canonical: true,
|
||||
block_hash: '0x1234',
|
||||
burn_block_time: 2837565,
|
||||
block_height: 1,
|
||||
tx_type: 'smart_contract',
|
||||
tx_id: '0x1111880000000000000000000000000000000000000000000000000000000000',
|
||||
},
|
||||
metadata: {
|
||||
anchor_mode: 'any',
|
||||
block_hash: '0x1234',
|
||||
block_height: 1,
|
||||
burn_block_time: 2837565,
|
||||
burn_block_time_iso: '1970-02-02T20:12:45.000Z',
|
||||
canonical: true,
|
||||
event_count: 0,
|
||||
execution_cost_read_count: 0,
|
||||
execution_cost_read_length: 0,
|
||||
execution_cost_runtime: 0,
|
||||
execution_cost_write_count: 0,
|
||||
execution_cost_write_length: 0,
|
||||
fee_rate: '1234',
|
||||
is_unanchored: false,
|
||||
microblock_canonical: true,
|
||||
microblock_hash: '',
|
||||
microblock_sequence: 2147483647,
|
||||
nonce: 0,
|
||||
parent_block_hash: '',
|
||||
parent_burn_block_time: 1626122935,
|
||||
parent_burn_block_time_iso: '2021-07-12T20:48:55.000Z',
|
||||
post_condition_mode: 'allow',
|
||||
post_conditions: [],
|
||||
sender_address: 'none',
|
||||
smart_contract: {
|
||||
contract_id: 'ST27W5M8BRKA7C5MZE2R1S1F4XTPHFWFRNHA9M04Y.hello-world',
|
||||
source_code: '(some-src)',
|
||||
},
|
||||
sponsored: false,
|
||||
tx_id: '0x1111880000000000000000000000000000000000000000000000000000000000',
|
||||
tx_index: 0,
|
||||
tx_result: {
|
||||
hex: '0x0100000000000000000000000000000001',
|
||||
repr: 'u1',
|
||||
},
|
||||
tx_status: 'success',
|
||||
tx_type: 'smart_contract',
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(JSON.parse(searchResult9.text)).toEqual(expectedResp9);
|
||||
|
||||
const smartContractMempoolTx: DbMempoolTx = {
|
||||
pruned: false,
|
||||
type_id: DbTxTypeId.SmartContract,
|
||||
tx_id: '0x1111882200000000000000000000000000000000000000000000000000000000',
|
||||
anchor_mode: 3,
|
||||
nonce: 0,
|
||||
raw_tx: Buffer.from('test-raw-tx'),
|
||||
receipt_time: 123456,
|
||||
smart_contract_contract_id: contractAddr2,
|
||||
smart_contract_source_code: '(some-src)',
|
||||
status: 1,
|
||||
post_conditions: Buffer.from([0x01, 0xf5]),
|
||||
fee_rate: 1234n,
|
||||
sponsored: false,
|
||||
sponsor_address: undefined,
|
||||
sender_address: 'none',
|
||||
origin_hash_mode: 1,
|
||||
};
|
||||
await db.updateMempoolTxs({ mempoolTxs: [smartContractMempoolTx] });
|
||||
|
||||
// test contract address associated with mempool tx
|
||||
const searchResult10 = await supertest(api.server).get(
|
||||
`/extended/v1/search/${contractAddr2}?include_metadata`
|
||||
);
|
||||
expect(searchResult10.status).toBe(200);
|
||||
expect(searchResult10.type).toBe('application/json');
|
||||
const expectedResp10 = {
|
||||
found: true,
|
||||
result: {
|
||||
entity_id: 'STSPS4JYDEYCPPCSHE3MM2NCEGR07KPBETNEZCBQ.contract-name',
|
||||
entity_type: 'contract_address',
|
||||
tx_data: {
|
||||
tx_type: 'smart_contract',
|
||||
tx_id: '0x1111882200000000000000000000000000000000000000000000000000000000',
|
||||
},
|
||||
metadata: {
|
||||
anchor_mode: 'any',
|
||||
fee_rate: '1234',
|
||||
nonce: 0,
|
||||
post_condition_mode: 'allow',
|
||||
post_conditions: [],
|
||||
receipt_time: 123456,
|
||||
receipt_time_iso: '1970-01-02T10:17:36.000Z',
|
||||
sender_address: 'none',
|
||||
smart_contract: {
|
||||
contract_id: 'STSPS4JYDEYCPPCSHE3MM2NCEGR07KPBETNEZCBQ.contract-name',
|
||||
source_code: '(some-src)',
|
||||
},
|
||||
sponsored: false,
|
||||
tx_id: '0x1111882200000000000000000000000000000000000000000000000000000000',
|
||||
tx_status: 'success',
|
||||
tx_type: 'smart_contract',
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(JSON.parse(searchResult10.text)).toEqual(expectedResp10);
|
||||
|
||||
// test contract address not found
|
||||
const searchResult11 = await supertest(api.server).get(
|
||||
`/extended/v1/search/${contractAddr3}?include_metadata`
|
||||
);
|
||||
expect(searchResult11.status).toBe(404);
|
||||
expect(searchResult11.type).toBe('application/json');
|
||||
const expectedResp11 = {
|
||||
found: false,
|
||||
result: { entity_type: 'contract_address' },
|
||||
error:
|
||||
'No principal found with address "STSPS4JYDEYCPPCSHE3MM2NCEGR07KPBETNEZCBQ.test-contract"',
|
||||
};
|
||||
expect(JSON.parse(searchResult11.text)).toEqual(expectedResp11);
|
||||
|
||||
// test standard address not found
|
||||
const searchResult12 = await supertest(api.server).get(
|
||||
`/extended/v1/search/${addr9}?include_metadata`
|
||||
);
|
||||
expect(searchResult12.status).toBe(404);
|
||||
expect(searchResult12.type).toBe('application/json');
|
||||
const expectedResp12 = {
|
||||
found: false,
|
||||
result: { entity_type: 'standard_address' },
|
||||
error: 'No principal found with address "STAR26VJ4BC24SMNKRY533MAM0K3JA5ZJDVBD45A"',
|
||||
};
|
||||
expect(JSON.parse(searchResult12.text)).toEqual(expectedResp12);
|
||||
|
||||
// test invalid term
|
||||
const invalidTerm = 'bogus123';
|
||||
const searchResult13 = await supertest(api.server).get(
|
||||
`/extended/v1/search/${invalidTerm}?include_metadata`
|
||||
);
|
||||
expect(searchResult13.status).toBe(404);
|
||||
expect(searchResult13.type).toBe('application/json');
|
||||
const expectedResp13 = {
|
||||
found: false,
|
||||
result: { entity_type: 'invalid_term' },
|
||||
error:
|
||||
'The term "bogus123" is not a valid block hash, transaction ID, contract principal, or account address principal',
|
||||
};
|
||||
expect(JSON.parse(searchResult13.text)).toEqual(expectedResp13);
|
||||
});
|
||||
|
||||
test('address transaction transfers', async () => {
|
||||
const testAddr1 = 'ST3J8EVYHVKH6XXPD61EE8XEHW4Y2K83861225AB1';
|
||||
const testAddr2 = 'ST1HB64MAJ1MBV4CQ80GF01DZS4T1DSMX20ADCRA4';
|
||||
|
||||
Reference in New Issue
Block a user