@bnbagent/studio-cli 0.0.11-alpha.7 → 0.0.12-alpha.1

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
@@ -52,7 +52,7 @@ import {
52
52
  x402SellerIsFree,
53
53
  x402SellerPricingState,
54
54
  x402SellerUsesB402
55
- } from "./chunk-3QSCJAXQ.js";
55
+ } from "./chunk-AT2UYK5U.js";
56
56
  import {
57
57
  TWAK_CLI_MIN_VERSION,
58
58
  TWAK_CLI_VERSION,
@@ -230,7 +230,7 @@ import {
230
230
  var CAMPAIGN_DOC_URL = "https://www.bnbchain.org/en/blog/bnb-agent-studio-is-live-on-bnb-chain-ai-agents-from-one-prompt";
231
231
  var CAMPAIGN_CHECK_TIMEOUT_MS = 6e3;
232
232
  async function fetchCampaignActive() {
233
- const { bnbPlatformApiUrl: bnbPlatformApiUrl2 } = await import("./deployCli-374HYGDA.js");
233
+ const { bnbPlatformApiUrl: bnbPlatformApiUrl2 } = await import("./deployCli-LBDK2B4U.js");
234
234
  const controller = new AbortController();
235
235
  const timer = setTimeout(() => controller.abort(), CAMPAIGN_CHECK_TIMEOUT_MS);
236
236
  try {
@@ -2937,7 +2937,7 @@ function canBuildWallet(root, data) {
2937
2937
  const cfg = data ?? loadAgentToml(root);
2938
2938
  const walletCfg = tableOf2(cfg, "wallet");
2939
2939
  const kind = walletCfg.kind;
2940
- if (kind === "twak" || kind === "altana") {
2940
+ if (kind === "twak" || kind === "altana" || kind === "turnkey") {
2941
2941
  return false;
2942
2942
  }
2943
2943
  if (!process.env.WALLET_PASSWORD) {
@@ -2955,7 +2955,7 @@ function canBuildWallet(root, data) {
2955
2955
  function walletAddressForChecks(root, data) {
2956
2956
  const cfg = data ?? loadAgentToml(root);
2957
2957
  const walletCfg = tableOf2(cfg, "wallet");
2958
- if (walletCfg.kind === "twak" || walletCfg.kind === "altana") {
2958
+ if (walletCfg.kind === "twak" || walletCfg.kind === "altana" || walletCfg.kind === "turnkey") {
2959
2959
  const addr = String(walletCfg.address ?? "").trim();
2960
2960
  return addr || null;
2961
2961
  }
@@ -6587,6 +6587,7 @@ import {
6587
6587
  } from "@bnbagent/studio-runtime/config";
6588
6588
  import { tbnbFaucetHint as tbnbFaucetHint2 } from "@bnbagent/studio-runtime/networks";
6589
6589
  import { buildPolicy, buildWallet } from "@bnbagent/studio-runtime/wallet";
6590
+ import { getAddress as toChecksumAddress } from "viem";
6590
6591
 
6591
6592
  // src/cli/walletSession.ts
6592
6593
  import * as fs21 from "fs";
@@ -7318,6 +7319,9 @@ async function cmdNew(opts) {
7318
7319
  if (kind === "twak") {
7319
7320
  return cmdNewTwak(projectRoot, walletCfg);
7320
7321
  }
7322
+ if (kind === "turnkey") {
7323
+ return cmdNewTurnkey(projectRoot);
7324
+ }
7321
7325
  const privateKey = await resolvePrivateKeyArg(opts.privateKey);
7322
7326
  const password = requireDurablePassword(projectRoot);
7323
7327
  const keystoreDir = resolveKeystoreDir2(
@@ -7381,6 +7385,27 @@ Install it (Node >=22): npm install -g @trustwallet/cli@${TWAK_CLI_VERSION}`
7381
7385
  printNextAfterNew(projectRoot, address);
7382
7386
  return 0;
7383
7387
  }
7388
+ function cmdNewTurnkey(projectRoot) {
7389
+ const signWith = String(process.env.TURNKEY_SIGN_WITH ?? "").trim();
7390
+ if (signWith === "") {
7391
+ printErr(
7392
+ "error: TURNKEY_SIGN_WITH is not set (wallet.kind = 'turnkey').\nCreate a wallet account in the Turnkey dashboard, set the TURNKEY_* credentials in .studio/.env.local (bag auto-loads it), then re-run `bag wallet new` to adopt + anchor the address."
7393
+ );
7394
+ return 1;
7395
+ }
7396
+ let address;
7397
+ try {
7398
+ address = toChecksumAddress(signWith);
7399
+ } catch {
7400
+ printErr(
7401
+ `error: TURNKEY_SIGN_WITH must be the wallet account's Ethereum address (0x + 40 hex chars), not a Turnkey wallet id \u2014 copy it from the dashboard wallet-account view. Got: '${signWith}'`
7402
+ );
7403
+ return 1;
7404
+ }
7405
+ printOut(address);
7406
+ persistAddress(projectRoot, address);
7407
+ return 0;
7408
+ }
7384
7409
  function ensureTwakCredentials(walletFile, homeDir) {
7385
7410
  const twakDir = path21.dirname(walletFile);
7386
7411
  const target = path21.join(twakDir, "credentials.json");
@@ -7558,6 +7583,33 @@ function cmdShow5(projectRootArg2) {
7558
7583
  );
7559
7584
  return 0;
7560
7585
  }
7586
+ if (kind === "turnkey") {
7587
+ const anchored = String(walletCfg.address ?? "").trim();
7588
+ const signWith = String(process.env.TURNKEY_SIGN_WITH ?? "").trim();
7589
+ const address2 = anchored || signWith;
7590
+ if (!address2) {
7591
+ throw new Error(
7592
+ "cannot resolve the Turnkey wallet address: no [wallet].address is anchored in studio.toml and TURNKEY_SIGN_WITH is not set \u2014 fill .studio/.env.local, then run `bag wallet new`."
7593
+ );
7594
+ }
7595
+ const requiredEnv = [
7596
+ "TURNKEY_API_PUBLIC_KEY",
7597
+ "TURNKEY_API_PRIVATE_KEY",
7598
+ "TURNKEY_ORG_ID",
7599
+ "TURNKEY_SIGN_WITH"
7600
+ ];
7601
+ const present = requiredEnv.filter((key) => process.env[key]).length;
7602
+ const baseUrl = process.env.TURNKEY_API_BASE_URL || "https://api.turnkey.com";
7603
+ printOut(`address: ${address2}`);
7604
+ printOut("source: turnkey");
7605
+ printOut(
7606
+ `key_location: remote:turnkey (${baseUrl}; key held in AWS Nitro enclave, never leaves)`
7607
+ );
7608
+ printOut(
7609
+ `credentials: ${present}/${requiredEnv.length} TURNKEY_* env vars set`
7610
+ );
7611
+ return 0;
7612
+ }
7561
7613
  const password = requirePassword2();
7562
7614
  const keystoreDir = resolveKeystoreDir2(projectRoot, null, walletCfg);
7563
7615
  const provider = new EVMWalletProvider({
@@ -7650,7 +7702,14 @@ async function cmdSign(msg, projectRootArg2) {
7650
7702
  );
7651
7703
  }
7652
7704
  let provider;
7653
- if (kind === "twak") {
7705
+ if (kind === "turnkey") {
7706
+ printErr(
7707
+ "note: Turnkey signatures are billed (free tier 25/month at 1 req/s; pay-as-you-go $0.10 each)."
7708
+ );
7709
+ provider = buildWallet(walletCfg, projectRoot, {
7710
+ network: projectNetwork(projectRoot)
7711
+ });
7712
+ } else if (kind === "twak") {
7654
7713
  provider = twakProvider(projectRoot, walletCfg);
7655
7714
  } else {
7656
7715
  const password = requirePassword2();
@@ -7675,10 +7734,10 @@ async function cmdBalance(opts) {
7675
7734
  const walletCfg = data.wallet !== null && typeof data.wallet === "object" ? data.wallet : {};
7676
7735
  let address;
7677
7736
  const kind = walletKind(walletCfg);
7678
- if (kind === "twak" || kind === "altana") {
7737
+ if (kind === "twak" || kind === "altana" || kind === "turnkey") {
7679
7738
  address = String(walletCfg.address ?? "").trim();
7680
7739
  if (!address) {
7681
- const nextStep = kind === "twak" ? "(adopts the twak wallet's address) first." : "to create the Altana admin keystore first.";
7740
+ const nextStep = kind === "twak" ? "(adopts the twak wallet's address) first." : kind === "turnkey" ? "(set TURNKEY_SIGN_WITH in .studio/.env.local first \u2014 `bag wallet new` adopts it)." : "to create the Altana admin keystore first.";
7682
7741
  throw new Error(
7683
7742
  `no [wallet].address anchored in studio.toml \u2014 run bag wallet new ${nextStep}`
7684
7743
  );
@@ -8068,8 +8127,8 @@ function registerInit(program) {
8068
8127
  ).addOption(
8069
8128
  new Option2(
8070
8129
  "--wallet-kind <kind>",
8071
- "Wallet backend for the Agent (default: evm-local = encrypted local keystore at the workspace root .studio/wallets/; CodeZip deploy). altana \u2014 encrypted local admin keystore plus a bounded runtime session; deploys ship ONLY the session (ALTANA_SESSION secret). twak \u2014 FULLY SUPPORTED, opt in: Trust Wallet Agent Kit CLI, self-custody encrypted mnemonic in a PROJECT-DEDICATED home (.studio/twak); deploys as a container image."
8072
- ).choices(["twak", "evm-local", "altana"])
8130
+ "Wallet backend for the Agent (default: evm-local = encrypted local keystore at the workspace root .studio/wallets/; CodeZip deploy). altana \u2014 encrypted local admin keystore plus a bounded runtime session; deploys ship ONLY the session (ALTANA_SESSION secret). twak \u2014 FULLY SUPPORTED, opt in: Trust Wallet Agent Kit CLI, self-custody encrypted mnemonic in a PROJECT-DEDICATED home (.studio/twak); deploys as a container image. turnkey \u2014 remote signer: the key lives in Turnkey's AWS Nitro enclave and the Agent signs via TURNKEY_* API credentials; CodeZip deploy; every successful signature is billed."
8131
+ ).choices(["twak", "evm-local", "altana", "turnkey"])
8073
8132
  ).option(
8074
8133
  "--twak-home <path>",
8075
8134
  "twak only: point this project at an EXISTING twak wallet home (the HOME-STYLE PARENT dir that contains .twak/wallet.json \u2014 NOT the .twak dir itself; a .twak path holding a wallet is auto-corrected to its parent) instead of the dedicated .studio/twak. Written to [wallet].twak_home."
@@ -8382,9 +8441,9 @@ async function cmdInitFlow(nameArg, opts) {
8382
8441
  }
8383
8442
  const budgetFlag = opts.enableAutoTopup ? "enable" : opts.autoTopup === false ? "disable" : null;
8384
8443
  const budgetChoice = walletKind2 === "altana" && llmProvider !== "pieverse-llm" && budgetFlag === null ? null : await resolveBudgetChoice(budgetFlag);
8385
- if ((rails === "b402" || rails === "both") && x402SellerPricingState({ price_usd: b402Price }).kind === "paid" && !["evm-local", "twak"].includes(walletKind2)) {
8444
+ if ((rails === "b402" || rails === "both") && x402SellerPricingState({ price_usd: b402Price }).kind === "paid" && !["evm-local", "twak", "turnkey"].includes(walletKind2)) {
8386
8445
  printErr(
8387
- `error: the b402 seller rail supports wallet.kind evm-local or twak only; '${walletKind2}' cannot act as the b402 payout wallet. Re-run with --rails 8183 or a supported --wallet-kind.`
8446
+ `error: the b402 seller rail supports wallet.kind evm-local, twak or turnkey only; '${walletKind2}' cannot act as the b402 payout wallet. Re-run with --rails 8183 or a supported --wallet-kind.`
8388
8447
  );
8389
8448
  fs23.rmSync(target, { recursive: true, force: true });
8390
8449
  return 2;
@@ -8481,6 +8540,8 @@ async function cmdInitFlow(nameArg, opts) {
8481
8540
  }
8482
8541
  } else if (walletKind2 === "altana") {
8483
8542
  printAltanaNotice();
8543
+ } else if (walletKind2 === "turnkey") {
8544
+ printTurnkeyNotice();
8484
8545
  } else {
8485
8546
  printEvmLocalNotice(
8486
8547
  derivePackaging(walletKind2, destination, platformArtifact2, runtime)
@@ -8717,6 +8778,9 @@ function computeAgentNodeDeps(runtime, provider, protocols = "A2A", walletKind2
8717
8778
  if (walletKind2 === "altana") {
8718
8779
  raw.push(...readRecipeNodeDeps("wallet", "altana"));
8719
8780
  }
8781
+ if (walletKind2 === "turnkey") {
8782
+ raw.push(...readRecipeNodeDeps("wallet", "turnkey"));
8783
+ }
8720
8784
  const out = {};
8721
8785
  const core = splitDep(coreRequirement());
8722
8786
  for (const dep of raw) {
@@ -8890,6 +8954,18 @@ kind = "altana"
8890
8954
  keystore_dir = "../../.studio/wallets"
8891
8955
  session_file = "../../.studio/wallets/altana-session.json"
8892
8956
  # address = "0x..." # filled by \`bag wallet new\`; session must match it`;
8957
+ }
8958
+ if (walletKind2 === "turnkey") {
8959
+ return `[wallet]
8960
+ # Turnkey remote signer. The key is generated and held in Turnkey's AWS Nitro
8961
+ # enclave \u2014 no key material ever exists in this project or its deploys. The
8962
+ # Agent signs via the Turnkey API using the TURNKEY_* credentials in
8963
+ # .studio/.env.local. Billing: every successful signature is billed (free
8964
+ # tier: 25 signatures/month at 1 request/second; pay-as-you-go $0.10 each).
8965
+ # PRODUCTION: use a NON-ROOT API user restricted by an explicit ALLOW
8966
+ # policy \u2014 a root user's API key bypasses ALL Turnkey server-side policies.
8967
+ kind = "turnkey"
8968
+ # address = "0x..." # filled by \`bag wallet new\` (adopts TURNKEY_SIGN_WITH)`;
8893
8969
  }
8894
8970
  return `[wallet]
8895
8971
  # Local keystore signer. The Agent is the SOLE key-holder/signer.
@@ -9088,6 +9164,23 @@ function renderAgentEnvLocal(provider, storageProvider, walletKind2, rails, b402
9088
9164
  "# TWAK_ACCESS_ID=",
9089
9165
  "# TWAK_HMAC_SECRET="
9090
9166
  );
9167
+ } else if (walletKind2 === "turnkey") {
9168
+ lines.push(
9169
+ "",
9170
+ "# Turnkey API credentials (dashboard \u2192 API keys). The P-256 API key",
9171
+ "# pair authenticates this Agent to the enclave; the wallet's signing",
9172
+ "# key never leaves Turnkey. Use a NON-ROOT API user + explicit ALLOW",
9173
+ "# policy in production (root keys bypass all Turnkey policies).",
9174
+ "TURNKEY_API_PUBLIC_KEY=",
9175
+ "TURNKEY_API_PRIVATE_KEY=",
9176
+ "# Organization id (dashboard \u2192 settings):",
9177
+ "TURNKEY_ORG_ID=",
9178
+ "# Account to sign with \u2014 MUST be the 0x Ethereum ADDRESS itself, not",
9179
+ "# a Turnkey wallet id. `bag wallet new` anchors it into studio.toml.",
9180
+ "TURNKEY_SIGN_WITH=",
9181
+ "# Optional API host override (default https://api.turnkey.com):",
9182
+ "# TURNKEY_API_BASE_URL="
9183
+ );
9091
9184
  } else if (walletKind2 === "evm-local" || walletKind2 === "altana") {
9092
9185
  lines.push(
9093
9186
  "",
@@ -9202,7 +9295,7 @@ entrypoint code in \`src/signing.ts\` \u2014 never an LLM-callable tool.
9202
9295
  ${mode === "both" ? filesBoth : mode === "mcp" ? filesMcp : filesA2a}
9203
9296
  ${shared}
9204
9297
  - \`.env.local\` \u2014 Agent secrets; on deploy they are sent to the **operator's**
9205
- Secrets Manager (the scoped, consented commitment-#2 exception). ${walletKind2 === "altana" ? "Only the\n bounded session ships (as `ALTANA_SESSION`); tighten it with\n `bag wallet session grant --force` \u2014 do not create a new wallet." : "Use a\n THROWAWAY testnet wallet \u2014 `(cd app/agent && bag wallet new)`."}
9298
+ Secrets Manager (the scoped, consented commitment-#2 exception). ${walletKind2 === "altana" ? "Only the\n bounded session ships (as `ALTANA_SESSION`); tighten it with\n `bag wallet session grant --force` \u2014 do not create a new wallet." : walletKind2 === "turnkey" ? "No key\n material exists locally \u2014 only the TURNKEY_* API credentials ship; the\n signing key stays in Turnkey's enclave. Fund the TURNKEY_SIGN_WITH\n address with a little tBNB; every successful signature is billed." : "Use a\n THROWAWAY testnet wallet \u2014 `(cd app/agent && bag wallet new)`."}
9206
9299
 
9207
9300
  ## Run locally
9208
9301
 
@@ -9311,12 +9404,12 @@ function ensureCursorGitignore(gitignore) {
9311
9404
  ${addition}`);
9312
9405
  }
9313
9406
  async function resolveWalletKind(flagValue, isTty) {
9314
- if (flagValue === "evm-local" || flagValue === "twak" || flagValue === "altana") {
9407
+ if (flagValue === "evm-local" || flagValue === "twak" || flagValue === "altana" || flagValue === "turnkey") {
9315
9408
  return flagValue;
9316
9409
  }
9317
9410
  if (!isTty) {
9318
9411
  printErr(
9319
- "info: wallet backend defaulting to 'evm-local' (non-interactive). To choose explicitly, re-run with --wallet-kind evm-local or --wallet-kind twak or --wallet-kind altana."
9412
+ "info: wallet backend defaulting to 'evm-local' (non-interactive). To choose explicitly, re-run with --wallet-kind evm-local or --wallet-kind twak, --wallet-kind altana or --wallet-kind turnkey."
9320
9413
  );
9321
9414
  return "evm-local";
9322
9415
  }
@@ -9330,9 +9423,13 @@ async function resolveWalletKind(flagValue, isTty) {
9330
9423
  printOut(
9331
9424
  " 3. altana \u2014 bounded session signer; incompatible with pieverse-llm and paid b402 selling"
9332
9425
  );
9333
- const answer = (await promptUser("Wallet backend [1-3, Enter = evm-local]: ")).trim().toLowerCase();
9426
+ printOut(
9427
+ " 4. turnkey \u2014 remote signer; key held in Turnkey's AWS Nitro enclave; billable signatures"
9428
+ );
9429
+ const answer = (await promptUser("Wallet backend [1-4, Enter = evm-local]: ")).trim().toLowerCase();
9334
9430
  if (answer === "2" || answer === "twak") return "twak";
9335
9431
  if (answer === "3" || answer === "altana") return "altana";
9432
+ if (answer === "4" || answer === "turnkey") return "turnkey";
9336
9433
  if (answer !== "" && answer !== "1" && answer !== "evm-local") {
9337
9434
  printErr(`hint: unknown wallet backend '${answer}' \u2014 using evm-local.`);
9338
9435
  }
@@ -9614,6 +9711,12 @@ async function runOnboarding(o) {
9614
9711
  if (o.walletKind === "twak") {
9615
9712
  return runOnboardingTwak(o);
9616
9713
  }
9714
+ if (o.walletKind === "turnkey") {
9715
+ printOut(
9716
+ "turnkey onboarding: no local key is created. Fill the TURNKEY_* credentials in .studio/.env.local (Turnkey dashboard \u2192 API keys / settings / wallet account), then run `(cd app/agent && bag wallet new)` to adopt + anchor the signing address."
9717
+ );
9718
+ return false;
9719
+ }
9617
9720
  const password = await promptPassword();
9618
9721
  if (password === null) {
9619
9722
  return false;
@@ -9983,7 +10086,33 @@ NOTE \u2014 wallet.kind = "altana" (bounded runtime session):
9983
10086
  \`bag wallet session grant --force\` and redeploy.`
9984
10087
  );
9985
10088
  }
10089
+ function printTurnkeyNotice() {
10090
+ printOut(
10091
+ `
10092
+ NOTE \u2014 wallet.kind = "turnkey" (remote signer, keys in Turnkey's AWS Nitro enclave):
10093
+ \xB7 WALLET: no key material exists locally \u2014 the Agent signs via the TURNKEY_*
10094
+ API credentials in .studio/.env.local (dashboard \u2192 API keys). Set them,
10095
+ then run \`(cd app/agent && bag wallet new)\` to adopt + anchor the address.
10096
+ \xB7 BILLING: every successful signature is billed (free tier: 25/month at
10097
+ 1 request/second; pay-as-you-go $0.10 each) \u2014 budget accordingly.
10098
+ \xB7 PRODUCTION: use a NON-ROOT API user restricted by an explicit ALLOW policy;
10099
+ a root user's API key bypasses ALL Turnkey server-side policies.`
10100
+ );
10101
+ }
9986
10102
  function printPlatformWalletSuggestion(walletKind2) {
10103
+ if (walletKind2 === "turnkey") {
10104
+ printOut(
10105
+ `
10106
+ NOTE \u2014 destination = "platform" (48h TESTNET TRIAL on the BNB Chain managed platform):
10107
+ \xB7 This is a sandbox, not a production seller \u2014 the runtime is auto-reclaimed at 48h.
10108
+ \xB7 To sign, ONLY the TURNKEY_* API credentials are transmitted to the operator's
10109
+ Secrets Manager on deploy \u2014 the signing key never leaves Turnkey's enclave.
10110
+ \xB7 Want a smaller blast radius? Use a NON-ROOT API user whose ALLOW policy
10111
+ covers only this Agent's operations, and revoke it from the Turnkey
10112
+ dashboard when the trial ends.`
10113
+ );
10114
+ return;
10115
+ }
9987
10116
  if (walletKind2 === "altana") {
9988
10117
  printOut(
9989
10118
  `
@@ -10214,6 +10343,17 @@ function printNextSteps(name, provider, o = {}) {
10214
10343
  printOut(
10215
10344
  " (cd app/agent && bag wallet session status) # local, secret-free summary"
10216
10345
  );
10346
+ } else if (walletKind2 === "turnkey") {
10347
+ printOut(
10348
+ " # 1. edit .studio/.env.local and set the TURNKEY_* credentials (Turnkey dashboard \u2192 API keys / settings / wallet account)"
10349
+ );
10350
+ printOut(
10351
+ " # use a NON-ROOT API user + explicit ALLOW policy; TURNKEY_SIGN_WITH is the 0x ADDRESS, not a wallet id"
10352
+ );
10353
+ printOut(
10354
+ " # 2. adopt + anchor the Turnkey address (no local key is created):"
10355
+ );
10356
+ printOut(" (cd app/agent && bag wallet new)");
10217
10357
  } else {
10218
10358
  printOut(
10219
10359
  " # 1. edit .studio/.env.local and set WALLET_PASSWORD (encrypts the keystore + signs all on-chain ops)"
@@ -10437,6 +10577,10 @@ function printConfigSummary(agentRoot2) {
10437
10577
  ".studio/wallets/altana-session.json"
10438
10578
  )}`
10439
10579
  );
10580
+ } else if (walletKind2 === "turnkey") {
10581
+ printOut(
10582
+ " wallet : Turnkey remote signer (key in AWS Nitro enclave; TURNKEY_* credentials in .studio/.env.local) \u2014 the sole signer; receives your $U"
10583
+ );
10440
10584
  } else {
10441
10585
  printOut(
10442
10586
  ` wallet : local keystore (${shown(
@@ -10557,6 +10701,16 @@ function runtimeEnvKeysCore(cfg, resolvable) {
10557
10701
  }
10558
10702
  } else if (walletKind2 === "altana") {
10559
10703
  keys = [];
10704
+ } else if (walletKind2 === "turnkey") {
10705
+ keys = [
10706
+ "TURNKEY_API_PUBLIC_KEY",
10707
+ "TURNKEY_API_PRIVATE_KEY",
10708
+ "TURNKEY_ORG_ID",
10709
+ "TURNKEY_SIGN_WITH"
10710
+ ];
10711
+ if (resolvable("TURNKEY_API_BASE_URL")) {
10712
+ keys.push("TURNKEY_API_BASE_URL");
10713
+ }
10560
10714
  } else {
10561
10715
  keys = ["WALLET_PASSWORD"];
10562
10716
  }
@@ -11081,10 +11235,17 @@ async function cmdDev(opts) {
11081
11235
  );
11082
11236
  return 2;
11083
11237
  }
11084
- const pwEnv = walletKind2 === "twak" ? "TWAK_WALLET_PASSWORD" : "WALLET_PASSWORD";
11085
- if (walletKind2 !== "altana" && !process.env[pwEnv]) {
11238
+ const requiredWalletEnv = walletKind2 === "twak" ? ["TWAK_WALLET_PASSWORD"] : walletKind2 === "altana" ? [] : walletKind2 === "turnkey" ? [
11239
+ "TURNKEY_API_PUBLIC_KEY",
11240
+ "TURNKEY_API_PRIVATE_KEY",
11241
+ "TURNKEY_ORG_ID",
11242
+ "TURNKEY_SIGN_WITH"
11243
+ ] : ["WALLET_PASSWORD"];
11244
+ const missingWalletEnv = requiredWalletEnv.filter((key) => !process.env[key]);
11245
+ if (missingWalletEnv.length > 0) {
11246
+ const plural = missingWalletEnv.length > 1 ? "vars are" : "var is";
11086
11247
  printErr(
11087
- `error: ${pwEnv} env var is required to run \`bag dev\` (the Agent is the sole signer). Set it in .studio/.env.local or your shell.`
11248
+ `error: ${missingWalletEnv.join(", ")} env ${plural} required to run \`bag dev\` with wallet.kind='${walletKind2}' (the Agent is the sole signer). Set ${missingWalletEnv.length > 1 ? "them" : "it"} in .studio/.env.local or your shell.`
11088
11249
  );
11089
11250
  return 2;
11090
11251
  }
@@ -12353,7 +12514,7 @@ function collectRuntimeSecretsDetailed(root) {
12353
12514
  sources[KEYSTORE_JSON_ENV] = "wallet-file";
12354
12515
  }
12355
12516
  }
12356
- if (walletKind2 !== "altana" && !(KEYSTORE_JSON_ENV in payload) && !(TWAK_WALLET_JSON_ENV in payload)) {
12517
+ if (walletKind2 !== "altana" && walletKind2 !== "turnkey" && !(KEYSTORE_JSON_ENV in payload) && !(TWAK_WALLET_JSON_ENV in payload)) {
12357
12518
  printErr(
12358
12519
  `WARNING: no wallet signing material found (${KEYSTORE_JSON_ENV} for evm-local / ${TWAK_WALLET_JSON_ENV} for twak). The deployed runtime won't be able to sign quotes or deliveries. Create/adopt a wallet first (e.g. \`bag wallet new\`) and redeploy.`
12359
12520
  );
@@ -17438,7 +17599,8 @@ var SECRET_ENV_KEYS = [
17438
17599
  "PIEVERSE_LLM_API_KEY",
17439
17600
  "STORAGE_API_KEY",
17440
17601
  "TWAK_ACCESS_ID",
17441
- "TWAK_HMAC_SECRET"
17602
+ "TWAK_HMAC_SECRET",
17603
+ "TURNKEY_API_PRIVATE_KEY"
17442
17604
  ];
17443
17605
  function tableOf14(cfg, key) {
17444
17606
  const v = cfg[key];
@@ -17685,6 +17847,9 @@ async function checkWallet(projectRoot, data) {
17685
17847
  if (walletCfg.kind === "altana") {
17686
17848
  return checkWalletAltana(walletCfg, projectRoot, data);
17687
17849
  }
17850
+ if (walletCfg.kind === "turnkey") {
17851
+ return checkWalletTurnkey(walletCfg);
17852
+ }
17688
17853
  if (!isLocalEvmWallet(walletCfg)) {
17689
17854
  return [
17690
17855
  {
@@ -17738,6 +17903,58 @@ async function checkWallet(projectRoot, data) {
17738
17903
  }
17739
17904
  return out;
17740
17905
  }
17906
+ var TURNKEY_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
17907
+ var TURNKEY_REQUIRED_ENV = [
17908
+ "TURNKEY_API_PUBLIC_KEY",
17909
+ "TURNKEY_API_PRIVATE_KEY",
17910
+ "TURNKEY_ORG_ID",
17911
+ "TURNKEY_SIGN_WITH"
17912
+ ];
17913
+ function checkWalletTurnkey(walletCfg) {
17914
+ const out = [];
17915
+ const missing = TURNKEY_REQUIRED_ENV.filter((key) => !process.env[key]);
17916
+ out.push(
17917
+ missing.length === 0 ? {
17918
+ name: "[wallet] turnkey credentials",
17919
+ status: PASS,
17920
+ detail: "API key pair + org id + sign-with address set; reminder: production should use a non-root API user with an explicit ALLOW policy (root keys bypass all Turnkey policies)"
17921
+ } : {
17922
+ name: "[wallet] turnkey credentials",
17923
+ status: FAIL,
17924
+ detail: `missing: ${missing.join(", ")} \u2014 set them in .studio/.env.local (Turnkey dashboard \u2192 API keys / settings / wallet account)`
17925
+ }
17926
+ );
17927
+ const signWith = String(process.env.TURNKEY_SIGN_WITH ?? "").trim();
17928
+ if (signWith !== "" && !TURNKEY_ADDRESS_RE.test(signWith)) {
17929
+ out.push({
17930
+ name: "TURNKEY_SIGN_WITH format",
17931
+ status: FAIL,
17932
+ detail: "must be the wallet account's Ethereum address (0x + 40 hex chars), not a Turnkey wallet id \u2014 copy it from the dashboard wallet-account view"
17933
+ });
17934
+ }
17935
+ const anchored = String(walletCfg.address ?? "").trim();
17936
+ if (anchored === "") {
17937
+ out.push({
17938
+ name: "[wallet] address anchor",
17939
+ status: WARN,
17940
+ detail: "no [wallet].address \u2014 run `bag wallet new` to adopt TURNKEY_SIGN_WITH"
17941
+ });
17942
+ } else if (signWith !== "" && TURNKEY_ADDRESS_RE.test(signWith)) {
17943
+ const matches = anchored.toLowerCase() === signWith.toLowerCase();
17944
+ out.push(
17945
+ matches ? {
17946
+ name: "[wallet] address anchor",
17947
+ status: PASS,
17948
+ detail: `${anchored} matches TURNKEY_SIGN_WITH`
17949
+ } : {
17950
+ name: "[wallet] address anchor",
17951
+ status: FAIL,
17952
+ detail: `[wallet].address ${anchored} does not match TURNKEY_SIGN_WITH ${signWith} \u2014 stale anchor or different Turnkey credentials; fix .studio/.env.local, then re-run \`bag wallet new\``
17953
+ }
17954
+ );
17955
+ }
17956
+ return out;
17957
+ }
17741
17958
  function checkWalletAltana(walletCfg, projectRoot, data) {
17742
17959
  const out = [];
17743
17960
  const contracts = erc8183ContractOverrideState(
@@ -17930,6 +18147,20 @@ function checkPassword(data) {
17930
18147
  }
17931
18148
  ];
17932
18149
  }
18150
+ if (walletKind2 === "turnkey") {
18151
+ const missing = TURNKEY_REQUIRED_ENV.filter((key) => !process.env[key]);
18152
+ return [
18153
+ missing.length === 0 ? {
18154
+ name: "TURNKEY_* env",
18155
+ status: PASS,
18156
+ detail: "all Turnkey credentials set (WALLET_PASSWORD is not used for this kind)"
18157
+ } : {
18158
+ name: "TURNKEY_* env",
18159
+ status: WARN,
18160
+ detail: `missing: ${missing.join(", ")} \u2014 signing will fail (set in .studio/.env.local)`
18161
+ }
18162
+ ];
18163
+ }
17933
18164
  if (process.env.WALLET_PASSWORD) {
17934
18165
  return [{ name: "WALLET_PASSWORD env", status: PASS, detail: "set" }];
17935
18166
  }
@@ -18298,11 +18529,11 @@ function checkX402Seller(data, agentRoot2) {
18298
18529
  }
18299
18530
  ];
18300
18531
  const walletKind2 = String(tableOf14(data, "wallet").kind ?? "evm-local");
18301
- if (!["evm-local", "twak"].includes(walletKind2)) {
18532
+ if (!["evm-local", "twak", "turnkey"].includes(walletKind2)) {
18302
18533
  out.push({
18303
18534
  name: "x402 payout wallet",
18304
18535
  status: FAIL,
18305
- detail: `PAID B402 supports evm-local or twak, not ${walletKind2}.`
18536
+ detail: `PAID B402 supports evm-local, twak or turnkey, not ${walletKind2}.`
18306
18537
  });
18307
18538
  }
18308
18539
  const credentials = b402Credentials(agentRoot2);
@@ -18376,7 +18607,7 @@ function checkStorage(data) {
18376
18607
  }
18377
18608
  function balanceAddress(projectRoot, data) {
18378
18609
  const walletCfg = tableOf14(data, "wallet");
18379
- if (walletCfg.kind === "twak" || walletCfg.kind === "altana") {
18610
+ if (walletCfg.kind === "twak" || walletCfg.kind === "altana" || walletCfg.kind === "turnkey") {
18380
18611
  const addr = String(walletCfg.address ?? "").trim();
18381
18612
  return addr || null;
18382
18613
  }
@@ -686,8 +686,8 @@ function packageRoot() {
686
686
  }
687
687
  }
688
688
  function studioCliVersion() {
689
- if ("0.0.11-alpha.7") {
690
- return "0.0.11-alpha.7";
689
+ if ("0.0.12-alpha.1") {
690
+ return "0.0.12-alpha.1";
691
691
  }
692
692
  const file = path3.join(packageRoot(), "package.json");
693
693
  const pkg = JSON.parse(fs3.readFileSync(file, "utf-8"));
@@ -18,7 +18,7 @@ import {
18
18
  runPlatformAccountCommand,
19
19
  trialFromDeployCliJson,
20
20
  withDeployFiles
21
- } from "./chunk-3QSCJAXQ.js";
21
+ } from "./chunk-AT2UYK5U.js";
22
22
  import "./chunk-RO726HJG.js";
23
23
  export {
24
24
  BNB_PLATFORM_API_URL,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bnbagent/studio-cli",
3
- "version": "0.0.11-alpha.7",
3
+ "version": "0.0.12-alpha.1",
4
4
  "description": "Skills-first toolkit and bag CLI for BNB Chain seller agents: ERC-8004 identity, ERC-8183 escrowed commerce, and x402 payments.",
5
5
  "keywords": [
6
6
  "bnb-chain",
@@ -42,7 +42,7 @@
42
42
  "bag": "./dist/bag.js"
43
43
  },
44
44
  "dependencies": {
45
- "@bnbagent/sdk": "0.5.0",
45
+ "@bnbagent/sdk": "0.5.1-alpha.2",
46
46
  "ai": "^7.0.29",
47
47
  "archiver": "^8.0.0",
48
48
  "commander": "^15.0.0",
@@ -54,7 +54,7 @@
54
54
  "tar": "^7.4.0",
55
55
  "viem": "^2.54.0",
56
56
  "yaml": "^2.9.0",
57
- "@bnbagent/studio-runtime": "0.0.11-alpha.7"
57
+ "@bnbagent/studio-runtime": "0.0.12-alpha.1"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@a2a-js/sdk": "^0.3.14",
@@ -11,7 +11,7 @@ node = [
11
11
  # neutral. The agent project's serving deps (@a2a-js/sdk / MCP SDK / ai)
12
12
  # come from the runtimes/<R>/ recipe selected at `bag init` time.
13
13
  "@bnbagent/studio-runtime",
14
- "@bnbagent/sdk@0.5.0",
14
+ "@bnbagent/sdk@0.5.1-alpha.2",
15
15
  ]
16
16
 
17
17
  [env]
@@ -32,7 +32,7 @@ node = [
32
32
  # BNBAGENT_RUNTIME_SECRET_ID is set (default secretsmanager mode + platform).
33
33
  "@aws-sdk/client-secrets-manager@^3.600.0",
34
34
  "@bnbagent/studio-runtime",
35
- "@bnbagent/sdk@0.5.0",
35
+ "@bnbagent/sdk@0.5.1-alpha.2",
36
36
  # The LLM work hook (generateText + tools) and the model factory.
37
37
  "ai@^7.0.29",
38
38
  # Tool input schemas (AI SDK tools + MCP registerTool).
@@ -29,7 +29,7 @@ node = [
29
29
  # on either cloud.
30
30
  "@aws-sdk/client-secrets-manager@^3.600.0",
31
31
  "@bnbagent/studio-runtime",
32
- "@bnbagent/sdk@0.5.0",
32
+ "@bnbagent/sdk@0.5.1-alpha.2",
33
33
  # The LLM work hook (generateText + tools) and the model factory
34
34
  # (model.ts buildModel — studio.toml [llm] + the provider key env).
35
35
  "ai@^7.0.29",
@@ -1,6 +1,6 @@
1
1
  [recipe]
2
2
  name = "wallet"
3
- description = "Configures the agent's wallet: 'evm-local' (default encrypted keystore), 'twak' (Trust Wallet Agent Kit CLI), or 'altana' (encrypted admin keystore plus one bounded runtime session)."
3
+ description = "Configures the agent's wallet: 'evm-local' (default encrypted keystore), 'twak' (Trust Wallet Agent Kit CLI), 'altana' (encrypted admin keystore plus one bounded runtime session), or 'turnkey' (remote signer; the key lives in Turnkey's AWS Nitro enclave)."
4
4
 
5
5
  [dependencies]
6
6
  node = ["@bnbagent/studio-runtime"]
@@ -8,10 +8,15 @@ node = ["@bnbagent/studio-runtime"]
8
8
  [dependencies.altana]
9
9
  node = ["@altananetwork/sdk@0.5.1"]
10
10
 
11
+ [dependencies.turnkey]
12
+ node = ["@turnkey/sdk-server@8.1.0", "@turnkey/viem@0.14.34"]
13
+
11
14
  [env]
12
15
  # kind='twak': the twak CLI reads the unlock password from the environment —
13
16
  # it never goes on a command line.
14
17
  TWAK_WALLET_PASSWORD = "unlocks ~/.twak/wallet.json (twak kind); set in .studio/.env.local"
18
+ # kind='turnkey': P-256 API credential; the signing key never leaves Turnkey's enclave.
19
+ TURNKEY_API_PRIVATE_KEY = "Turnkey P-256 API private key (turnkey kind); set in .studio/.env.local"
15
20
  # kind='evm-local' (the `bag init` default) or kind='altana' admin operations:
16
21
  WALLET_PASSWORD = "set to a strong password (>= 12 chars); encrypts .studio/wallets/<addr>.json (runtime never receives it for altana)"
17
22
 
@@ -6,7 +6,7 @@ status = "v0.0.x"
6
6
  [dependencies]
7
7
  node = [
8
8
  "@bnbagent/studio-runtime",
9
- "@bnbagent/sdk@0.5.0",
9
+ "@bnbagent/sdk@0.5.1-alpha.2",
10
10
  "ai@^7.0.29", # AI SDK `tool` wrappers around the buyer functions
11
11
  "zod@^3.25.0", # tool input schemas
12
12
  ]