Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions .github/workflows/continuous-integration-blockfrost-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,29 @@ env:
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_ASSET_PROVIDER: 'blockfrost'
TEST_CLIENT_ASSET_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:3015"}'
TEST_CLIENT_CHAIN_HISTORY_PROVIDER: 'ws'
TEST_CLIENT_CHAIN_HISTORY_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4000/"}'
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_REWARDS_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4000/"}'
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_UTXO_PROVIDER: 'ws'
TEST_CLIENT_UTXO_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4000/"}'
TEST_CLIENT_STAKE_POOL_PROVIDER: 'http'
TEST_CLIENT_STAKE_POOL_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4000/"}'
WS_PROVIDER_URL: 'http://localhost:4100/ws'

on:
workflow_dispatch:
pull_request:
push:
branches: ['master']
tags: ['*.*.*']

jobs:
build_and_test:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class BlockfrostAssetProvider extends BlockfrostProvider implements Asset
return {
mediaType: Asset.MediaType(mediaType),
name: fileName,
otherProperties: this.mapNftMetadataOtherProperties(file),
otherProperties: this.mapFileMetadataOtherProperties(file),
src: Asset.Uri(src)
};
} catch (error) {
Expand Down Expand Up @@ -88,15 +88,26 @@ export class BlockfrostAssetProvider extends BlockfrostProvider implements Asset
return typeof metadata?.version === 'string' ? metadata.version : '1.0';
}

private mapFileMetadataOtherProperties(
metadata: Responses['asset']['onchain_metadata']
): Map<string, Cardano.Metadatum> | undefined {
return this.mapOtherProperties(metadata, ['name', 'mediaType', 'src']);
}

private mapNftMetadataOtherProperties(
metadata: Responses['asset']['onchain_metadata']
): Map<string, Cardano.Metadatum> | undefined {
return this.mapOtherProperties(metadata, ['name', 'image', 'description', 'mediaType', 'files', 'version']);
}

private mapOtherProperties(
metadata: Responses['asset']['onchain_metadata'],
mainProperties: string[]
): Map<string, Cardano.Metadatum> | undefined {
if (!metadata) {
return;
}
const otherProperties = Object.entries(
omit(metadata, ['name', 'image', 'description', 'mediaType', 'files', 'version'])
);
const otherProperties = Object.entries(omit(metadata, mainProperties));
if (otherProperties.length === 0) return;
// eslint-disable-next-line consistent-return
return new Map(otherProperties.map(([key, value]) => [key, this.objToMetadatum(value)]));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ describe('BlockfrostAssetProvider', () => {
...mockedAssetResponse,
onchain_metadata: {
...mockedAssetResponse.onchain_metadata,
files: [{ mediaType: 'image/png', src: ['http://', 'some.png'] }]
files: [{ image: 'should be in other properties', mediaType: 'image/png', src: ['http://', 'some.png'] }]
}
}
]
Expand All @@ -190,6 +190,7 @@ describe('BlockfrostAssetProvider', () => {
});

expect(response.nftMetadata!.files![0].src).toBe('http://some.png');
expect(response.nftMetadata!.files![0].otherProperties?.get('image')).toBe('should be in other properties');
});

test('version', async () => {
Expand Down
20 changes: 12 additions & 8 deletions packages/e2e/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,15 @@ STAKE_POOL_PROJECTOR_URL='http://localhost:4002/'
NETWORK_SPEED=fast

# to run tests against local blockfrost
# Blockfrost secrets
#BLOCKFROST_CUSTOM_BACKEND_URL='http://blockfrost-ryo:3000'
#ASSET_PROVIDER: 'blockfrost'
#UTXO_PROVIDER: 'blockfrost'
#CHAIN_HISTORY_PROVIDER: 'blockfrost'
#REWARDS_PROVIDER: 'blockfrost'
#NETWORK_INFO_PROVIDER: 'blockfrost'
#TX_SUBMIT_PROVIDER: 'blockfrost'
TEST_CLIENT_ASSET_PROVIDER='blockfrost'
TEST_CLIENT_ASSET_PROVIDER_PARAMS='{"baseUrl":"http://localhost:3015"}'
#TEST_CLIENT_UTXO_PROVIDER='blockfrost'
#TEST_UTXO_PROVIDER_PARAMS='{"baseUrl":"http://localhost:3015"}'
#TEST_CLIENT_CHAIN_HISTORY_PROVIDER='blockfrost'
#TEST_CLIENT_CHAIN_HISTORY_PROVIDER_PARAMS='{"baseUrl":"http://localhost:3015"}'
#TEST_CLIENT_REWARDS_PROVIDER='blockfrost'
#TEST_REWARDS_PROVIDER_PARAMS='{"baseUrl":"http://localhost:3015"}'
#TEST_CLIENT_NETWORK_INFO_PROVIDER='blockfrost'
#TEST_NETWORK_INFO_PROVIDER_PARAMS='{"baseUrl":"http://localhost:3015"}'
#TEST_CLIENT_TX_SUBMIT_PROVIDER='blockfrost'
#TEST_TX_SUBMIT_PROVIDER_PARAMS='{"baseUrl":"http://localhost:3015"}'
16 changes: 16 additions & 0 deletions packages/e2e/src/factories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import {
util
} from '@cardano-sdk/key-management';
import {
BlockfrostAssetProvider,
BlockfrostClient,
CardanoWsClient,
assetInfoHttpProvider,
chainHistoryHttpProvider,
Expand Down Expand Up @@ -66,6 +68,7 @@ const HTTP_PROVIDER = 'http';
const OGMIOS_PROVIDER = 'ogmios';
const STUB_PROVIDER = 'stub';
const WS_PROVIDER = 'ws';
const BLOCKFROST_PROVIDER = 'blockfrost';

const MISSING_URL_PARAM = 'Missing URL';

Expand Down Expand Up @@ -130,6 +133,19 @@ assetProviderFactory.register(HTTP_PROVIDER, async (params: any, logger: Logger)
});
});

assetProviderFactory.register(BLOCKFROST_PROVIDER, async (params: any, logger): Promise<AssetProvider> => {
if (params.baseUrl === undefined) throw new Error(`${BlockfrostAssetProvider.name}: ${MISSING_URL_PARAM}`);

return new Promise<AssetProvider>(async (resolve) => {
resolve(
new BlockfrostAssetProvider(
new BlockfrostClient({ baseUrl: params.baseUrl }, { rateLimiter: { schedule: (task) => task() } }),
logger
)
);
});
});

chainHistoryProviderFactory.register(
HTTP_PROVIDER,
async (params: any, logger: Logger): Promise<ChainHistoryProvider> => {
Expand Down
2 changes: 1 addition & 1 deletion packages/e2e/test/blockfrost-providers/networkInfo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ 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
// LW-11858 to enable this
describe.skip('Web Socket', () => {
const legacyProvider = networkInfoHttpProvider({ baseUrl: 'http://localhost:4000/', logger });
const provider = networkInfoHttpProvider({ baseUrl: 'http://localhost:4001/', logger });
Expand Down
16 changes: 13 additions & 3 deletions packages/e2e/test/blockfrost/getAsset.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { BlockfrostAssetProvider } from '@cardano-sdk/cardano-services-client';
import { Cardano } from '@cardano-sdk/core';
import { assetProviderFactory, getEnv, walletVariables } from '../../src';
import { logger } from '@cardano-sdk/util-dev';
import { util } from '@cardano-sdk/cardano-services';

const env = getEnv(walletVariables);

describe('BlockfrostAssetProvider', () => {
beforeAll(() => {
if (env.TEST_CLIENT_ASSET_PROVIDER !== 'blockfrost')
throw new Error('TEST_CLIENT_ASSET_PROVIDER must be "blockfrost" to run these tests');
});

test('getAsset', async () => {
const assetProvider = new BlockfrostAssetProvider(util.getBlockfrostClient(), logger);
const assetProvider = await assetProviderFactory.create(
'blockfrost',
env.TEST_CLIENT_ASSET_PROVIDER_PARAMS,
logger
);
const asset = await assetProvider.getAsset({
assetId: Cardano.AssetId(
'b27160f0c50a9cf168bf945dcbfcabbfbee5c7a801e7b467093b41534d6574616c4d6f6e7374657230303036'
Expand Down
101 changes: 29 additions & 72 deletions packages/e2e/test/wallet_epoch_0/PersonalWallet/nft.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ describe('PersonalWallet.assets/nft', () => {
let policyId: Cardano.PolicyId;
let policyScript: Cardano.NativeScript;
let assetIds: Cardano.AssetId[];
let fingerprints: Cardano.AssetFingerprint[];
const assetNames = ['4e46542d66696c6573', '4e46542d303031', '4e46542d303032'];
let walletAddress: Cardano.PaymentAddress;
const coins = 10_000_000n; // number of coins to use in each transaction
Expand Down Expand Up @@ -101,12 +100,6 @@ describe('PersonalWallet.assets/nft', () => {
[assetIds[TOKEN_BURN_INDEX], 1n]
]);

fingerprints = [
Cardano.AssetFingerprint.fromParts(policyId, Cardano.AssetName(assetNames[TOKEN_METADATA_1_INDEX])),
Cardano.AssetFingerprint.fromParts(policyId, Cardano.AssetName(assetNames[TOKEN_METADATA_2_INDEX])),
Cardano.AssetFingerprint.fromParts(policyId, Cardano.AssetName(assetNames[TOKEN_BURN_INDEX]))
];

walletAddress = (await firstValueFrom(wallet.addresses$))[0].address;

const txMetadatum = metadatum.jsonToMetadatum({
Expand Down Expand Up @@ -199,23 +192,13 @@ describe('PersonalWallet.assets/nft', () => {
// Check balance here because asset info will not be re-fetched when balance changes due to minting and burning
expect(walletAssetBalance?.get(assetIds[TOKEN_METADATA_2_INDEX])).toBe(1n);

expect(nfts.find((nft) => nft.assetId === assetIds[TOKEN_METADATA_2_INDEX])).toMatchObject({
assetId: assetIds[TOKEN_METADATA_2_INDEX],
fingerprint: fingerprints[TOKEN_METADATA_2_INDEX],
name: assetNames[TOKEN_METADATA_2_INDEX],
nftMetadata: {
image: 'ipfs://some_hash1',
name: 'One',
otherProperties: new Map([['version', '1.0']]),
version: '1.0'
},
policyId,
// in case of repeated tests on the same network, total asset supply is not updated due to
// the limitation that asset info is not refreshed on wallet balance changes
quantity: expect.anything(),
supply: expect.anything(),
tokenMetadata: null
const secondTokenMetadata = nfts.find((nft) => nft.assetId === assetIds[TOKEN_METADATA_2_INDEX])?.nftMetadata;
expect(secondTokenMetadata).toMatchObject({
image: 'ipfs://some_hash1',
name: 'One',
version: '1.0'
});

expect(nfts.find((nft) => nft.assetId === assetIds[TOKEN_METADATA_1_INDEX])).toBeDefined();
});

Expand All @@ -225,37 +208,20 @@ describe('PersonalWallet.assets/nft', () => {
// Check balance here because asset info will not be re-fetched when balance changes due to minting and burning
expect(walletAssetBalance?.get(assetIds[TOKEN_METADATA_1_INDEX])).toBe(1n);

expect(nfts.find((nft) => nft.assetId === assetIds[TOKEN_METADATA_1_INDEX])).toMatchObject({
assetId: assetIds[TOKEN_METADATA_1_INDEX],
fingerprint: fingerprints[TOKEN_METADATA_1_INDEX],
name: assetNames[TOKEN_METADATA_1_INDEX],
nftMetadata: {
description: 'NFT with different types of files',
files: [
{
mediaType: 'video/mp4',
name: 'some name',
src: 'ipfs://Qmb78QQ4RXxKQrteRn4X3WaMXXfmi2BU2dLjfWxuJoF2N5'
},
{
mediaType: 'audio/mpeg',
name: 'some name',
src: 'ipfs://Qmb78QQ4RXxKQrteRn4X3WaMXXfmi2BU2dLjfWxuJoF2Ny'
}
],
image: 'ipfs://somehash',
mediaType: 'image/png',
name: 'NFT with files',
otherProperties: new Map([
['id', '1'],
['version', '1.0']
]),
version: '1.0'
} as Asset.NftMetadata,
policyId,
supply: expect.anything(),
tokenMetadata: null
});
const nftMetadata = nfts.find((nft) => nft.assetId === assetIds[TOKEN_METADATA_1_INDEX])?.nftMetadata;
expect(nftMetadata?.otherProperties?.get('id')).toBe('1');
expect(nftMetadata?.files).toEqual([
expect.objectContaining({
mediaType: 'video/mp4',
name: 'some name',
src: 'ipfs://Qmb78QQ4RXxKQrteRn4X3WaMXXfmi2BU2dLjfWxuJoF2N5'
}),
expect.objectContaining({
mediaType: 'audio/mpeg',
name: 'some name',
src: 'ipfs://Qmb78QQ4RXxKQrteRn4X3WaMXXfmi2BU2dLjfWxuJoF2Ny'
})
]);
});

it('supports burning tokens', async () => {
Expand Down Expand Up @@ -313,7 +279,6 @@ describe('PersonalWallet.assets/nft', () => {

const assetNameHex = Buffer.from(assetName).toString('hex');
const assetId = Cardano.AssetId(`${policyId}${assetNameHex}`);
const fingerprint = Cardano.AssetFingerprint.fromParts(policyId, Cardano.AssetName(assetNameHex));
const tokens = new Map([[assetId, 1n]]);

const txDataMetadatum = new Map([
Expand All @@ -325,7 +290,7 @@ describe('PersonalWallet.assets/nft', () => {
metadatum.jsonToMetadatum({
image: ['ipfs://some_hash1'],
name: assetName,
version: '1.0'
version
})
]
])
Expand Down Expand Up @@ -369,26 +334,18 @@ describe('PersonalWallet.assets/nft', () => {

// try remove the asset.nftMetadata filter
const [, nfts] = await firstValueFromTimed(walletBalanceAssetsAndNfts(wallet));
const nftMetadata = nfts.find((nft) => nft.assetId === assetId)?.nftMetadata;

expect(nfts.find((nft) => nft.assetId === assetId)).toMatchObject({
assetId,
fingerprint,
name: assetNameHex,
nftMetadata: {
image: 'ipfs://some_hash1',
name: assetName,
otherProperties: new Map([['version', '1.0']]),
version: '1.0'
},
policyId,
quantity: expect.anything(),
supply: expect.anything(),
tokenMetadata: null
});
expect(nftMetadata?.image).toBe('ipfs://some_hash1');
expect(nftMetadata?.name).toBe(assetName);
});

CIP0025Test('supports CIP-25 v1, assetName hex encoded', 'CIP-0025-v1-hex', 1, 'hex');
CIP0025Test('supports CIP-25 v1, assetName utf8 encoded', 'CIP-0025-v1-utf8', 1, 'utf8');
CIP0025Test('supports CIP-25 v2', 'CIP-0025-v2', 2);

// https://input-output-rnd.slack.com/archives/C06J663L2A2/p1731505470694659
env.TEST_CLIENT_ASSET_PROVIDER !== 'blockfrost'
? CIP0025Test('supports CIP-25 v2', 'CIP-0025-v2', 2)
: test.todo('"supports CIP-25 v2" test is disabled when running with Blockfrost asset provider');
});
});
Loading