diff --git a/.github/workflows/continuous-integration-blockfrost-e2e.yaml b/.github/workflows/continuous-integration-blockfrost-e2e.yaml new file mode 100644 index 00000000000..fab690b0d83 --- /dev/null +++ b/.github/workflows/continuous-integration-blockfrost-e2e.yaml @@ -0,0 +1,93 @@ +name: Continuous Integration - E2E BF + +env: + TL_DEPTH: ${{ github.event.pull_request.head.repo.fork && '0' || fromJson(vars.TL_DEPTH) }} + TL_LEVEL: ${{ github.event.pull_request.head.repo.fork && 'info' || vars.TL_LEVEL }} + # ----------------------------------------------------------------------------------------- + DB_SYNC_CONNECTION_STRING: 'postgresql://postgres:doNoUseThisSecret!@localhost:5435/cexplorer' + KEY_MANAGEMENT_PROVIDER: 'inMemory' + KEY_MANAGEMENT_PARAMS: '{"bip32Ed25519": "Sodium", "accountIndex": 0, "chainId":{"networkId": 0, "networkMagic": 888}, "passphrase":"some_passphrase","mnemonic":"vacant violin soft weird deliver render brief always monitor general maid smart jelly core drastic erode echo there clump dizzy card filter option defense"}' + OGMIOS_URL: 'ws://localhost:1340/' + STAKE_POOL_CONNECTION_STRING: 'postgresql://postgres:doNoUseThisSecret!@localhost:5435/stake_pool' + STAKE_POOL_TEST_CONNECTION_STRING: 'postgresql://postgres:doNoUseThisSecret!@localhost:5435/stake_pool_test' + TEST_CLIENT_ASSET_PROVIDER: 'http' + TEST_CLIENT_ASSET_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4014/"}' + TEST_CLIENT_CHAIN_HISTORY_PROVIDER: 'http' + TEST_CLIENT_CHAIN_HISTORY_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4001/"}' + TEST_CLIENT_HANDLE_PROVIDER: 'http' + TEST_CLIENT_HANDLE_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4011/"}' + TEST_CLIENT_NETWORK_INFO_PROVIDER: 'ws' + TEST_CLIENT_NETWORK_INFO_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4000/"}' + TEST_CLIENT_REWARDS_PROVIDER: 'http' + TEST_CLIENT_REWARDS_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4001/"}' + TEST_CLIENT_TX_SUBMIT_PROVIDER: 'http' + TEST_CLIENT_TX_SUBMIT_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4000/"}' + TEST_CLIENT_UTXO_PROVIDER: 'http' + TEST_CLIENT_UTXO_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4001/"}' + TEST_CLIENT_STAKE_POOL_PROVIDER: 'http' + TEST_CLIENT_STAKE_POOL_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4000/"}' + WS_PROVIDER_URL: 'http://localhost:4100/ws' + +on: + pull_request: + push: + branches: ['master'] + tags: ['*.*.*'] + +jobs: + build_and_test: + strategy: + matrix: + os: [ubuntu-20.04] + runs-on: ${{ matrix.os }} + steps: + - name: 📥 Checkout repository + uses: actions/checkout@v3 + + - name: 🧰 Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 18.12.0 + + - name: 🔨 Build + run: | + yarn install --immutable --inline-builds --mode=skip-build + yarn workspace @cardano-sdk/cardano-services-client build:cjs + yarn workspace @cardano-sdk/cardano-services build:cjs + yarn workspace @cardano-sdk/e2e build:cjs + yarn workspace @cardano-sdk/util-dev build:cjs + docker build --no-cache . + env: + NODE_OPTIONS: '--max_old_space_size=8192' + + - name: 🌐 Setup local test network + working-directory: packages/e2e + run: | + yarn local-network:up -d + env: + CARDANO_NODE_CHAINDB_LOG_LEVEL: 'Warning' + CARDANO_NODE_LOG_LEVEL: 'Warning' + OGMIOS_PORT: '1340' + OGMIOS_URL: 'ws://ogmios:1340' + POSTGRES_PORT: '5435' + + - name: Wait for network init + run: | + yarn workspace @cardano-sdk/e2e wait-for-network-init + + - name: 🔬 Test - e2e - wallet at epoch 0 + run: | + yarn workspace @cardano-sdk/e2e test:wallet:epoch0 + + - name: Wait for epoch 3 + run: | + yarn workspace @cardano-sdk/e2e wait-for-network-epoch-3 + + - name: 🔬 Test - e2e - wallet at epoch 3 + run: | + yarn workspace @cardano-sdk/e2e test:wallet:epoch3 + yarn workspace @cardano-sdk/e2e test:blockfrost:providers + + - name: Dump docker logs + if: ${{ cancelled() || failure() }} + uses: jwalton/gh-docker-logs@v2 diff --git a/.github/workflows/continuous-integration-e2e.yaml b/.github/workflows/continuous-integration-e2e.yaml index 30900c70f8f..435063a37c2 100644 --- a/.github/workflows/continuous-integration-e2e.yaml +++ b/.github/workflows/continuous-integration-e2e.yaml @@ -4,20 +4,20 @@ env: TL_DEPTH: ${{ github.event.pull_request.head.repo.fork && '0' || fromJson(vars.TL_DEPTH) }} TL_LEVEL: ${{ github.event.pull_request.head.repo.fork && 'info' || vars.TL_LEVEL }} # ----------------------------------------------------------------------------------------- + DB_SYNC_CONNECTION_STRING: 'postgresql://postgres:doNoUseThisSecret!@localhost:5435/cexplorer' KEY_MANAGEMENT_PROVIDER: 'inMemory' KEY_MANAGEMENT_PARAMS: '{"bip32Ed25519": "Sodium", "accountIndex": 0, "chainId":{"networkId": 0, "networkMagic": 888}, "passphrase":"some_passphrase","mnemonic":"vacant violin soft weird deliver render brief always monitor general maid smart jelly core drastic erode echo there clump dizzy card filter option defense"}' + OGMIOS_URL: 'ws://localhost:1340/' + STAKE_POOL_CONNECTION_STRING: 'postgresql://postgres:doNoUseThisSecret!@localhost:5435/stake_pool' + STAKE_POOL_TEST_CONNECTION_STRING: 'postgresql://postgres:doNoUseThisSecret!@localhost:5435/stake_pool_test' TEST_CLIENT_ASSET_PROVIDER: 'http' TEST_CLIENT_ASSET_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4014/"}' TEST_CLIENT_CHAIN_HISTORY_PROVIDER: 'ws' TEST_CLIENT_CHAIN_HISTORY_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4000/"}' - DB_SYNC_CONNECTION_STRING: 'postgresql://postgres:doNoUseThisSecret!@localhost:5435/cexplorer' TEST_CLIENT_HANDLE_PROVIDER: 'http' TEST_CLIENT_HANDLE_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4011/"}' - STAKE_POOL_CONNECTION_STRING: 'postgresql://postgres:doNoUseThisSecret!@localhost:5435/stake_pool' - STAKE_POOL_TEST_CONNECTION_STRING: 'postgresql://postgres:doNoUseThisSecret!@localhost:5435/stake_pool_test' TEST_CLIENT_NETWORK_INFO_PROVIDER: 'ws' TEST_CLIENT_NETWORK_INFO_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4000/"}' - OGMIOS_URL: 'ws://localhost:1340/' TEST_CLIENT_REWARDS_PROVIDER: 'http' TEST_CLIENT_REWARDS_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4000/"}' TEST_CLIENT_TX_SUBMIT_PROVIDER: 'http' diff --git a/compose/common.yml b/compose/common.yml index b963e2be790..9f3cc53fb16 100644 --- a/compose/common.yml +++ b/compose/common.yml @@ -79,8 +79,6 @@ x-provider-server-environment: &provider-server-environment TX_SUBMIT_PROVIDER: ${TX_SUBMIT_PROVIDER:-submit-node} STAKE_POOL_PROVIDER: ${STAKE_POOL_PROVIDER:-dbsync} NETWORK: ${NETWORK:-mainnet} - BLOCKFROST_API_KEY: ${BLOCKFROST_API_KEY} - BLOCKFROST_CUSTOM_BACKEND_URL: ${BLOCKFROST_CUSTOM_BACKEND_URL} x-sdk-environment: &sdk-environment LOGGER_MIN_SEVERITY: ${LOGGER_MIN_SEVERITY:-info} @@ -118,21 +116,23 @@ x-sdk-environment: &sdk-environment POSTGRES_USER_FILE_STAKE_POOL: /run/secrets/postgres_user POSTGRES_USER_FILE_WALLET_API: /run/secrets/postgres_user TOKEN_METADATA_SERVER_URL: https://metadata.world.dev.cardano.org - USE_WEB_SOCKET_API: true WEB_SOCKET_API_URL: ws://ws-server:3000/ws services: blockfrost-ryo: build: - context: "https://github.com/ginnun/blockfrost-backend-ryo.git#feat/custom-network-support" + context: 'https://github.com/ginnun/blockfrost-backend-ryo.git#feat/custom-network-support' dockerfile: Dockerfile environment: BLOCKFROST_CONFIG_SERVER_LISTEN_ADDRESS: 0.0.0.0 depends_on: cardano-db-sync: condition: service_started + healthcheck: + test: ['CMD-SHELL', 'curl -s --fail http://localhost:3000/health'] ports: - - "3015:3000" + - 3015:3000 + restart: always cardano-db-sync: <<: @@ -350,9 +350,34 @@ services: <<: - *sdk-environment - *provider-server-environment + USE_WEB_SOCKET_API: true ports: - ${API_PORT:-4000}:3000 + blockfrost-provider-server: + <<: + - *from-sdk + - *logging + - *provider-server + - *with-postgres + depends_on: + blockfrost-ryo: + condition: service_healthy + environment: + <<: + - *sdk-environment + - *provider-server-environment + # ATM we don't have BlockfrostHandleProvider and BlockfrostStakePoolProvider + ASSET_PROVIDER: blockfrost + BLOCKFROST_CUSTOM_BACKEND_URL: 'http://blockfrost-ryo:3000' + CHAIN_HISTORY_PROVIDER: blockfrost + NETWORK_INFO_PROVIDER: blockfrost + REWARDS_PROVIDER: blockfrost + UTXO_PROVIDER: blockfrost + ports: + - ${API_PORT:-4001}:3000 + - 9229:9229 + stake-pool-provider-server: <<: - *from-sdk diff --git a/packages/cardano-services/package.json b/packages/cardano-services/package.json index d52d387646f..a8505d4260d 100644 --- a/packages/cardano-services/package.json +++ b/packages/cardano-services/package.json @@ -29,7 +29,7 @@ "cleanup": "rm -rf dist node_modules", "cli": "ts-node --transpile-only src/cli.ts", "compose:single:up": "yarn compose:up cardano-node ogmios postgres", - "compose:up": "__FIX_UMASK__=$(chmod -R a+r ../../compose/placeholder-secrets) docker compose --env-file environments/.env.$NETWORK -p cardano-services-$NETWORK -f docker-compose.yml -f ../../compose/common.yml -f ../../compose/pg-agent.yml -f ../../compose/$(uname -m).yml ${FILES:-} --profile ${DOCKER_COMPOSE_PROFILE:-none} up", + "compose:up": "__FIX_UMASK__=$(chmod -R a+r ../../compose/placeholder-secrets) docker compose --env-file environments/.env.$NETWORK -p cardano-services-$NETWORK -f docker-compose.yml -f ../../compose/common.yml -f ../../compose/pg-agent.yml -f ../../compose/$(uname -m).yml ${FILES:-} up", "compose:down": "docker compose -p cardano-services-$NETWORK -f docker-compose.yml -f ../../compose/common.yml -f ../../compose/pg-agent.yml -f ../../compose/$(uname -m).yml down -t 120", "coverage": "yarn test --coverage || true", "circular-deps:check": "madge --circular dist/cjs", @@ -38,21 +38,17 @@ "generate-migration": "typeorm-ts-node-commonjs migration:generate src/Projection/migrations/migrations -d src/migrationDataSource.ts", "mainnet:single:up": "NETWORK=mainnet yarn compose:single:up", "mainnet:up": "NETWORK=mainnet SUBMIT_API_ARGS=--mainnet yarn compose:up", - "mainnet:blockfrost:up": "DOCKER_COMPOSE_PROFILE='blockfrost-ryo' yarn mainnet:up", "mainnet:down": "NETWORK=mainnet yarn compose:down", "prepack": "yarn build", "preprod:single:up": "NETWORK=preprod yarn compose:single:up", "preprod:up": "NETWORK=preprod SUBMIT_API_ARGS='--testnet-magic 1' yarn compose:up", - "preprod:blockfrost:up": "DOCKER_COMPOSE_PROFILE='blockfrost-ryo' yarn preprod:up", "preprod:down": "NETWORK=preprod yarn compose:down", "pretest": "yarn build", "preview:single:up": "NETWORK=preview yarn compose:single:up", "preview:up": "NETWORK=preview SUBMIT_API_ARGS='--testnet-magic 2' yarn compose:up", - "preview:blockfrost:up": "DOCKER_COMPOSE_PROFILE='blockfrost-ryo' yarn preview:up", "preview:down": "NETWORK=preview yarn compose:down", "sanchonet:single:up": "NETWORK=sanchonet yarn compose:single:up", "sanchonet:up": "NETWORK=sanchonet SUBMIT_API_ARGS='--testnet-magic 4' yarn compose:up", - "sanchonet:blockfrost:up": "DOCKER_COMPOSE_PROFILE='blockfrost-ryo' yarn sanchonet:up", "sanchonet:down": "NETWORK=sanchonet yarn compose:down", "test": "jest --runInBand -c ./jest.config.js --selectProjects unit", "test:build:verify": "tsc --build ./test", diff --git a/packages/cardano-services/src/ChainHistory/BlockrostChainHistoryProvider/BlockfrostChainHistoryProvider.ts b/packages/cardano-services/src/ChainHistory/BlockrostChainHistoryProvider/BlockfrostChainHistoryProvider.ts index 7b568b9c660..bc75fee397f 100644 --- a/packages/cardano-services/src/ChainHistory/BlockrostChainHistoryProvider/BlockfrostChainHistoryProvider.ts +++ b/packages/cardano-services/src/ChainHistory/BlockrostChainHistoryProvider/BlockfrostChainHistoryProvider.ts @@ -1,6 +1,6 @@ // eslint-disable-next-line jsdoc/check-param-names import * as Crypto from '@cardano-sdk/crypto'; -import { BlockfrostProvider } from '../../util/BlockfrostProvider/BlockfrostProvider'; +import { BlockfrostProvider, BlockfrostProviderDependencies } from '../../util/BlockfrostProvider/BlockfrostProvider'; import { BlockfrostToCore, BlockfrostTransactionContent, @@ -13,46 +13,64 @@ import { BlocksByIdsArgs, Cardano, ChainHistoryProvider, + NetworkInfoProvider, Paginated, ProviderError, ProviderFailure, + Serialization, TransactionsByAddressesArgs, - TransactionsByIdsArgs + TransactionsByIdsArgs, + createSlotEpochCalc } from '@cardano-sdk/core'; +import { DB_MAX_SAFE_INTEGER } from '../DbSyncChainHistory/queries'; import { Responses } from '@blockfrost/blockfrost-js'; +import { Schemas } from '@blockfrost/blockfrost-js/lib/types/open-api'; +import omit from 'lodash/omit.js'; type WithCertIndex = T & { cert_index: number }; +export interface BlockfrostChainHistoryProviderDependencies extends BlockfrostProviderDependencies { + networkInfoProvider: NetworkInfoProvider; +} + export class BlockfrostChainHistoryProvider extends BlockfrostProvider implements ChainHistoryProvider { + private networkInfoProvider: NetworkInfoProvider; + + constructor({ logger, blockfrost, networkInfoProvider }: BlockfrostChainHistoryProviderDependencies) { + super({ blockfrost, logger }); + this.networkInfoProvider = networkInfoProvider; + } + protected async fetchRedeemers({ hash, redeemer_count }: Responses['tx_content']): Promise { if (!redeemer_count) return; - const response = await this.blockfrost.txsRedeemers(hash); - return response.map( - ({ purpose, script_hash, unit_mem, unit_steps, tx_index }): Cardano.Redeemer => ({ - data: Buffer.from(script_hash), - executionUnits: { - memory: Number.parseInt(unit_mem), - steps: Number.parseInt(unit_steps) - }, - index: tx_index, - purpose: ((): Cardano.Redeemer['purpose'] => { - switch (purpose) { - case 'cert': - return Cardano.RedeemerPurpose.certificate; - case 'reward': - return Cardano.RedeemerPurpose.withdrawal; - case 'mint': - return Cardano.RedeemerPurpose.mint; - case 'spend': - return Cardano.RedeemerPurpose.spend; - default: - return purpose; - } - })() - }) + return this.blockfrost.txsRedeemers(hash).then((response) => + response.map( + ({ purpose, script_hash, unit_mem, unit_steps, tx_index }): Cardano.Redeemer => ({ + data: Buffer.from(script_hash), + executionUnits: { + memory: Number.parseInt(unit_mem), + steps: Number.parseInt(unit_steps) + }, + index: tx_index, + purpose: ((): Cardano.Redeemer['purpose'] => { + switch (purpose) { + case 'cert': + return Cardano.RedeemerPurpose.certificate; + case 'reward': + return Cardano.RedeemerPurpose.withdrawal; + case 'mint': + return Cardano.RedeemerPurpose.mint; + case 'spend': + return Cardano.RedeemerPurpose.spend; + default: + return purpose; + } + })() + }) + ) ); } @@ -61,14 +79,16 @@ export class BlockfrostChainHistoryProvider extends BlockfrostProvider implement hash }: Responses['tx_content']): Promise { if (!withdrawal_count) return; - const response = await this.blockfrost.txsWithdrawals(hash); - return response.map( - ({ address, amount }): Cardano.Withdrawal => ({ - quantity: BigInt(amount), - stakeAddress: Cardano.RewardAccount(address) - }) + return this.blockfrost.txsWithdrawals(hash).then((response) => + response.map( + ({ address, amount }): Cardano.Withdrawal => ({ + quantity: BigInt(amount), + stakeAddress: Cardano.RewardAccount(address) + }) + ) ); } + /** This method gathers mints by finding the amounts that doesn't exist in 'inputs' but exist in 'outputs'. */ protected gatherMintsFromUtxos( { asset_mint_or_burn_count }: Responses['tx_content'], @@ -87,65 +107,101 @@ export class BlockfrostChainHistoryProvider extends BlockfrostProvider implement } protected async fetchPoolRetireCerts(hash: string): Promise[]> { - const response = await this.blockfrost.txsPoolRetires(hash); - return response.map(({ pool_id, retiring_epoch, cert_index }) => ({ - __typename: Cardano.CertificateType.PoolRetirement, - cert_index, - epoch: Cardano.EpochNo(retiring_epoch), - poolId: Cardano.PoolId(pool_id) - })); + return this.blockfrost.txsPoolRetires(hash).then((response) => + response.map(({ pool_id, retiring_epoch, cert_index }) => ({ + __typename: Cardano.CertificateType.PoolRetirement, + cert_index, + epoch: Cardano.EpochNo(retiring_epoch), + poolId: Cardano.PoolId(pool_id) + })) + ); } protected async fetchPoolUpdateCerts(hash: string): Promise[]> { - const response = await this.blockfrost.txsPoolUpdates(hash); - return response.map(({ pool_id, cert_index }) => ({ - __typename: Cardano.CertificateType.PoolRegistration, - cert_index, - poolId: Cardano.PoolId(pool_id), - poolParameters: ((): Cardano.PoolParameters => { - this.logger.warn('Omitting poolParameters for certificate in tx', hash); - return null as unknown as Cardano.PoolParameters; - })() - })); + return this.blockfrost.txsPoolUpdates(hash).then((response) => + response.map(({ pool_id, cert_index, fixed_cost, margin_cost, pledge, reward_account, vrf_key }) => ({ + __typename: Cardano.CertificateType.PoolRegistration, + cert_index, + poolId: Cardano.PoolId(pool_id), + poolParameters: { + cost: BigInt(fixed_cost), + id: pool_id as Cardano.PoolId, + margin: Cardano.FractionUtils.toFraction(margin_cost), + owners: [], + pledge: BigInt(pledge), + relays: [], + rewardAccount: reward_account as Cardano.RewardAccount, + vrf: vrf_key as Cardano.VrfVkHex + } + })) + ); + } + + async fetchCBOR(hash: string): Promise { + return this.blockfrost + .instance(`txs/${hash}/cbor`) + .then((response) => { + if (response.body.cbor) return response.body.cbor; + throw new Error('CBOR is null'); + }) + .catch((_error) => { + throw new Error('CBOR fetch failed'); + }); + } + + protected async fetchDetailsFromCBOR(hash: string) { + return this.fetchCBOR(hash) + .then((cbor) => { + const tx = Serialization.Transaction.fromCbor(Serialization.TxCBOR(cbor)).toCore(); + this.logger.info('Fetched details from CBOR for tx', hash); + return tx; + }) + .catch((error) => { + this.logger.warn('Failed to fetch details from CBOR for tx', hash, error); + return null; + }); } protected async fetchMirCerts(hash: string): Promise[]> { - const response = await this.blockfrost.txsMirs(hash); - return response.map(({ address, amount, cert_index, pot }) => ({ - __typename: Cardano.CertificateType.MIR, - cert_index, - kind: Cardano.MirCertificateKind.ToStakeCreds, - pot: pot === 'reserve' ? Cardano.MirCertificatePot.Reserves : Cardano.MirCertificatePot.Treasury, - quantity: BigInt(amount), - rewardAccount: Cardano.RewardAccount(address) - })); + return this.blockfrost.txsMirs(hash).then((response) => + response.map(({ address, amount, cert_index, pot }) => ({ + __typename: Cardano.CertificateType.MIR, + cert_index, + kind: Cardano.MirCertificateKind.ToStakeCreds, + pot: pot === 'reserve' ? Cardano.MirCertificatePot.Reserves : Cardano.MirCertificatePot.Treasury, + quantity: BigInt(amount), + rewardAccount: Cardano.RewardAccount(address) + })) + ); } protected async fetchStakeCerts(hash: string): Promise[]> { - const response = await this.blockfrost.txsStakes(hash); - return response.map(({ address, cert_index, registration }) => ({ - __typename: registration - ? Cardano.CertificateType.StakeRegistration - : Cardano.CertificateType.StakeDeregistration, - cert_index, - stakeCredential: { - hash: Cardano.RewardAccount.toHash(Cardano.RewardAccount(address)) as unknown as Crypto.Hash28ByteBase16, - type: Cardano.CredentialType.KeyHash - } - })); + return this.blockfrost.txsStakes(hash).then((response) => + response.map(({ address, cert_index, registration }) => ({ + __typename: registration + ? Cardano.CertificateType.StakeRegistration + : Cardano.CertificateType.StakeDeregistration, + cert_index, + stakeCredential: { + hash: Cardano.RewardAccount.toHash(Cardano.RewardAccount(address)) as unknown as Crypto.Hash28ByteBase16, + type: Cardano.CredentialType.KeyHash + } + })) + ); } protected async fetchDelegationCerts(hash: string): Promise[]> { - const response = await this.blockfrost.txsDelegations(hash); - return response.map(({ address, pool_id, cert_index }) => ({ - __typename: Cardano.CertificateType.StakeDelegation, - cert_index, - poolId: Cardano.PoolId(pool_id), - stakeCredential: { - hash: Cardano.RewardAccount.toHash(Cardano.RewardAccount(address)) as unknown as Crypto.Hash28ByteBase16, - type: Cardano.CredentialType.KeyHash - } - })); + return this.blockfrost.txsDelegations(hash).then((response) => + response.map(({ address, pool_id, cert_index }) => ({ + __typename: Cardano.CertificateType.StakeDelegation, + cert_index, + poolId: Cardano.PoolId(pool_id), + stakeCredential: { + hash: Cardano.RewardAccount.toHash(Cardano.RewardAccount(address)) as unknown as Crypto.Hash28ByteBase16, + type: Cardano.CredentialType.KeyHash + } + })) + ); } protected async fetchCertificates({ @@ -157,86 +213,235 @@ export class BlockfrostChainHistoryProvider extends BlockfrostProvider implement hash }: Responses['tx_content']): Promise { if (pool_retire_count + pool_update_count + mir_cert_count + stake_cert_count + delegation_count === 0) return; - return [ - ...(pool_retire_count ? await this.fetchPoolRetireCerts(hash) : []), - ...(pool_update_count ? await this.fetchPoolUpdateCerts(hash) : []), - ...(mir_cert_count ? await this.fetchMirCerts(hash) : []), - ...(stake_cert_count ? await this.fetchStakeCerts(hash) : []), - ...(delegation_count ? await this.fetchDelegationCerts(hash) : []) - ] - .sort((a, b) => b.cert_index - a.cert_index) - .map((cert) => cert as Cardano.Certificate); + + return Promise.all([ + pool_retire_count ? this.fetchPoolRetireCerts(hash) : [], + pool_update_count ? this.fetchPoolUpdateCerts(hash) : [], + mir_cert_count ? this.fetchMirCerts(hash) : [], + stake_cert_count ? this.fetchStakeCerts(hash) : [], + delegation_count ? this.fetchDelegationCerts(hash) : [] + ]).then((results) => + results + .flat() + .sort((a, b) => b.cert_index - a.cert_index) + .map((cert) => cert as Cardano.Certificate) + ); } - protected async fetchJsonMetadata(txHash: Cardano.TransactionId): Promise { - try { - const response = await this.blockfrost.txsMetadata(txHash.toString()); - // Not sure if types are correct, missing 'label', but it's present in docs - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return blockfrostMetadataToTxMetadata(response as any); - } catch (error) { - if (isBlockfrostNotFoundError(error)) { - return null; - } - throw error; - } + protected async fetchJsonMetadataAsAuxiliaryData(txHash: string): Promise { + const UNDEFINED = undefined; + return this.blockfrost + .txsMetadata(txHash) + .then((m) => { + const metadata = blockfrostMetadataToTxMetadata(m); + return metadata && metadata.size > 0 + ? { + blob: metadata + } + : UNDEFINED; + }) + .catch((error) => { + if (isBlockfrostNotFoundError(error)) { + return UNDEFINED; + } + throw error; + }); } // eslint-disable-next-line unicorn/consistent-function-scoping protected parseValidityInterval = (num: string | null) => Cardano.Slot(Number.parseInt(num || '')) || undefined; - protected async fetchTransaction(hash: Cardano.TransactionId): Promise { - try { - const utxos: Responses['tx_content_utxo'] = await this.blockfrost.txsUtxos(hash.toString()); - const { inputs, outputs, collaterals } = BlockfrostToCore.transactionUtxos(utxos); - - const response = await this.blockfrost.txs(hash.toString()); - const metadata = await this.fetchJsonMetadata(hash); - const certificates = await this.fetchCertificates(response); - const withdrawals = await this.fetchWithdrawals(response); - const inputSource: Cardano.InputSource = response.valid_contract - ? Cardano.InputSource.inputs - : Cardano.InputSource.collaterals; - - return { - auxiliaryData: metadata - ? { - blob: metadata - } - : undefined, + protected async fetchEpochNo(slotNo: Cardano.Slot) { + const calc = await this.networkInfoProvider.eraSummaries().then(createSlotEpochCalc); + return calc(slotNo); + } - blockHeader: { - blockNo: Cardano.BlockNo(response.block_height), - hash: Cardano.BlockId(response.block), - slot: Cardano.Slot(response.slot) - }, - body: { - certificates, - collaterals, - fee: BigInt(response.fees), - inputs, - mint: this.gatherMintsFromUtxos(response, utxos), - outputs, - validityInterval: { - invalidBefore: this.parseValidityInterval(response.invalid_before), - invalidHereafter: this.parseValidityInterval(response.invalid_hereafter) - }, - withdrawals - }, - id: hash, - index: response.index, - inputSource, - txSize: response.size, - witness: { - redeemers: await this.fetchRedeemers(response), - signatures: new Map() // not available in blockfrost + protected async fetchEpochParameters(epochNo: Cardano.EpochNo): Promise { + return await this.blockfrost.epochsParameters(epochNo); + } + + protected async processCertificates( + txContent: Schemas['tx_content'], + certificates?: Cardano.Certificate[] + ): Promise { + if (!certificates) return; + + const epochNo = await this.fetchEpochNo(Cardano.Slot(txContent.slot)); + const { pool_deposit, key_deposit } = await this.fetchEpochParameters(epochNo); + + return certificates.map((c) => { + const cert = omit(c, 'cert_index') as Cardano.Certificate; + switch (cert.__typename) { + case Cardano.CertificateType.PoolRegistration: { + cert.poolParameters.owners = []; + cert.poolParameters.relays = []; + const deposit = + txContent.deposit === undefined || txContent.deposit === '' || txContent.deposit === '0' + ? 0n + : BigInt(pool_deposit); + + delete cert.poolParameters.metadataJson; + + return { ...cert, deposit }; + } + case Cardano.CertificateType.StakeRegistration: { + const deposit = BigInt(key_deposit); + + return { ...cert, __typename: Cardano.CertificateType.Registration, deposit }; } - }; + case Cardano.CertificateType.StakeDeregistration: { + const deposit = BigInt(key_deposit); + + return { ...cert, __typename: Cardano.CertificateType.Unregistration, deposit }; + } + default: + return cert; + } + }); + } + + protected async transactionDetailsUsingAPIs(txContent: Responses['tx_content']): Promise { + const id = Cardano.TransactionId(txContent.hash); + + const [certificates, withdrawals, utxos, auxiliaryData] = await Promise.all([ + this.fetchCertificates(txContent), + this.fetchWithdrawals(txContent), + this.fetchUtxos(id), + this.fetchJsonMetadataAsAuxiliaryData(id) + ]); + + const { inputs, outputs, collaterals } = this.transactionUtxos(utxos); + + const mintPreOrder = this.gatherMintsFromUtxos(txContent, utxos); + const mint = mintPreOrder ? new Map([...mintPreOrder].sort()) : mintPreOrder; + + const inputSource: Cardano.InputSource = txContent.valid_contract + ? Cardano.InputSource.inputs + : Cardano.InputSource.collaterals; + + const body: Cardano.HydratedTxBody = this.mapTxBody( + { + certificates: await this.processCertificates(txContent, certificates), + collateralReturn: undefined, + collaterals, + fee: BigInt(txContent.fees), + inputs, + mint, + outputs, + proposalProcedures: undefined, + validityInterval: { + invalidBefore: this.parseValidityInterval(txContent.invalid_before), + invalidHereafter: this.parseValidityInterval(txContent.invalid_hereafter) + }, + votingProcedures: undefined, + withdrawals + }, + inputSource + ); + + return { + auxiliaryData, + blockHeader: this.mapBlockHeader(txContent), + body, + id, + index: txContent.index, + inputSource, + txSize: txContent.size, + witness: this.witnessFromRedeemers(await this.fetchRedeemers(txContent)) + }; + } + + private witnessFromRedeemers(redeemers: Cardano.Redeemer[] | undefined): Cardano.Witness { + // Although cbor has the data, this stub is used for compatibility with DbSyncChainHistoryProvider + const stubRedeemerData = Buffer.from('not implemented'); + + if (redeemers) { + for (const redeemer of redeemers) { + redeemer.data = stubRedeemerData; + } + } + + return { + redeemers, + signatures: new Map() // available in cbor, but skipped for compatibility with DbSyncChainHistoryProvider + }; + } + + protected async transactionDetailsUsingCBOR( + txContent: Responses['tx_content'] + ): Promise { + const id = Cardano.TransactionId(txContent.hash); + + const txFromCBOR = await this.fetchDetailsFromCBOR(id); + if (!txFromCBOR) return; + + const utxos: Schemas['tx_content_utxo'] = (await this.blockfrost.txsUtxos(id)) as Schemas['tx_content_utxo']; + + // We can't use txFromCBOR.body.inputs since it misses HydratedTxIn.address + const { inputs, outputs, collaterals } = this.transactionUtxos(utxos, txFromCBOR); + + // txFromCBOR.isValid can be also be used + const inputSource: Cardano.InputSource = txContent.valid_contract + ? Cardano.InputSource.inputs + : Cardano.InputSource.collaterals; + + const body: Cardano.HydratedTxBody = this.mapTxBody( + { + certificates: await this.processCertificates(txContent, txFromCBOR.body.certificates), + collateralReturn: txFromCBOR.body.collateralReturn, + collaterals, + fee: txFromCBOR.body.fee, + inputs, + mint: txFromCBOR.body.mint ? new Map([...txFromCBOR.body.mint].sort()) : undefined, + outputs, + proposalProcedures: txFromCBOR.body.proposalProcedures, + validityInterval: txFromCBOR.body.validityInterval + ? txFromCBOR.body.validityInterval + : { invalidBefore: undefined, invalidHereafter: undefined }, + votingProcedures: txFromCBOR.body.votingProcedures, + withdrawals: txFromCBOR.body.withdrawals + }, + inputSource + ); + + return { + auxiliaryData: txFromCBOR.auxiliaryData, + blockHeader: this.mapBlockHeader(txContent), + body, + id, + index: txContent.index, + inputSource, + txSize: txContent.size, + witness: this.witnessFromRedeemers(txFromCBOR.witness.redeemers) + }; + } + + protected async fetchTransaction(txId: Cardano.TransactionId): Promise { + try { + const txContent = await this.blockfrost.txs(txId.toString()); + + return (await this.transactionDetailsUsingCBOR(txContent)) ?? (await this.transactionDetailsUsingAPIs(txContent)); } catch (error) { throw blockfrostToProviderError(error); } } + private transactionUtxos(utxoResponse: Responses['tx_content_utxo'], txContent?: Cardano.Tx) { + const collaterals = utxoResponse.inputs.filter((input) => input.collateral).map(BlockfrostToCore.hydratedTxIn); + const inputs = utxoResponse.inputs + .filter((input) => !input.collateral && !input.reference) + .map(BlockfrostToCore.hydratedTxIn); + const outputPromises: Cardano.TxOut[] = utxoResponse.outputs + .filter((output) => !output.collateral) + .map((output) => { + const foundScript = txContent?.body.outputs.find((o) => o.address === output.address); + + return BlockfrostToCore.txOut(output, foundScript); + }); + + return { collaterals, inputs, outputs: outputPromises }; + } + public async blocksByHashes({ ids }: BlocksByIdsArgs): Promise { try { const responses = await Promise.all(ids.map((id) => this.blockfrost.blocks(id.toString()))); @@ -277,11 +482,17 @@ export class BlockfrostChainHistoryProvider extends BlockfrostProvider implement } } + // eslint-disable-next-line sonarjs/cognitive-complexity public async transactionsByAddresses({ addresses, + pagination, blockRange }: TransactionsByAddressesArgs): Promise> { + this.logger.info(`transactionsByAddresses: ${JSON.stringify(blockRange)} ${JSON.stringify(addresses)}`); try { + const lowerBound = blockRange?.lowerBound ?? 0; + const upperBound = blockRange?.upperBound ?? DB_MAX_SAFE_INTEGER; + const addressTransactions = await Promise.all( addresses.map(async (address) => fetchByAddressSequentially< @@ -295,18 +506,22 @@ export class BlockfrostChainHistoryProvider extends BlockfrostProvider implement transactions[transactions.length - 1].block_height < blockRange!.lowerBound! : undefined, request: (addr: Cardano.PaymentAddress, paginationOptions) => - this.blockfrost.addressesTransactions(addr.toString(), paginationOptions) + this.blockfrost.addressesTransactions(addr.toString(), paginationOptions, { + from: blockRange?.lowerBound ? blockRange?.lowerBound.toString() : undefined, + to: blockRange?.upperBound ? blockRange?.upperBound.toString() : undefined + }) }) ) ); - const allTransactions = addressTransactions - .flat(1) - .sort((a, b) => b.block_height - a.block_height || b.tx_index - a.tx_index); - const addressTransactionsSinceBlock = blockRange?.lowerBound - ? allTransactions.filter(({ block_height }) => block_height >= blockRange!.lowerBound!) - : allTransactions; - const ids = addressTransactionsSinceBlock.map(({ tx_hash }) => Cardano.TransactionId(tx_hash)); + const allTransactions = addressTransactions.flat(1); + + const ids = allTransactions + .filter(({ block_height }) => block_height >= lowerBound && block_height <= upperBound) + .sort((a, b) => a.block_height - b.block_height || a.tx_index - b.tx_index) + .map(({ tx_hash }) => Cardano.TransactionId(tx_hash)) + .splice(pagination.startAt, pagination.limit); + const pageResults = await this.transactionsByHashes({ ids }); return { pageResults, totalResultCount: allTransactions.length }; @@ -314,4 +529,58 @@ export class BlockfrostChainHistoryProvider extends BlockfrostProvider implement throw blockfrostToProviderError(error); } } + + private mapTxBody( + { + collateralReturn, + collaterals, + fee, + inputs, + outputs, + mint, + proposalProcedures, + validityInterval, + votingProcedures, + withdrawals, + certificates + }: Cardano.HydratedTxBody, + inputSource: Cardano.InputSource + ) { + return { + ...(inputSource === Cardano.InputSource.collaterals + ? { + collateralReturn: outputs.length > 0 ? outputs[0] : undefined, + collaterals: inputs, + fee: BigInt(0), + inputs: [], + outputs: [], + totalCollateral: fee + } + : { + collateralReturn: collateralReturn ?? undefined, + collaterals, + fee, + inputs, + outputs + }), + certificates, + mint, + proposalProcedures, + validityInterval, + votingProcedures, + withdrawals + }; + } + + private mapBlockHeader({ block, block_height, slot }: Responses['tx_content']): Cardano.PartialBlockHeader { + return { + blockNo: Cardano.BlockNo(block_height), + hash: Cardano.BlockId(block), + slot: Cardano.Slot(slot) + }; + } + + private fetchUtxos(id: Cardano.TransactionId): Promise { + return this.blockfrost.txsUtxos(id); + } } diff --git a/packages/cardano-services/src/NetworkInfo/BlockfrostNetworkInfoProvider/BlockfrostNetworkInfoProvider.ts b/packages/cardano-services/src/NetworkInfo/BlockfrostNetworkInfoProvider/BlockfrostNetworkInfoProvider.ts index 73395340d2b..ee06719853b 100644 --- a/packages/cardano-services/src/NetworkInfo/BlockfrostNetworkInfoProvider/BlockfrostNetworkInfoProvider.ts +++ b/packages/cardano-services/src/NetworkInfo/BlockfrostNetworkInfoProvider/BlockfrostNetworkInfoProvider.ts @@ -1,5 +1,5 @@ import { BlockfrostProvider } from '../../util/BlockfrostProvider/BlockfrostProvider'; -import { BlockfrostToCore, blockfrostToProviderError, networkMagicToIdMap } from '../../util'; +import { BlockfrostToCore, blockfrostToProviderError } from '../../util'; import { Cardano, EraSummary, @@ -63,7 +63,10 @@ export class BlockfrostNetworkInfoProvider extends BlockfrostProvider implements epochLength: response.epoch_length, maxKesEvolutions: response.max_kes_evolutions, maxLovelaceSupply: BigInt(response.max_lovelace_supply), - networkId: networkMagicToIdMap[response.network_magic], + networkId: + response.network_magic === Cardano.NetworkMagics.Mainnet + ? Cardano.NetworkId.Mainnet + : Cardano.NetworkId.Testnet, networkMagic: response.network_magic, securityParameter: response.security_param, slotLength: Seconds(response.slot_length), @@ -80,7 +83,7 @@ export class BlockfrostNetworkInfoProvider extends BlockfrostProvider implements try { // Although Blockfrost have the endpoint, the blockfrost-js library don't have a call for it // https://github.com/blockfrost/blockfrost-js/issues/294 - const response = await this.blockfrost.instance('network-eras'); + const response = await this.blockfrost.instance('network/eras'); return response.body; } catch (error) { throw handleError(error); diff --git a/packages/cardano-services/src/Program/programs/providerServer.ts b/packages/cardano-services/src/Program/programs/providerServer.ts index c616557f64a..0d24d38ae7e 100644 --- a/packages/cardano-services/src/Program/programs/providerServer.ts +++ b/packages/cardano-services/src/Program/programs/providerServer.ts @@ -6,6 +6,7 @@ import { CardanoNode, ChainHistoryProvider, HandleProvider, + NetworkInfoProvider, Provider, RewardsProvider, Seconds, @@ -330,8 +331,17 @@ const serviceMapFactory = (options: ServiceMapFactoryOptions) => { }); }, ServiceNames.NetworkInfo); - const getBlockfrostChainHistoryProvider = () => - new BlockfrostChainHistoryProvider({ blockfrost: getBlockfrostApi(), logger }); + let networkInfoProvider: NetworkInfoProvider; + const getNetworkInfoProvider = () => { + if (!networkInfoProvider) + networkInfoProvider = + args.networkInfoProvider === ProviderImplementation.BLOCKFROST + ? getBlockfrostNetworkInfoProvider() + : getDbSyncNetworkInfoProvider(); + return networkInfoProvider; + }; + const getBlockfrostChainHistoryProvider = (nInfoProvider: NetworkInfoProvider | DbSyncNetworkInfoProvider) => + new BlockfrostChainHistoryProvider({ blockfrost: getBlockfrostApi(), logger, networkInfoProvider: nInfoProvider }); const getBlockfrostRewardsProvider = () => new BlockfrostRewardsProvider({ blockfrost: getBlockfrostApi(), logger }); @@ -395,7 +405,10 @@ const serviceMapFactory = (options: ServiceMapFactoryOptions) => { new ChainHistoryHttpService({ chainHistoryProvider: selectProviderImplementation( args.chainHistoryProvider ?? ProviderImplementation.DBSYNC, - { blockfrost: getBlockfrostChainHistoryProvider, dbsync: getDbSyncChainHistoryProvider }, + { + blockfrost: () => getBlockfrostChainHistoryProvider(getNetworkInfoProvider()), + dbsync: getDbSyncChainHistoryProvider + }, logger, ServiceNames.ChainHistory ), @@ -412,16 +425,11 @@ const serviceMapFactory = (options: ServiceMapFactoryOptions) => { ServiceNames.Rewards ) }), - [ServiceNames.NetworkInfo]: async () => { - const networkInfoProvider = - args.networkInfoProvider === ProviderImplementation.BLOCKFROST - ? getBlockfrostNetworkInfoProvider() - : getDbSyncNetworkInfoProvider(); - return new NetworkInfoHttpService({ + [ServiceNames.NetworkInfo]: async () => + new NetworkInfoHttpService({ logger, - networkInfoProvider - }); - }, + networkInfoProvider: getNetworkInfoProvider() + }), [ServiceNames.TxSubmit]: async () => { const txSubmitProvider = args.useSubmitApi ? getSubmitApiProvider() diff --git a/packages/cardano-services/src/Utxo/BlockfrostUtxoProvider/BlockfrostUtxoProvider.ts b/packages/cardano-services/src/Utxo/BlockfrostUtxoProvider/BlockfrostUtxoProvider.ts index 703d4c39bdc..cdffc7f872b 100644 --- a/packages/cardano-services/src/Utxo/BlockfrostUtxoProvider/BlockfrostUtxoProvider.ts +++ b/packages/cardano-services/src/Utxo/BlockfrostUtxoProvider/BlockfrostUtxoProvider.ts @@ -1,19 +1,56 @@ import { BlockfrostProvider } from '../../util/BlockfrostProvider/BlockfrostProvider'; -import { BlockfrostToCore, BlockfrostUtxo, blockfrostToProviderError, fetchByAddressSequentially } from '../../util'; -import { Cardano, UtxoByAddressesArgs, UtxoProvider } from '@cardano-sdk/core'; +import { BlockfrostToCore, blockfrostToProviderError, fetchByAddressSequentially } from '../../util'; +import { Cardano, Serialization, UtxoByAddressesArgs, UtxoProvider } from '@cardano-sdk/core'; +import { PaginationOptions } from '@blockfrost/blockfrost-js/lib/types'; import { Responses } from '@blockfrost/blockfrost-js'; +import { Schemas } from '@blockfrost/blockfrost-js/lib/types/open-api'; export class BlockfrostUtxoProvider extends BlockfrostProvider implements UtxoProvider { + protected async fetchUtxos(addr: Cardano.PaymentAddress, pagination: PaginationOptions): Promise { + const utxos: Responses['address_utxo_content'] = (await this.blockfrost.addressesUtxos( + addr.toString(), + pagination + )) as Responses['address_utxo_content']; + + const utxoPromises = utxos.map((utxo) => + this.fetchDetailsFromCBOR(utxo.tx_hash).then((tx) => { + const txOut = tx ? tx.body.outputs.find((output) => output.address === utxo.address) : undefined; + return BlockfrostToCore.addressUtxoContent(addr.toString(), utxo, txOut); + }) + ); + return Promise.all(utxoPromises); + } + + async fetchCBOR(hash: string): Promise { + return this.blockfrost + .instance(`txs/${hash}/cbor`) + .then((response) => { + if (response.body.cbor) return response.body.cbor; + throw new Error('CBOR is null'); + }) + .catch((_error) => { + throw new Error('CBOR fetch failed'); + }); + } + protected async fetchDetailsFromCBOR(hash: string) { + return this.fetchCBOR(hash) + .then((cbor) => { + const tx = Serialization.Transaction.fromCbor(Serialization.TxCBOR(cbor)).toCore(); + this.logger.info('Fetched details from CBOR for tx', hash); + return tx; + }) + .catch((error) => { + this.logger.warn('Failed to fetch details from CBOR for tx', hash, error); + return null; + }); + } public async utxoByAddresses({ addresses }: UtxoByAddressesArgs): Promise { try { const utxoResults = await Promise.all( addresses.map(async (address) => - fetchByAddressSequentially({ + fetchByAddressSequentially({ address, - request: (addr: Cardano.PaymentAddress, pagination) => - this.blockfrost.addressesUtxos(addr.toString(), pagination), - responseTranslator: (addr: Cardano.PaymentAddress, response: Responses['address_utxo_content']) => - BlockfrostToCore.addressUtxoContent(addr.toString(), response) + request: async (addr: Cardano.PaymentAddress, pagination) => await this.fetchUtxos(addr, pagination) }) ) ); diff --git a/packages/cardano-services/src/util/BlockfrostProvider/BlockfrostClientFactory.ts b/packages/cardano-services/src/util/BlockfrostProvider/BlockfrostClientFactory.ts index 86b3d8e8ec7..a90f25d390d 100644 --- a/packages/cardano-services/src/util/BlockfrostProvider/BlockfrostClientFactory.ts +++ b/packages/cardano-services/src/util/BlockfrostProvider/BlockfrostClientFactory.ts @@ -14,7 +14,8 @@ export const getBlockfrostApi = () => { // custom hosted instance if (process.env.BLOCKFROST_CUSTOM_BACKEND_URL && process.env.BLOCKFROST_CUSTOM_BACKEND_URL !== '') { blockfrostApi = new BlockFrostAPI({ - customBackend: process.env.BLOCKFROST_CUSTOM_BACKEND_URL + customBackend: process.env.BLOCKFROST_CUSTOM_BACKEND_URL, + rateLimiter: false }); return blockfrostApi; @@ -29,7 +30,8 @@ export const getBlockfrostApi = () => { // network is not mandatory, we keep it for safety. blockfrostApi = new BlockFrostAPI({ network: process.env.NETWORK as AvailableNetworks, - projectId: process.env.BLOCKFROST_API_KEY + projectId: process.env.BLOCKFROST_API_KEY, + rateLimiter: false }); return blockfrostApi; diff --git a/packages/cardano-services/src/util/BlockfrostProvider/BlockfrostProvider.ts b/packages/cardano-services/src/util/BlockfrostProvider/BlockfrostProvider.ts index d88030662a0..f72253695fb 100644 --- a/packages/cardano-services/src/util/BlockfrostProvider/BlockfrostProvider.ts +++ b/packages/cardano-services/src/util/BlockfrostProvider/BlockfrostProvider.ts @@ -3,7 +3,7 @@ import { HealthCheckResponse, Provider, ProviderDependencies } from '@cardano-sd import { blockfrostToProviderError } from './blockfrostUtil'; import type { Logger } from 'ts-log'; -/** Properties that are need to create a BlockfrostProvider */ +/** Properties needed to create a BlockfrostProvider */ export interface BlockfrostProviderDependencies extends ProviderDependencies { blockfrost: BlockFrostAPI; logger: Logger; diff --git a/packages/cardano-services/src/util/BlockfrostProvider/BlockfrostToCore.ts b/packages/cardano-services/src/util/BlockfrostProvider/BlockfrostToCore.ts index 26dde88bcc2..9f3ade205ae 100644 --- a/packages/cardano-services/src/util/BlockfrostProvider/BlockfrostToCore.ts +++ b/packages/cardano-services/src/util/BlockfrostProvider/BlockfrostToCore.ts @@ -1,4 +1,6 @@ -import { Cardano } from '@cardano-sdk/core'; +import { Cardano, Serialization } from '@cardano-sdk/core'; +import { Hash32ByteBase16 } from '@cardano-sdk/crypto'; +import { HexBlob } from '@cardano-sdk/util'; import { Responses } from '@blockfrost/blockfrost-js'; type Unpacked = T extends (infer U)[] ? U : T; @@ -11,11 +13,15 @@ export type BlockfrostTransactionContent = Unpacked; export const BlockfrostToCore = { - addressUtxoContent: (address: string, blockfrost: Responses['address_utxo_content']): Cardano.Utxo[] => - blockfrost.map((utxo) => [ + addressUtxoContent: ( + address: string, + utxo: Responses['address_utxo_content'][0], + txOutFromCbor?: Cardano.TxOut + ): Cardano.Utxo => + [ BlockfrostToCore.hydratedTxIn(BlockfrostToCore.inputFromUtxo(address, utxo)), - BlockfrostToCore.txOut(BlockfrostToCore.outputFromUtxo(address, utxo)) - ]) as Cardano.Utxo[], + BlockfrostToCore.txOut(BlockfrostToCore.outputFromUtxo(address, utxo), txOutFromCbor) + ] as Cardano.Utxo, blockToTip: (block: Responses['block_content']): Cardano.Tip => ({ blockNo: Cardano.BlockNo(block.height!), @@ -92,30 +98,30 @@ export const BlockfrostToCore = { treasuryExpansion: blockfrost.tau.toString() }), - transactionUtxos: (utxoResponse: Responses['tx_content_utxo']) => ({ - collaterals: utxoResponse.inputs.filter((input) => input.collateral).map(BlockfrostToCore.hydratedTxIn), - inputs: utxoResponse.inputs.filter((input) => !input.collateral).map(BlockfrostToCore.hydratedTxIn), - outputs: utxoResponse.outputs.map(BlockfrostToCore.txOut) - }), - - txContentUtxo: (blockfrost: Responses['tx_content_utxo']) => ({ - hash: blockfrost.hash, - inputs: BlockfrostToCore.inputs(blockfrost.inputs), - outputs: BlockfrostToCore.outputs(blockfrost.outputs) - }), + txOut: (blockfrost: BlockfrostOutput, txOutFromCbor?: Cardano.TxOut): Cardano.TxOut => { + const value: Cardano.Value = { + coins: BigInt(blockfrost.amount.find(({ unit }) => unit === 'lovelace')!.quantity) + }; - txOut: (blockfrost: BlockfrostOutput): Cardano.TxOut => { const assets: Cardano.TokenMap = new Map(); for (const { quantity, unit } of blockfrost.amount) { if (unit === 'lovelace') continue; assets.set(Cardano.AssetId(unit), BigInt(quantity)); } - return { + + if (assets.size > 0) value.assets = assets; + + const txOut: Cardano.TxOut = { address: Cardano.PaymentAddress(blockfrost.address), - value: { - assets, - coins: BigInt(blockfrost.amount.find(({ unit }) => unit === 'lovelace')!.quantity) - } + value }; + + if (blockfrost.inline_datum) + txOut.datum = Serialization.PlutusData.fromCbor(HexBlob(blockfrost.inline_datum)).toCore(); + if (blockfrost.data_hash) txOut.datumHash = Hash32ByteBase16(blockfrost.data_hash); + + if (txOutFromCbor?.scriptReference) txOut.scriptReference = txOutFromCbor.scriptReference; + + return txOut; } }; diff --git a/packages/cardano-services/src/util/BlockfrostProvider/blockfrostUtil.ts b/packages/cardano-services/src/util/BlockfrostProvider/blockfrostUtil.ts index dd0ae980c0f..8f0f7c47677 100644 --- a/packages/cardano-services/src/util/BlockfrostProvider/blockfrostUtil.ts +++ b/packages/cardano-services/src/util/BlockfrostProvider/blockfrostUtil.ts @@ -39,7 +39,7 @@ export const fetchSequentially = async ( request: (arg: Arg, pagination: PaginationOptions) => Promise; responseTranslator?: (response: Response[], arg: Arg) => Item[]; /** - * @returns true to indicatate that current result set should be returned + * @returns true to indicate that current result set should be returned */ haveEnoughItems?: (items: Item[]) => boolean; paginationOptions?: PaginationOptions; @@ -88,7 +88,7 @@ export const fetchByAddressSequentially = async (props: { request: (address: Cardano.PaymentAddress, pagination: PaginationOptions) => Promise; responseTranslator?: (address: Cardano.PaymentAddress, response: Response[]) => Item[]; /** - * @returns true to indicatate that current result set should be returned + * @returns true to indicate that current result set should be returned */ haveEnoughItems?: (items: Item[]) => boolean; paginationOptions?: PaginationOptions; @@ -103,11 +103,6 @@ export const fetchByAddressSequentially = async (props: { : undefined }); -export const networkMagicToIdMap: { [key in number]: Cardano.NetworkId } = { - [Cardano.NetworkMagics.Mainnet]: Cardano.NetworkId.Mainnet, - [Cardano.NetworkMagics.Preprod]: Cardano.NetworkId.Testnet -}; - // copied from util-dev export const testnetEraSummaries: EraSummary[] = [ { diff --git a/packages/cardano-services/test/ChainHistory/BlockfrostChainHistoryProvider/BlockfrostChainHistoryProvider.test.ts b/packages/cardano-services/test/ChainHistory/BlockfrostChainHistoryProvider/BlockfrostChainHistoryProvider.test.ts index 629e3bce19b..6743a00bc5c 100644 --- a/packages/cardano-services/test/ChainHistory/BlockfrostChainHistoryProvider/BlockfrostChainHistoryProvider.test.ts +++ b/packages/cardano-services/test/ChainHistory/BlockfrostChainHistoryProvider/BlockfrostChainHistoryProvider.test.ts @@ -1,6 +1,6 @@ -import { BlockFrostAPI, Responses } from '@blockfrost/blockfrost-js'; +import { BlockFrostAPI } from '@blockfrost/blockfrost-js'; import { BlockfrostChainHistoryProvider } from '../../../src'; -import { Cardano } from '@cardano-sdk/core'; +import { Cardano, NetworkInfoProvider } from '@cardano-sdk/core'; import { dummyLogger as logger } from 'ts-log'; jest.mock('@blockfrost/blockfrost-js'); @@ -8,254 +8,405 @@ jest.mock('@blockfrost/blockfrost-js'); describe('blockfrostChainHistoryProvider', () => { const apiKey = 'someapikey'; - describe('transactionsBy*', () => { - const txsUtxosResponse = { - hash: '4123d70f66414cc921f6ffc29a899aafc7137a99a0fd453d6b200863ef5702d6', - inputs: [ + const txsUtxosResponse = { + hash: '4123d70f66414cc921f6ffc29a899aafc7137a99a0fd453d6b200863ef5702d6', + inputs: [ + { + address: + 'addr_test1qr05llxkwg5t6c4j3ck5mqfax9wmz35rpcgw3qthrn9z7xcxu2hyfhlkwuxupa9d5085eunq2qywy7hvmvej456flknstdz3k2', + amount: [ + { + quantity: '9732978705764', + unit: 'lovelace' + } + ], + output_index: 1, + tx_hash: '6d50c330a6fba79de6949a8dcd5e4b7ffa3f9442f0c5bed7a78fa6d786c6c863' + } + ], + outputs: [ + { + address: + 'addr_test1qzx9hu8j4ah3auytk0mwcupd69hpc52t0cw39a65ndrah86djs784u92a3m5w475w3w35tyd6v3qumkze80j8a6h5tuqq5xe8y', + amount: [ + { + quantity: '1000000000', + unit: 'lovelace' + }, + { + quantity: '63', + unit: '06f8c5655b4e2b5911fee8ef2fc66b4ce64c8835642695c730a3d108617364' + }, + { + quantity: '22', + unit: '06f8c5655b4e2b5911fee8ef2fc66b4ce64c8835642695c730a3d108646464' + } + ] + }, + { + address: + 'addr_test1qra788mu4sg8kwd93ns9nfdh3k4ufxwg4xhz2r3n064tzfgxu2hyfhlkwuxupa9d5085eunq2qywy7hvmvej456flkns6cy45x', + amount: [ + { + quantity: '9731978536963', + unit: 'lovelace' + } + ] + } + ] + }; + const mockedTxResponse = { + asset_mint_or_burn_count: 5, + block: '356b7d7dbb696ccd12775c016941057a9dc70898d87a63fc752271bb46856940', + block_height: 123_456, + delegation_count: 0, + fees: '182485', + hash: '1e043f100dce12d107f679685acd2fc0610e10f72a92d412794c9773d11d8477', + index: 1, + invalid_before: null, + invalid_hereafter: '13885913', + mir_cert_count: 1, + output_amount: [ + { + quantity: '42000000', + unit: 'lovelace' + }, + { + quantity: '12', + unit: 'b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e' + } + ], + pool_retire_count: 1, + pool_update_count: 1, + redeemer_count: 1, + size: 433, + slot: 42_000_000, + stake_cert_count: 1, + utxo_count: 5, + valid_contract: true, + withdrawal_count: 1 + }; + const mockedMetadataResponse = [ + { + json_metadata: { + hash: '6bf124f217d0e5a0a8adb1dbd8540e1334280d49ab861127868339f43b3948af', + metadata: 'https://nut.link/metadata.json' + }, + label: '1967' + }, + { + json_metadata: { + ADAUSD: [ + { + source: 'ergoOracles', + value: 3 + } + ] + }, + label: '1968' + } + ]; + const mockedMirResponse = [ + { + address: 'stake1u9r76ypf5fskppa0cmttas05cgcswrttn6jrq4yd7jpdnvc7gt0yc', + amount: '431833601', + cert_index: 0, + pot: 'reserve' + } + ]; + const mockedPoolUpdateResponse = [ + { + active_epoch: 210, + cert_index: 0, + fixed_cost: '340000000', + margin_cost: 0.05, + metadata: { + description: 'The best pool ever', + hash: '47c0c68cb57f4a5b4a87bad896fc274678e7aea98e200fa14a1cb40c0cab1d8c', + homepage: 'https://stakentus.com/', + name: 'Stake Nuts', + ticker: 'NUTS', + url: 'https://stakenuts.com/mainnet.json' + }, + owners: ['stake1u98nnlkvkk23vtvf9273uq7cph5ww6u2yq2389psuqet90sv4xv9v'], + pledge: '5000000000', + pool_id: 'pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy', + relays: [ { - address: - 'addr_test1qr05llxkwg5t6c4j3ck5mqfax9wmz35rpcgw3qthrn9z7xcxu2hyfhlkwuxupa9d5085eunq2qywy7hvmvej456flknstdz3k2', - amount: [ - { - quantity: '9732978705764', - unit: 'lovelace' - } - ], - output_index: 1, - tx_hash: '6d50c330a6fba79de6949a8dcd5e4b7ffa3f9442f0c5bed7a78fa6d786c6c863' + dns: 'relay1.stakenuts.com', + dns_srv: '_relays._tcp.relays.stakenuts.com', + ipv4: '4.4.4.4', + ipv6: 'https://stakenuts.com/mainnet.json', + port: 3001 } ], - outputs: [ + reward_account: 'stake1uxkptsa4lkr55jleztw43t37vgdn88l6ghclfwuxld2eykgpgvg3f', + vrf_key: '0b5245f9934ec2151116fb8ec00f35fd00e0aa3b075c4ed12cce440f999d8233' + } + ]; + const mockedPoolRetireResponse = [ + { + cert_index: 0, + pool_id: 'pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy', + retiring_epoch: 216 + } + ]; + const mockedStakeResponse = [ + { + address: 'stake1u9t3a0tcwune5xrnfjg4q7cpvjlgx9lcv0cuqf5mhfjwrvcwrulda', + cert_index: 0, + registration: true + } + ]; + const mockedDelegationResponse = [ + { + active_epoch: 210, + address: 'stake1u9r76ypf5fskppa0cmttas05cgcswrttn6jrq4yd7jpdnvc7gt0yc', + cert_index: 0, + pool_id: 'pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy' + } + ]; + const mockedWithdrawalResponse = [ + { + address: 'stake1u9r76ypf5fskppa0cmttas05cgcswrttn6jrq4yd7jpdnvc7gt0yc', + amount: '431833601' + } + ]; + const mockedReedemerResponse = [ + { + fee: '172033', + purpose: 'spend', + redeemer_data_hash: '923918e403bf43c34b4ef6b48eb2ee04babed17320d8d1b9ff9ad086e86f44ec', + script_hash: 'ec26b89af41bef0f7585353831cb5da42b5b37185e0c8a526143b824', + tx_index: 0, + unit_mem: '1700', + unit_steps: '476468' + } + ]; + const mockedAddressTransactionResponse = [ + { + block_height: 123, + block_time: 131_322, + tx_hash: '1e043f100dce12d107f679685acd2fc0610e10f72a92d412794c9773d11d8477', + tx_index: 0 + } + ]; + const mockedEpochParametersResponse = { key_deposit: '0', pool_deposit: '0' }; + const expectedHydratedTx = { + auxiliaryData: { + blob: new Map([ + [ + 1967n, + new Map([ + ['hash', '6bf124f217d0e5a0a8adb1dbd8540e1334280d49ab861127868339f43b3948af'], + ['metadata', 'https://nut.link/metadata.json'] + ]) + ], + [ + 1968n, + new Map([ + [ + 'ADAUSD', + [ + new Map([ + ['source', 'ergoOracles'], + ['value', 3n] + ]) + ] + ] + ]) + ] + ]) + }, + blockHeader: { + blockNo: Cardano.BlockNo(123_456), + hash: Cardano.BlockId('356b7d7dbb696ccd12775c016941057a9dc70898d87a63fc752271bb46856940'), + slot: Cardano.Slot(42_000_000) + }, + body: { + certificates: [ { - address: - 'addr_test1qzx9hu8j4ah3auytk0mwcupd69hpc52t0cw39a65ndrah86djs784u92a3m5w475w3w35tyd6v3qumkze80j8a6h5tuqq5xe8y', - amount: [ - { - quantity: '1000000000', - unit: 'lovelace' - }, - { - quantity: '63', - unit: '06f8c5655b4e2b5911fee8ef2fc66b4ce64c8835642695c730a3d108617364' + __typename: Cardano.CertificateType.PoolRetirement, + epoch: 216, + poolId: 'pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy' + } as unknown as Cardano.HydratedCertificate, + { + __typename: 'PoolRegistrationCertificate', + deposit: 0n, + poolId: 'pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy', + poolParameters: { + cost: 340_000_000n, + id: 'pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy', + margin: { + denominator: 20, + numerator: 1 }, - { - quantity: '22', - unit: '06f8c5655b4e2b5911fee8ef2fc66b4ce64c8835642695c730a3d108646464' - } - ] + owners: [], + pledge: 5_000_000_000n, + relays: [], + rewardAccount: 'stake1uxkptsa4lkr55jleztw43t37vgdn88l6ghclfwuxld2eykgpgvg3f', + vrf: '0b5245f9934ec2151116fb8ec00f35fd00e0aa3b075c4ed12cce440f999d8233' + } }, { - address: - 'addr_test1qra788mu4sg8kwd93ns9nfdh3k4ufxwg4xhz2r3n064tzfgxu2hyfhlkwuxupa9d5085eunq2qywy7hvmvej456flkns6cy45x', - amount: [ - { - quantity: '9731978536963', - unit: 'lovelace' - } - ] + __typename: 'MirCertificate', + kind: 'ToStakeCreds', + pot: 'reserve', + quantity: 431_833_601n, + rewardAccount: 'stake1u9r76ypf5fskppa0cmttas05cgcswrttn6jrq4yd7jpdnvc7gt0yc' + }, + { + __typename: 'RegistrationCertificate', + deposit: 0n, + stakeCredential: { + hash: '571ebd7877279a18734c91507b0164be8317f863f1c0269bba64e1b3', + type: 0 + } } - ] - }; - const mockedTxResponse = { - asset_mint_or_burn_count: 5, - block: '356b7d7dbb696ccd12775c016941057a9dc70898d87a63fc752271bb46856940', - block_height: 123_456, - delegation_count: 0, - fees: '182485', - hash: '1e043f100dce12d107f679685acd2fc0610e10f72a92d412794c9773d11d8477', - index: 1, - invalid_before: null, - invalid_hereafter: '13885913', - mir_cert_count: 1, - output_amount: [ + ], + collateralReturn: undefined, + collaterals: new Array(), + fee: 182_485n, + inputs: [ + { + address: Cardano.PaymentAddress( + 'addr_test1qr05llxkwg5t6c4j3ck5mqfax9wmz35rpcgw3qthrn9z7xcxu2hyfhlkwuxupa9d5085eunq2qywy7hvmvej456flknstdz3k2' + ), + index: 1, + txId: Cardano.TransactionId('6d50c330a6fba79de6949a8dcd5e4b7ffa3f9442f0c5bed7a78fa6d786c6c863') + } + ], + mint: new Map([ + [Cardano.AssetId('06f8c5655b4e2b5911fee8ef2fc66b4ce64c8835642695c730a3d108617364'), 63n], + [Cardano.AssetId('06f8c5655b4e2b5911fee8ef2fc66b4ce64c8835642695c730a3d108646464'), 22n] + ]), + outputs: [ { - quantity: '42000000', - unit: 'lovelace' + address: Cardano.PaymentAddress( + 'addr_test1qzx9hu8j4ah3auytk0mwcupd69hpc52t0cw39a65ndrah86djs784u92a3m5w475w3w35tyd6v3qumkze80j8a6h5tuqq5xe8y' + ), + value: { + assets: new Map([ + [Cardano.AssetId('06f8c5655b4e2b5911fee8ef2fc66b4ce64c8835642695c730a3d108617364'), 63n], + [Cardano.AssetId('06f8c5655b4e2b5911fee8ef2fc66b4ce64c8835642695c730a3d108646464'), 22n] + ]), + coins: 1_000_000_000n + } }, { - quantity: '12', - unit: 'b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e' + address: Cardano.PaymentAddress( + 'addr_test1qra788mu4sg8kwd93ns9nfdh3k4ufxwg4xhz2r3n064tzfgxu2hyfhlkwuxupa9d5085eunq2qywy7hvmvej456flkns6cy45x' + ), + value: { + coins: 9_731_978_536_963n + } } ], - pool_retire_count: 1, - pool_update_count: 1, - redeemer_count: 1, - size: 433, - slot: 42_000_000, - stake_cert_count: 1, - utxo_count: 5, - valid_contract: true, - withdrawal_count: 1 - }; - const mockedMetadataResponse = [ - { - json_metadata: { - hash: '6bf124f217d0e5a0a8adb1dbd8540e1334280d49ab861127868339f43b3948af', - metadata: 'https://nut.link/metadata.json' - }, - label: '1967' + proposalProcedures: undefined, + validityInterval: { + invalidBefore: undefined, + invalidHereafter: Cardano.Slot(13_885_913) }, + votingProcedures: undefined, + withdrawals: [ + { + quantity: 431_833_601n, + stakeAddress: 'stake1u9r76ypf5fskppa0cmttas05cgcswrttn6jrq4yd7jpdnvc7gt0yc' + } + ] + }, + id: Cardano.TransactionId('1e043f100dce12d107f679685acd2fc0610e10f72a92d412794c9773d11d8477'), + index: 1, + inputSource: Cardano.InputSource.inputs, + txSize: 433, + witness: { + redeemers: [ + { + data: Buffer.from(new Uint8Array([110, 111, 116, 32, 105, 109, 112, 108, 101, 109, 101, 110, 116, 101, 100])), + executionUnits: { + memory: 1700, + steps: 476_468 + }, + index: 0, + purpose: 'spend' + } as Cardano.Redeemer + ], + signatures: new Map() // not available in blockfrost + } + } as Cardano.HydratedTx; + + const mockedNetworkInfoProvider = { + eraSummaries: jest.fn().mockResolvedValue([ { - json_metadata: { - ADAUSD: [ - { - source: 'ergoOracles', - value: 3 - } - ] - }, - label: '1968' - } - ]; - const mockedMirResponse = [ - { - address: 'stake1u9r76ypf5fskppa0cmttas05cgcswrttn6jrq4yd7jpdnvc7gt0yc', - amount: '431833601', - cert_index: 0, - pot: 'reserve' - } - ]; - const mockedPoolUpdateResponse = [ - { - active_epoch: 210, - cert_index: 0, - fixed_cost: '340000000', - margin_cost: 0.05, - metadata: { - description: 'The best pool ever', - hash: '47c0c68cb57f4a5b4a87bad896fc274678e7aea98e200fa14a1cb40c0cab1d8c', - homepage: 'https://stakentus.com/', - name: 'Stake Nuts', - ticker: 'NUTS', - url: 'https://stakenuts.com/mainnet.json' - }, - owners: ['stake1u98nnlkvkk23vtvf9273uq7cph5ww6u2yq2389psuqet90sv4xv9v'], - pledge: '5000000000', - pool_id: 'pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy', - relays: [ - { - dns: 'relay1.stakenuts.com', - dns_srv: '_relays._tcp.relays.stakenuts.com', - ipv4: '4.4.4.4', - ipv6: 'https://stakenuts.com/mainnet.json', - port: 3001 - } - ], - reward_account: 'stake1uxkptsa4lkr55jleztw43t37vgdn88l6ghclfwuxld2eykgpgvg3f', - vrf_key: '0b5245f9934ec2151116fb8ec00f35fd00e0aa3b075c4ed12cce440f999d8233' - } - ]; - const mockedPoolRetireResponse = [ - { - cert_index: 0, - pool_id: 'pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy', - retiring_epoch: 216 - } - ]; - const mockedStakeResponse = [ - { - address: 'stake1u9t3a0tcwune5xrnfjg4q7cpvjlgx9lcv0cuqf5mhfjwrvcwrulda', - cert_index: 0, - registration: true - } - ]; - const mockedDelegationResponse = [ - { - active_epoch: 210, - address: 'stake1u9r76ypf5fskppa0cmttas05cgcswrttn6jrq4yd7jpdnvc7gt0yc', - cert_index: 0, - pool_id: 'pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy' - } - ]; - const mockedWithdrawalResponse = [ - { - address: 'stake1u9r76ypf5fskppa0cmttas05cgcswrttn6jrq4yd7jpdnvc7gt0yc', - amount: '431833601' - } - ]; - const mockedReedemerResponse = [ - { - fee: '172033', - purpose: 'spend', - redeemer_data_hash: '923918e403bf43c34b4ef6b48eb2ee04babed17320d8d1b9ff9ad086e86f44ec', - script_hash: 'ec26b89af41bef0f7585353831cb5da42b5b37185e0c8a526143b824', - tx_index: 0, - unit_mem: '1700', - unit_steps: '476468' - } - ]; - const mockedAddressTransactionResponse = [ - { - block_height: 123, - block_time: 131_322, - tx_hash: '1e043f100dce12d107f679685acd2fc0610e10f72a92d412794c9773d11d8477', - tx_index: 0 + end: { slot: 100, time: new Date(1_506_203_092_000) }, + parameters: { epochLength: 100, safeZone: 0, slotLength: 1 }, + start: { slot: 0, time: new Date(1_506_203_091_000) } } - ]; - const expectedHydratedTx = { - auxiliaryData: { - blob: new Map([ - [ - 1967n, - new Map([ - ['hash', '6bf124f217d0e5a0a8adb1dbd8540e1334280d49ab861127868339f43b3948af'], - ['metadata', 'https://nut.link/metadata.json'] - ]) - ], - [ - 1968n, - new Map([ - [ - 'ADAUSD', - [ - new Map([ - ['source', 'ergoOracles'], - ['value', 3n] - ]) - ] - ] - ]) - ] - ]) - }, + ]) + } as unknown as NetworkInfoProvider; + + describe('transactionsBy*', () => { + let blockfrost: BlockFrostAPI; + let provider: BlockfrostChainHistoryProvider; + beforeEach(() => { + BlockFrostAPI.prototype.txsUtxos = jest.fn().mockResolvedValue(txsUtxosResponse); + BlockFrostAPI.prototype.txs = jest.fn().mockResolvedValue(mockedTxResponse); + BlockFrostAPI.prototype.txsMetadata = jest.fn().mockResolvedValue(mockedMetadataResponse); + BlockFrostAPI.prototype.txsMirs = jest.fn().mockResolvedValue(mockedMirResponse); + BlockFrostAPI.prototype.txsPoolUpdates = jest.fn().mockResolvedValue(mockedPoolUpdateResponse); + BlockFrostAPI.prototype.txsPoolRetires = jest.fn().mockResolvedValue(mockedPoolRetireResponse); + BlockFrostAPI.prototype.txsStakes = jest.fn().mockResolvedValue(mockedStakeResponse); + BlockFrostAPI.prototype.txsDelegations = jest.fn().mockResolvedValue(mockedDelegationResponse); + BlockFrostAPI.prototype.txsWithdrawals = jest.fn().mockResolvedValue(mockedWithdrawalResponse); + BlockFrostAPI.prototype.txsRedeemers = jest.fn().mockResolvedValue(mockedReedemerResponse); + BlockFrostAPI.prototype.addressesTransactions = jest.fn().mockResolvedValue(mockedAddressTransactionResponse); + BlockFrostAPI.prototype.epochsParameters = jest.fn().mockResolvedValue(mockedEpochParametersResponse); + + blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey }); + + provider = new BlockfrostChainHistoryProvider({ + blockfrost, + logger, + networkInfoProvider: mockedNetworkInfoProvider + }); + provider.fetchCBOR = jest.fn().mockRejectedValue('CBOR is null'); + }); + describe('transactionsByAddresses', () => { + test('converts responses correctly', async () => { + const response = await provider.transactionsByAddresses({ + addresses: [Cardano.PaymentAddress('2cWKMJemoBai9J7kVvRTukMmdfxtjL9z7c396rTfrrzfAZ6EeQoKLC2y1k34hswwm4SVr')], + pagination: { limit: 20, startAt: 0 } + }); + + expect(response.totalResultCount).toBe(1); + expect(response.pageResults[0]).toEqual(expectedHydratedTx); + }); + }); + + describe('transactionsByHashes', () => { + test('converts responses correctly', async () => { + const response = await provider.transactionsByHashes({ + ids: ['1e043f100dce12d107f679685acd2fc0610e10f72a92d412794c9773d11d8477'].map(Cardano.TransactionId) + }); + + expect(response).toHaveLength(1); + expect(response[0]).toEqual(expectedHydratedTx); + }); + }); + }); + describe('transactionsBy* with CBOR', () => { + const expectedHydratedTxCBOR = { + auxiliaryData: undefined, blockHeader: { blockNo: Cardano.BlockNo(123_456), hash: Cardano.BlockId('356b7d7dbb696ccd12775c016941057a9dc70898d87a63fc752271bb46856940'), slot: Cardano.Slot(42_000_000) }, body: { - certificates: [ - { - __typename: Cardano.CertificateType.PoolRetirement, - cert_index: 0, - epoch: 216, - poolId: 'pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy' - } as unknown as Cardano.HydratedCertificate, - { - __typename: 'PoolRegistrationCertificate', - cert_index: 0, - poolId: 'pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy', - poolParameters: null - }, - { - __typename: 'MirCertificate', - cert_index: 0, - kind: 'ToStakeCreds', - pot: 'reserve', - quantity: 431_833_601n, - rewardAccount: 'stake1u9r76ypf5fskppa0cmttas05cgcswrttn6jrq4yd7jpdnvc7gt0yc' - }, - { - __typename: 'StakeRegistrationCertificate', - cert_index: 0, - stakeCredential: { - hash: '571ebd7877279a18734c91507b0164be8317f863f1c0269bba64e1b3', - type: 0 - } - } - ], + certificates: undefined, + collateralReturn: undefined, collaterals: new Array(), - fee: 182_485n, + fee: 261_983n, inputs: [ { address: Cardano.PaymentAddress( @@ -265,10 +416,7 @@ describe('blockfrostChainHistoryProvider', () => { txId: Cardano.TransactionId('6d50c330a6fba79de6949a8dcd5e4b7ffa3f9442f0c5bed7a78fa6d786c6c863') } ], - mint: new Map([ - [Cardano.AssetId('06f8c5655b4e2b5911fee8ef2fc66b4ce64c8835642695c730a3d108617364'), 63n], - [Cardano.AssetId('06f8c5655b4e2b5911fee8ef2fc66b4ce64c8835642695c730a3d108646464'), 22n] - ]), + mint: undefined, outputs: [ { address: Cardano.PaymentAddress( @@ -287,21 +435,17 @@ describe('blockfrostChainHistoryProvider', () => { 'addr_test1qra788mu4sg8kwd93ns9nfdh3k4ufxwg4xhz2r3n064tzfgxu2hyfhlkwuxupa9d5085eunq2qywy7hvmvej456flkns6cy45x' ), value: { - assets: new Map(), coins: 9_731_978_536_963n } } ], + proposalProcedures: undefined, validityInterval: { - invalidBefore: undefined, - invalidHereafter: Cardano.Slot(13_885_913) + invalidBefore: Cardano.Slot(72_258_832), + invalidHereafter: Cardano.Slot(72_259_732) }, - withdrawals: [ - { - quantity: 431_833_601n, - stakeAddress: 'stake1u9r76ypf5fskppa0cmttas05cgcswrttn6jrq4yd7jpdnvc7gt0yc' - } - ] + votingProcedures: undefined, + withdrawals: undefined }, id: Cardano.TransactionId('1e043f100dce12d107f679685acd2fc0610e10f72a92d412794c9773d11d8477'), index: 1, @@ -311,25 +455,21 @@ describe('blockfrostChainHistoryProvider', () => { redeemers: [ { data: Buffer.from( - new Uint8Array([ - 101, 99, 50, 54, 98, 56, 57, 97, 102, 52, 49, 98, 101, 102, 48, 102, 55, 53, 56, 53, 51, 53, 51, 56, 51, - 49, 99, 98, 53, 100, 97, 52, 50, 98, 53, 98, 51, 55, 49, 56, 53, 101, 48, 99, 56, 97, 53, 50, 54, 49, - 52, 51, 98, 56, 50, 52 - ]) + new Uint8Array([110, 111, 116, 32, 105, 109, 112, 108, 101, 109, 101, 110, 116, 101, 100]) ), executionUnits: { - memory: 1700, - steps: 476_468 + memory: 436_212, + steps: 179_492_261 }, index: 0, purpose: 'spend' - } as Cardano.Redeemer + } ], - signatures: new Map() // not available in blockfrost + signatures: new Map() } } as Cardano.HydratedTx; let blockfrost: BlockFrostAPI; - + let provider: BlockfrostChainHistoryProvider; beforeEach(() => { BlockFrostAPI.prototype.txsUtxos = jest.fn().mockResolvedValue(txsUtxosResponse); BlockFrostAPI.prototype.txs = jest.fn().mockResolvedValue(mockedTxResponse); @@ -342,175 +482,42 @@ describe('blockfrostChainHistoryProvider', () => { BlockFrostAPI.prototype.txsWithdrawals = jest.fn().mockResolvedValue(mockedWithdrawalResponse); BlockFrostAPI.prototype.txsRedeemers = jest.fn().mockResolvedValue(mockedReedemerResponse); BlockFrostAPI.prototype.addressesTransactions = jest.fn().mockResolvedValue(mockedAddressTransactionResponse); + BlockFrostAPI.prototype.epochsParameters = jest.fn().mockResolvedValue(mockedEpochParametersResponse); blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey }); + + provider = new BlockfrostChainHistoryProvider({ + blockfrost, + logger, + networkInfoProvider: mockedNetworkInfoProvider + }); + provider.fetchCBOR = jest + .fn() + .mockResolvedValue( + '84a90082825820262e95982cfe9fbc565a0e9a5343d323be8e08e51de23a5262b75ce6984179a900825820262e95982cfe9fbc565a0e9a5343d323be8e08e51de23a5262b75ce6984179a9010d81825820262e95982cfe9fbc565a0e9a5343d323be8e08e51de23a5262b75ce6984179a90112818258205de304d9c8884dd62ad8535d529e3d8fd5f212cc3c0c39410e4f3bccfca6e46b010182a300581d7039a4c3afe97b4c2d3385fefd5206d1865c74786b7ce955ebb6532e7a01821a001b9f18a1581cdab1406f1c769fbdb00514c494eed47a54c7ffc5d7aafc524cca069aa14d446a65644f7261636c654e465401028201d81858a6d8799f58404fe28ad1e94e742a6c79eb8f7cc44128965579792e4b5be940bfa61c3797914970fe2055016c2b7c3bbd9de43194e82b22a4ccdbee80b4099f73a304d9550707d8799fd8799f1a000f42401a00053dc9ffd8799fd8799fd87a9f1b00000192515efa80ffd87a80ffd8799fd87a9f1b00000192516cb620ffd87a80ffff43555344ff581cdab1406f1c769fbdb00514c494eed47a54c7ffc5d7aafc524cca069aff82583900f0a26fc170ad82b64a3d43dede08e78ea6e2028b5101058d0263a80a4c0ec23eba3aa27a4b8d61107f59f3e0d24b7bb6d7ae1a4bbd689c8d1abe8373ea021a0003ff5f031a044e9894081a044e95100e81581cf0a26fc170ad82b64a3d43dede08e78ea6e2028b5101058d0263a80a0b58205b3d8c3bc5032e4d2dbe3c07d23d7fb5133f4ec7466e73744317cebe2ed12610a200818258205401b7f67442e6cc870fbcffe921d24ab03e97e03bb655a24b4f424fd832c61558405bddd2f0d88e254ef1f0a84276aaadea4df3b05f8c441d9774b6a8d1c2556437a79e5131ea4475169d45e6e02bb3b31cd93216c5499e2c316aa68e485d6800000581840000d87980821a0006a7f41a0ab2d5a5f5f6' + ); }); - describe('transactionsByAddresses', () => { - test('converts responses correctly', async () => { - const provider = new BlockfrostChainHistoryProvider({ blockfrost, logger }); + describe('transactionsByAddresses (CBOR)', () => { + test('converts responses correctly (CBOR)', async () => { const response = await provider.transactionsByAddresses({ addresses: [Cardano.PaymentAddress('2cWKMJemoBai9J7kVvRTukMmdfxtjL9z7c396rTfrrzfAZ6EeQoKLC2y1k34hswwm4SVr')], pagination: { limit: 20, startAt: 0 } }); expect(response.totalResultCount).toBe(1); - expect(response.pageResults[0]).toEqual(expectedHydratedTx); + expect(response.pageResults[0]).toEqual(expectedHydratedTxCBOR); }); }); - describe('transactionsByHashes', () => { - test('converts responses correctly', async () => { - const provider = new BlockfrostChainHistoryProvider({ blockfrost, logger }); + describe('transactionsByHashes (CBOR)', () => { + test('converts responses correctly (CBOR)', async () => { const response = await provider.transactionsByHashes({ ids: ['1e043f100dce12d107f679685acd2fc0610e10f72a92d412794c9773d11d8477'].map(Cardano.TransactionId) }); expect(response).toHaveLength(1); - expect(response[0]).toEqual(expectedHydratedTx); + expect(response[0]).toEqual(expectedHydratedTxCBOR); }); }); }); - - describe('transactionsBy* throws', () => { - let blockfrost: BlockFrostAPI; - const mockedError = { - error: 'Forbidden', - message: 'Invalid project token.', - status_code: 403, - url: 'test' - }; - - const mockedErrorMethod = jest.fn().mockRejectedValue(mockedError); - beforeAll(() => { - BlockFrostAPI.prototype.txsUtxos = mockedErrorMethod; - BlockFrostAPI.prototype.txs = mockedErrorMethod; - BlockFrostAPI.prototype.txsMetadata = mockedErrorMethod; - BlockFrostAPI.prototype.txsMirs = mockedErrorMethod; - BlockFrostAPI.prototype.txsPoolUpdates = mockedErrorMethod; - BlockFrostAPI.prototype.txsPoolRetires = mockedErrorMethod; - BlockFrostAPI.prototype.txsStakes = mockedErrorMethod; - BlockFrostAPI.prototype.txsDelegations = mockedErrorMethod; - BlockFrostAPI.prototype.txsWithdrawals = mockedErrorMethod; - BlockFrostAPI.prototype.txsRedeemers = mockedErrorMethod; - BlockFrostAPI.prototype.addressesTransactions = mockedErrorMethod; - - blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey }); - }); - beforeEach(() => { - mockedErrorMethod.mockClear(); - }); - describe('transactionsByAddresses', () => { - test('throws', async () => { - const provider = new BlockfrostChainHistoryProvider({ blockfrost, logger }); - - await expect(() => - provider.transactionsByAddresses({ - addresses: [ - Cardano.PaymentAddress('2cWKMJemoBai9J7kVvRTukMmdfxtjL9z7c396rTfrrzfAZ6EeQoKLC2y1k34hswwm4SVr') - ], - pagination: { limit: 20, startAt: 0 } - }) - ).rejects.toThrow(); - - expect(mockedErrorMethod).toBeCalledTimes(1); - }); - }); - - describe('transactionsByHashes', () => { - test('throws', async () => { - const provider = new BlockfrostChainHistoryProvider({ blockfrost, logger }); - - await expect(() => - provider.transactionsByHashes({ - ids: ['1e043f100dce12d107f679685acd2fc0610e10f72a92d412794c9773d11d8477'].map(Cardano.TransactionId) - }) - ).rejects.toThrow(); - expect(mockedErrorMethod).toBeCalledTimes(1); - }); - }); - }); - describe('blocksByHashes', () => { - const blockResponse = { - block_vrf: 'vrf_vk19j362pkr4t9y0m3qxgmrv0365vd7c4ze03ny4jh84q8agjy4ep4s99zvg8', - confirmations: 0, - epoch: 157, - epoch_slot: 312_794, - fees: '513839', - hash: '86e837d8a6cdfddaf364525ce9857eb93430b7e59a5fd776f0a9e11df476a7e5', - height: 2_927_618, - next_block: null, - output: '9249073880', - previous_block: 'da56fa53483a3a087c893b41aa0d73a303148c2887b3f7535e0b505ea5dc10aa', - size: 1050, - slot: 37_767_194, - slot_leader: 'pool1zuevzm3xlrhmwjw87ec38mzs02tlkwec9wxpgafcaykmwg7efhh', - time: 1_632_136_410, - tx_count: 3 - } as Responses['block_content']; - test('blocksByHashes', async () => { - BlockFrostAPI.prototype.blocks = jest.fn().mockResolvedValue(blockResponse); - - const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey }); - const provider = new BlockfrostChainHistoryProvider({ blockfrost, logger }); - const response = await provider.blocksByHashes({ - ids: [Cardano.BlockId('0dbe461fb5f981c0d01615332b8666340eb1a692b3034f46bcb5f5ea4172b2ed')] - }); - - expect(response).toMatchObject([ - { - confirmations: 0, - date: new Date(1_632_136_410_000), - epoch: 157, - epochSlot: 312_794, - fees: 513_839n, - header: { - blockNo: 2_927_618, - hash: Cardano.BlockId('86e837d8a6cdfddaf364525ce9857eb93430b7e59a5fd776f0a9e11df476a7e5'), - slot: 37_767_194 - }, - nextBlock: undefined, - previousBlock: Cardano.BlockId('da56fa53483a3a087c893b41aa0d73a303148c2887b3f7535e0b505ea5dc10aa'), - size: 1050, - slotLeader: Cardano.PoolId('pool1zuevzm3xlrhmwjw87ec38mzs02tlkwec9wxpgafcaykmwg7efhh'), - totalOutput: 9_249_073_880n, - txCount: 3, - vrf: Cardano.VrfVkBech32('vrf_vk19j362pkr4t9y0m3qxgmrv0365vd7c4ze03ny4jh84q8agjy4ep4s99zvg8') - } as Cardano.ExtendedBlockInfo - ]); - }); - - test('blocksByHashes, genesis delegate slot leader', async () => { - const slotLeader = 'ShelleyGenesis-eff1b5b26e65b791'; - BlockFrostAPI.prototype.blocks = jest.fn().mockResolvedValue({ ...blockResponse, slot_leader: slotLeader }); - - const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey }); - const provider = new BlockfrostChainHistoryProvider({ blockfrost, logger }); - const response = await provider.blocksByHashes({ - ids: [Cardano.BlockId('0dbe461fb5f981c0d01615332b8666340eb1a692b3034f46bcb5f5ea4172b2ed')] - }); - - expect(response[0].slotLeader).toBe(slotLeader); - }); - test('throws', async () => { - const mockedError = { - error: 'Forbidden', - message: 'Invalid project token.', - status_code: 403, - url: 'test' - }; - const mockedErrorMethod = jest.fn().mockRejectedValue(mockedError); - - BlockFrostAPI.prototype.blocks = mockedErrorMethod; - - const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey }); - const provider = new BlockfrostChainHistoryProvider({ blockfrost, logger }); - - await expect(() => - provider.blocksByHashes({ - ids: [Cardano.BlockId('0dbe461fb5f981c0d01615332b8666340eb1a692b3034f46bcb5f5ea4172b2ed')] - }) - ).rejects.toThrow(); - expect(mockedErrorMethod).toBeCalledTimes(1); - }); - }); }); diff --git a/packages/core/src/Provider/providerUtil.ts b/packages/core/src/Provider/providerUtil.ts index 067b432c012..a46acad0cc9 100644 --- a/packages/core/src/Provider/providerUtil.ts +++ b/packages/core/src/Provider/providerUtil.ts @@ -17,6 +17,9 @@ export const withProviderErrors = (providerImplementation: T, toPr const tryParseBigIntKey = (key: string) => { // skip converting hex values if (key.startsWith('0x')) return key.slice(2); + // skip converting empty string + if (key.length === 0) return key; + try { return BigInt(key); } catch { diff --git a/packages/core/src/Serialization/AuxiliaryData/AuxiliaryData.ts b/packages/core/src/Serialization/AuxiliaryData/AuxiliaryData.ts index 92e85c0d0bc..089f1b9ee75 100644 --- a/packages/core/src/Serialization/AuxiliaryData/AuxiliaryData.ts +++ b/packages/core/src/Serialization/AuxiliaryData/AuxiliaryData.ts @@ -228,10 +228,13 @@ export class AuxiliaryData { */ toCore(): Cardano.AuxiliaryData { const scripts = this.#getCoreScripts(); - return { - blob: this.#metadata ? this.#metadata.toCore() : undefined, - scripts: scripts.length > 0 ? scripts : undefined + const auxiliaryData: Cardano.AuxiliaryData = { + blob: this.#metadata ? this.#metadata.toCore() : undefined }; + + if (scripts.length > 0) auxiliaryData.scripts = scripts; + + return auxiliaryData; } /** diff --git a/packages/core/src/Serialization/Certificates/PoolParams/PoolParams.ts b/packages/core/src/Serialization/Certificates/PoolParams/PoolParams.ts index 73bf485c9c4..f55523b1e99 100644 --- a/packages/core/src/Serialization/Certificates/PoolParams/PoolParams.ts +++ b/packages/core/src/Serialization/Certificates/PoolParams/PoolParams.ts @@ -194,11 +194,10 @@ export class PoolParams { toCore(): Cardano.PoolParameters { const rewardAccountAddress = this.#rewardAccount.toAddress(); - return { + const poolParams: Cardano.PoolParameters = { cost: this.#cost, id: PoolId.fromKeyHash(this.#operator), margin: this.#margin.toCore(), - metadataJson: this.#poolMetadata?.toCore(), owners: this.#poolOwners .toCore() .map((keyHash) => createRewardAccount(keyHash, rewardAccountAddress.getNetworkId())), @@ -207,6 +206,10 @@ export class PoolParams { rewardAccount: this.#rewardAccount.toAddress().toBech32() as Cardano.RewardAccount, vrf: this.#vrfKeyHash }; + + if (this.#poolMetadata) poolParams.metadataJson = this.#poolMetadata.toCore(); + + return poolParams; } /** diff --git a/packages/core/src/Serialization/Transaction.ts b/packages/core/src/Serialization/Transaction.ts index c922e806a4a..6547666b53e 100644 --- a/packages/core/src/Serialization/Transaction.ts +++ b/packages/core/src/Serialization/Transaction.ts @@ -122,13 +122,18 @@ export class Transaction { * @returns The Core Tx object. */ toCore(): Cardano.Tx { - return { - auxiliaryData: this.#auxiliaryData ? this.#auxiliaryData.toCore() : undefined, + const tx: Cardano.Tx = { body: this.#body.toCore(), id: this.getId(), isValid: this.#isValid, witness: this.#witnessSet.toCore() }; + + if (this.#auxiliaryData) { + tx.auxiliaryData = this.#auxiliaryData.toCore(); + } + + return tx; } /** diff --git a/packages/core/src/Serialization/TransactionBody/TransactionOutput.ts b/packages/core/src/Serialization/TransactionBody/TransactionOutput.ts index d516e59d51a..5b7b34cbee1 100644 --- a/packages/core/src/Serialization/TransactionBody/TransactionOutput.ts +++ b/packages/core/src/Serialization/TransactionBody/TransactionOutput.ts @@ -208,16 +208,21 @@ export class TransactionOutput { * @returns The Core TransactionOutput object. */ toCore(): Cardano.TxOut { - return { + const value = this.#amount.toCore(); + if (!value.assets) delete value.assets; + + const txOut: Cardano.TxOut = { address: this.#address.asByron() ? this.#address.toBase58() : (this.#address.toBech32() as unknown as Cardano.PaymentAddress), - datum: - this.#datum && this.#datum.kind() === DatumKind.InlineData ? this.#datum.asInlineData()?.toCore() : undefined, - datumHash: this.#datum && this.#datum.kind() === DatumKind.DataHash ? this.#datum.asDataHash() : undefined, - scriptReference: this.#scriptRef ? this.#scriptRef.toCore() : undefined, - value: this.#amount.toCore() + value }; + + if (this.#datum && this.#datum.kind() === DatumKind.InlineData) txOut.datum = this.#datum.asInlineData()?.toCore(); + if (this.#datum && this.#datum.kind() === DatumKind.DataHash) txOut.datumHash = this.#datum.asDataHash(); + if (this.#scriptRef) txOut.scriptReference = this.#scriptRef.toCore(); + + return txOut; } /** diff --git a/packages/core/src/Serialization/Update/ProtocolParamUpdate.ts b/packages/core/src/Serialization/Update/ProtocolParamUpdate.ts index 4f3b39eedb0..632af8f47f9 100644 --- a/packages/core/src/Serialization/Update/ProtocolParamUpdate.ts +++ b/packages/core/src/Serialization/Update/ProtocolParamUpdate.ts @@ -412,7 +412,7 @@ export class ProtocolParamUpdate { * @returns The Core ProtocolParamUpdate object. */ toCore(): Cardano.ProtocolParametersUpdate { - return { + const protocolParametersUpdate: Cardano.ProtocolParametersUpdate = { coinsPerUtxoByte: this.#adaPerUtxoByte ? Number(this.#adaPerUtxoByte) : undefined, collateralPercentage: this.#collateralPercentage, committeeTermLimit: this.#committeeTermLimit ? EpochNo(this.#committeeTermLimit) : undefined, @@ -420,9 +420,7 @@ export class ProtocolParamUpdate { dRepDeposit: this.#drepDeposit, dRepInactivityPeriod: this.#drepInactivityPeriod ? EpochNo(this.#drepInactivityPeriod) : undefined, dRepVotingThresholds: this.#drepVotingThresholds?.toCore(), - decentralizationParameter: this.#d ? this.#d.toFloat().toString() : undefined, desiredNumberOfPools: this.#nOpt, - extraEntropy: this.#extraEntropy, governanceActionDeposit: this.#governanceActionDeposit, governanceActionValidityPeriod: this.#governanceActionValidityPeriod ? EpochNo(this.#governanceActionValidityPeriod) @@ -447,10 +445,15 @@ export class ProtocolParamUpdate { poolRetirementEpochBound: this.#maxEpoch, poolVotingThresholds: this.#poolVotingThresholds?.toCore(), prices: this.#executionCosts?.toCore(), - protocolVersion: this.#protocolVersion?.toCore(), stakeKeyDeposit: this.#keyDeposit ? Number(this.#keyDeposit) : undefined, treasuryExpansion: this.#treasuryGrowthRate ? this.#treasuryGrowthRate.toFloat().toString() : undefined }; + + if (this.#d) protocolParametersUpdate.decentralizationParameter = this.#d.toFloat().toString(); + if (this.#extraEntropy !== undefined) protocolParametersUpdate.extraEntropy = this.#extraEntropy; + if (this.#protocolVersion) protocolParametersUpdate.protocolVersion = this.#protocolVersion.toCore(); + + return protocolParametersUpdate; } /** diff --git a/packages/e2e/.env.example b/packages/e2e/.env.example index 7a9bd4986e1..24fc638e6ae 100644 --- a/packages/e2e/.env.example +++ b/packages/e2e/.env.example @@ -28,6 +28,13 @@ TEST_CLIENT_STAKE_POOL_PROVIDER=http TEST_CLIENT_STAKE_POOL_PROVIDER_PARAMS='{"baseUrl":"http://localhost:4000/"}' WS_PROVIDER_URL='ws://localhost:4100/ws' +# Uncomment following to run e2e tests agains blockfrost providers locally +#TEST_CLIENT_CHAIN_HISTORY_PROVIDER=http +#TEST_CLIENT_CHAIN_HISTORY_PROVIDER_PARAMS='{"baseUrl":"http://localhost:4001/"}' +#TEST_CLIENT_REWARDS_PROVIDER_PARAMS='{"baseUrl":"http://localhost:4001/"}' +#TEST_CLIENT_UTXO_PROVIDER=http +#TEST_CLIENT_UTXO_PROVIDER_PARAMS='{"baseUrl":"http://localhost:4001/"}' + # Required by test:ogmios, test:blockfrost DB_SYNC_CONNECTION_STRING='postgresql://postgres:doNoUseThisSecret!@localhost:5435/cexplorer' STAKE_POOL_CONNECTION_STRING='postgresql://postgres:doNoUseThisSecret!@localhost:5435/stake_pool' diff --git a/packages/e2e/docker-compose.yml b/packages/e2e/docker-compose.yml index f4ca9a127d3..8ffa7ba53cd 100644 --- a/packages/e2e/docker-compose.yml +++ b/packages/e2e/docker-compose.yml @@ -6,7 +6,6 @@ x-logging: &logging max-file: '10' services: - blockfrost-ryo: depends_on: local-testnet: @@ -15,7 +14,6 @@ services: NODE_ENV: local-network volumes: - ./local-network/config/network/blockfrost-ryo:/app/config - profiles: [blockfrost-ryo] local-testnet: <<: *logging diff --git a/packages/e2e/jest.config.js b/packages/e2e/jest.config.js index 944d2806e22..a3731178edd 100644 --- a/packages/e2e/jest.config.js +++ b/packages/e2e/jest.config.js @@ -26,6 +26,7 @@ const realAdaTestFileNames = [ module.exports = { projects: [ { ...project('blockfrost'), globalSetup: './test/blockfrost/setup.ts' }, + project('blockfrost-providers'), project('local-network'), project('long-running'), project('ogmios'), diff --git a/packages/e2e/local-network/Dockerfile b/packages/e2e/local-network/Dockerfile index d8f9bf00b7c..a31cf59a79c 100644 --- a/packages/e2e/local-network/Dockerfile +++ b/packages/e2e/local-network/Dockerfile @@ -1,6 +1,6 @@ ARG UBUNTU_VERSION=20.04 -FROM ubuntu:${UBUNTU_VERSION} as builder +FROM ubuntu:${UBUNTU_VERSION} AS builder ENV DEBIAN_FRONTEND=nonintercative diff --git a/packages/e2e/local-network/templates/babbage/db-sync-config.json b/packages/e2e/local-network/templates/babbage/db-sync-config.json index 733b54ece5f..a117060f50b 100644 --- a/packages/e2e/local-network/templates/babbage/db-sync-config.json +++ b/packages/e2e/local-network/templates/babbage/db-sync-config.json @@ -111,5 +111,8 @@ "scName": "stdout", "scRotation": null } - ] + ], + "insert_options": { + "tx_cbor": "enable" + } } diff --git a/packages/e2e/local-network/templates/blockfrost-ryo/local-network.yaml b/packages/e2e/local-network/templates/blockfrost-ryo/local-network.yaml index 468c196276d..d1b14d16c6e 100644 --- a/packages/e2e/local-network/templates/blockfrost-ryo/local-network.yaml +++ b/packages/e2e/local-network/templates/blockfrost-ryo/local-network.yaml @@ -11,4 +11,5 @@ dbSync: maxConnections: 5 network: "custom" genesisDataFolder: '/app/config' -tokenRegistryUrl: "https://metadata.cardano-testnet.iohkdev.io" +tokenRegistryUrl: "https://tokens.cardano.org" +tokenRegistryEnabled: false diff --git a/packages/e2e/package.json b/packages/e2e/package.json index f96389bd193..3a86a161ab8 100644 --- a/packages/e2e/package.json +++ b/packages/e2e/package.json @@ -23,6 +23,7 @@ "load-test-custom:wallet-restoration": "ts-node test/load-test-custom/wallet-restoration/wallet-restoration.test.ts", "test": "echo 'test' command not implemented yet", "test:blockfrost": "jest -c jest.config.js --forceExit --selectProjects blockfrost --runInBand --verbose", + "test:blockfrost:providers": "jest -c jest.config.js --forceExit --selectProjects blockfrost-providers --runInBand --verbose", "test:utils": "jest -c jest.config.js --forceExit --selectProjects utils --verbose", "test:long-running": "jest -c jest.config.js --forceExit --selectProjects long-running --runInBand --verbose", "test:local-network": "jest -c jest.config.js --forceExit --selectProjects local-network --runInBand --verbose", @@ -46,9 +47,8 @@ "test:web-extension:watch": "run-s test:web-extension:build test:web-extension:watch:bg", "test:web-extension:watch:bg": "run-p test:web-extension:watch:build test:web-extension:watch:run", "test:ws": "jest -c jest.config.js --forceExit --selectProjects ws-server --runInBand --verbose", - "local-network:common": "DISABLE_DB_CACHE=${DISABLE_DB_CACHE:-true} SUBMIT_API_ARGS='--testnet-magic 888' USE_BLOCKFROST=false __FIX_UMASK__=$(chmod -R a+r ../../compose/placeholder-secrets) docker compose --env-file ../cardano-services/environments/.env.local -p local-network-e2e -f docker-compose.yml -f ../../compose/common.yml -f ../../compose/$(uname -m).yml $FILES --profile ${DOCKER_COMPOSE_PROFILE:-none} up", + "local-network:common": "DISABLE_DB_CACHE=${DISABLE_DB_CACHE:-true} SUBMIT_API_ARGS='--testnet-magic 888' USE_BLOCKFROST=false __FIX_UMASK__=$(chmod -R a+r ../../compose/placeholder-secrets) docker compose --env-file ../cardano-services/environments/.env.local -p local-network-e2e -f docker-compose.yml -f ../../compose/common.yml -f ../../compose/$(uname -m).yml $FILES up", "local-network:up": "FILES='' yarn local-network:common", - "local-network:blockfrost:up": "FILES='' DOCKER_COMPOSE_PROFILE='blockfrost-ryo' yarn local-network:common", "local-network:single:up": "FILES='' yarn local-network:common cardano-node file-server local-testnet ogmios postgres", "local-network:profile:up": "FILES='-f ../../compose/pg-agent.yml' yarn local-network:common", "local-network:down": "docker compose -p local-network-e2e -f docker-compose.yml -f ../../compose/common.yml -f ../../compose/pg-agent.yml down -v --remove-orphans", diff --git a/packages/e2e/test/blockfrost-providers/networkInfo.test.ts b/packages/e2e/test/blockfrost-providers/networkInfo.test.ts new file mode 100644 index 00000000000..819b2a30dcb --- /dev/null +++ b/packages/e2e/test/blockfrost-providers/networkInfo.test.ts @@ -0,0 +1,26 @@ +// cSpell:ignore cardano utxos + +import { NetworkInfoProvider } from '@cardano-sdk/core'; +import { logger } from '@cardano-sdk/util-dev'; +import { networkInfoHttpProvider } from '@cardano-sdk/cardano-services-client'; +import { toSerializableObject } from '@cardano-sdk/util'; + +// LW-11697 to enable this +describe.skip('Web Socket', () => { + const legacyProvider = networkInfoHttpProvider({ baseUrl: 'http://localhost:4000/', logger }); + const provider = networkInfoHttpProvider({ baseUrl: 'http://localhost:4001/', logger }); + + const methods: (keyof NetworkInfoProvider)[] = [ + 'eraSummaries', + 'genesisParameters', + 'lovelaceSupply', + 'protocolParameters', + 'stake' + ]; + + test.each(methods)('compare %s', async (method) => { + const [legacyResponse, response] = await Promise.all([legacyProvider[method](), provider[method]()]); + + expect(toSerializableObject(response)).toEqual(toSerializableObject(legacyResponse)); + }); +}); diff --git a/packages/e2e/test/blockfrost/queryTransactions.test.ts b/packages/e2e/test/blockfrost/queryTransactions.test.ts index 2997d78ebef..b16f8e57017 100644 --- a/packages/e2e/test/blockfrost/queryTransactions.test.ts +++ b/packages/e2e/test/blockfrost/queryTransactions.test.ts @@ -1,4 +1,4 @@ -import { BlockfrostChainHistoryProvider, util } from '@cardano-sdk/cardano-services'; +import { BlockfrostChainHistoryProvider, BlockfrostNetworkInfoProvider, util } from '@cardano-sdk/cardano-services'; import { Cardano, ChainHistoryProvider } from '@cardano-sdk/core'; import { logger } from '@cardano-sdk/util-dev'; @@ -7,9 +7,11 @@ describe.only('BlockfrostChainHistoryProvider', () => { let blockfrost; beforeAll(async () => { blockfrost = util.getBlockfrostApi(); + const networkInfoProvider = new BlockfrostNetworkInfoProvider({ blockfrost, logger }); chainHistoryProvider = new BlockfrostChainHistoryProvider({ blockfrost, - logger + logger, + networkInfoProvider }); }); diff --git a/packages/ogmios/test/CardanoNode/__snapshots__/ObservableOgmiosCardanoNode.test.ts.snap b/packages/ogmios/test/CardanoNode/__snapshots__/ObservableOgmiosCardanoNode.test.ts.snap index 27aee5644cf..218b6dbc46f 100644 --- a/packages/ogmios/test/CardanoNode/__snapshots__/ObservableOgmiosCardanoNode.test.ts.snap +++ b/packages/ogmios/test/CardanoNode/__snapshots__/ObservableOgmiosCardanoNode.test.ts.snap @@ -34,7 +34,6 @@ Array [ "block": Object { "body": Array [ Object { - "auxiliaryData": undefined, "body": Object { "auxiliaryDataHash": undefined, "certificates": Array [ @@ -54,7 +53,6 @@ Array [ "denominator": 1, "numerator": 1, }, - "metadataJson": undefined, "owners": Array [ "stake_test1uztg6yppa0t30rslkrneva5c9qju40rhndjnuy356kxw83s6n95nu", ], @@ -94,7 +92,6 @@ Array [ "denominator": 1, "numerator": 1, }, - "metadataJson": undefined, "owners": Array [ "stake_test1urcnqgzt2x8hpsvej4zfudehahknm8lux894pmqwg5qshgcrn346q", ], @@ -134,7 +131,6 @@ Array [ "denominator": 1, "numerator": 1, }, - "metadataJson": undefined, "owners": Array [ "stake_test1uquj460qdrj4az6uy7kvtzct4w8226xq4t30dlzfhc360tgegny4m", ], @@ -174,41 +170,25 @@ Array [ "outputs": Array [ Object { "address": "addr_test1vz09v9yfxguvlp0zsnrpa3tdtm7el8xufp3m5lsm7qxzclgmzkket", - "datum": undefined, - "datumHash": undefined, - "scriptReference": undefined, "value": Object { - "assets": undefined, "coins": 29699998493561943n, }, }, Object { "address": "addr_test1qz09v9yfxguvlp0zsnrpa3tdtm7el8xufp3m5lsm7qxzclvk35gzr67hz78plv88jemfs2p9e2780xm98cfrf4vvu0rq83pdz2", - "datum": undefined, - "datumHash": undefined, - "scriptReference": undefined, "value": Object { - "assets": undefined, "coins": 100000000000000n, }, }, Object { "address": "addr_test1qz09v9yfxguvlp0zsnrpa3tdtm7el8xufp3m5lsm7qxzcl03xqsyk5v0wrqen92yncmn0m0d8k0lcvwt2rkqu3gppw3sdexkvh", - "datum": undefined, - "datumHash": undefined, - "scriptReference": undefined, "value": Object { - "assets": undefined, "coins": 100000000000000n, }, }, Object { "address": "addr_test1qz09v9yfxguvlp0zsnrpa3tdtm7el8xufp3m5lsm7qxzclfe9t57q689t694cfavck9sh2uw545vp2hz7m7yn03r57kslz5rjf", - "datum": undefined, - "datumHash": undefined, - "scriptReference": undefined, "value": Object { - "assets": undefined, "coins": 100000000000000n, }, }, @@ -269,4 +249,4 @@ Array [ }, }, ] -`; \ No newline at end of file +`; diff --git a/packages/ogmios/test/ogmiosToCore/__snapshots__/block.test.ts.snap b/packages/ogmios/test/ogmiosToCore/__snapshots__/block.test.ts.snap index ceb03db80fa..26acd1e8c25 100644 --- a/packages/ogmios/test/ogmiosToCore/__snapshots__/block.test.ts.snap +++ b/packages/ogmios/test/ogmiosToCore/__snapshots__/block.test.ts.snap @@ -4,7 +4,6 @@ exports[`ogmiosToCore block babbage can translate from babbage block 1`] = ` Object { "body": Array [ Object { - "auxiliaryData": undefined, "body": Object { "auxiliaryDataHash": undefined, "certificates": Array [ @@ -31,11 +30,7 @@ Object { "outputs": Array [ Object { "address": "addr_test1qq2u3e0afx3dqk95hfptv7wm8rl0fht2ecehxzy2yr3g5z79dq6pms68sakpc70q0h37wcn92c9u5jaeu6he7dhypy3sxd05k6", - "datum": undefined, - "datumHash": undefined, - "scriptReference": undefined, "value": Object { - "assets": undefined, "coins": 9999134743n, }, }, @@ -73,7 +68,6 @@ Object { "blob": Map { 58n => "", }, - "scripts": undefined, }, "body": Object { "auxiliaryDataHash": "3f5d7adf353de22cf1fc514aa911901c5fbd1b5ee3ed57950a6dc5a0fe1dbb20", @@ -99,9 +93,6 @@ Object { "outputs": Array [ Object { "address": "addr_test1qq9tud0fzpj6pdeqeegrma25ngfrre9ruwwcuj2m6dnsreu2qtzuksrwqtv4stsk3hq2uscsh90rf5rdgg38k5t0f0nsp30w94", - "datum": undefined, - "datumHash": undefined, - "scriptReference": undefined, "value": Object { "assets": Map { "eb48b495393986032e8cef0b0a9b2ce64b3881e8a29347e169c7122c4654" => 70n, @@ -111,11 +102,7 @@ Object { }, Object { "address": "addr_test1qq9tud0fzpj6pdeqeegrma25ngfrre9ruwwcuj2m6dnsreu2qtzuksrwqtv4stsk3hq2uscsh90rf5rdgg38k5t0f0nsp30w94", - "datum": undefined, - "datumHash": undefined, - "scriptReference": undefined, "value": Object { - "assets": undefined, "coins": 9692318369n, }, }, @@ -159,7 +146,6 @@ Object { "blob": Map { 0n => Array [], }, - "scripts": undefined, }, "body": Object { "auxiliaryDataHash": "b93d8e428871a0b0e12c5c554653f515f50fe48dc5b55af7e3667e7d9661ea2c", @@ -183,21 +169,13 @@ Object { "outputs": Array [ Object { "address": "addr_test1qry0mqhwlf84py9nhz5qukujgjjx26cwg3zqvm2aqeuqqneyz5g72vhh8ws2fcjudfjesyl8fy4qss4rdx9v34e2pxps5s28ar", - "datum": undefined, - "datumHash": undefined, - "scriptReference": undefined, "value": Object { - "assets": undefined, "coins": 1000000n, }, }, Object { "address": "addr_test1qra40qtvuef6dr4ckvzgme7sg5thtdjhjrc268uvn22reqeyz5g72vhh8ws2fcjudfjesyl8fy4qss4rdx9v34e2pxpsglu3mg", - "datum": undefined, - "datumHash": undefined, - "scriptReference": undefined, "value": Object { - "assets": undefined, "coins": 1639738n, }, }, @@ -231,7 +209,6 @@ Object { }, }, Object { - "auxiliaryData": undefined, "body": Object { "auxiliaryDataHash": undefined, "certificates": undefined, @@ -257,19 +234,13 @@ Object { "outputs": Array [ Object { "address": "addr_test1qrc7mfr62uj0zg07ylfq37ukce6ahxtumdl0d3ar5aw35x758sc7z8763mfpes0j7wqrkkrdgna98c0rtlps585ypdnst69syh", - "datum": undefined, - "datumHash": undefined, - "scriptReference": undefined, "value": Object { - "assets": undefined, "coins": 2371174n, }, }, Object { "address": "addr_test1wrn96kc0xx774qnyzccygr0qdxs4escmy8ptfu38p3hfehsu5427k", - "datum": undefined, "datumHash": "118cd93427a8e035d484e55ddd4c70200ac5a4d8e63d9d1b89e8a8d49d229415", - "scriptReference": undefined, "value": Object { "assets": Map { "4d2047d7af3d8de799de5daa6e1b0ebfc039f4274084d5bb9ba3975d42594e45544c6963656e7365" => 1n, @@ -279,9 +250,7 @@ Object { }, Object { "address": "addr_test1wrn96kc0xx774qnyzccygr0qdxs4escmy8ptfu38p3hfehsu5427k", - "datum": undefined, "datumHash": "118cd93427a8e035d484e55ddd4c70200ac5a4d8e63d9d1b89e8a8d49d229415", - "scriptReference": undefined, "value": Object { "assets": Map { "4d2047d7af3d8de799de5daa6e1b0ebfc039f4274084d5bb9ba3975d42594e45544c6963656e7365" => 1n, @@ -380,7 +349,6 @@ Object { }, }, Object { - "auxiliaryData": undefined, "body": Object { "auxiliaryDataHash": undefined, "certificates": undefined, @@ -406,19 +374,13 @@ Object { "outputs": Array [ Object { "address": "addr_test1qrc7mfr62uj0zg07ylfq37ukce6ahxtumdl0d3ar5aw35x758sc7z8763mfpes0j7wqrkkrdgna98c0rtlps585ypdnst69syh", - "datum": undefined, - "datumHash": undefined, - "scriptReference": undefined, "value": Object { - "assets": undefined, "coins": 4444865n, }, }, Object { "address": "addr_test1wrn96kc0xx774qnyzccygr0qdxs4escmy8ptfu38p3hfehsu5427k", - "datum": undefined, "datumHash": "118cd93427a8e035d484e55ddd4c70200ac5a4d8e63d9d1b89e8a8d49d229415", - "scriptReference": undefined, "value": Object { "assets": Map { "4d2047d7af3d8de799de5daa6e1b0ebfc039f4274084d5bb9ba3975d42594e45544c6963656e7365" => 1n, @@ -428,9 +390,7 @@ Object { }, Object { "address": "addr_test1wrn96kc0xx774qnyzccygr0qdxs4escmy8ptfu38p3hfehsu5427k", - "datum": undefined, "datumHash": "118cd93427a8e035d484e55ddd4c70200ac5a4d8e63d9d1b89e8a8d49d229415", - "scriptReference": undefined, "value": Object { "assets": Map { "4d2047d7af3d8de799de5daa6e1b0ebfc039f4274084d5bb9ba3975d42594e45544c6963656e7365" => 1n, @@ -440,9 +400,7 @@ Object { }, Object { "address": "addr_test1wrn96kc0xx774qnyzccygr0qdxs4escmy8ptfu38p3hfehsu5427k", - "datum": undefined, "datumHash": "118cd93427a8e035d484e55ddd4c70200ac5a4d8e63d9d1b89e8a8d49d229415", - "scriptReference": undefined, "value": Object { "assets": Map { "4d2047d7af3d8de799de5daa6e1b0ebfc039f4274084d5bb9ba3975d42594e45544c6963656e7365" => 1n, @@ -545,7 +503,6 @@ Object { "blob": Map { 0n => Array [], }, - "scripts": undefined, }, "body": Object { "auxiliaryDataHash": "b93d8e428871a0b0e12c5c554653f515f50fe48dc5b55af7e3667e7d9661ea2c", @@ -573,21 +530,13 @@ Object { "outputs": Array [ Object { "address": "addr_test1qry0mqhwlf84py9nhz5qukujgjjx26cwg3zqvm2aqeuqqneyz5g72vhh8ws2fcjudfjesyl8fy4qss4rdx9v34e2pxps5s28ar", - "datum": undefined, - "datumHash": undefined, - "scriptReference": undefined, "value": Object { - "assets": undefined, "coins": 1000000n, }, }, Object { "address": "addr_test1qpavsa3m303vjnpsa4cpjzsgeqmkaa7wya3v94s9pq4wtrpyz5g72vhh8ws2fcjudfjesyl8fy4qss4rdx9v34e2pxpsnutk58", - "datum": undefined, - "datumHash": undefined, - "scriptReference": undefined, "value": Object { - "assets": undefined, "coins": 9986971687n, }, }, @@ -640,7 +589,6 @@ exports[`ogmiosToCore block can translate from allegra block 1`] = ` Object { "body": Array [ Object { - "auxiliaryData": undefined, "body": Object { "auxiliaryDataHash": undefined, "certificates": undefined, @@ -659,11 +607,7 @@ Object { "outputs": Array [ Object { "address": "addr_test1vz09v9yfxguvlp0zsnrpa3tdtm7el8xufp3m5lsm7qxzclgmzkket", - "datum": undefined, - "datumHash": undefined, - "scriptReference": undefined, "value": Object { - "assets": undefined, "coins": 29699998493147869n, }, }, @@ -719,7 +663,6 @@ exports[`ogmiosToCore block can translate from alonzo block 1`] = ` Object { "body": Array [ Object { - "auxiliaryData": undefined, "body": Object { "auxiliaryDataHash": undefined, "certificates": undefined, @@ -738,11 +681,8 @@ Object { "outputs": Array [ Object { "address": "addr_test1vz09v9yfxguvlp0zsnrpa3tdtm7el8xufp3m5lsm7qxzclgmzkket", - "datum": undefined, "datumHash": "c5dfa8c3cbd5a959829618a7b46e163078cb3f1b39f152514d0c3686d553529a", - "scriptReference": undefined, "value": Object { - "assets": undefined, "coins": 29699998492735907n, }, }, @@ -854,7 +794,6 @@ exports[`ogmiosToCore block can translate from mary block 1`] = ` Object { "body": Array [ Object { - "auxiliaryData": undefined, "body": Object { "auxiliaryDataHash": undefined, "certificates": undefined, @@ -873,11 +812,7 @@ Object { "outputs": Array [ Object { "address": "addr_test1vz09v9yfxguvlp0zsnrpa3tdtm7el8xufp3m5lsm7qxzclgmzkket", - "datum": undefined, - "datumHash": undefined, - "scriptReference": undefined, "value": Object { - "assets": undefined, "coins": 29699998492941888n, }, }, @@ -933,7 +868,6 @@ exports[`ogmiosToCore block can translate from shelley block 1`] = ` Object { "body": Array [ Object { - "auxiliaryData": undefined, "body": Object { "auxiliaryDataHash": undefined, "certificates": Array [ @@ -953,7 +887,6 @@ Object { "denominator": 1, "numerator": 1, }, - "metadataJson": undefined, "owners": Array [ "stake_test1uztg6yppa0t30rslkrneva5c9qju40rhndjnuy356kxw83s6n95nu", ], @@ -993,7 +926,6 @@ Object { "denominator": 1, "numerator": 1, }, - "metadataJson": undefined, "owners": Array [ "stake_test1urcnqgzt2x8hpsvej4zfudehahknm8lux894pmqwg5qshgcrn346q", ], @@ -1033,7 +965,6 @@ Object { "denominator": 1, "numerator": 1, }, - "metadataJson": undefined, "owners": Array [ "stake_test1uquj460qdrj4az6uy7kvtzct4w8226xq4t30dlzfhc360tgegny4m", ], @@ -1073,41 +1004,25 @@ Object { "outputs": Array [ Object { "address": "addr_test1vz09v9yfxguvlp0zsnrpa3tdtm7el8xufp3m5lsm7qxzclgmzkket", - "datum": undefined, - "datumHash": undefined, - "scriptReference": undefined, "value": Object { - "assets": undefined, "coins": 29699998493561943n, }, }, Object { "address": "addr_test1qz09v9yfxguvlp0zsnrpa3tdtm7el8xufp3m5lsm7qxzclvk35gzr67hz78plv88jemfs2p9e2780xm98cfrf4vvu0rq83pdz2", - "datum": undefined, - "datumHash": undefined, - "scriptReference": undefined, "value": Object { - "assets": undefined, "coins": 100000000000000n, }, }, Object { "address": "addr_test1qz09v9yfxguvlp0zsnrpa3tdtm7el8xufp3m5lsm7qxzcl03xqsyk5v0wrqen92yncmn0m0d8k0lcvwt2rkqu3gppw3sdexkvh", - "datum": undefined, - "datumHash": undefined, - "scriptReference": undefined, "value": Object { - "assets": undefined, "coins": 100000000000000n, }, }, Object { "address": "addr_test1qz09v9yfxguvlp0zsnrpa3tdtm7el8xufp3m5lsm7qxzclfe9t57q689t694cfavck9sh2uw545vp2hz7m7yn03r57kslz5rjf", - "datum": undefined, - "datumHash": undefined, - "scriptReference": undefined, "value": Object { - "assets": undefined, "coins": 100000000000000n, }, }, @@ -1159,4 +1074,4 @@ Object { "txCount": 1, "vrf": "vrf_vk1ny8dyz3pa9u7v7h8l5evsmxxjq07dk6j5uda60lxe3zsya5ea20s2c6jph", } -`; \ No newline at end of file +`;