AgentSkillsCN

aavegotchi-baazaar

在 Base 主网(8453)上查看、添加并执行 Aavegotchi Baazaar 上市商品。可直接使用 GHST 购买,或通过 swapAndBuy* 使用 USDC 购买。安全第一:dryRun 默认为 true(以 cast call 模拟交易;仅在 dryRun=false / DRY_RUN=0 时才通过 cast send 广播交易)。

SKILL.md
--- frontmatter
name: aavegotchi-baazaar
description: >
  View, add, and execute Aavegotchi Baazaar listings on Base mainnet (8453).
  Buy with GHST directly or buy with USDC using swapAndBuy*.
  Safety-first: dryRun defaults true (simulate with cast call; only broadcast with cast send when dryRun=false / DRY_RUN=0).
homepage: https://github.com/aavegotchi/aavegotchi-baazaar-skill
metadata:
  openclaw:
    requires:
      bins:
        - cast
        - curl
        - python3
      env:
        - FROM_ADDRESS
        - PRIVATE_KEY
        - BASE_MAINNET_RPC
        - RECIPIENT_ADDRESS
        - DRY_RUN
        - SLIPPAGE_PCT
        - PAYMENT_FEE_PCT_USDC
        - GHST_USD_PRICE
        - DIAMOND
        - GHST
        - USDC
        - SUBGRAPH_URL
    primaryEnv: PRIVATE_KEY

Safety Rules

  • Default to dryRun=true (DRY_RUN=1). Never broadcast unless explicitly instructed to do so.
  • Always verify Base mainnet:
    • ~/.foundry/bin/cast chain-id --rpc-url "${BASE_MAINNET_RPC:-https://mainnet.base.org}" must be 8453.
  • Always verify key/address alignment:
    • ~/.foundry/bin/cast wallet address --private-key "$PRIVATE_KEY" must equal $FROM_ADDRESS.
  • Always refetch the listing from the subgraph immediately before simulating or broadcasting (listings can be cancelled/sold/price-updated).
  • Never print or log $PRIVATE_KEY.

Shell Input Safety (Avoid RCE)

This skill includes shell commands. Treat any value you copy from a user or an external source (subgraph responses, chat messages, etc.) as untrusted.

Rules:

  • Never execute user-provided strings as shell code (avoid eval, bash -c, sh -c).
  • Only substitute addresses that match 0x + 40 hex chars.
  • Only substitute uint values that are base-10 digits (no commas, no decimals).
  • In the command examples below, listing-specific inputs are written as quoted placeholders like "<LISTING_ID>" to avoid accidental shell interpolation. Replace them with literal values after you validate them.

Quick validators (replace the placeholder values):

bash
python3 - <<'PY'
import re

listing_id = "<LISTING_ID>"  # digits only
token_contract = "<TOKEN_CONTRACT_ADDRESS>"  # 0x + 40 hex chars
price_in_wei = "<PRICE_IN_WEI>"  # digits only

if not re.fullmatch(r"[0-9]+", listing_id):
    raise SystemExit("LISTING_ID must be base-10 digits only")
if not re.fullmatch(r"0x[a-fA-F0-9]{40}", token_contract):
    raise SystemExit("TOKEN_CONTRACT_ADDRESS must be a 0x + 40-hex address")
if not re.fullmatch(r"[0-9]+", price_in_wei):
    raise SystemExit("PRICE_IN_WEI must be base-10 digits only")

print("ok")
PY

Required Setup

Required env vars:

  • PRIVATE_KEY: EOA private key used for cast send (never print/log).
  • FROM_ADDRESS: EOA address that owns funds/NFTs and will submit txs.
  • BASE_MAINNET_RPC: RPC URL. If unset, use https://mainnet.base.org.

Hardcoded Base mainnet constants (override via env if needed):

bash
export BASE_MAINNET_RPC="${BASE_MAINNET_RPC:-https://mainnet.base.org}"
export DIAMOND="${DIAMOND:-0xA99c4B08201F2913Db8D28e71d020c4298F29dBF}"
export GHST="${GHST:-0xcD2F22236DD9Dfe2356D7C543161D4d260FD9BcB}"
export USDC="${USDC:-0x833589fCD6eDb6E08f4c7C32D4f71b54BDA02913}"
export SUBGRAPH_URL="${SUBGRAPH_URL:-https://api.goldsky.com/api/public/project_cmh3flagm0001r4p25foufjtt/subgraphs/aavegotchi-core-base/prod/gn}"

Optional env vars:

  • RECIPIENT_ADDRESS: defaults to FROM_ADDRESS.
  • DRY_RUN: 1 (default) to only simulate via cast call. Set to 0 to broadcast via cast send.
  • SLIPPAGE_PCT: defaults to 1 (used for USDC swapAmount math).
  • PAYMENT_FEE_PCT_USDC: defaults to 1 (used for USDC swapAmount math).
  • GHST_USD_PRICE: optional override; if unset, fetch from CoinGecko in the USDC flow.

Notes:

  • Commands below use ~/.foundry/bin/cast (works reliably in cron/non-interactive shells). If cast is on PATH, you can replace ~/.foundry/bin/cast with cast.
  • Canonical addresses and endpoints live in:
    • references/addresses.md
    • references/subgraph.md

View Listings (Subgraph)

Subgraph endpoint (Goldsky):

  • Default: $SUBGRAPH_URL (see exports above)
  • Value: https://api.goldsky.com/api/public/project_cmh3flagm0001r4p25foufjtt/subgraphs/aavegotchi-core-base/prod/gn

Get ERC721 listing by id:

bash
curl -s "$SUBGRAPH_URL" -H 'content-type: application/json' --data '{
  "query":"query($id: ID!){ erc721Listing(id:$id){ id category erc721TokenAddress tokenId seller priceInWei cancelled timeCreated timePurchased } }",
  "variables":{"id":"1"}
}'

Get ERC1155 listing by id:

  • Subgraph field name is erc1155TypeId (this maps to the onchain typeId / itemId argument).
bash
curl -s "$SUBGRAPH_URL" -H 'content-type: application/json' --data '{
  "query":"query($id: ID!){ erc1155Listing(id:$id){ id category erc1155TokenAddress erc1155TypeId quantity seller priceInWei cancelled sold timeCreated } }",
  "variables":{"id":"1"}
}'

Find active listings:

  • ERC721: where:{cancelled:false, timePurchased:\"0\"}
  • ERC1155: where:{cancelled:false, sold:false}

Example (active ERC721, newest first):

bash
curl -s "$SUBGRAPH_URL" -H 'content-type: application/json' --data '{
  "query":"query{ erc721Listings(first:20, orderBy:timeCreated, orderDirection:desc, where:{cancelled:false, timePurchased:\"0\"}){ id erc721TokenAddress tokenId priceInWei seller timeCreated } }"
}'

Example (active ERC1155, newest first):

bash
curl -s "$SUBGRAPH_URL" -H 'content-type: application/json' --data '{
  "query":"query{ erc1155Listings(first:20, orderBy:timeCreated, orderDirection:desc, where:{cancelled:false, sold:false}){ id erc1155TokenAddress erc1155TypeId quantity priceInWei seller timeCreated } }"
}'

Execute Listing (Buy With GHST)

Onchain methods (Diamond):

  • executeERC721ListingToRecipient(uint256 listingId,address contractAddress,uint256 priceInWei,uint256 tokenId,address recipient)
  • executeERC1155ListingToRecipient(uint256 listingId,address contractAddress,uint256 itemId,uint256 quantity,uint256 priceInWei,address recipient)

Total cost:

  • ERC721: totalCostGhstWei = priceInWei
  • ERC1155: totalCostGhstWei = priceInWei * quantity (but you still pass quantity and priceInWei separately to the method)

Before buying:

  1. Fetch listing details from the subgraph (id, token contract address, tokenId/typeId, quantity, priceInWei).
  2. Check GHST balance/allowance and prepare approvals if needed (see references/recipes.md).

Dry-run (simulate) ERC721 buy:

bash
~/.foundry/bin/cast call "$DIAMOND" \
  'executeERC721ListingToRecipient(uint256,address,uint256,uint256,address)' \
  "<LISTING_ID>" "<ERC721_TOKEN_ADDRESS>" "<PRICE_IN_WEI>" "<TOKEN_ID>" "${RECIPIENT_ADDRESS:-$FROM_ADDRESS}" \
  --from "$FROM_ADDRESS" \
  --rpc-url "${BASE_MAINNET_RPC:-https://mainnet.base.org}"

Broadcast (real) ERC721 buy (only when explicitly instructed):

bash
~/.foundry/bin/cast send "$DIAMOND" \
  'executeERC721ListingToRecipient(uint256,address,uint256,uint256,address)' \
  "<LISTING_ID>" "<ERC721_TOKEN_ADDRESS>" "<PRICE_IN_WEI>" "<TOKEN_ID>" "${RECIPIENT_ADDRESS:-$FROM_ADDRESS}" \
  --private-key "$PRIVATE_KEY" \
  --rpc-url "${BASE_MAINNET_RPC:-https://mainnet.base.org}"

Dry-run (simulate) ERC1155 buy:

bash
~/.foundry/bin/cast call "$DIAMOND" \
  'executeERC1155ListingToRecipient(uint256,address,uint256,uint256,uint256,address)' \
  "<LISTING_ID>" "<ERC1155_TOKEN_ADDRESS>" "<TYPE_ID>" "<QUANTITY>" "<PRICE_IN_WEI>" "${RECIPIENT_ADDRESS:-$FROM_ADDRESS}" \
  --from "$FROM_ADDRESS" \
  --rpc-url "${BASE_MAINNET_RPC:-https://mainnet.base.org}"

Broadcast (real) ERC1155 buy (only when explicitly instructed):

bash
~/.foundry/bin/cast send "$DIAMOND" \
  'executeERC1155ListingToRecipient(uint256,address,uint256,uint256,uint256,address)' \
  "<LISTING_ID>" "<ERC1155_TOKEN_ADDRESS>" "<TYPE_ID>" "<QUANTITY>" "<PRICE_IN_WEI>" "${RECIPIENT_ADDRESS:-$FROM_ADDRESS}" \
  --private-key "$PRIVATE_KEY" \
  --rpc-url "${BASE_MAINNET_RPC:-https://mainnet.base.org}"

Execute Listing (Buy With USDC swapAndBuy*)

Onchain methods (Diamond):

  • swapAndBuyERC721(address tokenIn,uint256 swapAmount,uint256 minGhstOut,uint256 swapDeadline,uint256 listingId,address contractAddress,uint256 priceInWei,uint256 tokenId,address recipient)
  • swapAndBuyERC1155(address tokenIn,uint256 swapAmount,uint256 minGhstOut,uint256 swapDeadline,uint256 listingId,address contractAddress,uint256 itemId,uint256 quantity,uint256 priceInWei,address recipient)

Required computed args:

  • swapDeadline = now + 600
  • minGhstOut = totalCostGhstWei (exactly)
  • swapAmount (USDC base units, 6 decimals): compute per references/usdc-swap-math.md

Before buying:

  1. Fetch listing details from the subgraph (and compute totalCostGhstWei).
  2. Compute swapAmount in USDC base units (integer, rounded up).
  3. Ensure USDC allowance to the Diamond is at least swapAmount (see references/recipes.md).

Dry-run (simulate) ERC721 USDC swap+buy:

bash
~/.foundry/bin/cast call "$DIAMOND" \
  'swapAndBuyERC721(address,uint256,uint256,uint256,uint256,address,uint256,uint256,address)' \
  "$USDC" "<SWAP_AMOUNT_USDC_6DP>" "<MIN_GHST_OUT_GHST_WEI>" "<SWAP_DEADLINE_UNIX>" "<LISTING_ID>" "<ERC721_TOKEN_ADDRESS>" "<PRICE_IN_WEI>" "<TOKEN_ID>" "${RECIPIENT_ADDRESS:-$FROM_ADDRESS}" \
  --from "$FROM_ADDRESS" \
  --rpc-url "${BASE_MAINNET_RPC:-https://mainnet.base.org}"

Dry-run (simulate) ERC1155 USDC swap+buy:

bash
~/.foundry/bin/cast call "$DIAMOND" \
  'swapAndBuyERC1155(address,uint256,uint256,uint256,uint256,address,uint256,uint256,uint256,address)' \
  "$USDC" "<SWAP_AMOUNT_USDC_6DP>" "<MIN_GHST_OUT_GHST_WEI>" "<SWAP_DEADLINE_UNIX>" "<LISTING_ID>" "<ERC1155_TOKEN_ADDRESS>" "<TYPE_ID>" "<QUANTITY>" "<PRICE_IN_WEI>" "${RECIPIENT_ADDRESS:-$FROM_ADDRESS}" \
  --from "$FROM_ADDRESS" \
  --rpc-url "${BASE_MAINNET_RPC:-https://mainnet.base.org}"

Broadcast (real) ERC721 swap+buy (only when explicitly instructed):

bash
~/.foundry/bin/cast send "$DIAMOND" \
  'swapAndBuyERC721(address,uint256,uint256,uint256,uint256,address,uint256,uint256,address)' \
  "$USDC" "<SWAP_AMOUNT_USDC_6DP>" "<MIN_GHST_OUT_GHST_WEI>" "<SWAP_DEADLINE_UNIX>" "<LISTING_ID>" "<ERC721_TOKEN_ADDRESS>" "<PRICE_IN_WEI>" "<TOKEN_ID>" "${RECIPIENT_ADDRESS:-$FROM_ADDRESS}" \
  --private-key "$PRIVATE_KEY" \
  --rpc-url "${BASE_MAINNET_RPC:-https://mainnet.base.org}"

Broadcast (real) ERC1155 swap+buy (only when explicitly instructed):

bash
~/.foundry/bin/cast send "$DIAMOND" \
  'swapAndBuyERC1155(address,uint256,uint256,uint256,uint256,address,uint256,uint256,uint256,address)' \
  "$USDC" "<SWAP_AMOUNT_USDC_6DP>" "<MIN_GHST_OUT_GHST_WEI>" "<SWAP_DEADLINE_UNIX>" "<LISTING_ID>" "<ERC1155_TOKEN_ADDRESS>" "<TYPE_ID>" "<QUANTITY>" "<PRICE_IN_WEI>" "${RECIPIENT_ADDRESS:-$FROM_ADDRESS}" \
  --private-key "$PRIVATE_KEY" \
  --rpc-url "${BASE_MAINNET_RPC:-https://mainnet.base.org}"

Add Listing

Onchain methods (Diamond):

  • getListingFeeInWei()(uint256)
  • addERC721Listing(address erc721TokenAddress,uint256 tokenId,uint256 category,uint256 priceInWei)
  • setERC1155Listing(address erc1155TokenAddress,uint256 typeId,uint256 quantity,uint256 category,uint256 priceInWei)

Steps:

  1. Check listing fee:
    • ~/.foundry/bin/cast call "$DIAMOND" 'getListingFeeInWei()(uint256)' --rpc-url "${BASE_MAINNET_RPC:-https://mainnet.base.org}"
  2. Ensure the NFT contract has setApprovalForAll($DIAMOND,true) (ERC721/1155) before listing.
  3. Submit the listing tx (simulate with cast call when dryRun=true, broadcast with cast send only when explicitly instructed).
  4. After listing, find the newest listingId via the subgraph for seller=$FROM_ADDRESS ordered by timeCreated desc and confirm it matches token/typeId.

ERC721 list (simulate):

bash
~/.foundry/bin/cast call "$DIAMOND" \
  'addERC721Listing(address,uint256,uint256,uint256)' \
  "<ERC721_TOKEN_ADDRESS>" "<TOKEN_ID>" "<CATEGORY>" "<PRICE_IN_WEI>" \
  --from "$FROM_ADDRESS" \
  --rpc-url "${BASE_MAINNET_RPC:-https://mainnet.base.org}"

ERC1155 list (simulate):

bash
~/.foundry/bin/cast call "$DIAMOND" \
  'setERC1155Listing(address,uint256,uint256,uint256,uint256)' \
  "<ERC1155_TOKEN_ADDRESS>" "<TYPE_ID>" "<QUANTITY>" "<CATEGORY>" "<PRICE_IN_WEI>" \
  --from "$FROM_ADDRESS" \
  --rpc-url "${BASE_MAINNET_RPC:-https://mainnet.base.org}"

Common Failure Modes

  • Diamond: Function does not exist: wrong contract address or wrong function signature (or wrong chain).
  • ERC1155Marketplace: not enough GHST: insufficient balance or allowance (or computed totalCostGhstWei is wrong).
  • ERC1155Marketplace: Not approved / approval errors: missing setApprovalForAll for listing, or missing ERC20 approve for buying.
  • Swap errors (e.g. LibTokenSwap: swapAmount must be > 0): bad swapAmount math or missing inputs.
  • Listing cancelled/sold or price changed: refetch from subgraph and re-simulate before broadcasting.