mirror of
https://github.com/pezkuwichain/pezkuwi-subquery.git
synced 2026-04-22 01:57:58 +00:00
Add multi-staking entities, APY calculation, Asset Hub indexer support
- Add StakingApy, ActiveStaker, Reward entities to schema.graphql - Add APY calculation engine in NewEra.ts (inflation curve + validator commission) - Add saveMultiStakingReward to Rewards.ts and PoolRewards.ts - Add handleAssetTransfer for assets.Transferred events - Add constants.ts with genesis hashes and inflation params - Add docker-compose.prod.yml for production deployment (relay + assethub nodes) - Add deploy-vps.yml GitHub Actions workflow for auto-deploy on push
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
name: Deploy to VPS
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Deploy via SSH
|
||||
uses: appleboy/ssh-action@v1
|
||||
with:
|
||||
host: ${{ secrets.VPS_HOST }}
|
||||
username: root
|
||||
key: ${{ secrets.VPS_SSH_KEY }}
|
||||
script: |
|
||||
cd /opt/subquery
|
||||
git pull origin main
|
||||
docker compose -f docker-compose.prod.yml pull
|
||||
docker compose -f docker-compose.prod.yml up -d
|
||||
@@ -0,0 +1,79 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
postgres:
|
||||
container_name: postgres-pezkuwi
|
||||
image: postgres:16-alpine
|
||||
ports:
|
||||
- 5432:5432
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
- ./docker/init:/docker-entrypoint-initdb.d
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
environment:
|
||||
POSTGRES_PASSWORD: pezkuwi_subquery_2024
|
||||
restart: always
|
||||
|
||||
subquery-node-relay:
|
||||
container_name: node-pezkuwi-relay
|
||||
image: onfinality/subql-node:latest
|
||||
depends_on:
|
||||
postgres: { condition: service_healthy }
|
||||
restart: always
|
||||
environment:
|
||||
DB_USER: postgres
|
||||
DB_PASS: pezkuwi_subquery_2024
|
||||
DB_DATABASE: postgres
|
||||
DB_HOST: postgres
|
||||
DB_PORT: 5432
|
||||
volumes:
|
||||
- ./:/app/project
|
||||
command:
|
||||
- -f=/app/project/pezkuwi.yaml
|
||||
- --disable-historical=true
|
||||
- --batch-size=30
|
||||
|
||||
subquery-node-assethub:
|
||||
container_name: node-pezkuwi-assethub
|
||||
image: onfinality/subql-node:latest
|
||||
depends_on:
|
||||
postgres: { condition: service_healthy }
|
||||
restart: always
|
||||
environment:
|
||||
DB_USER: postgres
|
||||
DB_PASS: pezkuwi_subquery_2024
|
||||
DB_DATABASE: postgres
|
||||
DB_HOST: postgres
|
||||
DB_PORT: 5432
|
||||
volumes:
|
||||
- ./:/app/project
|
||||
command:
|
||||
- -f=/app/project/pezkuwi-assethub.yaml
|
||||
- --disable-historical=true
|
||||
- --batch-size=30
|
||||
|
||||
graphql-engine:
|
||||
container_name: query-pezkuwi
|
||||
image: onfinality/subql-query:v1.5.0
|
||||
ports:
|
||||
- 3000:3000
|
||||
depends_on:
|
||||
- subquery-node-relay
|
||||
- subquery-node-assethub
|
||||
restart: always
|
||||
environment:
|
||||
DB_USER: postgres
|
||||
DB_PASS: pezkuwi_subquery_2024
|
||||
DB_DATABASE: postgres
|
||||
DB_HOST: postgres
|
||||
DB_PORT: 5432
|
||||
command:
|
||||
- --name=subquery-pezkuwi-staking
|
||||
- --playground
|
||||
- --indexer=http://subquery-node-relay:3000
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
+29
-2
@@ -17,7 +17,7 @@ type AssetTransfer @jsonField {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type Reward @jsonField {
|
||||
type RewardInfo @jsonField {
|
||||
eventIdx: Int!
|
||||
amount: String!
|
||||
isReward: Boolean!
|
||||
@@ -97,7 +97,7 @@ type HistoryElement @entity {
|
||||
extrinsicHash: String
|
||||
timestamp: BigInt! @index
|
||||
address: String! @index
|
||||
reward: Reward
|
||||
reward: RewardInfo
|
||||
poolReward: PoolReward
|
||||
extrinsic: Extrinsic
|
||||
transfer: Transfer
|
||||
@@ -123,3 +123,30 @@ type ErrorEvent @entity {
|
||||
id: ID!
|
||||
description: String!
|
||||
}
|
||||
|
||||
# ===== Multi-staking API entities (used by PezWallet dashboard) =====
|
||||
|
||||
type StakingApy @entity {
|
||||
id: ID!
|
||||
networkId: String! @index
|
||||
stakingType: String! @index
|
||||
maxAPY: Float!
|
||||
}
|
||||
|
||||
type ActiveStaker @entity {
|
||||
id: ID!
|
||||
networkId: String! @index
|
||||
stakingType: String! @index
|
||||
address: String! @index
|
||||
}
|
||||
|
||||
type Reward @entity {
|
||||
id: ID!
|
||||
networkId: String! @index
|
||||
stakingType: String! @index
|
||||
address: String! @index
|
||||
type: RewardType! @index
|
||||
amount: BigInt!
|
||||
timestamp: BigInt!
|
||||
blockNumber: Int! @index
|
||||
}
|
||||
|
||||
+227
-11
@@ -1,9 +1,20 @@
|
||||
import { SubstrateEvent } from "@subql/types";
|
||||
import { eventId } from "./common";
|
||||
import { EraValidatorInfo } from "../types/models/EraValidatorInfo";
|
||||
import { EraValidatorInfo, StakingApy, ActiveStaker } from "../types";
|
||||
import { IndividualExposure } from "../types";
|
||||
import { Option } from "@pezkuwi/types";
|
||||
import { Exposure } from "@pezkuwi/types/interfaces/staking";
|
||||
import {
|
||||
PEZKUWI_RELAY_GENESIS,
|
||||
PEZKUWI_ASSET_HUB_GENESIS,
|
||||
STAKING_TYPE_RELAYCHAIN,
|
||||
STAKING_TYPE_NOMINATION_POOL,
|
||||
INFLATION_FALLOFF,
|
||||
INFLATION_MAX,
|
||||
INFLATION_MIN,
|
||||
INFLATION_STAKE_TARGET,
|
||||
PERBILL_DIVISOR,
|
||||
} from "./constants";
|
||||
|
||||
export async function handleStakersElected(
|
||||
event: SubstrateEvent,
|
||||
@@ -16,45 +27,75 @@ export async function handleNewEra(event: SubstrateEvent): Promise<void> {
|
||||
.unwrap()
|
||||
.toNumber();
|
||||
|
||||
let validatorExposures: Array<{
|
||||
address: string;
|
||||
total: bigint;
|
||||
own: bigint;
|
||||
others: IndividualExposure[];
|
||||
}>;
|
||||
|
||||
if (api.query.staking.erasStakersOverview) {
|
||||
await processEraStakersPaged(event, currentEra);
|
||||
validatorExposures = await processEraStakersPaged(event, currentEra);
|
||||
} else {
|
||||
await processEraStakersClipped(event, currentEra);
|
||||
validatorExposures = await processEraStakersClipped(event, currentEra);
|
||||
}
|
||||
|
||||
// Compute and save APY + active stakers
|
||||
await updateStakingApyAndActiveStakers(currentEra, validatorExposures);
|
||||
}
|
||||
|
||||
interface ValidatorExposureData {
|
||||
address: string;
|
||||
total: bigint;
|
||||
own: bigint;
|
||||
others: IndividualExposure[];
|
||||
}
|
||||
|
||||
async function processEraStakersClipped(
|
||||
event: SubstrateEvent,
|
||||
currentEra: number,
|
||||
): Promise<void> {
|
||||
): Promise<ValidatorExposureData[]> {
|
||||
const exposures =
|
||||
await api.query.staking.erasStakersClipped.entries(currentEra);
|
||||
|
||||
const result: ValidatorExposureData[] = [];
|
||||
|
||||
for (const [key, exposure] of exposures) {
|
||||
const [, validatorId] = key.args;
|
||||
let validatorIdString = validatorId.toString();
|
||||
const exp = exposure as unknown as Exposure;
|
||||
const others = exp.others.map((other) => {
|
||||
return {
|
||||
who: other.who.toString(),
|
||||
value: other.value.toString(),
|
||||
} as IndividualExposure;
|
||||
});
|
||||
|
||||
const eraValidatorInfo = new EraValidatorInfo(
|
||||
eventId(event) + validatorIdString,
|
||||
validatorIdString,
|
||||
currentEra,
|
||||
exp.total.toBigInt(),
|
||||
exp.own.toBigInt(),
|
||||
exp.others.map((other) => {
|
||||
return {
|
||||
who: other.who.toString(),
|
||||
value: other.value.toString(),
|
||||
} as IndividualExposure;
|
||||
}),
|
||||
others,
|
||||
);
|
||||
await eraValidatorInfo.save();
|
||||
|
||||
result.push({
|
||||
address: validatorIdString,
|
||||
total: exp.total.toBigInt(),
|
||||
own: exp.own.toBigInt(),
|
||||
others,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function processEraStakersPaged(
|
||||
event: SubstrateEvent,
|
||||
currentEra: number,
|
||||
): Promise<void> {
|
||||
): Promise<ValidatorExposureData[]> {
|
||||
const overview =
|
||||
await api.query.staking.erasStakersOverview.entries(currentEra);
|
||||
const pages = await api.query.staking.erasStakersPaged.entries(currentEra);
|
||||
@@ -87,6 +128,8 @@ async function processEraStakersPaged(
|
||||
{},
|
||||
);
|
||||
|
||||
const result: ValidatorExposureData[] = [];
|
||||
|
||||
for (const [key, exp] of overview) {
|
||||
const exposure = (exp as Option<any>).unwrap();
|
||||
const [, validatorId] = key.args;
|
||||
@@ -106,5 +149,178 @@ async function processEraStakersPaged(
|
||||
others,
|
||||
);
|
||||
await eraValidatorInfo.save();
|
||||
|
||||
result.push({
|
||||
address: validatorIdString,
|
||||
total: exposure.total.toBigInt(),
|
||||
own: exposure.own.toBigInt(),
|
||||
others,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ===== APY Calculation (Substrate inflation curve) =====
|
||||
|
||||
function calculateYearlyInflation(stakedPortion: number): number {
|
||||
const idealStake = INFLATION_STAKE_TARGET; // No parachains on Pezkuwi
|
||||
const idealInterest = INFLATION_MAX / idealStake;
|
||||
|
||||
if (stakedPortion >= 0 && stakedPortion <= idealStake) {
|
||||
return (
|
||||
INFLATION_MIN +
|
||||
stakedPortion * (idealInterest - INFLATION_MIN / idealStake)
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
INFLATION_MIN +
|
||||
(idealInterest * idealStake - INFLATION_MIN) *
|
||||
Math.pow(2, (idealStake - stakedPortion) / INFLATION_FALLOFF)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface ValidatorAPYData {
|
||||
totalStake: bigint;
|
||||
commission: number; // 0.0 to 1.0
|
||||
}
|
||||
|
||||
function calculateMaxAPY(
|
||||
totalIssuance: bigint,
|
||||
validators: ValidatorAPYData[],
|
||||
): number {
|
||||
if (validators.length === 0 || totalIssuance === BigInt(0)) return 0;
|
||||
|
||||
const totalStaked = validators.reduce(
|
||||
(sum, v) => sum + v.totalStake,
|
||||
BigInt(0),
|
||||
);
|
||||
if (totalStaked === BigInt(0)) return 0;
|
||||
|
||||
// Use scaled division for precision with large BigInts
|
||||
const SCALE = BigInt(1_000_000_000);
|
||||
const stakedPortion =
|
||||
Number((totalStaked * SCALE) / totalIssuance) / Number(SCALE);
|
||||
|
||||
const yearlyInflation = calculateYearlyInflation(stakedPortion);
|
||||
const averageValidatorRewardPercentage = yearlyInflation / stakedPortion;
|
||||
const averageValidatorStake = totalStaked / BigInt(validators.length);
|
||||
|
||||
let maxAPY = 0;
|
||||
for (const v of validators) {
|
||||
if (v.totalStake === BigInt(0)) continue;
|
||||
const stakeRatio =
|
||||
Number((averageValidatorStake * SCALE) / v.totalStake) / Number(SCALE);
|
||||
const yearlyRewardPercentage =
|
||||
averageValidatorRewardPercentage * stakeRatio;
|
||||
const apy = yearlyRewardPercentage * (1 - v.commission);
|
||||
if (apy > maxAPY) maxAPY = apy;
|
||||
}
|
||||
|
||||
return maxAPY;
|
||||
}
|
||||
|
||||
async function updateStakingApyAndActiveStakers(
|
||||
currentEra: number,
|
||||
validatorExposures: ValidatorExposureData[],
|
||||
): Promise<void> {
|
||||
if (validatorExposures.length === 0) return;
|
||||
|
||||
// 1. Get total issuance from the relay chain
|
||||
const totalIssuance = (
|
||||
(await api.query.balances.totalIssuance()) as any
|
||||
).toBigInt();
|
||||
|
||||
// 2. Get validator commissions
|
||||
const validatorAddresses = validatorExposures.map((v) => v.address);
|
||||
const validatorPrefs = await api.query.staking.validators.multi(
|
||||
validatorAddresses,
|
||||
);
|
||||
|
||||
const validatorsWithCommission: ValidatorAPYData[] =
|
||||
validatorExposures.map((v, i) => {
|
||||
const prefs = validatorPrefs[i] as any;
|
||||
const commissionPerbill = prefs.commission
|
||||
? Number(prefs.commission.toString())
|
||||
: 0;
|
||||
return {
|
||||
totalStake: v.total,
|
||||
commission: commissionPerbill / PERBILL_DIVISOR,
|
||||
};
|
||||
});
|
||||
|
||||
// 3. Calculate maxAPY
|
||||
const maxAPY = calculateMaxAPY(totalIssuance, validatorsWithCommission);
|
||||
|
||||
// 4. Save StakingApy for relay chain (relaychain staking)
|
||||
const relayApyId = `${PEZKUWI_RELAY_GENESIS}-${STAKING_TYPE_RELAYCHAIN}`;
|
||||
const relayApy = StakingApy.create({
|
||||
id: relayApyId,
|
||||
networkId: PEZKUWI_RELAY_GENESIS,
|
||||
stakingType: STAKING_TYPE_RELAYCHAIN,
|
||||
maxAPY,
|
||||
});
|
||||
await relayApy.save();
|
||||
|
||||
// 5. Save StakingApy for Asset Hub (relaychain staking option)
|
||||
const ahRelayApyId = `${PEZKUWI_ASSET_HUB_GENESIS}-${STAKING_TYPE_RELAYCHAIN}`;
|
||||
const ahRelayApy = StakingApy.create({
|
||||
id: ahRelayApyId,
|
||||
networkId: PEZKUWI_ASSET_HUB_GENESIS,
|
||||
stakingType: STAKING_TYPE_RELAYCHAIN,
|
||||
maxAPY,
|
||||
});
|
||||
await ahRelayApy.save();
|
||||
|
||||
// 6. Save StakingApy for Asset Hub (nomination-pool staking option)
|
||||
const ahPoolApyId = `${PEZKUWI_ASSET_HUB_GENESIS}-${STAKING_TYPE_NOMINATION_POOL}`;
|
||||
const ahPoolApy = StakingApy.create({
|
||||
id: ahPoolApyId,
|
||||
networkId: PEZKUWI_ASSET_HUB_GENESIS,
|
||||
stakingType: STAKING_TYPE_NOMINATION_POOL,
|
||||
maxAPY,
|
||||
});
|
||||
await ahPoolApy.save();
|
||||
|
||||
logger.info(
|
||||
`Era ${currentEra}: maxAPY=${(maxAPY * 100).toFixed(2)}% validators=${validatorExposures.length} totalIssuance=${totalIssuance}`,
|
||||
);
|
||||
|
||||
// 7. Collect all unique nominator addresses from exposures (active stakers)
|
||||
const activeNominators = new Set<string>();
|
||||
for (const v of validatorExposures) {
|
||||
for (const nominator of v.others) {
|
||||
activeNominators.add(nominator.who);
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Clear previous active stakers and save new ones
|
||||
// For relay chain direct staking
|
||||
for (const address of activeNominators) {
|
||||
const relayStakerId = `${PEZKUWI_RELAY_GENESIS}-${STAKING_TYPE_RELAYCHAIN}-${address}`;
|
||||
const staker = ActiveStaker.create({
|
||||
id: relayStakerId,
|
||||
networkId: PEZKUWI_RELAY_GENESIS,
|
||||
stakingType: STAKING_TYPE_RELAYCHAIN,
|
||||
address,
|
||||
});
|
||||
await staker.save();
|
||||
}
|
||||
|
||||
// Also save validators themselves as active stakers
|
||||
for (const v of validatorExposures) {
|
||||
const validatorStakerId = `${PEZKUWI_RELAY_GENESIS}-${STAKING_TYPE_RELAYCHAIN}-${v.address}`;
|
||||
const staker = ActiveStaker.create({
|
||||
id: validatorStakerId,
|
||||
networkId: PEZKUWI_RELAY_GENESIS,
|
||||
stakingType: STAKING_TYPE_RELAYCHAIN,
|
||||
address: v.address,
|
||||
});
|
||||
await staker.save();
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Era ${currentEra}: saved ${activeNominators.size} active stakers (relay)`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@ import {
|
||||
AccumulatedReward,
|
||||
AccumulatedPoolReward,
|
||||
HistoryElement,
|
||||
Reward,
|
||||
RewardType,
|
||||
} from "../types";
|
||||
import { SubstrateEvent } from "@subql/types";
|
||||
import {
|
||||
eventId,
|
||||
eventIdFromBlockAndIdxAndAddress,
|
||||
timestamp,
|
||||
eventIdWithAddress,
|
||||
@@ -18,6 +20,11 @@ import {
|
||||
} from "./Rewards";
|
||||
import { getPoolMembers } from "./Cache";
|
||||
import { Option } from "@pezkuwi/types";
|
||||
import {
|
||||
PEZKUWI_RELAY_GENESIS,
|
||||
PEZKUWI_ASSET_HUB_GENESIS,
|
||||
STAKING_TYPE_NOMINATION_POOL,
|
||||
} from "./constants";
|
||||
|
||||
export async function handlePoolReward(
|
||||
rewardEvent: SubstrateEvent,
|
||||
@@ -37,6 +44,13 @@ export async function handlePoolReward(
|
||||
RewardType.reward,
|
||||
accumulatedReward.amount,
|
||||
);
|
||||
// Save to multi-staking Reward entity for both relay and Asset Hub networkIds
|
||||
await savePoolMultiStakingReward(
|
||||
rewardEvent,
|
||||
accountId.toString(),
|
||||
(amount as any).toBigInt(),
|
||||
RewardType.reward,
|
||||
);
|
||||
}
|
||||
|
||||
async function handlePoolRewardForTxHistory(
|
||||
@@ -200,6 +214,13 @@ async function handleRelaychainPooledStakingSlash(
|
||||
RewardType.slash,
|
||||
accumulatedReward.amount,
|
||||
);
|
||||
// Save to multi-staking Reward entity
|
||||
await savePoolMultiStakingReward(
|
||||
event,
|
||||
accountId,
|
||||
personalSlash,
|
||||
RewardType.slash,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,3 +260,32 @@ async function handlePoolSlashForTxHistory(
|
||||
|
||||
await element.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save pool reward/slash to the multi-staking Reward entity
|
||||
* Saves for both relay chain and Asset Hub networkIds since nom pools
|
||||
* are accessible from both contexts
|
||||
*/
|
||||
async function savePoolMultiStakingReward(
|
||||
event: SubstrateEvent,
|
||||
accountAddress: string,
|
||||
amount: bigint,
|
||||
rewardType: RewardType,
|
||||
): Promise<void> {
|
||||
const ts = timestamp(event.block);
|
||||
const bn = blockNumber(event);
|
||||
const baseId = `${eventId(event)}-${accountAddress}-pool`;
|
||||
|
||||
// Save for Asset Hub networkId (nomination pools are accessed via Asset Hub)
|
||||
const ahReward = Reward.create({
|
||||
id: `${baseId}-ah`,
|
||||
networkId: PEZKUWI_ASSET_HUB_GENESIS,
|
||||
stakingType: STAKING_TYPE_NOMINATION_POOL,
|
||||
address: accountAddress,
|
||||
type: rewardType,
|
||||
amount,
|
||||
timestamp: ts,
|
||||
blockNumber: bn,
|
||||
});
|
||||
await ahReward.save();
|
||||
}
|
||||
|
||||
+34
-1
@@ -3,6 +3,7 @@ import {
|
||||
AccumulatedReward,
|
||||
HistoryElement,
|
||||
Reward,
|
||||
RewardInfo,
|
||||
RewardType,
|
||||
} from "../types";
|
||||
import {
|
||||
@@ -26,6 +27,10 @@ import {
|
||||
cachedController,
|
||||
cachedStakingRewardEraIndex,
|
||||
} from "./Cache";
|
||||
import {
|
||||
PEZKUWI_RELAY_GENESIS,
|
||||
STAKING_TYPE_RELAYCHAIN,
|
||||
} from "./constants";
|
||||
|
||||
function isPayoutStakers(call: any): boolean {
|
||||
return call.method == "payoutStakers";
|
||||
@@ -74,6 +79,7 @@ export async function handleReward(rewardEvent: SubstrateEvent): Promise<void> {
|
||||
RewardType.reward,
|
||||
accumulatedReward.amount,
|
||||
);
|
||||
await saveMultiStakingReward(rewardEvent, RewardType.reward, STAKING_TYPE_RELAYCHAIN);
|
||||
}
|
||||
|
||||
async function handleRewardForTxHistory(
|
||||
@@ -220,6 +226,7 @@ export async function handleSlash(slashEvent: SubstrateEvent): Promise<void> {
|
||||
RewardType.slash,
|
||||
accumulatedReward.amount,
|
||||
);
|
||||
await saveMultiStakingReward(slashEvent, RewardType.slash, STAKING_TYPE_RELAYCHAIN);
|
||||
}
|
||||
|
||||
async function getValidators(era: number): Promise<Set<string>> {
|
||||
@@ -297,7 +304,7 @@ async function buildRewardEvents<A>(
|
||||
eventIdx: number,
|
||||
stash: string,
|
||||
amount: string,
|
||||
) => Reward,
|
||||
) => RewardInfo,
|
||||
) {
|
||||
let blockNum = block.block.header.number.toString();
|
||||
let blockTimestamp = timestamp(block);
|
||||
@@ -521,3 +528,29 @@ function decodeDataFromReward(event: SubstrateEvent): [any, any] {
|
||||
}
|
||||
return [account, amount];
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a reward/slash to the multi-staking Reward entity
|
||||
* (used by PezWallet dashboard for rewards aggregation)
|
||||
*/
|
||||
export async function saveMultiStakingReward(
|
||||
event: SubstrateEvent,
|
||||
rewardType: RewardType,
|
||||
stakingType: string,
|
||||
): Promise<void> {
|
||||
const [accountId, amount] = decodeDataFromReward(event);
|
||||
const accountAddress = accountId.toString();
|
||||
const id = `${eventId(event)}-${accountAddress}-${stakingType}`;
|
||||
|
||||
const reward = Reward.create({
|
||||
id,
|
||||
networkId: PEZKUWI_RELAY_GENESIS,
|
||||
stakingType,
|
||||
address: accountAddress,
|
||||
type: rewardType,
|
||||
amount: (amount as any).toBigInt(),
|
||||
timestamp: timestamp(event.block),
|
||||
blockNumber: blockNumber(event),
|
||||
});
|
||||
await reward.save();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HistoryElement, Transfer } from "../types";
|
||||
import { HistoryElement, Transfer, AssetTransfer } from "../types";
|
||||
import { SubstrateEvent } from "@subql/types";
|
||||
import {
|
||||
blockNumber,
|
||||
@@ -61,3 +61,42 @@ async function createTransfer(
|
||||
element.transfer = transfer;
|
||||
await element.save();
|
||||
}
|
||||
|
||||
export async function handleAssetTransfer(event: SubstrateEvent): Promise<void> {
|
||||
const [assetId, from, to, amount] = getEventData(event);
|
||||
|
||||
const assetTransferData: AssetTransfer = {
|
||||
assetId: assetId.toString(),
|
||||
amount: amount.toString(),
|
||||
from: from.toString(),
|
||||
to: to.toString(),
|
||||
fee: calculateFeeAsString(event.extrinsic, from.toString()),
|
||||
eventIdx: event.idx,
|
||||
success: true,
|
||||
};
|
||||
|
||||
await createAssetTransferHistory(event, from.toString(), "-from", assetTransferData);
|
||||
await createAssetTransferHistory(event, to.toString(), "-to", assetTransferData);
|
||||
}
|
||||
|
||||
async function createAssetTransferHistory(
|
||||
event: SubstrateEvent,
|
||||
address: string,
|
||||
suffix: string,
|
||||
data: AssetTransfer,
|
||||
): Promise<void> {
|
||||
const element = new HistoryElement(
|
||||
`${eventId(event)}${suffix}`,
|
||||
blockNumber(event),
|
||||
timestamp(event.block),
|
||||
address,
|
||||
);
|
||||
|
||||
if (event.extrinsic !== undefined) {
|
||||
element.extrinsicHash = event.extrinsic.extrinsic.hash.toString();
|
||||
element.extrinsicIdx = event.extrinsic.idx;
|
||||
}
|
||||
|
||||
element.assetTransfer = data;
|
||||
await element.save();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// Pezkuwi chain genesis hashes (with 0x prefix, as the app expects)
|
||||
export const PEZKUWI_RELAY_GENESIS =
|
||||
"0xbb4a61ab0c4b8c12f5eab71d0c86c482e03a275ecdafee678dea712474d33d75";
|
||||
export const PEZKUWI_ASSET_HUB_GENESIS =
|
||||
"0x00d0e1d0581c3cd5c5768652d52f4520184018b44f56a2ae1e0dc9d65c00c948";
|
||||
|
||||
// Staking type identifiers (must match the app's mapStakingTypeToSubQueryId)
|
||||
export const STAKING_TYPE_RELAYCHAIN = "relaychain";
|
||||
export const STAKING_TYPE_NOMINATION_POOL = "nomination-pool";
|
||||
|
||||
// Substrate default inflation parameters (Kusama-like, no parachains)
|
||||
export const INFLATION_FALLOFF = 0.05;
|
||||
export const INFLATION_MAX = 0.1;
|
||||
export const INFLATION_MIN = 0.025;
|
||||
export const INFLATION_STAKE_TARGET = 0.75;
|
||||
|
||||
// Commission is stored in perbill (1_000_000_000 = 100%)
|
||||
export const PERBILL_DIVISOR = 1_000_000_000;
|
||||
Reference in New Issue
Block a user