Skip to content
Merged
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
160 changes: 86 additions & 74 deletions workspace/BOOTSTRAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,27 @@ There is no memory yet on a totally fresh deploy. That's normal: `memory/` and m

## Helper: locate the Pinata Platform CLI

Phases 0, 1, and 2 use the bundled Pinata Platform skill to attach secrets and restart. Find it once at the top of any session that needs it:
Phase 0 uses the bundled Pinata Platform skill to attach secrets and restart. Find it once at the top of any session that needs it:

```bash
PINATA_CLI=$(find skills -path '*pinata*platform*/cli.mjs' 2>/dev/null | head -1)
```

If `PINATA_CLI` is empty, the skill didn't mount. Tell the user, point at `manifest.json` → `skills`, and stop.

## Phase 0 — Auto-provision the OpenSea API key
## Phase 0 — Provision all secrets in one restart

**Skip if:** all of `OPENSEA_API_KEY`, `PRIVY_APP_ID`, `PRIVY_APP_SECRET`, `PRIVY_WALLET_ID`, `PRIVY_AUTH_SIGNING_KEY` are set.

This phase batches every secret-attach into a single Pinata restart. Each `restart` ends the conversation and forces a cold resume, so the goal here is to make the user paste their Privy credentials exactly once, do all the auto-provisioning in the same shell session using inline env, then attach everything and restart together.

Walk through 0a–0d in order. Each sub-step has its own skip check so partial state from a previous attempt resumes cleanly.

### 0a — OPENSEA_API_KEY

**Skip if:** `OPENSEA_API_KEY` is set.

Otherwise:
Otherwise auto-fetch a free-tier instant key:

```bash
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST https://api.opensea.io/api/v2/auth/keys)
Expand All @@ -37,139 +45,143 @@ if [ "$STATUS" = "429" ]; then
exit 1
fi

KEY=$(printf '%s' "$BODY" | jq -r .api_key)
[ -n "$KEY" ] && [ "$KEY" != "null" ] || { echo "instant key fetch failed (status $STATUS): $BODY"; exit 1; }

node "$PINATA_CLI" create-secret OPENSEA_API_KEY "$KEY" --attach
```

Then tell the user:

> "I've fetched a free-tier OpenSea API key and attached it. Restarting now — I'll come back as a fresh session (no chat memory) and pick up from `IDENTITY.md` + env."

Then:

```bash
node "$PINATA_CLI" restart
OPENSEA_API_KEY=$(printf '%s' "$BODY" | jq -r .api_key)
[ -n "$OPENSEA_API_KEY" ] && [ "$OPENSEA_API_KEY" != "null" ] || { echo "instant key fetch failed (status $STATUS): $BODY"; exit 1; }
```

Stop. The conversation ends here; you'll resume cold and the env will have `OPENSEA_API_KEY`.
Hold the value in the shell var. Don't `--attach` yet — that happens at the end of the phase in 0d.

## Phase 1 — Collect Privy app credentials
### 0b — PRIVY_APP_ID + PRIVY_APP_SECRET

**Skip if:** both `PRIVY_APP_ID` and `PRIVY_APP_SECRET` are set.

Otherwise, ask the user:

> "I need a Privy application before I can create your wallet. Create one at https://dashboard.privy.io (free tier is fine), then either paste your App ID and App Secret here and I'll attach them, or add them via Pinata's env UI yourself and restart."
Otherwise, ask the user once:

If the user pastes them in chat:

```bash
node "$PINATA_CLI" create-secret PRIVY_APP_ID "$ID" --attach
node "$PINATA_CLI" create-secret PRIVY_APP_SECRET "$SECRET" --attach
node "$PINATA_CLI" restart
```
> "I need a Privy application before I can create your wallet. Create one at https://dashboard.privy.io (free tier is fine), then paste your **App ID** and **App Secret** here. I'll create the wallet, generate my own signer, and attach all the secrets in a single batch — only one restart at the end of this phase."

Stop. The agent comes back as a fresh session and resumes at Phase 2.
When the user pastes them, capture into shell vars (`PRIVY_APP_ID=...`, `PRIVY_APP_SECRET=...`). Do not echo `PRIVY_APP_SECRET` back to chat or to logs.

If the user adds them via Pinata UI directly, ask them to restart from the UI — same outcome.
If the user prefers to add them via Pinata's env UI directly, ask them to do that and restart from the UI. After restart, this sub-step will see them in env and skip.

## Phase 2 — Auto-provision wallet ID + additional_signer keypair
### 0c — Wallet ID + additional_signer keypair

**Skip if:** both `PRIVY_WALLET_ID` and `PRIVY_AUTH_SIGNING_KEY` are set.

**Precondition:** `PRIVY_APP_ID` + `PRIVY_APP_SECRET` present.
**Precondition:** `PRIVY_APP_ID` and `PRIVY_APP_SECRET` are present, either in env or in shell vars from 0b.

Generate the agent's additional_signer keypair (pure-local, no API call):

```bash
opensea wallet generate-auth-key --format json
KEYPAIR=$(opensea wallet generate-auth-key --format json)
PRIVY_AUTH_SIGNING_KEY=$(printf '%s' "$KEYPAIR" | jq -r .privateKey)
ADDITIONAL_SIGNER_PUBKEY=$(printf '%s' "$KEYPAIR" | jq -r .publicKey)
```

Capture `privateKey` (PKCS8 base64) and `publicKey` (SPKI base64) from the output. Do **not** echo `privateKey` to the chat or to logs — pass it directly to Pinata in the next step.
Do **not** echo `PRIVY_AUTH_SIGNING_KEY` to chat or logs.

Create the wallet without an owner (it will be hardened in Phase 3):
Create the wallet without an owner (it will be hardened in Phase 1). Pass the Privy creds **inline** as subprocess env so this works in the same shell session that just collected them — no restart needed before the call:

```bash
opensea wallet create --chain-type ethereum --format json
WALLET_JSON=$(PRIVY_APP_ID="$PRIVY_APP_ID" PRIVY_APP_SECRET="$PRIVY_APP_SECRET" \
opensea wallet create --chain-type ethereum --format json)
PRIVY_WALLET_ID=$(printf '%s' "$WALLET_JSON" | jq -r .id)
WALLET_ADDRESS=$(printf '%s' "$WALLET_JSON" | jq -r .address)
```

Capture the wallet `id` from the output. The CLI will print a loud `WARNING: created without --owner-public-key` to stderr — that's expected; we register the owner in the off-machine ceremony in Phase 3. The brief unhardened window between wallet creation and owner registration is acceptable because the wallet has zero balance and no policy attached during this window — there is nothing to lose if the credentials in env were misused. Phase 3 refuses to advance to any signing-capable step until owner gating is verified.

Attach both secrets:
The CLI will print a loud `WARNING: created without --owner-public-key` to stderr — that's expected; we register the owner and attach the spend policy together in the off-machine ceremony in Phase 1. The brief unhardened window between wallet creation and that ceremony is acceptable because the wallet has zero balance and no policy attached during this window — there is nothing to lose if the credentials in env were misused. Phase 1 refuses to advance until owner gating AND policy are both confirmed.

```bash
node "$PINATA_CLI" create-secret PRIVY_WALLET_ID "$ID" --attach
node "$PINATA_CLI" create-secret PRIVY_AUTH_SIGNING_KEY "$PRIVATE_KEY" --attach
```
If `opensea wallet create` fails with an auth error, the Privy credentials the user pasted are wrong. Tell the user, ask them to recheck the dashboard, and re-collect 0b. **Don't proceed to 0d** with bad creds — you'd attach them, restart, and discover the failure cold next session.

Record in `IDENTITY.md` under `## Wallet`:

- `Address: <address>`
- `Wallet ID: <id>`
- `Additional-signer pubkey (SPKI base64): <publicKey>` — Phase 3 needs to re-show this to the user across restarts.
- `Address: <WALLET_ADDRESS>`
- `Wallet ID: <PRIVY_WALLET_ID>`
- `Additional-signer pubkey (SPKI base64): <ADDITIONAL_SIGNER_PUBKEY>` — Phase 1 needs to re-show this to the user across restarts.
- `hardening_status: pending_owner_registration`

### 0d — Attach everything and restart once

Attach each secret you provisioned in 0a–0c (skip the line for any secret that was already in env coming into this phase — those are already persisted):

```bash
node "$PINATA_CLI" create-secret OPENSEA_API_KEY "$OPENSEA_API_KEY" --attach
node "$PINATA_CLI" create-secret PRIVY_APP_ID "$PRIVY_APP_ID" --attach
node "$PINATA_CLI" create-secret PRIVY_APP_SECRET "$PRIVY_APP_SECRET" --attach
node "$PINATA_CLI" create-secret PRIVY_WALLET_ID "$PRIVY_WALLET_ID" --attach
node "$PINATA_CLI" create-secret PRIVY_AUTH_SIGNING_KEY "$PRIVY_AUTH_SIGNING_KEY" --attach
```

Tell the user what just happened, then restart:

> "Wallet `<address>` created. I generated my own additional_signer keypair and stored the private half as `PRIVY_AUTH_SIGNING_KEY`. Restarting now — I'll come back as a fresh session and walk you through the one-time off-machine ceremony to register an owner key."
> "All set: OpenSea key fetched, Privy app credentials saved, wallet `<WALLET_ADDRESS>` created, and my additional_signer keypair generated (private half stored as `PRIVY_AUTH_SIGNING_KEY`). Restarting now — I'll come back as a fresh session and walk you through the one-time off-machine ceremony to register your owner key and attach a spend policy."

```bash
node "$PINATA_CLI" restart
```

Stop. Resume cold from Phase 3.
Stop. The conversation ends here; you'll resume cold from Phase 1.

## Phase 3Verify hardening (user off-machine ceremony)
## Phase 1One-time off-machine ceremony (owner + signer + policy)

**Skip if:** `IDENTITY.md` says `hardening_status: confirmed`.
**Skip if:** `IDENTITY.md` says `hardening_status: confirmed` AND `opensea wallet info` shows `policyIds.length > 0`.

**Precondition:** `PRIVY_WALLET_ID` + `PRIVY_AUTH_SIGNING_KEY` set.

This phase bundles the three off-machine actions that all require the user's owner private key into one focused session, so the user only has to bring out their owner key once.

Check current posture:

```bash
opensea wallet info --format json
```

If the output shows `ownerEnforcesAuthKey: true` AND `additionalSignerCount >= 1`, hardening is complete. Update `IDENTITY.md`:
The three properties to verify:

- `ownerEnforcesAuthKey: true` — owner key registered
- `additionalSignerCount >= 1` — agent's signer registered
- `policyIds.length > 0` — spend policy attached

If all three pass, the ceremony is complete. Update `IDENTITY.md`:

- `hardening_status: confirmed`
- `auth_key_gating: yes`
- Policy template: *Agent Trading — Conservative*
- Per-tx cap: `<cap from earlier conversation, if known — otherwise note "see policy"`>

Then advance to Phase 4.
Then advance to Phase 2.

Otherwise (most likely on first arrival here), tell the user:
Otherwise (most likely on first arrival here), walk the user through the combined ceremony. First, settle the per-tx cap conversationally so they can plug it into the policy template before going off-machine:

> "Wallet is at `<address>` (id `<wallet_id>`). Before I can sign anything, two things need to happen on YOUR machine — never here:
1. Ask them their per-tx cap in ETH (no example — they choose). Convert to wei: `0.05 ETH = 50000000000000000 wei`.
2. Show them the *Agent Trading — Conservative* template from `skills/opensea/references/wallet-policies.md`, with their cap substituted into the `value lte` rule. Use the chain allowlist and Seaport destination from the same reference, verbatim.

Then tell them:

> "Wallet is at `<address>` (id `<wallet_id>`). Before I can sign anything, three things need to happen on YOUR machine — never here. Get them all out of the way in one session with your owner key:
>
> 1. **Generate your owner keypair locally.** Easiest path: install the OpenSea CLI locally (`npm install -g @opensea/cli`) and run `opensea wallet generate-auth-key`. Or use any P-256 keygen (e.g. `openssl ecparam -name prime256v1 -genkey -noout -out owner.pem` and convert to SPKI base64). Keep the private key on your machine — never paste it to me, never put it in env, never check it into anything.
>
> 1. Generate your own owner keypair. Easiest path: install the OpenSea CLI locally (`npm install -g @opensea/cli`) and run `opensea wallet generate-auth-key`. Or use any P-256 keygen (e.g. `openssl ecparam -name prime256v1 -genkey -noout -out owner.pem` and convert to SPKI base64). Keep the private key on your machine — never paste it to me, never put it in env, never check it into anything.
> 2. **Register two keys on the wallet:** your owner public key as `owner_id`, AND my additional_signer public key (`<additional_signer_pubkey from IDENTITY.md>`) as a signer.
>
> 2. Register both keys on the wallet: your owner public key as `owner_id`, AND my additional_signer public key (`<additional_signer_pubkey from IDENTITY.md>`) as a signer. Both via the Node script in https://github.com/ProjectOpenSea/opensea-skill/blob/main/docs/policy-administration.md.
> 3. **Attach the spend policy** I just showed you (with your `<cap>` ETH cap), so the per-tx ceiling is enforced in Privy's TEE.
>
> Steps 2 and 3 are both done with the Node script in https://github.com/ProjectOpenSea/opensea-skill/blob/main/docs/policy-administration.md, signed by your owner key (off-machine). Do them in order: register first, attach policy second — once owner gating is on, the policy attach also requires the owner signature.
>
> After this one-time ceremony, the day-to-day is asymmetric: I sign every trade with my additional_signer key (lives in env, scoped to `/rpc` only). Your owner key only comes out when you decide to change the policy itself — raise the cap, edit the chain allowlist, etc. You don't need to be online with the owner key for me to trade.
>
> Once both keys are registered, type 'done' and I'll verify."

When user says done, re-run `opensea wallet info`. If the checks pass, record `hardening_status: confirmed` and advance. If not, surface the actual `ownerEnforcesAuthKey` and `additionalSignerCount` values from the JSON and ask the user to confirm both off-machine steps completed.
> Once all three are done, type 'done' and I'll verify."

**Refuse to advance to Phase 4 until hardening is confirmed.** Do not proceed to spend-policy attachment, balance checks, watchlist calibration, or any signing-capable operation while `ownerEnforcesAuthKey` is false. If the user pushes back, explain: without owner gating, the credentials in env can rewrite the policy that's supposed to constrain spending — there's no real ceiling.
When the user says done, re-run `opensea wallet info`. Verify each property and surface specifically which (if any) failed:

## Phase 4 — Apply spend policy (user off-machine)
- `ownerEnforcesAuthKey: false` → owner registration didn't take. Re-do step 2.
- `additionalSignerCount < 1` → signer registration didn't take. Re-do step 2.
- `policyIds.length == 0` → policy attach didn't take. Re-do step 3.

**Skip if:** `opensea wallet info` shows `policyIds.length > 0`.
Don't advance with partial state. Once all three pass, record the IDENTITY.md fields above and advance to Phase 2.

**Precondition:** `hardening_status: confirmed`.

Walk the user through choosing and applying a per-tx policy:

1. Ask them their per-tx cap in ETH (no example — they choose). Convert to wei: `0.05 ETH = 50000000000000000 wei`.
2. Show them the *Agent Trading — Conservative* template from `skills/opensea/references/wallet-policies.md`, with their cap substituted into the `value lte` rule. Use the chain allowlist and Seaport destination from the same reference, verbatim.
3. Tell them to attach it via the Node script in https://github.com/ProjectOpenSea/opensea-skill/blob/main/docs/policy-administration.md. They'll need their owner private key (off-machine) to sign the request. Do NOT construct or run the `PATCH` request from here.
4. When they say done, re-run `opensea wallet info`. Confirm `policyIds.length > 0`. Record the policy template and per-tx cap in `IDENTITY.md`.
**Refuse to advance to Phase 2 (funding) until all three pass.** Without owner gating, the credentials in env can rewrite the policy that's supposed to constrain spending. Without a policy, there is no per-tx ceiling. Funding before either is in place creates a window where env credentials could drain the wallet. If the user pushes back, explain the order and offer to keep waiting.

## Phase 5 — Hot-wallet funding
## Phase 2 — Hot-wallet funding

**Skip if:** `IDENTITY.md` already has a `float_per_chain` entry.

Expand All @@ -187,7 +199,7 @@ Record in `IDENTITY.md` under `## Wallet`:

For each funded chain, do a quick balance check (use the native-balance path from `skills/opensea/references/wallet-setup.md` or `eth_getBalance` via a public RPC). If a target chain shows 0, note it — you can still track floors there, you just can't buy.

## Phase 6 — Conversation, identity, watchlist
## Phase 3 — Conversation, identity, watchlist

Don't interrogate. Just talk. Start with something like:

Expand All @@ -214,7 +226,7 @@ Update:
- `USER.md` — their name, timezone, collector profile, risk tolerance, per-tx comfort.
- `MEMORY.md` (create it in `workspace/`) — seed with any taste signals from the opening conversation.

## Phase 7 — Cleanup
## Phase 4 — Cleanup

Delete this file. You don't need a bootstrap script anymore — you're you now.

Expand Down
4 changes: 2 additions & 2 deletions workspace/IDENTITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
- **Policy template:**
- **Spending limit (per tx):** _(ETH / wei — record both for clarity, e.g. "0.05 ETH / 50000000000000000 wei")_
- **hardening_status:** _(pending_creation | pending_owner_registration | pending_policy | confirmed — used by BOOTSTRAP for resumability across restarts)_
- **additional_signer_pubkey:** _(SPKI base64; the public half of `PRIVY_AUTH_SIGNING_KEY`. Persisted so BOOTSTRAP Phase 3 can re-show it to the user across cold restarts.)_
- **auth_key_gating:** _(yes | no — captured from `opensea wallet info` after Phase 3 verifies `ownerEnforcesAuthKey: true`)_
- **additional_signer_pubkey:** _(SPKI base64; the public half of `PRIVY_AUTH_SIGNING_KEY`. Persisted so BOOTSTRAP Phase 1 can re-show it to the user across cold restarts.)_
- **auth_key_gating:** _(yes | no — captured from `opensea wallet info` after Phase 1 verifies `ownerEnforcesAuthKey: true`)_
- **float_per_chain:** _(mirrored from `TOOLS.md` → `floatPerChain`; the real aggregate ceiling)_
- **funding_wallet:** _(optional — user's cold/funding wallet address, so the agent can recognize replenishment events in `events by-account` streams)_
Loading
Loading