Skip to content

Commit

Permalink
Add a filter for token transfers;
Browse files Browse the repository at this point in the history
Make the fields of fetch options as nullable
  • Loading branch information
skubarenko committed Mar 22, 2024
1 parent 38f5270 commit e052e6b
Show file tree
Hide file tree
Showing 14 changed files with 534 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class TzKTBalancesProvider extends RemoteService implements BalancesBridg
: this.getAllTokenBalances(accountAddress, tokensOrFetchOptions?.offset, tokensOrFetchOptions?.limit);
}

protected async getAllTokenBalances(accountAddress: string, offset?: number, limit?: number) {
protected async getAllTokenBalances(accountAddress: string, offset?: number | null, limit?: number | null) {
loggerProvider.logger.log(`Getting balances of the all tokens for the ${accountAddress} address`);

let accountTokenBalances: AccountTokenBalances;
Expand Down Expand Up @@ -127,8 +127,8 @@ export class TzKTBalancesProvider extends RemoteService implements BalancesBridg
protected async getNonNativeTezosTokenBalances(
address: string,
tokenOrTokens: NonNativeTezosToken | readonly NonNativeTezosToken[] | null | undefined,
offset?: number,
limit?: number
offset?: number | null,
limit?: number | null
): Promise<AccountTokenBalances> {
const queryParams = this.getNonNativeTezosTokenBalancesQueryParams(address, tokenOrTokens);
if (offset && offset > 0)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { DipDupBridgeDataProviderOptions } from './dipDupBridgeDataProviderOptions';
import { DipDupGraphQLQueryBuilder } from './dipDupGraphQLQueryBuilder';
import { DipDupGraphQLQueryBuilder, type GraphQLTransfersFilter } from './dipDupGraphQLQueryBuilder';
import type { GraphQLResponse, BridgeOperationsDto, TokenBalancesDto } from './dtos';
import { DipDupAutoUpdateIsDisabledError, DipDupGraphQLError, DipDupTokenBalanceNotSupported, DipDupTokenTransferIdInvalid } from './errors';
import * as mappers from './mappers';
Expand Down Expand Up @@ -314,19 +314,21 @@ export class DipDupBridgeDataProvider extends RemoteService implements Transfers
): Promise<BridgeTokenTransfer[]> {
const offset = this.getPreparedOffsetParameter(fetchOptions);
const limit = this.getPreparedLimitParameter(fetchOptions);
const filter = this.mapTransfersFilterToGraphQLTransfersFilter(fetchOptions?.filter);

loggerProvider.lazyLogger.log?.(addressOrAddresses
? `Getting token transfers for ${typeof addressOrAddresses === 'string'
? `${addressOrAddresses} address.`
: `[${addressOrAddresses.join(', ')}] addresses.`}`
: 'Getting all token transfers.',
`Offset == ${offset}; Limit == ${limit}`
`Offset == ${offset}; Limit == ${limit}.`,
`Filter: ${JSON.stringify(filter)}.`
);

const bridgeOperationsResponse = await this.fetch<GraphQLResponse<BridgeOperationsDto>>('/v1/graphql', 'json', {
method: 'POST',
body: JSON.stringify({
query: this.dipDupGraphQLQueryBuilder.getTokenTransfersQuery(addressOrAddresses, offset, limit)
query: this.dipDupGraphQLQueryBuilder.getTokenTransfersQuery(addressOrAddresses, offset, limit, filter)
})
});
this.ensureNoDipDupGraphQLErrors(bridgeOperationsResponse);
Expand Down Expand Up @@ -400,6 +402,23 @@ export class DipDupBridgeDataProvider extends RemoteService implements Transfers
return new DipDupGraphQLQueryBuilder();
}

protected mapTransfersFilterToGraphQLTransfersFilter(filter: TransfersFetchOptions['filter']): GraphQLTransfersFilter | null {
if (!filter || (!filter.kind && !filter.status))
return null;

const type = filter.kind
? mappers.mapBridgeTokenTransferKindsToBridgeOperationDtoTypes(filter.kind)
: undefined;
const status = filter.status
? mappers.mapBridgeTokenTransferStatusesToBridgeOperationDtoStatuses(filter.status)
: undefined;

return {
type,
status
};
}

protected ensureNoDipDupGraphQLErrors<TData>(response: GraphQLResponse<TData>) {
if (!response.errors || !response.errors.length)
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ interface DipDupGraphQLQueryBuilderQueryParts {
readonly l2TokenHolderFields: string;
}

export interface GraphQLTransfersFilter {
type?: Array<'deposit' | 'withdrawal'> | null;
status?: Array<'Created' | 'Sealed' | 'Finished' | 'Failed'> | null;
}

export class DipDupGraphQLQueryBuilder {
protected static readonly defaultQueryParts: DipDupGraphQLQueryBuilderQueryParts = {
getBridgeOperationsFields,
Expand Down Expand Up @@ -49,12 +54,14 @@ export class DipDupGraphQLQueryBuilder {
getTokenTransfersQuery(
addressOrAddresses: string | readonly string[] | undefined | null,
offset: number,
limit: number
limit: number,
filter?: GraphQLTransfersFilter | undefined | null
): string {
return this.getTokenTransfersQueryOrSteamSubscription(
addressOrAddresses,
'query',
'bridge_operation',
this.getTransfersFilterWhereCondition(filter),
`order_by: { created_at: desc }, offset: ${offset}, limit: ${limit}`
);
}
Expand All @@ -68,6 +75,7 @@ export class DipDupGraphQLQueryBuilder {
addressOrAddresses,
'subscription',
'bridge_operation_stream',
undefined,
`batch_size: ${batchSize}, cursor: {initial_value: {updated_at: "${startUpdatedAt.toISOString()}"}, ordering: ASC}`
);
}
Expand Down Expand Up @@ -167,22 +175,17 @@ export class DipDupGraphQLQueryBuilder {
addressOrAddresses: string | readonly string[] | undefined | null,
queryType: string,
rootFieldName: string,
transfersFilterWhereCondition: string | undefined | null,
queryExtraArguments: string
): GraphQLQuery {
let whereArgument = '';

if (addressOrAddresses) {
if (typeof addressOrAddresses === 'string' || (addressOrAddresses.length === 1)) {
const address = typeof addressOrAddresses === 'string' ? addressOrAddresses : addressOrAddresses[0]!;
whereArgument = this.getTokenTransfersWhereArgumentForOneAddress(address);
}
else if (addressOrAddresses.length > 1) {
whereArgument = this.getTokenTransfersWhereArgumentForMultipleAddresses(addressOrAddresses);
}
}

if (whereArgument)
whereArgument += ',';
const addressOrAddressesWhereCondition = this.getTokenTransfersByAddressOrAddressesWhereCondition(addressOrAddresses);
const whereArgument = transfersFilterWhereCondition && addressOrAddressesWhereCondition
? `where: { ${transfersFilterWhereCondition}, ${addressOrAddressesWhereCondition} },`
: transfersFilterWhereCondition
? `where: { ${transfersFilterWhereCondition} },`
: addressOrAddressesWhereCondition
? `where: { ${addressOrAddressesWhereCondition} },`
: '';

return `${queryType} TokenTransfers {
${rootFieldName}(
Expand All @@ -194,7 +197,45 @@ export class DipDupGraphQLQueryBuilder {
}`;
}

private getTokenTransfersWhereArgumentForOneAddress(address: string): string {
protected getTransfersFilterWhereCondition(filter: GraphQLTransfersFilter | undefined | null): string {
if (!filter)
return '';

let condition = '';

if (filter.type) {
condition += filter.type.length === 1
? `type: { _eq: "${filter.type[0]}" }`
: `type: { _in: ${this.arrayToInOperatorValue(filter.type)} }`;
}

if (filter.status) {
if (condition)
condition += ', ';

condition += filter.status.length === 1
? `status: { _eq: "${filter.status[0]}" }`
: `status: { _in: ${this.arrayToInOperatorValue(filter.status)} }`;
}

return condition;
}

private getTokenTransfersByAddressOrAddressesWhereCondition(addressOrAddresses: string | readonly string[] | undefined | null) {
if (addressOrAddresses) {
if (typeof addressOrAddresses === 'string' || (addressOrAddresses.length === 1)) {
const address = typeof addressOrAddresses === 'string' ? addressOrAddresses : addressOrAddresses[0]!;
return this.getTokenTransfersByOneAddressWhereCondition(address);
}
else if (addressOrAddresses.length > 1) {
return this.getTokenTransfersByMultipleAddressesWhereCondition(addressOrAddresses);
}
}

return '';
}

private getTokenTransfersByOneAddressWhereCondition(address: string): string {
let accountFieldName: string;
let preparedAddress = address;

Expand All @@ -206,15 +247,13 @@ export class DipDupGraphQLQueryBuilder {
accountFieldName = 'l1_account';
}

return `where: {
_or: [
{ deposit: { l1_transaction: { ${accountFieldName}: { _eq: "${preparedAddress}" } } } }
{ withdrawal: { l2_transaction: { ${accountFieldName}: { _eq: "${preparedAddress}" } } } }
]
}`;
return `_or: [
{ deposit: { l1_transaction: { ${accountFieldName}: { _eq: "${preparedAddress}" } } } }
{ withdrawal: { l2_transaction: { ${accountFieldName}: { _eq: "${preparedAddress}" } } } }
]`;
}

private getTokenTransfersWhereArgumentForMultipleAddresses(addresses: readonly string[]): string {
private getTokenTransfersByMultipleAddressesWhereCondition(addresses: readonly string[]): string {
const { tezosAddresses, etherlinkAddresses } = this.splitAddressesToTezosAndEtherlinkAddresses(addresses);
const isNeedRootOrOperator = tezosAddresses.length > 0 && etherlinkAddresses.length > 0;

Expand All @@ -239,7 +278,7 @@ export class DipDupGraphQLQueryBuilder {
if (isNeedRootOrOperator)
transactionCondition += ' ] }';

return `where: { _or: [
return `_or: [
{
deposit: {
l1_transaction: ${transactionCondition}
Expand All @@ -250,7 +289,7 @@ export class DipDupGraphQLQueryBuilder {
l2_transaction: ${transactionCondition}
}
}
] }`;
]`;
}

private splitAddressesToTezosAndEtherlinkAddresses(
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { DipDupGraphQLQueryBuilder } from './dipDupGraphQLQueryBuilder';
export { DipDupGraphQLQueryBuilder, type GraphQLTransfersFilter } from './dipDupGraphQLQueryBuilder';
1 change: 1 addition & 0 deletions src/bridgeDataProviders/dipDupBridgeDataProvider/dtos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export interface BridgeWithdrawalDto {

export interface BridgeOperationDto {
type: 'deposit' | 'withdrawal';
status: 'Created' | 'Sealed' | 'Finished' | 'Failed';
is_completed: boolean;
is_successful: boolean;
created_at: string;
Expand Down
2 changes: 1 addition & 1 deletion src/bridgeDataProviders/dipDupBridgeDataProvider/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { DipDupBridgeDataProvider } from './dipDupBridgeDataProvider';
export type { DipDupBridgeDataProviderOptions } from './dipDupBridgeDataProviderOptions';

export { DipDupGraphQLQueryBuilder } from './dipDupGraphQLQueryBuilder';
export { DipDupGraphQLQueryBuilder, type GraphQLTransfersFilter } from './dipDupGraphQLQueryBuilder';
42 changes: 42 additions & 0 deletions src/bridgeDataProviders/dipDupBridgeDataProvider/mappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,3 +248,45 @@ export const mapTokenBalancesDtoToAccountTokenBalances = (dto: TokenBalancesDto,
} satisfies AccountTokenBalances;
}
};

const tokenTransferKindToBridgeOperationDtoTypeMap = new Map<BridgeTokenTransferKind, BridgeOperationDto['type']>()
.set(BridgeTokenTransferKind.Deposit, 'deposit')
.set(BridgeTokenTransferKind.Withdrawal, 'withdrawal');

export const mapBridgeTokenTransferKindToBridgeOperationDtoType = (kind: BridgeTokenTransferKind): BridgeOperationDto['type'] | null => {
return tokenTransferKindToBridgeOperationDtoTypeMap.get(kind) || null;
};

export const mapBridgeTokenTransferKindsToBridgeOperationDtoTypes = (kinds: readonly BridgeTokenTransferKind[]): Array<BridgeOperationDto['type']> => {
const result: Array<BridgeOperationDto['type']> = [];

for (const kind of kinds) {
const type = mapBridgeTokenTransferKindToBridgeOperationDtoType(kind);
if (type)
result.push(type);
}

return result;
};

const tokenTransferStatusToBridgeOperationDtoStatusMap = new Map<BridgeTokenTransferStatus, BridgeOperationDto['status']>()
.set(BridgeTokenTransferStatus.Created, 'Created')
.set(BridgeTokenTransferStatus.Sealed, 'Sealed')
.set(BridgeTokenTransferStatus.Finished, 'Finished')
.set(BridgeTokenTransferStatus.Failed, 'Failed');

export const mapBridgeTokenTransferStatusToBridgeOperationDtoStatus = (status: BridgeTokenTransferStatus): BridgeOperationDto['status'] | null => {
return tokenTransferStatusToBridgeOperationDtoStatusMap.get(status) || null;
};

export const mapBridgeTokenTransferStatusesToBridgeOperationDtoStatuses = (statuses: readonly BridgeTokenTransferStatus[]): Array<BridgeOperationDto['status']> => {
const result: Array<BridgeOperationDto['status']> = [];

for (const status of statuses) {
const type = mapBridgeTokenTransferStatusToBridgeOperationDtoStatus(status);
if (type)
result.push(type);
}

return result;
};
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class LocalTokensBridgeDataProvider implements TokensBridgeDataProvider {
const offset = fetchOptions.offset || 0;
const limit = fetchOptions.limit && (fetchOptions.limit + offset);

return Promise.resolve(this.tokenPairs.slice(offset, limit));
return Promise.resolve(this.tokenPairs.slice(offset, limit || undefined));
}

private createTokenPairsByTokenMap(tokenPairs: readonly TokenPair[]): TokenPairsByTokenMap {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { BridgeTokenTransferKind, BridgeTokenTransferStatus } from '../../bridgeCore';
import type { FetchOptions } from '../../common';

export interface TransfersFetchOptions extends FetchOptions {
filter?: {
kind?: BridgeTokenTransferKind[] | null;
status?: BridgeTokenTransferStatus[] | null;
}
}
4 changes: 2 additions & 2 deletions src/common/fetchOptions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface FetchOptions {
offset?: number;
limit?: number;
offset?: number | null;
limit?: number | null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe('DipDup GraphQL Query Builder', () => {
test.each(getTokenTransfersQueryTestCases)(
'Build the getTokenTransfers query %s',
(_, testData) => {
const query = queryBuilder.getTokenTransfersQuery(null, testData.offset, testData.limit);
const query = queryBuilder.getTokenTransfersQuery(null, testData.offset, testData.limit, testData.filter);
const preparedQuery = prepareQueryFormatting(query);

expect(preparedQuery).toBe(testData.expectedQuery);
Expand All @@ -51,7 +51,7 @@ describe('DipDup GraphQL Query Builder', () => {
test.each(getTokenTransfersQueryByAccountAddressesTestCases)(
'Build the getTokenTransfers query %s',
(_, testData) => {
const query = queryBuilder.getTokenTransfersQuery(testData.address, testData.offset, testData.limit);
const query = queryBuilder.getTokenTransfersQuery(testData.address, testData.offset, testData.limit, testData.filter);
const preparedQuery = prepareQueryFormatting(query);

expect(preparedQuery).toBe(testData.expectedQuery);
Expand Down
Loading

0 comments on commit e052e6b

Please sign in to comment.