diff --git a/packages/projection/src/operators/Mappers/withHandles.ts b/packages/projection/src/operators/Mappers/withHandles.ts index 6bb0a59f062..f4267c5b276 100644 --- a/packages/projection/src/operators/Mappers/withHandles.ts +++ b/packages/projection/src/operators/Mappers/withHandles.ts @@ -1,12 +1,13 @@ import { Asset, Cardano } from '@cardano-sdk/core'; import { FilterByPolicyIds } from './types'; import { ProjectionOperator } from '../../types'; +import { WithMint } from './withMint'; import { isNotNil } from '@cardano-sdk/util'; import { map } from 'rxjs'; export interface Handle { handle: string; - address: Cardano.PaymentAddress; + address: Cardano.PaymentAddress | null; assetId: Cardano.AssetId; policyId: Cardano.PolicyId; datum?: Cardano.Datum; @@ -16,13 +17,15 @@ export interface WithHandles { handles: Handle[]; } +const handleFromAssetId = (assetId: Cardano.AssetId) => + Buffer.from(Asset.util.assetNameFromAssetId(assetId), 'hex').toString('utf8'); + export const withHandles = - ({ policyIds }: FilterByPolicyIds): ProjectionOperator => + ({ policyIds }: FilterByPolicyIds): ProjectionOperator => (evt$) => evt$.pipe( - map((evt) => ({ - ...evt, - handles: evt.block.body + map((evt) => { + const outputHandles = evt.block.body .flatMap(({ body: { outputs } }) => outputs.flatMap(({ address, value, datum }) => [...(value.assets?.entries() || [])].map(([assetId]): Handle | null => { @@ -32,12 +35,29 @@ export const withHandles = address, assetId, datum, - handle: Buffer.from(Asset.util.assetNameFromAssetId(assetId), 'hex').toString('utf8'), + handle: handleFromAssetId(assetId), policyId }; }) ) ) - .filter(isNotNil) - })) + .filter(isNotNil); + const lostHandles = evt.mint + .filter( + ({ policyId, assetId }) => + policyIds.includes(policyId) && !outputHandles.some((handle) => handle.assetId === assetId) + ) + .map( + ({ assetId, policyId }): Handle => ({ + address: null, + assetId, + handle: handleFromAssetId(assetId), + policyId + }) + ); + return { + ...evt, + handles: [...outputHandles, ...lostHandles] + }; + }) ); diff --git a/packages/projection/test/operators/Mappers/withHandle.test.ts b/packages/projection/test/operators/Mappers/withHandle.test.ts index a17f2d34994..8fd1d014548 100644 --- a/packages/projection/test/operators/Mappers/withHandle.test.ts +++ b/packages/projection/test/operators/Mappers/withHandle.test.ts @@ -1,7 +1,7 @@ import { Asset, Cardano } from '@cardano-sdk/core'; import { Buffer } from 'buffer'; import { HexBlob } from '@cardano-sdk/util'; -import { ProjectionEvent } from '../../../src'; +import { Mappers, ProjectionEvent } from '../../../src'; import { firstValueFrom, of } from 'rxjs'; import { withHandles } from '../../../src/operators/Mappers'; @@ -27,8 +27,9 @@ describe('withHandles', () => { } ]; const validTxSource$ = of({ - block: { body: [{ body: { outputs } }] } - } as ProjectionEvent); + block: { body: [{ body: { outputs } }] }, + mint: [] as Mappers.Mint[] + } as ProjectionEvent); const { handles } = await firstValueFrom( validTxSource$.pipe( withHandles({ @@ -40,6 +41,29 @@ describe('withHandles', () => { expect(handles[0].datum).toEqual(datum); }); + it('includes a handle with "null" address, when transaction burns a handle', async () => { + const validTxSource$ = of({ + block: { body: [{ body: { outputs: [] as Cardano.TxOut[] } }] }, + mint: [ + { + assetId: assetIdFromHandle('bob'), + compactTxId: 123, + policyId: handlePolicyId, + quantity: -1n + } + ] as Mappers.Mint[] + } as ProjectionEvent); + const { handles } = await firstValueFrom( + validTxSource$.pipe( + withHandles({ + policyIds: [handlePolicyId] + }) + ) + ); + expect(handles.length).toBe(1); + expect(handles[0].address).toBeNull(); + }); + it('maps and filters assets, from outputs, containing handles matching the given policy ID to an array of objects', async () => { const bobHandleOne = 'bob.handle.one'; const bobHandleTwo = 'bob.handle.two'; @@ -107,8 +131,9 @@ describe('withHandles', () => { } } ] - } - } as ProjectionEvent); + }, + mint: [] as Mappers.Mint[] + } as ProjectionEvent); const { handles } = await firstValueFrom( validTxSource$.pipe(