gotchi-dao-voting
Check active Aavegotchi DAO proposals and vote on Snapshot via Bankr EIP-712 signatures.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install openclaw-skills-gotchi-dao-voting
Repository
Skill path: skills/aaigotchi/gotchi-dao-voting
Check active Aavegotchi DAO proposals and vote on Snapshot via Bankr EIP-712 signatures.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: openclaw.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install gotchi-dao-voting into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/openclaw/skills before adding gotchi-dao-voting to shared team environments
- Use gotchi-dao-voting for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: gotchi-dao-voting
description: Check active Aavegotchi DAO proposals and vote on Snapshot via Bankr EIP-712 signatures.
homepage: https://github.com/aaigotchi/gotchi-dao-voting
metadata:
openclaw:
requires:
bins:
- curl
- jq
env:
- BANKR_API_KEY
---
# gotchi-dao-voting
Vote on Snapshot proposals for `aavegotchi.eth`.
## Scripts
- `./scripts/list-proposals.sh`
- Lists active proposals and your VP per proposal.
- `./scripts/vote.sh [--dry-run] <proposal-id> <choice>`
- Submits signed vote through Snapshot sequencer.
- `--dry-run` prints typed data and exits without signing/submitting.
## Choice Formats
- Single-choice proposal: numeric option, e.g. `2`
- Weighted proposal: JSON object string, e.g. `'{"2":2238}'`
- If you pass just `2` for a weighted vote, script auto-converts to `{"2":<floor(vp)>}`.
## Config
`config.json` keys:
- `wallet`
- `space`
- `snapshotApiUrl`
- `snapshotSequencer`
## Security
- Uses Bankr signing API (no local private key usage).
- Off-chain Snapshot voting (no gas transaction).
- Input validation for proposal ID, wallet, choice format, and choice range.
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### scripts/list-proposals.sh
```bash
#!/usr/bin/env bash
# List active Aavegotchi DAO proposals on Snapshot
set -euo pipefail
usage() {
cat <<USAGE
Usage: ./scripts/list-proposals.sh
Lists active proposals in configured Snapshot space and prints
wallet voting power per proposal.
USAGE
}
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
usage
exit 0
fi
if [ "$#" -gt 0 ]; then
usage
exit 1
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib.sh
source "$SCRIPT_DIR/lib.sh"
require_tools
load_config
echo "π³οΈ AAVEGOTCHI DAO ACTIVE PROPOSALS"
echo "==================================="
echo
QUERY='query($space:String!){ proposals(first: 20, skip: 0, where: {space_in: [$space], state: "active"}, orderBy: "created", orderDirection: desc) { id title end state choices type } }'
VARS="$(jq -n --arg space "$SPACE" '{space:$space}')"
PROPOSALS="$(snapshot_query "$QUERY" "$VARS")"
if snapshot_has_errors "$PROPOSALS"; then
echo "β Snapshot query error"
echo "$PROPOSALS" | jq '.errors'
exit 1
fi
COUNT="$(echo "$PROPOSALS" | jq '.data.proposals | length')"
if [ "$COUNT" = "0" ]; then
echo "π No active proposals found"
echo
echo "π Check: https://snapshot.org/#/$SPACE"
exit 0
fi
echo "π Found $COUNT active proposal(s)"
echo
echo "$PROPOSALS" | jq -r '.data.proposals[] | @base64' | while read -r p64; do
proposal="$(printf '%s' "$p64" | base64 -d)"
ID="$(echo "$proposal" | jq -r '.id')"
TITLE="$(echo "$proposal" | jq -r '.title')"
TYPE="$(echo "$proposal" | jq -r '.type')"
END="$(echo "$proposal" | jq -r '.end')"
CHOICES="$(echo "$proposal" | jq -r '.choices | length')"
END_DATE="$(format_utc "$END")"
echo "βββββββββββββββββββββββββββββββββββββ"
echo "π $TITLE"
echo
echo " ID: $ID"
echo " Type: $TYPE"
echo " Choices: $CHOICES"
echo " Ends: $END_DATE"
echo
VP_QUERY='query($voter:String!, $space:String!, $proposal:String!){ vp(voter: $voter, space: $space, proposal: $proposal) { vp } }'
VP_VARS="$(jq -n --arg voter "$WALLET" --arg space "$SPACE" --arg proposal "$ID" '{voter:$voter,space:$space,proposal:$proposal}')"
VP_DATA="$(snapshot_query "$VP_QUERY" "$VP_VARS")"
if snapshot_has_errors "$VP_DATA"; then
echo " β οΈ Could not fetch VP"
else
VP="$(echo "$VP_DATA" | jq -r '.data.vp.vp // 0')"
if [ "$VP" != "0" ] && [ "$VP" != "null" ]; then
printf " πͺ Your VP: %.2f\n" "$VP"
else
echo " β οΈ Your VP: 0 (cannot vote)"
fi
fi
echo " π https://snapshot.org/#/$SPACE/proposal/$ID"
echo
done
echo "βββββββββββββββββββββββββββββββββββββ"
echo
echo "π‘ To vote, use: ./scripts/vote.sh <proposal-id> <choice>"
```
### scripts/vote.sh
```bash
#!/usr/bin/env bash
# Vote on Aavegotchi Snapshot proposals via Bankr signature
set -euo pipefail
usage() {
cat <<USAGE
Usage: $0 [--dry-run] <proposal-id> <choice>
Examples:
Single-choice: $0 0xabc123... 2
Weighted: $0 0xabc123... '{"2": 2238}'
Dry run: $0 --dry-run 0xabc123... 2
USAGE
}
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib.sh
source "$SCRIPT_DIR/lib.sh"
DRY_RUN=0
POSITIONAL=()
while [ "$#" -gt 0 ]; do
case "$1" in
--dry-run)
DRY_RUN=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
POSITIONAL+=("$1")
shift
;;
esac
done
if [ "${#POSITIONAL[@]}" -lt 2 ]; then
usage
exit 1
fi
PROPOSAL_ID="$(normalize_proposal_id "${POSITIONAL[0]}")"
CHOICE_RAW="${POSITIONAL[1]}"
require_tools
load_config
API_KEY="$(resolve_bankr_api_key)"
TMP_TYPED="$(mktemp /tmp/vote-typed-XXXXXX.json)"
TMP_PAYLOAD="$(mktemp /tmp/vote-payload-XXXXXX.json)"
cleanup() {
rm -f "$TMP_TYPED" "$TMP_PAYLOAD"
}
trap cleanup EXIT
echo "π³οΈ AAVEGOTCHI DAO VOTING"
echo "========================"
echo
printf "π€ Wallet: %s\n" "$WALLET"
printf "π Proposal: %s\n" "$PROPOSAL_ID"
printf "β
Choice: %s\n" "$CHOICE_RAW"
echo
echo "π Fetching proposal details..."
P_QUERY='query($id:String!){ proposal(id: $id) { id title type choices state } }'
P_VARS="$(jq -n --arg id "$PROPOSAL_ID" '{id:$id}')"
PROPOSAL_DATA="$(snapshot_query "$P_QUERY" "$P_VARS")"
if snapshot_has_errors "$PROPOSAL_DATA"; then
echo "β Snapshot proposal query error"
echo "$PROPOSAL_DATA" | jq '.errors'
exit 1
fi
if [ "$(echo "$PROPOSAL_DATA" | jq -r '.data.proposal.id // empty')" = "" ]; then
echo "β Proposal not found: $PROPOSAL_ID"
exit 1
fi
TITLE="$(echo "$PROPOSAL_DATA" | jq -r '.data.proposal.title')"
TYPE="$(echo "$PROPOSAL_DATA" | jq -r '.data.proposal.type')"
STATE="$(echo "$PROPOSAL_DATA" | jq -r '.data.proposal.state')"
CHOICE_COUNT="$(echo "$PROPOSAL_DATA" | jq -r '.data.proposal.choices | length')"
echo "π Title: $TITLE"
echo "π― Type: $TYPE"
echo "β‘ State: $STATE"
echo
echo "π Available choices:"
echo "$PROPOSAL_DATA" | jq -r '.data.proposal.choices[]' | nl
if [ "$STATE" != "active" ]; then
echo
echo "β οΈ Proposal is not active (state: $STATE); sequencer may reject this vote."
fi
echo
echo "πͺ Checking voting power..."
VP_QUERY='query($voter:String!, $space:String!, $proposal:String!){ vp(voter: $voter, space: $space, proposal: $proposal) { vp vp_by_strategy } }'
VP_VARS="$(jq -n --arg voter "$WALLET" --arg space "$SPACE" --arg proposal "$PROPOSAL_ID" '{voter:$voter,space:$space,proposal:$proposal}')"
VP_DATA="$(snapshot_query "$VP_QUERY" "$VP_VARS")"
if snapshot_has_errors "$VP_DATA"; then
echo "β Snapshot VP query error"
echo "$VP_DATA" | jq '.errors'
exit 1
fi
VP="$(echo "$VP_DATA" | jq -r '.data.vp.vp // 0')"
VP_BY_STRATEGY="$(echo "$VP_DATA" | jq -c '.data.vp.vp_by_strategy // []')"
echo " Total VP: $VP"
echo " Breakdown: $VP_BY_STRATEGY"
if [ "$VP" = "0" ] || [ "$VP" = "null" ]; then
echo "β You have 0 voting power on this proposal"
exit 1
fi
CHOICE_TYPE="uint32"
CHOICE_VALUE="$CHOICE_RAW"
if [ "$TYPE" = "weighted" ]; then
CHOICE_TYPE="string"
if [[ "$CHOICE_RAW" =~ ^\{.*\}$ ]]; then
echo "$CHOICE_RAW" | jq -e --argjson max "$CHOICE_COUNT" '
type == "object" and
(keys | length > 0) and
all(keys[]; test("^[0-9]+$") and ((tonumber >= 1) and (tonumber <= $max))) and
all(.[]; (type == "number") and (. >= 0))
' >/dev/null 2>&1 || err "Invalid weighted JSON choice (keys must be 1..$CHOICE_COUNT, values numeric >= 0)"
CHOICE_VALUE="$(echo "$CHOICE_RAW" | jq -c '.')"
elif [[ "$CHOICE_RAW" =~ ^[0-9]+$ ]]; then
[ "$CHOICE_RAW" -ge 1 ] && [ "$CHOICE_RAW" -le "$CHOICE_COUNT" ] || err "Choice out of range (1..$CHOICE_COUNT)"
VP_FLOOR="$(printf '%.0f' "$VP")"
CHOICE_VALUE="$(jq -n --arg key "$CHOICE_RAW" --argjson vp "$VP_FLOOR" '{($key):$vp}' | jq -c '.')"
echo "π‘ Converted weighted choice: $CHOICE_VALUE"
else
err "Weighted voting requires numeric choice or JSON object"
fi
else
[[ "$CHOICE_RAW" =~ ^[0-9]+$ ]] || err "Single-choice voting requires numeric choice"
[ "$CHOICE_RAW" -ge 1 ] && [ "$CHOICE_RAW" -le "$CHOICE_COUNT" ] || err "Choice out of range (1..$CHOICE_COUNT)"
CHOICE_VALUE="$CHOICE_RAW"
fi
TIMESTAMP="$(date +%s)"
if [ "$CHOICE_TYPE" = "string" ]; then
jq -n \
--arg from "$WALLET" \
--arg space "$SPACE" \
--arg proposal "$PROPOSAL_ID" \
--arg choice "$CHOICE_VALUE" \
--arg app "openclaw" \
--arg metadata "{}" \
--argjson timestamp "$TIMESTAMP" \
'{
types: {
Vote: [
{name:"from",type:"address"},
{name:"space",type:"string"},
{name:"timestamp",type:"uint64"},
{name:"proposal",type:"bytes32"},
{name:"choice",type:"string"},
{name:"reason",type:"string"},
{name:"app",type:"string"},
{name:"metadata",type:"string"}
]
},
domain: {name:"snapshot",version:"0.1.4"},
primaryType:"Vote",
message: {from:$from,space:$space,timestamp:$timestamp,proposal:$proposal,choice:$choice,reason:"",app:$app,metadata:$metadata}
}' > "$TMP_TYPED"
else
jq -n \
--arg from "$WALLET" \
--arg space "$SPACE" \
--arg proposal "$PROPOSAL_ID" \
--arg app "openclaw" \
--arg metadata "{}" \
--argjson timestamp "$TIMESTAMP" \
--argjson choice "$CHOICE_VALUE" \
'{
types: {
Vote: [
{name:"from",type:"address"},
{name:"space",type:"string"},
{name:"timestamp",type:"uint64"},
{name:"proposal",type:"bytes32"},
{name:"choice",type:"uint32"},
{name:"reason",type:"string"},
{name:"app",type:"string"},
{name:"metadata",type:"string"}
]
},
domain: {name:"snapshot",version:"0.1.4"},
primaryType:"Vote",
message: {from:$from,space:$space,timestamp:$timestamp,proposal:$proposal,choice:$choice,reason:"",app:$app,metadata:$metadata}
}' > "$TMP_TYPED"
fi
echo
echo "π Typed data prepared"
if [ "$DRY_RUN" -eq 1 ]; then
echo "--- DRY RUN ---"
cat "$TMP_TYPED" | jq '.'
exit 0
fi
echo "π Signing vote with Bankr..."
SIGN_PAYLOAD="$(jq -n --slurpfile typed "$TMP_TYPED" '{signatureType:"eth_signTypedData_v4",typedData:$typed[0]}')"
SIGN_RESPONSE="$(curl -sS -X POST "https://api.bankr.bot/agent/sign" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "$SIGN_PAYLOAD")"
SIGNATURE="$(echo "$SIGN_RESPONSE" | jq -r '.signature // empty')"
if [ -z "$SIGNATURE" ]; then
echo "β Failed to get signature from Bankr"
echo "$SIGN_RESPONSE" | jq '.'
exit 1
fi
echo "β
Signature obtained"
echo "π€ Submitting vote to Snapshot..."
jq -n \
--arg address "$WALLET" \
--arg sig "$SIGNATURE" \
--slurpfile data "$TMP_TYPED" \
'{address:$address,sig:$sig,data:$data[0]}' > "$TMP_PAYLOAD"
VOTE_RESPONSE="$(curl -sS -X POST "$SEQUENCER" -H "Content-Type: application/json" -d @"$TMP_PAYLOAD")"
echo "π¬ Response:"
echo "$VOTE_RESPONSE" | jq '.'
if echo "$VOTE_RESPONSE" | jq -e '.id' >/dev/null 2>&1; then
VOTE_ID="$(echo "$VOTE_RESPONSE" | jq -r '.id')"
IPFS="$(echo "$VOTE_RESPONSE" | jq -r '.ipfs // empty')"
echo
echo "βββββββββββββββββββββββββββββββββββββ"
echo "β
VOTE SUCCESSFUL"
echo "βββββββββββββββββββββββββββββββββββββ"
echo "π Vote ID: $VOTE_ID"
if [ -n "$IPFS" ]; then
echo "π¦ IPFS: $IPFS"
fi
echo "π View: https://snapshot.org/#/$SPACE/proposal/$PROPOSAL_ID"
else
ERROR="$(echo "$VOTE_RESPONSE" | jq -r '.error_description // .error // "Unknown error"')"
echo "β Vote failed: $ERROR"
exit 1
fi
```
---
## Skill Companion Files
> Additional files collected from the skill directory layout.
### README.md
```markdown
# Gotchi DAO Voting
Snapshot voting automation for Aavegotchi DAO (`aavegotchi.eth`) using Bankr signing.
## Scripts
- `./scripts/list-proposals.sh`
- Lists active proposals + your current VP for each
- `./scripts/vote.sh [--dry-run] <proposal-id> <choice>`
- Single choice: `2`
- Weighted choice: `'{"2":2238}'`
## Quick Start
```bash
# 1) List active proposals
./scripts/list-proposals.sh
# 2) Preview typed vote payload (safe)
./scripts/vote.sh --dry-run <proposal-id> 2
# 3) Submit vote
./scripts/vote.sh <proposal-id> 2
```
## Requirements
- `curl`, `jq`
- `BANKR_API_KEY` (env recommended)
Bankr API key resolution order:
1. `BANKR_API_KEY`
2. user systemd environment (`systemctl --user show-environment`)
3. `~/.openclaw/skills/bankr/config.json`
4. `~/.openclaw/workspace/skills/bankr/config.json`
## Config
`config.json`:
```json
{
"wallet": "0xYourBankrWallet",
"space": "aavegotchi.eth",
"snapshotApiUrl": "https://hub.snapshot.org/graphql",
"snapshotSequencer": "https://seq.snapshot.org/"
}
```
## Notes
- Snapshot voting is off-chain (no gas fee).
- Voting still requires correct VP at proposal snapshot block.
- `--dry-run` builds typed data without signing/submitting.
```
### _meta.json
```json
{
"owner": "aaigotchi",
"slug": "gotchi-dao-voting",
"displayName": "Gotchi DAO Voting",
"latest": {
"version": "1.1.0",
"publishedAt": 1772826869163,
"commit": "https://github.com/openclaw/skills/commit/6b803680e0e3c0128bbaf60a4b73c0eee10e8ad4"
},
"history": [
{
"version": "1.0.0",
"publishedAt": 1771690950048,
"commit": "https://github.com/openclaw/skills/commit/eedf7a87e5cda72421b243f9c7cdbf07e55a71f8"
}
]
}
```
### scripts/lib.sh
```bash
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="${GOTCHI_DAO_CONFIG_FILE:-$SCRIPT_DIR/../config.json}"
err() {
echo "ERROR: $*" >&2
exit 1
}
require_bin() {
local bin="$1"
command -v "$bin" >/dev/null 2>&1 || err "Missing required binary: $bin"
}
require_tools() {
require_bin curl
require_bin jq
}
normalize_wallet() {
local wallet="$1"
[[ "$wallet" =~ ^0x[0-9a-fA-F]{40}$ ]] || err "Invalid wallet address: $wallet"
printf '%s\n' "$wallet"
}
normalize_proposal_id() {
local proposal_id="$1"
[[ "$proposal_id" =~ ^0x[0-9a-fA-F]{64}$ ]] || err "Invalid proposal ID: $proposal_id"
printf '%s\n' "$proposal_id"
}
load_config() {
[ -f "$CONFIG_FILE" ] || err "Config file not found: $CONFIG_FILE"
WALLET="$(jq -r '.wallet // empty' "$CONFIG_FILE")"
SPACE="$(jq -r '.space // empty' "$CONFIG_FILE")"
SNAPSHOT_API="$(jq -r '.snapshotApiUrl // empty' "$CONFIG_FILE")"
SEQUENCER="$(jq -r '.snapshotSequencer // empty' "$CONFIG_FILE")"
[ -n "$WALLET" ] || err "Missing config.wallet"
[ -n "$SPACE" ] || err "Missing config.space"
[ -n "$SNAPSHOT_API" ] || err "Missing config.snapshotApiUrl"
[ -n "$SEQUENCER" ] || err "Missing config.snapshotSequencer"
normalize_wallet "$WALLET" >/dev/null
}
resolve_bankr_api_key() {
local key="${BANKR_API_KEY:-}"
if [ -z "$key" ] && command -v systemctl >/dev/null 2>&1; then
key="$(systemctl --user show-environment 2>/dev/null | sed -n 's/^BANKR_API_KEY=//p' | head -n1 || true)"
fi
if [ -z "$key" ] && [ -f "$HOME/.openclaw/skills/bankr/config.json" ]; then
key="$(jq -r '.apiKey // empty' "$HOME/.openclaw/skills/bankr/config.json" 2>/dev/null || true)"
fi
if [ -z "$key" ] && [ -f "$HOME/.openclaw/workspace/skills/bankr/config.json" ]; then
key="$(jq -r '.apiKey // empty' "$HOME/.openclaw/workspace/skills/bankr/config.json" 2>/dev/null || true)"
fi
[ -n "$key" ] || err "BANKR_API_KEY missing (env/systemd/bankr config)"
printf '%s\n' "$key"
}
snapshot_query() {
local query="$1"
local variables_json="${2-}"
local payload
if [ -z "$variables_json" ]; then
variables_json='{}'
fi
printf '%s' "$variables_json" | jq -e . >/dev/null 2>&1 || err "Invalid Snapshot variables JSON"
payload="$(jq -n --arg query "$query" --argjson variables "$variables_json" '{query:$query,variables:$variables}')" || err "Failed to build Snapshot query payload"
curl -sS -X POST "$SNAPSHOT_API" \
-H "Content-Type: application/json" \
-d "$payload"
}
snapshot_has_errors() {
local response="$1"
printf '%s' "$response" | jq -e '.errors and (.errors | length > 0)' >/dev/null 2>&1
}
format_utc() {
local ts="$1"
date -u -d "@$ts" '+%Y-%m-%d %H:%M UTC' 2>/dev/null || echo "Unknown"
}
```