Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/proposals #31

Merged
merged 2 commits into from
Jun 18, 2024
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
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ MY_STAKING_OVERVIEW=120 #2 mins
GLOBAL_STAKING_OVERVIEW=120 #2 mins
STAKING_VALIDATORS=120 #2 mins
STAKING_VALIDATOR_DELEGATION=120 #2 mins
VALIDATOR_SIGNATURE=120 #2 mins
PROPOSALS_CACHE_TTL=120 #2 mins
PROPOSAL_CACHE_TTL=120 #2 mins

#Keybase
KEYBASE_URL=https://keybase.io/_/api/1.0
KEYBASE_URL=https://keybase.io/_/api/1.0
4 changes: 3 additions & 1 deletion src/core/config/config.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ export interface CacheConfig {
validators: number;
validatorDelegation: number;
validatorSignature: number;
proposals: number;
proposal: number;
}

export interface KeybaseConfig {
url: string;
}
}
2 changes: 2 additions & 0 deletions src/core/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export const config: ConfigDto = {
validators: +process.env.STAKING_VALIDATORS!,
validatorDelegation: +process.env.STAKING_VALIDATOR_DELEGATION!,
validatorSignature: +process.env.VALIDATOR_SIGNATURE!,
proposals: +process.env.PROPOSALS_CACHE_TTL!,
proposal: +process.env.PROPOSAL_CACHE_TTL!,
},
keybase: {
url: process.env.KEYBASE_URL!,
Expand Down
1 change: 1 addition & 0 deletions src/core/lib/okp4/enums/endpoints.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export enum Endpoints {
BLOCKS_BY_HEIGHT = 'cosmos/base/tendermint/v1beta1/blocks/:height',
GOV_PARAMS = 'cosmos/gov/v1/params/:params_type',
GOV_PROPOSALS = 'cosmos/gov/v1/proposals',
GOV_PROPOSAL = 'cosmos/gov/v1/proposals/:proposal_id',
}
3 changes: 2 additions & 1 deletion src/core/lib/okp4/enums/route-param.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export enum RouteParam {
VALIDATOR_ADDRES = ':validator_addr',
HEIGHT = ':height',
PARAMS_TYPE = ':params_type',
}
PROPOSAL_ID = ':proposal_id',
}
148 changes: 93 additions & 55 deletions src/core/lib/okp4/okp4.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,75 +14,81 @@ import { SpendableBalancesResponse } from "./responses/spendable-balances.respon
import { SupplyResponse } from "./responses/supply.response";
import { ValidatorStatus } from "./enums/validator-status.enum";
import { ValidatorDelegationsResponse } from "./responses/validator-delegations.response";
import { fromBase64, toBase64, fromHex, toHex } from '@cosmjs/encoding';
import { sha256 } from '@cosmjs/crypto';
import { fromBase64, toBase64, fromHex, toHex } from "@cosmjs/encoding";
import { sha256 } from "@cosmjs/crypto";
import { BlocksResponse } from "./responses/blocks.response";
import { WebSocket } from 'ws';
import { WebSocket } from "ws";
import { Log } from "@core/loggers/log";
import { EventEmitter2 } from "@nestjs/event-emitter";
import { GovType } from "./enums/gov-type.enum";
import { GovParamsResponse } from "./responses/gov-params.response";
import { GetProposalsResponse } from "./responses/get-proposals.response";
import { GetProposalResponse } from "@core/lib/okp4/responses/get-proposal.response";

@Injectable()
export class Okp4Service {
private BASE_URL = config.okp4.url;

constructor(
private readonly httpService: HttpService,
private eventEmitter: EventEmitter2,
) { }
private readonly httpService: HttpService,
private eventEmitter: EventEmitter2
) {}

private constructUrl(endpoint: string, params?: string): string {
return `${this.BASE_URL}/${endpoint}${params ? `?${params}` : ''}`;
return `${this.BASE_URL}/${endpoint}${params ? `?${params}` : ""}`;
}

private getWithErrorHandling<T>(url: string): Promise<T> {
return this.errorHandleWrapper(
this.httpService.get.bind(
null,
url,
),
);
return this.errorHandleWrapper(this.httpService.get.bind(null, url));
}

async getSupplyByDenom(denom: string): Promise<SupplyByDenomResponse> {
return this.getWithErrorHandling(
this.constructUrl(
Endpoints.SUPPLY_BY_DENOM,
createUrlParams({ denom }),
createUrlParams({ denom })
)
);
}

async getDelegations(addr: string): Promise<GetDelegationsResponse> {
return this.getWithErrorHandling(this.constructUrl(`${Endpoints.STAKING_DELEGATIONS}/${addr}`));
return this.getWithErrorHandling(
this.constructUrl(`${Endpoints.STAKING_DELEGATIONS}/${addr}`)
);
}

async getDelegatorsValidators(addr: string): Promise<DelegatorValidatorsResponse> {
async getDelegatorsValidators(
addr: string
): Promise<DelegatorValidatorsResponse> {
return this.getWithErrorHandling(
this.constructUrl(
Endpoints.DELEGATORS_VALIDATORS.replace(
RouteParam.DELEGATOR_ADDRES,
addr,
addr
)
)
);
}

async getDelegatorsRewards(addr: string): Promise<DelegatorsRewardsResponse> {
async getDelegatorsRewards(
addr: string
): Promise<DelegatorsRewardsResponse> {
return this.getWithErrorHandling(
this.constructUrl(
Endpoints.DELEGATORS_REWARDS.replace(
RouteParam.DELEGATOR_ADDRES,
addr,
addr
)
)
);
}

async getSpendableBalances(addr: string): Promise<SpendableBalancesResponse> {
return this.getWithErrorHandling(this.constructUrl(`${Endpoints.SPENDABLE_BALANCE}/${addr}`));
async getSpendableBalances(
addr: string
): Promise<SpendableBalancesResponse> {
return this.getWithErrorHandling(
this.constructUrl(`${Endpoints.SPENDABLE_BALANCE}/${addr}`)
);
}

async getBondValidators() {
Expand All @@ -94,34 +100,33 @@ export class Okp4Service {
if (status) {
params = createUrlParams({ status });
}
const url = this.constructUrl(
Endpoints.VALIDATORS,
params,
);
const url = this.constructUrl(Endpoints.VALIDATORS, params);
return this.getWithErrorHandling(url);
}

async getTotalSupply(): Promise<SupplyResponse> {
const url = this.constructUrl(
Endpoints.TOTAL_SUPPLY,
);
const url = this.constructUrl(Endpoints.TOTAL_SUPPLY);
return this.getWithErrorHandling(url);
}

async getValidatorDelegations(validatorAddr: string, limit?: number, offset?: number): Promise<ValidatorDelegationsResponse> {
async getValidatorDelegations(
validatorAddr: string,
limit?: number,
offset?: number
): Promise<ValidatorDelegationsResponse> {
let params = undefined;
if (limit && offset) {
params = createUrlParams({
'pagination.offset': offset.toString(),
'pagination.limit': limit.toString(),
'pagination.count_total': true.toString()
})
"pagination.offset": offset.toString(),
"pagination.limit": limit.toString(),
"pagination.count_total": true.toString(),
});
}
return this.getWithErrorHandling(
this.constructUrl(
Endpoints.VALIDATOR_DELEGATIONS.replace(
RouteParam.VALIDATOR_ADDRES,
validatorAddr,
validatorAddr
),
params
)
Expand All @@ -130,20 +135,25 @@ export class Okp4Service {

async getLatestBlocks(): Promise<BlocksResponse> {
return this.getWithErrorHandling(
this.constructUrl(Endpoints.BLOCKS_LATEST),
)
this.constructUrl(Endpoints.BLOCKS_LATEST)
);
}

async getBlocksByHeight(height: number): Promise<BlocksResponse> {
async getBlocksByHeight(height: number): Promise<BlocksResponse> {
return this.getWithErrorHandling(
this.constructUrl(
Endpoints.BLOCKS_BY_HEIGHT.replace(RouteParam.HEIGHT, height.toString())
Endpoints.BLOCKS_BY_HEIGHT.replace(
RouteParam.HEIGHT,
height.toString()
)
)
)
);
}

apiPubkeyToAddr(pubkey: string) {
return toBase64(fromHex(toHex(sha256(fromBase64(pubkey))).slice(0, 40)))
return toBase64(
fromHex(toHex(sha256(fromBase64(pubkey))).slice(0, 40))
);
}

wssPubkeyToAddr(pubkey: string) {
Expand All @@ -152,24 +162,37 @@ export class Okp4Service {

async connectToNewBlockSocket(event: string) {
const client = new WebSocket(config.okp4.wss);
client.on('open', () => {
client.send(JSON.stringify({"jsonrpc":"2.0","method":"subscribe","id":0,"params":{"query":"tm.event='NewBlock'"}}));
client.on("open", () => {
client.send(
JSON.stringify({
jsonrpc: "2.0",
method: "subscribe",
id: 0,
params: { query: "tm.event='NewBlock'" },
})
);
});
client.on('message', (data) => {
client.on("message", (data) => {
if (Buffer.isBuffer(data)) {
const message = data.toString('utf-8');
const message = data.toString("utf-8");
try {
const jsonData = JSON.parse(message);
if (
jsonData &&
jsonData?.result &&
jsonData?.result?.query === "tm.event='NewBlock'"
jsonData?.result &&
jsonData?.result?.query === "tm.event='NewBlock'"
) {
this.eventEmitter.emit(event, jsonData?.result?.data?.value);
this.eventEmitter.emit(
event,
jsonData?.result?.data?.value
);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
Log.warn('[OKP4] Problem with parsing data from wss\n' + e.message);
Log.warn(
"[OKP4] Problem with parsing data from wss\n" +
e.message
);
}
}
});
Expand All @@ -189,23 +212,38 @@ export class Okp4Service {
);
}

async getProposal(
proposalId: string | number
): Promise<GetProposalResponse> {
return this.getWithErrorHandling(
this.constructUrl(
Endpoints.GOV_PROPOSAL.replace(
RouteParam.PROPOSAL_ID,
String(proposalId)
)
)
);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
private async errorHandleWrapper<T>(fn: any): Promise<T> {
try {
const response: GSFResponse<T> = await fn();

if (this.isFailedResponse(response)) {
throw new BadRequestException(response.message);
}

return response as T;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
throw new BadRequestException(e.message);
}
}

private isFailedResponse<T>(response: GSFResponse<T>): response is FailedResponse {
private isFailedResponse<T>(
response: GSFResponse<T>
): response is FailedResponse {
return (response as FailedResponse).message !== undefined;
}
}
}
4 changes: 4 additions & 0 deletions src/core/lib/okp4/responses/get-proposal.response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { WithPaginationResponse } from "./with-pagination.response"
import { Proposal } from "@core/lib/okp4/responses/get-proposals.response";

export type GetProposalResponse = WithPaginationResponse<{ proposal: Proposal }>;
1 change: 1 addition & 0 deletions src/modules/staking/enums/query-param.enum.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export enum QueryParam {
ADDRESS = 'address',
VALIDATOR_ADDRESS = 'validatorAddress',
PROPOSAL_ID = 'proposal_id'
}
4 changes: 3 additions & 1 deletion src/modules/staking/enums/staking-cache-prefix.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ export enum StakingCachePrefix {
VALIDATOR_IMG = 'validator_img',
VALIDATOR_SIGNATURES = 'validator_signatures',
VALIDATOR_RECENTLY_PROPOSED_BLOCKS = 'validator_recently_propored_blocks',
}
PROPOSALS = 'proposals',
PROPOSAL = 'proposal',
}
4 changes: 3 additions & 1 deletion src/modules/staking/enums/staking-endpoints.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ export enum StakingEndpoints {
VALIDATORS_BY_ADDRESS = '/validators/:address',
VALIDATORS_UPTIME = '/validators/:address/uptime',
VALIDATORS_RECENTLY_PROPOSED_BLOCKS = '/validators/:address/recently-proposed-blocks',
}
PROPOSALS = '/proposals',
PROPOSAL = '/proposals/:proposal_id',
}
Loading
Loading