Skip to content

Commit

Permalink
Merge pull request #185 from securesecrets/fix-secret-api
Browse files Browse the repository at this point in the history
fix: secret api had breaking changes after upgrade related to the lcd api
  • Loading branch information
AustinWoetzel authored Jan 3, 2025
2 parents 63b774e + f6e4265 commit 260950f
Show file tree
Hide file tree
Showing 10 changed files with 480 additions and 393 deletions.
5 changes: 5 additions & 0 deletions .changeset/spicy-badgers-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shadeprotocol/shadejs": patch
---

change secret endpoints to new api after upgrade
20 changes: 20 additions & 0 deletions src/client/services/clientServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,26 @@ const sendSecretClientContractQuery$ = ({
first(),
);

// This function queries the total supply of the a token
const secretClientTokenSupplyQuery$ = (
client: SecretNetworkClient,
denom: string,
) => createFetchClient(defer(
() => from(client.query.bank.supplyOf({
denom,
})),
));

// This function queries the total supply of the a token
const secretClientValidatorQuery$ = (
client: SecretNetworkClient,
validatorAddress: string,
) => createFetchClient(defer(
() => from(client.query.staking.validator({ validator_addr: validatorAddress })),
));

export {
sendSecretClientContractQuery$,
secretClientTokenSupplyQuery$,
secretClientValidatorQuery$,
};
3 changes: 3 additions & 0 deletions src/lib/apy/derivativeScrt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { of } from 'rxjs';
import chainQueryParsedResponse from '~/test/mocks/secretChainQueries/chainQueryParsedResponse.json';
import { queryDerivativeScrtInfo$ } from '~/contracts/services/derivativeScrt';
import stakingInfoResponseMainnet from '~/test/mocks/derivativeScrt/stakingInfoResponseMainnet.json';
import { mockValidatorsCommissions } from '~/test/mocks/secretChainQueries/validatorsCommissionsParsedResponse';
import {
secretChainQueries$,
SecretQueryOptions,
Expand All @@ -21,6 +22,8 @@ beforeAll(() => {
vi.mock('~/lib/apy/secretQueries', async (importOriginal: any) => ({
...(await importOriginal()),
secretChainQueries$: vi.fn(() => of(chainQueryParsedResponse)),
queryScrtTotalSupply$: vi.fn(() => of(292470737038201)),
queryValidatorsCommission$: vi.fn(() => of(mockValidatorsCommissions)),
}));

vi.mock('~/contracts/services/derivativeScrt', () => ({
Expand Down
59 changes: 38 additions & 21 deletions src/lib/apy/derivativeScrt.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import {
SecretChainDataQueryModel,
} from '~/types/apy';
import { forkJoin, lastValueFrom, map } from 'rxjs';
import {
forkJoin,
lastValueFrom,
map,
switchMap,
} from 'rxjs';
import {
DerivativeScrtInfo,
} from '~/types/contracts/derivativeScrt/model';
import { convertCoinFromUDenom } from '~/lib/utils';
import { queryDerivativeScrtInfo$ } from '~/contracts/services/derivativeScrt';
import {
queryScrtTotalSupply$,
queryValidatorsCommission$,
secretChainQueries$,
SecretQueryOptions,
} from './secretQueries';
} from '~/lib/apy/secretQueries';
import { calcAggregateAPR, calcAPY } from './utils';

const SECRET_DECIMALS = 6;
Expand Down Expand Up @@ -39,6 +46,7 @@ function calculateDerivativeScrtApy$({
const queries = Object.values(SecretQueryOptions);
return forkJoin({
chainParameters: secretChainQueries$<SecretChainDataQueryModel>(lcdEndpoint, queries),
scrtTotalSupplyRaw: queryScrtTotalSupply$(lcdEndpoint, chainId),
derivativeInfo: queryDerivativeScrtInfo$({
queryRouterContractAddress,
queryRouterCodeHash,
Expand All @@ -48,27 +56,36 @@ function calculateDerivativeScrtApy$({
chainId,
}),
}).pipe(
map((response: {
switchMap((response: {
chainParameters: SecretChainDataQueryModel,
scrtTotalSupplyRaw: number,
derivativeInfo: DerivativeScrtInfo,
}) => {
const apr = calcAggregateAPR({
networkValidatorList: response.chainParameters.secretValidators,
validatorSet: response.derivativeInfo.validators,
inflationRate: response.chainParameters.secretInflationPercent,
totalScrtStaked: convertCoinFromUDenom(
response.chainParameters.secretTotalStakedRaw,
SECRET_DECIMALS,
).toNumber(),
totalScrtSupply: convertCoinFromUDenom(
response.chainParameters.secretTotalSupplyRaw,
SECRET_DECIMALS,
).toNumber(),
foundationTax: response.chainParameters.secretTaxes!.foundationTaxPercent,
communityTax: response.chainParameters.secretTaxes!.communityTaxPercent,
});
return calcAPY(365, apr);
}),
}) => queryValidatorsCommission$({
lcdEndpoint,
chainId,
validatorAddresses: response.derivativeInfo.validators.map((
validator,
) => validator.validatorAddress),
}).pipe(
map((validatorCommissions) => {
const apr = calcAggregateAPR({
networkValidatorList: validatorCommissions,
validatorSet: response.derivativeInfo.validators,
inflationRate: response.chainParameters.secretInflationPercent,
totalScrtStaked: convertCoinFromUDenom(
response.chainParameters.secretTotalStakedRaw,
SECRET_DECIMALS,
).toNumber(),
totalScrtSupply: convertCoinFromUDenom(
response.scrtTotalSupplyRaw,
SECRET_DECIMALS,
).toNumber(),
foundationTax: response.chainParameters.secretTaxes!.foundationTaxPercent,
communityTax: response.chainParameters.secretTaxes!.communityTaxPercent,
});
return calcAPY(365, apr);
}),
)),
);
}

Expand Down
16 changes: 3 additions & 13 deletions src/lib/apy/secretQueries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,20 @@ beforeAll(async () => {

test('it can parse chain queries', () => {
expect(parseSecretQueryResponse(
{ result: 10 },
{ inflation: 10 },
SecretQueryOptions.INFLATION,
)).toStrictEqual({ secretInflationPercent: 10 });

expect(parseSecretQueryResponse(
{ amount: { amount: 10 } },
SecretQueryOptions.TOTAL_SUPPLY,
)).toStrictEqual({ secretTotalSupplyRaw: 10 });

expect(parseSecretQueryResponse(
{ result: { bonded_tokens: 10 } },
{ pool: { bonded_tokens: 10 } },
SecretQueryOptions.TOTAL_STAKED,
)).toStrictEqual({ secretTotalStakedRaw: 10 });

expect(parseSecretQueryResponse(
{ result: { community_tax: '10', secret_foundation_tax: '11' } },
{ params: { community_tax: '10', secret_foundation_tax: '11' } },
SecretQueryOptions.TAXES,
)).toStrictEqual({ secretTaxes: { foundationTaxPercent: 11, communityTaxPercent: 10 } });

expect(parseSecretQueryResponse(
{ result: [{ commission: { commission_rates: { rate: '10' } }, operator_address: 'MOCK_ADDRESS' }] },
SecretQueryOptions.VALIDATORS,
)).toStrictEqual({ secretValidators: [{ ratePercent: 10, validatorAddress: 'MOCK_ADDRESS' }] });

expect(parseSecretQueryResponse(
'nonsence',
'/nonsence/api',
Expand Down
126 changes: 97 additions & 29 deletions src/lib/apy/secretQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,26 @@ import {
forkJoin,
lastValueFrom,
map,
first, switchMap,
from,
mergeMap,
toArray,
} from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
import { createFetch } from '~/client/services/createFetch';
import { QuerySupplyOfResponse } from 'secretjs/dist/grpc_gateway/cosmos/bank/v1beta1/query.pb';
import { getActiveQueryClient$ } from '~/client';
import {
SecretValidatorItemResponse,
} from '~/types/apy';
secretClientTokenSupplyQuery$,
secretClientValidatorQuery$,
} from '~/client/services/clientServices';
import { QueryValidatorResponse } from 'secretjs/dist/grpc_gateway/cosmos/staking/v1beta1/query.pb';
import { ValidatorRate } from '~/types/apy';

enum SecretQueryOptions {
INFLATION = '/minting/inflation',
TOTAL_SUPPLY = '/cosmos/bank/v1beta1/supply/uscrt',
TOTAL_STAKED = '/staking/pool',
TAXES = '/distribution/parameters',
VALIDATORS = '/staking/validators',
INFLATION = '/cosmos/mint/v1beta1/inflation',
TOTAL_STAKED = '/cosmos/staking/v1beta1/pool',
TAXES = '/cosmos/distribution/v1beta1/params',
}

/**
Expand All @@ -29,40 +36,25 @@ function parseSecretQueryResponse<ResponseType>(
switch (query) {
case SecretQueryOptions.INFLATION:
return {
secretInflationPercent: response?.result,
secretInflationPercent: Number(response?.inflation),
} as ResponseType;
case SecretQueryOptions.TOTAL_SUPPLY:
return {
secretTotalSupplyRaw: response?.amount?.amount,
}as ResponseType;
case SecretQueryOptions.TOTAL_STAKED:
return {
secretTotalStakedRaw: response?.result?.bonded_tokens,
secretTotalStakedRaw: Number(response?.pool?.bonded_tokens),
}as ResponseType;
case SecretQueryOptions.TAXES:
if (response.result
&& response.result.community_tax
&& response.result.secret_foundation_tax
if (response.params
&& response.params.community_tax
&& response.params.secret_foundation_tax
) {
return {
secretTaxes: {
foundationTaxPercent: Number(response.result.secret_foundation_tax),
communityTaxPercent: Number(response.result.community_tax),
foundationTaxPercent: Number(response.params.secret_foundation_tax),
communityTaxPercent: Number(response.params.community_tax),
},
}as ResponseType;
}
return response as ResponseType;
case SecretQueryOptions.VALIDATORS: {
const parsedValidators = response?.result?.map((
nextValidator: SecretValidatorItemResponse,
) => ({
ratePercent: Number(nextValidator.commission.commission_rates.rate),
validatorAddress: nextValidator.operator_address,
}));
return {
secretValidators: parsedValidators,
} as ResponseType;
}
default:
return response as ResponseType;
}
Expand Down Expand Up @@ -138,11 +130,87 @@ async function secretChainQueries<ResponseType>(
return lastValueFrom(secretChainQueries$(url, queries));
}

const parseTotalSupplyResponse = (response:QuerySupplyOfResponse) => {
if (response === undefined
|| response.amount === undefined
|| response.amount.amount === undefined
) {
throw new Error('No response from the total supply query');
}
return Number(response.amount.amount);
};

const parseValidatorResponse = (response: QueryValidatorResponse): ValidatorRate => {
if (response === undefined
|| response.validator === undefined
|| response.validator?.commission === undefined
|| response.validator?.commission?.commission_rates === undefined
|| response.validator?.operator_address === undefined
|| response.validator?.commission?.commission_rates?.rate === undefined

) {
throw new Error('Validator commission not found');
}
return {
validatorAddress: response.validator.operator_address,
ratePercent: Number(response.validator.commission.commission_rates.rate),
};
};

/**
* query the SCRT token total supply
*/
const queryScrtTotalSupply$ = (
lcdEndpoint?: string,
chainId?: string,
) => getActiveQueryClient$(lcdEndpoint, chainId).pipe(
switchMap(({ client }) => secretClientTokenSupplyQuery$(client, 'uscrt')),
map((response) => parseTotalSupplyResponse(response)),
first(),
);

/**
* query the validator commission for an individual validator
*/
const queryValidatorCommission$ = ({
lcdEndpoint,
chainId,
validatorAddress,
}:{
lcdEndpoint?: string,
chainId?: string,
validatorAddress: string,
}) => getActiveQueryClient$(lcdEndpoint, chainId).pipe(
switchMap(({ client }) => secretClientValidatorQuery$(client, validatorAddress)),
map((response) => parseValidatorResponse(response)),
first(),
);

/**
* query the commission for multiple validators
*/
const queryValidatorsCommission$ = ({
lcdEndpoint,
chainId,
validatorAddresses,
}: {
lcdEndpoint?: string;
chainId?: string;
validatorAddresses: string[];
}) => from(validatorAddresses).pipe(
mergeMap((
validatorAddress,
) => queryValidatorCommission$({ lcdEndpoint, chainId, validatorAddress })),
toArray(),
);
export {
SecretQueryOptions,
parseSecretQueryResponse,
secretChainQuery$,
secretChainQuery,
secretChainQueries$,
secretChainQueries,
queryScrtTotalSupply$,
queryValidatorCommission$,
queryValidatorsCommission$,
};
6 changes: 3 additions & 3 deletions src/lib/apy/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import {
test,
expect,
} from 'vitest';
import chainQueryParsedResponse from '~/test/mocks/secretChainQueries/chainQueryParsedResponse.json';
import stakingInfoResponseMainnet from '~/test/mocks/derivativeScrt/stakingInfoResponseMainnet.json';
import { mockValidatorsCommissions } from '~/test/mocks/secretChainQueries/validatorsCommissionsParsedResponse';
import {
getValidatorCommission,
calcValidatorAPR,
Expand All @@ -14,7 +14,7 @@ import {

test('It gets a validator commission', () => {
expect(() => getValidatorCommission('MOCK_VAL_ADDR', [])).toThrowError(/^Error: validator address MOCK_VAL_ADDR not found in list$/);
expect(getValidatorCommission('secretvaloper1rfnmcuwzf3zn7r025j9zr3ncc7mt9ge56l7se7', chainQueryParsedResponse.secretValidators)).toStrictEqual(0.08);
expect(getValidatorCommission('secretvaloper1rfnmcuwzf3zn7r025j9zr3ncc7mt9ge56l7se7', mockValidatorsCommissions)).toStrictEqual(0.08);
});

test('It calculates APR for a given validator', () => {
Expand All @@ -30,7 +30,7 @@ test('It calculates APR for a given validator', () => {

test('It can calculate aggregate apr for a list of validators', () => {
expect(calcAggregateAPR({
networkValidatorList: chainQueryParsedResponse.secretValidators,
networkValidatorList: mockValidatorsCommissions,
validatorSet: stakingInfoResponseMainnet.validators,
inflationRate: 100,
totalScrtStaked: 1234567891234,
Expand Down
Loading

0 comments on commit 260950f

Please sign in to comment.