diff --git a/packages/bitcore-node/src/providers/chain-state/svm/api/csp.ts b/packages/bitcore-node/src/providers/chain-state/svm/api/csp.ts index 2cff5652f09..4c0eae53524 100644 --- a/packages/bitcore-node/src/providers/chain-state/svm/api/csp.ts +++ b/packages/bitcore-node/src/providers/chain-state/svm/api/csp.ts @@ -36,6 +36,11 @@ import type { SolRpc } from '@bitpay-labs/crypto-rpc/lib/sol/SolRpc'; export interface GetSolWeb3Response { rpc: SolRpc; connection: any; umi: any; dataType: string; lastPingTime?: number }; +function isMissingAtaError(err: any): boolean { + const message = err?.message || ''; + return message === 'Missing ATA' || message.includes('ATA not initialized'); +} + export class BaseSVMStateProvider extends InternalStateProvider implements IChainStateService { static rpcs: { [chainNetwork: string]: { historical: GetSolWeb3Response[]; realtime: GetSolWeb3Response[] } } = {}; static rpcIndicies: { [chainNetwork: string]: { historical: number; realtime: number } } = {}; @@ -285,13 +290,18 @@ export class BaseSVMStateProvider extends InternalStateProvider implements IChai let _address = address; if (tokenAddress) { try { - const { rpc } = await this.getRpc(network); _address = await rpc.getConfirmedAta({ solAddress: address, mintAddress: tokenAddress }); if (!_address) throw new Error('Missing ATA'); } catch (e: any) { - const errMsg = 'Error getting ATA address'; - logger.error(`${errMsg} %o`, e.stack || e.message || e); - throw new Error(errMsg); + if (isMissingAtaError(e)) { + // A closed ATA is not returned by getConfirmedAta, but its address can still have history. + _address = await rpc.deriveAta({ solAddress: address, mintAddress: tokenAddress }); + if (!_address) throw new Error('Missing ATA'); + } else { + const errMsg = 'Error getting ATA address'; + logger.error(`${errMsg} %o`, e.stack || e.message || e); + throw new Error(errMsg); + } } } do { diff --git a/packages/bitcore-node/test/integration/solana/csp.test.ts b/packages/bitcore-node/test/integration/solana/csp.test.ts index 0e2ec2c214c..fed309626fc 100644 --- a/packages/bitcore-node/test/integration/solana/csp.test.ts +++ b/packages/bitcore-node/test/integration/solana/csp.test.ts @@ -220,6 +220,58 @@ describe('Solana API', function() { expect(err).to.not.exist; expect(counter).to.be.gt(0); }); + + it('should stream empty SPL token history from derived ATA when current ATA is not initialized', async () => { + const tokenAddress = '11111111111111111111111111111112'; + const derivedAtaAddress = 'derivedTokenAccountAddress'; + const chunks: string[] = []; + const connection = { + getSignaturesForAddress: sandbox.stub().returns({ send: sandbox.stub().resolves([]) }) + }; + const rpc = { + getConfirmedAta: sandbox.stub().rejects(new Error('ATA not initialized on mint for provided account. Initialize ATA first.')), + deriveAta: sandbox.stub().resolves(derivedAtaAddress) + }; + + sandbox.stub(SOL, 'getRpc').resolves({ rpc, connection }); + sandbox.stub(SOL, 'getWalletAddresses').resolves([{ address }]); + + const req = (new Writable({ + write: function(_data, _, cb) { + cb(); + } + }) as unknown) as Request; + + const res = (new Writable({ + write: function(data, _, cb) { + chunks.push(data.toString()); + cb(); + } + }) as unknown) as Response; + res.type = () => res; + + const err = await new Promise(r => { + res + .on('error', r) + .on('finish', () => r(undefined)); + + SOL.streamWalletTransactions({ + chain, + network, + wallet, + req, + res, + args: { tokenAddress } + }) + .catch(e => r(e)); + }); + + expect(err).to.not.exist; + expect(chunks.join('')).to.equal(''); + expect(rpc.deriveAta.calledOnceWithExactly({ solAddress: address, mintAddress: tokenAddress })).to.be.true; + expect(connection.getSignaturesForAddress.calledOnce).to.be.true; + expect(connection.getSignaturesForAddress.firstCall.args[0]).to.equal(derivedAtaAddress); + }); }); it('should correctly transform transaction data', () => {