Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: nft metadata service #769

Merged
merged 71 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
cf9b173
fix(cardano-services-client): use service version instead of root ver…
mkazlauskas Sep 14, 2023
c31603e
test(cardano-services): refactor HttpServer tests to work with non-ma…
mkazlauskas Sep 14, 2023
d8cc93b
feat!: update core types with deserialized PlutusData
mkazlauskas Jun 14, 2023
8928869
fix(cardano-services): do not return datum hash for TxOut with inline…
mkazlauskas Jun 15, 2023
ff0b923
feat(util-dev): add with-inline-datum.json chain sync data
mkazlauskas Jun 15, 2023
c36d7ef
refactor!: hoist metadatumToCip25 to NftMetadata.fromMetadatum
mkazlauskas Jul 3, 2023
91e1e7a
refactor(core): loosen arg type of isPlutusData utils to accept 'unkn…
mkazlauskas Jul 4, 2023
d3da1a6
feat(core): add hexToBytes, utf8ToBytes, utf8ToHex utils
mkazlauskas Jul 5, 2023
64b263b
feat(core): add NftMetadata.fromPlutusData mapping from cip68 datum
mkazlauskas Jul 5, 2023
45cfecb
ci: enable unit and e2e tests for non-master targeting PRs
mkazlauskas Jul 10, 2023
eb0ddc0
fix: correct ogmiosToCore auxiliaryData mapping
mkazlauskas Jul 18, 2023
8b3296e
refactor!: renamed field handle to handleResolutions
iadmytro Jul 14, 2023
d2610d7
refactor(core): convert AssetNameLabelNum into object
greatertomi Jul 25, 2023
91fe7df
feat: add NFT metadata projection
greatertomi Jul 25, 2023
e654196
refactor: update nullable TypeORM entity field types to include '| null'
mkazlauskas Jul 25, 2023
bf6c9e2
test(core): add a failing test for PlutusData
mkazlauskas Jul 26, 2023
b79419b
fix(core): do not log a warning when nft metadata files are missing
mkazlauskas Jul 26, 2023
72e600c
feat(core): added custom PlutusData serialization classes
AngelCastilloB Jul 27, 2023
ab8bb29
feat(projection): ignore burn transactions when projecting cip25 meta…
mkazlauskas Aug 7, 2023
31b0f0a
fix!: convert tokens.quantity column to numeric
mkazlauskas Aug 3, 2023
5e77b5d
test(projection-typeorm): hoist createRollForwardEventBasedOn util fr…
mkazlauskas Aug 4, 2023
0b236c9
fix(util-dev): add missing 'inputSource' prop to some chainSync datasets
mkazlauskas Aug 4, 2023
416e5f5
feat: add address projection
mkazlauskas Aug 4, 2023
0b6aa71
test(cardano-services): re-generated fixtures
iccicci Aug 9, 2023
72f6c1b
feat(cardano-services): create typeorm nft-metadata service
greatertomi Aug 7, 2023
6016a65
refactor(cardano-services): extend typeormProvider with typeormService
greatertomi Aug 10, 2023
4c0a7ee
refactor!: remove AssetInfo.history and AssetInfo.mintOrBurnCount
greatertomi Aug 22, 2023
aaf133b
feat(cardano-services): create TypeormAssetProvider
greatertomi Aug 18, 2023
15a6ba6
fix(core): bytes field on core plutus script type now contains the co…
AngelCastilloB Sep 3, 2023
2e9c439
feat(core): added update field to the transaction body core type
AngelCastilloB Sep 1, 2023
9451a05
feat(core): added transaction body serialization classes
AngelCastilloB Aug 22, 2023
0dfaeb7
feat(core): replaced CML TransactionBody serialization class with out…
AngelCastilloB Sep 1, 2023
f9a1a58
test(wallet): fix transaction body initialization
AngelCastilloB Sep 1, 2023
22318dd
docs(core): tagged all inline CDDL documentation on serialization cla…
AngelCastilloB Sep 2, 2023
132599d
feat: added witness set serialization classes
AngelCastilloB Sep 2, 2023
4b49e57
feat(core): added serialization classes for tx auxiliary data
AngelCastilloB Sep 5, 2023
c34076c
feat(core): plutus data map now uses deep equality when being indexed…
AngelCastilloB Sep 5, 2023
67f6892
fix(core): fix circular dependency on cip67 module
AngelCastilloB Sep 5, 2023
b0ba261
feat(core): added native functions to convert between json and metadatum
AngelCastilloB Sep 5, 2023
76bed53
feat(key-management)!: remove deprecated trezor hardware wallet imple…
AngelCastilloB Sep 5, 2023
62f4252
feat!: remove the CML serialization code from core package
AngelCastilloB Sep 5, 2023
060ac99
feat(projection): add cip67.byAssetId
mkazlauskas Aug 8, 2023
4cb2f55
refactor!: rename Mappers.Handle type to HandleOwnership
mkazlauskas Aug 17, 2023
1711969
fix!: correct cip68 handle name (without label)
mkazlauskas Aug 25, 2023
2267689
refactor!: make withHandles 'logger' argument required
mkazlauskas Aug 30, 2023
892c2eb
feat(projection): map 'extra' data with Mappers.withNftMetadata
mkazlauskas Aug 29, 2023
3f8242a
fix(projection): keep latest nft metadata
mkazlauskas Aug 31, 2023
645db52
feat(core): export tryConvertPlutusMapToUtf8Record from Cardano.util
mkazlauskas Aug 30, 2023
9fc4722
feat(projection): add withHandleMetadata mapper
mkazlauskas Aug 31, 2023
5f13b4f
feat(core): add AssetName.toUTF8 util
mkazlauskas Aug 31, 2023
5b4704b
refactor(projection): dedupe withHandles and withHandleMetadata imple…
mkazlauskas Aug 31, 2023
cadbedb
test(projection): hoist common handle utils to shared module
mkazlauskas Aug 31, 2023
5aa9ebe
refactor(projection-typeorm): rm redundant try-catch from storeHandles
mkazlauskas Aug 31, 2023
8520e4e
feat(projection-typeorm): hydrate event with stored utxo row IDs
mkazlauskas Aug 31, 2023
bca30bf
feat(projection-typeorm): add HandleMetadataEntity and storeHandleMet…
mkazlauskas Aug 31, 2023
716470a
test(projection-typeorm): split storeHandles.test.ts into 2 files
mkazlauskas Sep 1, 2023
53cd873
fix(projection): infer incoming event type for withNftMetadata
mkazlauskas Sep 1, 2023
481996f
feat(projection-typeorm): add defaultForStakeCredential and defaultFo…
mkazlauskas Sep 1, 2023
233ed70
feat(cardano-services): add HandleMetadata to handle projection
mkazlauskas Sep 1, 2023
903faf2
feat(cardano-services): update handle projector to project 'default' …
mkazlauskas Sep 4, 2023
6674b77
fix(cardano-services): return 0 for stake pool status
mkazlauskas Sep 5, 2023
3b61c93
fix(wallet): do not track reference NFTs as handles
mkazlauskas Sep 5, 2023
e49f3fd
feat(e2e): hoist handle minting utils to src
mkazlauskas Sep 6, 2023
808dfde
test(cardano-services): re-generate test DBs
mkazlauskas Sep 5, 2023
877279f
refactor(core)!: rename HandleResolution default props to credential
mkazlauskas Sep 5, 2023
361824b
refactor(projection-typeorm): change NftMetadataEntity.image type to …
mkazlauskas Sep 5, 2023
5ac43d1
refactor(projection-typeorm): add NftMetadataEntity.userTokenAssetId …
mkazlauskas Sep 5, 2023
9c446ac
feat(cardano-services): resolve default handle, image, pfp and bg
mkazlauskas Sep 5, 2023
dd1cc86
chore(wallet): set logger verbosity of duplicate utxo message to 'debug'
mkazlauskas Sep 11, 2023
9331d01
fix(core): return consistent bytes type in Serialization
mkazlauskas Sep 12, 2023
78460d3
fix(util): deserialize bytes as Uint8Array instead of Buffer
mkazlauskas Sep 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 0 additions & 1 deletion .github/workflows/continuous-integration-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ env:

on:
pull_request:
branches: ['master']
push:
branches: ['master']
tags: ['*.*.*']
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/continuous-integration-unit-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ env:

on:
pull_request:
branches: ['master']
push:
branches: ['master']
tags: ['*.*.*']
Expand Down
3 changes: 1 addition & 2 deletions packages/cardano-services-client/src/HttpProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import { HttpProviderConfigPaths, Provider, ProviderError, ProviderFailure } from '@cardano-sdk/core';
import { Logger } from 'ts-log';
import { fromSerializableObject, toSerializableObject } from '@cardano-sdk/util';
import { apiVersion as staticApiVersion } from './version';
import axios, { AxiosAdapter, AxiosRequestConfig, AxiosResponseTransformer } from 'axios';
import packageJson from '../package.json';

Expand Down Expand Up @@ -104,7 +103,7 @@ export const createHttpProvider = <T extends Provider>({
const req: AxiosRequestConfig = {
...axiosOptions,
adapter,
baseURL: `${baseUrl.replace(/\/$/, '')}/v${staticApiVersion.root}/${serviceSlug}`,
baseURL: `${baseUrl.replace(/\/$/, '')}/v${apiVersion}/${serviceSlug}`,
data: { ...args[0] },
headers: {
...axiosOptions?.headers,
Expand Down
6 changes: 3 additions & 3 deletions packages/cardano-services-client/src/version.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// auto-generated using ../scripts/createVersionSource.js
export const apiVersion = {
assetInfo: '1.0.0',
chainHistory: '1.0.0',
chainHistory: '2.0.0',
handle: '1.0.0',
networkInfo: '1.0.0',
rewards: '1.0.0',
root: '1.0.0',
stakePool: '1.0.0',
txSubmit: '1.0.0',
utxo: '1.0.0'
txSubmit: '2.0.0',
utxo: '2.0.0'
};
8 changes: 4 additions & 4 deletions packages/cardano-services-client/test/HttpProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import path from 'path';

const packageJson = require(path.join(__dirname, '..', 'package.json'));

type ComplexArg2 = { map: Map<string, Buffer> };
type ComplexResponse = Map<bigint, Buffer>[];
type ComplexArg2 = { map: Map<string, Uint8Array> };
type ComplexResponse = Map<bigint, Uint8Array>[];
interface TestProvider extends Provider {
noArgsEmptyReturn(): Promise<void>;
complexArgsAndReturn({ arg1, arg2 }: { arg1: bigint; arg2: ComplexArg2 }): Promise<ComplexResponse>;
Expand Down Expand Up @@ -95,8 +95,8 @@ describe('createHttpProvider', () => {
describe('method with complex args and return', () => {
it('serializes args and deserializes response using core serializableObject', async () => {
const arg1 = 123n;
const arg2: ComplexArg2 = { map: new Map([['key', Buffer.from('abc')]]) };
const expectedResponse: ComplexResponse = [new Map([[1234n, Buffer.from('response data')]])];
const arg2: ComplexArg2 = { map: new Map([['key', new Uint8Array(Buffer.from('abc'))]]) };
const expectedResponse: ComplexResponse = [new Map([[1234n, new Uint8Array(Buffer.from('response data'))]])];
const provider = createTxSubmitProviderClient();
closeServer = await createStubHttpProviderServer(port, stubProviderPaths.complexArgsAndReturn, (req, res) => {
expect(fromSerializableObject(req.body)).toEqual({ arg1, arg2 });
Expand Down
6 changes: 3 additions & 3 deletions packages/cardano-services-client/version.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"assetInfo": "1.0.0",
"chainHistory": "1.0.0",
"chainHistory": "2.0.0",
"handle": "1.0.0",
"networkInfo": "1.0.0",
"rewards": "1.0.0",
"root": "1.0.0",
"stakePool": "1.0.0",
"txSubmit": "1.0.0",
"utxo": "1.0.0"
"txSubmit": "2.0.0",
"utxo": "2.0.0"
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Cardano } from '@cardano-sdk/core';
import { LastMintTxModel, MultiAssetHistoryModel, MultiAssetModel } from './types';
import { LastMintTxModel, MultiAssetModel } from './types';
import { Logger } from 'ts-log';
import { Pool } from 'pg';
import Queries from './queries';
Expand Down Expand Up @@ -32,14 +32,4 @@ export class AssetBuilder {
});
return result.rows[0];
}

public async queryMultiAssetHistory(policyId: Cardano.PolicyId, name: Cardano.AssetName) {
this.#logger.debug('About to query multi asset history', { name, policyId });
const result = await this.#db.query<MultiAssetHistoryModel>({
name: 'find_multi_asset_history',
text: Queries.findMultiAssetHistory,
values: [Buffer.from(policyId, 'hex'), Buffer.from(name, 'hex')]
});
return result.rows;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { AssetBuilder } from './AssetBuilder';
import { AssetPolicyIdAndName, NftMetadataService, TokenMetadataService } from '../types';
import { DB_CACHE_TTL_DEFAULT, InMemoryCache, NoCache } from '../../InMemoryCache';
import { DbSyncProvider, DbSyncProviderDependencies } from '../../util/DbSyncProvider';
import { DbSyncProvider, DbSyncProviderDependencies } from '../../util';

/**
* Properties that are need to create DbSyncAssetProvider
Expand Down Expand Up @@ -74,7 +74,6 @@ export class DbSyncAssetProvider extends DbSyncProvider() implements AssetProvid
async getAsset({ assetId, extraData }: GetAssetArgs) {
const assetInfo = await this.#getAssetInfo(assetId);

if (extraData?.history) await this.loadHistory(assetInfo);
if (extraData?.nftMetadata) assetInfo.nftMetadata = await this.#getNftMetadata(assetInfo);
if (extraData?.tokenMetadata) {
try {
Expand Down Expand Up @@ -133,15 +132,6 @@ export class DbSyncAssetProvider extends DbSyncProvider() implements AssetProvid
return Promise.all(assetIds.map((_) => getAssetInfo(_)));
}

private async loadHistory(assetInfo: Asset.AssetInfo) {
assetInfo.history = (
await this.#builder.queryMultiAssetHistory(assetInfo.policyId, assetInfo.name)
).map<Asset.AssetMintOrBurn>(({ hash, quantity }) => ({
quantity: BigInt(quantity),
transactionId: hash.toString('hex') as unknown as Cardano.TransactionId
}));
}

async #getNftMetadata(asset: AssetPolicyIdAndName): Promise<Asset.NftMetadata | null> {
return this.#cache.get(
nftMetadataCacheKey(Cardano.AssetId.fromParts(asset.policyId, asset.name)),
Expand All @@ -166,9 +156,8 @@ export class DbSyncAssetProvider extends DbSyncProvider() implements AssetProvid
const supply = BigInt(multiAsset.sum);
// Backwards compatibility
const quantity = supply;
const mintOrBurnCount = Number(multiAsset.count);

return { assetId, fingerprint, mintOrBurnCount, name, policyId, quantity, supply };
return { assetId, fingerprint, name, policyId, quantity, supply };
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@ export class DbSyncNftMetadataService implements NftMetadataService {
this.#logger.debug('Querying tx metadata', lastMintedTxId);
const metadatas = await this.#metadataService.queryTxMetadataByHashes([lastMintedTxId]);
const metadata = metadatas.get(lastMintedTxId);
return Asset.util.metadatumToCip25(assetInfo, metadata, this.#logger);
return Asset.NftMetadata.fromMetadatum(assetInfo, metadata, this.#logger);
}
}
37 changes: 37 additions & 0 deletions packages/cardano-services/src/Asset/TypeOrmNftMetadataService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Asset, Cardano } from '@cardano-sdk/core';
import { AssetPolicyIdAndName, NftMetadataService } from './types';
import { NftMetadataEntity } from '@cardano-sdk/projection-typeorm';
import { TypeormProviderDependencies, TypeormService } from '../util';

export class TypeOrmNftMetadataService extends TypeormService implements NftMetadataService {
constructor({ connectionConfig$, logger, entities }: TypeormProviderDependencies) {
super('TypeOrmNftMetadataService', { connectionConfig$, entities, logger });
}

async getNftMetadata(assetInfo: AssetPolicyIdAndName): Promise<Asset.NftMetadata | null> {
const assetId = Cardano.AssetId.fromParts(assetInfo.policyId, assetInfo.name);
const stringAssetName = Buffer.from(assetInfo.name, 'hex').toString('utf8');
return this.withDataSource(async (dataSource) => {
const queryRunner = dataSource.createQueryRunner();
const nftMetadataRepository = queryRunner.manager.getRepository(NftMetadataEntity);

const asset = await nftMetadataRepository.findOneBy({
name: stringAssetName,
userTokenAsset: { id: assetId }
});

if (!asset) {
return null;
}

return {
image: asset.image,
name: asset.name,
...(asset.description && { description: asset.description }),
...(asset.files && { files: asset.files }),
...(asset.mediaType && { mediaType: asset.mediaType }),
...(asset.otherProperties && { otherProperties: asset.otherProperties })
} as unknown as Asset.NftMetadata;
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import {
Asset,
AssetProvider,
Cardano,
GetAssetArgs,
GetAssetsArgs,
ProviderError,
ProviderFailure
} from '@cardano-sdk/core';
import { AssetEntity } from '@cardano-sdk/projection-typeorm';
import { TokenMetadataService } from '../types';
import { TypeOrmNftMetadataService } from '../TypeOrmNftMetadataService';
import { TypeormProvider, TypeormProviderDependencies } from '../../util';

interface TypeormAssetProviderProps {
paginationPageSizeLimit: number;
}

interface TypeormAssetProviderDependencies extends TypeormProviderDependencies {
tokenMetadataService: TokenMetadataService;
}

export class TypeormAssetProvider extends TypeormProvider implements AssetProvider {
#dependencies: TypeormAssetProviderDependencies;
#nftMetadataService: TypeOrmNftMetadataService;
#paginationPageSizeLimit: number;

constructor({ paginationPageSizeLimit }: TypeormAssetProviderProps, dependencies: TypeormAssetProviderDependencies) {
const { connectionConfig$, entities, logger } = dependencies;
super('TypeormAssetProvider', { connectionConfig$, entities, logger });

this.#dependencies = dependencies;
this.#paginationPageSizeLimit = paginationPageSizeLimit;
this.#nftMetadataService = new TypeOrmNftMetadataService({ connectionConfig$, entities, logger });
}

async getAsset({ assetId, extraData }: GetAssetArgs): Promise<Asset.AssetInfo> {
const assetInfo = await this.#getAssetInfo(assetId);

if (extraData?.nftMetadata) assetInfo.nftMetadata = await this.#getNftMetadata(assetInfo);
if (extraData?.tokenMetadata) {
assetInfo.tokenMetadata = (await this.#fetchTokenMetadataList([assetId]))[0];
}

return assetInfo;
}

async getAssets({ assetIds, extraData }: GetAssetsArgs): Promise<Asset.AssetInfo[]> {
if (assetIds.length > this.#paginationPageSizeLimit) {
throw new ProviderError(
ProviderFailure.BadRequest,
undefined,
`AssetIds count of ${assetIds.length} can not be greater than ${this.#paginationPageSizeLimit}`
);
}

const assetInfoList = await Promise.all(assetIds.map((assetId) => this.#getAssetInfo(assetId)));

if (extraData?.nftMetadata) {
await Promise.all(
assetInfoList.map(async (assetInfo) => {
assetInfo.nftMetadata = await this.#getNftMetadata(assetInfo);
})
);
}

if (extraData?.tokenMetadata) {
const tokenMetadataList = await this.#fetchTokenMetadataList(assetIds);

for (const [index, assetInfo] of assetInfoList.entries()) {
assetInfo.tokenMetadata = tokenMetadataList[index];
}
}

return assetInfoList;
}

async #fetchTokenMetadataList(assetIds: Cardano.AssetId[]) {
let tokenMetadataList: (Asset.TokenMetadata | null | undefined)[] = [];

try {
tokenMetadataList = await this.#dependencies.tokenMetadataService.getTokenMetadata(assetIds);
} catch (error) {
if (error instanceof ProviderError && error.reason === ProviderFailure.Unhealthy) {
this.logger.error(`Failed to fetch token metadata for assets ${assetIds} due to: ${error.message}`);
tokenMetadataList = Array.from({ length: assetIds.length });
} else {
throw error;
}
}

return tokenMetadataList;
}

async #getNftMetadata(asset: Asset.AssetInfo): Promise<Asset.NftMetadata | null | undefined> {
try {
return this.#nftMetadataService.getNftMetadata({
name: asset.name,
policyId: asset.policyId
});
} catch (error) {
this.logger.error('Failed to get nft metadata', asset.assetId, error);
}
}

async #getAssetInfo(assetId: Cardano.AssetId): Promise<Asset.AssetInfo> {
const assetName = Cardano.AssetId.getAssetName(assetId);
const policyId = Cardano.AssetId.getPolicyId(assetId);
const fingerprint = Cardano.AssetFingerprint.fromParts(policyId, Cardano.AssetName(assetName));

return this.withDataSource(async (dataSource) => {
const queryRunner = dataSource.createQueryRunner();
const assetRepository = queryRunner.manager.getRepository(AssetEntity);

const asset = await assetRepository.findOneBy({ id: assetId });

if (!asset) throw new ProviderError(ProviderFailure.NotFound, undefined, `Asset not found '${assetId}'`);
const supply = asset.supply!;

return {
assetId,
fingerprint,
name: assetName,
policyId,
quantity: supply,
supply
};
});
}

async initializeImpl() {
await super.initializeImpl();
await this.#nftMetadataService.initialize();
}

async startImpl() {
await super.startImpl();
await this.#nftMetadataService.start();
}

async shutdownImpl() {
await super.shutdownImpl();
await this.#nftMetadataService.shutdown();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './TypeormAssetProvider';
2 changes: 2 additions & 0 deletions packages/cardano-services/src/Asset/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ export * from './CardanoTokenRegistry';
export * from './StubTokenMetadataService';
export * from './DbSyncNftMetadataService';
export * from './DbSyncAssetProvider';
export * from './TypeOrmNftMetadataService';
export * from './TypeormAssetProvider';
export * from './types';
6 changes: 0 additions & 6 deletions packages/cardano-services/src/Asset/openApi.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,6 @@
"fingerprint": {
"type": "string"
},
"mintOrBurnCount": {
"type": "number"
},
"name": {
"type": "string"
},
Expand Down Expand Up @@ -155,9 +152,6 @@
"ExtraData": {
"type": "object",
"properties": {
"history": {
"type": "boolean"
},
"nftMetadata": {
"type": "boolean"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export class DbSyncChainHistoryProvider extends DbSyncProvider() implements Chai
this.#builder.queryTxMintByIds(ids),
this.#builder.queryWithdrawalsByTxIds(ids),
this.#builder.queryRedeemersByIds(ids),
// Missing witness datums
this.#metadataService.queryTxMetadataByRecordIds(ids),
this.#builder.queryTransactionInputsByIds(ids, true),
this.#builder.queryCertificatesByIds(ids)
Expand Down
Loading
Loading