Skip to content

Commit 1eba8a5

Browse files
authored
Merge pull request #8 from ProjectOpenSea/hardening/wallet-policy-and-autoprovision
Harden Privy wallet model + auto-provision secrets via Pinata Platform skill
2 parents 9e22aa6 + 87a4c80 commit 1eba8a5

8 files changed

Lines changed: 287 additions & 83 deletions

File tree

.github/workflows/validate.yml

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,12 @@ jobs:
3737
jq -e '.template.tags | type == "array" and length > 0 and length <= 10' manifest.json > /dev/null
3838
jq -e '.template.authorName | type == "string"' manifest.json > /dev/null
3939
40-
# Skills shape — every entry needs clawhub_slug + name
41-
jq -e 'all(.skills[]; .clawhub_slug | type == "string" and test("^[a-z0-9-]+$"))' manifest.json > /dev/null
40+
# Skills shape — every entry needs `name` plus EITHER `clawhub_slug` (resolves to latest published version) OR `cid` (pinned).
4241
jq -e 'all(.skills[]; .name | type == "string" and length > 0)' manifest.json > /dev/null
42+
jq -e 'all(.skills[];
43+
((.clawhub_slug // "") | test("^[a-z0-9-]+$"))
44+
or ((.cid // "") | type == "string" and length >= 46)
45+
)' manifest.json > /dev/null
4346
4447
# Scripts shape — build is required (start is optional)
4548
jq -e '.scripts.build | type == "string" and length > 0' manifest.json > /dev/null
@@ -49,12 +52,22 @@ jobs:
4952
jq -e 'all(.secrets[]; .name | type == "string" and test("^[A-Z][A-Z0-9_]*$"))' manifest.json > /dev/null
5053
jq -e 'all(.secrets[]; .description | type == "string" and length > 0)' manifest.json > /dev/null
5154
52-
# All four required env vars must be declared so Pinata prompts for them at deploy time
53-
for v in OPENSEA_API_KEY PRIVY_APP_ID PRIVY_APP_SECRET PRIVY_WALLET_ID; do
55+
# Required secrets — only the irreducible ones. The agent auto-provisions the rest at bootstrap
56+
# (OPENSEA_API_KEY via the OpenSea instant-key endpoint; PRIVY_WALLET_ID via `opensea wallet create`;
57+
# PRIVY_AUTH_SIGNING_KEY via `opensea wallet generate-auth-key`), then attaches them via the bundled
58+
# @pinata/platform skill. App credentials still have to come from the user.
59+
for v in PRIVY_APP_ID PRIVY_APP_SECRET; do
5460
jq -e --arg v "$v" 'any(.secrets[]; .name == $v and .required == true)' manifest.json > /dev/null \
5561
|| { echo "missing required secret: $v"; exit 1; }
5662
done
5763
64+
# Auto-provisioned secrets must be declared (with required: false) so Pinata renders the field
65+
# in the env UI for users who want to override the auto-provisioned default.
66+
for v in OPENSEA_API_KEY PRIVY_WALLET_ID PRIVY_AUTH_SIGNING_KEY; do
67+
jq -e --arg v "$v" 'any(.secrets[]; .name == $v)' manifest.json > /dev/null \
68+
|| { echo "missing optional secret declaration: $v"; exit 1; }
69+
done
70+
5871
echo "manifest.json OK"
5972
6073
- name: Validate .openclaw/openclaw.json

README.md

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# NFT Collector Copilot
22

3-
An AI agent for NFT collectors. Watches [OpenSea](https://opensea.io), scores every listing across five signals (price, volume, depth, holders, momentum), and executes Seaport trades through a [Privy](https://privy.io) server wallet with a TEE-enforced spend cap. You approve; it acts.
3+
An AI agent for NFT collectors. Watches [OpenSea](https://opensea.io), scores every listing across five signals (price, volume, depth, holders, momentum), and executes Seaport trades through a [Privy](https://privy.io) server wallet with a TEE-enforced per-tx cap and an off-machine owner key the agent cannot reach. You approve; it acts. The wallet's float is your real aggregate ceiling — you fund and replenish it on your own cadence.
44

55
## The killer feature: **Whale-Cross Alerts**
66

@@ -14,7 +14,7 @@ Point it at a list of wallets you respect (vitalik.eth, your favorite PFP whale,
1414
- **Seaport buys + sells** via `@opensea/cli` + the skill's fulfillment scripts, signed by your Privy wallet.
1515
- **Drop Radar** — upcoming/featured drops cross-referenced against your taste model.
1616
- **Taste learning** — every buy/pass/ask updates a structured `taste.json` so recommendations get sharper over time.
17-
- **Spend-cap safety.** The Privy policy is the real enforcement — the agent literally cannot overspend.
17+
- **Spend-cap safety.** Per-tx cap enforced in Privy's TEE; owner key off-machine so the agent can't rewrite the policy; aggregate spend bounded by the wallet float you fund. Three independent layers — one in the user's hands, two in Privy's, none the agent can lift.
1818

1919
Supported chains: see `workspace/TOOLS.md`. Mainnets only by default.
2020

@@ -25,25 +25,33 @@ Supported chains: see `workspace/TOOLS.md`. Mainnets only by default.
2525

2626
## Secrets you'll need
2727

28-
Paste these into Pinata's environment UI at deploy time:
28+
Only two are required up front. The rest the agent provisions for you on first run.
2929

3030
| Variable | Required | How to get it |
3131
|---|---|---|
32-
| `OPENSEA_API_KEY` | yes | `curl -s -X POST https://api.opensea.io/api/v2/auth/keys \| jq -r .api_key` — no signup |
33-
| `PRIVY_APP_ID` | yes | [dashboard.privy.io](https://dashboard.privy.io) → create app |
34-
| `PRIVY_APP_SECRET` | yes | Same page as App ID |
35-
| `PRIVY_WALLET_ID` | yes | Create a server wallet — see `skills/opensea/references/wallet-setup.md`*Privy* |
32+
| `PRIVY_APP_ID` | **yes** | [dashboard.privy.io](https://dashboard.privy.io) → create app |
33+
| `PRIVY_APP_SECRET` | **yes** | Same page as App ID. Pair with an `owner_id` registered on the wallet — bootstrap walks you through it. |
34+
| `OPENSEA_API_KEY` | optional | If unset, the agent fetches a free-tier instant key on first run and attaches it via the bundled Pinata Platform skill. Provide your own paid key here only if you need higher rate limits. |
35+
| `PRIVY_WALLET_ID` | optional | If unset, the agent creates a new server wallet via `opensea wallet create` once Privy app creds are present. Provide one only to reuse an existing wallet. |
36+
| `PRIVY_AUTH_SIGNING_KEY` | optional | Auto-generated by the agent on first run via `opensea wallet generate-auth-key`. The matching public key gets registered as the wallet's `additional_signer`; the owner key stays off-machine on your host. |
3637

37-
Full walkthrough: `skills/opensea/references/wallet-setup.md`. Policy templates: `skills/opensea/references/wallet-policies.md`.
38+
Full walkthrough: `skills/opensea/references/wallet-setup.md`. Policy templates: `skills/opensea/references/wallet-policies.md`. Hot-wallet float pattern: `skills/opensea/references/wallet-funding.md`. User-only mutation recipes (policy attachment, owner registration): https://github.com/ProjectOpenSea/opensea-skill/blob/main/docs/policy-administration.md.
3839

3940
## 60-second setup
4041

41-
1. **Grab an OpenSea API key** — run the curl above.
42-
2. **Create a Privy app** at [dashboard.privy.io](https://dashboard.privy.io); copy App ID + App Secret.
43-
3. **Create a server wallet** following `skills/opensea/references/wallet-setup.md`*Privy* → step 2. Save `id` as `PRIVY_WALLET_ID`.
44-
4. **Attach a spend policy** — the agent will walk you through this on first run using the "Agent Trading — Conservative" template from `skills/opensea/references/wallet-policies.md`. Start tight.
45-
5. **Fund the wallet** on whichever chains you plan to trade on.
46-
6. **Deploy** — paste all four env vars into Pinata, then chat.
42+
1. **Create a Privy app** at [dashboard.privy.io](https://dashboard.privy.io); copy App ID + App Secret. Paste both into Pinata's env UI.
43+
2. **Deploy.** That's the entire required setup.
44+
45+
On first conversation the agent will walk you through:
46+
47+
- Fetching an OpenSea API key and attaching it (free-tier instant key, no signup).
48+
- Creating your Privy server wallet (`opensea wallet create`) and storing the ID.
49+
- Generating its own additional_signer keypair and storing the private half as `PRIVY_AUTH_SIGNING_KEY`.
50+
- An off-machine ceremony you do on your own host: generate an owner keypair, then register both your owner public key (as `owner_id`) and the agent's additional_signer public key on the wallet via https://github.com/ProjectOpenSea/opensea-skill/blob/main/docs/policy-administration.md. Your owner private key never touches the agent.
51+
- Choosing and attaching a per-tx policy (Agent Trading — Conservative is the default template).
52+
- Funding the agent wallet to a hot-wallet float you set per chain.
53+
54+
Each step that needs new secrets ends with a Pinata restart, after which the agent resumes from where you left off. BOOTSTRAP is a resumable state machine — cold restarts pick up at the first incomplete phase.
4755

4856
## Example prompts
4957

@@ -63,12 +71,19 @@ Full walkthrough: `skills/opensea/references/wallet-setup.md`. Policy templates:
6371
6472
## Safety model
6573

66-
- **Env-only credentials.** No private keys in the repo or agent workspace.
67-
- **Privy policy is the hard ceiling.** The spend cap, destination allowlist, and chain filter are enforced inside a TEE before signing.
68-
- **Per-turn confirmation for material actions.** Any buy, offer acceptance, approval, or transfer above `confirmAboveEth` (in `workspace/TOOLS.md`) needs explicit "yes" in the current turn. Snipes can bypass this only when the listing is fully inside your configured envelope — see `workspace/SOUL.md`*Hierarchy of Ceilings*.
74+
Three independent layers, in order of how the bound is actually enforced:
75+
76+
1. **Wallet float** — the **real aggregate ceiling**. The agent doesn't hold a key to a treasury; it can only spend what's in the agent wallet. You fund it from your own cold/funding wallet to ≈ a day or week of intended budget and replenish on your cadence. Privy can't enforce daily/weekly cumulative limits, so wallet balance is what stops a runaway spend.
77+
2. **Privy per-tx policy (TEE-enforced)** — caps each individual transaction. Conditional on `owner_id` being registered: the agent's env credentials cannot rewrite the policy because mutations require an authorization signature from the owner key (which lives on your host, not the agent's). Bootstrap verifies this is in place before any signing-capable step.
78+
3. **Per-turn confirmation** for material actions. Any buy, offer acceptance, approval, or transfer above `confirmAboveEth` (in `workspace/TOOLS.md`) needs explicit "yes" in the current turn. Snipes can bypass this only when the listing is fully inside your configured envelope — see `workspace/SOUL.md`*Hierarchy of Ceilings*.
79+
80+
Other invariants:
81+
82+
- **Env-only credentials.** No private keys in the repo or agent workspace. The owner key never touches the agent host.
6983
- **Sell-side always confirms.** Privy's native-value cap is denominated in the chain's native token (ETH, etc.) — it does **not** apply to WETH transfers, which is how Seaport offer acceptances pay out. Sells, offer acceptances, and ERC721/1155 approvals therefore always require per-turn confirmation, regardless of price.
70-
- **Pre-Buy Gate.** Wash trades, thin markets, uneconomic gas, and fee surprises all block a buy before it's proposed.
84+
- **Pre-Buy Gate.** Wash trades, thin markets, uneconomic gas, fee surprises, and float exhaustion all block a buy before it's proposed.
7185
- **Policy rejections surface verbatim.** No workarounds.
86+
- **Forbidden operations.** The agent will refuse to call any Privy admin endpoint (policy mutation, owner key rotation, auth key changes) — even if you ask. To change the cap or the policy, do it yourself on your machine via https://github.com/ProjectOpenSea/opensea-skill/blob/main/docs/policy-administration.md.
7287

7388
## Repository layout
7489

manifest.json

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": 1,
44
"agent": {
55
"name": "NFT Collector Copilot",
6-
"description": "A discerning collector assistant. Watches your favorite OpenSea collections, tracks floor and volume moves, and — when you say so — buys listings through a Privy server wallet whose spend-cap is enforced in a TEE.",
6+
"description": "A discerning collector assistant. Watches your favorite OpenSea collections, tracks floor and volume moves, and — when you say so — buys listings through a Privy server wallet whose spend cap is enforced in a TEE. Hardened by default: bootstrap walks you through registering an off-machine owner key so the agent's env credentials cannot rewrite the policy that constrains them.",
77
"vibe": "calm, numerate, protective of your ETH",
88
"emoji": ""
99
},
@@ -16,13 +16,14 @@
1616
"authorLogoUrl": "https://static.seadn.io/logos/Logomark-Blue.png"
1717
},
1818
"skills": [
19-
{ "clawhub_slug": "opensea-marketplace", "name": "OpenSea" }
19+
{ "clawhub_slug": "opensea-marketplace", "name": "OpenSea" },
20+
{ "cid": "bafybeicglyjdb6wrrcbfyu6i2fe4lpxdxgvfvlht7yzdim7cvwt656whue", "name": "@pinata/platform" }
2021
],
2122
"secrets": [
2223
{
2324
"name": "OPENSEA_API_KEY",
24-
"description": "OpenSea API key — used for all reads, fulfillment, and swaps. Get one instantly with: curl -s -X POST https://api.opensea.io/api/v2/auth/keys | jq -r .api_key",
25-
"required": true
25+
"description": "OpenSea API key. Optional — if not set, the agent will fetch a free-tier instant key on first run via curl -X POST https://api.opensea.io/api/v2/auth/keys and attach it via the Pinata Platform skill. Provide your own paid key here if you need higher rate limits.",
26+
"required": false
2627
},
2728
{
2829
"name": "PRIVY_APP_ID",
@@ -31,13 +32,18 @@
3132
},
3233
{
3334
"name": "PRIVY_APP_SECRET",
34-
"description": "Privy application secret — same dashboard page as the App ID. Treat as a password.",
35+
"description": "Privy application secret — same dashboard page as the App ID. Treat as a password. IMPORTANT: pair this with an owner_id registered on the wallet — without it, this secret can rewrite the wallet's policy unilaterally. Bootstrap walks you through the hardening (see https://github.com/ProjectOpenSea/opensea-skill/blob/main/docs/policy-administration.md).",
3536
"required": true
3637
},
3738
{
3839
"name": "PRIVY_WALLET_ID",
39-
"description": "Privy server wallet ID. Create a server wallet (see skills/opensea/references/wallet-setup.md) and paste its id here.",
40-
"required": true
40+
"description": "Privy server wallet ID. Optional — if not set, the agent will create a new server wallet via `opensea wallet create` once PRIVY_APP_ID + PRIVY_APP_SECRET are present and attach the ID via the Pinata Platform skill. Set this only to reuse an existing wallet.",
41+
"required": false
42+
},
43+
{
44+
"name": "PRIVY_AUTH_SIGNING_KEY",
45+
"description": "Privy additional_signer private key (PKCS8 base64, P-256). Optional — auto-generated on first run via `opensea wallet generate-auth-key` and attached via the Pinata Platform skill. Required for /rpc signing once owner_id is registered on the wallet. The matching public key is what gets registered as additional_signer; the owner key stays off-machine on your own host. See https://github.com/ProjectOpenSea/opensea-skill/blob/main/docs/policy-administration.md.",
46+
"required": false
4147
}
4248
],
4349
"scripts": {

workspace/AGENTS.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ Free-text long-form observations that don't fit a schema: API quirks, volatile s
8585

8686
- Never push directly to `main` — use feature branches + PRs for any *template* code changes.
8787
- Don't run destructive commands without confirmation.
88-
- Enforce SOUL.md → *Hierarchy of Ceilings* and *Pre-Buy Gate* on every value action.
89-
- Never ask for a raw private key. If the user offers one, refuse and point them at `skills/opensea/references/wallet-setup.md`.
88+
- Enforce SOUL.md → *Hierarchy of Ceilings*, *Forbidden operations*, and *Pre-Buy Gate* on every value action.
89+
- Never write to any Privy admin endpoint (policy, owner keys, auth keys, chain config). The agent's role at Privy is signing-only; `PRIVY_AUTH_SIGNING_KEY` permits `/rpc` signing, nothing else. Policy administration is performed by the user on their own machine, via https://github.com/ProjectOpenSea/opensea-skill/blob/main/docs/policy-administration.md — never from this environment.
90+
- Never request, accept, or store the user's Privy owner private key. If they offer one, refuse.
91+
- Never invoke the Pinata Platform skill (`create-secret`, `restart`, `list-secrets`) after BOOTSTRAP completes. It's setup-only.
92+
- Never ask for a raw wallet private key. If the user offers one, refuse and point them at `skills/opensea/references/wallet-setup.md`.
9093
- Never commit secrets to any file in this workspace.

0 commit comments

Comments
 (0)