Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,39 @@ TRANSLATE_ALL__MAX_MESSAGES_PER_MINUTE=30
GROUP_PREFERENCES__PERSIST=true
GROUP_PREFERENCES__STORAGE_PATH=/data/group_prefs.enc

# Poa DAO tools (read + gated on-chain writes against a TaskManager)
# Off by default. See docs/poa-integration.md.
TOOLS__POA__ENABLED=false
# Chain the org lives on and its Poa subgraph (Gnosis shown; Poa gov lives on Arbitrum).
TOOLS__POA__RPC_URL=https://rpc.gnosischain.com
TOOLS__POA__SUBGRAPH_URL=https://api.studio.thegraph.com/query/73367/poa-gnosis-v-1/version/latest
TOOLS__POA__NETWORK_NAME=gnosis
# The org's TaskManager proxy address (find via the subgraph or OrgRegistry).
# TOOLS__POA__TASK_MANAGER=0x...
# Wallet: leave PRIVATE_KEY unset in production to derive the key inside the TEE
# (dstack derive_key). Only set PRIVATE_KEY for local dev against a testnet.
# TOOLS__POA__PRIVATE_KEY=0x...
TOOLS__POA__DERIVE_KEY_PATH=poa-tools/task-manager-wallet
# Write tools (create/update/assign/complete/reject/cancel task) are additionally
# gated: they are only offered to the allowlisted senders below, and only when
# ENABLE_WRITES=true. The bot wallet must also hold project-manager / hat rights
# on-chain (see docs/poa-integration.md).
TOOLS__POA__ENABLE_WRITES=false
# TOOLS__POA__AUTHORIZED_SENDERS=+15551230001,+15551230002
# HybridVoting proxy address — required for governance tools (poa_create_poll / poa_vote / poa_list_proposals).
# TOOLS__POA__VOTING_CONTRACT=0x...
# Seconds a staged value-moving action (poa_complete_task) stays confirmable via !poa-confirm.
TOOLS__POA__CONFIRM_TTL_SECS=300

# Autonomous board steward: periodically posts a digest of expired/at-risk claims.
TOOLS__POA__STEWARD_ENABLED=false
# Signal target (group id or number) to post digests to, and the account to send from.
# TOOLS__POA__STEWARD_TARGET=group.xxxxx
# TOOLS__POA__STEWARD_FROM=+15551230000
TOOLS__POA__STEWARD_INTERVAL_SECS=21600
TOOLS__POA__STEWARD_WARN_WINDOW_SECS=86400
TOOLS__POA__STEWARD_QUIET_WHEN_EMPTY=true

# Payment Configuration (x402)
# Set to true to enable credit tracking and payment system
PAYMENTS__ENABLED=false
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/commitlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: commitlint

on:
pull_request:
push:
branches: [main, master]

jobs:
commitlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
- run: npm ci
- name: Lint commit messages
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
npx --no -- commitlint --from "${{ github.event.pull_request.base.sha }}" --to "${{ github.event.pull_request.head.sha }}" --verbose
else
npx --no -- commitlint --from "${{ github.event.before }}" --to "${{ github.sha }}" --verbose
fi
42 changes: 42 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: test

on:
pull_request:
push:
branches: [main, master]

jobs:
rust:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable
with:
# Keep in sync with scripts/ci.sh local prerequisites.
components: rustfmt, clippy, llvm-tools-preview

- uses: Swatinem/rust-cache@v2

- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov

- name: Format
run: cargo fmt --all -- --check

- name: Clippy
run: cargo clippy --workspace --all-targets -- -D warnings

- name: Test with coverage gate (≥90% lines)
run: |
cargo llvm-cov --workspace --lcov --output-path lcov.info \
--fail-under-lines 90 \
--ignore-filename-regex '(^|/)main\.rs$'

- name: Upload LCOV
if: always()
uses: actions/upload-artifact@v4
with:
name: lcov
path: lcov.info
if-no-files-found: ignore
18 changes: 18 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ members = [
"crates/signal-client",
"crates/signal-registration-proxy",
"crates/tools",
"crates/poa-tools",
"crates/x402-payments",
"crates/whisper-client",
]
Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ This project implements a Signal bot that runs inside a Dstack-powered TEE (Inte
- In-memory conversation storage (no external persistence)
- Group chat support with shared conversation context
- OpenAI-compatible API integration
- Tool use (web search, weather, calculator) and **Poa DAO task tools** — read an
org's projects/tasks and, for authorized operators, create and manage tasks
on-chain with a TEE-derived wallet ([docs/poa-integration.md](docs/poa-integration.md))

## Bot Commands

Expand Down Expand Up @@ -61,6 +64,34 @@ Bob: "What's Alice's favorite color?"
Bot: "Alice mentioned her favorite color is blue"
```

## Poa DAO Tools

When `TOOLS__POA__ENABLED=true`, the bot can operate a [Poa](https://github.com/poa-box)
DAO org's task board. Ask it things like "what open tasks are there?" or "create a
5 PT task in the Docs project to fix the changelog".

- **Read tools** (everyone): projects, tasks, proposals, wallet info — backed by
the Poa subgraph.
- **Write tools** (allowlisted operators only), signed by a wallet **derived
inside the TEE** (no key leaves the enclave):
- *task authoring*: create / update / assign / complete / reject / cancel task
- *participation*: claim / submit / apply — the bot does work and **earns** PT
- *governance*: create non-executable polls, vote

Extra safety on top of the allowlist:

- **Confirmation** — value-moving actions (`poa_complete_task`, which mints a
payout) are staged and require a deterministic `!poa-confirm <code>` reply from
the same sender; the LLM cannot self-confirm.
- **Board steward** — an optional background loop posts a digest of expired /
at-risk claims to a Signal group (read-only; never sends transactions).

Writes are gated by `TOOLS__POA__ENABLE_WRITES` plus a
`TOOLS__POA__AUTHORIZED_SENDERS` allowlist, and the chain enforces that the bot's
wallet holds the right TaskManager permission. Full setup — including how to grant
the wallet project-manager rights on the Poa side — is in
[docs/poa-integration.md](docs/poa-integration.md).

## Verification

Users can cryptographically verify the bot runs in a genuine TEE:
Expand Down
45 changes: 45 additions & 0 deletions crates/poa-tools/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
[package]
name = "poa-tools"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
description = "Poa (proof-of-authority DAO protocol) tools for signal-bot-tee: query orgs via subgraph and update TaskManager on-chain"

[dependencies]
tools = { path = "../tools" }

# Async runtime
tokio = { workspace = true }
async-trait = { workspace = true }

# HTTP client (subgraph queries)
reqwest = { version = "0.11", features = ["json"] }

# Serialization
serde = { workspace = true }
serde_json = { workspace = true }

# Error handling
thiserror = { workspace = true }

# Logging
tracing = { workspace = true }

# Hashing (rejection/metadata hashes)
sha2 = { workspace = true }

# EVM support via Alloy (same version as x402-payments)
alloy = { version = "1.1", features = [
"contract",
"network",
"provider-http",
"rpc-types",
"signer-local",
"sol-types",
] }

[dev-dependencies]
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
wiremock = "0.5"
Loading