Skip to content

Commit

Permalink
Shade staking query interface and APY calculation for derivatives (#118)
Browse files Browse the repository at this point in the history
* feat: added scaffolding to calculate deriavtive APY on back end

* feat: added tests

* feat: cleaning up pr

* feat: added routing for docs

* feat: added changeset

* fix: wrong constant

* fix: tests

* feat: moved some files into a utils file, wrote tests, moved mocks

* feat: moved a comment
  • Loading branch information
SissonJ authored Apr 4, 2024
1 parent def9038 commit 34ce7af
Show file tree
Hide file tree
Showing 29 changed files with 2,283 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/healthy-hornets-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shadeprotocol/shadejs": minor
---

added apy calculations for derivatives and shade staking query interface
2 changes: 2 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default defineConfig({
{ text: 'Swap', link: '/queries/swap' },
{ text: 'Oracle', link: '/queries/oracle' },
{ text: 'stkd-SCRT', link: '/queries/derivativeScrt' },
{ text: 'Shade Staking', link: '/queries/shadeStaking' },
{ text: 'Batch Query', link: '/queries/batch-query' },
{ text: 'Snip20', link: '/queries/snip20' },
]
Expand All @@ -45,6 +46,7 @@ export default defineConfig({
items: [
{ text: 'Swap', link: '/calculations/swap' },
{ text: 'Routing', link: '/calculations/routing' },
{ text: 'Apy', link: '/calculations/apy' },
]
},
{
Expand Down
29 changes: 29 additions & 0 deletions docs/calculations/apy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# APY Calculations

This page demonstrates how to calculate outputs the shade derivative contracts

### stkd-SCRT APY
Calculates the apy expected for the stkd-SCRT token
```ts
/**
* Will calculate APY for the stkd secret derivative contract
*
* returns a number that is the decimal form of the percent APY
* @param lcdEndpoint is not optional due to the requirement of the secretChainQueries() function
*/
function calculateDerivativeScrtApy({
queryRouterContractAddress,
queryRouterCodeHash,
contractAddress,
codeHash,
lcdEndpoint,
chainId,
}: {
queryRouterContractAddress: string,
queryRouterCodeHash?: string,
contractAddress: string,
codeHash: string,
lcdEndpoint: string,
chainId?: string,
}): Promise<number>
```
2 changes: 2 additions & 0 deletions docs/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This page contains a list of deployed contracts.
| Batch Query Router | secret17gnlxnwux0szd7qhl90ym8lw22qvedjz4v09dm | 72a09535b77b76862f7b568baf1ddbe158a2e4bbd0f0879c69ada9b398e31c1f |
| Oracle | secret10n2xl5jmez6r9umtdrth78k0vwmce0l5m9f5dm | 32c4710842b97a526c243a68511b15f58d6e72a388af38a7221ff3244c754e91 |
| stkd-scrt | secret1k6u0cy4feepm6pehnz804zmwakuwdapm69tuc4 | f6be719b3c6feb498d3554ca0398eb6b7e7db262acb33f84a8f12106da6bbb09 |
| Shade Staking | secret1y6px5x7jzrk8hyvy67f06ytn8v0jwculypwxws | 2a1ae7fd2be82931cb11d0ce82b2e243507f2006074e2f316da661beb1abe3c3 |

::: tip
The ShadeSwap pairs contracts are accessible via the factory registered pairs query.
Expand All @@ -24,3 +25,4 @@ The ShadeSwap pairs contracts are accessible via the factory registered pairs qu
| Batch Query Router | secret10cxkxmspt44mp2wym6dcguk5wqqkm5a9ydw3du | 72a09535b77b76862f7b568baf1ddbe158a2e4bbd0f0879c69ada9b398e31c1f |
| Oracle | secret17z47r9u4nqytpdgvewxq4jqd965sfj2wpsnlak | 113c47c016667817b315dde03b4ee9774edf1fb293a7ea3f02d983c6b1fa1cf1 |
| stkd-scrt | secret14svk0x3sztxwta9kv9dv6fwzqlc26mmjfyypc2 | 680fbb3c8f8eb1c920da13d857daaedaa46ab8f9a8e26e892bb18a16985ec29e |
| Shade Staking | secret1f9ph34ydnnqg0rzs6h39ucswspllutp9c4ch4k | 5e1e1b0c2a8e2114d29725002be7206598ec68f4bdb28718b082fd84748d416f |
46 changes: 46 additions & 0 deletions docs/queries/shadeStaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Shade Staking Examples

This page demonstrates how to query the shade staking contracts

## Staking Info

**input**

```ts
/**
* query the staking info from the shade staking contract
*/
async function queryShadeStakingOpportunity({
shadeStakingContractAddress,
shadeStakingCodeHash,
lcdEndpoint,
chainId,
}: {
shadeStakingContractAddress: string,
shadeStakingCodeHash?: string,
lcdEndpoint?: string,
chainId?: string,
}): Promise<StakingInfoServiceModel>
```

**output**

```ts
type StakingInfoServiceModel = {
stakeTokenAddress: string,
totalStakedRaw: string,
unbondingPeriod: number,
rewardPools: StakingRewardPoolServiceModel[],
}
// type references below
type StakingRewardPoolServiceModel = {
id: string,
amountRaw: string,
startDate: Date,
endDate: Date,
tokenAddress: string,
rateRaw: string,
}
```
1 change: 1 addition & 0 deletions src/contracts/definitions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './snip20';
export * from './swap';
export * from './derivativeShd';
export * from './derivativeScrt';
export * from './shadeStaking';
10 changes: 10 additions & 0 deletions src/contracts/definitions/shadeStaking.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {
test,
expect,
} from 'vitest';
import { msgQueryShadeStakingOpportunity } from './shadeStaking';

test('it tests the form of the query staking info msg', () => {
const output = { staking_info: {} };
expect(msgQueryShadeStakingOpportunity()).toStrictEqual(output);
});
8 changes: 8 additions & 0 deletions src/contracts/definitions/shadeStaking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* message for the getting staking opportunity info from the shade staking contract
*/
const msgQueryShadeStakingOpportunity = () => ({ staking_info: {} });

export {
msgQueryShadeStakingOpportunity,
};
4 changes: 2 additions & 2 deletions src/contracts/services/derivativeShd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import {
import { convertCoinFromUDenom } from '~/lib/utils';
import { msgQueryShdDerivativeStakingInfo } from '~/contracts/definitions/derivativeShd';

// Contract returns price as a rate of dSHD/SHD with 6 decimals
const DERIVATE_PRICE_DECIMALS = 6;
// Contract returns price as a rate of dSHD/SHD with 8 decimals
const DERIVATE_PRICE_DECIMALS = 8;

/**
* Parses the staking info query into a cleaner data model
Expand Down
1 change: 1 addition & 0 deletions src/contracts/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './snip20';
export * from './swap';
export * from './derivativeScrt';
export * from './derivativeShd';
export * from './shadeStaking';
78 changes: 78 additions & 0 deletions src/contracts/services/shadeStaking.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
test,
expect,
vi,
beforeAll,
} from 'vitest';
import { of } from 'rxjs';
import stakingOpportunityResponse from '~/test/mocks/shadeStaking/stakingOpportunityResponse.json';
import { stakingOpportunityResponseParsed } from '~/test/mocks/shadeStaking/response';
import {
parseStakingOpportunity,
queryShadeStakingOpportunity,
queryShadeStakingOpportunity$,
} from '~/contracts/services/shadeStaking';
import { StakingInfoServiceResponse } from '~/types/contracts/shadeStaking/index';

const sendSecretClientContractQuery$ = vi.hoisted(() => vi.fn());

beforeAll(() => {
vi.mock('~/contracts/definitions/shadeStaking', () => ({
msgQueryShadeStakingOpportunity: vi.fn(() => 'STAKING_INFO_MSG'),
}));

vi.mock('~/client/index', () => ({
getActiveQueryClient$: vi.fn(() => of({ client: 'CLIENT' })),
}));

vi.mock('~/client/services/clientServices', () => ({
sendSecretClientContractQuery$,
}));
});

test('it can parse the shade staking info', () => {
expect(parseStakingOpportunity(
stakingOpportunityResponse as StakingInfoServiceResponse,
)).toStrictEqual(stakingOpportunityResponseParsed);
});

test('it can call the query shade staking info service', async () => {
// observables function
sendSecretClientContractQuery$.mockReturnValueOnce(of(stakingOpportunityResponse));

const input = {
shadeStakingContractAddress: 'CONTRACT_ADDRESS',
shadeStakingCodeHash: 'CODE_HASH',
lcdEndpoint: 'LCD_ENDPOINT',
chainId: 'CHAIN_ID',
};

let output;
queryShadeStakingOpportunity$(input).subscribe({
next: (response) => {
output = response;
},
});

expect(sendSecretClientContractQuery$).toHaveBeenCalledWith({
queryMsg: 'STAKING_INFO_MSG',
client: 'CLIENT',
contractAddress: input.shadeStakingContractAddress,
codeHash: input.shadeStakingCodeHash,
});

expect(output).toStrictEqual(stakingOpportunityResponseParsed);

// async/await function
sendSecretClientContractQuery$.mockReturnValueOnce(of(stakingOpportunityResponse));
const output2 = await queryShadeStakingOpportunity(input);

expect(sendSecretClientContractQuery$).toHaveBeenCalledWith({
queryMsg: 'STAKING_INFO_MSG',
client: 'CLIENT',
contractAddress: input.shadeStakingContractAddress,
codeHash: input.shadeStakingCodeHash,
});

expect(output2).toStrictEqual(stakingOpportunityResponseParsed);
});
95 changes: 95 additions & 0 deletions src/contracts/services/shadeStaking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { getActiveQueryClient$ } from '~/client';
import { sendSecretClientContractQuery$ } from '~/client/services/clientServices';
import {
switchMap,
first,
map,
lastValueFrom,
} from 'rxjs';
import { convertCoinFromUDenom } from '~/lib/utils';
import { msgQueryShadeStakingOpportunity } from '~/contracts/definitions/shadeStaking';
import {
StakingInfoServiceResponse,
StakingRewardPoolServiceModel,
StakingInfoServiceModel,
} from '~/types/contracts/shadeStaking/index';

// data returned from the contract in normalized form with
// 18 decimals, in addition to any decimals on the individual token
const NORMALIZATION_FACTOR = 18;

/**
* parses the response from the shade staking contract into a model
*/
function parseStakingOpportunity(data: StakingInfoServiceResponse): StakingInfoServiceModel {
const stakeTokenAddress = data.staking_info.info.stake_token;
const totalStakedRaw = data.staking_info.info.total_staked;
const unbondingPeriod = Number(data.staking_info.info.unbond_period);
const rewardPools: StakingRewardPoolServiceModel[] = data.staking_info.info.reward_pools
.map((reward) => ({
id: reward.id,
amountRaw: reward.amount,
startDate: new Date(Number(reward.start) * 1000),
endDate: new Date(Number(reward.end) * 1000),
tokenAddress: reward.token.address,
rateRaw: convertCoinFromUDenom(reward.rate, NORMALIZATION_FACTOR).toString(),
}));
return {
stakeTokenAddress,
totalStakedRaw,
unbondingPeriod,
rewardPools,
};
}

/**
* query the staking info from the shade staking contract
*/
const queryShadeStakingOpportunity$ = ({
shadeStakingContractAddress,
shadeStakingCodeHash,
lcdEndpoint,
chainId,
}: {
shadeStakingContractAddress: string,
shadeStakingCodeHash?: string,
lcdEndpoint?: string,
chainId?: string,
}) => getActiveQueryClient$(lcdEndpoint, chainId).pipe(
switchMap(({ client }) => sendSecretClientContractQuery$({
queryMsg: msgQueryShadeStakingOpportunity(),
client,
contractAddress: shadeStakingContractAddress,
codeHash: shadeStakingCodeHash,
})),
map((response) => parseStakingOpportunity(response as StakingInfoServiceResponse)),
first(),
);

/**
* query the staking info from the shade staking contract
*/
async function queryShadeStakingOpportunity({
shadeStakingContractAddress,
shadeStakingCodeHash,
lcdEndpoint,
chainId,
}: {
shadeStakingContractAddress: string,
shadeStakingCodeHash?: string,
lcdEndpoint?: string,
chainId?: string,
}) {
return lastValueFrom(queryShadeStakingOpportunity$({
shadeStakingContractAddress,
shadeStakingCodeHash,
lcdEndpoint,
chainId,
}));
}

export {
parseStakingOpportunity,
queryShadeStakingOpportunity$,
queryShadeStakingOpportunity,
};
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ export {
convertCoinToUDenom,
} from '~/lib/utils';
export * from './types';
export * from '~/lib/apy/derivativeShd';
export * from '~/lib/apy/derivativeScrt';
Loading

0 comments on commit 34ce7af

Please sign in to comment.