repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
Mirrored from https://github.com/ProjectOpenSea/opensea-skill
stars
latest
clone command
git clone gitlawb://did:key:z6MkqRzA...RfoM/ProjectOpenSea-...git clone gitlawb://did:key:z6MkqRzA.../ProjectOpenSea-...fef93001Release v2.14.011h ago| #1 | # Wallet Setup |
| #2 | |
| #3 | Transaction signing in the OpenSea CLI and SDK uses wallet providers through the `WalletAdapter` interface. Four providers are supported out of the box. |
| #4 | |
| #5 | | Provider | Best For | Docs | |
| #6 | |----------|----------|------| |
| #7 | | **Privy** (default) | TEE-enforced policies, embedded wallets | [privy.io](https://privy.io) | |
| #8 | | **Turnkey** | HSM-backed keys, multi-party approval | [turnkey.com](https://www.turnkey.com) | |
| #9 | | **Fireblocks** | Enterprise MPC custody, institutional use | [fireblocks.com](https://www.fireblocks.com) | |
| #10 | | **Bankr** | Agent wallets via HTTP signing API | [bankr.bot](https://bankr.bot) | |
| #11 | | **Private Key** (not recommended) | Local dev/testing only | (none) | |
| #12 | |
| #13 | Managed providers (Privy, Turnkey, Fireblocks) are **strongly recommended** over raw private keys. They provide spending limits, destination allowlists, and policy enforcement that raw keys cannot. |
| #14 | |
| #15 | The CLI auto-detects the provider based on which environment variables are set. You can also specify one explicitly with `--wallet-provider privy|turnkey|fireblocks|private-key`. |
| #16 | |
| #17 | --- |
| #18 | |
| #19 | ## Privy Setup |
| #20 | |
| #21 | ### Prerequisites |
| #22 | |
| #23 | - A Privy account ([privy.io](https://privy.io)) |
| #24 | - An OpenSea API key (`OPENSEA_API_KEY`) |
| #25 | - A trusted operator machine (your laptop, vault host, etc.) — separate from the machine that will run the agent. You'll generate the wallet's authorization key on this machine and keep its private key off the agent. |
| #26 | |
| #27 | ### 1. Create a Privy App |
| #28 | |
| #29 | 1. Go to [dashboard.privy.io](https://dashboard.privy.io) and create a new app |
| #30 | 2. Note your **App ID** and **App Secret** from the app settings page |
| #31 | |
| #32 | ### 2. Create a Server Wallet |
| #33 | |
| #34 | ```bash |
| #35 | curl -X POST https://api.privy.io/v1/wallets \ |
| #36 | -H "Authorization: Basic $(printf "%s:%s" "$PRIVY_APP_ID" "$PRIVY_APP_SECRET" | base64)" \ |
| #37 | -H "privy-app-id: $PRIVY_APP_ID" \ |
| #38 | -H "Content-Type: application/json" \ |
| #39 | -d '{ "chain_type": "ethereum" }' |
| #40 | ``` |
| #41 | |
| #42 | > **Heads-up:** use `printf %s` (not `echo`) when building the basic-auth header. `echo` adds a trailing newline that corrupts the base64-encoded credentials and produces a 401 with a misleading "Invalid app ID or app secret" body. |
| #43 | |
| #44 | Save the wallet `id` from the response as `PRIVY_WALLET_ID`. |
| #45 | |
| #46 | ### 3. Register an authorization key on the wallet |
| #47 | |
| #48 | This step is part of the happy path, not optional hardening. Without it, the same `PRIVY_APP_ID` + `PRIVY_APP_SECRET` the agent uses for signing can also rewrite the wallet's policy unilaterally — including raising any spending cap you set in step 5. |
| #49 | |
| #50 | **On your operator machine** (not the agent host), generate a P-256 keypair: |
| #51 | |
| #52 | ```bash |
| #53 | # Generates a base64-encoded PKCS8 P-256 private key + the matching public key. |
| #54 | node -e ' |
| #55 | const { generateKeyPairSync } = require("crypto"); |
| #56 | const { publicKey, privateKey } = generateKeyPairSync("ec", { namedCurve: "P-256" }); |
| #57 | const pkcs8 = privateKey.export({ type: "pkcs8", format: "der" }).toString("base64"); |
| #58 | const spki = publicKey.export({ type: "spki", format: "der" }).toString("base64"); |
| #59 | console.log("PRIVATE (keep on operator machine):", pkcs8); |
| #60 | console.log("PUBLIC (register with Privy): ", spki); |
| #61 | ' |
| #62 | ``` |
| #63 | |
| #64 | Register the public key as the wallet's **owner** by following [Privy's authorization key setup guide](https://docs.privy.io/controls/authorization-keys/overview). Save the private key in your password manager / vault. **Do not put the owner private key in the agent's env.** |
| #65 | |
| #66 | To let the agent sign while keeping the owner key off-machine, register a *second* P-256 keypair as an `additional_signer` on the wallet, with `override_policy_ids` pointing at the policy you'll create in step 5. The additional signer's private key is what goes in the agent's `PRIVY_AUTH_SIGNING_KEY`. It can sign transactions subject to the policy; it cannot mutate policy or signers. |
| #67 | |
| #68 | ### 4. Set Environment Variables |
| #69 | |
| #70 | ```bash |
| #71 | export OPENSEA_API_KEY="your-opensea-api-key" |
| #72 | export PRIVY_APP_ID="your-privy-app-id" |
| #73 | export PRIVY_APP_SECRET="your-privy-app-secret" |
| #74 | export PRIVY_WALLET_ID="your-privy-wallet-id" |
| #75 | |
| #76 | # Required when the wallet has owner_id set (recommended). Base64-encoded |
| #77 | # PKCS8 P-256 private key for an additional_signer registered on the |
| #78 | # wallet. NEVER set this to the OWNER private key. |
| #79 | export PRIVY_AUTH_SIGNING_KEY="..." |
| #80 | ``` |
| #81 | |
| #82 | ### 5. Verify |
| #83 | |
| #84 | ```bash |
| #85 | opensea wallet info |
| #86 | ``` |
| #87 | |
| #88 | Expected output: a JSON block with the wallet address, policy IDs, owner key ID, and `ownerEnforcesAuthKey: true`. If you see warnings on stderr, the corresponding hardening step is missing — fix it before funding the wallet. |
| #89 | |
| #90 | <details> |
| #91 | <summary>Fallback: verify via curl</summary> |
| #92 | |
| #93 | ```bash |
| #94 | curl -s "https://api.privy.io/v1/wallets/$PRIVY_WALLET_ID" \ |
| #95 | -H "Authorization: Basic $(printf "%s:%s" "$PRIVY_APP_ID" "$PRIVY_APP_SECRET" | base64)" \ |
| #96 | -H "privy-app-id: $PRIVY_APP_ID" | jq . |
| #97 | ``` |
| #98 | |
| #99 | > Use `printf %s` here, not `echo` — the trailing newline `echo` adds breaks the basic-auth header. |
| #100 | |
| #101 | </details> |
| #102 | |
| #103 | ### 6. Configure a per-tx policy |
| #104 | |
| #105 | See `references/wallet-policies.md` for templates and field reference. Apply the policy via the user-only recipe in `../../docs/policy-administration.md` — never construct or run the policy-update request from the agent. |
| #106 | |
| #107 | For aggregate (daily/weekly cumulative) caps, see `references/wallet-funding.md` — Privy policies cannot enforce these natively, and the wallet float pattern is the answer. |
| #108 | |
| #109 | --- |
| #110 | |
| #111 | ## Turnkey Setup |
| #112 | |
| #113 | ### Prerequisites |
| #114 | |
| #115 | - A Turnkey account ([turnkey.com](https://www.turnkey.com)) |
| #116 | - An OpenSea API key (`OPENSEA_API_KEY`) |
| #117 | |
| #118 | ### 1. Create an Organization & a non-root, signer-only API user |
| #119 | |
| #120 | 1. Sign up at [app.turnkey.com](https://app.turnkey.com). |
| #121 | 2. Create an organization. The bootstrap user is automatically a **root** user — keep this account credentials on your operator machine, separate from the agent. Do not use it as the agent's API key. |
| #122 | 3. Create a **second** API user (Users → Add User → API user). This is the agent's user. |
| #123 | 4. Generate a fresh P-256 keypair on your operator machine and upload the public key to the new user. The private key is what goes in `TURNKEY_API_PRIVATE_KEY` on the agent host. |
| #124 | 5. Create a policy that scopes the agent user to **only** `ACTIVITY_TYPE_SIGN_TRANSACTION_V2` (and `ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2` if EIP-712 is needed) for the wallet addresses the agent should sign for. Default-deny on everything else; Turnkey defaults to deny so this is mostly an allowlist. Reference: [Turnkey policy examples](https://docs.turnkey.com/concepts/policies/examples). |
| #125 | |
| #126 | > **Why this matters:** root users in Turnkey **bypass the policy engine entirely**. A leaked root API key has the same blast radius as a raw private key — sign anything, mutate any policy, mint new wallets, export keys. The hardening goal is that the agent's key is non-root and policy-scoped, so even a fully compromised agent cannot escalate. |
| #127 | |
| #128 | ### 2. Create a Wallet |
| #129 | |
| #130 | Create a wallet in the Turnkey dashboard or via API. Note the Ethereum address. Verify the agent user has signing permission on this wallet's address per the policy from step 1. |
| #131 | |
| #132 | ### 3. Set Environment Variables |
| #133 | |
| #134 | ```bash |
| #135 | export OPENSEA_API_KEY="your-opensea-api-key" |
| #136 | export TURNKEY_API_PUBLIC_KEY="your-turnkey-public-key" |
| #137 | export TURNKEY_API_PRIVATE_KEY="your-turnkey-private-key" # hex-encoded P-256 private key |
| #138 | export TURNKEY_ORGANIZATION_ID="your-turnkey-org-id" |
| #139 | export TURNKEY_WALLET_ADDRESS="0xYourTurnkeyWalletAddress" |
| #140 | export TURNKEY_RPC_URL="https://mainnet.infura.io/v3/YOUR_KEY" # required |
| #141 | # Optional: |
| #142 | # export TURNKEY_PRIVATE_KEY_ID="your-turnkey-private-key-id" # if signing with a specific key |
| #143 | # export TURNKEY_API_BASE_URL="https://api.turnkey.com" # override API base URL |
| #144 | ``` |
| #145 | |
| #146 | > **Note:** `TURNKEY_RPC_URL` is **required**. Turnkey is a pure signing service: it does not estimate gas or broadcast transactions. The adapter uses `TURNKEY_RPC_URL` to populate gas fields (nonce, gasLimit, maxFeePerGas, maxPriorityFeePerGas) via `eth_getTransactionCount`, `eth_estimateGas`, and `eth_feeHistory`, then broadcasts the signed transaction via `eth_sendRawTransaction`. The RPC endpoint must match the target chain. |
| #147 | |
| #148 | ### 4. Verify |
| #149 | |
| #150 | ```bash |
| #151 | opensea wallet info |
| #152 | ``` |
| #153 | |
| #154 | Confirm `isRootUser: false`. If you see `isRootUser: true`, the agent is running as a root user and you've defeated the hardening — go back to step 1 and create a non-root user. |
| #155 | |
| #156 | ### 5. Fund |
| #157 | |
| #158 | Send ETH to `TURNKEY_WALLET_ADDRESS`, then execute a swap: |
| #159 | |
| #160 | ```bash |
| #161 | opensea swaps execute \ |
| #162 | --wallet-provider turnkey \ |
| #163 | --from-chain base \ |
| #164 | --from-address 0x0000000000000000000000000000000000000000 \ |
| #165 | --to-chain base \ |
| #166 | --to-address 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \ |
| #167 | --quantity 0.001 |
| #168 | ``` |
| #169 | |
| #170 | For aggregate caps, see `references/wallet-funding.md` — Turnkey policies are stateless per-activity evaluators and cannot enforce daily/weekly cumulative caps. Wallet float is the answer. |
| #171 | |
| #172 | --- |
| #173 | |
| #174 | ## Fireblocks Setup |
| #175 | |
| #176 | ### Prerequisites |
| #177 | |
| #178 | - A Fireblocks account ([fireblocks.com](https://www.fireblocks.com)) |
| #179 | - An OpenSea API key (`OPENSEA_API_KEY`) |
| #180 | |
| #181 | ### 1. Create a Signer-role API User |
| #182 | |
| #183 | 1. In the Fireblocks console, go to **Settings → API Users**. |
| #184 | 2. Create a new API user. **Set the role to `Signer`** (or `NCW_SIGNER` for non-custodial wallets). **Do not pick `Admin`, `Editor`, `Non-Signing Admin`, or any role with governance access.** |
| #185 | 3. The role is set at user-creation time; there is no downgrade path. To change a role, delete the user and create a new one. |
| #186 | 4. Approval of the new API user requires admin quorum (per workspace settings). After the quorum approves, download the **API secret** (RSA private key PEM file) and note the **API key**. |
| #187 | |
| #188 | > **Why this matters:** Fireblocks does not expose API-user role via API — there is no `/v1/users/me` introspection endpoint. `opensea wallet info` therefore cannot verify the role at runtime, only print a static reminder. **Verifying that the API key has the `Signer` role and only the `Signer` role is on you.** Re-confirm whenever the key is rotated. |
| #189 | > |
| #190 | > The platform's TAP-edits-require-quorum property only protects you if the agent's key is correctly scoped in the first place. An `Admin` key can edit TAP unilaterally; a `Signer` key cannot. |
| #191 | |
| #192 | ### 2. Create a Vault Account |
| #193 | |
| #194 | Create a vault account with an ETH (or relevant EVM) wallet. Note the **vault account ID**. |
| #195 | |
| #196 | ### 3. Set Environment Variables |
| #197 | |
| #198 | ```bash |
| #199 | export OPENSEA_API_KEY="your-opensea-api-key" |
| #200 | export FIREBLOCKS_API_KEY="your-fireblocks-api-key" |
| #201 | export FIREBLOCKS_API_SECRET="$(cat /path/to/fireblocks-secret.pem)" |
| #202 | export FIREBLOCKS_VAULT_ID="your-vault-account-id" |
| #203 | # Optional: override asset ID (default: auto-detected from chain) |
| #204 | # export FIREBLOCKS_ASSET_ID="ETH" |
| #205 | # Optional: override max polling attempts for async transactions (default: 60 = 120s) |
| #206 | # export FIREBLOCKS_MAX_POLL_ATTEMPTS="120" # 240s for multi-party approval workflows |
| #207 | ``` |
| #208 | |
| #209 | > **Note:** Fireblocks transactions are asynchronous (MPC signing). The adapter polls for completion with a default timeout of 120 seconds (60 attempts × 2s). For transactions requiring multi-party approval, increase `FIREBLOCKS_MAX_POLL_ATTEMPTS`. |
| #210 | |
| #211 | ### 4. Fund & Verify |
| #212 | |
| #213 | Fund the vault account via the Fireblocks console or an external transfer, then execute a swap: |
| #214 | |
| #215 | ```bash |
| #216 | opensea swaps execute \ |
| #217 | --wallet-provider fireblocks \ |
| #218 | --from-chain base \ |
| #219 | --from-address 0x0000000000000000000000000000000000000000 \ |
| #220 | --to-chain base \ |
| #221 | --to-address 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \ |
| #222 | --quantity 0.001 |
| #223 | ``` |
| #224 | |
| #225 | --- |
| #226 | |
| #227 | ## Bankr Setup |
| #228 | |
| #229 | Bankr is an HTTP signing service for agent wallets. The adapter calls Bankr's API for transaction signing rather than holding a key locally. |
| #230 | |
| #231 | ### Prerequisites |
| #232 | |
| #233 | - A Bankr account ([bankr.bot](https://bankr.bot)) |
| #234 | - An OpenSea API key (`OPENSEA_API_KEY`) |
| #235 | |
| #236 | ### 1. Provision an API Key with appropriate scope flags |
| #237 | |
| #238 | Sign up at [bankr.bot](https://bankr.bot) and create a key at [bankr.bot/api](https://bankr.bot/api). Configure the key's scope flags as part of the happy path, not optional hardening: |
| #239 | |
| #240 | - **For monitoring-only agents:** enable `readOnly`. The agent can call read endpoints (prices, balances, analytics) but `/wallet/sign` and `/wallet/submit` will return 403. |
| #241 | - **For signing agents:** set the following on the key — |
| #242 | - `allowedRecipients`: an EVM (and Solana, if needed) address allowlist of destinations the agent should be permitted to send to. Empty means no allowlist; that's a footgun. |
| #243 | - `allowedIps`: CIDR ranges for the agent's deploy environment. Restricts where the key can be used from. |
| #244 | - Daily message limit: set this on the key page. |
| #245 | - Disable `agentApiEnabled` if the agent does not need Bankr's prompt API; only enable `walletApiEnabled` if signing is actually needed. |
| #246 | |
| #247 | > **Why this matters:** Bankr does not expose key-scope flags via API. `opensea wallet info` cannot verify these settings at runtime, only print a static reminder. **You are responsible for setting them correctly at the dashboard and re-confirming after any key rotation.** |
| #248 | |
| #249 | ### 2. Set Environment Variables |
| #250 | |
| #251 | ```bash |
| #252 | export OPENSEA_API_KEY="your-opensea-api-key" |
| #253 | export BANKR_API_KEY="your-bankr-api-key" |
| #254 | ``` |
| #255 | |
| #256 | ### 3. Verify |
| #257 | |
| #258 | ```bash |
| #259 | opensea wallet info |
| #260 | ``` |
| #261 | |
| #262 | Confirms the key reaches Bankr and prints the wallet address. The output will include the static reminder to re-verify scope flags at bankr.bot/api — this is by design, not a bug. |
| #263 | |
| #264 | ### 4. Execute a Swap |
| #265 | |
| #266 | ```bash |
| #267 | opensea swaps execute \ |
| #268 | --wallet-provider bankr \ |
| #269 | --from-chain base \ |
| #270 | --from-address 0x0000000000000000000000000000000000000000 \ |
| #271 | --to-chain base \ |
| #272 | --to-address 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \ |
| #273 | --quantity 0.001 |
| #274 | ``` |
| #275 | |
| #276 | ### SDK Usage |
| #277 | |
| #278 | ```typescript |
| #279 | import { createBankrAccount } from '@opensea/wallet-adapters'; |
| #280 | |
| #281 | const account = await createBankrAccount(process.env.BANKR_API_KEY); |
| #282 | // Use with eip3009AuthenticatedFetch, paidAuthenticatedFetch, or the OpenSeaCLI swaps API |
| #283 | ``` |
| #284 | |
| #285 | --- |
| #286 | |
| #287 | ## Private Key Setup (Not Recommended) |
| #288 | |
| #289 | > **WARNING:** Using a raw private key provides no spending limits, no destination allowlists, and no human-in-the-loop approval. Use a managed provider (Privy, Turnkey, Fireblocks) for anything beyond local development. |
| #290 | |
| #291 | ### Set Environment Variables |
| #292 | |
| #293 | ```bash |
| #294 | export OPENSEA_API_KEY="your-opensea-api-key" |
| #295 | export PRIVATE_KEY="0xYourHexPrivateKey" |
| #296 | export RPC_URL="http://127.0.0.1:8545" # local dev node only (Hardhat/Anvil/Ganache) |
| #297 | export WALLET_ADDRESS="0xYourWalletAddress" |
| #298 | ``` |
| #299 | |
| #300 | ### Execute a Swap |
| #301 | |
| #302 | ```bash |
| #303 | opensea swaps execute \ |
| #304 | --wallet-provider private-key \ |
| #305 | --from-chain base \ |
| #306 | --from-address 0x0000000000000000000000000000000000000000 \ |
| #307 | --to-chain base \ |
| #308 | --to-address 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \ |
| #309 | --quantity 0.001 |
| #310 | ``` |
| #311 | |
| #312 | **Note:** The private-key adapter uses `eth_sendTransaction` on the RPC node, which requires the node to manage the imported key (e.g. Hardhat, Anvil, Ganache). The `PRIVATE_KEY` env var is validated to confirm intent but is not used for signing; the RPC node signs server-side. This adapter does **not** work with production RPC providers like Infura or Alchemy. Use a managed wallet instead. |
| #313 | |
| #314 | --- |
| #315 | |
| #316 | ## Using the Wallet |
| #317 | |
| #318 | ### CLI |
| #319 | |
| #320 | ```bash |
| #321 | # Auto-detect provider from env vars (defaults to Privy) |
| #322 | opensea swaps execute \ |
| #323 | --from-chain base \ |
| #324 | --from-address 0x0000000000000000000000000000000000000000 \ |
| #325 | --to-chain base \ |
| #326 | --to-address 0xb695559b26bb2c9703ef1935c37aeae9526bab07 \ |
| #327 | --quantity 0.02 |
| #328 | |
| #329 | # Explicitly specify provider |
| #330 | opensea swaps execute --wallet-provider turnkey ... |
| #331 | opensea swaps execute --wallet-provider fireblocks ... |
| #332 | opensea swaps execute --wallet-provider bankr ... |
| #333 | opensea swaps execute --wallet-provider private-key ... # not recommended |
| #334 | ``` |
| #335 | |
| #336 | ### SDK (TypeScript) |
| #337 | |
| #338 | ```typescript |
| #339 | import { |
| #340 | OpenSeaCLI, |
| #341 | PrivyAdapter, |
| #342 | TurnkeyAdapter, |
| #343 | FireblocksAdapter, |
| #344 | BankrAdapter, |
| #345 | PrivateKeyAdapter, |
| #346 | createWalletFromEnv, |
| #347 | } from '@opensea/cli'; |
| #348 | |
| #349 | const sdk = new OpenSeaCLI({ apiKey: process.env.OPENSEA_API_KEY }); |
| #350 | |
| #351 | // Auto-detect from env vars |
| #352 | const wallet = createWalletFromEnv(); |
| #353 | |
| #354 | // Or use a specific provider |
| #355 | // const wallet = PrivyAdapter.fromEnv(); |
| #356 | // const wallet = TurnkeyAdapter.fromEnv(); |
| #357 | // const wallet = FireblocksAdapter.fromEnv(); |
| #358 | // const wallet = BankrAdapter.fromEnv(); |
| #359 | // const wallet = PrivateKeyAdapter.fromEnv(); // not recommended |
| #360 | |
| #361 | const results = await sdk.swaps.execute({ |
| #362 | fromChain: 'base', |
| #363 | fromAddress: '0x0000000000000000000000000000000000000000', |
| #364 | toChain: 'base', |
| #365 | toAddress: '0xb695559b26bb2c9703ef1935c37aeae9526bab07', |
| #366 | quantity: '0.02', |
| #367 | }, wallet); |
| #368 | ``` |
| #369 | |
| #370 | ### Shell Script |
| #371 | |
| #372 | ```bash |
| #373 | ./scripts/opensea-swap.sh 0xb695559b26bb2c9703ef1935c37aeae9526bab07 0.02 base |
| #374 | ``` |
| #375 | |
| #376 | ## Troubleshooting |
| #377 | |
| #378 | | Error | Cause | Fix | |
| #379 | |-------|-------|-----| |
| #380 | | `PRIVY_APP_ID environment variable is required` | Missing Privy env var | Set Privy credentials or use `--wallet-provider` to pick another provider | |
| #381 | | `Privy getAddress failed (401)` | Bad Privy credentials | Check `PRIVY_APP_ID` and `PRIVY_APP_SECRET` | |
| #382 | | `Privy getAddress failed (401): Invalid app ID or app secret` with creds confirmed correct in env | Almost always `echo` vs `printf %s` when verifying via curl. `echo` adds a trailing newline that breaks the basic-auth header. | Use `opensea wallet info` instead, or build the header with `printf %s "$PRIVY_APP_ID:$PRIVY_APP_SECRET" \| base64`. | |
| #383 | | `Privy sendTransaction failed (403)` | Policy violation | Review wallet policies (see `wallet-policies.md`) | |
| #384 | | `TURNKEY_API_PUBLIC_KEY environment variable is required` | Missing Turnkey env var | Set Turnkey credentials | |
| #385 | | `Turnkey sendTransaction failed` | Turnkey API error | Check API keys and organization ID | |
| #386 | | `FIREBLOCKS_API_KEY environment variable is required` | Missing Fireblocks env var | Set Fireblocks credentials | |
| #387 | | `No Fireblocks asset ID mapping for chain` | Unsupported chain | Set `FIREBLOCKS_ASSET_ID` explicitly | |
| #388 | | `Fireblocks transaction ended with status: REJECTED` | Policy rejection | Review Fireblocks TAP rules | |
| #389 | | `BANKR_API_KEY environment variable is required` | Missing Bankr env var | Set `BANKR_API_KEY` | |
| #390 | | `Bankr signing failed (401)` | Bad Bankr API key | Verify the key at [bankr.bot](https://bankr.bot) | |
| #391 | | `PRIVATE_KEY environment variable is required` | Missing private key env var | Set `PRIVATE_KEY`, `RPC_URL`, and `WALLET_ADDRESS` | |
| #392 | | `RPC_URL environment variable is required` | Missing RPC URL | Set `RPC_URL` for the target chain | |
| #393 | | `insufficient funds` | Wallet not funded | Send ETH to the wallet address | |
| #394 |