From de3fc12c8b647996fea3ec284b771d8446520375 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Jun 2026 09:54:20 +0000 Subject: [PATCH 1/6] Initial plan From 05107474a075744fc670d596f11fca2775f083b3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Jun 2026 09:55:56 +0000 Subject: [PATCH 2/6] Plan GoodDaoHouses outline --- .agents/skills/gooddollar/CONTRIBUTING.md | 121 ++ .agents/skills/gooddollar/SKILL.md | 215 ++++ .../contracts/BuyGDCloneFactory.abi.yaml | 205 ++++ .../BuyGDCloneFactory.selectors.yaml | 27 + .../contracts/BuyGDCloneV2.abi.yaml | 282 +++++ .../contracts/BuyGDCloneV2.selectors.yaml | 36 + .../contracts/CFAv1Forwarder.abi.yaml | 237 ++++ .../contracts/CFAv1Forwarder.selectors.yaml | 19 + .../ConstantFlowAgreementV1.abi.yaml | 560 +++++++++ .../ConstantFlowAgreementV1.selectors.yaml | 33 + .../contracts/GoodDollarOFTAdapter.abi.yaml | 100 ++ .../GoodDollarOFTAdapter.selectors.yaml | 12 + .../GooddollarSavingsStream.abi.yaml | 210 ++++ .../GooddollarSavingsStream.selectors.yaml | 27 + .../contracts/GovernanceStakingV2.abi.yaml | 202 ++++ .../GovernanceStakingV2.selectors.yaml | 26 + .../references/contracts/IdentityV3.abi.yaml | 390 +++++++ .../contracts/IdentityV3.selectors.yaml | 49 + .../references/contracts/IdentityV4.abi.yaml | 424 +++++++ .../contracts/IdentityV4.selectors.yaml | 51 + .../references/contracts/InvitesV2.abi.yaml | 333 ++++++ .../contracts/InvitesV2.selectors.yaml | 41 + .../references/contracts/MentoBroker.abi.yaml | 259 +++++ .../contracts/MentoBroker.selectors.yaml | 24 + .../contracts/MessagePassingBridge.abi.yaml | 542 +++++++++ .../MessagePassingBridge.selectors.yaml | 52 + .../references/contracts/NameService.abi.yaml | 104 ++ .../contracts/NameService.selectors.yaml | 11 + .../references/contracts/SuperToken.abi.yaml | 1033 +++++++++++++++++ .../contracts/SuperToken.selectors.yaml | 99 ++ .../references/contracts/Superfluid.abi.yaml | 757 ++++++++++++ .../contracts/Superfluid.selectors.yaml | 51 + .../references/contracts/UBISchemeV2.abi.yaml | 305 +++++ .../contracts/UBISchemeV2.selectors.yaml | 40 + .../contracts/_rich-abi-yaml-format.md | 93 ++ .../deep-researches/faucet-flows.md | 42 + .../fuse-to-celo-staking-migration.md | 38 + .../gooddao-daostack-surface.md | 37 + .../deep-researches/how-ubi-is-minted.md | 68 ++ .../inviter-invitee-reward-model.md | 58 + .../mento-reserve-economics.md | 27 + .../deep-researches/on-off-ramp-service.md | 69 ++ .../gooddollar/references/guides/bridge.md | 187 +++ .../references/guides/check-identity.md | 87 ++ .../gooddollar/references/guides/claim.md | 83 ++ .../gooddollar/references/guides/faucet.md | 76 ++ .../gooddollar/references/guides/gooddocs.md | 37 + .../gooddollar/references/guides/goodsdks.md | 110 ++ .../references/guides/hypersync-hyperrpc.md | 110 ++ .../references/guides/invite-bounties.md | 115 ++ .../migrate-fuse-staking-to-celo-savings.md | 116 ++ .../references/guides/on-off-ramp.md | 77 ++ .../gooddollar/references/guides/save.md | 93 ++ .../gooddollar/references/guides/stream.md | 135 +++ .../gooddollar/references/guides/swap.md | 83 ++ .../references/subgraphs/_query-patterns.md | 35 + .../subgraphs/goodcollective-guide.md | 54 + .../subgraphs/goodcollective.graphql | 200 ++++ .../subgraphs/gooddollar-celo-guide.md | 104 ++ .../subgraphs/gooddollar-celo.graphql | 113 ++ .../subgraphs/reserve-celo-guide.md | 41 + .../references/subgraphs/reserve-celo.graphql | 90 ++ skills-lock.json | 11 + 63 files changed, 9266 insertions(+) create mode 100644 .agents/skills/gooddollar/CONTRIBUTING.md create mode 100644 .agents/skills/gooddollar/SKILL.md create mode 100644 .agents/skills/gooddollar/references/contracts/BuyGDCloneFactory.abi.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/BuyGDCloneFactory.selectors.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/BuyGDCloneV2.abi.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/BuyGDCloneV2.selectors.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/CFAv1Forwarder.abi.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/CFAv1Forwarder.selectors.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/ConstantFlowAgreementV1.abi.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/ConstantFlowAgreementV1.selectors.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/GoodDollarOFTAdapter.abi.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/GoodDollarOFTAdapter.selectors.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/GooddollarSavingsStream.abi.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/GooddollarSavingsStream.selectors.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/GovernanceStakingV2.abi.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/GovernanceStakingV2.selectors.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/IdentityV3.abi.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/IdentityV3.selectors.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/IdentityV4.abi.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/IdentityV4.selectors.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/InvitesV2.abi.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/InvitesV2.selectors.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/MentoBroker.abi.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/MentoBroker.selectors.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/MessagePassingBridge.abi.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/MessagePassingBridge.selectors.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/NameService.abi.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/NameService.selectors.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/SuperToken.abi.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/SuperToken.selectors.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/Superfluid.abi.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/Superfluid.selectors.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/UBISchemeV2.abi.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/UBISchemeV2.selectors.yaml create mode 100644 .agents/skills/gooddollar/references/contracts/_rich-abi-yaml-format.md create mode 100644 .agents/skills/gooddollar/references/deep-researches/faucet-flows.md create mode 100644 .agents/skills/gooddollar/references/deep-researches/fuse-to-celo-staking-migration.md create mode 100644 .agents/skills/gooddollar/references/deep-researches/gooddao-daostack-surface.md create mode 100644 .agents/skills/gooddollar/references/deep-researches/how-ubi-is-minted.md create mode 100644 .agents/skills/gooddollar/references/deep-researches/inviter-invitee-reward-model.md create mode 100644 .agents/skills/gooddollar/references/deep-researches/mento-reserve-economics.md create mode 100644 .agents/skills/gooddollar/references/deep-researches/on-off-ramp-service.md create mode 100644 .agents/skills/gooddollar/references/guides/bridge.md create mode 100644 .agents/skills/gooddollar/references/guides/check-identity.md create mode 100644 .agents/skills/gooddollar/references/guides/claim.md create mode 100644 .agents/skills/gooddollar/references/guides/faucet.md create mode 100644 .agents/skills/gooddollar/references/guides/gooddocs.md create mode 100644 .agents/skills/gooddollar/references/guides/goodsdks.md create mode 100644 .agents/skills/gooddollar/references/guides/hypersync-hyperrpc.md create mode 100644 .agents/skills/gooddollar/references/guides/invite-bounties.md create mode 100644 .agents/skills/gooddollar/references/guides/migrate-fuse-staking-to-celo-savings.md create mode 100644 .agents/skills/gooddollar/references/guides/on-off-ramp.md create mode 100644 .agents/skills/gooddollar/references/guides/save.md create mode 100644 .agents/skills/gooddollar/references/guides/stream.md create mode 100644 .agents/skills/gooddollar/references/guides/swap.md create mode 100644 .agents/skills/gooddollar/references/subgraphs/_query-patterns.md create mode 100644 .agents/skills/gooddollar/references/subgraphs/goodcollective-guide.md create mode 100644 .agents/skills/gooddollar/references/subgraphs/goodcollective.graphql create mode 100644 .agents/skills/gooddollar/references/subgraphs/gooddollar-celo-guide.md create mode 100644 .agents/skills/gooddollar/references/subgraphs/gooddollar-celo.graphql create mode 100644 .agents/skills/gooddollar/references/subgraphs/reserve-celo-guide.md create mode 100644 .agents/skills/gooddollar/references/subgraphs/reserve-celo.graphql create mode 100644 skills-lock.json diff --git a/.agents/skills/gooddollar/CONTRIBUTING.md b/.agents/skills/gooddollar/CONTRIBUTING.md new file mode 100644 index 00000000..ab855d38 --- /dev/null +++ b/.agents/skills/gooddollar/CONTRIBUTING.md @@ -0,0 +1,121 @@ +# Contributing to GoodSkills + +This repository is an AI skill pack. The goal of each update is to make agent behavior more reliable, more explicit, and easier to audit. + +## Update workflow + +1. Define the user-facing problem first. +2. For contract-related updates, add or update Rich ABI first (`references/contracts/*.abi.yaml`), then refresh selectors. +3. Decide the remaining artifact types: + - `references/guides/` for "what to do" + - `references/deep-researches/` for "why it works this way" + - `scripts/` for deterministic and repeatable execution +4. Update `SKILL.md` routing so the new artifact is discoverable. +5. Validate consistency (paths, naming, links, selectors, assumptions). + +If a change touches contract behavior, treat Rich ABI update/add as mandatory first step before guides, deep-research, or scripts. + +## Add or update a guide + +Use guides for execution playbooks and operator workflows. + +Required structure: + +- title and one-line usage trigger +- `## Goal` +- `## Required inputs` +- `## Execution flow` as numbered steps +- deterministic snippet when execution is non-trivial +- failure handling and output contract + +Guide rules: + +- prefer explicit pre-checks before state-changing actions +- include only one primary workflow per file +- use [GoodProtocol `releases/deployment.json`](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) as the **only** source for contract addresses (rich ABI `meta.deployments` mirrors those rows); use GoodDocs for behavior and UX, not for resolving addresses; use on-chain `NameService.getAddress` only when the deployment documents the string key +- avoid implementation-deep theory; keep that in deep-research files + +After adding a guide: + +- add it to `SKILL.md` in `Guides` +- add an entry in `Use-case to guide map` + +## Add or update a deep-research note + +Use deep-research files for architecture, rationale, tradeoffs, and root-cause logic. + +Deep-research rules: + +- explain causality, not only API surfaces +- distinguish current behavior from legacy behavior +- link source contracts/docs for traceability +- keep language natural and decision-oriented + +Do not turn deep-research files into step-by-step runbooks; move operational steps into guides. + +## Add or update Rich ABI YAML + +Location: `references/contracts/`. + +For each contract: + +- create or update `Foo.abi.yaml` +- generate or refresh `Foo.selectors.yaml` +- include function-level notes for non-obvious behavior +- when `meta.deployments` lists concrete addresses, add **`creationBlock`** next to each **`address`** (placement: `references/contracts/_rich-abi-yaml-format.md`; using it as **`fromBlock`** for log or HyperSync fetches: `references/guides/hypersync-hyperrpc.md`) + +Minimum ABI documentation quality: + +- correct mutability, inputs, outputs +- access pattern (`owner`, `avatar`, `anyone`, etc.) where relevant +- emitted events and practical errors +- notes for routing/edge-case semantics + +Source-of-truth policy: + +- prefer canonical contract repos (GoodProtocol, GoodBridge, mento-core) +- avoid inferred behavior when source is unclear +- update notes when protocol behavior changed + +Selector generation: + +```bash +node scripts/selectors.mjs generate Foo.abi.yaml +``` + +## Add or update scripts + +Location: `scripts/`. + +Use scripts when: + +- a workflow is repeated +- deterministic output is needed +- manual querying is error-prone + +Script standards: + +- require inputs through env vars or explicit args +- fail loudly with actionable messages +- print structured output for easy reuse +- keep script intent narrow + +When a script supports a guide: + +- reference it from that guide +- document expected inputs and outputs in the guide + +## Naming and organization + +- use lowercase kebab-case for guides and deep-research files +- keep one topic per file +- avoid duplicate guidance across files +- prefer updating existing files over creating near-duplicates + +## Update checklist before merge + +- `SKILL.md` routing updated +- links resolve and point to public sources +- guides and deep-research files respect "what" vs "why" separation +- ABI + selectors pairs are in sync +- new behavior is reflected in notes where needed diff --git a/.agents/skills/gooddollar/SKILL.md b/.agents/skills/gooddollar/SKILL.md new file mode 100644 index 00000000..2d59116b --- /dev/null +++ b/.agents/skills/gooddollar/SKILL.md @@ -0,0 +1,215 @@ +--- +name: gooddollar +description: > + Knowledge base for GoodProtocol action execution and GoodDollar (G$) integrations. + Use this skill BEFORE ad-hoc web search for claim, save/stake, swap, bridge, + stream, and identity tasks. Prefer GoodDocs (https://docs.gooddollar.org/) for + narrative; contract addresses only from GoodProtocol releases/deployment.json. +metadata: + version: 1.0.0 +license: MIT +--- + +# GoodDollar Skill Pack + +Routing index for GoodProtocol. This repo complements [GoodDocs](https://docs.gooddollar.org/) for behavior and user flows. **Contract addresses** come only from [GoodProtocol/releases/deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) (and `meta.deployments` in `references/contracts/*.abi.yaml`, which mirror those rows)—not from GoodDocs pages. + +Repository maintenance and update process is documented in `CONTRIBUTING.md`. + +## Protocol snapshot (from GoodDocs) + +- G$ is reserve-backed; issuance and pricing tie to the reserve and bonding-curve mechanics described in [How GoodDollar works](https://docs.gooddollar.org/how-gooddollar-works). +- The stack is multi-chain; which contracts exist per environment is defined only in [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) (for example `GoodDollar`, `Identity`, `NameService`, `UBIScheme`, Mento keys, `MpbBridge`, and related entries under `production`, `production-celo`, and `production-xdc`). +- UBI is daily for verified users; identity verification and connected accounts are documented under [user guides](https://docs.gooddollar.org/user-guides). + +## Guides (single location for action playbooks) + +All task-specific instructions live under `references/guides/`. + +- `references/guides/claim.md` — daily UBI (`claim` / UBIScheme). +- `references/guides/save.md` — stake, rewards, unstake. +- `references/guides/swap.md` — buy or sell G$ (Mento on supported chains). +- `references/guides/bridge.md` — MessagePassingBridge (GoodDocs); optional OFT path via ABI refs. +- `references/guides/stream.md` — Superfluid streams (Celo-oriented in GoodDocs). +- `references/guides/check-identity.md` — whitelist and connected-address semantics. +- `references/guides/goodsdks.md` — SDK-first integration routing for GoodSDKs packages. +- `references/guides/gooddocs.md` — hub links to [GoodDocs](https://docs.gooddollar.org/). +- `references/guides/hypersync-hyperrpc.md` — Envio HyperSync/HyperRPC data-source routing for high-volume historical reads. +- `references/guides/faucet.md` — Faucet gas top-up execution flow and preflight checks. +- `references/guides/on-off-ramp.md` — stable-token ramp service flow into and out of G$. +- `references/guides/invite-bounties.md` — verify and execute inviter-invitee bounty payouts. +- `references/guides/migrate-fuse-staking-to-celo-savings.md` — migrate Fuse governance stake to CELO savings flow. + +## Subgraphs (indexed chain history) + +Use this folder with the same pattern as the protocol subgraph references: one `*-guide.md` plus one companion `.graphql` per deployment. + +For historical on-chain data, **start with the subgraph**: confirm the deployment covers the question (entities and fields in the guide, freshness via `_meta`). If the subgraph does not work for the request—missing schema coverage, stale or lagging indexing, query limits, or endpoint errors—**then** move to **HyperSync** or **HyperRPC** using `references/guides/hypersync-hyperrpc.md`. + +- `references/subgraphs/_query-patterns.md` — cross-cutting query discipline. +- `references/subgraphs/reserve-celo-guide.md` + `references/subgraphs/reserve-celo.graphql` — reserve pricing and swap history. +- `references/subgraphs/gooddollar-celo-guide.md` + `references/subgraphs/gooddollar-celo.graphql` — GoodDollar Celo schema discovery and starter probes. +- `references/subgraphs/goodcollective-guide.md` + `references/subgraphs/goodcollective.graphql` — GoodCollective schema discovery and starter probes. + +For Superfluid protocol subgraphs (streams, pools, vesting schedulers), see [Superfluid documentation](https://docs.superfluid.finance/) and [subgraph endpoints](https://subgraph-endpoints.superfluid.dev/). + +## Historical data routing policy (strict) + +1. Query subgraphs first for all historical/indexed requests. +2. Validate required entities and fields against the target subgraph schema and guide before declaring a gap. +3. Use **HyperSync** or **HyperRPC** fallback only when at least one of these is true: + - required entities or fields are not available in subgraph schema + - indexing lag makes subgraph data stale for the requested range + - query limits or endpoint instability block reliable retrieval +4. Do not start with HyperSync or HyperRPC when subgraph data is available and fresh. +5. HyperRPC fallback requires a valid Envio API key; if missing, **explicitly ask the user** to provide `HYPERRPC_API_TOKEN` or `ENVIO_API_TOKEN` (or paste a full `HYPERRPC_URL`); do not treat anonymous HyperRPC as production. +6. When **HyperSync** is the best option for the query and no Envio API token is available (`ENVIO_API_TOKEN` or equivalent per `references/guides/hypersync-hyperrpc.md`), **explicitly ask the user** to provide the token before proceeding; do not silently substitute anonymous HyperSync usage. +7. When fallback is used, report reason explicitly (schema gap, lag, or reliability issue). + +## Data source decision table + +| Query type | Primary source | Secondary source | Notes | +|---|---|---|---| +| Current on-chain state (latest balances, allowances, config, flags, view calls) | RPC | None | Use direct contract RPC reads for latest state. | +| Historical indexed entity data (time-series, aggregates, protocol entities, event-derived analytics) | Subgraph | HyperSync/HyperRPC | Prefer subgraph first; fall back when it cannot answer. | +| Historical raw on-chain data when subgraph is missing fields/entities or stale | HyperSync | HyperRPC | Prefer HyperSync for bulk scans and data pipelines. | +| Historical data for existing JSON-RPC integrations | HyperRPC | HyperSync | Use HyperRPC when strict JSON-RPC compatibility is required. | + +Decision rule: + +1. If request is current state -> use RPC. +2. If request is historical/indexed -> query subgraph first. +3. If subgraph cannot satisfy request -> fallback to HyperSync or HyperRPC per compatibility and scale needs. +4. HyperRPC fallback requires Envio API key credentials. +5. HyperSync client usage requires an Envio API token; if HyperSync is chosen and the token is missing, explicitly ask the user to provide it (see `references/guides/hypersync-hyperrpc.md`). + +## Mapping data retrieval rule + +Solidity mappings are not iterable on-chain by keyspace scan. Do not assume full-key enumeration is possible from RPC alone. + +When data is stored in mapping-like structures: + +1. Check contract source and ABI for key-discovery paths first: + - events emitted on set or update + - arrays, counters, linked lists, or index getters storing keys + - dedicated pagination or enumerable view functions +2. If key discovery exists, reconstruct key set from those sources and then read mapping entries. +3. If key discovery does not exist, report that complete iteration is not possible from chain state alone. +4. For historical reconstruction, prefer subgraph indexing first; if unavailable, use HyperSync or HyperRPC log scans with explicit limitations. + +## Use-case to guide map + +- Claim requests -> `references/guides/claim.md` +- Eligibility or connected-address questions -> `references/guides/check-identity.md` +- Stake, save, unstake -> `references/guides/save.md` +- Buy or sell G$ against reserve rails -> `references/guides/swap.md` +- Cross-chain bridge -> `references/guides/bridge.md` +- Stream management -> `references/guides/stream.md` +- SDK app integration tasks -> `references/guides/goodsdks.md` +- Bulk historical reads or data-engineering fetches -> `references/guides/hypersync-hyperrpc.md` +- Faucet top-up tasks -> `references/guides/faucet.md` +- On-/off-ramp service flow tasks -> `references/guides/on-off-ramp.md` +- Invite bounty eligibility and payout tasks -> `references/guides/invite-bounties.md` +- Fuse to CELO staking migration tasks -> `references/guides/migrate-fuse-staking-to-celo-savings.md` +- Indexed history, analytics, or GraphQL against GoodDollar subgraphs -> `references/subgraphs/_query-patterns.md` +- Historical on-chain fetch when subgraph data is insufficient -> subgraphs first, then HyperSync or HyperRPC per `references/guides/hypersync-hyperrpc.md`; if HyperSync is best and `ENVIO_API_TOKEN` is missing, ask the user for it explicitly. + +## Ambiguous prompts and incomplete inputs + +Stop and **ask the user** whenever the task is underspecified or required facts are missing. List what you need in short, concrete questions (for example chain, contract, address, amount, account, RPC or signer access, time or block range, prior tx hash, approval scope). + +- **Ambiguous** means the goal, environment, contract surface, or acceptance criteria are not clear enough to choose a safe path. +- **Incomplete** means you lack inputs that would change what you build, call, or sign next. + +**Do not invent** chain, address, amount, or policy details that affect correctness, funds, or eligibility. For **information-only** work you may state a single explicit assumption, label it, and ask the user to confirm or correct it before going further. + +**Execution work** (writing or editing runnable code, sending transactions, migrations, or anything that can move funds or alter on-chain state) has **no guessing**: settle every required input with the user, then implement or run. + +## Execution rules + +1. Collect missing required inputs before sending transactions. +2. Run pre-checks first (allowance, whitelist, quotes, bridge **amount** limits, peer wiring when using OFT paths). +3. If a pre-check fails, stop and return the exact corrective action. +4. Return tx hash and key output values. +5. Never fabricate addresses, amounts, or ABI behavior. +6. Resolve decimals and units per chain as in [How to integrate the G$ token](https://docs.gooddollar.org/for-developers/developer-guides/how-to-integrate-the-gusd-token) (for example 18 decimals on Celo, 2 on Fuse and Ethereum where applicable). + +## Pre-check matrix + +- Claim: verify identity whitelist status before `claim()`. +- Save or stake: verify balance and allowance before `stake()`. +- Swap: fetch quote, apply slippage bounds, verify allowance; confirm Mento contract keys for the active chain exist in `deployment.json` (for example `MentoBroker` under `production-celo` or `production-xdc`). +- Bridge (MessagePassingBridge): on the **source** chain approve G$ to the bridge; optionally preflight `canBridge(from, amount)` on that same contract (outbound `_bridgeTo` does not call it internally). For LZ use `estimateSendFee` with the **normalized** burn amount per `references/guides/bridge.md`, then `bridgeToWithLz` with nonzero `msg.value` for the **cross-chain transport** fee only (distinct from destination **`bridgeFees`** on minted G$; see **Bridge fee context** in that guide). Read **`bridgeLimits`** / daily trackers when debugging **amount** caps; see **Bridge amount limit context** in that guide. Respect `isClosed`, `LZ_FEE`, `MISSING_FEE`, and `UNSUPPORTED_CHAIN`. **Destination** mint applies `_enforceLimits` and can still revert. Use **Axelar** only when `toAxelarChainId` returns a route (implementation maps 1, 5, 42220, 44787); for Fuse or XDC style targets prefer LZ unless mapping is extended on-chain. +- Bridge (OFT adapter path): verify peer wiring and `quoteSend` fee data. +- Stream: confirm Celo (or documented Superfluid network) and correct Super Token and forwarder or host addresses. +- Identity: resolve Identity from NameService; remember connected addresses do not multiply daily claims ([connect wallet guide](https://docs.gooddollar.org/user-guides/connect-another-wallet-address-to-identity)). + +## Output format requirements + +For any state-changing action return: + +- network and key contract addresses used +- normalized input amounts and min or max guards +- tx hash +- key post-state output when available +- follow-up action if user intervention is required + +## Rich contract ABI references + +Convention: each `Foo.abi.yaml` has a companion `Foo.selectors.yaml` (function, event, and custom error selectors). Schema: `references/contracts/_rich-abi-yaml-format.md`. + +GoodDollar / Mento: + +- `references/contracts/NameService.abi.yaml` +- `references/contracts/IdentityV3.abi.yaml` +- `references/contracts/IdentityV4.abi.yaml` +- `references/contracts/InvitesV2.abi.yaml` +- `references/contracts/BuyGDCloneFactory.abi.yaml` +- `references/contracts/BuyGDCloneV2.abi.yaml` +- `references/contracts/GovernanceStakingV2.abi.yaml` +- `references/contracts/GooddollarSavingsStream.abi.yaml` (Ubeswap Superfluid stream savings; Celo deployment) +- `references/contracts/UBISchemeV2.abi.yaml` +- `references/contracts/MentoBroker.abi.yaml` +- `references/contracts/MessagePassingBridge.abi.yaml` +- `references/contracts/GoodDollarOFTAdapter.abi.yaml` +- `references/contracts/CFAv1Forwarder.abi.yaml` +- `references/contracts/ConstantFlowAgreementV1.abi.yaml` +- `references/contracts/Superfluid.abi.yaml` +- `references/contracts/SuperToken.abi.yaml` + +Superfluid (CFA, CFAv1Forwarder, Host, full ABI library): use [Superfluid docs](https://docs.superfluid.finance/), npm packages such as `@superfluid-finance/ethereum-contracts` and `@sfpro/sdk`, and contract ABIs published with those packages. + +## Deep researches + +- `references/deep-researches/on-off-ramp-service.md` +- `references/deep-researches/how-ubi-is-minted.md` +- `references/deep-researches/inviter-invitee-reward-model.md` +- `references/deep-researches/mento-reserve-economics.md` +- `references/deep-researches/gooddao-daostack-surface.md` +- `references/deep-researches/faucet-flows.md` +- `references/deep-researches/fuse-to-celo-staking-migration.md` + +## Revert debugging quick map + +- Identity or eligibility errors -> Identity and UBIScheme ABIs; live addresses from `deployment.json` only; GoodDocs for whitelist and claim behavior. +- Approval or transfer failures -> token approvals and balances; see integration guide for `transferAndCall` vs `approve` plus `transferFrom`. +- Swap bound failures -> quote freshness and slippage settings. +- MessagePassingBridge failures -> `canBridge`; **`BRIDGE_LIMITS`** (amount caps, whitelist, **`closed`**, and related policy strings); transport `msg.value` (`MISSING_FEE`, `LZ_FEE`) vs destination protocol fee (`bridgeFees`, `feeRecipient`); correct `bridgeTo` arguments; [Bridge GoodDollars](https://docs.gooddollar.org/user-guides/bridge-gooddollars). +- OFT path failures -> peer wiring and `quoteSend` fee data. +- Stream failures -> CFA forwarder or host agreement calls, buffer and flow-rate limits per Superfluid docs linked from GoodDocs. +- Faucet top-up failures -> `canTop`, `onlyAuthorized`, daily or weekly caps; `references/deep-researches/faucet-flows.md`. +- DAO-gated reverts -> caller is not avatar; scheme not registered; `references/deep-researches/gooddao-daostack-surface.md`. + +## Library usage discipline + +1. Open `references/guides/gooddocs.md` when unsure which GoodDocs page applies. +2. Start at this file to classify intent. +3. Open one guide under `references/guides/` unless the user requests a multi-step workflow. For subgraph or indexed-data tasks, start at `references/subgraphs/_query-patterns.md`. +4. Read only the ABI references and matching `.selectors.yaml` files needed for the chosen action. +5. Prefer GoodDocs for documented behavior; use only `deployment.json` (and rich ABI `meta.deployments` aligned with it) for contract addresses—never infer addresses from GoodDocs. +6. For large historical reads, prefer `references/guides/hypersync-hyperrpc.md` and choose HyperSync over HyperRPC unless strict JSON-RPC compatibility is required. +7. Historical data routing is strict: subgraphs first; HyperSync or HyperRPC only with an explicit fallback reason. +8. HyperRPC usage requires Envio API key credentials; when absent, **explicitly ask the user** for `HYPERRPC_API_TOKEN` or `ENVIO_API_TOKEN` (or a full `HYPERRPC_URL`) and do not attempt anonymous production flow. +9. When HyperSync is the best historical-data path and no Envio API token is available, explicitly ask the user to provide `ENVIO_API_TOKEN` (or the token your client expects) before continuing; see `references/guides/hypersync-hyperrpc.md`. +10. For subgraph tasks, validate field availability from the relevant `references/subgraphs/*-guide.md` and companion `.graphql` before guessing alternate entities. +11. For local shells repeating HyperRPC log pulls (for example last N whitelist events), from the **GoodSkills repository root** run `scripts/fetch-whitelist-events-hyperrpc.mjs` per `references/guides/hypersync-hyperrpc.md` instead of re-deriving JSON-RPC setup each time; that script ships with **defaults for production Celo** (HyperRPC host + `Identity` contract from `deployment.json`) and URL composition from `HYPERRPC_API_TOKEN` or `ENVIO_API_TOKEN` unless you override `CONTRACT_ADDRESS` / `HYPERRPC_URL`. HyperSync remains a separate client install path documented in the same guide. diff --git a/.agents/skills/gooddollar/references/contracts/BuyGDCloneFactory.abi.yaml b/.agents/skills/gooddollar/references/contracts/BuyGDCloneFactory.abi.yaml new file mode 100644 index 00000000..40226b3b --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/BuyGDCloneFactory.abi.yaml @@ -0,0 +1,205 @@ +meta: + name: BuyGDCloneFactory + version: "1" + source: + - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/contracts/utils/BuyGDClone.sol + note: > + Deterministic clone factory for BuyGDCloneV2 and DonateGDClone. Used to create per-owner + executors and optional create+swap flows for ramp services. + deployments: + mainnet: + production-celo: + BuyGDFactoryV2: + networkId: 42220 + address: "0x1F60C4C7037C6766924A43666B781ED1479587a2" + creationBlock: 22909812 + BuyGDFactory: + networkId: 42220 + address: "0x00e533B7d6255D05b7f15034B1c989c21F51b91C" + creationBlock: 21006324 + related: + - references/deep-researches/on-off-ramp-service.md + - references/guides/on-off-ramp.md + +create: + notes: + - "Deploys deterministic BuyGDCloneV2 clone for owner and initializes it." + - "Difference vs createAndSwap: this only deploys; no swap is executed." + mutability: nonpayable + access: anyone + inputs: + - owner: address + outputs: + - clone: address + +createDonation: + notes: + - "Deploys deterministic DonateGDClone with donation target and call payload." + - "Difference vs create: deploys donation-capable implementation instead of plain swap clone." + mutability: nonpayable + access: anyone + inputs: + - owner: address + - donateOrExecTo: address + - callData: bytes + outputs: + - clone: address + +createAndSwap: + notes: + - "One-transaction helper: create BuyGDCloneV2 then immediately call clone.swap(minAmount, msg.sender)." + - "Difference vs create: bundles deployment and first swap for faster ramp UX." + mutability: nonpayable + access: anyone + inputs: + - owner: address + - minAmount: uint256 + outputs: + - clone: address + +createDonationAndSwap: + notes: + - "One-transaction helper for DonateGDClone: deploy, then execute donation flow with optional pre-swap." + - "Difference vs createDonation: can execute immediate swap and/or target call instead of deployment only." + mutability: nonpayable + access: anyone + inputs: + - owner: address + - donateOrExecTo: address + - withSwap: bool + - minAmount: uint256 + - callData: bytes + outputs: + - clone: address + +predict: + notes: + - "Computes deterministic address for create(owner) salt." + mutability: view + inputs: + - owner: address + outputs: + - clone: address + +predictDonation: + notes: + - "Computes deterministic address for createDonation(owner, donateOrExecTo, callData) salt." + mutability: view + inputs: + - owner: address + - donateOrExecTo: address + - callData: bytes + outputs: + - clone: address + +getBaseFee: + mutability: view + inputs: [] + outputs: + - baseFee: uint256 + +quoter: + mutability: pure + inputs: [] + outputs: + - addr: address + +CUSD: + mutability: pure + inputs: [] + outputs: + - token: address + +celo: + mutability: pure + inputs: [] + outputs: + - token: address + +USDC: + mutability: pure + inputs: [] + outputs: + - token: address + +GLOUSD: + mutability: pure + inputs: [] + outputs: + - token: address + +PERIOD: + mutability: pure + inputs: [] + outputs: + - seconds: uint24 + +impl: + mutability: view + inputs: [] + outputs: + - addr: address + +donateImpl: + mutability: view + inputs: [] + outputs: + - addr: address + +gd: + mutability: view + inputs: [] + outputs: + - token: address + +stable: + mutability: view + inputs: [] + outputs: + - token: address + +oracle: + mutability: view + inputs: [] + outputs: + - addr: address + +router: + mutability: view + inputs: [] + outputs: + - addr: address + +mentoBroker: + mutability: view + inputs: [] + outputs: + - addr: address + +mentoExchangeProvider: + mutability: view + inputs: [] + outputs: + - addr: address + +mentoExchangeId: + mutability: view + inputs: [] + outputs: + - id: bytes32 + +events: + GDSwapToCusd: + indexed: [] + data: + - from: address + - to: address + - amountIn: uint256 + - amountOut: uint256 + - note: bytes + +errors: + NOT_GD_TOKEN: "onTokenTransfer caller is not G$ token." + INVALID_TWAP: "TWAP validation failed." + RECIPIENT_ZERO: "Recipient cannot be zero address." + ZERO_MINAMOUNT: "Minimum amount cannot be zero." diff --git a/.agents/skills/gooddollar/references/contracts/BuyGDCloneFactory.selectors.yaml b/.agents/skills/gooddollar/references/contracts/BuyGDCloneFactory.selectors.yaml new file mode 100644 index 00000000..16b13f22 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/BuyGDCloneFactory.selectors.yaml @@ -0,0 +1,27 @@ +# Generated by scripts/selectors.mjs +functions: + create(address): 0x9ed93318 + createDonation(address,address,bytes): 0x48199e6b + createAndSwap(address,uint256): 0x89643a29 + createDonationAndSwap(address,address,bool,uint256,bytes): 0xeb5621b8 + predict(address): 0x901b96e7 + predictDonation(address,address,bytes): 0x3895de30 + getBaseFee(): 0x15e812ad + quoter(): 0xc6bbd5a7 + CUSD(): 0x758316c9 + celo(): 0x051ed8ef + USDC(): 0x89a30271 + GLOUSD(): 0x4b5b02d6 + PERIOD(): 0xb4d1d795 + impl(): 0x8abf6077 + donateImpl(): 0x21fc2eef + gd(): 0xa5e598fc + stable(): 0x22be3de1 + oracle(): 0x7dc0d1d0 + router(): 0xf887ea40 + mentoBroker(): 0x7b89f117 + mentoExchangeProvider(): 0x4f62feec + mentoExchangeId(): 0xd373b333 +events: + GDSwapToCusd(address,address,uint256,uint256,bytes): 0x252bc23e3fb01f9986fa157af621236fb8a706ea12622da296e7f2f30d4f1a56 +errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/BuyGDCloneV2.abi.yaml b/.agents/skills/gooddollar/references/contracts/BuyGDCloneV2.abi.yaml new file mode 100644 index 00000000..5f35ee91 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/BuyGDCloneV2.abi.yaml @@ -0,0 +1,282 @@ +meta: + name: BuyGDCloneV2 + version: "2" + source: + - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/contracts/utils/BuyGDClone.sol + inherits: + - Initializable + note: > + Clone implementation used by BuyGDCloneFactory for deterministic per-owner swap executors. + Supports CELO and cUSD flows, with cUSD route selection between Uniswap and Mento when configured. + deployments: + mainnet: + production-celo: + BuyGDFactoryV2: + networkId: 42220 + address: "0x1F60C4C7037C6766924A43666B781ED1479587a2" + creationBlock: 22909812 + related: + - references/deep-researches/on-off-ramp-service.md + - references/guides/on-off-ramp.md + +initialize: + mutability: nonpayable + access: initializer + inputs: + - _owner: address + outputs: [] + +getSwapPath: + mutability: pure + inputs: + - tokens: address[] + - fees: uint24[] + outputs: + - path: bytes + +swap: + notes: + - "Dispatcher entrypoint: if clone holds native CELO balance it executes CELO route, otherwise uses cUSD route." + - "Difference vs swapCelo/swapCusd: this function chooses route by contract balances; specialized functions force a specific asset path." + mutability: payable + access: anyone + inputs: + - _minAmount: uint256 + - refundGas: address + outputs: + - bought: uint256 + emits: [Bought] + errors: [NO_BALANCE] + +swapCelo: + notes: + - "CELO-only convenience path using the default preconfigured Uniswap route." + - "Difference vs swapCeloWithPath: same Uniswap execution, but caller cannot override token path and fee tiers." + - "Difference vs cUSD paths: CELO path does not use Mento route selection." + mutability: payable + access: anyone + inputs: + - _minAmount: uint256 + - refundGas: address + outputs: + - bought: uint256 + emits: [BoughtFromUniswap] + errors: [REFUND_FAILED] + +swapCeloWithPath: + notes: + - "CELO-only path with caller-supplied Uniswap multi-hop route." + - "Difference vs swapCelo: allows custom path optimization when default route is not preferred." + - "Difference vs cUSD paths: still Uniswap-only and does not compare against Mento." + mutability: payable + access: anyone + inputs: + - _minAmount: uint256 + - refundGas: address + - _path: (address[],uint24[]) + outputs: + - bought: uint256 + emits: [BoughtFromUniswap] + errors: [REFUND_FAILED] + +swapCusd: + notes: + - "cUSD-only convenience path that compares expected output from default-path Uniswap and Mento, then executes the better quote." + - "Difference vs swapCusdWithPath: this uses hardcoded default Uniswap path for comparison." + - "Difference vs CELO paths: includes dual-route best-execution logic with optional Mento." + mutability: nonpayable + access: anyone + inputs: + - _minAmount: uint256 + - refundGas: address + outputs: + - bought: uint256 + emits: [BoughtFromMento, BoughtFromUniswap] + errors: [MENTO_NOT_CONFIGURED] + +swapCusdWithPath: + notes: + - "cUSD path with caller-supplied Uniswap route; still compares custom Uniswap quote against Mento quote and picks larger expected output." + - "Difference vs swapCusd: custom path changes only the Uniswap side of the comparison." + - "Difference vs swapCeloWithPath: this function performs route competition (Uniswap vs Mento), not only route customization." + mutability: nonpayable + access: anyone + inputs: + - _minAmount: uint256 + - refundGas: address + - _path: (address[],uint24[]) + outputs: + - bought: uint256 + emits: [BoughtFromMento, BoughtFromUniswap] + errors: [MENTO_NOT_CONFIGURED] + +getExpectedReturnFromUniswapPath: + notes: + - "Quote helper for Uniswap path expected output used by cUSD route selection and preflight checks." + mutability: nonpayable + inputs: + - amountIn: uint256 + - _path: (address[],uint24[]) + outputs: + - expectedReturn: uint256 + +getExpectedReturnFromMento: + notes: + - "Quote helper for Mento expected output for cUSD->G$ used in best-route decision." + mutability: view + inputs: + - cusdAmount: uint256 + outputs: + - expectedReturn: uint256 + errors: [MENTO_NOT_CONFIGURED] + +minAmountByTWAP: + mutability: view + inputs: + - baseAmount: uint256 + - baseToken: address + - period: uint32 + outputs: + - minTwap: uint256 + - quote: uint256 + +recover: + mutability: nonpayable + access: anyone + inputs: + - token: address + outputs: [] + errors: [REFUND_FAILED] + +router: + mutability: view + inputs: [] + outputs: + - addr: address + +celo: + mutability: pure + inputs: [] + outputs: + - token: address + +CUSD: + mutability: pure + inputs: [] + outputs: + - token: address + +USDC: + mutability: pure + inputs: [] + outputs: + - token: address + +GLOUSD: + mutability: pure + inputs: [] + outputs: + - token: address + +GD_FEE_TIER: + mutability: pure + inputs: [] + outputs: + - tier: uint24 + +CUSD_STABLE_FEE_TIER: + mutability: pure + inputs: [] + outputs: + - tier: uint24 + +CELO_STABLE_FEE_TIER: + mutability: pure + inputs: [] + outputs: + - tier: uint24 + +twapPeriod: + mutability: view + inputs: [] + outputs: + - period: uint32 + +stable: + mutability: view + inputs: [] + outputs: + - token: address + +gd: + mutability: view + inputs: [] + outputs: + - token: address + +oracle: + mutability: view + inputs: [] + outputs: + - addr: address + +quoter: + mutability: view + inputs: [] + outputs: + - addr: address + +mentoBroker: + mutability: view + inputs: [] + outputs: + - addr: address + +mentoExchangeProvider: + mutability: view + inputs: [] + outputs: + - addr: address + +mentoExchangeId: + mutability: view + inputs: [] + outputs: + - id: bytes32 + +owner: + mutability: view + inputs: [] + outputs: + - addr: address + +CUSD_GAS_COSTS: + mutability: pure + inputs: [] + outputs: + - amount: uint256 + +events: + Bought: + indexed: [] + data: + - inToken: address + - inAmount: uint256 + - outAmount: uint256 + BoughtFromMento: + indexed: [] + data: + - inToken: address + - inAmount: uint256 + - outAmount: uint256 + BoughtFromUniswap: + indexed: [] + data: + - inToken: address + - inAmount: uint256 + - outAmount: uint256 + +errors: + REFUND_FAILED: "Refund call failed." + NO_BALANCE: "No CELO or cUSD available on clone." + MENTO_NOT_CONFIGURED: "Mento broker path not configured." diff --git a/.agents/skills/gooddollar/references/contracts/BuyGDCloneV2.selectors.yaml b/.agents/skills/gooddollar/references/contracts/BuyGDCloneV2.selectors.yaml new file mode 100644 index 00000000..1476ae35 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/BuyGDCloneV2.selectors.yaml @@ -0,0 +1,36 @@ +# Generated by scripts/selectors.mjs +functions: + initialize(address): 0xc4d66de8 + getSwapPath(address[],uint24[]): 0xf0036d5d + swap(uint256,address): 0xd3986f08 + swapCelo(uint256,address): 0xaa6bfa9d + swapCeloWithPath(uint256,address,(address[],uint24[])): 0x81b7d2e0 + swapCusd(uint256,address): 0xb1a5fa9f + swapCusdWithPath(uint256,address,(address[],uint24[])): 0x170ba915 + getExpectedReturnFromUniswapPath(uint256,(address[],uint24[])): 0x12194320 + getExpectedReturnFromMento(uint256): 0xdbb15eb2 + minAmountByTWAP(uint256,address,uint32): 0x821dc910 + recover(address): 0x0cd865ec + router(): 0xf887ea40 + celo(): 0x051ed8ef + CUSD(): 0x758316c9 + USDC(): 0x89a30271 + GLOUSD(): 0x4b5b02d6 + GD_FEE_TIER(): 0xe00e8fdd + CUSD_STABLE_FEE_TIER(): 0xba428926 + CELO_STABLE_FEE_TIER(): 0x60e4bf4b + twapPeriod(): 0xf6207326 + stable(): 0x22be3de1 + gd(): 0xa5e598fc + oracle(): 0x7dc0d1d0 + quoter(): 0xc6bbd5a7 + mentoBroker(): 0x7b89f117 + mentoExchangeProvider(): 0x4f62feec + mentoExchangeId(): 0xd373b333 + owner(): 0x8da5cb5b + CUSD_GAS_COSTS(): 0x32f90ac3 +events: + Bought(address,uint256,uint256): 0xa9a40dec7a304e5915d11358b968c1e8d365992abf20f82285d1df1b30c8e24c + BoughtFromMento(address,uint256,uint256): 0x8e2ac24d7ef5662ee242823a19dbd1c952b3e96ae127228f4bbce83e2816e3fb + BoughtFromUniswap(address,uint256,uint256): 0xdb1f2a6cbbfd964f19c648b140a35992aa80f482df8f519d3211d0bd86c9f335 +errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/CFAv1Forwarder.abi.yaml b/.agents/skills/gooddollar/references/contracts/CFAv1Forwarder.abi.yaml new file mode 100644 index 00000000..32ba0de5 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/CFAv1Forwarder.abi.yaml @@ -0,0 +1,237 @@ +# CFAv1Forwarder — convenience wrapper for ConstantFlowAgreementV1 +# Allows direct interaction with CFA functions without manually routing through +# the Host's batchCall / forwardBatchCall. Operates as a trusted forwarder, +# preserving msg.sender via EIP-2771. +# +# Each forwarder call is a standalone transaction — forwarder calls cannot be +# combined in a Host.batchCall. To batch multiple operations atomically (e.g. +# wrap tokens + create stream), use Host.batchCall with the raw CFA agreement +# (operationType 201). See Superfluid.abi.yaml for batch operation details. +# Tradeoff: forwarder calls produce human-readable descriptions in wallets +# (e.g. "setFlowrate(token, receiver, flowrate)"), while Host.batchCall +# shows encoded tuples that are difficult for users to verify. +# +# This contract has no events — all events are emitted by the underlying CFA. +# Errors from the CFA (and Host) propagate through to the caller. + +meta: + name: CFAv1Forwarder + version: v1 + source: + - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/utils/CFAv1Forwarder.sol + - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/utils/ForwarderBase.sol + inherits: [ForwarderBase] + deployments: + # Same address on all networks except avalanche-fuji + mainnet: + eth-mainnet: "0xcfA132E353cB4E398080B9700609bb008eceB125" + polygon-mainnet: "0xcfA132E353cB4E398080B9700609bb008eceB125" + xdai-mainnet: "0xcfA132E353cB4E398080B9700609bb008eceB125" + base-mainnet: "0xcfA132E353cB4E398080B9700609bb008eceB125" + optimism-mainnet: "0xcfA132E353cB4E398080B9700609bb008eceB125" + arbitrum-one: "0xcfA132E353cB4E398080B9700609bb008eceB125" + bsc-mainnet: "0xcfA132E353cB4E398080B9700609bb008eceB125" + avalanche-c: "0xcfA132E353cB4E398080B9700609bb008eceB125" + celo-mainnet: "0xcfA132E353cB4E398080B9700609bb008eceB125" + scroll-mainnet: "0xcfA132E353cB4E398080B9700609bb008eceB125" + degenchain: "0xcfA132E353cB4E398080B9700609bb008eceB125" + testnet: + avalanche-fuji: "0x2CDd45c5182602a36d391F7F16DD9f8386C3bD8D" + base-sepolia: "0xcfA132E353cB4E398080B9700609bb008eceB125" + eth-sepolia: "0xcfA132E353cB4E398080B9700609bb008eceB125" + optimism-sepolia: "0xcfA132E353cB4E398080B9700609bb008eceB125" + scroll-sepolia: "0xcfA132E353cB4E398080B9700609bb008eceB125" + deploymentCreationBlocks: + mainnet: + celo-mainnet: 17404156 + +# == Glossary == +# flowrate — token transfer rate in wad/second (int96) +# flowOperator — account authorized to create/update/delete flows on behalf of another +# buffer — deposit locked as solvency collateral while a flow is active +# permissions — bitmask of create | update | delete rights for an operator +# flowrateAllowance — max flowrate an operator may set per individual flow + +# == Flow Management == +# High-level functions that automatically create, update, or delete flows as needed. + +setFlowrate: + # Smart setter: creates a new flow if none exists, updates if flowrate changed, + # deletes if flowrate is zero. No-ops if the current rate already matches. + notes: + - "Gotcha: Negative flowrate values revert with CFA_FWD_INVALID_FLOW_RATE." + mutability: nonpayable + access: anyone # flow from msg.sender + inputs: + - token: address + - receiver: address + - flowrate: int96 # must be >= 0 + outputs: + - bool + errors: [CFA_FWD_INVALID_FLOW_RATE] + +setFlowrateFrom: + # Same as setFlowrate but can be called by a flow operator on behalf of `sender`. + # msg.sender must have sufficient operator permissions and flowrateAllowance. + mutability: nonpayable + access: sender | operator + inputs: + - token: address + - sender: address + - receiver: address + - flowrate: int96 + outputs: + - bool + errors: [CFA_FWD_INVALID_FLOW_RATE] + +# == Low-Level Flow Operations == +# Direct wrappers around CFA functions. These give full control (including userData) +# but require the caller to know whether a flow already exists. +# If sender != msg.sender, the *ByOperator variant is used internally. + +createFlow: + # Create a new flow. Reverts if a flow already exists between sender and receiver. + mutability: nonpayable + access: sender | operator + inputs: + - token: address + - sender: address + - receiver: address + - flowrate: int96 + - userData: bytes + outputs: + - bool + +updateFlow: + # Update an existing flow's rate. Reverts if no flow exists. + mutability: nonpayable + access: sender | operator + inputs: + - token: address + - sender: address + - receiver: address + - flowrate: int96 + - userData: bytes + outputs: + - bool + +deleteFlow: + # Delete an existing flow. Can be called by sender, receiver, or an operator. + # If msg.sender is neither sender nor receiver, deleteFlowByOperator is used. + mutability: nonpayable + access: sender | receiver | operator + inputs: + - token: address + - sender: address + - receiver: address + - userData: bytes + outputs: + - bool + +# == ACL (Operator Permissions) == + +grantPermissions: + # Grant full create/update/delete permissions with max flowrateAllowance to an operator. + # Convenience wrapper — equivalent to updateFlowOperatorPermissions with full control. + mutability: nonpayable + access: anyone # grants on msg.sender's flows + inputs: + - token: address + - flowOperator: address + outputs: + - bool + +revokePermissions: + # Revoke all permissions from an operator. Does not affect existing flows. + mutability: nonpayable + access: anyone # revokes on msg.sender's flows + inputs: + - token: address + - flowOperator: address + outputs: + - bool + +updateFlowOperatorPermissions: + # Set granular operator permissions and flowrate allowance. + notes: + - "Gotcha: flowrateAllowance limits per-flow rate, NOT aggregate net flow." + mutability: nonpayable + access: anyone # grants on msg.sender's flows + inputs: + - token: address + - flowOperator: address + - permissions: uint8 # bitmask: 1=create, 2=update, 4=delete + - flowrateAllowance: int96 + outputs: + - bool + +# == Flow Queries == + +getFlowrate: + mutability: view + inputs: + - token: address + - sender: address + - receiver: address + outputs: + - flowrate: int96 + +getFlowInfo: + mutability: view + inputs: + - token: address + - sender: address + - receiver: address + outputs: + - lastUpdated: uint256 + - flowrate: int96 + - deposit: uint256 + - owedDeposit: uint256 + +getBufferAmountByFlowrate: + # Returns the deposit/buffer required for a given flowrate. + notes: + - "Gotcha: This value is governance-configurable and may change over time. Changes only affect newly created/updated flows." + mutability: view + inputs: + - token: address + - flowrate: int96 + outputs: + - bufferAmount: uint256 + +getAccountFlowrate: + # Net aggregate flowrate for an account (incoming minus outgoing). + mutability: view + inputs: + - token: address + - account: address + outputs: + - flowrate: int96 + +getAccountFlowInfo: + mutability: view + inputs: + - token: address + - account: address + outputs: + - lastUpdated: uint256 + - flowrate: int96 + - deposit: uint256 + - owedDeposit: uint256 + +# == ACL Queries == + +getFlowOperatorPermissions: + mutability: view + inputs: + - token: address + - sender: address + - flowOperator: address + outputs: + - permissions: uint8 + - flowrateAllowance: int96 + +# == Errors == + +errors: + - CFA_FWD_INVALID_FLOW_RATE # flowrate argument was negative diff --git a/.agents/skills/gooddollar/references/contracts/CFAv1Forwarder.selectors.yaml b/.agents/skills/gooddollar/references/contracts/CFAv1Forwarder.selectors.yaml new file mode 100644 index 00000000..f98e21ea --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/CFAv1Forwarder.selectors.yaml @@ -0,0 +1,19 @@ +# Generated by scripts/selectors.mjs +functions: + setFlowrate(address,address): 0x2f57fb6f + setFlowrateFrom(address,address,address,int96): 0xc5ad5c1a + createFlow(address,address,address,int96,bytes): 0xe15536b6 + updateFlow(address,address,address,int96,bytes): 0x0c033991 + deleteFlow(address,address,address,bytes): 0xb4b333c6 + grantPermissions(address,address): 0x7243fb93 + revokePermissions(address,address): 0x0bd0728d + updateFlowOperatorPermissions(address,address): 0x42294caf + getFlowrate(address,address,address): 0x1d8b6526 + getFlowInfo(address,address,address): 0x2860fd93 + getBufferAmountByFlowrate(address,int96): 0x09f0b495 + getAccountFlowrate(address,address): 0x22c904d9 + getAccountFlowInfo(address,address): 0x0f1ac495 + getFlowOperatorPermissions(address,address,address): 0x4d3f60f9 +events: +{} +errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/ConstantFlowAgreementV1.abi.yaml b/.agents/skills/gooddollar/references/contracts/ConstantFlowAgreementV1.abi.yaml new file mode 100644 index 00000000..afaec5dc --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/ConstantFlowAgreementV1.abi.yaml @@ -0,0 +1,560 @@ +# Superfluid Constant Flow Agreement (CFA) v1 +# Manages continuous per-second token streams between accounts. +# +# NOTE: emits/errors mappings are traced from source code — verify against implementation. +# Proxy/upgradability functions (castrate, updateCode, getCodeAddress, proxiableUUID) +# are omitted — they belong to the UUPSProxiable / AgreementBase layer. +# Pure helpers addPermissions/removePermissions are omitted — they were made public +# for testability only and are not part of the protocol interface. + +meta: + name: ConstantFlowAgreementV1 + version: v1 + source: + - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol + - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/interfaces/agreements/IConstantFlowAgreementV1.sol + - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/agreements/AgreementBase.sol + implements: [IConstantFlowAgreementV1, ISuperAgreement] + inherits: [AgreementBase] + deployments: + mainnet: + eth-mainnet: "0x2844c1BBdA121E9E43105630b9C8310e5c72744b" + polygon-mainnet: "0x6EeE6060f715257b970700bc2656De21dEdF074C" + xdai-mainnet: "0xEbdA4ceF883A7B12c4E669Ebc58927FBa8447C7D" + base-mainnet: "0x19ba78B9cDB05A877718841c574325fdB53601bb" + optimism-mainnet: "0x204C6f131bb7F258b2Ea1593f5309911d8E458eD" + arbitrum-one: "0x731FdBB12944973B500518aea61942381d7e240D" + bsc-mainnet: "0x49c38108870e74Cb9420C0991a85D3edd6363F75" + avalanche-c: "0x6946c5B38Ffea373b0a2340b4AEf0De8F6782e58" + celo-mainnet: "0x9d369e78e1a682cE0F8d9aD849BeA4FE1c3bD3Ad" + scroll-mainnet: "0xB3bcD6da1eeB6c97258B3806A853A6dcD3B6C00c" + degenchain: "0x82cc052d1b17aC554a22A88D5876B56c6b51e95c" + testnet: + avalanche-fuji: "0x16843ac25Ccc58Aa7960ba05f61cBB17b36b130A" + base-sepolia: "0x6836F23d6171D74Ef62FcF776655aBcD2bcd62Ef" + eth-sepolia: "0x6836F23d6171D74Ef62FcF776655aBcD2bcd62Ef" + optimism-sepolia: "0x8a3170AdbC67233196371226141736E4151e7C26" + scroll-sepolia: "0xbc46B4Aa41c055578306820013d4B65fff42711E" + deploymentCreationBlocks: + mainnet: + celo-mainnet: 16393492 + +# == Abbreviations == +# 3Ps — Three Periods: liquidation, patrician, and pleb (solvency model) +# ACL — Access Control List (flow operator permissions) +# CFA — Constant Flow Agreement +# ctx — context (Superfluid call context bytes, carries msg.sender and userData) +# GDA — General Distribution Agreement (the other Superfluid agreement) +# PPP — Patrician-Pleb-Pirate (the 3Ps solvency periods) + +# == Glossary == +# flow / stream — continuous per-second token transfer, used interchangeably +# flowRate — tokens per second (int96, wei-denominated); positive = outgoing +# deposit — buffer locked when opening a flow; protects against insolvency +# owed deposit — portion of deposit owed back to the sender by a Super App receiver +# flow operator — an address authorized to create/update/delete flows on behalf of a sender +# flow rate allowance — maximum net flow rate an operator may allocate (int96); decrements on use +# permissions bitmask — uint8: bit 0 = create (1), bit 1 = update (2), bit 2 = delete (4) +# liquidation — closing an insolvent sender's flow; rewards go to the liquidator or bond +# patrician period — grace window after insolvency where reward goes to the bond account +# pleb period — window after patrician where reward goes to the liquidator +# pirate / bailout — state where total deposit cannot cover the deficit; protocol bails out +# Super App — a contract registered with the Host that receives agreement callbacks +# app credit — deposit credit enabling "zero-balance Super Apps" to relay flows +# without pre-funded tokens. 1:1 relay (one in, one out at same rate) +# always works at zero balance. Fan-out (1:N) needs the app to hold +# tokens. Credit is settled as "owed deposit" on the original sender. +# critical — an account whose available balance is negative (eligible for liquidation) +# jailed — a Super App penalized for violating protocol rules; loses callbacks +# +# == Time Conventions == +# Conventional seconds-per-period used in Superfluid apps for flow rate conversion: +# month: 2628000 (365.25 / 12 * 86400) +# year: 31536000 (365 * 86400) +# Flow rate from a monthly amount: flowRate = monthlyAmount / 2628000 +# Flow rate from a yearly amount: flowRate = yearlyAmount / 31536000 + +# == Flow Management == +# Core operations for creating, updating, and deleting token streams. +# Functions with ctx are called through the Host — either via +# Host.callAgreement (single op) or Host.batchCall (operationType 201). +# For batchCall, the data field is abi.encode(callData, userData) where +# callData is the full ABI-encoded function call with an empty ctx ("0x") +# as placeholder. The Host replaces the placeholder with the real context. +# The access field reflects who can initiate the call, not the direct caller. + +createFlow: + # Start a new stream from ctx.msgSender to receiver. + # The deposit amount depends on the governance-configured liquidation period + # and minimum deposit. The lower 32 bits of the deposit are clipped (rounded up). + notes: + - "Gotcha: A deposit is taken as a safety margin for solvency agents." + mutability: nonpayable + access: anyone # flow from ctx.msgSender + inputs: + - token: address + - receiver: address + - flowRate: int96 # must be > 0 + - ctx: bytes + outputs: + - newCtx: bytes + emits: [FlowUpdated, FlowUpdatedExtension] + errors: [CFA_ZERO_ADDRESS_RECEIVER, CFA_NO_SELF_FLOW, CFA_INVALID_FLOW_RATE, CFA_FLOW_ALREADY_EXISTS, CFA_INSUFFICIENT_BALANCE] + +updateFlow: + # Change the flow rate of an existing stream from ctx.msgSender to receiver. + # Note: owedDeposit accumulates when a Super App receiver uses app credit to + # open outgoing streams. The sender bears the deposit cost for those streams. + notes: + - "Gotcha: Deposit is adjusted to match the new flow rate but never refunds owed deposit — that adjustment happens separately via the app credit system." + mutability: nonpayable + access: anyone # flow from ctx.msgSender + inputs: + - token: address + - receiver: address + - flowRate: int96 # must be > 0 + - ctx: bytes + outputs: + - newCtx: bytes + emits: [FlowUpdated, FlowUpdatedExtension] + errors: [CFA_ZERO_ADDRESS_RECEIVER, CFA_NO_SELF_FLOW, CFA_INVALID_FLOW_RATE, CFA_FLOW_DOES_NOT_EXIST, CFA_INSUFFICIENT_BALANCE] + +deleteFlow: + # Stop a stream between sender and receiver. + # During liquidation the reward distribution depends on the solvency period: + # patrician → bond account gets reward; pleb → liquidator gets reward; + # pirate → liquidator gets full single deposit, protocol covers the bailout. + notes: + - "Gotcha: Third-party callers (not sender, receiver, or operator) only succeed if the sender is critical (negative available balance) or either party is jailed." + mutability: nonpayable + access: sender | receiver | operator | anyone(if-critical-or-jailed) + inputs: + - token: address + - sender: address + - receiver: address + - ctx: bytes + outputs: + - newCtx: bytes + emits: [FlowUpdated, FlowUpdatedExtension] + errors: [CFA_ZERO_ADDRESS_SENDER, CFA_ZERO_ADDRESS_RECEIVER, CFA_FLOW_DOES_NOT_EXIST, CFA_NON_CRITICAL_SENDER] + +# == Operator Flow Management == +# Act on behalf of another account. Requires ACL permissions granted by the sender. +# The operator's flow rate allowance is decremented on create/update. +# Functions with ctx are called through the Host. + +createFlowByOperator: + # Create a flow on behalf of sender. Consumes flow rate allowance. + notes: + - "Gotcha: Reverts if ctx.msgSender IS the sender — use createFlow instead." + mutability: nonpayable + access: operator + inputs: + - token: address + - sender: address + - receiver: address + - flowRate: int96 # must be > 0 + - ctx: bytes + outputs: + - newCtx: bytes + emits: [FlowUpdated, FlowUpdatedExtension] + errors: [CFA_ACL_NO_SENDER_CREATE, CFA_ACL_OPERATOR_NO_CREATE_PERMISSIONS, CFA_ACL_FLOW_RATE_ALLOWANCE_EXCEEDED, CFA_ZERO_ADDRESS_RECEIVER, CFA_NO_SELF_FLOW, CFA_INVALID_FLOW_RATE, CFA_FLOW_ALREADY_EXISTS, CFA_INSUFFICIENT_BALANCE] + +updateFlowByOperator: + # Update a flow on behalf of sender. Only consumes allowance if flow rate increases. + # If flowRateAllowance is type(int96).max, it is treated as unlimited. + notes: + - "Gotcha: If the new rate is lower than the old rate, no allowance is consumed." + mutability: nonpayable + access: operator + inputs: + - token: address + - sender: address + - receiver: address + - flowRate: int96 # must be > 0 + - ctx: bytes + outputs: + - newCtx: bytes + emits: [FlowUpdated, FlowUpdatedExtension] + errors: [CFA_ACL_NO_SENDER_UPDATE, CFA_ACL_OPERATOR_NO_UPDATE_PERMISSIONS, CFA_ACL_FLOW_RATE_ALLOWANCE_EXCEEDED, CFA_ZERO_ADDRESS_RECEIVER, CFA_NO_SELF_FLOW, CFA_INVALID_FLOW_RATE, CFA_FLOW_DOES_NOT_EXIST, CFA_INSUFFICIENT_BALANCE] + +deleteFlowByOperator: + # Delete a flow on behalf of sender. Does not consume flow rate allowance. + mutability: nonpayable + access: operator + inputs: + - token: address + - sender: address + - receiver: address + - ctx: bytes + outputs: + - newCtx: bytes + emits: [FlowUpdated, FlowUpdatedExtension] + errors: [CFA_ACL_OPERATOR_NO_DELETE_PERMISSIONS, CFA_ZERO_ADDRESS_SENDER, CFA_ZERO_ADDRESS_RECEIVER, CFA_FLOW_DOES_NOT_EXIST, CFA_NON_CRITICAL_SENDER] + +# == Flow Queries == + +realtimeBalanceOf: + # Compute the real-time CFA balance contribution for an account. + # Returns the dynamic balance delta (flowRate * elapsed), total deposit held, + # and total owed deposit. This is called by the token's realtimeBalanceOfNow + # to assemble the full balance across all agreements. + # To get an account's actual token balance, use SuperToken.realtimeBalanceOfNow + # (or SuperToken.balanceOf for ERC-20 compatible, clamped to zero). + # It can be deeply negative for net-outflow accounts as time progresses. + notes: + - "Gotcha: Returns only the CFA portion of the balance, not the total." + - "Gotcha: The returned dynamicBalance is a delta, not an absolute balance." + mutability: view + inputs: + - token: address + - account: address + - time: uint256 + outputs: + - dynamicBalance: int256 # flowRate * (time - lastUpdate); can be negative + - deposit: uint256 # total deposit locked across all outflows + - owedDeposit: uint256 # total owed deposit from Super App receivers + +getFlow: + # Get flow data between a specific sender-receiver pair. + mutability: view + inputs: + - token: address + - sender: address + - receiver: address + outputs: + - timestamp: uint256 # last update time + - flowRate: int96 + - deposit: uint256 + - owedDeposit: uint256 + +getFlowByID: + # Get flow data using the agreement ID (keccak256 of sender and receiver). + mutability: view + inputs: + - token: address + - flowId: bytes32 # keccak256(abi.encode(sender, receiver)) + outputs: + - timestamp: uint256 + - flowRate: int96 + - deposit: uint256 + - owedDeposit: uint256 + +getAccountFlowInfo: + # Aggregated flow state for an account across all its CFA flows. + mutability: view + inputs: + - token: address + - account: address + outputs: + - timestamp: uint256 # last time any flow was updated for this account + - flowRate: int96 # net flow rate (inflows - outflows) + - deposit: uint256 # sum of deposits across all outflows + - owedDeposit: uint256 # sum of owed deposits across all outflows + +getNetFlow: + # Net flow rate for an account (sum of inflows minus outflows). + mutability: view + inputs: + - token: address + - account: address + outputs: + - flowRate: int96 + +# == ACL Management == +# Manage flow operator permissions and flow rate allowances. +# The caller (ctx.msgSender) is always the permission granter — they control +# who can operate on their own flows. +# Functions with ctx are called through the Host. + +updateFlowOperatorPermissions: + # Set exact permissions and flow rate allowance for a flow operator. + mutability: nonpayable + access: anyone # grants on ctx.msgSender's flows + inputs: + - token: address + - flowOperator: address + - permissions: uint8 # bitmask: 1=create, 2=update, 4=delete + - flowRateAllowance: int96 + - ctx: bytes + outputs: + - newCtx: bytes + emits: [FlowOperatorUpdated] + errors: [CFA_ACL_UNCLEAN_PERMISSIONS, CFA_ACL_NO_SENDER_FLOW_OPERATOR, CFA_ACL_NO_NEGATIVE_ALLOWANCE] + +authorizeFlowOperatorWithFullControl: + # Grant all permissions (create+update+delete) with unlimited allowance. + # Shorthand for updateFlowOperatorPermissions(token, op, 7, type(int96).max, ctx). + mutability: nonpayable + access: anyone # grants on ctx.msgSender's flows + inputs: + - token: address + - flowOperator: address + - ctx: bytes + outputs: + - newCtx: bytes + emits: [FlowOperatorUpdated] + errors: [CFA_ACL_UNCLEAN_PERMISSIONS, CFA_ACL_NO_SENDER_FLOW_OPERATOR, CFA_ACL_NO_NEGATIVE_ALLOWANCE] + +revokeFlowOperatorWithFullControl: + # Revoke all permissions and set allowance to zero. + # Shorthand for updateFlowOperatorPermissions(token, op, 0, 0, ctx). + mutability: nonpayable + access: anyone # revokes on ctx.msgSender's flows + inputs: + - token: address + - flowOperator: address + - ctx: bytes + outputs: + - newCtx: bytes + emits: [FlowOperatorUpdated] + errors: [CFA_ACL_UNCLEAN_PERMISSIONS, CFA_ACL_NO_SENDER_FLOW_OPERATOR, CFA_ACL_NO_NEGATIVE_ALLOWANCE] + +increaseFlowRateAllowance: + # Increase flow rate allowance for an operator by a delta. + # Delegates to increaseFlowRateAllowanceWithPermissions with permissionsToAdd=0. + mutability: nonpayable + access: anyone # ctx.msgSender's allowance + inputs: + - token: address + - flowOperator: address + - addedFlowRateAllowance: int96 + - ctx: bytes + outputs: + - newCtx: bytes + emits: [FlowOperatorUpdated] + errors: [CFA_ACL_UNCLEAN_PERMISSIONS, CFA_ACL_NO_SENDER_FLOW_OPERATOR, CFA_ACL_NO_NEGATIVE_ALLOWANCE] + +decreaseFlowRateAllowance: + # Decrease flow rate allowance for an operator by a delta. + # Delegates to decreaseFlowRateAllowanceWithPermissions with permissionsToRemove=0. + mutability: nonpayable + access: anyone # ctx.msgSender's allowance + inputs: + - token: address + - flowOperator: address + - subtractedFlowRateAllowance: int96 + - ctx: bytes + outputs: + - newCtx: bytes + emits: [FlowOperatorUpdated] + errors: [CFA_ACL_UNCLEAN_PERMISSIONS, CFA_ACL_NO_SENDER_FLOW_OPERATOR, CFA_ACL_NO_NEGATIVE_ALLOWANCE] + +increaseFlowRateAllowanceWithPermissions: + # Increase flow rate allowance and add permission bits in one call. + # permissionsToAdd is OR'd with existing permissions. + mutability: nonpayable + access: anyone # ctx.msgSender's allowance + inputs: + - token: address + - flowOperator: address + - permissionsToAdd: uint8 # bitmask OR'd onto existing permissions + - addedFlowRateAllowance: int96 + - ctx: bytes + outputs: + - newCtx: bytes + emits: [FlowOperatorUpdated] + errors: [CFA_ACL_UNCLEAN_PERMISSIONS, CFA_ACL_NO_SENDER_FLOW_OPERATOR, CFA_ACL_NO_NEGATIVE_ALLOWANCE] + +decreaseFlowRateAllowanceWithPermissions: + # Decrease flow rate allowance and remove permission bits in one call. + # permissionsToRemove is AND-NOT'd from existing permissions. + notes: + - "Gotcha: Reverts if the resulting allowance would go negative." + mutability: nonpayable + access: anyone # ctx.msgSender's allowance + inputs: + - token: address + - flowOperator: address + - permissionsToRemove: uint8 # bitmask AND-NOT'd from existing permissions + - subtractedFlowRateAllowance: int96 + - ctx: bytes + outputs: + - newCtx: bytes + emits: [FlowOperatorUpdated] + errors: [CFA_ACL_UNCLEAN_PERMISSIONS, CFA_ACL_NO_SENDER_FLOW_OPERATOR, CFA_ACL_NO_NEGATIVE_ALLOWANCE] + +# == ACL Queries == + +getFlowOperatorData: + # Get permissions and allowance for a flow operator. + mutability: view + inputs: + - token: address + - sender: address # the permission granter + - flowOperator: address # the permission grantee + outputs: + - flowOperatorId: bytes32 # keccak256(abi.encode("flowOperator", sender, flowOperator)) + - permissions: uint8 # bitmask: 1=create, 2=update, 4=delete + - flowRateAllowance: int96 + +getFlowOperatorDataByID: + # Get permissions and allowance using the pre-computed operator ID. + mutability: view + inputs: + - token: address + - flowOperatorId: bytes32 + outputs: + - permissions: uint8 + - flowRateAllowance: int96 + +# == Solvency Queries == +# Check whether an account is in the patrician period (grace window for liquidation). + +isPatricianPeriodNow: + # Check patrician period status using the Host's current timestamp. + mutability: view + inputs: + - token: address + - account: address + outputs: + - isCurrentlyPatricianPeriod: bool + - timestamp: uint256 # the Host's block.timestamp used for the check + +isPatricianPeriod: + # Check patrician period status at a specific timestamp. + mutability: view + inputs: + - token: address + - account: address + - timestamp: uint256 + outputs: + - bool + +# == Deposit Helpers == + +getMaximumFlowRateFromDeposit: + # Calculate the maximum flow rate achievable with a given deposit. + notes: + - "Gotcha: Deposit is clipped (lower 32 bits zeroed) and rounded down." + mutability: view + inputs: + - token: address # needed to look up liquidation period from governance + - deposit: uint256 + outputs: + - flowRate: int96 + errors: [CFA_DEPOSIT_TOO_BIG] + +getDepositRequiredForFlowRate: + # Calculate the deposit required for a given flow rate. + # Returns max(minimumDeposit, flowRate * liquidationPeriod) with rounding. + mutability: view + inputs: + - token: address # needed to look up liquidation period and minimum deposit + - flowRate: int96 + outputs: + - deposit: uint256 + errors: [CFA_INVALID_FLOW_RATE, CFA_FLOW_RATE_TOO_BIG] + +# == Protocol Constants == + +agreementType: + # Returns keccak256("org.superfluid-finance.agreements.ConstantFlowAgreement.v1") + mutability: pure + outputs: + - bytes32 + +DEFAULT_MINIMUM_DEPOSIT: + # Minimum deposit floor: uint96(1 << 32) ≈ 4.29 * 10^9 wei. + # Governance may set a higher per-token minimum; this is the absolute floor. + mutability: view + outputs: + - uint256 + +MAXIMUM_DEPOSIT: + # Maximum deposit cap: type(int96).max + mutability: view + outputs: + - uint256 + +MAXIMUM_FLOW_RATE: + # Maximum flow rate: type(int96).max + mutability: view + outputs: + - uint256 + +CFA_HOOK_GAS_LIMIT: + # Gas limit for external hook calls (Super App callbacks): 250,000 + mutability: view + outputs: + - uint64 + +# == Events == + +events: + FlowUpdated: + # Emitted on every create, update, and delete flow operation. + # totalSenderFlowRate and totalReceiverFlowRate are the NET flow rates + # after the operation, not the individual flow rate. + notes: + - "Gotcha: Always emitted together with FlowUpdatedExtension." + indexed: + - token: address + - sender: address + - receiver: address + data: + - flowRate: int96 # new rate for this specific flow (0 on delete) + - totalSenderFlowRate: int256 # sender's net flow rate after this operation + - totalReceiverFlowRate: int256 + - userData: bytes + + FlowUpdatedExtension: + # Companion event to FlowUpdated. Carries the operator and new deposit. + # Emitted immediately after FlowUpdated so indexers can correlate them. + indexed: + - flowOperator: address # ctx.msgSender who initiated the operation + data: + - deposit: uint256 # new deposit for this specific flow + + FlowOperatorUpdated: + # Emitted when operator permissions or flow rate allowance change. + indexed: + - token: address + - sender: address # the permission granter + - flowOperator: address # the permission grantee + data: + - permissions: uint8 # updated bitmask + - flowRateAllowance: int96 # updated allowance + + # Inherited events (from AgreementBase / UUPSProxiable): + # CodeUpdated — emitted on proxy upgrade (uuid, codeAddress) + # Initialized — emitted on proxy initialization (version) + +# == Errors == + +errors: + # Flow validation + - CFA_FLOW_ALREADY_EXISTS # createFlow when flow exists + - CFA_FLOW_DOES_NOT_EXIST # update/delete when no flow + - CFA_INVALID_FLOW_RATE # flowRate <= 0 + - CFA_NO_SELF_FLOW # sender == receiver + - CFA_ZERO_ADDRESS_SENDER # sender is address(0) + - CFA_ZERO_ADDRESS_RECEIVER # receiver is address(0) + # Solvency + - CFA_INSUFFICIENT_BALANCE # sender cannot cover deposit + - CFA_NON_CRITICAL_SENDER # third-party delete but sender is solvent + - CFA_DEPOSIT_TOO_BIG # deposit > MAXIMUM_DEPOSIT + - CFA_FLOW_RATE_TOO_BIG # flowRate * liquidationPeriod overflows + # ACL + - CFA_ACL_FLOW_RATE_ALLOWANCE_EXCEEDED # operator exceeds granted allowance + - CFA_ACL_NO_NEGATIVE_ALLOWANCE # resulting allowance would be < 0 + - CFA_ACL_NO_SENDER_CREATE # operator cannot be the sender (use createFlow) + - CFA_ACL_NO_SENDER_FLOW_OPERATOR # cannot set yourself as your own operator + - CFA_ACL_NO_SENDER_UPDATE # operator cannot be the sender (use updateFlow) + - CFA_ACL_OPERATOR_NO_CREATE_PERMISSIONS + - CFA_ACL_OPERATOR_NO_DELETE_PERMISSIONS + - CFA_ACL_OPERATOR_NO_UPDATE_PERMISSIONS + - CFA_ACL_UNCLEAN_PERMISSIONS # permission bits outside valid range + # Super App / Host + - CFA_HOOK_OUT_OF_GAS # Super App callback exceeded gas limit + - APP_RULE: # Super App rule violation + inputs: + - _code: uint256 + - AGREEMENT_BASE_ONLY_HOST # call not routed through the Host contract + # SafeCast (inherited from OpenZeppelin) + - SafeCastOverflowedIntToUint: # int256 value overflows on cast to uint256 + inputs: + - value: int256 + - SafeCastOverflowedUintToInt: # uint256 value overflows on cast to int256 + inputs: + - value: uint256 diff --git a/.agents/skills/gooddollar/references/contracts/ConstantFlowAgreementV1.selectors.yaml b/.agents/skills/gooddollar/references/contracts/ConstantFlowAgreementV1.selectors.yaml new file mode 100644 index 00000000..a929e06c --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/ConstantFlowAgreementV1.selectors.yaml @@ -0,0 +1,33 @@ +# Generated by scripts/selectors.mjs +functions: + createFlow(address,address): 0x7deac86a + updateFlow(address,address): 0xb6214437 + deleteFlow(address,address,address,bytes): 0xb4b333c6 + createFlowByOperator(address,address,address): 0x97b0b745 + updateFlowByOperator(address,address,address): 0xaea4dc29 + deleteFlowByOperator(address,address,address,bytes): 0x4c8b181f + realtimeBalanceOf(address,address,uint256): 0x9b2e48bc + getFlow(address,address,address): 0xe6a1e888 + getFlowByID(address): 0x4eccd8ac + getAccountFlowInfo(address,address): 0x0f1ac495 + getNetFlow(address,address): 0xe8e7e2d1 + updateFlowOperatorPermissions(address,address): 0x42294caf + authorizeFlowOperatorWithFullControl(address,address,bytes): 0x54b770e3 + revokeFlowOperatorWithFullControl(address,address,bytes): 0x062e56ec + increaseFlowRateAllowance(address,address,int96,bytes): 0xac5f5d00 + decreaseFlowRateAllowance(address,address,int96,bytes): 0x5f51fb23 + increaseFlowRateAllowanceWithPermissions(address,address): 0x5e6a4dc3 + decreaseFlowRateAllowanceWithPermissions(address,address): 0x8de39d9b + getFlowOperatorData(address): 0x5f13dbbc + getFlowOperatorDataByID(address,bytes32): 0x09d256ef + isPatricianPeriodNow(address,address): 0x4fe9c291 + isPatricianPeriod(address,address,uint256): 0x4b839e0b + getMaximumFlowRateFromDeposit(): 0x4ba52c34 + getDepositRequiredForFlowRate(): 0xcc111b9e + CFA_HOOK_GAS_LIMIT(): 0xbf3fbc28 +events: + FlowUpdated(address,address,address,int256,bytes): 0x8e3b8f31fe09d2ca20fa7f76ec574bc9fea49f16a91f1ca828154ea76e76a20a + FlowUpdatedExtension(): 0x9bf0b3e0199b411ca09607d040b7672430af5dae9d159b715c9f8004c876065f + FlowOperatorUpdated(address): 0x401d2cb05b3db80617d70aabf976f52d2a5dac64298eadeed7a81ad4bb5d4fdd + # Inherited events (from AgreementBase / UUPSProxiable)(): 0xcabfd4ad95e3bb803739ac513717fccee724fc0c5741e01cf84319a96120eefa +errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/GoodDollarOFTAdapter.abi.yaml b/.agents/skills/gooddollar/references/contracts/GoodDollarOFTAdapter.abi.yaml new file mode 100644 index 00000000..c60d4985 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/GoodDollarOFTAdapter.abi.yaml @@ -0,0 +1,100 @@ +# GoodDollarOFTAdapter — LayerZero V2 OFT adapter pattern bridging underlying G$ +# Exact bytecode lives in the GoodDollar OFT deployment; this documents the surface agents call. + +meta: + name: GoodDollarOFTAdapter + version: LayerZero-OFT-adapter (generic) + source: + - https://docs.layerzero.network/ + - https://github.com/LayerZero-Labs/devtools + note: > + Flow: optional peers(dstEid) check -> approve underlying to minterBurner if + MessagingFee, refundAddress) paying nativeFee in msg.value when applicable. + SendParam mirrors LayerZero OFT: dstEid, to (bytes32 padded recipient), + amountLD, minAmountLD, extraOptions, composeMsg, oftCmd. + Verify tuple field order and OFT revision against your deployed artifact before mainnet use. + Event names and topic0 hashes vary by OFT package version — pull from the deployment ABI. + related: + - references/guides/bridge.md + +token: + mutability: view + inputs: [] + outputs: + - underlyingToken: address + +minterBurner: + notes: + - "Underlying G$ must approve this spender when burning for cross-chain send." + mutability: view + inputs: [] + outputs: + - minterBurner: address + +oftVersion: + mutability: view + inputs: [] + outputs: + - interfaceId: bytes4 + - version: uint64 + +peers: + notes: + - "Returns bytes32 peer address configured for destination endpoint id." + mutability: view + inputs: + - dstEid: uint32 + outputs: + - peer: bytes32 + +endpoint: + mutability: view + inputs: [] + outputs: + - lzEndpoint: address + +owner: + mutability: view + inputs: [] + outputs: + - account: address + +quoteSend: + notes: + - "Simulates messaging + bridge fee; use nativeFee as msg.value on send when paying in native gas token." + mutability: view + inputs: + - sendParam: SendParam + - payInLzToken: bool + outputs: + - nativeFee: uint256 + - lzTokenFee: uint256 + errors: + - NO_PEER + - LZ_INVALID_OPTIONS + +send: + notes: + - "Payable: include MessagingFee.nativeFee in msg.value when fee is native." + - "Consumes sendParam.amountLD from sender on source chain and emits cross-chain message to dstEid peer." + - "sendParam.to must be destination receiver encoded as bytes32." + mutability: payable + access: sender + inputs: + - sendParam: SendParam + - fee: MessagingFee + - refundAddress: address + outputs: + - msgReceipt: MessageReceipt + - oftReceipt: OFTReceipt + errors: + - NO_PEER + - SLIPPAGE_OR_AMOUNT + - LZ_INVALID_OPTIONS + +events: {} + +errors: + NO_PEER: "Destination peer not configured for dstEid." + LZ_INVALID_OPTIONS: "Composer/options payload rejected by LayerZero." + SLIPPAGE_OR_AMOUNT: "Bridged amount violates minAmount or available balance." diff --git a/.agents/skills/gooddollar/references/contracts/GoodDollarOFTAdapter.selectors.yaml b/.agents/skills/gooddollar/references/contracts/GoodDollarOFTAdapter.selectors.yaml new file mode 100644 index 00000000..e5615a1e --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/GoodDollarOFTAdapter.selectors.yaml @@ -0,0 +1,12 @@ +# Generated by scripts/selectors.mjs +functions: + token(): 0xfc0c546a + minterBurner(): 0x2ef8c5a4 + approvalRequired(): 0x9f68b964 + oftVersion(): 0x156a0d0f + peers(uint32): 0xbb0b6a53 + endpoint(): 0x5e280f11 + owner(): 0x8da5cb5b +events: +{} +errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/GooddollarSavingsStream.abi.yaml b/.agents/skills/gooddollar/references/contracts/GooddollarSavingsStream.abi.yaml new file mode 100644 index 00000000..d736bc19 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/GooddollarSavingsStream.abi.yaml @@ -0,0 +1,210 @@ +# GooddollarSavingsStream (Ubeswap) — G$ native Super Token stake with Superfluid GDA reward stream + +meta: + name: GooddollarSavingsStream + version: "1" + source: + - https://raw.githubusercontent.com/Ubeswap/gooddollar-contracts/main/contracts/GooddollarSavingsStream.sol + - https://raw.githubusercontent.com/Ubeswap/gooddollar-contracts/main/contracts/interfaces/IGooddollarSavingsStream.sol + - https://raw.githubusercontent.com/Ubeswap/gooddollar-contracts/main/contracts/StakingVault.sol + inherits: + - IGooddollarSavingsStream + - Ownable + - ERC2771Context + - ReentrancyGuard + note: > + Celo production savings. Staked principal sits in StakingVault; this contract holds reward + balance and streams via a Superfluid GDA distribution pool (`distributeFlow`). Effective rate is + min(rewardRate, maxRewardRatePerToken * totalSupply / 1e18), throttled when reward balance is low. + Staking token is the G$ native Super Token (`superToken`). Fuse->CELO migration uses `stakeFor` + after bridge. Ubeswap lists this as Celo mainnet savings (streaming). + deployments: + mainnet: + production-celo: + GooddollarSavingsStream: + networkId: 42220 + address: "0x059ee811414230d1Fb157878D2b491240F4D8d3B" + creationBlock: 66685884 + related: + - https://github.com/Ubeswap/gooddollar-contracts/blob/main/README.md + - https://celoscan.io/address/0x059ee811414230d1Fb157878D2b491240F4D8d3B + - references/contracts/SuperToken.abi.yaml + - references/guides/migrate-fuse-staking-to-celo-savings.md + - references/deep-researches/fuse-to-celo-staking-migration.md + +totalSupply: + mutability: view + inputs: [] + outputs: + - supply: uint256 + +balanceOf: + mutability: view + inputs: + - account: address + outputs: + - balance: uint256 + +getDailyRewards: + mutability: view + inputs: [] + outputs: + - daily: uint256 + +getEffectiveFlowRate: + notes: + - "Returns 0 when totalSupply is 0, rewardRate is 0, or reward balance cannot sustain MIN_STREAM_BUFFER_SECONDS." + - "Otherwise min(global rewardRate, APR cap) as int96 for Superfluid distributeFlow." + mutability: view + inputs: [] + outputs: + - flowRate: int96 + +periodFinish: + notes: + - "Estimated timestamp when current reward balance is drained at getEffectiveFlowRate; 0 if not streaming." + mutability: view + inputs: [] + outputs: + - finish: uint256 + +getUnits: + mutability: view + inputs: + - account: address + outputs: + - units: uint128 + +getTotalUnits: + mutability: view + inputs: [] + outputs: + - units: uint128 + +stake: + notes: + - "Pulls G$ Super Token from msg.sender, deposits principal to StakingVault, updates GDA pool units, syncFlowRate." + - "Requires prior superToken.approve(this, amount)." + mutability: nonpayable + access: anyone + inputs: + - amount: uint256 + outputs: [] + emits: [Staked] + +stakeFor: + notes: + - "Migrator path: caller funds transfer; stake credits `_balances[recipient]` and pool units for recipient." + - "recipient must not be zero, this, superToken, or vault." + - "Used after Fuse->CELO bridge when backend wallet holds bridged G$." + mutability: nonpayable + access: anyone + inputs: + - amount: uint256 + - recipient: address + outputs: [] + emits: [Staked] + +withdraw: + notes: + - "Reduces stake and pool units, syncFlowRate, then vault.withdraw to msg.sender." + mutability: nonpayable + access: anyone + inputs: + - amount: uint256 + outputs: [] + emits: [Withdrawn] + +exit: + notes: + - "Full withdraw of caller principal via withdraw(balance)." + mutability: nonpayable + access: anyone + inputs: [] + outputs: [] + emits: [Withdrawn] + +addToReward: + notes: + - "Pulls reward Super Token from caller; re-syncs flow rate when balance was throttling the stream." + mutability: nonpayable + access: anyone + inputs: + - reward: uint256 + outputs: [] + emits: [RewardAdded] + +syncFlowRate: + notes: + - "Permissionless; call when tokens were sent directly to the contract so distributeFlow picks up balance." + mutability: nonpayable + access: anyone + inputs: [] + outputs: [] + emits: [FlowRateUpdated] + +setDailyRewards: + mutability: nonpayable + access: owner + inputs: + - _dailyRewards: uint256 + outputs: [] + emits: [DailyRewardsUpdated] + +setMaxRewardRatePerToken: + mutability: nonpayable + access: owner + inputs: + - _value: uint256 + outputs: [] + emits: [MaxRewardRateUpdated] + +recoverERC20: + notes: + - "Cannot recover the G$ Super Token (staking/reward asset)." + mutability: nonpayable + access: owner + inputs: + - tokenAddress: address + - tokenAmount: uint256 + outputs: [] + emits: [Recovered] + +events: + Staked: + indexed: + - user: address + data: + - amount: uint256 + Withdrawn: + indexed: + - user: address + data: + - amount: uint256 + RewardAdded: + data: + - reward: uint256 + DailyRewardsUpdated: + data: + - rewardRate: uint256 + - givenDailyRewards: uint256 + MaxRewardRateUpdated: + data: + - newMaxRate: uint256 + FlowRateUpdated: + data: + - newFlowRate: int96 + Recovered: + indexed: + - token: address + data: + - amount: uint256 + - receiver: address + +errors: + - CannotStakeZero + - CannotWithdrawZero + - InsufficientStake + - InvalidAddress + - NoRewardToAdd + - CannotRecoverStakingToken diff --git a/.agents/skills/gooddollar/references/contracts/GooddollarSavingsStream.selectors.yaml b/.agents/skills/gooddollar/references/contracts/GooddollarSavingsStream.selectors.yaml new file mode 100644 index 00000000..f04d75a4 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/GooddollarSavingsStream.selectors.yaml @@ -0,0 +1,27 @@ +# Generated by scripts/selectors.mjs +functions: + totalSupply(): 0x18160ddd + balanceOf(address): 0x70a08231 + getDailyRewards(): 0x68527008 + getEffectiveFlowRate(): 0xa2efe857 + periodFinish(): 0xebe2b12b + getUnits(address): 0x0fefbc09 + getTotalUnits(): 0xa754a702 + stake(uint256): 0xa694fc3a + stakeFor(uint256,address): 0x51746bb2 + withdraw(uint256): 0x2e1a7d4d + exit(): 0xe9fad8ee + addToReward(uint256): 0x2ce618fa + syncFlowRate(): 0x0e83284a + setDailyRewards(uint256): 0x4adef718 + setMaxRewardRatePerToken(uint256): 0x6a03a9e2 + recoverERC20(address,uint256): 0x8980f11f +events: + Staked(address,uint256): 0x9e71bc8eea02a63969f509818f2dafb9254532904319f9dbda79b67bd34a5f3d + Withdrawn(address,uint256): 0x7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5 + RewardAdded(uint256): 0xde88a922e0d3b88b24e9623efeb464919c6bf9f66857a65e2bfcf2ce87a9433d + DailyRewardsUpdated(uint256,uint256): 0x54f5b7ef058007cebca11af0127cd80c0bac4e968788eabb3c1e70d1bfb78edd + MaxRewardRateUpdated(uint256): 0x9041b23d05af9bceefc73becffefd1887907ad55c3d6c6655230b37ef87dbb07 + FlowRateUpdated(int96): 0xd02fcf6b96acb60ac68942c7d5075e11a8a0b38cc865c1916934f4e1f129ded0 + Recovered(address,uint256,address): 0xb197f0a554c4d7840105e6ae65f0e275e9e8605a969dffa8caa7f1f118a2e1f5 +errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/GovernanceStakingV2.abi.yaml b/.agents/skills/gooddollar/references/contracts/GovernanceStakingV2.abi.yaml new file mode 100644 index 00000000..5adccb32 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/GovernanceStakingV2.abi.yaml @@ -0,0 +1,202 @@ +# GovernanceStakingV2 (Fuse) — G$ staking share token with GDAO reward accrual +# Deployment key in GoodProtocol release metadata is GovernanceStakingV2. + +meta: + name: GovernanceStakingV2 + version: "2" + source: + - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/contracts/governance/GovernanceStaking.sol + - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/releases/deployment.json + inherits: + - ERC20Upgradeable + - MultiBaseGovernanceShareField + - DAOUpgradeableContract + - ReentrancyGuardUpgradeable + note: > + Fuse governance staking contract where users stake G$ and receive staking shares (`sG$`) with + GDAO-style rewards minted on reward withdrawal paths. `withdrawStake(0)` means full unstake. + In GoodProtocol `releases/deployment.json` this address is listed as GovernanceStakingV2 on Fuse `production`. + This is the old staking contract in Fuse->CELO migration flows. + deployments: + mainnet: + production: + GovernanceStakingV2: + networkId: 122 + address: "0xB7C3e738224625289C573c54d402E9Be46205546" + creationBlock: 15956809 + related: + - references/guides/migrate-fuse-staking-to-celo-savings.md + - references/deep-researches/fuse-to-celo-staking-migration.md + +getChainBlocksPerMonth: + mutability: pure + inputs: [] + outputs: + - blocks: uint256 + +stake: + notes: + - "Requires prior G$ ERC20 approval to staking contract." + - "Mints staking share token and updates reward productivity before event emission." + mutability: nonpayable + access: anyone + inputs: + - _amount: uint256 + outputs: [] + emits: [Staked] + +withdrawStake: + notes: + - "If _amount is 0, contract interprets it as full user unstake." + - "Burns staking shares, updates productivity, mints pending rewards, then transfers G$ out." + mutability: nonpayable + access: anyone + inputs: + - _amount: uint256 + outputs: [] + emits: [StakeWithdraw] + +withdrawRewards: + notes: + - "Claims rewards without changing staked principal." + mutability: nonpayable + access: anyone + inputs: [] + outputs: + - minted: uint256 + emits: [ReputationEarned] + +setMonthlyRewards: + notes: + - "DAO-controlled update of rewardsPerBlock schedule via avatar authorization." + mutability: nonpayable + access: avatar + inputs: + - _monthlyAmount: uint256 + outputs: [] + +getRewardsPerBlock: + mutability: view + inputs: [] + outputs: + - amount: uint256 + +getProductivity: + mutability: view + inputs: + - _user: address + outputs: + - productivity: uint256 + - rewardDebt: uint256 + +getUserPendingReward: + mutability: view + inputs: + - _user: address + outputs: + - pending: uint256 + +users: + notes: + - "UserInfo getter for internal staking productivity record." + mutability: view + inputs: + - _user: address + outputs: + - amount: uint256 + - rewardDebt: uint256 + +totalRewardsPerShare: + mutability: view + inputs: [] + outputs: + - value: uint256 + +decimals: + mutability: view + inputs: [] + outputs: + - d: uint8 + +totalSupply: + mutability: view + inputs: [] + outputs: + - supply: uint256 + +balanceOf: + mutability: view + inputs: + - account: address + outputs: + - balance: uint256 + +allowance: + mutability: view + inputs: + - owner: address + - spender: address + outputs: + - amount: uint256 + +approve: + mutability: nonpayable + access: anyone + inputs: + - spender: address + - amount: uint256 + outputs: + - ok: bool + emits: [Approval] + +transfer: + mutability: nonpayable + access: anyone + inputs: + - to: address + - amount: uint256 + outputs: + - ok: bool + emits: [Transfer] + +transferFrom: + mutability: nonpayable + access: anyone + inputs: + - from: address + - to: address + - amount: uint256 + outputs: + - ok: bool + emits: [Transfer, Approval] + +events: + ReputationEarned: + indexed: + - staker: address + data: + - amount: uint256 + Staked: + indexed: + - staker: address + data: + - amount: uint256 + StakeWithdraw: + indexed: + - staker: address + data: + - amount: uint256 + Transfer: + indexed: + - from: address + - to: address + data: + - value: uint256 + Approval: + indexed: + - owner: address + - spender: address + data: + - value: uint256 + +errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/GovernanceStakingV2.selectors.yaml b/.agents/skills/gooddollar/references/contracts/GovernanceStakingV2.selectors.yaml new file mode 100644 index 00000000..37388ba0 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/GovernanceStakingV2.selectors.yaml @@ -0,0 +1,26 @@ +# Generated by scripts/selectors.mjs +functions: + getChainBlocksPerMonth(): 0x213b329e + stake(uint256): 0xa694fc3a + withdrawStake(uint256): 0x25d5971f + withdrawRewards(): 0xc7b8981c + setMonthlyRewards(uint256): 0xc76279a2 + getRewardsPerBlock(): 0x0c1cd7f3 + getProductivity(address): 0x28e964e9 + getUserPendingReward(address): 0xc6710629 + users(address): 0xa87430ba + totalRewardsPerShare(): 0xbf8e9b6e + decimals(): 0x313ce567 + totalSupply(): 0x18160ddd + balanceOf(address): 0x70a08231 + allowance(address,address): 0xdd62ed3e + approve(address,uint256): 0x095ea7b3 + transfer(address,uint256): 0xa9059cbb + transferFrom(address,address,uint256): 0x23b872dd +events: + ReputationEarned(address,uint256): 0x43848d0574703c28d68ae8958e0571521618f60c4bcacfb094cff2156eaae0f1 + Staked(address,uint256): 0x9e71bc8eea02a63969f509818f2dafb9254532904319f9dbda79b67bd34a5f3d + StakeWithdraw(address,uint256): 0x1248d48e2de900a1010c7fce73506969ecec243600bfc08b641b158f26d857cd + Transfer(address,address,uint256): 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef + Approval(address,address,uint256): 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925 +errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/IdentityV3.abi.yaml b/.agents/skills/gooddollar/references/contracts/IdentityV3.abi.yaml new file mode 100644 index 00000000..aea2efe9 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/IdentityV3.abi.yaml @@ -0,0 +1,390 @@ +# IdentityV3 — whitelist, blacklist, DID, and connected-account graph for GoodDollar +# UBI eligibility uses getWhitelistedRoot; connectAccount links extra wallets to a root. + +meta: + name: IdentityV3 + version: v3 + source: + - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/contracts/identity/IdentityV3.sol + inherits: + - DAOUpgradeableContract + - AccessControlUpgradeable + - PausableUpgradeable + - EIP712Upgradeable + note: > + isWhitelisted enforces authenticationPeriod against dateAuthenticated and status==1, + with fallback to oldIdentity. getWhitelistedRoot returns the root address for a + connected wallet or the wallet itself when directly whitelisted. + deployments: + mainnet: + production: + Identity: + networkId: 122 + address: "0x2F9C28de9e6d44b71B91b8BA337A5D82e308E7BE" + creationBlock: 22022901 + IdentityOld: + networkId: 122 + address: "0xFa8d865A962ca8456dF331D78806152d3aC5B84F" + creationBlock: 6246324 + production-celo: + Identity: + networkId: 42220 + address: "0xC361A6E67822a0EDc17D899227dd9FC50BD62F42" + creationBlock: 17237952 + production-xdc: + Identity: + networkId: 50 + address: "0x27a4a02C9ed591E1a86e2e5D05870292c34622C9" + creationBlock: 95143058 + related: + - https://docs.gooddollar.org/for-developers/core-contracts/identity + - https://docs.gooddollar.org/user-guides/connect-another-wallet-address-to-identity + +initialize: + mutability: nonpayable + access: initializer + inputs: + - _owner: address + - _oldIdentity: address + outputs: [] + +initDAO: + notes: + - "Wires NameService via setDAO and transfers DEFAULT_ADMIN_ROLE, PAUSER_ROLE, IDENTITY_ADMIN_ROLE to avatar." + mutability: nonpayable + access: DEFAULT_ADMIN_ROLE + inputs: + - _ns: address + outputs: [] + errors: [ALREADY_INITIALIZED] + +setAuthenticationPeriod: + mutability: nonpayable + access: avatar + inputs: + - period: uint256 + outputs: [] + errors: [WHEN_NOT_PAUSED_REVERT] + +authenticate: + notes: + - "Public wrapper; forwards to authenticateWithTimestamp(account, block.timestamp)." + mutability: nonpayable + inputs: + - account: address + outputs: [] + +authenticateWithTimestamp: + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - account: address + - timestamp: uint256 + outputs: [] + emits: [WhitelistedAuthenticated] + errors: [NOT_WHITELISTED_STATUS, WHEN_NOT_PAUSED_REVERT] + +addWhitelisted: + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - account: address + outputs: [] + emits: [WhitelistedAdded] + errors: [ALREADY_HAS_STATUS, WHEN_NOT_PAUSED_REVERT] + +addWhitelistedWithDIDAndChain: + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - account: address + - did: string + - orgChain: uint256 + - dateAuthenticated: uint256 + outputs: [] + emits: [WhitelistedAdded] + errors: [DID_ALREADY_REGISTERED, ALREADY_HAS_STATUS, WHEN_NOT_PAUSED_REVERT] + +addWhitelistedWithDID: + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - account: address + - did: string + outputs: [] + emits: [WhitelistedAdded] + errors: [DID_ALREADY_REGISTERED, ALREADY_HAS_STATUS, WHEN_NOT_PAUSED_REVERT] + +removeWhitelisted: + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - account: address + outputs: [] + emits: [WhitelistedRemoved] + errors: [WHEN_NOT_PAUSED_REVERT] + +renounceWhitelisted: + mutability: nonpayable + access: whitelisted sender + inputs: [] + outputs: [] + emits: [WhitelistedRemoved] + errors: [NOT_WHITELISTED_SENDER, WHEN_NOT_PAUSED_REVERT] + +isWhitelisted: + notes: + - "Combines local Identity record with optional oldIdentity.isWhitelisted try/catch." + mutability: view + inputs: + - account: address + outputs: + - ok: bool + +lastAuthenticated: + mutability: view + inputs: + - account: address + outputs: + - ts: uint256 + +addBlacklisted: + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - account: address + outputs: [] + emits: [BlacklistAdded, WhitelistedRemoved] + errors: [WHEN_NOT_PAUSED_REVERT] + +removeBlacklisted: + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - account: address + outputs: [] + emits: [BlacklistRemoved] + errors: [WHEN_NOT_PAUSED_REVERT] + +addContract: + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - account: address + outputs: [] + emits: [ContractAdded] + errors: [NOT_CONTRACT, ALREADY_HAS_STATUS, WHEN_NOT_PAUSED_REVERT] + +removeContract: + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - account: address + outputs: [] + emits: [ContractRemoved] + errors: [WHEN_NOT_PAUSED_REVERT] + +isDAOContract: + mutability: view + inputs: + - account: address + outputs: + - ok: bool + +isBlacklisted: + mutability: view + inputs: + - account: address + outputs: + - ok: bool + +connectAccount: + notes: + - "Caller must be whitelisted; target must not already be whitelisted or blacklisted; stores connectedAccounts[account]=msg.sender." + mutability: nonpayable + access: whitelisted sender + inputs: + - account: address + outputs: [] + emits: [AccountConnected] + errors: [INVALID_ACCOUNT_FOR_CONNECT, ALREADY_CONNECTED, WHEN_NOT_PAUSED_REVERT] + +disconnectAccount: + notes: + - "Caller must be either the linked root or the connected wallet." + mutability: nonpayable + inputs: + - connected: address + outputs: [] + emits: [AccountDisconnected] + errors: [UNAUTHORIZED_DISCONNECT] + +getWhitelistedRoot: + notes: + - "Returns account if directly whitelisted; returns connected root if connected account is whitelisted; else zero." + mutability: view + inputs: + - account: address + outputs: + - whitelisted: address + +pause: + mutability: nonpayable + access: PAUSER_ROLE + inputs: + - toPause: bool + outputs: [] + +setDID: + mutability: nonpayable + inputs: + - account: address + - did: string + outputs: [] + errors: [NOT_AUTHORIZED_SET_DID, NOT_WHITELISTED_FOR_DID, DID_EMPTY, DID_ALREADY_REGISTERED, DID_ALREADY_REGISTERED_OLD, WHEN_NOT_PAUSED_REVERT] + +addrToDID: + mutability: view + inputs: + - account: address + outputs: + - did: string + +getWhitelistedOnChainId: + mutability: view + inputs: + - account: address + outputs: + - chainId: uint256 + +isRegistered: + notes: + - "Compatibility shim; always returns true." + mutability: pure + inputs: [] + outputs: + - ok: bool + +IDENTITY_ADMIN_ROLE: + mutability: view + inputs: [] + outputs: + - role: bytes32 + +PAUSER_ROLE: + mutability: view + inputs: [] + outputs: + - role: bytes32 + +TYPED_STRUCTURE: + mutability: view + inputs: [] + outputs: + - schema: string + +whitelistedCount: + mutability: view + inputs: [] + outputs: + - n: uint256 + +whitelistedContracts: + mutability: view + inputs: [] + outputs: + - n: uint256 + +authenticationPeriod: + mutability: view + inputs: [] + outputs: + - seconds: uint256 + +identities: + mutability: view + inputs: + - account: address + outputs: + - dateAuthenticated: uint256 + - dateAdded: uint256 + - did: string + - whitelistedOnChainId: uint256 + - status: uint8 + +didHashToAddress: + mutability: view + inputs: + - hash: bytes32 + outputs: + - account: address + +connectedAccounts: + mutability: view + inputs: + - account: address + outputs: + - root: address + +oldIdentity: + mutability: view + inputs: [] + outputs: + - addr: address + +events: + BlacklistAdded: + indexed: + - account: address + data: [] + BlacklistRemoved: + indexed: + - account: address + data: [] + WhitelistedAdded: + indexed: + - account: address + data: [] + WhitelistedRemoved: + indexed: + - account: address + data: [] + WhitelistedAuthenticated: + indexed: + - account: address + data: + - timestamp: uint256 + ContractAdded: + indexed: + - account: address + data: [] + ContractRemoved: + indexed: + - account: address + data: [] + AccountConnected: + indexed: + - connected: address + - to: address + data: [] + AccountDisconnected: + indexed: + - disconnected: address + - from: address + data: [] + +errors: + ALREADY_INITIALIZED: "already initialized — initDAO twice." + NOT_WHITELISTED_STATUS: "not whitelisted — authenticateWithTimestamp." + NOT_WHITELISTED_SENDER: "not whitelisted — renounceWhitelisted modifier." + NOT_WHITELISTED_FOR_DID: "not whitelisted — setDID internal." + ALREADY_HAS_STATUS: "already has status — _addWhitelisted." + DID_ALREADY_REGISTERED: "DID already registered — _addWhitelistedWithDID or _setDID." + DID_ALREADY_REGISTERED_OLD: "DID already registered oldIdentity — _setDID conflict path." + NOT_CONTRACT: "Given address is not a contract — addContract." + INVALID_ACCOUNT_FOR_CONNECT: "invalid account — connectAccount target checks." + ALREADY_CONNECTED: "already connected — connectAccount." + UNAUTHORIZED_DISCONNECT: "unauthorized — disconnectAccount." + NOT_AUTHORIZED_SET_DID: "not authorized — setDID caller." + DID_EMPTY: "did empty — _setDID." + WHEN_NOT_PAUSED_REVERT: "OpenZeppelin Pausable whenNotPaused on gated calls." diff --git a/.agents/skills/gooddollar/references/contracts/IdentityV3.selectors.yaml b/.agents/skills/gooddollar/references/contracts/IdentityV3.selectors.yaml new file mode 100644 index 00000000..9b53f001 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/IdentityV3.selectors.yaml @@ -0,0 +1,49 @@ +# Generated by scripts/selectors.mjs +functions: + initialize(address,address): 0x485cc955 + initDAO(address): 0x2b14dda8 + setAuthenticationPeriod(uint256): 0xfff930d4 + authenticate(address): 0x08e0d29d + authenticateWithTimestamp(address,uint256): 0x96a1ef79 + addWhitelisted(address): 0x10154bad + addWhitelistedWithDIDAndChain(address,string,uint256,uint256): 0xe737031a + addWhitelistedWithDID(address,string): 0x1b027099 + removeWhitelisted(address): 0x291d9549 + renounceWhitelisted(): 0xd6cd9473 + isWhitelisted(address): 0x3af32abf + lastAuthenticated(address): 0xe1e360ba + addBlacklisted(address): 0x188efc16 + removeBlacklisted(address): 0xc6a276c2 + addContract(address): 0x5f539d69 + removeContract(address): 0xc375c2ef + isDAOContract(address): 0xc73cc4ae + isBlacklisted(address): 0xfe575a87 + connectAccount(address): 0xd21685f4 + disconnectAccount(address): 0x37a1a987 + getWhitelistedRoot(address): 0x2d0e9b46 + pause(bool): 0x02329a29 + setDID(address,string): 0xd3262816 + addrToDID(address): 0x54f9f7a3 + getWhitelistedOnChainId(address): 0xa061922d + isRegistered(): 0x22366844 + IDENTITY_ADMIN_ROLE(): 0x1aaff63c + PAUSER_ROLE(): 0xe63ab1e9 + TYPED_STRUCTURE(): 0x2cec5330 + whitelistedCount(): 0xb2a1de22 + whitelistedContracts(): 0xb30f7e7f + authenticationPeriod(): 0x31b376e2 + identities(address): 0xf653b81e + didHashToAddress(bytes32): 0x67c75937 + connectedAccounts(address): 0x61320040 + oldIdentity(): 0x4125f0f2 +events: + BlacklistAdded(address): 0x44d5fe68b00f68950fb9c1ff0a61ef7f747b1a36359a7e3a7f3324db4b878967 + BlacklistRemoved(address): 0x1747ca720b1a174a464b6513ace29b1d3190b5f632b9f34147017c81425bfde8 + WhitelistedAdded(address): 0xee1504a83b6d4a361f4c1dc78ab59bfa30d6a3b6612c403e86bb01ef2984295f + WhitelistedRemoved(address): 0x270d9b30cf5b0793bbfd54c9d5b94aeb49462b8148399000265144a8722da6b6 + WhitelistedAuthenticated(address,uint256): 0xb2a82fce6d8c7a633efe9579f77b4edb96bfdf171a49bfc2ce666dc543a1f500 + ContractAdded(address): 0x89c66952b48f3e96bf1d8ba1b63189520fd988a6979b8b740bd5c5d8dc53e205 + ContractRemoved(address): 0x8d30d41865a0b811b9545d879520d2dde9f4cc49e4241f486ad9752bc904b565 + AccountConnected(address,address): 0x18f7736ef54539debd9afd3c9500b106e12ae7c70e685f5a5efd727b1ce1d54c + AccountDisconnected(address,address): 0x7cdef5f9c5cb8ce728661ede956fef26cb91eb4d7d2180cc041b73f9fef568d2 +errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/IdentityV4.abi.yaml b/.agents/skills/gooddollar/references/contracts/IdentityV4.abi.yaml new file mode 100644 index 00000000..485254d7 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/IdentityV4.abi.yaml @@ -0,0 +1,424 @@ +# IdentityV4 — whitelist, blacklist, DID, connected accounts, and reverification schedule +# isWhitelisted uses shouldReverify against reverifyDaysOptions and authCount; oldIdentity fallback preserved. + +meta: + name: IdentityV4 + version: v4 + source: + - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/contracts/identity/IdentityV4.sol + inherits: + - DAOUpgradeableContract + - AccessControlUpgradeable + - PausableUpgradeable + - EIP712Upgradeable + note: > + authenticationPeriod() returns the largest reverify interval in days (last entry of reverifyDaysOptions), + not seconds. isWhitelisted is false when shouldReverify is true for the current elapsed days since + dateAuthenticated. Post-upgrade, accounts with dateAuthenticated before 1772697574 are treated as + having authCount at the last schedule step for reverify logic. + deployments: + mainnet: + production: + Identity: + networkId: 122 + address: "0x2F9C28de9e6d44b71B91b8BA337A5D82e308E7BE" + creationBlock: 22022901 + production-celo: + Identity: + networkId: 42220 + address: "0xC361A6E67822a0EDc17D899227dd9FC50BD62F42" + creationBlock: 17237952 + production-xdc: + Identity: + networkId: 50 + address: "0x27a4a02C9ed591E1a86e2e5D05870292c34622C9" + creationBlock: 95143058 + related: + - https://docs.gooddollar.org/for-developers/core-contracts/identity + - https://docs.gooddollar.org/user-guides/connect-another-wallet-address-to-identity + +initialize: + mutability: nonpayable + access: initializer + inputs: + - _owner: address + - _oldIdentity: address + outputs: [] + +initDAO: + notes: + - "Wires NameService via setDAO and transfers DEFAULT_ADMIN_ROLE, PAUSER_ROLE, IDENTITY_ADMIN_ROLE to avatar." + mutability: nonpayable + access: DEFAULT_ADMIN_ROLE + inputs: + - _ns: address + outputs: [] + errors: [ALREADY_INITIALIZED] + +setReverifyDaysOptions: + notes: + - "Replaces the full schedule; values must be strictly ascending uint8 days; non-empty array." + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - options: uint8[] + outputs: [] + errors: [EMPTY_REVERIFY_OPTIONS, OPTIONS_NOT_ASCENDING, WHEN_NOT_PAUSED_REVERT] + +authenticate: + notes: + - "Forwards to authenticateWithTimestamp(account, block.timestamp); requires IDENTITY_ADMIN_ROLE on the inner call." + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - account: address + outputs: [] + errors: [NOT_WHITELISTED_STATUS, WHEN_NOT_PAUSED_REVERT] + +authenticateWithTimestamp: + notes: + - "Refreshes authentication timestamp for status-1 account and advances authCount when reverification threshold is reached." + - "Legacy accounts before cutoff are evaluated at last schedule step for reverify math." + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - account: address + - timestamp: uint256 + outputs: [] + emits: [WhitelistedAuthenticated] + errors: [NOT_WHITELISTED_STATUS, WHEN_NOT_PAUSED_REVERT] + +addWhitelisted: + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - account: address + outputs: [] + emits: [WhitelistedAdded] + errors: [ALREADY_HAS_STATUS, WHEN_NOT_PAUSED_REVERT] + +addWhitelistedWithDIDAndChain: + notes: + - "Adds whitelisted account and stores DID plus originating chain id and authentication timestamp." + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - account: address + - did: string + - orgChain: uint256 + - dateAuthenticated: uint256 + outputs: [] + emits: [WhitelistedAdded] + errors: [DID_ALREADY_REGISTERED, ALREADY_HAS_STATUS, WHEN_NOT_PAUSED_REVERT] + +addWhitelistedWithDID: + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - account: address + - did: string + outputs: [] + emits: [WhitelistedAdded] + errors: [DID_ALREADY_REGISTERED, ALREADY_HAS_STATUS, WHEN_NOT_PAUSED_REVERT] + +removeWhitelisted: + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - account: address + outputs: [] + emits: [WhitelistedRemoved] + errors: [WHEN_NOT_PAUSED_REVERT] + +renounceWhitelisted: + mutability: nonpayable + access: whitelisted sender + inputs: [] + outputs: [] + emits: [WhitelistedRemoved] + errors: [NOT_WHITELISTED_SENDER, WHEN_NOT_PAUSED_REVERT] + +shouldReverify: + notes: + - "Compares daysSinceAuth against current reverifyDaysOptions step selected by authCount." + mutability: view + inputs: + - account: address + - daysSinceAuth: uint256 + outputs: + - needed: bool + +isWhitelisted: + notes: + - "Local status 1 with shouldReverify false; else oldIdentity.isWhitelisted try/catch." + mutability: view + inputs: + - account: address + outputs: + - ok: bool + +lastAuthenticated: + notes: + - "Returns local dateAuthenticated or falls back to oldIdentity when local record has no timestamp." + mutability: view + inputs: + - account: address + outputs: + - ts: uint256 + +addBlacklisted: + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - account: address + outputs: [] + emits: [BlacklistAdded, WhitelistedRemoved] + errors: [WHEN_NOT_PAUSED_REVERT] + +removeBlacklisted: + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - account: address + outputs: [] + emits: [BlacklistRemoved] + errors: [WHEN_NOT_PAUSED_REVERT] + +addContract: + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - account: address + outputs: [] + emits: [ContractAdded] + errors: [NOT_CONTRACT, ALREADY_HAS_STATUS, WHEN_NOT_PAUSED_REVERT] + +removeContract: + mutability: nonpayable + access: IDENTITY_ADMIN_ROLE + inputs: + - account: address + outputs: [] + emits: [ContractRemoved] + errors: [WHEN_NOT_PAUSED_REVERT] + +isDAOContract: + mutability: view + inputs: + - account: address + outputs: + - ok: bool + +isBlacklisted: + mutability: view + inputs: + - account: address + outputs: + - ok: bool + +connectAccount: + notes: + - "Caller must be whitelisted; target must not already be whitelisted or blacklisted; stores connectedAccounts[account]=msg.sender." + mutability: nonpayable + access: whitelisted sender + inputs: + - account: address + outputs: [] + emits: [AccountConnected] + errors: [INVALID_ACCOUNT_FOR_CONNECT, ALREADY_CONNECTED, WHEN_NOT_PAUSED_REVERT] + +disconnectAccount: + notes: + - "Caller must be either the linked root or the connected wallet." + mutability: nonpayable + inputs: + - connected: address + outputs: [] + emits: [AccountDisconnected] + errors: [UNAUTHORIZED_DISCONNECT] + +getWhitelistedRoot: + notes: + - "Returns account if directly whitelisted; returns connected root if connected account is whitelisted; else zero." + mutability: view + inputs: + - account: address + outputs: + - whitelisted: address + +pause: + mutability: nonpayable + access: PAUSER_ROLE + inputs: + - toPause: bool + outputs: [] + +setDID: + notes: + - "Sets DID for account by admin or account itself when whitelisted; enforces uniqueness across local and old identity." + mutability: nonpayable + inputs: + - account: address + - did: string + outputs: [] + errors: [NOT_AUTHORIZED_SET_DID, NOT_WHITELISTED_FOR_DID, DID_EMPTY, DID_ALREADY_REGISTERED, DID_ALREADY_REGISTERED_OLD, WHEN_NOT_PAUSED_REVERT] + +addrToDID: + mutability: view + inputs: + - account: address + outputs: + - did: string + +getWhitelistedOnChainId: + notes: + - "Returns chain id recorded during whitelisting for cross-chain eligibility checks." + mutability: view + inputs: + - account: address + outputs: + - chainId: uint256 + +isRegistered: + notes: + - "Compatibility shim; always returns true." + mutability: pure + inputs: [] + outputs: + - ok: bool + +IDENTITY_ADMIN_ROLE: + mutability: view + inputs: [] + outputs: + - role: bytes32 + +PAUSER_ROLE: + mutability: view + inputs: [] + outputs: + - role: bytes32 + +TYPED_STRUCTURE: + mutability: view + inputs: [] + outputs: + - schema: string + +whitelistedCount: + mutability: view + inputs: [] + outputs: + - n: uint256 + +whitelistedContracts: + mutability: view + inputs: [] + outputs: + - n: uint256 + +authenticationPeriod: + notes: + - "Returns reverifyDaysOptions[last] in days, not seconds." + mutability: view + inputs: [] + outputs: + - days: uint256 + +reverifyDaysOptions: + mutability: view + inputs: + - index: uint256 + outputs: + - days: uint32 + +identities: + mutability: view + inputs: + - account: address + outputs: + - dateAuthenticated: uint256 + - dateAdded: uint256 + - did: string + - whitelistedOnChainId: uint256 + - status: uint8 + - authCount: uint32 + +didHashToAddress: + mutability: view + inputs: + - hash: bytes32 + outputs: + - account: address + +connectedAccounts: + mutability: view + inputs: + - account: address + outputs: + - root: address + +oldIdentity: + mutability: view + inputs: [] + outputs: + - addr: address + +events: + BlacklistAdded: + indexed: + - account: address + data: [] + BlacklistRemoved: + indexed: + - account: address + data: [] + WhitelistedAdded: + indexed: + - account: address + data: [] + WhitelistedRemoved: + indexed: + - account: address + data: [] + WhitelistedAuthenticated: + indexed: + - account: address + data: + - timestamp: uint256 + ContractAdded: + indexed: + - account: address + data: [] + ContractRemoved: + indexed: + - account: address + data: [] + AccountConnected: + indexed: + - connected: address + - to: address + data: [] + AccountDisconnected: + indexed: + - disconnected: address + - from: address + data: [] + +errors: + ALREADY_INITIALIZED: "already initialized — initDAO twice." + EMPTY_REVERIFY_OPTIONS: "empty options — setReverifyDaysOptions." + OPTIONS_NOT_ASCENDING: "options not in ascending order — setReverifyDaysOptions." + NOT_WHITELISTED_STATUS: "not whitelisted — authenticateWithTimestamp." + NOT_WHITELISTED_SENDER: "not whitelisted — renounceWhitelisted modifier." + NOT_WHITELISTED_FOR_DID: "not whitelisted — _setDID." + ALREADY_HAS_STATUS: "already has status — _addWhitelisted." + DID_ALREADY_REGISTERED: "DID already registered — _addWhitelistedWithDID or _setDID." + DID_ALREADY_REGISTERED_OLD: "DID already registered oldIdentity — _setDID conflict path." + NOT_CONTRACT: "Given address is not a contract — addContract." + INVALID_ACCOUNT_FOR_CONNECT: "invalid account — connectAccount target checks." + ALREADY_CONNECTED: "already connected — connectAccount." + UNAUTHORIZED_DISCONNECT: "unauthorized — disconnectAccount." + NOT_AUTHORIZED_SET_DID: "not authorized — setDID caller." + DID_EMPTY: "did empty — _setDID." + WHEN_NOT_PAUSED_REVERT: "OpenZeppelin Pausable whenNotPaused on gated calls." diff --git a/.agents/skills/gooddollar/references/contracts/IdentityV4.selectors.yaml b/.agents/skills/gooddollar/references/contracts/IdentityV4.selectors.yaml new file mode 100644 index 00000000..3440a5ec --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/IdentityV4.selectors.yaml @@ -0,0 +1,51 @@ +# Generated by scripts/selectors.mjs +functions: + initialize(address,address): 0x485cc955 + initDAO(address): 0x2b14dda8 + setReverifyDaysOptions(uint8[]): 0x925e8c1a + authenticate(address): 0x08e0d29d + authenticateWithTimestamp(address,uint256): 0x96a1ef79 + addWhitelisted(address): 0x10154bad + addWhitelistedWithDIDAndChain(address,string,uint256,uint256): 0xe737031a + addWhitelistedWithDID(address,string): 0x1b027099 + removeWhitelisted(address): 0x291d9549 + renounceWhitelisted(): 0xd6cd9473 + shouldReverify(address,uint256): 0x4a03813f + isWhitelisted(address): 0x3af32abf + lastAuthenticated(address): 0xe1e360ba + addBlacklisted(address): 0x188efc16 + removeBlacklisted(address): 0xc6a276c2 + addContract(address): 0x5f539d69 + removeContract(address): 0xc375c2ef + isDAOContract(address): 0xc73cc4ae + isBlacklisted(address): 0xfe575a87 + connectAccount(address): 0xd21685f4 + disconnectAccount(address): 0x37a1a987 + getWhitelistedRoot(address): 0x2d0e9b46 + pause(bool): 0x02329a29 + setDID(address,string): 0xd3262816 + addrToDID(address): 0x54f9f7a3 + getWhitelistedOnChainId(address): 0xa061922d + isRegistered(): 0x22366844 + IDENTITY_ADMIN_ROLE(): 0x1aaff63c + PAUSER_ROLE(): 0xe63ab1e9 + TYPED_STRUCTURE(): 0x2cec5330 + whitelistedCount(): 0xb2a1de22 + whitelistedContracts(): 0xb30f7e7f + authenticationPeriod(): 0x31b376e2 + reverifyDaysOptions(uint256): 0xcadef5ac + identities(address): 0xf653b81e + didHashToAddress(bytes32): 0x67c75937 + connectedAccounts(address): 0x61320040 + oldIdentity(): 0x4125f0f2 +events: + BlacklistAdded(address): 0x44d5fe68b00f68950fb9c1ff0a61ef7f747b1a36359a7e3a7f3324db4b878967 + BlacklistRemoved(address): 0x1747ca720b1a174a464b6513ace29b1d3190b5f632b9f34147017c81425bfde8 + WhitelistedAdded(address): 0xee1504a83b6d4a361f4c1dc78ab59bfa30d6a3b6612c403e86bb01ef2984295f + WhitelistedRemoved(address): 0x270d9b30cf5b0793bbfd54c9d5b94aeb49462b8148399000265144a8722da6b6 + WhitelistedAuthenticated(address,uint256): 0xb2a82fce6d8c7a633efe9579f77b4edb96bfdf171a49bfc2ce666dc543a1f500 + ContractAdded(address): 0x89c66952b48f3e96bf1d8ba1b63189520fd988a6979b8b740bd5c5d8dc53e205 + ContractRemoved(address): 0x8d30d41865a0b811b9545d879520d2dde9f4cc49e4241f486ad9752bc904b565 + AccountConnected(address,address): 0x18f7736ef54539debd9afd3c9500b106e12ae7c70e685f5a5efd727b1ce1d54c + AccountDisconnected(address,address): 0x7cdef5f9c5cb8ce728661ede956fef26cb91eb4d7d2180cc041b73f9fef568d2 +errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/InvitesV2.abi.yaml b/.agents/skills/gooddollar/references/contracts/InvitesV2.abi.yaml new file mode 100644 index 00000000..d69e3d60 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/InvitesV2.abi.yaml @@ -0,0 +1,333 @@ +# InvitesV2 — invite codes, inviter levels, and G$ bounties (UUPS upgradeable) +# Bounty amounts on Level use G$ cents (2 decimals). Invitee receives half the inviter bounty on payout. + +meta: + name: InvitesV2 + version: "2.4" + source: + - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/contracts/invite/InvitesV2.sol + inherits: + - DAOUpgradeableContract + note: > + join binds invite code and optional inviter; bounty eligibility uses minimumDays, minimumClaims, + whitelist on invitee (and inviter when present), and same-chain whitelisting via Identity + getWhitelistedOnChainId. collectBounties and bountyFor pay invitee bountyToPay/2 and inviter + bountyToPay when applicable. Campaign inviter is address(this) when inviter code maps to contract. + deployments: + mainnet: + production: + Invites: + networkId: 122 + address: "0xCa2F09c3ccFD7aD5cB9276918Bd1868f2b922ea0" + creationBlock: 8853311 + production-celo: + Invites: + networkId: 42220 + address: "0x36829D1Cda92FFF5782d5d48991620664FC857d3" + creationBlock: 18003063 + production-xdc: + Invites: + networkId: 50 + address: "0x6bd698566632bf2e81e2278f1656CB24aAF06D2e" + creationBlock: 95144756 + related: + - references/deep-researches/inviter-invitee-reward-model.md + +initialize: + mutability: nonpayable + access: initializer + inputs: + - _ns: address + - _level0Bounty: uint256 + - _owner: address + outputs: [] + +updateAvatar: + mutability: nonpayable + access: anyone + inputs: [] + outputs: [] + +nativeToken: + mutability: view + inputs: [] + outputs: + - token: address + +dao: + mutability: view + inputs: [] + outputs: + - controller: address + +avatar: + mutability: view + inputs: [] + outputs: + - addr: address + +nameService: + mutability: view + inputs: [] + outputs: + - ns: address + +upgradeTo: + mutability: nonpayable + access: ownerOrAvatar + inputs: + - newImplementation: address + outputs: [] + +upgradeToAndCall: + mutability: payable + access: ownerOrAvatar + inputs: + - newImplementation: address + - data: bytes + outputs: [] + +proxiableUUID: + mutability: view + inputs: [] + outputs: + - slot: bytes32 + +setLevelExpirationEnabled: + mutability: nonpayable + access: ownerOrAvatar + inputs: + - _isEnabled: bool + outputs: [] + +getIdentity: + mutability: view + inputs: [] + outputs: + - identity: address + +join: + notes: + - "Registers invite code; may set inviter; may trigger _bountyFor when canCollectBountyFor is already true." + mutability: nonpayable + access: anyone + inputs: + - _myCode: bytes32 + - _inviterCode: bytes32 + outputs: [] + emits: [InviteeJoined] + errors: [NOT_ACTIVE, INVITE_CODE_IN_USE, SELF_INVITE, USER_ALREADY_JOINED] + +canCollectBountyFor: + notes: + - "Primary eligibility gate: checks active state, whitelist, minimumDays, minimumClaims, unpaid bounty, and chain constraints." + mutability: view + inputs: + - _invitee: address + outputs: + - ok: bool + +getInvitees: + mutability: view + inputs: + - _inviter: address + outputs: + - invitees: address[] + +getPendingInvitees: + mutability: view + inputs: + - _inviter: address + outputs: + - pending: address[] + +getPendingBounties: + mutability: view + inputs: + - _inviter: address + outputs: + - count: uint256 + +bountyFor: + notes: + - "Single-invitee payout path; revalidates eligibility and transfers bounty shares to inviter/invitee on success." + mutability: nonpayable + access: anyone + inputs: + - _invitee: address + outputs: + - bounty: uint256 + emits: [InviterBounty] + errors: [NOT_ACTIVE, NOT_ELIGIBLE_BOUNTY] + +collectBounties: + notes: + - "Batch payout path over caller pending invitees; useful for claiming multiple matured bounties in one tx." + mutability: nonpayable + access: anyone + inputs: [] + outputs: [] + emits: [InviterBounty] + errors: [NOT_ACTIVE] + +setLevel: + notes: + - "Configures inviter level progression rule and bounty amount (bounty units are G$ cents)." + mutability: nonpayable + access: ownerOrAvatar + inputs: + - _lvl: uint256 + - _toNext: uint256 + - _bounty: uint256 + - _daysToComplete: uint256 + outputs: [] + +setActive: + mutability: nonpayable + access: ownerOrAvatar + inputs: + - _active: bool + outputs: [] + +setCampaignCode: + notes: + - "Maps a campaign code to contract-address inviter flow (address(this)) used by join." + mutability: nonpayable + access: ownerOrAvatar + inputs: + - _code: bytes32 + outputs: [] + +setMinimums: + notes: + - "Sets minimumClaims and minimumDays thresholds used by canCollectBountyFor." + mutability: nonpayable + access: ownerOrAvatar + inputs: + - _minClaims: uint8 + - _minDays: uint8 + outputs: [] + +end: + mutability: nonpayable + access: ownerOrAvatar + inputs: [] + outputs: [] + errors: [NOT_ACTIVE] + +setOwner: + mutability: nonpayable + access: ownerOrAvatar + inputs: + - _owner: address + outputs: [] + +version: + mutability: pure + inputs: [] + outputs: + - v: string + +codeToUser: + mutability: view + inputs: + - code: bytes32 + outputs: + - user: address + +users: + notes: + - "Public getter omits dynamic invitees/pending arrays; use getInvitees and getPendingInvitees." + mutability: view + inputs: + - user: address + outputs: + - invitedBy: address + - inviteCode: bytes32 + - bountyPaid: bool + - level: uint256 + - levelStarted: uint256 + - totalApprovedInvites: uint256 + - totalEarned: uint256 + - joinedAt: uint256 + - bountyAtJoin: uint256 + +levels: + notes: + - "bounty is in G$ cents (2 decimals). Reserved struct slots are not exposed on the getter." + mutability: view + inputs: + - lvl: uint256 + outputs: + - toNext: uint256 + - bounty: uint256 + - daysToComplete: uint256 + +owner: + mutability: view + inputs: [] + outputs: + - addr: address + +goodDollar: + mutability: view + inputs: [] + outputs: + - token: address + +active: + mutability: view + inputs: [] + outputs: + - ok: bool + +stats: + notes: + - "Reserved Stats struct slots are not exposed on the getter." + mutability: view + inputs: [] + outputs: + - totalApprovedInvites: uint256 + - totalBountiesPaid: uint256 + - totalInvited: uint256 + +levelExpirationEnabled: + mutability: view + inputs: [] + outputs: + - ok: bool + +minimumClaims: + mutability: view + inputs: [] + outputs: + - n: uint8 + +minimumDays: + mutability: view + inputs: [] + outputs: + - n: uint8 + +events: + InviteeJoined: + indexed: + - inviter: address + - invitee: address + data: [] + InviterBounty: + indexed: + - inviter: address + - invitee: address + data: + - bountyPaid: uint256 + - inviterLevel: uint256 + - earnedLevel: bool + +errors: + NOT_ACTIVE: "not active — isActive modifier." + ONLY_OWNER_OR_AVATAR: "Only owner or avatar can perform this action — ownerOrAvatar." + INVITE_CODE_IN_USE: "invite code already in use — join." + SELF_INVITE: "self invite — join." + USER_ALREADY_JOINED: "user already joined — join." + NOT_ELIGIBLE_BOUNTY: "user not elligble for bounty yet — bountyFor." diff --git a/.agents/skills/gooddollar/references/contracts/InvitesV2.selectors.yaml b/.agents/skills/gooddollar/references/contracts/InvitesV2.selectors.yaml new file mode 100644 index 00000000..d7179550 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/InvitesV2.selectors.yaml @@ -0,0 +1,41 @@ +# Generated by scripts/selectors.mjs +functions: + initialize(address,uint256,address): 0xc350a1b5 + updateAvatar(): 0x1b3c90a8 + nativeToken(): 0xe1758bd8 + dao(): 0x4162169f + avatar(): 0x5aef7de6 + nameService(): 0x3e6326fc + upgradeTo(address): 0x3659cfe6 + upgradeToAndCall(address,bytes): 0x4f1ef286 + proxiableUUID(): 0x52d1902d + setLevelExpirationEnabled(bool): 0x21132aad + getIdentity(): 0x36afc6fa + join(bytes32,bytes32): 0x5b419a65 + canCollectBountyFor(address): 0x6d619ef8 + getInvitees(address): 0xe9881a5e + getPendingInvitees(address): 0xe951a3aa + getPendingBounties(address): 0x41155d5e + bountyFor(address): 0xb6567cd5 + collectBounties(): 0xaf6346b0 + setLevel(uint256,uint256,uint256,uint256): 0xb9fb2d18 + setActive(bool): 0xacec338a + setCampaignCode(bytes32): 0xf14b8649 + setMinimums(uint8,uint8): 0x47826c82 + end(): 0xefbe1c1c + setOwner(address): 0x13af4035 + version(): 0x54fd4d50 + codeToUser(bytes32): 0xba6f5680 + users(address): 0xa87430ba + levels(uint256): 0xb2596a67 + owner(): 0x8da5cb5b + goodDollar(): 0x119e5bf3 + active(): 0x02fb0c5e + stats(): 0xd80528ae + levelExpirationEnabled(): 0xa1df6fd3 + minimumClaims(): 0xc121cb92 + minimumDays(): 0xdf0dca04 +events: + InviteeJoined(address,address): 0xd8c638d8979e2ba5dba1f0d66246ee4b1c54b838f0e0a2b601365345eb23b051 + InviterBounty(address,address,uint256,uint256,bool): 0x6081787cd1bd02ab1576c52f03e8710d792d460e7881c3155d77d23893f3768b +errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/MentoBroker.abi.yaml b/.agents/skills/gooddollar/references/contracts/MentoBroker.abi.yaml new file mode 100644 index 00000000..f6f47f46 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/MentoBroker.abi.yaml @@ -0,0 +1,259 @@ +# Mento Broker (IBroker + IBrokerAdmin) — swap router and trading-limit gate for GoodDollar Mento rails +# Interface definitions are vendored in GoodProtocol; implementation lives in mento-core. + +meta: + name: Broker + version: IBroker / IBrokerAdmin + source: + - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/contracts/MentoInterfaces.sol + - https://raw.githubusercontent.com/mento-org/mento-core/main/contracts/swap/Broker.sol + - https://raw.githubusercontent.com/mento-org/mento-core/main/contracts/interfaces/IBroker.sol + implements: [IBroker, IBrokerAdmin] + note: > + swapIn is exact-input; swapOut is exact-output. Quotes route through the + configured exchangeProvider + exchangeId. Reverts use require strings in + Broker.sol plus TradingLimits library checks ("amountOutMin not met", + "amountInMax exceeded", trading-limit errors). + deployments: + mainnet: + production-celo: + MentoBroker: + networkId: 42220 + address: "0x88de45906D4F5a57315c133620cfa484cB297541" + creationBlock: 31415857 + production-xdc: + MentoBroker: + networkId: 50 + address: "0x88de45906D4F5a57315c133620cfa484cB297541" + creationBlock: 100091095 + related: + - https://docs.gooddollar.org/for-developers/core-contracts/mentobroker + - references/guides/swap.md + +initialize: + mutability: nonpayable + access: initializer + inputs: + - _exchangeProviders: address[] + - _reserves: address[] + outputs: [] + +setReserves: + notes: + - "Updates reserve address per already-listed provider without changing provider registration." + mutability: nonpayable + access: owner + inputs: + - _exchangeProviders: address[] + - _reserves: address[] + outputs: [] + emits: [ReserveSet] + errors: + - EXCHANGE_PROVIDER_MISSING + - RESERVE_ZERO + +addExchangeProvider: + mutability: nonpayable + access: owner + inputs: + - exchangeProvider: address + - reserve: address + outputs: + - index: uint256 + emits: [ExchangeProviderAdded, ReserveSet] + errors: + - PROVIDER_ALREADY_LISTED + - PROVIDER_ZERO + - RESERVE_ZERO + +removeExchangeProvider: + mutability: nonpayable + access: owner + inputs: + - exchangeProvider: address + - index: uint256 + outputs: [] + emits: [ExchangeProviderRemoved] + errors: + - INDEX_MISMATCH + +getAmountIn: + notes: + - "Exact-output quote path: returns required tokenIn for target amountOut." + - "Pre-checks collateral reserve balance when tokenOut is collateral." + mutability: view + inputs: + - exchangeProvider: address + - exchangeId: bytes32 + - tokenIn: address + - tokenOut: address + - amountOut: uint256 + outputs: + - amountIn: uint256 + errors: + - EXCHANGE_PROVIDER_MISSING + - INSUFFICIENT_RESERVE_BALANCE + +getAmountOut: + notes: + - "Exact-input quote path: returns expected tokenOut for fixed amountIn." + - "Pre-checks collateral reserve balance when tokenOut is collateral." + mutability: view + inputs: + - exchangeProvider: address + - exchangeId: bytes32 + - tokenIn: address + - tokenOut: address + - amountIn: uint256 + outputs: + - amountOut: uint256 + errors: + - EXCHANGE_PROVIDER_MISSING + - INSUFFICIENT_RESERVE_BALANCE + +swapIn: + notes: + - "Transfers tokenIn from msg.sender then delivers tokenOut; enforces amountOut >= amountOutMin after exchange + limits." + mutability: nonpayable + access: nonReentrant + inputs: + - exchangeProvider: address + - exchangeId: bytes32 + - tokenIn: address + - tokenOut: address + - amountIn: uint256 + - amountOutMin: uint256 + outputs: + - amountOut: uint256 + emits: [Swap] + errors: + - EXCHANGE_PROVIDER_MISSING + - INSUFFICIENT_RESERVE_BALANCE + - AMOUNT_OUT_MIN_NOT_MET + - TRADING_LIMIT_OR_SAFE_TRANSFER + +swapOut: + notes: + - "Computes required tokenIn then pulls up to amountInMax; reverts if amountIn would exceed cap." + mutability: nonpayable + access: nonReentrant + inputs: + - exchangeProvider: address + - exchangeId: bytes32 + - tokenIn: address + - tokenOut: address + - amountOut: uint256 + - amountInMax: uint256 + outputs: + - amountIn: uint256 + emits: [Swap] + errors: + - EXCHANGE_PROVIDER_MISSING + - INSUFFICIENT_RESERVE_BALANCE + - AMOUNT_IN_MAX_EXCEEDED + - TRADING_LIMIT_OR_SAFE_TRANSFER + +burnStableTokens: + notes: + - "Owner-maintained escape hatch burning stables from msg.sender via reserve plumbing." + mutability: nonpayable + access: owner + inputs: + - token: address + - amount: uint256 + outputs: + - ok: bool + errors: + - COLLATERAL_TRANSFER_FAILED + +configureTradingLimit: + notes: + - "Sets per (exchangeId, token) limit config used by swapIn/swapOut guardTradingLimits checks." + mutability: nonpayable + access: owner + inputs: + - exchangeId: bytes32 + - token: address + - config: TradingLimits.Config + outputs: [] + emits: [TradingLimitConfigured] + +getExchangeProviders: + notes: + - "Discovery helper for clients that need the active provider set before quoting/swapping." + mutability: view + inputs: [] + outputs: + - providers: address[] + +exchangeProviders: + mutability: view + inputs: + - index: uint256 + outputs: + - exchangeProvider: address + +isExchangeProvider: + mutability: view + inputs: + - exchangeProvider: address + outputs: + - ok: bool + +tradingLimitsState: + mutability: view + inputs: + - eid: bytes32 + outputs: + - state: TradingLimits.State + +tradingLimitsConfig: + mutability: view + inputs: + - eid: bytes32 + outputs: + - config: TradingLimits.Config + +events: + Swap: + indexed: + - exchangeId: bytes32 + - trader: address + - tokenIn: address + data: + - exchangeProvider: address + - tokenOut: address + - amountIn: uint256 + - amountOut: uint256 + TradingLimitConfigured: + indexed: [] + data: + - exchangeId: bytes32 + - token: address + - config: TradingLimits.Config + ExchangeProviderAdded: + indexed: + - exchangeProvider: address + data: [] + ExchangeProviderRemoved: + indexed: + - exchangeProvider: address + data: [] + ReserveSet: + indexed: + - newAddress: address + - prevAddress: address + data: [] + +errors: + EXCHANGE_PROVIDER_MISSING: "ExchangeProvider does not exist" + EXCHANGE_PROVIDER_EXISTS: "ExchangeProvider already exists in the list" + PROVIDER_ZERO: "ExchangeProvider address can't be 0" + RESERVE_ZERO: "Reserve address can't be 0" + INDEX_MISMATCH: "index doesn't match provider" + INSUFFICIENT_RESERVE_BALANCE: "Insufficient balance in reserve" + AMOUNT_OUT_MIN_NOT_MET: "amountOutMin not met" + AMOUNT_IN_MAX_EXCEEDED: "amountInMax exceeded" + COLLATERAL_TRANSFER_FAILED: "Transfer of the collateral asset failed" + AMOUNT_TOO_LARGE: "amountIn too large / amountOut too large — uint256 to int256 safety checks" + TRADING_LIMIT_OR_SAFE_TRANSFER: "TradingLimits library reverts or ERC20 safety failures during swap" diff --git a/.agents/skills/gooddollar/references/contracts/MentoBroker.selectors.yaml b/.agents/skills/gooddollar/references/contracts/MentoBroker.selectors.yaml new file mode 100644 index 00000000..249edce4 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/MentoBroker.selectors.yaml @@ -0,0 +1,24 @@ +# Generated by scripts/selectors.mjs +functions: + initialize(address[],address[]): 0x73cf25f8 + setReserves(address[],address[]): 0xddeb9dd2 + addExchangeProvider(address,address): 0x770d0a34 + removeExchangeProvider(address,uint256): 0x04710d53 + getAmountIn(address,bytes32,address,address,uint256): 0x04e45640 + getAmountOut(address,bytes32,address,address,uint256): 0xa20f2305 + swapIn(address,bytes32,address,address,uint256,uint256): 0xddbbe850 + swapOut(address,bytes32,address,address,uint256,uint256): 0xd163b135 + burnStableTokens(address,uint256): 0x131cab2a + configureTradingLimit(bytes32,address,(uint32,uint32,int48,int48,int48,int48,int48,uint8)): 0xa868d140 + getExchangeProviders(): 0x2cac2568 + exchangeProviders(uint256): 0xc4454fdc + isExchangeProvider(address): 0xd1d786b1 + tradingLimitsState(bytes32): 0xf01ecd17 + tradingLimitsConfig(bytes32): 0x821a816c +events: + Swap(bytes32,address,address,address,address,uint256,uint256): 0x46e6aeaebfb8f1b6a9be6403b5fc420d9827172046f365e786acb9d5b56c9409 + TradingLimitConfigured(bytes32,address,(uint32,uint32,int48,int48,int48,int48,int48,uint8)): 0x3ed50aa123e1e547aa90fcf88624ef8278d253e252bb1feac75eda93e1905bb6 + ExchangeProviderAdded(address): 0x2ee2cb0721ec60b86190cae5c48e25064b69b35abad32452a4ec99d232033de2 + ExchangeProviderRemoved(address): 0x29e92ab2e30f4f74283034c28c451b6faac986b554f1808101eb6418bdba19d4 + ReserveSet(address,address): 0xb69e1c416d8be92ac92c8e97e77c4626fba5e6ab50161099f659ea3303479e50 +errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/MessagePassingBridge.abi.yaml b/.agents/skills/gooddollar/references/contracts/MessagePassingBridge.abi.yaml new file mode 100644 index 00000000..d4bb1f2d --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/MessagePassingBridge.abi.yaml @@ -0,0 +1,542 @@ +meta: + name: MessagePassingBridge + version: Axelar + LayerZero message passing + source: + - https://raw.githubusercontent.com/GoodDollar/GoodBridge/master/packages/bridge-contracts/contracts/messagePassingBridge/MessagePassingBridge.sol + - https://raw.githubusercontent.com/GoodDollar/GoodBridge/master/packages/bridge-contracts/contracts/messagePassingBridge/IMessagePassingBridge.sol + note: > + Bridge implementation that burns source tokens and mints destination tokens, + transporting requests through Axelar or LayerZero. + Cross-chain transport uses msg.value on the source (LayerZero estimateSendFee path, Axelar gas prepay); destination mint applies bridgeFees bps to the delivered G$ amount. See references/guides/bridge.md "Bridge fee context". + deployments: + mainnet: + production: + MpbBridge: + networkId: 122 + address: "0xa3247276DbCC76Dd7705273f766eB3E8a5ecF4a5" + creationBlock: 25464921 + production-celo: + MpbBridge: + networkId: 42220 + address: "0xa3247276DbCC76Dd7705273f766eB3E8a5ecF4a5" + creationBlock: 21473545 + production-xdc: + MpbBridge: + networkId: 50 + address: "0xa3247276DbCC76Dd7705273f766eB3E8a5ecF4a5" + creationBlock: 95254417 + related: + - references/guides/bridge.md + +initialize: + notes: + - "Initial bootstrap for DAO wiring and bridge policy state." + - "Sets guardian to msg.sender and uses NameService UBISCHEME as feeRecipient fallback to avatar when missing." + - "Preloads default LZ mappings for Ethereum, Celo, Fuse, and XDC using this contract as trusted remote." + mutability: nonpayable + access: initializer + inputs: + - nameService: address + - limits: (uint256,uint256,uint256,uint256,bool) + - fees: (uint256,uint256,uint256) + outputs: [] + +upgrade: + notes: + - "Reinitializer for migration path; re-applies default LZ mappings through internal add function." + - "Used when upgrading existing proxy deployments to mapping-aware version." + mutability: nonpayable + access: reinitializer + inputs: [] + outputs: [] + +addLzChainSupport: + notes: + - "Administrative chain wiring method for LayerZero transport." + - "Writes both forward (chainId->lzChainId) and reverse (lzChainId->chainId) mappings." + - "Also updates trustedRemoteLookup to enforce source-contract authenticity on receive." + mutability: nonpayable + access: ownerOrAvatarOrGuardian + inputs: + - lzChainId: uint16 + - chainId: uint256 + - remote: address + outputs: [] + +approveRequest: + notes: + - "Manual override for stuck/exceptional inbound requests." + - "When approved, destination _bridgeFrom skips limits enforcement for that request id." + mutability: nonpayable + access: ownerOrAvatarOrGuardian + inputs: + - id: uint256 + outputs: [] + +preventRequest: + notes: + - "Emergency/manual block for a specific request id." + - "Prevents execution by pre-marking executedRequests[id]=true." + mutability: nonpayable + access: ownerOrAvatarOrGuardian + inputs: + - id: uint256 + outputs: [] + +setFeeRecipient: + notes: + - "Sets destination fee mint recipient." + - "If feeRecipient is zero, fee minting is skipped in _bridgeFrom." + mutability: nonpayable + access: ownerOrAvatarOrGuardian + inputs: + - recipient: address + outputs: [] + +setBridgeLimits: + notes: + - "Updates operational throttles: min amount, tx cap, account/day cap, global/day cap, whitelist mode." + - "These limits are checked by canBridge and enforced in _enforceLimits." + mutability: nonpayable + access: ownerOrAvatarOrGuardian + inputs: + - limits: (uint256,uint256,uint256,uint256,bool) + outputs: [] + +setBridgeFees: + notes: + - "Updates bridge fee parameters in basis points with min/max clamps." + - "Reverts when fee exceeds 10000 bps." + - "Effective fee deduction is computed in _takeFee during destination execution." + mutability: nonpayable + access: ownerOrAvatarOrGuardian + inputs: + - fees: (uint256,uint256,uint256) + outputs: [] + +setDisabledBridges: + notes: + - "Toggles allowlist state for source bridge combinations." + - "Key format is keccak256(abi.encode(sourceChainId, BridgeService))." + - "Checked in _bridgeFrom before request execution." + mutability: nonpayable + access: ownerOrAvatarOrGuardian + inputs: + - bridgeKeys: bytes32[] + - disabled: bool[] + outputs: [] + +setFaucet: + notes: + - "Configures optional faucet top-up callback used after successful destination processing." + mutability: nonpayable + access: ownerOrAvatarOrGuardian + inputs: + - _faucet: address + outputs: [] + +setGuardian: + notes: + - "Rotates guardian authority used by owner/avatar/guardian admin gate." + mutability: nonpayable + access: ownerOrAvatarOrGuardian + inputs: + - _guardian: address + outputs: [] + +withdraw: + notes: + - "Avatar-only rescue function for arbitrary token balances held by bridge." + - "Amount 0 means full balance withdrawal." + mutability: nonpayable + access: avatar + inputs: + - token: address + - amount: uint256 + outputs: [] + +pauseBridge: + notes: + - "Sets isClosed state for outbound flow." + - "When closed, _bridgeTo reverts with BRIDGE_LIMITS('closed')." + mutability: nonpayable + access: ownerOrAvatarOrGuardian + inputs: + - isPaused: bool + outputs: [] + +canBridge: + notes: + - "Purely diagnostic read path for preflight checks." + - "Returns (ok, reason) using BridgeHelperLibrary policy evaluation." + - "Includes close flag, whitelist gate, and per-account/global daily limit windows." + mutability: view + inputs: + - from: address + - amount: uint256 + outputs: + - isWithinLimit: bool + - error: string + +bridgeTo: + notes: + - "Generic outbound bridge entrypoint selecting BridgeService.AXELAR or BridgeService.LZ." + - "Burns source-side token amount, normalizes to 18 decimals, and emits BridgeRequest with request id." + - "Requires msg.value for transport fee payment." + mutability: payable + access: anyone + inputs: + - target: address + - targetChainId: uint256 + - amount: uint256 + - bridge: uint8 + outputs: [] + +bridgeToWithLz: + notes: + - "Outbound convenience wrapper fixed to LayerZero transport." + - "If adapterParams is empty, contract injects default type-1 options with 400k gas." + - "Compares required native fee from estimateSendFee against msg.value and reverts on underpayment." + mutability: payable + access: anyone + inputs: + - target: address + - targetChainId: uint256 + - amount: uint256 + - adapterParams: bytes + outputs: [] + +bridgeToWithAxelar: + notes: + - "Outbound convenience wrapper fixed to Axelar transport." + - "Uses gasRefundAddress when provided, otherwise msg.sender as refund recipient." + mutability: payable + access: anyone + inputs: + - target: address + - targetChainId: uint256 + - amount: uint256 + - gasRefundAddress: address + outputs: [] + +toLzChainId: + notes: + - "Lookup helper from local chain id to LayerZero chain id." + mutability: view + inputs: + - chainId: uint256 + outputs: + - lzChainId: uint16 + +fromLzChainId: + notes: + - "Lookup helper from LayerZero chain id to local chain id." + mutability: view + inputs: + - lzChainId: uint16 + outputs: + - chainId: uint256 + +toAxelarChainId: + notes: + - "Pure mapping in current implementation: chainId 1 -> 'Ethereum', 5 -> 'ethereum-2', 42220 and 44787 -> 'celo'; any other id returns empty string and outbound Axelar reverts UNSUPPORTED_CHAIN." + mutability: pure + inputs: + - chainId: uint256 + outputs: + - axlChainId: string + +fromAxelarChainId: + notes: + - "Maps Axelar chain-name strings back into local numeric chain ids." + mutability: pure + inputs: + - axlChainId: string + outputs: + - chainId: uint256 + +estimateSendFee: + notes: + - "Transport fee preflight for LZ path." + - "Builds payload with request id set to 0 for estimation." + - "Returns (0,0) on endpoint estimation failure in handler implementation." + - "_normalizedAmount must match BridgeHelperLibrary.normalizeFromTokenTo18Decimals burned raw amount using native token decimals on the outbound chain." + mutability: view + inputs: + - _dstChainId: uint16 + - _fromAddress: address + - _toAddress: address + - _normalizedAmount: uint256 + - _useZro: bool + - _adapterParams: bytes + outputs: + - nativeFee: uint256 + - zroFee: uint256 + +lzReceive: + notes: + - "LayerZero receive entrypoint." + - "Validates caller is configured endpoint before dispatching nonblocking receive pipeline." + mutability: nonpayable + inputs: + - _srcChainId: uint16 + - _srcAddress: bytes + - _nonce: uint64 + - _payload: bytes + outputs: [] + +lzEndpoint_: + notes: + - "Immutable LayerZero endpoint configured in constructor." + mutability: view + inputs: [] + outputs: + - endpoint: address + +HOME_CHAIN_ID: + notes: + - "Immutable deployment home chain marker." + mutability: view + inputs: [] + outputs: + - chainId: uint32 + +guardian: + notes: + - "Current guardian account authorized for admin operations." + mutability: view + inputs: [] + outputs: + - account: address + +executedRequests: + notes: + - "Replay protection state for inbound request ids." + mutability: view + inputs: + - id: uint256 + outputs: + - done: bool + +disabledSourceBridges: + notes: + - "Per-source bridge toggle state keyed by (sourceChainId, bridgeService) hash." + mutability: view + inputs: + - key: bytes32 + outputs: + - disabled: bool + +isClosed: + notes: + - "Outbound bridge pause state." + mutability: view + inputs: [] + outputs: + - closed: bool + +bridgeFees: + notes: + - "Current fee policy parameters: minFee, maxFee, feeBps." + mutability: view + inputs: [] + outputs: + - minFee: uint256 + - maxFee: uint256 + - fee: uint256 + +bridgeLimits: + notes: + - "Current bridge traffic limits and whitelist policy." + mutability: view + inputs: [] + outputs: + - dailyLimit: uint256 + - txLimit: uint256 + - accountDailyLimit: uint256 + - minAmount: uint256 + - onlyWhitelisted: bool + +bridgeDailyLimit: + notes: + - "Aggregate 24h bridge usage tracker." + mutability: view + inputs: [] + outputs: + - lastTransferReset: uint256 + - bridged24Hours: uint256 + +accountsDailyLimit: + notes: + - "Per-account 24h usage tracker." + mutability: view + inputs: + - account: address + outputs: + - lastTransferReset: uint256 + - bridged24Hours: uint256 + +faucet: + notes: + - "Optional gas top-up contract used on destination completion." + mutability: view + inputs: [] + outputs: + - addr: address + +currentId: + notes: + - "Monotonic nonce component used to derive outbound request ids." + mutability: view + inputs: [] + outputs: + - id: uint256 + +lzChainIdsMapping: + notes: + - "Local chain id -> LayerZero chain id mapping." + mutability: view + inputs: + - chainId: uint256 + outputs: + - lzChainId: uint16 + +feeRecipient: + notes: + - "Address receiving bridge fee mints on destination." + mutability: view + inputs: [] + outputs: + - recipient: address + +approvedRequests: + notes: + - "Manual approvals allowing request execution above standard limits." + mutability: view + inputs: + - id: uint256 + outputs: + - approved: bool + +lzChainToIdsMapping: + notes: + - "LayerZero chain id -> local chain id mapping." + mutability: view + inputs: + - lzChainId: uint16 + outputs: + - chainId: uint256 + +trustedRemoteLookup: + notes: + - "Trusted remote path used to authenticate inbound LZ/Axelar source contract." + mutability: view + inputs: + - lzChainId: uint16 + outputs: + - path: bytes + +gateway: + notes: + - "Axelar gateway address inherited from AxelarExecutable." + mutability: view + inputs: [] + outputs: + - addr: address + +gasService: + notes: + - "Axelar gas service used to prepay cross-chain execution." + mutability: view + inputs: [] + outputs: + - addr: address + +nameService: + notes: + - "DAO NameService pointer from DAOUpgradeableContract." + mutability: view + inputs: [] + outputs: + - addr: address + +avatar: + notes: + - "DAO avatar authority address." + mutability: view + inputs: [] + outputs: + - addr: address + +owner: + notes: + - "Ownable owner from upgradeable ownership flow." + mutability: view + inputs: [] + outputs: + - account: address + +events: + BridgeRequest: + indexed: + - from: address + - to: address + - id: uint256 + data: + - targetChainId: uint256 + - normalizedAmount: uint256 + - timestamp: uint256 + - bridge: uint8 + note: "Outbound intent event; relayers consume this to deliver payload on destination." + ExecutedTransfer: + indexed: + - from: address + - to: address + - id: uint256 + data: + - normalizedAmount: uint256 + - fee: uint256 + - sourceChainId: uint256 + - bridge: uint8 + note: "Destination completion event after replay checks, source-auth checks, limit checks, and mint/fee mint." + FalseSender: + indexed: [] + data: + - sourceChainId: uint256 + - sourceAddress: address + note: "Inbound message ignored because source contract does not match trusted remote path." + +errors: + AlreadyInitialized: "LZ chain support already initialized for this lz chain id." + NOT_GUARDIAN: + inputs: + - sender: address + WRONG_TOKEN: + inputs: + - token: address + INVALID_TARGET_OR_CHAINID: + inputs: + - target: address + - chainId: uint256 + BRIDGE_LIMITS: + inputs: + - reason: string + TRANSFER_FROM: "Source burn/transferFrom style failure." + TRANSFER: "Destination transfer style failure." + ALREADY_EXECUTED: + inputs: + - requestId: uint256 + MISSING_FEE: "Bridge call requires msg.value for transport fee." + UNSUPPORTED_CHAIN: + inputs: + - chainId: uint256 + LZ_FEE: + inputs: + - required: uint256 + - sent: uint256 + INVALID_SENDER: + inputs: + - _srcAddress: bytes + INVALID_ENDPOINT: + inputs: + - lzEndpoint: address diff --git a/.agents/skills/gooddollar/references/contracts/MessagePassingBridge.selectors.yaml b/.agents/skills/gooddollar/references/contracts/MessagePassingBridge.selectors.yaml new file mode 100644 index 00000000..104b9435 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/MessagePassingBridge.selectors.yaml @@ -0,0 +1,52 @@ +# Generated by scripts/selectors.mjs +functions: + initialize(address,(uint256,uint256,uint256,uint256,bool),(uint256,uint256,uint256)): 0xfad16abe + upgrade(): 0xd55ec697 + addLzChainSupport(uint16,uint256,address): 0xc01c8123 + approveRequest(uint256): 0xd7d1bbdb + preventRequest(uint256): 0xc612f9ea + setFeeRecipient(address): 0xe74b981b + setBridgeLimits((uint256,uint256,uint256,uint256,bool)): 0x29b70872 + setBridgeFees((uint256,uint256,uint256)): 0x82dc737d + setDisabledBridges(bytes32[],bool[]): 0xc78ab882 + setFaucet(address): 0xd8b31c77 + setGuardian(address): 0x8a0dac4a + withdraw(address,uint256): 0xf3fef3a3 + pauseBridge(bool): 0x1a394795 + canBridge(address,uint256): 0x3095634a + bridgeTo(address,uint256,uint256,uint8): 0x1fec5c5c + bridgeToWithLz(address,uint256,uint256,bytes): 0xc56bbdd9 + bridgeToWithAxelar(address,uint256,uint256,address): 0x40a00aaf + toLzChainId(uint256): 0x5b23a990 + fromLzChainId(uint16): 0x16ad5512 + toAxelarChainId(uint256): 0x9a9ee081 + fromAxelarChainId(string): 0x56026f37 + estimateSendFee(uint16,address,address,uint256,bool,bytes): 0x05fead15 + lzReceive(uint16,bytes,uint64,bytes): 0x001d3567 + lzEndpoint_(): 0x020051cf + HOME_CHAIN_ID(): 0x8f65be85 + guardian(): 0x452a9320 + executedRequests(uint256): 0x425cfb53 + disabledSourceBridges(bytes32): 0x69c20d36 + isClosed(): 0xc2b6b58c + bridgeFees(): 0x7b0240c0 + bridgeLimits(): 0xc6dd812f + bridgeDailyLimit(): 0xb2f7667f + accountsDailyLimit(address): 0xd4227947 + faucet(): 0xde5f72fd + currentId(): 0xe00dd161 + lzChainIdsMapping(uint256): 0xb5569f18 + feeRecipient(): 0x46904840 + approvedRequests(uint256): 0x0dce292e + lzChainToIdsMapping(uint16): 0x73800fc4 + trustedRemoteLookup(uint16): 0x7533d788 + gateway(): 0x116191b6 + gasService(): 0x6a22d8cc + nameService(): 0x3e6326fc + avatar(): 0x5aef7de6 + owner(): 0x8da5cb5b +events: + BridgeRequest(address,address,uint256,uint256,uint256,uint256,uint8): 0x34b675c8c84b6a9e7979a0d3a54f2b036a19d6444167091a925af2d81e8e66fe + ExecutedTransfer(address,address,uint256,uint256,uint256,uint256,uint8): 0x81e772e0c4366ddbae472926005267ef278dbb257be45dfe97c676ceae348dbe + FalseSender(uint256,address): 0x1eafb58197ea0dc76b9278ccad47f61a239f00b017d89a19081a89321cce213d +errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/NameService.abi.yaml b/.agents/skills/gooddollar/references/contracts/NameService.abi.yaml new file mode 100644 index 00000000..01692b7a --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/NameService.abi.yaml @@ -0,0 +1,104 @@ +# NameService — string-key to address registry for GoodProtocol deployments +# Resolves keys such as IDENTITY, UBISCHEME, GOODDOLLAR, GDAO_STAKING. + +meta: + name: NameService + version: UUPS-upgradeable v1 + source: + - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/contracts/utils/NameService.sol + implements: [] + inherits: [Initializable, UUPSUpgradeable] + note: > + Almost every integration starts here. Writes are restricted to the DAO + avatar. Reads are permissionless. Use getAddress(string) with the same + key strings your deployment documented in releases/deployment.json. + deployments: + mainnet: + production: + NameService: + networkId: 122 + address: "0xec6dcE387B1616a0c44fF2E4fA9E90e53Cf14eb0" + creationBlock: 15740314 + production-celo: + NameService: + networkId: 42220 + address: "0x0F5dB7a64A6a64052693676CA898EC7F7A94FF4e" + creationBlock: 17237962 + production-xdc: + NameService: + networkId: 50 + address: "0x1e5154Bf5e31FF56051bbd45958b879Fb7a290FE" + creationBlock: 95143608 + related: + - https://docs.gooddollar.org/for-developers/core-contracts/nameservice + - https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json + +addresses: + # Solidity auto-getter for public mapping addresses(bytes32 => address). + notes: + - "Prefer getAddress(string) off-chain; this expects keccak256(abi.encodePacked(name)) for manual lookups." + mutability: view + inputs: + - nameHash: bytes32 + outputs: + - addr: address + +dao: + mutability: view + inputs: [] + outputs: + - controller: address + +initialize: + notes: + - "Initializer wires Controller and optional pre-hashed name batch." + - "Automatically registers CONTROLLER and AVATAR hashes to dao and dao.avatar()." + mutability: nonpayable + access: initializer + inputs: + - _dao: address + - _nameHashes: bytes32[] + - _addresses: address[] + outputs: [] + +setAddress: + notes: + - "Stores addresses[keccak256(bytes(name))] = addr and emits AddressChanged." + mutability: nonpayable + access: avatar + inputs: + - name: string + - addr: address + outputs: [] + emits: [AddressChanged] + errors: [ONLY_AVATAR] + +setAddresses: + notes: + - "Batch write by raw bytes32 keys; no per-name event emission in this loop." + mutability: nonpayable + access: avatar + inputs: + - hash: bytes32[] + - addrs: address[] + outputs: [] + errors: [ONLY_AVATAR] + +getAddress: + notes: + - "Primary lookup used by claim, staking, identity, and swap integrations." + mutability: view + inputs: + - name: string + outputs: + - addr: address + +events: + AddressChanged: + indexed: [] + data: + - name: string + - addr: address + +errors: + ONLY_AVATAR: "only avatar can call this method — revert string on any avatar-gated path including UUPS _authorizeUpgrade." diff --git a/.agents/skills/gooddollar/references/contracts/NameService.selectors.yaml b/.agents/skills/gooddollar/references/contracts/NameService.selectors.yaml new file mode 100644 index 00000000..48e1bcfa --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/NameService.selectors.yaml @@ -0,0 +1,11 @@ +# Generated by scripts/selectors.mjs +functions: + addresses(bytes32): 0x699f200f + dao(): 0x4162169f + initialize(address,bytes32[],address[]): 0xd41b4e72 + setAddress(string,address): 0x9b2ea4bd + setAddresses(bytes32[],address[]): 0x4ab01f5b + getAddress(string): 0xbf40fac1 +events: + AddressChanged(string,address): 0x135cf55549d8538a41f19f46cc85625da93e68b63484cca8fcb9aaf19e520137 +errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/SuperToken.abi.yaml b/.agents/skills/gooddollar/references/contracts/SuperToken.abi.yaml new file mode 100644 index 00000000..abfc91cb --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/SuperToken.abi.yaml @@ -0,0 +1,1033 @@ +# SuperToken — the Superfluid Protocol's token standard +# Merges three variants into a single ABI surface: +# • Wrapper Super Token — wraps an existing ERC-20 (upgrade / downgrade) +# • Native Asset Super Token (SETH) — wraps chain-native asset (upgradeByETH / downgradeToETH) +# • Pure Super Token — pre-minted, no underlying token (upgrade/downgrade revert) +# +# Inherits ERC-20, ERC-777, ERC-2612 (permit), EIP-5267, and the Superfluid real-time +# balance and agreement hosting layer from SuperfluidToken. +# +# Proxy / upgradability functions are omitted: +# castrate, getCodeAddress, proxiableUUID, initialize, initializeWithAdmin, updateCode +# Proxy-related events are also omitted: CodeUpdated, Initialized. + +meta: + name: SuperToken + version: v1 + source: + - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol + - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol + - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/superfluid/SuperfluidToken.sol + - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/interfaces/superfluid/IYieldBackend.sol + inherits: [SuperfluidToken, ISuperToken, UUPSProxiable] + yield_backends: + - name: AaveYieldBackend + note: "Production backend for ERC-20 wrapper tokens using Aave V3" + source: https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/superfluid/AaveYieldBackend.sol + - name: AaveETHYieldBackend + note: "Variant for native-asset (ETH/WETH) wrapper tokens using Aave V3" + source: https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/superfluid/AaveETHYieldBackend.sol + - name: ERC4626YieldBackend + note: "Generic backend for any ERC-4626 vault" + source: https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/superfluid/ERC4626YieldBackend.sol + - name: SparkYieldBackend + note: "Extends ERC4626YieldBackend for Spark Protocol vaults with referral tracking" + source: https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/superfluid/SparkYieldBackend.sol + variants: + wrapper: "Standard ERC-20 wrapper — upgrade() / downgrade()" + native-asset: "Native asset wrapper (SETH) — upgradeByETH() / downgradeToETH()" + pure: "Pre-minted supply, no underlying — upgrade/downgrade revert" + note: > + Super Tokens are deployed as individual proxies (one per token) by the + SuperTokenFactory. There is no single canonical deployment address. + All proxies share the same logic contract which is upgraded via governance. + +# wrapper super token — a Super Token backed by an ERC-20 underlying token +# native asset super token (SETH) — a Super Token backed by the chain-native asset (ETH, MATIC, etc.) +# pure super token — a Super Token with no underlying; supply is pre-minted at deploy time +# underlying token — the ERC-20 token that a wrapper super token wraps +# real-time balance — balance that changes continuously based on active agreements (CFA/GDA) +# available balance — real-time balance minus locked deposits +# critical — an account whose available balance is negative +# solvent — an account whose real-time balance (before deposit deductions) is non-negative +# operator (ERC-777) — an address authorized to send/burn tokens on behalf of a holder +# buffer / deposit — collateral locked while an outgoing flow is active + +# == ERC-20 Token Info == + +name: + mutability: view + inputs: [] + outputs: + - string + +symbol: + mutability: view + inputs: [] + outputs: + - string + +decimals: + # Always returns 18 regardless of underlying token decimals. + notes: + - "Always returns 18, regardless of the underlying token's decimals (e.g. USDC has 6). The upgrade/downgrade functions handle decimal conversion internally." + mutability: pure + inputs: [] + outputs: + - uint8 + +totalSupply: + mutability: view + inputs: [] + outputs: + - uint256 + +# == ERC-20 Balance & Allowance == + +balanceOf: + # Returns max(0, availableBalance) — clamps negative balances to zero. + # For the full real-time picture, use realtimeBalanceOfNow. + notes: + - "Returns max(0, availableBalance) — negative/critical balances are clamped to zero. Use realtimeBalanceOfNow to detect critical accounts." + mutability: view + inputs: + - account: address + outputs: + - balance: uint256 + +allowance: + mutability: view + inputs: + - account: address + - spender: address + outputs: + - uint256 + +approve: + mutability: nonpayable + access: anyone + inputs: + - spender: address + - amount: uint256 + outputs: + - bool + emits: [Approval] + errors: [SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS, SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS] + +increaseAllowance: + mutability: nonpayable + access: anyone + inputs: + - spender: address + - addedValue: uint256 + outputs: + - bool + emits: [Approval] + errors: [SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS, SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS] + +decreaseAllowance: + mutability: nonpayable + access: anyone + inputs: + - spender: address + - subtractedValue: uint256 + outputs: + - bool + emits: [Approval] + errors: [SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS, SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS] + +# == ERC-20 Transfers == + +transfer: + # ERC-20 transfer. Does NOT invoke ERC-777 send/receive hooks (by design). + mutability: nonpayable + access: anyone + inputs: + - recipient: address + - amount: uint256 + outputs: + - bool + emits: [Sent, Transfer] + errors: [SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS, SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS, SF_TOKEN_MOVE_INSUFFICIENT_BALANCE] + +transferFrom: + # ERC-20 transferFrom. Does NOT invoke ERC-777 send/receive hooks. + # Does NOT emit an Approval event on allowance spend (OZ v5 behaviour). + mutability: nonpayable + access: anyone + inputs: + - holder: address + - recipient: address + - amount: uint256 + outputs: + - bool + emits: [Sent, Transfer] + errors: [SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS, SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS, SF_TOKEN_MOVE_INSUFFICIENT_BALANCE] + +transferAll: + # Transfer the caller's entire balanceOf to recipient. + mutability: nonpayable + access: anyone # transfers msg.sender's entire balance + inputs: + - recipient: address + emits: [Sent, Transfer] + errors: [SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS, SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS, SF_TOKEN_MOVE_INSUFFICIENT_BALANCE] + +# == ERC-777 == + +granularity: + # Always returns 1. + mutability: pure + inputs: [] + outputs: + - uint256 + +send: + # ERC-777 send — invokes tokensToSend / tokensReceived hooks. + mutability: nonpayable + access: anyone + inputs: + - recipient: address + - amount: uint256 + - userData: bytes + emits: [Sent, Transfer] + errors: [SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS, SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS, SF_TOKEN_MOVE_INSUFFICIENT_BALANCE, SUPER_TOKEN_NOT_ERC777_TOKENS_RECIPIENT] + +burn: + # ERC-777 burn — in SuperToken this actually performs a downgrade (unwrap). + notes: + - "Gotcha: Reverts with SUPER_TOKEN_NO_UNDERLYING_TOKEN on Pure Super Tokens." + mutability: nonpayable + access: anyone + inputs: + - amount: uint256 + - userData: bytes + emits: [Burned, Transfer, TokenDowngraded] + errors: [SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS, SF_TOKEN_BURN_INSUFFICIENT_BALANCE, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED] + +authorizeOperator: + mutability: nonpayable + access: anyone + inputs: + - operator: address + emits: [AuthorizedOperator] + +revokeOperator: + mutability: nonpayable + access: anyone + inputs: + - operator: address + emits: [RevokedOperator] + +isOperatorFor: + mutability: view + inputs: + - operator: address + - tokenHolder: address + outputs: + - bool + +defaultOperators: + mutability: view + inputs: [] + outputs: + - "address[]" + +operatorSend: + # ERC-777 operatorSend — invokes tokensToSend / tokensReceived hooks. + mutability: nonpayable + access: operator + inputs: + - sender: address + - recipient: address + - amount: uint256 + - userData: bytes + - operatorData: bytes + emits: [Sent, Transfer] + errors: [SUPER_TOKEN_CALLER_IS_NOT_OPERATOR_FOR_HOLDER, SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS, SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS, SF_TOKEN_MOVE_INSUFFICIENT_BALANCE, SUPER_TOKEN_NOT_ERC777_TOKENS_RECIPIENT] + +operatorBurn: + # ERC-777 operatorBurn — like burn(), actually performs a downgrade. + mutability: nonpayable + access: operator + inputs: + - account: address + - amount: uint256 + - userData: bytes + - operatorData: bytes + emits: [Burned, Transfer, TokenDowngraded] + errors: [SUPER_TOKEN_CALLER_IS_NOT_OPERATOR_FOR_HOLDER, SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS, SF_TOKEN_BURN_INSUFFICIENT_BALANCE, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED] + +# == ERC-20 Permit == + +permit: + mutability: nonpayable + access: anyone + inputs: + - owner: address + - spender: address + - value: uint256 + - deadline: uint256 + - v: uint8 + - r: bytes32 + - s: bytes32 + emits: [Approval] + errors: [SUPER_TOKEN_PERMIT_EXPIRED_SIGNATURE, SUPER_TOKEN_PERMIT_INVALID_SIGNER, ECDSAInvalidSignature, ECDSAInvalidSignatureLength, ECDSAInvalidSignatureS, SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS, SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS] + +nonces: + mutability: view + inputs: + - owner: address + outputs: + - uint256 + +DOMAIN_SEPARATOR: + mutability: view + inputs: [] + outputs: + - bytes32 + +eip712Domain: + # EIP-5267 domain retrieval. + mutability: view + inputs: [] + outputs: + - fields: bytes1 + - name: string + - version: string + - chainId: uint256 + - verifyingContract: address + - salt: bytes32 + - "extensions: uint256[]" + +# == ERC-20 Wrapping == +# These functions interact with the underlying ERC-20 token. +# They revert with SUPER_TOKEN_NO_UNDERLYING_TOKEN on Pure and Native Asset +# Super Tokens (use upgradeByETH / downgradeToETH for SETH). + +upgrade: + # Wrap underlying ERC-20 into Super Tokens for msg.sender. + # Requires prior ERC-20 approval on the underlying token. + notes: + - "Gotcha: amount is always in SuperToken decimals (18), regardless of the underlying token's decimals. The contract handles downscaling internally when pulling from the underlying via transferFrom. However, the ERC-20 approval on the underlying token must use the underlying's native decimals. Example: to wrap 1000 USDC, approve 1000e6 on USDC, then call upgrade(1000e18)." + mutability: nonpayable + access: anyone # wraps for msg.sender + inputs: + - amount: uint256 # in SuperToken decimals (always 18) + emits: [Minted, Transfer, TokenUpgraded] + errors: [SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED, SUPER_TOKEN_MINT_TO_ZERO_ADDRESS, SafeERC20FailedOperation, SafeCastOverflowedUintToInt] + +upgradeTo: + # Wrap underlying ERC-20 and mint Super Tokens to a different address. + mutability: nonpayable + access: anyone + inputs: + - to: address + - amount: uint256 + - userData: bytes + emits: [Minted, Transfer, TokenUpgraded] + errors: [SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED, SUPER_TOKEN_MINT_TO_ZERO_ADDRESS, SUPER_TOKEN_NOT_ERC777_TOKENS_RECIPIENT, SafeERC20FailedOperation, SafeCastOverflowedUintToInt] + +downgrade: + # Unwrap Super Tokens back to underlying ERC-20 for msg.sender. + mutability: nonpayable + access: anyone # unwraps to msg.sender + inputs: + - amount: uint256 + emits: [Burned, Transfer, TokenDowngraded] + errors: [SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS, SF_TOKEN_BURN_INSUFFICIENT_BALANCE, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED, SafeERC20FailedOperation] + +downgradeTo: + # Unwrap Super Tokens and send underlying ERC-20 to a different address. + mutability: nonpayable + access: anyone + inputs: + - to: address + - amount: uint256 + emits: [Burned, Transfer, TokenDowngraded] + errors: [SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS, SF_TOKEN_BURN_INSUFFICIENT_BALANCE, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED, SafeERC20FailedOperation] + +getUnderlyingToken: + # Returns address(0) for Pure and Native Asset Super Tokens. + mutability: view + inputs: [] + outputs: + - address + +getUnderlyingDecimals: + mutability: view + inputs: [] + outputs: + - uint8 + +toUnderlyingAmount: + # Convert a Super Token amount to its underlying equivalent, accounting for + # decimal differences. adjustedAmount strips precision loss. + mutability: view + inputs: + - amount: uint256 + outputs: + - underlyingAmount: uint256 + - adjustedAmount: uint256 + +# == Native Asset Wrapping (SETH only) == +# These functions exist only on Native Asset Super Token (SETH) proxies. +# They are defined in the SETHProxy contract, not in the SuperToken logic. +# The SETH proxy also accepts plain ETH transfers via receive() which mints +# Super Tokens to the sender. + +upgradeByETH: + # Wrap sent ETH/native asset into Super Tokens for msg.sender. + # Variant: native-asset + mutability: payable + access: anyone # wraps for msg.sender + inputs: [] + emits: [Minted, Transfer, TokenUpgraded] + +upgradeByETHTo: + # Wrap sent ETH/native asset into Super Tokens for a different address. + # Variant: native-asset + mutability: payable + access: anyone + inputs: + - to: address + emits: [Minted, Transfer, TokenUpgraded] + +downgradeToETH: + # Unwrap Super Tokens back to ETH/native asset for msg.sender. + # Variant: native-asset + mutability: nonpayable + access: anyone # unwraps to msg.sender + inputs: + - wad: uint256 + emits: [Burned, Transfer, TokenDowngraded] + +# == Real-time Balance == + +realtimeBalanceOf: + # Full real-time balance across ALL agreements at a given timestamp. + # This aggregates the realtimeBalanceOf from every agreement (CFA, GDA, IDA) + # into a single balance. Use this (or realtimeBalanceOfNow) to check an + # account's actual balance — not the per-agreement versions. + # availableBalance = settled + agreement dynamics − max(0, deposit − owedDeposit) + mutability: view + inputs: + - account: address + - timestamp: uint256 + outputs: + - availableBalance: int256 + - deposit: uint256 + - owedDeposit: uint256 + +realtimeBalanceOfNow: + # Convenience wrapper: realtimeBalanceOf at the current Host timestamp. + # This is the recommended way to check an account's real-time balance. + mutability: view + inputs: + - account: address + outputs: + - availableBalance: int256 + - deposit: uint256 + - owedDeposit: uint256 + - timestamp: uint256 + +isAccountCritical: + # True if availableBalance < 0 at the given timestamp. + mutability: view + inputs: + - account: address + - timestamp: uint256 + outputs: + - isCritical: bool + +isAccountCriticalNow: + mutability: view + inputs: + - account: address + outputs: + - isCritical: bool + +isAccountSolvent: + # True if realtime balance (before deposit deduction) >= 0 at the given timestamp. + mutability: view + inputs: + - account: address + - timestamp: uint256 + outputs: + - isSolvent: bool + +isAccountSolventNow: + mutability: view + inputs: + - account: address + outputs: + - isSolvent: bool + +# == Admin == +# The admin defaults to the Host contract when address(0). +# An explicit admin can be set during initializeWithAdmin or via changeAdmin. + +changeAdmin: + mutability: nonpayable + access: admin + inputs: + - newAdmin: address + emits: [AdminChanged] + errors: [SUPER_TOKEN_ONLY_ADMIN] + +getAdmin: + # Returns address(0) if the Host is the implicit admin. + mutability: view + inputs: [] + outputs: + - address + +# == Yield Backend == +# Hot-pluggable yield generation for wrapper Super Tokens. The yield backend is a +# contract implementing IYieldBackend that is invoked via delegatecall. When enabled, +# the underlying ERC-20 is deposited into a yield protocol (e.g. Aave, Spark, ERC4626 vaults). +# Surplus yield (balance exceeding totalSupply) can be withdrawn by the admin. + +enableYieldBackend: + # Enable a yield backend and deposit all underlying token balance into it. + # Requires no existing yield backend to be set. + notes: + - "Gotcha: Reverts if a yield backend is already enabled — must call disableYieldBackend first to switch backends." + - "The yield backend contract is invoked via delegatecall — it executes in the SuperToken's context." + mutability: nonpayable + access: admin + inputs: + - newYieldBackend: address # must implement IYieldBackend + emits: [YieldBackendEnabled] + +disableYieldBackend: + # Withdraw all assets from the yield backend and disable it. + notes: + - "Calls withdrawMax() then disable() on the backend via delegatecall." + mutability: nonpayable + access: admin + inputs: [] + emits: [YieldBackendDisabled] + +getYieldBackend: + # Returns the current yield backend address, or address(0) if none is set. + mutability: view + inputs: [] + outputs: + - yieldBackend: address + +withdrawSurplusFromYieldBackend: + # Withdraw yield surplus (deposited amount exceeding totalSupply) from the backend. + # The surplus is sent to a receiver defined by the yield backend implementation. + mutability: nonpayable + access: admin + inputs: [] + +# == Self Operations == +# Can only be called by the token contract itself (address(this)). +# Used by custom super token proxies (SETH, Pure) to mint/burn/approve/transfer +# tokens through their own proxy logic. + +selfMint: + mutability: nonpayable + access: self + inputs: + - account: address + - amount: uint256 + - userData: bytes + emits: [Minted, Transfer] + errors: [SUPER_TOKEN_ONLY_SELF, SUPER_TOKEN_MINT_TO_ZERO_ADDRESS] + +selfBurn: + mutability: nonpayable + access: self + inputs: + - account: address + - amount: uint256 + - userData: bytes + emits: [Burned, Transfer] + errors: [SUPER_TOKEN_ONLY_SELF, SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS, SF_TOKEN_BURN_INSUFFICIENT_BALANCE] + +selfApproveFor: + mutability: nonpayable + access: self + inputs: + - account: address + - spender: address + - amount: uint256 + emits: [Approval] + errors: [SUPER_TOKEN_ONLY_SELF, SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS, SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS] + +selfTransferFrom: + mutability: nonpayable + access: self + inputs: + - holder: address + - spender: address + - recipient: address + - amount: uint256 + emits: [Sent, Transfer] + errors: [SUPER_TOKEN_ONLY_SELF, SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS, SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS, SF_TOKEN_MOVE_INSUFFICIENT_BALANCE] + +# == Host Batch Operations == +# Called by the Host contract on behalf of a user within batchCall / +# forwardBatchCall. Each mirrors a user-facing function but takes the +# account address explicitly. + +operationApprove: + mutability: nonpayable + access: host + inputs: + - account: address + - spender: address + - amount: uint256 + emits: [Approval] + errors: [SF_TOKEN_ONLY_HOST, SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS, SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS] + +operationIncreaseAllowance: + mutability: nonpayable + access: host + inputs: + - account: address + - spender: address + - addedValue: uint256 + emits: [Approval] + errors: [SF_TOKEN_ONLY_HOST, SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS, SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS] + +operationDecreaseAllowance: + mutability: nonpayable + access: host + inputs: + - account: address + - spender: address + - subtractedValue: uint256 + emits: [Approval] + errors: [SF_TOKEN_ONLY_HOST, SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS, SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS] + +operationTransferFrom: + mutability: nonpayable + access: host + inputs: + - account: address + - spender: address + - recipient: address + - amount: uint256 + emits: [Sent, Transfer] + errors: [SF_TOKEN_ONLY_HOST, SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS, SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS, SF_TOKEN_MOVE_INSUFFICIENT_BALANCE] + +operationSend: + mutability: nonpayable + access: host + inputs: + - spender: address + - recipient: address + - amount: uint256 + - userData: bytes + emits: [Sent, Transfer] + errors: [SF_TOKEN_ONLY_HOST, SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS, SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS, SF_TOKEN_MOVE_INSUFFICIENT_BALANCE, SUPER_TOKEN_NOT_ERC777_TOKENS_RECIPIENT] + +operationUpgrade: + mutability: nonpayable + access: host + inputs: + - account: address + - amount: uint256 + emits: [Minted, Transfer, TokenUpgraded] + errors: [SF_TOKEN_ONLY_HOST, SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED, SUPER_TOKEN_MINT_TO_ZERO_ADDRESS, SafeERC20FailedOperation, SafeCastOverflowedUintToInt] + +operationDowngrade: + mutability: nonpayable + access: host + inputs: + - account: address + - amount: uint256 + emits: [Burned, Transfer, TokenDowngraded] + errors: [SF_TOKEN_ONLY_HOST, SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS, SF_TOKEN_BURN_INSUFFICIENT_BALANCE, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED, SafeERC20FailedOperation] + +operationUpgradeTo: + mutability: nonpayable + access: host + inputs: + - account: address + - to: address + - amount: uint256 + emits: [Minted, Transfer, TokenUpgraded] + errors: [SF_TOKEN_ONLY_HOST, SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED, SUPER_TOKEN_MINT_TO_ZERO_ADDRESS, SafeERC20FailedOperation, SafeCastOverflowedUintToInt] + +operationDowngradeTo: + mutability: nonpayable + access: host + inputs: + - account: address + - to: address + - amount: uint256 + emits: [Burned, Transfer, TokenDowngraded] + errors: [SF_TOKEN_ONLY_HOST, SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS, SF_TOKEN_BURN_INSUFFICIENT_BALANCE, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED, SafeERC20FailedOperation] + +# == Agreement Hosting == +# The token acts as storage host for agreement data. These functions are +# called by registered agreement contracts (CFA, GDA, IDA) through the Host. + +createAgreement: + mutability: nonpayable + access: agreement + inputs: + - id: bytes32 + - "data: bytes32[]" + emits: [AgreementCreated] + errors: [SF_TOKEN_AGREEMENT_ALREADY_EXISTS] + +getAgreementData: + mutability: view + inputs: + - agreementClass: address + - id: bytes32 + - dataLength: uint256 + outputs: + - "data: bytes32[]" + +updateAgreementData: + mutability: nonpayable + access: agreement + inputs: + - id: bytes32 + - "data: bytes32[]" + emits: [AgreementUpdated] + +terminateAgreement: + mutability: nonpayable + access: agreement + inputs: + - id: bytes32 + - dataLength: uint256 + emits: [AgreementTerminated] + errors: [SF_TOKEN_AGREEMENT_DOES_NOT_EXIST] + +updateAgreementStateSlot: + mutability: nonpayable + access: agreement + inputs: + - account: address + - slotId: uint256 + - "slotData: bytes32[]" + emits: [AgreementStateUpdated] + +getAgreementStateSlot: + mutability: view + inputs: + - agreementClass: address + - account: address + - slotId: uint256 + - dataLength: uint256 + outputs: + - "slotData: bytes32[]" + +settleBalance: + # Adjust an account's settled balance. Only listed agreements can call this. + mutability: nonpayable + access: agreement + inputs: + - account: address + - delta: int256 + errors: [SF_TOKEN_ONLY_LISTED_AGREEMENT] + +makeLiquidationPayoutsV2: + # Execute liquidation payouts. Transfers reward to liquidator or reward account, + # and adjusts the target account's balance. Only listed agreements can call this. + mutability: nonpayable + access: agreement + inputs: + - id: bytes32 + - liquidationTypeData: bytes + - liquidatorAccount: address + - useDefaultRewardAccount: bool + - targetAccount: address + - rewardAmount: uint256 + - targetAccountBalanceDelta: int256 + emits: [Transfer, AgreementLiquidatedV2] + errors: [SF_TOKEN_ONLY_LISTED_AGREEMENT] + +getAccountActiveAgreements: + mutability: view + inputs: + - account: address + outputs: + - "ISuperAgreement[]" + +# == Protocol Info == + +getHost: + mutability: view + inputs: [] + outputs: + - host: address + +POOL_ADMIN_NFT: + # Immutable address of the canonical PoolAdminNFT proxy. + mutability: view + inputs: [] + outputs: + - address + +POOL_MEMBER_NFT: + # Immutable address of the (deprecated) PoolMemberNFT proxy. + mutability: view + inputs: [] + outputs: + - address + +VERSION: + # Returns the version string of the SuperToken logic contract (e.g. "1.0.0"). + mutability: view + inputs: [] + outputs: + - string + +# == Events == + +events: + + # -- ERC-20 -- + + Transfer: + indexed: + - from: address + - to: address + data: + - value: uint256 + + Approval: + indexed: + - owner: address + - spender: address + data: + - value: uint256 + + # -- ERC-777 -- + + Sent: + indexed: + - operator: address + - from: address + - to: address + data: + - amount: uint256 + - data: bytes + - operatorData: bytes + + Minted: + indexed: + - operator: address + - to: address + data: + - amount: uint256 + - data: bytes + - operatorData: bytes + + Burned: + indexed: + - operator: address + - from: address + data: + - amount: uint256 + - data: bytes + - operatorData: bytes + + AuthorizedOperator: + indexed: + - operator: address + - tokenHolder: address + + RevokedOperator: + indexed: + - operator: address + - tokenHolder: address + + # -- EIP-5267 -- + + EIP712DomainChanged: + # Signalled when the EIP-712 domain changes. Not explicitly emitted by + # SuperToken, but declared in the EIP-5267 interface for indexing support. + + # -- Wrapping -- + + TokenUpgraded: + indexed: + - account: address + data: + - amount: uint256 + + TokenDowngraded: + indexed: + - account: address + data: + - amount: uint256 + + # -- Admin -- + + AdminChanged: + indexed: [] + data: + - oldAdmin: address + - newAdmin: address + + # -- Yield Backend -- + + YieldBackendEnabled: + indexed: + - yieldBackend: address + data: + - depositAmount: uint256 + + YieldBackendDisabled: + indexed: + - yieldBackend: address + + PoolAdminNFTCreated: + # Emitted once during logic contract constructor deployment. + indexed: [] + data: + - poolAdminNFT: address + + # -- Agreement Hosting -- + + AgreementCreated: + indexed: + - agreementClass: address + - id: bytes32 + data: + - "data: bytes32[]" + + AgreementUpdated: + indexed: + - agreementClass: address + - id: bytes32 + data: + - "data: bytes32[]" + + AgreementTerminated: + indexed: + - agreementClass: address + - id: bytes32 + + AgreementStateUpdated: + indexed: + - agreementClass: address + - account: address + data: + - slotId: uint256 + + AgreementLiquidatedV2: + indexed: + - agreementClass: address + - id: bytes32 + data: + - liquidatorAccount: address + - targetAccount: address + - rewardAmountReceiver: address + - rewardAmount: uint256 + - targetAccountBalanceDelta: int256 + - liquidationTypeData: bytes + + # -- Deprecated events (from ISuperfluidToken, no longer emitted) -- + + AgreementLiquidated: + deprecated: true + note: "Use AgreementLiquidatedV2" + indexed: + - agreementClass: address + - id: bytes32 + data: + - penaltyAccount: address + - rewardAccount: address + - rewardAmount: uint256 + + AgreementLiquidatedBy: + deprecated: true + note: "Use AgreementLiquidatedV2" + indexed: + - liquidatorAccount: address + - agreementClass: address + - id: bytes32 + data: + - penaltyAccount: address + - bondAccount: address + - rewardAmount: uint256 + - bailoutAmount: uint256 + + Bailout: + deprecated: true + note: "Use AgreementLiquidatedV2" + indexed: + - bailoutAccount: address + - bailoutAmount: uint256 + +# == Errors == + +errors: + + # -- Super Token access -- + - SUPER_TOKEN_ONLY_SELF + - SUPER_TOKEN_ONLY_ADMIN + - SUPER_TOKEN_ONLY_GOV_OWNER + + # -- SuperfluidToken access -- + - SF_TOKEN_ONLY_HOST + - SF_TOKEN_ONLY_LISTED_AGREEMENT + + # -- Wrapping -- + - SUPER_TOKEN_NO_UNDERLYING_TOKEN + - SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED + + # -- ERC-20 / ERC-777 transfer -- + - SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS + - SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS + - SF_TOKEN_MOVE_INSUFFICIENT_BALANCE + + # -- Approve -- + - SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS + - SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS + + # -- Mint / Burn -- + - SUPER_TOKEN_MINT_TO_ZERO_ADDRESS + - SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS + - SF_TOKEN_BURN_INSUFFICIENT_BALANCE + + # -- ERC-777 operator -- + - SUPER_TOKEN_CALLER_IS_NOT_OPERATOR_FOR_HOLDER + - SUPER_TOKEN_NOT_ERC777_TOKENS_RECIPIENT + + # -- ERC-2612 permit -- + - SUPER_TOKEN_PERMIT_EXPIRED_SIGNATURE: + inputs: + - deadline: uint256 + - SUPER_TOKEN_PERMIT_INVALID_SIGNER: + inputs: + - signer: address + - owner: address + + # -- NFT proxy -- + - SUPER_TOKEN_NFT_PROXY_ADDRESS_CHANGED + + # -- Agreement hosting -- + - SF_TOKEN_AGREEMENT_ALREADY_EXISTS + - SF_TOKEN_AGREEMENT_DOES_NOT_EXIST + + # -- OpenZeppelin ECDSA (from permit) -- + - ECDSAInvalidSignature + - ECDSAInvalidSignatureLength: + inputs: + - length: uint256 + - ECDSAInvalidSignatureS: + inputs: + - s: bytes32 + + # -- OpenZeppelin SafeCast -- + - SafeCastOverflowedUintToInt: + inputs: + - value: uint256 + + # -- OpenZeppelin SafeERC20 -- + - SafeERC20FailedOperation: + inputs: + - token: address diff --git a/.agents/skills/gooddollar/references/contracts/SuperToken.selectors.yaml b/.agents/skills/gooddollar/references/contracts/SuperToken.selectors.yaml new file mode 100644 index 00000000..f9bb474d --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/SuperToken.selectors.yaml @@ -0,0 +1,99 @@ +# Generated by scripts/selectors.mjs +functions: + name(): 0x06fdde03 + symbol(): 0x95d89b41 + decimals(): 0x313ce567 + totalSupply(): 0x18160ddd + balanceOf(address): 0x70a08231 + allowance(address,address): 0xdd62ed3e + approve(address,uint256): 0x095ea7b3 + increaseAllowance(address,uint256): 0x39509351 + decreaseAllowance(address,uint256): 0xa457c2d7 + transfer(address,uint256): 0xa9059cbb + transferFrom(address,address,uint256): 0x23b872dd + transferAll(address): 0xa3a7e7f3 + granularity(): 0x556f0dc7 + send(address,uint256,bytes): 0x9bd9bbc6 + burn(uint256,bytes): 0xfe9d9303 + authorizeOperator(address): 0x959b8c3f + revokeOperator(address): 0xfad8b32a + isOperatorFor(address,address): 0xd95b6371 + defaultOperators(): 0x06e48538 + operatorSend(address,address,uint256,bytes,bytes): 0x62ad1b83 + operatorBurn(address,uint256,bytes,bytes): 0xfc673c4f + permit(address,address,uint256,uint256,uint8,bytes32,bytes32): 0xd505accf + nonces(address): 0x7ecebe00 + DOMAIN_SEPARATOR(): 0x3644e515 + eip712Domain(): 0x84b0196e + upgrade(): 0xd55ec697 + upgradeTo(address,uint256,bytes): 0x5b9d09cc + downgrade(uint256): 0x11bcc81e + downgradeTo(address,uint256): 0x83ba2525 + getUnderlyingToken(): 0xee719bc8 + getUnderlyingDecimals(): 0x92081a47 + toUnderlyingAmount(uint256): 0x282a050b + upgradeByETH(): 0xcf81464b + upgradeByETHTo(address): 0x7687d19b + downgradeToETH(uint256): 0x160e8be3 + realtimeBalanceOf(address,uint256): 0xeb3537cc + realtimeBalanceOfNow(address): 0x2ec8eec7 + isAccountCritical(address,uint256): 0xd9d078d6 + isAccountCriticalNow(address): 0x79359f6f + isAccountSolvent(address,uint256): 0xb84cdd4a + isAccountSolventNow(address): 0xbb0d196e + changeAdmin(address): 0x8f283970 + getAdmin(): 0x6e9960c3 + enableYieldBackend(): 0x5127b621 + disableYieldBackend(): 0x370a190f + getYieldBackend(): 0xe729804b + withdrawSurplusFromYieldBackend(): 0x4a3acda0 + selfMint(address,uint256,bytes): 0xc68d4283 + selfBurn(address,uint256,bytes): 0x9d876741 + selfApproveFor(address,address,uint256): 0x66a12fb6 + selfTransferFrom(address,address,address,uint256): 0x41b706be + operationApprove(address,address,uint256): 0x62aa5287 + operationIncreaseAllowance(address,address,uint256): 0x4b2763b3 + operationDecreaseAllowance(address,address,uint256): 0xc780fd82 + operationTransferFrom(address,address,address,uint256): 0x16d055d6 + operationSend(address,address,uint256,bytes): 0xca0c1e7f + operationUpgrade(address,uint256): 0xca789464 + operationDowngrade(address,uint256): 0x245887fc + operationUpgradeTo(address,address,uint256): 0x1ae88ffc + operationDowngradeTo(address,address,uint256): 0x47ba7ad1 + createAgreement(bytes32,bytes32[]"): 0x816e2dc5 + getAgreementData(address,bytes32,uint256): 0x6c2d9f2f + updateAgreementData(bytes32,bytes32[]"): 0x1bced9c7 + terminateAgreement(bytes32,uint256): 0x27048397 + updateAgreementStateSlot(address,uint256,bytes32[]"): 0x003af740 + getAgreementStateSlot(address,address,uint256,uint256): 0x4b61cc33 + settleBalance(address,int256): 0xcf97256d + makeLiquidationPayoutsV2(bytes32,bytes,address,bool,address,uint256,int256): 0x1863e809 + getAccountActiveAgreements(address): 0x386fa221 + getHost(): 0x20bc4425 + POOL_ADMIN_NFT(): 0xb20db1ac + POOL_MEMBER_NFT(): 0xf5a8b4dd + VERSION(): 0xffa1ad74 +events: + Transfer(address,address,uint256): 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef + Approval(address,address,uint256): 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925 + Sent(address,address,address,uint256,bytes,bytes): 0x06b541ddaa720db2b10a4d0cdac39b8d360425fc073085fac19bc82614677987 + Minted(address,address,uint256,bytes,bytes): 0x2fe5be0146f74c5bce36c0b80911af6c7d86ff27e89d5cfa61fc681327954e5d + Burned(address,address,uint256,bytes,bytes): 0xa78a9be3a7b862d26933ad85fb11d80ef66b8f972d7cbba06621d583943a4098 + AuthorizedOperator(address,address): 0xf4caeb2d6ca8932a215a353d0703c326ec2d81fc68170f320eb2ab49e9df61f9 + RevokedOperator(address,address): 0x50546e66e5f44d728365dc3908c63bc5cfeeab470722c1677e3073a6ac294aa1 + EIP712DomainChanged(): 0x0a6387c9ea3628b88a633bb4f3b151770f70085117a15f9bf3787cda53f13d31 + TokenUpgraded(address,uint256): 0x25ca84076773b0455db53621c459ddc84fe40840e4932a62706a032566f399df + TokenDowngraded(address,uint256): 0x3bc27981aebbb57f9247dc00fde9d6cd91e4b230083fec3238fedbcba1f9ab3d + AdminChanged(address,address): 0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f + YieldBackendEnabled(address,uint256): 0x8d15dd569157df615aedb4b16273001ae8980ce2aac93e39adc48481dfaefbb5 + YieldBackendDisabled(address): 0x2486b5241145ce1b97a13655ecd4e8e51094ac93259a0589d24524304d6d70d4 + PoolAdminNFTCreated(address): 0xeb87fb34067547f3dc0b85096c3da73c99d4fbb08ff41212b8d7c0b5008b42e6 + AgreementCreated(address,bytes32,bytes32[]"): 0x9a5caaf4460c6dff81ed3dc58f3aef40fe511737a163340e06a4abc1bfe21a73 + AgreementUpdated(address,bytes32,bytes32[]"): 0xc00aaaf56818c1a9d6c54e14c6f93965eb34c9729232fdafa62f90a10600e70a + AgreementTerminated(address,bytes32): 0x71a63dc095de07aa5512ad57a7596a39516317e316981a1cd71000057be1537b + AgreementStateUpdated(address,address,uint256): 0x30f416fa68fca014a0f334464c64b000ba53e99b6d2afdea9d5ca756372d5985 + AgreementLiquidatedV2(address,bytes32,address,address,address,uint256,int256,bytes): 0xb8381a3ce157650e06186e3e8f4dd4dc29236f2688b6eed1893d0a60d7c6386f + AgreementLiquidated(address,bytes32,address,address,uint256): 0x8505c3d8f1f184f032cf0bc4cd80ee61c8b9d94f8907c3281bf0101a2610fe80 + AgreementLiquidatedBy(address,address,bytes32,address,address,uint256,uint256): 0x5f22b60e58b1d6de858bc27c48d5a4653e052da99e083c1d88bb8c58e1abc8ef + Bailout(address,uint256): 0xd6c9a04afc81e8c614310bbee6c9e84f5abe15b82038bf8347014ce0852e6ffd +errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/Superfluid.abi.yaml b/.agents/skills/gooddollar/references/contracts/Superfluid.abi.yaml new file mode 100644 index 00000000..33ac5a94 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/Superfluid.abi.yaml @@ -0,0 +1,757 @@ +# Superfluid Host — the central protocol router +# All agreement calls (CFA, IDA, GDA) are routed through the Host, which manages +# the Superfluid call context, Super App callbacks, app credit, and governance. +# The Host is also the entry point for batch calls and meta-transactions. +# +# NOTE: emits/errors mappings are traced from source code — verify against implementation. +# Proxy/upgradability functions (castrate, updateCode, getCodeAddress, proxiableUUID, initialize) +# are omitted — they belong to the UUPSProxiable layer. + +meta: + name: Superfluid + version: v1 + source: + - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol + - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol + implements: [ISuperfluid] + inherits: [UUPSProxiable, BaseRelayRecipient] + deployments: + mainnet: + eth-mainnet: "0x4E583d9390082B65Bef884b629DFA426114CED6d" + polygon-mainnet: "0x3E14dC1b13c488a8d5D310918780c983bD5982E7" + xdai-mainnet: "0x2dFe937cD98Ab92e59cF3139138f18c823a4efE7" + base-mainnet: "0x4C073B3baB6d8826b8C5b229f3cfdC1eC6E47E74" + optimism-mainnet: "0x567c4B141ED61923967cA25Ef4906C8781069a10" + arbitrum-one: "0xCf8Acb4eF033efF16E8080aed4c7D5B9285D2192" + bsc-mainnet: "0xd1e2cFb6441680002Eb7A44223160aB9B67d7E6E" + avalanche-c: "0x60377C7016E4cdB03C87EF474896C11cB560752C" + celo-mainnet: "0xA4Ff07cF81C02CFD356184879D953970cA957585" + scroll-mainnet: "0x0F86a21F6216c061B222c224e315d9FC34520bb7" + degenchain: "0xc1314EdcD7e478C831a7a24169F7dEADB2646eD2" + testnet: + avalanche-fuji: "0x85Fe79b998509B77BF10A8BD4001D58475D29386" + base-sepolia: "0x109412E3C84f0539b43d39dB691B08c90f58dC7c" + eth-sepolia: "0x109412E3C84f0539b43d39dB691B08c90f58dC7c" + optimism-sepolia: "0xd399e2Fb5f4cf3722a11F65b88FAB6B2B8621005" + scroll-sepolia: "0x42b05a6016B9eED232E13fd56a8F0725693DBF8e" + deploymentCreationBlocks: + mainnet: + celo-mainnet: 16393469 + +# == Abbreviations == +# ctx — context (Superfluid call context bytes) +# ACL — Access Control List +# ERC — Ethereum Request for Comments + +# == Glossary == +# Host — central Superfluid contract routing agreement/app calls +# agreement class — a registered agreement contract (CFA, IDA, GDA) +# Super App — a smart contract registered with the Host that receives callbacks +# callback — beforeAgreement* / afterAgreement* hooks called on Super Apps +# context (ctx) — encoded call metadata: msgSender, timestamp, userData, app credit +# app credit — temporary token allowance granted to a Super App during a callback +# jailed — a Super App that violated protocol rules; its callbacks are bypassed +# configWord — bitmask encoding a Super App's app level and callback noop flags. +# App level: APP_LEVEL_FINAL (1 << 1) or APP_LEVEL_SECOND (1 << 0). +# Noop flags disable specific before/after callbacks per agreement type. +# batch operation — a single step in a batchCall array (see BatchOperation in Definitions.sol) +# trusted forwarder — a contract allowed to call forwardBatchCall for meta-transactions + +# == Batch Call == +# Primary entry point for interacting with the protocol. Users compose arrays of +# typed operations (ERC-20 approvals, SuperToken wrapping, agreement calls, etc.). +# +# == Batch Operation Types == +# Used in the operationType field of batchCall/forwardBatchCall operations. +# Data encoding varies by category — see notes on each type. +# +# Category 1–5: ERC-20/ERC-777 token ops +# target: must be a SuperToken (routes through SuperToken.operation* methods, +# NOT arbitrary ERC-20 contracts — use a separate tx for underlying approvals) +# data: abi-encoded parameters only (strip the 4-byte function selector) +# +# Category 101–102: SuperToken wrap/unwrap +# target: the SuperToken +# data: abi-encoded parameters only (strip the 4-byte function selector) +# +# Category 201: agreement call +# target: the agreement contract (e.g. CFA, GDA) +# data: abi.encode(callData, userData) where callData is full +# encodeFunctionData (selector included, with empty ctx placeholder "0x") +# +# Category 202: app action +# target: the Super App +# data: abi.encode(callData) where callData is full encodeFunctionData +# (selector included, with empty ctx placeholder "0x") +# +# Category 301: simple forward +# target: any contract +# data: full encodeFunctionData (selector included), or "0x" for pure value transfers +# +# Category 302: ERC-2771 forward +# target: any contract (must be an ERC-2771 recipient to extract the real sender) +# data: full encodeFunctionData (selector included) +# The ERC2771Forwarder appends the original msg.sender to calldata per ERC-2771. +# Use when the target contract needs to know who initiated the batch. + +batch_operation_types: + ERC20_APPROVE: 1 + ERC20_TRANSFER_FROM: 2 + ERC777_SEND: 3 # deprecated — ERC-777 is being phased out + ERC20_INCREASE_ALLOWANCE: 4 + ERC20_DECREASE_ALLOWANCE: 5 + SUPERTOKEN_UPGRADE: 101 + SUPERTOKEN_DOWNGRADE: 102 + SUPERFLUID_CALL_AGREEMENT: 201 + CALL_APP_ACTION: 202 + SIMPLE_FORWARD_CALL: 301 + ERC2771_FORWARD_CALL: 302 + +batchCall: + # Execute an array of operations in a single transaction as msg.sender. + notes: + - "Gotcha: For SIMPLE_FORWARD_CALL (type 301), target sees msg.sender as the SimpleForwarder contract, NOT the original caller. Use only when the target doesn't need to identify the sender." + - "Gotcha: ERC-20 ops (types 1-5) route through SuperToken.operation* methods, NOT arbitrary ERC-20 contracts. The target must be a SuperToken address. To approve an underlying ERC-20 (e.g. for wrapping), use a separate transaction." + - "Gotcha: If msg.value > 0, the entire amount is forwarded to the first CALL_APP_ACTION, SIMPLE_FORWARD, or ERC2771_FORWARD operation. If none exist, the native tokens are returned to the sender." + mutability: payable + access: anyone + inputs: + - operations: tuple[] + components: + - operationType: uint32 # see batch_operation_types above + - target: address + - data: bytes # encoding varies by operationType — see above + errors: [HOST_UNKNOWN_BATCH_CALL_OPERATION_TYPE] + +forwardBatchCall: + # Same as batchCall but uses EIP-2771 to extract msgSender from calldata. + # Can only be called by contracts registered as trusted forwarders in governance. + # If native tokens are left over, they are refunded to the EIP-2771 sender. + mutability: payable + access: trusted-forwarder + inputs: + - operations: tuple[] + components: + - operationType: uint32 # see batch_operation_types above + - target: address + - data: bytes # encoding varies by operationType — see above + errors: [HOST_UNKNOWN_BATCH_CALL_OPERATION_TYPE] + +# == Contextless Call Proxies == +# Entry points for EOAs and non-app contracts to interact with agreements or apps. +# These create a fresh Superfluid context with msg.sender, inject it into the call, +# and route through to the target agreement/app. + +callAgreement: + # Call an agreement function. The Host creates a context with msg.sender and replaces + # the placeholder ctx bytes in callData. This is how EOAs interact with CFA/IDA/GDA. + mutability: nonpayable + access: anyone # context set to msg.sender + inputs: + - agreementClass: address # must be a registered agreement + - callData: bytes # abi-encoded agreement call with placeholder ctx + - userData: bytes # extra data forwarded to Super App callbacks + outputs: + - returnedData: bytes + errors: [APP_RULE, HOST_ONLY_LISTED_AGREEMENT] + +callAppAction: + # Call an app action function on a Super App. The Host creates a context and routes + # the call. The action must not be an agreement callback selector. + mutability: nonpayable + access: anyone # context set to msg.sender + inputs: + - app: address # must be a registered, non-jailed Super App + - callData: bytes # abi-encoded app action call with placeholder ctx + outputs: + - returnedData: bytes + errors: [APP_RULE, HOST_NOT_A_SUPER_APP, HOST_SUPER_APP_IS_JAILED, HOST_AGREEMENT_CALLBACK_IS_NOT_ACTION] + +# == Contextual Call Proxies == +# For Super Apps to chain calls during callbacks. The app must pass the ctx it received. +# Only callable by the app currently executing a callback (ctx.appAddress == msg.sender). + +callAgreementWithContext: + # Chain an agreement call from within a Super App callback. + # The ctx must be valid and msg.sender must be the current callback app. + mutability: nonpayable + access: super-app + inputs: + - agreementClass: address + - callData: bytes + - userData: bytes + - ctx: bytes + outputs: + - newCtx: bytes + - returnedData: bytes + errors: [APP_RULE, HOST_ONLY_LISTED_AGREEMENT, HOST_CALL_AGREEMENT_WITH_CTX_FROM_WRONG_ADDRESS] + +callAppActionWithContext: + # Chain an app action call from within a Super App callback. + mutability: nonpayable + access: super-app + inputs: + - app: address + - callData: bytes + - ctx: bytes + outputs: + - newCtx: bytes + errors: [APP_RULE, HOST_NOT_A_SUPER_APP, HOST_SUPER_APP_IS_JAILED, HOST_AGREEMENT_CALLBACK_IS_NOT_ACTION, HOST_CALL_APP_ACTION_WITH_CTX_FROM_WRONG_ADDRESS] + +# == Context Utilities == + +decodeCtx: + # Decode a raw context bytes blob into the structured Context fields. + mutability: pure + inputs: + - ctx: bytes + outputs: + - context: + type: tuple + components: + - appCallbackLevel: uint8 + - callType: uint8 + - timestamp: uint256 + - msgSender: address + - agreementSelector: bytes4 + - userData: bytes + - appCreditGranted: uint256 + - appCreditWantedDeprecated: uint256 + - appCreditUsed: int256 + - appAddress: address + - appCreditToken: address + +isCtxValid: + # Check if a context blob matches the current transaction's context stamp. + mutability: view + inputs: + - ctx: bytes + outputs: + - bool + +# == App Registry == +# Super App registration. On some deployments, governance permission (via SimpleACL +# or legacy governance config) is required before an app can register. + +registerApp: + # Register msg.sender as a Super App with the given config word. + # On whitelisting-enabled deployments, tx.origin must have the + # ACL_SUPERAPP_REGISTRATION_ROLE or a valid legacy governance key. + mutability: nonpayable + access: self + inputs: + - configWord: uint256 # Super App manifest flags (see SuperAppDefinitions) + emits: [AppRegistered] + errors: [HOST_NO_APP_REGISTRATION_PERMISSION, HOST_INVALID_CONFIG_WORD, HOST_SUPER_APP_ALREADY_REGISTERED] + +registerApp(address,uint256): + # Register an external contract as a Super App. + # Useful for factory patterns where the deployer registers the app. + mutability: nonpayable + access: anyone + inputs: + - app: address # must be a contract + - configWord: uint256 + emits: [AppRegistered] + errors: [HOST_MUST_BE_CONTRACT, HOST_NO_APP_REGISTRATION_PERMISSION, HOST_INVALID_CONFIG_WORD, HOST_SUPER_APP_ALREADY_REGISTERED] + +registerAppWithKey: + # DEPRECATED — use registerApp(uint256) instead. + # Legacy registration using a governance-provided registration key string. + mutability: nonpayable + access: self + inputs: + - configWord: uint256 + - registrationKey: string + emits: [AppRegistered] + errors: [HOST_NO_APP_REGISTRATION_PERMISSION, HOST_INVALID_CONFIG_WORD, HOST_SUPER_APP_ALREADY_REGISTERED] + +registerAppByFactory: + # DEPRECATED — use registerApp(address, uint256) instead. + # Legacy factory-based registration using governance-authorized factory addresses. + mutability: nonpayable + access: factory + inputs: + - app: address + - configWord: uint256 + emits: [AppRegistered] + errors: [HOST_MUST_BE_CONTRACT, HOST_NO_APP_REGISTRATION_PERMISSION, HOST_INVALID_CONFIG_WORD, HOST_SUPER_APP_ALREADY_REGISTERED] + +# == App Queries == + +isApp: + mutability: view + inputs: + - app: address + outputs: + - bool + +getAppCallbackLevel: + mutability: view + inputs: + - appAddr: address + outputs: + - uint8 + +getAppManifest: + # Returns the full manifest of a Super App. + # isSuperApp is false if the address was never registered. + mutability: view + inputs: + - app: address + outputs: + - isSuperApp: bool + - isJailed: bool + - noopMask: uint256 # bitmask of noop'd agreement callback selectors + +isAppJailed: + mutability: view + inputs: + - app: address + outputs: + - isJail: bool + +# == App Composition == +# Source apps can whitelist target apps for composability (calling downstream). +# Currently limited to MAX_APP_CALLBACK_LEVEL = 1 (one level of nesting). + +allowCompositeApp: + # Whitelist a target Super App for composition. msg.sender must be a Super App + # with a higher callback level than the target. + mutability: nonpayable + access: super-app + inputs: + - targetApp: address + errors: [HOST_SENDER_IS_NOT_SUPER_APP, HOST_RECEIVER_IS_NOT_SUPER_APP, HOST_SOURCE_APP_NEEDS_HIGHER_APP_LEVEL] + +isCompositeAppAllowed: + mutability: view + inputs: + - app: address + - targetApp: address + outputs: + - isAppAllowed: bool + +# == Agreement Framework == +# Internal protocol functions called by registered agreement contracts (CFA, IDA, GDA) +# to manage Super App callbacks and app credit. Only callable by agreements. +# +# Callback lifecycle (called by agreement during flow operations): +# 1. appCallbackPush — set up credit, push callback frame +# 2. callAppBeforeCallback — staticcall app's before hook (read-only) +# 3. (agreement executes core logic) +# 4. callAppAfterCallback — call app's after hook (state changes OK) +# 5. appCallbackPop — finalize credit accounting, pop frame + +callAppBeforeCallback: + # StaticCall a Super App's before-callback. Returns the callback data. + # If the callback reverts during a termination, the app is jailed instead of reverting. + mutability: nonpayable + access: agreement + inputs: + - app: address + - callData: bytes + - isTermination: bool + - ctx: bytes + outputs: + - cbdata: bytes + errors: [HOST_ONLY_LISTED_AGREEMENT, HOST_NEED_MORE_GAS, APP_RULE] + +callAppAfterCallback: + # Call a Super App's after-callback. Returns the (potentially modified) context. + # If the app returns an invalid ctx, it is jailed on termination or reverts otherwise. + mutability: nonpayable + access: agreement + inputs: + - app: address + - callData: bytes + - isTermination: bool + - ctx: bytes + outputs: + - newCtx: bytes + errors: [HOST_ONLY_LISTED_AGREEMENT, HOST_NEED_MORE_GAS, APP_RULE] + +appCallbackPush: + # Push a new callback frame onto the context stack before invoking a Super App. + # Sets up app credit and tracks the callback nesting level. + notes: + - "Gotcha: The appCreditGranted is backed by the flow sender's deposit as \"owed deposit\". If the Super App opens outgoing streams using this credit, the sender's total locked capital increases accordingly (roughly doubles for 1:1 relay, more for fan-out patterns)." + mutability: nonpayable + access: agreement + inputs: + - ctx: bytes + - app: address + - appCreditGranted: uint256 + - appCreditUsed: int256 + - appCreditToken: address + outputs: + - appCtx: bytes + errors: [HOST_ONLY_LISTED_AGREEMENT, APP_RULE] + +appCallbackPop: + # Pop the callback frame after a Super App callback completes. + # Adjusts appCreditUsed by the delta from the callback. + mutability: nonpayable + access: agreement + inputs: + - ctx: bytes + - appCreditUsedDelta: int256 + outputs: + - newCtx: bytes + errors: [HOST_ONLY_LISTED_AGREEMENT] + +ctxUseCredit: + # Record additional app credit usage during an agreement operation. + mutability: nonpayable + access: agreement + inputs: + - ctx: bytes + - appCreditUsedMore: int256 + outputs: + - newCtx: bytes + errors: [HOST_ONLY_LISTED_AGREEMENT] + +jailApp: + # Jail a Super App from within an agreement. Jailed apps have their callbacks bypassed. + mutability: nonpayable + access: agreement + inputs: + - ctx: bytes + - app: address + - reason: uint256 # see SuperAppDefinitions for jail reason codes + outputs: + - newCtx: bytes + emits: [Jail] + errors: [HOST_ONLY_LISTED_AGREEMENT] + +# == Governance == + +getGovernance: + mutability: view + outputs: + - governance: address + +replaceGovernance: + mutability: nonpayable + access: governance + inputs: + - newGov: address + emits: [GovernanceReplaced] + errors: [HOST_ONLY_GOVERNANCE] + +# == Agreement Whitelisting == +# Governance registers and upgrades agreement classes (CFA, IDA, GDA). + +registerAgreementClass: + # Register a new agreement class. Creates a UUPS proxy in upgradable deployments. + mutability: nonpayable + access: governance + inputs: + - agreementClassLogic: address + emits: [AgreementClassRegistered] + errors: [HOST_ONLY_GOVERNANCE, HOST_AGREEMENT_ALREADY_REGISTERED, HOST_MAX_256_AGREEMENTS] + +updateAgreementClass: + # Upgrade an existing agreement class to a new implementation. + mutability: nonpayable + access: governance + inputs: + - agreementClassLogic: address + emits: [AgreementClassUpdated] + errors: [HOST_ONLY_GOVERNANCE, HOST_NON_UPGRADEABLE, HOST_AGREEMENT_IS_NOT_REGISTERED] + +isAgreementTypeListed: + mutability: view + inputs: + - agreementType: bytes32 # keccak256 of agreement name string + outputs: + - yes: bool + +isAgreementClassListed: + mutability: view + inputs: + - agreementClass: address + outputs: + - yes: bool + +getAgreementClass: + mutability: view + inputs: + - agreementType: bytes32 + outputs: + - agreementClass: address + errors: [HOST_AGREEMENT_IS_NOT_REGISTERED] + +mapAgreementClasses: + # Decode a bitmap into an array of agreement class addresses. + mutability: view + inputs: + - bitmap: uint256 + outputs: + - agreementClasses: address[] + +addToAgreementClassesBitmap: + mutability: view + inputs: + - bitmap: uint256 + - agreementType: bytes32 + outputs: + - newBitmap: uint256 + errors: [HOST_AGREEMENT_IS_NOT_REGISTERED] + +removeFromAgreementClassesBitmap: + mutability: view + inputs: + - bitmap: uint256 + - agreementType: bytes32 + outputs: + - newBitmap: uint256 + errors: [HOST_AGREEMENT_IS_NOT_REGISTERED] + +# == Super Token Factory == + +getSuperTokenFactory: + mutability: view + outputs: + - factory: address + +getSuperTokenFactoryLogic: + # Returns the logic address behind the factory proxy. For non-upgradable + # deployments, returns the factory address itself. + mutability: view + outputs: + - logic: address + +updateSuperTokenFactory: + # Deploy or upgrade the SuperTokenFactory. On first call, creates a UUPS proxy. + mutability: nonpayable + access: governance + inputs: + - newFactory: address + emits: [SuperTokenFactoryUpdated] + errors: [HOST_ONLY_GOVERNANCE, HOST_NON_UPGRADEABLE] + +updateSuperTokenLogic: + # Upgrade a SuperToken proxy to new logic. If newLogicOverride is address(0), + # uses the canonical logic from the SuperTokenFactory. + mutability: nonpayable + access: governance + inputs: + - token: address + - newLogicOverride: address # address(0) means use canonical logic + emits: [SuperTokenLogicUpdated] + errors: [HOST_ONLY_GOVERNANCE] + +changeSuperTokenAdmin: + # Change the admin of a SuperToken. The admin is the only account allowed to + # upgrade the token logic. Default admin is the Host itself. + mutability: nonpayable + access: governance + inputs: + - token: address + - newAdmin: address + errors: [HOST_ONLY_GOVERNANCE] + +# == Pool Beacon == + +updatePoolBeaconLogic: + # Upgrade the SuperfluidPool beacon to new logic. Affects all GDA pool proxies. + mutability: nonpayable + access: governance + inputs: + - newLogic: address + emits: [PoolBeaconLogicUpdated] + errors: [HOST_ONLY_GOVERNANCE] + +# == ERC2771 / Forwarder == + +isTrustedForwarder: + # Check if an address is a governance-approved trusted forwarder for meta-transactions. + mutability: view + inputs: + - forwarder: address + outputs: + - bool + +versionRecipient: + # ERC2771 relay recipient version string. Returns "v1". + mutability: pure + outputs: + - string + +getERC2771Forwarder: + # Returns the ERC2771Forwarder contract used for ERC2771_FORWARD_CALL batch operations. + mutability: view + outputs: + - address + +getSimpleACL: + # Returns the SimpleACL contract used for Super App registration permissioning. + mutability: view + outputs: + - address + +# == Time == + +getNow: + # Returns block.timestamp. Useful for off-chain tooling and testing. + mutability: view + outputs: + - uint256 + +# == Protocol Constants == + +NON_UPGRADABLE_DEPLOYMENT: + # True if the Host was deployed in non-upgradable mode. + mutability: view + outputs: + - bool + +APP_WHITE_LISTING_ENABLED: + # True if Super App registration requires governance permission. + mutability: view + outputs: + - bool + +CALLBACK_GAS_LIMIT: + # Maximum gas forwarded to Super App callbacks. + mutability: view + outputs: + - uint64 + +MAX_APP_CALLBACK_LEVEL: + # Maximum callback nesting depth for composed Super Apps. Currently 1. + mutability: view + outputs: + - uint256 + +MAX_NUM_AGREEMENTS: + # Maximum number of agreement classes that can be registered. 256. + mutability: view + outputs: + - uint32 + +ACL_SUPERAPP_REGISTRATION_ROLE: + # keccak256("ACL_SUPERAPP_REGISTRATION_ROLE") — the SimpleACL role for app registration. + mutability: view + outputs: + - bytes32 + +SIMPLE_FORWARDER: + # Address of the SimpleForwarder used for SIMPLE_FORWARD_CALL batch operations. + mutability: view + outputs: + - address + +# == Events == + +events: + GovernanceReplaced: + data: + - oldGov: address + - newGov: address + + AgreementClassRegistered: + # agreementType is keccak256("org.superfluid-finance.agreements..") + data: + - agreementType: bytes32 + - code: address + + AgreementClassUpdated: + data: + - agreementType: bytes32 + - code: address + + SuperTokenFactoryUpdated: + data: + - newFactory: address + + SuperTokenLogicUpdated: + indexed: + - token: address + data: + - code: address + + PoolBeaconLogicUpdated: + indexed: + - beaconProxy: address + data: + - newBeaconLogic: address + + AppRegistered: + indexed: + - app: address + + Jail: + # Emitted when a Super App is jailed for violating protocol rules. + # See APP_RULE error below for reason code values. + indexed: + - app: address + data: + - reason: uint256 + + # Inherited events (from UUPSProxiable): + # CodeUpdated — emitted on proxy upgrade (uuid, codeAddress) + # Initialized — emitted on proxy initialization (version) + +# == Errors == + +errors: + # Governance + - HOST_ONLY_GOVERNANCE # caller is not the governance contract + + # Agreement whitelisting + - HOST_AGREEMENT_ALREADY_REGISTERED + - HOST_AGREEMENT_IS_NOT_REGISTERED + - HOST_MAX_256_AGREEMENTS # maximum agreement slots exhausted + - HOST_ONLY_LISTED_AGREEMENT # caller is not a registered agreement + + # Upgradability + - HOST_NON_UPGRADEABLE # deployment is non-upgradable + - HOST_CANNOT_DOWNGRADE_TO_NON_UPGRADEABLE + + # App registration + - HOST_MUST_BE_CONTRACT # app address has no code + - HOST_NO_APP_REGISTRATION_PERMISSION + - HOST_INVALID_CONFIG_WORD # invalid Super App manifest flags + - HOST_SUPER_APP_ALREADY_REGISTERED + - HOST_NOT_A_SUPER_APP # target is not a registered Super App + - HOST_SUPER_APP_IS_JAILED + + # App composition + - HOST_SENDER_IS_NOT_SUPER_APP + - HOST_RECEIVER_IS_NOT_SUPER_APP + - HOST_SOURCE_APP_NEEDS_HIGHER_APP_LEVEL + + # Context & call proxies + - HOST_NON_ZERO_LENGTH_PLACEHOLDER_CTX # placeholder ctx must be empty + - HOST_CALL_AGREEMENT_WITH_CTX_FROM_WRONG_ADDRESS # msg.sender != ctx.appAddress + - HOST_CALL_APP_ACTION_WITH_CTX_FROM_WRONG_ADDRESS # msg.sender != ctx.appAddress + - HOST_AGREEMENT_CALLBACK_IS_NOT_ACTION # app action selector matches a callback + + # Batch call + - HOST_UNKNOWN_BATCH_CALL_OPERATION_TYPE + + # Callback execution + - HOST_NEED_MORE_GAS # insufficient gas for Super App callback + + # Super App rule violations (generic, carries a reason code) + # Reason codes (from SuperAppDefinitions.sol): + # 10 — NO_REVERT_ON_TERMINATION_CALLBACK (delete callback reverted) + # 11 — NO_CRITICAL_SENDER_ACCOUNT (callback made sender critical) + # 12 — NO_CRITICAL_RECEIVER_ACCOUNT (callback made receiver critical) + # 20 — CTX_IS_READONLY (state change in before-callback staticcall) + # 21 — CTX_IS_NOT_CLEAN (context not properly returned) + # 22 — CTX_IS_MALFORMATED (context bytes corrupted/fabricated) + # 30 — COMPOSITE_APP_IS_NOT_WHITELISTED (no allowCompositeApp) + # 31 — COMPOSITE_APP_IS_JAILED (downstream app is jailed) + # 40 — MAX_APP_LEVEL_REACHED (callback nesting exceeded limit) + - APP_RULE: + inputs: + - _code: uint256 + + # SafeCast (inherited from OpenZeppelin) + - SafeCastOverflowedUintDowncast: + inputs: + - bits: uint8 + - value: uint256 diff --git a/.agents/skills/gooddollar/references/contracts/Superfluid.selectors.yaml b/.agents/skills/gooddollar/references/contracts/Superfluid.selectors.yaml new file mode 100644 index 00000000..18edf9b0 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/Superfluid.selectors.yaml @@ -0,0 +1,51 @@ +# Generated by scripts/selectors.mjs +functions: + batchCall(tuple[]): 0x31131761 + forwardBatchCall(tuple[]): 0xbc5a2101 + callAgreement(): 0xb7a9d0f0 + callAppAction(): 0x40df3d29 + callAgreementWithContext(address,bytes,bytes,bytes): 0x4329d293 + callAppActionWithContext(address,bytes,bytes): 0xba48b5f8 + decodeCtx(bytes): 0x3f6c923a + isCtxValid(bytes): 0xbf428734 + registerApp(): 0x6d6c85c3 + registerAppWithKey(uint256,string): 0xbd1c448b + registerAppByFactory(address,uint256): 0xf3733052 + isApp(address): 0x3ca3ad4e + getAppCallbackLevel(address): 0x9378fa13 + getAppManifest(address): 0xf9f522f4 + isAppJailed(address): 0x6b4f3335 + allowCompositeApp(address): 0x57121e0c + isCompositeAppAllowed(address,address): 0xbb84cfa1 + callAppBeforeCallback(address,bytes,bool,bytes): 0x74041e02 + callAppAfterCallback(address,bytes,bool,bytes): 0x1e6d0a84 + appCallbackPush(bytes,address,uint256,int256,address): 0x768fabb0 + appCallbackPop(bytes,int256): 0x989b0c3e + ctxUseCredit(bytes,int256): 0x59a29141 + jailApp(bytes,address): 0x5578431e + replaceGovernance(address): 0x7283100c + registerAgreementClass(address): 0x15a024e1 + updateAgreementClass(address): 0x06cecba8 + isAgreementTypeListed(): 0xf8dba358 + isAgreementClassListed(address): 0x8ca48484 + getAgreementClass(bytes32): 0xb6d200de + mapAgreementClasses(uint256): 0xc56a069d + addToAgreementClassesBitmap(uint256,bytes32): 0xbced3ddc + removeFromAgreementClassesBitmap(uint256,bytes32): 0xa5dbbbcd + updateSuperTokenFactory(address): 0x54fbc493 + updateSuperTokenLogic(address): 0x787afde7 + changeSuperTokenAdmin(address,address): 0x0c565075 + updatePoolBeaconLogic(address): 0x2f89bf89 + isTrustedForwarder(address): 0x572b6c05 + SIMPLE_FORWARDER(): 0xf85263b9 +events: + GovernanceReplaced(address,address): 0x13abda02e63c790d0e2818b251282cfe5cbe0a8abd69c54bf5d2260c0907bd2e + AgreementClassRegistered(bytes32,address): 0x878135063a6cfb3bc333e534b1fdc83f4f12221cad6705c31c0567048a8bd3d1 + AgreementClassUpdated(bytes32,address): 0x9279aa773f2b588996032d8de89911555039f28b13a11a7c17074330bc082d9a + SuperTokenFactoryUpdated(address): 0xce13a9895a1719ad4493b2ac1a9bfb36070566161abab408e7ecbe586da8d499 + SuperTokenLogicUpdated(address,address): 0x840acbd291b38534819f47f875839277e502f40e1c7bfea2c5fc2c8017442cd3 + PoolBeaconLogicUpdated(address,address): 0x052cea8931962dd445ef48b0b998d3056bd0705f437087d60fe3c46a3fa09e1f + AppRegistered(address): 0x0d540ad8f39e07d19909687352b9fa017405d93c91a6760981fbae9cf28bfef7 + Jail(address,uint256): 0xbe3aa33bd245135e4e26b223d79d14ea479a47bff09f2b03c53838af1edbb14b + # Inherited events (from UUPSProxiable)(): 0x3a1d0f622cf5be33dc2558b359f025fbeca1d6fb271fb70452a02b0ec2666ce8 +errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/UBISchemeV2.abi.yaml b/.agents/skills/gooddollar/references/contracts/UBISchemeV2.abi.yaml new file mode 100644 index 00000000..75e5e437 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/UBISchemeV2.abi.yaml @@ -0,0 +1,305 @@ +# UBISchemeV2 — dynamic daily UBI pool with per-root claiming and cycle accounting +# Claim uses Identity getWhitelistedRoot(msg.sender); transfers use the root as claim key. + +meta: + name: UBISchemeV2 + version: v2 + source: + - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/contracts/ubi/UBISchemeV2.sol + inherits: [DAOUpgradeableContract] + note: > + Pool sizing uses cycleLength, reserveFactor, minActiveUsers, and on-chain G$ + balance. claim() reverts if Identity root is zero. Internal accounting keys + claims by whitelisted root, payouts go to msg.sender. + deployments: + mainnet: + production: + UBIScheme: + networkId: 122 + address: "0xd253A5203817225e9768C05E5996d642fb96bA86" + creationBlock: 15747401 + production-celo: + UBIScheme: + networkId: 42220 + address: "0x43d72Ff17701B2DA814620735C39C620Ce0ea4A1" + creationBlock: 18006679 + production-xdc: + UBIScheme: + networkId: 50 + address: "0x22867567E2D80f2049200E25C6F31CB6Ec2F0faf" + creationBlock: 95249624 + related: + - https://docs.gooddollar.org/for-developers/core-contracts/ubischeme + - references/guides/claim.md + +getClaimerCount: + mutability: view + inputs: + - day: uint256 + outputs: + - count: uint256 + +getClaimAmount: + notes: + - "Returns aggregate amount claimed for the provided day index." + mutability: view + inputs: + - day: uint256 + outputs: + - amount: uint256 + +getDailyStats: + notes: + - "Returns (claimer count, total claimed amount) for the calendar day derived from periodStart." + mutability: view + inputs: [] + outputs: + - claimerCount: uint256 + - claimAmount: uint256 + +setCycleLength: + mutability: nonpayable + access: avatar + inputs: + - _newLength: uint256 + outputs: [] + emits: [CycleLengthSet] + errors: [CYCLE_TOO_SHORT, ONLY_AVATAR] + +setDay: + notes: + - "Anyone may advance currentDay when wall-clock day crosses the next boundary." + mutability: nonpayable + inputs: [] + outputs: [] + emits: [DaySet] + +hasClaimed: + mutability: view + inputs: + - account: address + outputs: + - claimed: bool + +isNotNewUser: + mutability: view + inputs: + - _account: address + outputs: + - isReturning: bool + +estimateNextDailyUBI: + notes: + - "View-only projection when dailyUbi not yet fixed for the current wall day." + mutability: view + inputs: [] + outputs: + - projected: uint256 + +"checkEntitlement()": + notes: + - "Uses msg.sender as member; resolves Identity root before hasClaimed check." + mutability: view + inputs: [] + outputs: + - amount: uint256 + +"checkEntitlement(address)": + notes: + - "Checks claimable amount for the provided member using that account's whitelisted root." + mutability: view + inputs: + - _member: address + outputs: + - amount: uint256 + +claim: + notes: + - "Resolves whitelistedRoot = Identity.getWhitelistedRoot(msg.sender); reverts if zero." + - "_claim keys bookkeeping by root but pays msg.sender." + - "May notify ClaimersDistribution at GDAO_CLAIMERS if configured." + - "On success emits UBIClaimed; distributionFormula may emit UBICalculated, UBICycleCalculated, DaySet, WithdrawFromDao." + mutability: nonpayable + access: anyone + inputs: [] + outputs: + - didClaim: bool + errors: + - NOT_IN_PERIOD_OR_PAUSED + - NOT_WHITELISTED + - CLAIM_TRANSFER_FAILED + - DAO_TRANSFER_FAILED + +setShouldWithdrawFromDAO: + mutability: nonpayable + access: avatar + inputs: + - _shouldWithdraw: bool + outputs: [] + emits: [ShouldWithdrawFromDAOSet] + errors: [ONLY_AVATAR] + +pause: + mutability: nonpayable + access: avatar + inputs: + - _pause: bool + outputs: [] + errors: [ONLY_AVATAR] + +setNewClaimersReserveFactor: + notes: + - "Adjusts reserveFactor used by distribution formula to reserve part of pool for new claimers." + mutability: nonpayable + access: avatar + inputs: + - _reserveFactor: uint32 + outputs: [] + errors: [ONLY_AVATAR] + +withdraw: + notes: + - "Avatar-controlled treasury action transferring G$ from scheme balance to recipient." + mutability: nonpayable + access: avatar + inputs: + - _amount: uint256 + - _recipient: address + outputs: [] + errors: + - ONLY_AVATAR + - WITHDRAW_FAILED + +lastClaimed: + notes: + - "Returns last claim timestamp keyed by account root logic used by hasClaimed checks." + mutability: view + inputs: + - account: address + outputs: + - ts: uint256 + +currentDay: + mutability: view + inputs: [] + outputs: + - day: uint256 + +periodStart: + mutability: view + inputs: [] + outputs: + - ts: uint256 + +dailyUbi: + mutability: view + inputs: [] + outputs: + - amount: uint256 + +lastWithdrawDay: + mutability: view + inputs: [] + outputs: + - day: uint256 + +shouldWithdrawFromDAO: + mutability: view + inputs: [] + outputs: + - flag: bool + +cycleLength: + mutability: view + inputs: [] + outputs: + - days: uint256 + +dailyCyclePool: + mutability: view + inputs: [] + outputs: + - pool: uint256 + +startOfCycle: + mutability: view + inputs: [] + outputs: + - ts: uint256 + +currentCycleLength: + mutability: view + inputs: [] + outputs: + - length: uint256 + +minActiveUsers: + mutability: view + inputs: [] + outputs: + - n: uint256 + +totalClaimsPerUser: + mutability: view + inputs: + - account: address + outputs: + - count: uint256 + +reserveFactor: + mutability: view + inputs: [] + outputs: + - bps: uint32 + +paused: + mutability: view + inputs: [] + outputs: + - isPaused: bool + +events: + WithdrawFromDao: + indexed: [] + data: + - prevBalance: uint256 + - newBalance: uint256 + UBICalculated: + indexed: [] + data: + - day: uint256 + - dailyUbi: uint256 + - blockNumber: uint256 + UBICycleCalculated: + indexed: [] + data: + - day: uint256 + - pool: uint256 + - cycleLength: uint256 + - dailyUBIPool: uint256 + UBIClaimed: + indexed: + - claimer: address + data: + - amount: uint256 + CycleLengthSet: + indexed: [] + data: + - newCycleLength: uint256 + DaySet: + indexed: [] + data: + - newDay: uint256 + ShouldWithdrawFromDAOSet: + indexed: [] + data: + - ShouldWithdrawFromDAO: bool + +errors: + NOT_IN_PERIOD_OR_PAUSED: "not in periodStarted or paused — requireStarted modifier." + CYCLE_TOO_SHORT: "cycle must be at least 1 day long" + DAO_TRANSFER_FAILED: "DAO transfer has failed — _withdrawFromDao balance check." + NOT_WHITELISTED: "UBIScheme: not whitelisted — claim()" + CLAIM_TRANSFER_FAILED: "claim transfer failed — G$ transfer in _transferTokens." + WITHDRAW_FAILED: "withdraw failed — avatar withdraw." + ONLY_AVATAR: "Inherited avatar-only guard on DAOUpgradeable paths." diff --git a/.agents/skills/gooddollar/references/contracts/UBISchemeV2.selectors.yaml b/.agents/skills/gooddollar/references/contracts/UBISchemeV2.selectors.yaml new file mode 100644 index 00000000..666b89d5 --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/UBISchemeV2.selectors.yaml @@ -0,0 +1,40 @@ +# Generated by scripts/selectors.mjs +functions: + getClaimerCount(uint256): 0xc7a76adf + getClaimAmount(uint256): 0xcef63600 + getDailyStats(): 0x069786ea + setCycleLength(uint256): 0x3d84ceca + setDay(): 0xdddc3616 + hasClaimed(address): 0x73b2e80e + isNotNewUser(address): 0xa21f698a + estimateNextDailyUBI(): 0xc7713870 + checkEntitlement(): 0x98d6621b + checkEntitlement(address): 0x1a787f2e + claim(): 0x4e71d92d + setShouldWithdrawFromDAO(bool): 0xde1de3a0 + pause(bool): 0x02329a29 + setNewClaimersReserveFactor(uint32): 0x414089be + withdraw(uint256,address): 0x00f714ce + lastClaimed(address): 0x013eba92 + currentDay(): 0x5c9302c9 + periodStart(): 0xeda4e6d6 + dailyUbi(): 0x1d8f5ea9 + lastWithdrawDay(): 0xd7c4cbb8 + shouldWithdrawFromDAO(): 0x456ac1c2 + cycleLength(): 0xeac471a0 + dailyCyclePool(): 0x9dc2c033 + startOfCycle(): 0xba075410 + currentCycleLength(): 0x741470ac + minActiveUsers(): 0x37658574 + totalClaimsPerUser(address): 0xcc054dfc + reserveFactor(): 0x4322b714 + paused(): 0x5c975abb +events: + WithdrawFromDao(uint256,uint256): 0x3107ec7eaa50b775d2486c7a394472235804b6fe1c0d4b7bd1d79b09df60f2ba + UBICalculated(uint256,uint256,uint256): 0x836fa39995340265746dfe9587d9fe5c5de35b7bce778afd9b124ce1cfeafdc4 + UBICycleCalculated(uint256,uint256,uint256,uint256): 0x83e0d535b9e84324e0a25922406398d6ff5f96d0c686204ee490e16d7670566f + UBIClaimed(address,uint256): 0x89ed24731df6b066e4c5186901fffdba18cd9a10f07494aff900bdee260d1304 + CycleLengthSet(uint256): 0xa61e6cca2c12e2a0a493683acfe95b034f0f50d793434f4dfe3ba06ea201f344 + DaySet(uint256): 0x67eb03bd555181f9dd23f546e4331ddfb8b4a7d0c8d261ba44e037f30ce894ea + ShouldWithdrawFromDAOSet(bool): 0x6cd9a0fd2e006be39a9918bf56c85cae1d4f4599474483ff18cb93355ebaaf8e +errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/_rich-abi-yaml-format.md b/.agents/skills/gooddollar/references/contracts/_rich-abi-yaml-format.md new file mode 100644 index 00000000..41e2baeb --- /dev/null +++ b/.agents/skills/gooddollar/references/contracts/_rich-abi-yaml-format.md @@ -0,0 +1,93 @@ +# Reading the Rich ABI YAMLs + +Each YAML is a self-contained contract reference. Here's how to parse them. + +## Root structure + +``` +# Header comment — contract name, description, key notes +meta: # name, version, source, implements, inherits, deployments, + # deploymentCreationBlocks (optional), notes +# == Section == # Grouped functions (these are the core content) +events: # All events the contract emits +errors: # Complete error index +``` + +Three root keys are reserved: `meta`, `events`, `errors`. Every other +root-level key is a **function**. + +## Function entries + +```yaml +createFlow: + # Description of what the function does. + notes: + - "Gotcha: Non-obvious behavior or edge cases listed here as structured data." + mutability: nonpayable # view | pure | nonpayable | payable + access: sender | operator # who can call (omitted for view/pure) + inputs: + - token: address + - receiver: address + - flowRate: int96 # inline comments for non-obvious params + - ctx: bytes + outputs: + - newCtx: bytes + emits: [FlowUpdated, FlowUpdatedExtension] # ordered by emission sequence + errors: [CFA_FLOW_ALREADY_EXISTS, CFA_INVALID_FLOW_RATE] # ordered by check sequence +``` + +Fields appear in this order: description comment, `notes`, `mutability`, +`access`, `inputs`, `outputs`, `emits`, `errors`. All are omitted when not +applicable. + +## Key conventions + +- **`ctx: bytes` parameter** = function is called through the Host + (`callAgreement` / `batchCall`), never directly. +- **`access` labels**: `anyone`, `host`, `self`, `admin`, `governance`, + `sender`, `receiver`, `operator`, `manager`, `pic`, `agreement`, + `trusted-forwarder`, `factory`, `super-app`. Combine with `|`. Conditional: + `anyone(if-critical-or-jailed)`. +- **`emits` and `errors` ordering** carries meaning: matches execution flow, + not alphabetical. First errors in the list are the most likely causes. +- **`notes:` field** on functions (and `meta.notes:` at contract level) + lists non-obvious behavior, common mistakes, or edge cases. Always read + these carefully. +- **`meta.source`** is an array of raw GitHub URLs to the Solidity source files + (implementation, interface, base — filenames are self-documenting). +- **`meta.deployments`** has per-network addresses split into `mainnet` and + `testnet` subgroups. +- **`creationBlock`**: decimal block where that **`address`** first has code; + put it on the same object as **`networkId`** / **`address`**, immediately after **`address`**. +- **`meta.deploymentCreationBlocks`**: same shape as **`meta.deployments`**, for + deployments stored as plain address strings; leaves are decimal block numbers. + +## Events section + +```yaml +events: + FlowUpdated: + indexed: # log topics (filterable) + - token: address + - sender: address + data: # log payload + - flowRate: int96 +``` + +## Errors section + +```yaml +errors: + # -- Category -- + - SIMPLE_ERROR # description + - PARAMETERIZED_ERROR: # errors with diagnostic data + inputs: + - value: uint256 +``` + +## Selector sidecar files + +Every `Foo.abi.yaml` has a companion `Foo.selectors.yaml` in the same +directory. These contain computed hex selectors (function 4-byte, error +4-byte, event 32-byte topic0) with full Solidity signatures for +verification. Generated by `scripts/selectors.mjs`. diff --git a/.agents/skills/gooddollar/references/deep-researches/faucet-flows.md b/.agents/skills/gooddollar/references/deep-researches/faucet-flows.md new file mode 100644 index 00000000..12b18990 --- /dev/null +++ b/.agents/skills/gooddollar/references/deep-researches/faucet-flows.md @@ -0,0 +1,42 @@ +# Faucet flows (user-facing explanation) + +This note explains the Faucet in plain language: what it does for users, why a top-up can fail, and what limits exist. + +The on-chain Faucet contract is used to add a small amount of native gas token to a wallet so the user can pay transaction fees. +Reference implementation: [`contracts/fuseFaucet/Faucet.sol`](https://github.com/GoodDollar/GoodProtocol/blob/master/contracts/fuseFaucet/Faucet.sol). +Addresses per chain: [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) only (for example `Faucet` under `production`, `production-celo`, `production-xdc`). + +## What this means for users + +- If your wallet is eligible, Faucet can send a small gas top-up. +- Eligibility usually depends on identity status and anti-abuse limits. +- This is a support mechanism for transaction fees, not a general transfer or swap service. + +## Main actions (in user language) + +- **Top up wallet** (`topWallet`) + Attempts to send gas to the target wallet after checks pass. + +- **Check eligibility first** (`canTop`) + Fast pre-check to see if top-up is currently allowed. + +- **Estimate top-up amount** (`getToppingAmount`) + Shows the amount Faucet would try to send right now. + +## Why a top-up may fail + +- You are not currently authorized by identity rules. +- Daily limit reached. +- Weekly limit reached. +- Wallet is temporarily banned. +- Wallet is too new for current policy. +- Calculated top-up is below minimum threshold. + +## Important safety note + +- The `onTokenTransfer` path includes a swap-like mechanism and is not meant as a normal user swap route. +- It does not enforce slippage protection in the same way users expect from a dedicated swap UI. + +## For developers and agents + +Use `references/guides/faucet.md` for step-by-step execution flow and deterministic preflight calls. diff --git a/.agents/skills/gooddollar/references/deep-researches/fuse-to-celo-staking-migration.md b/.agents/skills/gooddollar/references/deep-researches/fuse-to-celo-staking-migration.md new file mode 100644 index 00000000..261c2d7f --- /dev/null +++ b/.agents/skills/gooddollar/references/deep-researches/fuse-to-celo-staking-migration.md @@ -0,0 +1,38 @@ +# Why Fuse to CELO staking migration uses a staged backend flow + +This explains why the migration is split into allowance detection, unstake, bridge, and destination re-stake instead of a single transaction. In this context, Fuse `GovernanceStakingV2` is the old/source staking contract and Celo **`GooddollarSavingsStream`** is the destination staking contract ([source](https://github.com/Ubeswap/gooddollar-contracts/blob/main/contracts/GooddollarSavingsStream.sol), [0x059ee811414230d1Fb157878D2b491240F4D8d3B](https://celoscan.io/address/0x059ee811414230d1Fb157878D2b491240F4D8d3B)). + +## Why this cannot be one-chain atomic + +Fuse governance staking and CELO destination savings live on different chains, so the system cannot atomically close and reopen stake in one EVM transaction. Cross-chain migration is asynchronous by design and must tolerate timing gaps between source completion and destination finalization. + +## Why user allowance is first + +The migration flow assumes a backend-operated execution wallet. If that wallet needs to pull or operate on user funds in the source path, user approval must exist first. Without allowance, all downstream steps fail, so approval state is the earliest hard gate. + +## Why unstake is separated from bridge + +The source staking position is the canonical balance record on Fuse. The migration must first materialize transferable G$ by closing or reducing stake, then bridge only the confirmed unlocked amount. Bridging before final unstake confirmation introduces mismatch risk. + +## Why bridge finalization must be explicit + +Bridge transfers are eventually consistent across chains. The destination stake step must only run after the bridged G$ is confirmed on CELO. This avoids phantom staking attempts and preserves deterministic accounting. + +## Why destination uses `stakeFor` + +Ubeswap `GooddollarSavingsStream` on Celo supports `stakeFor(amount, recipient)`, which allows the backend execution wallet to stake on behalf of the user after bridge finalization. This fits migration operations where custody is temporary during the transfer window. + +## Main operational risks + +- partial migration from source unstake or bridge limit constraints +- stuck-in-transit bridge messages delaying destination staking +- stale assumptions about contract addresses across networks +- reward expectation mismatch when moving from Fuse governance staking mechanics to CELO savings mechanics + +## Contract/source map + +- Fuse staking family (GoodProtocol): [`GovernanceStaking.sol`](https://github.com/GoodDollar/GoodProtocol/blob/master/contracts/governance/GovernanceStaking.sol) +- Fuse deployment mapping: [`deployment.json`](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) (`production.GovernanceStakingV2`) +- Celo savings (Ubeswap): [`GooddollarSavingsStream.sol`](https://github.com/Ubeswap/gooddollar-contracts/blob/main/contracts/GooddollarSavingsStream.sol) +- Ubeswap contracts repository: [Ubeswap/gooddollar-contracts](https://github.com/Ubeswap/gooddollar-contracts) +- Bridge normalization for LZ quotes: [`BridgeHelperLibrary.normalizeFromTokenTo18Decimals`](https://github.com/GoodDollar/GoodBridge/blob/master/packages/bridge-contracts/contracts/messagePassingBridge/BridgeHelperLibrary.sol) (off-chain LayerZero fee estimation must match this; `canBridge` and `bridgeToWithLz` use the raw burn amount on the source chain) diff --git a/.agents/skills/gooddollar/references/deep-researches/gooddao-daostack-surface.md b/.agents/skills/gooddollar/references/deep-researches/gooddao-daostack-surface.md new file mode 100644 index 00000000..754a3a05 --- /dev/null +++ b/.agents/skills/gooddollar/references/deep-researches/gooddao-daostack-surface.md @@ -0,0 +1,37 @@ +# GoodDAO and DAOStack surface + +GoodProtocol’s on-chain **governance shell** is largely **DAOStack-shaped**: an **Avatar** holds protocol assets and reputation context; a **Controller** registers **schemes** and routes privileged calls. GoodDocs summarizes DAO-facing roles; **Avatar**, **Controller**, and other DAO contract addresses live only in [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json). Implementation follows [DAOStack Arc](https://github.com/daostack/arc) patterns. + +## Core interfaces (GoodProtocol) + +[`DAOStackInterfaces.sol`](https://github.com/GoodDollar/GoodProtocol/blob/master/contracts/DAOStackInterfaces.sol) defines the pieces most integrations touch: + +### Avatar + +- **`nativeToken()`** — often the G$ token address for the deployment. +- **`nativeReputation()`** — **`GReputation`** token for voting weight (see `GReputation` in `deployment.json`). +- **`owner()`** — owner of the Avatar, typically the **`Controller`** contract. + +### Controller + +- **`avatar()`** — address of the Avatar contract. +- **`registerScheme` / `unregisterScheme` / `unregisterSelf` / `isSchemeRegistered` / `getSchemePermissions`** — scheme lifecycle and permission bitmask per scheme+avatar. +- **`genericCall(contract, data, avatar, value)`** — executes arbitrary calls **as the avatar** (used heavily by DAO-backed contracts to move tokens or call NameService). +- **`mintTokens`**, **`externalTokenTransfer`**, **`sendEther`** — treasury-style operations through the controller/avatar. + +## How GoodProtocol contracts use it + +- **`DAOUpgradeableContract`** / **`DAOContract`** descendants resolve **`dao`** (Controller) and **`avatar`** from **NameService** keys such as **`CONTROLLER`** and **`AVATAR`** (also written during **NameService.initialize**). +- **Avatar-gated writes** (for example **NameService.setAddress**, **UBISchemeV2** admin functions, **IdentityV3** after `initDAO`) require **`msg.sender == dao.avatar()`** (or equivalent role), not EOAs. +- Schemes that upgrade themselves (for example staking **`upgrade()`**) use **`dao.genericCall`** and **`unregisterSelf`** against the avatar. + +## What agents should do + +1. Treat **DAO calls** as **governance-only** unless the user explicitly controls the avatar or a registered scheme. +2. For **read-only** work, use **NameService** and per-contract **view** functions; use **Controller.isSchemeRegistered** when validating that a target contract is an approved scheme (for example staking migrations). +3. Never fabricate **Avatar** or **Controller** addresses — use [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) or NameService. + +## References + +- [Core contracts — DAO contracts](https://docs.gooddollar.org/for-developers/core-contracts) (narrative only; addresses from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) only) +- DAOStack Arc controller and avatar concepts in the upstream repo linked from GoodDocs. diff --git a/.agents/skills/gooddollar/references/deep-researches/how-ubi-is-minted.md b/.agents/skills/gooddollar/references/deep-researches/how-ubi-is-minted.md new file mode 100644 index 00000000..96d321a3 --- /dev/null +++ b/.agents/skills/gooddollar/references/deep-researches/how-ubi-is-minted.md @@ -0,0 +1,68 @@ +# How UBI is minted + +This document aligns agent explanations with [How GoodDollar works](https://docs.gooddollar.org/how-gooddollar-works) and GoodDocs component pages; **contract addresses** come only from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json). + +## Monetary creation (protocol level) + +- New G$ is created in connection with reserve mechanics: purchases into the reserve and reserve-side parameters (including reserve ratio) influence how much G$ can be issued while maintaining backing (see sustainability and issuance sections in GoodDocs). +- Selling G$ back to the reserve burns supply in that model. +- G$ that the protocol creates is allocated across UBI, savings incentives, treasury, and ecosystem uses per the distribution section of GoodDocs. + +## Where G$ is actually created + +Creation happens at the G$ token `mint(...)` call site, not inside `UBIScheme.claim()`. + +Current implementation uses the Mento-core expansion path: + +- `GoodDollarExpansionController` mints and routes to distribution helper. + +**DistributionHelper recipients** decide how much eventually lands in the UBI pool. + +## Claim vs reserve minting (important distinction) + +- **Reserve path:** Buying G$ through the reserve-backed AMM (or related Mento rails) is where mint and burn tied to the reserve model most directly apply at the token level. +- **Daily UBI `claim()`:** On `UBISchemeV2`, a successful claim typically **transfers G$ from the scheme contract’s balance** to the user (`token.transfer` in `_transferTokens`). The scheme may be **refilled** from the DAO avatar via internal `_withdrawFromDao` when configured, not necessarily minting in the same transaction as `claim()`. So describe user-facing UBI as **receipt from the UBI scheme balance**; reserve **minting** is the macro story, **transfer** is the usual claim-time mechanism. + +## Mento-core expansion flow (detailed) + +This is the detailed path for modern reserve-ratio-aware expansion. + +1. A caller triggers `mintUBIFromExpansion(exchangeId)` on `GoodDollarExpansionController`. +2. Expansion is time-gated by config (`expansionFrequency`, `lastExpansion`), so it does not run every block. +3. Controller computes a reserve-ratio scalar (effectively compounding `(1 - expansionRate)` for elapsed periods). +4. Controller calls `GoodDollarExchangeProvider.mintFromExpansion(exchangeId, reserveRatioScalar)`. +5. Exchange provider updates exchange state (including reserve-ratio math) and returns `amountToMint`. +6. Controller mints G$ to `distributionHelper`. +7. Controller triggers distribution (`onDistribution`) so recipients (including UBIScheme when configured) receive allocation. + +Also in `GoodDollarExpansionController`: + +- `mintUBIFromInterest(exchangeId, reserveInterest)` +- `mintUBIFromReserveBalance(exchangeId)` + +These are additional funding paths that mint to distribution helper as part of reserve-driven policy. + +## Reserve ratio, expansion, and risk + +- Practical reserve-ratio intuition: collateral backing strength per unit of G$ supply. +- Lower reserve ratio means weaker backing and higher risk when adding new supply. +- Expansion uses reserve-ratio-aware math instead of blind fixed minting, but aggressive params can still increase sell pressure. +- Key policy levers are expansion rate and expansion frequency (plus caller cadence/automation quality). + +## What claimers experience + +- Verified users receive daily UBI from a pool split among those who claim in each period (GoodDocs). +- The user-facing transaction is a UBIScheme-style `claim` on chains where it is deployed; ABI and version follow your deployment. + +## On-chain components (typical) + +- Identity system for verification and whitelist roots. +- UBIScheme (or successor) for entitlement and claim execution. +- G$ token: value reaches users via **transfer** from scheme balance and/or broader minting economics from the reserve side depending on which action you analyze. +- DistributionHelper: bridge layer between mint source and recipient buckets (UBI, others). + +## Agent guidance + +- Use GoodDocs for macro issuance and allocation; use UBIScheme + token transfer behavior for **claim** explanations. +- Use [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) for contract addresses instead of guessing. +- When users ask "why no UBI funding today", check whether mint functions were executed, then verify DistributionHelper recipient config and UBIScheme balance. diff --git a/.agents/skills/gooddollar/references/deep-researches/inviter-invitee-reward-model.md b/.agents/skills/gooddollar/references/deep-researches/inviter-invitee-reward-model.md new file mode 100644 index 00000000..267fa744 --- /dev/null +++ b/.agents/skills/gooddollar/references/deep-researches/inviter-invitee-reward-model.md @@ -0,0 +1,58 @@ +# Inviter and invitee reward model + +This explains why invite rewards sometimes work and sometimes fail, in user-facing terms. + +Invite rewards are handled by `InvitesV2`, but eligibility depends heavily on the identity system (`Identity`, often `IdentityV4`). In practice, most confusing cases are caused by whitelist or reverification state, not by the invite contract itself. + +## How the reward model works + +There are two separate moments: + +- `join`: the invitee registers with an invite code. +- bounty payout: the contract later checks if this invitee is eligible and pays inviter and invitee when rules pass. + +So joining does not guarantee immediate payout. Payout depends on current eligibility at claim time. + +## Why whitelist status is the main gate + +For bounty eligibility, `InvitesV2` checks whitelist state through Identity. + +The important behavior is: + +- A user can still have status `1` in identity storage but fail `isWhitelisted(...)` if reverification is due. +- When reverification is due, bounty checks fail until an admin refreshes authentication(Face Verification). +- Connected-wallet setups can still fail if the specific address used in invite flow does not pass the whitelist check expected by the contract path. + +## Why reverification blocks rewards + +Reverification cadence is defined in `IdentityV4` with day-based options (`reverifyDaysOptions`) and per-user progression (`authCount`). + +When too many days pass since the last authentication for that user’s current step: + +- `shouldReverify(...)` becomes true +- `isWhitelisted(...)` becomes false for bounty gating +- `canCollectBountyFor(...)` fails until authentication is refreshed + +This is why teams may see users who were once valid but are currently not eligible for invite rewards. + +## Common reasons a bounty is not paid + +- Invitee or inviter is not currently whitelisted. +- Reverification is due for invitee or inviter. +- `minimumClaims` or `minimumDays` thresholds are not met yet. +- Bounty was already paid or was zero at join time. +- Contract is inactive, or identity-chain checks do not match the active chain. +- Invite code or join state is invalid (duplicate code, self-invite, already joined). + +## What to measure for analytics + +- Historical pass: account was authenticated at least once (`lastAuthenticated > 0`). +- Current eligibility: account is currently whitelist-valid (`isWhitelisted`/non-zero root, depending on query design). + +Do not treat these as the same metric. Historical pass explains past onboarding success; current eligibility explains current payout success. + +## Contract sources + +- Invite contract: [`InvitesV2.sol`](https://github.com/GoodDollar/GoodProtocol/blob/master/contracts/invite/InvitesV2.sol) +- Identity contract: [`IdentityV4.sol`](https://github.com/GoodDollar/GoodProtocol/blob/master/contracts/identity/IdentityV4.sol) +- Deployment addresses: [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) diff --git a/.agents/skills/gooddollar/references/deep-researches/mento-reserve-economics.md b/.agents/skills/gooddollar/references/deep-researches/mento-reserve-economics.md new file mode 100644 index 00000000..e46db2a8 --- /dev/null +++ b/.agents/skills/gooddollar/references/deep-researches/mento-reserve-economics.md @@ -0,0 +1,27 @@ +# Mento and reserve economics + +This note ties **macro G$ economics** (GoodDocs) to **Mento trading surfaces** (broker, reserve, expansion) that agents integrate on-chain. + +## Protocol-level story (GoodDocs) + +- G$ is **reserve-backed**; issuance and price discovery follow an **augmented bonding curve** (Bancor-style dynamics) described in [How GoodDollar works](https://docs.gooddollar.org/how-gooddollar-works). +- **Buying** from the reserve side increases reserve assets and can support **new G$ supply** within reserve rules; **selling back** burns G$ and returns collateral, with parameters governed by **GoodDAO**. +- Distribution of newly created G$ spans UBI, savings incentives, treasury, and ecosystem allocations (same doc), but execution timing depends on the mint path and distribution trigger used by the deployment flow. + +## User-facing buy and sell (historical vs current) + +- [Buy and Sell G$](https://docs.gooddollar.org/user-guides) describes reserve interaction, fees (including exit contribution on some paths), and older explorer flows (GoodMarketMaker / exchangeHelper on Ethereum testnets). Treat that page as **product narrative**; **live contract addresses** must come only from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json). + +## Mento stack (agent integration) + +On networks where GoodDollar uses Mento (see `MentoBroker` and related keys in [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) for your environment): + +- **`IBroker`** (implementation in [mento-core `Broker.sol`](https://github.com/mento-org/mento-core)) is the usual **swap entrypoint**: `getAmountIn` / `getAmountOut`, `swapIn` (exact in), `swapOut` (exact out), plus **trading limits** state per exchange id and token. +- **Reserve** holds collateral; **exchange provider** contracts price trades against the reserve; **GoodDollarExpansionController** and related interfaces in GoodProtocol’s [`MentoInterfaces.sol`](https://github.com/GoodDollar/GoodProtocol/blob/master/contracts/MentoInterfaces.sol) describe expansion and avatar wiring for governance-facing changes. +- Agents should not hardcode **exchangeId** or provider addresses: read them from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) or discover via `getExchangeProviders()` after confirming the broker address for the chain from that file. + +## Agent guidance + +1. Explain **macro** supply and reserve behavior with GoodDocs language. +2. Execute **swaps** with broker quotes, slippage bounds, and allowances per `references/guides/swap.md`. +3. On revert, distinguish **slippage / limit** failures (broker) from **reserve liquidity** messages (see `MentoBroker.abi.yaml` error map and mento-core source). diff --git a/.agents/skills/gooddollar/references/deep-researches/on-off-ramp-service.md b/.agents/skills/gooddollar/references/deep-researches/on-off-ramp-service.md new file mode 100644 index 00000000..b59e5c92 --- /dev/null +++ b/.agents/skills/gooddollar/references/deep-researches/on-off-ramp-service.md @@ -0,0 +1,69 @@ +# On- and off-ramp service via stable token swap + +Use this note for the service pattern where ramp providers do not list G$ directly. The practical path is: ramp in/out with a listed stable token (for example cUSD), then swap between stable and G$ on-chain. + +## Why this is required + +- Most on-/off-ramp providers list mainstream stable tokens, not G$. +- Service needs a bridge asset for fiat rails. +- Stable token becomes the integration point with ramp providers, while G$ remains the in-app asset. + +## On-chain source of truth + +- Solidity: [`contracts/utils/BuyGDClone.sol`](https://github.com/GoodDollar/GoodProtocol/blob/master/contracts/utils/BuyGDClone.sol) +- Components: + - `BuyGDCloneFactory` + - `BuyGDCloneV2` + - `DonateGDClone` +- Deployments: [releases/deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) + +## Service architecture + +The factory deploys EIP-1167 minimal clones and wires swap infrastructure (router, oracle, quoter, optional Mento broker configuration, G$ token, stable token). + +Swap execution is **dual-path**: + +- **Uniswap-style route** (router/quoter path) +- **Mento-based route** (broker/exchange-provider path) + +For each swap request, the service compares quoted outputs and selects the route with the **larger `amountOut`** (best execution for the same input amount), then enforces `minAmount` guard on the selected path. + +Each user gets a deterministic clone address from owner-based salt: + +- `predict(owner)` for buy clone +- `predictDonation(owner, donor)` for donation clone + +This is why clone-per-user design is used: + +- predictable per-user addresses for audit and routing +- isolated execution context +- cheaper deployment than full contract instances + +## Execution surface (conceptual) + +The operational surface is intentionally small and deterministic: + +- `create(owner)` +- `createAndSwap(owner, minAmount)` +- `predict(owner)` +- `createDonation`, `createDonationAndSwap`, `predictDonation` + +This keeps on-/off-ramp architecture auditable and predictable across users. + +## Risks + +- Wrong factory or wrong chain causes permanent fund loss risk. +- Stale router/oracle/mento config can fail swap or produce bad execution. +- Missing `minAmount` protection increases slippage risk. +- Quote source mismatch or stale quotes across Uniswap/Mento can pick a suboptimal route if not refreshed just before execution. + +## Boundary note + +This file explains **why** this architecture exists for ramp services and why per-user clones matter. +For step-by-step service execution flow, use `references/guides/on-off-ramp.md`. + +## Cross-reference + +- User narrative: [Buy and Sell G$](https://docs.gooddollar.org/user-guides) +- Token integration details: [How to integrate the G$ token](https://docs.gooddollar.org/for-developers/developer-guides/how-to-integrate-the-gusd-token) +- Broker ABI: `references/contracts/MentoBroker.abi.yaml` diff --git a/.agents/skills/gooddollar/references/guides/bridge.md b/.agents/skills/gooddollar/references/guides/bridge.md new file mode 100644 index 00000000..71fd6e8c --- /dev/null +++ b/.agents/skills/gooddollar/references/guides/bridge.md @@ -0,0 +1,187 @@ +# Bridge guide + +Use for moving G$ across supported networks with deployment-specific bridge contracts. + +Primary local ABI reference for MessagePassingBridge flow: + +- `references/contracts/MessagePassingBridge.abi.yaml` + +## GoodDocs alignment + +- User flow and high-level behavior: [Bridge GoodDollars](https://docs.gooddollar.org/user-guides/bridge-gooddollars). +- Resolve supported bridge contract addresses per chain from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) only (for example `MpbBridge` under `production`, `production-celo`, `production-xdc`). Use [Bridge GoodDollars](https://docs.gooddollar.org/user-guides/bridge-gooddollars) for user-facing flow and troubleshooting, not for addresses. + +## Goal + +Bridge with deterministic pre-checks: bridge support, allowance, amount, cross-chain transport fee, and delivered G$ after destination **amount** limits and protocol fee. + +## Required inputs + +- source and destination chain metadata +- bridge contract address for source chain +- source G$ token address +- amount in source token decimals +- signer and rpc url + +## Execution flow + +1. Resolve source bridge and token addresses for the network pair. +2. Run bridge eligibility checks for sender and amount via `canBridge(from, amount)` on the **source** bridge (same contract you call for `bridgeToWithLz` / `bridgeToWithAxelar`). Outbound burn does not invoke `canBridge` inside `_bridgeTo`; destination mint still enforces **amount** limits (see **Bridge amount limit context**). +3. Read allowance and approve bridge spender when required. +4. Resolve transport mode (`LZ` or `AXELAR`) and estimate required native fee. +5. Send bridge transaction with nonzero `msg.value` and explicit transport method. +6. Return tx hash and normalized bridge parameters. + +## Bridge fee context + +Two different costs show up on `MessagePassingBridge`; do not conflate them. + +**1. Cross-chain transport fee (native gas token on the source chain)** +Paid as **`msg.value`** on the outbound call. The contract reverts with **`MISSING_FEE`** if `msg.value` is zero. On the **LayerZero** path the contract compares your `msg.value` to **`estimateSendFee`** and reverts **`LZ_FEE(required, sent)`** if it is too low. Use the same **normalized** amount for `estimateSendFee` as the contract uses internally (see the next section). On the **Axelar** path you still attach native value for Gas Service / execution; there is no single `estimateSendFee` analogue in the snippet—follow Envio/Axelar docs or simulate the exact call for production amounts. + +**2. Protocol fee on minted G$ (destination chain, basis points)** +When the message is executed on the **destination** chain, the bridge applies **`bridgeFees`** (min / max / fee bps via `setBridgeFees`) and mints the recipient **minus** that fee; the fee portion is minted to **`feeRecipient`** when it is non-zero (see `bridgeFees()`, `feeRecipient`, and `_takeFee` / `ExecutedTransfer` in `references/contracts/MessagePassingBridge.abi.yaml`). This is **not** the LayerZero relayer fee; it is a separate cut on the **token amount** delivered on arrival. + +**3. Optional OFT / LayerZero token-adapter path** +If the flow uses the GoodDollar OFT-style adapter instead of `MessagePassingBridge`, fee quoting follows **`quoteSend`** / **`MessagingFee`** on that contract; see `references/contracts/GoodDollarOFTAdapter.abi.yaml`. + +## Bridge amount limit context + +**Bridge limit** means **bridge amount limit**: policy on **how much G$** (token volume) may move—**`minAmount`**, per-transfer cap, per-account daily cap, and aggregate daily cap—plus **`onlyWhitelisted`**. It does **not** mean the cross-chain **native** transport fee (`msg.value`, **`LZ_FEE`**), and it does **not** mean the **destination mint fee** in **`bridgeFees`** (bps); those are covered under **Bridge fee context**. + +Amount caps and counters are **per bridge deployment**: read the **source** contract for outbound **amount** policy and usage meters tied to the burn, and the **destination** contract for **`_enforceLimits`** at inbound mint completion; do not assume identical **`bridgeLimits`** across chains. + +**1. Amount caps and usage meters** +**`bridgeLimits()`** exposes **`dailyLimit`**, **`txLimit`**, **`accountDailyLimit`**, **`minAmount`**, and **`onlyWhitelisted`**. Compare those caps to **`bridgeDailyLimit()`** (aggregate **`bridged24Hours`** and **`lastTransferReset`**) and **`accountsDailyLimit(account)`** (same fields per sender). Updates use **`setBridgeLimits`** (access per the ABI). Field-level notes and accessors live in `references/contracts/MessagePassingBridge.abi.yaml`. + +**2. Source preflight vs destination enforcement** +**`canBridge(from, amount)`** on the **source** is a view-only diagnostic for **that amount**: same policy family as amount limit checks, evaluated on the **raw** burn size (not the LayerZero fee normalization). Outbound **`_bridgeTo`** does **not** call **`canBridge`**; call it from the client if you want **`(false, reason)`** before signing instead of learning only from a revert after burn setup. When the message is executed on the **destination**, **`_enforceLimits`** is the hard gate for **amount** throttles and whitelist behavior at mint time. + +## Outbound pause, approved requests, and inbound source bridges + +These controls are separate from numeric **amount** caps; they still block or relax bridging and can surface as **`BRIDGE_LIMITS`** or inbound skips. + +**`pauseBridge`** sets **`isClosed`**; when closed, outbound flow reverts with **`BRIDGE_LIMITS('closed')`**. **`approvedRequests(requestId)`** on the destination lets **`_bridgeFrom`** skip standard **amount** limit enforcement for that completion when set. **`setDisabledBridges`** toggles **`disabledSourceBridges`** entries keyed by **`keccak256(abi.encode(sourceChainId, BridgeService))`**, controlling whether an inbound relay from that source is accepted before the rest of destination handling. See `references/contracts/MessagePassingBridge.abi.yaml`. + +## Axelar vs LayerZero on GoodDollar deployments + +LayerZero mappings are initialized for Ethereum, Celo, Fuse, and XDC in `initialize` / `upgrade` on `MessagePassingBridge`. The **Axelar** path is only usable where `toAxelarChainId(targetChainId)` returns a non-empty string; the on-chain pure function currently maps **1**, **5**, **42220**, and **44787** only. For **Fuse (122)** or **XDC (50)** targets, use **LZ** (`bridgeToWithLz`) unless governance ships a broader Axelar mapping. + +## LayerZero fee and `estimateSendFee` + +`bridgeToWithLz` burns the G$ **raw** amount in the source token’s `decimals()`. Inside the contract, LayerZero payload and `estimateSendFee` use a value normalized to **18 decimals** the same way as [GoodBridge `BridgeHelperLibrary.normalizeFromTokenTo18Decimals`](https://github.com/GoodDollar/GoodBridge/blob/master/packages/bridge-contracts/contracts/messagePassingBridge/BridgeHelperLibrary.sol): if `decimals < 18`, multiply by `10^(18 - decimals)`; if `decimals > 18`, divide by `10^(decimals - 18)`; otherwise use the raw amount. + +`canBridge(from, amount)` is evaluated on the **raw** burn amount, not the normalized value. + +Read `decimals()` from the source G$ contract when building off-chain fee quotes so you stay aligned if a deployment differs. + +## Deterministic snippet + +```js +import { ethers } from "ethers"; + +function normalizedForLzFee(raw, tokenDecimals) { + if (tokenDecimals < 18) return raw * 10n ** BigInt(18 - tokenDecimals); + if (tokenDecimals > 18) return raw / 10n ** BigInt(tokenDecimals - 18); + return raw; +} + +const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); +const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); + +const token = new ethers.Contract( + process.env.GOODDOLLAR_ADDRESS, + [ + "function decimals() view returns (uint8)", + "function allowance(address,address) view returns (uint256)", + "function approve(address,uint256) returns (bool)", + ], + signer, +); + +const bridge = new ethers.Contract( + process.env.BRIDGE_ADDRESS, + [ + "function canBridge(address,uint256) view returns (bool,string)", + "function toLzChainId(uint256) view returns (uint16)", + "function estimateSendFee(uint16,address,address,uint256,bool,bytes) view returns (uint256,uint256)", + "function bridgeToWithLz(address,uint256,uint256,bytes) payable", + "function bridgeToWithAxelar(address,uint256,uint256,address) payable", + ], + signer, +); + +const owner = await signer.getAddress(); +const targetChainId = Number(process.env.TARGET_CHAIN_ID); +const recipient = process.env.RECIPIENT; +const amount = ethers.parseUnits(process.env.AMOUNT, Number(process.env.DECIMALS)); +const transport = (process.env.BRIDGE_TRANSPORT || "LZ").toUpperCase(); +const tokenDecimals = await token.decimals(); +const normalizedForLzEstimate = normalizedForLzFee(amount, Number(tokenDecimals)); + +const [canBridge, reason] = await bridge.canBridge(owner, amount); +if (!canBridge) throw new Error(`Bridge blocked: ${reason}`); + +const allowance = await token.allowance(owner, process.env.BRIDGE_ADDRESS); +if (allowance < amount) { + const approveTx = await token.approve(process.env.BRIDGE_ADDRESS, amount); + await approveTx.wait(); +} + +let tx; + +if (transport === "LZ") { + const dstEid = await bridge.toLzChainId(targetChainId); + if (dstEid === 0) throw new Error("Unsupported target chain for LayerZero"); + + const adapterParams = process.env.LZ_ADAPTER_PARAMS || "0x"; + const [nativeFee] = await bridge.estimateSendFee( + dstEid, + owner, + recipient, + normalizedForLzEstimate, + false, + adapterParams, + ); + if (nativeFee <= 0n) throw new Error("Estimated LayerZero fee is zero"); + + tx = await bridge.bridgeToWithLz(recipient, targetChainId, amount, adapterParams, { + value: nativeFee, + }); +} else if (transport === "AXELAR") { + const nativeFee = ethers.parseEther(process.env.AXELAR_FEE_ETH || "0.01"); + tx = await bridge.bridgeToWithAxelar(recipient, targetChainId, amount, owner, { + value: nativeFee, + }); +} else { + throw new Error("Unsupported BRIDGE_TRANSPORT. Use LZ or AXELAR"); +} + +const receipt = await tx.wait(); +console.log( + JSON.stringify( + { + txHash: receipt.hash, + sourceBridge: process.env.BRIDGE_ADDRESS, + targetChainId, + transport, + recipient, + rawAmount: amount.toString(), + tokenDecimals: Number(tokenDecimals), + normalizedAmountForLz: normalizedForLzEstimate.toString(), + }, + null, + 2, + ), +); +``` + +## Failure handling + +- unsupported destination: return targetChainId, bridge address, and transport mode +- fee too low (`LZ_FEE` or underpriced Axelar fee): re-estimate and retry with user confirmation +- approval or balance issue: return required delta +- credited G$ on destination is reduced by **`bridgeFees`** (bps / min / max); that is independent of the source **`msg.value`** transport fee +- **`canBridge`** false on source: return the **`reason`** string from the view call +- **`BRIDGE_LIMITS(reason)`** custom error (see `references/contracts/MessagePassingBridge.abi.yaml` **errors**): **`reason`** labels the failing check (numeric **amount** limit, whitelist, **`closed`**, or other policy string from the implementation) +- source preflight passed but destination still reverts: re-read **`bridgeLimits`** and daily counters for **amount** caps and **`onlyWhitelisted`**; check **`isClosed`**, **`approvedRequests`**, and **`disabledSourceBridges`** per **Outbound pause, approved requests, and inbound source bridges**; message delivery can cross a reset boundary or policy change diff --git a/.agents/skills/gooddollar/references/guides/check-identity.md b/.agents/skills/gooddollar/references/guides/check-identity.md new file mode 100644 index 00000000..adc01659 --- /dev/null +++ b/.agents/skills/gooddollar/references/guides/check-identity.md @@ -0,0 +1,87 @@ +# Check identity guide + +Use when the user asks whether an address is eligible for UBI or how identity links wallets. + +## GoodDocs alignment + +- [Connect another wallet address to identity](https://docs.gooddollar.org/user-guides/connect-another-wallet-address-to-identity): associated addresses resolve to a verified root in the Identity contract; `connectAccount` links wallets. +- One claim per day applies across all connected addresses for the same verified identity (see the hint on that page). + +## Goal + +Determine whitelist or authentication status with deterministic on-chain reads. + +## Metric semantics + +- **Passed whitelisting (historical):** use `lastAuthenticated(account) > 0`. +- **Still whitelisted (current):** use `getWhitelistedRoot(account) != 0x0` or `isWhitelisted(account) == true`. +- `getWhitelistedRoot(account) != 0x0` is a current-state signal, not an "ever passed" signal. + +## Required inputs + +- `nameServiceAddress` or explicit Identity address +- `account` to check +- `rpcUrl` and chain configuration + +## Execution flow + +1. Resolve `IDENTITY` from NameService when used on the deployment. +2. Read `getWhitelistedRoot(account)` or equivalent for the deployed Identity version. +3. Treat non-zero root as tied to a whitelisted identity tree when that is the protocol rule for the deployment. +4. Read `lastAuthenticated(account)` for historical pass status and `getWhitelistedRoot(account)` or `isWhitelisted(account)` for current status. + +## Deterministic snippet + +```js +import { ethers } from "ethers"; + +const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); + +const nameService = new ethers.Contract( + process.env.NAMESERVICE_ADDRESS, + ["function getAddress(string) view returns (address)"], + provider, +); + +const identityAddress = await nameService.getAddress("IDENTITY"); +const identity = new ethers.Contract( + identityAddress, + [ + "function getWhitelistedRoot(address) view returns (address)", + "function isWhitelisted(address) view returns (bool)", + "function lastAuthenticated(address) view returns (uint256)", + ], + provider, +); + +const account = process.env.ACCOUNT; +const root = await identity.getWhitelistedRoot(account); +const isWhitelisted = await identity.isWhitelisted(account); +const lastAuthenticated = await identity.lastAuthenticated(account); + +console.log( + JSON.stringify( + { + account, + identityAddress, + whitelistedRoot: root, + isWhitelisted, + lastAuthenticated: lastAuthenticated.toString(), + }, + null, + 2, + ), +); +``` + +## Return shape + +- `isWhitelisted` or equivalent boolean summary +- `whitelistedRoot` or equivalent +- `lastAuthenticated` for historical-pass classification +- optional metadata fields when available + +## Failure handling + +- NameService cannot resolve `IDENTITY`: stop and fix inputs using [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) (`Identity` / `NameService` for the target environment)—not GoodDocs tables. +- Read failures: return the failing call and next step. diff --git a/.agents/skills/gooddollar/references/guides/claim.md b/.agents/skills/gooddollar/references/guides/claim.md new file mode 100644 index 00000000..bd51abd0 --- /dev/null +++ b/.agents/skills/gooddollar/references/guides/claim.md @@ -0,0 +1,83 @@ +# Claim guide + +Use when the user wants to claim daily UBI. Protocol context: [How GoodDollar works](https://docs.gooddollar.org/how-gooddollar-works) and [UBIScheme (GoodDocs behavior)](https://docs.gooddollar.org/for-developers/core-contracts/ubischeme)—contract addresses only from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json). + +## Goal + +Execute a safe `claim()` with identity pre-checks and clear outputs. + +## GoodDocs alignment + +- UBI is distributed daily to verified users; the active pool is split among claimers in each period (see [How GoodDollar works](https://docs.gooddollar.org/how-gooddollar-works)). +- UBIScheme deployments vary by chain (Fuse, Celo, XDC); resolve live contract addresses only from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) (`UBIScheme` under `production`, `production-celo`, or `production-xdc`). Use [Core contracts / UBIScheme](https://docs.gooddollar.org/for-developers/core-contracts/ubischeme) for documented behavior, not for addresses. + +## Required inputs + +- `nameServiceAddress` or explicit UBIScheme and Identity addresses from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) +- `rpcUrl` and chain configuration +- signer context + +## Execution flow + +1. Resolve `IDENTITY` and `UBISCHEME` from NameService when NameService is the source of truth for the deployment. +2. Confirm whitelist status for the claiming account. +3. Optionally read entitlement or claimable state before sending `claim()`. +4. Call `claim()` on the resolved UBIScheme (contract generation may differ by deployment; align ABI with your target). +5. Return tx hash and claimed amount when derivable from events or balance delta. + +## Deterministic snippet + +```js +import { ethers } from "ethers"; + +const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); +const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); + +const nameService = new ethers.Contract( + process.env.NAMESERVICE_ADDRESS, + ["function getAddress(string) view returns (address)"], + provider, +); + +const identityAddress = await nameService.getAddress("IDENTITY"); +const ubiAddress = await nameService.getAddress("UBISCHEME"); + +const identity = new ethers.Contract( + identityAddress, + [ + "function isWhitelisted(address) view returns (bool)", + "function getWhitelistedRoot(address) view returns (address)", + ], + provider, +); + +const account = await signer.getAddress(); +const isWhitelisted = await identity.isWhitelisted(account); +if (!isWhitelisted) throw new Error("Account is not whitelisted"); + +const root = await identity.getWhitelistedRoot(account); +if (root === ethers.ZeroAddress) throw new Error("No whitelisted root"); + +const ubi = new ethers.Contract( + ubiAddress, + ["function claim()", "event UBICalculated(address,uint256,uint256,uint256)"], + signer, +); + +const tx = await ubi.claim(); +const receipt = await tx.wait(); +console.log(JSON.stringify({ txHash: receipt.hash, account, root }, null, 2)); +``` + +## Pre-check failures + +- Not whitelisted: stop and point the user to identity verification flows in GoodDocs. +- Missing contract address: stop; use [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) only. +- Zero entitlement: communicate that nothing is claimable in the current period without guessing amounts. + +## Output contract + +- network +- resolved contract addresses +- tx hash +- claim outcome details when available diff --git a/.agents/skills/gooddollar/references/guides/faucet.md b/.agents/skills/gooddollar/references/guides/faucet.md new file mode 100644 index 00000000..d224e85e --- /dev/null +++ b/.agents/skills/gooddollar/references/guides/faucet.md @@ -0,0 +1,76 @@ +# Faucet top-up guide + +Use when the user needs native gas top-up via GoodProtocol Faucet. + +## Goal + +Run deterministic pre-checks and call `topWallet` only when eligibility and limits pass. + +## Required inputs + +- `rpcUrl`, chain configuration, signer +- Faucet address for the chain (from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) `Faucet` under the matching environment, or `NameService.getAddress` when the deployment documents the key) +- target user address + +## Execution flow + +1. Resolve Faucet contract address for the active chain from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json). +2. Run `canTop(user)` as preflight. +3. Read `getToppingAmount(user)` and communicate expected top-up. +4. If eligible, call `topWallet(user)`. +5. Return tx hash and resulting top-up context. + +## Deterministic snippet + +```js +import { ethers } from "ethers"; + +const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); +const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); + +const faucet = new ethers.Contract( + process.env.FAUCET_ADDRESS, + [ + "function canTop(address) view returns (bool)", + "function getToppingAmount(address) view returns (uint256)", + "function topWallet(address payable)", + ], + signer, +); + +const user = process.env.USER; +const canTop = await faucet.canTop(user); +if (!canTop) throw new Error("Faucet canTop returned false"); + +const amount = await faucet.getToppingAmount(user); +const tx = await faucet.topWallet(user); +const receipt = await tx.wait(); + +console.log( + JSON.stringify( + { + txHash: receipt.hash, + user, + toppingAmount: amount.toString(), + }, + null, + 2, + ), +); +``` + +## Common rejection reasons + +- `not authorized` +- daily or weekly cap reached +- banned address +- low effective `toTop` vs minimum threshold +- faucet inactive or wrong chain/address + +## Output contract + +- network +- faucet address +- `canTop` preflight result +- top-up amount estimate +- tx hash (when executed) diff --git a/.agents/skills/gooddollar/references/guides/gooddocs.md b/.agents/skills/gooddollar/references/guides/gooddocs.md new file mode 100644 index 00000000..5e5b4b39 --- /dev/null +++ b/.agents/skills/gooddollar/references/guides/gooddocs.md @@ -0,0 +1,37 @@ +# GoodDocs hub + +Canonical protocol documentation lives at [GoodDocs](https://docs.gooddollar.org/). + +This is a **routing guide** (quick link map), not a deep protocol analysis note. + +## Start here + +- [Welcome](https://docs.gooddollar.org/) +- [How GoodDollar works](https://docs.gooddollar.org/how-gooddollar-works) + +## User guides + +- [Buy and Sell G$](https://docs.gooddollar.org/user-guides) (reserve-backed buy/sell; includes historical Ethereum/Kovan explorer workflows in the doc) +- [Bridge GoodDollars](https://docs.gooddollar.org/user-guides/bridge-gooddollars) (MessagePassingBridge, fees, limits, troubleshooting) +- [Connect another wallet address to identity](https://docs.gooddollar.org/user-guides/connect-another-wallet-address-to-identity) + +## Developers + +- [Core contracts](https://docs.gooddollar.org/for-developers/core-contracts) (module overview; **do not** read contract addresses from GoodDocs—use [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) only) +- [Developer guides index](https://docs.gooddollar.org/for-developers/developer-guides) +- [Integrate the G$ token](https://docs.gooddollar.org/for-developers/developer-guides/how-to-integrate-the-gusd-token) (ERC-677, ERC-777, decimals by chain, `transferAndCall`, fees) +- [Use G$ streaming](https://docs.gooddollar.org/for-developers/developer-guides/use-gusd-streaming) (Superfluid on Celo, CFAv1Forwarder) + +## Chain IDs (bridge doc) + +| Network | Chain ID | +| -------- | -------- | +| Ethereum | 1 | +| Fuse | 122 | +| Celo | 42220 | +| XDC | 50 | + +## This repo + +- Action playbooks: `references/guides/*.md`. +- Rich ABIs: `references/contracts/*.abi.yaml`. diff --git a/.agents/skills/gooddollar/references/guides/goodsdks.md b/.agents/skills/gooddollar/references/guides/goodsdks.md new file mode 100644 index 00000000..8092e407 --- /dev/null +++ b/.agents/skills/gooddollar/references/guides/goodsdks.md @@ -0,0 +1,110 @@ +# GoodSDKs integration guide + +Use this guide when the task is SDK-first (app integration), not raw contract-first. + +## Scope + +GoodSDKs is the app integration layer for GoodDollar: + +- `@goodsdks/citizen-sdk` for identity and claim flows. +- `@goodsdks/react-hooks` for Wagmi React hooks. +- `@goodsdks/good-reserve` for reserve buy or sell flows. +- `@goodsdks/engagement-sdk` for engagement rewards flows. +- `@goodsdks/ui-components` and `@goodsdks/savings-widget` for web components. + +## Routing map + +- Check whitelist, identity root, FV link -> `@goodsdks/citizen-sdk` (`IdentitySDK`) +- Claim UBI with entitlement checks and fallback chains -> `@goodsdks/citizen-sdk` (`ClaimSDK`) +- React app with Wagmi and minimal glue code -> `@goodsdks/react-hooks` +- Buy or sell via reserve rails (Celo or XDC support rules) -> `@goodsdks/good-reserve` +- Reward app registration, claims, reward history -> `@goodsdks/engagement-sdk` +- Embeddable UI in non-React or mixed stacks -> `@goodsdks/ui-components` or `@goodsdks/savings-widget` + +## Deterministic setup + +Monorepo prerequisites: + +```bash +cd ~/Projects/GoodSDKs +corepack enable +yarn install --immutable +yarn build +``` + +Target one workspace: + +```bash +yarn workspace @goodsdks/citizen-sdk build +yarn workspace @goodsdks/react-hooks build +``` + +## Deterministic usage snippets + +Identity SDK: + +```ts +import { createPublicClient, createWalletClient, custom, http } from "viem"; +import { IdentitySDK } from "@goodsdks/citizen-sdk"; + +const publicClient = createPublicClient({ transport: http("https://forno.celo.org") }); +const walletClient = createWalletClient({ transport: custom(window.ethereum) }); + +const identitySDK = await IdentitySDK.init({ + publicClient, + walletClient, + env: "production", +}); + +const { isWhitelisted, root } = await identitySDK.getWhitelistedRoot("0xYourAccount"); +console.log({ isWhitelisted, root }); +``` + +Claim SDK: + +```ts +import { ClaimSDK, IdentitySDK } from "@goodsdks/citizen-sdk"; + +const identitySDK = await IdentitySDK.init({ publicClient, walletClient, env: "production" }); +const claimSDK = await ClaimSDK.init({ + publicClient, + walletClient, + identitySDK, + env: "production", +}); + +const entitlement = await claimSDK.checkEntitlement(); +if (entitlement.amount > 0n) { + const receipt = await claimSDK.claim(); + console.log(receipt.transactionHash); +} +``` + +React hooks: + +```tsx +import { useIdentitySDK, useClaimSDK, useGoodReserve } from "@goodsdks/react-hooks"; + +const identity = useIdentitySDK("production"); +const claim = useClaimSDK("production"); +const reserve = useGoodReserve("production"); +``` + +Reserve SDK: + +```ts +import { GoodReserveSDK } from "@goodsdks/good-reserve"; + +const sdk = new GoodReserveSDK(publicClient, walletClient, "production"); +const quote = await sdk.getBuyQuote(CUSD_ADDRESS, amountIn); +const tx = await sdk.buy(CUSD_ADDRESS, amountIn, (quote * 95n) / 100n); +console.log(tx.hash); +``` + +## Agent rules + +1. Prefer SDK methods first for app tasks. +2. Use contract-level guides only when SDK does not expose required behavior. +3. Do not invent SDK method names; align with package READMEs and exported types. +4. For chain support errors, report chain and env explicitly (do not silently fallback). +5. For UI tasks, prefer hooks or components over bespoke wallet and viem plumbing. diff --git a/.agents/skills/gooddollar/references/guides/hypersync-hyperrpc.md b/.agents/skills/gooddollar/references/guides/hypersync-hyperrpc.md new file mode 100644 index 00000000..e7b6f286 --- /dev/null +++ b/.agents/skills/gooddollar/references/guides/hypersync-hyperrpc.md @@ -0,0 +1,110 @@ +# Envio HyperSync and HyperRPC + +Use this guide when the task is high-volume historical blockchain data fetch (events, blocks, txs), especially analytics and indexing workflows. + +## Official docs + +- HyperSync overview: [docs.envio.dev/docs/HyperSync/overview](https://docs.envio.dev/docs/HyperSync/overview) +- HyperRPC overview: [docs.envio.dev/docs/HyperRPC/overview-hyperrpc](https://docs.envio.dev/docs/HyperRPC/overview-hyperrpc) +- HyperRPC supported networks: [docs.envio.dev/docs/HyperRPC/hyperrpc-supported-networks](https://docs.envio.dev/docs/HyperRPC/hyperrpc-supported-networks) + +## What to use + +- **HyperSync**: preferred for new data pipelines and heavy historical scans. +- **HyperRPC**: read-only JSON-RPC drop-in for existing RPC code paths. + +## HyperRPC vs HyperSync (avoid mixing them up) + +- **HyperRPC** is a **hosted JSON-RPC URL** (same methods as `eth_getLogs`, `eth_blockNumber`, and so on). Any HTTP client or existing RPC stack can call it; put the API token in the URL path as Envio documents. +- **HyperSync** is a **separate high-throughput query API** used through **Envio client libraries** (for example `@envio-dev/hypersync-client` in Node). It is **not** “just another RPC endpoint” with the same ergonomics as a one-line `fetch` to `eth_getLogs` at large scale. + +## Decision rule + +1. For GoodDollar protocol history that exists on subgraphs, query the subgraph first and validate fields in `references/subgraphs/*-guide.md`. +2. If subgraph schema or freshness cannot satisfy the request, use **HyperSync** for large scans and pipelines, or **HyperRPC** when you must stay inside standard JSON-RPC. +3. For write operations (sending tx), use normal RPC providers; HyperRPC is read-only. + +## GoodDollar-relevant network coverage + +- Celo and XDC are supported on HyperRPC. +- Fuse is not currently listed; treat this as non-blocking and use existing providers for Fuse. + +## Access and auth + +- HyperRPC/HyperSync usage is account-based. +- HyperRPC requires an API key for reliable production use. +- Requests without API token are rate-limited and should be treated as non-production fallback only. +- Add API key in endpoint URL as documented by Envio. +- HyperRPC token pattern example from docs: `https://.rpc.hypersync.xyz/` + +## Agents: Envio API token when HyperSync is the best option + +After you decide **HyperSync** is the right tool for the user query (for example large historical scans or pipeline-scale log pulls where subgraphs are insufficient), check for a usable Envio credential in the execution environment (`ENVIO_API_TOKEN` for `@envio-dev/hypersync-client`, or the token Envio documents for your chosen URL pattern). + +If **no** Envio API token is available and you cannot complete the HyperSync path without it, **stop and explicitly ask the user** to provide an Envio API token (name the env var you need, typically `ENVIO_API_TOKEN`). Do not silently rely on anonymous or heavily rate-limited access as a substitute when HyperSync was already identified as the best approach. + +## Practical use in this repo + +- Keep subgraphs as first option for indexed protocol entities. +- Use HyperSync/HyperRPC when subgraph coverage is missing, stale, or insufficient for bulk historical pulls. +- When an agent chooses **HyperSync** as the best path and no Envio API token is available, follow **Agents: Envio API token when HyperSync is the best option** in this file and ask the user for `ENVIO_API_TOKEN` before proceeding. +- Keep contract addresses from [GoodProtocol/deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) only; use GoodDocs for product context, not for resolving contract addresses. +- For implementation details (client setup, query structure, supported methods), follow the Envio docs links above directly. + +## From block for historical fetches + +For **`eth_getLogs`**, HyperRPC, and HyperSync range queries, the lower bound is **`fromBlock`** (or the client’s equivalent). Prefer the deployment’s **`creationBlock`** from the matching row in `references/contracts/*.abi.yaml` (or **`meta.deploymentCreationBlocks`** where deployments are plain address strings) so scans do not start at genesis when you only need post-deploy history. Field placement for **`creationBlock`** is defined in `references/contracts/_rich-abi-yaml-format.md`. If you cannot determine the creation block, **`fromBlock` 0** is valid. + +## Prebuilt scripts (developers and local agents) + +These scripts avoid rediscovering HyperRPC wiring on every task. They require **Node.js 18 or newer** (global `fetch`). Paths like `scripts/...` are relative to the **GoodSkills repository root** (the directory that contains both `skills/` and `scripts/`), not relative to `skills/gooddollar/` alone. + +### Last N Identity `WhitelistedAdded` logs via HyperRPC + +- Script: `scripts/fetch-whitelist-events-hyperrpc.mjs` +- Default `EVENT_TOPIC0` matches `WhitelistedAdded(address)` on `IdentityV4`; override `EVENT_TOPIC0` for other events. +- Production Celo defaults: `CONTRACT_ADDRESS` defaults to `Identity` from `production-celo` in [GoodProtocol deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) (`0xC361A6E67822a0EDc17D899227dd9FC50BD62F42`). If `HYPERRPC_URL` is unset, the script builds `https://celo.rpc.hypersync.xyz/` from `HYPERRPC_API_TOKEN` or `ENVIO_API_TOKEN`. +- Optional env: `HYPERRPC_URL` (overrides token-based default), `CONTRACT_ADDRESS`, `LIMIT` (default `500`), `STEP` (default `2000`), `FROM_BLOCK` (decimal; omit to read **`creationBlock`** for `CONTRACT_ADDRESS` from `ABI_PATH` or the default `skills/gooddollar/references/contracts/IdentityV4.abi.yaml`), `ABI_PATH`, `TO_BLOCK` (default `latest`). **`fromBlock`** behavior is described in **From block for historical fetches** above. + +```bash +cd /path/to/GoodSkills +export HYPERRPC_API_TOKEN='' +node scripts/fetch-whitelist-events-hyperrpc.mjs +``` + +Web-only assistants without a shell cannot run the file; they should return the same env keys and command text so the user runs it locally. + +## HyperSync client minimal path (install required) + +HyperSync uses the official client. Install and query pattern (Celo example URLs from [Envio Celo docs](https://docs.envio.dev/docs/HyperIndex/celo)): + +```bash +npm install @envio-dev/hypersync-client +export ENVIO_API_TOKEN='' +``` + +Save as a `.mjs` file (or use `"type": "module"` in a local `package.json`) and run with `node`: + +```javascript +import { HypersyncClient, presetQueryLogsOfEvent } from "@envio-dev/hypersync-client"; + +const client = new HypersyncClient({ + url: "https://celo.hypersync.xyz", + apiToken: process.env.ENVIO_API_TOKEN, +}); + +const identity = "0x..."; +const whitelistedAddedTopic0 = + "0xee1504a83b6d4a361f4c1dc78ab59bfa30d6a3b6612c403e86bb01ef2984295f"; + +const fromBlock = 17237952; +const toBlock = await client.getHeight(); + +const query = presetQueryLogsOfEvent(identity, whitelistedAddedTopic0, fromBlock, toBlock); +const res = await client.get(query); +console.log(res.data.logs.length); +``` + +The example **`fromBlock`** matches **`creationBlock`** for production Celo Identity in `skills/gooddollar/references/contracts/IdentityV4.abi.yaml`; see **From block for historical fetches** above. + +Full API and streaming patterns: [HyperSync clients](https://docs.envio.dev/docs/HyperSync/hypersync-clients) and the package README for `@envio-dev/hypersync-client`. diff --git a/.agents/skills/gooddollar/references/guides/invite-bounties.md b/.agents/skills/gooddollar/references/guides/invite-bounties.md new file mode 100644 index 00000000..d60aafdd --- /dev/null +++ b/.agents/skills/gooddollar/references/guides/invite-bounties.md @@ -0,0 +1,115 @@ +# Invite bounties guide + +Use when the task is to verify or execute inviter-invitee bounty payout flow and explain why payout is blocked. + +## Goal + +Check eligibility deterministically, execute payout only when eligible, and return exact failure reason when not eligible. + +## Required inputs + +- target chain +- `InvitesV2` address +- `Identity` address +- optional `UBISchemeV2` address when claims threshold is active +- invitee address +- inviter address +- rpc url and signer + +## Execution flow + +1. Resolve contract addresses from `deployment.json`. +2. Read invitee state from `users(invitee)` and global thresholds (`minimumClaims`, `minimumDays`, `active`). +3. Check current eligibility with `canCollectBountyFor(invitee)`. +4. If not eligible, read identity whitelist for invitee and inviter and return concrete blocker. +5. If eligible, execute `bountyFor(invitee)` or `collectBounties()` and return tx hash plus payout values from events. + +## Deterministic snippet + +```js +import { ethers } from "ethers"; + +const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); +const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); + +const invites = new ethers.Contract( + process.env.INVITES_ADDRESS, + [ + "function canCollectBountyFor(address) view returns (bool)", + "function bountyFor(address)", + "function users(address) view returns (bytes32,address,uint40,uint24,bool,uint256)", + "function minimumClaims() view returns (uint256)", + "function minimumDays() view returns (uint256)", + "function active() view returns (bool)", + "event InviterBounty(address indexed inviter,address indexed invitee,uint256 bountyPaid,uint256 inviterLevel,bool earnedLevel)" + ], + signer, +); + +const identity = new ethers.Contract( + process.env.IDENTITY_ADDRESS, + [ + "function isWhitelisted(address) view returns (bool)" + ], + provider, +); + +const invitee = process.env.INVITEE_ADDRESS; +const inviter = process.env.INVITER_ADDRESS; + +const [isActive, eligible, inviteeWhitelisted, inviterWhitelisted, minClaims, minDays] = await Promise.all([ + invites.active(), + invites.canCollectBountyFor(invitee), + identity.isWhitelisted(invitee), + identity.isWhitelisted(inviter), + invites.minimumClaims(), + invites.minimumDays(), +]); + +if (!isActive) throw new Error("Invites contract is inactive"); +if (!eligible) { + throw new Error( + `Not eligible. inviteeWhitelisted=${inviteeWhitelisted} inviterWhitelisted=${inviterWhitelisted} minimumClaims=${minClaims} minimumDays=${minDays}`, + ); +} + +const tx = await invites.bountyFor(invitee); +const receipt = await tx.wait(); +const bountyEvent = receipt.logs + .map((log) => { + try { + return invites.interface.parseLog(log); + } catch { + return null; + } + }) + .find((e) => e && e.name === "InviterBounty"); + +console.log( + JSON.stringify( + { + txHash: receipt.hash, + invitee, + inviter, + bountyPaid: bountyEvent?.args?.bountyPaid?.toString() ?? null, + }, + null, + 2, + ), +); +``` + +## Failure handling + +- invitee or inviter is not currently whitelisted +- reverification is due and whitelist check fails until re-authentication +- minimum claims or minimum days is not met +- bounty already paid or bounty-at-join is zero +- contract inactive or wrong deployment addresses + +## Output contract + +- network and addresses used +- eligibility status and blockers +- tx hash when sent +- payout values when available from logs diff --git a/.agents/skills/gooddollar/references/guides/migrate-fuse-staking-to-celo-savings.md b/.agents/skills/gooddollar/references/guides/migrate-fuse-staking-to-celo-savings.md new file mode 100644 index 00000000..4ad9fd26 --- /dev/null +++ b/.agents/skills/gooddollar/references/guides/migrate-fuse-staking-to-celo-savings.md @@ -0,0 +1,116 @@ +# Fuse to CELO staking migration guide + +Use when the user wants to migrate an existing Fuse governance stake into a CELO destination savings flow. In this flow, Fuse `GovernanceStakingV2` is the old staking contract (source) and **`GooddollarSavingsStream`** on Celo is the destination savings contract ([`GooddollarSavingsStream.sol`](https://github.com/Ubeswap/gooddollar-contracts/blob/main/contracts/GooddollarSavingsStream.sol), [CeloScan](https://celoscan.io/address/0x059ee811414230d1Fb157878D2b491240F4D8d3B)). + +## Goal + +Close a user stake on Fuse, bridge the resulting G$ to CELO, and stake on CELO for that user in a controlled backend flow. + +## Required inputs + +- user address on Fuse and corresponding destination address on CELO +- Fuse `GovernanceStakingV2` address (`production.GovernanceStakingV2` in [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json)) +- Fuse G$ token address and bridge contract address (from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json): `GoodDollar`, `MpbBridge` under `production`) +- CELO G$ token address (from `production-celo` in the same file) and destination savings contract address +- backend signer or service wallet with required execution permissions +- chain RPC URLs for Fuse and CELO + +## Address resolution quick table + +| Purpose | Network | Source key/path | Value | +|---|---|---|---| +| Governance staking (source close) | Fuse (`production`, `networkId: 122`) | `deployment.json` -> `production.GovernanceStakingV2` | `0xB7C3e738224625289C573c54d402E9Be46205546` | +| Governance staking (previous) | Fuse (`production`, `networkId: 122`) | `deployment.json` -> `production.GovernanceStaking` | `0xFAF457Fb4A978Be059506F6CD41f9B30fCa753b0` | +| Fuse G$ token | Fuse (`production`, `networkId: 122`) | `deployment.json` -> `production.GoodDollar` | `0x495d133B938596C9984d462F007B676bDc57eCEC` | +| Fuse bridge | Fuse (`production`, `networkId: 122`) | `deployment.json` -> `production.MpbBridge` | `0xa3247276DbCC76Dd7705273f766eB3E8a5ecF4a5` | +| Destination savings | CELO (`networkId: 42220`) | [CeloScan](https://celoscan.io/address/0x059ee811414230d1Fb157878D2b491240F4D8d3B) / `GooddollarSavingsStream` | `0x059ee811414230d1Fb157878D2b491240F4D8d3B` (`process.env.CELO_SAVINGS`) | + +Canonical sources: + +- [GoodProtocol deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) +- [Fuse explorer contract (GovernanceStakingV2 address)](https://explorer.fuse.io/address/0xB7C3e738224625289C573c54d402E9Be46205546?tab=contract) +- [Ubeswap gooddollar-contracts](https://github.com/Ubeswap/gooddollar-contracts) +- [GoodBridge bridge helper normalization](https://github.com/GoodDollar/GoodBridge/blob/master/packages/bridge-contracts/contracts/messagePassingBridge/BridgeHelperLibrary.sol) + +## Token decimals and `MessagePassingBridge` LZ fees + +Production deployments checked on-chain: Fuse `GoodDollar` uses **2** decimals and Celo `GoodDollar` uses **18**. Fuse `GovernanceStakingV2` (sG$) uses **2** decimals. Resolve `decimals()` from each live token in your runner so you stay correct if deployments change. + +On the Fuse `MpbBridge` (`MessagePassingBridge`), `canBridge(from, amount)` and `bridgeToWithLz(..., amount, ...)` use the **raw G$ burn amount** in source token decimals. Off-chain **`estimateSendFee`’s `_normalizedAmount` argument** must match what the contract builds internally: **`normalizeFromTokenTo18Decimals(amount, IERC20(nativeToken()).decimals())`** ([`BridgeHelperLibrary`](https://github.com/GoodDollar/GoodBridge/blob/master/packages/bridge-contracts/contracts/messagePassingBridge/BridgeHelperLibrary.sol)). Format displayed amounts per chain (`formatUnits`/UI) using each chain’s G$ decimals. + +## Execution flow + +1. Confirm allowance for whichever asset your flow spends first (for example sG$ allowance to the backend on Fuse `GovernanceStakingV2`, or Fuse G$ allowance if you pull G$ directly), before any transfers or bridges. +2. Verify current stake state on Fuse before closing: + - stake token balance + - withdrawable stake amount + - pending rewards if any +3. Execute Fuse unstake or close flow on governance staking (`withdrawStake` or equivalent full-close path). +4. Compute net G$ available for migration after unstake completion and any reward claim behavior. +5. Bridge G$ from Fuse to CELO using the configured bridge path and track the transfer id or tx hash pair. +6. Wait for destination finalization on CELO and verify credited G$ balance at the backend execution wallet. +7. Approve destination savings contract to spend migrated G$ amount. +8. Stake for the user on CELO with `stakeFor(amount, recipient)` on `GooddollarSavingsStream` (G$ native Super Token; approve the savings contract, not only ERC20 GoodDollar from `deployment.json` if your wallet holds the Super Token). +9. Return a migration result with both chain tx hashes and final CELO staked amount. + +## Deterministic snippet + +```js +import { ethers } from "ethers"; + +const fuse = new ethers.JsonRpcProvider(process.env.FUSE_RPC_URL); +const celo = new ethers.JsonRpcProvider(process.env.CELO_RPC_URL); +const signerFuse = new ethers.Wallet(process.env.BACKEND_PK, fuse); +const signerCelo = new ethers.Wallet(process.env.BACKEND_PK, celo); + +const user = process.env.USER_ADDRESS; +const migrateAmount = BigInt(process.env.MIGRATE_AMOUNT); + +const celoGd = new ethers.Contract( + process.env.CELO_GD_TOKEN, + [ + "function approve(address spender,uint256 amount) returns (bool)", + "function balanceOf(address) view returns (uint256)", + ], + signerCelo, +); + +const savings = new ethers.Contract( + process.env.CELO_SAVINGS, + ["function stakeFor(uint256 amount,address recipient)"], + signerCelo, +); + +const approveTx = await celoGd.approve(process.env.CELO_SAVINGS, migrateAmount); +await approveTx.wait(); + +const stakeTx = await savings.stakeFor(migrateAmount, user); +const receipt = await stakeTx.wait(); + +console.log( + JSON.stringify( + { + user, + celoStakeTx: receipt.hash, + migratedAmount: migrateAmount.toString(), + }, + null, + 2, + ), +); +``` + +## Pre-check failures + +- User allowance missing on Fuse: stop and request allowance tx from user. +- Stake close fails on Fuse: stop and return exact revert reason before bridge. +- Bridge transfer not finalized on CELO: do not call `stakeFor` until destination balance is confirmed. +- CELO savings approval missing or too low: re-approve exact amount before staking. + +## Output contract + +- user address +- Fuse unstake tx hash +- bridge tx hash or transfer identifier +- CELO stake tx hash +- final staked amount on CELO diff --git a/.agents/skills/gooddollar/references/guides/on-off-ramp.md b/.agents/skills/gooddollar/references/guides/on-off-ramp.md new file mode 100644 index 00000000..d2127051 --- /dev/null +++ b/.agents/skills/gooddollar/references/guides/on-off-ramp.md @@ -0,0 +1,77 @@ +# On- and off-ramp service guide + +Use when implementing service flow where fiat ramps support a listed stable token (for example cUSD), and the app needs stable <-> G$ swap on-chain. + +## Goal + +Operate deterministic clone-based swap routing with explicit chain/factory verification and slippage guard. + +## Required inputs + +- target chain and factory address +- owner address used for clone derivation +- stable token and G$ token addresses +- direction: on-ramp or off-ramp +- `minAmount` guard +- signer and rpc url + +## Execution flow + +1. Resolve factory for target chain from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) (`BuyGDFactory` / `BuyGDFactoryV2` under `production-celo`, or the key your deployment uses). +2. Compute expected clone via `predict(owner)`. +3. If clone not yet deployed for flow, call `create(owner)` or `createAndSwap(owner, minAmount)`. +4. Execute stable -> G$ (on-ramp) or G$ -> stable (off-ramp) through clone path. +5. Return chain id, factory, predicted clone, effective clone, tx hashes. + +## Deterministic snippet + +```js +import { ethers } from "ethers"; + +const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); +const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); + +const factory = new ethers.Contract( + process.env.BUY_GD_CLONE_FACTORY, + [ + "function predict(address) view returns (address)", + "function create(address) returns (address)", + "function createAndSwap(address,uint256) returns (address)", + ], + signer, +); + +const owner = process.env.OWNER; +const minAmount = ethers.parseUnits(process.env.MIN_AMOUNT, Number(process.env.DECIMALS_OUT)); +const predicted = await factory.predict(owner); + +const tx = await factory.createAndSwap(owner, minAmount); +const receipt = await tx.wait(); + +console.log( + JSON.stringify( + { + txHash: receipt.hash, + owner, + predictedClone: predicted, + chainId: (await provider.getNetwork()).chainId.toString(), + }, + null, + 2, + ), +); +``` + +## Failure handling + +- predicted clone mismatch with trusted expectation +- wrong chain or wrong factory address +- swap output below `minAmount` +- stale router/oracle or exchange configuration + +## Output contract + +- network and chain id +- factory address +- predicted and actual clone addresses +- tx hashes diff --git a/.agents/skills/gooddollar/references/guides/save.md b/.agents/skills/gooddollar/references/guides/save.md new file mode 100644 index 00000000..d0e716cb --- /dev/null +++ b/.agents/skills/gooddollar/references/guides/save.md @@ -0,0 +1,93 @@ +# Save and stake guide + +Use when the user wants to stake G$, withdraw rewards, or exit stake. Staking economics sit alongside other protocol allocations described in [How GoodDollar works](https://docs.gooddollar.org/how-gooddollar-works). + +## GoodDocs alignment + +- Token integration and fee awareness: [How to integrate the G$ token](https://docs.gooddollar.org/for-developers/developer-guides/how-to-integrate-the-gusd-token) (`_processFees`, decimals per chain). +- Contract addresses: [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) only (for example staking and G$ token keys under `production` / `production-celo`). GoodDocs covers behavior and decimals patterns, not canonical deployment addresses. + +## Goal + +Run staking actions with balance and allowance safety checks. + +## Required inputs + +- `nameServiceAddress` or explicit staking and token addresses +- `amount` or `shares` depending on the action +- `rpcUrl`, chain configuration, signer + +## Execution flow + +1. Resolve staking and G$ token addresses from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) or, when the deployment documents the key, from `NameService.getAddress` on chain. +2. Read token balance and allowance. +3. Approve the staking contract when `stake` uses `transferFrom`. +4. Execute `stake`, `withdrawRewards`, or `withdrawStake` as requested. +5. Return tx hash and key resulting balances or events. + +## Deterministic snippets + +```js +import { ethers } from "ethers"; + +const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); +const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); + +const token = new ethers.Contract( + process.env.GOODDOLLAR_ADDRESS, + [ + "function balanceOf(address) view returns (uint256)", + "function allowance(address,address) view returns (uint256)", + "function approve(address,uint256) returns (bool)", + ], + signer, +); + +const staking = new ethers.Contract( + process.env.STAKING_ADDRESS, + [ + "function stake(uint256)", + "function withdrawRewards()", + "function withdrawStake(uint256)", + ], + signer, +); +``` + +Stake: + +```js +const amount = ethers.parseUnits(process.env.AMOUNT, Number(process.env.DECIMALS)); +const owner = await signer.getAddress(); +const allowance = await token.allowance(owner, process.env.STAKING_ADDRESS); +if (allowance < amount) { + const approveTx = await token.approve(process.env.STAKING_ADDRESS, amount); + await approveTx.wait(); +} +const tx = await staking.stake(amount); +const receipt = await tx.wait(); +console.log(JSON.stringify({ txHash: receipt.hash, action: "stake" }, null, 2)); +``` + +Withdraw rewards: + +```js +const tx = await staking.withdrawRewards(); +const receipt = await tx.wait(); +console.log(JSON.stringify({ txHash: receipt.hash, action: "withdrawRewards" }, null, 2)); +``` + +Withdraw stake: + +```js +const shares = ethers.parseUnits(process.env.SHARES, Number(process.env.DECIMALS)); +const tx = await staking.withdrawStake(shares); +const receipt = await tx.wait(); +console.log(JSON.stringify({ txHash: receipt.hash, action: "withdrawStake" }, null, 2)); +``` + +## Failure handling + +- Insufficient balance: report shortfall. +- Approval issues: report token, spender, and required allowance. +- Reverts: return attempted function and parameters without guessing custom errors. diff --git a/.agents/skills/gooddollar/references/guides/stream.md b/.agents/skills/gooddollar/references/guides/stream.md new file mode 100644 index 00000000..6cbf7a45 --- /dev/null +++ b/.agents/skills/gooddollar/references/guides/stream.md @@ -0,0 +1,135 @@ +# Stream guide + +Primary references for stream execution are local ABI assets in this repo: + +- `references/contracts/CFAv1Forwarder.abi.yaml` +- `references/contracts/ConstantFlowAgreementV1.abi.yaml` +- `references/contracts/Superfluid.abi.yaml` +- `references/contracts/SuperToken.abi.yaml` + +## Goal + +Create, update, or delete Superfluid constant flows using deterministic contract calls and local ABI references. + +## Protocol facts used by this guide + +- Forwarder path uses `CFAv1Forwarder.createFlow`, `updateFlow`, `deleteFlow`. +- Host path uses `Superfluid.callAgreement` with CFA calldata for `createFlow`, `updateFlow`, `deleteFlow`. +- Stream token is a SuperToken; flow rates are `int96` in token-wei per second. +- `getBufferAmountByFlowrate(token, flowRate)` is the canonical pre-check for required buffer. + +## Two implementation styles in this repo + +1. **Forwarder (matches GoodDocs):** call CFAv1Forwarder with token, sender, receiver, flowRate, userData. +2. **Host callAgreement:** encode CFA `createFlow` / `updateFlow` / `deleteFlow` and call `Superfluid.callAgreement`. + +## Minimal method map + +- Forwarder: + - `createFlow(address token, address receiver, int96 flowrate, bytes userData)` + - `updateFlow(address token, address receiver, int96 flowrate, bytes userData)` + - `deleteFlow(address token, address sender, address receiver, bytes userData)` + - `getBufferAmountByFlowrate(address token, int96 flowrate)` +- Host: + - `callAgreement(address agreementClass, bytes callData, bytes userData)` +- CFA: + - `createFlow(address token, address receiver, int96 flowRate, bytes ctx)` + - `updateFlow(address token, address receiver, int96 flowRate, bytes ctx)` + - `deleteFlow(address token, address sender, address receiver, bytes ctx)` + +## Required inputs + +- G$ Super Token address for the environment +- CFA forwarder address, or Superfluid host address plus CFA agreement address +- `action`: create, update, delete +- `receiver`, `flowRate` where applicable +- `rpcUrl`, chain configuration, signer + +## Deterministic snippet + +```js +import { ethers } from "ethers"; + +const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); +const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); + +const forwarder = new ethers.Contract( + process.env.CFA_FORWARDER, + [ + "function createFlow(address,address,int96,bytes)", + "function updateFlow(address,address,int96,bytes)", + "function deleteFlow(address,address,address,bytes)", + ], + signer, +); + +const token = process.env.SUPER_TOKEN; +const sender = await signer.getAddress(); +const receiver = process.env.RECEIVER; +const flowRate = BigInt(process.env.FLOW_RATE); + +if (process.env.ACTION === "create") { + const tx = await forwarder.createFlow(token, receiver, flowRate, "0x"); + const receipt = await tx.wait(); + console.log(JSON.stringify({ txHash: receipt.hash, action: "create" }, null, 2)); +} + +if (process.env.ACTION === "update") { + const tx = await forwarder.updateFlow(token, receiver, flowRate, "0x"); + const receipt = await tx.wait(); + console.log(JSON.stringify({ txHash: receipt.hash, action: "update" }, null, 2)); +} + +if (process.env.ACTION === "delete") { + const tx = await forwarder.deleteFlow(token, sender, receiver, "0x"); + const receipt = await tx.wait(); + console.log(JSON.stringify({ txHash: receipt.hash, action: "delete" }, null, 2)); +} +``` + +Host callAgreement example: + +```js +import { ethers } from "ethers"; + +const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); +const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); + +const cfa = new ethers.Interface([ + "function createFlow(address,address,int96,bytes)", + "function updateFlow(address,address,int96,bytes)", + "function deleteFlow(address,address,address,bytes)", +]); + +const host = new ethers.Contract( + process.env.SUPERFLUID_HOST, + ["function callAgreement(address,bytes,bytes) returns (bytes)"], + signer, +); + +const token = process.env.SUPER_TOKEN; +const sender = await signer.getAddress(); +const receiver = process.env.RECEIVER; +const flowRate = BigInt(process.env.FLOW_RATE); + +let callData = "0x"; +if (process.env.ACTION === "create") { + callData = cfa.encodeFunctionData("createFlow", [token, receiver, flowRate, "0x"]); +} +if (process.env.ACTION === "update") { + callData = cfa.encodeFunctionData("updateFlow", [token, receiver, flowRate, "0x"]); +} +if (process.env.ACTION === "delete") { + callData = cfa.encodeFunctionData("deleteFlow", [token, sender, receiver, "0x"]); +} + +const tx = await host.callAgreement(process.env.CFA_ADDRESS, callData, "0x"); +const receipt = await tx.wait(); +console.log(JSON.stringify({ txHash: receipt.hash, action: process.env.ACTION }, null, 2)); +``` + +## Failure handling + +- Wrong network or missing addresses: stop and return missing host or forwarder or token addresses. +- Insufficient buffer: use `getBufferAmountByFlowrate` and reduce flow rate or top up balance. +- Revert on create or update: verify token is a SuperToken and flowRate is positive. diff --git a/.agents/skills/gooddollar/references/guides/swap.md b/.agents/skills/gooddollar/references/guides/swap.md new file mode 100644 index 00000000..3db8c2dc --- /dev/null +++ b/.agents/skills/gooddollar/references/guides/swap.md @@ -0,0 +1,83 @@ +# Swap guide + +Use for buying or selling G$ through Mento-connected contracts on networks where they appear in [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) (for example `MentoBroker`, `MentoReserve`, `MentoExchangeProvider`, `MentoExpansionController` keys under `production-celo` or `production-xdc`). GoodDocs describes Mento product behavior, not deployment addresses. + +## GoodDocs alignment + +- Reserve and buy or sell mechanics at the protocol level: [How GoodDollar works](https://docs.gooddollar.org/how-gooddollar-works) and [Buy and Sell G$ user guide](https://docs.gooddollar.org/user-guides) (includes reserve AMM narrative; older explorer step-by-step for Ethereum testnets remains in that page for reference). +- Integration patterns and decimals: [How to integrate the G$ token](https://docs.gooddollar.org/for-developers/developer-guides/how-to-integrate-the-gusd-token). + +## Goal + +Execute bounded swaps using broker quotes and correct allowances. + +## Required inputs + +- `direction` as buy or sell +- broker and exchange identifiers for the deployment +- amounts in correct token decimals for the chain +- `rpcUrl`, chain configuration, signer + +## Execution flow + +1. Confirm Mento Broker (and related) addresses for the chain from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) only. +2. Fetch quote (`getAmountOut` or `getAmountIn` depending on direction and ABI). +3. Apply slippage bounds. +4. Approve the spent token for the broker when required. +5. Call `swapIn` or `swapOut` per your integration. +6. Return tx hash and effective amounts. + +## Deterministic snippet + +```js +import { ethers } from "ethers"; + +const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); +const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); + +const broker = new ethers.Contract( + process.env.BROKER_ADDRESS, + [ + "function getAmountOut(address,address,uint256) view returns (uint256)", + "function swapIn(address,address,uint256,uint256) returns (uint256)", + ], + signer, +); + +const tokenIn = new ethers.Contract( + process.env.TOKEN_IN, + [ + "function allowance(address,address) view returns (uint256)", + "function approve(address,uint256) returns (bool)", + ], + signer, +); + +const amountIn = ethers.parseUnits(process.env.AMOUNT_IN, Number(process.env.DECIMALS_IN)); +const quotedOut = await broker.getAmountOut(process.env.TOKEN_IN, process.env.TOKEN_OUT, amountIn); +const slippageBps = BigInt(process.env.SLIPPAGE_BPS); +const minOut = quotedOut * (10000n - slippageBps) / 10000n; + +const owner = await signer.getAddress(); +const allowance = await tokenIn.allowance(owner, process.env.BROKER_ADDRESS); +if (allowance < amountIn) { + const approveTx = await tokenIn.approve(process.env.BROKER_ADDRESS, amountIn); + await approveTx.wait(); +} + +const tx = await broker.swapIn(process.env.TOKEN_IN, process.env.TOKEN_OUT, amountIn, minOut); +const receipt = await tx.wait(); +console.log( + JSON.stringify( + { txHash: receipt.hash, amountIn: amountIn.toString(), minOut: minOut.toString() }, + null, + 2, + ), +); +``` + +## Failure handling + +- No deployment on chain: direct the user to an environment that defines the needed keys in [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) (for example `production-celo` with `MentoBroker`). +- Stale quote or tight slippage: refresh quote or relax bounds with user consent. +- Allowance or balance shortfall: report exact delta. diff --git a/.agents/skills/gooddollar/references/subgraphs/_query-patterns.md b/.agents/skills/gooddollar/references/subgraphs/_query-patterns.md new file mode 100644 index 00000000..36af6398 --- /dev/null +++ b/.agents/skills/gooddollar/references/subgraphs/_query-patterns.md @@ -0,0 +1,35 @@ +# Subgraph query discipline (GoodDollar) + +## Subgraph vs RPC + +- **Subgraph:** historical events, lists, aggregates, time ranges, analytics. Data lags chain head. +- **RPC / SDK:** current balances, live `claim` eligibility, exact view calls. Prefer for user-facing “what is true right now”. + +## Generic GraphQL mechanics + +Graph-node generates `entity`, `entities`, `Entity_filter`, `Entity_orderBy`, pagination, and `_meta` from each deployment’s `schema.graphql`. For scalar rules, filters, `_meta`, and common pitfalls, see [The Graph — Querying a subgraph](https://thegraph.com/docs/en/querying/graphql-api/). + +## GoodDollar-specific + +- **Addresses in `where` clauses:** normalize to **lowercase** hex strings; subgraphs store addresses lowercased. +- **BigInt fields:** query as **string** literals in GraphQL JSON (e.g. `"1000000000000000000"`). +- **Schema truth:** entity names differ per deployment. Run introspection or read the deployment’s `schema.graphql` under the relevant package in [GoodDollar/GoodSubGraphs](https://github.com/GoodDollar/GoodSubGraphs) before assuming field names. + +## Meta block + +Use `_meta { block { number } }` to detect how far behind indexing is when debugging stale data. + +```graphql +{ + _meta { + block { + number + } + hasIndexingErrors + } +} +``` + +## When subgraphs are not enough + +If the subgraph cannot answer the question (missing entities or fields, or stale indexing per `_meta`), switch to the decision rules in `references/guides/hypersync-hyperrpc.md`. When **HyperSync** is the best fit and no Envio API token is available in the environment, **ask the user directly** for `ENVIO_API_TOKEN` (or the token your HyperSync client expects) before running large scans; do not silently use anonymous quota as a stand-in. diff --git a/.agents/skills/gooddollar/references/subgraphs/goodcollective-guide.md b/.agents/skills/gooddollar/references/subgraphs/goodcollective-guide.md new file mode 100644 index 00000000..a266ceab --- /dev/null +++ b/.agents/skills/gooddollar/references/subgraphs/goodcollective-guide.md @@ -0,0 +1,54 @@ +# GoodCollective — Subgraph Usage Guide + +Companion to `goodcollective.graphql`. + +## Endpoint + +- Explorer: [GoodCollective](https://thegraph.com/explorer/subgraphs/3LbJh9DXhJVvuVDdm5i6StNboJmL9oMNNkBaKyzc4Y8Y?view=Query&chain=arbitrum-one) +- Gateway form: `https://gateway.thegraph.com/api/subgraphs/id/3LbJh9DXhJVvuVDdm5i6StNboJmL9oMNNkBaKyzc4Y8Y` + +--- + +## Terminology: “claim” here is not daily UBI + +In this subgraph, **Claim** and **ClaimEvent** refer to **GoodCollective reward or pool claim flows**, not the protocol’s **daily UBI claim** from `UBIScheme` / `UBISchemeV2`. + +When a user says **“claim”** in normal GoodDollar product language, they almost always mean **claim daily UBI**. For that, use the GoodDollar Celo subgraph (`walletStats` / claim-related aggregates) and on-chain `claim` per `references/guides/claim.md` — do not answer “last N UBI claims” from GoodCollective **Claim** alone. + +--- + +## Entity Overview + +### Core collective graph + +**Collective** — pool identity, limits/settings links, totals, and claim/payment counters. +**Donor** / **Steward** — participant-level donation/support state. +**DonorCollective** / **StewardCollective** — join entities tying participants to a collective. + +### Claim and support flow + +**Claim** and **ClaimEvent** — GoodCollective **reward** claim lifecycle and per-claim reward events (not daily UBI from UBIScheme). +**SupportEvent** — support/donation change events across donor/collective links. + +### Metadata and policy entities + +**IpfsCollective** — IPFS metadata projection. +**PoolSettings**, **UBILimits**, **SafetyLimits** — pool policy and operational bounds. +**ProvableNFT** — NFT linkage used in claim/reward flows. + +--- + +## Typical Questions This Subgraph Answers + +- Which donors/stewards are attached to a collective? +- How much was donated/rewarded per collective and per participant? +- Which claim events occurred and what reward quantities were emitted? +- What limits and settings govern a specific collective pool? + +--- + +## Query Discipline + +- Use authenticated gateway access for programmatic queries. +- Lowercase address-like identifiers where applicable. +- Validate `_meta` before operational dashboards or reporting exports. diff --git a/.agents/skills/gooddollar/references/subgraphs/goodcollective.graphql b/.agents/skills/gooddollar/references/subgraphs/goodcollective.graphql new file mode 100644 index 00000000..e503db9c --- /dev/null +++ b/.agents/skills/gooddollar/references/subgraphs/goodcollective.graphql @@ -0,0 +1,200 @@ +scalar BigDecimal + +scalar BigInt + +scalar Boolean + +scalar Bytes + +type Claim { + id: String! + collective: Collective! + txHash: String! + networkFee: BigInt! + totalRewards: BigInt! + events: [ClaimEvent!]! + timestamp: Int! +} + +type ClaimEvent { + id: String! + eventType: Int! + timestamp: Int! + quantity: BigInt! + rewardPerContributor: BigInt! + contributors: [Steward!]! + nft: ProvableNFT + claim: Claim! +} + +type Collective { + id: String! + pooltype: PoolType! + ipfs: IpfsCollective + settings: PoolSettings! + ubiLimits: UBILimits + limits: SafetyLimits + donors: [DonorCollective!] + stewards: [StewardCollective!] + projectId: String! + isVerified: Boolean! + poolFactory: String! + timestamp: Int! + paymentsMade: Int! + totalDonations: BigInt! + totalRewards: BigInt! + claims: [Claim!]! +} + +type Donor { + id: String! + timestamp: Int! + totalDonated: BigInt! + collectives: [DonorCollective!]! +} + +""" +Represents the relationship between a Donor and a Collective + +""" + +type DonorCollective { + id: String! + donor: Donor! + collective: Collective! + contribution: BigInt! + flowRate: BigInt! + timestamp: Int! + events: [SupportEvent!]! +} + +scalar Float + +scalar ID + +""" +4 bytes signed integer + +""" + +scalar Int + +""" +8 bytes signed integer + +""" + +scalar Int8 + +type IpfsCollective { + id: String! + name: String! + description: String! + rewardDescription: String + goodidDescription: String + email: String + website: String + twitter: String + instagram: String + threads: String + infoLabel: String + headerImage: String! + logo: String! + images: [String!] +} + +type PoolSettings { + id: String! + nftType: BigInt! + manager: Bytes! + membersValidator: Bytes! + uniquenessValidator: Bytes! + rewardToken: Bytes! +} + +enum PoolType { + DirectPayments + UBI +} + +type ProvableNFT { + id: String! + owner: String! + hash: String! + stewards: [Steward!]! + collective: Collective +} + +type SafetyLimits { + id: String! + maxTotalPerMonth: BigInt! + maxMemberPerMonth: BigInt! + maxMemberPerDay: BigInt! +} + +type Steward { + """ + { user address} + + """ + id: String! + """ + Number of actions performed + + """ + actions: Int! + totalEarned: BigInt! + totalUBIEarned: BigInt! + """ + NFT's minted to steward + + """ + nfts: [ProvableNFT!]! + """ + Collectives the steward is apart of + + """ + collectives: [StewardCollective!]! +} + +""" +Represents the relationship between a Steward and a Collective + +""" + +type StewardCollective { + id: String! + steward: Steward! + collective: Collective! + actions: Int! + totalEarned: BigInt! +} + +scalar String + +type SupportEvent { + id: String! + networkFee: BigInt! + donor: Donor! + collective: Collective! + donorCollective: DonorCollective! + contribution: BigInt! + previousContribution: BigInt! + isFlowUpdate: Boolean! + flowRate: BigInt! + previousFlowRate: BigInt! + timestamp: Int! +} + +scalar Timestamp + +type UBILimits { + id: String! + cycleLengthDays: BigInt! + claimPeriodDays: BigInt! + minActiveUsers: BigInt! + claimForEnabled: Boolean! + maxClaimAmount: BigInt! + maxClaimers: BigInt! + onlyMembers: Boolean! +} diff --git a/.agents/skills/gooddollar/references/subgraphs/gooddollar-celo-guide.md b/.agents/skills/gooddollar/references/subgraphs/gooddollar-celo-guide.md new file mode 100644 index 00000000..347951f4 --- /dev/null +++ b/.agents/skills/gooddollar/references/subgraphs/gooddollar-celo-guide.md @@ -0,0 +1,104 @@ +# GoodDollar Celo — Subgraph Usage Guide + +Companion to `gooddollar-celo.graphql`. + +## Endpoint + +- Explorer: [GoodDollarCelo](https://thegraph.com/explorer/subgraphs/F7314rxGdcpKPC1nN5KCoFW84EGRoUyzseY2sAT9PEkw?view=Query&chain=arbitrum-one) +- Gateway form: `https://gateway.thegraph.com/api/subgraphs/id/F7314rxGdcpKPC1nN5KCoFW84EGRoUyzseY2sAT9PEkw` + +--- + +## Entity Overview + +### UBI and usage statistics + +**DailyUBI** — day-level UBI pool/quota activity and cycle fields. +**WalletStat** — wallet behavior aggregates: tx counts/values, claim stats, active/whitelist indicators. +**TransactionStat** — day-level transaction totals and circulation view. +**GlobalStatistics** — global claim and distribution rollups. + +### Additional UBI history entities + +**UBICollected** — collected UBI/community-pool values by block event. +**UBIHistory** — timeline totals for daily UBI/community-pool. + +--- + +## Field Availability Reference (use before drafting queries) + +Use this section to validate what the subgraph already provides before switching data sources. + +### DailyUBI + +- `id` — day index key (`unix / 86400`) +- `pool` — UBI cycle pool for that day +- `quota` — daily UBI amount per eligible claimer +- `activeUsers` — active users count in scheme context +- `totalUBIDistributed` — amount actually claimed/distributed that day +- `totalClaims` — claim tx count for that day +- `newClaimers` — newly whitelisted users for that day +- `timestamp` — last update timestamp for the record +- `ubiSchemeAddress` — UBIScheme address used for the record +- `balance` — G$ balance held by UBIScheme +- `cycleLength` — current cycle length +- `dayInCycle` — current day position inside cycle + +### WalletStat + +- `id` — wallet address +- `dateAppeared` — first indexed wallet activity timestamp +- `balance` — running token balance from transfers +- `inTransactionsCount`, `inTransactionsValue` — incoming tx count and value +- `outTransactionsCount`, `outTransactionsValue` — outgoing tx count and value +- `totalTransactionsCount`, `totalTransactionsValue` — total tx count and value +- `inTransactionsCountClean`, `inTransactionsValueClean` — incoming metrics excluding contract-address flows +- `outTransactionsCountClean`, `outTransactionsValueClean` — outgoing metrics excluding contract-address flows +- `totalTransactionsCountClean`, `totalTransactionsValueClean` — total clean traffic metrics +- `lastClaimed` — timestamp of latest UBI claim +- `totalClaimedCount`, `totalClaimedValue` — total claims and cumulative claimed value +- `claimStreak`, `longestClaimStreak` — current and best historical streaks +- `isWhitelisted` — current whitelist status +- `isActiveUser` — current active-user status +- `dateJoined` — first-whitelist timestamp +- `lastTransactionFrom`, `lastTransactionTo` — latest outgoing and incoming tx timestamps + +### TransactionStat + +- `id` — bucket key (day key or `"aggregated"`) +- `dayStartBlockNumber` — first block in bucket +- `transactionsCount`, `transactionsValue` — all transfer tx count and value +- `transactionsCountClean`, `transactionsValueClean` — transfer metrics excluding contract-address flows +- `totalInCirculation` — inferred circulating supply from mint or burn behavior + +### GlobalStatistics + +- `id` — fixed key (`"statistics"`) +- `TransactionStat` — link to aggregated transaction stats +- `totalUBIDistributed` — lifetime distributed UBI +- `uniqueClaimers` — tracked unique claimers via whitelist add or remove +- `totalClaims` — lifetime UBI claim events + +### Explorer naming + +- Singular names (`dailyUBI`, `walletStat`) fetch by `id` +- Plural names (`dailyUBIs`, `walletStats`) query lists with filters and pagination + +--- + +## Typical Questions This Subgraph Answers + +- How much UBI was distributed on a day/cycle? +- Which wallets are active or recently claiming? +- What are aggregate tx/circulation trends? +- How did collected UBI/community-pool values evolve over time? + +--- + +## Query Discipline + +- Use lowercase address strings in filters. +- Use string-safe handling for large integer values. +- Use `_meta` to validate freshness before cross-day analytics. +- Use authenticated gateway access for programmatic queries. +- Before claiming a field or entity is missing, verify availability from this guide and schema first. diff --git a/.agents/skills/gooddollar/references/subgraphs/gooddollar-celo.graphql b/.agents/skills/gooddollar/references/subgraphs/gooddollar-celo.graphql new file mode 100644 index 00000000..e544a0dc --- /dev/null +++ b/.agents/skills/gooddollar/references/subgraphs/gooddollar-celo.graphql @@ -0,0 +1,113 @@ +scalar BigDecimal + +scalar BigInt + +scalar Boolean + +scalar Bytes + +type DailyUBI { + id: ID! + pool: BigInt! + quota: BigInt! + activeUsers: BigInt! + totalUBIDistributed: BigInt! + totalClaims: BigInt! + newClaimers: BigInt! + timestamp: BigInt! + ubiSchemeAddress: Bytes + balance: BigInt! + cycleLength: BigInt! + dayInCycle: BigInt! +} + +scalar Float + +type GlobalStatistics { + id: ID! + TransactionStat: TransactionStat + totalUBIDistributed: BigInt! + uniqueClaimers: BigInt! + totalClaims: BigInt! +} + +scalar ID + +""" +4 bytes signed integer +""" + +scalar Int + +""" +8 bytes signed integer +""" + +scalar Int8 + +""" +Defines the order direction, either ascending or descending +""" + +scalar String + +""" +A string representation of microseconds UNIX timestamp (16 digits) +""" + +scalar Timestamp + +type TransactionStat { + id: ID! + dayStartBlockNumber: BigInt! + transactionsCount: BigInt! + transactionsCountClean: BigInt! + transactionsValue: BigInt! + transactionsValueClean: BigInt! + totalInCirculation: BigInt! +} + +type UBICollected { + id: ID! + contract: Bytes + block: BigInt! + blockTimestamp: BigInt! + ubi: BigDecimal! + communityPool: BigDecimal! +} + +type UBIHistory { + id: ID! + block: BigInt! + blockTimestamp: BigInt! + totalDailyUBI: BigDecimal! + totalDailyCommunityPool: BigDecimal! +} + +type WalletStat { + id: ID! + dateAppeared: BigInt! + balance: BigInt! + inTransactionsCount: BigInt! + inTransactionsCountClean: BigInt! + inTransactionsValue: BigInt! + inTransactionsValueClean: BigInt! + outTransactionsCount: BigInt! + outTransactionsCountClean: BigInt! + outTransactionsValue: BigInt! + outTransactionsValueClean: BigInt! + totalTransactionsCount: BigInt! + totalTransactionsCountClean: BigInt! + totalTransactionsValue: BigInt! + totalTransactionsValueClean: BigInt! + lastClaimed: BigInt! + totalClaimedCount: BigInt! + totalClaimedValue: BigInt! + claimStreak: BigInt! + longestClaimStreak: BigInt! + isWhitelisted: Boolean! + isActiveUser: Boolean! + dateJoined: BigInt! + lastTransactionFrom: BigInt! + lastTransactionTo: BigInt! +} diff --git a/.agents/skills/gooddollar/references/subgraphs/reserve-celo-guide.md b/.agents/skills/gooddollar/references/subgraphs/reserve-celo-guide.md new file mode 100644 index 00000000..9f55b734 --- /dev/null +++ b/.agents/skills/gooddollar/references/subgraphs/reserve-celo-guide.md @@ -0,0 +1,41 @@ +# Reserve Celo — Subgraph Usage Guide + +Companion to `reserve-celo.graphql`. This guide is for reserve pricing and broker swap indexing on Celo. + +## Endpoint + +- Goldsky: `https://api.goldsky.com/api/public/project_cmizuamdtfouu01x4csuk5dk1/subgraphs/reserve_celo/1.0/gn` + +--- + +## Entity Overview + +### Core entity + +**ReservePrice** — one indexed reserve pricing point produced from broker swap flow plus exchange-provider price read. +Key fields: `exchangeId`, `exchangeProvider`, `price`, `timestamp`, `day`, `tokenIn`, `tokenOut`, `amountIn`, `amountOut`, `user`, `blockNumber`, `transactionHash`. + +--- + +## Typical Questions This Subgraph Answers + +- What are the most recent reserve prices? +- What was the reserve price on a specific day window? +- Which token pair and user triggered a pricing point? +- Which tx hash/block produced a given price point? + +--- + +## Query Discipline + +- Lowercase all address values in filters. +- Use string values for large integer variables. +- Use `_meta` before analytics queries when stale indexing is suspected. + +--- + +## Practical Start + +1. Check `_meta` block height and `hasIndexingErrors`. +2. Pull latest `ReservePrice` records sorted by `timestamp desc`. +3. Add `day`-based narrowing for historical windows. diff --git a/.agents/skills/gooddollar/references/subgraphs/reserve-celo.graphql b/.agents/skills/gooddollar/references/subgraphs/reserve-celo.graphql new file mode 100644 index 00000000..56cac37b --- /dev/null +++ b/.agents/skills/gooddollar/references/subgraphs/reserve-celo.graphql @@ -0,0 +1,90 @@ +scalar BigDecimal + +scalar BigInt + +scalar Boolean + +scalar Bytes + +scalar Float + +scalar ID + +""" +4 bytes signed integer +""" + +scalar Int + +""" +8 bytes signed integer +""" + +scalar Int8 + +""" +Defines the order direction, either ascending or descending +""" + +type ReservePrice { + """ + Exchange ID combined with timestamp + """ + id: ID! + """ + The exchange ID from the Swap event + """ + exchangeId: Bytes! + """ + The exchange provider address + """ + exchangeProvider: Bytes! + """ + The current price from IBancorExchangeProvider.currentPrice() + """ + price: BigInt! + """ + Transaction timestamp + """ + timestamp: BigInt! + """ + Day ID (timestamp / 86400) + """ + day: BigInt! + """ + Token in address from the swap + """ + tokenIn: Bytes! + """ + Token out address from the swap + """ + tokenOut: Bytes! + """ + Amount in from the swap + """ + amountIn: BigInt! + """ + Amount out from the swap + """ + amountOut: BigInt! + """ + User who initiated the swap + """ + user: Bytes! + """ + Block number + """ + blockNumber: BigInt! + """ + Transaction hash + """ + transactionHash: Bytes! +} + +scalar String + +""" +A string representation of microseconds UNIX timestamp (16 digits) +""" + +scalar Timestamp diff --git a/skills-lock.json b/skills-lock.json new file mode 100644 index 00000000..9b92c725 --- /dev/null +++ b/skills-lock.json @@ -0,0 +1,11 @@ +{ + "version": 1, + "skills": { + "gooddollar": { + "source": "GoodDollar/GoodSkills", + "sourceType": "github", + "skillPath": "skills/gooddollar/SKILL.md", + "computedHash": "d5f8cc5079f386e7170925e61214fb3334176aa6d0fc6bdc50400e535692b377" + } + } +} From 39026e7466e47510cd1f08108339b1f0ab93c3fb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Jun 2026 09:59:39 +0000 Subject: [PATCH 3/6] Add GoodDaoHouses MVP plan --- scripts/governance/gooddao-houses-mvp.md | 190 +++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 scripts/governance/gooddao-houses-mvp.md diff --git a/scripts/governance/gooddao-houses-mvp.md b/scripts/governance/gooddao-houses-mvp.md new file mode 100644 index 00000000..e6aa5183 --- /dev/null +++ b/scripts/governance/gooddao-houses-mvp.md @@ -0,0 +1,190 @@ +# GoodDaoHouses MVP implementation plan + +This document captures the maintainer-reviewable MVP outline requested in GoodDollar/GoodProtocol#297 without introducing the contract yet. + +## Contract placement and protocol wiring + +- Add `GoodDaoHouses.sol` under `contracts/governance/`. +- Make it upgradeable with `DAOUpgradeableContract` so avatar-authorized upgrades follow the existing governance pattern. +- Resolve protocol dependencies through `NameService`, including the `GOODDOLLAR` token and any execution-time configuration that should remain DAO-managed. +- Keep the MVP as a single contract with internal structs and enums for: + - house membership state + - HoA eligibility state + - vote snapshots + - per-voter ballots + - finalized allocations + - FlowSplitter pool configuration and execution state + +## Existing repository patterns to reuse + +- Reuse `DAOUpgradeableContract` + `NameService` lookup for DAO wiring and upgrades. +- Reuse OpenZeppelin role patterns already present in governance contracts for admin and committee permissions. +- Reuse the explicit vote lifecycle pattern from `CompoundVotingMachine.sol`: manual creation, stored start and end boundaries, and fixed eligibility at vote creation time. +- Reuse the ERC677 `transferAndCall` staking flow from `GoodDollarStaking.sol` as the preferred one-transaction membership path. + +## Membership model + +- Model the two houses as: + - `enum House { Citizens, Alignment }` + - `enum MemberStatus { None, Pending, Active, Revoked, Unstaked }` +- Store per-member state with: + - selected house + - current status + - staked amount + - `joinedAt` + - `updatedAt` + - `unstakedAt` + - plain-string metadata fields needed by each house +- Store HoA eligibility separately with: + - `isEligible` + - `listedAt` + - `updatedAt` + - optional delist timestamp +- HoC membership can activate immediately once the minimum stake is met. +- HoA registration should remain gated by committee-managed eligibility and enter `Pending` until approved. +- Revocation and unstake should preserve history through timestamps and events instead of deleting prior participation data. + +## Staking and registration flow + +- Primary MVP path: `GoodDollar.transferAndCall(housesContract, amount, encodedRegistrationData)`. +- `onTokenTransfer` should decode the selected house and metadata, then perform register-and-stake atomically. +- Keep direct `registerAndStake` or `stake` helpers only if needed for operational flexibility, but treat ERC677 as the main path. +- Minimum stake should be configurable per house by the governance committee role. +- Unstaking should fully clear HoA approval state and produce any required downstream zero-allocation execution updates. + +## Permission model + +- `DEFAULT_ADMIN_ROLE` should cover emergency administration such as pause and role management. +- The MVP should use a single `GOVERNANCE_COMMITTEE_ROLE` for: + - HoA eligibility management + - HoA approval and revocation + - vote creation + - vote finalization + - vote execution + - stake configuration updates +- Apply pause protection on sensitive write paths only: + - registration + - approval and revocation + - unstake + - vote creation + - vote updates + - finalization + - execution + - FlowSplitter configuration writes + +## Alignment vote model + +- Implement a single committee-created `AlignmentVote` type for the quarterly cycle. +- On vote creation, snapshot: + - active HoA recipients + - active HoA voters + - active HoC voters + - fixed per-house vote weights of `40` for HoA and `4` for HoC + - explicit open and close timestamps, with a seven-day default +- Ballots should store each voter’s current allocation so the voter can replace the full allocation while the vote remains open. +- Finalization should compute and persist canonical on-chain results before any downstream execution attempt. +- Execution should stay as a separate permissioned action, one time per vote, and must not erase finalized results when downstream execution fails. + +## Finalized allocation representation + +- Finalization should persist a deterministic internal `recipient -> uint128 units` result for each vote. +- The contract should translate those finalized units into `IFlowSplitter.Member[]` only during execution. +- Allocation math should normalize every recipient against a configured `totalUnits` value so execution is deterministic across pool creation and later pool updates. + +## FlowSplitter integration boundary + +- Use the published `flow-state-coop/flow-splitter` `src/IFlowSplitter.sol` interface shape rather than a custom approximation. +- Plan around the pool-based integration surface: + - `createPool(...)` + - `updateMembersUnits(...)` + - `updatePoolAdmins(...)` + - `updatePoolMetadata(...)` + - `isPoolAdmin(...)` + - `getPoolById(...)` +- Mirror the external FlowSplitter structs exactly: + - `Member { address account; uint128 units; }` + - `Admin { address account; AdminStatus status; }` + - `Pool { uint256 id; address poolAddress; address token; string metadata; bytes32 adminRole; }` + - `PoolConfig { bool transferabilityForUnitsOwner; bool distributionFromAnyAddress; }` + - `PoolERC20Metadata { string name; string symbol; uint8 decimals; }` +- Persist enough pool state to continue operating the same pool after first execution: + - FlowSplitter contract address + - configured Super Token address + - pool id + - pool address + - pool metadata + - initialization flag +- Register `GoodDaoHouses` itself as the acting pool admin so GovCo authorization remains enforced inside the governance contract instead of through rotating EOAs. +- Keep execution optional and configurable until the exact Celo Super Token and pool token choice is confirmed. +- Recommended MVP execution path: + 1. finalize vote allocations on-chain + 2. create the FlowSplitter pool on the first successful execution + 3. reuse `updateMembersUnits(...)` on later executions + 4. use `updatePoolMetadata(...)` when metadata changes +- Unstake or HoA approval removal should emit an explicit zero-unit member update for the affected recipient on the next relevant execution path. +- Recommended pool config for the governance-controlled MVP: + - `transferabilityForUnitsOwner = false` + - `distributionFromAnyAddress = false` + +## Read and write surface + +- Read methods should cover: + - member records + - HoA eligibility records + - per-house stake requirements + - active-member checks + - vote configuration + - vote snapshots + - ballot state + - finalized allocations + - execution status + - FlowSplitter configuration + - pool id and pool address +- Write methods should cover: + - add and remove HoA eligibility + - ERC677 registration callback + - HoA approval and revocation + - unstake + - create vote + - cast or replace vote allocations + - finalize vote + - execute results + - configure FlowSplitter + - create the pool + - sync pool metadata + - pause and unpause + +## Events + +- Include events for: + - HoA eligibility added and removed + - member registered + - member approved + - member revoked + - member unstaked + - vote created + - vote updated + - vote finalized + - vote executed + - FlowSplitter configuration updated + - FlowSplitter pool created + +## Deployment and test expectations + +- Follow the existing upgradeable governance deployment pattern and register the contract through `NameService`. +- Add governance tests under `test/governance/` using `createDAO()` fixtures and `loadFixture(...)`. +- Minimum test coverage for the implementation phase should include: + - HoA eligibility gating + - ERC677 register-and-stake flow + - HoC immediate activation + - HoA pending-to-active approval flow + - revocation and unstake transitions + - vote snapshot correctness + - weighted vote replacement logic + - finalize-before-execute guarantees + - single-execution enforcement + - failed-execution persistence + - first execution pool creation + - re-execution member unit updates + - zero-unit updates after unstake or approval removal + - role and pause enforcement From 9395d71379b038b85e740ffa27f69f0c688cad56 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Jun 2026 11:54:39 +0000 Subject: [PATCH 4/6] Remove mistaken GoodDaoHouses plan --- scripts/governance/gooddao-houses-mvp.md | 190 ----------------------- 1 file changed, 190 deletions(-) delete mode 100644 scripts/governance/gooddao-houses-mvp.md diff --git a/scripts/governance/gooddao-houses-mvp.md b/scripts/governance/gooddao-houses-mvp.md deleted file mode 100644 index e6aa5183..00000000 --- a/scripts/governance/gooddao-houses-mvp.md +++ /dev/null @@ -1,190 +0,0 @@ -# GoodDaoHouses MVP implementation plan - -This document captures the maintainer-reviewable MVP outline requested in GoodDollar/GoodProtocol#297 without introducing the contract yet. - -## Contract placement and protocol wiring - -- Add `GoodDaoHouses.sol` under `contracts/governance/`. -- Make it upgradeable with `DAOUpgradeableContract` so avatar-authorized upgrades follow the existing governance pattern. -- Resolve protocol dependencies through `NameService`, including the `GOODDOLLAR` token and any execution-time configuration that should remain DAO-managed. -- Keep the MVP as a single contract with internal structs and enums for: - - house membership state - - HoA eligibility state - - vote snapshots - - per-voter ballots - - finalized allocations - - FlowSplitter pool configuration and execution state - -## Existing repository patterns to reuse - -- Reuse `DAOUpgradeableContract` + `NameService` lookup for DAO wiring and upgrades. -- Reuse OpenZeppelin role patterns already present in governance contracts for admin and committee permissions. -- Reuse the explicit vote lifecycle pattern from `CompoundVotingMachine.sol`: manual creation, stored start and end boundaries, and fixed eligibility at vote creation time. -- Reuse the ERC677 `transferAndCall` staking flow from `GoodDollarStaking.sol` as the preferred one-transaction membership path. - -## Membership model - -- Model the two houses as: - - `enum House { Citizens, Alignment }` - - `enum MemberStatus { None, Pending, Active, Revoked, Unstaked }` -- Store per-member state with: - - selected house - - current status - - staked amount - - `joinedAt` - - `updatedAt` - - `unstakedAt` - - plain-string metadata fields needed by each house -- Store HoA eligibility separately with: - - `isEligible` - - `listedAt` - - `updatedAt` - - optional delist timestamp -- HoC membership can activate immediately once the minimum stake is met. -- HoA registration should remain gated by committee-managed eligibility and enter `Pending` until approved. -- Revocation and unstake should preserve history through timestamps and events instead of deleting prior participation data. - -## Staking and registration flow - -- Primary MVP path: `GoodDollar.transferAndCall(housesContract, amount, encodedRegistrationData)`. -- `onTokenTransfer` should decode the selected house and metadata, then perform register-and-stake atomically. -- Keep direct `registerAndStake` or `stake` helpers only if needed for operational flexibility, but treat ERC677 as the main path. -- Minimum stake should be configurable per house by the governance committee role. -- Unstaking should fully clear HoA approval state and produce any required downstream zero-allocation execution updates. - -## Permission model - -- `DEFAULT_ADMIN_ROLE` should cover emergency administration such as pause and role management. -- The MVP should use a single `GOVERNANCE_COMMITTEE_ROLE` for: - - HoA eligibility management - - HoA approval and revocation - - vote creation - - vote finalization - - vote execution - - stake configuration updates -- Apply pause protection on sensitive write paths only: - - registration - - approval and revocation - - unstake - - vote creation - - vote updates - - finalization - - execution - - FlowSplitter configuration writes - -## Alignment vote model - -- Implement a single committee-created `AlignmentVote` type for the quarterly cycle. -- On vote creation, snapshot: - - active HoA recipients - - active HoA voters - - active HoC voters - - fixed per-house vote weights of `40` for HoA and `4` for HoC - - explicit open and close timestamps, with a seven-day default -- Ballots should store each voter’s current allocation so the voter can replace the full allocation while the vote remains open. -- Finalization should compute and persist canonical on-chain results before any downstream execution attempt. -- Execution should stay as a separate permissioned action, one time per vote, and must not erase finalized results when downstream execution fails. - -## Finalized allocation representation - -- Finalization should persist a deterministic internal `recipient -> uint128 units` result for each vote. -- The contract should translate those finalized units into `IFlowSplitter.Member[]` only during execution. -- Allocation math should normalize every recipient against a configured `totalUnits` value so execution is deterministic across pool creation and later pool updates. - -## FlowSplitter integration boundary - -- Use the published `flow-state-coop/flow-splitter` `src/IFlowSplitter.sol` interface shape rather than a custom approximation. -- Plan around the pool-based integration surface: - - `createPool(...)` - - `updateMembersUnits(...)` - - `updatePoolAdmins(...)` - - `updatePoolMetadata(...)` - - `isPoolAdmin(...)` - - `getPoolById(...)` -- Mirror the external FlowSplitter structs exactly: - - `Member { address account; uint128 units; }` - - `Admin { address account; AdminStatus status; }` - - `Pool { uint256 id; address poolAddress; address token; string metadata; bytes32 adminRole; }` - - `PoolConfig { bool transferabilityForUnitsOwner; bool distributionFromAnyAddress; }` - - `PoolERC20Metadata { string name; string symbol; uint8 decimals; }` -- Persist enough pool state to continue operating the same pool after first execution: - - FlowSplitter contract address - - configured Super Token address - - pool id - - pool address - - pool metadata - - initialization flag -- Register `GoodDaoHouses` itself as the acting pool admin so GovCo authorization remains enforced inside the governance contract instead of through rotating EOAs. -- Keep execution optional and configurable until the exact Celo Super Token and pool token choice is confirmed. -- Recommended MVP execution path: - 1. finalize vote allocations on-chain - 2. create the FlowSplitter pool on the first successful execution - 3. reuse `updateMembersUnits(...)` on later executions - 4. use `updatePoolMetadata(...)` when metadata changes -- Unstake or HoA approval removal should emit an explicit zero-unit member update for the affected recipient on the next relevant execution path. -- Recommended pool config for the governance-controlled MVP: - - `transferabilityForUnitsOwner = false` - - `distributionFromAnyAddress = false` - -## Read and write surface - -- Read methods should cover: - - member records - - HoA eligibility records - - per-house stake requirements - - active-member checks - - vote configuration - - vote snapshots - - ballot state - - finalized allocations - - execution status - - FlowSplitter configuration - - pool id and pool address -- Write methods should cover: - - add and remove HoA eligibility - - ERC677 registration callback - - HoA approval and revocation - - unstake - - create vote - - cast or replace vote allocations - - finalize vote - - execute results - - configure FlowSplitter - - create the pool - - sync pool metadata - - pause and unpause - -## Events - -- Include events for: - - HoA eligibility added and removed - - member registered - - member approved - - member revoked - - member unstaked - - vote created - - vote updated - - vote finalized - - vote executed - - FlowSplitter configuration updated - - FlowSplitter pool created - -## Deployment and test expectations - -- Follow the existing upgradeable governance deployment pattern and register the contract through `NameService`. -- Add governance tests under `test/governance/` using `createDAO()` fixtures and `loadFixture(...)`. -- Minimum test coverage for the implementation phase should include: - - HoA eligibility gating - - ERC677 register-and-stake flow - - HoC immediate activation - - HoA pending-to-active approval flow - - revocation and unstake transitions - - vote snapshot correctness - - weighted vote replacement logic - - finalize-before-execute guarantees - - single-execution enforcement - - failed-execution persistence - - first execution pool creation - - re-execution member unit updates - - zero-unit updates after unstake or approval removal - - role and pause enforcement From 478c1c797ffb3e1aac3cae834188c9c7e1b07226 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:42:56 +0000 Subject: [PATCH 5/6] feat: implement GoodDaoHouses governance contract --- .agents/skills/gooddollar/CONTRIBUTING.md | 121 -- .agents/skills/gooddollar/SKILL.md | 215 ---- .../contracts/BuyGDCloneFactory.abi.yaml | 205 ---- .../BuyGDCloneFactory.selectors.yaml | 27 - .../contracts/BuyGDCloneV2.abi.yaml | 282 ----- .../contracts/BuyGDCloneV2.selectors.yaml | 36 - .../contracts/CFAv1Forwarder.abi.yaml | 237 ---- .../contracts/CFAv1Forwarder.selectors.yaml | 19 - .../ConstantFlowAgreementV1.abi.yaml | 560 --------- .../ConstantFlowAgreementV1.selectors.yaml | 33 - .../contracts/GoodDollarOFTAdapter.abi.yaml | 100 -- .../GoodDollarOFTAdapter.selectors.yaml | 12 - .../GooddollarSavingsStream.abi.yaml | 210 ---- .../GooddollarSavingsStream.selectors.yaml | 27 - .../contracts/GovernanceStakingV2.abi.yaml | 202 ---- .../GovernanceStakingV2.selectors.yaml | 26 - .../references/contracts/IdentityV3.abi.yaml | 390 ------- .../contracts/IdentityV3.selectors.yaml | 49 - .../references/contracts/IdentityV4.abi.yaml | 424 ------- .../contracts/IdentityV4.selectors.yaml | 51 - .../references/contracts/InvitesV2.abi.yaml | 333 ------ .../contracts/InvitesV2.selectors.yaml | 41 - .../references/contracts/MentoBroker.abi.yaml | 259 ----- .../contracts/MentoBroker.selectors.yaml | 24 - .../contracts/MessagePassingBridge.abi.yaml | 542 --------- .../MessagePassingBridge.selectors.yaml | 52 - .../references/contracts/NameService.abi.yaml | 104 -- .../contracts/NameService.selectors.yaml | 11 - .../references/contracts/SuperToken.abi.yaml | 1033 ----------------- .../contracts/SuperToken.selectors.yaml | 99 -- .../references/contracts/Superfluid.abi.yaml | 757 ------------ .../contracts/Superfluid.selectors.yaml | 51 - .../references/contracts/UBISchemeV2.abi.yaml | 305 ----- .../contracts/UBISchemeV2.selectors.yaml | 40 - .../contracts/_rich-abi-yaml-format.md | 93 -- .../deep-researches/faucet-flows.md | 42 - .../fuse-to-celo-staking-migration.md | 38 - .../gooddao-daostack-surface.md | 37 - .../deep-researches/how-ubi-is-minted.md | 68 -- .../inviter-invitee-reward-model.md | 58 - .../mento-reserve-economics.md | 27 - .../deep-researches/on-off-ramp-service.md | 69 -- .../gooddollar/references/guides/bridge.md | 187 --- .../references/guides/check-identity.md | 87 -- .../gooddollar/references/guides/claim.md | 83 -- .../gooddollar/references/guides/faucet.md | 76 -- .../gooddollar/references/guides/gooddocs.md | 37 - .../gooddollar/references/guides/goodsdks.md | 110 -- .../references/guides/hypersync-hyperrpc.md | 110 -- .../references/guides/invite-bounties.md | 115 -- .../migrate-fuse-staking-to-celo-savings.md | 116 -- .../references/guides/on-off-ramp.md | 77 -- .../gooddollar/references/guides/save.md | 93 -- .../gooddollar/references/guides/stream.md | 135 --- .../gooddollar/references/guides/swap.md | 83 -- .../references/subgraphs/_query-patterns.md | 35 - .../subgraphs/goodcollective-guide.md | 54 - .../subgraphs/goodcollective.graphql | 200 ---- .../subgraphs/gooddollar-celo-guide.md | 104 -- .../subgraphs/gooddollar-celo.graphql | 113 -- .../subgraphs/reserve-celo-guide.md | 41 - .../references/subgraphs/reserve-celo.graphql | 90 -- contracts/governance/GoodDaoHouses.sol | 764 ++++++++++++ contracts/governance/IFlowSplitter.sol | 103 ++ contracts/mocks/MockFlowSplitter.sol | 177 +++ skills-lock.json | 11 - test/governance/GoodDaoHouses.test.ts | 343 ++++++ 67 files changed, 1387 insertions(+), 9266 deletions(-) delete mode 100644 .agents/skills/gooddollar/CONTRIBUTING.md delete mode 100644 .agents/skills/gooddollar/SKILL.md delete mode 100644 .agents/skills/gooddollar/references/contracts/BuyGDCloneFactory.abi.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/BuyGDCloneFactory.selectors.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/BuyGDCloneV2.abi.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/BuyGDCloneV2.selectors.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/CFAv1Forwarder.abi.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/CFAv1Forwarder.selectors.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/ConstantFlowAgreementV1.abi.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/ConstantFlowAgreementV1.selectors.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/GoodDollarOFTAdapter.abi.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/GoodDollarOFTAdapter.selectors.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/GooddollarSavingsStream.abi.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/GooddollarSavingsStream.selectors.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/GovernanceStakingV2.abi.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/GovernanceStakingV2.selectors.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/IdentityV3.abi.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/IdentityV3.selectors.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/IdentityV4.abi.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/IdentityV4.selectors.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/InvitesV2.abi.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/InvitesV2.selectors.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/MentoBroker.abi.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/MentoBroker.selectors.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/MessagePassingBridge.abi.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/MessagePassingBridge.selectors.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/NameService.abi.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/NameService.selectors.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/SuperToken.abi.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/SuperToken.selectors.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/Superfluid.abi.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/Superfluid.selectors.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/UBISchemeV2.abi.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/UBISchemeV2.selectors.yaml delete mode 100644 .agents/skills/gooddollar/references/contracts/_rich-abi-yaml-format.md delete mode 100644 .agents/skills/gooddollar/references/deep-researches/faucet-flows.md delete mode 100644 .agents/skills/gooddollar/references/deep-researches/fuse-to-celo-staking-migration.md delete mode 100644 .agents/skills/gooddollar/references/deep-researches/gooddao-daostack-surface.md delete mode 100644 .agents/skills/gooddollar/references/deep-researches/how-ubi-is-minted.md delete mode 100644 .agents/skills/gooddollar/references/deep-researches/inviter-invitee-reward-model.md delete mode 100644 .agents/skills/gooddollar/references/deep-researches/mento-reserve-economics.md delete mode 100644 .agents/skills/gooddollar/references/deep-researches/on-off-ramp-service.md delete mode 100644 .agents/skills/gooddollar/references/guides/bridge.md delete mode 100644 .agents/skills/gooddollar/references/guides/check-identity.md delete mode 100644 .agents/skills/gooddollar/references/guides/claim.md delete mode 100644 .agents/skills/gooddollar/references/guides/faucet.md delete mode 100644 .agents/skills/gooddollar/references/guides/gooddocs.md delete mode 100644 .agents/skills/gooddollar/references/guides/goodsdks.md delete mode 100644 .agents/skills/gooddollar/references/guides/hypersync-hyperrpc.md delete mode 100644 .agents/skills/gooddollar/references/guides/invite-bounties.md delete mode 100644 .agents/skills/gooddollar/references/guides/migrate-fuse-staking-to-celo-savings.md delete mode 100644 .agents/skills/gooddollar/references/guides/on-off-ramp.md delete mode 100644 .agents/skills/gooddollar/references/guides/save.md delete mode 100644 .agents/skills/gooddollar/references/guides/stream.md delete mode 100644 .agents/skills/gooddollar/references/guides/swap.md delete mode 100644 .agents/skills/gooddollar/references/subgraphs/_query-patterns.md delete mode 100644 .agents/skills/gooddollar/references/subgraphs/goodcollective-guide.md delete mode 100644 .agents/skills/gooddollar/references/subgraphs/goodcollective.graphql delete mode 100644 .agents/skills/gooddollar/references/subgraphs/gooddollar-celo-guide.md delete mode 100644 .agents/skills/gooddollar/references/subgraphs/gooddollar-celo.graphql delete mode 100644 .agents/skills/gooddollar/references/subgraphs/reserve-celo-guide.md delete mode 100644 .agents/skills/gooddollar/references/subgraphs/reserve-celo.graphql create mode 100644 contracts/governance/GoodDaoHouses.sol create mode 100644 contracts/governance/IFlowSplitter.sol create mode 100644 contracts/mocks/MockFlowSplitter.sol delete mode 100644 skills-lock.json create mode 100644 test/governance/GoodDaoHouses.test.ts diff --git a/.agents/skills/gooddollar/CONTRIBUTING.md b/.agents/skills/gooddollar/CONTRIBUTING.md deleted file mode 100644 index ab855d38..00000000 --- a/.agents/skills/gooddollar/CONTRIBUTING.md +++ /dev/null @@ -1,121 +0,0 @@ -# Contributing to GoodSkills - -This repository is an AI skill pack. The goal of each update is to make agent behavior more reliable, more explicit, and easier to audit. - -## Update workflow - -1. Define the user-facing problem first. -2. For contract-related updates, add or update Rich ABI first (`references/contracts/*.abi.yaml`), then refresh selectors. -3. Decide the remaining artifact types: - - `references/guides/` for "what to do" - - `references/deep-researches/` for "why it works this way" - - `scripts/` for deterministic and repeatable execution -4. Update `SKILL.md` routing so the new artifact is discoverable. -5. Validate consistency (paths, naming, links, selectors, assumptions). - -If a change touches contract behavior, treat Rich ABI update/add as mandatory first step before guides, deep-research, or scripts. - -## Add or update a guide - -Use guides for execution playbooks and operator workflows. - -Required structure: - -- title and one-line usage trigger -- `## Goal` -- `## Required inputs` -- `## Execution flow` as numbered steps -- deterministic snippet when execution is non-trivial -- failure handling and output contract - -Guide rules: - -- prefer explicit pre-checks before state-changing actions -- include only one primary workflow per file -- use [GoodProtocol `releases/deployment.json`](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) as the **only** source for contract addresses (rich ABI `meta.deployments` mirrors those rows); use GoodDocs for behavior and UX, not for resolving addresses; use on-chain `NameService.getAddress` only when the deployment documents the string key -- avoid implementation-deep theory; keep that in deep-research files - -After adding a guide: - -- add it to `SKILL.md` in `Guides` -- add an entry in `Use-case to guide map` - -## Add or update a deep-research note - -Use deep-research files for architecture, rationale, tradeoffs, and root-cause logic. - -Deep-research rules: - -- explain causality, not only API surfaces -- distinguish current behavior from legacy behavior -- link source contracts/docs for traceability -- keep language natural and decision-oriented - -Do not turn deep-research files into step-by-step runbooks; move operational steps into guides. - -## Add or update Rich ABI YAML - -Location: `references/contracts/`. - -For each contract: - -- create or update `Foo.abi.yaml` -- generate or refresh `Foo.selectors.yaml` -- include function-level notes for non-obvious behavior -- when `meta.deployments` lists concrete addresses, add **`creationBlock`** next to each **`address`** (placement: `references/contracts/_rich-abi-yaml-format.md`; using it as **`fromBlock`** for log or HyperSync fetches: `references/guides/hypersync-hyperrpc.md`) - -Minimum ABI documentation quality: - -- correct mutability, inputs, outputs -- access pattern (`owner`, `avatar`, `anyone`, etc.) where relevant -- emitted events and practical errors -- notes for routing/edge-case semantics - -Source-of-truth policy: - -- prefer canonical contract repos (GoodProtocol, GoodBridge, mento-core) -- avoid inferred behavior when source is unclear -- update notes when protocol behavior changed - -Selector generation: - -```bash -node scripts/selectors.mjs generate Foo.abi.yaml -``` - -## Add or update scripts - -Location: `scripts/`. - -Use scripts when: - -- a workflow is repeated -- deterministic output is needed -- manual querying is error-prone - -Script standards: - -- require inputs through env vars or explicit args -- fail loudly with actionable messages -- print structured output for easy reuse -- keep script intent narrow - -When a script supports a guide: - -- reference it from that guide -- document expected inputs and outputs in the guide - -## Naming and organization - -- use lowercase kebab-case for guides and deep-research files -- keep one topic per file -- avoid duplicate guidance across files -- prefer updating existing files over creating near-duplicates - -## Update checklist before merge - -- `SKILL.md` routing updated -- links resolve and point to public sources -- guides and deep-research files respect "what" vs "why" separation -- ABI + selectors pairs are in sync -- new behavior is reflected in notes where needed diff --git a/.agents/skills/gooddollar/SKILL.md b/.agents/skills/gooddollar/SKILL.md deleted file mode 100644 index 2d59116b..00000000 --- a/.agents/skills/gooddollar/SKILL.md +++ /dev/null @@ -1,215 +0,0 @@ ---- -name: gooddollar -description: > - Knowledge base for GoodProtocol action execution and GoodDollar (G$) integrations. - Use this skill BEFORE ad-hoc web search for claim, save/stake, swap, bridge, - stream, and identity tasks. Prefer GoodDocs (https://docs.gooddollar.org/) for - narrative; contract addresses only from GoodProtocol releases/deployment.json. -metadata: - version: 1.0.0 -license: MIT ---- - -# GoodDollar Skill Pack - -Routing index for GoodProtocol. This repo complements [GoodDocs](https://docs.gooddollar.org/) for behavior and user flows. **Contract addresses** come only from [GoodProtocol/releases/deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) (and `meta.deployments` in `references/contracts/*.abi.yaml`, which mirror those rows)—not from GoodDocs pages. - -Repository maintenance and update process is documented in `CONTRIBUTING.md`. - -## Protocol snapshot (from GoodDocs) - -- G$ is reserve-backed; issuance and pricing tie to the reserve and bonding-curve mechanics described in [How GoodDollar works](https://docs.gooddollar.org/how-gooddollar-works). -- The stack is multi-chain; which contracts exist per environment is defined only in [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) (for example `GoodDollar`, `Identity`, `NameService`, `UBIScheme`, Mento keys, `MpbBridge`, and related entries under `production`, `production-celo`, and `production-xdc`). -- UBI is daily for verified users; identity verification and connected accounts are documented under [user guides](https://docs.gooddollar.org/user-guides). - -## Guides (single location for action playbooks) - -All task-specific instructions live under `references/guides/`. - -- `references/guides/claim.md` — daily UBI (`claim` / UBIScheme). -- `references/guides/save.md` — stake, rewards, unstake. -- `references/guides/swap.md` — buy or sell G$ (Mento on supported chains). -- `references/guides/bridge.md` — MessagePassingBridge (GoodDocs); optional OFT path via ABI refs. -- `references/guides/stream.md` — Superfluid streams (Celo-oriented in GoodDocs). -- `references/guides/check-identity.md` — whitelist and connected-address semantics. -- `references/guides/goodsdks.md` — SDK-first integration routing for GoodSDKs packages. -- `references/guides/gooddocs.md` — hub links to [GoodDocs](https://docs.gooddollar.org/). -- `references/guides/hypersync-hyperrpc.md` — Envio HyperSync/HyperRPC data-source routing for high-volume historical reads. -- `references/guides/faucet.md` — Faucet gas top-up execution flow and preflight checks. -- `references/guides/on-off-ramp.md` — stable-token ramp service flow into and out of G$. -- `references/guides/invite-bounties.md` — verify and execute inviter-invitee bounty payouts. -- `references/guides/migrate-fuse-staking-to-celo-savings.md` — migrate Fuse governance stake to CELO savings flow. - -## Subgraphs (indexed chain history) - -Use this folder with the same pattern as the protocol subgraph references: one `*-guide.md` plus one companion `.graphql` per deployment. - -For historical on-chain data, **start with the subgraph**: confirm the deployment covers the question (entities and fields in the guide, freshness via `_meta`). If the subgraph does not work for the request—missing schema coverage, stale or lagging indexing, query limits, or endpoint errors—**then** move to **HyperSync** or **HyperRPC** using `references/guides/hypersync-hyperrpc.md`. - -- `references/subgraphs/_query-patterns.md` — cross-cutting query discipline. -- `references/subgraphs/reserve-celo-guide.md` + `references/subgraphs/reserve-celo.graphql` — reserve pricing and swap history. -- `references/subgraphs/gooddollar-celo-guide.md` + `references/subgraphs/gooddollar-celo.graphql` — GoodDollar Celo schema discovery and starter probes. -- `references/subgraphs/goodcollective-guide.md` + `references/subgraphs/goodcollective.graphql` — GoodCollective schema discovery and starter probes. - -For Superfluid protocol subgraphs (streams, pools, vesting schedulers), see [Superfluid documentation](https://docs.superfluid.finance/) and [subgraph endpoints](https://subgraph-endpoints.superfluid.dev/). - -## Historical data routing policy (strict) - -1. Query subgraphs first for all historical/indexed requests. -2. Validate required entities and fields against the target subgraph schema and guide before declaring a gap. -3. Use **HyperSync** or **HyperRPC** fallback only when at least one of these is true: - - required entities or fields are not available in subgraph schema - - indexing lag makes subgraph data stale for the requested range - - query limits or endpoint instability block reliable retrieval -4. Do not start with HyperSync or HyperRPC when subgraph data is available and fresh. -5. HyperRPC fallback requires a valid Envio API key; if missing, **explicitly ask the user** to provide `HYPERRPC_API_TOKEN` or `ENVIO_API_TOKEN` (or paste a full `HYPERRPC_URL`); do not treat anonymous HyperRPC as production. -6. When **HyperSync** is the best option for the query and no Envio API token is available (`ENVIO_API_TOKEN` or equivalent per `references/guides/hypersync-hyperrpc.md`), **explicitly ask the user** to provide the token before proceeding; do not silently substitute anonymous HyperSync usage. -7. When fallback is used, report reason explicitly (schema gap, lag, or reliability issue). - -## Data source decision table - -| Query type | Primary source | Secondary source | Notes | -|---|---|---|---| -| Current on-chain state (latest balances, allowances, config, flags, view calls) | RPC | None | Use direct contract RPC reads for latest state. | -| Historical indexed entity data (time-series, aggregates, protocol entities, event-derived analytics) | Subgraph | HyperSync/HyperRPC | Prefer subgraph first; fall back when it cannot answer. | -| Historical raw on-chain data when subgraph is missing fields/entities or stale | HyperSync | HyperRPC | Prefer HyperSync for bulk scans and data pipelines. | -| Historical data for existing JSON-RPC integrations | HyperRPC | HyperSync | Use HyperRPC when strict JSON-RPC compatibility is required. | - -Decision rule: - -1. If request is current state -> use RPC. -2. If request is historical/indexed -> query subgraph first. -3. If subgraph cannot satisfy request -> fallback to HyperSync or HyperRPC per compatibility and scale needs. -4. HyperRPC fallback requires Envio API key credentials. -5. HyperSync client usage requires an Envio API token; if HyperSync is chosen and the token is missing, explicitly ask the user to provide it (see `references/guides/hypersync-hyperrpc.md`). - -## Mapping data retrieval rule - -Solidity mappings are not iterable on-chain by keyspace scan. Do not assume full-key enumeration is possible from RPC alone. - -When data is stored in mapping-like structures: - -1. Check contract source and ABI for key-discovery paths first: - - events emitted on set or update - - arrays, counters, linked lists, or index getters storing keys - - dedicated pagination or enumerable view functions -2. If key discovery exists, reconstruct key set from those sources and then read mapping entries. -3. If key discovery does not exist, report that complete iteration is not possible from chain state alone. -4. For historical reconstruction, prefer subgraph indexing first; if unavailable, use HyperSync or HyperRPC log scans with explicit limitations. - -## Use-case to guide map - -- Claim requests -> `references/guides/claim.md` -- Eligibility or connected-address questions -> `references/guides/check-identity.md` -- Stake, save, unstake -> `references/guides/save.md` -- Buy or sell G$ against reserve rails -> `references/guides/swap.md` -- Cross-chain bridge -> `references/guides/bridge.md` -- Stream management -> `references/guides/stream.md` -- SDK app integration tasks -> `references/guides/goodsdks.md` -- Bulk historical reads or data-engineering fetches -> `references/guides/hypersync-hyperrpc.md` -- Faucet top-up tasks -> `references/guides/faucet.md` -- On-/off-ramp service flow tasks -> `references/guides/on-off-ramp.md` -- Invite bounty eligibility and payout tasks -> `references/guides/invite-bounties.md` -- Fuse to CELO staking migration tasks -> `references/guides/migrate-fuse-staking-to-celo-savings.md` -- Indexed history, analytics, or GraphQL against GoodDollar subgraphs -> `references/subgraphs/_query-patterns.md` -- Historical on-chain fetch when subgraph data is insufficient -> subgraphs first, then HyperSync or HyperRPC per `references/guides/hypersync-hyperrpc.md`; if HyperSync is best and `ENVIO_API_TOKEN` is missing, ask the user for it explicitly. - -## Ambiguous prompts and incomplete inputs - -Stop and **ask the user** whenever the task is underspecified or required facts are missing. List what you need in short, concrete questions (for example chain, contract, address, amount, account, RPC or signer access, time or block range, prior tx hash, approval scope). - -- **Ambiguous** means the goal, environment, contract surface, or acceptance criteria are not clear enough to choose a safe path. -- **Incomplete** means you lack inputs that would change what you build, call, or sign next. - -**Do not invent** chain, address, amount, or policy details that affect correctness, funds, or eligibility. For **information-only** work you may state a single explicit assumption, label it, and ask the user to confirm or correct it before going further. - -**Execution work** (writing or editing runnable code, sending transactions, migrations, or anything that can move funds or alter on-chain state) has **no guessing**: settle every required input with the user, then implement or run. - -## Execution rules - -1. Collect missing required inputs before sending transactions. -2. Run pre-checks first (allowance, whitelist, quotes, bridge **amount** limits, peer wiring when using OFT paths). -3. If a pre-check fails, stop and return the exact corrective action. -4. Return tx hash and key output values. -5. Never fabricate addresses, amounts, or ABI behavior. -6. Resolve decimals and units per chain as in [How to integrate the G$ token](https://docs.gooddollar.org/for-developers/developer-guides/how-to-integrate-the-gusd-token) (for example 18 decimals on Celo, 2 on Fuse and Ethereum where applicable). - -## Pre-check matrix - -- Claim: verify identity whitelist status before `claim()`. -- Save or stake: verify balance and allowance before `stake()`. -- Swap: fetch quote, apply slippage bounds, verify allowance; confirm Mento contract keys for the active chain exist in `deployment.json` (for example `MentoBroker` under `production-celo` or `production-xdc`). -- Bridge (MessagePassingBridge): on the **source** chain approve G$ to the bridge; optionally preflight `canBridge(from, amount)` on that same contract (outbound `_bridgeTo` does not call it internally). For LZ use `estimateSendFee` with the **normalized** burn amount per `references/guides/bridge.md`, then `bridgeToWithLz` with nonzero `msg.value` for the **cross-chain transport** fee only (distinct from destination **`bridgeFees`** on minted G$; see **Bridge fee context** in that guide). Read **`bridgeLimits`** / daily trackers when debugging **amount** caps; see **Bridge amount limit context** in that guide. Respect `isClosed`, `LZ_FEE`, `MISSING_FEE`, and `UNSUPPORTED_CHAIN`. **Destination** mint applies `_enforceLimits` and can still revert. Use **Axelar** only when `toAxelarChainId` returns a route (implementation maps 1, 5, 42220, 44787); for Fuse or XDC style targets prefer LZ unless mapping is extended on-chain. -- Bridge (OFT adapter path): verify peer wiring and `quoteSend` fee data. -- Stream: confirm Celo (or documented Superfluid network) and correct Super Token and forwarder or host addresses. -- Identity: resolve Identity from NameService; remember connected addresses do not multiply daily claims ([connect wallet guide](https://docs.gooddollar.org/user-guides/connect-another-wallet-address-to-identity)). - -## Output format requirements - -For any state-changing action return: - -- network and key contract addresses used -- normalized input amounts and min or max guards -- tx hash -- key post-state output when available -- follow-up action if user intervention is required - -## Rich contract ABI references - -Convention: each `Foo.abi.yaml` has a companion `Foo.selectors.yaml` (function, event, and custom error selectors). Schema: `references/contracts/_rich-abi-yaml-format.md`. - -GoodDollar / Mento: - -- `references/contracts/NameService.abi.yaml` -- `references/contracts/IdentityV3.abi.yaml` -- `references/contracts/IdentityV4.abi.yaml` -- `references/contracts/InvitesV2.abi.yaml` -- `references/contracts/BuyGDCloneFactory.abi.yaml` -- `references/contracts/BuyGDCloneV2.abi.yaml` -- `references/contracts/GovernanceStakingV2.abi.yaml` -- `references/contracts/GooddollarSavingsStream.abi.yaml` (Ubeswap Superfluid stream savings; Celo deployment) -- `references/contracts/UBISchemeV2.abi.yaml` -- `references/contracts/MentoBroker.abi.yaml` -- `references/contracts/MessagePassingBridge.abi.yaml` -- `references/contracts/GoodDollarOFTAdapter.abi.yaml` -- `references/contracts/CFAv1Forwarder.abi.yaml` -- `references/contracts/ConstantFlowAgreementV1.abi.yaml` -- `references/contracts/Superfluid.abi.yaml` -- `references/contracts/SuperToken.abi.yaml` - -Superfluid (CFA, CFAv1Forwarder, Host, full ABI library): use [Superfluid docs](https://docs.superfluid.finance/), npm packages such as `@superfluid-finance/ethereum-contracts` and `@sfpro/sdk`, and contract ABIs published with those packages. - -## Deep researches - -- `references/deep-researches/on-off-ramp-service.md` -- `references/deep-researches/how-ubi-is-minted.md` -- `references/deep-researches/inviter-invitee-reward-model.md` -- `references/deep-researches/mento-reserve-economics.md` -- `references/deep-researches/gooddao-daostack-surface.md` -- `references/deep-researches/faucet-flows.md` -- `references/deep-researches/fuse-to-celo-staking-migration.md` - -## Revert debugging quick map - -- Identity or eligibility errors -> Identity and UBIScheme ABIs; live addresses from `deployment.json` only; GoodDocs for whitelist and claim behavior. -- Approval or transfer failures -> token approvals and balances; see integration guide for `transferAndCall` vs `approve` plus `transferFrom`. -- Swap bound failures -> quote freshness and slippage settings. -- MessagePassingBridge failures -> `canBridge`; **`BRIDGE_LIMITS`** (amount caps, whitelist, **`closed`**, and related policy strings); transport `msg.value` (`MISSING_FEE`, `LZ_FEE`) vs destination protocol fee (`bridgeFees`, `feeRecipient`); correct `bridgeTo` arguments; [Bridge GoodDollars](https://docs.gooddollar.org/user-guides/bridge-gooddollars). -- OFT path failures -> peer wiring and `quoteSend` fee data. -- Stream failures -> CFA forwarder or host agreement calls, buffer and flow-rate limits per Superfluid docs linked from GoodDocs. -- Faucet top-up failures -> `canTop`, `onlyAuthorized`, daily or weekly caps; `references/deep-researches/faucet-flows.md`. -- DAO-gated reverts -> caller is not avatar; scheme not registered; `references/deep-researches/gooddao-daostack-surface.md`. - -## Library usage discipline - -1. Open `references/guides/gooddocs.md` when unsure which GoodDocs page applies. -2. Start at this file to classify intent. -3. Open one guide under `references/guides/` unless the user requests a multi-step workflow. For subgraph or indexed-data tasks, start at `references/subgraphs/_query-patterns.md`. -4. Read only the ABI references and matching `.selectors.yaml` files needed for the chosen action. -5. Prefer GoodDocs for documented behavior; use only `deployment.json` (and rich ABI `meta.deployments` aligned with it) for contract addresses—never infer addresses from GoodDocs. -6. For large historical reads, prefer `references/guides/hypersync-hyperrpc.md` and choose HyperSync over HyperRPC unless strict JSON-RPC compatibility is required. -7. Historical data routing is strict: subgraphs first; HyperSync or HyperRPC only with an explicit fallback reason. -8. HyperRPC usage requires Envio API key credentials; when absent, **explicitly ask the user** for `HYPERRPC_API_TOKEN` or `ENVIO_API_TOKEN` (or a full `HYPERRPC_URL`) and do not attempt anonymous production flow. -9. When HyperSync is the best historical-data path and no Envio API token is available, explicitly ask the user to provide `ENVIO_API_TOKEN` (or the token your client expects) before continuing; see `references/guides/hypersync-hyperrpc.md`. -10. For subgraph tasks, validate field availability from the relevant `references/subgraphs/*-guide.md` and companion `.graphql` before guessing alternate entities. -11. For local shells repeating HyperRPC log pulls (for example last N whitelist events), from the **GoodSkills repository root** run `scripts/fetch-whitelist-events-hyperrpc.mjs` per `references/guides/hypersync-hyperrpc.md` instead of re-deriving JSON-RPC setup each time; that script ships with **defaults for production Celo** (HyperRPC host + `Identity` contract from `deployment.json`) and URL composition from `HYPERRPC_API_TOKEN` or `ENVIO_API_TOKEN` unless you override `CONTRACT_ADDRESS` / `HYPERRPC_URL`. HyperSync remains a separate client install path documented in the same guide. diff --git a/.agents/skills/gooddollar/references/contracts/BuyGDCloneFactory.abi.yaml b/.agents/skills/gooddollar/references/contracts/BuyGDCloneFactory.abi.yaml deleted file mode 100644 index 40226b3b..00000000 --- a/.agents/skills/gooddollar/references/contracts/BuyGDCloneFactory.abi.yaml +++ /dev/null @@ -1,205 +0,0 @@ -meta: - name: BuyGDCloneFactory - version: "1" - source: - - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/contracts/utils/BuyGDClone.sol - note: > - Deterministic clone factory for BuyGDCloneV2 and DonateGDClone. Used to create per-owner - executors and optional create+swap flows for ramp services. - deployments: - mainnet: - production-celo: - BuyGDFactoryV2: - networkId: 42220 - address: "0x1F60C4C7037C6766924A43666B781ED1479587a2" - creationBlock: 22909812 - BuyGDFactory: - networkId: 42220 - address: "0x00e533B7d6255D05b7f15034B1c989c21F51b91C" - creationBlock: 21006324 - related: - - references/deep-researches/on-off-ramp-service.md - - references/guides/on-off-ramp.md - -create: - notes: - - "Deploys deterministic BuyGDCloneV2 clone for owner and initializes it." - - "Difference vs createAndSwap: this only deploys; no swap is executed." - mutability: nonpayable - access: anyone - inputs: - - owner: address - outputs: - - clone: address - -createDonation: - notes: - - "Deploys deterministic DonateGDClone with donation target and call payload." - - "Difference vs create: deploys donation-capable implementation instead of plain swap clone." - mutability: nonpayable - access: anyone - inputs: - - owner: address - - donateOrExecTo: address - - callData: bytes - outputs: - - clone: address - -createAndSwap: - notes: - - "One-transaction helper: create BuyGDCloneV2 then immediately call clone.swap(minAmount, msg.sender)." - - "Difference vs create: bundles deployment and first swap for faster ramp UX." - mutability: nonpayable - access: anyone - inputs: - - owner: address - - minAmount: uint256 - outputs: - - clone: address - -createDonationAndSwap: - notes: - - "One-transaction helper for DonateGDClone: deploy, then execute donation flow with optional pre-swap." - - "Difference vs createDonation: can execute immediate swap and/or target call instead of deployment only." - mutability: nonpayable - access: anyone - inputs: - - owner: address - - donateOrExecTo: address - - withSwap: bool - - minAmount: uint256 - - callData: bytes - outputs: - - clone: address - -predict: - notes: - - "Computes deterministic address for create(owner) salt." - mutability: view - inputs: - - owner: address - outputs: - - clone: address - -predictDonation: - notes: - - "Computes deterministic address for createDonation(owner, donateOrExecTo, callData) salt." - mutability: view - inputs: - - owner: address - - donateOrExecTo: address - - callData: bytes - outputs: - - clone: address - -getBaseFee: - mutability: view - inputs: [] - outputs: - - baseFee: uint256 - -quoter: - mutability: pure - inputs: [] - outputs: - - addr: address - -CUSD: - mutability: pure - inputs: [] - outputs: - - token: address - -celo: - mutability: pure - inputs: [] - outputs: - - token: address - -USDC: - mutability: pure - inputs: [] - outputs: - - token: address - -GLOUSD: - mutability: pure - inputs: [] - outputs: - - token: address - -PERIOD: - mutability: pure - inputs: [] - outputs: - - seconds: uint24 - -impl: - mutability: view - inputs: [] - outputs: - - addr: address - -donateImpl: - mutability: view - inputs: [] - outputs: - - addr: address - -gd: - mutability: view - inputs: [] - outputs: - - token: address - -stable: - mutability: view - inputs: [] - outputs: - - token: address - -oracle: - mutability: view - inputs: [] - outputs: - - addr: address - -router: - mutability: view - inputs: [] - outputs: - - addr: address - -mentoBroker: - mutability: view - inputs: [] - outputs: - - addr: address - -mentoExchangeProvider: - mutability: view - inputs: [] - outputs: - - addr: address - -mentoExchangeId: - mutability: view - inputs: [] - outputs: - - id: bytes32 - -events: - GDSwapToCusd: - indexed: [] - data: - - from: address - - to: address - - amountIn: uint256 - - amountOut: uint256 - - note: bytes - -errors: - NOT_GD_TOKEN: "onTokenTransfer caller is not G$ token." - INVALID_TWAP: "TWAP validation failed." - RECIPIENT_ZERO: "Recipient cannot be zero address." - ZERO_MINAMOUNT: "Minimum amount cannot be zero." diff --git a/.agents/skills/gooddollar/references/contracts/BuyGDCloneFactory.selectors.yaml b/.agents/skills/gooddollar/references/contracts/BuyGDCloneFactory.selectors.yaml deleted file mode 100644 index 16b13f22..00000000 --- a/.agents/skills/gooddollar/references/contracts/BuyGDCloneFactory.selectors.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by scripts/selectors.mjs -functions: - create(address): 0x9ed93318 - createDonation(address,address,bytes): 0x48199e6b - createAndSwap(address,uint256): 0x89643a29 - createDonationAndSwap(address,address,bool,uint256,bytes): 0xeb5621b8 - predict(address): 0x901b96e7 - predictDonation(address,address,bytes): 0x3895de30 - getBaseFee(): 0x15e812ad - quoter(): 0xc6bbd5a7 - CUSD(): 0x758316c9 - celo(): 0x051ed8ef - USDC(): 0x89a30271 - GLOUSD(): 0x4b5b02d6 - PERIOD(): 0xb4d1d795 - impl(): 0x8abf6077 - donateImpl(): 0x21fc2eef - gd(): 0xa5e598fc - stable(): 0x22be3de1 - oracle(): 0x7dc0d1d0 - router(): 0xf887ea40 - mentoBroker(): 0x7b89f117 - mentoExchangeProvider(): 0x4f62feec - mentoExchangeId(): 0xd373b333 -events: - GDSwapToCusd(address,address,uint256,uint256,bytes): 0x252bc23e3fb01f9986fa157af621236fb8a706ea12622da296e7f2f30d4f1a56 -errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/BuyGDCloneV2.abi.yaml b/.agents/skills/gooddollar/references/contracts/BuyGDCloneV2.abi.yaml deleted file mode 100644 index 5f35ee91..00000000 --- a/.agents/skills/gooddollar/references/contracts/BuyGDCloneV2.abi.yaml +++ /dev/null @@ -1,282 +0,0 @@ -meta: - name: BuyGDCloneV2 - version: "2" - source: - - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/contracts/utils/BuyGDClone.sol - inherits: - - Initializable - note: > - Clone implementation used by BuyGDCloneFactory for deterministic per-owner swap executors. - Supports CELO and cUSD flows, with cUSD route selection between Uniswap and Mento when configured. - deployments: - mainnet: - production-celo: - BuyGDFactoryV2: - networkId: 42220 - address: "0x1F60C4C7037C6766924A43666B781ED1479587a2" - creationBlock: 22909812 - related: - - references/deep-researches/on-off-ramp-service.md - - references/guides/on-off-ramp.md - -initialize: - mutability: nonpayable - access: initializer - inputs: - - _owner: address - outputs: [] - -getSwapPath: - mutability: pure - inputs: - - tokens: address[] - - fees: uint24[] - outputs: - - path: bytes - -swap: - notes: - - "Dispatcher entrypoint: if clone holds native CELO balance it executes CELO route, otherwise uses cUSD route." - - "Difference vs swapCelo/swapCusd: this function chooses route by contract balances; specialized functions force a specific asset path." - mutability: payable - access: anyone - inputs: - - _minAmount: uint256 - - refundGas: address - outputs: - - bought: uint256 - emits: [Bought] - errors: [NO_BALANCE] - -swapCelo: - notes: - - "CELO-only convenience path using the default preconfigured Uniswap route." - - "Difference vs swapCeloWithPath: same Uniswap execution, but caller cannot override token path and fee tiers." - - "Difference vs cUSD paths: CELO path does not use Mento route selection." - mutability: payable - access: anyone - inputs: - - _minAmount: uint256 - - refundGas: address - outputs: - - bought: uint256 - emits: [BoughtFromUniswap] - errors: [REFUND_FAILED] - -swapCeloWithPath: - notes: - - "CELO-only path with caller-supplied Uniswap multi-hop route." - - "Difference vs swapCelo: allows custom path optimization when default route is not preferred." - - "Difference vs cUSD paths: still Uniswap-only and does not compare against Mento." - mutability: payable - access: anyone - inputs: - - _minAmount: uint256 - - refundGas: address - - _path: (address[],uint24[]) - outputs: - - bought: uint256 - emits: [BoughtFromUniswap] - errors: [REFUND_FAILED] - -swapCusd: - notes: - - "cUSD-only convenience path that compares expected output from default-path Uniswap and Mento, then executes the better quote." - - "Difference vs swapCusdWithPath: this uses hardcoded default Uniswap path for comparison." - - "Difference vs CELO paths: includes dual-route best-execution logic with optional Mento." - mutability: nonpayable - access: anyone - inputs: - - _minAmount: uint256 - - refundGas: address - outputs: - - bought: uint256 - emits: [BoughtFromMento, BoughtFromUniswap] - errors: [MENTO_NOT_CONFIGURED] - -swapCusdWithPath: - notes: - - "cUSD path with caller-supplied Uniswap route; still compares custom Uniswap quote against Mento quote and picks larger expected output." - - "Difference vs swapCusd: custom path changes only the Uniswap side of the comparison." - - "Difference vs swapCeloWithPath: this function performs route competition (Uniswap vs Mento), not only route customization." - mutability: nonpayable - access: anyone - inputs: - - _minAmount: uint256 - - refundGas: address - - _path: (address[],uint24[]) - outputs: - - bought: uint256 - emits: [BoughtFromMento, BoughtFromUniswap] - errors: [MENTO_NOT_CONFIGURED] - -getExpectedReturnFromUniswapPath: - notes: - - "Quote helper for Uniswap path expected output used by cUSD route selection and preflight checks." - mutability: nonpayable - inputs: - - amountIn: uint256 - - _path: (address[],uint24[]) - outputs: - - expectedReturn: uint256 - -getExpectedReturnFromMento: - notes: - - "Quote helper for Mento expected output for cUSD->G$ used in best-route decision." - mutability: view - inputs: - - cusdAmount: uint256 - outputs: - - expectedReturn: uint256 - errors: [MENTO_NOT_CONFIGURED] - -minAmountByTWAP: - mutability: view - inputs: - - baseAmount: uint256 - - baseToken: address - - period: uint32 - outputs: - - minTwap: uint256 - - quote: uint256 - -recover: - mutability: nonpayable - access: anyone - inputs: - - token: address - outputs: [] - errors: [REFUND_FAILED] - -router: - mutability: view - inputs: [] - outputs: - - addr: address - -celo: - mutability: pure - inputs: [] - outputs: - - token: address - -CUSD: - mutability: pure - inputs: [] - outputs: - - token: address - -USDC: - mutability: pure - inputs: [] - outputs: - - token: address - -GLOUSD: - mutability: pure - inputs: [] - outputs: - - token: address - -GD_FEE_TIER: - mutability: pure - inputs: [] - outputs: - - tier: uint24 - -CUSD_STABLE_FEE_TIER: - mutability: pure - inputs: [] - outputs: - - tier: uint24 - -CELO_STABLE_FEE_TIER: - mutability: pure - inputs: [] - outputs: - - tier: uint24 - -twapPeriod: - mutability: view - inputs: [] - outputs: - - period: uint32 - -stable: - mutability: view - inputs: [] - outputs: - - token: address - -gd: - mutability: view - inputs: [] - outputs: - - token: address - -oracle: - mutability: view - inputs: [] - outputs: - - addr: address - -quoter: - mutability: view - inputs: [] - outputs: - - addr: address - -mentoBroker: - mutability: view - inputs: [] - outputs: - - addr: address - -mentoExchangeProvider: - mutability: view - inputs: [] - outputs: - - addr: address - -mentoExchangeId: - mutability: view - inputs: [] - outputs: - - id: bytes32 - -owner: - mutability: view - inputs: [] - outputs: - - addr: address - -CUSD_GAS_COSTS: - mutability: pure - inputs: [] - outputs: - - amount: uint256 - -events: - Bought: - indexed: [] - data: - - inToken: address - - inAmount: uint256 - - outAmount: uint256 - BoughtFromMento: - indexed: [] - data: - - inToken: address - - inAmount: uint256 - - outAmount: uint256 - BoughtFromUniswap: - indexed: [] - data: - - inToken: address - - inAmount: uint256 - - outAmount: uint256 - -errors: - REFUND_FAILED: "Refund call failed." - NO_BALANCE: "No CELO or cUSD available on clone." - MENTO_NOT_CONFIGURED: "Mento broker path not configured." diff --git a/.agents/skills/gooddollar/references/contracts/BuyGDCloneV2.selectors.yaml b/.agents/skills/gooddollar/references/contracts/BuyGDCloneV2.selectors.yaml deleted file mode 100644 index 1476ae35..00000000 --- a/.agents/skills/gooddollar/references/contracts/BuyGDCloneV2.selectors.yaml +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by scripts/selectors.mjs -functions: - initialize(address): 0xc4d66de8 - getSwapPath(address[],uint24[]): 0xf0036d5d - swap(uint256,address): 0xd3986f08 - swapCelo(uint256,address): 0xaa6bfa9d - swapCeloWithPath(uint256,address,(address[],uint24[])): 0x81b7d2e0 - swapCusd(uint256,address): 0xb1a5fa9f - swapCusdWithPath(uint256,address,(address[],uint24[])): 0x170ba915 - getExpectedReturnFromUniswapPath(uint256,(address[],uint24[])): 0x12194320 - getExpectedReturnFromMento(uint256): 0xdbb15eb2 - minAmountByTWAP(uint256,address,uint32): 0x821dc910 - recover(address): 0x0cd865ec - router(): 0xf887ea40 - celo(): 0x051ed8ef - CUSD(): 0x758316c9 - USDC(): 0x89a30271 - GLOUSD(): 0x4b5b02d6 - GD_FEE_TIER(): 0xe00e8fdd - CUSD_STABLE_FEE_TIER(): 0xba428926 - CELO_STABLE_FEE_TIER(): 0x60e4bf4b - twapPeriod(): 0xf6207326 - stable(): 0x22be3de1 - gd(): 0xa5e598fc - oracle(): 0x7dc0d1d0 - quoter(): 0xc6bbd5a7 - mentoBroker(): 0x7b89f117 - mentoExchangeProvider(): 0x4f62feec - mentoExchangeId(): 0xd373b333 - owner(): 0x8da5cb5b - CUSD_GAS_COSTS(): 0x32f90ac3 -events: - Bought(address,uint256,uint256): 0xa9a40dec7a304e5915d11358b968c1e8d365992abf20f82285d1df1b30c8e24c - BoughtFromMento(address,uint256,uint256): 0x8e2ac24d7ef5662ee242823a19dbd1c952b3e96ae127228f4bbce83e2816e3fb - BoughtFromUniswap(address,uint256,uint256): 0xdb1f2a6cbbfd964f19c648b140a35992aa80f482df8f519d3211d0bd86c9f335 -errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/CFAv1Forwarder.abi.yaml b/.agents/skills/gooddollar/references/contracts/CFAv1Forwarder.abi.yaml deleted file mode 100644 index 32ba0de5..00000000 --- a/.agents/skills/gooddollar/references/contracts/CFAv1Forwarder.abi.yaml +++ /dev/null @@ -1,237 +0,0 @@ -# CFAv1Forwarder — convenience wrapper for ConstantFlowAgreementV1 -# Allows direct interaction with CFA functions without manually routing through -# the Host's batchCall / forwardBatchCall. Operates as a trusted forwarder, -# preserving msg.sender via EIP-2771. -# -# Each forwarder call is a standalone transaction — forwarder calls cannot be -# combined in a Host.batchCall. To batch multiple operations atomically (e.g. -# wrap tokens + create stream), use Host.batchCall with the raw CFA agreement -# (operationType 201). See Superfluid.abi.yaml for batch operation details. -# Tradeoff: forwarder calls produce human-readable descriptions in wallets -# (e.g. "setFlowrate(token, receiver, flowrate)"), while Host.batchCall -# shows encoded tuples that are difficult for users to verify. -# -# This contract has no events — all events are emitted by the underlying CFA. -# Errors from the CFA (and Host) propagate through to the caller. - -meta: - name: CFAv1Forwarder - version: v1 - source: - - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/utils/CFAv1Forwarder.sol - - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/utils/ForwarderBase.sol - inherits: [ForwarderBase] - deployments: - # Same address on all networks except avalanche-fuji - mainnet: - eth-mainnet: "0xcfA132E353cB4E398080B9700609bb008eceB125" - polygon-mainnet: "0xcfA132E353cB4E398080B9700609bb008eceB125" - xdai-mainnet: "0xcfA132E353cB4E398080B9700609bb008eceB125" - base-mainnet: "0xcfA132E353cB4E398080B9700609bb008eceB125" - optimism-mainnet: "0xcfA132E353cB4E398080B9700609bb008eceB125" - arbitrum-one: "0xcfA132E353cB4E398080B9700609bb008eceB125" - bsc-mainnet: "0xcfA132E353cB4E398080B9700609bb008eceB125" - avalanche-c: "0xcfA132E353cB4E398080B9700609bb008eceB125" - celo-mainnet: "0xcfA132E353cB4E398080B9700609bb008eceB125" - scroll-mainnet: "0xcfA132E353cB4E398080B9700609bb008eceB125" - degenchain: "0xcfA132E353cB4E398080B9700609bb008eceB125" - testnet: - avalanche-fuji: "0x2CDd45c5182602a36d391F7F16DD9f8386C3bD8D" - base-sepolia: "0xcfA132E353cB4E398080B9700609bb008eceB125" - eth-sepolia: "0xcfA132E353cB4E398080B9700609bb008eceB125" - optimism-sepolia: "0xcfA132E353cB4E398080B9700609bb008eceB125" - scroll-sepolia: "0xcfA132E353cB4E398080B9700609bb008eceB125" - deploymentCreationBlocks: - mainnet: - celo-mainnet: 17404156 - -# == Glossary == -# flowrate — token transfer rate in wad/second (int96) -# flowOperator — account authorized to create/update/delete flows on behalf of another -# buffer — deposit locked as solvency collateral while a flow is active -# permissions — bitmask of create | update | delete rights for an operator -# flowrateAllowance — max flowrate an operator may set per individual flow - -# == Flow Management == -# High-level functions that automatically create, update, or delete flows as needed. - -setFlowrate: - # Smart setter: creates a new flow if none exists, updates if flowrate changed, - # deletes if flowrate is zero. No-ops if the current rate already matches. - notes: - - "Gotcha: Negative flowrate values revert with CFA_FWD_INVALID_FLOW_RATE." - mutability: nonpayable - access: anyone # flow from msg.sender - inputs: - - token: address - - receiver: address - - flowrate: int96 # must be >= 0 - outputs: - - bool - errors: [CFA_FWD_INVALID_FLOW_RATE] - -setFlowrateFrom: - # Same as setFlowrate but can be called by a flow operator on behalf of `sender`. - # msg.sender must have sufficient operator permissions and flowrateAllowance. - mutability: nonpayable - access: sender | operator - inputs: - - token: address - - sender: address - - receiver: address - - flowrate: int96 - outputs: - - bool - errors: [CFA_FWD_INVALID_FLOW_RATE] - -# == Low-Level Flow Operations == -# Direct wrappers around CFA functions. These give full control (including userData) -# but require the caller to know whether a flow already exists. -# If sender != msg.sender, the *ByOperator variant is used internally. - -createFlow: - # Create a new flow. Reverts if a flow already exists between sender and receiver. - mutability: nonpayable - access: sender | operator - inputs: - - token: address - - sender: address - - receiver: address - - flowrate: int96 - - userData: bytes - outputs: - - bool - -updateFlow: - # Update an existing flow's rate. Reverts if no flow exists. - mutability: nonpayable - access: sender | operator - inputs: - - token: address - - sender: address - - receiver: address - - flowrate: int96 - - userData: bytes - outputs: - - bool - -deleteFlow: - # Delete an existing flow. Can be called by sender, receiver, or an operator. - # If msg.sender is neither sender nor receiver, deleteFlowByOperator is used. - mutability: nonpayable - access: sender | receiver | operator - inputs: - - token: address - - sender: address - - receiver: address - - userData: bytes - outputs: - - bool - -# == ACL (Operator Permissions) == - -grantPermissions: - # Grant full create/update/delete permissions with max flowrateAllowance to an operator. - # Convenience wrapper — equivalent to updateFlowOperatorPermissions with full control. - mutability: nonpayable - access: anyone # grants on msg.sender's flows - inputs: - - token: address - - flowOperator: address - outputs: - - bool - -revokePermissions: - # Revoke all permissions from an operator. Does not affect existing flows. - mutability: nonpayable - access: anyone # revokes on msg.sender's flows - inputs: - - token: address - - flowOperator: address - outputs: - - bool - -updateFlowOperatorPermissions: - # Set granular operator permissions and flowrate allowance. - notes: - - "Gotcha: flowrateAllowance limits per-flow rate, NOT aggregate net flow." - mutability: nonpayable - access: anyone # grants on msg.sender's flows - inputs: - - token: address - - flowOperator: address - - permissions: uint8 # bitmask: 1=create, 2=update, 4=delete - - flowrateAllowance: int96 - outputs: - - bool - -# == Flow Queries == - -getFlowrate: - mutability: view - inputs: - - token: address - - sender: address - - receiver: address - outputs: - - flowrate: int96 - -getFlowInfo: - mutability: view - inputs: - - token: address - - sender: address - - receiver: address - outputs: - - lastUpdated: uint256 - - flowrate: int96 - - deposit: uint256 - - owedDeposit: uint256 - -getBufferAmountByFlowrate: - # Returns the deposit/buffer required for a given flowrate. - notes: - - "Gotcha: This value is governance-configurable and may change over time. Changes only affect newly created/updated flows." - mutability: view - inputs: - - token: address - - flowrate: int96 - outputs: - - bufferAmount: uint256 - -getAccountFlowrate: - # Net aggregate flowrate for an account (incoming minus outgoing). - mutability: view - inputs: - - token: address - - account: address - outputs: - - flowrate: int96 - -getAccountFlowInfo: - mutability: view - inputs: - - token: address - - account: address - outputs: - - lastUpdated: uint256 - - flowrate: int96 - - deposit: uint256 - - owedDeposit: uint256 - -# == ACL Queries == - -getFlowOperatorPermissions: - mutability: view - inputs: - - token: address - - sender: address - - flowOperator: address - outputs: - - permissions: uint8 - - flowrateAllowance: int96 - -# == Errors == - -errors: - - CFA_FWD_INVALID_FLOW_RATE # flowrate argument was negative diff --git a/.agents/skills/gooddollar/references/contracts/CFAv1Forwarder.selectors.yaml b/.agents/skills/gooddollar/references/contracts/CFAv1Forwarder.selectors.yaml deleted file mode 100644 index f98e21ea..00000000 --- a/.agents/skills/gooddollar/references/contracts/CFAv1Forwarder.selectors.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by scripts/selectors.mjs -functions: - setFlowrate(address,address): 0x2f57fb6f - setFlowrateFrom(address,address,address,int96): 0xc5ad5c1a - createFlow(address,address,address,int96,bytes): 0xe15536b6 - updateFlow(address,address,address,int96,bytes): 0x0c033991 - deleteFlow(address,address,address,bytes): 0xb4b333c6 - grantPermissions(address,address): 0x7243fb93 - revokePermissions(address,address): 0x0bd0728d - updateFlowOperatorPermissions(address,address): 0x42294caf - getFlowrate(address,address,address): 0x1d8b6526 - getFlowInfo(address,address,address): 0x2860fd93 - getBufferAmountByFlowrate(address,int96): 0x09f0b495 - getAccountFlowrate(address,address): 0x22c904d9 - getAccountFlowInfo(address,address): 0x0f1ac495 - getFlowOperatorPermissions(address,address,address): 0x4d3f60f9 -events: -{} -errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/ConstantFlowAgreementV1.abi.yaml b/.agents/skills/gooddollar/references/contracts/ConstantFlowAgreementV1.abi.yaml deleted file mode 100644 index afaec5dc..00000000 --- a/.agents/skills/gooddollar/references/contracts/ConstantFlowAgreementV1.abi.yaml +++ /dev/null @@ -1,560 +0,0 @@ -# Superfluid Constant Flow Agreement (CFA) v1 -# Manages continuous per-second token streams between accounts. -# -# NOTE: emits/errors mappings are traced from source code — verify against implementation. -# Proxy/upgradability functions (castrate, updateCode, getCodeAddress, proxiableUUID) -# are omitted — they belong to the UUPSProxiable / AgreementBase layer. -# Pure helpers addPermissions/removePermissions are omitted — they were made public -# for testability only and are not part of the protocol interface. - -meta: - name: ConstantFlowAgreementV1 - version: v1 - source: - - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol - - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/interfaces/agreements/IConstantFlowAgreementV1.sol - - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/agreements/AgreementBase.sol - implements: [IConstantFlowAgreementV1, ISuperAgreement] - inherits: [AgreementBase] - deployments: - mainnet: - eth-mainnet: "0x2844c1BBdA121E9E43105630b9C8310e5c72744b" - polygon-mainnet: "0x6EeE6060f715257b970700bc2656De21dEdF074C" - xdai-mainnet: "0xEbdA4ceF883A7B12c4E669Ebc58927FBa8447C7D" - base-mainnet: "0x19ba78B9cDB05A877718841c574325fdB53601bb" - optimism-mainnet: "0x204C6f131bb7F258b2Ea1593f5309911d8E458eD" - arbitrum-one: "0x731FdBB12944973B500518aea61942381d7e240D" - bsc-mainnet: "0x49c38108870e74Cb9420C0991a85D3edd6363F75" - avalanche-c: "0x6946c5B38Ffea373b0a2340b4AEf0De8F6782e58" - celo-mainnet: "0x9d369e78e1a682cE0F8d9aD849BeA4FE1c3bD3Ad" - scroll-mainnet: "0xB3bcD6da1eeB6c97258B3806A853A6dcD3B6C00c" - degenchain: "0x82cc052d1b17aC554a22A88D5876B56c6b51e95c" - testnet: - avalanche-fuji: "0x16843ac25Ccc58Aa7960ba05f61cBB17b36b130A" - base-sepolia: "0x6836F23d6171D74Ef62FcF776655aBcD2bcd62Ef" - eth-sepolia: "0x6836F23d6171D74Ef62FcF776655aBcD2bcd62Ef" - optimism-sepolia: "0x8a3170AdbC67233196371226141736E4151e7C26" - scroll-sepolia: "0xbc46B4Aa41c055578306820013d4B65fff42711E" - deploymentCreationBlocks: - mainnet: - celo-mainnet: 16393492 - -# == Abbreviations == -# 3Ps — Three Periods: liquidation, patrician, and pleb (solvency model) -# ACL — Access Control List (flow operator permissions) -# CFA — Constant Flow Agreement -# ctx — context (Superfluid call context bytes, carries msg.sender and userData) -# GDA — General Distribution Agreement (the other Superfluid agreement) -# PPP — Patrician-Pleb-Pirate (the 3Ps solvency periods) - -# == Glossary == -# flow / stream — continuous per-second token transfer, used interchangeably -# flowRate — tokens per second (int96, wei-denominated); positive = outgoing -# deposit — buffer locked when opening a flow; protects against insolvency -# owed deposit — portion of deposit owed back to the sender by a Super App receiver -# flow operator — an address authorized to create/update/delete flows on behalf of a sender -# flow rate allowance — maximum net flow rate an operator may allocate (int96); decrements on use -# permissions bitmask — uint8: bit 0 = create (1), bit 1 = update (2), bit 2 = delete (4) -# liquidation — closing an insolvent sender's flow; rewards go to the liquidator or bond -# patrician period — grace window after insolvency where reward goes to the bond account -# pleb period — window after patrician where reward goes to the liquidator -# pirate / bailout — state where total deposit cannot cover the deficit; protocol bails out -# Super App — a contract registered with the Host that receives agreement callbacks -# app credit — deposit credit enabling "zero-balance Super Apps" to relay flows -# without pre-funded tokens. 1:1 relay (one in, one out at same rate) -# always works at zero balance. Fan-out (1:N) needs the app to hold -# tokens. Credit is settled as "owed deposit" on the original sender. -# critical — an account whose available balance is negative (eligible for liquidation) -# jailed — a Super App penalized for violating protocol rules; loses callbacks -# -# == Time Conventions == -# Conventional seconds-per-period used in Superfluid apps for flow rate conversion: -# month: 2628000 (365.25 / 12 * 86400) -# year: 31536000 (365 * 86400) -# Flow rate from a monthly amount: flowRate = monthlyAmount / 2628000 -# Flow rate from a yearly amount: flowRate = yearlyAmount / 31536000 - -# == Flow Management == -# Core operations for creating, updating, and deleting token streams. -# Functions with ctx are called through the Host — either via -# Host.callAgreement (single op) or Host.batchCall (operationType 201). -# For batchCall, the data field is abi.encode(callData, userData) where -# callData is the full ABI-encoded function call with an empty ctx ("0x") -# as placeholder. The Host replaces the placeholder with the real context. -# The access field reflects who can initiate the call, not the direct caller. - -createFlow: - # Start a new stream from ctx.msgSender to receiver. - # The deposit amount depends on the governance-configured liquidation period - # and minimum deposit. The lower 32 bits of the deposit are clipped (rounded up). - notes: - - "Gotcha: A deposit is taken as a safety margin for solvency agents." - mutability: nonpayable - access: anyone # flow from ctx.msgSender - inputs: - - token: address - - receiver: address - - flowRate: int96 # must be > 0 - - ctx: bytes - outputs: - - newCtx: bytes - emits: [FlowUpdated, FlowUpdatedExtension] - errors: [CFA_ZERO_ADDRESS_RECEIVER, CFA_NO_SELF_FLOW, CFA_INVALID_FLOW_RATE, CFA_FLOW_ALREADY_EXISTS, CFA_INSUFFICIENT_BALANCE] - -updateFlow: - # Change the flow rate of an existing stream from ctx.msgSender to receiver. - # Note: owedDeposit accumulates when a Super App receiver uses app credit to - # open outgoing streams. The sender bears the deposit cost for those streams. - notes: - - "Gotcha: Deposit is adjusted to match the new flow rate but never refunds owed deposit — that adjustment happens separately via the app credit system." - mutability: nonpayable - access: anyone # flow from ctx.msgSender - inputs: - - token: address - - receiver: address - - flowRate: int96 # must be > 0 - - ctx: bytes - outputs: - - newCtx: bytes - emits: [FlowUpdated, FlowUpdatedExtension] - errors: [CFA_ZERO_ADDRESS_RECEIVER, CFA_NO_SELF_FLOW, CFA_INVALID_FLOW_RATE, CFA_FLOW_DOES_NOT_EXIST, CFA_INSUFFICIENT_BALANCE] - -deleteFlow: - # Stop a stream between sender and receiver. - # During liquidation the reward distribution depends on the solvency period: - # patrician → bond account gets reward; pleb → liquidator gets reward; - # pirate → liquidator gets full single deposit, protocol covers the bailout. - notes: - - "Gotcha: Third-party callers (not sender, receiver, or operator) only succeed if the sender is critical (negative available balance) or either party is jailed." - mutability: nonpayable - access: sender | receiver | operator | anyone(if-critical-or-jailed) - inputs: - - token: address - - sender: address - - receiver: address - - ctx: bytes - outputs: - - newCtx: bytes - emits: [FlowUpdated, FlowUpdatedExtension] - errors: [CFA_ZERO_ADDRESS_SENDER, CFA_ZERO_ADDRESS_RECEIVER, CFA_FLOW_DOES_NOT_EXIST, CFA_NON_CRITICAL_SENDER] - -# == Operator Flow Management == -# Act on behalf of another account. Requires ACL permissions granted by the sender. -# The operator's flow rate allowance is decremented on create/update. -# Functions with ctx are called through the Host. - -createFlowByOperator: - # Create a flow on behalf of sender. Consumes flow rate allowance. - notes: - - "Gotcha: Reverts if ctx.msgSender IS the sender — use createFlow instead." - mutability: nonpayable - access: operator - inputs: - - token: address - - sender: address - - receiver: address - - flowRate: int96 # must be > 0 - - ctx: bytes - outputs: - - newCtx: bytes - emits: [FlowUpdated, FlowUpdatedExtension] - errors: [CFA_ACL_NO_SENDER_CREATE, CFA_ACL_OPERATOR_NO_CREATE_PERMISSIONS, CFA_ACL_FLOW_RATE_ALLOWANCE_EXCEEDED, CFA_ZERO_ADDRESS_RECEIVER, CFA_NO_SELF_FLOW, CFA_INVALID_FLOW_RATE, CFA_FLOW_ALREADY_EXISTS, CFA_INSUFFICIENT_BALANCE] - -updateFlowByOperator: - # Update a flow on behalf of sender. Only consumes allowance if flow rate increases. - # If flowRateAllowance is type(int96).max, it is treated as unlimited. - notes: - - "Gotcha: If the new rate is lower than the old rate, no allowance is consumed." - mutability: nonpayable - access: operator - inputs: - - token: address - - sender: address - - receiver: address - - flowRate: int96 # must be > 0 - - ctx: bytes - outputs: - - newCtx: bytes - emits: [FlowUpdated, FlowUpdatedExtension] - errors: [CFA_ACL_NO_SENDER_UPDATE, CFA_ACL_OPERATOR_NO_UPDATE_PERMISSIONS, CFA_ACL_FLOW_RATE_ALLOWANCE_EXCEEDED, CFA_ZERO_ADDRESS_RECEIVER, CFA_NO_SELF_FLOW, CFA_INVALID_FLOW_RATE, CFA_FLOW_DOES_NOT_EXIST, CFA_INSUFFICIENT_BALANCE] - -deleteFlowByOperator: - # Delete a flow on behalf of sender. Does not consume flow rate allowance. - mutability: nonpayable - access: operator - inputs: - - token: address - - sender: address - - receiver: address - - ctx: bytes - outputs: - - newCtx: bytes - emits: [FlowUpdated, FlowUpdatedExtension] - errors: [CFA_ACL_OPERATOR_NO_DELETE_PERMISSIONS, CFA_ZERO_ADDRESS_SENDER, CFA_ZERO_ADDRESS_RECEIVER, CFA_FLOW_DOES_NOT_EXIST, CFA_NON_CRITICAL_SENDER] - -# == Flow Queries == - -realtimeBalanceOf: - # Compute the real-time CFA balance contribution for an account. - # Returns the dynamic balance delta (flowRate * elapsed), total deposit held, - # and total owed deposit. This is called by the token's realtimeBalanceOfNow - # to assemble the full balance across all agreements. - # To get an account's actual token balance, use SuperToken.realtimeBalanceOfNow - # (or SuperToken.balanceOf for ERC-20 compatible, clamped to zero). - # It can be deeply negative for net-outflow accounts as time progresses. - notes: - - "Gotcha: Returns only the CFA portion of the balance, not the total." - - "Gotcha: The returned dynamicBalance is a delta, not an absolute balance." - mutability: view - inputs: - - token: address - - account: address - - time: uint256 - outputs: - - dynamicBalance: int256 # flowRate * (time - lastUpdate); can be negative - - deposit: uint256 # total deposit locked across all outflows - - owedDeposit: uint256 # total owed deposit from Super App receivers - -getFlow: - # Get flow data between a specific sender-receiver pair. - mutability: view - inputs: - - token: address - - sender: address - - receiver: address - outputs: - - timestamp: uint256 # last update time - - flowRate: int96 - - deposit: uint256 - - owedDeposit: uint256 - -getFlowByID: - # Get flow data using the agreement ID (keccak256 of sender and receiver). - mutability: view - inputs: - - token: address - - flowId: bytes32 # keccak256(abi.encode(sender, receiver)) - outputs: - - timestamp: uint256 - - flowRate: int96 - - deposit: uint256 - - owedDeposit: uint256 - -getAccountFlowInfo: - # Aggregated flow state for an account across all its CFA flows. - mutability: view - inputs: - - token: address - - account: address - outputs: - - timestamp: uint256 # last time any flow was updated for this account - - flowRate: int96 # net flow rate (inflows - outflows) - - deposit: uint256 # sum of deposits across all outflows - - owedDeposit: uint256 # sum of owed deposits across all outflows - -getNetFlow: - # Net flow rate for an account (sum of inflows minus outflows). - mutability: view - inputs: - - token: address - - account: address - outputs: - - flowRate: int96 - -# == ACL Management == -# Manage flow operator permissions and flow rate allowances. -# The caller (ctx.msgSender) is always the permission granter — they control -# who can operate on their own flows. -# Functions with ctx are called through the Host. - -updateFlowOperatorPermissions: - # Set exact permissions and flow rate allowance for a flow operator. - mutability: nonpayable - access: anyone # grants on ctx.msgSender's flows - inputs: - - token: address - - flowOperator: address - - permissions: uint8 # bitmask: 1=create, 2=update, 4=delete - - flowRateAllowance: int96 - - ctx: bytes - outputs: - - newCtx: bytes - emits: [FlowOperatorUpdated] - errors: [CFA_ACL_UNCLEAN_PERMISSIONS, CFA_ACL_NO_SENDER_FLOW_OPERATOR, CFA_ACL_NO_NEGATIVE_ALLOWANCE] - -authorizeFlowOperatorWithFullControl: - # Grant all permissions (create+update+delete) with unlimited allowance. - # Shorthand for updateFlowOperatorPermissions(token, op, 7, type(int96).max, ctx). - mutability: nonpayable - access: anyone # grants on ctx.msgSender's flows - inputs: - - token: address - - flowOperator: address - - ctx: bytes - outputs: - - newCtx: bytes - emits: [FlowOperatorUpdated] - errors: [CFA_ACL_UNCLEAN_PERMISSIONS, CFA_ACL_NO_SENDER_FLOW_OPERATOR, CFA_ACL_NO_NEGATIVE_ALLOWANCE] - -revokeFlowOperatorWithFullControl: - # Revoke all permissions and set allowance to zero. - # Shorthand for updateFlowOperatorPermissions(token, op, 0, 0, ctx). - mutability: nonpayable - access: anyone # revokes on ctx.msgSender's flows - inputs: - - token: address - - flowOperator: address - - ctx: bytes - outputs: - - newCtx: bytes - emits: [FlowOperatorUpdated] - errors: [CFA_ACL_UNCLEAN_PERMISSIONS, CFA_ACL_NO_SENDER_FLOW_OPERATOR, CFA_ACL_NO_NEGATIVE_ALLOWANCE] - -increaseFlowRateAllowance: - # Increase flow rate allowance for an operator by a delta. - # Delegates to increaseFlowRateAllowanceWithPermissions with permissionsToAdd=0. - mutability: nonpayable - access: anyone # ctx.msgSender's allowance - inputs: - - token: address - - flowOperator: address - - addedFlowRateAllowance: int96 - - ctx: bytes - outputs: - - newCtx: bytes - emits: [FlowOperatorUpdated] - errors: [CFA_ACL_UNCLEAN_PERMISSIONS, CFA_ACL_NO_SENDER_FLOW_OPERATOR, CFA_ACL_NO_NEGATIVE_ALLOWANCE] - -decreaseFlowRateAllowance: - # Decrease flow rate allowance for an operator by a delta. - # Delegates to decreaseFlowRateAllowanceWithPermissions with permissionsToRemove=0. - mutability: nonpayable - access: anyone # ctx.msgSender's allowance - inputs: - - token: address - - flowOperator: address - - subtractedFlowRateAllowance: int96 - - ctx: bytes - outputs: - - newCtx: bytes - emits: [FlowOperatorUpdated] - errors: [CFA_ACL_UNCLEAN_PERMISSIONS, CFA_ACL_NO_SENDER_FLOW_OPERATOR, CFA_ACL_NO_NEGATIVE_ALLOWANCE] - -increaseFlowRateAllowanceWithPermissions: - # Increase flow rate allowance and add permission bits in one call. - # permissionsToAdd is OR'd with existing permissions. - mutability: nonpayable - access: anyone # ctx.msgSender's allowance - inputs: - - token: address - - flowOperator: address - - permissionsToAdd: uint8 # bitmask OR'd onto existing permissions - - addedFlowRateAllowance: int96 - - ctx: bytes - outputs: - - newCtx: bytes - emits: [FlowOperatorUpdated] - errors: [CFA_ACL_UNCLEAN_PERMISSIONS, CFA_ACL_NO_SENDER_FLOW_OPERATOR, CFA_ACL_NO_NEGATIVE_ALLOWANCE] - -decreaseFlowRateAllowanceWithPermissions: - # Decrease flow rate allowance and remove permission bits in one call. - # permissionsToRemove is AND-NOT'd from existing permissions. - notes: - - "Gotcha: Reverts if the resulting allowance would go negative." - mutability: nonpayable - access: anyone # ctx.msgSender's allowance - inputs: - - token: address - - flowOperator: address - - permissionsToRemove: uint8 # bitmask AND-NOT'd from existing permissions - - subtractedFlowRateAllowance: int96 - - ctx: bytes - outputs: - - newCtx: bytes - emits: [FlowOperatorUpdated] - errors: [CFA_ACL_UNCLEAN_PERMISSIONS, CFA_ACL_NO_SENDER_FLOW_OPERATOR, CFA_ACL_NO_NEGATIVE_ALLOWANCE] - -# == ACL Queries == - -getFlowOperatorData: - # Get permissions and allowance for a flow operator. - mutability: view - inputs: - - token: address - - sender: address # the permission granter - - flowOperator: address # the permission grantee - outputs: - - flowOperatorId: bytes32 # keccak256(abi.encode("flowOperator", sender, flowOperator)) - - permissions: uint8 # bitmask: 1=create, 2=update, 4=delete - - flowRateAllowance: int96 - -getFlowOperatorDataByID: - # Get permissions and allowance using the pre-computed operator ID. - mutability: view - inputs: - - token: address - - flowOperatorId: bytes32 - outputs: - - permissions: uint8 - - flowRateAllowance: int96 - -# == Solvency Queries == -# Check whether an account is in the patrician period (grace window for liquidation). - -isPatricianPeriodNow: - # Check patrician period status using the Host's current timestamp. - mutability: view - inputs: - - token: address - - account: address - outputs: - - isCurrentlyPatricianPeriod: bool - - timestamp: uint256 # the Host's block.timestamp used for the check - -isPatricianPeriod: - # Check patrician period status at a specific timestamp. - mutability: view - inputs: - - token: address - - account: address - - timestamp: uint256 - outputs: - - bool - -# == Deposit Helpers == - -getMaximumFlowRateFromDeposit: - # Calculate the maximum flow rate achievable with a given deposit. - notes: - - "Gotcha: Deposit is clipped (lower 32 bits zeroed) and rounded down." - mutability: view - inputs: - - token: address # needed to look up liquidation period from governance - - deposit: uint256 - outputs: - - flowRate: int96 - errors: [CFA_DEPOSIT_TOO_BIG] - -getDepositRequiredForFlowRate: - # Calculate the deposit required for a given flow rate. - # Returns max(minimumDeposit, flowRate * liquidationPeriod) with rounding. - mutability: view - inputs: - - token: address # needed to look up liquidation period and minimum deposit - - flowRate: int96 - outputs: - - deposit: uint256 - errors: [CFA_INVALID_FLOW_RATE, CFA_FLOW_RATE_TOO_BIG] - -# == Protocol Constants == - -agreementType: - # Returns keccak256("org.superfluid-finance.agreements.ConstantFlowAgreement.v1") - mutability: pure - outputs: - - bytes32 - -DEFAULT_MINIMUM_DEPOSIT: - # Minimum deposit floor: uint96(1 << 32) ≈ 4.29 * 10^9 wei. - # Governance may set a higher per-token minimum; this is the absolute floor. - mutability: view - outputs: - - uint256 - -MAXIMUM_DEPOSIT: - # Maximum deposit cap: type(int96).max - mutability: view - outputs: - - uint256 - -MAXIMUM_FLOW_RATE: - # Maximum flow rate: type(int96).max - mutability: view - outputs: - - uint256 - -CFA_HOOK_GAS_LIMIT: - # Gas limit for external hook calls (Super App callbacks): 250,000 - mutability: view - outputs: - - uint64 - -# == Events == - -events: - FlowUpdated: - # Emitted on every create, update, and delete flow operation. - # totalSenderFlowRate and totalReceiverFlowRate are the NET flow rates - # after the operation, not the individual flow rate. - notes: - - "Gotcha: Always emitted together with FlowUpdatedExtension." - indexed: - - token: address - - sender: address - - receiver: address - data: - - flowRate: int96 # new rate for this specific flow (0 on delete) - - totalSenderFlowRate: int256 # sender's net flow rate after this operation - - totalReceiverFlowRate: int256 - - userData: bytes - - FlowUpdatedExtension: - # Companion event to FlowUpdated. Carries the operator and new deposit. - # Emitted immediately after FlowUpdated so indexers can correlate them. - indexed: - - flowOperator: address # ctx.msgSender who initiated the operation - data: - - deposit: uint256 # new deposit for this specific flow - - FlowOperatorUpdated: - # Emitted when operator permissions or flow rate allowance change. - indexed: - - token: address - - sender: address # the permission granter - - flowOperator: address # the permission grantee - data: - - permissions: uint8 # updated bitmask - - flowRateAllowance: int96 # updated allowance - - # Inherited events (from AgreementBase / UUPSProxiable): - # CodeUpdated — emitted on proxy upgrade (uuid, codeAddress) - # Initialized — emitted on proxy initialization (version) - -# == Errors == - -errors: - # Flow validation - - CFA_FLOW_ALREADY_EXISTS # createFlow when flow exists - - CFA_FLOW_DOES_NOT_EXIST # update/delete when no flow - - CFA_INVALID_FLOW_RATE # flowRate <= 0 - - CFA_NO_SELF_FLOW # sender == receiver - - CFA_ZERO_ADDRESS_SENDER # sender is address(0) - - CFA_ZERO_ADDRESS_RECEIVER # receiver is address(0) - # Solvency - - CFA_INSUFFICIENT_BALANCE # sender cannot cover deposit - - CFA_NON_CRITICAL_SENDER # third-party delete but sender is solvent - - CFA_DEPOSIT_TOO_BIG # deposit > MAXIMUM_DEPOSIT - - CFA_FLOW_RATE_TOO_BIG # flowRate * liquidationPeriod overflows - # ACL - - CFA_ACL_FLOW_RATE_ALLOWANCE_EXCEEDED # operator exceeds granted allowance - - CFA_ACL_NO_NEGATIVE_ALLOWANCE # resulting allowance would be < 0 - - CFA_ACL_NO_SENDER_CREATE # operator cannot be the sender (use createFlow) - - CFA_ACL_NO_SENDER_FLOW_OPERATOR # cannot set yourself as your own operator - - CFA_ACL_NO_SENDER_UPDATE # operator cannot be the sender (use updateFlow) - - CFA_ACL_OPERATOR_NO_CREATE_PERMISSIONS - - CFA_ACL_OPERATOR_NO_DELETE_PERMISSIONS - - CFA_ACL_OPERATOR_NO_UPDATE_PERMISSIONS - - CFA_ACL_UNCLEAN_PERMISSIONS # permission bits outside valid range - # Super App / Host - - CFA_HOOK_OUT_OF_GAS # Super App callback exceeded gas limit - - APP_RULE: # Super App rule violation - inputs: - - _code: uint256 - - AGREEMENT_BASE_ONLY_HOST # call not routed through the Host contract - # SafeCast (inherited from OpenZeppelin) - - SafeCastOverflowedIntToUint: # int256 value overflows on cast to uint256 - inputs: - - value: int256 - - SafeCastOverflowedUintToInt: # uint256 value overflows on cast to int256 - inputs: - - value: uint256 diff --git a/.agents/skills/gooddollar/references/contracts/ConstantFlowAgreementV1.selectors.yaml b/.agents/skills/gooddollar/references/contracts/ConstantFlowAgreementV1.selectors.yaml deleted file mode 100644 index a929e06c..00000000 --- a/.agents/skills/gooddollar/references/contracts/ConstantFlowAgreementV1.selectors.yaml +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by scripts/selectors.mjs -functions: - createFlow(address,address): 0x7deac86a - updateFlow(address,address): 0xb6214437 - deleteFlow(address,address,address,bytes): 0xb4b333c6 - createFlowByOperator(address,address,address): 0x97b0b745 - updateFlowByOperator(address,address,address): 0xaea4dc29 - deleteFlowByOperator(address,address,address,bytes): 0x4c8b181f - realtimeBalanceOf(address,address,uint256): 0x9b2e48bc - getFlow(address,address,address): 0xe6a1e888 - getFlowByID(address): 0x4eccd8ac - getAccountFlowInfo(address,address): 0x0f1ac495 - getNetFlow(address,address): 0xe8e7e2d1 - updateFlowOperatorPermissions(address,address): 0x42294caf - authorizeFlowOperatorWithFullControl(address,address,bytes): 0x54b770e3 - revokeFlowOperatorWithFullControl(address,address,bytes): 0x062e56ec - increaseFlowRateAllowance(address,address,int96,bytes): 0xac5f5d00 - decreaseFlowRateAllowance(address,address,int96,bytes): 0x5f51fb23 - increaseFlowRateAllowanceWithPermissions(address,address): 0x5e6a4dc3 - decreaseFlowRateAllowanceWithPermissions(address,address): 0x8de39d9b - getFlowOperatorData(address): 0x5f13dbbc - getFlowOperatorDataByID(address,bytes32): 0x09d256ef - isPatricianPeriodNow(address,address): 0x4fe9c291 - isPatricianPeriod(address,address,uint256): 0x4b839e0b - getMaximumFlowRateFromDeposit(): 0x4ba52c34 - getDepositRequiredForFlowRate(): 0xcc111b9e - CFA_HOOK_GAS_LIMIT(): 0xbf3fbc28 -events: - FlowUpdated(address,address,address,int256,bytes): 0x8e3b8f31fe09d2ca20fa7f76ec574bc9fea49f16a91f1ca828154ea76e76a20a - FlowUpdatedExtension(): 0x9bf0b3e0199b411ca09607d040b7672430af5dae9d159b715c9f8004c876065f - FlowOperatorUpdated(address): 0x401d2cb05b3db80617d70aabf976f52d2a5dac64298eadeed7a81ad4bb5d4fdd - # Inherited events (from AgreementBase / UUPSProxiable)(): 0xcabfd4ad95e3bb803739ac513717fccee724fc0c5741e01cf84319a96120eefa -errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/GoodDollarOFTAdapter.abi.yaml b/.agents/skills/gooddollar/references/contracts/GoodDollarOFTAdapter.abi.yaml deleted file mode 100644 index c60d4985..00000000 --- a/.agents/skills/gooddollar/references/contracts/GoodDollarOFTAdapter.abi.yaml +++ /dev/null @@ -1,100 +0,0 @@ -# GoodDollarOFTAdapter — LayerZero V2 OFT adapter pattern bridging underlying G$ -# Exact bytecode lives in the GoodDollar OFT deployment; this documents the surface agents call. - -meta: - name: GoodDollarOFTAdapter - version: LayerZero-OFT-adapter (generic) - source: - - https://docs.layerzero.network/ - - https://github.com/LayerZero-Labs/devtools - note: > - Flow: optional peers(dstEid) check -> approve underlying to minterBurner if - MessagingFee, refundAddress) paying nativeFee in msg.value when applicable. - SendParam mirrors LayerZero OFT: dstEid, to (bytes32 padded recipient), - amountLD, minAmountLD, extraOptions, composeMsg, oftCmd. - Verify tuple field order and OFT revision against your deployed artifact before mainnet use. - Event names and topic0 hashes vary by OFT package version — pull from the deployment ABI. - related: - - references/guides/bridge.md - -token: - mutability: view - inputs: [] - outputs: - - underlyingToken: address - -minterBurner: - notes: - - "Underlying G$ must approve this spender when burning for cross-chain send." - mutability: view - inputs: [] - outputs: - - minterBurner: address - -oftVersion: - mutability: view - inputs: [] - outputs: - - interfaceId: bytes4 - - version: uint64 - -peers: - notes: - - "Returns bytes32 peer address configured for destination endpoint id." - mutability: view - inputs: - - dstEid: uint32 - outputs: - - peer: bytes32 - -endpoint: - mutability: view - inputs: [] - outputs: - - lzEndpoint: address - -owner: - mutability: view - inputs: [] - outputs: - - account: address - -quoteSend: - notes: - - "Simulates messaging + bridge fee; use nativeFee as msg.value on send when paying in native gas token." - mutability: view - inputs: - - sendParam: SendParam - - payInLzToken: bool - outputs: - - nativeFee: uint256 - - lzTokenFee: uint256 - errors: - - NO_PEER - - LZ_INVALID_OPTIONS - -send: - notes: - - "Payable: include MessagingFee.nativeFee in msg.value when fee is native." - - "Consumes sendParam.amountLD from sender on source chain and emits cross-chain message to dstEid peer." - - "sendParam.to must be destination receiver encoded as bytes32." - mutability: payable - access: sender - inputs: - - sendParam: SendParam - - fee: MessagingFee - - refundAddress: address - outputs: - - msgReceipt: MessageReceipt - - oftReceipt: OFTReceipt - errors: - - NO_PEER - - SLIPPAGE_OR_AMOUNT - - LZ_INVALID_OPTIONS - -events: {} - -errors: - NO_PEER: "Destination peer not configured for dstEid." - LZ_INVALID_OPTIONS: "Composer/options payload rejected by LayerZero." - SLIPPAGE_OR_AMOUNT: "Bridged amount violates minAmount or available balance." diff --git a/.agents/skills/gooddollar/references/contracts/GoodDollarOFTAdapter.selectors.yaml b/.agents/skills/gooddollar/references/contracts/GoodDollarOFTAdapter.selectors.yaml deleted file mode 100644 index e5615a1e..00000000 --- a/.agents/skills/gooddollar/references/contracts/GoodDollarOFTAdapter.selectors.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# Generated by scripts/selectors.mjs -functions: - token(): 0xfc0c546a - minterBurner(): 0x2ef8c5a4 - approvalRequired(): 0x9f68b964 - oftVersion(): 0x156a0d0f - peers(uint32): 0xbb0b6a53 - endpoint(): 0x5e280f11 - owner(): 0x8da5cb5b -events: -{} -errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/GooddollarSavingsStream.abi.yaml b/.agents/skills/gooddollar/references/contracts/GooddollarSavingsStream.abi.yaml deleted file mode 100644 index d736bc19..00000000 --- a/.agents/skills/gooddollar/references/contracts/GooddollarSavingsStream.abi.yaml +++ /dev/null @@ -1,210 +0,0 @@ -# GooddollarSavingsStream (Ubeswap) — G$ native Super Token stake with Superfluid GDA reward stream - -meta: - name: GooddollarSavingsStream - version: "1" - source: - - https://raw.githubusercontent.com/Ubeswap/gooddollar-contracts/main/contracts/GooddollarSavingsStream.sol - - https://raw.githubusercontent.com/Ubeswap/gooddollar-contracts/main/contracts/interfaces/IGooddollarSavingsStream.sol - - https://raw.githubusercontent.com/Ubeswap/gooddollar-contracts/main/contracts/StakingVault.sol - inherits: - - IGooddollarSavingsStream - - Ownable - - ERC2771Context - - ReentrancyGuard - note: > - Celo production savings. Staked principal sits in StakingVault; this contract holds reward - balance and streams via a Superfluid GDA distribution pool (`distributeFlow`). Effective rate is - min(rewardRate, maxRewardRatePerToken * totalSupply / 1e18), throttled when reward balance is low. - Staking token is the G$ native Super Token (`superToken`). Fuse->CELO migration uses `stakeFor` - after bridge. Ubeswap lists this as Celo mainnet savings (streaming). - deployments: - mainnet: - production-celo: - GooddollarSavingsStream: - networkId: 42220 - address: "0x059ee811414230d1Fb157878D2b491240F4D8d3B" - creationBlock: 66685884 - related: - - https://github.com/Ubeswap/gooddollar-contracts/blob/main/README.md - - https://celoscan.io/address/0x059ee811414230d1Fb157878D2b491240F4D8d3B - - references/contracts/SuperToken.abi.yaml - - references/guides/migrate-fuse-staking-to-celo-savings.md - - references/deep-researches/fuse-to-celo-staking-migration.md - -totalSupply: - mutability: view - inputs: [] - outputs: - - supply: uint256 - -balanceOf: - mutability: view - inputs: - - account: address - outputs: - - balance: uint256 - -getDailyRewards: - mutability: view - inputs: [] - outputs: - - daily: uint256 - -getEffectiveFlowRate: - notes: - - "Returns 0 when totalSupply is 0, rewardRate is 0, or reward balance cannot sustain MIN_STREAM_BUFFER_SECONDS." - - "Otherwise min(global rewardRate, APR cap) as int96 for Superfluid distributeFlow." - mutability: view - inputs: [] - outputs: - - flowRate: int96 - -periodFinish: - notes: - - "Estimated timestamp when current reward balance is drained at getEffectiveFlowRate; 0 if not streaming." - mutability: view - inputs: [] - outputs: - - finish: uint256 - -getUnits: - mutability: view - inputs: - - account: address - outputs: - - units: uint128 - -getTotalUnits: - mutability: view - inputs: [] - outputs: - - units: uint128 - -stake: - notes: - - "Pulls G$ Super Token from msg.sender, deposits principal to StakingVault, updates GDA pool units, syncFlowRate." - - "Requires prior superToken.approve(this, amount)." - mutability: nonpayable - access: anyone - inputs: - - amount: uint256 - outputs: [] - emits: [Staked] - -stakeFor: - notes: - - "Migrator path: caller funds transfer; stake credits `_balances[recipient]` and pool units for recipient." - - "recipient must not be zero, this, superToken, or vault." - - "Used after Fuse->CELO bridge when backend wallet holds bridged G$." - mutability: nonpayable - access: anyone - inputs: - - amount: uint256 - - recipient: address - outputs: [] - emits: [Staked] - -withdraw: - notes: - - "Reduces stake and pool units, syncFlowRate, then vault.withdraw to msg.sender." - mutability: nonpayable - access: anyone - inputs: - - amount: uint256 - outputs: [] - emits: [Withdrawn] - -exit: - notes: - - "Full withdraw of caller principal via withdraw(balance)." - mutability: nonpayable - access: anyone - inputs: [] - outputs: [] - emits: [Withdrawn] - -addToReward: - notes: - - "Pulls reward Super Token from caller; re-syncs flow rate when balance was throttling the stream." - mutability: nonpayable - access: anyone - inputs: - - reward: uint256 - outputs: [] - emits: [RewardAdded] - -syncFlowRate: - notes: - - "Permissionless; call when tokens were sent directly to the contract so distributeFlow picks up balance." - mutability: nonpayable - access: anyone - inputs: [] - outputs: [] - emits: [FlowRateUpdated] - -setDailyRewards: - mutability: nonpayable - access: owner - inputs: - - _dailyRewards: uint256 - outputs: [] - emits: [DailyRewardsUpdated] - -setMaxRewardRatePerToken: - mutability: nonpayable - access: owner - inputs: - - _value: uint256 - outputs: [] - emits: [MaxRewardRateUpdated] - -recoverERC20: - notes: - - "Cannot recover the G$ Super Token (staking/reward asset)." - mutability: nonpayable - access: owner - inputs: - - tokenAddress: address - - tokenAmount: uint256 - outputs: [] - emits: [Recovered] - -events: - Staked: - indexed: - - user: address - data: - - amount: uint256 - Withdrawn: - indexed: - - user: address - data: - - amount: uint256 - RewardAdded: - data: - - reward: uint256 - DailyRewardsUpdated: - data: - - rewardRate: uint256 - - givenDailyRewards: uint256 - MaxRewardRateUpdated: - data: - - newMaxRate: uint256 - FlowRateUpdated: - data: - - newFlowRate: int96 - Recovered: - indexed: - - token: address - data: - - amount: uint256 - - receiver: address - -errors: - - CannotStakeZero - - CannotWithdrawZero - - InsufficientStake - - InvalidAddress - - NoRewardToAdd - - CannotRecoverStakingToken diff --git a/.agents/skills/gooddollar/references/contracts/GooddollarSavingsStream.selectors.yaml b/.agents/skills/gooddollar/references/contracts/GooddollarSavingsStream.selectors.yaml deleted file mode 100644 index f04d75a4..00000000 --- a/.agents/skills/gooddollar/references/contracts/GooddollarSavingsStream.selectors.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by scripts/selectors.mjs -functions: - totalSupply(): 0x18160ddd - balanceOf(address): 0x70a08231 - getDailyRewards(): 0x68527008 - getEffectiveFlowRate(): 0xa2efe857 - periodFinish(): 0xebe2b12b - getUnits(address): 0x0fefbc09 - getTotalUnits(): 0xa754a702 - stake(uint256): 0xa694fc3a - stakeFor(uint256,address): 0x51746bb2 - withdraw(uint256): 0x2e1a7d4d - exit(): 0xe9fad8ee - addToReward(uint256): 0x2ce618fa - syncFlowRate(): 0x0e83284a - setDailyRewards(uint256): 0x4adef718 - setMaxRewardRatePerToken(uint256): 0x6a03a9e2 - recoverERC20(address,uint256): 0x8980f11f -events: - Staked(address,uint256): 0x9e71bc8eea02a63969f509818f2dafb9254532904319f9dbda79b67bd34a5f3d - Withdrawn(address,uint256): 0x7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5 - RewardAdded(uint256): 0xde88a922e0d3b88b24e9623efeb464919c6bf9f66857a65e2bfcf2ce87a9433d - DailyRewardsUpdated(uint256,uint256): 0x54f5b7ef058007cebca11af0127cd80c0bac4e968788eabb3c1e70d1bfb78edd - MaxRewardRateUpdated(uint256): 0x9041b23d05af9bceefc73becffefd1887907ad55c3d6c6655230b37ef87dbb07 - FlowRateUpdated(int96): 0xd02fcf6b96acb60ac68942c7d5075e11a8a0b38cc865c1916934f4e1f129ded0 - Recovered(address,uint256,address): 0xb197f0a554c4d7840105e6ae65f0e275e9e8605a969dffa8caa7f1f118a2e1f5 -errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/GovernanceStakingV2.abi.yaml b/.agents/skills/gooddollar/references/contracts/GovernanceStakingV2.abi.yaml deleted file mode 100644 index 5adccb32..00000000 --- a/.agents/skills/gooddollar/references/contracts/GovernanceStakingV2.abi.yaml +++ /dev/null @@ -1,202 +0,0 @@ -# GovernanceStakingV2 (Fuse) — G$ staking share token with GDAO reward accrual -# Deployment key in GoodProtocol release metadata is GovernanceStakingV2. - -meta: - name: GovernanceStakingV2 - version: "2" - source: - - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/contracts/governance/GovernanceStaking.sol - - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/releases/deployment.json - inherits: - - ERC20Upgradeable - - MultiBaseGovernanceShareField - - DAOUpgradeableContract - - ReentrancyGuardUpgradeable - note: > - Fuse governance staking contract where users stake G$ and receive staking shares (`sG$`) with - GDAO-style rewards minted on reward withdrawal paths. `withdrawStake(0)` means full unstake. - In GoodProtocol `releases/deployment.json` this address is listed as GovernanceStakingV2 on Fuse `production`. - This is the old staking contract in Fuse->CELO migration flows. - deployments: - mainnet: - production: - GovernanceStakingV2: - networkId: 122 - address: "0xB7C3e738224625289C573c54d402E9Be46205546" - creationBlock: 15956809 - related: - - references/guides/migrate-fuse-staking-to-celo-savings.md - - references/deep-researches/fuse-to-celo-staking-migration.md - -getChainBlocksPerMonth: - mutability: pure - inputs: [] - outputs: - - blocks: uint256 - -stake: - notes: - - "Requires prior G$ ERC20 approval to staking contract." - - "Mints staking share token and updates reward productivity before event emission." - mutability: nonpayable - access: anyone - inputs: - - _amount: uint256 - outputs: [] - emits: [Staked] - -withdrawStake: - notes: - - "If _amount is 0, contract interprets it as full user unstake." - - "Burns staking shares, updates productivity, mints pending rewards, then transfers G$ out." - mutability: nonpayable - access: anyone - inputs: - - _amount: uint256 - outputs: [] - emits: [StakeWithdraw] - -withdrawRewards: - notes: - - "Claims rewards without changing staked principal." - mutability: nonpayable - access: anyone - inputs: [] - outputs: - - minted: uint256 - emits: [ReputationEarned] - -setMonthlyRewards: - notes: - - "DAO-controlled update of rewardsPerBlock schedule via avatar authorization." - mutability: nonpayable - access: avatar - inputs: - - _monthlyAmount: uint256 - outputs: [] - -getRewardsPerBlock: - mutability: view - inputs: [] - outputs: - - amount: uint256 - -getProductivity: - mutability: view - inputs: - - _user: address - outputs: - - productivity: uint256 - - rewardDebt: uint256 - -getUserPendingReward: - mutability: view - inputs: - - _user: address - outputs: - - pending: uint256 - -users: - notes: - - "UserInfo getter for internal staking productivity record." - mutability: view - inputs: - - _user: address - outputs: - - amount: uint256 - - rewardDebt: uint256 - -totalRewardsPerShare: - mutability: view - inputs: [] - outputs: - - value: uint256 - -decimals: - mutability: view - inputs: [] - outputs: - - d: uint8 - -totalSupply: - mutability: view - inputs: [] - outputs: - - supply: uint256 - -balanceOf: - mutability: view - inputs: - - account: address - outputs: - - balance: uint256 - -allowance: - mutability: view - inputs: - - owner: address - - spender: address - outputs: - - amount: uint256 - -approve: - mutability: nonpayable - access: anyone - inputs: - - spender: address - - amount: uint256 - outputs: - - ok: bool - emits: [Approval] - -transfer: - mutability: nonpayable - access: anyone - inputs: - - to: address - - amount: uint256 - outputs: - - ok: bool - emits: [Transfer] - -transferFrom: - mutability: nonpayable - access: anyone - inputs: - - from: address - - to: address - - amount: uint256 - outputs: - - ok: bool - emits: [Transfer, Approval] - -events: - ReputationEarned: - indexed: - - staker: address - data: - - amount: uint256 - Staked: - indexed: - - staker: address - data: - - amount: uint256 - StakeWithdraw: - indexed: - - staker: address - data: - - amount: uint256 - Transfer: - indexed: - - from: address - - to: address - data: - - value: uint256 - Approval: - indexed: - - owner: address - - spender: address - data: - - value: uint256 - -errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/GovernanceStakingV2.selectors.yaml b/.agents/skills/gooddollar/references/contracts/GovernanceStakingV2.selectors.yaml deleted file mode 100644 index 37388ba0..00000000 --- a/.agents/skills/gooddollar/references/contracts/GovernanceStakingV2.selectors.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by scripts/selectors.mjs -functions: - getChainBlocksPerMonth(): 0x213b329e - stake(uint256): 0xa694fc3a - withdrawStake(uint256): 0x25d5971f - withdrawRewards(): 0xc7b8981c - setMonthlyRewards(uint256): 0xc76279a2 - getRewardsPerBlock(): 0x0c1cd7f3 - getProductivity(address): 0x28e964e9 - getUserPendingReward(address): 0xc6710629 - users(address): 0xa87430ba - totalRewardsPerShare(): 0xbf8e9b6e - decimals(): 0x313ce567 - totalSupply(): 0x18160ddd - balanceOf(address): 0x70a08231 - allowance(address,address): 0xdd62ed3e - approve(address,uint256): 0x095ea7b3 - transfer(address,uint256): 0xa9059cbb - transferFrom(address,address,uint256): 0x23b872dd -events: - ReputationEarned(address,uint256): 0x43848d0574703c28d68ae8958e0571521618f60c4bcacfb094cff2156eaae0f1 - Staked(address,uint256): 0x9e71bc8eea02a63969f509818f2dafb9254532904319f9dbda79b67bd34a5f3d - StakeWithdraw(address,uint256): 0x1248d48e2de900a1010c7fce73506969ecec243600bfc08b641b158f26d857cd - Transfer(address,address,uint256): 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef - Approval(address,address,uint256): 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925 -errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/IdentityV3.abi.yaml b/.agents/skills/gooddollar/references/contracts/IdentityV3.abi.yaml deleted file mode 100644 index aea2efe9..00000000 --- a/.agents/skills/gooddollar/references/contracts/IdentityV3.abi.yaml +++ /dev/null @@ -1,390 +0,0 @@ -# IdentityV3 — whitelist, blacklist, DID, and connected-account graph for GoodDollar -# UBI eligibility uses getWhitelistedRoot; connectAccount links extra wallets to a root. - -meta: - name: IdentityV3 - version: v3 - source: - - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/contracts/identity/IdentityV3.sol - inherits: - - DAOUpgradeableContract - - AccessControlUpgradeable - - PausableUpgradeable - - EIP712Upgradeable - note: > - isWhitelisted enforces authenticationPeriod against dateAuthenticated and status==1, - with fallback to oldIdentity. getWhitelistedRoot returns the root address for a - connected wallet or the wallet itself when directly whitelisted. - deployments: - mainnet: - production: - Identity: - networkId: 122 - address: "0x2F9C28de9e6d44b71B91b8BA337A5D82e308E7BE" - creationBlock: 22022901 - IdentityOld: - networkId: 122 - address: "0xFa8d865A962ca8456dF331D78806152d3aC5B84F" - creationBlock: 6246324 - production-celo: - Identity: - networkId: 42220 - address: "0xC361A6E67822a0EDc17D899227dd9FC50BD62F42" - creationBlock: 17237952 - production-xdc: - Identity: - networkId: 50 - address: "0x27a4a02C9ed591E1a86e2e5D05870292c34622C9" - creationBlock: 95143058 - related: - - https://docs.gooddollar.org/for-developers/core-contracts/identity - - https://docs.gooddollar.org/user-guides/connect-another-wallet-address-to-identity - -initialize: - mutability: nonpayable - access: initializer - inputs: - - _owner: address - - _oldIdentity: address - outputs: [] - -initDAO: - notes: - - "Wires NameService via setDAO and transfers DEFAULT_ADMIN_ROLE, PAUSER_ROLE, IDENTITY_ADMIN_ROLE to avatar." - mutability: nonpayable - access: DEFAULT_ADMIN_ROLE - inputs: - - _ns: address - outputs: [] - errors: [ALREADY_INITIALIZED] - -setAuthenticationPeriod: - mutability: nonpayable - access: avatar - inputs: - - period: uint256 - outputs: [] - errors: [WHEN_NOT_PAUSED_REVERT] - -authenticate: - notes: - - "Public wrapper; forwards to authenticateWithTimestamp(account, block.timestamp)." - mutability: nonpayable - inputs: - - account: address - outputs: [] - -authenticateWithTimestamp: - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - account: address - - timestamp: uint256 - outputs: [] - emits: [WhitelistedAuthenticated] - errors: [NOT_WHITELISTED_STATUS, WHEN_NOT_PAUSED_REVERT] - -addWhitelisted: - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - account: address - outputs: [] - emits: [WhitelistedAdded] - errors: [ALREADY_HAS_STATUS, WHEN_NOT_PAUSED_REVERT] - -addWhitelistedWithDIDAndChain: - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - account: address - - did: string - - orgChain: uint256 - - dateAuthenticated: uint256 - outputs: [] - emits: [WhitelistedAdded] - errors: [DID_ALREADY_REGISTERED, ALREADY_HAS_STATUS, WHEN_NOT_PAUSED_REVERT] - -addWhitelistedWithDID: - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - account: address - - did: string - outputs: [] - emits: [WhitelistedAdded] - errors: [DID_ALREADY_REGISTERED, ALREADY_HAS_STATUS, WHEN_NOT_PAUSED_REVERT] - -removeWhitelisted: - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - account: address - outputs: [] - emits: [WhitelistedRemoved] - errors: [WHEN_NOT_PAUSED_REVERT] - -renounceWhitelisted: - mutability: nonpayable - access: whitelisted sender - inputs: [] - outputs: [] - emits: [WhitelistedRemoved] - errors: [NOT_WHITELISTED_SENDER, WHEN_NOT_PAUSED_REVERT] - -isWhitelisted: - notes: - - "Combines local Identity record with optional oldIdentity.isWhitelisted try/catch." - mutability: view - inputs: - - account: address - outputs: - - ok: bool - -lastAuthenticated: - mutability: view - inputs: - - account: address - outputs: - - ts: uint256 - -addBlacklisted: - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - account: address - outputs: [] - emits: [BlacklistAdded, WhitelistedRemoved] - errors: [WHEN_NOT_PAUSED_REVERT] - -removeBlacklisted: - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - account: address - outputs: [] - emits: [BlacklistRemoved] - errors: [WHEN_NOT_PAUSED_REVERT] - -addContract: - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - account: address - outputs: [] - emits: [ContractAdded] - errors: [NOT_CONTRACT, ALREADY_HAS_STATUS, WHEN_NOT_PAUSED_REVERT] - -removeContract: - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - account: address - outputs: [] - emits: [ContractRemoved] - errors: [WHEN_NOT_PAUSED_REVERT] - -isDAOContract: - mutability: view - inputs: - - account: address - outputs: - - ok: bool - -isBlacklisted: - mutability: view - inputs: - - account: address - outputs: - - ok: bool - -connectAccount: - notes: - - "Caller must be whitelisted; target must not already be whitelisted or blacklisted; stores connectedAccounts[account]=msg.sender." - mutability: nonpayable - access: whitelisted sender - inputs: - - account: address - outputs: [] - emits: [AccountConnected] - errors: [INVALID_ACCOUNT_FOR_CONNECT, ALREADY_CONNECTED, WHEN_NOT_PAUSED_REVERT] - -disconnectAccount: - notes: - - "Caller must be either the linked root or the connected wallet." - mutability: nonpayable - inputs: - - connected: address - outputs: [] - emits: [AccountDisconnected] - errors: [UNAUTHORIZED_DISCONNECT] - -getWhitelistedRoot: - notes: - - "Returns account if directly whitelisted; returns connected root if connected account is whitelisted; else zero." - mutability: view - inputs: - - account: address - outputs: - - whitelisted: address - -pause: - mutability: nonpayable - access: PAUSER_ROLE - inputs: - - toPause: bool - outputs: [] - -setDID: - mutability: nonpayable - inputs: - - account: address - - did: string - outputs: [] - errors: [NOT_AUTHORIZED_SET_DID, NOT_WHITELISTED_FOR_DID, DID_EMPTY, DID_ALREADY_REGISTERED, DID_ALREADY_REGISTERED_OLD, WHEN_NOT_PAUSED_REVERT] - -addrToDID: - mutability: view - inputs: - - account: address - outputs: - - did: string - -getWhitelistedOnChainId: - mutability: view - inputs: - - account: address - outputs: - - chainId: uint256 - -isRegistered: - notes: - - "Compatibility shim; always returns true." - mutability: pure - inputs: [] - outputs: - - ok: bool - -IDENTITY_ADMIN_ROLE: - mutability: view - inputs: [] - outputs: - - role: bytes32 - -PAUSER_ROLE: - mutability: view - inputs: [] - outputs: - - role: bytes32 - -TYPED_STRUCTURE: - mutability: view - inputs: [] - outputs: - - schema: string - -whitelistedCount: - mutability: view - inputs: [] - outputs: - - n: uint256 - -whitelistedContracts: - mutability: view - inputs: [] - outputs: - - n: uint256 - -authenticationPeriod: - mutability: view - inputs: [] - outputs: - - seconds: uint256 - -identities: - mutability: view - inputs: - - account: address - outputs: - - dateAuthenticated: uint256 - - dateAdded: uint256 - - did: string - - whitelistedOnChainId: uint256 - - status: uint8 - -didHashToAddress: - mutability: view - inputs: - - hash: bytes32 - outputs: - - account: address - -connectedAccounts: - mutability: view - inputs: - - account: address - outputs: - - root: address - -oldIdentity: - mutability: view - inputs: [] - outputs: - - addr: address - -events: - BlacklistAdded: - indexed: - - account: address - data: [] - BlacklistRemoved: - indexed: - - account: address - data: [] - WhitelistedAdded: - indexed: - - account: address - data: [] - WhitelistedRemoved: - indexed: - - account: address - data: [] - WhitelistedAuthenticated: - indexed: - - account: address - data: - - timestamp: uint256 - ContractAdded: - indexed: - - account: address - data: [] - ContractRemoved: - indexed: - - account: address - data: [] - AccountConnected: - indexed: - - connected: address - - to: address - data: [] - AccountDisconnected: - indexed: - - disconnected: address - - from: address - data: [] - -errors: - ALREADY_INITIALIZED: "already initialized — initDAO twice." - NOT_WHITELISTED_STATUS: "not whitelisted — authenticateWithTimestamp." - NOT_WHITELISTED_SENDER: "not whitelisted — renounceWhitelisted modifier." - NOT_WHITELISTED_FOR_DID: "not whitelisted — setDID internal." - ALREADY_HAS_STATUS: "already has status — _addWhitelisted." - DID_ALREADY_REGISTERED: "DID already registered — _addWhitelistedWithDID or _setDID." - DID_ALREADY_REGISTERED_OLD: "DID already registered oldIdentity — _setDID conflict path." - NOT_CONTRACT: "Given address is not a contract — addContract." - INVALID_ACCOUNT_FOR_CONNECT: "invalid account — connectAccount target checks." - ALREADY_CONNECTED: "already connected — connectAccount." - UNAUTHORIZED_DISCONNECT: "unauthorized — disconnectAccount." - NOT_AUTHORIZED_SET_DID: "not authorized — setDID caller." - DID_EMPTY: "did empty — _setDID." - WHEN_NOT_PAUSED_REVERT: "OpenZeppelin Pausable whenNotPaused on gated calls." diff --git a/.agents/skills/gooddollar/references/contracts/IdentityV3.selectors.yaml b/.agents/skills/gooddollar/references/contracts/IdentityV3.selectors.yaml deleted file mode 100644 index 9b53f001..00000000 --- a/.agents/skills/gooddollar/references/contracts/IdentityV3.selectors.yaml +++ /dev/null @@ -1,49 +0,0 @@ -# Generated by scripts/selectors.mjs -functions: - initialize(address,address): 0x485cc955 - initDAO(address): 0x2b14dda8 - setAuthenticationPeriod(uint256): 0xfff930d4 - authenticate(address): 0x08e0d29d - authenticateWithTimestamp(address,uint256): 0x96a1ef79 - addWhitelisted(address): 0x10154bad - addWhitelistedWithDIDAndChain(address,string,uint256,uint256): 0xe737031a - addWhitelistedWithDID(address,string): 0x1b027099 - removeWhitelisted(address): 0x291d9549 - renounceWhitelisted(): 0xd6cd9473 - isWhitelisted(address): 0x3af32abf - lastAuthenticated(address): 0xe1e360ba - addBlacklisted(address): 0x188efc16 - removeBlacklisted(address): 0xc6a276c2 - addContract(address): 0x5f539d69 - removeContract(address): 0xc375c2ef - isDAOContract(address): 0xc73cc4ae - isBlacklisted(address): 0xfe575a87 - connectAccount(address): 0xd21685f4 - disconnectAccount(address): 0x37a1a987 - getWhitelistedRoot(address): 0x2d0e9b46 - pause(bool): 0x02329a29 - setDID(address,string): 0xd3262816 - addrToDID(address): 0x54f9f7a3 - getWhitelistedOnChainId(address): 0xa061922d - isRegistered(): 0x22366844 - IDENTITY_ADMIN_ROLE(): 0x1aaff63c - PAUSER_ROLE(): 0xe63ab1e9 - TYPED_STRUCTURE(): 0x2cec5330 - whitelistedCount(): 0xb2a1de22 - whitelistedContracts(): 0xb30f7e7f - authenticationPeriod(): 0x31b376e2 - identities(address): 0xf653b81e - didHashToAddress(bytes32): 0x67c75937 - connectedAccounts(address): 0x61320040 - oldIdentity(): 0x4125f0f2 -events: - BlacklistAdded(address): 0x44d5fe68b00f68950fb9c1ff0a61ef7f747b1a36359a7e3a7f3324db4b878967 - BlacklistRemoved(address): 0x1747ca720b1a174a464b6513ace29b1d3190b5f632b9f34147017c81425bfde8 - WhitelistedAdded(address): 0xee1504a83b6d4a361f4c1dc78ab59bfa30d6a3b6612c403e86bb01ef2984295f - WhitelistedRemoved(address): 0x270d9b30cf5b0793bbfd54c9d5b94aeb49462b8148399000265144a8722da6b6 - WhitelistedAuthenticated(address,uint256): 0xb2a82fce6d8c7a633efe9579f77b4edb96bfdf171a49bfc2ce666dc543a1f500 - ContractAdded(address): 0x89c66952b48f3e96bf1d8ba1b63189520fd988a6979b8b740bd5c5d8dc53e205 - ContractRemoved(address): 0x8d30d41865a0b811b9545d879520d2dde9f4cc49e4241f486ad9752bc904b565 - AccountConnected(address,address): 0x18f7736ef54539debd9afd3c9500b106e12ae7c70e685f5a5efd727b1ce1d54c - AccountDisconnected(address,address): 0x7cdef5f9c5cb8ce728661ede956fef26cb91eb4d7d2180cc041b73f9fef568d2 -errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/IdentityV4.abi.yaml b/.agents/skills/gooddollar/references/contracts/IdentityV4.abi.yaml deleted file mode 100644 index 485254d7..00000000 --- a/.agents/skills/gooddollar/references/contracts/IdentityV4.abi.yaml +++ /dev/null @@ -1,424 +0,0 @@ -# IdentityV4 — whitelist, blacklist, DID, connected accounts, and reverification schedule -# isWhitelisted uses shouldReverify against reverifyDaysOptions and authCount; oldIdentity fallback preserved. - -meta: - name: IdentityV4 - version: v4 - source: - - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/contracts/identity/IdentityV4.sol - inherits: - - DAOUpgradeableContract - - AccessControlUpgradeable - - PausableUpgradeable - - EIP712Upgradeable - note: > - authenticationPeriod() returns the largest reverify interval in days (last entry of reverifyDaysOptions), - not seconds. isWhitelisted is false when shouldReverify is true for the current elapsed days since - dateAuthenticated. Post-upgrade, accounts with dateAuthenticated before 1772697574 are treated as - having authCount at the last schedule step for reverify logic. - deployments: - mainnet: - production: - Identity: - networkId: 122 - address: "0x2F9C28de9e6d44b71B91b8BA337A5D82e308E7BE" - creationBlock: 22022901 - production-celo: - Identity: - networkId: 42220 - address: "0xC361A6E67822a0EDc17D899227dd9FC50BD62F42" - creationBlock: 17237952 - production-xdc: - Identity: - networkId: 50 - address: "0x27a4a02C9ed591E1a86e2e5D05870292c34622C9" - creationBlock: 95143058 - related: - - https://docs.gooddollar.org/for-developers/core-contracts/identity - - https://docs.gooddollar.org/user-guides/connect-another-wallet-address-to-identity - -initialize: - mutability: nonpayable - access: initializer - inputs: - - _owner: address - - _oldIdentity: address - outputs: [] - -initDAO: - notes: - - "Wires NameService via setDAO and transfers DEFAULT_ADMIN_ROLE, PAUSER_ROLE, IDENTITY_ADMIN_ROLE to avatar." - mutability: nonpayable - access: DEFAULT_ADMIN_ROLE - inputs: - - _ns: address - outputs: [] - errors: [ALREADY_INITIALIZED] - -setReverifyDaysOptions: - notes: - - "Replaces the full schedule; values must be strictly ascending uint8 days; non-empty array." - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - options: uint8[] - outputs: [] - errors: [EMPTY_REVERIFY_OPTIONS, OPTIONS_NOT_ASCENDING, WHEN_NOT_PAUSED_REVERT] - -authenticate: - notes: - - "Forwards to authenticateWithTimestamp(account, block.timestamp); requires IDENTITY_ADMIN_ROLE on the inner call." - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - account: address - outputs: [] - errors: [NOT_WHITELISTED_STATUS, WHEN_NOT_PAUSED_REVERT] - -authenticateWithTimestamp: - notes: - - "Refreshes authentication timestamp for status-1 account and advances authCount when reverification threshold is reached." - - "Legacy accounts before cutoff are evaluated at last schedule step for reverify math." - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - account: address - - timestamp: uint256 - outputs: [] - emits: [WhitelistedAuthenticated] - errors: [NOT_WHITELISTED_STATUS, WHEN_NOT_PAUSED_REVERT] - -addWhitelisted: - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - account: address - outputs: [] - emits: [WhitelistedAdded] - errors: [ALREADY_HAS_STATUS, WHEN_NOT_PAUSED_REVERT] - -addWhitelistedWithDIDAndChain: - notes: - - "Adds whitelisted account and stores DID plus originating chain id and authentication timestamp." - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - account: address - - did: string - - orgChain: uint256 - - dateAuthenticated: uint256 - outputs: [] - emits: [WhitelistedAdded] - errors: [DID_ALREADY_REGISTERED, ALREADY_HAS_STATUS, WHEN_NOT_PAUSED_REVERT] - -addWhitelistedWithDID: - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - account: address - - did: string - outputs: [] - emits: [WhitelistedAdded] - errors: [DID_ALREADY_REGISTERED, ALREADY_HAS_STATUS, WHEN_NOT_PAUSED_REVERT] - -removeWhitelisted: - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - account: address - outputs: [] - emits: [WhitelistedRemoved] - errors: [WHEN_NOT_PAUSED_REVERT] - -renounceWhitelisted: - mutability: nonpayable - access: whitelisted sender - inputs: [] - outputs: [] - emits: [WhitelistedRemoved] - errors: [NOT_WHITELISTED_SENDER, WHEN_NOT_PAUSED_REVERT] - -shouldReverify: - notes: - - "Compares daysSinceAuth against current reverifyDaysOptions step selected by authCount." - mutability: view - inputs: - - account: address - - daysSinceAuth: uint256 - outputs: - - needed: bool - -isWhitelisted: - notes: - - "Local status 1 with shouldReverify false; else oldIdentity.isWhitelisted try/catch." - mutability: view - inputs: - - account: address - outputs: - - ok: bool - -lastAuthenticated: - notes: - - "Returns local dateAuthenticated or falls back to oldIdentity when local record has no timestamp." - mutability: view - inputs: - - account: address - outputs: - - ts: uint256 - -addBlacklisted: - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - account: address - outputs: [] - emits: [BlacklistAdded, WhitelistedRemoved] - errors: [WHEN_NOT_PAUSED_REVERT] - -removeBlacklisted: - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - account: address - outputs: [] - emits: [BlacklistRemoved] - errors: [WHEN_NOT_PAUSED_REVERT] - -addContract: - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - account: address - outputs: [] - emits: [ContractAdded] - errors: [NOT_CONTRACT, ALREADY_HAS_STATUS, WHEN_NOT_PAUSED_REVERT] - -removeContract: - mutability: nonpayable - access: IDENTITY_ADMIN_ROLE - inputs: - - account: address - outputs: [] - emits: [ContractRemoved] - errors: [WHEN_NOT_PAUSED_REVERT] - -isDAOContract: - mutability: view - inputs: - - account: address - outputs: - - ok: bool - -isBlacklisted: - mutability: view - inputs: - - account: address - outputs: - - ok: bool - -connectAccount: - notes: - - "Caller must be whitelisted; target must not already be whitelisted or blacklisted; stores connectedAccounts[account]=msg.sender." - mutability: nonpayable - access: whitelisted sender - inputs: - - account: address - outputs: [] - emits: [AccountConnected] - errors: [INVALID_ACCOUNT_FOR_CONNECT, ALREADY_CONNECTED, WHEN_NOT_PAUSED_REVERT] - -disconnectAccount: - notes: - - "Caller must be either the linked root or the connected wallet." - mutability: nonpayable - inputs: - - connected: address - outputs: [] - emits: [AccountDisconnected] - errors: [UNAUTHORIZED_DISCONNECT] - -getWhitelistedRoot: - notes: - - "Returns account if directly whitelisted; returns connected root if connected account is whitelisted; else zero." - mutability: view - inputs: - - account: address - outputs: - - whitelisted: address - -pause: - mutability: nonpayable - access: PAUSER_ROLE - inputs: - - toPause: bool - outputs: [] - -setDID: - notes: - - "Sets DID for account by admin or account itself when whitelisted; enforces uniqueness across local and old identity." - mutability: nonpayable - inputs: - - account: address - - did: string - outputs: [] - errors: [NOT_AUTHORIZED_SET_DID, NOT_WHITELISTED_FOR_DID, DID_EMPTY, DID_ALREADY_REGISTERED, DID_ALREADY_REGISTERED_OLD, WHEN_NOT_PAUSED_REVERT] - -addrToDID: - mutability: view - inputs: - - account: address - outputs: - - did: string - -getWhitelistedOnChainId: - notes: - - "Returns chain id recorded during whitelisting for cross-chain eligibility checks." - mutability: view - inputs: - - account: address - outputs: - - chainId: uint256 - -isRegistered: - notes: - - "Compatibility shim; always returns true." - mutability: pure - inputs: [] - outputs: - - ok: bool - -IDENTITY_ADMIN_ROLE: - mutability: view - inputs: [] - outputs: - - role: bytes32 - -PAUSER_ROLE: - mutability: view - inputs: [] - outputs: - - role: bytes32 - -TYPED_STRUCTURE: - mutability: view - inputs: [] - outputs: - - schema: string - -whitelistedCount: - mutability: view - inputs: [] - outputs: - - n: uint256 - -whitelistedContracts: - mutability: view - inputs: [] - outputs: - - n: uint256 - -authenticationPeriod: - notes: - - "Returns reverifyDaysOptions[last] in days, not seconds." - mutability: view - inputs: [] - outputs: - - days: uint256 - -reverifyDaysOptions: - mutability: view - inputs: - - index: uint256 - outputs: - - days: uint32 - -identities: - mutability: view - inputs: - - account: address - outputs: - - dateAuthenticated: uint256 - - dateAdded: uint256 - - did: string - - whitelistedOnChainId: uint256 - - status: uint8 - - authCount: uint32 - -didHashToAddress: - mutability: view - inputs: - - hash: bytes32 - outputs: - - account: address - -connectedAccounts: - mutability: view - inputs: - - account: address - outputs: - - root: address - -oldIdentity: - mutability: view - inputs: [] - outputs: - - addr: address - -events: - BlacklistAdded: - indexed: - - account: address - data: [] - BlacklistRemoved: - indexed: - - account: address - data: [] - WhitelistedAdded: - indexed: - - account: address - data: [] - WhitelistedRemoved: - indexed: - - account: address - data: [] - WhitelistedAuthenticated: - indexed: - - account: address - data: - - timestamp: uint256 - ContractAdded: - indexed: - - account: address - data: [] - ContractRemoved: - indexed: - - account: address - data: [] - AccountConnected: - indexed: - - connected: address - - to: address - data: [] - AccountDisconnected: - indexed: - - disconnected: address - - from: address - data: [] - -errors: - ALREADY_INITIALIZED: "already initialized — initDAO twice." - EMPTY_REVERIFY_OPTIONS: "empty options — setReverifyDaysOptions." - OPTIONS_NOT_ASCENDING: "options not in ascending order — setReverifyDaysOptions." - NOT_WHITELISTED_STATUS: "not whitelisted — authenticateWithTimestamp." - NOT_WHITELISTED_SENDER: "not whitelisted — renounceWhitelisted modifier." - NOT_WHITELISTED_FOR_DID: "not whitelisted — _setDID." - ALREADY_HAS_STATUS: "already has status — _addWhitelisted." - DID_ALREADY_REGISTERED: "DID already registered — _addWhitelistedWithDID or _setDID." - DID_ALREADY_REGISTERED_OLD: "DID already registered oldIdentity — _setDID conflict path." - NOT_CONTRACT: "Given address is not a contract — addContract." - INVALID_ACCOUNT_FOR_CONNECT: "invalid account — connectAccount target checks." - ALREADY_CONNECTED: "already connected — connectAccount." - UNAUTHORIZED_DISCONNECT: "unauthorized — disconnectAccount." - NOT_AUTHORIZED_SET_DID: "not authorized — setDID caller." - DID_EMPTY: "did empty — _setDID." - WHEN_NOT_PAUSED_REVERT: "OpenZeppelin Pausable whenNotPaused on gated calls." diff --git a/.agents/skills/gooddollar/references/contracts/IdentityV4.selectors.yaml b/.agents/skills/gooddollar/references/contracts/IdentityV4.selectors.yaml deleted file mode 100644 index 3440a5ec..00000000 --- a/.agents/skills/gooddollar/references/contracts/IdentityV4.selectors.yaml +++ /dev/null @@ -1,51 +0,0 @@ -# Generated by scripts/selectors.mjs -functions: - initialize(address,address): 0x485cc955 - initDAO(address): 0x2b14dda8 - setReverifyDaysOptions(uint8[]): 0x925e8c1a - authenticate(address): 0x08e0d29d - authenticateWithTimestamp(address,uint256): 0x96a1ef79 - addWhitelisted(address): 0x10154bad - addWhitelistedWithDIDAndChain(address,string,uint256,uint256): 0xe737031a - addWhitelistedWithDID(address,string): 0x1b027099 - removeWhitelisted(address): 0x291d9549 - renounceWhitelisted(): 0xd6cd9473 - shouldReverify(address,uint256): 0x4a03813f - isWhitelisted(address): 0x3af32abf - lastAuthenticated(address): 0xe1e360ba - addBlacklisted(address): 0x188efc16 - removeBlacklisted(address): 0xc6a276c2 - addContract(address): 0x5f539d69 - removeContract(address): 0xc375c2ef - isDAOContract(address): 0xc73cc4ae - isBlacklisted(address): 0xfe575a87 - connectAccount(address): 0xd21685f4 - disconnectAccount(address): 0x37a1a987 - getWhitelistedRoot(address): 0x2d0e9b46 - pause(bool): 0x02329a29 - setDID(address,string): 0xd3262816 - addrToDID(address): 0x54f9f7a3 - getWhitelistedOnChainId(address): 0xa061922d - isRegistered(): 0x22366844 - IDENTITY_ADMIN_ROLE(): 0x1aaff63c - PAUSER_ROLE(): 0xe63ab1e9 - TYPED_STRUCTURE(): 0x2cec5330 - whitelistedCount(): 0xb2a1de22 - whitelistedContracts(): 0xb30f7e7f - authenticationPeriod(): 0x31b376e2 - reverifyDaysOptions(uint256): 0xcadef5ac - identities(address): 0xf653b81e - didHashToAddress(bytes32): 0x67c75937 - connectedAccounts(address): 0x61320040 - oldIdentity(): 0x4125f0f2 -events: - BlacklistAdded(address): 0x44d5fe68b00f68950fb9c1ff0a61ef7f747b1a36359a7e3a7f3324db4b878967 - BlacklistRemoved(address): 0x1747ca720b1a174a464b6513ace29b1d3190b5f632b9f34147017c81425bfde8 - WhitelistedAdded(address): 0xee1504a83b6d4a361f4c1dc78ab59bfa30d6a3b6612c403e86bb01ef2984295f - WhitelistedRemoved(address): 0x270d9b30cf5b0793bbfd54c9d5b94aeb49462b8148399000265144a8722da6b6 - WhitelistedAuthenticated(address,uint256): 0xb2a82fce6d8c7a633efe9579f77b4edb96bfdf171a49bfc2ce666dc543a1f500 - ContractAdded(address): 0x89c66952b48f3e96bf1d8ba1b63189520fd988a6979b8b740bd5c5d8dc53e205 - ContractRemoved(address): 0x8d30d41865a0b811b9545d879520d2dde9f4cc49e4241f486ad9752bc904b565 - AccountConnected(address,address): 0x18f7736ef54539debd9afd3c9500b106e12ae7c70e685f5a5efd727b1ce1d54c - AccountDisconnected(address,address): 0x7cdef5f9c5cb8ce728661ede956fef26cb91eb4d7d2180cc041b73f9fef568d2 -errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/InvitesV2.abi.yaml b/.agents/skills/gooddollar/references/contracts/InvitesV2.abi.yaml deleted file mode 100644 index d69e3d60..00000000 --- a/.agents/skills/gooddollar/references/contracts/InvitesV2.abi.yaml +++ /dev/null @@ -1,333 +0,0 @@ -# InvitesV2 — invite codes, inviter levels, and G$ bounties (UUPS upgradeable) -# Bounty amounts on Level use G$ cents (2 decimals). Invitee receives half the inviter bounty on payout. - -meta: - name: InvitesV2 - version: "2.4" - source: - - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/contracts/invite/InvitesV2.sol - inherits: - - DAOUpgradeableContract - note: > - join binds invite code and optional inviter; bounty eligibility uses minimumDays, minimumClaims, - whitelist on invitee (and inviter when present), and same-chain whitelisting via Identity - getWhitelistedOnChainId. collectBounties and bountyFor pay invitee bountyToPay/2 and inviter - bountyToPay when applicable. Campaign inviter is address(this) when inviter code maps to contract. - deployments: - mainnet: - production: - Invites: - networkId: 122 - address: "0xCa2F09c3ccFD7aD5cB9276918Bd1868f2b922ea0" - creationBlock: 8853311 - production-celo: - Invites: - networkId: 42220 - address: "0x36829D1Cda92FFF5782d5d48991620664FC857d3" - creationBlock: 18003063 - production-xdc: - Invites: - networkId: 50 - address: "0x6bd698566632bf2e81e2278f1656CB24aAF06D2e" - creationBlock: 95144756 - related: - - references/deep-researches/inviter-invitee-reward-model.md - -initialize: - mutability: nonpayable - access: initializer - inputs: - - _ns: address - - _level0Bounty: uint256 - - _owner: address - outputs: [] - -updateAvatar: - mutability: nonpayable - access: anyone - inputs: [] - outputs: [] - -nativeToken: - mutability: view - inputs: [] - outputs: - - token: address - -dao: - mutability: view - inputs: [] - outputs: - - controller: address - -avatar: - mutability: view - inputs: [] - outputs: - - addr: address - -nameService: - mutability: view - inputs: [] - outputs: - - ns: address - -upgradeTo: - mutability: nonpayable - access: ownerOrAvatar - inputs: - - newImplementation: address - outputs: [] - -upgradeToAndCall: - mutability: payable - access: ownerOrAvatar - inputs: - - newImplementation: address - - data: bytes - outputs: [] - -proxiableUUID: - mutability: view - inputs: [] - outputs: - - slot: bytes32 - -setLevelExpirationEnabled: - mutability: nonpayable - access: ownerOrAvatar - inputs: - - _isEnabled: bool - outputs: [] - -getIdentity: - mutability: view - inputs: [] - outputs: - - identity: address - -join: - notes: - - "Registers invite code; may set inviter; may trigger _bountyFor when canCollectBountyFor is already true." - mutability: nonpayable - access: anyone - inputs: - - _myCode: bytes32 - - _inviterCode: bytes32 - outputs: [] - emits: [InviteeJoined] - errors: [NOT_ACTIVE, INVITE_CODE_IN_USE, SELF_INVITE, USER_ALREADY_JOINED] - -canCollectBountyFor: - notes: - - "Primary eligibility gate: checks active state, whitelist, minimumDays, minimumClaims, unpaid bounty, and chain constraints." - mutability: view - inputs: - - _invitee: address - outputs: - - ok: bool - -getInvitees: - mutability: view - inputs: - - _inviter: address - outputs: - - invitees: address[] - -getPendingInvitees: - mutability: view - inputs: - - _inviter: address - outputs: - - pending: address[] - -getPendingBounties: - mutability: view - inputs: - - _inviter: address - outputs: - - count: uint256 - -bountyFor: - notes: - - "Single-invitee payout path; revalidates eligibility and transfers bounty shares to inviter/invitee on success." - mutability: nonpayable - access: anyone - inputs: - - _invitee: address - outputs: - - bounty: uint256 - emits: [InviterBounty] - errors: [NOT_ACTIVE, NOT_ELIGIBLE_BOUNTY] - -collectBounties: - notes: - - "Batch payout path over caller pending invitees; useful for claiming multiple matured bounties in one tx." - mutability: nonpayable - access: anyone - inputs: [] - outputs: [] - emits: [InviterBounty] - errors: [NOT_ACTIVE] - -setLevel: - notes: - - "Configures inviter level progression rule and bounty amount (bounty units are G$ cents)." - mutability: nonpayable - access: ownerOrAvatar - inputs: - - _lvl: uint256 - - _toNext: uint256 - - _bounty: uint256 - - _daysToComplete: uint256 - outputs: [] - -setActive: - mutability: nonpayable - access: ownerOrAvatar - inputs: - - _active: bool - outputs: [] - -setCampaignCode: - notes: - - "Maps a campaign code to contract-address inviter flow (address(this)) used by join." - mutability: nonpayable - access: ownerOrAvatar - inputs: - - _code: bytes32 - outputs: [] - -setMinimums: - notes: - - "Sets minimumClaims and minimumDays thresholds used by canCollectBountyFor." - mutability: nonpayable - access: ownerOrAvatar - inputs: - - _minClaims: uint8 - - _minDays: uint8 - outputs: [] - -end: - mutability: nonpayable - access: ownerOrAvatar - inputs: [] - outputs: [] - errors: [NOT_ACTIVE] - -setOwner: - mutability: nonpayable - access: ownerOrAvatar - inputs: - - _owner: address - outputs: [] - -version: - mutability: pure - inputs: [] - outputs: - - v: string - -codeToUser: - mutability: view - inputs: - - code: bytes32 - outputs: - - user: address - -users: - notes: - - "Public getter omits dynamic invitees/pending arrays; use getInvitees and getPendingInvitees." - mutability: view - inputs: - - user: address - outputs: - - invitedBy: address - - inviteCode: bytes32 - - bountyPaid: bool - - level: uint256 - - levelStarted: uint256 - - totalApprovedInvites: uint256 - - totalEarned: uint256 - - joinedAt: uint256 - - bountyAtJoin: uint256 - -levels: - notes: - - "bounty is in G$ cents (2 decimals). Reserved struct slots are not exposed on the getter." - mutability: view - inputs: - - lvl: uint256 - outputs: - - toNext: uint256 - - bounty: uint256 - - daysToComplete: uint256 - -owner: - mutability: view - inputs: [] - outputs: - - addr: address - -goodDollar: - mutability: view - inputs: [] - outputs: - - token: address - -active: - mutability: view - inputs: [] - outputs: - - ok: bool - -stats: - notes: - - "Reserved Stats struct slots are not exposed on the getter." - mutability: view - inputs: [] - outputs: - - totalApprovedInvites: uint256 - - totalBountiesPaid: uint256 - - totalInvited: uint256 - -levelExpirationEnabled: - mutability: view - inputs: [] - outputs: - - ok: bool - -minimumClaims: - mutability: view - inputs: [] - outputs: - - n: uint8 - -minimumDays: - mutability: view - inputs: [] - outputs: - - n: uint8 - -events: - InviteeJoined: - indexed: - - inviter: address - - invitee: address - data: [] - InviterBounty: - indexed: - - inviter: address - - invitee: address - data: - - bountyPaid: uint256 - - inviterLevel: uint256 - - earnedLevel: bool - -errors: - NOT_ACTIVE: "not active — isActive modifier." - ONLY_OWNER_OR_AVATAR: "Only owner or avatar can perform this action — ownerOrAvatar." - INVITE_CODE_IN_USE: "invite code already in use — join." - SELF_INVITE: "self invite — join." - USER_ALREADY_JOINED: "user already joined — join." - NOT_ELIGIBLE_BOUNTY: "user not elligble for bounty yet — bountyFor." diff --git a/.agents/skills/gooddollar/references/contracts/InvitesV2.selectors.yaml b/.agents/skills/gooddollar/references/contracts/InvitesV2.selectors.yaml deleted file mode 100644 index d7179550..00000000 --- a/.agents/skills/gooddollar/references/contracts/InvitesV2.selectors.yaml +++ /dev/null @@ -1,41 +0,0 @@ -# Generated by scripts/selectors.mjs -functions: - initialize(address,uint256,address): 0xc350a1b5 - updateAvatar(): 0x1b3c90a8 - nativeToken(): 0xe1758bd8 - dao(): 0x4162169f - avatar(): 0x5aef7de6 - nameService(): 0x3e6326fc - upgradeTo(address): 0x3659cfe6 - upgradeToAndCall(address,bytes): 0x4f1ef286 - proxiableUUID(): 0x52d1902d - setLevelExpirationEnabled(bool): 0x21132aad - getIdentity(): 0x36afc6fa - join(bytes32,bytes32): 0x5b419a65 - canCollectBountyFor(address): 0x6d619ef8 - getInvitees(address): 0xe9881a5e - getPendingInvitees(address): 0xe951a3aa - getPendingBounties(address): 0x41155d5e - bountyFor(address): 0xb6567cd5 - collectBounties(): 0xaf6346b0 - setLevel(uint256,uint256,uint256,uint256): 0xb9fb2d18 - setActive(bool): 0xacec338a - setCampaignCode(bytes32): 0xf14b8649 - setMinimums(uint8,uint8): 0x47826c82 - end(): 0xefbe1c1c - setOwner(address): 0x13af4035 - version(): 0x54fd4d50 - codeToUser(bytes32): 0xba6f5680 - users(address): 0xa87430ba - levels(uint256): 0xb2596a67 - owner(): 0x8da5cb5b - goodDollar(): 0x119e5bf3 - active(): 0x02fb0c5e - stats(): 0xd80528ae - levelExpirationEnabled(): 0xa1df6fd3 - minimumClaims(): 0xc121cb92 - minimumDays(): 0xdf0dca04 -events: - InviteeJoined(address,address): 0xd8c638d8979e2ba5dba1f0d66246ee4b1c54b838f0e0a2b601365345eb23b051 - InviterBounty(address,address,uint256,uint256,bool): 0x6081787cd1bd02ab1576c52f03e8710d792d460e7881c3155d77d23893f3768b -errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/MentoBroker.abi.yaml b/.agents/skills/gooddollar/references/contracts/MentoBroker.abi.yaml deleted file mode 100644 index f6f47f46..00000000 --- a/.agents/skills/gooddollar/references/contracts/MentoBroker.abi.yaml +++ /dev/null @@ -1,259 +0,0 @@ -# Mento Broker (IBroker + IBrokerAdmin) — swap router and trading-limit gate for GoodDollar Mento rails -# Interface definitions are vendored in GoodProtocol; implementation lives in mento-core. - -meta: - name: Broker - version: IBroker / IBrokerAdmin - source: - - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/contracts/MentoInterfaces.sol - - https://raw.githubusercontent.com/mento-org/mento-core/main/contracts/swap/Broker.sol - - https://raw.githubusercontent.com/mento-org/mento-core/main/contracts/interfaces/IBroker.sol - implements: [IBroker, IBrokerAdmin] - note: > - swapIn is exact-input; swapOut is exact-output. Quotes route through the - configured exchangeProvider + exchangeId. Reverts use require strings in - Broker.sol plus TradingLimits library checks ("amountOutMin not met", - "amountInMax exceeded", trading-limit errors). - deployments: - mainnet: - production-celo: - MentoBroker: - networkId: 42220 - address: "0x88de45906D4F5a57315c133620cfa484cB297541" - creationBlock: 31415857 - production-xdc: - MentoBroker: - networkId: 50 - address: "0x88de45906D4F5a57315c133620cfa484cB297541" - creationBlock: 100091095 - related: - - https://docs.gooddollar.org/for-developers/core-contracts/mentobroker - - references/guides/swap.md - -initialize: - mutability: nonpayable - access: initializer - inputs: - - _exchangeProviders: address[] - - _reserves: address[] - outputs: [] - -setReserves: - notes: - - "Updates reserve address per already-listed provider without changing provider registration." - mutability: nonpayable - access: owner - inputs: - - _exchangeProviders: address[] - - _reserves: address[] - outputs: [] - emits: [ReserveSet] - errors: - - EXCHANGE_PROVIDER_MISSING - - RESERVE_ZERO - -addExchangeProvider: - mutability: nonpayable - access: owner - inputs: - - exchangeProvider: address - - reserve: address - outputs: - - index: uint256 - emits: [ExchangeProviderAdded, ReserveSet] - errors: - - PROVIDER_ALREADY_LISTED - - PROVIDER_ZERO - - RESERVE_ZERO - -removeExchangeProvider: - mutability: nonpayable - access: owner - inputs: - - exchangeProvider: address - - index: uint256 - outputs: [] - emits: [ExchangeProviderRemoved] - errors: - - INDEX_MISMATCH - -getAmountIn: - notes: - - "Exact-output quote path: returns required tokenIn for target amountOut." - - "Pre-checks collateral reserve balance when tokenOut is collateral." - mutability: view - inputs: - - exchangeProvider: address - - exchangeId: bytes32 - - tokenIn: address - - tokenOut: address - - amountOut: uint256 - outputs: - - amountIn: uint256 - errors: - - EXCHANGE_PROVIDER_MISSING - - INSUFFICIENT_RESERVE_BALANCE - -getAmountOut: - notes: - - "Exact-input quote path: returns expected tokenOut for fixed amountIn." - - "Pre-checks collateral reserve balance when tokenOut is collateral." - mutability: view - inputs: - - exchangeProvider: address - - exchangeId: bytes32 - - tokenIn: address - - tokenOut: address - - amountIn: uint256 - outputs: - - amountOut: uint256 - errors: - - EXCHANGE_PROVIDER_MISSING - - INSUFFICIENT_RESERVE_BALANCE - -swapIn: - notes: - - "Transfers tokenIn from msg.sender then delivers tokenOut; enforces amountOut >= amountOutMin after exchange + limits." - mutability: nonpayable - access: nonReentrant - inputs: - - exchangeProvider: address - - exchangeId: bytes32 - - tokenIn: address - - tokenOut: address - - amountIn: uint256 - - amountOutMin: uint256 - outputs: - - amountOut: uint256 - emits: [Swap] - errors: - - EXCHANGE_PROVIDER_MISSING - - INSUFFICIENT_RESERVE_BALANCE - - AMOUNT_OUT_MIN_NOT_MET - - TRADING_LIMIT_OR_SAFE_TRANSFER - -swapOut: - notes: - - "Computes required tokenIn then pulls up to amountInMax; reverts if amountIn would exceed cap." - mutability: nonpayable - access: nonReentrant - inputs: - - exchangeProvider: address - - exchangeId: bytes32 - - tokenIn: address - - tokenOut: address - - amountOut: uint256 - - amountInMax: uint256 - outputs: - - amountIn: uint256 - emits: [Swap] - errors: - - EXCHANGE_PROVIDER_MISSING - - INSUFFICIENT_RESERVE_BALANCE - - AMOUNT_IN_MAX_EXCEEDED - - TRADING_LIMIT_OR_SAFE_TRANSFER - -burnStableTokens: - notes: - - "Owner-maintained escape hatch burning stables from msg.sender via reserve plumbing." - mutability: nonpayable - access: owner - inputs: - - token: address - - amount: uint256 - outputs: - - ok: bool - errors: - - COLLATERAL_TRANSFER_FAILED - -configureTradingLimit: - notes: - - "Sets per (exchangeId, token) limit config used by swapIn/swapOut guardTradingLimits checks." - mutability: nonpayable - access: owner - inputs: - - exchangeId: bytes32 - - token: address - - config: TradingLimits.Config - outputs: [] - emits: [TradingLimitConfigured] - -getExchangeProviders: - notes: - - "Discovery helper for clients that need the active provider set before quoting/swapping." - mutability: view - inputs: [] - outputs: - - providers: address[] - -exchangeProviders: - mutability: view - inputs: - - index: uint256 - outputs: - - exchangeProvider: address - -isExchangeProvider: - mutability: view - inputs: - - exchangeProvider: address - outputs: - - ok: bool - -tradingLimitsState: - mutability: view - inputs: - - eid: bytes32 - outputs: - - state: TradingLimits.State - -tradingLimitsConfig: - mutability: view - inputs: - - eid: bytes32 - outputs: - - config: TradingLimits.Config - -events: - Swap: - indexed: - - exchangeId: bytes32 - - trader: address - - tokenIn: address - data: - - exchangeProvider: address - - tokenOut: address - - amountIn: uint256 - - amountOut: uint256 - TradingLimitConfigured: - indexed: [] - data: - - exchangeId: bytes32 - - token: address - - config: TradingLimits.Config - ExchangeProviderAdded: - indexed: - - exchangeProvider: address - data: [] - ExchangeProviderRemoved: - indexed: - - exchangeProvider: address - data: [] - ReserveSet: - indexed: - - newAddress: address - - prevAddress: address - data: [] - -errors: - EXCHANGE_PROVIDER_MISSING: "ExchangeProvider does not exist" - EXCHANGE_PROVIDER_EXISTS: "ExchangeProvider already exists in the list" - PROVIDER_ZERO: "ExchangeProvider address can't be 0" - RESERVE_ZERO: "Reserve address can't be 0" - INDEX_MISMATCH: "index doesn't match provider" - INSUFFICIENT_RESERVE_BALANCE: "Insufficient balance in reserve" - AMOUNT_OUT_MIN_NOT_MET: "amountOutMin not met" - AMOUNT_IN_MAX_EXCEEDED: "amountInMax exceeded" - COLLATERAL_TRANSFER_FAILED: "Transfer of the collateral asset failed" - AMOUNT_TOO_LARGE: "amountIn too large / amountOut too large — uint256 to int256 safety checks" - TRADING_LIMIT_OR_SAFE_TRANSFER: "TradingLimits library reverts or ERC20 safety failures during swap" diff --git a/.agents/skills/gooddollar/references/contracts/MentoBroker.selectors.yaml b/.agents/skills/gooddollar/references/contracts/MentoBroker.selectors.yaml deleted file mode 100644 index 249edce4..00000000 --- a/.agents/skills/gooddollar/references/contracts/MentoBroker.selectors.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by scripts/selectors.mjs -functions: - initialize(address[],address[]): 0x73cf25f8 - setReserves(address[],address[]): 0xddeb9dd2 - addExchangeProvider(address,address): 0x770d0a34 - removeExchangeProvider(address,uint256): 0x04710d53 - getAmountIn(address,bytes32,address,address,uint256): 0x04e45640 - getAmountOut(address,bytes32,address,address,uint256): 0xa20f2305 - swapIn(address,bytes32,address,address,uint256,uint256): 0xddbbe850 - swapOut(address,bytes32,address,address,uint256,uint256): 0xd163b135 - burnStableTokens(address,uint256): 0x131cab2a - configureTradingLimit(bytes32,address,(uint32,uint32,int48,int48,int48,int48,int48,uint8)): 0xa868d140 - getExchangeProviders(): 0x2cac2568 - exchangeProviders(uint256): 0xc4454fdc - isExchangeProvider(address): 0xd1d786b1 - tradingLimitsState(bytes32): 0xf01ecd17 - tradingLimitsConfig(bytes32): 0x821a816c -events: - Swap(bytes32,address,address,address,address,uint256,uint256): 0x46e6aeaebfb8f1b6a9be6403b5fc420d9827172046f365e786acb9d5b56c9409 - TradingLimitConfigured(bytes32,address,(uint32,uint32,int48,int48,int48,int48,int48,uint8)): 0x3ed50aa123e1e547aa90fcf88624ef8278d253e252bb1feac75eda93e1905bb6 - ExchangeProviderAdded(address): 0x2ee2cb0721ec60b86190cae5c48e25064b69b35abad32452a4ec99d232033de2 - ExchangeProviderRemoved(address): 0x29e92ab2e30f4f74283034c28c451b6faac986b554f1808101eb6418bdba19d4 - ReserveSet(address,address): 0xb69e1c416d8be92ac92c8e97e77c4626fba5e6ab50161099f659ea3303479e50 -errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/MessagePassingBridge.abi.yaml b/.agents/skills/gooddollar/references/contracts/MessagePassingBridge.abi.yaml deleted file mode 100644 index d4bb1f2d..00000000 --- a/.agents/skills/gooddollar/references/contracts/MessagePassingBridge.abi.yaml +++ /dev/null @@ -1,542 +0,0 @@ -meta: - name: MessagePassingBridge - version: Axelar + LayerZero message passing - source: - - https://raw.githubusercontent.com/GoodDollar/GoodBridge/master/packages/bridge-contracts/contracts/messagePassingBridge/MessagePassingBridge.sol - - https://raw.githubusercontent.com/GoodDollar/GoodBridge/master/packages/bridge-contracts/contracts/messagePassingBridge/IMessagePassingBridge.sol - note: > - Bridge implementation that burns source tokens and mints destination tokens, - transporting requests through Axelar or LayerZero. - Cross-chain transport uses msg.value on the source (LayerZero estimateSendFee path, Axelar gas prepay); destination mint applies bridgeFees bps to the delivered G$ amount. See references/guides/bridge.md "Bridge fee context". - deployments: - mainnet: - production: - MpbBridge: - networkId: 122 - address: "0xa3247276DbCC76Dd7705273f766eB3E8a5ecF4a5" - creationBlock: 25464921 - production-celo: - MpbBridge: - networkId: 42220 - address: "0xa3247276DbCC76Dd7705273f766eB3E8a5ecF4a5" - creationBlock: 21473545 - production-xdc: - MpbBridge: - networkId: 50 - address: "0xa3247276DbCC76Dd7705273f766eB3E8a5ecF4a5" - creationBlock: 95254417 - related: - - references/guides/bridge.md - -initialize: - notes: - - "Initial bootstrap for DAO wiring and bridge policy state." - - "Sets guardian to msg.sender and uses NameService UBISCHEME as feeRecipient fallback to avatar when missing." - - "Preloads default LZ mappings for Ethereum, Celo, Fuse, and XDC using this contract as trusted remote." - mutability: nonpayable - access: initializer - inputs: - - nameService: address - - limits: (uint256,uint256,uint256,uint256,bool) - - fees: (uint256,uint256,uint256) - outputs: [] - -upgrade: - notes: - - "Reinitializer for migration path; re-applies default LZ mappings through internal add function." - - "Used when upgrading existing proxy deployments to mapping-aware version." - mutability: nonpayable - access: reinitializer - inputs: [] - outputs: [] - -addLzChainSupport: - notes: - - "Administrative chain wiring method for LayerZero transport." - - "Writes both forward (chainId->lzChainId) and reverse (lzChainId->chainId) mappings." - - "Also updates trustedRemoteLookup to enforce source-contract authenticity on receive." - mutability: nonpayable - access: ownerOrAvatarOrGuardian - inputs: - - lzChainId: uint16 - - chainId: uint256 - - remote: address - outputs: [] - -approveRequest: - notes: - - "Manual override for stuck/exceptional inbound requests." - - "When approved, destination _bridgeFrom skips limits enforcement for that request id." - mutability: nonpayable - access: ownerOrAvatarOrGuardian - inputs: - - id: uint256 - outputs: [] - -preventRequest: - notes: - - "Emergency/manual block for a specific request id." - - "Prevents execution by pre-marking executedRequests[id]=true." - mutability: nonpayable - access: ownerOrAvatarOrGuardian - inputs: - - id: uint256 - outputs: [] - -setFeeRecipient: - notes: - - "Sets destination fee mint recipient." - - "If feeRecipient is zero, fee minting is skipped in _bridgeFrom." - mutability: nonpayable - access: ownerOrAvatarOrGuardian - inputs: - - recipient: address - outputs: [] - -setBridgeLimits: - notes: - - "Updates operational throttles: min amount, tx cap, account/day cap, global/day cap, whitelist mode." - - "These limits are checked by canBridge and enforced in _enforceLimits." - mutability: nonpayable - access: ownerOrAvatarOrGuardian - inputs: - - limits: (uint256,uint256,uint256,uint256,bool) - outputs: [] - -setBridgeFees: - notes: - - "Updates bridge fee parameters in basis points with min/max clamps." - - "Reverts when fee exceeds 10000 bps." - - "Effective fee deduction is computed in _takeFee during destination execution." - mutability: nonpayable - access: ownerOrAvatarOrGuardian - inputs: - - fees: (uint256,uint256,uint256) - outputs: [] - -setDisabledBridges: - notes: - - "Toggles allowlist state for source bridge combinations." - - "Key format is keccak256(abi.encode(sourceChainId, BridgeService))." - - "Checked in _bridgeFrom before request execution." - mutability: nonpayable - access: ownerOrAvatarOrGuardian - inputs: - - bridgeKeys: bytes32[] - - disabled: bool[] - outputs: [] - -setFaucet: - notes: - - "Configures optional faucet top-up callback used after successful destination processing." - mutability: nonpayable - access: ownerOrAvatarOrGuardian - inputs: - - _faucet: address - outputs: [] - -setGuardian: - notes: - - "Rotates guardian authority used by owner/avatar/guardian admin gate." - mutability: nonpayable - access: ownerOrAvatarOrGuardian - inputs: - - _guardian: address - outputs: [] - -withdraw: - notes: - - "Avatar-only rescue function for arbitrary token balances held by bridge." - - "Amount 0 means full balance withdrawal." - mutability: nonpayable - access: avatar - inputs: - - token: address - - amount: uint256 - outputs: [] - -pauseBridge: - notes: - - "Sets isClosed state for outbound flow." - - "When closed, _bridgeTo reverts with BRIDGE_LIMITS('closed')." - mutability: nonpayable - access: ownerOrAvatarOrGuardian - inputs: - - isPaused: bool - outputs: [] - -canBridge: - notes: - - "Purely diagnostic read path for preflight checks." - - "Returns (ok, reason) using BridgeHelperLibrary policy evaluation." - - "Includes close flag, whitelist gate, and per-account/global daily limit windows." - mutability: view - inputs: - - from: address - - amount: uint256 - outputs: - - isWithinLimit: bool - - error: string - -bridgeTo: - notes: - - "Generic outbound bridge entrypoint selecting BridgeService.AXELAR or BridgeService.LZ." - - "Burns source-side token amount, normalizes to 18 decimals, and emits BridgeRequest with request id." - - "Requires msg.value for transport fee payment." - mutability: payable - access: anyone - inputs: - - target: address - - targetChainId: uint256 - - amount: uint256 - - bridge: uint8 - outputs: [] - -bridgeToWithLz: - notes: - - "Outbound convenience wrapper fixed to LayerZero transport." - - "If adapterParams is empty, contract injects default type-1 options with 400k gas." - - "Compares required native fee from estimateSendFee against msg.value and reverts on underpayment." - mutability: payable - access: anyone - inputs: - - target: address - - targetChainId: uint256 - - amount: uint256 - - adapterParams: bytes - outputs: [] - -bridgeToWithAxelar: - notes: - - "Outbound convenience wrapper fixed to Axelar transport." - - "Uses gasRefundAddress when provided, otherwise msg.sender as refund recipient." - mutability: payable - access: anyone - inputs: - - target: address - - targetChainId: uint256 - - amount: uint256 - - gasRefundAddress: address - outputs: [] - -toLzChainId: - notes: - - "Lookup helper from local chain id to LayerZero chain id." - mutability: view - inputs: - - chainId: uint256 - outputs: - - lzChainId: uint16 - -fromLzChainId: - notes: - - "Lookup helper from LayerZero chain id to local chain id." - mutability: view - inputs: - - lzChainId: uint16 - outputs: - - chainId: uint256 - -toAxelarChainId: - notes: - - "Pure mapping in current implementation: chainId 1 -> 'Ethereum', 5 -> 'ethereum-2', 42220 and 44787 -> 'celo'; any other id returns empty string and outbound Axelar reverts UNSUPPORTED_CHAIN." - mutability: pure - inputs: - - chainId: uint256 - outputs: - - axlChainId: string - -fromAxelarChainId: - notes: - - "Maps Axelar chain-name strings back into local numeric chain ids." - mutability: pure - inputs: - - axlChainId: string - outputs: - - chainId: uint256 - -estimateSendFee: - notes: - - "Transport fee preflight for LZ path." - - "Builds payload with request id set to 0 for estimation." - - "Returns (0,0) on endpoint estimation failure in handler implementation." - - "_normalizedAmount must match BridgeHelperLibrary.normalizeFromTokenTo18Decimals burned raw amount using native token decimals on the outbound chain." - mutability: view - inputs: - - _dstChainId: uint16 - - _fromAddress: address - - _toAddress: address - - _normalizedAmount: uint256 - - _useZro: bool - - _adapterParams: bytes - outputs: - - nativeFee: uint256 - - zroFee: uint256 - -lzReceive: - notes: - - "LayerZero receive entrypoint." - - "Validates caller is configured endpoint before dispatching nonblocking receive pipeline." - mutability: nonpayable - inputs: - - _srcChainId: uint16 - - _srcAddress: bytes - - _nonce: uint64 - - _payload: bytes - outputs: [] - -lzEndpoint_: - notes: - - "Immutable LayerZero endpoint configured in constructor." - mutability: view - inputs: [] - outputs: - - endpoint: address - -HOME_CHAIN_ID: - notes: - - "Immutable deployment home chain marker." - mutability: view - inputs: [] - outputs: - - chainId: uint32 - -guardian: - notes: - - "Current guardian account authorized for admin operations." - mutability: view - inputs: [] - outputs: - - account: address - -executedRequests: - notes: - - "Replay protection state for inbound request ids." - mutability: view - inputs: - - id: uint256 - outputs: - - done: bool - -disabledSourceBridges: - notes: - - "Per-source bridge toggle state keyed by (sourceChainId, bridgeService) hash." - mutability: view - inputs: - - key: bytes32 - outputs: - - disabled: bool - -isClosed: - notes: - - "Outbound bridge pause state." - mutability: view - inputs: [] - outputs: - - closed: bool - -bridgeFees: - notes: - - "Current fee policy parameters: minFee, maxFee, feeBps." - mutability: view - inputs: [] - outputs: - - minFee: uint256 - - maxFee: uint256 - - fee: uint256 - -bridgeLimits: - notes: - - "Current bridge traffic limits and whitelist policy." - mutability: view - inputs: [] - outputs: - - dailyLimit: uint256 - - txLimit: uint256 - - accountDailyLimit: uint256 - - minAmount: uint256 - - onlyWhitelisted: bool - -bridgeDailyLimit: - notes: - - "Aggregate 24h bridge usage tracker." - mutability: view - inputs: [] - outputs: - - lastTransferReset: uint256 - - bridged24Hours: uint256 - -accountsDailyLimit: - notes: - - "Per-account 24h usage tracker." - mutability: view - inputs: - - account: address - outputs: - - lastTransferReset: uint256 - - bridged24Hours: uint256 - -faucet: - notes: - - "Optional gas top-up contract used on destination completion." - mutability: view - inputs: [] - outputs: - - addr: address - -currentId: - notes: - - "Monotonic nonce component used to derive outbound request ids." - mutability: view - inputs: [] - outputs: - - id: uint256 - -lzChainIdsMapping: - notes: - - "Local chain id -> LayerZero chain id mapping." - mutability: view - inputs: - - chainId: uint256 - outputs: - - lzChainId: uint16 - -feeRecipient: - notes: - - "Address receiving bridge fee mints on destination." - mutability: view - inputs: [] - outputs: - - recipient: address - -approvedRequests: - notes: - - "Manual approvals allowing request execution above standard limits." - mutability: view - inputs: - - id: uint256 - outputs: - - approved: bool - -lzChainToIdsMapping: - notes: - - "LayerZero chain id -> local chain id mapping." - mutability: view - inputs: - - lzChainId: uint16 - outputs: - - chainId: uint256 - -trustedRemoteLookup: - notes: - - "Trusted remote path used to authenticate inbound LZ/Axelar source contract." - mutability: view - inputs: - - lzChainId: uint16 - outputs: - - path: bytes - -gateway: - notes: - - "Axelar gateway address inherited from AxelarExecutable." - mutability: view - inputs: [] - outputs: - - addr: address - -gasService: - notes: - - "Axelar gas service used to prepay cross-chain execution." - mutability: view - inputs: [] - outputs: - - addr: address - -nameService: - notes: - - "DAO NameService pointer from DAOUpgradeableContract." - mutability: view - inputs: [] - outputs: - - addr: address - -avatar: - notes: - - "DAO avatar authority address." - mutability: view - inputs: [] - outputs: - - addr: address - -owner: - notes: - - "Ownable owner from upgradeable ownership flow." - mutability: view - inputs: [] - outputs: - - account: address - -events: - BridgeRequest: - indexed: - - from: address - - to: address - - id: uint256 - data: - - targetChainId: uint256 - - normalizedAmount: uint256 - - timestamp: uint256 - - bridge: uint8 - note: "Outbound intent event; relayers consume this to deliver payload on destination." - ExecutedTransfer: - indexed: - - from: address - - to: address - - id: uint256 - data: - - normalizedAmount: uint256 - - fee: uint256 - - sourceChainId: uint256 - - bridge: uint8 - note: "Destination completion event after replay checks, source-auth checks, limit checks, and mint/fee mint." - FalseSender: - indexed: [] - data: - - sourceChainId: uint256 - - sourceAddress: address - note: "Inbound message ignored because source contract does not match trusted remote path." - -errors: - AlreadyInitialized: "LZ chain support already initialized for this lz chain id." - NOT_GUARDIAN: - inputs: - - sender: address - WRONG_TOKEN: - inputs: - - token: address - INVALID_TARGET_OR_CHAINID: - inputs: - - target: address - - chainId: uint256 - BRIDGE_LIMITS: - inputs: - - reason: string - TRANSFER_FROM: "Source burn/transferFrom style failure." - TRANSFER: "Destination transfer style failure." - ALREADY_EXECUTED: - inputs: - - requestId: uint256 - MISSING_FEE: "Bridge call requires msg.value for transport fee." - UNSUPPORTED_CHAIN: - inputs: - - chainId: uint256 - LZ_FEE: - inputs: - - required: uint256 - - sent: uint256 - INVALID_SENDER: - inputs: - - _srcAddress: bytes - INVALID_ENDPOINT: - inputs: - - lzEndpoint: address diff --git a/.agents/skills/gooddollar/references/contracts/MessagePassingBridge.selectors.yaml b/.agents/skills/gooddollar/references/contracts/MessagePassingBridge.selectors.yaml deleted file mode 100644 index 104b9435..00000000 --- a/.agents/skills/gooddollar/references/contracts/MessagePassingBridge.selectors.yaml +++ /dev/null @@ -1,52 +0,0 @@ -# Generated by scripts/selectors.mjs -functions: - initialize(address,(uint256,uint256,uint256,uint256,bool),(uint256,uint256,uint256)): 0xfad16abe - upgrade(): 0xd55ec697 - addLzChainSupport(uint16,uint256,address): 0xc01c8123 - approveRequest(uint256): 0xd7d1bbdb - preventRequest(uint256): 0xc612f9ea - setFeeRecipient(address): 0xe74b981b - setBridgeLimits((uint256,uint256,uint256,uint256,bool)): 0x29b70872 - setBridgeFees((uint256,uint256,uint256)): 0x82dc737d - setDisabledBridges(bytes32[],bool[]): 0xc78ab882 - setFaucet(address): 0xd8b31c77 - setGuardian(address): 0x8a0dac4a - withdraw(address,uint256): 0xf3fef3a3 - pauseBridge(bool): 0x1a394795 - canBridge(address,uint256): 0x3095634a - bridgeTo(address,uint256,uint256,uint8): 0x1fec5c5c - bridgeToWithLz(address,uint256,uint256,bytes): 0xc56bbdd9 - bridgeToWithAxelar(address,uint256,uint256,address): 0x40a00aaf - toLzChainId(uint256): 0x5b23a990 - fromLzChainId(uint16): 0x16ad5512 - toAxelarChainId(uint256): 0x9a9ee081 - fromAxelarChainId(string): 0x56026f37 - estimateSendFee(uint16,address,address,uint256,bool,bytes): 0x05fead15 - lzReceive(uint16,bytes,uint64,bytes): 0x001d3567 - lzEndpoint_(): 0x020051cf - HOME_CHAIN_ID(): 0x8f65be85 - guardian(): 0x452a9320 - executedRequests(uint256): 0x425cfb53 - disabledSourceBridges(bytes32): 0x69c20d36 - isClosed(): 0xc2b6b58c - bridgeFees(): 0x7b0240c0 - bridgeLimits(): 0xc6dd812f - bridgeDailyLimit(): 0xb2f7667f - accountsDailyLimit(address): 0xd4227947 - faucet(): 0xde5f72fd - currentId(): 0xe00dd161 - lzChainIdsMapping(uint256): 0xb5569f18 - feeRecipient(): 0x46904840 - approvedRequests(uint256): 0x0dce292e - lzChainToIdsMapping(uint16): 0x73800fc4 - trustedRemoteLookup(uint16): 0x7533d788 - gateway(): 0x116191b6 - gasService(): 0x6a22d8cc - nameService(): 0x3e6326fc - avatar(): 0x5aef7de6 - owner(): 0x8da5cb5b -events: - BridgeRequest(address,address,uint256,uint256,uint256,uint256,uint8): 0x34b675c8c84b6a9e7979a0d3a54f2b036a19d6444167091a925af2d81e8e66fe - ExecutedTransfer(address,address,uint256,uint256,uint256,uint256,uint8): 0x81e772e0c4366ddbae472926005267ef278dbb257be45dfe97c676ceae348dbe - FalseSender(uint256,address): 0x1eafb58197ea0dc76b9278ccad47f61a239f00b017d89a19081a89321cce213d -errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/NameService.abi.yaml b/.agents/skills/gooddollar/references/contracts/NameService.abi.yaml deleted file mode 100644 index 01692b7a..00000000 --- a/.agents/skills/gooddollar/references/contracts/NameService.abi.yaml +++ /dev/null @@ -1,104 +0,0 @@ -# NameService — string-key to address registry for GoodProtocol deployments -# Resolves keys such as IDENTITY, UBISCHEME, GOODDOLLAR, GDAO_STAKING. - -meta: - name: NameService - version: UUPS-upgradeable v1 - source: - - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/contracts/utils/NameService.sol - implements: [] - inherits: [Initializable, UUPSUpgradeable] - note: > - Almost every integration starts here. Writes are restricted to the DAO - avatar. Reads are permissionless. Use getAddress(string) with the same - key strings your deployment documented in releases/deployment.json. - deployments: - mainnet: - production: - NameService: - networkId: 122 - address: "0xec6dcE387B1616a0c44fF2E4fA9E90e53Cf14eb0" - creationBlock: 15740314 - production-celo: - NameService: - networkId: 42220 - address: "0x0F5dB7a64A6a64052693676CA898EC7F7A94FF4e" - creationBlock: 17237962 - production-xdc: - NameService: - networkId: 50 - address: "0x1e5154Bf5e31FF56051bbd45958b879Fb7a290FE" - creationBlock: 95143608 - related: - - https://docs.gooddollar.org/for-developers/core-contracts/nameservice - - https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json - -addresses: - # Solidity auto-getter for public mapping addresses(bytes32 => address). - notes: - - "Prefer getAddress(string) off-chain; this expects keccak256(abi.encodePacked(name)) for manual lookups." - mutability: view - inputs: - - nameHash: bytes32 - outputs: - - addr: address - -dao: - mutability: view - inputs: [] - outputs: - - controller: address - -initialize: - notes: - - "Initializer wires Controller and optional pre-hashed name batch." - - "Automatically registers CONTROLLER and AVATAR hashes to dao and dao.avatar()." - mutability: nonpayable - access: initializer - inputs: - - _dao: address - - _nameHashes: bytes32[] - - _addresses: address[] - outputs: [] - -setAddress: - notes: - - "Stores addresses[keccak256(bytes(name))] = addr and emits AddressChanged." - mutability: nonpayable - access: avatar - inputs: - - name: string - - addr: address - outputs: [] - emits: [AddressChanged] - errors: [ONLY_AVATAR] - -setAddresses: - notes: - - "Batch write by raw bytes32 keys; no per-name event emission in this loop." - mutability: nonpayable - access: avatar - inputs: - - hash: bytes32[] - - addrs: address[] - outputs: [] - errors: [ONLY_AVATAR] - -getAddress: - notes: - - "Primary lookup used by claim, staking, identity, and swap integrations." - mutability: view - inputs: - - name: string - outputs: - - addr: address - -events: - AddressChanged: - indexed: [] - data: - - name: string - - addr: address - -errors: - ONLY_AVATAR: "only avatar can call this method — revert string on any avatar-gated path including UUPS _authorizeUpgrade." diff --git a/.agents/skills/gooddollar/references/contracts/NameService.selectors.yaml b/.agents/skills/gooddollar/references/contracts/NameService.selectors.yaml deleted file mode 100644 index 48e1bcfa..00000000 --- a/.agents/skills/gooddollar/references/contracts/NameService.selectors.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# Generated by scripts/selectors.mjs -functions: - addresses(bytes32): 0x699f200f - dao(): 0x4162169f - initialize(address,bytes32[],address[]): 0xd41b4e72 - setAddress(string,address): 0x9b2ea4bd - setAddresses(bytes32[],address[]): 0x4ab01f5b - getAddress(string): 0xbf40fac1 -events: - AddressChanged(string,address): 0x135cf55549d8538a41f19f46cc85625da93e68b63484cca8fcb9aaf19e520137 -errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/SuperToken.abi.yaml b/.agents/skills/gooddollar/references/contracts/SuperToken.abi.yaml deleted file mode 100644 index abfc91cb..00000000 --- a/.agents/skills/gooddollar/references/contracts/SuperToken.abi.yaml +++ /dev/null @@ -1,1033 +0,0 @@ -# SuperToken — the Superfluid Protocol's token standard -# Merges three variants into a single ABI surface: -# • Wrapper Super Token — wraps an existing ERC-20 (upgrade / downgrade) -# • Native Asset Super Token (SETH) — wraps chain-native asset (upgradeByETH / downgradeToETH) -# • Pure Super Token — pre-minted, no underlying token (upgrade/downgrade revert) -# -# Inherits ERC-20, ERC-777, ERC-2612 (permit), EIP-5267, and the Superfluid real-time -# balance and agreement hosting layer from SuperfluidToken. -# -# Proxy / upgradability functions are omitted: -# castrate, getCodeAddress, proxiableUUID, initialize, initializeWithAdmin, updateCode -# Proxy-related events are also omitted: CodeUpdated, Initialized. - -meta: - name: SuperToken - version: v1 - source: - - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol - - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol - - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/superfluid/SuperfluidToken.sol - - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/interfaces/superfluid/IYieldBackend.sol - inherits: [SuperfluidToken, ISuperToken, UUPSProxiable] - yield_backends: - - name: AaveYieldBackend - note: "Production backend for ERC-20 wrapper tokens using Aave V3" - source: https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/superfluid/AaveYieldBackend.sol - - name: AaveETHYieldBackend - note: "Variant for native-asset (ETH/WETH) wrapper tokens using Aave V3" - source: https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/superfluid/AaveETHYieldBackend.sol - - name: ERC4626YieldBackend - note: "Generic backend for any ERC-4626 vault" - source: https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/superfluid/ERC4626YieldBackend.sol - - name: SparkYieldBackend - note: "Extends ERC4626YieldBackend for Spark Protocol vaults with referral tracking" - source: https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/superfluid/SparkYieldBackend.sol - variants: - wrapper: "Standard ERC-20 wrapper — upgrade() / downgrade()" - native-asset: "Native asset wrapper (SETH) — upgradeByETH() / downgradeToETH()" - pure: "Pre-minted supply, no underlying — upgrade/downgrade revert" - note: > - Super Tokens are deployed as individual proxies (one per token) by the - SuperTokenFactory. There is no single canonical deployment address. - All proxies share the same logic contract which is upgraded via governance. - -# wrapper super token — a Super Token backed by an ERC-20 underlying token -# native asset super token (SETH) — a Super Token backed by the chain-native asset (ETH, MATIC, etc.) -# pure super token — a Super Token with no underlying; supply is pre-minted at deploy time -# underlying token — the ERC-20 token that a wrapper super token wraps -# real-time balance — balance that changes continuously based on active agreements (CFA/GDA) -# available balance — real-time balance minus locked deposits -# critical — an account whose available balance is negative -# solvent — an account whose real-time balance (before deposit deductions) is non-negative -# operator (ERC-777) — an address authorized to send/burn tokens on behalf of a holder -# buffer / deposit — collateral locked while an outgoing flow is active - -# == ERC-20 Token Info == - -name: - mutability: view - inputs: [] - outputs: - - string - -symbol: - mutability: view - inputs: [] - outputs: - - string - -decimals: - # Always returns 18 regardless of underlying token decimals. - notes: - - "Always returns 18, regardless of the underlying token's decimals (e.g. USDC has 6). The upgrade/downgrade functions handle decimal conversion internally." - mutability: pure - inputs: [] - outputs: - - uint8 - -totalSupply: - mutability: view - inputs: [] - outputs: - - uint256 - -# == ERC-20 Balance & Allowance == - -balanceOf: - # Returns max(0, availableBalance) — clamps negative balances to zero. - # For the full real-time picture, use realtimeBalanceOfNow. - notes: - - "Returns max(0, availableBalance) — negative/critical balances are clamped to zero. Use realtimeBalanceOfNow to detect critical accounts." - mutability: view - inputs: - - account: address - outputs: - - balance: uint256 - -allowance: - mutability: view - inputs: - - account: address - - spender: address - outputs: - - uint256 - -approve: - mutability: nonpayable - access: anyone - inputs: - - spender: address - - amount: uint256 - outputs: - - bool - emits: [Approval] - errors: [SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS, SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS] - -increaseAllowance: - mutability: nonpayable - access: anyone - inputs: - - spender: address - - addedValue: uint256 - outputs: - - bool - emits: [Approval] - errors: [SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS, SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS] - -decreaseAllowance: - mutability: nonpayable - access: anyone - inputs: - - spender: address - - subtractedValue: uint256 - outputs: - - bool - emits: [Approval] - errors: [SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS, SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS] - -# == ERC-20 Transfers == - -transfer: - # ERC-20 transfer. Does NOT invoke ERC-777 send/receive hooks (by design). - mutability: nonpayable - access: anyone - inputs: - - recipient: address - - amount: uint256 - outputs: - - bool - emits: [Sent, Transfer] - errors: [SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS, SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS, SF_TOKEN_MOVE_INSUFFICIENT_BALANCE] - -transferFrom: - # ERC-20 transferFrom. Does NOT invoke ERC-777 send/receive hooks. - # Does NOT emit an Approval event on allowance spend (OZ v5 behaviour). - mutability: nonpayable - access: anyone - inputs: - - holder: address - - recipient: address - - amount: uint256 - outputs: - - bool - emits: [Sent, Transfer] - errors: [SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS, SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS, SF_TOKEN_MOVE_INSUFFICIENT_BALANCE] - -transferAll: - # Transfer the caller's entire balanceOf to recipient. - mutability: nonpayable - access: anyone # transfers msg.sender's entire balance - inputs: - - recipient: address - emits: [Sent, Transfer] - errors: [SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS, SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS, SF_TOKEN_MOVE_INSUFFICIENT_BALANCE] - -# == ERC-777 == - -granularity: - # Always returns 1. - mutability: pure - inputs: [] - outputs: - - uint256 - -send: - # ERC-777 send — invokes tokensToSend / tokensReceived hooks. - mutability: nonpayable - access: anyone - inputs: - - recipient: address - - amount: uint256 - - userData: bytes - emits: [Sent, Transfer] - errors: [SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS, SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS, SF_TOKEN_MOVE_INSUFFICIENT_BALANCE, SUPER_TOKEN_NOT_ERC777_TOKENS_RECIPIENT] - -burn: - # ERC-777 burn — in SuperToken this actually performs a downgrade (unwrap). - notes: - - "Gotcha: Reverts with SUPER_TOKEN_NO_UNDERLYING_TOKEN on Pure Super Tokens." - mutability: nonpayable - access: anyone - inputs: - - amount: uint256 - - userData: bytes - emits: [Burned, Transfer, TokenDowngraded] - errors: [SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS, SF_TOKEN_BURN_INSUFFICIENT_BALANCE, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED] - -authorizeOperator: - mutability: nonpayable - access: anyone - inputs: - - operator: address - emits: [AuthorizedOperator] - -revokeOperator: - mutability: nonpayable - access: anyone - inputs: - - operator: address - emits: [RevokedOperator] - -isOperatorFor: - mutability: view - inputs: - - operator: address - - tokenHolder: address - outputs: - - bool - -defaultOperators: - mutability: view - inputs: [] - outputs: - - "address[]" - -operatorSend: - # ERC-777 operatorSend — invokes tokensToSend / tokensReceived hooks. - mutability: nonpayable - access: operator - inputs: - - sender: address - - recipient: address - - amount: uint256 - - userData: bytes - - operatorData: bytes - emits: [Sent, Transfer] - errors: [SUPER_TOKEN_CALLER_IS_NOT_OPERATOR_FOR_HOLDER, SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS, SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS, SF_TOKEN_MOVE_INSUFFICIENT_BALANCE, SUPER_TOKEN_NOT_ERC777_TOKENS_RECIPIENT] - -operatorBurn: - # ERC-777 operatorBurn — like burn(), actually performs a downgrade. - mutability: nonpayable - access: operator - inputs: - - account: address - - amount: uint256 - - userData: bytes - - operatorData: bytes - emits: [Burned, Transfer, TokenDowngraded] - errors: [SUPER_TOKEN_CALLER_IS_NOT_OPERATOR_FOR_HOLDER, SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS, SF_TOKEN_BURN_INSUFFICIENT_BALANCE, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED] - -# == ERC-20 Permit == - -permit: - mutability: nonpayable - access: anyone - inputs: - - owner: address - - spender: address - - value: uint256 - - deadline: uint256 - - v: uint8 - - r: bytes32 - - s: bytes32 - emits: [Approval] - errors: [SUPER_TOKEN_PERMIT_EXPIRED_SIGNATURE, SUPER_TOKEN_PERMIT_INVALID_SIGNER, ECDSAInvalidSignature, ECDSAInvalidSignatureLength, ECDSAInvalidSignatureS, SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS, SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS] - -nonces: - mutability: view - inputs: - - owner: address - outputs: - - uint256 - -DOMAIN_SEPARATOR: - mutability: view - inputs: [] - outputs: - - bytes32 - -eip712Domain: - # EIP-5267 domain retrieval. - mutability: view - inputs: [] - outputs: - - fields: bytes1 - - name: string - - version: string - - chainId: uint256 - - verifyingContract: address - - salt: bytes32 - - "extensions: uint256[]" - -# == ERC-20 Wrapping == -# These functions interact with the underlying ERC-20 token. -# They revert with SUPER_TOKEN_NO_UNDERLYING_TOKEN on Pure and Native Asset -# Super Tokens (use upgradeByETH / downgradeToETH for SETH). - -upgrade: - # Wrap underlying ERC-20 into Super Tokens for msg.sender. - # Requires prior ERC-20 approval on the underlying token. - notes: - - "Gotcha: amount is always in SuperToken decimals (18), regardless of the underlying token's decimals. The contract handles downscaling internally when pulling from the underlying via transferFrom. However, the ERC-20 approval on the underlying token must use the underlying's native decimals. Example: to wrap 1000 USDC, approve 1000e6 on USDC, then call upgrade(1000e18)." - mutability: nonpayable - access: anyone # wraps for msg.sender - inputs: - - amount: uint256 # in SuperToken decimals (always 18) - emits: [Minted, Transfer, TokenUpgraded] - errors: [SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED, SUPER_TOKEN_MINT_TO_ZERO_ADDRESS, SafeERC20FailedOperation, SafeCastOverflowedUintToInt] - -upgradeTo: - # Wrap underlying ERC-20 and mint Super Tokens to a different address. - mutability: nonpayable - access: anyone - inputs: - - to: address - - amount: uint256 - - userData: bytes - emits: [Minted, Transfer, TokenUpgraded] - errors: [SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED, SUPER_TOKEN_MINT_TO_ZERO_ADDRESS, SUPER_TOKEN_NOT_ERC777_TOKENS_RECIPIENT, SafeERC20FailedOperation, SafeCastOverflowedUintToInt] - -downgrade: - # Unwrap Super Tokens back to underlying ERC-20 for msg.sender. - mutability: nonpayable - access: anyone # unwraps to msg.sender - inputs: - - amount: uint256 - emits: [Burned, Transfer, TokenDowngraded] - errors: [SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS, SF_TOKEN_BURN_INSUFFICIENT_BALANCE, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED, SafeERC20FailedOperation] - -downgradeTo: - # Unwrap Super Tokens and send underlying ERC-20 to a different address. - mutability: nonpayable - access: anyone - inputs: - - to: address - - amount: uint256 - emits: [Burned, Transfer, TokenDowngraded] - errors: [SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS, SF_TOKEN_BURN_INSUFFICIENT_BALANCE, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED, SafeERC20FailedOperation] - -getUnderlyingToken: - # Returns address(0) for Pure and Native Asset Super Tokens. - mutability: view - inputs: [] - outputs: - - address - -getUnderlyingDecimals: - mutability: view - inputs: [] - outputs: - - uint8 - -toUnderlyingAmount: - # Convert a Super Token amount to its underlying equivalent, accounting for - # decimal differences. adjustedAmount strips precision loss. - mutability: view - inputs: - - amount: uint256 - outputs: - - underlyingAmount: uint256 - - adjustedAmount: uint256 - -# == Native Asset Wrapping (SETH only) == -# These functions exist only on Native Asset Super Token (SETH) proxies. -# They are defined in the SETHProxy contract, not in the SuperToken logic. -# The SETH proxy also accepts plain ETH transfers via receive() which mints -# Super Tokens to the sender. - -upgradeByETH: - # Wrap sent ETH/native asset into Super Tokens for msg.sender. - # Variant: native-asset - mutability: payable - access: anyone # wraps for msg.sender - inputs: [] - emits: [Minted, Transfer, TokenUpgraded] - -upgradeByETHTo: - # Wrap sent ETH/native asset into Super Tokens for a different address. - # Variant: native-asset - mutability: payable - access: anyone - inputs: - - to: address - emits: [Minted, Transfer, TokenUpgraded] - -downgradeToETH: - # Unwrap Super Tokens back to ETH/native asset for msg.sender. - # Variant: native-asset - mutability: nonpayable - access: anyone # unwraps to msg.sender - inputs: - - wad: uint256 - emits: [Burned, Transfer, TokenDowngraded] - -# == Real-time Balance == - -realtimeBalanceOf: - # Full real-time balance across ALL agreements at a given timestamp. - # This aggregates the realtimeBalanceOf from every agreement (CFA, GDA, IDA) - # into a single balance. Use this (or realtimeBalanceOfNow) to check an - # account's actual balance — not the per-agreement versions. - # availableBalance = settled + agreement dynamics − max(0, deposit − owedDeposit) - mutability: view - inputs: - - account: address - - timestamp: uint256 - outputs: - - availableBalance: int256 - - deposit: uint256 - - owedDeposit: uint256 - -realtimeBalanceOfNow: - # Convenience wrapper: realtimeBalanceOf at the current Host timestamp. - # This is the recommended way to check an account's real-time balance. - mutability: view - inputs: - - account: address - outputs: - - availableBalance: int256 - - deposit: uint256 - - owedDeposit: uint256 - - timestamp: uint256 - -isAccountCritical: - # True if availableBalance < 0 at the given timestamp. - mutability: view - inputs: - - account: address - - timestamp: uint256 - outputs: - - isCritical: bool - -isAccountCriticalNow: - mutability: view - inputs: - - account: address - outputs: - - isCritical: bool - -isAccountSolvent: - # True if realtime balance (before deposit deduction) >= 0 at the given timestamp. - mutability: view - inputs: - - account: address - - timestamp: uint256 - outputs: - - isSolvent: bool - -isAccountSolventNow: - mutability: view - inputs: - - account: address - outputs: - - isSolvent: bool - -# == Admin == -# The admin defaults to the Host contract when address(0). -# An explicit admin can be set during initializeWithAdmin or via changeAdmin. - -changeAdmin: - mutability: nonpayable - access: admin - inputs: - - newAdmin: address - emits: [AdminChanged] - errors: [SUPER_TOKEN_ONLY_ADMIN] - -getAdmin: - # Returns address(0) if the Host is the implicit admin. - mutability: view - inputs: [] - outputs: - - address - -# == Yield Backend == -# Hot-pluggable yield generation for wrapper Super Tokens. The yield backend is a -# contract implementing IYieldBackend that is invoked via delegatecall. When enabled, -# the underlying ERC-20 is deposited into a yield protocol (e.g. Aave, Spark, ERC4626 vaults). -# Surplus yield (balance exceeding totalSupply) can be withdrawn by the admin. - -enableYieldBackend: - # Enable a yield backend and deposit all underlying token balance into it. - # Requires no existing yield backend to be set. - notes: - - "Gotcha: Reverts if a yield backend is already enabled — must call disableYieldBackend first to switch backends." - - "The yield backend contract is invoked via delegatecall — it executes in the SuperToken's context." - mutability: nonpayable - access: admin - inputs: - - newYieldBackend: address # must implement IYieldBackend - emits: [YieldBackendEnabled] - -disableYieldBackend: - # Withdraw all assets from the yield backend and disable it. - notes: - - "Calls withdrawMax() then disable() on the backend via delegatecall." - mutability: nonpayable - access: admin - inputs: [] - emits: [YieldBackendDisabled] - -getYieldBackend: - # Returns the current yield backend address, or address(0) if none is set. - mutability: view - inputs: [] - outputs: - - yieldBackend: address - -withdrawSurplusFromYieldBackend: - # Withdraw yield surplus (deposited amount exceeding totalSupply) from the backend. - # The surplus is sent to a receiver defined by the yield backend implementation. - mutability: nonpayable - access: admin - inputs: [] - -# == Self Operations == -# Can only be called by the token contract itself (address(this)). -# Used by custom super token proxies (SETH, Pure) to mint/burn/approve/transfer -# tokens through their own proxy logic. - -selfMint: - mutability: nonpayable - access: self - inputs: - - account: address - - amount: uint256 - - userData: bytes - emits: [Minted, Transfer] - errors: [SUPER_TOKEN_ONLY_SELF, SUPER_TOKEN_MINT_TO_ZERO_ADDRESS] - -selfBurn: - mutability: nonpayable - access: self - inputs: - - account: address - - amount: uint256 - - userData: bytes - emits: [Burned, Transfer] - errors: [SUPER_TOKEN_ONLY_SELF, SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS, SF_TOKEN_BURN_INSUFFICIENT_BALANCE] - -selfApproveFor: - mutability: nonpayable - access: self - inputs: - - account: address - - spender: address - - amount: uint256 - emits: [Approval] - errors: [SUPER_TOKEN_ONLY_SELF, SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS, SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS] - -selfTransferFrom: - mutability: nonpayable - access: self - inputs: - - holder: address - - spender: address - - recipient: address - - amount: uint256 - emits: [Sent, Transfer] - errors: [SUPER_TOKEN_ONLY_SELF, SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS, SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS, SF_TOKEN_MOVE_INSUFFICIENT_BALANCE] - -# == Host Batch Operations == -# Called by the Host contract on behalf of a user within batchCall / -# forwardBatchCall. Each mirrors a user-facing function but takes the -# account address explicitly. - -operationApprove: - mutability: nonpayable - access: host - inputs: - - account: address - - spender: address - - amount: uint256 - emits: [Approval] - errors: [SF_TOKEN_ONLY_HOST, SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS, SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS] - -operationIncreaseAllowance: - mutability: nonpayable - access: host - inputs: - - account: address - - spender: address - - addedValue: uint256 - emits: [Approval] - errors: [SF_TOKEN_ONLY_HOST, SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS, SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS] - -operationDecreaseAllowance: - mutability: nonpayable - access: host - inputs: - - account: address - - spender: address - - subtractedValue: uint256 - emits: [Approval] - errors: [SF_TOKEN_ONLY_HOST, SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS, SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS] - -operationTransferFrom: - mutability: nonpayable - access: host - inputs: - - account: address - - spender: address - - recipient: address - - amount: uint256 - emits: [Sent, Transfer] - errors: [SF_TOKEN_ONLY_HOST, SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS, SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS, SF_TOKEN_MOVE_INSUFFICIENT_BALANCE] - -operationSend: - mutability: nonpayable - access: host - inputs: - - spender: address - - recipient: address - - amount: uint256 - - userData: bytes - emits: [Sent, Transfer] - errors: [SF_TOKEN_ONLY_HOST, SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS, SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS, SF_TOKEN_MOVE_INSUFFICIENT_BALANCE, SUPER_TOKEN_NOT_ERC777_TOKENS_RECIPIENT] - -operationUpgrade: - mutability: nonpayable - access: host - inputs: - - account: address - - amount: uint256 - emits: [Minted, Transfer, TokenUpgraded] - errors: [SF_TOKEN_ONLY_HOST, SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED, SUPER_TOKEN_MINT_TO_ZERO_ADDRESS, SafeERC20FailedOperation, SafeCastOverflowedUintToInt] - -operationDowngrade: - mutability: nonpayable - access: host - inputs: - - account: address - - amount: uint256 - emits: [Burned, Transfer, TokenDowngraded] - errors: [SF_TOKEN_ONLY_HOST, SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS, SF_TOKEN_BURN_INSUFFICIENT_BALANCE, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED, SafeERC20FailedOperation] - -operationUpgradeTo: - mutability: nonpayable - access: host - inputs: - - account: address - - to: address - - amount: uint256 - emits: [Minted, Transfer, TokenUpgraded] - errors: [SF_TOKEN_ONLY_HOST, SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED, SUPER_TOKEN_MINT_TO_ZERO_ADDRESS, SafeERC20FailedOperation, SafeCastOverflowedUintToInt] - -operationDowngradeTo: - mutability: nonpayable - access: host - inputs: - - account: address - - to: address - - amount: uint256 - emits: [Burned, Transfer, TokenDowngraded] - errors: [SF_TOKEN_ONLY_HOST, SUPER_TOKEN_NO_UNDERLYING_TOKEN, SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS, SF_TOKEN_BURN_INSUFFICIENT_BALANCE, SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED, SafeERC20FailedOperation] - -# == Agreement Hosting == -# The token acts as storage host for agreement data. These functions are -# called by registered agreement contracts (CFA, GDA, IDA) through the Host. - -createAgreement: - mutability: nonpayable - access: agreement - inputs: - - id: bytes32 - - "data: bytes32[]" - emits: [AgreementCreated] - errors: [SF_TOKEN_AGREEMENT_ALREADY_EXISTS] - -getAgreementData: - mutability: view - inputs: - - agreementClass: address - - id: bytes32 - - dataLength: uint256 - outputs: - - "data: bytes32[]" - -updateAgreementData: - mutability: nonpayable - access: agreement - inputs: - - id: bytes32 - - "data: bytes32[]" - emits: [AgreementUpdated] - -terminateAgreement: - mutability: nonpayable - access: agreement - inputs: - - id: bytes32 - - dataLength: uint256 - emits: [AgreementTerminated] - errors: [SF_TOKEN_AGREEMENT_DOES_NOT_EXIST] - -updateAgreementStateSlot: - mutability: nonpayable - access: agreement - inputs: - - account: address - - slotId: uint256 - - "slotData: bytes32[]" - emits: [AgreementStateUpdated] - -getAgreementStateSlot: - mutability: view - inputs: - - agreementClass: address - - account: address - - slotId: uint256 - - dataLength: uint256 - outputs: - - "slotData: bytes32[]" - -settleBalance: - # Adjust an account's settled balance. Only listed agreements can call this. - mutability: nonpayable - access: agreement - inputs: - - account: address - - delta: int256 - errors: [SF_TOKEN_ONLY_LISTED_AGREEMENT] - -makeLiquidationPayoutsV2: - # Execute liquidation payouts. Transfers reward to liquidator or reward account, - # and adjusts the target account's balance. Only listed agreements can call this. - mutability: nonpayable - access: agreement - inputs: - - id: bytes32 - - liquidationTypeData: bytes - - liquidatorAccount: address - - useDefaultRewardAccount: bool - - targetAccount: address - - rewardAmount: uint256 - - targetAccountBalanceDelta: int256 - emits: [Transfer, AgreementLiquidatedV2] - errors: [SF_TOKEN_ONLY_LISTED_AGREEMENT] - -getAccountActiveAgreements: - mutability: view - inputs: - - account: address - outputs: - - "ISuperAgreement[]" - -# == Protocol Info == - -getHost: - mutability: view - inputs: [] - outputs: - - host: address - -POOL_ADMIN_NFT: - # Immutable address of the canonical PoolAdminNFT proxy. - mutability: view - inputs: [] - outputs: - - address - -POOL_MEMBER_NFT: - # Immutable address of the (deprecated) PoolMemberNFT proxy. - mutability: view - inputs: [] - outputs: - - address - -VERSION: - # Returns the version string of the SuperToken logic contract (e.g. "1.0.0"). - mutability: view - inputs: [] - outputs: - - string - -# == Events == - -events: - - # -- ERC-20 -- - - Transfer: - indexed: - - from: address - - to: address - data: - - value: uint256 - - Approval: - indexed: - - owner: address - - spender: address - data: - - value: uint256 - - # -- ERC-777 -- - - Sent: - indexed: - - operator: address - - from: address - - to: address - data: - - amount: uint256 - - data: bytes - - operatorData: bytes - - Minted: - indexed: - - operator: address - - to: address - data: - - amount: uint256 - - data: bytes - - operatorData: bytes - - Burned: - indexed: - - operator: address - - from: address - data: - - amount: uint256 - - data: bytes - - operatorData: bytes - - AuthorizedOperator: - indexed: - - operator: address - - tokenHolder: address - - RevokedOperator: - indexed: - - operator: address - - tokenHolder: address - - # -- EIP-5267 -- - - EIP712DomainChanged: - # Signalled when the EIP-712 domain changes. Not explicitly emitted by - # SuperToken, but declared in the EIP-5267 interface for indexing support. - - # -- Wrapping -- - - TokenUpgraded: - indexed: - - account: address - data: - - amount: uint256 - - TokenDowngraded: - indexed: - - account: address - data: - - amount: uint256 - - # -- Admin -- - - AdminChanged: - indexed: [] - data: - - oldAdmin: address - - newAdmin: address - - # -- Yield Backend -- - - YieldBackendEnabled: - indexed: - - yieldBackend: address - data: - - depositAmount: uint256 - - YieldBackendDisabled: - indexed: - - yieldBackend: address - - PoolAdminNFTCreated: - # Emitted once during logic contract constructor deployment. - indexed: [] - data: - - poolAdminNFT: address - - # -- Agreement Hosting -- - - AgreementCreated: - indexed: - - agreementClass: address - - id: bytes32 - data: - - "data: bytes32[]" - - AgreementUpdated: - indexed: - - agreementClass: address - - id: bytes32 - data: - - "data: bytes32[]" - - AgreementTerminated: - indexed: - - agreementClass: address - - id: bytes32 - - AgreementStateUpdated: - indexed: - - agreementClass: address - - account: address - data: - - slotId: uint256 - - AgreementLiquidatedV2: - indexed: - - agreementClass: address - - id: bytes32 - data: - - liquidatorAccount: address - - targetAccount: address - - rewardAmountReceiver: address - - rewardAmount: uint256 - - targetAccountBalanceDelta: int256 - - liquidationTypeData: bytes - - # -- Deprecated events (from ISuperfluidToken, no longer emitted) -- - - AgreementLiquidated: - deprecated: true - note: "Use AgreementLiquidatedV2" - indexed: - - agreementClass: address - - id: bytes32 - data: - - penaltyAccount: address - - rewardAccount: address - - rewardAmount: uint256 - - AgreementLiquidatedBy: - deprecated: true - note: "Use AgreementLiquidatedV2" - indexed: - - liquidatorAccount: address - - agreementClass: address - - id: bytes32 - data: - - penaltyAccount: address - - bondAccount: address - - rewardAmount: uint256 - - bailoutAmount: uint256 - - Bailout: - deprecated: true - note: "Use AgreementLiquidatedV2" - indexed: - - bailoutAccount: address - - bailoutAmount: uint256 - -# == Errors == - -errors: - - # -- Super Token access -- - - SUPER_TOKEN_ONLY_SELF - - SUPER_TOKEN_ONLY_ADMIN - - SUPER_TOKEN_ONLY_GOV_OWNER - - # -- SuperfluidToken access -- - - SF_TOKEN_ONLY_HOST - - SF_TOKEN_ONLY_LISTED_AGREEMENT - - # -- Wrapping -- - - SUPER_TOKEN_NO_UNDERLYING_TOKEN - - SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED - - # -- ERC-20 / ERC-777 transfer -- - - SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS - - SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS - - SF_TOKEN_MOVE_INSUFFICIENT_BALANCE - - # -- Approve -- - - SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS - - SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS - - # -- Mint / Burn -- - - SUPER_TOKEN_MINT_TO_ZERO_ADDRESS - - SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS - - SF_TOKEN_BURN_INSUFFICIENT_BALANCE - - # -- ERC-777 operator -- - - SUPER_TOKEN_CALLER_IS_NOT_OPERATOR_FOR_HOLDER - - SUPER_TOKEN_NOT_ERC777_TOKENS_RECIPIENT - - # -- ERC-2612 permit -- - - SUPER_TOKEN_PERMIT_EXPIRED_SIGNATURE: - inputs: - - deadline: uint256 - - SUPER_TOKEN_PERMIT_INVALID_SIGNER: - inputs: - - signer: address - - owner: address - - # -- NFT proxy -- - - SUPER_TOKEN_NFT_PROXY_ADDRESS_CHANGED - - # -- Agreement hosting -- - - SF_TOKEN_AGREEMENT_ALREADY_EXISTS - - SF_TOKEN_AGREEMENT_DOES_NOT_EXIST - - # -- OpenZeppelin ECDSA (from permit) -- - - ECDSAInvalidSignature - - ECDSAInvalidSignatureLength: - inputs: - - length: uint256 - - ECDSAInvalidSignatureS: - inputs: - - s: bytes32 - - # -- OpenZeppelin SafeCast -- - - SafeCastOverflowedUintToInt: - inputs: - - value: uint256 - - # -- OpenZeppelin SafeERC20 -- - - SafeERC20FailedOperation: - inputs: - - token: address diff --git a/.agents/skills/gooddollar/references/contracts/SuperToken.selectors.yaml b/.agents/skills/gooddollar/references/contracts/SuperToken.selectors.yaml deleted file mode 100644 index f9bb474d..00000000 --- a/.agents/skills/gooddollar/references/contracts/SuperToken.selectors.yaml +++ /dev/null @@ -1,99 +0,0 @@ -# Generated by scripts/selectors.mjs -functions: - name(): 0x06fdde03 - symbol(): 0x95d89b41 - decimals(): 0x313ce567 - totalSupply(): 0x18160ddd - balanceOf(address): 0x70a08231 - allowance(address,address): 0xdd62ed3e - approve(address,uint256): 0x095ea7b3 - increaseAllowance(address,uint256): 0x39509351 - decreaseAllowance(address,uint256): 0xa457c2d7 - transfer(address,uint256): 0xa9059cbb - transferFrom(address,address,uint256): 0x23b872dd - transferAll(address): 0xa3a7e7f3 - granularity(): 0x556f0dc7 - send(address,uint256,bytes): 0x9bd9bbc6 - burn(uint256,bytes): 0xfe9d9303 - authorizeOperator(address): 0x959b8c3f - revokeOperator(address): 0xfad8b32a - isOperatorFor(address,address): 0xd95b6371 - defaultOperators(): 0x06e48538 - operatorSend(address,address,uint256,bytes,bytes): 0x62ad1b83 - operatorBurn(address,uint256,bytes,bytes): 0xfc673c4f - permit(address,address,uint256,uint256,uint8,bytes32,bytes32): 0xd505accf - nonces(address): 0x7ecebe00 - DOMAIN_SEPARATOR(): 0x3644e515 - eip712Domain(): 0x84b0196e - upgrade(): 0xd55ec697 - upgradeTo(address,uint256,bytes): 0x5b9d09cc - downgrade(uint256): 0x11bcc81e - downgradeTo(address,uint256): 0x83ba2525 - getUnderlyingToken(): 0xee719bc8 - getUnderlyingDecimals(): 0x92081a47 - toUnderlyingAmount(uint256): 0x282a050b - upgradeByETH(): 0xcf81464b - upgradeByETHTo(address): 0x7687d19b - downgradeToETH(uint256): 0x160e8be3 - realtimeBalanceOf(address,uint256): 0xeb3537cc - realtimeBalanceOfNow(address): 0x2ec8eec7 - isAccountCritical(address,uint256): 0xd9d078d6 - isAccountCriticalNow(address): 0x79359f6f - isAccountSolvent(address,uint256): 0xb84cdd4a - isAccountSolventNow(address): 0xbb0d196e - changeAdmin(address): 0x8f283970 - getAdmin(): 0x6e9960c3 - enableYieldBackend(): 0x5127b621 - disableYieldBackend(): 0x370a190f - getYieldBackend(): 0xe729804b - withdrawSurplusFromYieldBackend(): 0x4a3acda0 - selfMint(address,uint256,bytes): 0xc68d4283 - selfBurn(address,uint256,bytes): 0x9d876741 - selfApproveFor(address,address,uint256): 0x66a12fb6 - selfTransferFrom(address,address,address,uint256): 0x41b706be - operationApprove(address,address,uint256): 0x62aa5287 - operationIncreaseAllowance(address,address,uint256): 0x4b2763b3 - operationDecreaseAllowance(address,address,uint256): 0xc780fd82 - operationTransferFrom(address,address,address,uint256): 0x16d055d6 - operationSend(address,address,uint256,bytes): 0xca0c1e7f - operationUpgrade(address,uint256): 0xca789464 - operationDowngrade(address,uint256): 0x245887fc - operationUpgradeTo(address,address,uint256): 0x1ae88ffc - operationDowngradeTo(address,address,uint256): 0x47ba7ad1 - createAgreement(bytes32,bytes32[]"): 0x816e2dc5 - getAgreementData(address,bytes32,uint256): 0x6c2d9f2f - updateAgreementData(bytes32,bytes32[]"): 0x1bced9c7 - terminateAgreement(bytes32,uint256): 0x27048397 - updateAgreementStateSlot(address,uint256,bytes32[]"): 0x003af740 - getAgreementStateSlot(address,address,uint256,uint256): 0x4b61cc33 - settleBalance(address,int256): 0xcf97256d - makeLiquidationPayoutsV2(bytes32,bytes,address,bool,address,uint256,int256): 0x1863e809 - getAccountActiveAgreements(address): 0x386fa221 - getHost(): 0x20bc4425 - POOL_ADMIN_NFT(): 0xb20db1ac - POOL_MEMBER_NFT(): 0xf5a8b4dd - VERSION(): 0xffa1ad74 -events: - Transfer(address,address,uint256): 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef - Approval(address,address,uint256): 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925 - Sent(address,address,address,uint256,bytes,bytes): 0x06b541ddaa720db2b10a4d0cdac39b8d360425fc073085fac19bc82614677987 - Minted(address,address,uint256,bytes,bytes): 0x2fe5be0146f74c5bce36c0b80911af6c7d86ff27e89d5cfa61fc681327954e5d - Burned(address,address,uint256,bytes,bytes): 0xa78a9be3a7b862d26933ad85fb11d80ef66b8f972d7cbba06621d583943a4098 - AuthorizedOperator(address,address): 0xf4caeb2d6ca8932a215a353d0703c326ec2d81fc68170f320eb2ab49e9df61f9 - RevokedOperator(address,address): 0x50546e66e5f44d728365dc3908c63bc5cfeeab470722c1677e3073a6ac294aa1 - EIP712DomainChanged(): 0x0a6387c9ea3628b88a633bb4f3b151770f70085117a15f9bf3787cda53f13d31 - TokenUpgraded(address,uint256): 0x25ca84076773b0455db53621c459ddc84fe40840e4932a62706a032566f399df - TokenDowngraded(address,uint256): 0x3bc27981aebbb57f9247dc00fde9d6cd91e4b230083fec3238fedbcba1f9ab3d - AdminChanged(address,address): 0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f - YieldBackendEnabled(address,uint256): 0x8d15dd569157df615aedb4b16273001ae8980ce2aac93e39adc48481dfaefbb5 - YieldBackendDisabled(address): 0x2486b5241145ce1b97a13655ecd4e8e51094ac93259a0589d24524304d6d70d4 - PoolAdminNFTCreated(address): 0xeb87fb34067547f3dc0b85096c3da73c99d4fbb08ff41212b8d7c0b5008b42e6 - AgreementCreated(address,bytes32,bytes32[]"): 0x9a5caaf4460c6dff81ed3dc58f3aef40fe511737a163340e06a4abc1bfe21a73 - AgreementUpdated(address,bytes32,bytes32[]"): 0xc00aaaf56818c1a9d6c54e14c6f93965eb34c9729232fdafa62f90a10600e70a - AgreementTerminated(address,bytes32): 0x71a63dc095de07aa5512ad57a7596a39516317e316981a1cd71000057be1537b - AgreementStateUpdated(address,address,uint256): 0x30f416fa68fca014a0f334464c64b000ba53e99b6d2afdea9d5ca756372d5985 - AgreementLiquidatedV2(address,bytes32,address,address,address,uint256,int256,bytes): 0xb8381a3ce157650e06186e3e8f4dd4dc29236f2688b6eed1893d0a60d7c6386f - AgreementLiquidated(address,bytes32,address,address,uint256): 0x8505c3d8f1f184f032cf0bc4cd80ee61c8b9d94f8907c3281bf0101a2610fe80 - AgreementLiquidatedBy(address,address,bytes32,address,address,uint256,uint256): 0x5f22b60e58b1d6de858bc27c48d5a4653e052da99e083c1d88bb8c58e1abc8ef - Bailout(address,uint256): 0xd6c9a04afc81e8c614310bbee6c9e84f5abe15b82038bf8347014ce0852e6ffd -errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/Superfluid.abi.yaml b/.agents/skills/gooddollar/references/contracts/Superfluid.abi.yaml deleted file mode 100644 index 33ac5a94..00000000 --- a/.agents/skills/gooddollar/references/contracts/Superfluid.abi.yaml +++ /dev/null @@ -1,757 +0,0 @@ -# Superfluid Host — the central protocol router -# All agreement calls (CFA, IDA, GDA) are routed through the Host, which manages -# the Superfluid call context, Super App callbacks, app credit, and governance. -# The Host is also the entry point for batch calls and meta-transactions. -# -# NOTE: emits/errors mappings are traced from source code — verify against implementation. -# Proxy/upgradability functions (castrate, updateCode, getCodeAddress, proxiableUUID, initialize) -# are omitted — they belong to the UUPSProxiable layer. - -meta: - name: Superfluid - version: v1 - source: - - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol - - https://raw.githubusercontent.com/superfluid-org/protocol-monorepo/refs/heads/dev/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol - implements: [ISuperfluid] - inherits: [UUPSProxiable, BaseRelayRecipient] - deployments: - mainnet: - eth-mainnet: "0x4E583d9390082B65Bef884b629DFA426114CED6d" - polygon-mainnet: "0x3E14dC1b13c488a8d5D310918780c983bD5982E7" - xdai-mainnet: "0x2dFe937cD98Ab92e59cF3139138f18c823a4efE7" - base-mainnet: "0x4C073B3baB6d8826b8C5b229f3cfdC1eC6E47E74" - optimism-mainnet: "0x567c4B141ED61923967cA25Ef4906C8781069a10" - arbitrum-one: "0xCf8Acb4eF033efF16E8080aed4c7D5B9285D2192" - bsc-mainnet: "0xd1e2cFb6441680002Eb7A44223160aB9B67d7E6E" - avalanche-c: "0x60377C7016E4cdB03C87EF474896C11cB560752C" - celo-mainnet: "0xA4Ff07cF81C02CFD356184879D953970cA957585" - scroll-mainnet: "0x0F86a21F6216c061B222c224e315d9FC34520bb7" - degenchain: "0xc1314EdcD7e478C831a7a24169F7dEADB2646eD2" - testnet: - avalanche-fuji: "0x85Fe79b998509B77BF10A8BD4001D58475D29386" - base-sepolia: "0x109412E3C84f0539b43d39dB691B08c90f58dC7c" - eth-sepolia: "0x109412E3C84f0539b43d39dB691B08c90f58dC7c" - optimism-sepolia: "0xd399e2Fb5f4cf3722a11F65b88FAB6B2B8621005" - scroll-sepolia: "0x42b05a6016B9eED232E13fd56a8F0725693DBF8e" - deploymentCreationBlocks: - mainnet: - celo-mainnet: 16393469 - -# == Abbreviations == -# ctx — context (Superfluid call context bytes) -# ACL — Access Control List -# ERC — Ethereum Request for Comments - -# == Glossary == -# Host — central Superfluid contract routing agreement/app calls -# agreement class — a registered agreement contract (CFA, IDA, GDA) -# Super App — a smart contract registered with the Host that receives callbacks -# callback — beforeAgreement* / afterAgreement* hooks called on Super Apps -# context (ctx) — encoded call metadata: msgSender, timestamp, userData, app credit -# app credit — temporary token allowance granted to a Super App during a callback -# jailed — a Super App that violated protocol rules; its callbacks are bypassed -# configWord — bitmask encoding a Super App's app level and callback noop flags. -# App level: APP_LEVEL_FINAL (1 << 1) or APP_LEVEL_SECOND (1 << 0). -# Noop flags disable specific before/after callbacks per agreement type. -# batch operation — a single step in a batchCall array (see BatchOperation in Definitions.sol) -# trusted forwarder — a contract allowed to call forwardBatchCall for meta-transactions - -# == Batch Call == -# Primary entry point for interacting with the protocol. Users compose arrays of -# typed operations (ERC-20 approvals, SuperToken wrapping, agreement calls, etc.). -# -# == Batch Operation Types == -# Used in the operationType field of batchCall/forwardBatchCall operations. -# Data encoding varies by category — see notes on each type. -# -# Category 1–5: ERC-20/ERC-777 token ops -# target: must be a SuperToken (routes through SuperToken.operation* methods, -# NOT arbitrary ERC-20 contracts — use a separate tx for underlying approvals) -# data: abi-encoded parameters only (strip the 4-byte function selector) -# -# Category 101–102: SuperToken wrap/unwrap -# target: the SuperToken -# data: abi-encoded parameters only (strip the 4-byte function selector) -# -# Category 201: agreement call -# target: the agreement contract (e.g. CFA, GDA) -# data: abi.encode(callData, userData) where callData is full -# encodeFunctionData (selector included, with empty ctx placeholder "0x") -# -# Category 202: app action -# target: the Super App -# data: abi.encode(callData) where callData is full encodeFunctionData -# (selector included, with empty ctx placeholder "0x") -# -# Category 301: simple forward -# target: any contract -# data: full encodeFunctionData (selector included), or "0x" for pure value transfers -# -# Category 302: ERC-2771 forward -# target: any contract (must be an ERC-2771 recipient to extract the real sender) -# data: full encodeFunctionData (selector included) -# The ERC2771Forwarder appends the original msg.sender to calldata per ERC-2771. -# Use when the target contract needs to know who initiated the batch. - -batch_operation_types: - ERC20_APPROVE: 1 - ERC20_TRANSFER_FROM: 2 - ERC777_SEND: 3 # deprecated — ERC-777 is being phased out - ERC20_INCREASE_ALLOWANCE: 4 - ERC20_DECREASE_ALLOWANCE: 5 - SUPERTOKEN_UPGRADE: 101 - SUPERTOKEN_DOWNGRADE: 102 - SUPERFLUID_CALL_AGREEMENT: 201 - CALL_APP_ACTION: 202 - SIMPLE_FORWARD_CALL: 301 - ERC2771_FORWARD_CALL: 302 - -batchCall: - # Execute an array of operations in a single transaction as msg.sender. - notes: - - "Gotcha: For SIMPLE_FORWARD_CALL (type 301), target sees msg.sender as the SimpleForwarder contract, NOT the original caller. Use only when the target doesn't need to identify the sender." - - "Gotcha: ERC-20 ops (types 1-5) route through SuperToken.operation* methods, NOT arbitrary ERC-20 contracts. The target must be a SuperToken address. To approve an underlying ERC-20 (e.g. for wrapping), use a separate transaction." - - "Gotcha: If msg.value > 0, the entire amount is forwarded to the first CALL_APP_ACTION, SIMPLE_FORWARD, or ERC2771_FORWARD operation. If none exist, the native tokens are returned to the sender." - mutability: payable - access: anyone - inputs: - - operations: tuple[] - components: - - operationType: uint32 # see batch_operation_types above - - target: address - - data: bytes # encoding varies by operationType — see above - errors: [HOST_UNKNOWN_BATCH_CALL_OPERATION_TYPE] - -forwardBatchCall: - # Same as batchCall but uses EIP-2771 to extract msgSender from calldata. - # Can only be called by contracts registered as trusted forwarders in governance. - # If native tokens are left over, they are refunded to the EIP-2771 sender. - mutability: payable - access: trusted-forwarder - inputs: - - operations: tuple[] - components: - - operationType: uint32 # see batch_operation_types above - - target: address - - data: bytes # encoding varies by operationType — see above - errors: [HOST_UNKNOWN_BATCH_CALL_OPERATION_TYPE] - -# == Contextless Call Proxies == -# Entry points for EOAs and non-app contracts to interact with agreements or apps. -# These create a fresh Superfluid context with msg.sender, inject it into the call, -# and route through to the target agreement/app. - -callAgreement: - # Call an agreement function. The Host creates a context with msg.sender and replaces - # the placeholder ctx bytes in callData. This is how EOAs interact with CFA/IDA/GDA. - mutability: nonpayable - access: anyone # context set to msg.sender - inputs: - - agreementClass: address # must be a registered agreement - - callData: bytes # abi-encoded agreement call with placeholder ctx - - userData: bytes # extra data forwarded to Super App callbacks - outputs: - - returnedData: bytes - errors: [APP_RULE, HOST_ONLY_LISTED_AGREEMENT] - -callAppAction: - # Call an app action function on a Super App. The Host creates a context and routes - # the call. The action must not be an agreement callback selector. - mutability: nonpayable - access: anyone # context set to msg.sender - inputs: - - app: address # must be a registered, non-jailed Super App - - callData: bytes # abi-encoded app action call with placeholder ctx - outputs: - - returnedData: bytes - errors: [APP_RULE, HOST_NOT_A_SUPER_APP, HOST_SUPER_APP_IS_JAILED, HOST_AGREEMENT_CALLBACK_IS_NOT_ACTION] - -# == Contextual Call Proxies == -# For Super Apps to chain calls during callbacks. The app must pass the ctx it received. -# Only callable by the app currently executing a callback (ctx.appAddress == msg.sender). - -callAgreementWithContext: - # Chain an agreement call from within a Super App callback. - # The ctx must be valid and msg.sender must be the current callback app. - mutability: nonpayable - access: super-app - inputs: - - agreementClass: address - - callData: bytes - - userData: bytes - - ctx: bytes - outputs: - - newCtx: bytes - - returnedData: bytes - errors: [APP_RULE, HOST_ONLY_LISTED_AGREEMENT, HOST_CALL_AGREEMENT_WITH_CTX_FROM_WRONG_ADDRESS] - -callAppActionWithContext: - # Chain an app action call from within a Super App callback. - mutability: nonpayable - access: super-app - inputs: - - app: address - - callData: bytes - - ctx: bytes - outputs: - - newCtx: bytes - errors: [APP_RULE, HOST_NOT_A_SUPER_APP, HOST_SUPER_APP_IS_JAILED, HOST_AGREEMENT_CALLBACK_IS_NOT_ACTION, HOST_CALL_APP_ACTION_WITH_CTX_FROM_WRONG_ADDRESS] - -# == Context Utilities == - -decodeCtx: - # Decode a raw context bytes blob into the structured Context fields. - mutability: pure - inputs: - - ctx: bytes - outputs: - - context: - type: tuple - components: - - appCallbackLevel: uint8 - - callType: uint8 - - timestamp: uint256 - - msgSender: address - - agreementSelector: bytes4 - - userData: bytes - - appCreditGranted: uint256 - - appCreditWantedDeprecated: uint256 - - appCreditUsed: int256 - - appAddress: address - - appCreditToken: address - -isCtxValid: - # Check if a context blob matches the current transaction's context stamp. - mutability: view - inputs: - - ctx: bytes - outputs: - - bool - -# == App Registry == -# Super App registration. On some deployments, governance permission (via SimpleACL -# or legacy governance config) is required before an app can register. - -registerApp: - # Register msg.sender as a Super App with the given config word. - # On whitelisting-enabled deployments, tx.origin must have the - # ACL_SUPERAPP_REGISTRATION_ROLE or a valid legacy governance key. - mutability: nonpayable - access: self - inputs: - - configWord: uint256 # Super App manifest flags (see SuperAppDefinitions) - emits: [AppRegistered] - errors: [HOST_NO_APP_REGISTRATION_PERMISSION, HOST_INVALID_CONFIG_WORD, HOST_SUPER_APP_ALREADY_REGISTERED] - -registerApp(address,uint256): - # Register an external contract as a Super App. - # Useful for factory patterns where the deployer registers the app. - mutability: nonpayable - access: anyone - inputs: - - app: address # must be a contract - - configWord: uint256 - emits: [AppRegistered] - errors: [HOST_MUST_BE_CONTRACT, HOST_NO_APP_REGISTRATION_PERMISSION, HOST_INVALID_CONFIG_WORD, HOST_SUPER_APP_ALREADY_REGISTERED] - -registerAppWithKey: - # DEPRECATED — use registerApp(uint256) instead. - # Legacy registration using a governance-provided registration key string. - mutability: nonpayable - access: self - inputs: - - configWord: uint256 - - registrationKey: string - emits: [AppRegistered] - errors: [HOST_NO_APP_REGISTRATION_PERMISSION, HOST_INVALID_CONFIG_WORD, HOST_SUPER_APP_ALREADY_REGISTERED] - -registerAppByFactory: - # DEPRECATED — use registerApp(address, uint256) instead. - # Legacy factory-based registration using governance-authorized factory addresses. - mutability: nonpayable - access: factory - inputs: - - app: address - - configWord: uint256 - emits: [AppRegistered] - errors: [HOST_MUST_BE_CONTRACT, HOST_NO_APP_REGISTRATION_PERMISSION, HOST_INVALID_CONFIG_WORD, HOST_SUPER_APP_ALREADY_REGISTERED] - -# == App Queries == - -isApp: - mutability: view - inputs: - - app: address - outputs: - - bool - -getAppCallbackLevel: - mutability: view - inputs: - - appAddr: address - outputs: - - uint8 - -getAppManifest: - # Returns the full manifest of a Super App. - # isSuperApp is false if the address was never registered. - mutability: view - inputs: - - app: address - outputs: - - isSuperApp: bool - - isJailed: bool - - noopMask: uint256 # bitmask of noop'd agreement callback selectors - -isAppJailed: - mutability: view - inputs: - - app: address - outputs: - - isJail: bool - -# == App Composition == -# Source apps can whitelist target apps for composability (calling downstream). -# Currently limited to MAX_APP_CALLBACK_LEVEL = 1 (one level of nesting). - -allowCompositeApp: - # Whitelist a target Super App for composition. msg.sender must be a Super App - # with a higher callback level than the target. - mutability: nonpayable - access: super-app - inputs: - - targetApp: address - errors: [HOST_SENDER_IS_NOT_SUPER_APP, HOST_RECEIVER_IS_NOT_SUPER_APP, HOST_SOURCE_APP_NEEDS_HIGHER_APP_LEVEL] - -isCompositeAppAllowed: - mutability: view - inputs: - - app: address - - targetApp: address - outputs: - - isAppAllowed: bool - -# == Agreement Framework == -# Internal protocol functions called by registered agreement contracts (CFA, IDA, GDA) -# to manage Super App callbacks and app credit. Only callable by agreements. -# -# Callback lifecycle (called by agreement during flow operations): -# 1. appCallbackPush — set up credit, push callback frame -# 2. callAppBeforeCallback — staticcall app's before hook (read-only) -# 3. (agreement executes core logic) -# 4. callAppAfterCallback — call app's after hook (state changes OK) -# 5. appCallbackPop — finalize credit accounting, pop frame - -callAppBeforeCallback: - # StaticCall a Super App's before-callback. Returns the callback data. - # If the callback reverts during a termination, the app is jailed instead of reverting. - mutability: nonpayable - access: agreement - inputs: - - app: address - - callData: bytes - - isTermination: bool - - ctx: bytes - outputs: - - cbdata: bytes - errors: [HOST_ONLY_LISTED_AGREEMENT, HOST_NEED_MORE_GAS, APP_RULE] - -callAppAfterCallback: - # Call a Super App's after-callback. Returns the (potentially modified) context. - # If the app returns an invalid ctx, it is jailed on termination or reverts otherwise. - mutability: nonpayable - access: agreement - inputs: - - app: address - - callData: bytes - - isTermination: bool - - ctx: bytes - outputs: - - newCtx: bytes - errors: [HOST_ONLY_LISTED_AGREEMENT, HOST_NEED_MORE_GAS, APP_RULE] - -appCallbackPush: - # Push a new callback frame onto the context stack before invoking a Super App. - # Sets up app credit and tracks the callback nesting level. - notes: - - "Gotcha: The appCreditGranted is backed by the flow sender's deposit as \"owed deposit\". If the Super App opens outgoing streams using this credit, the sender's total locked capital increases accordingly (roughly doubles for 1:1 relay, more for fan-out patterns)." - mutability: nonpayable - access: agreement - inputs: - - ctx: bytes - - app: address - - appCreditGranted: uint256 - - appCreditUsed: int256 - - appCreditToken: address - outputs: - - appCtx: bytes - errors: [HOST_ONLY_LISTED_AGREEMENT, APP_RULE] - -appCallbackPop: - # Pop the callback frame after a Super App callback completes. - # Adjusts appCreditUsed by the delta from the callback. - mutability: nonpayable - access: agreement - inputs: - - ctx: bytes - - appCreditUsedDelta: int256 - outputs: - - newCtx: bytes - errors: [HOST_ONLY_LISTED_AGREEMENT] - -ctxUseCredit: - # Record additional app credit usage during an agreement operation. - mutability: nonpayable - access: agreement - inputs: - - ctx: bytes - - appCreditUsedMore: int256 - outputs: - - newCtx: bytes - errors: [HOST_ONLY_LISTED_AGREEMENT] - -jailApp: - # Jail a Super App from within an agreement. Jailed apps have their callbacks bypassed. - mutability: nonpayable - access: agreement - inputs: - - ctx: bytes - - app: address - - reason: uint256 # see SuperAppDefinitions for jail reason codes - outputs: - - newCtx: bytes - emits: [Jail] - errors: [HOST_ONLY_LISTED_AGREEMENT] - -# == Governance == - -getGovernance: - mutability: view - outputs: - - governance: address - -replaceGovernance: - mutability: nonpayable - access: governance - inputs: - - newGov: address - emits: [GovernanceReplaced] - errors: [HOST_ONLY_GOVERNANCE] - -# == Agreement Whitelisting == -# Governance registers and upgrades agreement classes (CFA, IDA, GDA). - -registerAgreementClass: - # Register a new agreement class. Creates a UUPS proxy in upgradable deployments. - mutability: nonpayable - access: governance - inputs: - - agreementClassLogic: address - emits: [AgreementClassRegistered] - errors: [HOST_ONLY_GOVERNANCE, HOST_AGREEMENT_ALREADY_REGISTERED, HOST_MAX_256_AGREEMENTS] - -updateAgreementClass: - # Upgrade an existing agreement class to a new implementation. - mutability: nonpayable - access: governance - inputs: - - agreementClassLogic: address - emits: [AgreementClassUpdated] - errors: [HOST_ONLY_GOVERNANCE, HOST_NON_UPGRADEABLE, HOST_AGREEMENT_IS_NOT_REGISTERED] - -isAgreementTypeListed: - mutability: view - inputs: - - agreementType: bytes32 # keccak256 of agreement name string - outputs: - - yes: bool - -isAgreementClassListed: - mutability: view - inputs: - - agreementClass: address - outputs: - - yes: bool - -getAgreementClass: - mutability: view - inputs: - - agreementType: bytes32 - outputs: - - agreementClass: address - errors: [HOST_AGREEMENT_IS_NOT_REGISTERED] - -mapAgreementClasses: - # Decode a bitmap into an array of agreement class addresses. - mutability: view - inputs: - - bitmap: uint256 - outputs: - - agreementClasses: address[] - -addToAgreementClassesBitmap: - mutability: view - inputs: - - bitmap: uint256 - - agreementType: bytes32 - outputs: - - newBitmap: uint256 - errors: [HOST_AGREEMENT_IS_NOT_REGISTERED] - -removeFromAgreementClassesBitmap: - mutability: view - inputs: - - bitmap: uint256 - - agreementType: bytes32 - outputs: - - newBitmap: uint256 - errors: [HOST_AGREEMENT_IS_NOT_REGISTERED] - -# == Super Token Factory == - -getSuperTokenFactory: - mutability: view - outputs: - - factory: address - -getSuperTokenFactoryLogic: - # Returns the logic address behind the factory proxy. For non-upgradable - # deployments, returns the factory address itself. - mutability: view - outputs: - - logic: address - -updateSuperTokenFactory: - # Deploy or upgrade the SuperTokenFactory. On first call, creates a UUPS proxy. - mutability: nonpayable - access: governance - inputs: - - newFactory: address - emits: [SuperTokenFactoryUpdated] - errors: [HOST_ONLY_GOVERNANCE, HOST_NON_UPGRADEABLE] - -updateSuperTokenLogic: - # Upgrade a SuperToken proxy to new logic. If newLogicOverride is address(0), - # uses the canonical logic from the SuperTokenFactory. - mutability: nonpayable - access: governance - inputs: - - token: address - - newLogicOverride: address # address(0) means use canonical logic - emits: [SuperTokenLogicUpdated] - errors: [HOST_ONLY_GOVERNANCE] - -changeSuperTokenAdmin: - # Change the admin of a SuperToken. The admin is the only account allowed to - # upgrade the token logic. Default admin is the Host itself. - mutability: nonpayable - access: governance - inputs: - - token: address - - newAdmin: address - errors: [HOST_ONLY_GOVERNANCE] - -# == Pool Beacon == - -updatePoolBeaconLogic: - # Upgrade the SuperfluidPool beacon to new logic. Affects all GDA pool proxies. - mutability: nonpayable - access: governance - inputs: - - newLogic: address - emits: [PoolBeaconLogicUpdated] - errors: [HOST_ONLY_GOVERNANCE] - -# == ERC2771 / Forwarder == - -isTrustedForwarder: - # Check if an address is a governance-approved trusted forwarder for meta-transactions. - mutability: view - inputs: - - forwarder: address - outputs: - - bool - -versionRecipient: - # ERC2771 relay recipient version string. Returns "v1". - mutability: pure - outputs: - - string - -getERC2771Forwarder: - # Returns the ERC2771Forwarder contract used for ERC2771_FORWARD_CALL batch operations. - mutability: view - outputs: - - address - -getSimpleACL: - # Returns the SimpleACL contract used for Super App registration permissioning. - mutability: view - outputs: - - address - -# == Time == - -getNow: - # Returns block.timestamp. Useful for off-chain tooling and testing. - mutability: view - outputs: - - uint256 - -# == Protocol Constants == - -NON_UPGRADABLE_DEPLOYMENT: - # True if the Host was deployed in non-upgradable mode. - mutability: view - outputs: - - bool - -APP_WHITE_LISTING_ENABLED: - # True if Super App registration requires governance permission. - mutability: view - outputs: - - bool - -CALLBACK_GAS_LIMIT: - # Maximum gas forwarded to Super App callbacks. - mutability: view - outputs: - - uint64 - -MAX_APP_CALLBACK_LEVEL: - # Maximum callback nesting depth for composed Super Apps. Currently 1. - mutability: view - outputs: - - uint256 - -MAX_NUM_AGREEMENTS: - # Maximum number of agreement classes that can be registered. 256. - mutability: view - outputs: - - uint32 - -ACL_SUPERAPP_REGISTRATION_ROLE: - # keccak256("ACL_SUPERAPP_REGISTRATION_ROLE") — the SimpleACL role for app registration. - mutability: view - outputs: - - bytes32 - -SIMPLE_FORWARDER: - # Address of the SimpleForwarder used for SIMPLE_FORWARD_CALL batch operations. - mutability: view - outputs: - - address - -# == Events == - -events: - GovernanceReplaced: - data: - - oldGov: address - - newGov: address - - AgreementClassRegistered: - # agreementType is keccak256("org.superfluid-finance.agreements..") - data: - - agreementType: bytes32 - - code: address - - AgreementClassUpdated: - data: - - agreementType: bytes32 - - code: address - - SuperTokenFactoryUpdated: - data: - - newFactory: address - - SuperTokenLogicUpdated: - indexed: - - token: address - data: - - code: address - - PoolBeaconLogicUpdated: - indexed: - - beaconProxy: address - data: - - newBeaconLogic: address - - AppRegistered: - indexed: - - app: address - - Jail: - # Emitted when a Super App is jailed for violating protocol rules. - # See APP_RULE error below for reason code values. - indexed: - - app: address - data: - - reason: uint256 - - # Inherited events (from UUPSProxiable): - # CodeUpdated — emitted on proxy upgrade (uuid, codeAddress) - # Initialized — emitted on proxy initialization (version) - -# == Errors == - -errors: - # Governance - - HOST_ONLY_GOVERNANCE # caller is not the governance contract - - # Agreement whitelisting - - HOST_AGREEMENT_ALREADY_REGISTERED - - HOST_AGREEMENT_IS_NOT_REGISTERED - - HOST_MAX_256_AGREEMENTS # maximum agreement slots exhausted - - HOST_ONLY_LISTED_AGREEMENT # caller is not a registered agreement - - # Upgradability - - HOST_NON_UPGRADEABLE # deployment is non-upgradable - - HOST_CANNOT_DOWNGRADE_TO_NON_UPGRADEABLE - - # App registration - - HOST_MUST_BE_CONTRACT # app address has no code - - HOST_NO_APP_REGISTRATION_PERMISSION - - HOST_INVALID_CONFIG_WORD # invalid Super App manifest flags - - HOST_SUPER_APP_ALREADY_REGISTERED - - HOST_NOT_A_SUPER_APP # target is not a registered Super App - - HOST_SUPER_APP_IS_JAILED - - # App composition - - HOST_SENDER_IS_NOT_SUPER_APP - - HOST_RECEIVER_IS_NOT_SUPER_APP - - HOST_SOURCE_APP_NEEDS_HIGHER_APP_LEVEL - - # Context & call proxies - - HOST_NON_ZERO_LENGTH_PLACEHOLDER_CTX # placeholder ctx must be empty - - HOST_CALL_AGREEMENT_WITH_CTX_FROM_WRONG_ADDRESS # msg.sender != ctx.appAddress - - HOST_CALL_APP_ACTION_WITH_CTX_FROM_WRONG_ADDRESS # msg.sender != ctx.appAddress - - HOST_AGREEMENT_CALLBACK_IS_NOT_ACTION # app action selector matches a callback - - # Batch call - - HOST_UNKNOWN_BATCH_CALL_OPERATION_TYPE - - # Callback execution - - HOST_NEED_MORE_GAS # insufficient gas for Super App callback - - # Super App rule violations (generic, carries a reason code) - # Reason codes (from SuperAppDefinitions.sol): - # 10 — NO_REVERT_ON_TERMINATION_CALLBACK (delete callback reverted) - # 11 — NO_CRITICAL_SENDER_ACCOUNT (callback made sender critical) - # 12 — NO_CRITICAL_RECEIVER_ACCOUNT (callback made receiver critical) - # 20 — CTX_IS_READONLY (state change in before-callback staticcall) - # 21 — CTX_IS_NOT_CLEAN (context not properly returned) - # 22 — CTX_IS_MALFORMATED (context bytes corrupted/fabricated) - # 30 — COMPOSITE_APP_IS_NOT_WHITELISTED (no allowCompositeApp) - # 31 — COMPOSITE_APP_IS_JAILED (downstream app is jailed) - # 40 — MAX_APP_LEVEL_REACHED (callback nesting exceeded limit) - - APP_RULE: - inputs: - - _code: uint256 - - # SafeCast (inherited from OpenZeppelin) - - SafeCastOverflowedUintDowncast: - inputs: - - bits: uint8 - - value: uint256 diff --git a/.agents/skills/gooddollar/references/contracts/Superfluid.selectors.yaml b/.agents/skills/gooddollar/references/contracts/Superfluid.selectors.yaml deleted file mode 100644 index 18edf9b0..00000000 --- a/.agents/skills/gooddollar/references/contracts/Superfluid.selectors.yaml +++ /dev/null @@ -1,51 +0,0 @@ -# Generated by scripts/selectors.mjs -functions: - batchCall(tuple[]): 0x31131761 - forwardBatchCall(tuple[]): 0xbc5a2101 - callAgreement(): 0xb7a9d0f0 - callAppAction(): 0x40df3d29 - callAgreementWithContext(address,bytes,bytes,bytes): 0x4329d293 - callAppActionWithContext(address,bytes,bytes): 0xba48b5f8 - decodeCtx(bytes): 0x3f6c923a - isCtxValid(bytes): 0xbf428734 - registerApp(): 0x6d6c85c3 - registerAppWithKey(uint256,string): 0xbd1c448b - registerAppByFactory(address,uint256): 0xf3733052 - isApp(address): 0x3ca3ad4e - getAppCallbackLevel(address): 0x9378fa13 - getAppManifest(address): 0xf9f522f4 - isAppJailed(address): 0x6b4f3335 - allowCompositeApp(address): 0x57121e0c - isCompositeAppAllowed(address,address): 0xbb84cfa1 - callAppBeforeCallback(address,bytes,bool,bytes): 0x74041e02 - callAppAfterCallback(address,bytes,bool,bytes): 0x1e6d0a84 - appCallbackPush(bytes,address,uint256,int256,address): 0x768fabb0 - appCallbackPop(bytes,int256): 0x989b0c3e - ctxUseCredit(bytes,int256): 0x59a29141 - jailApp(bytes,address): 0x5578431e - replaceGovernance(address): 0x7283100c - registerAgreementClass(address): 0x15a024e1 - updateAgreementClass(address): 0x06cecba8 - isAgreementTypeListed(): 0xf8dba358 - isAgreementClassListed(address): 0x8ca48484 - getAgreementClass(bytes32): 0xb6d200de - mapAgreementClasses(uint256): 0xc56a069d - addToAgreementClassesBitmap(uint256,bytes32): 0xbced3ddc - removeFromAgreementClassesBitmap(uint256,bytes32): 0xa5dbbbcd - updateSuperTokenFactory(address): 0x54fbc493 - updateSuperTokenLogic(address): 0x787afde7 - changeSuperTokenAdmin(address,address): 0x0c565075 - updatePoolBeaconLogic(address): 0x2f89bf89 - isTrustedForwarder(address): 0x572b6c05 - SIMPLE_FORWARDER(): 0xf85263b9 -events: - GovernanceReplaced(address,address): 0x13abda02e63c790d0e2818b251282cfe5cbe0a8abd69c54bf5d2260c0907bd2e - AgreementClassRegistered(bytes32,address): 0x878135063a6cfb3bc333e534b1fdc83f4f12221cad6705c31c0567048a8bd3d1 - AgreementClassUpdated(bytes32,address): 0x9279aa773f2b588996032d8de89911555039f28b13a11a7c17074330bc082d9a - SuperTokenFactoryUpdated(address): 0xce13a9895a1719ad4493b2ac1a9bfb36070566161abab408e7ecbe586da8d499 - SuperTokenLogicUpdated(address,address): 0x840acbd291b38534819f47f875839277e502f40e1c7bfea2c5fc2c8017442cd3 - PoolBeaconLogicUpdated(address,address): 0x052cea8931962dd445ef48b0b998d3056bd0705f437087d60fe3c46a3fa09e1f - AppRegistered(address): 0x0d540ad8f39e07d19909687352b9fa017405d93c91a6760981fbae9cf28bfef7 - Jail(address,uint256): 0xbe3aa33bd245135e4e26b223d79d14ea479a47bff09f2b03c53838af1edbb14b - # Inherited events (from UUPSProxiable)(): 0x3a1d0f622cf5be33dc2558b359f025fbeca1d6fb271fb70452a02b0ec2666ce8 -errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/UBISchemeV2.abi.yaml b/.agents/skills/gooddollar/references/contracts/UBISchemeV2.abi.yaml deleted file mode 100644 index 75e5e437..00000000 --- a/.agents/skills/gooddollar/references/contracts/UBISchemeV2.abi.yaml +++ /dev/null @@ -1,305 +0,0 @@ -# UBISchemeV2 — dynamic daily UBI pool with per-root claiming and cycle accounting -# Claim uses Identity getWhitelistedRoot(msg.sender); transfers use the root as claim key. - -meta: - name: UBISchemeV2 - version: v2 - source: - - https://raw.githubusercontent.com/GoodDollar/GoodProtocol/master/contracts/ubi/UBISchemeV2.sol - inherits: [DAOUpgradeableContract] - note: > - Pool sizing uses cycleLength, reserveFactor, minActiveUsers, and on-chain G$ - balance. claim() reverts if Identity root is zero. Internal accounting keys - claims by whitelisted root, payouts go to msg.sender. - deployments: - mainnet: - production: - UBIScheme: - networkId: 122 - address: "0xd253A5203817225e9768C05E5996d642fb96bA86" - creationBlock: 15747401 - production-celo: - UBIScheme: - networkId: 42220 - address: "0x43d72Ff17701B2DA814620735C39C620Ce0ea4A1" - creationBlock: 18006679 - production-xdc: - UBIScheme: - networkId: 50 - address: "0x22867567E2D80f2049200E25C6F31CB6Ec2F0faf" - creationBlock: 95249624 - related: - - https://docs.gooddollar.org/for-developers/core-contracts/ubischeme - - references/guides/claim.md - -getClaimerCount: - mutability: view - inputs: - - day: uint256 - outputs: - - count: uint256 - -getClaimAmount: - notes: - - "Returns aggregate amount claimed for the provided day index." - mutability: view - inputs: - - day: uint256 - outputs: - - amount: uint256 - -getDailyStats: - notes: - - "Returns (claimer count, total claimed amount) for the calendar day derived from periodStart." - mutability: view - inputs: [] - outputs: - - claimerCount: uint256 - - claimAmount: uint256 - -setCycleLength: - mutability: nonpayable - access: avatar - inputs: - - _newLength: uint256 - outputs: [] - emits: [CycleLengthSet] - errors: [CYCLE_TOO_SHORT, ONLY_AVATAR] - -setDay: - notes: - - "Anyone may advance currentDay when wall-clock day crosses the next boundary." - mutability: nonpayable - inputs: [] - outputs: [] - emits: [DaySet] - -hasClaimed: - mutability: view - inputs: - - account: address - outputs: - - claimed: bool - -isNotNewUser: - mutability: view - inputs: - - _account: address - outputs: - - isReturning: bool - -estimateNextDailyUBI: - notes: - - "View-only projection when dailyUbi not yet fixed for the current wall day." - mutability: view - inputs: [] - outputs: - - projected: uint256 - -"checkEntitlement()": - notes: - - "Uses msg.sender as member; resolves Identity root before hasClaimed check." - mutability: view - inputs: [] - outputs: - - amount: uint256 - -"checkEntitlement(address)": - notes: - - "Checks claimable amount for the provided member using that account's whitelisted root." - mutability: view - inputs: - - _member: address - outputs: - - amount: uint256 - -claim: - notes: - - "Resolves whitelistedRoot = Identity.getWhitelistedRoot(msg.sender); reverts if zero." - - "_claim keys bookkeeping by root but pays msg.sender." - - "May notify ClaimersDistribution at GDAO_CLAIMERS if configured." - - "On success emits UBIClaimed; distributionFormula may emit UBICalculated, UBICycleCalculated, DaySet, WithdrawFromDao." - mutability: nonpayable - access: anyone - inputs: [] - outputs: - - didClaim: bool - errors: - - NOT_IN_PERIOD_OR_PAUSED - - NOT_WHITELISTED - - CLAIM_TRANSFER_FAILED - - DAO_TRANSFER_FAILED - -setShouldWithdrawFromDAO: - mutability: nonpayable - access: avatar - inputs: - - _shouldWithdraw: bool - outputs: [] - emits: [ShouldWithdrawFromDAOSet] - errors: [ONLY_AVATAR] - -pause: - mutability: nonpayable - access: avatar - inputs: - - _pause: bool - outputs: [] - errors: [ONLY_AVATAR] - -setNewClaimersReserveFactor: - notes: - - "Adjusts reserveFactor used by distribution formula to reserve part of pool for new claimers." - mutability: nonpayable - access: avatar - inputs: - - _reserveFactor: uint32 - outputs: [] - errors: [ONLY_AVATAR] - -withdraw: - notes: - - "Avatar-controlled treasury action transferring G$ from scheme balance to recipient." - mutability: nonpayable - access: avatar - inputs: - - _amount: uint256 - - _recipient: address - outputs: [] - errors: - - ONLY_AVATAR - - WITHDRAW_FAILED - -lastClaimed: - notes: - - "Returns last claim timestamp keyed by account root logic used by hasClaimed checks." - mutability: view - inputs: - - account: address - outputs: - - ts: uint256 - -currentDay: - mutability: view - inputs: [] - outputs: - - day: uint256 - -periodStart: - mutability: view - inputs: [] - outputs: - - ts: uint256 - -dailyUbi: - mutability: view - inputs: [] - outputs: - - amount: uint256 - -lastWithdrawDay: - mutability: view - inputs: [] - outputs: - - day: uint256 - -shouldWithdrawFromDAO: - mutability: view - inputs: [] - outputs: - - flag: bool - -cycleLength: - mutability: view - inputs: [] - outputs: - - days: uint256 - -dailyCyclePool: - mutability: view - inputs: [] - outputs: - - pool: uint256 - -startOfCycle: - mutability: view - inputs: [] - outputs: - - ts: uint256 - -currentCycleLength: - mutability: view - inputs: [] - outputs: - - length: uint256 - -minActiveUsers: - mutability: view - inputs: [] - outputs: - - n: uint256 - -totalClaimsPerUser: - mutability: view - inputs: - - account: address - outputs: - - count: uint256 - -reserveFactor: - mutability: view - inputs: [] - outputs: - - bps: uint32 - -paused: - mutability: view - inputs: [] - outputs: - - isPaused: bool - -events: - WithdrawFromDao: - indexed: [] - data: - - prevBalance: uint256 - - newBalance: uint256 - UBICalculated: - indexed: [] - data: - - day: uint256 - - dailyUbi: uint256 - - blockNumber: uint256 - UBICycleCalculated: - indexed: [] - data: - - day: uint256 - - pool: uint256 - - cycleLength: uint256 - - dailyUBIPool: uint256 - UBIClaimed: - indexed: - - claimer: address - data: - - amount: uint256 - CycleLengthSet: - indexed: [] - data: - - newCycleLength: uint256 - DaySet: - indexed: [] - data: - - newDay: uint256 - ShouldWithdrawFromDAOSet: - indexed: [] - data: - - ShouldWithdrawFromDAO: bool - -errors: - NOT_IN_PERIOD_OR_PAUSED: "not in periodStarted or paused — requireStarted modifier." - CYCLE_TOO_SHORT: "cycle must be at least 1 day long" - DAO_TRANSFER_FAILED: "DAO transfer has failed — _withdrawFromDao balance check." - NOT_WHITELISTED: "UBIScheme: not whitelisted — claim()" - CLAIM_TRANSFER_FAILED: "claim transfer failed — G$ transfer in _transferTokens." - WITHDRAW_FAILED: "withdraw failed — avatar withdraw." - ONLY_AVATAR: "Inherited avatar-only guard on DAOUpgradeable paths." diff --git a/.agents/skills/gooddollar/references/contracts/UBISchemeV2.selectors.yaml b/.agents/skills/gooddollar/references/contracts/UBISchemeV2.selectors.yaml deleted file mode 100644 index 666b89d5..00000000 --- a/.agents/skills/gooddollar/references/contracts/UBISchemeV2.selectors.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# Generated by scripts/selectors.mjs -functions: - getClaimerCount(uint256): 0xc7a76adf - getClaimAmount(uint256): 0xcef63600 - getDailyStats(): 0x069786ea - setCycleLength(uint256): 0x3d84ceca - setDay(): 0xdddc3616 - hasClaimed(address): 0x73b2e80e - isNotNewUser(address): 0xa21f698a - estimateNextDailyUBI(): 0xc7713870 - checkEntitlement(): 0x98d6621b - checkEntitlement(address): 0x1a787f2e - claim(): 0x4e71d92d - setShouldWithdrawFromDAO(bool): 0xde1de3a0 - pause(bool): 0x02329a29 - setNewClaimersReserveFactor(uint32): 0x414089be - withdraw(uint256,address): 0x00f714ce - lastClaimed(address): 0x013eba92 - currentDay(): 0x5c9302c9 - periodStart(): 0xeda4e6d6 - dailyUbi(): 0x1d8f5ea9 - lastWithdrawDay(): 0xd7c4cbb8 - shouldWithdrawFromDAO(): 0x456ac1c2 - cycleLength(): 0xeac471a0 - dailyCyclePool(): 0x9dc2c033 - startOfCycle(): 0xba075410 - currentCycleLength(): 0x741470ac - minActiveUsers(): 0x37658574 - totalClaimsPerUser(address): 0xcc054dfc - reserveFactor(): 0x4322b714 - paused(): 0x5c975abb -events: - WithdrawFromDao(uint256,uint256): 0x3107ec7eaa50b775d2486c7a394472235804b6fe1c0d4b7bd1d79b09df60f2ba - UBICalculated(uint256,uint256,uint256): 0x836fa39995340265746dfe9587d9fe5c5de35b7bce778afd9b124ce1cfeafdc4 - UBICycleCalculated(uint256,uint256,uint256,uint256): 0x83e0d535b9e84324e0a25922406398d6ff5f96d0c686204ee490e16d7670566f - UBIClaimed(address,uint256): 0x89ed24731df6b066e4c5186901fffdba18cd9a10f07494aff900bdee260d1304 - CycleLengthSet(uint256): 0xa61e6cca2c12e2a0a493683acfe95b034f0f50d793434f4dfe3ba06ea201f344 - DaySet(uint256): 0x67eb03bd555181f9dd23f546e4331ddfb8b4a7d0c8d261ba44e037f30ce894ea - ShouldWithdrawFromDAOSet(bool): 0x6cd9a0fd2e006be39a9918bf56c85cae1d4f4599474483ff18cb93355ebaaf8e -errors: {} diff --git a/.agents/skills/gooddollar/references/contracts/_rich-abi-yaml-format.md b/.agents/skills/gooddollar/references/contracts/_rich-abi-yaml-format.md deleted file mode 100644 index 41e2baeb..00000000 --- a/.agents/skills/gooddollar/references/contracts/_rich-abi-yaml-format.md +++ /dev/null @@ -1,93 +0,0 @@ -# Reading the Rich ABI YAMLs - -Each YAML is a self-contained contract reference. Here's how to parse them. - -## Root structure - -``` -# Header comment — contract name, description, key notes -meta: # name, version, source, implements, inherits, deployments, - # deploymentCreationBlocks (optional), notes -# == Section == # Grouped functions (these are the core content) -events: # All events the contract emits -errors: # Complete error index -``` - -Three root keys are reserved: `meta`, `events`, `errors`. Every other -root-level key is a **function**. - -## Function entries - -```yaml -createFlow: - # Description of what the function does. - notes: - - "Gotcha: Non-obvious behavior or edge cases listed here as structured data." - mutability: nonpayable # view | pure | nonpayable | payable - access: sender | operator # who can call (omitted for view/pure) - inputs: - - token: address - - receiver: address - - flowRate: int96 # inline comments for non-obvious params - - ctx: bytes - outputs: - - newCtx: bytes - emits: [FlowUpdated, FlowUpdatedExtension] # ordered by emission sequence - errors: [CFA_FLOW_ALREADY_EXISTS, CFA_INVALID_FLOW_RATE] # ordered by check sequence -``` - -Fields appear in this order: description comment, `notes`, `mutability`, -`access`, `inputs`, `outputs`, `emits`, `errors`. All are omitted when not -applicable. - -## Key conventions - -- **`ctx: bytes` parameter** = function is called through the Host - (`callAgreement` / `batchCall`), never directly. -- **`access` labels**: `anyone`, `host`, `self`, `admin`, `governance`, - `sender`, `receiver`, `operator`, `manager`, `pic`, `agreement`, - `trusted-forwarder`, `factory`, `super-app`. Combine with `|`. Conditional: - `anyone(if-critical-or-jailed)`. -- **`emits` and `errors` ordering** carries meaning: matches execution flow, - not alphabetical. First errors in the list are the most likely causes. -- **`notes:` field** on functions (and `meta.notes:` at contract level) - lists non-obvious behavior, common mistakes, or edge cases. Always read - these carefully. -- **`meta.source`** is an array of raw GitHub URLs to the Solidity source files - (implementation, interface, base — filenames are self-documenting). -- **`meta.deployments`** has per-network addresses split into `mainnet` and - `testnet` subgroups. -- **`creationBlock`**: decimal block where that **`address`** first has code; - put it on the same object as **`networkId`** / **`address`**, immediately after **`address`**. -- **`meta.deploymentCreationBlocks`**: same shape as **`meta.deployments`**, for - deployments stored as plain address strings; leaves are decimal block numbers. - -## Events section - -```yaml -events: - FlowUpdated: - indexed: # log topics (filterable) - - token: address - - sender: address - data: # log payload - - flowRate: int96 -``` - -## Errors section - -```yaml -errors: - # -- Category -- - - SIMPLE_ERROR # description - - PARAMETERIZED_ERROR: # errors with diagnostic data - inputs: - - value: uint256 -``` - -## Selector sidecar files - -Every `Foo.abi.yaml` has a companion `Foo.selectors.yaml` in the same -directory. These contain computed hex selectors (function 4-byte, error -4-byte, event 32-byte topic0) with full Solidity signatures for -verification. Generated by `scripts/selectors.mjs`. diff --git a/.agents/skills/gooddollar/references/deep-researches/faucet-flows.md b/.agents/skills/gooddollar/references/deep-researches/faucet-flows.md deleted file mode 100644 index 12b18990..00000000 --- a/.agents/skills/gooddollar/references/deep-researches/faucet-flows.md +++ /dev/null @@ -1,42 +0,0 @@ -# Faucet flows (user-facing explanation) - -This note explains the Faucet in plain language: what it does for users, why a top-up can fail, and what limits exist. - -The on-chain Faucet contract is used to add a small amount of native gas token to a wallet so the user can pay transaction fees. -Reference implementation: [`contracts/fuseFaucet/Faucet.sol`](https://github.com/GoodDollar/GoodProtocol/blob/master/contracts/fuseFaucet/Faucet.sol). -Addresses per chain: [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) only (for example `Faucet` under `production`, `production-celo`, `production-xdc`). - -## What this means for users - -- If your wallet is eligible, Faucet can send a small gas top-up. -- Eligibility usually depends on identity status and anti-abuse limits. -- This is a support mechanism for transaction fees, not a general transfer or swap service. - -## Main actions (in user language) - -- **Top up wallet** (`topWallet`) - Attempts to send gas to the target wallet after checks pass. - -- **Check eligibility first** (`canTop`) - Fast pre-check to see if top-up is currently allowed. - -- **Estimate top-up amount** (`getToppingAmount`) - Shows the amount Faucet would try to send right now. - -## Why a top-up may fail - -- You are not currently authorized by identity rules. -- Daily limit reached. -- Weekly limit reached. -- Wallet is temporarily banned. -- Wallet is too new for current policy. -- Calculated top-up is below minimum threshold. - -## Important safety note - -- The `onTokenTransfer` path includes a swap-like mechanism and is not meant as a normal user swap route. -- It does not enforce slippage protection in the same way users expect from a dedicated swap UI. - -## For developers and agents - -Use `references/guides/faucet.md` for step-by-step execution flow and deterministic preflight calls. diff --git a/.agents/skills/gooddollar/references/deep-researches/fuse-to-celo-staking-migration.md b/.agents/skills/gooddollar/references/deep-researches/fuse-to-celo-staking-migration.md deleted file mode 100644 index 261c2d7f..00000000 --- a/.agents/skills/gooddollar/references/deep-researches/fuse-to-celo-staking-migration.md +++ /dev/null @@ -1,38 +0,0 @@ -# Why Fuse to CELO staking migration uses a staged backend flow - -This explains why the migration is split into allowance detection, unstake, bridge, and destination re-stake instead of a single transaction. In this context, Fuse `GovernanceStakingV2` is the old/source staking contract and Celo **`GooddollarSavingsStream`** is the destination staking contract ([source](https://github.com/Ubeswap/gooddollar-contracts/blob/main/contracts/GooddollarSavingsStream.sol), [0x059ee811414230d1Fb157878D2b491240F4D8d3B](https://celoscan.io/address/0x059ee811414230d1Fb157878D2b491240F4D8d3B)). - -## Why this cannot be one-chain atomic - -Fuse governance staking and CELO destination savings live on different chains, so the system cannot atomically close and reopen stake in one EVM transaction. Cross-chain migration is asynchronous by design and must tolerate timing gaps between source completion and destination finalization. - -## Why user allowance is first - -The migration flow assumes a backend-operated execution wallet. If that wallet needs to pull or operate on user funds in the source path, user approval must exist first. Without allowance, all downstream steps fail, so approval state is the earliest hard gate. - -## Why unstake is separated from bridge - -The source staking position is the canonical balance record on Fuse. The migration must first materialize transferable G$ by closing or reducing stake, then bridge only the confirmed unlocked amount. Bridging before final unstake confirmation introduces mismatch risk. - -## Why bridge finalization must be explicit - -Bridge transfers are eventually consistent across chains. The destination stake step must only run after the bridged G$ is confirmed on CELO. This avoids phantom staking attempts and preserves deterministic accounting. - -## Why destination uses `stakeFor` - -Ubeswap `GooddollarSavingsStream` on Celo supports `stakeFor(amount, recipient)`, which allows the backend execution wallet to stake on behalf of the user after bridge finalization. This fits migration operations where custody is temporary during the transfer window. - -## Main operational risks - -- partial migration from source unstake or bridge limit constraints -- stuck-in-transit bridge messages delaying destination staking -- stale assumptions about contract addresses across networks -- reward expectation mismatch when moving from Fuse governance staking mechanics to CELO savings mechanics - -## Contract/source map - -- Fuse staking family (GoodProtocol): [`GovernanceStaking.sol`](https://github.com/GoodDollar/GoodProtocol/blob/master/contracts/governance/GovernanceStaking.sol) -- Fuse deployment mapping: [`deployment.json`](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) (`production.GovernanceStakingV2`) -- Celo savings (Ubeswap): [`GooddollarSavingsStream.sol`](https://github.com/Ubeswap/gooddollar-contracts/blob/main/contracts/GooddollarSavingsStream.sol) -- Ubeswap contracts repository: [Ubeswap/gooddollar-contracts](https://github.com/Ubeswap/gooddollar-contracts) -- Bridge normalization for LZ quotes: [`BridgeHelperLibrary.normalizeFromTokenTo18Decimals`](https://github.com/GoodDollar/GoodBridge/blob/master/packages/bridge-contracts/contracts/messagePassingBridge/BridgeHelperLibrary.sol) (off-chain LayerZero fee estimation must match this; `canBridge` and `bridgeToWithLz` use the raw burn amount on the source chain) diff --git a/.agents/skills/gooddollar/references/deep-researches/gooddao-daostack-surface.md b/.agents/skills/gooddollar/references/deep-researches/gooddao-daostack-surface.md deleted file mode 100644 index 754a3a05..00000000 --- a/.agents/skills/gooddollar/references/deep-researches/gooddao-daostack-surface.md +++ /dev/null @@ -1,37 +0,0 @@ -# GoodDAO and DAOStack surface - -GoodProtocol’s on-chain **governance shell** is largely **DAOStack-shaped**: an **Avatar** holds protocol assets and reputation context; a **Controller** registers **schemes** and routes privileged calls. GoodDocs summarizes DAO-facing roles; **Avatar**, **Controller**, and other DAO contract addresses live only in [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json). Implementation follows [DAOStack Arc](https://github.com/daostack/arc) patterns. - -## Core interfaces (GoodProtocol) - -[`DAOStackInterfaces.sol`](https://github.com/GoodDollar/GoodProtocol/blob/master/contracts/DAOStackInterfaces.sol) defines the pieces most integrations touch: - -### Avatar - -- **`nativeToken()`** — often the G$ token address for the deployment. -- **`nativeReputation()`** — **`GReputation`** token for voting weight (see `GReputation` in `deployment.json`). -- **`owner()`** — owner of the Avatar, typically the **`Controller`** contract. - -### Controller - -- **`avatar()`** — address of the Avatar contract. -- **`registerScheme` / `unregisterScheme` / `unregisterSelf` / `isSchemeRegistered` / `getSchemePermissions`** — scheme lifecycle and permission bitmask per scheme+avatar. -- **`genericCall(contract, data, avatar, value)`** — executes arbitrary calls **as the avatar** (used heavily by DAO-backed contracts to move tokens or call NameService). -- **`mintTokens`**, **`externalTokenTransfer`**, **`sendEther`** — treasury-style operations through the controller/avatar. - -## How GoodProtocol contracts use it - -- **`DAOUpgradeableContract`** / **`DAOContract`** descendants resolve **`dao`** (Controller) and **`avatar`** from **NameService** keys such as **`CONTROLLER`** and **`AVATAR`** (also written during **NameService.initialize**). -- **Avatar-gated writes** (for example **NameService.setAddress**, **UBISchemeV2** admin functions, **IdentityV3** after `initDAO`) require **`msg.sender == dao.avatar()`** (or equivalent role), not EOAs. -- Schemes that upgrade themselves (for example staking **`upgrade()`**) use **`dao.genericCall`** and **`unregisterSelf`** against the avatar. - -## What agents should do - -1. Treat **DAO calls** as **governance-only** unless the user explicitly controls the avatar or a registered scheme. -2. For **read-only** work, use **NameService** and per-contract **view** functions; use **Controller.isSchemeRegistered** when validating that a target contract is an approved scheme (for example staking migrations). -3. Never fabricate **Avatar** or **Controller** addresses — use [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) or NameService. - -## References - -- [Core contracts — DAO contracts](https://docs.gooddollar.org/for-developers/core-contracts) (narrative only; addresses from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) only) -- DAOStack Arc controller and avatar concepts in the upstream repo linked from GoodDocs. diff --git a/.agents/skills/gooddollar/references/deep-researches/how-ubi-is-minted.md b/.agents/skills/gooddollar/references/deep-researches/how-ubi-is-minted.md deleted file mode 100644 index 96d321a3..00000000 --- a/.agents/skills/gooddollar/references/deep-researches/how-ubi-is-minted.md +++ /dev/null @@ -1,68 +0,0 @@ -# How UBI is minted - -This document aligns agent explanations with [How GoodDollar works](https://docs.gooddollar.org/how-gooddollar-works) and GoodDocs component pages; **contract addresses** come only from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json). - -## Monetary creation (protocol level) - -- New G$ is created in connection with reserve mechanics: purchases into the reserve and reserve-side parameters (including reserve ratio) influence how much G$ can be issued while maintaining backing (see sustainability and issuance sections in GoodDocs). -- Selling G$ back to the reserve burns supply in that model. -- G$ that the protocol creates is allocated across UBI, savings incentives, treasury, and ecosystem uses per the distribution section of GoodDocs. - -## Where G$ is actually created - -Creation happens at the G$ token `mint(...)` call site, not inside `UBIScheme.claim()`. - -Current implementation uses the Mento-core expansion path: - -- `GoodDollarExpansionController` mints and routes to distribution helper. - -**DistributionHelper recipients** decide how much eventually lands in the UBI pool. - -## Claim vs reserve minting (important distinction) - -- **Reserve path:** Buying G$ through the reserve-backed AMM (or related Mento rails) is where mint and burn tied to the reserve model most directly apply at the token level. -- **Daily UBI `claim()`:** On `UBISchemeV2`, a successful claim typically **transfers G$ from the scheme contract’s balance** to the user (`token.transfer` in `_transferTokens`). The scheme may be **refilled** from the DAO avatar via internal `_withdrawFromDao` when configured, not necessarily minting in the same transaction as `claim()`. So describe user-facing UBI as **receipt from the UBI scheme balance**; reserve **minting** is the macro story, **transfer** is the usual claim-time mechanism. - -## Mento-core expansion flow (detailed) - -This is the detailed path for modern reserve-ratio-aware expansion. - -1. A caller triggers `mintUBIFromExpansion(exchangeId)` on `GoodDollarExpansionController`. -2. Expansion is time-gated by config (`expansionFrequency`, `lastExpansion`), so it does not run every block. -3. Controller computes a reserve-ratio scalar (effectively compounding `(1 - expansionRate)` for elapsed periods). -4. Controller calls `GoodDollarExchangeProvider.mintFromExpansion(exchangeId, reserveRatioScalar)`. -5. Exchange provider updates exchange state (including reserve-ratio math) and returns `amountToMint`. -6. Controller mints G$ to `distributionHelper`. -7. Controller triggers distribution (`onDistribution`) so recipients (including UBIScheme when configured) receive allocation. - -Also in `GoodDollarExpansionController`: - -- `mintUBIFromInterest(exchangeId, reserveInterest)` -- `mintUBIFromReserveBalance(exchangeId)` - -These are additional funding paths that mint to distribution helper as part of reserve-driven policy. - -## Reserve ratio, expansion, and risk - -- Practical reserve-ratio intuition: collateral backing strength per unit of G$ supply. -- Lower reserve ratio means weaker backing and higher risk when adding new supply. -- Expansion uses reserve-ratio-aware math instead of blind fixed minting, but aggressive params can still increase sell pressure. -- Key policy levers are expansion rate and expansion frequency (plus caller cadence/automation quality). - -## What claimers experience - -- Verified users receive daily UBI from a pool split among those who claim in each period (GoodDocs). -- The user-facing transaction is a UBIScheme-style `claim` on chains where it is deployed; ABI and version follow your deployment. - -## On-chain components (typical) - -- Identity system for verification and whitelist roots. -- UBIScheme (or successor) for entitlement and claim execution. -- G$ token: value reaches users via **transfer** from scheme balance and/or broader minting economics from the reserve side depending on which action you analyze. -- DistributionHelper: bridge layer between mint source and recipient buckets (UBI, others). - -## Agent guidance - -- Use GoodDocs for macro issuance and allocation; use UBIScheme + token transfer behavior for **claim** explanations. -- Use [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) for contract addresses instead of guessing. -- When users ask "why no UBI funding today", check whether mint functions were executed, then verify DistributionHelper recipient config and UBIScheme balance. diff --git a/.agents/skills/gooddollar/references/deep-researches/inviter-invitee-reward-model.md b/.agents/skills/gooddollar/references/deep-researches/inviter-invitee-reward-model.md deleted file mode 100644 index 267fa744..00000000 --- a/.agents/skills/gooddollar/references/deep-researches/inviter-invitee-reward-model.md +++ /dev/null @@ -1,58 +0,0 @@ -# Inviter and invitee reward model - -This explains why invite rewards sometimes work and sometimes fail, in user-facing terms. - -Invite rewards are handled by `InvitesV2`, but eligibility depends heavily on the identity system (`Identity`, often `IdentityV4`). In practice, most confusing cases are caused by whitelist or reverification state, not by the invite contract itself. - -## How the reward model works - -There are two separate moments: - -- `join`: the invitee registers with an invite code. -- bounty payout: the contract later checks if this invitee is eligible and pays inviter and invitee when rules pass. - -So joining does not guarantee immediate payout. Payout depends on current eligibility at claim time. - -## Why whitelist status is the main gate - -For bounty eligibility, `InvitesV2` checks whitelist state through Identity. - -The important behavior is: - -- A user can still have status `1` in identity storage but fail `isWhitelisted(...)` if reverification is due. -- When reverification is due, bounty checks fail until an admin refreshes authentication(Face Verification). -- Connected-wallet setups can still fail if the specific address used in invite flow does not pass the whitelist check expected by the contract path. - -## Why reverification blocks rewards - -Reverification cadence is defined in `IdentityV4` with day-based options (`reverifyDaysOptions`) and per-user progression (`authCount`). - -When too many days pass since the last authentication for that user’s current step: - -- `shouldReverify(...)` becomes true -- `isWhitelisted(...)` becomes false for bounty gating -- `canCollectBountyFor(...)` fails until authentication is refreshed - -This is why teams may see users who were once valid but are currently not eligible for invite rewards. - -## Common reasons a bounty is not paid - -- Invitee or inviter is not currently whitelisted. -- Reverification is due for invitee or inviter. -- `minimumClaims` or `minimumDays` thresholds are not met yet. -- Bounty was already paid or was zero at join time. -- Contract is inactive, or identity-chain checks do not match the active chain. -- Invite code or join state is invalid (duplicate code, self-invite, already joined). - -## What to measure for analytics - -- Historical pass: account was authenticated at least once (`lastAuthenticated > 0`). -- Current eligibility: account is currently whitelist-valid (`isWhitelisted`/non-zero root, depending on query design). - -Do not treat these as the same metric. Historical pass explains past onboarding success; current eligibility explains current payout success. - -## Contract sources - -- Invite contract: [`InvitesV2.sol`](https://github.com/GoodDollar/GoodProtocol/blob/master/contracts/invite/InvitesV2.sol) -- Identity contract: [`IdentityV4.sol`](https://github.com/GoodDollar/GoodProtocol/blob/master/contracts/identity/IdentityV4.sol) -- Deployment addresses: [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) diff --git a/.agents/skills/gooddollar/references/deep-researches/mento-reserve-economics.md b/.agents/skills/gooddollar/references/deep-researches/mento-reserve-economics.md deleted file mode 100644 index e46db2a8..00000000 --- a/.agents/skills/gooddollar/references/deep-researches/mento-reserve-economics.md +++ /dev/null @@ -1,27 +0,0 @@ -# Mento and reserve economics - -This note ties **macro G$ economics** (GoodDocs) to **Mento trading surfaces** (broker, reserve, expansion) that agents integrate on-chain. - -## Protocol-level story (GoodDocs) - -- G$ is **reserve-backed**; issuance and price discovery follow an **augmented bonding curve** (Bancor-style dynamics) described in [How GoodDollar works](https://docs.gooddollar.org/how-gooddollar-works). -- **Buying** from the reserve side increases reserve assets and can support **new G$ supply** within reserve rules; **selling back** burns G$ and returns collateral, with parameters governed by **GoodDAO**. -- Distribution of newly created G$ spans UBI, savings incentives, treasury, and ecosystem allocations (same doc), but execution timing depends on the mint path and distribution trigger used by the deployment flow. - -## User-facing buy and sell (historical vs current) - -- [Buy and Sell G$](https://docs.gooddollar.org/user-guides) describes reserve interaction, fees (including exit contribution on some paths), and older explorer flows (GoodMarketMaker / exchangeHelper on Ethereum testnets). Treat that page as **product narrative**; **live contract addresses** must come only from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json). - -## Mento stack (agent integration) - -On networks where GoodDollar uses Mento (see `MentoBroker` and related keys in [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) for your environment): - -- **`IBroker`** (implementation in [mento-core `Broker.sol`](https://github.com/mento-org/mento-core)) is the usual **swap entrypoint**: `getAmountIn` / `getAmountOut`, `swapIn` (exact in), `swapOut` (exact out), plus **trading limits** state per exchange id and token. -- **Reserve** holds collateral; **exchange provider** contracts price trades against the reserve; **GoodDollarExpansionController** and related interfaces in GoodProtocol’s [`MentoInterfaces.sol`](https://github.com/GoodDollar/GoodProtocol/blob/master/contracts/MentoInterfaces.sol) describe expansion and avatar wiring for governance-facing changes. -- Agents should not hardcode **exchangeId** or provider addresses: read them from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) or discover via `getExchangeProviders()` after confirming the broker address for the chain from that file. - -## Agent guidance - -1. Explain **macro** supply and reserve behavior with GoodDocs language. -2. Execute **swaps** with broker quotes, slippage bounds, and allowances per `references/guides/swap.md`. -3. On revert, distinguish **slippage / limit** failures (broker) from **reserve liquidity** messages (see `MentoBroker.abi.yaml` error map and mento-core source). diff --git a/.agents/skills/gooddollar/references/deep-researches/on-off-ramp-service.md b/.agents/skills/gooddollar/references/deep-researches/on-off-ramp-service.md deleted file mode 100644 index b59e5c92..00000000 --- a/.agents/skills/gooddollar/references/deep-researches/on-off-ramp-service.md +++ /dev/null @@ -1,69 +0,0 @@ -# On- and off-ramp service via stable token swap - -Use this note for the service pattern where ramp providers do not list G$ directly. The practical path is: ramp in/out with a listed stable token (for example cUSD), then swap between stable and G$ on-chain. - -## Why this is required - -- Most on-/off-ramp providers list mainstream stable tokens, not G$. -- Service needs a bridge asset for fiat rails. -- Stable token becomes the integration point with ramp providers, while G$ remains the in-app asset. - -## On-chain source of truth - -- Solidity: [`contracts/utils/BuyGDClone.sol`](https://github.com/GoodDollar/GoodProtocol/blob/master/contracts/utils/BuyGDClone.sol) -- Components: - - `BuyGDCloneFactory` - - `BuyGDCloneV2` - - `DonateGDClone` -- Deployments: [releases/deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) - -## Service architecture - -The factory deploys EIP-1167 minimal clones and wires swap infrastructure (router, oracle, quoter, optional Mento broker configuration, G$ token, stable token). - -Swap execution is **dual-path**: - -- **Uniswap-style route** (router/quoter path) -- **Mento-based route** (broker/exchange-provider path) - -For each swap request, the service compares quoted outputs and selects the route with the **larger `amountOut`** (best execution for the same input amount), then enforces `minAmount` guard on the selected path. - -Each user gets a deterministic clone address from owner-based salt: - -- `predict(owner)` for buy clone -- `predictDonation(owner, donor)` for donation clone - -This is why clone-per-user design is used: - -- predictable per-user addresses for audit and routing -- isolated execution context -- cheaper deployment than full contract instances - -## Execution surface (conceptual) - -The operational surface is intentionally small and deterministic: - -- `create(owner)` -- `createAndSwap(owner, minAmount)` -- `predict(owner)` -- `createDonation`, `createDonationAndSwap`, `predictDonation` - -This keeps on-/off-ramp architecture auditable and predictable across users. - -## Risks - -- Wrong factory or wrong chain causes permanent fund loss risk. -- Stale router/oracle/mento config can fail swap or produce bad execution. -- Missing `minAmount` protection increases slippage risk. -- Quote source mismatch or stale quotes across Uniswap/Mento can pick a suboptimal route if not refreshed just before execution. - -## Boundary note - -This file explains **why** this architecture exists for ramp services and why per-user clones matter. -For step-by-step service execution flow, use `references/guides/on-off-ramp.md`. - -## Cross-reference - -- User narrative: [Buy and Sell G$](https://docs.gooddollar.org/user-guides) -- Token integration details: [How to integrate the G$ token](https://docs.gooddollar.org/for-developers/developer-guides/how-to-integrate-the-gusd-token) -- Broker ABI: `references/contracts/MentoBroker.abi.yaml` diff --git a/.agents/skills/gooddollar/references/guides/bridge.md b/.agents/skills/gooddollar/references/guides/bridge.md deleted file mode 100644 index 71fd6e8c..00000000 --- a/.agents/skills/gooddollar/references/guides/bridge.md +++ /dev/null @@ -1,187 +0,0 @@ -# Bridge guide - -Use for moving G$ across supported networks with deployment-specific bridge contracts. - -Primary local ABI reference for MessagePassingBridge flow: - -- `references/contracts/MessagePassingBridge.abi.yaml` - -## GoodDocs alignment - -- User flow and high-level behavior: [Bridge GoodDollars](https://docs.gooddollar.org/user-guides/bridge-gooddollars). -- Resolve supported bridge contract addresses per chain from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) only (for example `MpbBridge` under `production`, `production-celo`, `production-xdc`). Use [Bridge GoodDollars](https://docs.gooddollar.org/user-guides/bridge-gooddollars) for user-facing flow and troubleshooting, not for addresses. - -## Goal - -Bridge with deterministic pre-checks: bridge support, allowance, amount, cross-chain transport fee, and delivered G$ after destination **amount** limits and protocol fee. - -## Required inputs - -- source and destination chain metadata -- bridge contract address for source chain -- source G$ token address -- amount in source token decimals -- signer and rpc url - -## Execution flow - -1. Resolve source bridge and token addresses for the network pair. -2. Run bridge eligibility checks for sender and amount via `canBridge(from, amount)` on the **source** bridge (same contract you call for `bridgeToWithLz` / `bridgeToWithAxelar`). Outbound burn does not invoke `canBridge` inside `_bridgeTo`; destination mint still enforces **amount** limits (see **Bridge amount limit context**). -3. Read allowance and approve bridge spender when required. -4. Resolve transport mode (`LZ` or `AXELAR`) and estimate required native fee. -5. Send bridge transaction with nonzero `msg.value` and explicit transport method. -6. Return tx hash and normalized bridge parameters. - -## Bridge fee context - -Two different costs show up on `MessagePassingBridge`; do not conflate them. - -**1. Cross-chain transport fee (native gas token on the source chain)** -Paid as **`msg.value`** on the outbound call. The contract reverts with **`MISSING_FEE`** if `msg.value` is zero. On the **LayerZero** path the contract compares your `msg.value` to **`estimateSendFee`** and reverts **`LZ_FEE(required, sent)`** if it is too low. Use the same **normalized** amount for `estimateSendFee` as the contract uses internally (see the next section). On the **Axelar** path you still attach native value for Gas Service / execution; there is no single `estimateSendFee` analogue in the snippet—follow Envio/Axelar docs or simulate the exact call for production amounts. - -**2. Protocol fee on minted G$ (destination chain, basis points)** -When the message is executed on the **destination** chain, the bridge applies **`bridgeFees`** (min / max / fee bps via `setBridgeFees`) and mints the recipient **minus** that fee; the fee portion is minted to **`feeRecipient`** when it is non-zero (see `bridgeFees()`, `feeRecipient`, and `_takeFee` / `ExecutedTransfer` in `references/contracts/MessagePassingBridge.abi.yaml`). This is **not** the LayerZero relayer fee; it is a separate cut on the **token amount** delivered on arrival. - -**3. Optional OFT / LayerZero token-adapter path** -If the flow uses the GoodDollar OFT-style adapter instead of `MessagePassingBridge`, fee quoting follows **`quoteSend`** / **`MessagingFee`** on that contract; see `references/contracts/GoodDollarOFTAdapter.abi.yaml`. - -## Bridge amount limit context - -**Bridge limit** means **bridge amount limit**: policy on **how much G$** (token volume) may move—**`minAmount`**, per-transfer cap, per-account daily cap, and aggregate daily cap—plus **`onlyWhitelisted`**. It does **not** mean the cross-chain **native** transport fee (`msg.value`, **`LZ_FEE`**), and it does **not** mean the **destination mint fee** in **`bridgeFees`** (bps); those are covered under **Bridge fee context**. - -Amount caps and counters are **per bridge deployment**: read the **source** contract for outbound **amount** policy and usage meters tied to the burn, and the **destination** contract for **`_enforceLimits`** at inbound mint completion; do not assume identical **`bridgeLimits`** across chains. - -**1. Amount caps and usage meters** -**`bridgeLimits()`** exposes **`dailyLimit`**, **`txLimit`**, **`accountDailyLimit`**, **`minAmount`**, and **`onlyWhitelisted`**. Compare those caps to **`bridgeDailyLimit()`** (aggregate **`bridged24Hours`** and **`lastTransferReset`**) and **`accountsDailyLimit(account)`** (same fields per sender). Updates use **`setBridgeLimits`** (access per the ABI). Field-level notes and accessors live in `references/contracts/MessagePassingBridge.abi.yaml`. - -**2. Source preflight vs destination enforcement** -**`canBridge(from, amount)`** on the **source** is a view-only diagnostic for **that amount**: same policy family as amount limit checks, evaluated on the **raw** burn size (not the LayerZero fee normalization). Outbound **`_bridgeTo`** does **not** call **`canBridge`**; call it from the client if you want **`(false, reason)`** before signing instead of learning only from a revert after burn setup. When the message is executed on the **destination**, **`_enforceLimits`** is the hard gate for **amount** throttles and whitelist behavior at mint time. - -## Outbound pause, approved requests, and inbound source bridges - -These controls are separate from numeric **amount** caps; they still block or relax bridging and can surface as **`BRIDGE_LIMITS`** or inbound skips. - -**`pauseBridge`** sets **`isClosed`**; when closed, outbound flow reverts with **`BRIDGE_LIMITS('closed')`**. **`approvedRequests(requestId)`** on the destination lets **`_bridgeFrom`** skip standard **amount** limit enforcement for that completion when set. **`setDisabledBridges`** toggles **`disabledSourceBridges`** entries keyed by **`keccak256(abi.encode(sourceChainId, BridgeService))`**, controlling whether an inbound relay from that source is accepted before the rest of destination handling. See `references/contracts/MessagePassingBridge.abi.yaml`. - -## Axelar vs LayerZero on GoodDollar deployments - -LayerZero mappings are initialized for Ethereum, Celo, Fuse, and XDC in `initialize` / `upgrade` on `MessagePassingBridge`. The **Axelar** path is only usable where `toAxelarChainId(targetChainId)` returns a non-empty string; the on-chain pure function currently maps **1**, **5**, **42220**, and **44787** only. For **Fuse (122)** or **XDC (50)** targets, use **LZ** (`bridgeToWithLz`) unless governance ships a broader Axelar mapping. - -## LayerZero fee and `estimateSendFee` - -`bridgeToWithLz` burns the G$ **raw** amount in the source token’s `decimals()`. Inside the contract, LayerZero payload and `estimateSendFee` use a value normalized to **18 decimals** the same way as [GoodBridge `BridgeHelperLibrary.normalizeFromTokenTo18Decimals`](https://github.com/GoodDollar/GoodBridge/blob/master/packages/bridge-contracts/contracts/messagePassingBridge/BridgeHelperLibrary.sol): if `decimals < 18`, multiply by `10^(18 - decimals)`; if `decimals > 18`, divide by `10^(decimals - 18)`; otherwise use the raw amount. - -`canBridge(from, amount)` is evaluated on the **raw** burn amount, not the normalized value. - -Read `decimals()` from the source G$ contract when building off-chain fee quotes so you stay aligned if a deployment differs. - -## Deterministic snippet - -```js -import { ethers } from "ethers"; - -function normalizedForLzFee(raw, tokenDecimals) { - if (tokenDecimals < 18) return raw * 10n ** BigInt(18 - tokenDecimals); - if (tokenDecimals > 18) return raw / 10n ** BigInt(tokenDecimals - 18); - return raw; -} - -const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); -const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); - -const token = new ethers.Contract( - process.env.GOODDOLLAR_ADDRESS, - [ - "function decimals() view returns (uint8)", - "function allowance(address,address) view returns (uint256)", - "function approve(address,uint256) returns (bool)", - ], - signer, -); - -const bridge = new ethers.Contract( - process.env.BRIDGE_ADDRESS, - [ - "function canBridge(address,uint256) view returns (bool,string)", - "function toLzChainId(uint256) view returns (uint16)", - "function estimateSendFee(uint16,address,address,uint256,bool,bytes) view returns (uint256,uint256)", - "function bridgeToWithLz(address,uint256,uint256,bytes) payable", - "function bridgeToWithAxelar(address,uint256,uint256,address) payable", - ], - signer, -); - -const owner = await signer.getAddress(); -const targetChainId = Number(process.env.TARGET_CHAIN_ID); -const recipient = process.env.RECIPIENT; -const amount = ethers.parseUnits(process.env.AMOUNT, Number(process.env.DECIMALS)); -const transport = (process.env.BRIDGE_TRANSPORT || "LZ").toUpperCase(); -const tokenDecimals = await token.decimals(); -const normalizedForLzEstimate = normalizedForLzFee(amount, Number(tokenDecimals)); - -const [canBridge, reason] = await bridge.canBridge(owner, amount); -if (!canBridge) throw new Error(`Bridge blocked: ${reason}`); - -const allowance = await token.allowance(owner, process.env.BRIDGE_ADDRESS); -if (allowance < amount) { - const approveTx = await token.approve(process.env.BRIDGE_ADDRESS, amount); - await approveTx.wait(); -} - -let tx; - -if (transport === "LZ") { - const dstEid = await bridge.toLzChainId(targetChainId); - if (dstEid === 0) throw new Error("Unsupported target chain for LayerZero"); - - const adapterParams = process.env.LZ_ADAPTER_PARAMS || "0x"; - const [nativeFee] = await bridge.estimateSendFee( - dstEid, - owner, - recipient, - normalizedForLzEstimate, - false, - adapterParams, - ); - if (nativeFee <= 0n) throw new Error("Estimated LayerZero fee is zero"); - - tx = await bridge.bridgeToWithLz(recipient, targetChainId, amount, adapterParams, { - value: nativeFee, - }); -} else if (transport === "AXELAR") { - const nativeFee = ethers.parseEther(process.env.AXELAR_FEE_ETH || "0.01"); - tx = await bridge.bridgeToWithAxelar(recipient, targetChainId, amount, owner, { - value: nativeFee, - }); -} else { - throw new Error("Unsupported BRIDGE_TRANSPORT. Use LZ or AXELAR"); -} - -const receipt = await tx.wait(); -console.log( - JSON.stringify( - { - txHash: receipt.hash, - sourceBridge: process.env.BRIDGE_ADDRESS, - targetChainId, - transport, - recipient, - rawAmount: amount.toString(), - tokenDecimals: Number(tokenDecimals), - normalizedAmountForLz: normalizedForLzEstimate.toString(), - }, - null, - 2, - ), -); -``` - -## Failure handling - -- unsupported destination: return targetChainId, bridge address, and transport mode -- fee too low (`LZ_FEE` or underpriced Axelar fee): re-estimate and retry with user confirmation -- approval or balance issue: return required delta -- credited G$ on destination is reduced by **`bridgeFees`** (bps / min / max); that is independent of the source **`msg.value`** transport fee -- **`canBridge`** false on source: return the **`reason`** string from the view call -- **`BRIDGE_LIMITS(reason)`** custom error (see `references/contracts/MessagePassingBridge.abi.yaml` **errors**): **`reason`** labels the failing check (numeric **amount** limit, whitelist, **`closed`**, or other policy string from the implementation) -- source preflight passed but destination still reverts: re-read **`bridgeLimits`** and daily counters for **amount** caps and **`onlyWhitelisted`**; check **`isClosed`**, **`approvedRequests`**, and **`disabledSourceBridges`** per **Outbound pause, approved requests, and inbound source bridges**; message delivery can cross a reset boundary or policy change diff --git a/.agents/skills/gooddollar/references/guides/check-identity.md b/.agents/skills/gooddollar/references/guides/check-identity.md deleted file mode 100644 index adc01659..00000000 --- a/.agents/skills/gooddollar/references/guides/check-identity.md +++ /dev/null @@ -1,87 +0,0 @@ -# Check identity guide - -Use when the user asks whether an address is eligible for UBI or how identity links wallets. - -## GoodDocs alignment - -- [Connect another wallet address to identity](https://docs.gooddollar.org/user-guides/connect-another-wallet-address-to-identity): associated addresses resolve to a verified root in the Identity contract; `connectAccount` links wallets. -- One claim per day applies across all connected addresses for the same verified identity (see the hint on that page). - -## Goal - -Determine whitelist or authentication status with deterministic on-chain reads. - -## Metric semantics - -- **Passed whitelisting (historical):** use `lastAuthenticated(account) > 0`. -- **Still whitelisted (current):** use `getWhitelistedRoot(account) != 0x0` or `isWhitelisted(account) == true`. -- `getWhitelistedRoot(account) != 0x0` is a current-state signal, not an "ever passed" signal. - -## Required inputs - -- `nameServiceAddress` or explicit Identity address -- `account` to check -- `rpcUrl` and chain configuration - -## Execution flow - -1. Resolve `IDENTITY` from NameService when used on the deployment. -2. Read `getWhitelistedRoot(account)` or equivalent for the deployed Identity version. -3. Treat non-zero root as tied to a whitelisted identity tree when that is the protocol rule for the deployment. -4. Read `lastAuthenticated(account)` for historical pass status and `getWhitelistedRoot(account)` or `isWhitelisted(account)` for current status. - -## Deterministic snippet - -```js -import { ethers } from "ethers"; - -const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); - -const nameService = new ethers.Contract( - process.env.NAMESERVICE_ADDRESS, - ["function getAddress(string) view returns (address)"], - provider, -); - -const identityAddress = await nameService.getAddress("IDENTITY"); -const identity = new ethers.Contract( - identityAddress, - [ - "function getWhitelistedRoot(address) view returns (address)", - "function isWhitelisted(address) view returns (bool)", - "function lastAuthenticated(address) view returns (uint256)", - ], - provider, -); - -const account = process.env.ACCOUNT; -const root = await identity.getWhitelistedRoot(account); -const isWhitelisted = await identity.isWhitelisted(account); -const lastAuthenticated = await identity.lastAuthenticated(account); - -console.log( - JSON.stringify( - { - account, - identityAddress, - whitelistedRoot: root, - isWhitelisted, - lastAuthenticated: lastAuthenticated.toString(), - }, - null, - 2, - ), -); -``` - -## Return shape - -- `isWhitelisted` or equivalent boolean summary -- `whitelistedRoot` or equivalent -- `lastAuthenticated` for historical-pass classification -- optional metadata fields when available - -## Failure handling - -- NameService cannot resolve `IDENTITY`: stop and fix inputs using [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) (`Identity` / `NameService` for the target environment)—not GoodDocs tables. -- Read failures: return the failing call and next step. diff --git a/.agents/skills/gooddollar/references/guides/claim.md b/.agents/skills/gooddollar/references/guides/claim.md deleted file mode 100644 index bd51abd0..00000000 --- a/.agents/skills/gooddollar/references/guides/claim.md +++ /dev/null @@ -1,83 +0,0 @@ -# Claim guide - -Use when the user wants to claim daily UBI. Protocol context: [How GoodDollar works](https://docs.gooddollar.org/how-gooddollar-works) and [UBIScheme (GoodDocs behavior)](https://docs.gooddollar.org/for-developers/core-contracts/ubischeme)—contract addresses only from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json). - -## Goal - -Execute a safe `claim()` with identity pre-checks and clear outputs. - -## GoodDocs alignment - -- UBI is distributed daily to verified users; the active pool is split among claimers in each period (see [How GoodDollar works](https://docs.gooddollar.org/how-gooddollar-works)). -- UBIScheme deployments vary by chain (Fuse, Celo, XDC); resolve live contract addresses only from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) (`UBIScheme` under `production`, `production-celo`, or `production-xdc`). Use [Core contracts / UBIScheme](https://docs.gooddollar.org/for-developers/core-contracts/ubischeme) for documented behavior, not for addresses. - -## Required inputs - -- `nameServiceAddress` or explicit UBIScheme and Identity addresses from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) -- `rpcUrl` and chain configuration -- signer context - -## Execution flow - -1. Resolve `IDENTITY` and `UBISCHEME` from NameService when NameService is the source of truth for the deployment. -2. Confirm whitelist status for the claiming account. -3. Optionally read entitlement or claimable state before sending `claim()`. -4. Call `claim()` on the resolved UBIScheme (contract generation may differ by deployment; align ABI with your target). -5. Return tx hash and claimed amount when derivable from events or balance delta. - -## Deterministic snippet - -```js -import { ethers } from "ethers"; - -const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); -const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); - -const nameService = new ethers.Contract( - process.env.NAMESERVICE_ADDRESS, - ["function getAddress(string) view returns (address)"], - provider, -); - -const identityAddress = await nameService.getAddress("IDENTITY"); -const ubiAddress = await nameService.getAddress("UBISCHEME"); - -const identity = new ethers.Contract( - identityAddress, - [ - "function isWhitelisted(address) view returns (bool)", - "function getWhitelistedRoot(address) view returns (address)", - ], - provider, -); - -const account = await signer.getAddress(); -const isWhitelisted = await identity.isWhitelisted(account); -if (!isWhitelisted) throw new Error("Account is not whitelisted"); - -const root = await identity.getWhitelistedRoot(account); -if (root === ethers.ZeroAddress) throw new Error("No whitelisted root"); - -const ubi = new ethers.Contract( - ubiAddress, - ["function claim()", "event UBICalculated(address,uint256,uint256,uint256)"], - signer, -); - -const tx = await ubi.claim(); -const receipt = await tx.wait(); -console.log(JSON.stringify({ txHash: receipt.hash, account, root }, null, 2)); -``` - -## Pre-check failures - -- Not whitelisted: stop and point the user to identity verification flows in GoodDocs. -- Missing contract address: stop; use [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) only. -- Zero entitlement: communicate that nothing is claimable in the current period without guessing amounts. - -## Output contract - -- network -- resolved contract addresses -- tx hash -- claim outcome details when available diff --git a/.agents/skills/gooddollar/references/guides/faucet.md b/.agents/skills/gooddollar/references/guides/faucet.md deleted file mode 100644 index d224e85e..00000000 --- a/.agents/skills/gooddollar/references/guides/faucet.md +++ /dev/null @@ -1,76 +0,0 @@ -# Faucet top-up guide - -Use when the user needs native gas top-up via GoodProtocol Faucet. - -## Goal - -Run deterministic pre-checks and call `topWallet` only when eligibility and limits pass. - -## Required inputs - -- `rpcUrl`, chain configuration, signer -- Faucet address for the chain (from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) `Faucet` under the matching environment, or `NameService.getAddress` when the deployment documents the key) -- target user address - -## Execution flow - -1. Resolve Faucet contract address for the active chain from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json). -2. Run `canTop(user)` as preflight. -3. Read `getToppingAmount(user)` and communicate expected top-up. -4. If eligible, call `topWallet(user)`. -5. Return tx hash and resulting top-up context. - -## Deterministic snippet - -```js -import { ethers } from "ethers"; - -const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); -const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); - -const faucet = new ethers.Contract( - process.env.FAUCET_ADDRESS, - [ - "function canTop(address) view returns (bool)", - "function getToppingAmount(address) view returns (uint256)", - "function topWallet(address payable)", - ], - signer, -); - -const user = process.env.USER; -const canTop = await faucet.canTop(user); -if (!canTop) throw new Error("Faucet canTop returned false"); - -const amount = await faucet.getToppingAmount(user); -const tx = await faucet.topWallet(user); -const receipt = await tx.wait(); - -console.log( - JSON.stringify( - { - txHash: receipt.hash, - user, - toppingAmount: amount.toString(), - }, - null, - 2, - ), -); -``` - -## Common rejection reasons - -- `not authorized` -- daily or weekly cap reached -- banned address -- low effective `toTop` vs minimum threshold -- faucet inactive or wrong chain/address - -## Output contract - -- network -- faucet address -- `canTop` preflight result -- top-up amount estimate -- tx hash (when executed) diff --git a/.agents/skills/gooddollar/references/guides/gooddocs.md b/.agents/skills/gooddollar/references/guides/gooddocs.md deleted file mode 100644 index 5e5b4b39..00000000 --- a/.agents/skills/gooddollar/references/guides/gooddocs.md +++ /dev/null @@ -1,37 +0,0 @@ -# GoodDocs hub - -Canonical protocol documentation lives at [GoodDocs](https://docs.gooddollar.org/). - -This is a **routing guide** (quick link map), not a deep protocol analysis note. - -## Start here - -- [Welcome](https://docs.gooddollar.org/) -- [How GoodDollar works](https://docs.gooddollar.org/how-gooddollar-works) - -## User guides - -- [Buy and Sell G$](https://docs.gooddollar.org/user-guides) (reserve-backed buy/sell; includes historical Ethereum/Kovan explorer workflows in the doc) -- [Bridge GoodDollars](https://docs.gooddollar.org/user-guides/bridge-gooddollars) (MessagePassingBridge, fees, limits, troubleshooting) -- [Connect another wallet address to identity](https://docs.gooddollar.org/user-guides/connect-another-wallet-address-to-identity) - -## Developers - -- [Core contracts](https://docs.gooddollar.org/for-developers/core-contracts) (module overview; **do not** read contract addresses from GoodDocs—use [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) only) -- [Developer guides index](https://docs.gooddollar.org/for-developers/developer-guides) -- [Integrate the G$ token](https://docs.gooddollar.org/for-developers/developer-guides/how-to-integrate-the-gusd-token) (ERC-677, ERC-777, decimals by chain, `transferAndCall`, fees) -- [Use G$ streaming](https://docs.gooddollar.org/for-developers/developer-guides/use-gusd-streaming) (Superfluid on Celo, CFAv1Forwarder) - -## Chain IDs (bridge doc) - -| Network | Chain ID | -| -------- | -------- | -| Ethereum | 1 | -| Fuse | 122 | -| Celo | 42220 | -| XDC | 50 | - -## This repo - -- Action playbooks: `references/guides/*.md`. -- Rich ABIs: `references/contracts/*.abi.yaml`. diff --git a/.agents/skills/gooddollar/references/guides/goodsdks.md b/.agents/skills/gooddollar/references/guides/goodsdks.md deleted file mode 100644 index 8092e407..00000000 --- a/.agents/skills/gooddollar/references/guides/goodsdks.md +++ /dev/null @@ -1,110 +0,0 @@ -# GoodSDKs integration guide - -Use this guide when the task is SDK-first (app integration), not raw contract-first. - -## Scope - -GoodSDKs is the app integration layer for GoodDollar: - -- `@goodsdks/citizen-sdk` for identity and claim flows. -- `@goodsdks/react-hooks` for Wagmi React hooks. -- `@goodsdks/good-reserve` for reserve buy or sell flows. -- `@goodsdks/engagement-sdk` for engagement rewards flows. -- `@goodsdks/ui-components` and `@goodsdks/savings-widget` for web components. - -## Routing map - -- Check whitelist, identity root, FV link -> `@goodsdks/citizen-sdk` (`IdentitySDK`) -- Claim UBI with entitlement checks and fallback chains -> `@goodsdks/citizen-sdk` (`ClaimSDK`) -- React app with Wagmi and minimal glue code -> `@goodsdks/react-hooks` -- Buy or sell via reserve rails (Celo or XDC support rules) -> `@goodsdks/good-reserve` -- Reward app registration, claims, reward history -> `@goodsdks/engagement-sdk` -- Embeddable UI in non-React or mixed stacks -> `@goodsdks/ui-components` or `@goodsdks/savings-widget` - -## Deterministic setup - -Monorepo prerequisites: - -```bash -cd ~/Projects/GoodSDKs -corepack enable -yarn install --immutable -yarn build -``` - -Target one workspace: - -```bash -yarn workspace @goodsdks/citizen-sdk build -yarn workspace @goodsdks/react-hooks build -``` - -## Deterministic usage snippets - -Identity SDK: - -```ts -import { createPublicClient, createWalletClient, custom, http } from "viem"; -import { IdentitySDK } from "@goodsdks/citizen-sdk"; - -const publicClient = createPublicClient({ transport: http("https://forno.celo.org") }); -const walletClient = createWalletClient({ transport: custom(window.ethereum) }); - -const identitySDK = await IdentitySDK.init({ - publicClient, - walletClient, - env: "production", -}); - -const { isWhitelisted, root } = await identitySDK.getWhitelistedRoot("0xYourAccount"); -console.log({ isWhitelisted, root }); -``` - -Claim SDK: - -```ts -import { ClaimSDK, IdentitySDK } from "@goodsdks/citizen-sdk"; - -const identitySDK = await IdentitySDK.init({ publicClient, walletClient, env: "production" }); -const claimSDK = await ClaimSDK.init({ - publicClient, - walletClient, - identitySDK, - env: "production", -}); - -const entitlement = await claimSDK.checkEntitlement(); -if (entitlement.amount > 0n) { - const receipt = await claimSDK.claim(); - console.log(receipt.transactionHash); -} -``` - -React hooks: - -```tsx -import { useIdentitySDK, useClaimSDK, useGoodReserve } from "@goodsdks/react-hooks"; - -const identity = useIdentitySDK("production"); -const claim = useClaimSDK("production"); -const reserve = useGoodReserve("production"); -``` - -Reserve SDK: - -```ts -import { GoodReserveSDK } from "@goodsdks/good-reserve"; - -const sdk = new GoodReserveSDK(publicClient, walletClient, "production"); -const quote = await sdk.getBuyQuote(CUSD_ADDRESS, amountIn); -const tx = await sdk.buy(CUSD_ADDRESS, amountIn, (quote * 95n) / 100n); -console.log(tx.hash); -``` - -## Agent rules - -1. Prefer SDK methods first for app tasks. -2. Use contract-level guides only when SDK does not expose required behavior. -3. Do not invent SDK method names; align with package READMEs and exported types. -4. For chain support errors, report chain and env explicitly (do not silently fallback). -5. For UI tasks, prefer hooks or components over bespoke wallet and viem plumbing. diff --git a/.agents/skills/gooddollar/references/guides/hypersync-hyperrpc.md b/.agents/skills/gooddollar/references/guides/hypersync-hyperrpc.md deleted file mode 100644 index e7b6f286..00000000 --- a/.agents/skills/gooddollar/references/guides/hypersync-hyperrpc.md +++ /dev/null @@ -1,110 +0,0 @@ -# Envio HyperSync and HyperRPC - -Use this guide when the task is high-volume historical blockchain data fetch (events, blocks, txs), especially analytics and indexing workflows. - -## Official docs - -- HyperSync overview: [docs.envio.dev/docs/HyperSync/overview](https://docs.envio.dev/docs/HyperSync/overview) -- HyperRPC overview: [docs.envio.dev/docs/HyperRPC/overview-hyperrpc](https://docs.envio.dev/docs/HyperRPC/overview-hyperrpc) -- HyperRPC supported networks: [docs.envio.dev/docs/HyperRPC/hyperrpc-supported-networks](https://docs.envio.dev/docs/HyperRPC/hyperrpc-supported-networks) - -## What to use - -- **HyperSync**: preferred for new data pipelines and heavy historical scans. -- **HyperRPC**: read-only JSON-RPC drop-in for existing RPC code paths. - -## HyperRPC vs HyperSync (avoid mixing them up) - -- **HyperRPC** is a **hosted JSON-RPC URL** (same methods as `eth_getLogs`, `eth_blockNumber`, and so on). Any HTTP client or existing RPC stack can call it; put the API token in the URL path as Envio documents. -- **HyperSync** is a **separate high-throughput query API** used through **Envio client libraries** (for example `@envio-dev/hypersync-client` in Node). It is **not** “just another RPC endpoint” with the same ergonomics as a one-line `fetch` to `eth_getLogs` at large scale. - -## Decision rule - -1. For GoodDollar protocol history that exists on subgraphs, query the subgraph first and validate fields in `references/subgraphs/*-guide.md`. -2. If subgraph schema or freshness cannot satisfy the request, use **HyperSync** for large scans and pipelines, or **HyperRPC** when you must stay inside standard JSON-RPC. -3. For write operations (sending tx), use normal RPC providers; HyperRPC is read-only. - -## GoodDollar-relevant network coverage - -- Celo and XDC are supported on HyperRPC. -- Fuse is not currently listed; treat this as non-blocking and use existing providers for Fuse. - -## Access and auth - -- HyperRPC/HyperSync usage is account-based. -- HyperRPC requires an API key for reliable production use. -- Requests without API token are rate-limited and should be treated as non-production fallback only. -- Add API key in endpoint URL as documented by Envio. -- HyperRPC token pattern example from docs: `https://.rpc.hypersync.xyz/` - -## Agents: Envio API token when HyperSync is the best option - -After you decide **HyperSync** is the right tool for the user query (for example large historical scans or pipeline-scale log pulls where subgraphs are insufficient), check for a usable Envio credential in the execution environment (`ENVIO_API_TOKEN` for `@envio-dev/hypersync-client`, or the token Envio documents for your chosen URL pattern). - -If **no** Envio API token is available and you cannot complete the HyperSync path without it, **stop and explicitly ask the user** to provide an Envio API token (name the env var you need, typically `ENVIO_API_TOKEN`). Do not silently rely on anonymous or heavily rate-limited access as a substitute when HyperSync was already identified as the best approach. - -## Practical use in this repo - -- Keep subgraphs as first option for indexed protocol entities. -- Use HyperSync/HyperRPC when subgraph coverage is missing, stale, or insufficient for bulk historical pulls. -- When an agent chooses **HyperSync** as the best path and no Envio API token is available, follow **Agents: Envio API token when HyperSync is the best option** in this file and ask the user for `ENVIO_API_TOKEN` before proceeding. -- Keep contract addresses from [GoodProtocol/deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) only; use GoodDocs for product context, not for resolving contract addresses. -- For implementation details (client setup, query structure, supported methods), follow the Envio docs links above directly. - -## From block for historical fetches - -For **`eth_getLogs`**, HyperRPC, and HyperSync range queries, the lower bound is **`fromBlock`** (or the client’s equivalent). Prefer the deployment’s **`creationBlock`** from the matching row in `references/contracts/*.abi.yaml` (or **`meta.deploymentCreationBlocks`** where deployments are plain address strings) so scans do not start at genesis when you only need post-deploy history. Field placement for **`creationBlock`** is defined in `references/contracts/_rich-abi-yaml-format.md`. If you cannot determine the creation block, **`fromBlock` 0** is valid. - -## Prebuilt scripts (developers and local agents) - -These scripts avoid rediscovering HyperRPC wiring on every task. They require **Node.js 18 or newer** (global `fetch`). Paths like `scripts/...` are relative to the **GoodSkills repository root** (the directory that contains both `skills/` and `scripts/`), not relative to `skills/gooddollar/` alone. - -### Last N Identity `WhitelistedAdded` logs via HyperRPC - -- Script: `scripts/fetch-whitelist-events-hyperrpc.mjs` -- Default `EVENT_TOPIC0` matches `WhitelistedAdded(address)` on `IdentityV4`; override `EVENT_TOPIC0` for other events. -- Production Celo defaults: `CONTRACT_ADDRESS` defaults to `Identity` from `production-celo` in [GoodProtocol deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) (`0xC361A6E67822a0EDc17D899227dd9FC50BD62F42`). If `HYPERRPC_URL` is unset, the script builds `https://celo.rpc.hypersync.xyz/` from `HYPERRPC_API_TOKEN` or `ENVIO_API_TOKEN`. -- Optional env: `HYPERRPC_URL` (overrides token-based default), `CONTRACT_ADDRESS`, `LIMIT` (default `500`), `STEP` (default `2000`), `FROM_BLOCK` (decimal; omit to read **`creationBlock`** for `CONTRACT_ADDRESS` from `ABI_PATH` or the default `skills/gooddollar/references/contracts/IdentityV4.abi.yaml`), `ABI_PATH`, `TO_BLOCK` (default `latest`). **`fromBlock`** behavior is described in **From block for historical fetches** above. - -```bash -cd /path/to/GoodSkills -export HYPERRPC_API_TOKEN='' -node scripts/fetch-whitelist-events-hyperrpc.mjs -``` - -Web-only assistants without a shell cannot run the file; they should return the same env keys and command text so the user runs it locally. - -## HyperSync client minimal path (install required) - -HyperSync uses the official client. Install and query pattern (Celo example URLs from [Envio Celo docs](https://docs.envio.dev/docs/HyperIndex/celo)): - -```bash -npm install @envio-dev/hypersync-client -export ENVIO_API_TOKEN='' -``` - -Save as a `.mjs` file (or use `"type": "module"` in a local `package.json`) and run with `node`: - -```javascript -import { HypersyncClient, presetQueryLogsOfEvent } from "@envio-dev/hypersync-client"; - -const client = new HypersyncClient({ - url: "https://celo.hypersync.xyz", - apiToken: process.env.ENVIO_API_TOKEN, -}); - -const identity = "0x..."; -const whitelistedAddedTopic0 = - "0xee1504a83b6d4a361f4c1dc78ab59bfa30d6a3b6612c403e86bb01ef2984295f"; - -const fromBlock = 17237952; -const toBlock = await client.getHeight(); - -const query = presetQueryLogsOfEvent(identity, whitelistedAddedTopic0, fromBlock, toBlock); -const res = await client.get(query); -console.log(res.data.logs.length); -``` - -The example **`fromBlock`** matches **`creationBlock`** for production Celo Identity in `skills/gooddollar/references/contracts/IdentityV4.abi.yaml`; see **From block for historical fetches** above. - -Full API and streaming patterns: [HyperSync clients](https://docs.envio.dev/docs/HyperSync/hypersync-clients) and the package README for `@envio-dev/hypersync-client`. diff --git a/.agents/skills/gooddollar/references/guides/invite-bounties.md b/.agents/skills/gooddollar/references/guides/invite-bounties.md deleted file mode 100644 index d60aafdd..00000000 --- a/.agents/skills/gooddollar/references/guides/invite-bounties.md +++ /dev/null @@ -1,115 +0,0 @@ -# Invite bounties guide - -Use when the task is to verify or execute inviter-invitee bounty payout flow and explain why payout is blocked. - -## Goal - -Check eligibility deterministically, execute payout only when eligible, and return exact failure reason when not eligible. - -## Required inputs - -- target chain -- `InvitesV2` address -- `Identity` address -- optional `UBISchemeV2` address when claims threshold is active -- invitee address -- inviter address -- rpc url and signer - -## Execution flow - -1. Resolve contract addresses from `deployment.json`. -2. Read invitee state from `users(invitee)` and global thresholds (`minimumClaims`, `minimumDays`, `active`). -3. Check current eligibility with `canCollectBountyFor(invitee)`. -4. If not eligible, read identity whitelist for invitee and inviter and return concrete blocker. -5. If eligible, execute `bountyFor(invitee)` or `collectBounties()` and return tx hash plus payout values from events. - -## Deterministic snippet - -```js -import { ethers } from "ethers"; - -const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); -const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); - -const invites = new ethers.Contract( - process.env.INVITES_ADDRESS, - [ - "function canCollectBountyFor(address) view returns (bool)", - "function bountyFor(address)", - "function users(address) view returns (bytes32,address,uint40,uint24,bool,uint256)", - "function minimumClaims() view returns (uint256)", - "function minimumDays() view returns (uint256)", - "function active() view returns (bool)", - "event InviterBounty(address indexed inviter,address indexed invitee,uint256 bountyPaid,uint256 inviterLevel,bool earnedLevel)" - ], - signer, -); - -const identity = new ethers.Contract( - process.env.IDENTITY_ADDRESS, - [ - "function isWhitelisted(address) view returns (bool)" - ], - provider, -); - -const invitee = process.env.INVITEE_ADDRESS; -const inviter = process.env.INVITER_ADDRESS; - -const [isActive, eligible, inviteeWhitelisted, inviterWhitelisted, minClaims, minDays] = await Promise.all([ - invites.active(), - invites.canCollectBountyFor(invitee), - identity.isWhitelisted(invitee), - identity.isWhitelisted(inviter), - invites.minimumClaims(), - invites.minimumDays(), -]); - -if (!isActive) throw new Error("Invites contract is inactive"); -if (!eligible) { - throw new Error( - `Not eligible. inviteeWhitelisted=${inviteeWhitelisted} inviterWhitelisted=${inviterWhitelisted} minimumClaims=${minClaims} minimumDays=${minDays}`, - ); -} - -const tx = await invites.bountyFor(invitee); -const receipt = await tx.wait(); -const bountyEvent = receipt.logs - .map((log) => { - try { - return invites.interface.parseLog(log); - } catch { - return null; - } - }) - .find((e) => e && e.name === "InviterBounty"); - -console.log( - JSON.stringify( - { - txHash: receipt.hash, - invitee, - inviter, - bountyPaid: bountyEvent?.args?.bountyPaid?.toString() ?? null, - }, - null, - 2, - ), -); -``` - -## Failure handling - -- invitee or inviter is not currently whitelisted -- reverification is due and whitelist check fails until re-authentication -- minimum claims or minimum days is not met -- bounty already paid or bounty-at-join is zero -- contract inactive or wrong deployment addresses - -## Output contract - -- network and addresses used -- eligibility status and blockers -- tx hash when sent -- payout values when available from logs diff --git a/.agents/skills/gooddollar/references/guides/migrate-fuse-staking-to-celo-savings.md b/.agents/skills/gooddollar/references/guides/migrate-fuse-staking-to-celo-savings.md deleted file mode 100644 index 4ad9fd26..00000000 --- a/.agents/skills/gooddollar/references/guides/migrate-fuse-staking-to-celo-savings.md +++ /dev/null @@ -1,116 +0,0 @@ -# Fuse to CELO staking migration guide - -Use when the user wants to migrate an existing Fuse governance stake into a CELO destination savings flow. In this flow, Fuse `GovernanceStakingV2` is the old staking contract (source) and **`GooddollarSavingsStream`** on Celo is the destination savings contract ([`GooddollarSavingsStream.sol`](https://github.com/Ubeswap/gooddollar-contracts/blob/main/contracts/GooddollarSavingsStream.sol), [CeloScan](https://celoscan.io/address/0x059ee811414230d1Fb157878D2b491240F4D8d3B)). - -## Goal - -Close a user stake on Fuse, bridge the resulting G$ to CELO, and stake on CELO for that user in a controlled backend flow. - -## Required inputs - -- user address on Fuse and corresponding destination address on CELO -- Fuse `GovernanceStakingV2` address (`production.GovernanceStakingV2` in [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json)) -- Fuse G$ token address and bridge contract address (from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json): `GoodDollar`, `MpbBridge` under `production`) -- CELO G$ token address (from `production-celo` in the same file) and destination savings contract address -- backend signer or service wallet with required execution permissions -- chain RPC URLs for Fuse and CELO - -## Address resolution quick table - -| Purpose | Network | Source key/path | Value | -|---|---|---|---| -| Governance staking (source close) | Fuse (`production`, `networkId: 122`) | `deployment.json` -> `production.GovernanceStakingV2` | `0xB7C3e738224625289C573c54d402E9Be46205546` | -| Governance staking (previous) | Fuse (`production`, `networkId: 122`) | `deployment.json` -> `production.GovernanceStaking` | `0xFAF457Fb4A978Be059506F6CD41f9B30fCa753b0` | -| Fuse G$ token | Fuse (`production`, `networkId: 122`) | `deployment.json` -> `production.GoodDollar` | `0x495d133B938596C9984d462F007B676bDc57eCEC` | -| Fuse bridge | Fuse (`production`, `networkId: 122`) | `deployment.json` -> `production.MpbBridge` | `0xa3247276DbCC76Dd7705273f766eB3E8a5ecF4a5` | -| Destination savings | CELO (`networkId: 42220`) | [CeloScan](https://celoscan.io/address/0x059ee811414230d1Fb157878D2b491240F4D8d3B) / `GooddollarSavingsStream` | `0x059ee811414230d1Fb157878D2b491240F4D8d3B` (`process.env.CELO_SAVINGS`) | - -Canonical sources: - -- [GoodProtocol deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) -- [Fuse explorer contract (GovernanceStakingV2 address)](https://explorer.fuse.io/address/0xB7C3e738224625289C573c54d402E9Be46205546?tab=contract) -- [Ubeswap gooddollar-contracts](https://github.com/Ubeswap/gooddollar-contracts) -- [GoodBridge bridge helper normalization](https://github.com/GoodDollar/GoodBridge/blob/master/packages/bridge-contracts/contracts/messagePassingBridge/BridgeHelperLibrary.sol) - -## Token decimals and `MessagePassingBridge` LZ fees - -Production deployments checked on-chain: Fuse `GoodDollar` uses **2** decimals and Celo `GoodDollar` uses **18**. Fuse `GovernanceStakingV2` (sG$) uses **2** decimals. Resolve `decimals()` from each live token in your runner so you stay correct if deployments change. - -On the Fuse `MpbBridge` (`MessagePassingBridge`), `canBridge(from, amount)` and `bridgeToWithLz(..., amount, ...)` use the **raw G$ burn amount** in source token decimals. Off-chain **`estimateSendFee`’s `_normalizedAmount` argument** must match what the contract builds internally: **`normalizeFromTokenTo18Decimals(amount, IERC20(nativeToken()).decimals())`** ([`BridgeHelperLibrary`](https://github.com/GoodDollar/GoodBridge/blob/master/packages/bridge-contracts/contracts/messagePassingBridge/BridgeHelperLibrary.sol)). Format displayed amounts per chain (`formatUnits`/UI) using each chain’s G$ decimals. - -## Execution flow - -1. Confirm allowance for whichever asset your flow spends first (for example sG$ allowance to the backend on Fuse `GovernanceStakingV2`, or Fuse G$ allowance if you pull G$ directly), before any transfers or bridges. -2. Verify current stake state on Fuse before closing: - - stake token balance - - withdrawable stake amount - - pending rewards if any -3. Execute Fuse unstake or close flow on governance staking (`withdrawStake` or equivalent full-close path). -4. Compute net G$ available for migration after unstake completion and any reward claim behavior. -5. Bridge G$ from Fuse to CELO using the configured bridge path and track the transfer id or tx hash pair. -6. Wait for destination finalization on CELO and verify credited G$ balance at the backend execution wallet. -7. Approve destination savings contract to spend migrated G$ amount. -8. Stake for the user on CELO with `stakeFor(amount, recipient)` on `GooddollarSavingsStream` (G$ native Super Token; approve the savings contract, not only ERC20 GoodDollar from `deployment.json` if your wallet holds the Super Token). -9. Return a migration result with both chain tx hashes and final CELO staked amount. - -## Deterministic snippet - -```js -import { ethers } from "ethers"; - -const fuse = new ethers.JsonRpcProvider(process.env.FUSE_RPC_URL); -const celo = new ethers.JsonRpcProvider(process.env.CELO_RPC_URL); -const signerFuse = new ethers.Wallet(process.env.BACKEND_PK, fuse); -const signerCelo = new ethers.Wallet(process.env.BACKEND_PK, celo); - -const user = process.env.USER_ADDRESS; -const migrateAmount = BigInt(process.env.MIGRATE_AMOUNT); - -const celoGd = new ethers.Contract( - process.env.CELO_GD_TOKEN, - [ - "function approve(address spender,uint256 amount) returns (bool)", - "function balanceOf(address) view returns (uint256)", - ], - signerCelo, -); - -const savings = new ethers.Contract( - process.env.CELO_SAVINGS, - ["function stakeFor(uint256 amount,address recipient)"], - signerCelo, -); - -const approveTx = await celoGd.approve(process.env.CELO_SAVINGS, migrateAmount); -await approveTx.wait(); - -const stakeTx = await savings.stakeFor(migrateAmount, user); -const receipt = await stakeTx.wait(); - -console.log( - JSON.stringify( - { - user, - celoStakeTx: receipt.hash, - migratedAmount: migrateAmount.toString(), - }, - null, - 2, - ), -); -``` - -## Pre-check failures - -- User allowance missing on Fuse: stop and request allowance tx from user. -- Stake close fails on Fuse: stop and return exact revert reason before bridge. -- Bridge transfer not finalized on CELO: do not call `stakeFor` until destination balance is confirmed. -- CELO savings approval missing or too low: re-approve exact amount before staking. - -## Output contract - -- user address -- Fuse unstake tx hash -- bridge tx hash or transfer identifier -- CELO stake tx hash -- final staked amount on CELO diff --git a/.agents/skills/gooddollar/references/guides/on-off-ramp.md b/.agents/skills/gooddollar/references/guides/on-off-ramp.md deleted file mode 100644 index d2127051..00000000 --- a/.agents/skills/gooddollar/references/guides/on-off-ramp.md +++ /dev/null @@ -1,77 +0,0 @@ -# On- and off-ramp service guide - -Use when implementing service flow where fiat ramps support a listed stable token (for example cUSD), and the app needs stable <-> G$ swap on-chain. - -## Goal - -Operate deterministic clone-based swap routing with explicit chain/factory verification and slippage guard. - -## Required inputs - -- target chain and factory address -- owner address used for clone derivation -- stable token and G$ token addresses -- direction: on-ramp or off-ramp -- `minAmount` guard -- signer and rpc url - -## Execution flow - -1. Resolve factory for target chain from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) (`BuyGDFactory` / `BuyGDFactoryV2` under `production-celo`, or the key your deployment uses). -2. Compute expected clone via `predict(owner)`. -3. If clone not yet deployed for flow, call `create(owner)` or `createAndSwap(owner, minAmount)`. -4. Execute stable -> G$ (on-ramp) or G$ -> stable (off-ramp) through clone path. -5. Return chain id, factory, predicted clone, effective clone, tx hashes. - -## Deterministic snippet - -```js -import { ethers } from "ethers"; - -const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); -const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); - -const factory = new ethers.Contract( - process.env.BUY_GD_CLONE_FACTORY, - [ - "function predict(address) view returns (address)", - "function create(address) returns (address)", - "function createAndSwap(address,uint256) returns (address)", - ], - signer, -); - -const owner = process.env.OWNER; -const minAmount = ethers.parseUnits(process.env.MIN_AMOUNT, Number(process.env.DECIMALS_OUT)); -const predicted = await factory.predict(owner); - -const tx = await factory.createAndSwap(owner, minAmount); -const receipt = await tx.wait(); - -console.log( - JSON.stringify( - { - txHash: receipt.hash, - owner, - predictedClone: predicted, - chainId: (await provider.getNetwork()).chainId.toString(), - }, - null, - 2, - ), -); -``` - -## Failure handling - -- predicted clone mismatch with trusted expectation -- wrong chain or wrong factory address -- swap output below `minAmount` -- stale router/oracle or exchange configuration - -## Output contract - -- network and chain id -- factory address -- predicted and actual clone addresses -- tx hashes diff --git a/.agents/skills/gooddollar/references/guides/save.md b/.agents/skills/gooddollar/references/guides/save.md deleted file mode 100644 index d0e716cb..00000000 --- a/.agents/skills/gooddollar/references/guides/save.md +++ /dev/null @@ -1,93 +0,0 @@ -# Save and stake guide - -Use when the user wants to stake G$, withdraw rewards, or exit stake. Staking economics sit alongside other protocol allocations described in [How GoodDollar works](https://docs.gooddollar.org/how-gooddollar-works). - -## GoodDocs alignment - -- Token integration and fee awareness: [How to integrate the G$ token](https://docs.gooddollar.org/for-developers/developer-guides/how-to-integrate-the-gusd-token) (`_processFees`, decimals per chain). -- Contract addresses: [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) only (for example staking and G$ token keys under `production` / `production-celo`). GoodDocs covers behavior and decimals patterns, not canonical deployment addresses. - -## Goal - -Run staking actions with balance and allowance safety checks. - -## Required inputs - -- `nameServiceAddress` or explicit staking and token addresses -- `amount` or `shares` depending on the action -- `rpcUrl`, chain configuration, signer - -## Execution flow - -1. Resolve staking and G$ token addresses from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) or, when the deployment documents the key, from `NameService.getAddress` on chain. -2. Read token balance and allowance. -3. Approve the staking contract when `stake` uses `transferFrom`. -4. Execute `stake`, `withdrawRewards`, or `withdrawStake` as requested. -5. Return tx hash and key resulting balances or events. - -## Deterministic snippets - -```js -import { ethers } from "ethers"; - -const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); -const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); - -const token = new ethers.Contract( - process.env.GOODDOLLAR_ADDRESS, - [ - "function balanceOf(address) view returns (uint256)", - "function allowance(address,address) view returns (uint256)", - "function approve(address,uint256) returns (bool)", - ], - signer, -); - -const staking = new ethers.Contract( - process.env.STAKING_ADDRESS, - [ - "function stake(uint256)", - "function withdrawRewards()", - "function withdrawStake(uint256)", - ], - signer, -); -``` - -Stake: - -```js -const amount = ethers.parseUnits(process.env.AMOUNT, Number(process.env.DECIMALS)); -const owner = await signer.getAddress(); -const allowance = await token.allowance(owner, process.env.STAKING_ADDRESS); -if (allowance < amount) { - const approveTx = await token.approve(process.env.STAKING_ADDRESS, amount); - await approveTx.wait(); -} -const tx = await staking.stake(amount); -const receipt = await tx.wait(); -console.log(JSON.stringify({ txHash: receipt.hash, action: "stake" }, null, 2)); -``` - -Withdraw rewards: - -```js -const tx = await staking.withdrawRewards(); -const receipt = await tx.wait(); -console.log(JSON.stringify({ txHash: receipt.hash, action: "withdrawRewards" }, null, 2)); -``` - -Withdraw stake: - -```js -const shares = ethers.parseUnits(process.env.SHARES, Number(process.env.DECIMALS)); -const tx = await staking.withdrawStake(shares); -const receipt = await tx.wait(); -console.log(JSON.stringify({ txHash: receipt.hash, action: "withdrawStake" }, null, 2)); -``` - -## Failure handling - -- Insufficient balance: report shortfall. -- Approval issues: report token, spender, and required allowance. -- Reverts: return attempted function and parameters without guessing custom errors. diff --git a/.agents/skills/gooddollar/references/guides/stream.md b/.agents/skills/gooddollar/references/guides/stream.md deleted file mode 100644 index 6cbf7a45..00000000 --- a/.agents/skills/gooddollar/references/guides/stream.md +++ /dev/null @@ -1,135 +0,0 @@ -# Stream guide - -Primary references for stream execution are local ABI assets in this repo: - -- `references/contracts/CFAv1Forwarder.abi.yaml` -- `references/contracts/ConstantFlowAgreementV1.abi.yaml` -- `references/contracts/Superfluid.abi.yaml` -- `references/contracts/SuperToken.abi.yaml` - -## Goal - -Create, update, or delete Superfluid constant flows using deterministic contract calls and local ABI references. - -## Protocol facts used by this guide - -- Forwarder path uses `CFAv1Forwarder.createFlow`, `updateFlow`, `deleteFlow`. -- Host path uses `Superfluid.callAgreement` with CFA calldata for `createFlow`, `updateFlow`, `deleteFlow`. -- Stream token is a SuperToken; flow rates are `int96` in token-wei per second. -- `getBufferAmountByFlowrate(token, flowRate)` is the canonical pre-check for required buffer. - -## Two implementation styles in this repo - -1. **Forwarder (matches GoodDocs):** call CFAv1Forwarder with token, sender, receiver, flowRate, userData. -2. **Host callAgreement:** encode CFA `createFlow` / `updateFlow` / `deleteFlow` and call `Superfluid.callAgreement`. - -## Minimal method map - -- Forwarder: - - `createFlow(address token, address receiver, int96 flowrate, bytes userData)` - - `updateFlow(address token, address receiver, int96 flowrate, bytes userData)` - - `deleteFlow(address token, address sender, address receiver, bytes userData)` - - `getBufferAmountByFlowrate(address token, int96 flowrate)` -- Host: - - `callAgreement(address agreementClass, bytes callData, bytes userData)` -- CFA: - - `createFlow(address token, address receiver, int96 flowRate, bytes ctx)` - - `updateFlow(address token, address receiver, int96 flowRate, bytes ctx)` - - `deleteFlow(address token, address sender, address receiver, bytes ctx)` - -## Required inputs - -- G$ Super Token address for the environment -- CFA forwarder address, or Superfluid host address plus CFA agreement address -- `action`: create, update, delete -- `receiver`, `flowRate` where applicable -- `rpcUrl`, chain configuration, signer - -## Deterministic snippet - -```js -import { ethers } from "ethers"; - -const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); -const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); - -const forwarder = new ethers.Contract( - process.env.CFA_FORWARDER, - [ - "function createFlow(address,address,int96,bytes)", - "function updateFlow(address,address,int96,bytes)", - "function deleteFlow(address,address,address,bytes)", - ], - signer, -); - -const token = process.env.SUPER_TOKEN; -const sender = await signer.getAddress(); -const receiver = process.env.RECEIVER; -const flowRate = BigInt(process.env.FLOW_RATE); - -if (process.env.ACTION === "create") { - const tx = await forwarder.createFlow(token, receiver, flowRate, "0x"); - const receipt = await tx.wait(); - console.log(JSON.stringify({ txHash: receipt.hash, action: "create" }, null, 2)); -} - -if (process.env.ACTION === "update") { - const tx = await forwarder.updateFlow(token, receiver, flowRate, "0x"); - const receipt = await tx.wait(); - console.log(JSON.stringify({ txHash: receipt.hash, action: "update" }, null, 2)); -} - -if (process.env.ACTION === "delete") { - const tx = await forwarder.deleteFlow(token, sender, receiver, "0x"); - const receipt = await tx.wait(); - console.log(JSON.stringify({ txHash: receipt.hash, action: "delete" }, null, 2)); -} -``` - -Host callAgreement example: - -```js -import { ethers } from "ethers"; - -const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); -const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); - -const cfa = new ethers.Interface([ - "function createFlow(address,address,int96,bytes)", - "function updateFlow(address,address,int96,bytes)", - "function deleteFlow(address,address,address,bytes)", -]); - -const host = new ethers.Contract( - process.env.SUPERFLUID_HOST, - ["function callAgreement(address,bytes,bytes) returns (bytes)"], - signer, -); - -const token = process.env.SUPER_TOKEN; -const sender = await signer.getAddress(); -const receiver = process.env.RECEIVER; -const flowRate = BigInt(process.env.FLOW_RATE); - -let callData = "0x"; -if (process.env.ACTION === "create") { - callData = cfa.encodeFunctionData("createFlow", [token, receiver, flowRate, "0x"]); -} -if (process.env.ACTION === "update") { - callData = cfa.encodeFunctionData("updateFlow", [token, receiver, flowRate, "0x"]); -} -if (process.env.ACTION === "delete") { - callData = cfa.encodeFunctionData("deleteFlow", [token, sender, receiver, "0x"]); -} - -const tx = await host.callAgreement(process.env.CFA_ADDRESS, callData, "0x"); -const receipt = await tx.wait(); -console.log(JSON.stringify({ txHash: receipt.hash, action: process.env.ACTION }, null, 2)); -``` - -## Failure handling - -- Wrong network or missing addresses: stop and return missing host or forwarder or token addresses. -- Insufficient buffer: use `getBufferAmountByFlowrate` and reduce flow rate or top up balance. -- Revert on create or update: verify token is a SuperToken and flowRate is positive. diff --git a/.agents/skills/gooddollar/references/guides/swap.md b/.agents/skills/gooddollar/references/guides/swap.md deleted file mode 100644 index 3db8c2dc..00000000 --- a/.agents/skills/gooddollar/references/guides/swap.md +++ /dev/null @@ -1,83 +0,0 @@ -# Swap guide - -Use for buying or selling G$ through Mento-connected contracts on networks where they appear in [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) (for example `MentoBroker`, `MentoReserve`, `MentoExchangeProvider`, `MentoExpansionController` keys under `production-celo` or `production-xdc`). GoodDocs describes Mento product behavior, not deployment addresses. - -## GoodDocs alignment - -- Reserve and buy or sell mechanics at the protocol level: [How GoodDollar works](https://docs.gooddollar.org/how-gooddollar-works) and [Buy and Sell G$ user guide](https://docs.gooddollar.org/user-guides) (includes reserve AMM narrative; older explorer step-by-step for Ethereum testnets remains in that page for reference). -- Integration patterns and decimals: [How to integrate the G$ token](https://docs.gooddollar.org/for-developers/developer-guides/how-to-integrate-the-gusd-token). - -## Goal - -Execute bounded swaps using broker quotes and correct allowances. - -## Required inputs - -- `direction` as buy or sell -- broker and exchange identifiers for the deployment -- amounts in correct token decimals for the chain -- `rpcUrl`, chain configuration, signer - -## Execution flow - -1. Confirm Mento Broker (and related) addresses for the chain from [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) only. -2. Fetch quote (`getAmountOut` or `getAmountIn` depending on direction and ABI). -3. Apply slippage bounds. -4. Approve the spent token for the broker when required. -5. Call `swapIn` or `swapOut` per your integration. -6. Return tx hash and effective amounts. - -## Deterministic snippet - -```js -import { ethers } from "ethers"; - -const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); -const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); - -const broker = new ethers.Contract( - process.env.BROKER_ADDRESS, - [ - "function getAmountOut(address,address,uint256) view returns (uint256)", - "function swapIn(address,address,uint256,uint256) returns (uint256)", - ], - signer, -); - -const tokenIn = new ethers.Contract( - process.env.TOKEN_IN, - [ - "function allowance(address,address) view returns (uint256)", - "function approve(address,uint256) returns (bool)", - ], - signer, -); - -const amountIn = ethers.parseUnits(process.env.AMOUNT_IN, Number(process.env.DECIMALS_IN)); -const quotedOut = await broker.getAmountOut(process.env.TOKEN_IN, process.env.TOKEN_OUT, amountIn); -const slippageBps = BigInt(process.env.SLIPPAGE_BPS); -const minOut = quotedOut * (10000n - slippageBps) / 10000n; - -const owner = await signer.getAddress(); -const allowance = await tokenIn.allowance(owner, process.env.BROKER_ADDRESS); -if (allowance < amountIn) { - const approveTx = await tokenIn.approve(process.env.BROKER_ADDRESS, amountIn); - await approveTx.wait(); -} - -const tx = await broker.swapIn(process.env.TOKEN_IN, process.env.TOKEN_OUT, amountIn, minOut); -const receipt = await tx.wait(); -console.log( - JSON.stringify( - { txHash: receipt.hash, amountIn: amountIn.toString(), minOut: minOut.toString() }, - null, - 2, - ), -); -``` - -## Failure handling - -- No deployment on chain: direct the user to an environment that defines the needed keys in [deployment.json](https://github.com/GoodDollar/GoodProtocol/blob/master/releases/deployment.json) (for example `production-celo` with `MentoBroker`). -- Stale quote or tight slippage: refresh quote or relax bounds with user consent. -- Allowance or balance shortfall: report exact delta. diff --git a/.agents/skills/gooddollar/references/subgraphs/_query-patterns.md b/.agents/skills/gooddollar/references/subgraphs/_query-patterns.md deleted file mode 100644 index 36af6398..00000000 --- a/.agents/skills/gooddollar/references/subgraphs/_query-patterns.md +++ /dev/null @@ -1,35 +0,0 @@ -# Subgraph query discipline (GoodDollar) - -## Subgraph vs RPC - -- **Subgraph:** historical events, lists, aggregates, time ranges, analytics. Data lags chain head. -- **RPC / SDK:** current balances, live `claim` eligibility, exact view calls. Prefer for user-facing “what is true right now”. - -## Generic GraphQL mechanics - -Graph-node generates `entity`, `entities`, `Entity_filter`, `Entity_orderBy`, pagination, and `_meta` from each deployment’s `schema.graphql`. For scalar rules, filters, `_meta`, and common pitfalls, see [The Graph — Querying a subgraph](https://thegraph.com/docs/en/querying/graphql-api/). - -## GoodDollar-specific - -- **Addresses in `where` clauses:** normalize to **lowercase** hex strings; subgraphs store addresses lowercased. -- **BigInt fields:** query as **string** literals in GraphQL JSON (e.g. `"1000000000000000000"`). -- **Schema truth:** entity names differ per deployment. Run introspection or read the deployment’s `schema.graphql` under the relevant package in [GoodDollar/GoodSubGraphs](https://github.com/GoodDollar/GoodSubGraphs) before assuming field names. - -## Meta block - -Use `_meta { block { number } }` to detect how far behind indexing is when debugging stale data. - -```graphql -{ - _meta { - block { - number - } - hasIndexingErrors - } -} -``` - -## When subgraphs are not enough - -If the subgraph cannot answer the question (missing entities or fields, or stale indexing per `_meta`), switch to the decision rules in `references/guides/hypersync-hyperrpc.md`. When **HyperSync** is the best fit and no Envio API token is available in the environment, **ask the user directly** for `ENVIO_API_TOKEN` (or the token your HyperSync client expects) before running large scans; do not silently use anonymous quota as a stand-in. diff --git a/.agents/skills/gooddollar/references/subgraphs/goodcollective-guide.md b/.agents/skills/gooddollar/references/subgraphs/goodcollective-guide.md deleted file mode 100644 index a266ceab..00000000 --- a/.agents/skills/gooddollar/references/subgraphs/goodcollective-guide.md +++ /dev/null @@ -1,54 +0,0 @@ -# GoodCollective — Subgraph Usage Guide - -Companion to `goodcollective.graphql`. - -## Endpoint - -- Explorer: [GoodCollective](https://thegraph.com/explorer/subgraphs/3LbJh9DXhJVvuVDdm5i6StNboJmL9oMNNkBaKyzc4Y8Y?view=Query&chain=arbitrum-one) -- Gateway form: `https://gateway.thegraph.com/api/subgraphs/id/3LbJh9DXhJVvuVDdm5i6StNboJmL9oMNNkBaKyzc4Y8Y` - ---- - -## Terminology: “claim” here is not daily UBI - -In this subgraph, **Claim** and **ClaimEvent** refer to **GoodCollective reward or pool claim flows**, not the protocol’s **daily UBI claim** from `UBIScheme` / `UBISchemeV2`. - -When a user says **“claim”** in normal GoodDollar product language, they almost always mean **claim daily UBI**. For that, use the GoodDollar Celo subgraph (`walletStats` / claim-related aggregates) and on-chain `claim` per `references/guides/claim.md` — do not answer “last N UBI claims” from GoodCollective **Claim** alone. - ---- - -## Entity Overview - -### Core collective graph - -**Collective** — pool identity, limits/settings links, totals, and claim/payment counters. -**Donor** / **Steward** — participant-level donation/support state. -**DonorCollective** / **StewardCollective** — join entities tying participants to a collective. - -### Claim and support flow - -**Claim** and **ClaimEvent** — GoodCollective **reward** claim lifecycle and per-claim reward events (not daily UBI from UBIScheme). -**SupportEvent** — support/donation change events across donor/collective links. - -### Metadata and policy entities - -**IpfsCollective** — IPFS metadata projection. -**PoolSettings**, **UBILimits**, **SafetyLimits** — pool policy and operational bounds. -**ProvableNFT** — NFT linkage used in claim/reward flows. - ---- - -## Typical Questions This Subgraph Answers - -- Which donors/stewards are attached to a collective? -- How much was donated/rewarded per collective and per participant? -- Which claim events occurred and what reward quantities were emitted? -- What limits and settings govern a specific collective pool? - ---- - -## Query Discipline - -- Use authenticated gateway access for programmatic queries. -- Lowercase address-like identifiers where applicable. -- Validate `_meta` before operational dashboards or reporting exports. diff --git a/.agents/skills/gooddollar/references/subgraphs/goodcollective.graphql b/.agents/skills/gooddollar/references/subgraphs/goodcollective.graphql deleted file mode 100644 index e503db9c..00000000 --- a/.agents/skills/gooddollar/references/subgraphs/goodcollective.graphql +++ /dev/null @@ -1,200 +0,0 @@ -scalar BigDecimal - -scalar BigInt - -scalar Boolean - -scalar Bytes - -type Claim { - id: String! - collective: Collective! - txHash: String! - networkFee: BigInt! - totalRewards: BigInt! - events: [ClaimEvent!]! - timestamp: Int! -} - -type ClaimEvent { - id: String! - eventType: Int! - timestamp: Int! - quantity: BigInt! - rewardPerContributor: BigInt! - contributors: [Steward!]! - nft: ProvableNFT - claim: Claim! -} - -type Collective { - id: String! - pooltype: PoolType! - ipfs: IpfsCollective - settings: PoolSettings! - ubiLimits: UBILimits - limits: SafetyLimits - donors: [DonorCollective!] - stewards: [StewardCollective!] - projectId: String! - isVerified: Boolean! - poolFactory: String! - timestamp: Int! - paymentsMade: Int! - totalDonations: BigInt! - totalRewards: BigInt! - claims: [Claim!]! -} - -type Donor { - id: String! - timestamp: Int! - totalDonated: BigInt! - collectives: [DonorCollective!]! -} - -""" -Represents the relationship between a Donor and a Collective - -""" - -type DonorCollective { - id: String! - donor: Donor! - collective: Collective! - contribution: BigInt! - flowRate: BigInt! - timestamp: Int! - events: [SupportEvent!]! -} - -scalar Float - -scalar ID - -""" -4 bytes signed integer - -""" - -scalar Int - -""" -8 bytes signed integer - -""" - -scalar Int8 - -type IpfsCollective { - id: String! - name: String! - description: String! - rewardDescription: String - goodidDescription: String - email: String - website: String - twitter: String - instagram: String - threads: String - infoLabel: String - headerImage: String! - logo: String! - images: [String!] -} - -type PoolSettings { - id: String! - nftType: BigInt! - manager: Bytes! - membersValidator: Bytes! - uniquenessValidator: Bytes! - rewardToken: Bytes! -} - -enum PoolType { - DirectPayments - UBI -} - -type ProvableNFT { - id: String! - owner: String! - hash: String! - stewards: [Steward!]! - collective: Collective -} - -type SafetyLimits { - id: String! - maxTotalPerMonth: BigInt! - maxMemberPerMonth: BigInt! - maxMemberPerDay: BigInt! -} - -type Steward { - """ - { user address} - - """ - id: String! - """ - Number of actions performed - - """ - actions: Int! - totalEarned: BigInt! - totalUBIEarned: BigInt! - """ - NFT's minted to steward - - """ - nfts: [ProvableNFT!]! - """ - Collectives the steward is apart of - - """ - collectives: [StewardCollective!]! -} - -""" -Represents the relationship between a Steward and a Collective - -""" - -type StewardCollective { - id: String! - steward: Steward! - collective: Collective! - actions: Int! - totalEarned: BigInt! -} - -scalar String - -type SupportEvent { - id: String! - networkFee: BigInt! - donor: Donor! - collective: Collective! - donorCollective: DonorCollective! - contribution: BigInt! - previousContribution: BigInt! - isFlowUpdate: Boolean! - flowRate: BigInt! - previousFlowRate: BigInt! - timestamp: Int! -} - -scalar Timestamp - -type UBILimits { - id: String! - cycleLengthDays: BigInt! - claimPeriodDays: BigInt! - minActiveUsers: BigInt! - claimForEnabled: Boolean! - maxClaimAmount: BigInt! - maxClaimers: BigInt! - onlyMembers: Boolean! -} diff --git a/.agents/skills/gooddollar/references/subgraphs/gooddollar-celo-guide.md b/.agents/skills/gooddollar/references/subgraphs/gooddollar-celo-guide.md deleted file mode 100644 index 347951f4..00000000 --- a/.agents/skills/gooddollar/references/subgraphs/gooddollar-celo-guide.md +++ /dev/null @@ -1,104 +0,0 @@ -# GoodDollar Celo — Subgraph Usage Guide - -Companion to `gooddollar-celo.graphql`. - -## Endpoint - -- Explorer: [GoodDollarCelo](https://thegraph.com/explorer/subgraphs/F7314rxGdcpKPC1nN5KCoFW84EGRoUyzseY2sAT9PEkw?view=Query&chain=arbitrum-one) -- Gateway form: `https://gateway.thegraph.com/api/subgraphs/id/F7314rxGdcpKPC1nN5KCoFW84EGRoUyzseY2sAT9PEkw` - ---- - -## Entity Overview - -### UBI and usage statistics - -**DailyUBI** — day-level UBI pool/quota activity and cycle fields. -**WalletStat** — wallet behavior aggregates: tx counts/values, claim stats, active/whitelist indicators. -**TransactionStat** — day-level transaction totals and circulation view. -**GlobalStatistics** — global claim and distribution rollups. - -### Additional UBI history entities - -**UBICollected** — collected UBI/community-pool values by block event. -**UBIHistory** — timeline totals for daily UBI/community-pool. - ---- - -## Field Availability Reference (use before drafting queries) - -Use this section to validate what the subgraph already provides before switching data sources. - -### DailyUBI - -- `id` — day index key (`unix / 86400`) -- `pool` — UBI cycle pool for that day -- `quota` — daily UBI amount per eligible claimer -- `activeUsers` — active users count in scheme context -- `totalUBIDistributed` — amount actually claimed/distributed that day -- `totalClaims` — claim tx count for that day -- `newClaimers` — newly whitelisted users for that day -- `timestamp` — last update timestamp for the record -- `ubiSchemeAddress` — UBIScheme address used for the record -- `balance` — G$ balance held by UBIScheme -- `cycleLength` — current cycle length -- `dayInCycle` — current day position inside cycle - -### WalletStat - -- `id` — wallet address -- `dateAppeared` — first indexed wallet activity timestamp -- `balance` — running token balance from transfers -- `inTransactionsCount`, `inTransactionsValue` — incoming tx count and value -- `outTransactionsCount`, `outTransactionsValue` — outgoing tx count and value -- `totalTransactionsCount`, `totalTransactionsValue` — total tx count and value -- `inTransactionsCountClean`, `inTransactionsValueClean` — incoming metrics excluding contract-address flows -- `outTransactionsCountClean`, `outTransactionsValueClean` — outgoing metrics excluding contract-address flows -- `totalTransactionsCountClean`, `totalTransactionsValueClean` — total clean traffic metrics -- `lastClaimed` — timestamp of latest UBI claim -- `totalClaimedCount`, `totalClaimedValue` — total claims and cumulative claimed value -- `claimStreak`, `longestClaimStreak` — current and best historical streaks -- `isWhitelisted` — current whitelist status -- `isActiveUser` — current active-user status -- `dateJoined` — first-whitelist timestamp -- `lastTransactionFrom`, `lastTransactionTo` — latest outgoing and incoming tx timestamps - -### TransactionStat - -- `id` — bucket key (day key or `"aggregated"`) -- `dayStartBlockNumber` — first block in bucket -- `transactionsCount`, `transactionsValue` — all transfer tx count and value -- `transactionsCountClean`, `transactionsValueClean` — transfer metrics excluding contract-address flows -- `totalInCirculation` — inferred circulating supply from mint or burn behavior - -### GlobalStatistics - -- `id` — fixed key (`"statistics"`) -- `TransactionStat` — link to aggregated transaction stats -- `totalUBIDistributed` — lifetime distributed UBI -- `uniqueClaimers` — tracked unique claimers via whitelist add or remove -- `totalClaims` — lifetime UBI claim events - -### Explorer naming - -- Singular names (`dailyUBI`, `walletStat`) fetch by `id` -- Plural names (`dailyUBIs`, `walletStats`) query lists with filters and pagination - ---- - -## Typical Questions This Subgraph Answers - -- How much UBI was distributed on a day/cycle? -- Which wallets are active or recently claiming? -- What are aggregate tx/circulation trends? -- How did collected UBI/community-pool values evolve over time? - ---- - -## Query Discipline - -- Use lowercase address strings in filters. -- Use string-safe handling for large integer values. -- Use `_meta` to validate freshness before cross-day analytics. -- Use authenticated gateway access for programmatic queries. -- Before claiming a field or entity is missing, verify availability from this guide and schema first. diff --git a/.agents/skills/gooddollar/references/subgraphs/gooddollar-celo.graphql b/.agents/skills/gooddollar/references/subgraphs/gooddollar-celo.graphql deleted file mode 100644 index e544a0dc..00000000 --- a/.agents/skills/gooddollar/references/subgraphs/gooddollar-celo.graphql +++ /dev/null @@ -1,113 +0,0 @@ -scalar BigDecimal - -scalar BigInt - -scalar Boolean - -scalar Bytes - -type DailyUBI { - id: ID! - pool: BigInt! - quota: BigInt! - activeUsers: BigInt! - totalUBIDistributed: BigInt! - totalClaims: BigInt! - newClaimers: BigInt! - timestamp: BigInt! - ubiSchemeAddress: Bytes - balance: BigInt! - cycleLength: BigInt! - dayInCycle: BigInt! -} - -scalar Float - -type GlobalStatistics { - id: ID! - TransactionStat: TransactionStat - totalUBIDistributed: BigInt! - uniqueClaimers: BigInt! - totalClaims: BigInt! -} - -scalar ID - -""" -4 bytes signed integer -""" - -scalar Int - -""" -8 bytes signed integer -""" - -scalar Int8 - -""" -Defines the order direction, either ascending or descending -""" - -scalar String - -""" -A string representation of microseconds UNIX timestamp (16 digits) -""" - -scalar Timestamp - -type TransactionStat { - id: ID! - dayStartBlockNumber: BigInt! - transactionsCount: BigInt! - transactionsCountClean: BigInt! - transactionsValue: BigInt! - transactionsValueClean: BigInt! - totalInCirculation: BigInt! -} - -type UBICollected { - id: ID! - contract: Bytes - block: BigInt! - blockTimestamp: BigInt! - ubi: BigDecimal! - communityPool: BigDecimal! -} - -type UBIHistory { - id: ID! - block: BigInt! - blockTimestamp: BigInt! - totalDailyUBI: BigDecimal! - totalDailyCommunityPool: BigDecimal! -} - -type WalletStat { - id: ID! - dateAppeared: BigInt! - balance: BigInt! - inTransactionsCount: BigInt! - inTransactionsCountClean: BigInt! - inTransactionsValue: BigInt! - inTransactionsValueClean: BigInt! - outTransactionsCount: BigInt! - outTransactionsCountClean: BigInt! - outTransactionsValue: BigInt! - outTransactionsValueClean: BigInt! - totalTransactionsCount: BigInt! - totalTransactionsCountClean: BigInt! - totalTransactionsValue: BigInt! - totalTransactionsValueClean: BigInt! - lastClaimed: BigInt! - totalClaimedCount: BigInt! - totalClaimedValue: BigInt! - claimStreak: BigInt! - longestClaimStreak: BigInt! - isWhitelisted: Boolean! - isActiveUser: Boolean! - dateJoined: BigInt! - lastTransactionFrom: BigInt! - lastTransactionTo: BigInt! -} diff --git a/.agents/skills/gooddollar/references/subgraphs/reserve-celo-guide.md b/.agents/skills/gooddollar/references/subgraphs/reserve-celo-guide.md deleted file mode 100644 index 9f55b734..00000000 --- a/.agents/skills/gooddollar/references/subgraphs/reserve-celo-guide.md +++ /dev/null @@ -1,41 +0,0 @@ -# Reserve Celo — Subgraph Usage Guide - -Companion to `reserve-celo.graphql`. This guide is for reserve pricing and broker swap indexing on Celo. - -## Endpoint - -- Goldsky: `https://api.goldsky.com/api/public/project_cmizuamdtfouu01x4csuk5dk1/subgraphs/reserve_celo/1.0/gn` - ---- - -## Entity Overview - -### Core entity - -**ReservePrice** — one indexed reserve pricing point produced from broker swap flow plus exchange-provider price read. -Key fields: `exchangeId`, `exchangeProvider`, `price`, `timestamp`, `day`, `tokenIn`, `tokenOut`, `amountIn`, `amountOut`, `user`, `blockNumber`, `transactionHash`. - ---- - -## Typical Questions This Subgraph Answers - -- What are the most recent reserve prices? -- What was the reserve price on a specific day window? -- Which token pair and user triggered a pricing point? -- Which tx hash/block produced a given price point? - ---- - -## Query Discipline - -- Lowercase all address values in filters. -- Use string values for large integer variables. -- Use `_meta` before analytics queries when stale indexing is suspected. - ---- - -## Practical Start - -1. Check `_meta` block height and `hasIndexingErrors`. -2. Pull latest `ReservePrice` records sorted by `timestamp desc`. -3. Add `day`-based narrowing for historical windows. diff --git a/.agents/skills/gooddollar/references/subgraphs/reserve-celo.graphql b/.agents/skills/gooddollar/references/subgraphs/reserve-celo.graphql deleted file mode 100644 index 56cac37b..00000000 --- a/.agents/skills/gooddollar/references/subgraphs/reserve-celo.graphql +++ /dev/null @@ -1,90 +0,0 @@ -scalar BigDecimal - -scalar BigInt - -scalar Boolean - -scalar Bytes - -scalar Float - -scalar ID - -""" -4 bytes signed integer -""" - -scalar Int - -""" -8 bytes signed integer -""" - -scalar Int8 - -""" -Defines the order direction, either ascending or descending -""" - -type ReservePrice { - """ - Exchange ID combined with timestamp - """ - id: ID! - """ - The exchange ID from the Swap event - """ - exchangeId: Bytes! - """ - The exchange provider address - """ - exchangeProvider: Bytes! - """ - The current price from IBancorExchangeProvider.currentPrice() - """ - price: BigInt! - """ - Transaction timestamp - """ - timestamp: BigInt! - """ - Day ID (timestamp / 86400) - """ - day: BigInt! - """ - Token in address from the swap - """ - tokenIn: Bytes! - """ - Token out address from the swap - """ - tokenOut: Bytes! - """ - Amount in from the swap - """ - amountIn: BigInt! - """ - Amount out from the swap - """ - amountOut: BigInt! - """ - User who initiated the swap - """ - user: Bytes! - """ - Block number - """ - blockNumber: BigInt! - """ - Transaction hash - """ - transactionHash: Bytes! -} - -scalar String - -""" -A string representation of microseconds UNIX timestamp (16 digits) -""" - -scalar Timestamp diff --git a/contracts/governance/GoodDaoHouses.sol b/contracts/governance/GoodDaoHouses.sol new file mode 100644 index 00000000..384afd5a --- /dev/null +++ b/contracts/governance/GoodDaoHouses.sol @@ -0,0 +1,764 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; +import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + +import "../Interfaces.sol"; +import "../token/ERC677.sol"; +import "../utils/DAOUpgradeableContract.sol"; +import "./IFlowSplitter.sol"; + +interface IFlowSplitterCounter is IFlowSplitter { + function poolCounter() external view returns (uint256); +} + +contract GoodDaoHouses is + AccessControlUpgradeable, + PausableUpgradeable, + ReentrancyGuardUpgradeable, + DAOUpgradeableContract, + ERC677Receiver +{ + using EnumerableSet for EnumerableSet.AddressSet; + + bytes32 public constant GOVERNANCE_COMMITTEE_ROLE = + keccak256("GOVERNANCE_COMMITTEE_ROLE"); + + uint256 public constant HOUSE_ALIGNMENT_WEIGHT = 40; + uint256 public constant HOUSE_CITIZENS_WEIGHT = 4; + uint256 public constant BASIS_POINTS = 10000; + uint64 public constant DEFAULT_VOTE_DURATION = 7 days; + + enum House { + Citizens, + Alignment + } + + enum MemberStatus { + None, + Pending, + Active, + Revoked, + Unstaked + } + + struct MemberRecord { + House house; + MemberStatus status; + uint256 stakedAmount; + uint64 joinedAt; + uint64 updatedAt; + uint64 unstakedAt; + string metadata; + } + + struct EligibilityRecord { + bool isEligible; + uint64 listedAt; + uint64 updatedAt; + uint64 delistedAt; + } + + struct VoteConfig { + uint64 startTime; + uint64 endTime; + uint64 finalizedAt; + uint64 executedAt; + uint256 totalUnits; + uint256 totalWeight; + string metadata; + bool finalized; + bool executed; + } + + struct FlowSplitterConfig { + address splitter; + address superToken; + string metadata; + string poolName; + string poolSymbol; + uint8 poolDecimals; + bool transferabilityForUnitsOwner; + bool distributionFromAnyAddress; + uint256 poolId; + address poolAddress; + bool poolInitialized; + } + + mapping(address => MemberRecord) private _members; + mapping(address => EligibilityRecord) private _alignmentEligibility; + mapping(House => uint256) public minimumStake; + + EnumerableSet.AddressSet private _activeCitizens; + EnumerableSet.AddressSet private _activeAlignment; + + uint256 public voteCount; + mapping(uint256 => VoteConfig) private _votes; + mapping(uint256 => address[]) private _voteRecipients; + mapping(uint256 => address[]) private _voteAlignmentVoters; + mapping(uint256 => address[]) private _voteCitizensVoters; + mapping(uint256 => mapping(address => bool)) private _isVoteRecipient; + mapping(uint256 => mapping(address => uint256)) private _voteWeightSnapshot; + mapping(uint256 => mapping(address => uint256)) + private _voteRecipientWeightedVotes; + mapping(uint256 => mapping(address => uint128)) private _finalizedUnits; + mapping(uint256 => mapping(address => address[])) private _voterBallotRecipients; + mapping(uint256 => mapping(address => mapping(address => uint256))) + private _voterBallotBps; + + FlowSplitterConfig private _flowSplitterConfig; + + event StakeRequirementSet(House indexed house, uint256 amount); + event AlignmentEligibilityUpdated(address indexed account, bool isEligible); + event MemberRegistered( + address indexed account, + House indexed house, + MemberStatus status, + uint256 amount, + string metadata + ); + event MemberApproved(address indexed account, House indexed house); + event MemberRevoked(address indexed account, House indexed house); + event MemberStaked(address indexed account, House indexed house, uint256 amount); + event MemberUnstaked(address indexed account, House indexed house, uint256 amount); + event VoteCreated( + uint256 indexed voteId, + uint64 startTime, + uint64 endTime, + uint256 totalUnits, + string metadata + ); + event VoteUpdated(uint256 indexed voteId, address indexed voter); + event VoteFinalized(uint256 indexed voteId, uint256 totalWeight); + event VoteExecuted(uint256 indexed voteId, uint256 poolId, address poolAddress); + event FlowSplitterConfigured(address indexed splitter, address indexed superToken); + event FlowSplitterPoolCreated(uint256 indexed poolId, address poolAddress); + event FlowSplitterMetadataUpdated(uint256 indexed poolId, string metadata); + + function initialize( + INameService _ns, + address admin, + address committee, + uint256 citizensMinimumStake, + uint256 alignmentMinimumStake + ) public initializer { + __AccessControl_init(); + __Pausable_init(); + __ReentrancyGuard_init(); + __UUPSUpgradeable_init(); + + setDAO(_ns); + + _grantRole(DEFAULT_ADMIN_ROLE, admin); + _grantRole(GOVERNANCE_COMMITTEE_ROLE, committee); + if (admin != committee) { + _grantRole(GOVERNANCE_COMMITTEE_ROLE, admin); + } + + minimumStake[House.Citizens] = citizensMinimumStake; + minimumStake[House.Alignment] = alignmentMinimumStake; + + emit StakeRequirementSet(House.Citizens, citizensMinimumStake); + emit StakeRequirementSet(House.Alignment, alignmentMinimumStake); + } + + function setStakeRequirement(House house, uint256 amount) + external + onlyRole(GOVERNANCE_COMMITTEE_ROLE) + { + minimumStake[house] = amount; + emit StakeRequirementSet(house, amount); + } + + function setAlignmentEligibility(address account, bool isEligible) + external + onlyRole(GOVERNANCE_COMMITTEE_ROLE) + { + EligibilityRecord storage eligibility = _alignmentEligibility[account]; + eligibility.isEligible = isEligible; + eligibility.updatedAt = uint64(block.timestamp); + if (isEligible) { + if (eligibility.listedAt == 0) { + eligibility.listedAt = uint64(block.timestamp); + } + eligibility.delistedAt = 0; + } else { + eligibility.delistedAt = uint64(block.timestamp); + } + + emit AlignmentEligibilityUpdated(account, isEligible); + } + + function registerAndStake( + House house, + uint256 amount, + string calldata metadata + ) external whenNotPaused { + require( + _goodDollar().transferFrom(msg.sender, address(this), amount), + "TF" + ); + _registerMember(msg.sender, house, amount, metadata); + } + + function stake(uint256 amount) external whenNotPaused { + require( + _goodDollar().transferFrom(msg.sender, address(this), amount), + "TF" + ); + _addStake(msg.sender, amount); + } + + function onTokenTransfer( + address _from, + uint256 _amount, + bytes calldata _data + ) external override whenNotPaused returns (bool success) { + require(msg.sender == address(_goodDollar()), "UT"); + + if (_data.length == 0) { + _addStake(_from, _amount); + return true; + } + + (House house, string memory metadata) = abi.decode( + _data, + (House, string) + ); + _registerMember(_from, house, _amount, metadata); + return true; + } + + function approveAlignmentMember(address account) + external + onlyRole(GOVERNANCE_COMMITTEE_ROLE) + whenNotPaused + { + MemberRecord storage member = _members[account]; + require(member.house == House.Alignment, "WH"); + require(member.status == MemberStatus.Pending, "NP"); + require( + _alignmentEligibility[account].isEligible, + "NE" + ); + require( + member.stakedAmount >= minimumStake[House.Alignment], + "SBM" + ); + + member.status = MemberStatus.Active; + member.updatedAt = uint64(block.timestamp); + _activeAlignment.add(account); + + emit MemberApproved(account, member.house); + } + + function revokeMember(address account) + external + onlyRole(GOVERNANCE_COMMITTEE_ROLE) + whenNotPaused + { + MemberRecord storage member = _members[account]; + require( + member.status == MemberStatus.Active || + member.status == MemberStatus.Pending, + "NA" + ); + + _removeFromActiveSet(member.house, account); + member.status = MemberStatus.Revoked; + member.updatedAt = uint64(block.timestamp); + + _clearMemberUnits(account); + emit MemberRevoked(account, member.house); + } + + function unstake() external nonReentrant whenNotPaused { + MemberRecord storage member = _members[msg.sender]; + uint256 amount = member.stakedAmount; + + require(amount > 0, "NS"); + + _removeFromActiveSet(member.house, msg.sender); + member.stakedAmount = 0; + member.status = MemberStatus.Unstaked; + member.updatedAt = uint64(block.timestamp); + member.unstakedAt = uint64(block.timestamp); + + _clearMemberUnits(msg.sender); + + require(_goodDollar().transfer(msg.sender, amount), "WTF"); + + emit MemberUnstaked(msg.sender, member.house, amount); + } + + function createAlignmentVote( + uint64 duration, + uint256 totalUnits, + string calldata metadata + ) + external + onlyRole(GOVERNANCE_COMMITTEE_ROLE) + whenNotPaused + returns (uint256 voteId) + { + address[] memory recipients = _activeAlignment.values(); + address[] memory alignmentVoters = _activeAlignment.values(); + address[] memory citizensVoters = _activeCitizens.values(); + + require(recipients.length > 0, "NAM"); + + voteId = ++voteCount; + + if (duration == 0) { + duration = DEFAULT_VOTE_DURATION; + } + + VoteConfig storage vote = _votes[voteId]; + vote.startTime = uint64(block.timestamp); + vote.endTime = uint64(block.timestamp) + duration; + vote.totalUnits = totalUnits; + vote.totalWeight = + (alignmentVoters.length * HOUSE_ALIGNMENT_WEIGHT) + + (citizensVoters.length * HOUSE_CITIZENS_WEIGHT); + vote.metadata = metadata; + + for (uint256 i = 0; i < recipients.length; i++) { + _voteRecipients[voteId].push(recipients[i]); + _isVoteRecipient[voteId][recipients[i]] = true; + } + + for (uint256 i = 0; i < alignmentVoters.length; i++) { + address voter = alignmentVoters[i]; + _voteAlignmentVoters[voteId].push(voter); + _voteWeightSnapshot[voteId][voter] = HOUSE_ALIGNMENT_WEIGHT; + } + + for (uint256 i = 0; i < citizensVoters.length; i++) { + address voter = citizensVoters[i]; + _voteCitizensVoters[voteId].push(voter); + _voteWeightSnapshot[voteId][voter] = HOUSE_CITIZENS_WEIGHT; + } + + emit VoteCreated( + voteId, + vote.startTime, + vote.endTime, + totalUnits, + metadata + ); + } + + function castVote( + uint256 voteId, + address[] calldata recipients, + uint256[] calldata allocations + ) external whenNotPaused { + VoteConfig storage vote = _votes[voteId]; + uint256 voterWeight = _voteWeightSnapshot[voteId][msg.sender]; + + require(vote.startTime > 0, "VNF"); + require(block.timestamp >= vote.startTime, "VNS"); + require(block.timestamp <= vote.endTime, "VC"); + require(!vote.finalized, "VF"); + require(voterWeight > 0, "VE"); + require(recipients.length == allocations.length, "LM"); + require(recipients.length > 0, "EB"); + + uint256 allocationTotal; + for (uint256 i = 0; i < recipients.length; i++) { + require(_isVoteRecipient[voteId][recipients[i]], "IR"); + for (uint256 j = i + 1; j < recipients.length; j++) { + require(recipients[i] != recipients[j], "DR"); + } + allocationTotal += allocations[i]; + } + require(allocationTotal == BASIS_POINTS, "ASI"); + + _clearBallot(voteId, msg.sender, voterWeight); + + address[] storage storedRecipients = _voterBallotRecipients[voteId][ + msg.sender + ]; + for (uint256 i = 0; i < recipients.length; i++) { + address recipient = recipients[i]; + uint256 allocation = allocations[i]; + storedRecipients.push(recipient); + _voterBallotBps[voteId][msg.sender][recipient] = allocation; + _voteRecipientWeightedVotes[voteId][recipient] += + allocation * + voterWeight; + } + + emit VoteUpdated(voteId, msg.sender); + } + + function finalizeAlignmentVote(uint256 voteId) + external + onlyRole(GOVERNANCE_COMMITTEE_ROLE) + whenNotPaused + { + VoteConfig storage vote = _votes[voteId]; + address[] storage recipients = _voteRecipients[voteId]; + uint256 voteWeightTotal; + + require(vote.startTime > 0, "VNF"); + require(block.timestamp > vote.endTime, "VSO"); + require(!vote.finalized, "VF"); + + for (uint256 i = 0; i < recipients.length; i++) { + voteWeightTotal += _voteRecipientWeightedVotes[voteId][recipients[i]]; + } + + for (uint256 i = 0; i < recipients.length; i++) { + address recipient = recipients[i]; + if (voteWeightTotal == 0 || vote.totalUnits == 0) { + _finalizedUnits[voteId][recipient] = 0; + } else { + _finalizedUnits[voteId][recipient] = uint128( + (_voteRecipientWeightedVotes[voteId][recipient] * vote.totalUnits) / + voteWeightTotal + ); + } + } + + vote.finalized = true; + vote.finalizedAt = uint64(block.timestamp); + + emit VoteFinalized(voteId, voteWeightTotal); + } + + function executeVote(uint256 voteId) + external + onlyRole(GOVERNANCE_COMMITTEE_ROLE) + whenNotPaused + { + VoteConfig storage vote = _votes[voteId]; + FlowSplitterConfig storage flowConfig = _flowSplitterConfig; + address[] storage recipients = _voteRecipients[voteId]; + IFlowSplitter.Member[] memory members = new IFlowSplitter.Member[]( + recipients.length + ); + + require(vote.finalized, "VNFN"); + require(!vote.executed, "VAE"); + require(flowConfig.splitter != address(0), "FNC"); + require(flowConfig.superToken != address(0), "STNC"); + + for (uint256 i = 0; i < recipients.length; i++) { + address recipient = recipients[i]; + members[i] = IFlowSplitter.Member({ + account: recipient, + units: _finalizedUnits[voteId][recipient] + }); + } + + if (!flowConfig.poolInitialized) { + address[] memory admins = new address[](1); + admins[0] = address(this); + + ISuperfluidPool pool = IFlowSplitter(flowConfig.splitter).createPool( + ISuperToken(flowConfig.superToken), + PoolConfig({ + transferabilityForUnitsOwner: flowConfig + .transferabilityForUnitsOwner, + distributionFromAnyAddress: flowConfig.distributionFromAnyAddress + }), + PoolERC20Metadata({ + name: flowConfig.poolName, + symbol: flowConfig.poolSymbol, + decimals: flowConfig.poolDecimals + }), + members, + admins, + flowConfig.metadata + ); + + flowConfig.poolId = IFlowSplitterCounter(flowConfig.splitter) + .poolCounter(); + IFlowSplitter.Pool memory poolInfo = IFlowSplitter(flowConfig.splitter) + .getPoolById(flowConfig.poolId); + + flowConfig.poolInitialized = true; + flowConfig.poolAddress = poolInfo.poolAddress == address(0) + ? address(pool) + : poolInfo.poolAddress; + + emit FlowSplitterPoolCreated( + flowConfig.poolId, + flowConfig.poolAddress + ); + } else { + IFlowSplitter(flowConfig.splitter).updateMembersUnits( + flowConfig.poolId, + members + ); + } + + vote.executed = true; + vote.executedAt = uint64(block.timestamp); + + emit VoteExecuted(voteId, flowConfig.poolId, flowConfig.poolAddress); + } + + function configureFlowSplitter( + address splitter, + address superToken, + string calldata metadata, + string calldata poolName, + string calldata poolSymbol, + uint8 poolDecimals, + bool transferabilityForUnitsOwner, + bool distributionFromAnyAddress + ) external onlyRole(GOVERNANCE_COMMITTEE_ROLE) { + _flowSplitterConfig.splitter = splitter; + _flowSplitterConfig.superToken = superToken; + _flowSplitterConfig.metadata = metadata; + _flowSplitterConfig.poolName = poolName; + _flowSplitterConfig.poolSymbol = poolSymbol; + _flowSplitterConfig.poolDecimals = poolDecimals; + _flowSplitterConfig.transferabilityForUnitsOwner = transferabilityForUnitsOwner; + _flowSplitterConfig.distributionFromAnyAddress = distributionFromAnyAddress; + + emit FlowSplitterConfigured(splitter, superToken); + } + + function syncFlowSplitterPool(uint256 poolId) + external + onlyRole(GOVERNANCE_COMMITTEE_ROLE) + { + IFlowSplitter.Pool memory pool = IFlowSplitter(_flowSplitterConfig.splitter) + .getPoolById(poolId); + require(pool.poolAddress != address(0), "PNF"); + + _flowSplitterConfig.poolId = pool.id; + _flowSplitterConfig.poolAddress = pool.poolAddress; + _flowSplitterConfig.poolInitialized = true; + } + + function syncFlowSplitterMetadata(string calldata metadata) + external + onlyRole(GOVERNANCE_COMMITTEE_ROLE) + { + _flowSplitterConfig.metadata = metadata; + if (_flowSplitterConfig.poolInitialized) { + IFlowSplitter(_flowSplitterConfig.splitter).updatePoolMetadata( + _flowSplitterConfig.poolId, + metadata + ); + emit FlowSplitterMetadataUpdated( + _flowSplitterConfig.poolId, + metadata + ); + } + } + + function pause() external onlyRole(DEFAULT_ADMIN_ROLE) { + _pause(); + } + + function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) { + _unpause(); + } + + function getMember(address account) + external + view + returns (MemberRecord memory) + { + return _members[account]; + } + + function getAlignmentEligibility(address account) + external + view + returns (EligibilityRecord memory) + { + return _alignmentEligibility[account]; + } + + function isActiveMember(address account, House house) + external + view + returns (bool) + { + MemberRecord storage member = _members[account]; + return member.house == house && member.status == MemberStatus.Active; + } + + function getActiveMembers(House house) + external + view + returns (address[] memory) + { + return + house == House.Alignment + ? _activeAlignment.values() + : _activeCitizens.values(); + } + + function getVote(uint256 voteId) external view returns (VoteConfig memory) { + return _votes[voteId]; + } + + function getVoteRecipients(uint256 voteId) + external + view + returns (address[] memory) + { + return _voteRecipients[voteId]; + } + + function getVoteVoters(uint256 voteId) + external + view + returns ( + address[] memory alignmentVoters, + address[] memory citizensVoters + ) + { + return (_voteAlignmentVoters[voteId], _voteCitizensVoters[voteId]); + } + + function getBallot(uint256 voteId, address voter) + external + view + returns (address[] memory recipients, uint256[] memory allocations) + { + recipients = _voterBallotRecipients[voteId][voter]; + allocations = new uint256[](recipients.length); + for (uint256 i = 0; i < recipients.length; i++) { + allocations[i] = _voterBallotBps[voteId][voter][recipients[i]]; + } + } + + function getFinalizedUnits(uint256 voteId, address recipient) + external + view + returns (uint128) + { + return _finalizedUnits[voteId][recipient]; + } + + function getFlowSplitterConfig() + external + view + returns (FlowSplitterConfig memory) + { + return _flowSplitterConfig; + } + + function _registerMember( + address account, + House house, + uint256 amount, + string memory metadata + ) internal { + MemberRecord storage member = _members[account]; + uint64 joinedAt = member.joinedAt == 0 + ? uint64(block.timestamp) + : member.joinedAt; + + require(amount >= minimumStake[house], "SBM"); + require( + member.status == MemberStatus.None || + member.status == MemberStatus.Unstaked, + "MAR" + ); + + if (house == House.Alignment) { + require( + _alignmentEligibility[account].isEligible, + "NE" + ); + } + + _members[account] = MemberRecord({ + house: house, + status: house == House.Alignment + ? MemberStatus.Pending + : MemberStatus.Active, + stakedAmount: amount, + joinedAt: joinedAt, + updatedAt: uint64(block.timestamp), + unstakedAt: 0, + metadata: metadata + }); + + if (house == House.Citizens) { + _activeCitizens.add(account); + } + + emit MemberRegistered( + account, + house, + _members[account].status, + amount, + metadata + ); + } + + function _addStake(address account, uint256 amount) internal { + MemberRecord storage member = _members[account]; + require(member.status != MemberStatus.None, "MNF"); + require(member.status != MemberStatus.Unstaked, "MU"); + + member.stakedAmount += amount; + member.updatedAt = uint64(block.timestamp); + + emit MemberStaked(account, member.house, amount); + } + + function _clearBallot( + uint256 voteId, + address voter, + uint256 voterWeight + ) internal { + address[] storage previousRecipients = _voterBallotRecipients[voteId][voter]; + + for (uint256 i = 0; i < previousRecipients.length; i++) { + address recipient = previousRecipients[i]; + uint256 previousAllocation = _voterBallotBps[voteId][voter][recipient]; + if (previousAllocation > 0) { + _voteRecipientWeightedVotes[voteId][recipient] -= + previousAllocation * + voterWeight; + delete _voterBallotBps[voteId][voter][recipient]; + } + } + + delete _voterBallotRecipients[voteId][voter]; + } + + function _clearMemberUnits(address account) internal { + if (!_flowSplitterConfig.poolInitialized) { + return; + } + + IFlowSplitter.Member[] + memory members = new IFlowSplitter.Member[](1); + members[0] = IFlowSplitter.Member({ account: account, units: 0 }); + IFlowSplitter(_flowSplitterConfig.splitter).updateMembersUnits( + _flowSplitterConfig.poolId, + members + ); + } + + function _removeFromActiveSet(House house, address account) internal { + if (house == House.Alignment) { + _activeAlignment.remove(account); + } else { + _activeCitizens.remove(account); + } + } + + function _goodDollar() internal view returns (IGoodDollar) { + return IGoodDollar(nameService.getAddress("GOODDOLLAR")); + } + + uint256[42] private __gap; +} diff --git a/contracts/governance/IFlowSplitter.sol b/contracts/governance/IFlowSplitter.sol new file mode 100644 index 00000000..bdb54aca --- /dev/null +++ b/contracts/governance/IFlowSplitter.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; + +interface ISuperToken {} + +interface ISuperfluidPool { + function updateMemberUnits(address member, uint128 units) external; + + function name() external view returns (string memory); + + function symbol() external view returns (string memory); +} + +struct PoolConfig { + bool transferabilityForUnitsOwner; + bool distributionFromAnyAddress; +} + +struct PoolERC20Metadata { + string name; + string symbol; + uint8 decimals; +} + +/// @title FlowSplitter Interface +/// @notice Interface for the Flow Splitter contract. +interface IFlowSplitter { + struct Pool { + uint256 id; + address poolAddress; + address token; + string metadata; + bytes32 adminRole; + } + + struct Member { + address account; + uint128 units; + } + + struct Admin { + address account; + AdminStatus status; + } + + enum AdminStatus { + Added, + Removed + } + + event PoolCreated( + uint256 indexed poolId, + address poolAddress, + address token, + string metadata + ); + event PoolMetadataUpdated(uint256 indexed poolId, string metadata); + + error NOT_POOL_ADMIN(); + error ZERO_ADDRESS(); + + function createPool( + ISuperToken _poolSuperToken, + PoolConfig memory _poolConfig, + PoolERC20Metadata memory _erc20Metadata, + Member[] memory _members, + address[] memory _admins, + string memory _metadata + ) external returns (ISuperfluidPool gdaPool); + + function addPoolAdmin(uint256 poolId, address admin) external; + + function removePoolAdmin(uint256 poolId, address admin) external; + + function updatePoolAdmins(uint256 poolId, Admin[] memory admins) external; + + function updateMembersUnits(uint256 poolId, Member[] memory members) external; + + function updatePoolMetadata(uint256 poolId, string memory metadata) external; + + function isPoolAdmin(uint256 poolId, address account) + external + view + returns (bool); + + function getPoolById(uint256 poolId) external view returns (Pool memory pool); + + function getPoolByAdminRole(bytes32 adminRole) + external + view + returns (Pool memory pool); + + function getPoolNameById(uint256 _poolId) + external + view + returns (string memory name); + + function getPoolSymbolById(uint256 _poolId) + external + view + returns (string memory symbol); +} diff --git a/contracts/mocks/MockFlowSplitter.sol b/contracts/mocks/MockFlowSplitter.sol new file mode 100644 index 00000000..1317371c --- /dev/null +++ b/contracts/mocks/MockFlowSplitter.sol @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import "../governance/IFlowSplitter.sol"; + +contract MockSuperfluidPool { + string public name; + string public symbol; + mapping(address => uint128) public memberUnits; + + constructor(string memory _name, string memory _symbol) { + name = _name; + symbol = _symbol; + } + + function updateMemberUnits(address member, uint128 units) external { + memberUnits[member] = units; + } +} + +contract MockFlowSplitter is IFlowSplitter { + uint256 public poolCounter; + + mapping(uint256 => Pool) private _poolsById; + mapping(bytes32 => Pool) private _poolsByAdminRole; + mapping(uint256 => mapping(address => bool)) private _poolAdmins; + mapping(uint256 => address[]) private _poolAdminList; + mapping(uint256 => MockSuperfluidPool) private _poolContracts; + + modifier onlyPoolAdmin(uint256 poolId) { + if (!_poolAdmins[poolId][msg.sender]) { + revert NOT_POOL_ADMIN(); + } + _; + } + + function createPool( + ISuperToken _poolSuperToken, + PoolConfig memory, + PoolERC20Metadata memory _erc20Metadata, + Member[] memory _members, + address[] memory _admins, + string memory _metadata + ) external returns (ISuperfluidPool gdaPool) { + poolCounter++; + bytes32 adminRole = keccak256(abi.encodePacked(poolCounter, "admin")); + MockSuperfluidPool pool = new MockSuperfluidPool( + _erc20Metadata.name, + _erc20Metadata.symbol + ); + + _poolsById[poolCounter] = Pool({ + id: poolCounter, + poolAddress: address(pool), + token: address(_poolSuperToken), + metadata: _metadata, + adminRole: adminRole + }); + _poolsByAdminRole[adminRole] = _poolsById[poolCounter]; + _poolContracts[poolCounter] = pool; + + for (uint256 i = 0; i < _admins.length; i++) { + _poolAdmins[poolCounter][_admins[i]] = true; + _poolAdminList[poolCounter].push(_admins[i]); + } + + _updateMembers(poolCounter, _members); + + emit PoolCreated( + poolCounter, + address(pool), + address(_poolSuperToken), + _metadata + ); + + return ISuperfluidPool(address(pool)); + } + + function addPoolAdmin(uint256 poolId, address admin) + external + onlyPoolAdmin(poolId) + { + if (admin == address(0)) revert ZERO_ADDRESS(); + _poolAdmins[poolId][admin] = true; + _poolAdminList[poolId].push(admin); + } + + function removePoolAdmin(uint256 poolId, address admin) + external + onlyPoolAdmin(poolId) + { + _poolAdmins[poolId][admin] = false; + } + + function updatePoolAdmins(uint256 poolId, Admin[] memory admins) + external + onlyPoolAdmin(poolId) + { + for (uint256 i = 0; i < admins.length; i++) { + if (admins[i].status == AdminStatus.Added) { + _poolAdmins[poolId][admins[i].account] = true; + _poolAdminList[poolId].push(admins[i].account); + } else { + _poolAdmins[poolId][admins[i].account] = false; + } + } + } + + function updateMembersUnits(uint256 poolId, Member[] memory members) + external + onlyPoolAdmin(poolId) + { + _updateMembers(poolId, members); + } + + function updatePoolMetadata(uint256 poolId, string memory metadata) + external + onlyPoolAdmin(poolId) + { + _poolsById[poolId].metadata = metadata; + emit PoolMetadataUpdated(poolId, metadata); + } + + function isPoolAdmin(uint256 poolId, address account) + external + view + returns (bool) + { + return _poolAdmins[poolId][account]; + } + + function getPoolById(uint256 poolId) external view returns (Pool memory pool) { + return _poolsById[poolId]; + } + + function getPoolByAdminRole(bytes32 adminRole) + external + view + returns (Pool memory pool) + { + return _poolsByAdminRole[adminRole]; + } + + function getPoolNameById(uint256 poolId) + external + view + returns (string memory name) + { + return _poolContracts[poolId].name(); + } + + function getPoolSymbolById(uint256 poolId) + external + view + returns (string memory symbol) + { + return _poolContracts[poolId].symbol(); + } + + function getMemberUnits(uint256 poolId, address account) + external + view + returns (uint128) + { + return _poolContracts[poolId].memberUnits(account); + } + + function _updateMembers(uint256 poolId, Member[] memory members) internal { + for (uint256 i = 0; i < members.length; i++) { + _poolContracts[poolId].updateMemberUnits( + members[i].account, + members[i].units + ); + } + } +} diff --git a/skills-lock.json b/skills-lock.json deleted file mode 100644 index 9b92c725..00000000 --- a/skills-lock.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": 1, - "skills": { - "gooddollar": { - "source": "GoodDollar/GoodSkills", - "sourceType": "github", - "skillPath": "skills/gooddollar/SKILL.md", - "computedHash": "d5f8cc5079f386e7170925e61214fb3334176aa6d0fc6bdc50400e535692b377" - } - } -} diff --git a/test/governance/GoodDaoHouses.test.ts b/test/governance/GoodDaoHouses.test.ts new file mode 100644 index 00000000..d4320fa6 --- /dev/null +++ b/test/governance/GoodDaoHouses.test.ts @@ -0,0 +1,343 @@ +import { expect } from "chai"; +import { ethers, upgrades } from "hardhat"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; + +import { createDAO, increaseTime } from "../helpers"; + +const CITIZENS = 0; +const ALIGNMENT = 1; +const PENDING = 1; +const ACTIVE = 2; +const UNSTAKED = 4; + +describe("GoodDaoHouses", () => { + const citizensMinimumStake = 1000; + const alignmentMinimumStake = 2000; + + const fixture = async () => { + const [admin, committee, citizenOne, citizenTwo, alignmentOne, alignmentTwo] = + await ethers.getSigners(); + + const { gd, nameService } = await loadFixture(createDAO); + + const goodDollar = await ethers.getContractAt("IGoodDollar", gd); + const flowSplitter = await ethers.deployContract("MockFlowSplitter"); + const houses = await upgrades.deployProxy( + await ethers.getContractFactory("GoodDaoHouses"), + [ + nameService.address, + admin.address, + committee.address, + citizensMinimumStake, + alignmentMinimumStake + ], + { kind: "uups" } + ); + + return { + admin, + committee, + citizenOne, + citizenTwo, + alignmentOne, + alignmentTwo, + goodDollar, + flowSplitter, + houses + }; + }; + + const registerViaTransferAndCall = async ( + goodDollar, + houses, + signer, + house, + amount, + metadata + ) => { + const data = ethers.utils.defaultAbiCoder.encode( + ["uint8", "string"], + [house, metadata] + ); + + await goodDollar.mint(signer.address, amount); + await goodDollar.connect(signer).transferAndCall(houses.address, amount, data); + }; + + it("registers citizens immediately and alignment members through eligibility plus approval", async () => { + const { committee, citizenOne, alignmentOne, goodDollar, houses } = + await loadFixture(fixture); + + await houses + .connect(committee) + .setAlignmentEligibility(alignmentOne.address, true); + + await registerViaTransferAndCall( + goodDollar, + houses, + citizenOne, + CITIZENS, + citizensMinimumStake, + "citizen-one" + ); + await registerViaTransferAndCall( + goodDollar, + houses, + alignmentOne, + ALIGNMENT, + alignmentMinimumStake, + "alignment-one" + ); + + const citizenMember = await houses.getMember(citizenOne.address); + const alignmentMemberBeforeApproval = await houses.getMember( + alignmentOne.address + ); + + expect(citizenMember.status).to.equal(ACTIVE); + expect(alignmentMemberBeforeApproval.status).to.equal(PENDING); + + await houses.connect(committee).approveAlignmentMember(alignmentOne.address); + + const alignmentMemberAfterApproval = await houses.getMember( + alignmentOne.address + ); + const activeAlignmentMembers = await houses.getActiveMembers(ALIGNMENT); + + expect(alignmentMemberAfterApproval.status).to.equal(ACTIVE); + expect(activeAlignmentMembers).to.deep.equal([alignmentOne.address]); + }); + + it("snapshots voters, allows ballot replacement, and finalizes deterministic units", async () => { + const { + committee, + citizenOne, + citizenTwo, + alignmentOne, + alignmentTwo, + goodDollar, + houses + } = await loadFixture(fixture); + + await houses + .connect(committee) + .setAlignmentEligibility(alignmentOne.address, true); + await houses + .connect(committee) + .setAlignmentEligibility(alignmentTwo.address, true); + + await registerViaTransferAndCall( + goodDollar, + houses, + citizenOne, + CITIZENS, + citizensMinimumStake, + "citizen-one" + ); + await registerViaTransferAndCall( + goodDollar, + houses, + citizenTwo, + CITIZENS, + citizensMinimumStake, + "citizen-two" + ); + await registerViaTransferAndCall( + goodDollar, + houses, + alignmentOne, + ALIGNMENT, + alignmentMinimumStake, + "alignment-one" + ); + await registerViaTransferAndCall( + goodDollar, + houses, + alignmentTwo, + ALIGNMENT, + alignmentMinimumStake, + "alignment-two" + ); + + await houses.connect(committee).approveAlignmentMember(alignmentOne.address); + await houses.connect(committee).approveAlignmentMember(alignmentTwo.address); + + await houses.connect(committee).createAlignmentVote(3600, 1000, "Q1"); + + const voteId = await houses.voteCount(); + const [alignmentVoters, citizensVoters] = await houses.getVoteVoters(voteId); + + expect(alignmentVoters).to.deep.equal([ + alignmentOne.address, + alignmentTwo.address + ]); + expect(citizensVoters).to.deep.equal([ + citizenOne.address, + citizenTwo.address + ]); + + await houses + .connect(alignmentOne) + .castVote(voteId, [alignmentOne.address], [10000]); + await houses + .connect(citizenOne) + .castVote(voteId, [alignmentTwo.address], [10000]); + await houses + .connect(citizenTwo) + .castVote(voteId, [alignmentTwo.address], [10000]); + + await houses + .connect(alignmentTwo) + .castVote(voteId, [alignmentTwo.address], [10000]); + await houses + .connect(alignmentTwo) + .castVote(voteId, [alignmentOne.address], [10000]); + + const [ballotRecipients, ballotAllocations] = await houses.getBallot( + voteId, + alignmentTwo.address + ); + + expect(ballotRecipients).to.deep.equal([alignmentOne.address]); + expect(ballotAllocations[0]).to.equal(10000); + + await increaseTime(3601); + await houses.connect(committee).finalizeAlignmentVote(voteId); + + expect(await houses.getFinalizedUnits(voteId, alignmentOne.address)).to.equal( + 909 + ); + expect(await houses.getFinalizedUnits(voteId, alignmentTwo.address)).to.equal( + 90 + ); + }); + + it("creates the flow splitter pool once, updates units on later votes, and zeroes units on unstake", async () => { + const { + committee, + citizenOne, + citizenTwo, + alignmentOne, + alignmentTwo, + goodDollar, + flowSplitter, + houses + } = await loadFixture(fixture); + + await houses + .connect(committee) + .setAlignmentEligibility(alignmentOne.address, true); + await houses + .connect(committee) + .setAlignmentEligibility(alignmentTwo.address, true); + + await registerViaTransferAndCall( + goodDollar, + houses, + citizenOne, + CITIZENS, + citizensMinimumStake, + "citizen-one" + ); + await registerViaTransferAndCall( + goodDollar, + houses, + citizenTwo, + CITIZENS, + citizensMinimumStake, + "citizen-two" + ); + await registerViaTransferAndCall( + goodDollar, + houses, + alignmentOne, + ALIGNMENT, + alignmentMinimumStake, + "alignment-one" + ); + await registerViaTransferAndCall( + goodDollar, + houses, + alignmentTwo, + ALIGNMENT, + alignmentMinimumStake, + "alignment-two" + ); + + await houses.connect(committee).approveAlignmentMember(alignmentOne.address); + await houses.connect(committee).approveAlignmentMember(alignmentTwo.address); + + await houses.connect(committee).configureFlowSplitter( + flowSplitter.address, + goodDollar.address, + "GoodDao Houses pool", + "GoodDao Houses", + "GDH", + 18, + false, + false + ); + + await houses.connect(committee).createAlignmentVote(3600, 1000, "Q1"); + let voteId = await houses.voteCount(); + + await houses + .connect(alignmentOne) + .castVote(voteId, [alignmentOne.address], [10000]); + await houses + .connect(alignmentTwo) + .castVote(voteId, [alignmentOne.address], [10000]); + await houses + .connect(citizenOne) + .castVote(voteId, [alignmentOne.address], [10000]); + await houses + .connect(citizenTwo) + .castVote(voteId, [alignmentOne.address], [10000]); + + await increaseTime(3601); + await houses.connect(committee).finalizeAlignmentVote(voteId); + await houses.connect(committee).executeVote(voteId); + + let flowConfig = await houses.getFlowSplitterConfig(); + + expect(flowConfig.poolInitialized).to.equal(true); + expect(flowConfig.poolId).to.equal(1); + expect(await flowSplitter.getMemberUnits(1, alignmentOne.address)).to.equal( + 1000 + ); + expect(await flowSplitter.getMemberUnits(1, alignmentTwo.address)).to.equal(0); + + await houses.connect(committee).createAlignmentVote(3600, 1000, "Q2"); + voteId = await houses.voteCount(); + + await houses + .connect(alignmentOne) + .castVote(voteId, [alignmentTwo.address], [10000]); + await houses + .connect(alignmentTwo) + .castVote(voteId, [alignmentTwo.address], [10000]); + await houses + .connect(citizenOne) + .castVote(voteId, [alignmentTwo.address], [10000]); + await houses + .connect(citizenTwo) + .castVote(voteId, [alignmentTwo.address], [10000]); + + await increaseTime(3601); + await houses.connect(committee).finalizeAlignmentVote(voteId); + await houses.connect(committee).executeVote(voteId); + + flowConfig = await houses.getFlowSplitterConfig(); + expect(flowConfig.poolId).to.equal(1); + expect(await flowSplitter.getMemberUnits(1, alignmentOne.address)).to.equal(0); + expect(await flowSplitter.getMemberUnits(1, alignmentTwo.address)).to.equal( + 1000 + ); + + await houses.connect(alignmentTwo).unstake(); + + const alignmentMember = await houses.getMember(alignmentTwo.address); + expect(alignmentMember.status).to.equal(UNSTAKED); + expect(await flowSplitter.getMemberUnits(1, alignmentTwo.address)).to.equal(0); + }); +}); From 6fb988123691eb3ffeb633f10832dae57e655971 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 13:36:37 +0000 Subject: [PATCH 6/6] feat: align GoodDaoHouses term vote flow --- contracts/governance/GoodDaoHouses.sol | 381 +++++++++++++++---------- test/governance/GoodDaoHouses.test.ts | 276 ++++++++++-------- 2 files changed, 378 insertions(+), 279 deletions(-) diff --git a/contracts/governance/GoodDaoHouses.sol b/contracts/governance/GoodDaoHouses.sol index 384afd5a..e95abf83 100644 --- a/contracts/governance/GoodDaoHouses.sol +++ b/contracts/governance/GoodDaoHouses.sol @@ -5,7 +5,6 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; -import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "../Interfaces.sol"; import "../token/ERC677.sol"; @@ -23,15 +22,14 @@ contract GoodDaoHouses is DAOUpgradeableContract, ERC677Receiver { - using EnumerableSet for EnumerableSet.AddressSet; - bytes32 public constant GOVERNANCE_COMMITTEE_ROLE = keccak256("GOVERNANCE_COMMITTEE_ROLE"); uint256 public constant HOUSE_ALIGNMENT_WEIGHT = 40; uint256 public constant HOUSE_CITIZENS_WEIGHT = 4; uint256 public constant BASIS_POINTS = 10000; - uint64 public constant DEFAULT_VOTE_DURATION = 7 days; + uint64 public constant DEFAULT_TERM_DURATION = 90 days; + uint64 public constant DEFAULT_VOTING_TERM_LENGTH = 7 days; enum House { Citizens, @@ -53,7 +51,11 @@ contract GoodDaoHouses is uint64 joinedAt; uint64 updatedAt; uint64 unstakedAt; - string metadata; + string name; + string socialLinks; + string projectWebpage; + string missionStatement; + string distributionStrategy; } struct EligibilityRecord { @@ -66,12 +68,7 @@ contract GoodDaoHouses is struct VoteConfig { uint64 startTime; uint64 endTime; - uint64 finalizedAt; uint64 executedAt; - uint256 totalUnits; - uint256 totalWeight; - string metadata; - bool finalized; bool executed; } @@ -92,20 +89,17 @@ contract GoodDaoHouses is mapping(address => MemberRecord) private _members; mapping(address => EligibilityRecord) private _alignmentEligibility; mapping(House => uint256) public minimumStake; - - EnumerableSet.AddressSet private _activeCitizens; - EnumerableSet.AddressSet private _activeAlignment; + address[] private _memberAccounts; + mapping(address => bool) private _knownMember; + uint64 public termDuration; + uint64 public votingTermLength; uint256 public voteCount; mapping(uint256 => VoteConfig) private _votes; mapping(uint256 => address[]) private _voteRecipients; - mapping(uint256 => address[]) private _voteAlignmentVoters; - mapping(uint256 => address[]) private _voteCitizensVoters; mapping(uint256 => mapping(address => bool)) private _isVoteRecipient; - mapping(uint256 => mapping(address => uint256)) private _voteWeightSnapshot; mapping(uint256 => mapping(address => uint256)) private _voteRecipientWeightedVotes; - mapping(uint256 => mapping(address => uint128)) private _finalizedUnits; mapping(uint256 => mapping(address => address[])) private _voterBallotRecipients; mapping(uint256 => mapping(address => mapping(address => uint256))) private _voterBallotBps; @@ -118,8 +112,7 @@ contract GoodDaoHouses is address indexed account, House indexed house, MemberStatus status, - uint256 amount, - string metadata + uint256 amount ); event MemberApproved(address indexed account, House indexed house); event MemberRevoked(address indexed account, House indexed house); @@ -128,12 +121,9 @@ contract GoodDaoHouses is event VoteCreated( uint256 indexed voteId, uint64 startTime, - uint64 endTime, - uint256 totalUnits, - string metadata + uint64 endTime ); event VoteUpdated(uint256 indexed voteId, address indexed voter); - event VoteFinalized(uint256 indexed voteId, uint256 totalWeight); event VoteExecuted(uint256 indexed voteId, uint256 poolId, address poolAddress); event FlowSplitterConfigured(address indexed splitter, address indexed superToken); event FlowSplitterPoolCreated(uint256 indexed poolId, address poolAddress); @@ -161,6 +151,8 @@ contract GoodDaoHouses is minimumStake[House.Citizens] = citizensMinimumStake; minimumStake[House.Alignment] = alignmentMinimumStake; + termDuration = DEFAULT_TERM_DURATION; + votingTermLength = DEFAULT_VOTING_TERM_LENGTH; emit StakeRequirementSet(House.Citizens, citizensMinimumStake); emit StakeRequirementSet(House.Alignment, alignmentMinimumStake); @@ -196,13 +188,26 @@ contract GoodDaoHouses is function registerAndStake( House house, uint256 amount, - string calldata metadata + string calldata name, + string calldata socialLinks, + string calldata projectWebpage, + string calldata missionStatement, + string calldata distributionStrategy ) external whenNotPaused { require( _goodDollar().transferFrom(msg.sender, address(this), amount), "TF" ); - _registerMember(msg.sender, house, amount, metadata); + _registerMember( + msg.sender, + house, + amount, + name, + socialLinks, + projectWebpage, + missionStatement, + distributionStrategy + ); } function stake(uint256 amount) external whenNotPaused { @@ -225,11 +230,27 @@ contract GoodDaoHouses is return true; } - (House house, string memory metadata) = abi.decode( + ( + House house, + string memory name, + string memory socialLinks, + string memory projectWebpage, + string memory missionStatement, + string memory distributionStrategy + ) = abi.decode( _data, - (House, string) + (House, string, string, string, string, string) + ); + _registerMember( + _from, + house, + _amount, + name, + socialLinks, + projectWebpage, + missionStatement, + distributionStrategy ); - _registerMember(_from, house, _amount, metadata); return true; } @@ -252,7 +273,6 @@ contract GoodDaoHouses is member.status = MemberStatus.Active; member.updatedAt = uint64(block.timestamp); - _activeAlignment.add(account); emit MemberApproved(account, member.house); } @@ -269,7 +289,6 @@ contract GoodDaoHouses is "NA" ); - _removeFromActiveSet(member.house, account); member.status = MemberStatus.Revoked; member.updatedAt = uint64(block.timestamp); @@ -283,7 +302,6 @@ contract GoodDaoHouses is require(amount > 0, "NS"); - _removeFromActiveSet(member.house, msg.sender); member.stakedAmount = 0; member.status = MemberStatus.Unstaked; member.updatedAt = uint64(block.timestamp); @@ -296,76 +314,22 @@ contract GoodDaoHouses is emit MemberUnstaked(msg.sender, member.house, amount); } - function createAlignmentVote( - uint64 duration, - uint256 totalUnits, - string calldata metadata - ) + function castVote(address[] calldata recipients, uint256[] calldata allocations) external - onlyRole(GOVERNANCE_COMMITTEE_ROLE) whenNotPaused - returns (uint256 voteId) { - address[] memory recipients = _activeAlignment.values(); - address[] memory alignmentVoters = _activeAlignment.values(); - address[] memory citizensVoters = _activeCitizens.values(); + (uint256 voteId, uint64 voteStartTime) = _getCurrentVoteWindow(); + uint256 voterWeight = _getVoterWeight(msg.sender, voteStartTime); - require(recipients.length > 0, "NAM"); - - voteId = ++voteCount; - - if (duration == 0) { - duration = DEFAULT_VOTE_DURATION; - } - - VoteConfig storage vote = _votes[voteId]; - vote.startTime = uint64(block.timestamp); - vote.endTime = uint64(block.timestamp) + duration; - vote.totalUnits = totalUnits; - vote.totalWeight = - (alignmentVoters.length * HOUSE_ALIGNMENT_WEIGHT) + - (citizensVoters.length * HOUSE_CITIZENS_WEIGHT); - vote.metadata = metadata; - - for (uint256 i = 0; i < recipients.length; i++) { - _voteRecipients[voteId].push(recipients[i]); - _isVoteRecipient[voteId][recipients[i]] = true; - } - - for (uint256 i = 0; i < alignmentVoters.length; i++) { - address voter = alignmentVoters[i]; - _voteAlignmentVoters[voteId].push(voter); - _voteWeightSnapshot[voteId][voter] = HOUSE_ALIGNMENT_WEIGHT; - } + require(_isVotingPeriod(block.timestamp), "VNP"); + require(voterWeight > 0, "VE"); - for (uint256 i = 0; i < citizensVoters.length; i++) { - address voter = citizensVoters[i]; - _voteCitizensVoters[voteId].push(voter); - _voteWeightSnapshot[voteId][voter] = HOUSE_CITIZENS_WEIGHT; + if (_votes[voteId].startTime == 0) { + _createAlignmentVote(voteId, voteStartTime); } - emit VoteCreated( - voteId, - vote.startTime, - vote.endTime, - totalUnits, - metadata - ); - } - - function castVote( - uint256 voteId, - address[] calldata recipients, - uint256[] calldata allocations - ) external whenNotPaused { VoteConfig storage vote = _votes[voteId]; - uint256 voterWeight = _voteWeightSnapshot[voteId][msg.sender]; - - require(vote.startTime > 0, "VNF"); - require(block.timestamp >= vote.startTime, "VNS"); - require(block.timestamp <= vote.endTime, "VC"); - require(!vote.finalized, "VF"); - require(voterWeight > 0, "VE"); + require(!vote.executed, "VAE"); require(recipients.length == allocations.length, "LM"); require(recipients.length > 0, "EB"); @@ -390,48 +354,13 @@ contract GoodDaoHouses is storedRecipients.push(recipient); _voterBallotBps[voteId][msg.sender][recipient] = allocation; _voteRecipientWeightedVotes[voteId][recipient] += - allocation * - voterWeight; + (allocation * voterWeight) / + BASIS_POINTS; } emit VoteUpdated(voteId, msg.sender); } - function finalizeAlignmentVote(uint256 voteId) - external - onlyRole(GOVERNANCE_COMMITTEE_ROLE) - whenNotPaused - { - VoteConfig storage vote = _votes[voteId]; - address[] storage recipients = _voteRecipients[voteId]; - uint256 voteWeightTotal; - - require(vote.startTime > 0, "VNF"); - require(block.timestamp > vote.endTime, "VSO"); - require(!vote.finalized, "VF"); - - for (uint256 i = 0; i < recipients.length; i++) { - voteWeightTotal += _voteRecipientWeightedVotes[voteId][recipients[i]]; - } - - for (uint256 i = 0; i < recipients.length; i++) { - address recipient = recipients[i]; - if (voteWeightTotal == 0 || vote.totalUnits == 0) { - _finalizedUnits[voteId][recipient] = 0; - } else { - _finalizedUnits[voteId][recipient] = uint128( - (_voteRecipientWeightedVotes[voteId][recipient] * vote.totalUnits) / - voteWeightTotal - ); - } - } - - vote.finalized = true; - vote.finalizedAt = uint64(block.timestamp); - - emit VoteFinalized(voteId, voteWeightTotal); - } - function executeVote(uint256 voteId) external onlyRole(GOVERNANCE_COMMITTEE_ROLE) @@ -444,7 +373,8 @@ contract GoodDaoHouses is recipients.length ); - require(vote.finalized, "VNFN"); + require(vote.startTime > 0, "VNF"); + require(block.timestamp > vote.endTime, "VSO"); require(!vote.executed, "VAE"); require(flowConfig.splitter != address(0), "FNC"); require(flowConfig.superToken != address(0), "STNC"); @@ -453,7 +383,7 @@ contract GoodDaoHouses is address recipient = recipients[i]; members[i] = IFlowSplitter.Member({ account: recipient, - units: _finalizedUnits[voteId][recipient] + units: uint128(_voteRecipientWeightedVotes[voteId][recipient]) }); } @@ -595,10 +525,26 @@ contract GoodDaoHouses is view returns (address[] memory) { - return - house == House.Alignment - ? _activeAlignment.values() - : _activeCitizens.values(); + uint256 activeCount; + for (uint256 i = 0; i < _memberAccounts.length; i++) { + MemberRecord storage member = _members[_memberAccounts[i]]; + if (member.house == house && member.status == MemberStatus.Active) { + activeCount++; + } + } + + address[] memory activeMembers = new address[](activeCount); + uint256 index; + for (uint256 i = 0; i < _memberAccounts.length; i++) { + address account = _memberAccounts[i]; + MemberRecord storage member = _members[account]; + if (member.house == house && member.status == MemberStatus.Active) { + activeMembers[index] = account; + index++; + } + } + + return activeMembers; } function getVote(uint256 voteId) external view returns (VoteConfig memory) { @@ -621,7 +567,47 @@ contract GoodDaoHouses is address[] memory citizensVoters ) { - return (_voteAlignmentVoters[voteId], _voteCitizensVoters[voteId]); + VoteConfig storage vote = _votes[voteId]; + uint256 alignmentCount; + uint256 citizensCount; + + for (uint256 i = 0; i < _memberAccounts.length; i++) { + MemberRecord storage member = _members[_memberAccounts[i]]; + if ( + member.status != MemberStatus.Active || member.joinedAt > vote.startTime + ) { + continue; + } + + if (member.house == House.Alignment) { + alignmentCount++; + } else if (member.house == House.Citizens) { + citizensCount++; + } + } + + alignmentVoters = new address[](alignmentCount); + citizensVoters = new address[](citizensCount); + uint256 alignmentIndex; + uint256 citizensIndex; + + for (uint256 i = 0; i < _memberAccounts.length; i++) { + address account = _memberAccounts[i]; + MemberRecord storage member = _members[account]; + if ( + member.status != MemberStatus.Active || member.joinedAt > vote.startTime + ) { + continue; + } + + if (member.house == House.Alignment) { + alignmentVoters[alignmentIndex] = account; + alignmentIndex++; + } else if (member.house == House.Citizens) { + citizensVoters[citizensIndex] = account; + citizensIndex++; + } + } } function getBallot(uint256 voteId, address voter) @@ -641,7 +627,16 @@ contract GoodDaoHouses is view returns (uint128) { - return _finalizedUnits[voteId][recipient]; + return uint128(_voteRecipientWeightedVotes[voteId][recipient]); + } + + function getCurrentVoteId() external view returns (uint256) { + (uint256 voteId, ) = _getCurrentVoteWindow(); + return voteId; + } + + function isVotingPeriod() external view returns (bool) { + return _isVotingPeriod(block.timestamp); } function getFlowSplitterConfig() @@ -656,7 +651,11 @@ contract GoodDaoHouses is address account, House house, uint256 amount, - string memory metadata + string memory name, + string memory socialLinks, + string memory projectWebpage, + string memory missionStatement, + string memory distributionStrategy ) internal { MemberRecord storage member = _members[account]; uint64 joinedAt = member.joinedAt == 0 @@ -686,20 +685,19 @@ contract GoodDaoHouses is joinedAt: joinedAt, updatedAt: uint64(block.timestamp), unstakedAt: 0, - metadata: metadata + name: name, + socialLinks: socialLinks, + projectWebpage: projectWebpage, + missionStatement: missionStatement, + distributionStrategy: distributionStrategy }); - if (house == House.Citizens) { - _activeCitizens.add(account); + if (!_knownMember[account]) { + _knownMember[account] = true; + _memberAccounts.push(account); } - emit MemberRegistered( - account, - house, - _members[account].status, - amount, - metadata - ); + emit MemberRegistered(account, house, _members[account].status, amount); } function _addStake(address account, uint256 amount) internal { @@ -725,8 +723,8 @@ contract GoodDaoHouses is uint256 previousAllocation = _voterBallotBps[voteId][voter][recipient]; if (previousAllocation > 0) { _voteRecipientWeightedVotes[voteId][recipient] -= - previousAllocation * - voterWeight; + (previousAllocation * voterWeight) / + BASIS_POINTS; delete _voterBallotBps[voteId][voter][recipient]; } } @@ -734,6 +732,47 @@ contract GoodDaoHouses is delete _voterBallotRecipients[voteId][voter]; } + function _createAlignmentVote(uint256 voteId, uint64 voteStartTime) internal { + uint64 voteEndTime = voteStartTime + votingTermLength; + uint256 recipientCount; + + for (uint256 i = 0; i < _memberAccounts.length; i++) { + MemberRecord storage member = _members[_memberAccounts[i]]; + if ( + member.house == House.Alignment && + member.status == MemberStatus.Active && + member.joinedAt <= voteStartTime + ) { + recipientCount++; + } + } + + require(recipientCount > 0, "NAM"); + + VoteConfig storage vote = _votes[voteId]; + vote.startTime = voteStartTime; + vote.endTime = voteEndTime; + + for (uint256 i = 0; i < _memberAccounts.length; i++) { + address account = _memberAccounts[i]; + MemberRecord storage member = _members[account]; + if ( + member.house == House.Alignment && + member.status == MemberStatus.Active && + member.joinedAt <= voteStartTime + ) { + _voteRecipients[voteId].push(account); + _isVoteRecipient[voteId][account] = true; + } + } + + if (voteId > voteCount) { + voteCount = voteId; + } + + emit VoteCreated(voteId, voteStartTime, voteEndTime); + } + function _clearMemberUnits(address account) internal { if (!_flowSplitterConfig.poolInitialized) { return; @@ -748,12 +787,42 @@ contract GoodDaoHouses is ); } - function _removeFromActiveSet(House house, address account) internal { - if (house == House.Alignment) { - _activeAlignment.remove(account); - } else { - _activeCitizens.remove(account); + function _getCurrentVoteWindow() + internal + view + returns (uint256 voteId, uint64 voteStartTime) + { + voteId = block.timestamp / termDuration; + voteStartTime = uint64(voteId * termDuration); + } + + function _getVoterWeight(address voter, uint64 voteStartTime) + internal + view + returns (uint256) + { + MemberRecord storage member = _members[voter]; + if ( + member.status != MemberStatus.Active || + member.joinedAt == 0 || + member.joinedAt > voteStartTime + ) { + return 0; } + + if (member.house == House.Alignment) { + return HOUSE_ALIGNMENT_WEIGHT; + } + + if (member.house == House.Citizens) { + return HOUSE_CITIZENS_WEIGHT; + } + + return 0; + } + + function _isVotingPeriod(uint256 timestamp) internal view returns (bool) { + return timestamp % termDuration <= votingTermLength; } function _goodDollar() internal view returns (IGoodDollar) { diff --git a/test/governance/GoodDaoHouses.test.ts b/test/governance/GoodDaoHouses.test.ts index d4320fa6..425de8cb 100644 --- a/test/governance/GoodDaoHouses.test.ts +++ b/test/governance/GoodDaoHouses.test.ts @@ -13,9 +13,18 @@ const UNSTAKED = 4; describe("GoodDaoHouses", () => { const citizensMinimumStake = 1000; const alignmentMinimumStake = 2000; + const alignmentForumUrl = "https://forum.gooddollar.org/t/alignment-one"; const fixture = async () => { - const [admin, committee, citizenOne, citizenTwo, alignmentOne, alignmentTwo] = + const [ + admin, + committee, + citizenOne, + citizenTwo, + alignmentOne, + alignmentTwo, + lateCitizen + ] = await ethers.getSigners(); const { gd, nameService } = await loadFixture(createDAO); @@ -41,6 +50,7 @@ describe("GoodDaoHouses", () => { citizenTwo, alignmentOne, alignmentTwo, + lateCitizen, goodDollar, flowSplitter, houses @@ -53,18 +63,74 @@ describe("GoodDaoHouses", () => { signer, house, amount, - metadata + details ) => { const data = ethers.utils.defaultAbiCoder.encode( - ["uint8", "string"], - [house, metadata] + ["uint8", "string", "string", "string", "string", "string"], + [ + house, + details.name, + details.socialLinks ?? "", + details.projectWebpage ?? "", + details.missionStatement ?? "", + details.distributionStrategy ?? "" + ] ); await goodDollar.mint(signer.address, amount); await goodDollar.connect(signer).transferAndCall(houses.address, amount, data); }; - it("registers citizens immediately and alignment members through eligibility plus approval", async () => { + const registerCitizen = async (goodDollar, houses, signer, name = "citizen") => + registerViaTransferAndCall(goodDollar, houses, signer, CITIZENS, citizensMinimumStake, { + name, + socialLinks: "https://social.example/" + name + }); + + const registerAlignment = async ( + goodDollar, + houses, + signer, + name, + distributionStrategy = alignmentForumUrl + ) => + registerViaTransferAndCall( + goodDollar, + houses, + signer, + ALIGNMENT, + alignmentMinimumStake, + { + name, + projectWebpage: `https://${name}.example`, + missionStatement: `${name} mission`, + distributionStrategy + } + ); + + const moveToNextVotingWindow = async houses => { + const latestBlock = await ethers.provider.getBlock("latest"); + const termDuration = (await houses.termDuration()).toNumber(); + const offset = latestBlock.timestamp % termDuration; + const delta = offset === 0 ? 1 : termDuration - offset + 1; + + await increaseTime(delta); + + return houses.getCurrentVoteId(); + }; + + const movePastVotingWindow = async houses => { + const latestBlock = await ethers.provider.getBlock("latest"); + const termDuration = (await houses.termDuration()).toNumber(); + const votingTermLength = (await houses.votingTermLength()).toNumber(); + const offset = latestBlock.timestamp % termDuration; + + if (offset <= votingTermLength) { + await increaseTime(votingTermLength - offset + 1); + } + }; + + it("writes house fields on chain and approves alignment members after eligibility", async () => { const { committee, citizenOne, alignmentOne, goodDollar, houses } = await loadFixture(fixture); @@ -72,21 +138,13 @@ describe("GoodDaoHouses", () => { .connect(committee) .setAlignmentEligibility(alignmentOne.address, true); - await registerViaTransferAndCall( - goodDollar, - houses, - citizenOne, - CITIZENS, - citizensMinimumStake, - "citizen-one" - ); - await registerViaTransferAndCall( + await registerCitizen(goodDollar, houses, citizenOne, "citizen-one"); + await registerAlignment( goodDollar, houses, alignmentOne, - ALIGNMENT, - alignmentMinimumStake, - "alignment-one" + "alignment-one", + alignmentForumUrl ); const citizenMember = await houses.getMember(citizenOne.address); @@ -95,7 +153,20 @@ describe("GoodDaoHouses", () => { ); expect(citizenMember.status).to.equal(ACTIVE); + expect(citizenMember.name).to.equal("citizen-one"); + expect(citizenMember.socialLinks).to.equal( + "https://social.example/citizen-one" + ); expect(alignmentMemberBeforeApproval.status).to.equal(PENDING); + expect(alignmentMemberBeforeApproval.projectWebpage).to.equal( + "https://alignment-one.example" + ); + expect(alignmentMemberBeforeApproval.missionStatement).to.equal( + "alignment-one mission" + ); + expect(alignmentMemberBeforeApproval.distributionStrategy).to.equal( + alignmentForumUrl + ); await houses.connect(committee).approveAlignmentMember(alignmentOne.address); @@ -108,13 +179,14 @@ describe("GoodDaoHouses", () => { expect(activeAlignmentMembers).to.deep.equal([alignmentOne.address]); }); - it("snapshots voters, allows ballot replacement, and finalizes deterministic units", async () => { + it("creates the term vote on first ballot, blocks late joiners, and stores direct weighted units", async () => { const { committee, citizenOne, citizenTwo, alignmentOne, alignmentTwo, + lateCitizen, goodDollar, houses } = await loadFixture(fixture); @@ -126,89 +198,79 @@ describe("GoodDaoHouses", () => { .connect(committee) .setAlignmentEligibility(alignmentTwo.address, true); - await registerViaTransferAndCall( - goodDollar, - houses, - citizenOne, - CITIZENS, - citizensMinimumStake, - "citizen-one" - ); - await registerViaTransferAndCall( - goodDollar, - houses, - citizenTwo, - CITIZENS, - citizensMinimumStake, - "citizen-two" - ); - await registerViaTransferAndCall( - goodDollar, - houses, - alignmentOne, - ALIGNMENT, - alignmentMinimumStake, - "alignment-one" - ); - await registerViaTransferAndCall( - goodDollar, - houses, - alignmentTwo, - ALIGNMENT, - alignmentMinimumStake, - "alignment-two" - ); + await registerCitizen(goodDollar, houses, citizenOne, "citizen-one"); + await registerCitizen(goodDollar, houses, citizenTwo, "citizen-two"); + await registerAlignment(goodDollar, houses, alignmentOne, "alignment-one"); + await registerAlignment(goodDollar, houses, alignmentTwo, "alignment-two"); await houses.connect(committee).approveAlignmentMember(alignmentOne.address); await houses.connect(committee).approveAlignmentMember(alignmentTwo.address); - await houses.connect(committee).createAlignmentVote(3600, 1000, "Q1"); + const voteId = await moveToNextVotingWindow(houses); - const voteId = await houses.voteCount(); const [alignmentVoters, citizensVoters] = await houses.getVoteVoters(voteId); - expect(alignmentVoters).to.deep.equal([ - alignmentOne.address, - alignmentTwo.address - ]); - expect(citizensVoters).to.deep.equal([ - citizenOne.address, - citizenTwo.address - ]); + expect(alignmentVoters).to.deep.equal([]); + expect(citizensVoters).to.deep.equal([]); await houses .connect(alignmentOne) - .castVote(voteId, [alignmentOne.address], [10000]); + .castVote([alignmentOne.address], [10000]); await houses .connect(citizenOne) - .castVote(voteId, [alignmentTwo.address], [10000]); + .castVote([alignmentTwo.address], [10000]); await houses .connect(citizenTwo) - .castVote(voteId, [alignmentTwo.address], [10000]); + .castVote([alignmentTwo.address], [10000]); await houses .connect(alignmentTwo) - .castVote(voteId, [alignmentTwo.address], [10000]); + .castVote([alignmentTwo.address], [10000]); await houses .connect(alignmentTwo) - .castVote(voteId, [alignmentOne.address], [10000]); + .castVote([alignmentOne.address], [10000]); + + const createdVoteId = await houses.getCurrentVoteId(); + const vote = await houses.getVote(createdVoteId); + const [createdAlignmentVoters, createdCitizensVoters] = + await houses.getVoteVoters(createdVoteId); + + expect(createdVoteId).to.equal(voteId); + expect(createdAlignmentVoters).to.deep.equal([ + alignmentOne.address, + alignmentTwo.address + ]); + expect(createdCitizensVoters).to.deep.equal([ + citizenOne.address, + citizenTwo.address + ]); + expect(vote.startTime).to.equal( + createdVoteId.mul(await houses.termDuration()) + ); + + await registerCitizen(goodDollar, houses, lateCitizen, "late-citizen"); + + await expect( + houses.connect(lateCitizen).castVote([alignmentOne.address], [10000]) + ).to.be.revertedWith("VE"); const [ballotRecipients, ballotAllocations] = await houses.getBallot( - voteId, + createdVoteId, alignmentTwo.address ); expect(ballotRecipients).to.deep.equal([alignmentOne.address]); expect(ballotAllocations[0]).to.equal(10000); - await increaseTime(3601); - await houses.connect(committee).finalizeAlignmentVote(voteId); - - expect(await houses.getFinalizedUnits(voteId, alignmentOne.address)).to.equal( - 909 + expect( + await houses.getFinalizedUnits(createdVoteId, alignmentOne.address) + ).to.equal( + 80 ); - expect(await houses.getFinalizedUnits(voteId, alignmentTwo.address)).to.equal( - 90 + expect( + await houses.getFinalizedUnits(createdVoteId, alignmentTwo.address) + ).to.equal( + 8 ); }); @@ -231,38 +293,10 @@ describe("GoodDaoHouses", () => { .connect(committee) .setAlignmentEligibility(alignmentTwo.address, true); - await registerViaTransferAndCall( - goodDollar, - houses, - citizenOne, - CITIZENS, - citizensMinimumStake, - "citizen-one" - ); - await registerViaTransferAndCall( - goodDollar, - houses, - citizenTwo, - CITIZENS, - citizensMinimumStake, - "citizen-two" - ); - await registerViaTransferAndCall( - goodDollar, - houses, - alignmentOne, - ALIGNMENT, - alignmentMinimumStake, - "alignment-one" - ); - await registerViaTransferAndCall( - goodDollar, - houses, - alignmentTwo, - ALIGNMENT, - alignmentMinimumStake, - "alignment-two" - ); + await registerCitizen(goodDollar, houses, citizenOne, "citizen-one"); + await registerCitizen(goodDollar, houses, citizenTwo, "citizen-two"); + await registerAlignment(goodDollar, houses, alignmentOne, "alignment-one"); + await registerAlignment(goodDollar, houses, alignmentTwo, "alignment-two"); await houses.connect(committee).approveAlignmentMember(alignmentOne.address); await houses.connect(committee).approveAlignmentMember(alignmentTwo.address); @@ -278,24 +312,22 @@ describe("GoodDaoHouses", () => { false ); - await houses.connect(committee).createAlignmentVote(3600, 1000, "Q1"); - let voteId = await houses.voteCount(); + let voteId = await moveToNextVotingWindow(houses); await houses .connect(alignmentOne) - .castVote(voteId, [alignmentOne.address], [10000]); + .castVote([alignmentOne.address], [10000]); await houses .connect(alignmentTwo) - .castVote(voteId, [alignmentOne.address], [10000]); + .castVote([alignmentOne.address], [10000]); await houses .connect(citizenOne) - .castVote(voteId, [alignmentOne.address], [10000]); + .castVote([alignmentOne.address], [10000]); await houses .connect(citizenTwo) - .castVote(voteId, [alignmentOne.address], [10000]); + .castVote([alignmentOne.address], [10000]); - await increaseTime(3601); - await houses.connect(committee).finalizeAlignmentVote(voteId); + await movePastVotingWindow(houses); await houses.connect(committee).executeVote(voteId); let flowConfig = await houses.getFlowSplitterConfig(); @@ -303,35 +335,33 @@ describe("GoodDaoHouses", () => { expect(flowConfig.poolInitialized).to.equal(true); expect(flowConfig.poolId).to.equal(1); expect(await flowSplitter.getMemberUnits(1, alignmentOne.address)).to.equal( - 1000 + 88 ); expect(await flowSplitter.getMemberUnits(1, alignmentTwo.address)).to.equal(0); - await houses.connect(committee).createAlignmentVote(3600, 1000, "Q2"); - voteId = await houses.voteCount(); + voteId = await moveToNextVotingWindow(houses); await houses .connect(alignmentOne) - .castVote(voteId, [alignmentTwo.address], [10000]); + .castVote([alignmentTwo.address], [10000]); await houses .connect(alignmentTwo) - .castVote(voteId, [alignmentTwo.address], [10000]); + .castVote([alignmentTwo.address], [10000]); await houses .connect(citizenOne) - .castVote(voteId, [alignmentTwo.address], [10000]); + .castVote([alignmentTwo.address], [10000]); await houses .connect(citizenTwo) - .castVote(voteId, [alignmentTwo.address], [10000]); + .castVote([alignmentTwo.address], [10000]); - await increaseTime(3601); - await houses.connect(committee).finalizeAlignmentVote(voteId); + await movePastVotingWindow(houses); await houses.connect(committee).executeVote(voteId); flowConfig = await houses.getFlowSplitterConfig(); expect(flowConfig.poolId).to.equal(1); expect(await flowSplitter.getMemberUnits(1, alignmentOne.address)).to.equal(0); expect(await flowSplitter.getMemberUnits(1, alignmentTwo.address)).to.equal( - 1000 + 88 ); await houses.connect(alignmentTwo).unstake();