@bnbagent/studio-cli 0.0.6-alpha.4 → 0.0.6-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -115,12 +115,13 @@ function twakDoubledHomeHint(walletFile) {
115
115
  return `hint: the resolved wallet path ${walletFile} nests .twak/.twak \u2014 [wallet].twak_home in studio.toml likely points at the .twak directory itself. Set it to the PARENT directory (the runtime appends .twak/wallet.json) and re-run.`;
116
116
  }
117
117
  function twakCreateGuidance(fields) {
118
- return `error: wallet.kind = "twak" \u2014 \`bag wallet new\` does not create twak wallets.
119
- studio never puts a wallet password on argv (upstream S-8), so it will not shell
120
- out \`twak wallet create --password \u2026\` for you. Create the wallet ONCE
121
- (you choose the password; pick a strong one).
118
+ return `error: wallet.kind = "twak" \u2014 \`bag wallet new\` only adopts an existing
119
+ twak wallet's address. Create the wallet ONCE (you choose the password; pick a
120
+ strong one).
122
121
 
123
- RECOMMENDED \u2014 let studio drive the create step with the password OFF argv:
122
+ RECOMMENDED \u2014 let studio drive the create step so you never type the password
123
+ on a command line (twak requires --password on its own argv, so the value does
124
+ reach that short-lived child process):
124
125
 
125
126
  bag wallet twak-init --password-stdin # CI: printf %s "$PW" | bag wallet twak-init --password-stdin
126
127
  bag wallet twak-init --password-file <f> # file must be chmod 600
@@ -136,13 +137,15 @@ secret, then run \`twak setup\` (without them \`twak wallet create\` fails with
136
137
 
137
138
  ${fields.setupCmd}
138
139
 
139
- then create the wallet \u2014 interactive (password on twak's own argv; twak warns,
140
- and it lands in your shell history \u2014 acceptable for a throwaway hot wallet):
140
+ then create the wallet \u2014 interactive (you type the password on argv yourself;
141
+ twak warns, and it lands in your shell history \u2014 acceptable for a throwaway hot
142
+ wallet):
141
143
 
142
144
  ${fields.createCmd}
143
145
 
144
- \u2014 or, to keep the password OFF argv (CI / scripts), pass it via the
145
- environment and let \`twak setup\` create the wallet non-interactively:
146
+ \u2014 or, to keep the password out of your shell history (CI / scripts), pass it
147
+ via the environment and let \`twak setup\` create the wallet
148
+ non-interactively:
146
149
 
147
150
  TWAK_NONINTERACTIVE=1 TWAK_SETUP_WALLET=create TWAK_WALLET_PASSWORD=<pw> ${fields.setupCmd}
148
151
 
@@ -5,7 +5,7 @@ import {
5
5
  runCaptureOut,
6
6
  runStream,
7
7
  whichTwak
8
- } from "./chunk-7RAKL4AS.js";
8
+ } from "./chunk-RO726HJG.js";
9
9
 
10
10
  // src/cli/_deploy/deployCli.ts
11
11
  import * as fs4 from "fs";
@@ -296,6 +296,8 @@ function mb(n) {
296
296
  // src/cli/_x402SellerConfig.ts
297
297
  var DEFAULT_B402_PRICE_USD = "0.01";
298
298
  var DEFAULT_B402_TESTNET_BASE_URL = "https://qacb.sdtaop.com";
299
+ var B402_DEVELOPER_ACCOUNT_URL = "https://developers.binance.com/en/docs/products/onchainpay-x402/basics/6.apply-developer-account";
300
+ var B402_PAID_ONBOARDING_GUIDANCE = `Create the agent wallet first, then apply for a separate B402 merchant for that exact wallet and environment; never reuse merchant credentials across wallets. Apply: ${B402_DEVELOPER_ACCOUNT_URL}`;
299
301
  var PRICE_USD_RE = /^(?:0|[1-9]\d*)(?:\.\d+)?$/;
300
302
  var ZERO_PRICE_USD_RE = /^0(?:\.0+)?$/;
301
303
  function x402SellerPricingState(seller) {
@@ -687,7 +689,7 @@ function x402DeploySummary(root, destination, publicUrl) {
687
689
  const seller = table3(table3(cfg.payments).x402_seller);
688
690
  const pricing = x402SellerPricingState(seller);
689
691
  const credentials = b402Credentials(agentRoot);
690
- const activation = "run the bnbagent-studio-selling-via-b402 skill, fill the four B402_* variables in .studio/.env.local, then redeploy.";
692
+ const activation = `${B402_PAID_ONBOARDING_GUIDANCE} Then run the bnbagent-studio-selling-via-b402 skill, fill the four B402_* variables in .studio/.env.local, and redeploy.`;
691
693
  const runtime = String(table3(cfg.stack).runtime ?? "agentcore");
692
694
  if (destination !== "platform" && runtime !== "agentcore") {
693
695
  return `x402 rail is FORCED DORMANT: the ${runtime} runtime has no x402 path. Deploy to AgentCore (managed platform or self-hosted) to activate the rail.`;
@@ -701,7 +703,9 @@ function x402DeploySummary(root, destination, publicUrl) {
701
703
  "x402 rail is ACTIVE in FREE mode (self-hosted AgentCore).",
702
704
  "B402 verify/settle is bypassed; no credentials, token payment, or settlement audit is used.",
703
705
  "This target has no anonymous public URL; expose /x402 with your own HTTP front.",
704
- 'The front reaches the agent by wrapping each request as envelope-v1 JSON \u2014 {"v":1,"method":"POST","path":"/x402","headers":{...},"body":"<base64>"} \u2014 inside a SigV4-signed (or JWT bearer) InvokeAgentRuntime call. Limits: 1 MiB request, 5 MiB response, no streaming.'
706
+ 'The front wraps each request as envelope-v1 JSON, {"v":1,"method":"POST","path":"/x402","headers":{...},"body":"<base64>"}, inside an authenticated AgentCore invocation. The default Bag self-deploy uses Cognito OAuth over HTTPS; IAM runtimes may use SDK/SigV4. Limits: 1 MiB request, 5 MiB response, no streaming.',
707
+ "You may invoke AgentCore with envelope-v1 directly, but that is not a standard public x402 HTTP endpoint.",
708
+ "Gateway example: https://github.com/bnb-chain/bnbagent-studio/blob/main/docs/guides/self-hosted-x402-gateway.md"
705
709
  ].join("\n");
706
710
  }
707
711
  const url2 = publicUrl ?? "<available after the platform agentId is assigned>";
@@ -715,16 +719,18 @@ function x402DeploySummary(root, destination, publicUrl) {
715
719
  return `x402 rail is DORMANT. To activate: ${activation}`;
716
720
  }
717
721
  const settlement = [
718
- "B402 settlement runs inside every paid request and normally adds 10\u201345 s; buyer timeout must be at least 120 s.",
719
- "Settlement happens before work. If work later fails, the payment is retained and is not refunded automatically."
722
+ "B402 settlement can add significant latency; buyer timeout must be at least 120 s.",
723
+ "Settlement happens before work. If work later fails, the payment is retained and is not refunded automatically.",
724
+ "Compatibility note: current @bnb-chain/b402@0.1.0 makes one settle call and classifies an asynchronous pending response as outcome unknown; reconcile it and do not replay the paid request."
720
725
  ];
721
726
  if (destination !== "platform") {
722
727
  return [
723
728
  "x402 rail is ACTIVE (self-hosted AgentCore).",
724
729
  "This target has no anonymous public URL; expose /x402 with your own HTTP front.",
725
- 'The front reaches the agent by wrapping each request as envelope-v1 JSON \u2014 {"v":1,"method":"POST","path":"/x402","headers":{...},"body":"<base64>"} \u2014 inside a SigV4-signed (or JWT bearer) InvokeAgentRuntime call. Limits: 1 MiB request, 5 MiB response, no streaming.',
726
- "B402 allowlists merchant egress IPs and this deploy has no fixed egress IP: route facilitator egress through a fixed-IP forward proxy you control and submit that IP with the merchant application (see the bnbagent-studio-selling-via-b402 reference).",
727
- 'See docs/guides/x402-selling.md, section "Self-hosted AgentCore access".',
730
+ 'The front wraps each request as envelope-v1 JSON, {"v":1,"method":"POST","path":"/x402","headers":{...},"body":"<base64>"}, inside an authenticated AgentCore invocation. The default Bag self-deploy uses Cognito OAuth over HTTPS; IAM runtimes may use SDK/SigV4. Limits: 1 MiB request, 5 MiB response, no streaming.',
731
+ "You may invoke AgentCore with envelope-v1 directly, but the caller must unpack the inner HTTP response and the result is not a standard public x402 endpoint.",
732
+ "PAID also needs fixed facilitator egress. Operate a restricted B402 Relay on a fixed-IP host such as a user-managed VPS, or use AgentCore VPC mode with a private subnet, NAT Gateway, and Elastic IP.",
733
+ "Gateway and Relay examples: https://github.com/bnb-chain/bnbagent-studio/blob/main/docs/guides/self-hosted-x402-gateway.md",
728
734
  ...settlement
729
735
  ].join("\n");
730
736
  }
@@ -732,6 +738,8 @@ function x402DeploySummary(root, destination, publicUrl) {
732
738
  return [
733
739
  "x402 rail is ACTIVE.",
734
740
  `Anonymous paid URL: ${url}`,
741
+ "The managed platform supplies the x402 HTTP Gateway and the fixed-egress B402 Relay; no user-managed gateway, proxy, or VPC is required.",
742
+ "Payment policy and RSA signing remain inside the AgentCore seller. The B402 Relay only forwards the signed request to its fixed facilitator upstream.",
735
743
  ...settlement
736
744
  ].join("\n");
737
745
  }
@@ -996,28 +1004,76 @@ async function ensureRunnable(environment = process.env) {
996
1004
  }
997
1005
  return true;
998
1006
  }
1007
+ var MODULE_MISSING_MARKER = "Cannot find module";
1008
+ var BUNX_TREE_HINT = [
1009
+ `error: ${DEPLOY_CLI_PACKAGE} could not load its own dependencies \u2014 its `,
1010
+ "bunx install directory is incomplete. Remove it and retry:\n",
1011
+ ' rm -rf "${TMPDIR:-/tmp}"/bunx-*-@bnbagent\n',
1012
+ "If it persists, clear Bun's package cache (`bun pm cache rm`), or point ",
1013
+ "BNBAGENT_DEPLOY_COMMAND at a local checkout."
1014
+ ].join("");
1015
+ function bunxInstallDir(environment = process.env) {
1016
+ if (deployCommand(environment)[0] !== "bunx") {
1017
+ return null;
1018
+ }
1019
+ const uid = typeof process.getuid === "function" ? process.getuid() : null;
1020
+ return uid === null ? null : path5.join(os.tmpdir(), `bunx-${uid}-${DEPLOY_CLI_PACKAGE}`);
1021
+ }
1022
+ async function bunxTreeIsIncomplete() {
1023
+ const [bin, ...prefix] = deployCommand();
1024
+ const probe = await runCapture(bin, [...prefix, "--help"], {
1025
+ timeoutMs: 12e4
1026
+ });
1027
+ return probe.code !== 0 && `${probe.stderr}
1028
+ ${probe.stdout}`.includes(MODULE_MISSING_MARKER);
1029
+ }
1030
+ async function withBunxSelfHeal(hasFailed, attempt) {
1031
+ const first = await attempt();
1032
+ if (!hasFailed(first)) {
1033
+ return first;
1034
+ }
1035
+ const dir = bunxInstallDir();
1036
+ if (dir === null || !await bunxTreeIsIncomplete()) {
1037
+ return first;
1038
+ }
1039
+ if (!fs4.existsSync(dir)) {
1040
+ printErr(BUNX_TREE_HINT);
1041
+ return first;
1042
+ }
1043
+ printErr(
1044
+ `note: ${DEPLOY_CLI_PACKAGE} failed to load its dependencies; clearing its incomplete bunx install and retrying once (${dir}).`
1045
+ );
1046
+ fs4.rmSync(dir, { recursive: true, force: true });
1047
+ const second = await attempt();
1048
+ if (hasFailed(second)) {
1049
+ printErr(BUNX_TREE_HINT);
1050
+ }
1051
+ return second;
1052
+ }
999
1053
  async function runDeployCliStream(argv, opts = {}) {
1000
1054
  if (!await ensureRunnable()) {
1001
1055
  return 127;
1002
1056
  }
1003
1057
  const [bin, ...prefix] = deployCommand();
1004
- return runStream(bin, [...prefix, ...argv], {
1005
- cwd: opts.cwd,
1006
- env: opts.env ? { ...process.env, ...opts.env } : void 0
1007
- });
1058
+ return withBunxSelfHeal(
1059
+ (code) => code !== 0,
1060
+ () => runStream(bin, [...prefix, ...argv], {
1061
+ cwd: opts.cwd,
1062
+ env: opts.env ? { ...process.env, ...opts.env } : void 0
1063
+ })
1064
+ );
1008
1065
  }
1009
1066
  async function runDeployCliJson(argv, opts = {}) {
1010
1067
  if (!await ensureRunnable()) {
1011
1068
  return { code: 127, data: {} };
1012
1069
  }
1013
1070
  const [bin, ...prefix] = deployCommand();
1014
- const { code, stdout } = await runCaptureOut(
1015
- bin,
1016
- [...prefix, ...argv, "--json"],
1017
- {
1071
+ const { code, stdout } = await withBunxSelfHeal(
1072
+ (result) => result.code !== 0,
1073
+ () => runCaptureOut(bin, [...prefix, ...argv, "--json"], {
1018
1074
  cwd: opts.cwd,
1019
1075
  env: opts.env ? { ...process.env, ...opts.env } : void 0
1020
- }
1076
+ })
1021
1077
  );
1022
1078
  let data = {};
1023
1079
  const trimmed = stdout.trim();
@@ -1093,6 +1149,8 @@ export {
1093
1149
  recipeModeOf,
1094
1150
  DEFAULT_B402_PRICE_USD,
1095
1151
  DEFAULT_B402_TESTNET_BASE_URL,
1152
+ B402_DEVELOPER_ACCOUNT_URL,
1153
+ B402_PAID_ONBOARDING_GUIDANCE,
1096
1154
  x402SellerPricingState,
1097
1155
  normalizeB402PriceUsd,
1098
1156
  isB402TestnetBaseUrl,
@@ -1124,6 +1182,7 @@ export {
1124
1182
  buildDeploySpec,
1125
1183
  renderSecretEnvFile,
1126
1184
  withDeployFiles,
1185
+ bunxInstallDir,
1127
1186
  runDeployCliStream,
1128
1187
  runDeployCliJson,
1129
1188
  trialFromDeployCliJson,
@@ -8,6 +8,7 @@ import {
8
8
  bnbEnv,
9
9
  bnbPlatformApiUrl,
10
10
  buildDeploySpec,
11
+ bunxInstallDir,
11
12
  deployCommand,
12
13
  deployEndpointValue,
13
14
  providerPassthrough,
@@ -17,8 +18,8 @@ import {
17
18
  runPlatformAccountCommand,
18
19
  trialFromDeployCliJson,
19
20
  withDeployFiles
20
- } from "./chunk-A7NAGZHR.js";
21
- import "./chunk-7RAKL4AS.js";
21
+ } from "./chunk-VEOOFDSF.js";
22
+ import "./chunk-RO726HJG.js";
22
23
  export {
23
24
  BNB_PLATFORM_API_URL,
24
25
  BNB_PLATFORM_API_URL_ENV,
@@ -28,6 +29,7 @@ export {
28
29
  bnbEnv,
29
30
  bnbPlatformApiUrl,
30
31
  buildDeploySpec,
32
+ bunxInstallDir,
31
33
  deployCommand,
32
34
  deployEndpointValue,
33
35
  providerPassthrough,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bnbagent/studio-cli",
3
- "version": "0.0.6-alpha.4",
3
+ "version": "0.0.6-alpha.5",
4
4
  "description": "The `bag` CLI: scaffold, run, deploy, and monetize a single seller agent on BNB Chain (ERC-8004 identity, ERC-8183 commerce, x402 payments).",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -19,6 +19,7 @@
19
19
  "dist",
20
20
  "skills",
21
21
  "recipes",
22
+ "README.md",
22
23
  "DISCLAIMER.md"
23
24
  ],
24
25
  "bin": {
@@ -37,7 +38,7 @@
37
38
  "tar": "^7.4.0",
38
39
  "viem": "^2.54.0",
39
40
  "yaml": "^2.9.0",
40
- "@bnbagent/studio-runtime": "0.0.6-alpha.4"
41
+ "@bnbagent/studio-runtime": "0.0.6-alpha.5"
41
42
  },
42
43
  "devDependencies": {
43
44
  "@a2a-js/sdk": "^0.3.14",
@@ -59,14 +59,22 @@ flow now auto-adds dispute_window).
59
59
  - For a paid job, the wallet has ≥ 0.05 tBNB (gas) and enough U for the budget
60
60
  plus slack. On BSC testnet the
61
61
  ERC-8183 kernel writes (`createJob` / `fund` deposit / `settle` …) are
62
- gas-sponsored via the SDK's MegaFuel paymaster, so you spend far less tBNB than
62
+ gas-sponsored via the SDK's MegaFuel paymaster **when they target the
63
+ canonical contracts**, so you spend far less tBNB than
63
64
  that — but **not zero**: `fund` sends an ERC-20 `approve` (a token call, not
64
65
  sponsored) when the token allowance is too low — typically just the first fund,
65
66
  since studio approves a floored cap that later jobs reuse. Keep a little tBNB for
66
- it. (Mainnet is never sponsored.) For a FREE job, use `--budget-u 0`: no U
67
- balance, ERC-20 approval, or token escrow is needed, but the ERC-8183 writes
68
- still need the selected gas/paymaster path and a zero-price-compatible
69
- commerce/router/policy stack.
67
+ it. (Mainnet is never sponsored.) Sponsorship is granted per target contract
68
+ by the paymaster policy: a custom/QA stack selected via the
69
+ `ERC8183_*_ADDRESS` overrides is normally **not** covered, so every write
70
+ self-pays gas (the SDK logs `… is not sponsorable on this network;
71
+ self-paying gas` and falls back automatically) — keep tBNB for the whole
72
+ flow, or set `BNBAGENT_USE_PAYMASTER=0` to skip the per-transaction
73
+ sponsorship probe and self-pay directly. For a FREE job, use `--budget-u 0`:
74
+ no U balance, ERC-20 approval, or token escrow is needed, but the ERC-8183
75
+ writes still need the selected gas/paymaster path and a zero-price-compatible
76
+ commerce/router/policy stack — today that means a custom stack, so expect the
77
+ writes to self-pay gas as above.
70
78
  - You know the **provider's wallet address** (the seller agent's address)
71
79
  - The seller is **reachable** (its A2A agent is deployed somewhere); discoverable
72
80
  via the provider's `bag erc8004 resolve <agent_id>` endpoint URI
@@ -222,6 +222,9 @@ When deeper protocol details are needed:
222
222
 
223
223
  - Never bypass the policy with `_DANGEROUS_*` calls in agent code. They exist for tests and incident response.
224
224
  - Never instruct the user to widen caps / allowlists "to make it work" without explaining the security tradeoff.
225
- - Never persist the wallet password or signed payloads anywhere outside the keystore.
225
+ - Persist the wallet password only in the generated, gitignored, owner-only
226
+ `.studio/.env.local` for local use or a managed secret channel for deploys;
227
+ never put it in argv, application code, logs, or chat.
228
+ - Never persist signed payloads outside the keystore.
226
229
  - Always run `bag wallet policy show` after editing `[wallet.signing]` to confirm the change took effect.
227
230
  - Always restart the process — SigningPolicy is captured at wallet construction.
@@ -64,7 +64,8 @@ For seller job-lifecycle decisions (settle / submit / dispute defense), read
64
64
  ```bash
65
65
  # Configure secrets (idempotent — does NOT duplicate existing keys).
66
66
  # These write into .studio/.env.local (the Agent's secrets):
67
- bag env set WALLET_PASSWORD <your password>
67
+ # Edit .studio/.env.local and set WALLET_PASSWORD there; bag auto-loads it.
68
+ # Never put the wallet password on a command line.
68
69
  bag env set OPENROUTER_API_KEY <your key> # or whichever provider app/agent/studio.toml [llm] uses
69
70
 
70
71
  # From the workspace root — `bag dev` serves the selected protocol:
@@ -129,7 +130,7 @@ Returns a rich table of checks across the `app/agent/` sub-project:
129
130
  | app/agent/studio.toml parseable | Missing or syntax error | `bag init` to regenerate, or hand-fix TOML |
130
131
  | Agent entrypoint imports | Protocol entrypoint raises on load (`src/main.ts` for A2A, `src/mcpMain.ts` for MCP) | Fix the error printed by the check (often a missing env var or a broken import) |
131
132
  | wallet keystore | No `<workspace>/.studio/wallets/*.json` | `bag wallet new` (writes to the workspace root keystore dir) |
132
- | WALLET_PASSWORD env | Not in `.studio/.env.local` / not exported | `bag env set WALLET_PASSWORD ...` (targets .studio/.env.local) |
133
+ | WALLET_PASSWORD env | Not in `.studio/.env.local` / not exported | Edit the owner-only `.studio/.env.local`; never pass the password on argv |
133
134
  | LLM provider key | API key env not set | Edit `.studio/.env.local` or export the right `*_API_KEY` |
134
135
  | Network reachable | RPC down/wrong URL | Override via `STUDIO_BSC_TESTNET_RPC=...` (testnet) / `STUDIO_BSC_RPC=...` (mainnet) — per-network env vars read by `@bnbagent/studio-runtime/networks` `getNetwork` |
135
136
  | Wallet tBNB balance | 0 tBNB | Faucet: testnet.bnbchain.org/faucet-smart |
@@ -309,10 +309,10 @@ Step 6b when `storage=ipfs`):
309
309
  - **twak** (fully supported — opt in with `--wallet-kind twak`): `bag init` writes `[wallet].twak_home =
310
310
  "../../.studio/twak"` — a **project-dedicated** wallet isolated from your
311
311
  main `~/.twak`, so a deploy never pushes the main wallet's key material to
312
- Secrets Manager. The user creates it ONCE themselves the twak CLI forces
313
- the password onto argv (upstream S-8), so studio never runs it in their
314
- own terminal:
315
- own terminal the 3-step `twak setup` wizard:
312
+ Secrets Manager. Setting it up is a ONE-TIME job: the user runs the
313
+ interactive 3-step `twak setup` wizard themselves in their own terminal,
314
+ then `bag wallet twak-init` creates the wallet (so the password never goes
315
+ into a command line). Start with the wizard:
316
316
  ```bash
317
317
  HOME=<workspace>/.studio/twak twak setup
318
318
  ```
@@ -332,17 +332,21 @@ Step 6b when `storage=ipfs`):
332
332
  WalletConnect` = your main wallet.)
333
333
 
334
334
  ```bash
335
- HOME=<workspace>/.studio/twak twak wallet create --password <StrongPw> --no-keychain # UPPER + lower + digit
335
+ cd app/agent && bag wallet twak-init # hidden prompt
336
+ printf %s "$PW" | bag wallet twak-init --password-stdin # CI / scripts
336
337
  ```
337
338
  Use UPPER + lower + digit; put that same password in `.studio/.env.local`
338
- as `TWAK_WALLET_PASSWORD`. `--no-keychain` keeps the password out of the OS
339
- keychain (no macOS prompt); studio unlocks via the env. (If you omit it and a
340
- macOS prompt *loops*, do NOT "Reset Default Keychain" `pkill -9 -f twak`,
341
- then re-run with `--no-keychain`.) Full detail: the
339
+ as `TWAK_WALLET_PASSWORD`. `twak-init` passes `--no-keychain`, which keeps
340
+ the password out of the OS keychain (no macOS prompt); studio unlocks via
341
+ the env. (If a macOS prompt *loops* on a manual `twak` run, do NOT "Reset
342
+ Default Keychain" — `pkill -9 -f twak`, then re-run with `--no-keychain`.)
343
+ It also adopts the address into `studio.toml` and echoes it — confirm it's
344
+ the intended wallet before funding/deploy, and there is no separate
345
+ `bag wallet new` step. The manual equivalent is
346
+ `HOME=<workspace>/.studio/twak twak wallet create --password <StrongPw> --no-keychain`
347
+ followed by `bag wallet new`. Full detail: the
342
348
  `bnbagent-studio-using-twak-wallet.md` reference (in the router skill's `references/` directory).
343
- Then YOU run `bag wallet new`, which **adopts** the address into
344
- `studio.toml` (and echoes it — confirm it's the intended wallet before
345
- funding/deploy). To reuse an EXISTING wallet across agents, scaffold with
349
+ To reuse an EXISTING wallet across agents, scaffold with
346
350
  `bag init --twak-home <path-to-its-home>` instead. Reusing your main
347
351
  `~/.twak` is opt-in only (`--twak-home ~`) and discouraged. Full detail:
348
352
  the `bnbagent-studio-using-twak-wallet.md` reference (in the router skill's `references/` directory).
@@ -3,7 +3,7 @@ name: bnbagent-studio-selling-via-b402
3
3
  description: When the user wants a bnbagent-studio agent to sell paid or FREE HTTP requests through the B402-backed x402 rail. Owns the explicit pricing choice and, for PAID mode, per-agent merchant onboarding, RSA key preparation, egress-IP allowlisting, sandbox/production separation, B402 environment setup, seller status checks, and activation by redeploy (managed platform or self-hosted AgentCore).
4
4
  ---
5
5
 
6
- > **Reference file** of the `bnbagent-studio` router skill installed at
6
+ > **Reference file** of the `bnbagent-studio` router skill, installed at
7
7
  > `bnbagent-studio/references/` and loaded on demand (not a standalone skill).
8
8
  > Route here via the router's decision tree.
9
9
 
@@ -19,10 +19,9 @@ agent wallets, or mix sandbox and production values.
19
19
  - The agent wallet already exists. In PAID mode its address receives U.
20
20
  - The project targets the managed platform or self-hosted AgentCore
21
21
  (azure-foundry cannot activate the rail).
22
- - PAID managed platform only: a platform bearer token is available for reading the
23
- platform egress IPs the GitHub-login access token from
24
- `bag platform login`, or a `bnbk_…` API token minted once by
25
- `bag platform token`.
22
+ - PAID managed platform only: an interactive GitHub-login session from
23
+ `bag platform login` is available for reading the platform Relay egress
24
+ IPs. A `bnbk_…` CI token does not satisfy this endpoint's GitHub-user check.
26
25
  - `[payments.x402_seller]` exists. If not, run
27
26
  `bag x402 sell init`.
28
27
 
@@ -59,66 +58,86 @@ call under the
59
58
  the runtime signs each request automatically once the credentials are stored.
60
59
 
61
60
  Work from the workspace root. Keep private material under `.studio/`, which is
62
- excluded from source and deploy artifacts:
61
+ excluded from source and deploy artifacts. Generate a separate key pair for
62
+ each environment:
63
63
 
64
64
  ```bash
65
65
  umask 077
66
- mkdir -p .studio/b402
67
- openssl genrsa -out .studio/b402/private.pem 2048
68
- openssl pkey -in .studio/b402/private.pem -pubout \
69
- -out .studio/b402/public.pem
70
- openssl pkey -in .studio/b402/private.pem -pubout -outform DER |
71
- openssl base64 -A > .studio/b402/public.der.b64
72
- openssl pkcs8 -topk8 -nocrypt -in .studio/b402/private.pem -outform DER |
73
- openssl base64 -A > .studio/b402/private.der.b64
74
- chmod 600 .studio/b402/private.pem .studio/b402/private.der.b64
66
+ b402_env=sandbox
67
+ mkdir -p ".studio/b402/$b402_env"
68
+ openssl genpkey -algorithm RSA \
69
+ -out ".studio/b402/$b402_env/private.pem" \
70
+ -pkeyopt rsa_keygen_bits:1024
71
+ openssl pkey -in ".studio/b402/$b402_env/private.pem" -pubout \
72
+ -out ".studio/b402/$b402_env/public.pem"
73
+ openssl pkey -in ".studio/b402/$b402_env/private.pem" -pubout -outform DER |
74
+ openssl base64 -A > ".studio/b402/$b402_env/public.der.b64"
75
+ openssl pkcs8 -topk8 -nocrypt \
76
+ -in ".studio/b402/$b402_env/private.pem" -outform DER |
77
+ openssl base64 -A > ".studio/b402/$b402_env/private.der.b64"
78
+ chmod 600 ".studio/b402/$b402_env/private.pem" \
79
+ ".studio/b402/$b402_env/private.der.b64"
75
80
  ```
76
81
 
77
82
  Do not print the private key or its base64 form. Submit only the public key
78
83
  material to B402.
79
84
 
80
- Studio generates a 2048-bit RSA key. The current B402 application form text
81
- mentions 1024 bits, while 2048-bit acceptance still requires confirmation from
82
- the B402 operator. Confirm that field with the operator; do not silently
83
- downgrade the key.
85
+ The current B402 request-signing contract explicitly requires a 1024-bit RSA
86
+ key and RSA-SHA256. Follow that protocol requirement even if a larger RSA key
87
+ would normally be preferred elsewhere. Repeat with `b402_env=production`
88
+ instead of reusing the sandbox pair.
84
89
 
85
90
  ## Collect the IP allowlist
86
91
 
87
- B402 allowlists the merchant's **outbound** (egress) IPs the addresses the
92
+ B402 allowlists the merchant's **outbound** (egress) IPs, the addresses the
88
93
  agent's facilitator calls come FROM. Submit every part that applies to your
89
94
  deployment target:
90
95
 
91
- 1. **Platform egress IPs (managed-platform deploys)** the addresses
92
- deployed-agent requests reach B402 from. Authenticated read (bearer from
93
- the Preconditions):
96
+ 1. **Platform Relay egress IPs (managed-platform deploys)**: the addresses
97
+ that the platform B402 Relay uses to reach the facilitator. The managed
98
+ deployment worker points only the AgentCore runtime copy of
99
+ `B402_BASE_URL` at this Relay. Refresh and read the interactive session
100
+ without printing its bearer:
94
101
 
95
102
  ```bash
96
- curl -H "Authorization: Bearer $BNBAGENT_API_TOKEN" \
97
- "https://bnbagent-api.bnbchain.world/v1/b402/whitelist-ips"
103
+ bag platform whoami >/dev/null
104
+ platform_session="$HOME/.bnbagent-deploy/bnb/session.json"
105
+ platform_access_token="$(jq -er '.access_token' "$platform_session")"
106
+ platform_api_url="$(jq -er '.apiUrl' "$platform_session")"
107
+ curl -H "Authorization: Bearer $platform_access_token" \
108
+ "$platform_api_url/v1/b402/whitelist-ips"
109
+ unset platform_access_token
98
110
  # → {"whitelist_ips": ["13.115.15.190", …], "cache_ttl_seconds": 300}
99
111
  ```
100
112
 
101
113
  Submit every address in `whitelist_ips`. The list is served with a short
102
- cache TTL and can rotate re-read it right before submitting the form.
103
- Set `BAG_PLATFORM_API_BASE` to target a non-production platform
104
- environment.
114
+ cache TTL and can rotate, so re-read it right before submitting the form.
115
+ The login session is stored mode `0600`. Do not echo, log, or paste its
116
+ access or refresh token. `bnbk_` tokens from `bag platform token` are
117
+ deliberately rejected by this GitHub-login-only endpoint.
105
118
 
106
- 2. **Your local public IP** required so a local `bag dev` run can reach
119
+ 2. **Your local public IP**: required so a local `bag dev` run can reach
107
120
  B402:
108
121
 
109
122
  ```bash
110
123
  curl ipinfo.io/ip
111
124
  ```
112
125
 
113
- 3. **Self-hosted AgentCore egress (self-deploys)** AgentCore gives the
114
- container no fixed egress IP, so there is no address to submit directly.
115
- Route the facilitator egress through a fixed-IP forward proxy you control
116
- and submit THAT IP: set `HTTPS_PROXY` on the runtime and enable proxy
117
- support for Node's fetch, which ignores proxy env vars by default —
118
- install an undici `EnvHttpProxyAgent` global dispatcher in the agent
119
- entrypoint, or run the container on Node 24+ with `--use-env-proxy`.
120
- Without this, every verify/settle call is rejected as an unallowlisted
121
- IP even though the deploy summary reports the rail ACTIVE.
126
+ 3. **Self-hosted AgentCore egress (self-deploys)**: operate a restricted B402
127
+ Relay on a host with a fixed public egress IP, such as a user-managed VPS,
128
+ and submit that IP. Set the runtime `B402_BASE_URL` to the Relay base URL.
129
+ The Relay exposes only
130
+ `supported`, `verify`, and `settle`, fixes the upstream facilitator, and
131
+ forwards the signed body and Tesla header allowlist without holding the
132
+ merchant private key or automatically retrying a settlement transport
133
+ failure. See the
134
+ [self-hosted x402 gateway guide](https://github.com/bnb-chain/bnbagent-studio/blob/main/docs/guides/self-hosted-x402-gateway.md)
135
+ for a TypeScript example.
136
+
137
+ As an alternative, use AWS-supported AgentCore VPC mode with a private subnet, NAT
138
+ Gateway, and Elastic IP. Submit the Elastic IP and keep
139
+ `B402_BASE_URL` pointed at the facilitator. Studio does not deploy or
140
+ manage that AWS network.
122
141
 
123
142
  Do not add the public inbound gateway IP, a transient build-runner IP, or
124
143
  guessed addresses. If the whitelist endpoint is unreachable, stop onboarding
@@ -134,10 +153,11 @@ production. Fill the form as follows:
134
153
  | Field | Value |
135
154
  |---|---|
136
155
  | Business Name | Your agent or business display name |
137
- | Website | Your project or service URL |
138
- | Wallet | The agent wallet address |
139
- | RSA Public Key | The contents of `.studio/b402/public.der.b64` in the format the operator confirms |
140
- | IP allowlist | Every platform egress IP from `/v1/b402/whitelist-ips` plus your local public IP (see "Collect the IP allowlist") |
156
+ | Email | Primary contact email address |
157
+ | Wallet address | The agent wallet for that environment |
158
+ | Public key | The contents of `.studio/b402/<environment>/public.der.b64` |
159
+ | IP allowlist | For managed deploys, every platform Relay IP from `/v1/b402/whitelist-ips`; for self-hosted deploys, the user's Relay or NAT Elastic IP; add the local public IP when `bag dev` must reach B402 directly |
160
+ | Webhook callback URL | Supply only when the integration uses callbacks |
141
161
 
142
162
  Keep the two environments isolated:
143
163
 
@@ -152,20 +172,28 @@ Open the workspace `.studio/.env.local` in an editor and fill exactly four
152
172
  values:
153
173
 
154
174
  ```dotenv
155
- B402_BASE_URL=https://qacb.sdtaop.com
175
+ B402_BASE_URL=
156
176
  B402_CLIENT_ID=
157
177
  B402_ACCESS_TOKEN=
158
178
  B402_PRIVATE_KEY_B64=
159
179
  ```
160
180
 
161
181
  Copy the single-line DER value from
162
- `.studio/b402/private.der.b64` into `B402_PRIVATE_KEY_B64`. Do not include the
163
- value in shell history, terminal output, source files, TOML, screenshots, or
164
- support tickets.
182
+ `.studio/b402/<environment>/private.der.b64` into `B402_PRIVATE_KEY_B64`.
183
+ `B402_BASE_URL`, `B402_CLIENT_ID`, and `B402_ACCESS_TOKEN` are the values
184
+ issued together for that environment. Do not include any value in shell
185
+ history, terminal output, source files, TOML, screenshots, or support tickets.
165
186
 
166
187
  `B402_PRIVATE_KEY` accepts the PEM representation as an alternative. Keep
167
188
  exactly one private-key form; do not set both.
168
189
 
190
+ For the current managed BSC Testnet path, the platform-supported upstream is
191
+ `https://qacb.sdtaop.com`; the worker projects only the AgentCore runtime copy
192
+ to its Relay. For a generic self-hosted environment, use the authenticated
193
+ base URL issued during onboarding. With your own Relay, set the runtime
194
+ `B402_BASE_URL` to that Relay base URL and configure the Relay's fixed upstream
195
+ to the issued URL.
196
+
169
197
  ## Verify and activate
170
198
 
171
199
  For PAID mode, check names and presence without exposing values:
@@ -197,22 +225,34 @@ On the managed platform the deploy summary must say `x402 rail is ACTIVE` (or
197
225
  `ACTIVE in FREE mode`) and print the anonymous `/x402` URL. On a self-hosted
198
226
  AgentCore deploy it says `x402 rail is ACTIVE (self-hosted AgentCore)` or
199
227
  `ACTIVE in FREE mode (self-hosted AgentCore)`: the rail runs in-process, but
200
- there is no anonymous URL operate your own HTTP front that relays envelope-v1
201
- JSON over SigV4-signed `InvokeAgentRuntime` calls (see
202
- `docs/guides/x402-selling.md`, "Self-hosted AgentCore access"). A dormant or
203
- forced-dormant summary means the rail was not activated; fix the named
204
- credential, runtime, network, or tunnel condition and redeploy.
228
+ there is no anonymous URL. Operate your own HTTP front that relays envelope-v1
229
+ JSON through an authenticated AgentCore invocation. The default Bag
230
+ self-deploy uses Cognito OAuth over raw HTTPS; AWS SDK/SigV4 is only for a
231
+ runtime deliberately configured with IAM authorization. PAID also needs your
232
+ own fixed-egress B402 Relay or equivalent network path. The complete gateway
233
+ wrapper, response parser, Relay example, and direct-invocation fallback are in
234
+ the
235
+ [self-hosted x402 gateway guide](https://github.com/bnb-chain/bnbagent-studio/blob/main/docs/guides/self-hosted-x402-gateway.md).
236
+ A dormant or forced-dormant summary means the rail was not activated; fix the
237
+ named credential, runtime, network, or tunnel condition and redeploy.
205
238
 
206
239
  ## Hard rules
207
240
 
208
241
  - Never log or print any B402 value or private key.
209
242
  - Never put a B402 value in `studio.toml` or a deploy descriptor.
210
- - Never retry a settlement whose outcome is unknown. Follow
243
+ - Never replay a paid HTTP request whose outcome is unknown. Follow
211
244
  `docs/guides/x402-selling.md` and reconcile `(nonce, network, payer)` first.
245
+ - Binance `/settle` is asynchronous. A parseable `success: false` response
246
+ with a transaction is pending and requires an idempotent poll with the same
247
+ settlement payload. Studio v0.0.6 and the latest
248
+ `@bnb-chain/b402@0.1.0` do not yet perform that poll; they classify pending
249
+ as unknown. Do not claim current mainnet readiness until this is updated.
212
250
  - Settlement happens before work. A later work failure retains the payment and
213
251
  does not trigger an automatic refund.
214
252
  - The rail activates on AgentCore targets only (managed platform or
215
253
  self-hosted); azure-foundry stays forced dormant.
216
- - PAID payout wallets must use `wallet.kind` `evm-local` or `twak`; FREE has
217
- no payout and does not apply this B402-specific allowlist.
254
+ - PAID B402 payout wallets must use `wallet.kind` `evm-local` (private-key
255
+ wallet stored as an encrypted keystore) or `twak`. Altana is unsupported.
256
+ FREE x402 bypasses B402, has no payout, and must not be described as Altana
257
+ B402 support.
218
258
  - Never describe FREE as a zero-value B402 settlement. It bypasses B402.
@@ -199,8 +199,11 @@ bag deploy destroy --provider aws --execute # delegated teardown (add --purge)
199
199
  - b402/x402 selling on self-hosted AgentCore: the rail activates in-process
200
200
  with complete B402 credentials for PAID, or without them when explicit zero
201
201
  selects FREE. There is no anonymous URL — operate your own HTTP front that
202
- relays envelope-v1 over `InvokeAgentRuntime`. See
203
- `bnbagent-studio-selling-via-b402` and `docs/guides/x402-selling.md`.
202
+ relays envelope-v1 through the runtime's configured authorizer. The default
203
+ Bag deploy uses Cognito OAuth over HTTPS, not SDK/SigV4. PAID also needs a
204
+ fixed-egress B402 Relay or VPC/NAT/Elastic-IP path. See
205
+ `bnbagent-studio-selling-via-b402` and
206
+ `docs/guides/self-hosted-x402-gateway.md`.
204
207
  - `bag deploy --help` / `bag deploy <command> --help` (authoritative for commands + flags)
205
208
  - `agentcore/agentcore.json` — name (resource naming continuity), protocol, authorizer, envVars
206
209
  - `agentcore/aws-targets.json` — AWS account + region