mirror of
https://github.com/pezkuwichain/pezkuwi-subquery.git
synced 2026-04-21 23:37:56 +00:00
feat(governance): add OpenGov v2 indexer for referenda, votes, delegations
Index convictionVoting and referenda pallet data to support PezWallet governance UI — referendum tracking, casting votes, delegation stats, and delegator vote propagation.
This commit is contained in:
@@ -101,6 +101,48 @@ services:
|
|||||||
- --playground
|
- --playground
|
||||||
- --indexer=http://subquery-node-assethub:3000
|
- --indexer=http://subquery-node-assethub:3000
|
||||||
|
|
||||||
|
subquery-node-governance:
|
||||||
|
container_name: node-pezkuwi-governance
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: docker/Dockerfile.node
|
||||||
|
depends_on:
|
||||||
|
postgres: { condition: service_healthy }
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
TZ: UTC
|
||||||
|
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-governance.yaml
|
||||||
|
- --db-schema=subquery-pezkuwi-governance
|
||||||
|
- --disable-historical=true
|
||||||
|
- --batch-size=30
|
||||||
|
|
||||||
|
graphql-engine-governance:
|
||||||
|
container_name: query-pezkuwi-gov
|
||||||
|
image: onfinality/subql-query:v1.5.0
|
||||||
|
ports:
|
||||||
|
- 3002:3000
|
||||||
|
depends_on:
|
||||||
|
- subquery-node-governance
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
DB_USER: postgres
|
||||||
|
DB_PASS: pezkuwi_subquery_2024
|
||||||
|
DB_DATABASE: postgres
|
||||||
|
DB_HOST: postgres
|
||||||
|
DB_PORT: 5432
|
||||||
|
command:
|
||||||
|
- --name=subquery-pezkuwi-governance
|
||||||
|
- --playground
|
||||||
|
- --indexer=http://subquery-node-governance:3000
|
||||||
|
|
||||||
noter-bot:
|
noter-bot:
|
||||||
container_name: noter-pezkuwi
|
container_name: noter-pezkuwi
|
||||||
build:
|
build:
|
||||||
|
|||||||
+3
-2
@@ -2,7 +2,7 @@
|
|||||||
"name": "subquery-pezkuwi",
|
"name": "subquery-pezkuwi",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"packageManager": "yarn@4.12.0",
|
"packageManager": "yarn@4.12.0",
|
||||||
"description": "Pezkuwi SubQuery - Staking rewards, NominationPools, transfers indexer for PezWallet",
|
"description": "Pezkuwi SubQuery - Staking, governance, NominationPools, transfers indexer for PezWallet",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "./node_modules/.bin/subql build",
|
"build": "./node_modules/.bin/subql build",
|
||||||
@@ -15,7 +15,8 @@
|
|||||||
"dist",
|
"dist",
|
||||||
"schema.graphql",
|
"schema.graphql",
|
||||||
"pezkuwi.yaml",
|
"pezkuwi.yaml",
|
||||||
"pezkuwi-assethub.yaml"
|
"pezkuwi-assethub.yaml",
|
||||||
|
"pezkuwi-governance.yaml"
|
||||||
],
|
],
|
||||||
"author": "Pezkuwi Team",
|
"author": "Pezkuwi Team",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
specVersion: 1.0.0
|
||||||
|
name: subquery-pezkuwi-governance
|
||||||
|
version: 1.0.0
|
||||||
|
runner:
|
||||||
|
node:
|
||||||
|
name: "@subql/node"
|
||||||
|
version: ">=4.6.6"
|
||||||
|
query:
|
||||||
|
name: "@subql/query"
|
||||||
|
version: "*"
|
||||||
|
description: Pezkuwi Governance SubQuery - OpenGov referenda, votes, delegations for PezWallet
|
||||||
|
repository: https://github.com/pezkuwichain/pezkuwi-subquery
|
||||||
|
schema:
|
||||||
|
file: ./schema.graphql
|
||||||
|
network:
|
||||||
|
chainId: "0x1aa94987791a5544e9667ec249d2cef1b8fdd6083c85b93fc37892d54a1156ca"
|
||||||
|
endpoint:
|
||||||
|
- wss://rpc.pezkuwichain.io
|
||||||
|
- wss://mainnet.pezkuwichain.io
|
||||||
|
chaintypes:
|
||||||
|
file: ./chainTypes/pezkuwi.json
|
||||||
|
dataSources:
|
||||||
|
- name: governance
|
||||||
|
kind: substrate/Runtime
|
||||||
|
startBlock: 1
|
||||||
|
mapping:
|
||||||
|
file: ./dist/index.js
|
||||||
|
handlers:
|
||||||
|
# Referendum lifecycle
|
||||||
|
- handler: handleReferendumSubmitted
|
||||||
|
kind: substrate/EventHandler
|
||||||
|
filter:
|
||||||
|
module: referenda
|
||||||
|
method: Submitted
|
||||||
|
# Direct vote on referendum
|
||||||
|
- handler: handleVoteCall
|
||||||
|
kind: substrate/CallHandler
|
||||||
|
filter:
|
||||||
|
module: convictionVoting
|
||||||
|
method: vote
|
||||||
|
success: true
|
||||||
|
# Remove vote from referendum
|
||||||
|
- handler: handleRemoveVoteCall
|
||||||
|
kind: substrate/CallHandler
|
||||||
|
filter:
|
||||||
|
module: convictionVoting
|
||||||
|
method: removeVote
|
||||||
|
success: true
|
||||||
|
# Delegate voting power
|
||||||
|
- handler: handleDelegateCall
|
||||||
|
kind: substrate/CallHandler
|
||||||
|
filter:
|
||||||
|
module: convictionVoting
|
||||||
|
method: delegate
|
||||||
|
success: true
|
||||||
|
# Remove delegation
|
||||||
|
- handler: handleUndelegateCall
|
||||||
|
kind: substrate/CallHandler
|
||||||
|
filter:
|
||||||
|
module: convictionVoting
|
||||||
|
method: undelegate
|
||||||
|
success: true
|
||||||
@@ -150,3 +150,72 @@ type Reward @entity {
|
|||||||
timestamp: BigInt!
|
timestamp: BigInt!
|
||||||
blockNumber: Int! @index
|
blockNumber: Int! @index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ===== Governance — Dijital Kurdistan OpenGov v2 =====
|
||||||
|
|
||||||
|
type VoteBalance @jsonField {
|
||||||
|
amount: String!
|
||||||
|
conviction: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type StandardVote @jsonField {
|
||||||
|
aye: Boolean!
|
||||||
|
vote: VoteBalance!
|
||||||
|
}
|
||||||
|
|
||||||
|
type SplitVote @jsonField {
|
||||||
|
ayeAmount: String!
|
||||||
|
nayAmount: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type SplitAbstainVote @jsonField {
|
||||||
|
ayeAmount: String!
|
||||||
|
nayAmount: String!
|
||||||
|
abstainAmount: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Referendum @entity {
|
||||||
|
id: ID! # referendumIndex as string
|
||||||
|
trackId: Int! @index
|
||||||
|
}
|
||||||
|
|
||||||
|
type CastingVoting @entity {
|
||||||
|
id: ID! # referendumId-voter
|
||||||
|
referendum: Referendum!
|
||||||
|
voter: String! @index
|
||||||
|
delegateId: String @index
|
||||||
|
standardVote: StandardVote
|
||||||
|
splitVote: SplitVote
|
||||||
|
splitAbstainVote: SplitAbstainVote
|
||||||
|
at: Int! @index
|
||||||
|
timestamp: BigInt
|
||||||
|
}
|
||||||
|
|
||||||
|
type DelegatorVoting @entity {
|
||||||
|
id: ID! # parentId-delegator
|
||||||
|
parent: CastingVoting!
|
||||||
|
delegator: String! @index
|
||||||
|
vote: VoteBalance
|
||||||
|
}
|
||||||
|
|
||||||
|
type Delegation @entity {
|
||||||
|
id: ID! # delegator-trackId
|
||||||
|
delegateId: String! @index
|
||||||
|
delegator: String! @index
|
||||||
|
trackId: Int! @index
|
||||||
|
delegation: VoteBalance
|
||||||
|
}
|
||||||
|
|
||||||
|
type Delegate @entity {
|
||||||
|
id: ID! # accountId hex
|
||||||
|
accountId: String! @index
|
||||||
|
delegators: Int!
|
||||||
|
delegatorVotes: BigInt!
|
||||||
|
}
|
||||||
|
|
||||||
|
type DelegateVote @entity {
|
||||||
|
id: ID! # delegateId-referendumId
|
||||||
|
delegate: Delegate!
|
||||||
|
at: Int! @index
|
||||||
|
timestamp: BigInt
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ export * from "./mappings/PoolRewards";
|
|||||||
export * from "./mappings/Transfers";
|
export * from "./mappings/Transfers";
|
||||||
export * from "./mappings/NewEra";
|
export * from "./mappings/NewEra";
|
||||||
export * from "./mappings/PoolStakers";
|
export * from "./mappings/PoolStakers";
|
||||||
|
export * from "./mappings/Governance";
|
||||||
import "@pezkuwi/api-augment";
|
import "@pezkuwi/api-augment";
|
||||||
|
|||||||
@@ -0,0 +1,411 @@
|
|||||||
|
import { SubstrateEvent, SubstrateExtrinsic } from "@subql/types";
|
||||||
|
import {
|
||||||
|
Referendum,
|
||||||
|
CastingVoting,
|
||||||
|
DelegatorVoting,
|
||||||
|
Delegation,
|
||||||
|
Delegate,
|
||||||
|
DelegateVote,
|
||||||
|
} from "../types";
|
||||||
|
import { isBatch, isProxy, callsFromBatch, callFromProxy } from "./common";
|
||||||
|
|
||||||
|
// ========== Helpers ==========
|
||||||
|
|
||||||
|
function findCall(
|
||||||
|
extrinsic: SubstrateExtrinsic,
|
||||||
|
module: string,
|
||||||
|
method: string,
|
||||||
|
): any | null {
|
||||||
|
const topCall = extrinsic.extrinsic.method;
|
||||||
|
|
||||||
|
if (topCall.section === module && topCall.method === method) {
|
||||||
|
return topCall;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBatch(topCall)) {
|
||||||
|
for (const inner of callsFromBatch(topCall)) {
|
||||||
|
if (inner.section === module && inner.method === method) {
|
||||||
|
return inner;
|
||||||
|
}
|
||||||
|
// Nested batch/proxy
|
||||||
|
if (isBatch(inner)) {
|
||||||
|
for (const nested of callsFromBatch(inner)) {
|
||||||
|
if (nested.section === module && nested.method === method) {
|
||||||
|
return nested;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isProxy(inner)) {
|
||||||
|
const proxied = callFromProxy(inner);
|
||||||
|
if (proxied.section === module && proxied.method === method) {
|
||||||
|
return proxied;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isProxy(topCall)) {
|
||||||
|
const inner = callFromProxy(topCall);
|
||||||
|
if (inner.section === module && inner.method === method) {
|
||||||
|
return inner;
|
||||||
|
}
|
||||||
|
if (isBatch(inner)) {
|
||||||
|
for (const nested of callsFromBatch(inner)) {
|
||||||
|
if (nested.section === module && nested.method === method) {
|
||||||
|
return nested;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSigner(extrinsic: SubstrateExtrinsic): string {
|
||||||
|
return extrinsic.extrinsic.signer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBlockNum(extrinsic: SubstrateExtrinsic): number {
|
||||||
|
return extrinsic.block.block.header.number.toNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTimestamp(extrinsic: SubstrateExtrinsic): bigint {
|
||||||
|
const ts = extrinsic.block.timestamp;
|
||||||
|
return BigInt(Math.round(ts ? ts.getTime() / 1000 : 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAccountVote(voteData: any): {
|
||||||
|
standardVote?: any;
|
||||||
|
splitVote?: any;
|
||||||
|
splitAbstainVote?: any;
|
||||||
|
} {
|
||||||
|
if (voteData.isStandard) {
|
||||||
|
const std = voteData.asStandard;
|
||||||
|
return {
|
||||||
|
standardVote: {
|
||||||
|
aye: std.vote.isAye,
|
||||||
|
vote: {
|
||||||
|
amount: std.balance.toString(),
|
||||||
|
conviction: std.vote.conviction.toString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (voteData.isSplit) {
|
||||||
|
const s = voteData.asSplit;
|
||||||
|
return {
|
||||||
|
splitVote: {
|
||||||
|
ayeAmount: s.aye.toString(),
|
||||||
|
nayAmount: s.nay.toString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (voteData.isSplitAbstain) {
|
||||||
|
const sa = voteData.asSplitAbstain;
|
||||||
|
return {
|
||||||
|
splitAbstainVote: {
|
||||||
|
ayeAmount: sa.aye.toString(),
|
||||||
|
nayAmount: sa.nay.toString(),
|
||||||
|
abstainAmount: sa.abstain.toString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Delegation Propagation ==========
|
||||||
|
|
||||||
|
async function propagateVoteToDelegators(
|
||||||
|
delegateAddress: string,
|
||||||
|
cv: CastingVoting,
|
||||||
|
referendumId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const delegations = await Delegation.getByDelegateId(delegateAddress, { limit: 500 });
|
||||||
|
if (!delegations || delegations.length === 0) return;
|
||||||
|
|
||||||
|
const referendum = await Referendum.get(referendumId);
|
||||||
|
if (!referendum) return;
|
||||||
|
|
||||||
|
for (const d of delegations) {
|
||||||
|
if (d.trackId !== referendum.trackId) continue;
|
||||||
|
|
||||||
|
const dvId = `${cv.id}-${d.delegator}`;
|
||||||
|
const dv = DelegatorVoting.create({
|
||||||
|
id: dvId,
|
||||||
|
parentId: cv.id,
|
||||||
|
delegator: d.delegator,
|
||||||
|
vote: d.delegation,
|
||||||
|
});
|
||||||
|
await dv.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createDelegatorVotingsForNewDelegation(
|
||||||
|
delegation: Delegation,
|
||||||
|
): Promise<void> {
|
||||||
|
const cvList = await CastingVoting.getByVoter(delegation.delegateId, { limit: 500 });
|
||||||
|
if (!cvList || cvList.length === 0) return;
|
||||||
|
|
||||||
|
for (const cv of cvList) {
|
||||||
|
const ref = await Referendum.get(cv.referendumId);
|
||||||
|
if (!ref || ref.trackId !== delegation.trackId) continue;
|
||||||
|
|
||||||
|
const dvId = `${cv.id}-${delegation.delegator}`;
|
||||||
|
const dv = DelegatorVoting.create({
|
||||||
|
id: dvId,
|
||||||
|
parentId: cv.id,
|
||||||
|
delegator: delegation.delegator,
|
||||||
|
vote: delegation.delegation,
|
||||||
|
});
|
||||||
|
await dv.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeDelegatorVotings(
|
||||||
|
delegator: string,
|
||||||
|
delegateId: string,
|
||||||
|
trackId: number,
|
||||||
|
): Promise<void> {
|
||||||
|
const cvList = await CastingVoting.getByVoter(delegateId, { limit: 500 });
|
||||||
|
if (!cvList) return;
|
||||||
|
|
||||||
|
for (const cv of cvList) {
|
||||||
|
const ref = await Referendum.get(cv.referendumId);
|
||||||
|
if (!ref || ref.trackId !== trackId) continue;
|
||||||
|
|
||||||
|
const dvId = `${cv.id}-${delegator}`;
|
||||||
|
await store.remove("DelegatorVoting", dvId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateDelegateStats(
|
||||||
|
accountId: string,
|
||||||
|
delegationAmount: string,
|
||||||
|
isAdd: boolean,
|
||||||
|
): Promise<void> {
|
||||||
|
let delegate = await Delegate.get(accountId);
|
||||||
|
|
||||||
|
if (!delegate) {
|
||||||
|
if (!isAdd) return;
|
||||||
|
delegate = Delegate.create({
|
||||||
|
id: accountId,
|
||||||
|
accountId: accountId,
|
||||||
|
delegators: 0,
|
||||||
|
delegatorVotes: BigInt(0),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const amount = BigInt(delegationAmount || "0");
|
||||||
|
|
||||||
|
if (isAdd) {
|
||||||
|
delegate.delegators += 1;
|
||||||
|
delegate.delegatorVotes = (delegate.delegatorVotes || BigInt(0)) + amount;
|
||||||
|
} else {
|
||||||
|
delegate.delegators = Math.max(0, delegate.delegators - 1);
|
||||||
|
const newVotes = (delegate.delegatorVotes || BigInt(0)) - amount;
|
||||||
|
delegate.delegatorVotes = newVotes < BigInt(0) ? BigInt(0) : newVotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
await delegate.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Event Handlers ==========
|
||||||
|
|
||||||
|
export async function handleReferendumSubmitted(
|
||||||
|
event: SubstrateEvent,
|
||||||
|
): Promise<void> {
|
||||||
|
const {
|
||||||
|
event: {
|
||||||
|
data: [indexRaw, trackRaw],
|
||||||
|
},
|
||||||
|
} = event;
|
||||||
|
|
||||||
|
const refIndex = (indexRaw as any).toNumber();
|
||||||
|
const trackId = (trackRaw as any).toNumber();
|
||||||
|
const id = refIndex.toString();
|
||||||
|
|
||||||
|
let referendum = await Referendum.get(id);
|
||||||
|
if (!referendum) {
|
||||||
|
referendum = Referendum.create({ id, trackId });
|
||||||
|
} else {
|
||||||
|
referendum.trackId = trackId;
|
||||||
|
}
|
||||||
|
await referendum.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Call Handlers ==========
|
||||||
|
|
||||||
|
export async function handleVoteCall(
|
||||||
|
extrinsic: SubstrateExtrinsic,
|
||||||
|
): Promise<void> {
|
||||||
|
const call = findCall(extrinsic, "convictionVoting", "vote");
|
||||||
|
if (!call) return;
|
||||||
|
|
||||||
|
const pollIndex = (call.args[0] as any).toNumber();
|
||||||
|
const voteData = call.args[1];
|
||||||
|
const voter = getSigner(extrinsic);
|
||||||
|
const block = getBlockNum(extrinsic);
|
||||||
|
const ts = getTimestamp(extrinsic);
|
||||||
|
|
||||||
|
const referendumId = pollIndex.toString();
|
||||||
|
|
||||||
|
// Ensure referendum entity exists
|
||||||
|
let referendum = await Referendum.get(referendumId);
|
||||||
|
if (!referendum) {
|
||||||
|
referendum = Referendum.create({ id: referendumId, trackId: 0 });
|
||||||
|
await referendum.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse vote
|
||||||
|
const parsed = parseAccountVote(voteData);
|
||||||
|
|
||||||
|
// Create/update CastingVoting
|
||||||
|
const cvId = `${referendumId}-${voter}`;
|
||||||
|
let cv = await CastingVoting.get(cvId);
|
||||||
|
|
||||||
|
if (!cv) {
|
||||||
|
cv = CastingVoting.create({
|
||||||
|
id: cvId,
|
||||||
|
referendumId: referendumId,
|
||||||
|
voter: voter,
|
||||||
|
delegateId: voter,
|
||||||
|
standardVote: parsed.standardVote ?? undefined,
|
||||||
|
splitVote: parsed.splitVote ?? undefined,
|
||||||
|
splitAbstainVote: parsed.splitAbstainVote ?? undefined,
|
||||||
|
at: block,
|
||||||
|
timestamp: ts,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
cv.standardVote = parsed.standardVote ?? undefined;
|
||||||
|
cv.splitVote = parsed.splitVote ?? undefined;
|
||||||
|
cv.splitAbstainVote = parsed.splitAbstainVote ?? undefined;
|
||||||
|
cv.at = block;
|
||||||
|
cv.timestamp = ts;
|
||||||
|
}
|
||||||
|
await cv.save();
|
||||||
|
|
||||||
|
// If this voter is a delegate, track the vote and propagate
|
||||||
|
const delegate = await Delegate.get(voter);
|
||||||
|
if (delegate && delegate.delegators > 0) {
|
||||||
|
const dvoteId = `${voter}-${referendumId}`;
|
||||||
|
const dvote = DelegateVote.create({
|
||||||
|
id: dvoteId,
|
||||||
|
delegateId: voter,
|
||||||
|
at: block,
|
||||||
|
timestamp: ts,
|
||||||
|
});
|
||||||
|
await dvote.save();
|
||||||
|
|
||||||
|
await propagateVoteToDelegators(voter, cv, referendumId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleRemoveVoteCall(
|
||||||
|
extrinsic: SubstrateExtrinsic,
|
||||||
|
): Promise<void> {
|
||||||
|
const call = findCall(extrinsic, "convictionVoting", "removeVote");
|
||||||
|
if (!call) return;
|
||||||
|
|
||||||
|
const voter = getSigner(extrinsic);
|
||||||
|
// removeVote(class: Option<ClassOf>, index: PollIndexOf)
|
||||||
|
const pollIndex = (call.args[1] as any).toNumber();
|
||||||
|
const referendumId = pollIndex.toString();
|
||||||
|
const cvId = `${referendumId}-${voter}`;
|
||||||
|
|
||||||
|
// Remove child DelegatorVoting entries
|
||||||
|
const dvList = await DelegatorVoting.getByParentId(cvId, { limit: 500 });
|
||||||
|
if (dvList) {
|
||||||
|
for (const dv of dvList) {
|
||||||
|
await store.remove("DelegatorVoting", dv.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove DelegateVote
|
||||||
|
await store.remove("DelegateVote", `${voter}-${referendumId}`);
|
||||||
|
|
||||||
|
// Remove CastingVoting
|
||||||
|
await store.remove("CastingVoting", cvId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleDelegateCall(
|
||||||
|
extrinsic: SubstrateExtrinsic,
|
||||||
|
): Promise<void> {
|
||||||
|
const call = findCall(extrinsic, "convictionVoting", "delegate");
|
||||||
|
if (!call) return;
|
||||||
|
|
||||||
|
// delegate(class, to, conviction, balance)
|
||||||
|
const trackId = (call.args[0] as any).toNumber();
|
||||||
|
const target = (call.args[1] as any).toString();
|
||||||
|
const conviction = (call.args[2] as any).toString();
|
||||||
|
const balance = (call.args[3] as any).toString();
|
||||||
|
const delegator = getSigner(extrinsic);
|
||||||
|
|
||||||
|
const delegationId = `${delegator}-${trackId}`;
|
||||||
|
|
||||||
|
// Clean up old delegation on this track if exists
|
||||||
|
const oldDelegation = await Delegation.get(delegationId);
|
||||||
|
if (oldDelegation) {
|
||||||
|
await updateDelegateStats(
|
||||||
|
oldDelegation.delegateId,
|
||||||
|
oldDelegation.delegation?.amount || "0",
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
if (oldDelegation.delegateId !== target) {
|
||||||
|
await removeDelegatorVotings(
|
||||||
|
delegator,
|
||||||
|
oldDelegation.delegateId,
|
||||||
|
trackId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new delegation
|
||||||
|
const voteBalance = { amount: balance, conviction };
|
||||||
|
const delegation = Delegation.create({
|
||||||
|
id: delegationId,
|
||||||
|
delegateId: target,
|
||||||
|
delegator: delegator,
|
||||||
|
trackId: trackId,
|
||||||
|
delegation: voteBalance,
|
||||||
|
});
|
||||||
|
await delegation.save();
|
||||||
|
|
||||||
|
// Update delegate stats
|
||||||
|
await updateDelegateStats(target, balance, true);
|
||||||
|
|
||||||
|
// Create DelegatorVoting entries for existing votes by this delegate
|
||||||
|
await createDelegatorVotingsForNewDelegation(delegation);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleUndelegateCall(
|
||||||
|
extrinsic: SubstrateExtrinsic,
|
||||||
|
): Promise<void> {
|
||||||
|
const call = findCall(extrinsic, "convictionVoting", "undelegate");
|
||||||
|
if (!call) return;
|
||||||
|
|
||||||
|
// undelegate(class)
|
||||||
|
const trackId = (call.args[0] as any).toNumber();
|
||||||
|
const delegator = getSigner(extrinsic);
|
||||||
|
|
||||||
|
const delegationId = `${delegator}-${trackId}`;
|
||||||
|
const delegation = await Delegation.get(delegationId);
|
||||||
|
if (!delegation) return;
|
||||||
|
|
||||||
|
const target = delegation.delegateId;
|
||||||
|
|
||||||
|
// Remove DelegatorVoting entries
|
||||||
|
await removeDelegatorVotings(delegator, target, trackId);
|
||||||
|
|
||||||
|
// Update delegate stats
|
||||||
|
await updateDelegateStats(
|
||||||
|
target,
|
||||||
|
delegation.delegation?.amount || "0",
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Remove delegation
|
||||||
|
await store.remove("Delegation", delegationId);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user