@bnbagent/studio-cli 0.0.6-alpha.2 → 0.0.6-alpha.3

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.
package/dist/bag.js CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  B402_RUNTIME_KEYS,
4
4
  CliExit,
5
5
  DEFAULT_B402_PRICE_USD,
6
+ DEFAULT_B402_TESTNET_BASE_URL,
6
7
  act,
7
8
  agentcoreFlavor,
8
9
  b402Credentials,
@@ -19,6 +20,7 @@ import {
19
20
  hasA2aFace,
20
21
  hasMcpFace,
21
22
  hasX402Face,
23
+ isB402TestnetBaseUrl,
22
24
  nativeProtocolOf,
23
25
  normalizeB402PriceUsd,
24
26
  normalizeProtocolFaces,
@@ -40,7 +42,7 @@ import {
40
42
  x402SellerIsFree,
41
43
  x402SellerPricingState,
42
44
  x402SellerUsesB402
43
- } from "./chunk-6EYSXRFD.js";
45
+ } from "./chunk-A7NAGZHR.js";
44
46
  import {
45
47
  TWAK_CLI_MIN_VERSION,
46
48
  TWAK_CLI_VERSION,
@@ -102,7 +104,7 @@ import {
102
104
  var CAMPAIGN_DOC_URL = "https://www.bnbchain.org/en/blog/bnb-agent-studio-is-live-on-bnb-chain-ai-agents-from-one-prompt";
103
105
  var CAMPAIGN_CHECK_TIMEOUT_MS = 6e3;
104
106
  async function fetchCampaignActive() {
105
- const { bnbPlatformApiUrl: bnbPlatformApiUrl2 } = await import("./deployCli-3U2FESF4.js");
107
+ const { bnbPlatformApiUrl: bnbPlatformApiUrl2 } = await import("./deployCli-264UE6KB.js");
106
108
  const controller = new AbortController();
107
109
  const timer = setTimeout(() => controller.abort(), CAMPAIGN_CHECK_TIMEOUT_MS);
108
110
  try {
@@ -2483,7 +2485,7 @@ async function cmdSet(key, rawValue, typeFlag, projectRoot) {
2483
2485
  const contracts = erc8183ContractOverrideState();
2484
2486
  if (contracts.mode === "custom") {
2485
2487
  printOut(
2486
- "ERC-8183 contracts: custom/QA contract stack selected with all three address overrides."
2488
+ "ERC-8183 contracts: custom contract stack selected with all three address overrides."
2487
2489
  );
2488
2490
  } else if (contracts.mode === "partial") {
2489
2491
  printErr(
@@ -3197,6 +3199,9 @@ function packageRoot() {
3197
3199
  }
3198
3200
  }
3199
3201
  function pnpmVersion() {
3202
+ if ("10.24.0") {
3203
+ return "10.24.0";
3204
+ }
3200
3205
  const file = path12.join(packageRoot(), "package.json");
3201
3206
  const pkg = JSON.parse(fs13.readFileSync(file, "utf-8"));
3202
3207
  const value = String(pkg.packageManager ?? "");
@@ -3225,6 +3230,46 @@ import * as fs14 from "fs";
3225
3230
  import * as path13 from "path";
3226
3231
  import { envLocalPath as envLocalPath7 } from "@bnbagent/studio-runtime/config";
3227
3232
  import { resolveTwakHome } from "@bnbagent/studio-runtime/wallet";
3233
+
3234
+ // src/cli/_twakContractTargets.ts
3235
+ import { resolveNetwork } from "@bnbagent/sdk";
3236
+ var CONTRACT_OVERRIDES = [
3237
+ ["ERC8004_REGISTRY_ADDRESS", "registryContract"],
3238
+ ["ERC8183_COMMERCE_ADDRESS", "commerceContract"],
3239
+ ["ERC8183_ROUTER_ADDRESS", "routerContract"],
3240
+ ["ERC8183_POLICY_ADDRESS", "policyContract"]
3241
+ ];
3242
+ function twakCustomContractOverrides(networkName, env = process.env) {
3243
+ let network;
3244
+ try {
3245
+ network = resolveNetwork(networkName);
3246
+ } catch {
3247
+ return [];
3248
+ }
3249
+ const erc8183 = erc8183ContractOverrideState(env);
3250
+ const out = [];
3251
+ for (const [envKey, field] of CONTRACT_OVERRIDES) {
3252
+ if (envKey.startsWith("ERC8183_") && erc8183.mode !== "custom") {
3253
+ continue;
3254
+ }
3255
+ const configured = env[envKey]?.trim() ?? "";
3256
+ if (!/^0x[0-9a-fA-F]{40}$/u.test(configured)) {
3257
+ continue;
3258
+ }
3259
+ const canonical = network[field];
3260
+ if (configured.toLowerCase() !== canonical.toLowerCase()) {
3261
+ out.push({ envKey, configured, canonical });
3262
+ }
3263
+ }
3264
+ return out;
3265
+ }
3266
+ function twakUnsupportedContractOverrides(networkName, env = process.env) {
3267
+ return twakCustomContractOverrides(networkName, env).filter(
3268
+ (item) => item.envKey.startsWith("ERC8183_")
3269
+ );
3270
+ }
3271
+
3272
+ // src/cli/_deploy/checks/twak.ts
3228
3273
  var AGENTCORE_DESCRIPTOR = path13.join("agentcore", "agentcore.json");
3229
3274
  function isTwak(data) {
3230
3275
  return tableOf2(data, "wallet").kind === "twak";
@@ -3329,6 +3374,26 @@ async function checkTwakCliPresent(root, _target) {
3329
3374
  }
3330
3375
  return [];
3331
3376
  }
3377
+ function checkTwakCustomContractsUnsupported(root, _target) {
3378
+ const data = loadAgentToml(root);
3379
+ if (!isTwak(data)) {
3380
+ return [];
3381
+ }
3382
+ const networkName = String(tableOf2(data, "network").default ?? "bsc-testnet");
3383
+ const overrides = twakUnsupportedContractOverrides(networkName);
3384
+ if (overrides.length === 0) {
3385
+ return [];
3386
+ }
3387
+ const keys = overrides.map((item) => item.envKey);
3388
+ return [
3389
+ {
3390
+ level: Level.CRITICAL,
3391
+ name: "twak_custom_contracts_unsupported",
3392
+ message: `wallet.kind='twak' cannot use the selected custom ERC-8183 targets (${keys.join(", ")}): twak v0.20.0 has no Commerce/Router/Policy address option and would otherwise execute against the wrong canonical contracts. Use wallet.kind='evm-local' for custom ERC-8183 contracts, or remove the overrides and use the canonical stack. ERC8004_REGISTRY_ADDRESS is supported.`,
3393
+ details: { network: networkName, override_keys: keys }
3394
+ }
3395
+ ];
3396
+ }
3332
3397
  async function checkTwakWalletExists(root, _target) {
3333
3398
  const data = loadAgentToml(root);
3334
3399
  if (!isTwak(data)) {
@@ -5361,7 +5426,9 @@ var LEGACY_SKILL_NAMES = [
5361
5426
  ];
5362
5427
  var ROUTER_SKILL_NAME = "bnbagent-studio";
5363
5428
  var META_FILENAME = ".bag-meta.json";
5364
- var HIDDEN_SKILL_NAMES = /* @__PURE__ */ new Set();
5429
+ var HIDDEN_SKILL_NAMES = /* @__PURE__ */ new Set([
5430
+ "bnbagent-studio-use-azure-foundry"
5431
+ ]);
5365
5432
  function isDir3(p) {
5366
5433
  try {
5367
5434
  return fs19.statSync(p).isDirectory();
@@ -5767,6 +5834,19 @@ function installSkills(opts) {
5767
5834
  };
5768
5835
  }
5769
5836
 
5837
+ // src/cli/utils/options.ts
5838
+ import { InvalidArgumentError } from "commander";
5839
+ function acceptChoices(option, accepted, advertised) {
5840
+ return option.argParser((value) => {
5841
+ if (!accepted.includes(value)) {
5842
+ throw new InvalidArgumentError(
5843
+ `Allowed choices are ${advertised.join(", ")}.`
5844
+ );
5845
+ }
5846
+ return value;
5847
+ });
5848
+ }
5849
+
5770
5850
  // src/cli/wallet.ts
5771
5851
  import * as fs21 from "fs";
5772
5852
  import * as path20 from "path";
@@ -6876,7 +6956,7 @@ var SUPPORTED_STORAGE_PROVIDERS = ["ipfs", "local"];
6876
6956
  var SUPPORTED_DESTINATIONS = ["self", "platform"];
6877
6957
  var RUNTIMES = {
6878
6958
  agentcore: { label: "AWS Bedrock AgentCore", hidden: false },
6879
- "azure-foundry": { label: "Azure AI Foundry", hidden: false }
6959
+ "azure-foundry": { label: "Azure AI Foundry", hidden: true }
6880
6960
  };
6881
6961
  var SUPPORTED_RUNTIMES = Object.keys(RUNTIMES);
6882
6962
  var APP_DIR = "app";
@@ -6953,7 +7033,7 @@ every intent to the right playbook via its references.
6953
7033
  lives in \`src/signing.ts\`.
6954
7034
  4. **The quote path is deterministic** (fixed list price, clamp + sign).
6955
7035
  Never put an LLM in the quote path.
6956
- 5. **Deploy with \`bag deploy --provider bnb|aws|azure\`.** Every deploy or
7036
+ 5. **Deploy with \`bag deploy --provider bnb|aws\`.** Every deploy or
6957
7037
  redeploy requires a visible provider choice. Studio runs its local business
6958
7038
  gates, then delegates cloud credentials, secrets, packaging and lifecycle
6959
7039
  calls to the pinned bnbagent-deploy CLI. Do not bypass this boundary with
@@ -7095,7 +7175,7 @@ function validateProjectName(basename16) {
7095
7175
  }
7096
7176
  if (safe !== basename16) {
7097
7177
  throw new Error(
7098
- `Project name '${basename16}' is not a valid AgentCore runtime name: the default runtime (AWS Bedrock AgentCore) requires ASCII letters and digits only ('-', '_', '.' and other characters are not allowed) \u2014 this is an AgentCore naming constraint, not a general bag restriction. Re-run with an alphanumeric name, e.g. \`bag init ${safe}\` \u2014 or, if this project is not for AgentCore, keep the original name on a runtime that allows it: \`bag init --runtime azure-foundry ${basename16}\`.`
7178
+ `Project name '${basename16}' is not a valid AgentCore runtime name: the default runtime (AWS Bedrock AgentCore) requires ASCII letters and digits only ('-', '_', '.' and other characters are not allowed) \u2014 this is an AgentCore naming constraint, not a general bag restriction. Re-run with an alphanumeric name, e.g. \`bag init ${safe}\`.`
7099
7179
  );
7100
7180
  }
7101
7181
  return basename16;
@@ -7108,21 +7188,15 @@ function registerInit(program) {
7108
7188
  "<name>",
7109
7189
  "Project directory name (created in cwd). Becomes the AgentCore runtime name: ASCII alphanumerics only, must start with a letter, \u226423 chars. Names with '-'/'_'/'.' are rejected (not auto-renamed)."
7110
7190
  ).addOption(
7111
- new Option2(
7112
- "--runtime <name>",
7113
- `Runtime / deploy target to scaffold for (choices: ${visibleRuntimes.join(", ")}); recorded in studio.toml [stack].runtime.`
7114
- ).default("agentcore").choices(SUPPORTED_RUNTIMES)
7115
- ).addOption(
7116
- new Option2(
7117
- "--azure-account <name>",
7118
- "azure-foundry only: Azure AI Foundry account (resource) name used by the delegated deploy."
7119
- )
7120
- ).addOption(
7121
- new Option2(
7122
- "--azure-project <name>",
7123
- "azure-foundry only: Azure AI Foundry project name used by the delegated deploy."
7191
+ acceptChoices(
7192
+ new Option2(
7193
+ "--runtime <name>",
7194
+ `Runtime / deploy target to scaffold for (choices: ${visibleRuntimes.join(", ")}); recorded in studio.toml [stack].runtime.`
7195
+ ).default("agentcore"),
7196
+ SUPPORTED_RUNTIMES,
7197
+ visibleRuntimes
7124
7198
  )
7125
- ).addOption(
7199
+ ).addOption(new Option2("--azure-account <name>").hideHelp()).addOption(new Option2("--azure-project <name>").hideHelp()).addOption(
7126
7200
  new Option2(
7127
7201
  "--destination <dest>",
7128
7202
  "Where the agent deploys; recorded in studio.toml [deploy].destination (default: platform while the trial campaign runs; self once it has ended, or when a non-agentcore --runtime or --network bsc-mainnet is passed). 'platform' is a 48h testnet trial on the BNB Chain managed platform (forces runtime=agentcore + bsc-testnet; a trial wallet key is transmitted to the operator \u2014 run `bag wallet new` for a throwaway). 'self' deploys to YOUR own cloud."
@@ -7582,9 +7656,10 @@ function derivePackaging(walletKind2, destination, platformArtifact2 = "zip", ru
7582
7656
  return "codezip";
7583
7657
  }
7584
7658
  function scaffold(target, name, o) {
7659
+ const packageManagerVersion = pnpmVersion();
7585
7660
  fs22.writeFileSync(
7586
7661
  path21.join(target, "package.json"),
7587
- renderWorkspacePackageJson(name)
7662
+ renderWorkspacePackageJson(name, packageManagerVersion)
7588
7663
  );
7589
7664
  fs22.writeFileSync(
7590
7665
  path21.join(target, "pnpm-workspace.yaml"),
@@ -7646,7 +7721,8 @@ function scaffold(target, name, o) {
7646
7721
  o.storageProvider,
7647
7722
  o.walletKind,
7648
7723
  o.rails,
7649
- o.b402Price
7724
+ o.b402Price,
7725
+ o.network
7650
7726
  )
7651
7727
  );
7652
7728
  fs22.writeFileSync(path21.join(agentRoot2, ".gitignore"), renderAgentGitignore());
@@ -7667,7 +7743,7 @@ function scaffold(target, name, o) {
7667
7743
  const runtimeCtx = {
7668
7744
  PKG: AGENT_SRC,
7669
7745
  TWAK_CLI_VERSION,
7670
- PNPM_VERSION: pnpmVersion(),
7746
+ PNPM_VERSION: packageManagerVersion,
7671
7747
  DEPLOYMENT_TYPE: isContainer ? "container" : "direct_code_deploy",
7672
7748
  ENTRYPOINT: o.runtime === "azure-foundry" ? a2aEntry : `dist/${stem2}.js`,
7673
7749
  // Foundry Hosted Agents have a provider-level container contract that is
@@ -7756,11 +7832,12 @@ ${deployLine}
7756
7832
  In Claude Code / Cursor, type \`/bnbagent-studio\` \u2014 the skill drives every step.
7757
7833
  `;
7758
7834
  }
7759
- function renderWorkspacePackageJson(name) {
7835
+ function renderWorkspacePackageJson(name, packageManagerVersion) {
7760
7836
  return `${JSON.stringify(
7761
7837
  {
7762
7838
  name: `${name}-workspace`,
7763
- private: true
7839
+ private: true,
7840
+ packageManager: `pnpm@${packageManagerVersion}`
7764
7841
  // NOT a publishable package — the agent lives at app/agent (see
7765
7842
  // pnpm-workspace.yaml); this root only anchors the pnpm workspace.
7766
7843
  },
@@ -8078,7 +8155,7 @@ ${pieverseSection}${erc8183Section}
8078
8155
  ${storageSection}
8079
8156
  ${x402Section}${x402SellerSection}${budgetSection}${azureSection}`;
8080
8157
  }
8081
- function renderAgentEnvLocal(provider, storageProvider, walletKind2, rails, b402Price) {
8158
+ function renderAgentEnvLocal(provider, storageProvider, walletKind2, rails, b402Price, network) {
8082
8159
  const keyEnv = PROVIDER_KEY_ENV[provider];
8083
8160
  const lines = [
8084
8161
  "# \u26A0\uFE0F SECRETS \u2014 do NOT paste this file into issue trackers, logs, or AI chats.",
@@ -8142,11 +8219,12 @@ function renderAgentEnvLocal(provider, storageProvider, walletKind2, rails, b402
8142
8219
  }
8143
8220
  if (rails === "b402" || rails === "both") {
8144
8221
  const free = x402SellerPricingState({ price_usd: b402Price }).kind === "free";
8222
+ const baseUrl = !free && network === "bsc-testnet" ? DEFAULT_B402_TESTNET_BASE_URL : "";
8145
8223
  lines.push(
8146
8224
  "",
8147
8225
  "# B402 merchant credentials (issued per environment after manual approval).",
8148
8226
  free ? "# FREE price 0 does not use these credentials; leave them empty until switching to PAID." : "# PAID requires all four; all absent = dormant, partial = boot error.",
8149
- "B402_BASE_URL=",
8227
+ `B402_BASE_URL=${baseUrl}`,
8150
8228
  "B402_CLIENT_ID=",
8151
8229
  "B402_ACCESS_TOKEN=",
8152
8230
  "B402_PRIVATE_KEY=",
@@ -8156,7 +8234,7 @@ function renderAgentEnvLocal(provider, storageProvider, walletKind2, rails, b402
8156
8234
  if (rails === "8183" || rails === "both") {
8157
8235
  lines.push(
8158
8236
  "",
8159
- "# Optional ERC-8183 contract-stack override (QA/custom). Set all three",
8237
+ "# Optional ERC-8183 custom contract-stack override. Set all three",
8160
8238
  "# together; partial overrides can mix incompatible deployments.",
8161
8239
  "# Required for price=0 while canonical contracts reject zero funding.",
8162
8240
  "# ERC8183_COMMERCE_ADDRESS=",
@@ -9415,7 +9493,7 @@ function printConfigSummary(agentRoot2) {
9415
9493
  ` pricing : FREE \u2014 ${weiToU(String(price))} per job; zero token escrow`
9416
9494
  );
9417
9495
  printOut(
9418
- contractOverrides.mode === "custom" ? " contracts: custom/QA stack selected (all three address overrides)" : " contracts: configure all three ERC8183_*_ADDRESS overrides before deploy"
9496
+ contractOverrides.mode === "custom" ? " contracts: custom stack selected (all three address overrides)" : " contracts: configure all three ERC8183_*_ADDRESS overrides before deploy"
9419
9497
  );
9420
9498
  } else {
9421
9499
  printOut(
@@ -9455,7 +9533,7 @@ function printConfigSummary(agentRoot2) {
9455
9533
  }
9456
9534
  if (hasErc8183 && isFreePrice && contractOverrides.mode !== "custom") {
9457
9535
  todo.push(
9458
- "ERC8183_COMMERCE_ADDRESS / ROUTER_ADDRESS / POLICY_ADDRESS \u2014 select one zero-price-compatible QA/custom contract stack"
9536
+ "ERC8183_COMMERCE_ADDRESS / ROUTER_ADDRESS / POLICY_ADDRESS \u2014 select one zero-price-compatible custom contract stack"
9459
9537
  );
9460
9538
  }
9461
9539
  if (hasX402Seller && !isFreeX402) {
@@ -10647,14 +10725,14 @@ function erc8183RailChecks(cfg) {
10647
10725
  out.push({
10648
10726
  level: Level.CRITICAL,
10649
10727
  name: "commerce_zero_price_contract_unsupported",
10650
- message: "ERC-8183 pricing is FREE (zero token escrow), but the canonical contract stack rejects zero funding. Set ERC8183_COMMERCE_ADDRESS, ERC8183_ROUTER_ADDRESS, and ERC8183_POLICY_ADDRESS together to a zero-price-compatible QA/custom stack.",
10728
+ message: "ERC-8183 pricing is FREE (zero token escrow), but the canonical contract stack rejects zero funding. Set ERC8183_COMMERCE_ADDRESS, ERC8183_ROUTER_ADDRESS, and ERC8183_POLICY_ADDRESS together to a zero-price-compatible custom stack.",
10651
10729
  details: { effective_wei: "0", contract_profile: "canonical" }
10652
10730
  });
10653
10731
  } else if (contracts.mode === "custom") {
10654
10732
  out.push({
10655
10733
  level: Level.INFO,
10656
10734
  name: "commerce_zero_price_enabled",
10657
- message: "ERC-8183 pricing is FREE \u2014 buyers fund 0 token units with zero token escrow; a complete custom/QA contract stack is selected.",
10735
+ message: "ERC-8183 pricing is FREE \u2014 buyers fund 0 token units with zero token escrow; a complete custom contract stack is selected.",
10658
10736
  details: { effective_wei: "0", contract_profile: "custom" }
10659
10737
  });
10660
10738
  }
@@ -10776,7 +10854,7 @@ function x402SellerRailChecks(agentRoot2, cfg, target) {
10776
10854
  const network = String(
10777
10855
  tableOf2(cfg, "network").default ?? "bsc-mainnet"
10778
10856
  ).toLowerCase();
10779
- const testUrl = /sandbox|test/.test(credentials.baseUrl.toLowerCase());
10857
+ const testUrl = isB402TestnetBaseUrl(credentials.baseUrl);
10780
10858
  const testNetwork = network.includes("testnet");
10781
10859
  if (testUrl !== testNetwork) {
10782
10860
  out.push({
@@ -11009,6 +11087,7 @@ var allChecks = [
11009
11087
  // gates on the LEVEL, not on which module produced it.
11010
11088
  checkTwakRequiresContainer,
11011
11089
  checkTwakCliPresent,
11090
+ checkTwakCustomContractsUnsupported,
11012
11091
  checkTwakWalletExists,
11013
11092
  checkTwakPasswordEnvSet,
11014
11093
  checkTwakCredentialsAvailable,
@@ -12151,14 +12230,15 @@ async function checkWalletTbnbBalanceSufficient(root, _target) {
12151
12230
  return [];
12152
12231
  }
12153
12232
  const isTwak2 = tableOf2(data, "wallet").kind === "twak";
12154
- if (isTwak2 && netName === "bsc-testnet") {
12233
+ const customContracts = isTwak2 && twakCustomContractOverrides(netName).length > 0;
12234
+ if (isTwak2 && netName === "bsc-testnet" && !customContracts) {
12155
12235
  return [];
12156
12236
  }
12157
12237
  const bal = await readNativeBalanceRaw(address, netName);
12158
12238
  if (bal >= TBNB_MIN_WEI) {
12159
12239
  return [];
12160
12240
  }
12161
- const gasNote = isTwak2 ? " (twak: x402 topups are gasless and ERC-8004 registry writes are gas-sponsored, but mainnet ERC-8183 fund/settle self-pay gas)" : "";
12241
+ const gasNote = isTwak2 ? netName === "bsc-testnet" ? " (twak: custom contract sponsorship depends on the paymaster policy; keep fallback tBNB)" : " (twak: x402 topups are gasless and ERC-8004 registry writes are gas-sponsored, but mainnet ERC-8183 fund/settle self-pay gas)" : "";
12162
12242
  return [
12163
12243
  {
12164
12244
  level: Level.WARNING,
@@ -12691,6 +12771,21 @@ var PROVIDER_LABELS = {
12691
12771
  aws: "AWS AgentCore",
12692
12772
  azure: "Azure Foundry"
12693
12773
  };
12774
+ var PROVIDER_MENU_ORDER = [
12775
+ "bnb",
12776
+ "aws",
12777
+ "azure"
12778
+ ];
12779
+ var PROVIDER_SHORT_LABELS = {
12780
+ bnb: "BNB",
12781
+ aws: "AWS",
12782
+ azure: "Azure"
12783
+ };
12784
+ function providerDigit(provider) {
12785
+ return PROVIDER_MENU_ORDER.indexOf(provider) + 1;
12786
+ }
12787
+ var ADVERTISED_PROVIDERS = ["bnb", "aws"];
12788
+ var ADVERTISED_PROVIDERS_HELP = ADVERTISED_PROVIDERS.join("|");
12694
12789
  function tableOf10(data, key) {
12695
12790
  const value = data[key];
12696
12791
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
@@ -12783,7 +12878,7 @@ function unavailableProvidersForProject(root) {
12783
12878
  return {};
12784
12879
  }
12785
12880
  if (runtime === "azure-foundry") {
12786
- const reason = "this project uses the Azure Foundry container adapter; scaffold an agentcore project before selecting BNB or AWS";
12881
+ const reason = "this project uses a different container adapter; scaffold an agentcore project before selecting BNB or AWS";
12787
12882
  return {
12788
12883
  bnb: reason,
12789
12884
  aws: reason,
@@ -12814,9 +12909,12 @@ function unavailableReason(opts, provider) {
12814
12909
  }
12815
12910
  return opts.unavailable?.[provider] ?? null;
12816
12911
  }
12912
+ function providerPromptHint() {
12913
+ return PROVIDER_MENU_ORDER.filter((p) => ADVERTISED_PROVIDERS.includes(p)).map((p) => `${providerDigit(p)}=${PROVIDER_SHORT_LABELS[p]}`).join(", ");
12914
+ }
12817
12915
  async function promptProvider(opts) {
12818
12916
  for (; ; ) {
12819
- const answer = (await opts.prompt("Provider [1=BNB, 2=AWS, 3=Azure]: ")).trim().toLowerCase();
12917
+ const answer = (await opts.prompt(`Provider [${providerPromptHint()}]: `)).trim().toLowerCase();
12820
12918
  const provider = answer === "1" || answer === "bnb" ? "bnb" : answer === "2" || answer === "aws" ? "aws" : answer === "3" || answer === "azure" ? "azure" : null;
12821
12919
  if (provider === null) {
12822
12920
  if (answer === "" || answer === "q" || answer === "cancel") {
@@ -14042,9 +14140,11 @@ function isFile14(p) {
14042
14140
 
14043
14141
  // src/cli/_runtime/base.ts
14044
14142
  var DEPLOY_RUNTIME_TARGETS = ["agentcore", "azure-foundry"];
14143
+ var ADVERTISED_DEPLOY_RUNTIME_TARGETS = ["agentcore"];
14045
14144
 
14046
14145
  // src/cli/deploy.ts
14047
14146
  var TARGET_CHOICES = [...DEPLOY_RUNTIME_TARGETS];
14147
+ var ADVERTISED_TARGET_CHOICES = [...ADVERTISED_DEPLOY_RUNTIME_TARGETS];
14048
14148
  function isFile15(p) {
14049
14149
  try {
14050
14150
  return fs37.statSync(p).isFile();
@@ -14084,17 +14184,33 @@ function loadAgentCfg2(root) {
14084
14184
  function registerDeploy(program) {
14085
14185
  program.enablePositionalOptions();
14086
14186
  const p = program.command("deploy").description(
14087
- "Deploy the agent after explicitly selecting BNB, AWS, or Azure; lifecycle subcommands inspect recorded deployments."
14088
- );
14089
- const runtimeOption = () => new Option4(
14090
- "--runtime <name>",
14091
- "Deploy runtime to validate against (default: studio.toml [stack].runtime, else agentcore)."
14092
- ).choices(TARGET_CHOICES);
14093
- const targetAlias = () => new Option4("--target <name>", "Deprecated alias of --runtime.").choices(TARGET_CHOICES).hideHelp();
14094
- const providerOption = () => new Option4(
14095
- "--provider <provider>",
14096
- "Provider: bnb, aws, or azure. Required for non-interactive deploy; lifecycle commands need it only when multiple records exist."
14097
- ).choices(["bnb", "aws", "azure"]);
14187
+ "Deploy the agent after explicitly selecting BNB or AWS; lifecycle subcommands inspect recorded deployments."
14188
+ );
14189
+ const runtimeOption = () => acceptChoices(
14190
+ new Option4(
14191
+ "--runtime <name>",
14192
+ "Deploy runtime to validate against (default: studio.toml [stack].runtime, else agentcore)."
14193
+ ),
14194
+ TARGET_CHOICES,
14195
+ ADVERTISED_TARGET_CHOICES
14196
+ );
14197
+ const targetAlias = () => acceptChoices(
14198
+ new Option4(
14199
+ "--target <name>",
14200
+ "Deprecated alias of --runtime."
14201
+ ).hideHelp(),
14202
+ TARGET_CHOICES,
14203
+ ADVERTISED_TARGET_CHOICES
14204
+ );
14205
+ const providerOption = () => acceptChoices(
14206
+ new Option4(
14207
+ "--provider <provider>",
14208
+ "Provider: bnb or aws. Required for non-interactive deploy; lifecycle commands need it only when multiple records exist."
14209
+ ),
14210
+ // Unadvertised providers stay parseable (see ADVERTISED_PROVIDERS).
14211
+ PROVIDER_MENU_ORDER,
14212
+ ADVERTISED_PROVIDERS
14213
+ );
14098
14214
  p.addOption(providerOption()).option("--project-root <path>", "Override project root.").option("--skip-prepare", "Skip non-storage local readiness checks.").option(
14099
14215
  "--force-deploy-broken-storage",
14100
14216
  "DANGEROUS: bypass fatal deliverable-storage checks."
@@ -14103,7 +14219,7 @@ function registerDeploy(program) {
14103
14219
  ).option("--accept-risk", "Accept provider risk/terms disclosures.").option("--yes", "Confirm the selected deployment plan non-interactively.").option(
14104
14220
  "--allow-multiple",
14105
14221
  "Allow creating a deployment while another provider remains active."
14106
- ).option("--skip-smoke", "Skip Azure's delegated post-deploy smoke check.").action(act((opts) => cmdDeploy(opts, [])));
14222
+ ).option("--skip-smoke", "Skip the delegated post-deploy smoke check.").action(act((opts) => cmdDeploy(opts, [])));
14107
14223
  p.command("prepare").description("Run the deploy-readiness sweep on this project.").addOption(runtimeOption()).addOption(targetAlias()).option("--project-root <path>", "Override project root.").option("--json", "Emit JSON instead of a table.").option(
14108
14224
  "--ignore-warnings",
14109
14225
  "Exit 0 even if WARNING-level checks fire (still fails on BLOCKED/CRITICAL)."
@@ -14142,7 +14258,7 @@ function registerDeploy(program) {
14142
14258
  "Allow creating a deployment while another provider remains active."
14143
14259
  ).option(
14144
14260
  "--skip-smoke",
14145
- "(azure-foundry) Skip the post-deploy smoke check (omit `--smoke` from the delegated bnbagent-deploy invocation)."
14261
+ "Skip the post-deploy smoke check (omit `--smoke` from the delegated bnbagent-deploy invocation)."
14146
14262
  ).argument(
14147
14263
  "[agentcoreArgs...]",
14148
14264
  "Extra args passed straight through to `bnbagent-deploy deploy` (put after `--`)."
@@ -14200,7 +14316,7 @@ function registerDeploy(program) {
14200
14316
  (opts) => cmdDestroy(opts)
14201
14317
  )
14202
14318
  );
14203
- p.command("logs").description("Show the selected deployment's logs via bnbagent-deploy.").option("--project-root <path>", "Override project root.").addOption(providerOption()).option("--since <dur>", "Lower bound, e.g. 5m/2h/1d (default 10m).", "10m").option("--follow", "Stream new lines (poll ~3s).").option("--limit <n>", "Number of recent lines (Azure; default 50).").option("--session <id>", "Azure container session id.").addOption(
14319
+ p.command("logs").description("Show the selected deployment's logs via bnbagent-deploy.").option("--project-root <path>", "Override project root.").addOption(providerOption()).option("--since <dur>", "Lower bound, e.g. 5m/2h/1d (default 10m).", "10m").option("--follow", "Stream new lines (poll ~3s).").option("--limit <n>", "Number of recent lines (default 50).").option("--session <id>", "Container session id.").addOption(
14204
14320
  new Option4(
14205
14321
  "--job <id>",
14206
14322
  "Deprecated Studio direct-CloudWatch filter."
@@ -14652,14 +14768,18 @@ function renderProviderSelection(deployments, trial, unavailable) {
14652
14768
  }
14653
14769
  const remaining = formatTrialRemaining(trial.remainingSeconds);
14654
14770
  const bnbDetail = unavailable.bnb ? `unavailable \u2014 ${unavailable.bnb}` : trial.state === "expired" ? `unavailable \u2014 trial expired${trial.expiresAt ? ` ${trial.expiresAt}` : ""}` : trial.state === "active" ? `active${remaining ? ` \u2014 ${remaining} remaining` : ""}${trial.expiresAt ? ` \xB7 expires ${trial.expiresAt}` : ""}` : trial.state === "available" ? "available \u2014 free 48h testnet; starts on first successful deploy" : "login required to check trial eligibility";
14771
+ const detail = {
14772
+ bnb: bnbDetail,
14773
+ aws: unavailable.aws ? `unavailable \u2014 ${unavailable.aws}` : "deploy to your AWS account",
14774
+ azure: unavailable.azure ? `unavailable \u2014 ${unavailable.azure}` : "deploy to your Azure account"
14775
+ };
14655
14776
  printOut("Deployment providers");
14656
- printOut(` 1. BNB Chain Trial ${bnbDetail}`);
14657
- printOut(
14658
- ` 2. AWS AgentCore ${unavailable.aws ? `unavailable \u2014 ${unavailable.aws}` : "deploy to your AWS account"}`
14659
- );
14660
- printOut(
14661
- ` 3. Azure Foundry ${unavailable.azure ? `unavailable \u2014 ${unavailable.azure}` : "deploy to your Azure account"}`
14662
- );
14777
+ for (const provider of PROVIDER_MENU_ORDER) {
14778
+ if (!ADVERTISED_PROVIDERS.includes(provider)) continue;
14779
+ printOut(
14780
+ ` ${providerDigit(provider)}. ${PROVIDER_LABELS[provider].padEnd(18)}${detail[provider]}`
14781
+ );
14782
+ }
14663
14783
  }
14664
14784
  async function cmdDeploy(opts, deployArgs) {
14665
14785
  const rc = applyProjectRoot(opts.projectRoot);
@@ -14684,7 +14804,7 @@ async function cmdDeploy(opts, deployArgs) {
14684
14804
  });
14685
14805
  if (selection.kind === "selection_required") {
14686
14806
  printErr(
14687
- "error: deployment provider must be selected explicitly in a non-interactive terminal; pass --provider bnb|aws|azure --yes"
14807
+ `error: deployment provider must be selected explicitly in a non-interactive terminal; pass --provider ${ADVERTISED_PROVIDERS_HELP} --yes`
14688
14808
  );
14689
14809
  return selection.exitCode;
14690
14810
  }
@@ -15107,7 +15227,7 @@ async function printAccessNextSteps2(root, runtime, opts = {}) {
15107
15227
  return;
15108
15228
  }
15109
15229
  printOut(
15110
- " Azure Foundry buyer access is in Preview \u2014 see the bnbagent-studio-use-azure-foundry.md reference (skills/references/ in the bnbagent-studio repo) and docs/guides/foundry-a2a-access.md. The per-agent A2A card URL and OAuth scope are confirmed only after a live deploy."
15230
+ " Azure Foundry buyer access is in Preview \u2014 see docs/guides/foundry-a2a-access.md in the bnbagent-studio repo. The per-agent A2A card URL and OAuth scope are confirmed only after a live deploy."
15111
15231
  );
15112
15232
  }
15113
15233
  var CLIENT_PROMPT_RULE2 = "\u2500".repeat(62);
@@ -15381,7 +15501,9 @@ async function cmdStatus2(opts) {
15381
15501
  printOut(JSON.stringify({ deployments: [] }, null, 2));
15382
15502
  } else {
15383
15503
  printOut(`No recorded deployments${detail}.`);
15384
- printOut("Start one with `bag deploy --provider bnb|aws|azure`.");
15504
+ printOut(
15505
+ `Start one with \`bag deploy --provider ${ADVERTISED_PROVIDERS_HELP}\`.`
15506
+ );
15385
15507
  }
15386
15508
  return opts.provider ? 2 : 0;
15387
15509
  }
@@ -15538,13 +15660,13 @@ async function selectRecordedForLifecycle(root, requested, operation) {
15538
15660
  if (deployments.length === 1 && soleDeployment) return soleDeployment;
15539
15661
  if (deployments.length === 0) {
15540
15662
  printErr(
15541
- "error: no deployment is recorded; run `bag deploy --provider bnb|aws|azure` first"
15663
+ `error: no deployment is recorded; run \`bag deploy --provider ${ADVERTISED_PROVIDERS_HELP}\` first`
15542
15664
  );
15543
15665
  return 2;
15544
15666
  }
15545
15667
  if (!stdinIsTty()) {
15546
15668
  printErr(
15547
- `error: multiple deployments are active; non-interactive \`${operation}\` requires --provider bnb|aws|azure`
15669
+ `error: multiple deployments are active; non-interactive \`${operation}\` requires --provider ${ADVERTISED_PROVIDERS_HELP}`
15548
15670
  );
15549
15671
  return 2;
15550
15672
  }
@@ -15997,7 +16119,7 @@ function anchoredKeystoreDir(projectRoot, walletCfg) {
15997
16119
  async function checkWallet(projectRoot, data) {
15998
16120
  const walletCfg = tableOf13(data, "wallet");
15999
16121
  if (walletCfg.kind === "twak") {
16000
- return checkWalletTwak(walletCfg, projectRoot);
16122
+ return checkWalletTwak(walletCfg, projectRoot, data);
16001
16123
  }
16002
16124
  if (walletCfg.kind === "altana") {
16003
16125
  return checkWalletAltana(walletCfg, projectRoot);
@@ -16142,7 +16264,7 @@ function checkWalletAltana(walletCfg, projectRoot) {
16142
16264
  }
16143
16265
  return out;
16144
16266
  }
16145
- async function checkWalletTwak(walletCfg, projectRoot) {
16267
+ async function checkWalletTwak(walletCfg, projectRoot, data = {}) {
16146
16268
  const out = [];
16147
16269
  if (await whichTwak() !== null) {
16148
16270
  const version = await twakInstalledVersion();
@@ -16167,6 +16289,15 @@ async function checkWalletTwak(walletCfg, projectRoot) {
16167
16289
  detail: `not on PATH \u2014 npm install -g @trustwallet/cli@${TWAK_CLI_VERSION}`
16168
16290
  });
16169
16291
  }
16292
+ const networkName = String(tableOf13(data, "network").default ?? "bsc-testnet");
16293
+ const unsupportedTargets = twakUnsupportedContractOverrides(networkName);
16294
+ if (unsupportedTargets.length > 0) {
16295
+ out.push({
16296
+ name: "[wallet] twak contract targets",
16297
+ status: FAIL,
16298
+ detail: `twak v0.20.0 cannot target custom ERC-8183 contracts (${unsupportedTargets.map((item) => item.envKey).join(", ")}) and would use canonical contracts instead. Use wallet.kind='evm-local' for custom ERC-8183 contracts, or remove the overrides. ERC8004_REGISTRY_ADDRESS is supported.`
16299
+ });
16300
+ }
16170
16301
  const walletFile = twakWalletFile(walletCfg, projectRoot);
16171
16302
  if (isFile16(walletFile)) {
16172
16303
  out.push({
@@ -16507,7 +16638,7 @@ function checkErc8183Pricing(data) {
16507
16638
  {
16508
16639
  name: "erc8183 pricing",
16509
16640
  status: PASS,
16510
- detail: "FREE \u2014 zero token escrow; custom/QA contract stack selected with all three ERC-8183 address overrides."
16641
+ detail: "FREE \u2014 zero token escrow; custom contract stack selected with all three ERC-8183 address overrides."
16511
16642
  }
16512
16643
  ];
16513
16644
  }
@@ -16515,7 +16646,7 @@ function checkErc8183Pricing(data) {
16515
16646
  {
16516
16647
  name: "erc8183 pricing",
16517
16648
  status: FAIL,
16518
- detail: "FREE \u2014 zero token escrow, but the canonical ERC-8183 contracts reject zero funding. Set ERC8183_COMMERCE_ADDRESS, ERC8183_ROUTER_ADDRESS, and ERC8183_POLICY_ADDRESS together to a zero-price-compatible QA/custom stack."
16649
+ detail: "FREE \u2014 zero token escrow, but the canonical ERC-8183 contracts reject zero funding. Set ERC8183_COMMERCE_ADDRESS, ERC8183_ROUTER_ADDRESS, and ERC8183_POLICY_ADDRESS together to a zero-price-compatible custom stack."
16519
16650
  }
16520
16651
  ];
16521
16652
  }
@@ -17217,7 +17348,7 @@ function loadAgentCfg3() {
17217
17348
  return null;
17218
17349
  }
17219
17350
  }
17220
- function resolveNetwork(override) {
17351
+ function resolveNetwork2(override) {
17221
17352
  if (override) {
17222
17353
  return override;
17223
17354
  }
@@ -17407,7 +17538,7 @@ async function cmdRegister2(opts) {
17407
17538
  return 2;
17408
17539
  }
17409
17540
  const wallet = walletRt6.getWallet();
17410
- const network = resolveNetwork(opts.network);
17541
+ const network = resolveNetwork2(opts.network);
17411
17542
  const gasErr = await precheckRegisterGas(wallet, network);
17412
17543
  if (gasErr !== null) {
17413
17544
  printErr(`error: ${gasErr}`);
@@ -17495,7 +17626,7 @@ async function cmdUpdateEndpoint(opts) {
17495
17626
  return 2;
17496
17627
  }
17497
17628
  const wallet = walletRt6.getWallet();
17498
- const network = resolveNetwork(opts.network);
17629
+ const network = resolveNetwork2(opts.network);
17499
17630
  const protocol = resolveProtocol(opts.protocol);
17500
17631
  let accessDescription = null;
17501
17632
  const agentRoot2 = findProjectRoot5();
@@ -17538,7 +17669,7 @@ async function cmdUpdateEndpoint(opts) {
17538
17669
  }
17539
17670
  async function cmdUpdateMetadata(opts) {
17540
17671
  const wallet = walletRt6.getWallet();
17541
- const network = resolveNetwork(opts.network);
17672
+ const network = resolveNetwork2(opts.network);
17542
17673
  let txHash;
17543
17674
  try {
17544
17675
  txHash = await setMetadata(wallet, opts.key, opts.value, {
@@ -17570,7 +17701,7 @@ async function cmdClearPending(opts) {
17570
17701
  }
17571
17702
  const pendingTx = identity.pending_tx ? String(identity.pending_tx) : null;
17572
17703
  if (pendingTx !== null && !opts.force) {
17573
- const network = resolveNetwork(opts.network);
17704
+ const network = resolveNetwork2(opts.network);
17574
17705
  let seen;
17575
17706
  try {
17576
17707
  seen = await readTransactionSeen(pendingTx, network);
@@ -17601,7 +17732,7 @@ async function cmdGetMetadata(opts) {
17601
17732
  let value;
17602
17733
  try {
17603
17734
  value = await getMetadata(wallet, opts.key, {
17604
- network: resolveNetwork(opts.network)
17735
+ network: resolveNetwork2(opts.network)
17605
17736
  });
17606
17737
  } catch (exc) {
17607
17738
  printOut(`error: ${errMsg4(exc)}`);
@@ -17614,7 +17745,7 @@ async function cmdShow6(networkArg) {
17614
17745
  const wallet = walletRt6.getWallet();
17615
17746
  let record;
17616
17747
  try {
17617
- record = await show2(wallet, { network: resolveNetwork(networkArg) });
17748
+ record = await show2(wallet, { network: resolveNetwork2(networkArg) });
17618
17749
  } catch (exc) {
17619
17750
  printOut(`error: ${errMsg4(exc)}`);
17620
17751
  return errName2(exc) === "NotRegisteredError" ? 2 : 1;
@@ -17631,7 +17762,7 @@ async function cmdResolve(agentId, networkArg) {
17631
17762
  try {
17632
17763
  uri = await resolve20(agentId, {
17633
17764
  wallet,
17634
- network: resolveNetwork(networkArg)
17765
+ network: resolveNetwork2(networkArg)
17635
17766
  });
17636
17767
  } catch (exc) {
17637
17768
  printOut(`error: ${errMsg4(exc)}`);
@@ -19198,7 +19329,7 @@ function loadSellerConfig() {
19198
19329
  return null;
19199
19330
  }
19200
19331
  }
19201
- function resolveNetwork2(cfg, override) {
19332
+ function resolveNetwork3(cfg, override) {
19202
19333
  if (override) {
19203
19334
  return override;
19204
19335
  }
@@ -19276,7 +19407,12 @@ async function cmdSellInit(priceFlag) {
19276
19407
  }
19277
19408
  const envPath = envLocalPath17(root);
19278
19409
  for (const key of B402_ENV_KEYS) {
19279
- if (getEnvVar(envPath, key) === null) setEnvVar(envPath, key, "");
19410
+ const existing = getEnvVar(envPath, key);
19411
+ if (key === "B402_BASE_URL" && !free && resolveNetwork3(cfg).toLowerCase() === "bsc-testnet" && !process.env.B402_BASE_URL && !existing) {
19412
+ setEnvVar(envPath, key, DEFAULT_B402_TESTNET_BASE_URL);
19413
+ } else if (existing === null) {
19414
+ setEnvVar(envPath, key, "");
19415
+ }
19280
19416
  }
19281
19417
  printOut(
19282
19418
  free ? "\u2713 x402 seller config is ready in FREE mode; B402 credentials are not required." : "\u2713 x402 seller config and B402 placeholders are ready for PAID mode."
@@ -19355,7 +19491,7 @@ async function cmdSellStatus(probe) {
19355
19491
  printOut("B402 probe: skipped (rail disabled or credentials incomplete)");
19356
19492
  return 0;
19357
19493
  }
19358
- const networkName = resolveNetwork2(cfg);
19494
+ const networkName = resolveNetwork3(cfg);
19359
19495
  const network = caip2(getNetwork11(networkName).chainId);
19360
19496
  const token = SELLER_TOKENS[network];
19361
19497
  if (!token) {
@@ -19449,7 +19585,7 @@ async function cmdBuy2(url, opts) {
19449
19585
  printErr(`error: ${errMsg6(exc)}`);
19450
19586
  return 2;
19451
19587
  }
19452
- const networkName = resolveNetwork2(cfg, opts.network);
19588
+ const networkName = resolveNetwork3(cfg, opts.network);
19453
19589
  const wallet = resolveWallet2();
19454
19590
  if (wallet === null) {
19455
19591
  return 1;
@@ -19638,7 +19774,7 @@ async function cmdTrust(merchant, opts) {
19638
19774
  host = rec.domain;
19639
19775
  methodHint = rec.probeMethod;
19640
19776
  }
19641
- const networkName = opts.network || (rec ? rec.network : resolveNetwork2(cfg, null));
19777
+ const networkName = opts.network || (rec ? rec.network : resolveNetwork3(cfg, null));
19642
19778
  const probed = await probeChallenge(probeUrl, methodHint);
19643
19779
  if (probed === null) {
19644
19780
  return 1;
@@ -19784,7 +19920,7 @@ function buildProgram() {
19784
19920
  return program;
19785
19921
  }
19786
19922
  function cliVersion() {
19787
- return "0.0.6-alpha.2";
19923
+ return "0.0.6-alpha.3";
19788
19924
  }
19789
19925
 
19790
19926
  // src/cli/updateCheck.ts
@@ -295,6 +295,7 @@ function mb(n) {
295
295
 
296
296
  // src/cli/_x402SellerConfig.ts
297
297
  var DEFAULT_B402_PRICE_USD = "0.01";
298
+ var DEFAULT_B402_TESTNET_BASE_URL = "https://qacb.sdtaop.com";
298
299
  var PRICE_USD_RE = /^(?:0|[1-9]\d*)(?:\.\d+)?$/;
299
300
  var ZERO_PRICE_USD_RE = /^0(?:\.0+)?$/;
300
301
  function x402SellerPricingState(seller) {
@@ -317,6 +318,14 @@ function normalizeB402PriceUsd(value) {
317
318
  }
318
319
  return normalized;
319
320
  }
321
+ function isB402TestnetBaseUrl(value) {
322
+ try {
323
+ const url = new URL(value);
324
+ if (url.origin === DEFAULT_B402_TESTNET_BASE_URL) return true;
325
+ } catch {
326
+ }
327
+ return /sandbox|test/.test(value.toLowerCase());
328
+ }
320
329
  function x402SellerUsesB402(cfg) {
321
330
  const payments = table2(cfg.payments);
322
331
  const seller = table2(payments.x402_seller);
@@ -649,10 +658,14 @@ function b402Credentials(agentRoot) {
649
658
  if (value) values.set(key, value);
650
659
  }
651
660
  const privateKey = values.has("B402_PRIVATE_KEY") || values.has("B402_PRIVATE_KEY_B64");
661
+ const onlyDefaultBaseUrl = values.size === 1 && values.get("B402_BASE_URL")?.replace(/\/+$/, "") === DEFAULT_B402_TESTNET_BASE_URL;
652
662
  return {
653
663
  presentKeys: [...values.keys()],
654
664
  complete: values.has("B402_BASE_URL") && values.has("B402_CLIENT_ID") && values.has("B402_ACCESS_TOKEN") && privateKey,
655
- any: values.size > 0,
665
+ // The testnet facilitator URL is a non-secret platform default. On its own it must not turn an
666
+ // otherwise dormant rail into a "partial credentials" deployment failure. Any custom URL or
667
+ // merchant credential still preserves the existing partial/unused checks.
668
+ any: values.size > 0 && !onlyDefaultBaseUrl,
656
669
  bothPrivateKeyFormats: values.has("B402_PRIVATE_KEY") && values.has("B402_PRIVATE_KEY_B64"),
657
670
  baseUrl: values.get("B402_BASE_URL") ?? null
658
671
  };
@@ -1079,8 +1092,10 @@ export {
1079
1092
  devPortOf,
1080
1093
  recipeModeOf,
1081
1094
  DEFAULT_B402_PRICE_USD,
1095
+ DEFAULT_B402_TESTNET_BASE_URL,
1082
1096
  x402SellerPricingState,
1083
1097
  normalizeB402PriceUsd,
1098
+ isB402TestnetBaseUrl,
1084
1099
  x402SellerUsesB402,
1085
1100
  x402SellerIsFree,
1086
1101
  whichBin,
@@ -17,7 +17,7 @@ import {
17
17
  runPlatformAccountCommand,
18
18
  trialFromDeployCliJson,
19
19
  withDeployFiles
20
- } from "./chunk-6EYSXRFD.js";
20
+ } from "./chunk-A7NAGZHR.js";
21
21
  import "./chunk-7RAKL4AS.js";
22
22
  export {
23
23
  BNB_PLATFORM_API_URL,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bnbagent/studio-cli",
3
- "version": "0.0.6-alpha.2",
3
+ "version": "0.0.6-alpha.3",
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": {
@@ -25,7 +25,7 @@
25
25
  "bag": "./dist/bag.js"
26
26
  },
27
27
  "dependencies": {
28
- "@bnbagent/sdk": "0.5.0-alpha.1",
28
+ "@bnbagent/sdk": "0.5.0-alpha.2",
29
29
  "ai": "^7.0.29",
30
30
  "archiver": "^8.0.0",
31
31
  "commander": "^15.0.0",
@@ -37,7 +37,7 @@
37
37
  "tar": "^7.4.0",
38
38
  "viem": "^2.54.0",
39
39
  "yaml": "^2.9.0",
40
- "@bnbagent/studio-runtime": "0.0.6-alpha.2"
40
+ "@bnbagent/studio-runtime": "0.0.6-alpha.3"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@a2a-js/sdk": "^0.3.14",
@@ -159,8 +159,9 @@ function defaultNetwork(): string {
159
159
  // the ONLY automatic signing path outside signing.ts — it is budget-gated and
160
160
  // is NOT an LLM tool. It rides transparently into the delivery step.
161
161
  //
162
- // The LLM runs ONLY in the delivery step (the value hook). `negotiate` is
163
- // rule-based and never touches the LLM. The read-only chain tools are
162
+ // The LLM runs only in an authorized value step: verified ERC-8183 delivery
163
+ // or x402 work after its payment/free gate. `negotiate` is rule-based and
164
+ // never touches the LLM. The read-only chain tools are
164
165
  // attached so the work can read on-chain context if it needs to — drop them
165
166
  // from `tools.ts` if your work doesn't read chain. Signing / settle are NEVER
166
167
  // tools — they are fixed code in signing.ts, triggered by the A2A skills,
@@ -178,7 +179,9 @@ export function buildRunWork(): RunWork {
178
179
  const result = await generateText({
179
180
  model,
180
181
  system:
181
- "You are a seller agent. You do the actual work once a job is funded. " +
182
+ "You are a seller agent. The runtime has already authorized this task " +
183
+ "through its configured commerce rail. Complete the user's task now; " +
184
+ "do not ask for a job ID or additional payment. " +
182
185
  "Be concrete and concise. Use the read-only chain tools when on-chain " +
183
186
  "context helps. If a paid-data tool such as `buy_with_x402` is available " +
184
187
  "to you, USE IT to fetch the data a task needs — those merchants (e.g. " +
@@ -150,8 +150,9 @@ function defaultNetwork(): string {
150
150
  // the ONLY automatic signing path outside signing.ts — it is budget-gated and
151
151
  // is NOT an LLM tool. It rides transparently into the delivery step.
152
152
  //
153
- // The LLM runs ONLY in the delivery step (the value hook). `negotiate` is
154
- // rule-based and never touches the LLM. The read-only chain tools are
153
+ // The LLM runs only in an authorized value step: verified ERC-8183 delivery
154
+ // or x402 work after its payment/free gate. `negotiate` is rule-based and
155
+ // never touches the LLM. The read-only chain tools are
155
156
  // attached so the work can read on-chain context if it needs to — drop them
156
157
  // from `tools.ts` if your work doesn't read chain. Signing / settle are NEVER
157
158
  // tools — they are fixed code in signing.ts, triggered by the A2A skills,
@@ -169,7 +170,9 @@ export function buildRunWork(): RunWork {
169
170
  const result = await generateText({
170
171
  model,
171
172
  system:
172
- "You are a seller agent. You do the actual work once a job is funded. " +
173
+ "You are a seller agent. The runtime has already authorized this task " +
174
+ "through its configured commerce rail. Complete the user's task now; " +
175
+ "do not ask for a job ID or additional payment. " +
173
176
  "Be concrete and concise. Use the read-only chain tools when on-chain " +
174
177
  "context helps. If a paid-data tool such as `buy_with_x402` is available " +
175
178
  "to you, USE IT to fetch the data a task needs — those merchants (e.g. " +
@@ -184,10 +184,10 @@ function flatQuery(query: Record<string, unknown>): Record<string, string> {
184
184
  return out;
185
185
  }
186
186
 
187
- // ── LLM work hook (lazy: built on first delivery; negotiate never needs it) ──
188
- // Deferred construction keeps the negotiate-only path (and a cold start that
189
- // only quotes) from building the model, and keeps this module importable
190
- // without the provider env until a deliverable is actually produced.
187
+ // ── LLM work hook (lazy: built on first authorized task) ─────────────────────
188
+ // Deferred construction keeps negotiate and unpaid x402 quote paths from
189
+ // building the model, and keeps this module importable without the provider
190
+ // env until a deliverable is actually produced.
191
191
  type RunLlm = (prompt: string) => Promise<string>;
192
192
  let cachedRunLlm: RunLlm | null = null;
193
193
 
@@ -200,7 +200,9 @@ async function runLlm(prompt: string): Promise<string> {
200
200
  const result = await generateText({
201
201
  model,
202
202
  system:
203
- "You are a seller agent. You do the actual work once a job is funded. " +
203
+ "You are a seller agent. The runtime has already authorized this task " +
204
+ "through its configured commerce rail. Complete the user's task now; " +
205
+ "do not ask for a job ID or additional payment. " +
204
206
  "Be concrete and concise. Use the read-only chain tools when on-chain " +
205
207
  "context helps. If a paid-data tool such as `buy_with_x402` is " +
206
208
  "available to you, USE IT to fetch the data a task needs — those " +
@@ -105,8 +105,9 @@ async function withTimeout<T>(
105
105
  * The LLM work hook: produce the deliverable text for a prompt.
106
106
  *
107
107
  * Built in `main.ts` from the AI SDK (`generateText` + the read-only chain
108
- * tools); called ONLY inside the background delivery. `abortSignal` is wired
109
- * to the delivery timeout so a hung LLM call is actually cancelled.
108
+ * tools); called by verified ERC-8183 delivery and, through the runtime
109
+ * adapter, by x402 only after its commerce gate. `abortSignal` is wired to
110
+ * the delivery timeout so a hung LLM call is actually cancelled.
110
111
  */
111
112
  export type RunWork = (
112
113
  prompt: string,
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: bnbagent-studio
3
- description: The single entry point for bnbagent-studio — a TypeScript CLI (`bag`) for building a blockchain SELLER agent that earns $U on BNB Chain via ERC-8004 + ERC-8183 + x402 (Pieverse LLM inside). Load this skill whenever the user works in a bnbagent-studio / `bag` project, or wants to create/scaffold, deploy, run, debug, operate, or monetize such a seller agent (composable A2A, MCP, and X402 faces; BNB Chain trial, AWS AgentCore, or Azure Foundry). All detailed playbooks ship as references/ files inside this skill — route via the decision tree in the body. When invoked with arguments, treat them as the user's intent and route the same way.
3
+ description: The single entry point for bnbagent-studio — a TypeScript CLI (`bag`) for building a blockchain SELLER agent that earns $U on BNB Chain via ERC-8004 + ERC-8183 + x402 (Pieverse LLM inside). Load this skill whenever the user works in a bnbagent-studio / `bag` project, or wants to create/scaffold, deploy, run, debug, operate, or monetize such a seller agent (composable A2A, MCP, and X402 faces; BNB Chain trial or AWS AgentCore). All detailed playbooks ship as references/ files inside this skill — route via the decision tree in the body. When invoked with arguments, treat them as the user's intent and route the same way.
4
4
  ---
5
5
 
6
6
  # bnbagent-studio (the single entry point)
@@ -10,15 +10,13 @@ ERC-8004 / ERC-8183 / Pieverse LLM) into a TypeScript agent project, then deploy
10
10
  as a **single blockchain seller runtime**. A2A, MCP, and X402 are composable
11
11
  public faces selected with `--protocols`; A2A is the default. `bag deploy` uses
12
12
  **scheme C**: every new deploy
13
- or redeploy explicitly selects BNB, AWS, or Azure; a recorded deployment is
13
+ or redeploy explicitly selects BNB or AWS; a recorded deployment is
14
14
  used only to offer an explicit update action, never as a silent default. BNB is
15
- a 48h testnet trial and is disabled after expiry. AWS and Azure deploy into the
15
+ a 48h testnet trial and is disabled after expiry. AWS deploys into the
16
16
  user's own account. All cloud lifecycle calls go through the pinned
17
- `@bnbagent/deploy-cli`; never require `aws`, `az`, or `azd` CLIs.
18
- BNB/AWS share the agentcore scaffold; Azure requires the azure-foundry
19
- container scaffold and currently deploys A2A projects only (Foundry's
20
- Invocations contract is not the native MCP transport). Treat an incompatible provider row as unavailable—do not
21
- force through it or mutate the scaffold during deploy.
17
+ `@bnbagent/deploy-cli`; never require the `aws` CLI.
18
+ BNB/AWS share the agentcore scaffold. Treat an incompatible provider row as
19
+ unavailable—do not force through it or mutate the scaffold during deploy.
22
20
 
23
21
  Invoked as `/bnbagent-studio <ask>`? Treat `<ask>` as the user's intent and
24
22
  route it through the decision tree below, exactly like a natural-language ask.
@@ -57,7 +55,7 @@ not answer from memory.
57
55
  | Run / debug / dev / doctor / RPC / balance / incident triage | `references/bnbagent-studio-operating.md` |
58
56
  | Implement what the Agent sells, tune pricing, publish over A2A and/or MCP, defend disputes (seller flow) | `references/bnbagent-studio-selling-via-8183.md` |
59
57
  | Sell one paid or FREE HTTP request through the B402-backed x402 rail (pricing choice; paid merchant application, RSA key, credentials, IP allowlist, activation) | `references/bnbagent-studio-selling-via-b402.md` |
60
- | Deploy / redeploy / status / logs / destroy | Run `bag deploy` and explicitly choose a provider. Non-interactive deploy requires `--provider bnb\|aws\|azure --yes` (and `--allow-multiple` when keeping another provider active). Read `references/bnbagent-studio-use-bnb-trial.md`, `references/bnbagent-studio-use-aws-agentcore.md`, or `references/bnbagent-studio-use-azure-foundry.md` for the selected provider. `bag deploy status` lists every recorded provider; multi-deployment logs/verify/destroy require `--provider`. |
58
+ | Deploy / redeploy / status / logs / destroy | Run `bag deploy` and explicitly choose a provider. Non-interactive deploy requires `--provider bnb\|aws --yes` (and `--allow-multiple` when keeping another provider active). Read `references/bnbagent-studio-use-bnb-trial.md` or `references/bnbagent-studio-use-aws-agentcore.md` for the selected provider. `bag deploy status` lists every recorded provider; multi-deployment logs/verify/destroy require `--provider`. |
61
59
  | Wire chain-read tools into the Agent's LLM (AI SDK `tool()` wrappers, or any TS agent framework) | `references/bnbagent-studio-wiring-llm-tools.md` |
62
60
  | Buy a service from another ERC-8183 seller via CLI — incl. testing your own seller from the buyer side (v2/internal — NOT the v1 seller product flow) | `references/bnbagent-studio-buying-via-8183.md` |
63
61
  | Give the agent a PAID x402 capability — CMC market data / Binance Bazaar (B402) merchants / any pay-per-call API (`bag x402 trust`, x402-buyer recipe, 402 buyer errors) | `references/bnbagent-studio-buying-from-bazaar.md` |
@@ -99,7 +97,7 @@ prices retain the paid merchant flow.
99
97
 
100
98
  ## CLI groups at a glance
101
99
 
102
- `init`, `scan`, `recipe`, `skills`, `wallet`, `erc8004`, `erc8183`, `x402`, `agents`, `config`, `env`, `dev`, `doctor`, `audit`, `deploy`, `platform`, `llm`, `bundle`, `budget` — see `bag --help` for details. `bag deploy [--provider bnb\|aws\|azure]` is the primary deploy command; `prepare`, `verify`, `status`, `info`, `destroy`, `logs`, `fix-gitignore`, and `provision-cognito` remain lifecycle subcommands (`deploy agent` is a deprecated compatibility alias). Provider deploy/status/logs/destroy and deploy-time credential validation are delegated to pinned `@bnbagent/deploy-cli@0.4.14`.
100
+ `init`, `scan`, `recipe`, `skills`, `wallet`, `erc8004`, `erc8183`, `x402`, `agents`, `config`, `env`, `dev`, `doctor`, `audit`, `deploy`, `platform`, `llm`, `bundle`, `budget` — see `bag --help` for details. `bag deploy [--provider bnb\|aws]` is the primary deploy command; `prepare`, `verify`, `status`, `info`, `destroy`, `logs`, `fix-gitignore`, and `provision-cognito` remain lifecycle subcommands (`deploy agent` is a deprecated compatibility alias). Provider deploy/status/logs/destroy and deploy-time credential validation are delegated to pinned `@bnbagent/deploy-cli@0.4.14`.
103
101
 
104
102
  ## Tool surface
105
103
 
@@ -171,7 +171,7 @@ refill with `bag llm topup` or enable the budget with `bag budget enable`.
171
171
 
172
172
  ## Step 5 — Deploy the agent
173
173
 
174
- `bag deploy` always asks the operator to choose BNB, AWS, or Azure; it never
174
+ `bag deploy` always asks the operator to choose BNB or AWS; it never
175
175
  silently reuses `[deploy].destination` or the last provider.
176
176
 
177
177
  **Platform scaffold** (the bare-init default while the campaign runs) — one
@@ -27,7 +27,6 @@ directory (Claude Code: `~/.claude/skills/bnbagent-studio/references/`; Cursor:
27
27
  the file when the topic comes up — don't answer from memory:
28
28
  - `bnbagent-studio-use-aws-agentcore.md` — the delegated AgentCore lifecycle (`bag deploy --provider aws` / `status` / `logs` / `verify` / `destroy`, `provision-cognito`) + AWS prerequisites
29
29
  - `bnbagent-studio-use-bnb-trial.md` — GitHub device login, 48h eligibility, staging verification, and the delegated BNB trial lifecycle
30
- - `bnbagent-studio-use-azure-foundry.md` — the delegated Azure Foundry lifecycle (`bag deploy --provider azure`; no `az`/`azd` CLI)
31
30
  - `bnbagent-studio-using-twak-wallet.md` — `[wallet].kind = "twak"` create / fund / SIWE-bind / container deploy / limitations
32
31
  - `bnbagent-studio-extending-signing.md` — `PolicyViolation` / `X402PolicyError` diagnosis + extending the EIP-712 allowlist
33
32
  - `bnbagent-studio-adding-to-project.md` — adding the seller runtime to an existing TypeScript project
@@ -55,8 +54,8 @@ For seller job-lifecycle decisions (settle / submit / dispute defense), read
55
54
  | "submit work for job X" | **seller action** — read `bnbagent-studio-selling-via-8183.md` (same directory) for the submit/dispute flow |
56
55
  | "tx not confirming" | Read BscScan link from prior tx output + check `eth_getTransactionCount` |
57
56
  | "wallet balance is wrong" | Check both tBNB (gas) and U (token); see balance section |
58
- | "is it deployed?" / "deploy status" | `bag deploy status` lists every locally recorded BNB/AWS/Azure deployment and asks `bnbagent-deploy` for live state; add `--no-probe` for record-only output |
59
- | "deploy logs" / "verify" / "tear it down" | With one recorded deployment, `bag deploy {logs,verify,destroy}` selects it automatically. With multiple, choose interactively or pass `--provider bnb\|aws\|azure` in automation. Cloud calls always go through `bnbagent-deploy`. `bag platform credit` shows the BNB trial countdown. |
57
+ | "is it deployed?" / "deploy status" | `bag deploy status` lists every locally recorded BNB/AWS deployment and asks `bnbagent-deploy` for live state; add `--no-probe` for record-only output |
58
+ | "deploy logs" / "verify" / "tear it down" | With one recorded deployment, `bag deploy {logs,verify,destroy}` selects it automatically. With multiple, choose interactively or pass `--provider bnb\|aws` in automation. Cloud calls always go through `bnbagent-deploy`. `bag platform credit` shows the BNB trial countdown. |
60
59
 
61
60
  ## Common ops procedures
62
61
 
@@ -161,7 +160,7 @@ testnet U pays ERC-8183 jobs, mainnet U pays the Pieverse LLM auto-renew.
161
160
  Same wallet address on both chains.
162
161
 
163
162
  `bag doctor` prints ERC-8183 pricing as `PAID` or `FREE`. FREE is not ready on
164
- the canonical contract stack: select one zero-price-compatible QA/custom stack
163
+ the canonical contract stack: select one zero-price-compatible custom stack
165
164
  by setting `ERC8183_COMMERCE_ADDRESS`, `ERC8183_ROUTER_ADDRESS`, and
166
165
  `ERC8183_POLICY_ADDRESS` together. A partial set fails because it can mix
167
166
  incompatible commerce, router, and policy deployments.
@@ -216,7 +215,7 @@ covered in `bnbagent-studio-selling-via-8183.md` (same directory).
216
215
  | `notify_funded` replies `{"status":"rejected","reason":...}` | `verifySignedJob` failed synchronously in the ack — a **permanent** failure | `reason` names it: not our signature / tampered terms / underfunded / expired (or `error` for a malformed `job_id`). The job is refused outright; re-fund/re-notify with a correct, fully-funded job |
217
216
  | Job stays `FUNDED`, never reaches `SUBMITTED` after an `accepted` ack | Background delivery failed (`runWork` / `submitResult` raised) — **not** visible in the A2A reply | The ack only confirms verify passed; delivery runs in the background. Observe the failure via the chain (job never leaves `FUNDED`) + CloudWatch logs; a later `notify_funded` re-attempts it via the sweep |
218
217
  | `ERC8183JobOps` has no such export from `@bnbagent/sdk` | package.json pinned an old `@bnbagent/sdk` (missing class) | Bump the dependency and reinstall |
219
- | FREE price fails doctor/prepare on canonical contracts | `price = "0"` is selected without a zero-price-compatible stack | Set all three `ERC8183_*_ADDRESS` overrides from the current apex-contracts `bscTestnetQa` entry, then rerun `bag doctor` and `bag deploy prepare` |
218
+ | FREE price fails doctor/prepare on canonical contracts | `price = "0"` is selected without a zero-price-compatible stack | Set all three `ERC8183_*_ADDRESS` overrides from one compatible custom deployment, then rerun `bag doctor` and `bag deploy prepare` |
220
219
  | ERC-8183 contract override is incomplete | Only one or two of commerce/router/policy were selected | Set or remove all three together; never mix stacks |
221
220
  | `/x402` is public without a 402 challenge | `payments.x402_seller.price_usd = "0"` selected anonymous FREE passthrough | If payment is intended, set a positive decimal price, configure the complete B402 credential set, rerun `bag doctor`, and redeploy |
222
221
  | B402 credentials are missing but x402 reports FREE | Expected: FREE bypasses B402 and does not synchronize its secrets | No credential fix is needed; change to a positive price only when the route should charge |
@@ -235,7 +235,7 @@ Step 6b when `storage=ipfs`):
235
235
  `--erc8183-price 0` only when the user explicitly chose FREE; omitting the
236
236
  flag preserves the paid 0.1 U default. FREE additionally requires all three
237
237
  `ERC8183_COMMERCE_ADDRESS`, `ERC8183_ROUTER_ADDRESS`, and
238
- `ERC8183_POLICY_ADDRESS` values from one zero-price-compatible QA/custom
238
+ `ERC8183_POLICY_ADDRESS` values from one zero-price-compatible custom
239
239
  stack; set them with `bag env set` after scaffolding. For B402, pass
240
240
  `--b402-price 0` only after the user explicitly accepts an unrestricted
241
241
  anonymous FREE `/x402` endpoint. FREE bypasses B402 verify/settle and needs
@@ -115,16 +115,16 @@ string:
115
115
 
116
116
  ```bash
117
117
  bag config set payments.erc8183.price 0
118
- bag env set ERC8183_COMMERCE_ADDRESS <qa-commerce-proxy>
119
- bag env set ERC8183_ROUTER_ADDRESS <qa-router-proxy>
120
- bag env set ERC8183_POLICY_ADDRESS <qa-policy>
118
+ bag env set ERC8183_COMMERCE_ADDRESS <commerce-address>
119
+ bag env set ERC8183_ROUTER_ADDRESS <router-address>
120
+ bag env set ERC8183_POLICY_ADDRESS <policy-address>
121
121
  bag doctor
122
122
  bag deploy prepare
123
123
  ```
124
124
 
125
- Take all three addresses from the same apex-contracts `bscTestnetQa` entry.
125
+ Take all three addresses from the same compatible custom deployment.
126
126
  Doctor/prepare reject canonical or partial contract selection for FREE and
127
- announce `zero token escrow` only when the complete custom/QA stack is selected.
127
+ announce `zero token escrow` only when the complete custom stack is selected.
128
128
 
129
129
  ## Stage 3 — LLM credit continuity (Pieverse projects only)
130
130
 
@@ -152,7 +152,7 @@ Open the workspace `.studio/.env.local` in an editor and fill exactly four
152
152
  values:
153
153
 
154
154
  ```dotenv
155
- B402_BASE_URL=
155
+ B402_BASE_URL=https://qacb.sdtaop.com
156
156
  B402_CLIENT_ID=
157
157
  B402_ACCESS_TOKEN=
158
158
  B402_PRIVATE_KEY_B64=
@@ -7,7 +7,7 @@ description: When the user wants to deploy or operate an A2A bnbagent-studio pro
7
7
 
8
8
  # bnbagent-studio-use-azure-foundry
9
9
 
10
- > **Preview.** Azure Foundry support is fully wired and listed in the `--runtime` menu. The most recent end-to-end live verification predates the TypeScript rewrite — treat your first deploy as a verification run.
10
+ > **Preview — not advertised in this release.** Azure Foundry support is fully wired but hidden from the `--runtime` menu and the deploy provider menu; these steps still work if you select `azure-foundry` / `--provider azure` explicitly. The most recent end-to-end live verification predates the TypeScript rewrite — treat your first deploy as a verification run.
11
11
 
12
12
  Procedure for deploying and operating the seller Agent on **Azure AI
13
13
  Foundry Hosted Agents** (`[stack].runtime = "azure-foundry"`). ALL cloud
@@ -13,7 +13,7 @@ keep `bsc-testnet`, and explain that the runtime signing material is transmitted
13
13
  to the operator's managed secret store for the trial. Never use a mainnet key.
14
14
 
15
15
  All auth and cloud lifecycle work must cross the pinned
16
- `@bnbagent/deploy-cli@0.4.14` boundary. Do not call AWS/Azure CLIs or platform
16
+ `@bnbagent/deploy-cli@0.4.14` boundary. Do not call the AWS CLI or platform
17
17
  REST routes directly.
18
18
 
19
19
  ## Select and authenticate
@@ -35,7 +35,7 @@ Before offering BNB, inspect the trial result:
35
35
  - `available`: selectable; explain that the 48h clock starts on first success.
36
36
  - `active`: selectable; show remaining time and expiry immediately.
37
37
  - `expired`: show the row and expiry, but mark it unavailable and do not select
38
- it. AWS/Azure remain independently available when compatible with the
38
+ it. AWS remains independently available when compatible with the
39
39
  project scaffold.
40
40
  - unknown/auth required: explain that eligibility cannot be confirmed until
41
41
  login; the delegated deploy rechecks before building.
@@ -173,11 +173,11 @@ Two assets, two different rules:
173
173
  settles), so topping up burns no BNB. A twak wallet is also a supported
174
174
  b402 **seller** payout wallet (`bag init --wallet-kind twak --rails b402`);
175
175
  receiving needs no signature or gas either.
176
- - **BNB (gas)** — **testnet: none needed; mainnet: a little for ERC-8183.**
177
- Testnet: the SDK forwards MegaFuel's testnet paymaster (`--paymaster-url`,
178
- twak >= 0.20.0), so x402 topups (already gasless) plus **all** 8004/8183
179
- writes are sponsored a little tBNB (~0.007) is only a fallback in case
180
- sponsorship declines a tx. Mainnet: x402 stays gasless and `bag 8004
176
+ - **BNB (gas)** — **testnet canonical contracts normally use sponsorship;
177
+ mainnet needs a little for ERC-8183.** Testnet: the SDK forwards MegaFuel's
178
+ testnet paymaster (`--paymaster-url`, twak >= 0.20.0). Sponsorship still
179
+ depends on the paymaster policy covering the target contract and method;
180
+ keep a little tBNB (~0.007) as fallback. Mainnet: x402 stays gasless and `bag 8004
181
181
  register` is gas-sponsored by twak internally (Trust gateway — studio
182
182
  passes no paymaster flag), but **`8183 settle` / `fund` self-pay gas**, so
183
183
  keep ~0.007 BNB on the wallet for them.
@@ -203,8 +203,8 @@ spending limit nothing can bypass. Studio's daily caps
203
203
  (persisted to `.studio/spend-ledger.json`), best-effort in the deployed
204
204
  runtime (in-memory, resets on cold start).
205
205
 
206
- Testnet faucet: https://www.bnbchain.org/en/testnet-faucet (tBNB, fallback only).
207
- Mainnet: U via PancakeSwap (BNB not needed — gas is sponsored).
206
+ Testnet faucet: https://www.bnbchain.org/en/testnet-faucet (tBNB fallback).
207
+ Mainnet: U via PancakeSwap; keep BNB for ERC-8183 fund/settle.
208
208
 
209
209
  ## 4. SIWE binding (Pieverse) — ALWAYS bind before paying
210
210
 
@@ -244,7 +244,9 @@ registers a `Container` runtime and `app/agent/Dockerfile` builds the image
244
244
  |---|---|---|
245
245
  | ~~Seller `submit` unavailable~~ | ~~REQ-1~~ RESOLVED in v0.19.0 | `submit --opt-params` works — verified on-chain. |
246
246
  | ~~Seller `quote` signing broken~~ | ~~S-11 regression in v0.19.0~~ RESOLVED in v0.19.1 | v0.19.0 hex-decoded `0x…` messages and signed the bytes, so provider_sig never verified (testnet also rejected `sign-message --chain bsctestnet`). v0.19.1 signs the literal text (EIP-191): `sign_quote` works on both wallet kinds. |
247
- | ~~Testnet intent writes self-pay gas~~ | ~~REQ-2~~ RESOLVED in v0.20.0 | twak accepts `--paymaster-url`; the SDK forwards MegaFuel's testnet endpoint on every sponsored write, so testnet 8004/8183 writes are gasless too (the relay itself is flaky — see the BUG-029 warning in §3). Mainnet stays twak-internal (no flag passed). The CLI floor is now **0.20.0** — `bag doctor` / `bag deploy prepare` reject older. |
247
+ | ~~No testnet paymaster URL~~ | ~~REQ-2~~ RESOLVED in v0.20.0 | twak accepts `--paymaster-url`; the SDK forwards MegaFuel's testnet endpoint on eligible writes. Actual sponsorship depends on the paymaster policy covering the target and method (the relay itself is flaky — see the BUG-029 warning in §3). The CLI floor is **0.20.0**. |
248
+ | Custom ERC-8004 registry | supported | Set `ERC8004_REGISTRY_ADDRESS`; the SDK requires the intent target and env override to match before invoking twak. Sponsorship still depends on paymaster policy coverage, so keep fallback tBNB. |
249
+ | Custom ERC-8183 targets unavailable | upstream feature request | twak v0.20.0 has no Commerce/Router/Policy address option. Studio doctor/prepare and the SDK fail closed instead of silently executing on canonical contracts; use `evm-local` for a custom ERC-8183 deployment. |
248
250
  | No generic EIP-712 signing | P0 (won't fix) | `[wallet.signing]` is ignored; payments go through the delegated payer's own prechecks + `--max-payment`. Endpoints needing an `Authorization` header *and* x402 are unavailable (e.g. `bag llm key new --initial-usd > 0` — use `--initial-usd 0` + topup + allocate instead, same end state). |
249
251
  | No wallet import | S-6 | Switching wallet kinds changes your address → re-run `bag 8004 register` (new on-chain identity). |
250
252
  | Programmatic wallet creation forces password onto argv | S-8 | Bridged by `bag wallet twak-init` (password via stdin / 0600 file / hidden prompt — never argv); the manual twak commands remain a fallback. |