From 51d35f3ad2492de377fc2439ef8047acfdf2e961 Mon Sep 17 00:00:00 2001 From: Jack Ellis Date: Fri, 26 Jan 2024 20:40:27 +0000 Subject: [PATCH] fix: group activity by transaction --- .../__tests__/fetchVaultActivity.test.ts | 61 ++++--- .../core/src/vaults/fetchVaultActivity.ts | 150 +++++++++++------- 2 files changed, 134 insertions(+), 77 deletions(-) diff --git a/packages/core/src/vaults/__tests__/fetchVaultActivity.test.ts b/packages/core/src/vaults/__tests__/fetchVaultActivity.test.ts index cdd1dfa..c754a64 100644 --- a/packages/core/src/vaults/__tests__/fetchVaultActivity.test.ts +++ b/packages/core/src/vaults/__tests__/fetchVaultActivity.test.ts @@ -36,28 +36,28 @@ beforeEach(() => { // Sell { ...defaultActivityEvent, - id: 'MINT-0x0', + id: 'MINT-0x1', type: 'ZapSell', mintIds: ['1'], }, // Rdeem { ...defaultActivityEvent, - id: 'REDEEM-0x0', + id: 'REDEEM-0x2', type: 'Redeem', redeemIds: ['1'], }, // Buy { ...defaultActivityEvent, - id: 'REDEEM-0x0', + id: 'REDEEM-0x3', type: 'ZapBuy', redeemIds: ['1'], }, // Swap { ...defaultActivityEvent, - id: 'SWAP-0x0', + id: 'SWAP-0x4', type: 'Swap', swapMintIds: ['1'], swapRedeemIds: ['2'], @@ -65,43 +65,43 @@ beforeEach(() => { // LP Stake { ...defaultActivityEvent, - id: 'DEPOSIT-0x0', + id: 'DEPOSIT-0x5', type: 'AddLiquidity', }, // IP Stake { ...defaultActivityEvent, - id: 'DEPOSIT-0x0', + id: 'DEPOSIT-0x6', type: 'InventoryDeposit', }, // LP Unstake { ...defaultActivityEvent, - id: 'WITHDRAW-0x0', + id: 'WITHDRAW-0x7', type: 'RemoveLiquidity', }, // IP Unstake { ...defaultActivityEvent, - id: 'WITHDRAW-0x0', + id: 'WITHDRAW-0x8', type: 'InventoryWithdraw', }, // Created { ...defaultActivityEvent, - id: 'VAULT_CREATED-0x0', + id: 'VAULT_CREATED-0x9', type: 'VaultCreated', }, // Updated { ...defaultActivityEvent, - id: 'VAULT_FEE_UPDATE-0x0', + id: 'VAULT_FEE_UPDATE-0x10', type: 'VaultFeeUpdate', }, // Shutdown { ...defaultActivityEvent, - id: 'VAULT_SHUTDOWN-0x0', + id: 'VAULT_SHUTDOWN-0x11', type: 'VaultShutdown', }, ], @@ -137,7 +137,7 @@ it('returns a list of vault activity', async () => { eventType: 'ZapSell', source: 'source', tokenIds: ['1'], - txId: '0x0', + txId: '0x1', feeAmount: '1', }, { @@ -148,7 +148,7 @@ it('returns a list of vault activity', async () => { eventType: 'Redeem', source: 'source', tokenIds: ['1'], - txId: '0x0', + txId: '0x2', feeAmount: '1', }, { @@ -159,7 +159,7 @@ it('returns a list of vault activity', async () => { eventType: 'ZapBuy', source: 'source', tokenIds: ['1'], - txId: '0x0', + txId: '0x3', feeAmount: '1', }, { @@ -170,7 +170,7 @@ it('returns a list of vault activity', async () => { eventType: 'Swap', source: 'source', tokenIds: ['1'], - txId: '0x0', + txId: '0x4', feeAmount: '1', swapTokenIds: ['2'], }, @@ -181,7 +181,7 @@ it('returns a list of vault activity', async () => { date: 1, eventType: 'AddLiquidity', source: 'source', - txId: '0x0', + txId: '0x5', stakeType: 'liquidity', amount: '0', }, @@ -192,7 +192,7 @@ it('returns a list of vault activity', async () => { date: 1, eventType: 'InventoryDeposit', source: 'source', - txId: '0x0', + txId: '0x6', stakeType: 'inventory', amount: '0', }, @@ -202,6 +202,20 @@ it('returns a list of vault activity', async () => { expect(formatJson(result)).toEqual(expected); }); +it('groups activity by transaction id', async () => { + subgraphResponse.activityEvents.forEach((event: any) => { + const [type] = event.id.split('-'); + + event.id = [type, '0x0'].join('-'); + }); + + const result = await run(); + + expect(result).toHaveLength(1); + expect(result[0].type).toBe('stake'); + expect(result[0].eventType).toBe('AddLiquidity'); +}); + describe('when includeAllActivity is true', () => { beforeEach(() => { args.includeAllActivity = true; @@ -218,7 +232,7 @@ describe('when includeAllActivity is true', () => { date: 1, eventType: 'VaultCreated', source: 'source', - txId: '0x0', + txId: '0x9', }, { type: 'update', @@ -227,7 +241,7 @@ describe('when includeAllActivity is true', () => { date: 1, eventType: 'VaultFeeUpdate', source: 'source', - txId: '0x0', + txId: '0x10', }, { type: 'shutdown', @@ -236,7 +250,7 @@ describe('when includeAllActivity is true', () => { date: 1, eventType: 'VaultShutdown', source: 'source', - txId: '0x0', + txId: '0x11', }, ]; @@ -248,7 +262,12 @@ describe('when there are more than 1000 events', () => { beforeEach(() => { querySubgraph.mockResolvedValueOnce({ ...subgraphResponse, - activityEvents: Array(1000).fill(subgraphResponse.activityEvents[0]), + activityEvents: Array(1000) + .fill(subgraphResponse.activityEvents[0]) + .map((event, i) => ({ + ...event, + id: `MINT-0x000${i}`, + })), }); }); diff --git a/packages/core/src/vaults/fetchVaultActivity.ts b/packages/core/src/vaults/fetchVaultActivity.ts index ec0289e..858b7ef 100644 --- a/packages/core/src/vaults/fetchVaultActivity.ts +++ b/packages/core/src/vaults/fetchVaultActivity.ts @@ -25,6 +25,19 @@ type Type = | 'INVENTORY_DEPOSIT' | 'INVENTORY_DEPOSIT_WITH_NFT'; +const typePrecedence: VaultActivityType[] = [ + 'stake', + 'unstake', + 'buy', + 'sell', + 'swap', + 'redeem', + 'mint', + 'create', + 'update', + 'shutdown', +]; + type Response = NftxV3.Query & { activityEvents: NftxV3.ActivityEvent & { @@ -240,7 +253,6 @@ export const makeFetchVaultActivity = ({ async function fetchVaultActivity({ fromTimestamp, includeAllActivity = false, - lastId, network = config.network, toTimestamp, vaultAddresses, @@ -252,67 +264,93 @@ export const makeFetchVaultActivity = ({ vaultAddresses?: Address[]; vaultIds?: string[]; includeAllActivity?: boolean; - lastId?: string; }): Promise { - const query = createQuery() - .activityEvents.first(1000) - .orderBy('id') - .where((w) => [ - w.id.gt(lastId), - w.date.gt(fromTimestamp == null ? null : `${fromTimestamp}`), - w.date.lte(toTimestamp == null ? null : `${toTimestamp}`), - w.vault.in(vaultAddresses), - w.vault((v) => [v.vaultId.in(vaultIds)]), - ]) - .select((s) => [ - s.id, - s.source, - s.type, - s.date, - s.vault((v) => [v.id, v.vaultId]), - s.on('Mint', (s) => [ - s.nftIds.as('mintIds'), - s.feeReceipt((r) => [r.transfers((t) => [t.amount])]), - ]), - s.on('Redeem', (s) => [ - s.targetCount, - s.nftIds.as('redeemIds'), - s.feeReceipt((r) => [r.transfers((t) => [t.amount])]), - ]), - s.on('Swap', (s) => [ - s.mintedIds.as('swapMintIds'), - s.specificIds.as('swapRedeemIds'), - s.feeReceipt((r) => [r.transfers((t) => [t.amount])]), - ]), - ]); + let lastId: string | undefined; + const activity: VaultActivity[] = []; - const response = await querySubgraph({ - url: getChainConstant(config.subgraph.NFTX_SUBGRAPH, network), - query, - }); + do { + const query = createQuery() + .activityEvents.first(1000) + .orderBy('id') + .where((w) => [ + w.id.gt(lastId), + w.date.gt(fromTimestamp == null ? null : `${fromTimestamp}`), + w.date.lte(toTimestamp == null ? null : `${toTimestamp}`), + w.vault.in(vaultAddresses), + w.vault((v) => [v.vaultId.in(vaultIds)]), + ]) + .select((s) => [ + s.id, + s.source, + s.type, + s.date, + s.vault((v) => [v.id, v.vaultId]), + s.on('Mint', (s) => [ + s.nftIds.as('mintIds'), + s.feeReceipt((r) => [r.transfers((t) => [t.amount])]), + ]), + s.on('Redeem', (s) => [ + s.targetCount, + s.nftIds.as('redeemIds'), + s.feeReceipt((r) => [r.transfers((t) => [t.amount])]), + ]), + s.on('Swap', (s) => [ + s.mintedIds.as('swapMintIds'), + s.specificIds.as('swapRedeemIds'), + s.feeReceipt((r) => [r.transfers((t) => [t.amount])]), + ]), + ]); - const allActivity = response.activityEvents.map((e) => - activityEventToActivity(e as any, includeAllActivity) - ); + const response = await querySubgraph({ + url: getChainConstant(config.subgraph.NFTX_SUBGRAPH, network), + query, + }); - let activity = allActivity.filter(isDefined); + const allActivity = response.activityEvents + .map((e) => activityEventToActivity(e as any, includeAllActivity)) + .filter(isDefined); - if (response.activityEvents.length === 1000) { - const lastId = - response.activityEvents[response.activityEvents.length - 1].id; - const moreActivity = await fetchVaultActivity({ - fromTimestamp, - includeAllActivity, - network, - toTimestamp, - vaultAddresses, - vaultIds, - lastId, - }); - activity = [...activity, ...moreActivity]; - } + activity.push(...allActivity); + + if (response.activityEvents.length === 1000) { + lastId = response.activityEvents[response.activityEvents.length - 1].id; + } else { + lastId = undefined; + } + } while (lastId); + + return Object.values( + activity.reduce((acc, activity) => { + const { txId } = activity; + let arr = acc[txId]; + if (!arr) { + arr = acc[txId] = []; + } + arr.push(activity); + + return acc; + }, {} as Record) + ) + .map((activitySet) => { + activitySet.sort((a, b) => { + const aType = typePrecedence.indexOf(a.type); + const bType = typePrecedence.indexOf(b.type); + + if (aType === bType) { + return a.date - b.date; + } + + return aType - bType; + }); - return activity.sort((a, b) => a.date - b.date); + return activitySet.reduce((acc, activity) => { + return { + ...activity, + ...acc, + }; + }, activitySet[0]); + }) + .sort((a, b) => a.date - b.date); }; const fetchVaultActivity = makeFetchVaultActivity({ querySubgraph });