# agents-pay setup Full provisioning walkthrough. **A human runs every step here** except the final wiring. See the tool inventory in SKILL.md for what the agent can call. ## 0. Install ```bash python3 -m venv .venv source .venv/bin/activate python -m pip install -r requirements.txt npm install -g @aws/agentcore agentcore --version python -c "from bedrock_agentcore.payments import PaymentManager; print('payments OK')" ``` If that import fails, install a `bedrock-agentcore` release with payments support before continuing. Keep the virtual environment active for the Python commands below. `requirements.txt` pins the complete Python runtime graph used by this skill. For integrity pinning in your own release process, generate platform-specific hashes from that exact graph: ```bash python -m pip download -r requirements.txt -d wheels/ python -m pip hash wheels/* ``` ## 1. Obtain provider credentials Do this before running the connector command. **Never paste these into a chat, an agent prompt, or a command flag.** **Coinbase CDP** — 1. Create or open a CDP project 2. Create an API key — note the **API Key ID** and **API Key Secret** 3. Generate a **Wallet Secret** (used for signing operations) 4. Project → Wallet → Embedded Wallets → Policies → **enable Delegated signing** (required) **Stripe Privy** — 1. Create a **dedicated** Privy app for AgentCore (do not reuse an existing app) 2. Copy the **App ID** and **App Secret** 3. Wallet Infrastructure → Authorization → New Key → generate a P-256 key pair 4. Strip the `wallet-auth:` prefix from the private key; keep the raw base64 (starts `MIGHAgEA...`) 5. Note the **Authorization ID** ## 2. Create the payment manager Leave the LLM conversation and run this in a separate terminal. Do not paste credentials, command output, deployed state, or generated IDs back into chat. Use the bare command so the whole setup stays in the interactive terminal: ```bash agentcore add payment-manager ``` Names: start with a letter, alphanumeric plus underscores, ≤48 characters. Optionally tag the project so the service can distinguish skill-onboarded resources — add to the top-level `tags` object in `agentcore/agentcore.json`, keeping existing entries: ```json "tags": { "agentcore:onboarding-source": "agent-toolkit-skill" } ``` ## 3. Create the payment connector Run it with **no flags**. The wizard prompts for each secret, so nothing lands in shell history or the process list: ```bash agentcore add payment-connector ``` Passing `--manager`, `--name`, or `--provider` switches the CLI to non-interactive mode and then *requires every secret as a flag* — it will not fall back to prompting. Use the bare command. The CLI writes provider secrets in **plaintext** to `agentcore/.env.local` and uploads them to AgentCore Identity on deploy. Before deploying: ```bash grep -q '^\.env\.local$\|^\.env\.\*$' .gitignore || echo '.env.local' >> .gitignore chmod 600 agentcore/.env.local ``` The Python scaffold's default `.gitignore` lists only `.env`, so `.env.local` must be added. **The agent must never read this file.** ## 4. Deploy ```bash agentcore deploy ``` Provisions the manager and connector, plus a `PaymentProcessPaymentRole`. IDs land in `agentcore/.cli/deployed-state.json` under `targets..resources.payments[]` (`managerArn`, `connectors[].connectorId`, `processPaymentRoleArn`). ## 5. IAM: the four-role model **Follow the official guide:** AgentCore Payments defines four roles. This skill's design assumes them, and the security properties below depend on the separation being real in your account. | Role | Purpose | Who assumes it | |---|---|---| | **ControlPlaneRole** | Payment managers, connectors, credential providers | Administrator | | **ManagementRole** | Payment **instruments and sessions**. Explicitly denied `ProcessPayment` | The human running this admin CLI | | **ProcessPaymentRole** | Executes payments. Cannot create sessions | The agent runtime | | **ResourceRetrievalRole** | Service role AgentCore assumes to fetch credentials | `bedrock-agentcore.amazonaws.com` | The AWS guide states the reason directly: > "Separating payment management from payment execution prevents a single > compromised identity from both creating sessions with unlimited budgets and > executing payments against those sessions." This separation keeps payment management and execution in different trusted roles. ### Which role runs which command | Step | Command | Role | |---|---|---| | 1 | `agentcore add payment-manager` / `payment-connector` / `deploy` | **ControlPlaneRole** | | 2 | `agents_pay_admin.py init-config` | none — writes a local file only | | 3 | `agents_pay_admin.py create-instrument` | **ManagementRole** | | 4 | `agents_pay_admin.py new-session` | **ManagementRole** | | 6 | the agent calling `x402_fetch` | **ProcessPaymentRole** | ### ManagementRole — for the human running the admin CLI Note the explicit `Deny`. It is not decoration: it is what stops this role from being usable to both mint budget and spend it. ```json { "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPaymentManagement", "Effect": "Allow", "Action": [ "bedrock-agentcore:CreatePaymentInstrument", "bedrock-agentcore:GetPaymentInstrument", "bedrock-agentcore:ListPaymentInstruments", "bedrock-agentcore:DeletePaymentInstrument", "bedrock-agentcore:CreatePaymentSession", "bedrock-agentcore:GetPaymentSession", "bedrock-agentcore:ListPaymentSessions", "bedrock-agentcore:DeletePaymentSession" ], "Resource": [ "arn:aws:bedrock-agentcore:*:ACCOUNT:payment-manager/*/instrument/*", "arn:aws:bedrock-agentcore:*:ACCOUNT:payment-manager/*/session/*" ] }, { "Sid": "DenyProcessPayment", "Effect": "Deny", "Action": "bedrock-agentcore:ProcessPayment", "Resource": "*" } ] } ``` ### ProcessPaymentRole — for the agent runtime `ProcessPayment` plus reads. **No session-write actions.** ```json { "Version": "2012-10-17", "Statement": [ { "Sid": "AllowProcessPayment", "Effect": "Allow", "Action": "bedrock-agentcore:ProcessPayment", "Resource": ["arn:aws:bedrock-agentcore:*:ACCOUNT:payment-manager/*/session/*"] }, { "Sid": "AllowPaymentReadOperations", "Effect": "Allow", "Action": [ "bedrock-agentcore:GetPaymentInstrument", "bedrock-agentcore:GetPaymentInstrumentBalance", "bedrock-agentcore:GetPaymentSession" ], "Resource": [ "arn:aws:bedrock-agentcore:*:ACCOUNT:payment-manager/*/instrument/*", "arn:aws:bedrock-agentcore:*:ACCOUNT:payment-manager/*/session/*" ] } ] } ``` The AWS guide is emphatic about the combination to avoid: > "Do not include PaymentSession *write* permissions (for example, > `CreatePaymentSession`) and `ProcessPayment` in the same role, or the caller can > bypass payment limits by creating new sessions with elevated budgets." Verify after deploy: ```bash aws iam get-role-policy --role-name --policy-name \ | grep -c CreatePaymentSession # must be 0 ``` A runtime `AccessDeniedException` on `CreatePaymentSession` is **the control working**. Do not "fix" it by granting the permission. ### ResourceRetrievalRole The service role AgentCore assumes at runtime. Created and wired by `agentcore deploy`; it is not assigned to a human. Its trust policy must name `bedrock-agentcore.amazonaws.com` with an `aws:SourceAccount` condition. See the guide for the full base and per-connector permissions. ## 6. Write the payment policy ```bash python3 scripts/agents_pay_admin.py init-config \ --max-per-payment-usd 0.05 \ --network eip155:84532 \ --recipient 0xMerchantWalletAddress ``` To find a merchant's real `payTo` and amount before allowlisting, read its challenge without paying: ```bash curl -sD - https:/// -o /dev/null \ | awk '/^payment-required:/{print $2}' | base64 -d | python3 -m json.tool ``` Inspect `accepts[].payTo`, `.amount` (integer base units; USDC has 6 decimals, so `2000` = $0.002), `.asset`, and `.network`, then allowlist deliberately with `--recipient`. Unknown recipients are refused before signing. If the operator deliberately accepts publisher-selected beneficiaries, replace all `--recipient` flags with `--allow-any-recipient`. These modes are mutually exclusive. The flag does not relax scheme, network, asset, origin/resource, per-payment, or cumulative session limits. Add `--origin https://host` (repeatable) only if you want to pin the agent to a known merchant set; omitted, it may fetch any public HTTPS site. ## 7. Create the per-user instrument One wallet for this installation. The payer identity is read from the config, where `init-config` generated it — this skill is single-tenant, so there is nothing to invent or keep in sync: ```bash python3 scripts/agents_pay_admin.py create-instrument --email you@example.com ``` The manager ARN and connector ID are read from `agentcore/.cli/deployed-state.json` automatically, so run this from your AgentCore project directory. Otherwise pass `--manager-arn` and `--connector-id` explicitly. Add `--network-family SOLANA` for Solana; the default `ETHEREUM` covers Base and Base Sepolia. Run it under the **setup** role, not the runtime role. It prints the instrument ID, the wallet address, the delegation URL, and the `export` lines for the runtime. ## 8. Delegation and funding One-time per wallet, and **do this before creating a session.** Both steps attach to the *wallet*, not to a session: `create_payment_session` takes only a user, an expiry, and a budget. Sessions are time-bounded (60 minutes by default), so minting one first and then going off to complete a browser flow and a faucet transfer simply burns the clock — the budget can expire before the agent has spent anything. **Delegation** — authorize the agent to spend from the wallet: - *Coinbase CDP*: the end user visits the `redirectUrl`, signs in, grants permission to the wallet address - *Stripe Privy*: no redirect URL; use the Privy frontend SDK (), sign in with the end user's email, approve delegation **Funding** — send testnet USDC to the wallet address via (Base Sepolia). Fund only what the agent may plausibly spend: wallet balance is the backstop if every other control fails. ## 9. Approve a session and run preflight ```bash python3 scripts/agents_pay_admin.py new-session --budget 1.00 --expiry-minutes 60 export PAYMENT_MANAGER_ARN=... PAYMENT_INSTRUMENT_ID=... export PAYMENT_SESSION_ID=... PAYMENT_USER_ID=alice export AWS_REGION=us-west-2 python3 scripts/agents_pay_admin.py preflight python3 scripts/test_x402_policy.py ``` Keep sessions short (≤60 minutes) and budgets small. When the budget is spent, a human runs `new-session` again — the agent cannot. ## Networks Two distinct concepts, and mixing them up is a common setup failure: - **network family** — used when creating the instrument (`--network-family`) - **chain** — the CAIP-2 identifier that appears in x402 challenges and in this skill's `allowed_networks` **Families (instrument creation):** | Family | Value | Covers | Providers | |---|---|---|---| | Ethereum | `ETHEREUM` | Base, Base Sepolia, Ethereum mainnet | Coinbase, Stripe | | Solana | `SOLANA` | Solana, Solana Devnet | Coinbase, Stripe | **Chains (x402 challenges, `allowed_networks`, balance queries):** | Chain | CAIP-2 identifier | Balance API value | Type | Providers | |---|---|---|---|---| | Base Sepolia | `eip155:84532` (or `base-sepolia`) | `BASE_SEPOLIA` | **Testnet** | Coinbase | | Base | `eip155:8453` | `BASE` | Mainnet | Coinbase | | Ethereum | `eip155:1` | `ETHEREUM` | Mainnet | Coinbase, Stripe | | Solana | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | `SOLANA` | Mainnet | Coinbase, Stripe | | Solana Devnet | `solana-devnet` | `SOLANA_DEVNET` | **Testnet** | Stripe | Start on **Base Sepolia** (family `ETHEREUM`, chain `eip155:84532`) — free testnet USDC from . `init-config` only knows the USDC contract for the Base chains; adding another chain means adding its exact contract to `KNOWN_USDC` in the admin CLI, deliberately, rather than passing an address in. ## Operational monitoring - Confirm CloudTrail records `bedrock-agentcore` calls, especially `ProcessPayment` - Alarm on `CreatePaymentSession` by the runtime principal — it should be impossible, so any occurrence means the IAM split has regressed - Alarm on repeated `ProcessPayment` failures as an abuse signal - Review `show-config` output whenever payments start refusing unexpectedly