Skip to content

Commit e2159e1

Browse files
committed
feat!: partial BaseWallet tx history
BaseWallet will load only last n transactions on initial load BREAKING CHANGE: remove BaseWallet stake pool and drep provider dependency - add RewardAccountInfoProvider as a new BaseWallet dependency
1 parent f5bee9f commit e2159e1

File tree

56 files changed

+970
-2482
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+970
-2482
lines changed

.github/workflows/continuous-integration-blockfrost-e2e.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ env:
2626
TEST_CLIENT_TX_SUBMIT_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4000/"}'
2727
TEST_CLIENT_UTXO_PROVIDER: 'blockfrost'
2828
TEST_CLIENT_UTXO_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:3015"}'
29+
TEST_CLIENT_REWARD_ACCOUNT_INFO_PROVIDER: 'blockfrost'
30+
TEST_CLIENT_REWARD_ACCOUNT_INFO_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:3015"}'
2931
TEST_CLIENT_STAKE_POOL_PROVIDER: 'http'
3032
TEST_CLIENT_STAKE_POOL_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4000/"}'
3133
WS_PROVIDER_URL: 'http://localhost:4100/ws'

.github/workflows/continuous-integration-e2e.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ env:
2828
TEST_CLIENT_UTXO_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4000/"}'
2929
TEST_CLIENT_STAKE_POOL_PROVIDER: 'http'
3030
TEST_CLIENT_STAKE_POOL_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:4000/"}'
31+
TEST_CLIENT_REWARD_ACCOUNT_INFO_PROVIDER: 'blockfrost'
32+
TEST_CLIENT_REWARD_ACCOUNT_INFO_PROVIDER_PARAMS: '{"baseUrl":"http://localhost:3015"}'
3133
WS_PROVIDER_URL: 'http://localhost:4100/ws'
3234

3335
on:
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { Cardano, DRepProvider, RewardAccountInfoProvider, StakePoolProvider } from '@cardano-sdk/core';
2+
3+
import { BlockfrostClient, BlockfrostProvider, fetchSequentially, isBlockfrostNotFoundError } from '../blockfrost';
4+
import { Logger } from 'ts-log';
5+
import { isNotNil } from '@cardano-sdk/util';
6+
import uniq from 'lodash/uniq.js';
7+
import type { Responses } from '@blockfrost/blockfrost-js';
8+
9+
export type BlockfrostRewardAccountInfoProviderDependencies = {
10+
client: BlockfrostClient;
11+
logger: Logger;
12+
stakePoolProvider: StakePoolProvider;
13+
dRepProvider: DRepProvider;
14+
};
15+
16+
export class BlockfrostRewardAccountInfoProvider extends BlockfrostProvider implements RewardAccountInfoProvider {
17+
#dRepProvider: DRepProvider;
18+
#stakePoolProvider: StakePoolProvider;
19+
20+
constructor({ client, logger, stakePoolProvider, dRepProvider }: BlockfrostRewardAccountInfoProviderDependencies) {
21+
super(client, logger);
22+
this.#dRepProvider = dRepProvider;
23+
this.#stakePoolProvider = stakePoolProvider;
24+
}
25+
26+
async rewardAccountInfo(address: Cardano.RewardAccount): Promise<Cardano.RewardAccountInfo> {
27+
const [account, [lastRegistrationActivity]] = await Promise.all([
28+
await this.request<Responses['account_content']>(`accounts/${address}`).catch(
29+
(error): Responses['account_content'] => {
30+
if (isBlockfrostNotFoundError(error)) {
31+
return {
32+
active: false,
33+
active_epoch: null,
34+
controlled_amount: '0',
35+
drep_id: null,
36+
pool_id: null,
37+
reserves_sum: '0',
38+
rewards_sum: '0',
39+
stake_address: address,
40+
treasury_sum: '0',
41+
withdrawable_amount: '0',
42+
withdrawals_sum: '0'
43+
};
44+
}
45+
throw error;
46+
}
47+
),
48+
this.request<Responses['account_registration_content']>(
49+
`accounts/${address}/registrations?order=desc&count=1`
50+
).catch((error: unknown) => {
51+
if (isBlockfrostNotFoundError(error)) {
52+
return [];
53+
}
54+
throw error;
55+
})
56+
]);
57+
58+
// This provider maps 'Unregistering' into 'Unregistered',
59+
// because it is expensive to discriminate between those statuses (would have to fetch tx, tip, era summaries)
60+
// There is currently no logic within cardano-js-sdk that would work differently because of that.
61+
const credentialStatus = account.active
62+
? Cardano.StakeCredentialStatus.Registered
63+
: lastRegistrationActivity?.action === 'registered'
64+
? Cardano.StakeCredentialStatus.Registering
65+
: Cardano.StakeCredentialStatus.Unregistered;
66+
const rewardBalance = BigInt(account.withdrawable_amount || '0');
67+
68+
const [delegatee, dRepDelegatee, deposit] = await Promise.all([
69+
this.#getDelegatee(address),
70+
this.#getDrepDelegatee(account),
71+
// This provider currently does not find other deposits (pool/drep/govaction)
72+
await this.#getKeyDeposit(lastRegistrationActivity)
73+
]);
74+
75+
return {
76+
address,
77+
credentialStatus,
78+
dRepDelegatee,
79+
delegatee,
80+
deposit,
81+
rewardBalance
82+
};
83+
}
84+
85+
async delegationPortfolio(rewardAccounts: Cardano.RewardAccount[]): Promise<Cardano.Cip17DelegationPortfolio | null> {
86+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
87+
rewardAccounts;
88+
// TODO;
89+
return null;
90+
}
91+
92+
async #getDrepDelegatee(account: Responses['account_content']): Promise<Cardano.DRepDelegatee | undefined> {
93+
if (!account.drep_id) return;
94+
const dRepInfo = await this.#dRepProvider.getDRepInfo({ id: Cardano.DRepID(account.drep_id) });
95+
return {
96+
// TODO: always abstain or always no confidence
97+
delegateRepresentative: dRepInfo
98+
};
99+
}
100+
101+
async #getKeyDeposit(lastRegistrationActivity: Responses['account_registration_content'][0] | undefined) {
102+
if (!lastRegistrationActivity || lastRegistrationActivity.action === 'deregistered') {
103+
return 0n;
104+
}
105+
const tx = await this.request<Responses['tx_content']>(`txs/${lastRegistrationActivity.tx_hash}`);
106+
const block = await this.request<Responses['block_content']>(`blocks/${tx.block}`);
107+
const epochParameters = await this.request<Responses['epoch_param_content']>(`epochs/${block.epoch}/parameters`);
108+
return BigInt(epochParameters.key_deposit);
109+
}
110+
111+
async #getDelegatee(address: Cardano.RewardAccount): Promise<Cardano.Delegatee | undefined> {
112+
const latestEpoch = await this.request<Responses['epoch_content']>('epochs/latest');
113+
const delegationHistory = await fetchSequentially<Responses['account_delegation_content'][0]>({
114+
haveEnoughItems: (items) => items[items.length - 1]?.active_epoch <= latestEpoch.epoch,
115+
paginationOptions: { order: 'desc' },
116+
request: (paginationQueryString) => this.request(`accounts/${address}/delegations?${paginationQueryString}`)
117+
});
118+
119+
const poolIds = [
120+
delegationHistory.find(({ active_epoch }) => active_epoch <= latestEpoch.epoch)?.pool_id,
121+
delegationHistory.find(({ active_epoch }) => active_epoch <= latestEpoch.epoch + 1)?.pool_id,
122+
delegationHistory.find(({ active_epoch }) => active_epoch <= latestEpoch.epoch + 2)?.pool_id
123+
] as Array<Cardano.PoolId | undefined>;
124+
125+
const poolIdsToFetch = uniq(poolIds.filter(isNotNil));
126+
if (poolIdsToFetch.length === 0) {
127+
return undefined;
128+
}
129+
130+
const stakePools = await this.#stakePoolProvider.queryStakePools({
131+
filters: { identifier: { values: poolIdsToFetch.map((id) => ({ id })) } },
132+
pagination: { limit: 3, startAt: 0 }
133+
});
134+
135+
const stakePoolMathingPoolId = (index: number) => stakePools.pageResults.find((pool) => pool.id === poolIds[index]);
136+
return {
137+
currentEpoch: stakePoolMathingPoolId(0),
138+
nextEpoch: stakePoolMathingPoolId(1),
139+
nextNextEpoch: stakePoolMathingPoolId(2)
140+
};
141+
}
142+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './RewardAccountInfoProvider';

packages/cardano-services-client/src/RewardsProvider/BlockfrostRewardsProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export class BlockfrostRewardsProvider extends BlockfrostProvider implements Rew
3131
}: Range<Cardano.EpochNo> = {}
3232
): Promise<Reward[]> {
3333
const batchSize = 100;
34-
return fetchSequentially<Reward, Responses['account_reward_content'][0]>({
34+
return fetchSequentially<Responses['account_reward_content'][0], Reward>({
3535
haveEnoughItems: (_, rewardsPage) => {
3636
const lastReward = rewardsPage[rewardsPage.length - 1];
3737
return !lastReward || lastReward.epoch >= upperBound;

packages/cardano-services-client/src/blockfrost/util.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const buildQueryString = ({ page, count, order }: PaginationOptions) => {
2222
};
2323

2424
// copied from @cardano-sdk/cardano-services and updated to use custom blockfrost client instead of blockfrost-js
25-
export const fetchSequentially = async <Item, Response>(
25+
export const fetchSequentially = async <Response, Item = Response>(
2626
props: {
2727
request: (paginationQueryString: string) => Promise<Response[]>;
2828
responseTranslator?: (response: Response[]) => Item[];
@@ -42,7 +42,7 @@ export const fetchSequentially = async <Item, Response>(
4242
const newAccumulatedItems = [...accumulated, ...maybeTranslatedResponse] as Item[];
4343
const haveEnoughItems = props.haveEnoughItems?.(newAccumulatedItems, response);
4444
if (response.length === count && !haveEnoughItems) {
45-
return fetchSequentially<Item, Response>(props, page + 1, newAccumulatedItems);
45+
return fetchSequentially<Response, Item>(props, page + 1, newAccumulatedItems);
4646
}
4747
return newAccumulatedItems;
4848
} catch (error) {

packages/cardano-services-client/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export * from './StakePoolProvider';
66
export * from './UtxoProvider';
77
export * from './ChainHistoryProvider';
88
export * from './DRepProvider';
9+
export * from './RewardAccountInfoProvider';
910
export * from './NetworkInfoProvider';
1011
export * from './RewardsProvider';
1112
export * from './HandleProvider';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './types';
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { Cardano, Provider } from '../..';
2+
3+
export interface RewardAccountInfoProvider extends Provider {
4+
rewardAccountInfo(rewardAccount: Cardano.RewardAccount): Promise<Cardano.RewardAccountInfo>;
5+
delegationPortfolio(rewardAccounts: Cardano.RewardAccount[]): Promise<Cardano.Cip17DelegationPortfolio | null>;
6+
}

packages/core/src/Provider/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export * from './Provider';
22
export * from './StakePoolProvider';
33
export * from './AssetProvider';
44
export * from './NetworkInfoProvider';
5+
export * from './RewardAccountInfoProvider';
56
export * from './RewardsProvider';
67
export * from './TxSubmitProvider';
78
export * as ProviderUtil from './providerUtil';

0 commit comments

Comments
 (0)