@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 +254 -23
- package/dist/{chunk-3QSCJAXQ.js → chunk-AT2UYK5U.js} +2 -2
- package/dist/{deployCli-374HYGDA.js → deployCli-LBDK2B4U.js} +1 -1
- package/package.json +3 -3
- package/recipes/agent/recipe.toml +1 -1
- package/recipes/runtimes/agentcore/recipe.toml +1 -1
- package/recipes/runtimes/azure-foundry/recipe.toml +1 -1
- package/recipes/wallet/recipe.toml +6 -1
- package/recipes/x402-buyer/recipe.toml +1 -1
package/dist/bag.js
CHANGED
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
x402SellerIsFree,
|
|
53
53
|
x402SellerPricingState,
|
|
54
54
|
x402SellerUsesB402
|
|
55
|
-
} from "./chunk-
|
|
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-
|
|
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 === "
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
11085
|
-
|
|
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: ${
|
|
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
|
|
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.
|
|
690
|
-
return "0.0.
|
|
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"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bnbagent/studio-cli",
|
|
3
|
-
"version": "0.0.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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),
|
|
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
|
|