Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, I should've explained better how this file works. This file is edited by semantic-release automatically whenever a new API release is published. As such, it should stay as part of the repository because we want to keep its (automatic) edit history. In other words, the file was deleted with your latest commit but it should still remain in the repo unchanged.

Can you revert this file to how it was before? You can do that with

git checkout develop -- CHANGELOG.md

Sorry for the confusion on this!

Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## [8.11.0](https://github.com/hirosystems/stacks-blockchain-api/compare/v8.10.0...v8.11.0) (2025-07-03)

### Features
* add `exclude_function_args` query parameter to transaction and address endpoints to optionally omit contract-call `function_args`, reducing payload size and improving pagination performance ([#2312](https://github.com/hirosystems/stacks-blockchain-api/pull/2312)) ([f7a6b19](https://github.com/hirosystems/stacks-blockchain-api/commit/f7a6b194d))

## [8.10.0](https://github.com/hirosystems/stacks-blockchain-api/compare/v8.9.0...v8.10.0) (2025-04-18)


Expand Down
79 changes: 53 additions & 26 deletions src/api/controllers/db-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,7 @@ export async function getRosettaTransactionFromDataStore(
interface GetTxArgs {
txId: string;
includeUnanchored: boolean;
excludeFunctionArgs?: boolean;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
excludeFunctionArgs?: boolean;
excludeFunctionArgs: boolean;

}

interface GetTxFromDbTxArgs extends GetTxArgs {
Expand All @@ -878,6 +879,7 @@ interface GetTxsWithEventsArgs extends GetTxsArgs {
interface GetTxsArgs {
txIds: string[];
includeUnanchored: boolean;
excludeFunctionArgs?: boolean;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make these two new args required instead of optional. Set to false whenever you don't have it

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
excludeFunctionArgs?: boolean;
excludeFunctionArgs: boolean;

}

interface GetTxWithEventsArgs extends GetTxArgs {
Expand Down Expand Up @@ -905,7 +907,10 @@ function parseDbBaseTx(dbTx: DbTx | DbMempoolTx): BaseTransaction {
return tx;
}

function parseDbTxTypeMetadata(dbTx: DbTx | DbMempoolTx): TransactionMetadata {
function parseDbTxTypeMetadata(
dbTx: DbTx | DbMempoolTx,
excludeFunctionArgs: boolean = false
): TransactionMetadata {
switch (dbTx.type_id) {
case DbTxTypeId.TokenTransfer: {
const metadata: TokenTransferTransactionMetadata = {
Expand Down Expand Up @@ -965,7 +970,7 @@ function parseDbTxTypeMetadata(dbTx: DbTx | DbMempoolTx): TransactionMetadata {
return metadata;
}
case DbTxTypeId.ContractCall: {
return parseContractCallMetadata(dbTx);
return parseContractCallMetadata(dbTx, excludeFunctionArgs);
}
case DbTxTypeId.PoisonMicroblock: {
const metadata: PoisonMicroblockTransactionMetadata = {
Expand Down Expand Up @@ -1052,7 +1057,10 @@ function parseDbTxTypeMetadata(dbTx: DbTx | DbMempoolTx): TransactionMetadata {
}
}

export function parseContractCallMetadata(tx: BaseTx): ContractCallTransactionMetadata {
export function parseContractCallMetadata(
tx: BaseTx,
excludeFunctionArgs: boolean = false
): ContractCallTransactionMetadata {
const contractId = unwrapOptional(
tx.contract_call_contract_id,
() => 'Unexpected nullish contract_call_contract_id'
Expand All @@ -1063,38 +1071,51 @@ export function parseContractCallMetadata(tx: BaseTx): ContractCallTransactionMe
);
let functionAbi: ClarityAbiFunction | undefined;
const abi = tx.abi;

if (abi) {
const contractAbi: ClarityAbi = JSON.parse(abi);
functionAbi = contractAbi.functions.find(fn => fn.name === functionName);
if (!functionAbi) {
throw new Error(`Could not find function name "${functionName}" in ABI for ${contractId}`);
throw new Error(`Could not find function name \"${functionName}\" in ABI for ${contractId}`);
}
}

const functionArgs = tx.contract_call_function_args
? decodeClarityValueList(tx.contract_call_function_args).map((c, fnArgIndex) => {
const functionArgAbi = functionAbi
? functionAbi.args[fnArgIndex++]
: { name: '', type: undefined };
const contractCall: {
contract_id: string;
function_name: string;
function_signature: string;
function_args?: {
hex: string;
repr: string;
name: string;
type: string;
}[];
} = {
contract_id: contractId,
function_name: functionName,
function_signature: functionAbi ? abiFunctionToString(functionAbi) : '',
};

// Only process function_args if not excluded
if (!excludeFunctionArgs && tx.contract_call_function_args) {
contractCall.function_args = decodeClarityValueList(tx.contract_call_function_args).map(
(c, idx) => {
const functionArgAbi = functionAbi ? functionAbi.args[idx] : { name: '', type: undefined };
return {
hex: c.hex,
repr: c.repr,
name: functionArgAbi.name,
type: functionArgAbi.type
name: functionArgAbi?.name || '',
type: functionArgAbi?.type
? getTypeString(functionArgAbi.type)
: decodeClarityValueToTypeName(c.hex),
};
})
: undefined;
}
);
}

const metadata: ContractCallTransactionMetadata = {
tx_type: 'contract_call',
contract_call: {
contract_id: contractId,
function_name: functionName,
function_signature: functionAbi ? abiFunctionToString(functionAbi) : '',
function_args: functionArgs,
},
contract_call: contractCall,
};
return metadata;
}
Expand Down Expand Up @@ -1154,21 +1175,24 @@ function parseDbAbstractMempoolTx(
return abstractMempoolTx;
}

export function parseDbTx(dbTx: DbTx): Transaction {
export function parseDbTx(dbTx: DbTx, excludeFunctionArgs: boolean = false): Transaction {
const baseTx = parseDbBaseTx(dbTx);
const abstractTx = parseDbAbstractTx(dbTx, baseTx);
const txMetadata = parseDbTxTypeMetadata(dbTx);
const txMetadata = parseDbTxTypeMetadata(dbTx, excludeFunctionArgs);
const result: Transaction = {
...abstractTx,
...txMetadata,
};
return result;
}

export function parseDbMempoolTx(dbMempoolTx: DbMempoolTx): MempoolTransaction {
export function parseDbMempoolTx(
dbMempoolTx: DbMempoolTx,
excludeFunctionArgs: boolean = false
): MempoolTransaction {
const baseTx = parseDbBaseTx(dbMempoolTx);
const abstractTx = parseDbAbstractMempoolTx(dbMempoolTx, baseTx);
const txMetadata = parseDbTxTypeMetadata(dbMempoolTx);
const txMetadata = parseDbTxTypeMetadata(dbMempoolTx, excludeFunctionArgs);
const result: MempoolTransaction = {
...abstractTx,
...txMetadata,
Expand All @@ -1189,7 +1213,9 @@ export async function getMempoolTxsFromDataStore(
return [];
}

const parsedMempoolTxs = mempoolTxsQuery.map(tx => parseDbMempoolTx(tx));
const parsedMempoolTxs = mempoolTxsQuery.map(tx =>
parseDbMempoolTx(tx, args.excludeFunctionArgs ?? false)
);

return parsedMempoolTxs;
}
Expand All @@ -1211,7 +1237,7 @@ async function getTxsFromDataStore(
}

// parsing txQuery
const parsedTxs = txQuery.map(tx => parseDbTx(tx));
const parsedTxs = txQuery.map(tx => parseDbTx(tx, args.excludeFunctionArgs ?? false));

// incase transaction events are requested
if ('eventLimit' in args) {
Expand Down Expand Up @@ -1261,7 +1287,7 @@ export async function getTxFromDataStore(
dbTx = txQuery.result;
}

const parsedTx = parseDbTx(dbTx);
const parsedTx = parseDbTx(dbTx, args.excludeFunctionArgs ?? false);

// If tx events are requested
if ('eventLimit' in args) {
Expand Down Expand Up @@ -1315,6 +1341,7 @@ export async function searchTxs(
const mempoolTxsQuery = await getMempoolTxsFromDataStore(db, {
txIds: mempoolTxs,
includeUnanchored: args.includeUnanchored,
excludeFunctionArgs: args.excludeFunctionArgs,
});

// merging found mempool transaction in found transactions object
Expand Down
11 changes: 9 additions & 2 deletions src/api/routes/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
PrincipalSchema,
UnanchoredParamSchema,
UntilBlockSchema,
ExcludeFunctionArgsParamSchema,
} from '../schemas/params';
import {
AddressBalance,
Expand Down Expand Up @@ -290,6 +291,7 @@ export const AddressRoutes: FastifyPluginAsync<
),
unanchored: UnanchoredParamSchema,
until_block: UntilBlockSchema,
exclude_function_args: ExcludeFunctionArgsParamSchema,
}),
response: {
200: AddressTransactionsListResponseSchema,
Expand All @@ -302,6 +304,7 @@ export const AddressRoutes: FastifyPluginAsync<
const untilBlock = parseUntilBlockQuery(req.query.until_block, req.query.unanchored);
const limit = getPagingQueryLimit(ResourceType.Tx, req.query.limit);
const offset = req.query.offset ?? 0;
const excludeFunctionArgs = req.query.exclude_function_args ?? false;

const response = await fastify.db.sqlTransaction(async sql => {
const blockParams = getBlockParams(req.query.height, req.query.unanchored);
Expand All @@ -327,7 +330,7 @@ export const AddressRoutes: FastifyPluginAsync<
blockHeight,
atSingleBlock,
});
const results = txResults.map(dbTx => parseDbTx(dbTx));
const results = txResults.map(dbTx => parseDbTx(dbTx, excludeFunctionArgs));
const response = { limit, offset, total, results };
return response;
});
Expand Down Expand Up @@ -671,6 +674,7 @@ export const AddressRoutes: FastifyPluginAsync<
limit: LimitParam(ResourceType.Tx),
offset: OffsetParam(),
unanchored: UnanchoredParamSchema,
exclude_function_args: ExcludeFunctionArgsParamSchema,
}),
response: {
200: PaginatedResponse(MempoolTransactionSchema, {
Expand All @@ -690,13 +694,16 @@ export const AddressRoutes: FastifyPluginAsync<
);
}
const includeUnanchored = req.query.unanchored ?? false;
const excludeFunctionArgs = req.query.exclude_function_args ?? false;
const { results: txResults, total } = await fastify.db.getMempoolTxList({
offset,
limit,
address,
includeUnanchored,
});
const results: MempoolTransaction[] = txResults.map(tx => parseDbMempoolTx(tx));
const results: MempoolTransaction[] = txResults.map(tx =>
parseDbMempoolTx(tx, excludeFunctionArgs)
);
const response = { limit, offset, total, results };
await reply.send(response);
}
Expand Down
1 change: 1 addition & 0 deletions src/api/routes/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ export const BlockRoutes: FastifyPluginAsync<
if (!block.found) {
throw new NotFoundError(`cannot find block by hash`);
}

await reply.send(block.result);
}
);
Expand Down
Loading