Skip to content

Commit 9dd2f36

Browse files
committed
feat: browser compatible BlockfrostNetworkInfoProvider
1 parent f2db32d commit 9dd2f36

File tree

8 files changed

+201
-249
lines changed

8 files changed

+201
-249
lines changed
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import { BlockfrostClient } from '../blockfrost/BlockfrostClient';
2+
import { BlockfrostProvider } from '../blockfrost/BlockfrostProvider';
3+
import {
4+
Cardano,
5+
EraSummary,
6+
Milliseconds,
7+
NetworkInfoProvider,
8+
Seconds,
9+
StakeSummary,
10+
SupplySummary
11+
} from '@cardano-sdk/core';
12+
import { Logger } from 'ts-log';
13+
import { Responses } from '@blockfrost/blockfrost-js';
14+
import { Schemas } from '@blockfrost/blockfrost-js/lib/types/open-api';
15+
16+
export class BlockfrostNetworkInfoProvider extends BlockfrostProvider implements NetworkInfoProvider {
17+
constructor(client: BlockfrostClient, logger: Logger) {
18+
super(client, logger);
19+
}
20+
21+
public async stake(): Promise<StakeSummary> {
22+
try {
23+
const { stake } = await this.request<Responses['network']>('network');
24+
return {
25+
active: BigInt(stake.active),
26+
live: BigInt(stake.live)
27+
};
28+
} catch (error) {
29+
throw this.toProviderError(error);
30+
}
31+
}
32+
33+
public async lovelaceSupply(): Promise<SupplySummary> {
34+
try {
35+
const { supply } = await this.request<Responses['network']>('network');
36+
return {
37+
circulating: BigInt(supply.circulating),
38+
total: BigInt(supply.total)
39+
};
40+
} catch (error) {
41+
throw this.toProviderError(error);
42+
}
43+
}
44+
45+
public async ledgerTip(): Promise<Cardano.Tip> {
46+
try {
47+
const block = await this.request<Responses['block_content']>('blocks/latest');
48+
return {
49+
blockNo: Cardano.BlockNo(block.height!),
50+
hash: Cardano.BlockId(block.hash),
51+
slot: Cardano.Slot(block.slot!)
52+
};
53+
} catch (error) {
54+
throw this.toProviderError(error);
55+
}
56+
}
57+
58+
public async protocolParameters(): Promise<Cardano.ProtocolParameters> {
59+
try {
60+
const response = await this.request<Responses['epoch_param_content']>('epochs/latest/parameters');
61+
return {
62+
coinsPerUtxoByte: Number(response.coins_per_utxo_word),
63+
collateralPercentage: response.collateral_percent!,
64+
committeeTermLimit: Cardano.EpochNo(0),
65+
costModels: new Map<Cardano.PlutusLanguageVersion, Cardano.CostModel>([
66+
[
67+
Cardano.PlutusLanguageVersion.V1,
68+
Object.values(response.cost_models!.PlutusV1 as { [key: string]: number })
69+
],
70+
[Cardano.PlutusLanguageVersion.V2, Object.values(response.cost_models!.PlutusV2 as { [key: string]: number })]
71+
]),
72+
dRepDeposit: 0,
73+
dRepInactivityPeriod: Cardano.EpochNo(0),
74+
dRepVotingThresholds: null as unknown as Cardano.DelegateRepresentativeThresholds,
75+
desiredNumberOfPools: response.n_opt,
76+
governanceActionDeposit: 0,
77+
governanceActionValidityPeriod: Cardano.EpochNo(0),
78+
maxBlockBodySize: response.max_block_size,
79+
maxBlockHeaderSize: response.max_block_header_size,
80+
maxCollateralInputs: Number(response.max_collateral_inputs),
81+
maxExecutionUnitsPerBlock: {
82+
memory: Number.parseInt(response.max_block_ex_mem!),
83+
steps: Number.parseInt(response.max_block_ex_steps!)
84+
},
85+
maxExecutionUnitsPerTransaction: {
86+
memory: Number.parseInt(response.max_tx_ex_mem!),
87+
steps: Number.parseInt(response.max_tx_ex_steps!)
88+
},
89+
maxTxSize: Number(response.max_tx_size),
90+
maxValueSize: Number(response.max_val_size),
91+
minCommitteeSize: 0,
92+
minFeeCoefficient: response.min_fee_a,
93+
minFeeConstant: response.min_fee_b,
94+
minPoolCost: Number(response.min_pool_cost),
95+
monetaryExpansion: response.rho.toString(),
96+
poolDeposit: Number(response.pool_deposit),
97+
poolInfluence: response.a0.toString(),
98+
poolRetirementEpochBound: response.e_max,
99+
poolVotingThresholds: null as unknown as Cardano.PoolVotingThresholds,
100+
prices: {
101+
memory: response.price_mem!,
102+
steps: response.price_step!
103+
},
104+
protocolVersion: { major: response.protocol_major_ver, minor: response.protocol_minor_ver },
105+
stakeKeyDeposit: Number(response.key_deposit),
106+
treasuryExpansion: response.tau.toString()
107+
};
108+
} catch (error) {
109+
throw this.toProviderError(error);
110+
}
111+
}
112+
113+
public async genesisParameters(): Promise<Cardano.CompactGenesis> {
114+
return this.request<Responses['genesis_content']>('genesis')
115+
.then((response) => ({
116+
activeSlotsCoefficient: response.active_slots_coefficient,
117+
epochLength: response.epoch_length,
118+
maxKesEvolutions: response.max_kes_evolutions,
119+
maxLovelaceSupply: BigInt(response.max_lovelace_supply),
120+
networkId:
121+
response.network_magic === Cardano.NetworkMagics.Mainnet
122+
? Cardano.NetworkId.Mainnet
123+
: Cardano.NetworkId.Testnet,
124+
networkMagic: response.network_magic,
125+
securityParameter: response.security_param,
126+
slotLength: Seconds(response.slot_length),
127+
slotsPerKesPeriod: response.slots_per_kes_period,
128+
systemStart: new Date(response.system_start * 1000),
129+
updateQuorum: response.update_quorum
130+
}))
131+
.catch((error) => {
132+
throw this.toProviderError(error);
133+
});
134+
}
135+
136+
protected async fetchEraSummaries(): Promise<Schemas['network-eras']> {
137+
try {
138+
return await this.request<Responses['network-eras']>('network/eras');
139+
} catch (error) {
140+
throw this.toProviderError(error);
141+
}
142+
}
143+
144+
protected async parseEraSummaries(summaries: Schemas['network-eras'], systemStart: Date): Promise<EraSummary[]> {
145+
try {
146+
return summaries.map((r) => ({
147+
parameters: {
148+
epochLength: r.parameters.epoch_length,
149+
slotLength: Milliseconds(r.parameters.slot_length * 1000)
150+
},
151+
start: {
152+
slot: r.start.slot,
153+
time: new Date(systemStart.getTime() + r.start.time * 1000)
154+
}
155+
}));
156+
} catch (error) {
157+
throw this.toProviderError(error);
158+
}
159+
}
160+
161+
public async eraSummaries(): Promise<EraSummary[]> {
162+
try {
163+
const { systemStart } = await this.genesisParameters();
164+
const summaries = await this.fetchEraSummaries();
165+
return this.parseEraSummaries(summaries, systemStart);
166+
} catch (error) {
167+
throw this.toProviderError(error);
168+
}
169+
}
170+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
export * from './BlockfrostNetworkInfoProvider';
12
export * from './networkInfoHttpProvider';
Lines changed: 19 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
/* eslint-disable @typescript-eslint/no-explicit-any */
2-
/* eslint-disable max-len */
3-
import { BlockFrostAPI, Responses } from '@blockfrost/blockfrost-js';
4-
import { BlockfrostNetworkInfoProvider } from '../../../src';
1+
import { BlockfrostClient, BlockfrostNetworkInfoProvider } from '../../src';
52
import { Cardano, EraSummary, Milliseconds, StakeSummary, SupplySummary } from '@cardano-sdk/core';
3+
import { Responses } from '@blockfrost/blockfrost-js';
64
import { logger } from '@cardano-sdk/util-dev';
5+
import { mockResponses } from '../AssetInfoProvider/util';
76

87
jest.mock('@blockfrost/blockfrost-js');
98

@@ -38,74 +37,36 @@ const mockedNetworkResponse = {
3837
}
3938
} as Responses['network'];
4039

41-
const mockedError = {
42-
error: 'Forbidden',
43-
message: 'Invalid project token.',
44-
status_code: 403,
45-
url: 'test'
46-
};
40+
describe('BlockfrostNetworkInfoProvider', () => {
41+
let request: jest.Mock;
42+
let provider: BlockfrostNetworkInfoProvider;
4743

48-
const mockedErrorMethod = jest.fn().mockRejectedValue(mockedError);
49-
// const mockedProviderError = new ProviderError(ProviderFailure.Unknown, {}, 'testing');
50-
const apiKey = 'someapikey';
51-
const apiUrl = 'http://testnet.endpoint';
52-
53-
describe('blockfrostNetworkInfoProvider', () => {
5444
beforeEach(async () => {
55-
mockedErrorMethod.mockClear();
45+
request = jest.fn();
46+
const client = { request } as unknown as BlockfrostClient;
47+
provider = new BlockfrostNetworkInfoProvider(client, logger);
5648
});
57-
test('stake', async () => {
58-
BlockFrostAPI.prototype.network = jest.fn().mockResolvedValue(mockedNetworkResponse);
59-
BlockFrostAPI.prototype.apiUrl = apiUrl;
6049

61-
const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
62-
const client = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
63-
const response = await client.stake();
50+
test('stake', async () => {
51+
mockResponses(request, [['network', mockedNetworkResponse]]);
52+
const response = await provider.stake();
6453

6554
expect(response).toMatchObject<StakeSummary>({
6655
active: 1_060_378_314_781_343n,
6756
live: 15_001_884_895_856_815n
6857
});
6958
});
7059

71-
test('stake throws', async () => {
72-
BlockFrostAPI.prototype.network = mockedErrorMethod;
73-
74-
BlockFrostAPI.prototype.apiUrl = apiUrl;
75-
76-
const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
77-
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
78-
79-
await expect(() => provider.stake()).rejects.toThrow();
80-
expect(mockedErrorMethod).toBeCalledTimes(1);
81-
});
82-
8360
test('lovelaceSupply', async () => {
84-
BlockFrostAPI.prototype.network = jest.fn().mockResolvedValue(mockedNetworkResponse);
85-
BlockFrostAPI.prototype.apiUrl = apiUrl;
86-
87-
const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
88-
const client = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
89-
const response = await client.lovelaceSupply();
61+
mockResponses(request, [['network', mockedNetworkResponse]]);
62+
const response = await provider.lovelaceSupply();
9063

9164
expect(response).toMatchObject<SupplySummary>({
9265
circulating: 42_064_399_450_423_723n,
9366
total: 40_267_211_394_073_980n
9467
});
9568
});
9669

97-
test('lovelace throws', async () => {
98-
BlockFrostAPI.prototype.network = mockedErrorMethod;
99-
100-
BlockFrostAPI.prototype.apiUrl = apiUrl;
101-
102-
const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
103-
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
104-
105-
await expect(() => provider.lovelaceSupply()).rejects.toThrow();
106-
expect(mockedErrorMethod).toBeCalledTimes(1);
107-
});
108-
10970
test('eraSummaries', async () => {
11071
const genesis = {
11172
activeSlotsCoefficient: 0.05,
@@ -286,30 +247,13 @@ describe('blockfrostNetworkInfoProvider', () => {
286247
}
287248
];
288249

289-
const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
290-
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
291-
250+
mockResponses(request, [['network/eras', blockfrostResponseBody]]);
251+
// mockResponses(request, [['genesis', genesis]])
292252
provider.genesisParameters = jest.fn().mockResolvedValue(genesis);
293-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
294-
// @ts-ignore
295-
provider.fetchEraSummaries = jest.fn().mockResolvedValue(blockfrostResponseBody);
296-
297253
const response = await provider.eraSummaries();
298-
299254
expect(response).toMatchObject<EraSummary[]>(expected);
300255
});
301256

302-
test('eraSummaries throws', async () => {
303-
BlockFrostAPI.prototype.apiUrl = apiUrl;
304-
305-
const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
306-
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
307-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
308-
// @ts-ignore
309-
provider.fetchEraSummaries = mockedErrorMethod;
310-
await expect(() => provider.eraSummaries()).rejects.toThrow();
311-
});
312-
313257
test('genesisParameters', async () => {
314258
const mockedResponse = {
315259
active_slots_coefficient: 0.05,
@@ -323,10 +267,7 @@ describe('blockfrostNetworkInfoProvider', () => {
323267
system_start: 1_506_203_091,
324268
update_quorum: 5
325269
};
326-
BlockFrostAPI.prototype.genesis = jest.fn().mockResolvedValue(mockedResponse);
327-
328-
const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
329-
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
270+
mockResponses(request, [['genesis', mockedResponse]]);
330271
const response = await provider.genesisParameters();
331272

332273
expect(response).toMatchObject({
@@ -343,18 +284,6 @@ describe('blockfrostNetworkInfoProvider', () => {
343284
} as Cardano.CompactGenesis);
344285
});
345286

346-
test('genesisParameters throws', async () => {
347-
BlockFrostAPI.prototype.genesis = mockedErrorMethod;
348-
349-
BlockFrostAPI.prototype.apiUrl = apiUrl;
350-
351-
const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
352-
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
353-
354-
await expect(() => provider.genesisParameters()).rejects.toThrow();
355-
expect(mockedErrorMethod).toBeCalledTimes(1);
356-
});
357-
358287
test('protocolParameters', async () => {
359288
const mockedResponse = {
360289
a0: 0.3,
@@ -383,12 +312,7 @@ describe('blockfrostNetworkInfoProvider', () => {
383312
rho: 0.003,
384313
tau: 0.2
385314
};
386-
BlockFrostAPI.prototype.epochsLatestParameters = jest.fn().mockResolvedValue(mockedResponse) as any;
387-
388-
const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
389-
BlockFrostAPI.prototype.apiUrl = apiUrl;
390-
391-
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
315+
mockResponses(request, [['epochs/latest/parameters', mockedResponse]]);
392316
const response = await provider.protocolParameters();
393317

394318
expect(response).toMatchObject({
@@ -405,23 +329,8 @@ describe('blockfrostNetworkInfoProvider', () => {
405329
});
406330
});
407331

408-
test('protocolParameters throws', async () => {
409-
BlockFrostAPI.prototype.epochsLatestParameters = mockedErrorMethod;
410-
411-
BlockFrostAPI.prototype.apiUrl = apiUrl;
412-
413-
const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
414-
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
415-
416-
await expect(() => provider.protocolParameters()).rejects.toThrow();
417-
expect(mockedErrorMethod).toBeCalledTimes(1);
418-
});
419-
420332
test('ledgerTip', async () => {
421-
BlockFrostAPI.prototype.blocksLatest = jest.fn().mockResolvedValue(blockResponse);
422-
423-
const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
424-
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
333+
mockResponses(request, [['blocks/latest', blockResponse]]);
425334
const response = await provider.ledgerTip();
426335

427336
expect(response).toMatchObject({
@@ -430,16 +339,4 @@ describe('blockfrostNetworkInfoProvider', () => {
430339
slot: 37_767_194
431340
});
432341
});
433-
434-
test('ledgerTip throws', async () => {
435-
BlockFrostAPI.prototype.blocksLatest = mockedErrorMethod;
436-
437-
BlockFrostAPI.prototype.apiUrl = apiUrl;
438-
439-
const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
440-
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
441-
442-
await expect(() => provider.ledgerTip()).rejects.toThrow();
443-
expect(mockedErrorMethod).toBeCalledTimes(1);
444-
});
445342
});

0 commit comments

Comments
 (0)