Skip to content

Commit

Permalink
feat: make token metadata processing opt-in
Browse files Browse the repository at this point in the history
  • Loading branch information
zone117x committed Sep 1, 2021
1 parent b9b5984 commit 23f4faa
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 13 deletions.
4 changes: 4 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ MAINNET_SEND_MANY_CONTRACT_ID=SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.send-man
# Override the default file path for the proxy cache control file
# STACKS_API_PROXY_CACHE_CONTROL_FILE=/path/to/.proxy-cache-control.json

# Enable token metadata processing. Disabled by default.
# STACKS_API_ENABLE_FT_METADATA=1
# STACKS_API_ENABLE_NFT_METADATA=1

# Configure a script to handle image URLs during token metadata processing.
# This example script uses the `imgix.net` service to create CDN URLs.
# Must be an executable script that accepts the URL as the first program argument
Expand Down
28 changes: 28 additions & 0 deletions src/api/routes/tokens/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import {
NonFungibleTokensMetadataList,
} from '@stacks/stacks-blockchain-api-types';
import { parseLimitQuery, parsePagingQueryInput } from './../../pagination';
import {
isFtMetadataEnabled,
isNftMetadataEnabled,
} from '../../../event-stream/tokens-contract-handler';

const MAX_TOKENS_PER_REQUEST = 200;
const parseTokenQueryLimit = parseLimitQuery({
Expand All @@ -20,6 +24,12 @@ export function createTokenRouter(db: DataStore): RouterWithAsync {
router.use(express.json());

router.getAsync('/ft/metadata', async (req, res) => {
if (!isFtMetadataEnabled()) {
return res.status(500).json({
error: 'FT metadata processing is not enabled on this server',
});
}

const limit = parseTokenQueryLimit(req.query.limit ?? 96);
const offset = parsePagingQueryInput(req.query.offset ?? 0);

Expand All @@ -36,6 +46,12 @@ export function createTokenRouter(db: DataStore): RouterWithAsync {
});

router.getAsync('/nft/metadata', async (req, res) => {
if (!isNftMetadataEnabled()) {
return res.status(500).json({
error: 'NFT metadata processing is not enabled on this server',
});
}

const limit = parseTokenQueryLimit(req.query.limit ?? 96);
const offset = parsePagingQueryInput(req.query.offset ?? 0);

Expand All @@ -53,6 +69,12 @@ export function createTokenRouter(db: DataStore): RouterWithAsync {

//router for fungible tokens
router.getAsync('/:contractId/ft/metadata', async (req, res) => {
if (!isFtMetadataEnabled()) {
return res.status(500).json({
error: 'FT metadata processing is not enabled on this server',
});
}

const { contractId } = req.params;

const metadata = await db.getFtMetadata(contractId);
Expand Down Expand Up @@ -89,6 +111,12 @@ export function createTokenRouter(db: DataStore): RouterWithAsync {

//router for non-fungible tokens
router.getAsync('/:contractId/nft/metadata', async (req, res) => {
if (!isNftMetadataEnabled()) {
return res.status(500).json({
error: 'NFT metadata processing is not enabled on this server',
});
}

const { contractId } = req.params;
const metadata = await db.getNftMetadata(contractId);

Expand Down
4 changes: 2 additions & 2 deletions src/datastore/postgres-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ import {
AddressUnlockSchedule,
} from '@stacks/stacks-blockchain-api-types';
import { getTxTypeId } from '../api/controllers/db-controller';
import { isCompliantToken } from '../event-stream/tokens-contract-handler';
import { isProcessableTokenMetadata } from '../event-stream/tokens-contract-handler';
import { ClarityAbi } from '@stacks/transactions';

const MIGRATIONS_TABLE = 'pgmigrations';
Expand Down Expand Up @@ -1069,7 +1069,7 @@ export class PgDataStore
};
return queueEntry;
})
.filter(entry => isCompliantToken(entry.contractAbi));
.filter(entry => isProcessableTokenMetadata(entry.contractAbi));
for (const pendingQueueEntry of tokenContractDeployments) {
const queueEntry = await this.updateTokenMetadataQueue(client, pendingQueueEntry);
tokenMetadataQueueEntries.push(queueEntry);
Expand Down
20 changes: 18 additions & 2 deletions src/event-stream/tokens-contract-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ const METADATA_MAX_PAYLOAD_BYTE_SIZE = 1_000_000; // 1 megabyte

const PUBLIC_IPFS = 'https://ipfs.io';

export function isFtMetadataEnabled() {
const opt = process.env['STACKS_API_ENABLE_FT_METADATA']?.toLowerCase().trim();
return opt === '1' || opt === 'true';
}

export function isNftMetadataEnabled() {
const opt = process.env['STACKS_API_ENABLE_NFT_METADATA']?.toLowerCase().trim();
return opt === '1' || opt === 'true';
}

const FT_FUNCTIONS: ClarityAbiFunction[] = [
{
access: 'public',
Expand Down Expand Up @@ -195,8 +205,14 @@ export interface TokenHandlerArgs {
dbQueueId: number;
}

export function isCompliantToken(abi: ClarityAbi): boolean {
return isCompliantFt(abi) || isCompliantNft(abi);
/**
* Checks if the given ABI contains functions from FT or NFT metadata standards (e.g. sip-09, sip-10) which can be resolved.
* The function also checks if the server has FT and/or NFT metadata processing enabled.
*/
export function isProcessableTokenMetadata(abi: ClarityAbi): boolean {
return (
(isFtMetadataEnabled() && isCompliantFt(abi)) || (isNftMetadataEnabled() && isCompliantNft(abi))
);
}

function isCompliantNft(abi: ClarityAbi): boolean {
Expand Down
24 changes: 15 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import { cycleMigrations, dangerousDropAllTables, PgDataStore } from './datastor
import { MemoryDataStore } from './datastore/memory-store';
import { startApiServer } from './api/init';
import { startEventServer } from './event-stream/event-server';
import { TokensProcessorQueue } from './event-stream/tokens-contract-handler';
import {
isFtMetadataEnabled,
isNftMetadataEnabled,
TokensProcessorQueue,
} from './event-stream/tokens-contract-handler';
import { StacksCoreRpcClient } from './core-rpc/client';
import { createServer as createPrometheusServer } from '@promster/server';
import { ChainID } from '@stacks/transactions';
Expand Down Expand Up @@ -138,14 +142,16 @@ async function init(): Promise<void> {
logger.error(`Error monitoring RPC connection: ${error}`, error);
});

const tokenMetadataProcessor = new TokensProcessorQueue(db, configuredChainID);
registerShutdownConfig({
name: 'Token Metadata Processor',
handler: () => tokenMetadataProcessor.close(),
forceKillable: true,
});
// check if db has any non-processed token queues and await them all here
await tokenMetadataProcessor.drainDbQueue();
if (isFtMetadataEnabled() || isNftMetadataEnabled()) {
const tokenMetadataProcessor = new TokensProcessorQueue(db, configuredChainID);
registerShutdownConfig({
name: 'Token Metadata Processor',
handler: () => tokenMetadataProcessor.close(),
forceKillable: true,
});
// check if db has any non-processed token queues and await them all here
await tokenMetadataProcessor.drainDbQueue();
}
}

const apiServer = await startApiServer({ datastore: db, chainId: getConfiguredChainID() });
Expand Down
19 changes: 19 additions & 0 deletions src/tests-tokens/tokens-metadata-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,28 @@ describe('api tests', () => {
});

beforeEach(() => {
process.env['STACKS_API_ENABLE_FT_METADATA'] = '1';
process.env['STACKS_API_ENABLE_NFT_METADATA'] = '1';
nock.cleanAll();
});

test('metadata disabled', async () => {
process.env['STACKS_API_ENABLE_FT_METADATA'] = '0';
process.env['STACKS_API_ENABLE_NFT_METADATA'] = '0';
const query1 = await supertest(api.server).get(`/extended/v1/tokens/nft/metadata`);
expect(query1.status).toBe(500);
expect(query1.body.error).toMatch(/not enabled/);
const query2 = await supertest(api.server).get(`/extended/v1/tokens/ft/metadata`);
expect(query2.status).toBe(500);
expect(query2.body.error).toMatch(/not enabled/);
const query3 = await supertest(api.server).get(`/extended/v1/tokens/example/nft/metadata`);
expect(query3.status).toBe(500);
expect(query3.body.error).toMatch(/not enabled/);
const query4 = await supertest(api.server).get(`/extended/v1/tokens/example/ft/metadata`);
expect(query4.status).toBe(500);
expect(query4.body.error).toMatch(/not enabled/);
});

test('token nft-metadata data URL plain percent-encoded', async () => {
const contract1 = await deployContract(
'beeple-a',
Expand Down

0 comments on commit 23f4faa

Please sign in to comment.