From 86e42064a02e1aabb6e4b2ea1d53157b4e21a7ef Mon Sep 17 00:00:00 2001 From: Rhys Bartels-Waller Date: Wed, 13 Jan 2021 16:11:22 +1100 Subject: [PATCH] feature: Multi-asset support Adds support for the multi-asset ledger: - `Token` type - `tokens` and `tokens_aggregate` queries. - `TransactionOutput.tokens` and `TransactionInput.tokens` contains all non-ada assets being transacted, with TransactionOutput.value` remaining as the single value required for ada. - `Transaction.mint` is a nullable field. - 'paymentAddresses' query returns a count of the assets associated with the address/es provided --- .../hasura/project/metadata/tables.yaml | 86 ++++++++++++ .../migrations/1589369664961_init/down.sql | 2 + .../migrations/1589369664961_init/up.sql | 28 +++- packages/api-cardano-db-hasura/schema.graphql | 124 ++++++++++++++++++ .../api-cardano-db-hasura/src/HasuraClient.ts | 89 ++++++++++++- .../paymentAddress/summary.graphql | 13 ++ .../tokens/distinctAssets.graphql | 21 +++ .../src/example_queries/tokens/tokens.graphql | 23 ++++ .../transactionsByHashesWithTokens.graphql | 60 +++++++++ .../src/executableSchema.ts | 44 ++++++- .../transactions.query.test.ts.snap | 44 +++---- .../getAnyUtxoAddress.graphql | 5 + .../test/paymentAddress.query.test.ts | 70 ++++++++++ .../test/tokens.query.test.ts | 38 ++++++ packages/server/src/Server.ts | 2 - packages/server/src/config.ts | 4 +- packages/util/test/DataFetcher.test.ts | 4 +- 17 files changed, 622 insertions(+), 35 deletions(-) create mode 100644 packages/api-cardano-db-hasura/src/example_queries/paymentAddress/summary.graphql create mode 100644 packages/api-cardano-db-hasura/src/example_queries/tokens/distinctAssets.graphql create mode 100644 packages/api-cardano-db-hasura/src/example_queries/tokens/tokens.graphql create mode 100644 packages/api-cardano-db-hasura/src/example_queries/transactions/transactionsByHashesWithTokens.graphql create mode 100644 packages/api-cardano-db-hasura/test/paymentAddress.query.test.ts create mode 100644 packages/api-cardano-db-hasura/test/tokens.query.test.ts diff --git a/packages/api-cardano-db-hasura/hasura/project/metadata/tables.yaml b/packages/api-cardano-db-hasura/hasura/project/metadata/tables.yaml index 00f82ff0..1e30d00f 100644 --- a/packages/api-cardano-db-hasura/hasura/project/metadata/tables.yaml +++ b/packages/api-cardano-db-hasura/hasura/project/metadata/tables.yaml @@ -219,6 +219,29 @@ filter: {} limit: 2500 allow_aggregations: true +- table: + schema: public + name: Mint + object_relationships: + - name: transaction + using: + manual_configuration: + remote_table: + schema: public + name: Transaction + column_mapping: + tx_id: id + select_permissions: + - role: cardano-graphql + permission: + columns: + - assetId + - assetName + - policyId + - quantity + filter: {} + limit: 2500 + allow_aggregations: true - table: schema: public name: Reward @@ -451,6 +474,34 @@ filter: {} limit: 2500 allow_aggregations: true +- table: + schema: public + name: Token + configuration: + custom_root_fields: + select_aggregate: tokens_aggregate + select: tokens + custom_column_names: {} + object_relationships: + - name: transactionOutput + using: + manual_configuration: + remote_table: + schema: public + name: TransactionOutput + column_mapping: + tx_out_id: id + select_permissions: + - role: cardano-graphql + permission: + columns: + - assetId + - assetName + - policyId + - quantity + filter: {} + limit: 2500 + allow_aggregations: true - table: schema: public name: Transaction @@ -485,6 +536,14 @@ name: tx_metadata column_mapping: id: tx_id + - name: mint + using: + manual_configuration: + remote_table: + schema: public + name: Mint + column_mapping: + id: tx_id - name: outputs using: manual_configuration: @@ -538,6 +597,15 @@ name: Transaction column_mapping: txHash: hash + array_relationships: + - name: tokens + using: + manual_configuration: + remote_table: + schema: public + name: Token + column_mapping: + source_tx_out_id: tx_out_id select_permissions: - role: cardano-graphql permission: @@ -562,6 +630,15 @@ name: Transaction column_mapping: txHash: hash + array_relationships: + - name: tokens + using: + manual_configuration: + remote_table: + schema: public + name: Token + column_mapping: + id: tx_out_id select_permissions: - role: cardano-graphql permission: @@ -590,6 +667,15 @@ name: Transaction column_mapping: txHash: hash + array_relationships: + - name: tokens + using: + manual_configuration: + remote_table: + schema: public + name: Token + column_mapping: + id: tx_out_id select_permissions: - role: cardano-graphql permission: diff --git a/packages/api-cardano-db-hasura/hasura/project/migrations/1589369664961_init/down.sql b/packages/api-cardano-db-hasura/hasura/project/migrations/1589369664961_init/down.sql index 68743c37..816d1088 100644 --- a/packages/api-cardano-db-hasura/hasura/project/migrations/1589369664961_init/down.sql +++ b/packages/api-cardano-db-hasura/hasura/project/migrations/1589369664961_init/down.sql @@ -4,6 +4,7 @@ DROP VIEW IF EXISTS "Cardano", "Delegation", "Epoch", + "Mint", "ShelleyEpochProtocolParams", "Reward", "SlotLeader", @@ -11,6 +12,7 @@ DROP VIEW IF EXISTS "StakePool", "StakeRegistration", "StakePoolRetirement", + "Token", "Transaction", "TransactionInput", "TransactionOutput", diff --git a/packages/api-cardano-db-hasura/hasura/project/migrations/1589369664961_init/up.sql b/packages/api-cardano-db-hasura/hasura/project/migrations/1589369664961_init/up.sql index 2317f60b..d6e32053 100644 --- a/packages/api-cardano-db-hasura/hasura/project/migrations/1589369664961_init/up.sql +++ b/packages/api-cardano-db-hasura/hasura/project/migrations/1589369664961_init/up.sql @@ -170,6 +170,24 @@ SELECT ( SELECT pool_hash.hash_raw FROM pool_hash WHERE pool_hash.id = pool_id ) AS "pool_hash" FROM epoch_stake; +CREATE VIEW "Mint" AS +SELECT + CONCAT(policy,name) as "assetId", + name as "assetName", + policy as "policyId", + quantity, + tx_id +FROM ma_tx_mint; + +CREATE VIEW "Token" AS +SELECT + CONCAT(policy,name) as "assetId", + name as "assetName", + policy as "policyId", + quantity, + tx_out_id +FROM ma_tx_out; + CREATE VIEW "Transaction" AS SELECT block.hash AS "blockHash", @@ -194,7 +212,8 @@ SELECT source_tx_out.value, tx.hash AS "txHash", source_tx.hash AS "sourceTxHash", - tx_in.tx_out_index AS "sourceTxIndex" + tx_in.tx_out_index AS "sourceTxIndex", + source_tx_out.id AS source_tx_out_id FROM tx JOIN tx_in @@ -210,6 +229,7 @@ SELECT address, value, tx.hash AS "txHash", + tx_out.id, index FROM tx JOIN tx_out @@ -219,6 +239,7 @@ CREATE VIEW "Utxo" AS SELECT address, value, tx.hash AS "txHash", + tx_out.id, index FROM tx JOIN tx_out @@ -246,8 +267,8 @@ CREATE INDEX idx_block_hash CREATE INDEX idx_tx_hash ON tx(hash); - CREATE INDEX idx_tx_in_consuming_tx - ON tx_in(tx_out_id); +CREATE INDEX idx_tx_in_consuming_tx + ON tx_in(tx_out_id); CREATE INDEX idx_tx_out_tx ON tx_out(tx_id); @@ -258,6 +279,7 @@ RETURNS SETOF "TransactionOutput" AS $$ "TransactionOutput".address, "TransactionOutput".value, "TransactionOutput"."txHash", + "TransactionOutput"."id", "TransactionOutput".index FROM tx JOIN tx_out diff --git a/packages/api-cardano-db-hasura/schema.graphql b/packages/api-cardano-db-hasura/schema.graphql index 9a50abdb..b7a7d07e 100644 --- a/packages/api-cardano-db-hasura/schema.graphql +++ b/packages/api-cardano-db-hasura/schema.graphql @@ -69,6 +69,9 @@ type Query { where: Epoch_bool_exp ): Epoch_aggregate! genesis: Genesis! + paymentAddresses ( + addresses: [String]! + ): [PaymentAddress] rewards ( limit: Int order_by: [Reward_order_by!] @@ -117,6 +120,20 @@ type Query { offset: Int where: StakeRegistration_bool_exp ): StakeRegistration_aggregate + tokens ( + distinct_on: [Token_distinct_on!] + limit: Int + order_by: [Token_order_by!] + offset: Int + where: Token_bool_exp + ): [Token]! + tokens_aggregate ( + distinct_on: [Token_distinct_on!] + limit: Int + order_by: [Token_order_by!] + offset: Int + where: Token_bool_exp + ): Token_aggregate! transactions ( limit: Int order_by: [Transaction_order_by!] @@ -216,6 +233,13 @@ type Ada { supply: AssetSupply! } +type AssetBalance { + assetId: String! + assetName: String! + policyId: Hash28Hex! + quantity: String! +} + type AssetSupply { circulating: String! max: String! @@ -324,6 +348,18 @@ type Genesis { shelley: ShelleyGenesis } +type PaymentAddress { + address: String! + summary( + atBlock: Int + ): PaymentAddressSummary +} + +type PaymentAddressSummary { + assetBalances: [AssetBalance]! + utxosCount: Int! +} + type Relay { ipv4: IPv4 ipv6: IPv6 @@ -632,6 +668,68 @@ type StakeRegistration_aggregate_fields { count: String } +type Token { + assetId: String! + assetName: String! + policyId: Hash28Hex! + quantity: String! + transactionOutput: TransactionOutput +} + +input Token_order_by { + assetId: order_by + assetName: order_by + policyId: order_by + quantity: order_by +} + +input Token_bool_exp { + _and: [Token_bool_exp] + _not: Token_bool_exp + _or: [Token_bool_exp] + assetId: text_comparison_exp + assetName: text_comparison_exp + policyId: Hash28Hex_comparison_exp + quantity: text_comparison_exp + transactionOutput: TransactionOutput_bool_exp +} + +type Token_aggregate { + aggregate: Token_aggregate_fields + nodes: [Token!]! +} + +type Token_aggregate_fields { + avg: Token_avg_fields! + count: String! + max: Token_max_fields! + min: Token_min_fields! + sum: Token_sum_fields! +} + +type Token_avg_fields { + quantity: String +} + +enum Token_distinct_on { + assetId + assetName + policyId +} + +type Token_max_fields { + quantity: String +} + +type Token_min_fields { + quantity: String +} + +type Token_sum_fields { + quantity: String +} + + type Transaction { block: Block blockIndex: Int! @@ -653,6 +751,13 @@ type Transaction { invalidBefore: String invalidHereafter: String metadata: [TransactionMetadata] + mint: [Token!] + mint_aggregate( + limit: Int + order_by: [Token_order_by] + offset: Int + where: Token_bool_exp + ): Token_aggregate! outputs ( limit: Int order_by: [TransactionOutput_order_by] @@ -705,6 +810,7 @@ input Transaction_bool_exp { invalidBefore: text_comparison_exp invalidHereafter: text_comparison_exp metadata: TransactionMetadata_bool_exp + mint: Token_bool_exp outputs: TransactionOutput_bool_exp size: BigInt_comparison_exp totalOutput: text_comparison_exp @@ -726,6 +832,7 @@ type Transaction_aggregate_fields { type Transaction_avg_fields { deposit: Float fee: Float + mint: Token_avg_fields size: Float totalOutput: Float withdrawals: Withdrawal_ave_fields @@ -736,6 +843,7 @@ type Transaction_max_fields { fee: String invalidBefore: String invalidHereafter: String + mint: Token_max_fields size: String totalOutput: String withdrawals: Withdrawal_max_fields @@ -746,6 +854,7 @@ type Transaction_min_fields { fee: String invalidBefore: String invalidHereafter: String + mint: Token_min_fields size: String totalOutput: String withdrawals: Withdrawal_min_fields @@ -754,6 +863,7 @@ type Transaction_min_fields { type Transaction_sum_fields { deposit: String fee: String + mint: Token_sum_fields size: String totalOutput: String withdrawals: Withdrawal_sum_fields @@ -764,6 +874,8 @@ type TransactionInput { sourceTransaction: Transaction! sourceTxHash: Hash32Hex! sourceTxIndex: Int! + tokens: [Token!]! + tokens_aggregate: Token_aggregate! transaction: Transaction! txHash: Hash32Hex! value: String! @@ -782,6 +894,7 @@ input TransactionInput_bool_exp { _or: [TransactionInput_bool_exp] address: text_comparison_exp sourceTransaction: Transaction_bool_exp + tokens: Token_bool_exp transaction: Transaction_bool_exp value: text_comparison_exp } @@ -799,18 +912,22 @@ type TransactionInput_aggregate_fields { } type TransactionInput_avg_fields { + tokens: Token_avg_fields value: String } type TransactionInput_max_fields { + tokens: Token_max_fields value: String } type TransactionInput_min_fields { + tokens: Token_min_fields value: String } type TransactionInput_sum_fields { + tokens: Token_sum_fields value: String } @@ -828,6 +945,8 @@ type TransactionOutput { index: Int! transaction: Transaction! txHash: Hash32Hex! + tokens: [Token!]! + tokens_aggregate: Token_aggregate! value: String! } @@ -847,6 +966,7 @@ input TransactionOutput_bool_exp { _not: TransactionOutput_bool_exp _or: [TransactionOutput_bool_exp] address: text_comparison_exp + tokens: Token_bool_exp transaction: Transaction_bool_exp value: text_comparison_exp } @@ -864,18 +984,22 @@ type TransactionOutput_aggregate_fields { } type TransactionOutput_avg_fields { + tokens: Token_avg_fields value: String } type TransactionOutput_max_fields { + tokens: Token_max_fields value: String } type TransactionOutput_min_fields { + tokens: Token_min_fields value: String } type TransactionOutput_sum_fields { + tokens: Token_sum_fields value: String } diff --git a/packages/api-cardano-db-hasura/src/HasuraClient.ts b/packages/api-cardano-db-hasura/src/HasuraClient.ts index 3ebade6f..a276ba6b 100644 --- a/packages/api-cardano-db-hasura/src/HasuraClient.ts +++ b/packages/api-cardano-db-hasura/src/HasuraClient.ts @@ -9,8 +9,15 @@ import { DocumentNode, GraphQLSchema, print } from 'graphql' import { introspectSchema, wrapSchema } from '@graphql-tools/wrap' import pRetry from 'p-retry' import path from 'path' -import { AssetSupply } from './graphql_types' +import { + AssetBalance, + AssetSupply, + PaymentAddressSummary, + Token, + TransactionOutput +} from './graphql_types' import { dummyLogger, Logger } from 'ts-log' +import BigNumber from 'bignumber.js' dayjs.extend(utc) @@ -84,7 +91,6 @@ export class HasuraClient { } public async buildHasuraSchema () { - await this.applySchemaAndMetadata() const executor = async ({ document, variables }: { document: DocumentNode, variables?: Object }) => { const query = print(document) try { @@ -122,6 +128,85 @@ export class HasuraClient { return schema } + public async getPaymentAddressSummary (address: string, atBlock?: number): Promise { + const result = await this.client.query({ + query: gql`query PaymentAddressSummary ( + $address: String! + $atBlock: Int + ){ + utxos ( + where: { + _and: { + address: { _eq: $address }, + transaction: { block: { number: { _lte: $atBlock }}} + } + } + ) { + value + tokens { + assetId + assetName + policyId + quantity + } + } + utxos_aggregate ( + where: { + _and: { + address: { _eq: $address }, + transaction: { block: { number: { _lte: $atBlock }}} + } + } + ) { + aggregate { + count + } + } + }`, + variables: { address, atBlock } + }) + const map = new Map() + for (const utxo of result.data.utxos as TransactionOutput[]) { + if (map.has('ada')) { + const current = map.get('ada') + map.set('ada', { + ...current, + ...{ + quantity: new BigNumber(current.quantity) + .plus(new BigNumber(utxo.value)) + .toString() + } + }) + } else { + map.set('ada', { + assetId: 'ada', + assetName: 'ada', + policyId: '', + quantity: utxo.value + }) + } + for (const token of utxo.tokens as Token[]) { + if (map.has(token.assetId)) { + const current = map.get(token.assetId) + map.set(token.assetId, { + ...current, + ...{ + quantity: new BigNumber(current.quantity) + .plus(new BigNumber(token.quantity)) + .toString() + } + }) + } else { + map.set(token.assetId, token as unknown as AssetBalance) + } + } + } + return { + assetBalances: [...map.values()], + utxosCount: result.data.utxos_aggregate.aggregate.count + } + } + public async getMeta () { const result = await this.client.query({ query: gql`query { diff --git a/packages/api-cardano-db-hasura/src/example_queries/paymentAddress/summary.graphql b/packages/api-cardano-db-hasura/src/example_queries/paymentAddress/summary.graphql new file mode 100644 index 00000000..bbb696cb --- /dev/null +++ b/packages/api-cardano-db-hasura/src/example_queries/paymentAddress/summary.graphql @@ -0,0 +1,13 @@ +query paymentAddressSummay( + $addresses: [String!]! + $atBlock: Int +) { + paymentAddresses (addresses: $addresses) { + summary (atBlock: $atBlock){ + assetBalances { + assetName + quantity + } + } + } +} diff --git a/packages/api-cardano-db-hasura/src/example_queries/tokens/distinctAssets.graphql b/packages/api-cardano-db-hasura/src/example_queries/tokens/distinctAssets.graphql new file mode 100644 index 00000000..5095bf3c --- /dev/null +++ b/packages/api-cardano-db-hasura/src/example_queries/tokens/distinctAssets.graphql @@ -0,0 +1,21 @@ +query distinctAssets { + tokens_aggregate(distinct_on: assetId) { + aggregate { + count + avg { + quantity + } + min { + quantity + } + max { + quantity + } + } + nodes { + assetId + assetName + policyId + } + } +} diff --git a/packages/api-cardano-db-hasura/src/example_queries/tokens/tokens.graphql b/packages/api-cardano-db-hasura/src/example_queries/tokens/tokens.graphql new file mode 100644 index 00000000..c72fdb7e --- /dev/null +++ b/packages/api-cardano-db-hasura/src/example_queries/tokens/tokens.graphql @@ -0,0 +1,23 @@ +query tokens ( + $address: String! +){ + tokens( + order_by: { value: desc } + where: { address: { _eq: $address }} + ) { + assetId + assetName + policyId + quantity + transactionOutput { + address + index + transaction { + hash + block { + number + } + } + } + } +} diff --git a/packages/api-cardano-db-hasura/src/example_queries/transactions/transactionsByHashesWithTokens.graphql b/packages/api-cardano-db-hasura/src/example_queries/transactions/transactionsByHashesWithTokens.graphql new file mode 100644 index 00000000..8bd19733 --- /dev/null +++ b/packages/api-cardano-db-hasura/src/example_queries/transactions/transactionsByHashesWithTokens.graphql @@ -0,0 +1,60 @@ +query transactionsByHashesWithTokens( + $hashes: [Hash32Hex]! +) { + transactions( + where: { hash: { _in: $hashes }}, + order_by: { hash: desc } + ) { + block { + number + } + blockIndex + deposit + fee + hash + inputs(order_by: { sourceTxHash: asc }) { + address + sourceTxIndex + sourceTxHash + value + } + inputs_aggregate { + aggregate { + sum { + value + } + } + } + mint { + assetName + policyId + quantity + } + outputs(order_by: { index: asc }) { + index + address + value + tokens { + assetName + policyId + quantity + } + } + outputs_aggregate { + aggregate { + sum { + value + } + } + } + size + totalOutput + withdrawals_aggregate { + aggregate { + sum { + amount + } + } + } + } +} diff --git a/packages/api-cardano-db-hasura/src/executableSchema.ts b/packages/api-cardano-db-hasura/src/executableSchema.ts index 16b989e9..0e5749db 100644 --- a/packages/api-cardano-db-hasura/src/executableSchema.ts +++ b/packages/api-cardano-db-hasura/src/executableSchema.ts @@ -38,8 +38,22 @@ export async function buildSchema ( genesis: Genesis, cardanoNodeClient?: CardanoNodeClient ) { + const throwIfNotInCurrentEra = async (queryName: string) => { + if (!(await cardanoNodeClient.isInCurrentEra())) { + return new ApolloError(`${queryName} results are only available when close to the network tip. This is expected during the initial chain-sync.`) + } + } return makeExecutableSchema({ resolvers: Object.assign({}, scalarResolvers, { + PaymentAddress: { + summary: async (parent, args) => { + try { + return await hasuraClient.getPaymentAddressSummary(parent.address, args.atBlock) + } catch (error) { + throw new ApolloError(error) + } + } + }, Query: { activeStake: (_root, args, context, info) => { return delegateToSchema({ @@ -62,9 +76,7 @@ export async function buildSchema ( }) }, ada: async () => { - if (!(await cardanoNodeClient.isInCurrentEra())) { - return new ApolloError('ada query results are only available when close to the network tip. This is expected during the initial chain-sync.') - } + await throwIfNotInCurrentEra('ada') return { supply: { circulating: hasuraClient.adaCirculatingSupplyFetcher.value, @@ -160,6 +172,12 @@ export async function buildSchema ( }) }, genesis: async () => genesis, + paymentAddresses: async (_root, args) => { + await throwIfNotInCurrentEra('addressSummary') + return args.addresses.map(async (address) => { + return { address } + }) + }, rewards: (_root, args, context, info) => { return delegateToSchema({ args, @@ -240,6 +258,26 @@ export async function buildSchema ( schema: hasuraClient.schema }) }, + tokens: (_root, args, context, info) => { + return delegateToSchema({ + args, + context, + fieldName: 'tokens', + info, + operation: 'query', + schema: hasuraClient.schema + }) + }, + tokens_aggregate: (_root, args, context, info) => { + return delegateToSchema({ + args, + context, + fieldName: 'tokens_aggregate', + info, + operation: 'query', + schema: hasuraClient.schema + }) + }, transactions: (_root, args, context, info) => { return delegateToSchema({ args, diff --git a/packages/api-cardano-db-hasura/test/__snapshots__/transactions.query.test.ts.snap b/packages/api-cardano-db-hasura/test/__snapshots__/transactions.query.test.ts.snap index aa0a86db..42422726 100644 --- a/packages/api-cardano-db-hasura/test/__snapshots__/transactions.query.test.ts.snap +++ b/packages/api-cardano-db-hasura/test/__snapshots__/transactions.query.test.ts.snap @@ -273,12 +273,6 @@ Object { "transactions": Array [ Object { "metadata": Array [ - Object { - "key": "0", - "value": Object { - "La_RepsistancE": "Was here", - }, - }, Object { "key": "1", "value": Object { @@ -310,6 +304,12 @@ Object { "VoterId": "d990a1e8-cb90-4f59-b5cf-a8b59c9ba8ae", }, }, + Object { + "key": "0", + "value": Object { + "La_RepsistancE": "Was here", + }, + }, ], }, ], @@ -322,36 +322,36 @@ Object { Object { "metadata": Array [ Object { - "key": "0", - "value": "MelDemo1", - }, - Object { - "key": "1", - "value": "454654654564564654", + "key": "7", + "value": "http://scantrust/server_publickey.txt", }, Object { - "key": "2", - "value": "sha256", + "key": "6", + "value": "kjfsdhgdjiksfedhdgjksdhfgjksdfhgjklsdhf", }, Object { - "key": "3", - "value": "http://127.0.0.1/data.json", + "key": "5", + "value": "http://127.0.0.1/client_publickey.txt", }, Object { "key": "4", "value": "dsfgdfsgsdfgsdfgsdfgdsfdfgdsfgdfgdfsgsdef", }, Object { - "key": "5", - "value": "http://127.0.0.1/client_publickey.txt", + "key": "3", + "value": "http://127.0.0.1/data.json", }, Object { - "key": "6", - "value": "kjfsdhgdjiksfedhdgjksdhfgjksdfhgjklsdhf", + "key": "2", + "value": "sha256", }, Object { - "key": "7", - "value": "http://scantrust/server_publickey.txt", + "key": "1", + "value": "454654654564564654", + }, + Object { + "key": "0", + "value": "MelDemo1", }, ], }, diff --git a/packages/api-cardano-db-hasura/test/graphql_operations/getAnyUtxoAddress.graphql b/packages/api-cardano-db-hasura/test/graphql_operations/getAnyUtxoAddress.graphql index 79f89378..c6cfc322 100644 --- a/packages/api-cardano-db-hasura/test/graphql_operations/getAnyUtxoAddress.graphql +++ b/packages/api-cardano-db-hasura/test/graphql_operations/getAnyUtxoAddress.graphql @@ -3,5 +3,10 @@ query getAnyUtxoAddress ( ){ utxos (limit: $qty, where: { transaction: { block: { number: { _is_null: false }}}}) { address + transaction { + block { + number + } + } } } \ No newline at end of file diff --git a/packages/api-cardano-db-hasura/test/paymentAddress.query.test.ts b/packages/api-cardano-db-hasura/test/paymentAddress.query.test.ts new file mode 100644 index 00000000..06b5b769 --- /dev/null +++ b/packages/api-cardano-db-hasura/test/paymentAddress.query.test.ts @@ -0,0 +1,70 @@ +import path from 'path' + +import { DocumentNode } from 'graphql' +import util from '@cardano-graphql/util' +import { TestClient } from '@cardano-graphql/util-dev' +import { buildClient } from './util' +import { Genesis } from '@src/graphql_types' +import BigNumber from 'bignumber.js' + +const genesis = { + byron: require('../../../config/network/mainnet/genesis/byron.json'), + shelley: require('../../../config/network/mainnet/genesis/shelley.json') +} as Genesis + +function loadQueryNode (name: string): Promise { + return util.loadQueryNode(path.resolve(__dirname, '..', 'src', 'example_queries', 'paymentAddress'), name) +} + +function loadTestOperationDocument (name: string): Promise { + return util.loadQueryNode(path.resolve(__dirname, 'graphql_operations'), name) +} + +describe('paymentAddress', () => { + let client: TestClient + beforeAll(async () => { + client = await buildClient('http://localhost:3100', 'http://localhost:8090', 5442, genesis) + }) + + it('returns payment address summary for the provided addresses', async () => { + const anyUtxoResult = await client.query({ + query: await loadTestOperationDocument('getAnyUtxoAddress'), + variables: { qty: 2 } + }) + const address1 = anyUtxoResult.data.utxos[0].address + const result = await client.query({ + query: await loadQueryNode('summary'), + variables: { addresses: [address1] } + }) + const paymentAddress = result.data.paymentAddresses[0] + expect(paymentAddress.summary.assetBalances[0].assetName).toEqual('ada') + expect(new BigNumber(paymentAddress.summary.assetBalances[0].quantity).toNumber()) + .toBeGreaterThan(0) + }) + it('can bound the summary by chain length by block number', async () => { + const anyUtxoResult = await client.query({ + query: await loadTestOperationDocument('getAnyUtxoAddress'), + variables: { qty: 2 } + }) + const utxo = anyUtxoResult.data.utxos[0] + const blockBound = utxo.transaction.block.number - 1 + const unboundedResult = await client.query({ + query: await loadQueryNode('summary'), + variables: { addresses: [utxo.address] } + }) + const boundedResult = await client.query({ + query: await loadQueryNode('summary'), + variables: { + addresses: [utxo.address], + atBlock: blockBound + } + }) + const unboundedAdaBalance = new BigNumber( + unboundedResult.data.paymentAddresses[0].summary.assetBalances[0].quantity + ).toNumber() + const boundedAdaBalance = new BigNumber( + boundedResult.data.paymentAddresses[0].summary?.assetBalances[0]?.quantity + ).toNumber() || 0 + expect(unboundedAdaBalance).toBeGreaterThan(boundedAdaBalance) + }) +}) diff --git a/packages/api-cardano-db-hasura/test/tokens.query.test.ts b/packages/api-cardano-db-hasura/test/tokens.query.test.ts new file mode 100644 index 00000000..af67013e --- /dev/null +++ b/packages/api-cardano-db-hasura/test/tokens.query.test.ts @@ -0,0 +1,38 @@ +/* eslint-disable camelcase */ +import path from 'path' + +import { DocumentNode } from 'graphql' +import util from '@cardano-graphql/util' +import { TestClient } from '@cardano-graphql/util-dev' +import { buildClient } from './util' +import { Genesis } from '@src/graphql_types' + +const genesis = { + byron: require('../../../config/network/mainnet/genesis/byron.json'), + shelley: require('../../../config/network/mainnet/genesis/shelley.json') +} as Genesis + +function loadQueryNode (name: string): Promise { + return util.loadQueryNode(path.resolve(__dirname, '..', 'src', 'example_queries', 'tokens'), name) +} + +describe('tokens', () => { + let client: TestClient + beforeAll(async () => { + client = await buildClient('http://localhost:3100', 'http://localhost:8090', 5442, genesis) + }) + + it('can return information on distinct assets', async () => { + const result = await client.query({ + query: await loadQueryNode('distinctAssets') + }) + const { tokens_aggregate } = result.data + const { aggregate, nodes } = tokens_aggregate + expect(aggregate.count).toBeDefined() + if (aggregate.count > 0) { + expect(nodes[0].assetId).toBeDefined() + expect(nodes[0].assetName).toBeDefined() + expect(nodes[0].policyId).toBeDefined() + } + }) +}) diff --git a/packages/server/src/Server.ts b/packages/server/src/Server.ts index 46cbe99a..57e0f8f9 100644 --- a/packages/server/src/Server.ts +++ b/packages/server/src/Server.ts @@ -13,7 +13,6 @@ import { prometheusMetricsPlugin } from './apollo_server_plugins' import { IntrospectionNotPermitted, TracingRequired } from './errors' import { allowListMiddleware } from './express_middleware' import { dummyLogger, Logger } from 'ts-log' -import { LogLevelString } from 'bunyan' export type Config = { allowIntrospection: boolean @@ -22,7 +21,6 @@ export type Config = { apiPort: number cacheEnabled: boolean listenAddress: string - loggerLevel: LogLevelString prometheusMetrics: boolean queryDepthLimit?: number tracing: boolean diff --git a/packages/server/src/config.ts b/packages/server/src/config.ts index ad425cc1..4474f810 100644 --- a/packages/server/src/config.ts +++ b/packages/server/src/config.ts @@ -4,7 +4,9 @@ import fs from 'fs-extra' import { Config as ServerConfig } from './Server' import { LogLevelString } from 'bunyan' -export type Config = ServerConfig & ApiCardanoDbHasuraConfig +export type Config = ServerConfig & ApiCardanoDbHasuraConfig & { + loggerLevel: LogLevelString +} export async function getConfig (): Promise { const env = filterAndTypecastEnvs(process.env) diff --git a/packages/util/test/DataFetcher.test.ts b/packages/util/test/DataFetcher.test.ts index cf0a8d33..02d7d6b3 100644 --- a/packages/util/test/DataFetcher.test.ts +++ b/packages/util/test/DataFetcher.test.ts @@ -20,7 +20,7 @@ describe('DataFetcher', () => { describe('initialize', () => { beforeEach(() => { - dataFetcher = new DataFetcher(fetchFunctionResolvesTrueSpy, 10) + dataFetcher = new DataFetcher('testDataFetcher-initialize', fetchFunctionResolvesTrueSpy, 10) }) afterEach(() => { @@ -38,7 +38,7 @@ describe('DataFetcher', () => { describe('shutdown', () => { beforeEach(async () => { - dataFetcher = new DataFetcher(fetchFunctionResolvesTrueSpy, 10) + dataFetcher = new DataFetcher('testDataFetcher-shutdown', fetchFunctionResolvesTrueSpy, 10) await dataFetcher.initialize() })