Skip to content

Commit

Permalink
Merge pull request #31 from axone-protocol/feat/proposals
Browse files Browse the repository at this point in the history
Feat/proposals
  • Loading branch information
yevhen-burkovskyi authored Jun 18, 2024
2 parents 6400ec3 + 49dad51 commit dd42094
Show file tree
Hide file tree
Showing 13 changed files with 191 additions and 70 deletions.
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

0 comments on commit dd42094

Please sign in to comment.