feat: 8 new graph resolvers — governance, DAO, treasury, liquid, DID, prediction, securities, derivatives (84 tests)

This commit is contained in:
2026-04-12 11:34:18 -07:00
parent f85d32cd62
commit 72147a0672
17 changed files with 2679 additions and 2 deletions
+2 -2
View File
@@ -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
+10
View File
@@ -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
+779
View File
@@ -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])
}
})
}
}
+40
View File
@@ -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
View File
@@ -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)
}
+284
View File
@@ -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
}
+237
View File
@@ -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")
}
}
+129
View File
@@ -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,
}
}
+249
View File
@@ -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,
}
}
+56
View File
@@ -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
}
+56
View File
@@ -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
}
+56
View File
@@ -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
}
+56
View File
@@ -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
}
+65
View File
@@ -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
}
+56
View File
@@ -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
}
+65
View File
@@ -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
}
+65
View File
@@ -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
}