mirror of
https://github.com/luxfi/graph.git
synced 2026-09-24 00:26:07 +00:00
feat: 8 new graph resolvers — governance, DAO, treasury, liquid, DID, prediction, securities, derivatives (84 tests)
This commit is contained in:
+2
-2
@@ -1,12 +1,12 @@
|
||||
# Stage 1: Build
|
||||
FROM golang:1.26-alpine AS build
|
||||
RUN apk add --no-cache gcc musl-dev
|
||||
RUN apk add --no-cache gcc musl-dev sqlite-dev
|
||||
WORKDIR /src
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
ARG VERSION=dev
|
||||
RUN CGO_ENABLED=1 GOOS=linux go build \
|
||||
RUN CGO_ENABLED=1 CGO_CFLAGS="-D_LARGEFILE64_SOURCE" GOOS=linux go build \
|
||||
-ldflags "-s -w -X main.version=${VERSION}" \
|
||||
-o /graph ./cmd/graph
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
steps:
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: ['build', '-t', 'us-docker.pkg.dev/liquidity-registry/liquidityio/graph:${_TAG}', '.']
|
||||
images:
|
||||
- 'us-docker.pkg.dev/liquidity-registry/liquidityio/graph:${_TAG}'
|
||||
substitutions:
|
||||
_TAG: dev
|
||||
options:
|
||||
machineType: E2_HIGHCPU_8
|
||||
timeout: 600s
|
||||
@@ -0,0 +1,779 @@
|
||||
//go:build e2e
|
||||
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/graph/engine"
|
||||
"github.com/luxfi/graph/storage"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Governance resolvers — proposals, votes, delegations
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestResolver_Governance_Proposal(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("Proposal", "prop-1", map[string]interface{}{
|
||||
"id": "prop-1", "title": "Increase validator cap", "proposer": "0xabc", "status": "active",
|
||||
"forVotes": "1500000", "againstVotes": "200000",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ proposal(id: "prop-1") { id title proposer status } }`)
|
||||
p := requireMap(t, data, "proposal")
|
||||
if p["status"] != "active" {
|
||||
t.Errorf("status: got %v", p["status"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Governance_Proposals(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("Proposal", "prop-1", map[string]interface{}{
|
||||
"id": "prop-1", "title": "Increase fee", "status": "executed",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ proposals(first: 10) { id } }`)
|
||||
requireList(t, data, "proposals", 1)
|
||||
}
|
||||
|
||||
func TestResolver_Governance_Vote(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("Vote", "vote-1", map[string]interface{}{
|
||||
"id": "vote-1", "proposal": "prop-1", "voter": "0xdef", "support": true, "weight": "500000",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ vote(id: "vote-1") { id voter support weight } }`)
|
||||
v := requireMap(t, data, "vote")
|
||||
if v["voter"] != "0xdef" {
|
||||
t.Errorf("voter: got %v", v["voter"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Governance_Votes(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("Vote", "vote-1", map[string]interface{}{
|
||||
"id": "vote-1", "proposal": "prop-1", "voter": "0xdef",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ votes(first: 10) { id } }`)
|
||||
requireList(t, data, "votes", 1)
|
||||
}
|
||||
|
||||
func TestResolver_Governance_Delegation(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("Delegation", "del-1", map[string]interface{}{
|
||||
"id": "del-1", "delegator": "0xaaa", "delegate": "0xbbb", "amount": "100000",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ delegation(id: "del-1") { id delegator delegate amount } }`)
|
||||
d := requireMap(t, data, "delegation")
|
||||
if d["delegator"] != "0xaaa" {
|
||||
t.Errorf("delegator: got %v", d["delegator"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Governance_Delegations(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("Delegation", "del-1", map[string]interface{}{
|
||||
"id": "del-1", "delegator": "0xaaa", "delegate": "0xbbb",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ delegations(first: 10) { id } }`)
|
||||
requireList(t, data, "delegations", 1)
|
||||
}
|
||||
|
||||
func TestResolver_Governance_MissingID(t *testing.T) {
|
||||
eng, _ := newTestEngine(t)
|
||||
msg := execErr(t, eng, `{ proposal { id } }`)
|
||||
if msg != "proposal requires id" {
|
||||
t.Errorf("unexpected error: %s", msg)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DAO resolvers — proposals, members, treasury
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestResolver_DAO_Proposal(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("DAOProposal", "dp-1", map[string]interface{}{
|
||||
"id": "dp-1", "title": "Fund research", "author": "0xfed", "status": "queued",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ daoProposal(id: "dp-1") { id title status } }`)
|
||||
p := requireMap(t, data, "daoProposal")
|
||||
if p["status"] != "queued" {
|
||||
t.Errorf("status: got %v", p["status"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_DAO_Proposals(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("DAOProposal", "dp-1", map[string]interface{}{
|
||||
"id": "dp-1", "title": "Fund research",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ daoProposals(first: 10) { id } }`)
|
||||
requireList(t, data, "daoProposals", 1)
|
||||
}
|
||||
|
||||
func TestResolver_DAO_Member(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("DAOMember", "dm-1", map[string]interface{}{
|
||||
"id": "dm-1", "address": "0x111", "votingPower": "50000", "joinedAt": 1700000000,
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ daoMember(id: "dm-1") { id address votingPower } }`)
|
||||
m := requireMap(t, data, "daoMember")
|
||||
if m["address"] != "0x111" {
|
||||
t.Errorf("address: got %v", m["address"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_DAO_Members(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("DAOMember", "dm-1", map[string]interface{}{"id": "dm-1", "address": "0x111"})
|
||||
|
||||
data := execOK(t, eng, `{ daoMembers(first: 10) { id } }`)
|
||||
requireList(t, data, "daoMembers", 1)
|
||||
}
|
||||
|
||||
func TestResolver_DAO_Treasury(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("DAOTreasury", "dt-1", map[string]interface{}{
|
||||
"id": "dt-1", "balance": "5000000", "token": "LUX",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ daoTreasury(id: "dt-1") { id balance token } }`)
|
||||
tr := requireMap(t, data, "daoTreasury")
|
||||
if tr["token"] != "LUX" {
|
||||
t.Errorf("token: got %v", tr["token"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_DAO_Treasuries(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("DAOTreasury", "dt-1", map[string]interface{}{"id": "dt-1", "balance": "100"})
|
||||
|
||||
data := execOK(t, eng, `{ daoTreasuries(first: 10) { id } }`)
|
||||
requireList(t, data, "daoTreasuries", 1)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Treasury resolvers — deposits, withdrawals, allocations, budgets
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestResolver_Treasury_Deposit(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("TreasuryDeposit", "td-1", map[string]interface{}{
|
||||
"id": "td-1", "from": "0xabc", "amount": "10000", "token": "LUX", "timestamp": 1700000000,
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ treasuryDeposit(id: "td-1") { id from amount token } }`)
|
||||
d := requireMap(t, data, "treasuryDeposit")
|
||||
if d["amount"] != "10000" {
|
||||
t.Errorf("amount: got %v", d["amount"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Treasury_Deposits(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("TreasuryDeposit", "td-1", map[string]interface{}{"id": "td-1", "amount": "10000"})
|
||||
|
||||
data := execOK(t, eng, `{ treasuryDeposits(first: 10) { id } }`)
|
||||
requireList(t, data, "treasuryDeposits", 1)
|
||||
}
|
||||
|
||||
func TestResolver_Treasury_Withdrawal(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("TreasuryWithdrawal", "tw-1", map[string]interface{}{
|
||||
"id": "tw-1", "to": "0xdef", "amount": "5000", "reason": "dev grant",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ treasuryWithdrawal(id: "tw-1") { id to amount reason } }`)
|
||||
w := requireMap(t, data, "treasuryWithdrawal")
|
||||
if w["reason"] != "dev grant" {
|
||||
t.Errorf("reason: got %v", w["reason"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Treasury_Withdrawals(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("TreasuryWithdrawal", "tw-1", map[string]interface{}{"id": "tw-1", "amount": "5000"})
|
||||
|
||||
data := execOK(t, eng, `{ treasuryWithdrawals(first: 10) { id } }`)
|
||||
requireList(t, data, "treasuryWithdrawals", 1)
|
||||
}
|
||||
|
||||
func TestResolver_Treasury_Allocation(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("TreasuryAllocation", "ta-1", map[string]interface{}{
|
||||
"id": "ta-1", "recipient": "0x999", "amount": "25000", "category": "development",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ treasuryAllocation(id: "ta-1") { id recipient amount category } }`)
|
||||
a := requireMap(t, data, "treasuryAllocation")
|
||||
if a["category"] != "development" {
|
||||
t.Errorf("category: got %v", a["category"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Treasury_Allocations(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("TreasuryAllocation", "ta-1", map[string]interface{}{"id": "ta-1", "amount": "25000"})
|
||||
|
||||
data := execOK(t, eng, `{ treasuryAllocations(first: 10) { id } }`)
|
||||
requireList(t, data, "treasuryAllocations", 1)
|
||||
}
|
||||
|
||||
func TestResolver_Treasury_Budget(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("TreasuryBudget", "tb-1", map[string]interface{}{
|
||||
"id": "tb-1", "period": "Q1-2026", "total": "500000", "spent": "120000",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ treasuryBudget(id: "tb-1") { id period total spent } }`)
|
||||
b := requireMap(t, data, "treasuryBudget")
|
||||
if b["period"] != "Q1-2026" {
|
||||
t.Errorf("period: got %v", b["period"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Treasury_Budgets(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("TreasuryBudget", "tb-1", map[string]interface{}{"id": "tb-1", "total": "500000"})
|
||||
|
||||
data := execOK(t, eng, `{ treasuryBudgets(first: 10) { id } }`)
|
||||
requireList(t, data, "treasuryBudgets", 1)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Liquid staking resolvers — stakes, unstakes, rewards, validators
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestResolver_Liquid_Stake(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("LiquidStake", "ls-1", map[string]interface{}{
|
||||
"id": "ls-1", "staker": "0xabc", "amount": "100000", "shares": "95000", "validator": "NodeID-abc",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ liquidStake(id: "ls-1") { id staker amount shares } }`)
|
||||
s := requireMap(t, data, "liquidStake")
|
||||
if s["staker"] != "0xabc" {
|
||||
t.Errorf("staker: got %v", s["staker"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Liquid_Stakes(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("LiquidStake", "ls-1", map[string]interface{}{"id": "ls-1", "amount": "100000"})
|
||||
|
||||
data := execOK(t, eng, `{ liquidStakes(first: 10) { id } }`)
|
||||
requireList(t, data, "liquidStakes", 1)
|
||||
}
|
||||
|
||||
func TestResolver_Liquid_Unstake(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("LiquidUnstake", "lu-1", map[string]interface{}{
|
||||
"id": "lu-1", "staker": "0xdef", "shares": "50000", "amount": "52000", "status": "pending",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ liquidUnstake(id: "lu-1") { id staker shares status } }`)
|
||||
u := requireMap(t, data, "liquidUnstake")
|
||||
if u["status"] != "pending" {
|
||||
t.Errorf("status: got %v", u["status"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Liquid_Unstakes(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("LiquidUnstake", "lu-1", map[string]interface{}{"id": "lu-1", "shares": "50000"})
|
||||
|
||||
data := execOK(t, eng, `{ liquidUnstakes(first: 10) { id } }`)
|
||||
requireList(t, data, "liquidUnstakes", 1)
|
||||
}
|
||||
|
||||
func TestResolver_Liquid_Reward(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("LiquidReward", "lr-1", map[string]interface{}{
|
||||
"id": "lr-1", "epoch": 42, "amount": "1500", "validator": "NodeID-xyz",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ liquidReward(id: "lr-1") { id epoch amount } }`)
|
||||
r := requireMap(t, data, "liquidReward")
|
||||
if fmt.Sprint(r["epoch"]) != "42" {
|
||||
t.Errorf("epoch: got %v", r["epoch"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Liquid_Rewards(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("LiquidReward", "lr-1", map[string]interface{}{"id": "lr-1", "amount": "1500"})
|
||||
|
||||
data := execOK(t, eng, `{ liquidRewards(first: 10) { id } }`)
|
||||
requireList(t, data, "liquidRewards", 1)
|
||||
}
|
||||
|
||||
func TestResolver_Liquid_Validator(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("LiquidValidator", "lv-1", map[string]interface{}{
|
||||
"id": "lv-1", "nodeID": "NodeID-abc", "totalStaked": "500000", "commission": "5",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ liquidValidator(id: "lv-1") { id nodeID totalStaked commission } }`)
|
||||
v := requireMap(t, data, "liquidValidator")
|
||||
if v["nodeID"] != "NodeID-abc" {
|
||||
t.Errorf("nodeID: got %v", v["nodeID"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Liquid_Validators(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("LiquidValidator", "lv-1", map[string]interface{}{"id": "lv-1", "nodeID": "NodeID-abc"})
|
||||
|
||||
data := execOK(t, eng, `{ liquidValidators(first: 10) { id } }`)
|
||||
requireList(t, data, "liquidValidators", 1)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DID resolvers — documents, delegates, attributes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestResolver_DID_Document(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("DIDDocument", "did-1", map[string]interface{}{
|
||||
"id": "did-1", "controller": "0xabc", "created": 1700000000, "updated": 1700100000,
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ didDocument(id: "did-1") { id controller created } }`)
|
||||
d := requireMap(t, data, "didDocument")
|
||||
if d["controller"] != "0xabc" {
|
||||
t.Errorf("controller: got %v", d["controller"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_DID_Documents(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("DIDDocument", "did-1", map[string]interface{}{"id": "did-1", "controller": "0xabc"})
|
||||
|
||||
data := execOK(t, eng, `{ didDocuments(first: 10) { id } }`)
|
||||
requireList(t, data, "didDocuments", 1)
|
||||
}
|
||||
|
||||
func TestResolver_DID_Delegate(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("DIDDelegate", "dd-1", map[string]interface{}{
|
||||
"id": "dd-1", "identity": "did-1", "delegateType": "veriKey", "delegate": "0xdef",
|
||||
"validity": 86400,
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ didDelegate(id: "dd-1") { id identity delegateType delegate } }`)
|
||||
d := requireMap(t, data, "didDelegate")
|
||||
if d["delegateType"] != "veriKey" {
|
||||
t.Errorf("delegateType: got %v", d["delegateType"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_DID_Delegates(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("DIDDelegate", "dd-1", map[string]interface{}{"id": "dd-1", "delegate": "0xdef"})
|
||||
|
||||
data := execOK(t, eng, `{ didDelegates(first: 10) { id } }`)
|
||||
requireList(t, data, "didDelegates", 1)
|
||||
}
|
||||
|
||||
func TestResolver_DID_Attribute(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("DIDAttribute", "da-1", map[string]interface{}{
|
||||
"id": "da-1", "identity": "did-1", "name": "did/svc/hub", "value": "https://hub.example.com",
|
||||
"validity": 172800,
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ didAttribute(id: "da-1") { id identity name value } }`)
|
||||
a := requireMap(t, data, "didAttribute")
|
||||
if a["name"] != "did/svc/hub" {
|
||||
t.Errorf("name: got %v", a["name"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_DID_Attributes(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("DIDAttribute", "da-1", map[string]interface{}{"id": "da-1", "name": "did/svc/hub"})
|
||||
|
||||
data := execOK(t, eng, `{ didAttributes(first: 10) { id } }`)
|
||||
requireList(t, data, "didAttributes", 1)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Prediction market resolvers — markets, positions, resolutions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestResolver_Prediction_Market(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("PredictionMarket", "pm-1", map[string]interface{}{
|
||||
"id": "pm-1", "question": "LUX > $10 by Q2?", "creator": "0xabc",
|
||||
"totalStaked": "50000", "status": "open", "expiresAt": 1730000000,
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ predictionMarket(id: "pm-1") { id question status totalStaked } }`)
|
||||
m := requireMap(t, data, "predictionMarket")
|
||||
if m["status"] != "open" {
|
||||
t.Errorf("status: got %v", m["status"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Prediction_Markets(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("PredictionMarket", "pm-1", map[string]interface{}{"id": "pm-1", "status": "open"})
|
||||
|
||||
data := execOK(t, eng, `{ predictionMarkets(first: 10) { id } }`)
|
||||
requireList(t, data, "predictionMarkets", 1)
|
||||
}
|
||||
|
||||
func TestResolver_Prediction_Position(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("PredictionPosition", "pp-1", map[string]interface{}{
|
||||
"id": "pp-1", "market": "pm-1", "trader": "0xdef", "outcome": "yes", "amount": "1000",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ predictionPosition(id: "pp-1") { id trader outcome amount } }`)
|
||||
p := requireMap(t, data, "predictionPosition")
|
||||
if p["outcome"] != "yes" {
|
||||
t.Errorf("outcome: got %v", p["outcome"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Prediction_Positions(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("PredictionPosition", "pp-1", map[string]interface{}{"id": "pp-1", "outcome": "yes"})
|
||||
|
||||
data := execOK(t, eng, `{ predictionPositions(first: 10) { id } }`)
|
||||
requireList(t, data, "predictionPositions", 1)
|
||||
}
|
||||
|
||||
func TestResolver_Prediction_Resolution(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("PredictionResolution", "pr-1", map[string]interface{}{
|
||||
"id": "pr-1", "market": "pm-1", "outcome": "yes", "resolver": "0xfed", "timestamp": 1730000000,
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ predictionResolution(id: "pr-1") { id outcome resolver } }`)
|
||||
r := requireMap(t, data, "predictionResolution")
|
||||
if r["outcome"] != "yes" {
|
||||
t.Errorf("outcome: got %v", r["outcome"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Prediction_Resolutions(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("PredictionResolution", "pr-1", map[string]interface{}{"id": "pr-1", "outcome": "yes"})
|
||||
|
||||
data := execOK(t, eng, `{ predictionResolutions(first: 10) { id } }`)
|
||||
requireList(t, data, "predictionResolutions", 1)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Securities resolvers — issuances, transfers, dividends, compliance
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestResolver_Securities_Issuance(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("SecurityIssuance", "si-1", map[string]interface{}{
|
||||
"id": "si-1", "issuer": "0xabc", "symbol": "LUXS", "totalSupply": "1000000",
|
||||
"standard": "ERC-1400",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ securityIssuance(id: "si-1") { id issuer symbol totalSupply } }`)
|
||||
s := requireMap(t, data, "securityIssuance")
|
||||
if s["symbol"] != "LUXS" {
|
||||
t.Errorf("symbol: got %v", s["symbol"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Securities_Issuances(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("SecurityIssuance", "si-1", map[string]interface{}{"id": "si-1", "symbol": "LUXS"})
|
||||
|
||||
data := execOK(t, eng, `{ securityIssuances(first: 10) { id } }`)
|
||||
requireList(t, data, "securityIssuances", 1)
|
||||
}
|
||||
|
||||
func TestResolver_Securities_Transfer(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("SecurityTransfer", "st-1", map[string]interface{}{
|
||||
"id": "st-1", "from": "0xaaa", "to": "0xbbb", "amount": "500", "token": "LUXS",
|
||||
"partition": "default",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ securityTransfer(id: "st-1") { id from to amount } }`)
|
||||
tr := requireMap(t, data, "securityTransfer")
|
||||
if tr["from"] != "0xaaa" {
|
||||
t.Errorf("from: got %v", tr["from"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Securities_Transfers(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("SecurityTransfer", "st-1", map[string]interface{}{"id": "st-1", "amount": "500"})
|
||||
|
||||
data := execOK(t, eng, `{ securityTransfers(first: 10) { id } }`)
|
||||
requireList(t, data, "securityTransfers", 1)
|
||||
}
|
||||
|
||||
func TestResolver_Securities_Dividend(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("SecurityDividend", "sd-1", map[string]interface{}{
|
||||
"id": "sd-1", "token": "LUXS", "amountPerShare": "0.05", "recordDate": 1700000000,
|
||||
"paymentDate": 1700100000,
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ securityDividend(id: "sd-1") { id token amountPerShare } }`)
|
||||
d := requireMap(t, data, "securityDividend")
|
||||
if d["amountPerShare"] != "0.05" {
|
||||
t.Errorf("amountPerShare: got %v", d["amountPerShare"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Securities_Dividends(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("SecurityDividend", "sd-1", map[string]interface{}{"id": "sd-1", "token": "LUXS"})
|
||||
|
||||
data := execOK(t, eng, `{ securityDividends(first: 10) { id } }`)
|
||||
requireList(t, data, "securityDividends", 1)
|
||||
}
|
||||
|
||||
func TestResolver_Securities_Compliance(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("SecurityCompliance", "sc-1", map[string]interface{}{
|
||||
"id": "sc-1", "investor": "0xabc", "kycStatus": "approved", "accredited": true,
|
||||
"jurisdiction": "US",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ securityCompliance(id: "sc-1") { id investor kycStatus jurisdiction } }`)
|
||||
c := requireMap(t, data, "securityCompliance")
|
||||
if c["kycStatus"] != "approved" {
|
||||
t.Errorf("kycStatus: got %v", c["kycStatus"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Securities_Compliances(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("SecurityCompliance", "sc-1", map[string]interface{}{"id": "sc-1", "kycStatus": "approved"})
|
||||
|
||||
data := execOK(t, eng, `{ securityCompliances(first: 10) { id } }`)
|
||||
requireList(t, data, "securityCompliances", 1)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Derivatives resolvers — contracts, settlements, exercises
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestResolver_Derivatives_Contract(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("DerivativeContract", "dc-1", map[string]interface{}{
|
||||
"id": "dc-1", "underlying": "LUX", "contractType": "future", "strike": "10.00",
|
||||
"expiry": 1735000000, "notional": "100000",
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ derivativeContract(id: "dc-1") { id underlying contractType strike } }`)
|
||||
c := requireMap(t, data, "derivativeContract")
|
||||
if c["contractType"] != "future" {
|
||||
t.Errorf("contractType: got %v", c["contractType"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Derivatives_Contracts(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("DerivativeContract", "dc-1", map[string]interface{}{"id": "dc-1", "contractType": "future"})
|
||||
|
||||
data := execOK(t, eng, `{ derivativeContracts(first: 10) { id } }`)
|
||||
requireList(t, data, "derivativeContracts", 1)
|
||||
}
|
||||
|
||||
func TestResolver_Derivatives_Settlement(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("DerivativeSettlement", "ds-1", map[string]interface{}{
|
||||
"id": "ds-1", "contract": "dc-1", "settledPrice": "11.50", "pnl": "1500",
|
||||
"settledAt": 1735000000,
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ derivativeSettlement(id: "ds-1") { id settledPrice pnl } }`)
|
||||
s := requireMap(t, data, "derivativeSettlement")
|
||||
if s["settledPrice"] != "11.50" {
|
||||
t.Errorf("settledPrice: got %v", s["settledPrice"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Derivatives_Settlements(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("DerivativeSettlement", "ds-1", map[string]interface{}{"id": "ds-1", "pnl": "1500"})
|
||||
|
||||
data := execOK(t, eng, `{ derivativeSettlements(first: 10) { id } }`)
|
||||
requireList(t, data, "derivativeSettlements", 1)
|
||||
}
|
||||
|
||||
func TestResolver_Derivatives_Exercise(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("DerivativeExercise", "de-1", map[string]interface{}{
|
||||
"id": "de-1", "contract": "dc-1", "exerciser": "0xabc", "amount": "50000",
|
||||
"exercisedAt": 1734500000,
|
||||
})
|
||||
|
||||
data := execOK(t, eng, `{ derivativeExercise(id: "de-1") { id exerciser amount } }`)
|
||||
e := requireMap(t, data, "derivativeExercise")
|
||||
if e["exerciser"] != "0xabc" {
|
||||
t.Errorf("exerciser: got %v", e["exerciser"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolver_Derivatives_Exercises(t *testing.T) {
|
||||
eng, store := newTestEngine(t)
|
||||
store.SetEntity("DerivativeExercise", "de-1", map[string]interface{}{"id": "de-1", "amount": "50000"})
|
||||
|
||||
data := execOK(t, eng, `{ derivativeExercises(first: 10) { id } }`)
|
||||
requireList(t, data, "derivativeExercises", 1)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stats resolvers — verify all 8 stats endpoints resolve without error
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestResolver_NewStats_AllNil(t *testing.T) {
|
||||
eng, _ := newTestEngine(t)
|
||||
|
||||
statsFields := []struct {
|
||||
name string
|
||||
query string
|
||||
}{
|
||||
{"governanceStats", `{ governanceStats { id } }`},
|
||||
{"daoStats", `{ daoStats { id } }`},
|
||||
{"treasuryStats", `{ treasuryStats { id } }`},
|
||||
{"liquidStats", `{ liquidStats { id } }`},
|
||||
{"didStats", `{ didStats { id } }`},
|
||||
{"predictionStats", `{ predictionStats { id } }`},
|
||||
{"securityStats", `{ securityStats { id } }`},
|
||||
{"derivativeStats", `{ derivativeStats { id } }`},
|
||||
}
|
||||
|
||||
for _, tt := range statsFields {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
data := execOK(t, eng, tt.query)
|
||||
// nil is fine for unseeded stats
|
||||
if data[tt.name] != nil {
|
||||
t.Errorf("expected nil for unseeded %s, got %v", tt.name, data[tt.name])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// LoadBuiltin — verify each new schema loads by name
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestResolver_LoadBuiltin_NewSchemas(t *testing.T) {
|
||||
store, err := storage.New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := store.Init(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
names := []string{
|
||||
"governance", "dao", "treasury", "liquid", "liquid-staking",
|
||||
"did", "did-registry", "prediction", "prediction-market",
|
||||
"securities", "security-token", "derivatives", "futures", "options",
|
||||
}
|
||||
for _, name := range names {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
eng := engine.New(store, nil)
|
||||
if err := eng.LoadBuiltin(name); err != nil {
|
||||
t.Fatalf("LoadBuiltin(%q) failed: %v", name, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Missing ID errors — verify all new single-entity resolvers require id
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestResolver_NewResolvers_MissingID(t *testing.T) {
|
||||
eng, _ := newTestEngine(t)
|
||||
|
||||
cases := []struct {
|
||||
query string
|
||||
want string
|
||||
}{
|
||||
{`{ proposal { id } }`, "proposal requires id"},
|
||||
{`{ vote { id } }`, "vote requires id"},
|
||||
{`{ delegation { id } }`, "delegation requires id"},
|
||||
{`{ daoProposal { id } }`, "daoProposal requires id"},
|
||||
{`{ daoMember { id } }`, "daoMember requires id"},
|
||||
{`{ daoTreasury { id } }`, "daoTreasury requires id"},
|
||||
{`{ treasuryDeposit { id } }`, "treasuryDeposit requires id"},
|
||||
{`{ treasuryWithdrawal { id } }`, "treasuryWithdrawal requires id"},
|
||||
{`{ treasuryAllocation { id } }`, "treasuryAllocation requires id"},
|
||||
{`{ treasuryBudget { id } }`, "treasuryBudget requires id"},
|
||||
{`{ liquidStake { id } }`, "liquidStake requires id"},
|
||||
{`{ liquidUnstake { id } }`, "liquidUnstake requires id"},
|
||||
{`{ liquidReward { id } }`, "liquidReward requires id"},
|
||||
{`{ liquidValidator { id } }`, "liquidValidator requires id"},
|
||||
{`{ didDocument { id } }`, "didDocument requires id"},
|
||||
{`{ didDelegate { id } }`, "didDelegate requires id"},
|
||||
{`{ didAttribute { id } }`, "didAttribute requires id"},
|
||||
{`{ predictionMarket { id } }`, "predictionMarket requires id"},
|
||||
{`{ predictionPosition { id } }`, "predictionPosition requires id"},
|
||||
{`{ predictionResolution { id } }`, "predictionResolution requires id"},
|
||||
{`{ securityIssuance { id } }`, "securityIssuance requires id"},
|
||||
{`{ securityTransfer { id } }`, "securityTransfer requires id"},
|
||||
{`{ securityDividend { id } }`, "securityDividend requires id"},
|
||||
{`{ securityCompliance { id } }`, "securityCompliance requires id"},
|
||||
{`{ derivativeContract { id } }`, "derivativeContract requires id"},
|
||||
{`{ derivativeSettlement { id } }`, "derivativeSettlement requires id"},
|
||||
{`{ derivativeExercise { id } }`, "derivativeExercise requires id"},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.want, func(t *testing.T) {
|
||||
msg := execErr(t, eng, tt.query)
|
||||
if msg != tt.want {
|
||||
t.Errorf("got %q, want %q", msg, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Nonexistent ID — verify new single-entity resolvers return nil, not error
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestResolver_NewResolvers_NonexistentID(t *testing.T) {
|
||||
eng, _ := newTestEngine(t)
|
||||
|
||||
queries := []struct {
|
||||
field string
|
||||
query string
|
||||
}{
|
||||
{"proposal", `{ proposal(id: "nonexistent") { id } }`},
|
||||
{"daoProposal", `{ daoProposal(id: "nonexistent") { id } }`},
|
||||
{"treasuryDeposit", `{ treasuryDeposit(id: "nonexistent") { id } }`},
|
||||
{"liquidStake", `{ liquidStake(id: "nonexistent") { id } }`},
|
||||
{"didDocument", `{ didDocument(id: "nonexistent") { id } }`},
|
||||
{"predictionMarket", `{ predictionMarket(id: "nonexistent") { id } }`},
|
||||
{"securityIssuance", `{ securityIssuance(id: "nonexistent") { id } }`},
|
||||
{"derivativeContract", `{ derivativeContract(id: "nonexistent") { id } }`},
|
||||
}
|
||||
|
||||
for _, tt := range queries {
|
||||
t.Run(tt.field, func(t *testing.T) {
|
||||
data := execOK(t, eng, tt.query)
|
||||
if data[tt.field] != nil {
|
||||
t.Errorf("expected nil for nonexistent %s, got %v", tt.field, data[tt.field])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -15,18 +15,26 @@ import (
|
||||
|
||||
"github.com/luxfi/graph/resolvers/ai"
|
||||
"github.com/luxfi/graph/resolvers/bridge"
|
||||
"github.com/luxfi/graph/resolvers/dao"
|
||||
"github.com/luxfi/graph/resolvers/derivatives"
|
||||
"github.com/luxfi/graph/resolvers/dex"
|
||||
"github.com/luxfi/graph/resolvers/did"
|
||||
"github.com/luxfi/graph/resolvers/exchange"
|
||||
"github.com/luxfi/graph/resolvers/fhe"
|
||||
"github.com/luxfi/graph/resolvers/governance"
|
||||
"github.com/luxfi/graph/resolvers/identity"
|
||||
"github.com/luxfi/graph/resolvers/key"
|
||||
"github.com/luxfi/graph/resolvers/liquid"
|
||||
"github.com/luxfi/graph/resolvers/oracle"
|
||||
"github.com/luxfi/graph/resolvers/platform"
|
||||
"github.com/luxfi/graph/resolvers/precompile"
|
||||
"github.com/luxfi/graph/resolvers/prediction"
|
||||
"github.com/luxfi/graph/resolvers/privacy"
|
||||
"github.com/luxfi/graph/resolvers/quantum"
|
||||
"github.com/luxfi/graph/resolvers/relay"
|
||||
"github.com/luxfi/graph/resolvers/securities"
|
||||
"github.com/luxfi/graph/resolvers/servicenode"
|
||||
"github.com/luxfi/graph/resolvers/treasury"
|
||||
"github.com/luxfi/graph/storage"
|
||||
)
|
||||
|
||||
@@ -157,6 +165,30 @@ func (e *Engine) LoadBuiltin(name string) error {
|
||||
case "precompile", "precompiles":
|
||||
e.registerChain(precompile.Register)
|
||||
return nil
|
||||
case "governance":
|
||||
e.registerChain(governance.Register)
|
||||
return nil
|
||||
case "dao":
|
||||
e.registerChain(dao.Register)
|
||||
return nil
|
||||
case "treasury":
|
||||
e.registerChain(treasury.Register)
|
||||
return nil
|
||||
case "liquid", "liquid-staking":
|
||||
e.registerChain(liquid.Register)
|
||||
return nil
|
||||
case "did", "did-registry":
|
||||
e.registerChain(did.Register)
|
||||
return nil
|
||||
case "prediction", "prediction-market":
|
||||
e.registerChain(prediction.Register)
|
||||
return nil
|
||||
case "securities", "security-token":
|
||||
e.registerChain(securities.Register)
|
||||
return nil
|
||||
case "derivatives", "futures", "options":
|
||||
e.registerChain(derivatives.Register)
|
||||
return nil
|
||||
case "all":
|
||||
e.registerAMMResolvers()
|
||||
e.registerV4Resolvers()
|
||||
@@ -176,6 +208,14 @@ func (e *Engine) LoadBuiltin(name string) error {
|
||||
e.registerChain(relay.Register)
|
||||
e.registerChain(servicenode.Register)
|
||||
e.registerChain(precompile.Register)
|
||||
e.registerChain(governance.Register)
|
||||
e.registerChain(dao.Register)
|
||||
e.registerChain(treasury.Register)
|
||||
e.registerChain(liquid.Register)
|
||||
e.registerChain(did.Register)
|
||||
e.registerChain(prediction.Register)
|
||||
e.registerChain(securities.Register)
|
||||
e.registerChain(derivatives.Register)
|
||||
return nil
|
||||
case "erc20":
|
||||
e.registerERC20Resolvers()
|
||||
|
||||
+474
@@ -0,0 +1,474 @@
|
||||
package defi
|
||||
|
||||
import "math/big"
|
||||
|
||||
// DAO event topic0 signatures (keccak256 hashes).
|
||||
// Source: contracts/governance/GaugeController.sol, vLUX.sol, Karma.sol, DLUX.sol
|
||||
const (
|
||||
// GaugeController events
|
||||
// GaugeAdded(uint256 indexed gaugeId, address recipient, string name)
|
||||
TopicGaugeAdded = "0x0690c0953072daf431d5cdec64e67412376badca0df468b950c61659830da131"
|
||||
|
||||
// GaugeUpdated(uint256 indexed gaugeId, bool active)
|
||||
TopicGaugeUpdated = "0x1e2a3ff4241a28030cd83b954fb7bb7843a2555c29dc5b18f269308414d3bfb8"
|
||||
|
||||
// VoteCast(address indexed user, uint256 indexed gaugeId, uint256 weight) — gauge votes
|
||||
TopicGaugeVoteCast = "0xb4cfecf70861b7b150d8337780d34fb4cbc2114b5fb1fe51a5c5fca1849f7274"
|
||||
|
||||
// WeightsUpdated(uint256 timestamp)
|
||||
TopicWeightsUpdated = "0x90dd5985ed06249c1f4c7a5a37394070d774f2bc35c0bea714388d778c8f44c2"
|
||||
|
||||
// vLUX events
|
||||
// Deposit(address indexed user, uint256 amount, uint256 lockTime, uint256 indexed lockEnd, uint256 ts)
|
||||
TopicVLUXDeposit = "0x7162984403f6c73c8639375d45a9187dfd04602231bd8e587c415718b5f7e5f9"
|
||||
|
||||
// Withdraw(address indexed user, uint256 amount, uint256 ts)
|
||||
TopicVLUXWithdraw = "0xf279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b568"
|
||||
|
||||
// Supply(uint256 prevSupply, uint256 newSupply)
|
||||
TopicVLUXSupply = "0x5e2aa66efd74cce82b21852e317e5490d9ecc9e6bb953ae24d90851258cc2f5c"
|
||||
|
||||
// Karma events
|
||||
// KarmaMinted(address indexed to, uint256 amount, bytes32 reason)
|
||||
TopicKarmaMinted = "0x75a0780e98a4894b81145ffa61783249374538e763a84a56f131f73828eef94c"
|
||||
|
||||
// KarmaSlashed(address indexed from, uint256 amount, bytes32 reason)
|
||||
TopicKarmaSlashed = "0x3578cfe093e136b6c466311ff49a5625dff510e16844bed0f2731bc79bd0776d"
|
||||
|
||||
// KarmaDecayed(address indexed account, uint256 amount, bool wasActive)
|
||||
TopicKarmaDecayed = "0x5a32bf6cb8d08258f53813b731b066fd8804a627f0f0e0d32b898895841cbc6a"
|
||||
|
||||
// DIDLinked(address indexed account, bytes32 indexed did)
|
||||
TopicDIDLinked = "0xabdbb33c848055c04a7674a4d96d0173f89f9f7718cd5426e7bcd81b4596182e"
|
||||
|
||||
// Verified(address indexed account, bool status)
|
||||
TopicVerified = "0x04881682880396c5d7f330e192fcd4f8b3d4645842908712b7a005170b3120cc"
|
||||
|
||||
// DLUX events
|
||||
// Staked(address indexed user, uint256 amount, uint8 tier, uint256 lockEnd)
|
||||
TopicDLUXStaked = "0xbde7f0ba1630d25515c7ab99ba47d5640b7ffb4c673b2a5464ae679195589298"
|
||||
|
||||
// Unstaked(address indexed user, uint256 amount, uint256 luxReturned)
|
||||
TopicDLUXUnstaked = "0x7fc4727e062e336010f2c282598ef5f14facb3de68cf8195c2f23e1454b2b74e"
|
||||
|
||||
// Rebased(uint256 epoch, uint256 totalRebased, uint256 rate)
|
||||
TopicRebased = "0xd1a8a452d776b1b6802824ca2e8489c6448e2cb0963f552a9a19ab4ae064ca58"
|
||||
|
||||
// DemurrageApplied(address indexed account, uint256 burned)
|
||||
TopicDemurrageApplied = "0x92dbe9af03191016deca349de5f6439fc2d722818b96f7b02948d780a913e8e5"
|
||||
|
||||
// TierUpgraded(address indexed user, uint8 from, uint8 to)
|
||||
TopicTierUpgraded = "0xcbbf00b5f55f1857f9362f521999f79036413e420d1b236d6bc143af30db731a"
|
||||
|
||||
// Minted(address indexed to, uint256 amount, bytes32 indexed reason)
|
||||
TopicDLUXMinted = "0xc263b302aec62d29105026245f19e16f8e0137066ccd4a8bd941f716bd4096bb"
|
||||
)
|
||||
|
||||
// DAOTopics returns all DAO-related event topic0 strings.
|
||||
func DAOTopics() []string {
|
||||
return []string{
|
||||
TopicGaugeAdded, TopicGaugeUpdated, TopicGaugeVoteCast, TopicWeightsUpdated,
|
||||
TopicVLUXDeposit, TopicVLUXWithdraw, TopicVLUXSupply,
|
||||
TopicKarmaMinted, TopicKarmaSlashed, TopicKarmaDecayed,
|
||||
TopicDIDLinked, TopicVerified,
|
||||
TopicDLUXStaked, TopicDLUXUnstaked, TopicRebased,
|
||||
TopicDemurrageApplied, TopicTierUpgraded, TopicDLUXMinted,
|
||||
}
|
||||
}
|
||||
|
||||
// GaugeEvent represents a GaugeController event.
|
||||
type GaugeEvent struct {
|
||||
GaugeID *big.Int
|
||||
Recipient string
|
||||
Name string
|
||||
Active bool
|
||||
Event string // "added", "updated", "vote", "weights_updated"
|
||||
User string // for vote events
|
||||
Weight *big.Int
|
||||
Contract string
|
||||
Block uint64
|
||||
TxHash string
|
||||
}
|
||||
|
||||
// VLUXEvent represents a vLUX lock/unlock event.
|
||||
type VLUXEvent struct {
|
||||
User string
|
||||
Amount *big.Int
|
||||
LockEnd *big.Int
|
||||
Event string // "deposit", "withdraw"
|
||||
Contract string
|
||||
Block uint64
|
||||
TxHash string
|
||||
}
|
||||
|
||||
// KarmaEvent represents a Karma reputation change.
|
||||
type KarmaEvent struct {
|
||||
Account string
|
||||
Amount *big.Int
|
||||
Reason string // hex bytes32
|
||||
WasActive bool
|
||||
Event string // "minted", "slashed", "decayed", "did_linked", "verified"
|
||||
DID string // hex bytes32 for did_linked
|
||||
Contract string
|
||||
Block uint64
|
||||
TxHash string
|
||||
}
|
||||
|
||||
// DLUXEvent represents a DLUX token event.
|
||||
type DLUXEvent struct {
|
||||
User string
|
||||
Amount *big.Int
|
||||
Tier uint8
|
||||
LockEnd *big.Int
|
||||
Epoch *big.Int
|
||||
Rate *big.Int
|
||||
Reason string // hex bytes32
|
||||
Event string // "staked", "unstaked", "rebased", "demurrage", "tier_upgraded", "minted"
|
||||
Contract string
|
||||
Block uint64
|
||||
TxHash string
|
||||
}
|
||||
|
||||
// ParseDAOEvents extracts gauge, vLUX, Karma, and DLUX events from EVM logs.
|
||||
func ParseDAOEvents(logs []Log) ([]GaugeEvent, []VLUXEvent, []KarmaEvent, []DLUXEvent) {
|
||||
var gauges []GaugeEvent
|
||||
var vlux []VLUXEvent
|
||||
var karma []KarmaEvent
|
||||
var dlux []DLUXEvent
|
||||
|
||||
for i := range logs {
|
||||
l := &logs[i]
|
||||
if len(l.Topics) == 0 {
|
||||
continue
|
||||
}
|
||||
switch l.Topics[0] {
|
||||
// Gauge
|
||||
case TopicGaugeAdded:
|
||||
if e := parseGaugeAdded(l); e != nil {
|
||||
gauges = append(gauges, *e)
|
||||
}
|
||||
case TopicGaugeUpdated:
|
||||
if e := parseGaugeUpdated(l); e != nil {
|
||||
gauges = append(gauges, *e)
|
||||
}
|
||||
case TopicGaugeVoteCast:
|
||||
if e := parseGaugeVote(l); e != nil {
|
||||
gauges = append(gauges, *e)
|
||||
}
|
||||
case TopicWeightsUpdated:
|
||||
gauges = append(gauges, GaugeEvent{
|
||||
Event: "weights_updated", Contract: l.Address,
|
||||
Block: l.BlockNumber, TxHash: l.TxHash,
|
||||
})
|
||||
|
||||
// vLUX
|
||||
case TopicVLUXDeposit:
|
||||
if e := parseVLUXDeposit(l); e != nil {
|
||||
vlux = append(vlux, *e)
|
||||
}
|
||||
case TopicVLUXWithdraw:
|
||||
if e := parseVLUXWithdraw(l); e != nil {
|
||||
vlux = append(vlux, *e)
|
||||
}
|
||||
|
||||
// Karma
|
||||
case TopicKarmaMinted:
|
||||
if e := parseKarmaMinted(l); e != nil {
|
||||
karma = append(karma, *e)
|
||||
}
|
||||
case TopicKarmaSlashed:
|
||||
if e := parseKarmaSlashed(l); e != nil {
|
||||
karma = append(karma, *e)
|
||||
}
|
||||
case TopicKarmaDecayed:
|
||||
if e := parseKarmaDecayed(l); e != nil {
|
||||
karma = append(karma, *e)
|
||||
}
|
||||
case TopicDIDLinked:
|
||||
if e := parseDIDLinked(l); e != nil {
|
||||
karma = append(karma, *e)
|
||||
}
|
||||
case TopicVerified:
|
||||
if e := parseVerified(l); e != nil {
|
||||
karma = append(karma, *e)
|
||||
}
|
||||
|
||||
// DLUX
|
||||
case TopicDLUXStaked:
|
||||
if e := parseDLUXStaked(l); e != nil {
|
||||
dlux = append(dlux, *e)
|
||||
}
|
||||
case TopicDLUXUnstaked:
|
||||
if e := parseDLUXUnstaked(l); e != nil {
|
||||
dlux = append(dlux, *e)
|
||||
}
|
||||
case TopicRebased:
|
||||
if e := parseRebased(l); e != nil {
|
||||
dlux = append(dlux, *e)
|
||||
}
|
||||
case TopicDemurrageApplied:
|
||||
if e := parseDemurrage(l); e != nil {
|
||||
dlux = append(dlux, *e)
|
||||
}
|
||||
case TopicDLUXMinted:
|
||||
if e := parseDLUXMinted(l); e != nil {
|
||||
dlux = append(dlux, *e)
|
||||
}
|
||||
}
|
||||
}
|
||||
return gauges, vlux, karma, dlux
|
||||
}
|
||||
|
||||
func parseGaugeAdded(l *Log) *GaugeEvent {
|
||||
if len(l.Topics) < 2 {
|
||||
return nil
|
||||
}
|
||||
return &GaugeEvent{
|
||||
GaugeID: wordFromTopic(l.Topics[1]),
|
||||
Recipient: decodeAddress(l.Data, 0),
|
||||
Event: "added",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseGaugeUpdated(l *Log) *GaugeEvent {
|
||||
if len(l.Topics) < 2 {
|
||||
return nil
|
||||
}
|
||||
active := decodeWord(l.Data, 0)
|
||||
return &GaugeEvent{
|
||||
GaugeID: wordFromTopic(l.Topics[1]),
|
||||
Active: active != nil && active.Sign() > 0,
|
||||
Event: "updated",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseGaugeVote(l *Log) *GaugeEvent {
|
||||
// VoteCast(address indexed user, uint256 indexed gaugeId, uint256 weight)
|
||||
if len(l.Topics) < 3 {
|
||||
return nil
|
||||
}
|
||||
return &GaugeEvent{
|
||||
User: topicAddress(l.Topics[1]),
|
||||
GaugeID: wordFromTopic(l.Topics[2]),
|
||||
Weight: decodeWord(l.Data, 0),
|
||||
Event: "vote",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseVLUXDeposit(l *Log) *VLUXEvent {
|
||||
// Deposit(address indexed user, uint256 amount, uint256 lockTime, uint256 indexed lockEnd, uint256 ts)
|
||||
if len(l.Topics) < 3 {
|
||||
return nil
|
||||
}
|
||||
return &VLUXEvent{
|
||||
User: topicAddress(l.Topics[1]),
|
||||
Amount: decodeWord(l.Data, 0),
|
||||
LockEnd: wordFromTopic(l.Topics[2]),
|
||||
Event: "deposit",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseVLUXWithdraw(l *Log) *VLUXEvent {
|
||||
// Withdraw(address indexed user, uint256 amount, uint256 ts)
|
||||
if len(l.Topics) < 2 {
|
||||
return nil
|
||||
}
|
||||
return &VLUXEvent{
|
||||
User: topicAddress(l.Topics[1]),
|
||||
Amount: decodeWord(l.Data, 0),
|
||||
Event: "withdraw",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseKarmaMinted(l *Log) *KarmaEvent {
|
||||
// KarmaMinted(address indexed to, uint256 amount, bytes32 reason)
|
||||
if len(l.Topics) < 2 {
|
||||
return nil
|
||||
}
|
||||
reason := decodeWord(l.Data, 1)
|
||||
return &KarmaEvent{
|
||||
Account: topicAddress(l.Topics[1]),
|
||||
Amount: decodeWord(l.Data, 0),
|
||||
Reason: wordHex(reason),
|
||||
Event: "minted",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseKarmaSlashed(l *Log) *KarmaEvent {
|
||||
// KarmaSlashed(address indexed from, uint256 amount, bytes32 reason)
|
||||
if len(l.Topics) < 2 {
|
||||
return nil
|
||||
}
|
||||
reason := decodeWord(l.Data, 1)
|
||||
return &KarmaEvent{
|
||||
Account: topicAddress(l.Topics[1]),
|
||||
Amount: decodeWord(l.Data, 0),
|
||||
Reason: wordHex(reason),
|
||||
Event: "slashed",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseKarmaDecayed(l *Log) *KarmaEvent {
|
||||
// KarmaDecayed(address indexed account, uint256 amount, bool wasActive)
|
||||
if len(l.Topics) < 2 {
|
||||
return nil
|
||||
}
|
||||
wasActive := decodeWord(l.Data, 1)
|
||||
return &KarmaEvent{
|
||||
Account: topicAddress(l.Topics[1]),
|
||||
Amount: decodeWord(l.Data, 0),
|
||||
WasActive: wasActive != nil && wasActive.Sign() > 0,
|
||||
Event: "decayed",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseDIDLinked(l *Log) *KarmaEvent {
|
||||
// DIDLinked(address indexed account, bytes32 indexed did)
|
||||
if len(l.Topics) < 3 {
|
||||
return nil
|
||||
}
|
||||
return &KarmaEvent{
|
||||
Account: topicAddress(l.Topics[1]),
|
||||
DID: l.Topics[2],
|
||||
Event: "did_linked",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseVerified(l *Log) *KarmaEvent {
|
||||
// Verified(address indexed account, bool status)
|
||||
if len(l.Topics) < 2 {
|
||||
return nil
|
||||
}
|
||||
return &KarmaEvent{
|
||||
Account: topicAddress(l.Topics[1]),
|
||||
Event: "verified",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseDLUXStaked(l *Log) *DLUXEvent {
|
||||
// Staked(address indexed user, uint256 amount, uint8 tier, uint256 lockEnd)
|
||||
if len(l.Topics) < 2 {
|
||||
return nil
|
||||
}
|
||||
tier := decodeWord(l.Data, 1)
|
||||
var t uint8
|
||||
if tier != nil && tier.IsUint64() {
|
||||
t = uint8(tier.Uint64())
|
||||
}
|
||||
return &DLUXEvent{
|
||||
User: topicAddress(l.Topics[1]),
|
||||
Amount: decodeWord(l.Data, 0),
|
||||
Tier: t,
|
||||
LockEnd: decodeWord(l.Data, 2),
|
||||
Event: "staked",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseDLUXUnstaked(l *Log) *DLUXEvent {
|
||||
// Unstaked(address indexed user, uint256 amount, uint256 luxReturned)
|
||||
if len(l.Topics) < 2 {
|
||||
return nil
|
||||
}
|
||||
return &DLUXEvent{
|
||||
User: topicAddress(l.Topics[1]),
|
||||
Amount: decodeWord(l.Data, 0),
|
||||
Event: "unstaked",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseRebased(l *Log) *DLUXEvent {
|
||||
// Rebased(uint256 epoch, uint256 totalRebased, uint256 rate)
|
||||
return &DLUXEvent{
|
||||
Epoch: decodeWord(l.Data, 0),
|
||||
Amount: decodeWord(l.Data, 1),
|
||||
Rate: decodeWord(l.Data, 2),
|
||||
Event: "rebased",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseDemurrage(l *Log) *DLUXEvent {
|
||||
// DemurrageApplied(address indexed account, uint256 burned)
|
||||
if len(l.Topics) < 2 {
|
||||
return nil
|
||||
}
|
||||
return &DLUXEvent{
|
||||
User: topicAddress(l.Topics[1]),
|
||||
Amount: decodeWord(l.Data, 0),
|
||||
Event: "demurrage",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseDLUXMinted(l *Log) *DLUXEvent {
|
||||
// Minted(address indexed to, uint256 amount, bytes32 indexed reason)
|
||||
if len(l.Topics) < 3 {
|
||||
return nil
|
||||
}
|
||||
return &DLUXEvent{
|
||||
User: topicAddress(l.Topics[1]),
|
||||
Amount: decodeWord(l.Data, 0),
|
||||
Reason: l.Topics[2],
|
||||
Event: "minted",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
// wordFromTopic parses a topic hex string into a big.Int.
|
||||
func wordFromTopic(topic string) *big.Int {
|
||||
n := new(big.Int)
|
||||
if len(topic) > 2 {
|
||||
n.SetString(topic[2:], 16)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// wordHex formats a big.Int as a 0x-prefixed hex string.
|
||||
func wordHex(n *big.Int) string {
|
||||
if n == nil {
|
||||
return "0x0"
|
||||
}
|
||||
return "0x" + n.Text(16)
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
// Package defi provides EVM log parsers for DeFi protocol events.
|
||||
package defi
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Governance event topic0 signatures (keccak256 hashes).
|
||||
// Source: contracts/governance/DAO.sol, Governor.sol (OpenZeppelin IGovernor)
|
||||
const (
|
||||
// ProposalCreated(uint256,address,address[],uint256[],bytes[],uint256,uint256,string)
|
||||
TopicProposalCreated = "0x95f03e437e6d5037418f12bef80fc7e0a5f27754c078a1d5d3f62d39bac44e50"
|
||||
|
||||
// VoteCast(address indexed,uint256,uint8,uint256,string)
|
||||
TopicVoteCast = "0xb8e138887d0aa13bab447e82de9d5c1777041ecd21ca36ba824ff1e6c07ddda4"
|
||||
|
||||
// ProposalExecuted(uint256)
|
||||
TopicProposalExecuted = "0x712ae1383f79ac853f8d882153778e0260ef8f03b504e2866e0593e04d2b291f"
|
||||
|
||||
// ProposalCanceled(uint256)
|
||||
TopicProposalCanceled = "0x789cf55be980739dad1d0699b93b58e806b51c9d96619bfa8fe0a28abaa7b30c"
|
||||
|
||||
// ProposalQueued(uint256,uint256)
|
||||
TopicProposalQueued = "0x9a2e42fd6722813d69113e7d0079d3d940171428df7373df9c7f7617cfda2892"
|
||||
|
||||
// DelegateChanged(address indexed,address indexed,address indexed)
|
||||
TopicDelegateChanged = "0x3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f"
|
||||
|
||||
// DelegateVotesChanged(address indexed,uint256,uint256)
|
||||
TopicDelegateVotesChanged = "0xdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a724"
|
||||
|
||||
// GuardianUpdated(address,address)
|
||||
TopicGuardianUpdated = "0x064d28d3d3071c5cbc271a261c10c2f0f0d9e319390397101aa0eb23c6bad909"
|
||||
)
|
||||
|
||||
// GovernanceTopics returns all governance event topic0 strings.
|
||||
func GovernanceTopics() []string {
|
||||
return []string{
|
||||
TopicProposalCreated,
|
||||
TopicVoteCast,
|
||||
TopicProposalExecuted,
|
||||
TopicProposalCanceled,
|
||||
TopicProposalQueued,
|
||||
TopicDelegateChanged,
|
||||
TopicDelegateVotesChanged,
|
||||
TopicGuardianUpdated,
|
||||
}
|
||||
}
|
||||
|
||||
// Log is a decoded EVM log entry. Matches the shape used by the indexer package.
|
||||
type Log struct {
|
||||
Address string
|
||||
Topics []string
|
||||
Data string
|
||||
BlockNumber uint64
|
||||
TxHash string
|
||||
LogIndex string
|
||||
}
|
||||
|
||||
// GovernanceProposal represents a parsed proposal lifecycle event.
|
||||
type GovernanceProposal struct {
|
||||
ProposalID *big.Int
|
||||
Proposer string
|
||||
StartBlock *big.Int
|
||||
EndBlock *big.Int
|
||||
ETA *big.Int // queued only
|
||||
Event string // "created", "executed", "canceled", "queued"
|
||||
Contract string
|
||||
Block uint64
|
||||
TxHash string
|
||||
}
|
||||
|
||||
// Vote represents a parsed VoteCast event.
|
||||
type Vote struct {
|
||||
Voter string
|
||||
ProposalID *big.Int
|
||||
Support uint8 // 0=Against, 1=For, 2=Abstain
|
||||
Weight *big.Int
|
||||
Contract string
|
||||
Block uint64
|
||||
TxHash string
|
||||
}
|
||||
|
||||
// DelegateChange represents a DelegateChanged event.
|
||||
type DelegateChange struct {
|
||||
Delegator string
|
||||
FromDelegate string
|
||||
ToDelegate string
|
||||
Contract string
|
||||
Block uint64
|
||||
TxHash string
|
||||
}
|
||||
|
||||
// DelegateVotesChange represents a DelegateVotesChanged event.
|
||||
type DelegateVotesChange struct {
|
||||
Delegate string
|
||||
PreviousBalance *big.Int
|
||||
NewBalance *big.Int
|
||||
Contract string
|
||||
Block uint64
|
||||
TxHash string
|
||||
}
|
||||
|
||||
// ParseGovernanceEvents extracts governance proposals, votes, and delegation
|
||||
// changes from a slice of EVM logs.
|
||||
func ParseGovernanceEvents(logs []Log) ([]GovernanceProposal, []Vote, []DelegateChange, []DelegateVotesChange) {
|
||||
var proposals []GovernanceProposal
|
||||
var votes []Vote
|
||||
var delegates []DelegateChange
|
||||
var dvotes []DelegateVotesChange
|
||||
|
||||
for i := range logs {
|
||||
l := &logs[i]
|
||||
if len(l.Topics) == 0 {
|
||||
continue
|
||||
}
|
||||
switch l.Topics[0] {
|
||||
case TopicProposalCreated:
|
||||
p := parseProposalCreated(l)
|
||||
if p != nil {
|
||||
proposals = append(proposals, *p)
|
||||
}
|
||||
case TopicVoteCast:
|
||||
v := parseVoteCast(l)
|
||||
if v != nil {
|
||||
votes = append(votes, *v)
|
||||
}
|
||||
case TopicProposalExecuted:
|
||||
p := parseProposalSimple(l, "executed")
|
||||
if p != nil {
|
||||
proposals = append(proposals, *p)
|
||||
}
|
||||
case TopicProposalCanceled:
|
||||
p := parseProposalSimple(l, "canceled")
|
||||
if p != nil {
|
||||
proposals = append(proposals, *p)
|
||||
}
|
||||
case TopicProposalQueued:
|
||||
p := parseProposalQueued(l)
|
||||
if p != nil {
|
||||
proposals = append(proposals, *p)
|
||||
}
|
||||
case TopicDelegateChanged:
|
||||
d := parseDelegateChanged(l)
|
||||
if d != nil {
|
||||
delegates = append(delegates, *d)
|
||||
}
|
||||
case TopicDelegateVotesChanged:
|
||||
d := parseDelegateVotesChanged(l)
|
||||
if d != nil {
|
||||
dvotes = append(dvotes, *d)
|
||||
}
|
||||
}
|
||||
}
|
||||
return proposals, votes, delegates, dvotes
|
||||
}
|
||||
|
||||
func parseProposalCreated(l *Log) *GovernanceProposal {
|
||||
// ProposalCreated is not indexed beyond topic0; data has all fields.
|
||||
// data layout: proposalId(0), proposer(1), ... startBlock(5), endBlock(6), ...
|
||||
proposalID := decodeWord(l.Data, 0)
|
||||
proposer := decodeAddress(l.Data, 1)
|
||||
startBlock := decodeWord(l.Data, 5)
|
||||
endBlock := decodeWord(l.Data, 6)
|
||||
return &GovernanceProposal{
|
||||
ProposalID: proposalID,
|
||||
Proposer: proposer,
|
||||
StartBlock: startBlock,
|
||||
EndBlock: endBlock,
|
||||
Event: "created",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseVoteCast(l *Log) *Vote {
|
||||
// VoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 votes, string reason)
|
||||
if len(l.Topics) < 2 {
|
||||
return nil
|
||||
}
|
||||
voter := topicAddress(l.Topics[1])
|
||||
proposalID := decodeWord(l.Data, 0)
|
||||
support := decodeWord(l.Data, 1)
|
||||
weight := decodeWord(l.Data, 2)
|
||||
var s uint8
|
||||
if support != nil && support.IsUint64() {
|
||||
s = uint8(support.Uint64())
|
||||
}
|
||||
return &Vote{
|
||||
Voter: voter,
|
||||
ProposalID: proposalID,
|
||||
Support: s,
|
||||
Weight: weight,
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseProposalSimple(l *Log, event string) *GovernanceProposal {
|
||||
proposalID := decodeWord(l.Data, 0)
|
||||
return &GovernanceProposal{
|
||||
ProposalID: proposalID,
|
||||
Event: event,
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseProposalQueued(l *Log) *GovernanceProposal {
|
||||
proposalID := decodeWord(l.Data, 0)
|
||||
eta := decodeWord(l.Data, 1)
|
||||
return &GovernanceProposal{
|
||||
ProposalID: proposalID,
|
||||
ETA: eta,
|
||||
Event: "queued",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseDelegateChanged(l *Log) *DelegateChange {
|
||||
// DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate)
|
||||
if len(l.Topics) < 4 {
|
||||
return nil
|
||||
}
|
||||
return &DelegateChange{
|
||||
Delegator: topicAddress(l.Topics[1]),
|
||||
FromDelegate: topicAddress(l.Topics[2]),
|
||||
ToDelegate: topicAddress(l.Topics[3]),
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseDelegateVotesChanged(l *Log) *DelegateVotesChange {
|
||||
// DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance)
|
||||
if len(l.Topics) < 2 {
|
||||
return nil
|
||||
}
|
||||
return &DelegateVotesChange{
|
||||
Delegate: topicAddress(l.Topics[1]),
|
||||
PreviousBalance: decodeWord(l.Data, 0),
|
||||
NewBalance: decodeWord(l.Data, 1),
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
// decodeWord reads a 32-byte word from hex data at the given word index.
|
||||
func decodeWord(data string, wordIndex int) *big.Int {
|
||||
data = strings.TrimPrefix(data, "0x")
|
||||
start := wordIndex * 64
|
||||
if start+64 > len(data) {
|
||||
return new(big.Int)
|
||||
}
|
||||
n := new(big.Int)
|
||||
n.SetString(data[start:start+64], 16)
|
||||
return n
|
||||
}
|
||||
|
||||
// decodeAddress reads an address from the low 20 bytes of a 32-byte word.
|
||||
func decodeAddress(data string, wordIndex int) string {
|
||||
data = strings.TrimPrefix(data, "0x")
|
||||
start := wordIndex*64 + 24 // skip 12 bytes of zero padding
|
||||
if start+40 > len(data) {
|
||||
return ""
|
||||
}
|
||||
return "0x" + data[start:start+40]
|
||||
}
|
||||
|
||||
// topicAddress extracts an address from an indexed topic (last 40 hex chars).
|
||||
func topicAddress(topic string) string {
|
||||
if len(topic) >= 42 {
|
||||
return "0x" + topic[len(topic)-40:]
|
||||
}
|
||||
return topic
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
package defi
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGovernanceTopicsLength(t *testing.T) {
|
||||
topics := GovernanceTopics()
|
||||
if len(topics) != 8 {
|
||||
t.Fatalf("expected 8 governance topics, got %d", len(topics))
|
||||
}
|
||||
for _, topic := range topics {
|
||||
if len(topic) != 66 || topic[:2] != "0x" {
|
||||
t.Errorf("invalid topic format: %s", topic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseProposalCreated(t *testing.T) {
|
||||
// ProposalCreated: data has proposalId at word 0, proposer at word 1, startBlock at word 5, endBlock at word 6
|
||||
data := "0x" +
|
||||
"0000000000000000000000000000000000000000000000000000000000000007" + // word 0: proposalId = 7
|
||||
"000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + // word 1: proposer
|
||||
"0000000000000000000000000000000000000000000000000000000000000000" + // word 2: placeholder
|
||||
"0000000000000000000000000000000000000000000000000000000000000000" + // word 3: placeholder
|
||||
"0000000000000000000000000000000000000000000000000000000000000000" + // word 4: placeholder
|
||||
"0000000000000000000000000000000000000000000000000000000000000064" + // word 5: startBlock = 100
|
||||
"00000000000000000000000000000000000000000000000000000000000000c8" // word 6: endBlock = 200
|
||||
|
||||
logs := []Log{{
|
||||
Address: "0x1111111111111111111111111111111111111111",
|
||||
Topics: []string{TopicProposalCreated},
|
||||
Data: data,
|
||||
BlockNumber: 50,
|
||||
TxHash: "0xabc",
|
||||
}}
|
||||
|
||||
proposals, votes, delegates, dvotes := ParseGovernanceEvents(logs)
|
||||
if len(proposals) != 1 {
|
||||
t.Fatalf("expected 1 proposal, got %d", len(proposals))
|
||||
}
|
||||
if len(votes) != 0 || len(delegates) != 0 || len(dvotes) != 0 {
|
||||
t.Fatal("expected no votes/delegates")
|
||||
}
|
||||
|
||||
p := proposals[0]
|
||||
if p.ProposalID.Cmp(big.NewInt(7)) != 0 {
|
||||
t.Errorf("proposalId = %s, want 7", p.ProposalID)
|
||||
}
|
||||
if p.Proposer != "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" {
|
||||
t.Errorf("proposer = %s", p.Proposer)
|
||||
}
|
||||
if p.StartBlock.Cmp(big.NewInt(100)) != 0 {
|
||||
t.Errorf("startBlock = %s, want 100", p.StartBlock)
|
||||
}
|
||||
if p.EndBlock.Cmp(big.NewInt(200)) != 0 {
|
||||
t.Errorf("endBlock = %s, want 200", p.EndBlock)
|
||||
}
|
||||
if p.Event != "created" {
|
||||
t.Errorf("event = %s, want created", p.Event)
|
||||
}
|
||||
if p.Block != 50 {
|
||||
t.Errorf("block = %d, want 50", p.Block)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseVoteCast(t *testing.T) {
|
||||
// VoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 votes, string reason)
|
||||
voter := "0x000000000000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||
data := "0x" +
|
||||
"0000000000000000000000000000000000000000000000000000000000000003" + // proposalId = 3
|
||||
"0000000000000000000000000000000000000000000000000000000000000001" + // support = 1 (For)
|
||||
"00000000000000000000000000000000000000000000003635c9adc5dea00000" // votes = 1000e18
|
||||
|
||||
logs := []Log{{
|
||||
Address: "0x2222222222222222222222222222222222222222",
|
||||
Topics: []string{TopicVoteCast, voter},
|
||||
Data: data,
|
||||
BlockNumber: 60,
|
||||
TxHash: "0xdef",
|
||||
}}
|
||||
|
||||
_, votes, _, _ := ParseGovernanceEvents(logs)
|
||||
if len(votes) != 1 {
|
||||
t.Fatalf("expected 1 vote, got %d", len(votes))
|
||||
}
|
||||
|
||||
v := votes[0]
|
||||
if v.Voter != "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" {
|
||||
t.Errorf("voter = %s", v.Voter)
|
||||
}
|
||||
if v.ProposalID.Cmp(big.NewInt(3)) != 0 {
|
||||
t.Errorf("proposalId = %s, want 3", v.ProposalID)
|
||||
}
|
||||
if v.Support != 1 {
|
||||
t.Errorf("support = %d, want 1", v.Support)
|
||||
}
|
||||
expected := new(big.Int)
|
||||
expected.SetString("3635c9adc5dea00000", 16)
|
||||
if v.Weight.Cmp(expected) != 0 {
|
||||
t.Errorf("weight = %s, want %s", v.Weight, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseProposalExecuted(t *testing.T) {
|
||||
data := "0x0000000000000000000000000000000000000000000000000000000000000005"
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicProposalExecuted},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
proposals, _, _, _ := ParseGovernanceEvents(logs)
|
||||
if len(proposals) != 1 || proposals[0].Event != "executed" {
|
||||
t.Fatal("expected executed proposal")
|
||||
}
|
||||
if proposals[0].ProposalID.Cmp(big.NewInt(5)) != 0 {
|
||||
t.Errorf("proposalId = %s, want 5", proposals[0].ProposalID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseProposalQueued(t *testing.T) {
|
||||
data := "0x" +
|
||||
"000000000000000000000000000000000000000000000000000000000000000a" + // proposalId = 10
|
||||
"0000000000000000000000000000000000000000000000000000000065b8d800" // eta
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicProposalQueued},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
proposals, _, _, _ := ParseGovernanceEvents(logs)
|
||||
if len(proposals) != 1 || proposals[0].Event != "queued" {
|
||||
t.Fatal("expected queued proposal")
|
||||
}
|
||||
if proposals[0].ProposalID.Cmp(big.NewInt(10)) != 0 {
|
||||
t.Errorf("proposalId = %s, want 10", proposals[0].ProposalID)
|
||||
}
|
||||
if proposals[0].ETA == nil || proposals[0].ETA.Sign() == 0 {
|
||||
t.Error("expected non-zero ETA")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDelegateChanged(t *testing.T) {
|
||||
logs := []Log{{
|
||||
Topics: []string{
|
||||
TopicDelegateChanged,
|
||||
"0x0000000000000000000000001111111111111111111111111111111111111111", // delegator
|
||||
"0x0000000000000000000000002222222222222222222222222222222222222222", // from
|
||||
"0x0000000000000000000000003333333333333333333333333333333333333333", // to
|
||||
},
|
||||
Data: "0x",
|
||||
}}
|
||||
|
||||
_, _, delegates, _ := ParseGovernanceEvents(logs)
|
||||
if len(delegates) != 1 {
|
||||
t.Fatalf("expected 1 delegate change, got %d", len(delegates))
|
||||
}
|
||||
d := delegates[0]
|
||||
if d.Delegator != "0x1111111111111111111111111111111111111111" {
|
||||
t.Errorf("delegator = %s", d.Delegator)
|
||||
}
|
||||
if d.FromDelegate != "0x2222222222222222222222222222222222222222" {
|
||||
t.Errorf("from = %s", d.FromDelegate)
|
||||
}
|
||||
if d.ToDelegate != "0x3333333333333333333333333333333333333333" {
|
||||
t.Errorf("to = %s", d.ToDelegate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDelegateVotesChanged(t *testing.T) {
|
||||
data := "0x" +
|
||||
"00000000000000000000000000000000000000000000000000000000000003e8" + // prev = 1000
|
||||
"00000000000000000000000000000000000000000000000000000000000007d0" // new = 2000
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{
|
||||
TopicDelegateVotesChanged,
|
||||
"0x0000000000000000000000004444444444444444444444444444444444444444",
|
||||
},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
_, _, _, dvotes := ParseGovernanceEvents(logs)
|
||||
if len(dvotes) != 1 {
|
||||
t.Fatalf("expected 1 delegate votes change, got %d", len(dvotes))
|
||||
}
|
||||
d := dvotes[0]
|
||||
if d.PreviousBalance.Cmp(big.NewInt(1000)) != 0 {
|
||||
t.Errorf("prev = %s, want 1000", d.PreviousBalance)
|
||||
}
|
||||
if d.NewBalance.Cmp(big.NewInt(2000)) != 0 {
|
||||
t.Errorf("new = %s, want 2000", d.NewBalance)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeWord(t *testing.T) {
|
||||
data := "0x0000000000000000000000000000000000000000000000000000000000000042"
|
||||
w := decodeWord(data, 0)
|
||||
if w.Cmp(big.NewInt(0x42)) != 0 {
|
||||
t.Errorf("got %s, want 66", w)
|
||||
}
|
||||
|
||||
// Short data
|
||||
short := decodeWord("0x00", 0)
|
||||
if short.Sign() != 0 {
|
||||
t.Errorf("short data: got %s, want 0", short)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeAddress(t *testing.T) {
|
||||
data := "0x000000000000000000000000abcdef0123456789abcdef0123456789abcdef01"
|
||||
addr := decodeAddress(data, 0)
|
||||
if addr != "0xabcdef0123456789abcdef0123456789abcdef01" {
|
||||
t.Errorf("got %s", addr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopicAddress(t *testing.T) {
|
||||
topic := "0x000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
addr := topicAddress(topic)
|
||||
if addr != "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" {
|
||||
t.Errorf("got %s", addr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyLogs(t *testing.T) {
|
||||
proposals, votes, delegates, dvotes := ParseGovernanceEvents(nil)
|
||||
if proposals != nil || votes != nil || delegates != nil || dvotes != nil {
|
||||
t.Error("expected nil results for nil input")
|
||||
}
|
||||
|
||||
proposals, votes, delegates, dvotes = ParseGovernanceEvents([]Log{{Topics: []string{}}})
|
||||
if proposals != nil || votes != nil || delegates != nil || dvotes != nil {
|
||||
t.Error("expected nil results for empty topics")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package defi
|
||||
|
||||
import "math/big"
|
||||
|
||||
// Liquid staking event topic0 signatures (keccak256 hashes).
|
||||
// Source: contracts/liquid/LiquidLUX.sol
|
||||
const (
|
||||
// FeesReceived(address indexed from, uint256 amount, bytes32 indexed feeType, uint256 perfFee, uint256 toReserve)
|
||||
TopicFeesReceived = "0xffe15e5c651accdc94c3387fa6d493291053ff174cd74f8c8b7f8a820e5dd909"
|
||||
|
||||
// ValidatorRewardsReceived(address indexed from, uint256 amount)
|
||||
TopicValidatorRewardsReceived = "0x9d77a73cf83a1a8caf4495066223f6e1c6758b9b03f54ea006254cca0108e27c"
|
||||
|
||||
// SlashingApplied(uint256 amount, uint256 fromReserve, uint256 socialized)
|
||||
TopicSlashingApplied = "0xe0ce9feb262640cffce27987d6007683502f8828357b680a1d2a0902ff5f4c1c"
|
||||
|
||||
// EmergencyWithdrawal(address indexed to, uint256 amount)
|
||||
TopicEmergencyWithdrawal = "0x23d6711a1d031134a36921253c75aa59e967d38e369ac625992824315e204f20"
|
||||
)
|
||||
|
||||
// LiquidTopics returns all liquid staking event topic0 strings.
|
||||
func LiquidTopics() []string {
|
||||
return []string{
|
||||
TopicFeesReceived,
|
||||
TopicValidatorRewardsReceived,
|
||||
TopicSlashingApplied,
|
||||
TopicEmergencyWithdrawal,
|
||||
}
|
||||
}
|
||||
|
||||
// LiquidEvent represents a LiquidLUX vault event.
|
||||
type LiquidEvent struct {
|
||||
From string
|
||||
Amount *big.Int
|
||||
FeeType string // bytes32 hex (fees_received only)
|
||||
PerfFee *big.Int
|
||||
ToReserve *big.Int
|
||||
FromReserve *big.Int
|
||||
Socialized *big.Int
|
||||
Event string // "fees_received", "validator_rewards", "slashing", "emergency_withdrawal"
|
||||
Contract string
|
||||
Block uint64
|
||||
TxHash string
|
||||
}
|
||||
|
||||
// ParseLiquidEvents extracts LiquidLUX vault events from EVM logs.
|
||||
func ParseLiquidEvents(logs []Log) []LiquidEvent {
|
||||
var events []LiquidEvent
|
||||
|
||||
for i := range logs {
|
||||
l := &logs[i]
|
||||
if len(l.Topics) == 0 {
|
||||
continue
|
||||
}
|
||||
switch l.Topics[0] {
|
||||
case TopicFeesReceived:
|
||||
if e := parseFeesReceived(l); e != nil {
|
||||
events = append(events, *e)
|
||||
}
|
||||
case TopicValidatorRewardsReceived:
|
||||
if e := parseValidatorRewards(l); e != nil {
|
||||
events = append(events, *e)
|
||||
}
|
||||
case TopicSlashingApplied:
|
||||
events = append(events, LiquidEvent{
|
||||
Amount: decodeWord(l.Data, 0),
|
||||
FromReserve: decodeWord(l.Data, 1),
|
||||
Socialized: decodeWord(l.Data, 2),
|
||||
Event: "slashing",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
})
|
||||
case TopicEmergencyWithdrawal:
|
||||
if e := parseEmergencyWithdrawal(l); e != nil {
|
||||
events = append(events, *e)
|
||||
}
|
||||
}
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
func parseFeesReceived(l *Log) *LiquidEvent {
|
||||
// FeesReceived(address indexed from, uint256 amount, bytes32 indexed feeType, uint256 perfFee, uint256 toReserve)
|
||||
if len(l.Topics) < 3 {
|
||||
return nil
|
||||
}
|
||||
return &LiquidEvent{
|
||||
From: topicAddress(l.Topics[1]),
|
||||
Amount: decodeWord(l.Data, 0),
|
||||
FeeType: l.Topics[2],
|
||||
PerfFee: decodeWord(l.Data, 1),
|
||||
ToReserve: decodeWord(l.Data, 2),
|
||||
Event: "fees_received",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseValidatorRewards(l *Log) *LiquidEvent {
|
||||
// ValidatorRewardsReceived(address indexed from, uint256 amount)
|
||||
if len(l.Topics) < 2 {
|
||||
return nil
|
||||
}
|
||||
return &LiquidEvent{
|
||||
From: topicAddress(l.Topics[1]),
|
||||
Amount: decodeWord(l.Data, 0),
|
||||
Event: "validator_rewards",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseEmergencyWithdrawal(l *Log) *LiquidEvent {
|
||||
// EmergencyWithdrawal(address indexed to, uint256 amount)
|
||||
if len(l.Topics) < 2 {
|
||||
return nil
|
||||
}
|
||||
return &LiquidEvent{
|
||||
From: topicAddress(l.Topics[1]),
|
||||
Amount: decodeWord(l.Data, 0),
|
||||
Event: "emergency_withdrawal",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
package defi
|
||||
|
||||
import "math/big"
|
||||
|
||||
// Treasury event topic0 signatures (keccak256 hashes).
|
||||
// Source: contracts/treasury/Vault.sol, Router.sol, FeeGov.sol
|
||||
const (
|
||||
// Vault events
|
||||
// Receive(bytes32 indexed chain, uint256 amount, bytes32 warpId)
|
||||
TopicVaultReceive = "0x746cac8d52ab53d373654cb1bce8a77880a4fb35eba8502e644d45fb9a3544ad"
|
||||
|
||||
// Flush(bytes32 indexed chain, uint256 amount)
|
||||
TopicVaultFlush = "0xe033d0823ab017f8b7b1b1ef1d3581d03692445a7c735ce6a0ff5a08b4da24a1"
|
||||
|
||||
// FeeGov events
|
||||
// Rate(uint16 rate, uint32 version)
|
||||
TopicFeeGovRate = "0x3b973ff951e1f8921bccbc08ec81caee7ec5ce63a5dd396974dc6722bc3a7a2d"
|
||||
|
||||
// Chain(bytes32 indexed id, bool active)
|
||||
TopicFeeGovChain = "0x27f091fa3f90d453118174c469e669f3553b1d54c67aaf94ede31b8f0d04f48f"
|
||||
|
||||
// Broadcast(uint32 version, uint256 count)
|
||||
TopicFeeGovBroadcast = "0x4f63312ce57db5d707657e7ad8f4f74d1e3269b1ba60ee968c0c810713123928"
|
||||
|
||||
// Router events
|
||||
// Weight(address indexed recipient, uint256 weight)
|
||||
TopicRouterWeight = "0xf74e8b7d737d24757358039f81965e4a5b41873d22817a0a93a098bc4bdbb02a"
|
||||
|
||||
// Distribute(uint256 amount)
|
||||
TopicRouterDistribute = "0x4def474aca53bf221d07d9ab0f675b3f6d8d2494b8427271bcf43c018ef1eead"
|
||||
|
||||
// Claim(address indexed recipient, uint256 amount)
|
||||
TopicRouterClaim = "0x47cee97cb7acd717b3c0aa1435d004cd5b3c8c57d70dbceb4e4458bbd60e39d4"
|
||||
)
|
||||
|
||||
// TreasuryTopics returns all treasury event topic0 strings.
|
||||
func TreasuryTopics() []string {
|
||||
return []string{
|
||||
TopicVaultReceive, TopicVaultFlush,
|
||||
TopicFeeGovRate, TopicFeeGovChain, TopicFeeGovBroadcast,
|
||||
TopicRouterWeight, TopicRouterDistribute, TopicRouterClaim,
|
||||
}
|
||||
}
|
||||
|
||||
// VaultEvent represents a fee vault receive/flush event.
|
||||
type VaultEvent struct {
|
||||
Chain string // bytes32 hex
|
||||
Amount *big.Int
|
||||
WarpID string // bytes32 hex (receive only)
|
||||
Event string // "receive", "flush"
|
||||
Contract string
|
||||
Block uint64
|
||||
TxHash string
|
||||
}
|
||||
|
||||
// FeeGovEvent represents a FeeGov parameter change.
|
||||
type FeeGovEvent struct {
|
||||
Rate uint16
|
||||
Version uint32
|
||||
ChainID string // bytes32 hex
|
||||
Active bool
|
||||
Count *big.Int
|
||||
Event string // "rate", "chain", "broadcast"
|
||||
Contract string
|
||||
Block uint64
|
||||
TxHash string
|
||||
}
|
||||
|
||||
// RouterEvent represents a fee distribution event.
|
||||
type RouterEvent struct {
|
||||
Recipient string
|
||||
Amount *big.Int
|
||||
Weight *big.Int
|
||||
Event string // "weight", "distribute", "claim"
|
||||
Contract string
|
||||
Block uint64
|
||||
TxHash string
|
||||
}
|
||||
|
||||
// ParseTreasuryEvents extracts vault, fee governance, and router events from EVM logs.
|
||||
func ParseTreasuryEvents(logs []Log) ([]VaultEvent, []FeeGovEvent, []RouterEvent) {
|
||||
var vaults []VaultEvent
|
||||
var feegovs []FeeGovEvent
|
||||
var routers []RouterEvent
|
||||
|
||||
for i := range logs {
|
||||
l := &logs[i]
|
||||
if len(l.Topics) == 0 {
|
||||
continue
|
||||
}
|
||||
switch l.Topics[0] {
|
||||
case TopicVaultReceive:
|
||||
if e := parseVaultReceive(l); e != nil {
|
||||
vaults = append(vaults, *e)
|
||||
}
|
||||
case TopicVaultFlush:
|
||||
if e := parseVaultFlush(l); e != nil {
|
||||
vaults = append(vaults, *e)
|
||||
}
|
||||
case TopicFeeGovRate:
|
||||
if e := parseFeeGovRate(l); e != nil {
|
||||
feegovs = append(feegovs, *e)
|
||||
}
|
||||
case TopicFeeGovChain:
|
||||
if e := parseFeeGovChain(l); e != nil {
|
||||
feegovs = append(feegovs, *e)
|
||||
}
|
||||
case TopicFeeGovBroadcast:
|
||||
if e := parseFeeGovBroadcast(l); e != nil {
|
||||
feegovs = append(feegovs, *e)
|
||||
}
|
||||
case TopicRouterWeight:
|
||||
if e := parseRouterWeight(l); e != nil {
|
||||
routers = append(routers, *e)
|
||||
}
|
||||
case TopicRouterDistribute:
|
||||
routers = append(routers, RouterEvent{
|
||||
Amount: decodeWord(l.Data, 0),
|
||||
Event: "distribute",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
})
|
||||
case TopicRouterClaim:
|
||||
if e := parseRouterClaim(l); e != nil {
|
||||
routers = append(routers, *e)
|
||||
}
|
||||
}
|
||||
}
|
||||
return vaults, feegovs, routers
|
||||
}
|
||||
|
||||
func parseVaultReceive(l *Log) *VaultEvent {
|
||||
// Receive(bytes32 indexed chain, uint256 amount, bytes32 warpId)
|
||||
if len(l.Topics) < 2 {
|
||||
return nil
|
||||
}
|
||||
warpID := decodeWord(l.Data, 1)
|
||||
return &VaultEvent{
|
||||
Chain: l.Topics[1],
|
||||
Amount: decodeWord(l.Data, 0),
|
||||
WarpID: wordHex(warpID),
|
||||
Event: "receive",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseVaultFlush(l *Log) *VaultEvent {
|
||||
// Flush(bytes32 indexed chain, uint256 amount)
|
||||
if len(l.Topics) < 2 {
|
||||
return nil
|
||||
}
|
||||
return &VaultEvent{
|
||||
Chain: l.Topics[1],
|
||||
Amount: decodeWord(l.Data, 0),
|
||||
Event: "flush",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseFeeGovRate(l *Log) *FeeGovEvent {
|
||||
// Rate(uint16 rate, uint32 version)
|
||||
rate := decodeWord(l.Data, 0)
|
||||
ver := decodeWord(l.Data, 1)
|
||||
var r uint16
|
||||
var v uint32
|
||||
if rate != nil && rate.IsUint64() {
|
||||
r = uint16(rate.Uint64())
|
||||
}
|
||||
if ver != nil && ver.IsUint64() {
|
||||
v = uint32(ver.Uint64())
|
||||
}
|
||||
return &FeeGovEvent{
|
||||
Rate: r,
|
||||
Version: v,
|
||||
Event: "rate",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseFeeGovChain(l *Log) *FeeGovEvent {
|
||||
// Chain(bytes32 indexed id, bool active)
|
||||
if len(l.Topics) < 2 {
|
||||
return nil
|
||||
}
|
||||
active := decodeWord(l.Data, 0)
|
||||
return &FeeGovEvent{
|
||||
ChainID: l.Topics[1],
|
||||
Active: active != nil && active.Sign() > 0,
|
||||
Event: "chain",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseFeeGovBroadcast(l *Log) *FeeGovEvent {
|
||||
// Broadcast(uint32 version, uint256 count)
|
||||
ver := decodeWord(l.Data, 0)
|
||||
count := decodeWord(l.Data, 1)
|
||||
var v uint32
|
||||
if ver != nil && ver.IsUint64() {
|
||||
v = uint32(ver.Uint64())
|
||||
}
|
||||
return &FeeGovEvent{
|
||||
Version: v,
|
||||
Count: count,
|
||||
Event: "broadcast",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseRouterWeight(l *Log) *RouterEvent {
|
||||
// Weight(address indexed recipient, uint256 weight)
|
||||
if len(l.Topics) < 2 {
|
||||
return nil
|
||||
}
|
||||
return &RouterEvent{
|
||||
Recipient: topicAddress(l.Topics[1]),
|
||||
Weight: decodeWord(l.Data, 0),
|
||||
Event: "weight",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
|
||||
func parseRouterClaim(l *Log) *RouterEvent {
|
||||
// Claim(address indexed recipient, uint256 amount)
|
||||
if len(l.Topics) < 2 {
|
||||
return nil
|
||||
}
|
||||
return &RouterEvent{
|
||||
Recipient: topicAddress(l.Topics[1]),
|
||||
Amount: decodeWord(l.Data, 0),
|
||||
Event: "claim",
|
||||
Contract: l.Address,
|
||||
Block: l.BlockNumber,
|
||||
TxHash: l.TxHash,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Package dao provides resolvers for DAO operations.
|
||||
//
|
||||
// Indexes: DAO proposals, members, treasury actions.
|
||||
//
|
||||
// Entities: DAOProposal, DAOMember, DAOTreasury, DAOStats
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/luxfi/graph/storage"
|
||||
)
|
||||
|
||||
type ResolverFunc = func(context.Context, *storage.Store, map[string]interface{}) (interface{}, error)
|
||||
|
||||
func Register(resolvers map[string]ResolverFunc) {
|
||||
resolvers["daoProposal"] = resolveDAOProposal
|
||||
resolvers["daoProposals"] = resolveDAOProposals
|
||||
resolvers["daoMember"] = resolveDAOMember
|
||||
resolvers["daoMembers"] = resolveDAOMembers
|
||||
resolvers["daoTreasury"] = resolveDAOTreasury
|
||||
resolvers["daoTreasuries"] = resolveDAOTreasuries
|
||||
resolvers["daoStats"] = resolveDAOStats
|
||||
}
|
||||
|
||||
func resolveDAOProposal(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("DAOProposal", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("daoProposal requires id")
|
||||
}
|
||||
func resolveDAOProposals(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("DAOProposal", pl(args))
|
||||
}
|
||||
func resolveDAOMember(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("DAOMember", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("daoMember requires id")
|
||||
}
|
||||
func resolveDAOMembers(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("DAOMember", pl(args))
|
||||
}
|
||||
func resolveDAOTreasury(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("DAOTreasury", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("daoTreasury requires id")
|
||||
}
|
||||
func resolveDAOTreasuries(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("DAOTreasury", pl(args))
|
||||
}
|
||||
func resolveDAOStats(_ context.Context, s *storage.Store, _ map[string]interface{}) (interface{}, error) {
|
||||
return s.GetByType("DAOStats", "1")
|
||||
}
|
||||
|
||||
func pl(args map[string]interface{}) int {
|
||||
limit := 100
|
||||
if l, ok := args["first"]; ok { fmt.Sscanf(fmt.Sprint(l), "%d", &limit) }
|
||||
return limit
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Package derivatives provides resolvers for futures and options.
|
||||
//
|
||||
// Indexes: derivative contracts, settlements, option exercises.
|
||||
//
|
||||
// Entities: DerivativeContract, DerivativeSettlement, DerivativeExercise, DerivativeStats
|
||||
package derivatives
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/luxfi/graph/storage"
|
||||
)
|
||||
|
||||
type ResolverFunc = func(context.Context, *storage.Store, map[string]interface{}) (interface{}, error)
|
||||
|
||||
func Register(resolvers map[string]ResolverFunc) {
|
||||
resolvers["derivativeContract"] = resolveDerivativeContract
|
||||
resolvers["derivativeContracts"] = resolveDerivativeContracts
|
||||
resolvers["derivativeSettlement"] = resolveDerivativeSettlement
|
||||
resolvers["derivativeSettlements"] = resolveDerivativeSettlements
|
||||
resolvers["derivativeExercise"] = resolveDerivativeExercise
|
||||
resolvers["derivativeExercises"] = resolveDerivativeExercises
|
||||
resolvers["derivativeStats"] = resolveDerivativeStats
|
||||
}
|
||||
|
||||
func resolveDerivativeContract(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("DerivativeContract", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("derivativeContract requires id")
|
||||
}
|
||||
func resolveDerivativeContracts(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("DerivativeContract", pl(args))
|
||||
}
|
||||
func resolveDerivativeSettlement(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("DerivativeSettlement", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("derivativeSettlement requires id")
|
||||
}
|
||||
func resolveDerivativeSettlements(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("DerivativeSettlement", pl(args))
|
||||
}
|
||||
func resolveDerivativeExercise(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("DerivativeExercise", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("derivativeExercise requires id")
|
||||
}
|
||||
func resolveDerivativeExercises(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("DerivativeExercise", pl(args))
|
||||
}
|
||||
func resolveDerivativeStats(_ context.Context, s *storage.Store, _ map[string]interface{}) (interface{}, error) {
|
||||
return s.GetByType("DerivativeStats", "1")
|
||||
}
|
||||
|
||||
func pl(args map[string]interface{}) int {
|
||||
limit := 100
|
||||
if l, ok := args["first"]; ok { fmt.Sscanf(fmt.Sprint(l), "%d", &limit) }
|
||||
return limit
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Package did provides resolvers for the DID registry.
|
||||
//
|
||||
// Indexes: decentralized identifiers, delegates, attributes.
|
||||
//
|
||||
// Entities: DIDDocument, DIDDelegate, DIDAttribute, DIDStats
|
||||
package did
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/luxfi/graph/storage"
|
||||
)
|
||||
|
||||
type ResolverFunc = func(context.Context, *storage.Store, map[string]interface{}) (interface{}, error)
|
||||
|
||||
func Register(resolvers map[string]ResolverFunc) {
|
||||
resolvers["didDocument"] = resolveDIDDocument
|
||||
resolvers["didDocuments"] = resolveDIDDocuments
|
||||
resolvers["didDelegate"] = resolveDIDDelegate
|
||||
resolvers["didDelegates"] = resolveDIDDelegates
|
||||
resolvers["didAttribute"] = resolveDIDAttribute
|
||||
resolvers["didAttributes"] = resolveDIDAttributes
|
||||
resolvers["didStats"] = resolveDIDStats
|
||||
}
|
||||
|
||||
func resolveDIDDocument(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("DIDDocument", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("didDocument requires id")
|
||||
}
|
||||
func resolveDIDDocuments(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("DIDDocument", pl(args))
|
||||
}
|
||||
func resolveDIDDelegate(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("DIDDelegate", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("didDelegate requires id")
|
||||
}
|
||||
func resolveDIDDelegates(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("DIDDelegate", pl(args))
|
||||
}
|
||||
func resolveDIDAttribute(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("DIDAttribute", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("didAttribute requires id")
|
||||
}
|
||||
func resolveDIDAttributes(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("DIDAttribute", pl(args))
|
||||
}
|
||||
func resolveDIDStats(_ context.Context, s *storage.Store, _ map[string]interface{}) (interface{}, error) {
|
||||
return s.GetByType("DIDStats", "1")
|
||||
}
|
||||
|
||||
func pl(args map[string]interface{}) int {
|
||||
limit := 100
|
||||
if l, ok := args["first"]; ok { fmt.Sscanf(fmt.Sprint(l), "%d", &limit) }
|
||||
return limit
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Package governance provides resolvers for on-chain governance.
|
||||
//
|
||||
// Indexes: proposals, votes, delegations, governance parameters.
|
||||
//
|
||||
// Entities: Proposal, Vote, Delegation, GovernanceStats
|
||||
package governance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/luxfi/graph/storage"
|
||||
)
|
||||
|
||||
type ResolverFunc = func(context.Context, *storage.Store, map[string]interface{}) (interface{}, error)
|
||||
|
||||
func Register(resolvers map[string]ResolverFunc) {
|
||||
resolvers["proposal"] = resolveProposal
|
||||
resolvers["proposals"] = resolveProposals
|
||||
resolvers["vote"] = resolveVote
|
||||
resolvers["votes"] = resolveVotes
|
||||
resolvers["delegation"] = resolveDelegation
|
||||
resolvers["delegations"] = resolveDelegations
|
||||
resolvers["governanceStats"] = resolveGovernanceStats
|
||||
}
|
||||
|
||||
func resolveProposal(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("Proposal", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("proposal requires id")
|
||||
}
|
||||
func resolveProposals(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("Proposal", pl(args))
|
||||
}
|
||||
func resolveVote(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("Vote", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("vote requires id")
|
||||
}
|
||||
func resolveVotes(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("Vote", pl(args))
|
||||
}
|
||||
func resolveDelegation(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("Delegation", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("delegation requires id")
|
||||
}
|
||||
func resolveDelegations(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("Delegation", pl(args))
|
||||
}
|
||||
func resolveGovernanceStats(_ context.Context, s *storage.Store, _ map[string]interface{}) (interface{}, error) {
|
||||
return s.GetByType("GovernanceStats", "1")
|
||||
}
|
||||
|
||||
func pl(args map[string]interface{}) int {
|
||||
limit := 100
|
||||
if l, ok := args["first"]; ok { fmt.Sscanf(fmt.Sprint(l), "%d", &limit) }
|
||||
return limit
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Package liquid provides resolvers for liquid staking.
|
||||
//
|
||||
// Indexes: stakes, unstakes, rewards, validators.
|
||||
//
|
||||
// Entities: LiquidStake, LiquidUnstake, LiquidReward, LiquidValidator, LiquidStats
|
||||
package liquid
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/luxfi/graph/storage"
|
||||
)
|
||||
|
||||
type ResolverFunc = func(context.Context, *storage.Store, map[string]interface{}) (interface{}, error)
|
||||
|
||||
func Register(resolvers map[string]ResolverFunc) {
|
||||
resolvers["liquidStake"] = resolveLiquidStake
|
||||
resolvers["liquidStakes"] = resolveLiquidStakes
|
||||
resolvers["liquidUnstake"] = resolveLiquidUnstake
|
||||
resolvers["liquidUnstakes"] = resolveLiquidUnstakes
|
||||
resolvers["liquidReward"] = resolveLiquidReward
|
||||
resolvers["liquidRewards"] = resolveLiquidRewards
|
||||
resolvers["liquidValidator"] = resolveLiquidValidator
|
||||
resolvers["liquidValidators"] = resolveLiquidValidators
|
||||
resolvers["liquidStats"] = resolveLiquidStats
|
||||
}
|
||||
|
||||
func resolveLiquidStake(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("LiquidStake", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("liquidStake requires id")
|
||||
}
|
||||
func resolveLiquidStakes(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("LiquidStake", pl(args))
|
||||
}
|
||||
func resolveLiquidUnstake(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("LiquidUnstake", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("liquidUnstake requires id")
|
||||
}
|
||||
func resolveLiquidUnstakes(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("LiquidUnstake", pl(args))
|
||||
}
|
||||
func resolveLiquidReward(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("LiquidReward", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("liquidReward requires id")
|
||||
}
|
||||
func resolveLiquidRewards(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("LiquidReward", pl(args))
|
||||
}
|
||||
func resolveLiquidValidator(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("LiquidValidator", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("liquidValidator requires id")
|
||||
}
|
||||
func resolveLiquidValidators(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("LiquidValidator", pl(args))
|
||||
}
|
||||
func resolveLiquidStats(_ context.Context, s *storage.Store, _ map[string]interface{}) (interface{}, error) {
|
||||
return s.GetByType("LiquidStats", "1")
|
||||
}
|
||||
|
||||
func pl(args map[string]interface{}) int {
|
||||
limit := 100
|
||||
if l, ok := args["first"]; ok { fmt.Sscanf(fmt.Sprint(l), "%d", &limit) }
|
||||
return limit
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Package prediction provides resolvers for prediction markets.
|
||||
//
|
||||
// Indexes: markets, positions, resolutions.
|
||||
//
|
||||
// Entities: PredictionMarket, PredictionPosition, PredictionResolution, PredictionStats
|
||||
package prediction
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/luxfi/graph/storage"
|
||||
)
|
||||
|
||||
type ResolverFunc = func(context.Context, *storage.Store, map[string]interface{}) (interface{}, error)
|
||||
|
||||
func Register(resolvers map[string]ResolverFunc) {
|
||||
resolvers["predictionMarket"] = resolvePredictionMarket
|
||||
resolvers["predictionMarkets"] = resolvePredictionMarkets
|
||||
resolvers["predictionPosition"] = resolvePredictionPosition
|
||||
resolvers["predictionPositions"] = resolvePredictionPositions
|
||||
resolvers["predictionResolution"] = resolvePredictionResolution
|
||||
resolvers["predictionResolutions"] = resolvePredictionResolutions
|
||||
resolvers["predictionStats"] = resolvePredictionStats
|
||||
}
|
||||
|
||||
func resolvePredictionMarket(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("PredictionMarket", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("predictionMarket requires id")
|
||||
}
|
||||
func resolvePredictionMarkets(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("PredictionMarket", pl(args))
|
||||
}
|
||||
func resolvePredictionPosition(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("PredictionPosition", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("predictionPosition requires id")
|
||||
}
|
||||
func resolvePredictionPositions(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("PredictionPosition", pl(args))
|
||||
}
|
||||
func resolvePredictionResolution(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("PredictionResolution", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("predictionResolution requires id")
|
||||
}
|
||||
func resolvePredictionResolutions(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("PredictionResolution", pl(args))
|
||||
}
|
||||
func resolvePredictionStats(_ context.Context, s *storage.Store, _ map[string]interface{}) (interface{}, error) {
|
||||
return s.GetByType("PredictionStats", "1")
|
||||
}
|
||||
|
||||
func pl(args map[string]interface{}) int {
|
||||
limit := 100
|
||||
if l, ok := args["first"]; ok { fmt.Sscanf(fmt.Sprint(l), "%d", &limit) }
|
||||
return limit
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Package securities provides resolvers for security tokens.
|
||||
//
|
||||
// Indexes: issuances, transfers, dividends, compliance records.
|
||||
//
|
||||
// Entities: SecurityIssuance, SecurityTransfer, SecurityDividend, SecurityCompliance, SecurityStats
|
||||
package securities
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/luxfi/graph/storage"
|
||||
)
|
||||
|
||||
type ResolverFunc = func(context.Context, *storage.Store, map[string]interface{}) (interface{}, error)
|
||||
|
||||
func Register(resolvers map[string]ResolverFunc) {
|
||||
resolvers["securityIssuance"] = resolveSecurityIssuance
|
||||
resolvers["securityIssuances"] = resolveSecurityIssuances
|
||||
resolvers["securityTransfer"] = resolveSecurityTransfer
|
||||
resolvers["securityTransfers"] = resolveSecurityTransfers
|
||||
resolvers["securityDividend"] = resolveSecurityDividend
|
||||
resolvers["securityDividends"] = resolveSecurityDividends
|
||||
resolvers["securityCompliance"] = resolveSecurityCompliance
|
||||
resolvers["securityCompliances"] = resolveSecurityCompliances
|
||||
resolvers["securityStats"] = resolveSecurityStats
|
||||
}
|
||||
|
||||
func resolveSecurityIssuance(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("SecurityIssuance", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("securityIssuance requires id")
|
||||
}
|
||||
func resolveSecurityIssuances(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("SecurityIssuance", pl(args))
|
||||
}
|
||||
func resolveSecurityTransfer(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("SecurityTransfer", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("securityTransfer requires id")
|
||||
}
|
||||
func resolveSecurityTransfers(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("SecurityTransfer", pl(args))
|
||||
}
|
||||
func resolveSecurityDividend(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("SecurityDividend", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("securityDividend requires id")
|
||||
}
|
||||
func resolveSecurityDividends(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("SecurityDividend", pl(args))
|
||||
}
|
||||
func resolveSecurityCompliance(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("SecurityCompliance", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("securityCompliance requires id")
|
||||
}
|
||||
func resolveSecurityCompliances(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("SecurityCompliance", pl(args))
|
||||
}
|
||||
func resolveSecurityStats(_ context.Context, s *storage.Store, _ map[string]interface{}) (interface{}, error) {
|
||||
return s.GetByType("SecurityStats", "1")
|
||||
}
|
||||
|
||||
func pl(args map[string]interface{}) int {
|
||||
limit := 100
|
||||
if l, ok := args["first"]; ok { fmt.Sscanf(fmt.Sprint(l), "%d", &limit) }
|
||||
return limit
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Package treasury provides resolvers for on-chain treasury management.
|
||||
//
|
||||
// Indexes: deposits, withdrawals, allocations, budgets.
|
||||
//
|
||||
// Entities: TreasuryDeposit, TreasuryWithdrawal, TreasuryAllocation, TreasuryBudget, TreasuryStats
|
||||
package treasury
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/luxfi/graph/storage"
|
||||
)
|
||||
|
||||
type ResolverFunc = func(context.Context, *storage.Store, map[string]interface{}) (interface{}, error)
|
||||
|
||||
func Register(resolvers map[string]ResolverFunc) {
|
||||
resolvers["treasuryDeposit"] = resolveTreasuryDeposit
|
||||
resolvers["treasuryDeposits"] = resolveTreasuryDeposits
|
||||
resolvers["treasuryWithdrawal"] = resolveTreasuryWithdrawal
|
||||
resolvers["treasuryWithdrawals"] = resolveTreasuryWithdrawals
|
||||
resolvers["treasuryAllocation"] = resolveTreasuryAllocation
|
||||
resolvers["treasuryAllocations"] = resolveTreasuryAllocations
|
||||
resolvers["treasuryBudget"] = resolveTreasuryBudget
|
||||
resolvers["treasuryBudgets"] = resolveTreasuryBudgets
|
||||
resolvers["treasuryStats"] = resolveTreasuryStats
|
||||
}
|
||||
|
||||
func resolveTreasuryDeposit(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("TreasuryDeposit", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("treasuryDeposit requires id")
|
||||
}
|
||||
func resolveTreasuryDeposits(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("TreasuryDeposit", pl(args))
|
||||
}
|
||||
func resolveTreasuryWithdrawal(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("TreasuryWithdrawal", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("treasuryWithdrawal requires id")
|
||||
}
|
||||
func resolveTreasuryWithdrawals(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("TreasuryWithdrawal", pl(args))
|
||||
}
|
||||
func resolveTreasuryAllocation(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("TreasuryAllocation", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("treasuryAllocation requires id")
|
||||
}
|
||||
func resolveTreasuryAllocations(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("TreasuryAllocation", pl(args))
|
||||
}
|
||||
func resolveTreasuryBudget(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok { return s.GetByType("TreasuryBudget", fmt.Sprint(id)) }
|
||||
return nil, fmt.Errorf("treasuryBudget requires id")
|
||||
}
|
||||
func resolveTreasuryBudgets(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType("TreasuryBudget", pl(args))
|
||||
}
|
||||
func resolveTreasuryStats(_ context.Context, s *storage.Store, _ map[string]interface{}) (interface{}, error) {
|
||||
return s.GetByType("TreasuryStats", "1")
|
||||
}
|
||||
|
||||
func pl(args map[string]interface{}) int {
|
||||
limit := 100
|
||||
if l, ok := args["first"]; ok { fmt.Sscanf(fmt.Sprint(l), "%d", &limit) }
|
||||
return limit
|
||||
}
|
||||
Reference in New Issue
Block a user