diff --git a/src/token-processor/queue/job/process-token-job.ts b/src/token-processor/queue/job/process-token-job.ts index 9cf87120..6aa8e408 100644 --- a/src/token-processor/queue/job/process-token-job.ts +++ b/src/token-processor/queue/job/process-token-job.ts @@ -13,7 +13,7 @@ import { DbTokenType, } from '../../../pg/types'; import { StacksNodeRpcClient } from '../../stacks-node/stacks-node-rpc-client'; -import { TooManyRequestsHttpError } from '../../util/errors'; +import { StacksNodeClarityError, TooManyRequestsHttpError } from '../../util/errors'; import { fetchAllMetadataLocalesFromBaseUri, getFetchableUrl, @@ -106,9 +106,15 @@ export class ProcessTokenJob extends Job { } let fTotalSupply: PgNumeric | undefined; - const totalSupply = await client.readUIntFromContract('get-total-supply'); - if (totalSupply) { - fTotalSupply = totalSupply.toString(); + try { + const totalSupply = await client.readUIntFromContract('get-total-supply'); + if (totalSupply) fTotalSupply = totalSupply.toString(); + } catch (error) { + // We'll treat Clarity errors here as if the supply was `undefined` to accommodate ALEX's + // wrapped tokens which return an error in `get-total-supply`. + if (!(error instanceof StacksNodeClarityError)) { + throw error; + } } let metadataLocales: DbMetadataLocaleInsertBundle[] | undefined; diff --git a/tests/process-token-job.test.ts b/tests/process-token-job.test.ts index 584c22f9..c5448c2a 100644 --- a/tests/process-token-job.test.ts +++ b/tests/process-token-job.test.ts @@ -189,6 +189,71 @@ describe('ProcessTokenJob', () => { }); expect(bundle?.metadataLocale).toBeUndefined(); }); + + test('accepts FTs with incorrect total supply return type', async () => { + const agent = new MockAgent(); + agent.disableNetConnect(); + const interceptor = agent.get( + `http://${ENV.STACKS_NODE_RPC_HOST}:${ENV.STACKS_NODE_RPC_PORT}` + ); + interceptor + .intercept({ + path: '/v2/contracts/call-read/ABCD/test-ft/get-name', + method: 'POST', + }) + .reply(200, { + okay: true, + result: cvToHex(stringUtf8CV('FooToken')), + }); + interceptor + .intercept({ + path: '/v2/contracts/call-read/ABCD/test-ft/get-token-uri', + method: 'POST', + }) + .reply(200, { + okay: true, + result: cvToHex(noneCV()), + }); + interceptor + .intercept({ + path: '/v2/contracts/call-read/ABCD/test-ft/get-symbol', + method: 'POST', + }) + .reply(200, { + okay: true, + result: cvToHex(stringUtf8CV('FOO')), + }); + interceptor + .intercept({ + path: '/v2/contracts/call-read/ABCD/test-ft/get-decimals', + method: 'POST', + }) + .reply(200, { + okay: true, + result: cvToHex(uintCV(6)), + }); + interceptor + .intercept({ + path: '/v2/contracts/call-read/ABCD/test-ft/get-total-supply', + method: 'POST', + }) + .reply(200, { + okay: true, + // Simulate an ALEX-style error when fetching `get-total-supply` for wrapped tokens. + result: '0x080100000000000000000000000000001774', + }); + setGlobalDispatcher(agent); + + const processor = new ProcessTokenJob({ db, job: tokenJob }); + await processor.work(); + + const token = await db.getToken({ id: 1 }); + expect(token).not.toBeUndefined(); + expect(token?.name).toBe('FooToken'); + expect(token?.symbol).toBe('FOO'); + expect(token?.decimals).toBe(6); + expect(token?.total_supply).toBeUndefined(); + }); }); describe('NFT', () => {