@ostraagent/mcp 0.1.5 → 0.1.6

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/README.md CHANGED
@@ -45,12 +45,11 @@ Keep the file and the passphrase off the machine. On a new machine, run `npx -y
45
45
  - **Payments:** `pay` (calls a paid HTTP service and pays it from a channel of the vault, refused before signing above `maxPrice`), `open_channel`, `list_channels`, `close_channel`
46
46
  - **Ostra AI:** `ostra_ai` (a text, image or video task; Ostra chooses the model by its quality rating within `maxPrice`, paid in USDG from a channel of the vault, and what a task did not cost stays as the vault's credit), `ostra_ai_job` (a video job, saved as a file once completed), `ostra_ai_credit`
47
47
  - **Selling services:** `accept_payments`, `seller_balance`, `seller_withdraw`
48
- - **Privacy:** `shield`, `shield_status`, `auto_shield`, `unshield`, `private_withdraw`
49
48
  - **Recovery:** `recovery_status`, `set_guardian`, `set_recovery_policy`, `cancel_recovery`, `support_recovery`, `request_recovery`
50
49
  - **Trading:** `tokens`, `token_info`, `pool_info`, `quote_swap`, `buy`, `sell`, `token_vault`, `transfer_token`, `claim_dividends`, `claim_referral`
51
50
  - **Launching:** `pairs`, `launch` (paired with USDG, ETH, $OSTRA or a listed Robinhood stock token, at a starting market cap in US dollars, with an optional dev buy and vesting), `launches`, `set_pool_setting`, `set_creator_share`, `release_creator_payout`, `airdrop_fund`, `airdrop_start`, `airdrop_claim`, `airdrop_close`, `vesting_info`, `vesting_release`, `vesting_claim_dividends`
52
51
  - **Ostra Swap:** `ostra_swap_quote`, `ostra_swap` (between the agent's vaults of USDG, WETH, $OSTRA, listed stock tokens and launched tokens, routed through Uniswap v3 and v4; tokens by address or symbol)
53
- - **Private balances:** `private_shield` (from a vault into a private note), `private_balance`, `private_swap`, `private_unshield`, `private_recover` (finds the notes again from the seed alone) (spent with a zero-knowledge proof made on the agent's machine by the WebAssembly prover the package carries)
52
+ - **Privacy:** `private_shield` (from a vault into a private note), `private_balance`, `private_swap`, `private_unshield` (to an address, or with `toPrivateVault` to a new private vault of the agent), `private_withdraw` (from a private vault), `private_recover` (finds the notes and private vaults again from the seed alone), `auto_shield` (everything the creator vault holds beyond what it keeps, into private notes). Notes are spent with a zero-knowledge proof made on the agent's machine by the WebAssembly prover the package carries. A spend does not show which note it spends; the token, the amounts, the times, the vault a shield comes from and an unshield's recipient are public, so a private vault's first balance matches the unshield that paid it
54
53
  - **Identity:** `profile`, `upload_image`, `publish_profile`, `set_profile`, `publish_token_metadata`, `set_token_metadata`, `register_agent`, `set_agent_uri`
55
54
 
56
55
  Every spend is simulated before its one-time key signs it. When a tool refuses, the error says whether it refused before anything was signed.
@@ -68,7 +67,7 @@ All settings are optional.
68
67
  | `OSTRA_RPC_URL_FILE` | | A private file (mode 0600) holding the RPC URL, when it carries an API key |
69
68
  | `OSTRA_RELAYER_URL` | `https://relayer.ostraagent.xyz` | Sends every transaction, paid in USDG. With `off`, a local fee payer key in `OSTRA_HOME` pays gas in ETH |
70
69
  | `OSTRA_KEEPER_URL` | `https://keeper.ostraagent.xyz` | Claims payments for the seller side. `off` disables `accept_payments` |
71
- | `OSTRA_AGGREGATOR_URL` | `https://aggregator.ostraagent.xyz` | Batches shield deposits. `off` disables `shield` |
70
+ | `OSTRA_AGGREGATOR_URL` | `https://aggregator.ostraagent.xyz` | The shielded pool's batches, for shield deposits kept from before 0.1.6 only. `off` stops them |
72
71
  | `OSTRA_AI_URL` | `https://ai.ostraagent.xyz` | Ostra AI, the gateway of the `ostra_ai` tools. `off` disables them |
73
72
  | `OSTRA_SWAP_DEPLOYMENT` | Ostra Swap on Robinhood Chain mainnet | Path of another Ostra Swap deployment record (JSON). With `OSTRA_DEPLOYMENT` set and no Ostra Swap record, the Ostra Swap and private tools refuse |
74
73
  | `OSTRA_PROVER` | The WebAssembly prover in the package | Path of a native `ostra-prove` binary to prove with instead, or `off` (the private tools then refuse to spend) |
package/dist/ostra-mcp.js CHANGED
@@ -18,7 +18,7 @@ var __export = (target, all) => {
18
18
  var define_OSTRA_PACKAGED_default;
19
19
  var init_define_OSTRA_PACKAGED = __esm({
20
20
  "<define:OSTRA_PACKAGED>"() {
21
- define_OSTRA_PACKAGED_default = { version: "0.1.5", deployment: { version: 1, chainId: 4663, fromBlock: "91991509", token: "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168", batchSettlement: "0x4020074e9dF2ce1deE5A9C1b5c3f541D02a10003", vaultImplementation: "0xD20c6744Cfa46d9ec564b119cC923bAb2f9f9331", vaultBeacon: "0xE9F525B9EaeE48d13AEd28fE146F48f2832a18a0", vaultFactory: "0x043a15c514CFE2c797d33520bFF91a5DCdbf2979", poolImplementation: "0xC04d4a222dC1C966640B977608bE29949b0648ec", poolBeacon: "0x30669Ca534921038a531BBE27116d85bB7406fFF", pool: "0x6ea80F2462e5Fd783B0Ec3B2c7BaC96795980CD6", splitFactory: "0xAAf54A483B2c7c2c5ecFc1eDED5ed1619C1bA882", beaconOwner: "0x55533a51d9bd9b06F2D179d919BB31995EF96E5e", launchpad: { hook: "0x63A784afb8905452aDceC471a45E555F5caE68cC", fees: "0x8e79671DC9729476614C78aFcf2345D4c1963801", launchpad: "0xfC57162C0e3f9773F97fEa8d276e957d7c43A6C5", creatorSplit: "0x987994dE6A1d5303B9e3F50977cB0321BA4D033B", dispatcher: "0x44Bb65164b2855f8E80B142d5Fb9bDb72E869986", locker: "0xb593812363bAd70Ee381c7B54c4FC82b0BA3A4A7", devBuy: "0x0e1Fd799E136dcfEcad5695B07Ac5DAc5B2fF4bc", vesting: "0xadD9C1995910457312c156E7D8120D1b447E179a", airdrop: "0xBC798D956aF4c2B104FFC6106d5322294Bc103bb", owner: "0x6569E27A95a6A3E92A311B4Cc6600fed56aA50A2" }, registry: "0xcE6Cc1f93744f4090DA1444Bb8590d9206157506" }, swapDeployment: { chainId: 4663, fromBlock: "94940585", router: "0x5b9686f384A5Fb768B2360cF0B893F0d1E960B01", stake: "0xaDA4FaB9c8aB9165bf4d6cD9d4Da6d6b32Bb4BC6", buyback: "0xd86083031b7Af66ecD5C9c5e90D4C6d8E661F3f5", spendVerifier: "0x0a0E28a81DE6289e5044950163FfE95b37180e3B", privatePool: "0xe4250895Da2499602Cee4e178Fb46A0cF39F5b76" }, prover: { wasm: "0xd5f3069334ab49a2f955e4b4be9493b80c3f7b2a85f4ff2073a327ef5fbc2747", glue: "0x4bc8bef7c755c83daaf833623f85473a20a7c40b390d8d639d9e4353618a0537" }, relayerUrl: "https://relayer.ostraagent.xyz", keeperUrl: "https://keeper.ostraagent.xyz", aggregatorUrl: "https://aggregator.ostraagent.xyz", aiUrl: "https://ai.ostraagent.xyz" };
21
+ define_OSTRA_PACKAGED_default = { version: "0.1.6", deployment: { version: 1, chainId: 4663, fromBlock: "91991509", token: "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168", batchSettlement: "0x4020074e9dF2ce1deE5A9C1b5c3f541D02a10003", vaultImplementation: "0xD20c6744Cfa46d9ec564b119cC923bAb2f9f9331", vaultBeacon: "0xE9F525B9EaeE48d13AEd28fE146F48f2832a18a0", vaultFactory: "0x043a15c514CFE2c797d33520bFF91a5DCdbf2979", poolImplementation: "0xC04d4a222dC1C966640B977608bE29949b0648ec", poolBeacon: "0x30669Ca534921038a531BBE27116d85bB7406fFF", pool: "0x6ea80F2462e5Fd783B0Ec3B2c7BaC96795980CD6", splitFactory: "0xAAf54A483B2c7c2c5ecFc1eDED5ed1619C1bA882", beaconOwner: "0x55533a51d9bd9b06F2D179d919BB31995EF96E5e", launchpad: { hook: "0x63A784afb8905452aDceC471a45E555F5caE68cC", fees: "0x8e79671DC9729476614C78aFcf2345D4c1963801", launchpad: "0xfC57162C0e3f9773F97fEa8d276e957d7c43A6C5", creatorSplit: "0x987994dE6A1d5303B9e3F50977cB0321BA4D033B", dispatcher: "0x44Bb65164b2855f8E80B142d5Fb9bDb72E869986", locker: "0xb593812363bAd70Ee381c7B54c4FC82b0BA3A4A7", devBuy: "0x0e1Fd799E136dcfEcad5695B07Ac5DAc5B2fF4bc", vesting: "0xadD9C1995910457312c156E7D8120D1b447E179a", airdrop: "0xBC798D956aF4c2B104FFC6106d5322294Bc103bb", owner: "0x6569E27A95a6A3E92A311B4Cc6600fed56aA50A2" }, registry: "0xcE6Cc1f93744f4090DA1444Bb8590d9206157506" }, swapDeployment: { chainId: 4663, fromBlock: "94940585", router: "0x5b9686f384A5Fb768B2360cF0B893F0d1E960B01", stake: "0xaDA4FaB9c8aB9165bf4d6cD9d4Da6d6b32Bb4BC6", buyback: "0xd86083031b7Af66ecD5C9c5e90D4C6d8E661F3f5", spendVerifier: "0x0a0E28a81DE6289e5044950163FfE95b37180e3B", privatePool: "0xe4250895Da2499602Cee4e178Fb46A0cF39F5b76" }, prover: { wasm: "0xd5f3069334ab49a2f955e4b4be9493b80c3f7b2a85f4ff2073a327ef5fbc2747", glue: "0x4bc8bef7c755c83daaf833623f85473a20a7c40b390d8d639d9e4353618a0537" }, relayerUrl: "https://relayer.ostraagent.xyz", keeperUrl: "https://keeper.ostraagent.xyz", aggregatorUrl: "https://aggregator.ostraagent.xyz", aiUrl: "https://ai.ostraagent.xyz" };
22
22
  }
23
23
  });
24
24
 
@@ -15997,7 +15997,7 @@ var init_vault = __esm({
15997
15997
  return this.token === this.#config.deployment.token;
15998
15998
  }
15999
15999
  #missing() {
16000
- if (this.kind === "private") return `private vault ${this.privateIndex} does not exist yet; unshield creates it`;
16000
+ if (this.kind === "private") return `private vault ${this.privateIndex} does not exist yet; it is created on its first use`;
16001
16001
  if (this.kind === "creator") return `this agent has no creator vault${this.#inUsdg() ? "" : ` in ${this.token}`} yet; open it before a launch names it`;
16002
16002
  if (this.kind === "token") return `this agent has no vault in ${this.token} yet; buying the token creates it`;
16003
16003
  return this.kind === "buyer" ? "this agent has no vault yet; call open_vault first" : "this agent has no seller vault yet; call accept_payments first";
@@ -16124,7 +16124,6 @@ var init_vault = __esm({
16124
16124
  }
16125
16125
  open() {
16126
16126
  return this.#serial(async () => {
16127
- if (this.kind === "private") throw new Error("a private vault is created by unshield, together with the leaf spent into it");
16128
16127
  if (this.kind !== "token" && this.#inUsdg()) await this.#checkToken();
16129
16128
  const vault = await this.vaultAddress();
16130
16129
  const token = this.token;
@@ -16138,7 +16137,7 @@ var init_vault = __esm({
16138
16137
  await this.requireEth(CREATE_VAULT_GAS, "open the vault");
16139
16138
  const txHash = await this.#sender.send([await this.createCall()], this.#options({ prepay: this.kind === "seller" || !this.#paysRelayer() }));
16140
16139
  const result = { vault, token, nonce: "0", created: true, txHash, softLimit };
16141
- if (this.#sender.relayed && (this.kind === "buyer" || this.kind === "creator") && this.#paysRelayer()) {
16140
+ if (this.#sender.relayed && (this.kind === "buyer" || this.kind === "creator" || this.kind === "private") && this.#paysRelayer()) {
16142
16141
  result.note = "the Ostra relayer paid the vault's creation; the vault owes that price, paid with the first voucher of its channel to the relayer, which this server opens from the vault once it holds tokens (nothing else is relayed for the vault until then)";
16143
16142
  }
16144
16143
  if (!await this.#keys.backupExported()) result.backupReminder = BACKUP_REMINDER;
@@ -21720,11 +21719,11 @@ var init_src2 = __esm({
21720
21719
  });
21721
21720
 
21722
21721
  // ../core/src/private-balance.ts
21723
- import { getAddress as getAddress25, parseAbiItem as parseAbiItem4 } from "viem";
21722
+ import { bytesToHex as bytesToHex12, getAddress as getAddress25, parseAbiItem as parseAbiItem4, zeroAddress as zeroAddress12 } from "viem";
21724
21723
  function privateTokens(config) {
21725
21724
  return [config.deployment.token, WETH, ...LAUNCH_PAIRS.filter((p) => p.kind === "ostra" || p.kind === "stock").map((p) => getAddress25(p.address))];
21726
21725
  }
21727
- var PRIVATE_SWAP_DIR, PRIVATE_SHIELD_GAS, PRIVATE_SPEND_DEADLINE_SECONDS, MAX_SLIPPAGE_BPS2, PRIVATE_FEE_GAS, PRIVATE_FEE_MAX_FACTOR, SHIELD_SCAN_GAP, LOGS_CATCH_UP_TRIES, SHIELD_LOG_WAIT_MS, NOTES, TREES, NEXT_RHO, POOL_INDEX, PENDING_PREFIX, shieldedEvent, swappedEvent, unshieldedEvent, address3, PrivateBalanceService;
21726
+ var PRIVATE_SWAP_DIR, PRIVATE_SHIELD_GAS, PRIVATE_SPEND_DEADLINE_SECONDS, MAX_SLIPPAGE_BPS2, PRIVATE_FEE_GAS, PRIVATE_FEE_MAX_FACTOR, SHIELD_SCAN_GAP, LOGS_CATCH_UP_TRIES, SHIELD_LOG_WAIT_MS, PRIVATE_VAULT_SCAN_GAP, AUTO_SHIELD_MIN, NOTES, TREES, NEXT_RHO, POOL_INDEX, PENDING_PREFIX, PRIVATE_VAULTS, AUTO_SHIELD, shieldedEvent, swappedEvent, unshieldedEvent, address3, PrivateBalanceService;
21728
21727
  var init_private_balance = __esm({
21729
21728
  "../core/src/private-balance.ts"() {
21730
21729
  "use strict";
@@ -21733,6 +21732,7 @@ var init_private_balance = __esm({
21733
21732
  init_src2();
21734
21733
  init_amount();
21735
21734
  init_chain2();
21735
+ init_config();
21736
21736
  init_relay();
21737
21737
  init_vault();
21738
21738
  PRIVATE_SWAP_DIR = "ostra-swap-private";
@@ -21744,11 +21744,15 @@ var init_private_balance = __esm({
21744
21744
  SHIELD_SCAN_GAP = 20n;
21745
21745
  LOGS_CATCH_UP_TRIES = 30;
21746
21746
  SHIELD_LOG_WAIT_MS = 60 * 60 * 1e3;
21747
+ PRIVATE_VAULT_SCAN_GAP = 200n;
21748
+ AUTO_SHIELD_MIN = parseTokenAmount("1");
21747
21749
  NOTES = "notes.json";
21748
21750
  TREES = "trees.json";
21749
21751
  NEXT_RHO = "next-rho";
21750
21752
  POOL_INDEX = "pool-index.json";
21751
21753
  PENDING_PREFIX = "pending-";
21754
+ PRIVATE_VAULTS = "private-vaults.json";
21755
+ AUTO_SHIELD = "auto-shield-creator.json";
21752
21756
  shieldedEvent = parseAbiItem4("event Shielded(address indexed from, address indexed token, uint256 amount, bytes32 tag, bytes32 commitment)");
21753
21757
  swappedEvent = parseAbiItem4(
21754
21758
  "event PrivateSwapped(bytes32 indexed nullifier, address indexed tokenIn, address indexed tokenOut, uint256 amountIn, uint256 amountOut, uint256 fee, address relayer, bytes32 outTag, bytes32 outCommitment)"
@@ -21834,7 +21838,7 @@ var init_private_balance = __esm({
21834
21838
  shielded.set(tag, [...shielded.get(tag) ?? [], { token: getAddress25(l.args.token), amount: l.args.amount, commitment: l.args.commitment }]);
21835
21839
  notes += 1;
21836
21840
  } else if (l.eventName === "Unshielded") {
21837
- spends.set(l.args.nullifier.toLowerCase(), { kind: "unshield", v: l.args.amount, fee: l.args.fee });
21841
+ spends.set(l.args.nullifier.toLowerCase(), { kind: "unshield", v: l.args.amount, fee: l.args.fee, recipient: getAddress25(l.args.recipient) });
21838
21842
  notes += 1;
21839
21843
  } else {
21840
21844
  spends.set(l.args.nullifier.toLowerCase(), { kind: "swap", v: l.args.amountIn, fee: l.args.fee, tokenOut: getAddress25(l.args.tokenOut), amountOut: l.args.amountOut, outCommitment: l.args.outCommitment });
@@ -21919,6 +21923,7 @@ var init_private_balance = __esm({
21919
21923
  const shields = queue.length;
21920
21924
  const unspent = [];
21921
21925
  const seen = /* @__PURE__ */ new Set();
21926
+ const recipients = /* @__PURE__ */ new Set();
21922
21927
  let followed = 0;
21923
21928
  for (let note = queue.pop(); note !== void 0; note = queue.pop()) {
21924
21929
  if (seen.has(note.commitment.toLowerCase())) continue;
@@ -21930,6 +21935,7 @@ var init_private_balance = __esm({
21930
21935
  unspent.push(note);
21931
21936
  continue;
21932
21937
  }
21938
+ if (spend.kind === "unshield") recipients.add(spend.recipient);
21933
21939
  const change = note.amount - spend.v - spend.fee;
21934
21940
  if (change > 0n) {
21935
21941
  const rho = this.#spendRho("change", note.commitment);
@@ -21954,7 +21960,27 @@ var init_private_balance = __esm({
21954
21960
  const next = counted + 1n > last + 1n ? counted + 1n : last + 1n;
21955
21961
  await this.#p.folder.write(NEXT_RHO, `${next}
21956
21962
  `);
21957
- return { pool: this.#pool, shields, followed, notes: records.length, tokens: await this.#summary(records) };
21963
+ await this.#recoverPrivateVaults(recipients);
21964
+ return { pool: this.#pool, shields, followed, notes: records.length, tokens: await this.#summary(records), privateVaults: await this.#privateVaultViews() };
21965
+ }
21966
+ /**
21967
+ * The private vaults among `recipients` (the agent's unshields' recipients): numbers 0, 1, ... until
21968
+ * PRIVATE_VAULT_SCAN_GAP numbers in a row past the last one found, and past every number kept here or taken by
21969
+ * unshield.ts, are none of them. Kept with the ones recorded here (a record whose address is not its number's goes).
21970
+ */
21971
+ async #recoverPrivateVaults(recipients) {
21972
+ const kept = (await this.#privateVaults()).filter((r) => this.#privateVaultAddress(BigInt(r.index)) === getAddress25(r.vault));
21973
+ const byIndex = new Map(kept.map((r) => [r.index, r]));
21974
+ const known = [...kept.map((r) => BigInt(r.index)), ...await this.#takenPrivateIndexes()];
21975
+ const floor = known.reduce((m, i) => i + 1n > m ? i + 1n : m, 0n);
21976
+ let last = -1n;
21977
+ for (let i = 0n; recipients.size > 0 && (i <= last + PRIVATE_VAULT_SCAN_GAP || i < floor); i++) {
21978
+ const vault = this.#privateVaultAddress(i);
21979
+ if (!recipients.has(vault)) continue;
21980
+ last = i;
21981
+ if (!byIndex.has(i.toString())) byIndex.set(i.toString(), { index: i.toString(), vault, at: Date.now() });
21982
+ }
21983
+ await this.#savePrivateVaults([...byIndex.values()].sort((a, b) => BigInt(a.index) < BigInt(b.index) ? -1 : 1));
21958
21984
  }
21959
21985
  /** Per token: what `notes` hold, how many, and the largest. */
21960
21986
  async #summary(notes) {
@@ -21996,6 +22022,53 @@ var init_private_balance = __esm({
21996
22022
  await this.#p.folder.write(NOTES, `${JSON.stringify({ version: 1, notes })}
21997
22023
  `);
21998
22024
  }
22025
+ /** The address of private vault `index` (VaultService.vaultAddress of kind "private"; no crank). */
22026
+ #privateVaultAddress(index) {
22027
+ const { chainId, deployment: d } = this.#p.config;
22028
+ const initialKeyRoot = bytesToHex12(this.#p.keys.privateVaultKeychain(chainId, d.token, index).keyRoot(0n));
22029
+ return vaultAddress({ factory: d.vaultFactory, beacon: d.vaultBeacon, token: d.token, initialKeyRoot, initialCrank: zeroAddress12 });
22030
+ }
22031
+ async #privateVaults() {
22032
+ const text2 = await this.#p.folder.read(PRIVATE_VAULTS);
22033
+ return text2 === void 0 ? [] : JSON.parse(text2).vaults;
22034
+ }
22035
+ async #savePrivateVaults(vaults) {
22036
+ await this.#p.folder.ensure();
22037
+ await this.#p.folder.write(PRIVATE_VAULTS, `${JSON.stringify({ version: 1, vaults })}
22038
+ `);
22039
+ }
22040
+ async #takenPrivateIndexes() {
22041
+ return this.#p.takenPrivateIndexes === void 0 ? [] : this.#p.takenPrivateIndexes();
22042
+ }
22043
+ /**
22044
+ * A new private vault, kept before it is used: the first number past every one kept here or taken by unshield.ts
22045
+ * whose address has no code and no USDG yet. A number used from another copy of this OSTRA_HOME is passed over and
22046
+ * kept too (it is this seed's private vault), so private_balance and private_withdraw see it.
22047
+ */
22048
+ async #newPrivateVault() {
22049
+ const vaults = await this.#privateVaults();
22050
+ let index = [...vaults.map((r) => BigInt(r.index)), ...await this.#takenPrivateIndexes()].reduce((m, i) => i + 1n > m ? i + 1n : m, 0n);
22051
+ for (; ; index++) {
22052
+ const vault = this.#privateVaultAddress(index);
22053
+ const record = { index: index.toString(), vault, at: Date.now() };
22054
+ vaults.push(record);
22055
+ const used = await this.#p.client.getCode({ address: vault }) !== void 0 || await tokenBalance(this.#p.client, this.#p.config.deployment.token, vault) > 0n;
22056
+ if (used) continue;
22057
+ await this.#savePrivateVaults(vaults);
22058
+ return record;
22059
+ }
22060
+ }
22061
+ /** The private vaults kept here, read now: the ones created or holding USDG (a number whose unshield never landed stays kept, unused). */
22062
+ async #privateVaultViews() {
22063
+ const out = [];
22064
+ for (const r of await this.#privateVaults()) {
22065
+ const vault = getAddress25(r.vault);
22066
+ const exists2 = await this.#p.client.getCode({ address: vault }) !== void 0;
22067
+ const balance = await tokenBalance(this.#p.client, this.#p.config.deployment.token, vault);
22068
+ if (exists2 || balance > 0n) out.push({ index: r.index, vault, exists: exists2, balance: formatTokenAmount(balance) });
22069
+ }
22070
+ return out;
22071
+ }
21999
22072
  async #addNotes(add, remove = []) {
22000
22073
  const drop = new Set(remove.map((c) => c.toLowerCase()));
22001
22074
  const notes = (await this.#notes()).filter((n) => !drop.has(n.commitment.toLowerCase()));
@@ -22034,12 +22107,12 @@ var init_private_balance = __esm({
22034
22107
  async #done(id) {
22035
22108
  await this.#p.folder.remove(`${PENDING_PREFIX}${id}.json`);
22036
22109
  }
22037
- /** The agent's notes now (operations in flight finished first), and per token what they hold. */
22110
+ /** The agent's notes now (operations in flight finished first), per token what they hold, and its private vaults. */
22038
22111
  balance() {
22039
22112
  return this.#serial(async () => {
22040
22113
  const failures = await this.#refresh();
22041
22114
  await this.#ensureRecovered();
22042
- return { pool: this.#pool, tokens: await this.#summary(await this.#notes()), inFlight: failures.length };
22115
+ return { pool: this.#pool, tokens: await this.#summary(await this.#notes()), inFlight: failures.length, privateVaults: await this.#privateVaultViews() };
22043
22116
  });
22044
22117
  }
22045
22118
  /** Shields `amount` of `token` from the agent's vault of it into a new note (see the file comment). */
@@ -22047,43 +22120,58 @@ var init_private_balance = __esm({
22047
22120
  return this.#serial(async () => {
22048
22121
  const token = address3(a.token, "token");
22049
22122
  this.#checkToken(token);
22050
- const prover = this.#prover();
22051
22123
  const decimals = await tokenDecimals(this.#p.client, token);
22052
22124
  const amount = parseUnitsAmount(a.amount, decimals);
22053
22125
  if (amount === 0n) throw new RangeError("amount must be greater than zero");
22054
- const from = this.#p.vaultFor(token);
22055
- const s = await from.state();
22056
- if (!s.exists && from.kind === "buyer") throw new Error("this agent has no vault yet; call open_vault first");
22057
- if (s.balance < amount) throw new Error(`the vault in ${token} (${s.vault}) holds ${formatUnitsAmount(s.balance, decimals)}, less than ${a.amount}; nothing was signed`);
22058
- if (!s.exists) await from.open();
22059
- const sk = this.#sk();
22060
- await this.#ensureRecovered();
22061
- let pending = null;
22062
- const result = await from.operate({
22063
- gas: PRIVATE_SHIELD_GAS,
22064
- moves: amount,
22065
- what: "shield",
22066
- build: async ({ v, vault, keychain, chainId }) => {
22067
- const balance = await tokenBalance(this.#p.client, token, vault);
22068
- if (balance < amount) throw new Error(`the vault holds ${formatUnitsAmount(balance, decimals)} now, less than ${a.amount}; nothing was signed`);
22069
- const rho = await this.#nextShieldRho();
22070
- const tag = await tagOf(prover, sk, rho);
22071
- const escrow = shieldEscrowAddress(this.#pool, token, tag);
22072
- pending = { kind: "shield", id: tag.slice(2, 18), token, rho, tag, escrow, vaultNonce: v.nonce.toString(), block: (await this.#p.client.getBlockNumber({ cacheTime: 0 })).toString(), at: Date.now() };
22073
- await this.#keep(pending);
22074
- const signed = await signVaultTransfer(keychain, v, { chainId, to: escrow, amount });
22075
- return [spendCall(vault, signed), shieldFromEscrowCall(this.#pool, token, tag)];
22076
- }
22077
- });
22078
- if (result.status !== "sent") return result;
22079
- const kept = pending;
22080
- if (kept === null) throw new Error("the shield was sent without its record");
22081
- const note = await shieldedNote(prover, sk, kept.rho, await this.#p.client.getTransactionReceipt({ hash: result.txHash }), this.#pool);
22082
- await this.#addNotes([note]);
22083
- await this.#done(kept.id);
22084
- return { status: "sent", txHash: result.txHash, token, amount: formatUnitsAmount(note.amount, decimals), note: note.commitment };
22126
+ return this.#shieldFrom(this.#p.vaultFor(token), void 0, token, amount, decimals);
22085
22127
  });
22086
22128
  }
22129
+ /** Shields `amount` of `token` from the vault `from` (`source` "creator": the creator vault) into a new note. */
22130
+ async #shieldFrom(from, source, token, amount, decimals) {
22131
+ const prover = this.#prover();
22132
+ const text2 = formatUnitsAmount(amount, decimals);
22133
+ const s = await from.state();
22134
+ if (!s.exists && from.kind === "buyer") throw new Error("this agent has no vault yet; call open_vault first");
22135
+ if (s.balance < amount) throw new Error(`the vault in ${token} (${s.vault}) holds ${formatUnitsAmount(s.balance, decimals)}, less than ${text2}; nothing was signed`);
22136
+ if (!s.exists) await from.open();
22137
+ const sk = this.#sk();
22138
+ await this.#ensureRecovered();
22139
+ let pending = null;
22140
+ const result = await from.operate({
22141
+ gas: PRIVATE_SHIELD_GAS,
22142
+ moves: amount,
22143
+ what: "shield",
22144
+ build: async ({ v, vault, keychain, chainId }) => {
22145
+ const balance = await tokenBalance(this.#p.client, token, vault);
22146
+ if (balance < amount) throw new Error(`the vault holds ${formatUnitsAmount(balance, decimals)} now, less than ${text2}; nothing was signed`);
22147
+ const rho = await this.#nextShieldRho();
22148
+ const tag = await tagOf(prover, sk, rho);
22149
+ const escrow = shieldEscrowAddress(this.#pool, token, tag);
22150
+ pending = {
22151
+ kind: "shield",
22152
+ id: tag.slice(2, 18),
22153
+ token,
22154
+ rho,
22155
+ tag,
22156
+ escrow,
22157
+ ...source === void 0 ? {} : { source },
22158
+ vaultNonce: v.nonce.toString(),
22159
+ block: (await this.#p.client.getBlockNumber({ cacheTime: 0 })).toString(),
22160
+ at: Date.now()
22161
+ };
22162
+ await this.#keep(pending);
22163
+ const signed = await signVaultTransfer(keychain, v, { chainId, to: escrow, amount });
22164
+ return [spendCall(vault, signed), shieldFromEscrowCall(this.#pool, token, tag)];
22165
+ }
22166
+ });
22167
+ if (result.status !== "sent") return result;
22168
+ const kept = pending;
22169
+ if (kept === null) throw new Error("the shield was sent without its record");
22170
+ const note = await shieldedNote(prover, sk, kept.rho, await this.#p.client.getTransactionReceipt({ hash: result.txHash }), this.#pool);
22171
+ await this.#addNotes([note]);
22172
+ await this.#done(kept.id);
22173
+ return { status: "sent", txHash: result.txHash, token, amount: formatUnitsAmount(note.amount, decimals), note: note.commitment };
22174
+ }
22087
22175
  /** The smallest note of `token` holding at least `need`, or an error naming the largest. */
22088
22176
  async #pick(token, need, decimals, what) {
22089
22177
  const busy = await this.#notesInFlight();
@@ -22238,11 +22326,19 @@ var init_private_balance = __esm({
22238
22326
  };
22239
22327
  });
22240
22328
  }
22241
- /** Sends `amount` of `token` out of one private note to `destination` (default: the agent's vault of that token). */
22329
+ /**
22330
+ * Sends `amount` of `token` out of one private note to `destination` (default: the agent's vault of that token), or,
22331
+ * with `toPrivateVault`, to a new private vault of the agent (USDG only; see the file comment).
22332
+ */
22242
22333
  unshield(a) {
22243
22334
  return this.#serial(async () => {
22244
22335
  const token = address3(a.token, "token");
22245
22336
  this.#checkToken(token);
22337
+ if (a.toPrivateVault === true) {
22338
+ if (a.destination !== void 0) throw new RangeError("name a destination or toPrivateVault, not both");
22339
+ if (token !== this.#p.config.deployment.token) throw new RangeError("a private vault holds USDG: unshield another token to an address");
22340
+ if (this.#p.privateVault === void 0) throw new Error("private vaults are not set up here");
22341
+ }
22246
22342
  this.#requireRelayer();
22247
22343
  const prover = this.#prover();
22248
22344
  await this.#refresh();
@@ -22250,10 +22346,11 @@ var init_private_balance = __esm({
22250
22346
  const decimals = await tokenDecimals(this.#p.client, token);
22251
22347
  const v = parseUnitsAmount(a.amount, decimals);
22252
22348
  if (v === 0n) throw new RangeError("amount must be greater than zero");
22253
- const recipient = a.destination === void 0 ? await this.#p.vaultFor(token).vaultAddress() : address3(a.destination, "destination");
22254
- if (recipient === "0x0000000000000000000000000000000000000000") throw new RangeError("the destination must not be the zero address");
22255
22349
  const { fee: priced } = await this.#price(token, "unshield");
22256
22350
  const note = await this.#pick(token, v + priced, decimals, `${a.amount} and the relayer's fee`);
22351
+ const privateVault = a.toPrivateVault === true ? await this.#newPrivateVault() : null;
22352
+ const recipient = privateVault !== null ? privateVault.vault : a.destination === void 0 ? await this.#p.vaultFor(token).vaultAddress() : address3(a.destination, "destination");
22353
+ if (recipient === zeroAddress12) throw new RangeError("the destination must not be the zero address");
22257
22354
  const sk = this.#sk();
22258
22355
  const changeRho = this.#spendRho("change", note.commitment);
22259
22356
  const r = await this.#spend(note, (fee) => v + fee, "unshield", async (fee, relayer, deadline, trees) => {
@@ -22264,7 +22361,101 @@ var init_private_balance = __esm({
22264
22361
  const change = changeNote(spend, r.receipt, this.#pool);
22265
22362
  await this.#addNotes(change === null ? [] : [change], [note.commitment]);
22266
22363
  await this.#done(spend.terms.nullifier.slice(2, 18));
22267
- return { status: "sent", txHash: r.txHash, amountIn: formatUnitsAmount(v, decimals), fee: formatUnitsAmount(r.fee, decimals), recipient, change: formatUnitsAmount(spend.change.amount, decimals) };
22364
+ return {
22365
+ status: "sent",
22366
+ txHash: r.txHash,
22367
+ amountIn: formatUnitsAmount(v, decimals),
22368
+ fee: formatUnitsAmount(r.fee, decimals),
22369
+ recipient,
22370
+ change: formatUnitsAmount(spend.change.amount, decimals),
22371
+ ...privateVault === null ? {} : { privateVault: { index: privateVault.index, vault: privateVault.vault } }
22372
+ };
22373
+ });
22374
+ }
22375
+ /** The private vaults an unshield paid, read now. */
22376
+ privateVaults() {
22377
+ return this.#serial(() => this.#privateVaultViews());
22378
+ }
22379
+ /** The number of the private vault at `vault` kept here (found again from the seed first in a new OSTRA_HOME), or null. */
22380
+ async privateVaultIndex(vault) {
22381
+ const found = (await this.#serial(async () => {
22382
+ await this.#ensureRecovered();
22383
+ return this.#privateVaults();
22384
+ })).find((r) => r.vault.toLowerCase() === vault.toLowerCase());
22385
+ return found === void 0 ? null : BigInt(found.index);
22386
+ }
22387
+ /** Sends USDG from the private vault at `vault` (one an unshield paid), creating the vault first when it does not exist yet. */
22388
+ async withdrawPrivate(a) {
22389
+ const index = await this.privateVaultIndex(address3(a.vault, "vault"));
22390
+ if (index === null) throw new Error(`${a.vault} is not a private vault an unshield of this agent paid (private_balance lists them)`);
22391
+ if (this.#p.privateVault === void 0) throw new Error("private vaults are not set up here");
22392
+ this.#requireRelayer();
22393
+ const pv = await this.#p.privateVault(index);
22394
+ if (await pv.vaultAddress() !== getAddress25(a.vault)) throw new Error(`private vault ${index} is ${await pv.vaultAddress()}, not ${a.vault}`);
22395
+ const s = await pv.state();
22396
+ const created = !s.exists;
22397
+ if (created && this.#p.config.relayerUrl !== null) {
22398
+ const amount = parseTokenAmount(a.amount);
22399
+ if (s.balance < amount + RELAYER_CHANNEL_DEPOSIT) {
22400
+ throw new Error(`private vault ${index} holds ${formatTokenAmount(s.balance)}; its first withdrawal leaves ${formatTokenAmount(RELAYER_CHANNEL_DEPOSIT)} for its relayer channel, so at most ${formatTokenAmount(s.balance > RELAYER_CHANNEL_DEPOSIT ? s.balance - RELAYER_CHANNEL_DEPOSIT : 0n)} now; nothing was signed`);
22401
+ }
22402
+ }
22403
+ if (created) await pv.open();
22404
+ return { ...await pv.withdraw(a.destination, a.amount), ...created ? { created } : {} };
22405
+ }
22406
+ /** Auto-shield of the creator vault (off unless set). */
22407
+ autoShield() {
22408
+ return this.#serial(() => this.#autoShieldSetting());
22409
+ }
22410
+ #creator() {
22411
+ if (this.#p.creator === void 0) throw new Error("the creator vault is not set up here");
22412
+ return this.#p.creator;
22413
+ }
22414
+ async #autoShieldSetting() {
22415
+ const vault = await this.#creator().vaultAddress();
22416
+ const text2 = await this.#p.folder.read(AUTO_SHIELD);
22417
+ if (text2 === void 0) return { vault, enabled: false, keep: formatTokenAmount(RELAYER_CHANNEL_DEPOSIT) };
22418
+ const r = JSON.parse(text2);
22419
+ if (typeof r.enabled !== "boolean" || typeof r.keep !== "string") throw new Error(`${AUTO_SHIELD} is not an auto-shield setting`);
22420
+ return { vault, enabled: r.enabled, keep: formatTokenAmount(parseTokenAmount(r.keep)) };
22421
+ }
22422
+ /** Turns auto-shield of the creator vault on or off; `keep` (USDG, default RELAYER_CHANNEL_DEPOSIT) stays in the vault. */
22423
+ setAutoShield(enabled, keep) {
22424
+ return this.#serial(async () => {
22425
+ this.#creator();
22426
+ if (enabled) this.#prover();
22427
+ const kept = keep === void 0 ? RELAYER_CHANNEL_DEPOSIT : parseTokenAmount(keep);
22428
+ await this.#p.folder.ensure();
22429
+ await this.#p.folder.write(AUTO_SHIELD, `${JSON.stringify({ enabled, keep: formatTokenAmount(kept) })}
22430
+ `);
22431
+ return this.#autoShieldSetting();
22432
+ });
22433
+ }
22434
+ /**
22435
+ * One auto-shield step (the sweep): what the creator vault holds beyond `keep`, once at least AUTO_SHIELD_MIN, into
22436
+ * a note. Null when it is off, the creator vault does not exist yet (a launch creates it), it is below that, or a
22437
+ * shield from the creator vault is in flight that the vault no longer holds signed (refresh finishes it); one the
22438
+ * vault holds signed and not landed is sent again first (VaultService.operate), so a lost send does not stop it.
22439
+ */
22440
+ autoShieldStep() {
22441
+ return this.#serial(async () => {
22442
+ if (this.#p.creator === void 0 || this.#p.prover === null) return null;
22443
+ const setting = await this.#autoShieldSetting();
22444
+ if (!setting.enabled) return null;
22445
+ let inFlight = false;
22446
+ for (const name of await this.#p.folder.list()) {
22447
+ if (!name.startsWith(PENDING_PREFIX)) continue;
22448
+ const text2 = await this.#p.folder.read(name);
22449
+ const p = text2 === void 0 ? null : JSON.parse(text2);
22450
+ if (p !== null && p.kind === "shield" && p.source === "creator") inFlight = true;
22451
+ }
22452
+ const token = this.#p.config.deployment.token;
22453
+ const s = await this.#creator().state();
22454
+ if (!s.exists) return null;
22455
+ if (inFlight && s.pendingAction === null) return null;
22456
+ const amount = s.balance - parseTokenAmount(setting.keep);
22457
+ if (amount < AUTO_SHIELD_MIN) return null;
22458
+ return this.#shieldFrom(this.#creator(), "creator", token, amount, await tokenDecimals(this.#p.client, token));
22268
22459
  });
22269
22460
  }
22270
22461
  /** refresh() for the sweep: one at a time with the operations. Returns what is still in flight or failed. */
@@ -22303,7 +22494,7 @@ var init_private_balance = __esm({
22303
22494
  await this.#addNotes([await shieldedNote(prover, sk, p.rho, await this.#p.client.getTransactionReceipt({ hash: l.transactionHash }), this.#pool)]);
22304
22495
  found = true;
22305
22496
  }
22306
- const s = await this.#p.vaultFor(p.token).state();
22497
+ const s = await (p.source === "creator" ? this.#creator() : this.#p.vaultFor(p.token)).state();
22307
22498
  if (s.nonce === null || s.nonce <= BigInt(p.vaultNonce)) return false;
22308
22499
  if (await tokenBalance(this.#p.client, p.token, p.escrow) > 0n) {
22309
22500
  try {
@@ -22356,7 +22547,7 @@ var init_seed_backup = __esm({
22356
22547
  });
22357
22548
 
22358
22549
  // ../core/src/sessions.ts
22359
- import { bytesToHex as bytesToHex12, getAddress as getAddress26, isAddress as isAddress12 } from "viem";
22550
+ import { bytesToHex as bytesToHex13, getAddress as getAddress26, isAddress as isAddress12 } from "viem";
22360
22551
  import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
22361
22552
  var init_sessions = __esm({
22362
22553
  "../core/src/sessions.ts"() {
@@ -22368,7 +22559,7 @@ var init_sessions = __esm({
22368
22559
  });
22369
22560
 
22370
22561
  // ../core/src/shield.ts
22371
- import { bytesToHex as bytesToHex13, encodeFunctionData as encodeFunctionData13, getAddress as getAddress27, hexToBytes as hexToBytes8, parseSignature } from "viem";
22562
+ import { bytesToHex as bytesToHex14, encodeFunctionData as encodeFunctionData13, getAddress as getAddress27, hexToBytes as hexToBytes8, parseSignature } from "viem";
22372
22563
  function leafKeyIndex(attempt, j) {
22373
22564
  if (!Number.isInteger(attempt) || attempt < 0 || attempt >= MAX_LEAF_ATTEMPTS) throw new RangeError(`leaf set ${String(attempt)} is out of range`);
22374
22565
  if (!Number.isInteger(j) || j < 0 || j >= LEAF_KEYS_PER_ATTEMPT) throw new RangeError(`leaf ${String(j)} is out of range`);
@@ -22379,12 +22570,12 @@ function recordName(chainId, token, index) {
22379
22570
  }
22380
22571
  function decodeApproveMessage(message) {
22381
22572
  if (message.length !== APPROVE_MESSAGE_LEN) throw new Error("not an approval message");
22382
- const chainId = Number(BigInt(bytesToHex13(message.subarray(20, 52))));
22383
- const pool = getAddress27(bytesToHex13(message.subarray(52, 72)));
22384
- const key = bytesToHex13(message.subarray(72, 104));
22385
- const batchId = BigInt(bytesToHex13(message.subarray(104, 112)));
22386
- const state = bytesToHex13(message.subarray(112, 144));
22387
- const nextKeyRoot = bytesToHex13(message.subarray(144, 176));
22573
+ const chainId = Number(BigInt(bytesToHex14(message.subarray(20, 52))));
22574
+ const pool = getAddress27(bytesToHex14(message.subarray(52, 72)));
22575
+ const key = bytesToHex14(message.subarray(72, 104));
22576
+ const batchId = BigInt(bytesToHex14(message.subarray(104, 112)));
22577
+ const state = bytesToHex14(message.subarray(112, 144));
22578
+ const nextKeyRoot = bytesToHex14(message.subarray(144, 176));
22388
22579
  const again = approvalMessage({ chainId, pool, key, batchId, state, nextKeyRoot });
22389
22580
  if (again.length !== message.length || !again.every((x, i) => x === message[i])) throw new Error("not an approval message");
22390
22581
  return { chainId, pool, key, batchId, state, nextKeyRoot };
@@ -22753,7 +22944,7 @@ var init_shield = __esm({
22753
22944
  depositId: this.#keys.shieldDepositId(this.#config.chainId, this.#token(), index),
22754
22945
  amount,
22755
22946
  refundOwner: await this.#vault.vaultAddress(),
22756
- approvalKeyRoot: bytesToHex13(this.#approvalKeys(record).keyRoot(0n))
22947
+ approvalKeyRoot: bytesToHex14(this.#approvalKeys(record).keyRoot(0n))
22757
22948
  };
22758
22949
  if (depositKey(deposit.depositor, deposit.depositId) !== record.deposit) throw new Error("the deposit key does not match the record");
22759
22950
  const validAfter = 0n;
@@ -22856,7 +23047,7 @@ var init_shield = __esm({
22856
23047
  for (let attempt = 0; attempt < 2; attempt++) {
22857
23048
  let index = -1n;
22858
23049
  for (let i = 0n; i < 64n; i++) {
22859
- if (bytesToHex13(keys.keyRoot(i)) === current.approvalKeyRoot.toLowerCase()) {
23050
+ if (bytesToHex14(keys.keyRoot(i)) === current.approvalKeyRoot.toLowerCase()) {
22860
23051
  index = i;
22861
23052
  break;
22862
23053
  }
@@ -23161,7 +23352,7 @@ function view(r) {
23161
23352
  txHash: r.txHash
23162
23353
  };
23163
23354
  }
23164
- var UNSHIELD_DIR, UNSHIELD_DEFAULT_WINDOW_HOURS, UNSHIELD_MAX_WINDOW_HOURS, LEAF_SPEND_GAS, UnshieldService;
23355
+ var UNSHIELD_DIR, UNSHIELD_MAX_WINDOW_HOURS, LEAF_SPEND_GAS, UnshieldService;
23165
23356
  var init_unshield = __esm({
23166
23357
  "../core/src/unshield.ts"() {
23167
23358
  "use strict";
@@ -23170,7 +23361,6 @@ var init_unshield = __esm({
23170
23361
  init_amount();
23171
23362
  init_random();
23172
23363
  UNSHIELD_DIR = "unshield";
23173
- UNSHIELD_DEFAULT_WINDOW_HOURS = 6;
23174
23364
  UNSHIELD_MAX_WINDOW_HOURS = 24;
23175
23365
  LEAF_SPEND_GAS = 300000n;
23176
23366
  UnshieldService = class {
@@ -23354,7 +23544,7 @@ import {
23354
23544
  getAddress as getAddress30,
23355
23545
  maxUint256,
23356
23546
  parseAbi as parseAbi4,
23357
- zeroAddress as zeroAddress12
23547
+ zeroAddress as zeroAddress13
23358
23548
  } from "viem";
23359
23549
  var MAX_UINT160, MAX_UINT128, universalRouterAbi, permit2Abi, wrappedErrorAbi;
23360
23550
  var init_wallet_swap = __esm({
@@ -23420,7 +23610,7 @@ var init_src3 = __esm({
23420
23610
  import { randomBytes as randomBytes4 } from "node:crypto";
23421
23611
  import { stat as stat2 } from "node:fs/promises";
23422
23612
  import { join as join3 } from "node:path";
23423
- import { bytesToHex as bytesToHex14 } from "viem";
23613
+ import { bytesToHex as bytesToHex15 } from "viem";
23424
23614
  import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
23425
23615
  async function exists(path) {
23426
23616
  try {
@@ -23458,7 +23648,7 @@ async function openKeys(home, escrow) {
23458
23648
  await ensurePrivateDir(join3(home, WOTS_DIR));
23459
23649
  return new Keys({
23460
23650
  master: master.value,
23461
- feePayer: privateKeyToAccount4(bytesToHex14(fee.value)),
23651
+ feePayer: privateKeyToAccount4(bytesToHex15(fee.value)),
23462
23652
  store: new FileWotsStore(join3(home, WOTS_DIR)),
23463
23653
  folder,
23464
23654
  masterCreated: master.created,
@@ -23657,9 +23847,9 @@ var init_cli_prover = __esm({
23657
23847
  });
23658
23848
 
23659
23849
  // ../../ostra-swap/sdk/src/wasm-prover.ts
23660
- import { bytesToHex as bytesToHex15 } from "viem";
23850
+ import { bytesToHex as bytesToHex16 } from "viem";
23661
23851
  async function sha256Hex(bytes) {
23662
- return bytesToHex15(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)));
23852
+ return bytesToHex16(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)));
23663
23853
  }
23664
23854
  async function wasmProver(glue, bytes, sha2562) {
23665
23855
  const got = await sha256Hex(bytes);
@@ -23821,7 +24011,10 @@ var init_agent = __esm({
23821
24011
  vaultFor,
23822
24012
  sender: this.sender,
23823
24013
  folder: keys.folder.folder(PRIVATE_SWAP_DIR),
23824
- localSpends: config.privateLocalSpends
24014
+ localSpends: config.privateLocalSpends,
24015
+ privateVault: async (index) => (await this.privateVault(index)).vault,
24016
+ takenPrivateIndexes: async () => (await this.unshield.list()).map((r) => BigInt(r.index)),
24017
+ creator: this.creator
23825
24018
  });
23826
24019
  }
23827
24020
  }
@@ -23846,8 +24039,29 @@ var init_agent = __esm({
23846
24039
  const creatorKeys = await keys.forCreatorVault(config.chainId, config.deployment.token);
23847
24040
  const agent = new _Agent({ config, client, transport, keys, creatorKeys, escrow, log: log2 });
23848
24041
  await agent.#loadSeller();
24042
+ await agent.#moveAutoShield();
23849
24043
  return agent;
23850
24044
  }
24045
+ /**
24046
+ * An auto-shield of the creator vault set on the shielded pool's leaves (before 0.1.6) is turned on for the private
24047
+ * notes with the same `keep`, then off there; without Ostra Swap or a prover it is left as it is (and logged) for a
24048
+ * start that has them. It never stops the server from starting.
24049
+ */
24050
+ async #moveAutoShield() {
24051
+ try {
24052
+ const old = await this.creatorShield.autoShield();
24053
+ if (!old.enabled) return;
24054
+ if (this.privateBalance === null || this.config.prover === null) {
24055
+ this.#log("auto_shield_not_moved", { vault: old.vault, reason: "private notes need Ostra Swap and a prover" });
24056
+ return;
24057
+ }
24058
+ await this.privateBalance.setAutoShield(true, old.keep);
24059
+ await this.creatorShield.setAutoShield(false);
24060
+ this.#log("auto_shield_moved", { vault: old.vault, keep: old.keep });
24061
+ } catch (err) {
24062
+ this.#log("auto_shield_not_moved", { error: err instanceof Error ? err.message : String(err) });
24063
+ }
24064
+ }
23851
24065
  /** Private vault `index` (unshield made it), with its own records and relayer client. */
23852
24066
  async privateVault(index) {
23853
24067
  const known = this.#privates.get(index);
@@ -23958,7 +24172,10 @@ var init_agent = __esm({
23958
24172
  }
23959
24173
  await step2("unshield", () => this.unshield.sweep());
23960
24174
  if (this.swap !== null) await step2("ostra swap orders", () => this.swap.finishPending());
23961
- if (this.privateBalance !== null && this.config.prover !== null) await step2("private notes", () => this.privateBalance.refresh());
24175
+ if (this.privateBalance !== null && this.config.prover !== null) {
24176
+ await step2("private notes", () => this.privateBalance.refresh());
24177
+ await step2("private auto-shield", () => this.privateBalance.autoShieldStep());
24178
+ }
23962
24179
  } finally {
23963
24180
  this.#sweeping = false;
23964
24181
  }
@@ -24188,19 +24405,6 @@ function registerTools(server, agent) {
24188
24405
  if (!await agent.sellerReady()) throw new Error("this agent has no seller vault yet; call accept_payments first");
24189
24406
  return agent.seller.withdraw(a.destination ?? await agent.buyer.vaultAddress(), a.amount);
24190
24407
  });
24191
- tool("shield", "Deposits `leaves` standard leaves from the vault into the shielded pool (through the Ostra aggregator's batches).", { leaves: z.number().int().min(1).max(64) }, (a) => agent.shield.shield(a.leaves));
24192
- tool(
24193
- "unshield",
24194
- `Spends \`leaves\` shielded leaves, each into a new private vault at a random time within windowHours (default ${UNSHIELD_DEFAULT_WINDOW_HOURS}, at most ${UNSHIELD_MAX_WINDOW_HOURS}; 0: now, which lets the spends be timed together).`,
24195
- { leaves: z.number().int().min(1).max(64), windowHours: z.number().min(0).max(UNSHIELD_MAX_WINDOW_HOURS).default(UNSHIELD_DEFAULT_WINDOW_HOURS) },
24196
- (a) => agent.unshield.unshield(a.leaves, a.windowHours)
24197
- );
24198
- tool("shield_status", "Shield deposits, scheduled unshields and the private vaults.", {}, async () => ({ deposits: await agent.shield.list(), unshields: await agent.unshield.list(), privateVaults: await agent.unshield.vaults() }));
24199
- tool("auto_shield", "Turns the creator vault's auto-shield on or off (it shields what the vault holds beyond `keep` USDG).", { enabled: z.boolean(), keep: usdg.optional() }, (a) => agent.creatorShield.setAutoShield(a.enabled, a.keep));
24200
- tool("private_withdraw", "Sends USDG from one of the agent's private vaults.", { vault: address5, destination: address5, amount: usdg }, async (a) => {
24201
- const ctx = await agent.privateVault(await agent.unshield.indexOf(a.vault));
24202
- return ctx.vault.withdraw(a.destination, a.amount);
24203
- });
24204
24408
  tool(
24205
24409
  "ostra_swap_quote",
24206
24410
  "Quotes a swap on Ostra Swap from the agent's vault of tokenIn to its vault of tokenOut (USDG, WETH, OSTRA, listed stock tokens, tokens the Ostra launchpad launched), routed through Uniswap v3 and v4 against USDG or WETH: expected and minimum output, the hub and Ostra's fee (lower with OSTRA locked for the receiving vault). Nothing is signed.",
@@ -24221,11 +24425,11 @@ function registerTools(server, agent) {
24221
24425
  );
24222
24426
  tool(
24223
24427
  "private_recover",
24224
- "Finds the agent's private notes again from its seed and the pool's events alone (its shields, then each spend's change and swap output), and keeps the unspent ones: for a seed restored on another machine, or notes kept here that look wrong. It runs by itself on first use in a new OSTRA_HOME.",
24428
+ "Finds the agent's private notes again from its seed and the pool's events alone (its shields, then each spend's change and swap output), and keeps the unspent ones, with the private vaults its unshields paid: for a seed restored on another machine, or notes kept here that look wrong. It runs by itself on first use in a new OSTRA_HOME.",
24225
24429
  {},
24226
24430
  () => notes().recover()
24227
24431
  );
24228
- tool("private_balance", "The agent's private notes in OstraPrivatePool: per token the total, the number of notes and the largest one (a spend uses one note).", {}, () => notes().balance());
24432
+ tool("private_balance", "The agent's private notes in OstraPrivatePool: per token the total, the number of notes and the largest one (a spend uses one note); and its private vaults with what they hold.", {}, () => notes().balance());
24229
24433
  tool(
24230
24434
  "private_swap",
24231
24435
  "Swaps `amount` of tokenIn held in a private note into a new private note of tokenOut, through Ostra Swap, with a zero-knowledge proof made on this machine. The Ostra relayer sends it and is paid from the note (a fee in tokenIn); the change stays private.",
@@ -24234,9 +24438,29 @@ function registerTools(server, agent) {
24234
24438
  );
24235
24439
  tool(
24236
24440
  "private_unshield",
24237
- "Pays `amount` of a token out of a private note to an address, with a zero-knowledge proof made on this machine. The Ostra relayer sends it and is paid from the note; the change stays private. The default destination is the agent's vault of that token, which an observer can link to the vault that shielded: pay a fresh address for privacy.",
24238
- { token: swapToken, amount: units, destination: address5.optional() },
24239
- (a) => notes().unshield({ token: tokenAddress(a.token), amount: a.amount, ...opt("destination", a.destination) })
24441
+ "Pays `amount` of a token out of a private note to an address, with a zero-knowledge proof made on this machine. The Ostra relayer sends it and is paid from the note; the change stays private. The default destination is the agent's vault of that token, which an observer can link to the vault that shielded. With toPrivateVault (USDG only), it pays a new private vault of the agent instead, its own post-quantum vault found again from the seed (private_recover), which private_withdraw spends from. The amount is public, so a round amount, or a note split over several unshields, is harder to match to its shield.",
24442
+ { token: swapToken, amount: units, destination: address5.optional(), toPrivateVault: z.boolean().optional() },
24443
+ (a) => notes().unshield({ token: tokenAddress(a.token), amount: a.amount, ...opt("destination", a.destination), ...opt("toPrivateVault", a.toPrivateVault) })
24444
+ );
24445
+ tool(
24446
+ "private_withdraw",
24447
+ "Sends USDG from one of the agent's private vaults (private_balance lists them) to an address; a private vault an unshield paid is created on this first use.",
24448
+ { vault: address5, destination: address5, amount: usdg },
24449
+ async (a) => {
24450
+ const old = (await agent.unshield.vaults()).find((v) => v.vault.toLowerCase() === a.vault.toLowerCase());
24451
+ if (old !== void 0) return (await agent.privateVault(old.index)).vault.withdraw(a.destination, a.amount);
24452
+ if (agent.privateBalance !== null && await agent.privateBalance.privateVaultIndex(a.vault) !== null) return agent.privateBalance.withdrawPrivate(a);
24453
+ throw new Error(`${a.vault} is not a private vault of this agent (private_balance lists them)`);
24454
+ }
24455
+ );
24456
+ tool(
24457
+ "auto_shield",
24458
+ "Turns the creator vault's auto-shield on or off: every minute, everything the creator vault holds beyond `keep` USDG (default 1, which pays the relayer for its own transactions), once that is at least 1 USDG, is shielded into a private note (private_balance): launch payouts, and USDG put there for a launch too, so turn it off before funding a launch. The shield shows the creator vault it came from.",
24459
+ { enabled: z.boolean(), keep: usdg.optional() },
24460
+ async (a) => {
24461
+ await agent.creatorShield.setAutoShield(false);
24462
+ return notes().setAutoShield(a.enabled, a.keep);
24463
+ }
24240
24464
  );
24241
24465
  tool("recovery_status", "The vault's guardians, recovery policy and any recovery waiting.", {}, () => agent.buyer.recovery());
24242
24466
  tool("set_guardian", "Adds or removes a guardian of the vault (another Ostra vault keeps the recovery post-quantum).", { guardian: address5, enabled: z.boolean() }, (a) => agent.buyer.setGuardian(a.guardian, a.enabled));
@@ -24264,7 +24488,7 @@ function registerTools(server, agent) {
24264
24488
  (a) => lp().sell({ token: a.token, amount: a.amount, ...a.slippageBps === void 0 ? {} : { slippageBps: a.slippageBps }, ...a.referrer === void 0 ? {} : { referrer: a.referrer } })
24265
24489
  );
24266
24490
  tool("token_vault", "The agent's vault for a launched token: address, balance, dividends to claim.", { token: address5 }, (a) => lp().tokenVaultInfo(a.token));
24267
- tool("transfer_token", "Sends a launched token from the agent's token vault to an address.", { token: address5, destination: address5, amount: units }, (a) => lp().transferToken(a));
24491
+ tool("transfer_token", "Sends a token other than USDG (a launched token, WETH, OSTRA or a stock token) from the agent's vault of it to an address; USDG goes with withdraw.", { token: address5, destination: address5, amount: units }, (a) => lp().transferToken(a));
24268
24492
  tool("claim_dividends", "Claims the dividends the agent's token vault earned, into the agent's vault of the dividend token.", { token: address5 }, (a) => lp().claimDividends(a.token));
24269
24493
  tool(
24270
24494
  "claim_referral",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ostraagent/mcp",
3
- "version": "0.1.5",
4
- "description": "Ostra Agent's MCP server: an AI agent's own post-quantum USDG vault on Robinhood Chain, with payments, a shielded pool and the Ostra launchpad. The agent needs no ETH.",
3
+ "version": "0.1.6",
4
+ "description": "Ostra Agent's MCP server: an AI agent's own post-quantum USDG vault on Robinhood Chain, with payments, private balances and the Ostra launchpad. The agent needs no ETH.",
5
5
  "keywords": [
6
6
  "mcp",
7
7
  "model-context-protocol",