@ostraagent/mcp 0.1.4 → 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.4", 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 });
@@ -21844,14 +21848,24 @@ var init_private_balance = __esm({
21844
21848
  }
21845
21849
  return { shielded, spends, notes };
21846
21850
  }
21847
- /** The pool's Shielded, Unshielded and PrivateSwapped logs in [from, to]; a range the RPC refuses as too many logs is split in halves. */
21851
+ /**
21852
+ * The pool's Shielded, Unshielded and PrivateSwapped logs in [from, to], one event per request: Robinhood Chain's
21853
+ * public RPC serves LOG_WINDOW_BLOCKS only for one value per topic position, and a request naming the three events
21854
+ * at once over at most 100,000 blocks (measured 23 Oct 2026, error -32602).
21855
+ */
21848
21856
  async #poolLogs(from, to) {
21857
+ const out = [];
21858
+ for (const event of [shieldedEvent, unshieldedEvent, swappedEvent]) out.push(...await this.#eventLogs(event, from, to));
21859
+ return out;
21860
+ }
21861
+ /** The pool's logs of `event` in [from, to]; a range the RPC refuses as too many logs is split in halves. */
21862
+ async #eventLogs(event, from, to) {
21849
21863
  try {
21850
- return await this.#p.logsClient.getLogs({ address: this.#pool, events: [shieldedEvent, unshieldedEvent, swappedEvent], fromBlock: from, toBlock: to, strict: true });
21864
+ return await this.#p.logsClient.getLogs({ address: this.#pool, event, fromBlock: from, toBlock: to, strict: true });
21851
21865
  } catch (err) {
21852
21866
  if (from >= to || !/exceeds limit|too many|query returned more than/i.test(String(err.message ?? err))) throw err;
21853
21867
  const mid = from + (to - from) / 2n;
21854
- return [...await this.#poolLogs(from, mid), ...await this.#poolLogs(mid + 1n, to)];
21868
+ return [...await this.#eventLogs(event, from, mid), ...await this.#eventLogs(event, mid + 1n, to)];
21855
21869
  }
21856
21870
  }
21857
21871
  /** Rebuilds the agent's notes from the seed and the pool's events (see the file comment); the notes kept here become the unspent ones found. */
@@ -21909,6 +21923,7 @@ var init_private_balance = __esm({
21909
21923
  const shields = queue.length;
21910
21924
  const unspent = [];
21911
21925
  const seen = /* @__PURE__ */ new Set();
21926
+ const recipients = /* @__PURE__ */ new Set();
21912
21927
  let followed = 0;
21913
21928
  for (let note = queue.pop(); note !== void 0; note = queue.pop()) {
21914
21929
  if (seen.has(note.commitment.toLowerCase())) continue;
@@ -21920,6 +21935,7 @@ var init_private_balance = __esm({
21920
21935
  unspent.push(note);
21921
21936
  continue;
21922
21937
  }
21938
+ if (spend.kind === "unshield") recipients.add(spend.recipient);
21923
21939
  const change = note.amount - spend.v - spend.fee;
21924
21940
  if (change > 0n) {
21925
21941
  const rho = this.#spendRho("change", note.commitment);
@@ -21944,7 +21960,27 @@ var init_private_balance = __esm({
21944
21960
  const next = counted + 1n > last + 1n ? counted + 1n : last + 1n;
21945
21961
  await this.#p.folder.write(NEXT_RHO, `${next}
21946
21962
  `);
21947
- 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));
21948
21984
  }
21949
21985
  /** Per token: what `notes` hold, how many, and the largest. */
21950
21986
  async #summary(notes) {
@@ -21986,6 +22022,53 @@ var init_private_balance = __esm({
21986
22022
  await this.#p.folder.write(NOTES, `${JSON.stringify({ version: 1, notes })}
21987
22023
  `);
21988
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
+ }
21989
22072
  async #addNotes(add, remove = []) {
21990
22073
  const drop = new Set(remove.map((c) => c.toLowerCase()));
21991
22074
  const notes = (await this.#notes()).filter((n) => !drop.has(n.commitment.toLowerCase()));
@@ -22024,12 +22107,12 @@ var init_private_balance = __esm({
22024
22107
  async #done(id) {
22025
22108
  await this.#p.folder.remove(`${PENDING_PREFIX}${id}.json`);
22026
22109
  }
22027
- /** 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. */
22028
22111
  balance() {
22029
22112
  return this.#serial(async () => {
22030
22113
  const failures = await this.#refresh();
22031
22114
  await this.#ensureRecovered();
22032
- 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() };
22033
22116
  });
22034
22117
  }
22035
22118
  /** Shields `amount` of `token` from the agent's vault of it into a new note (see the file comment). */
@@ -22037,42 +22120,57 @@ var init_private_balance = __esm({
22037
22120
  return this.#serial(async () => {
22038
22121
  const token = address3(a.token, "token");
22039
22122
  this.#checkToken(token);
22040
- const prover = this.#prover();
22041
22123
  const decimals = await tokenDecimals(this.#p.client, token);
22042
22124
  const amount = parseUnitsAmount(a.amount, decimals);
22043
22125
  if (amount === 0n) throw new RangeError("amount must be greater than zero");
22044
- const from = this.#p.vaultFor(token);
22045
- const s = await from.state();
22046
- if (!s.exists && from.kind === "buyer") throw new Error("this agent has no vault yet; call open_vault first");
22047
- 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`);
22048
- if (!s.exists) await from.open();
22049
- const sk = this.#sk();
22050
- await this.#ensureRecovered();
22051
- let pending = null;
22052
- const result = await from.operate({
22053
- gas: PRIVATE_SHIELD_GAS,
22054
- moves: amount,
22055
- what: "shield",
22056
- build: async ({ v, vault, keychain, chainId }) => {
22057
- const balance = await tokenBalance(this.#p.client, token, vault);
22058
- if (balance < amount) throw new Error(`the vault holds ${formatUnitsAmount(balance, decimals)} now, less than ${a.amount}; nothing was signed`);
22059
- const rho = await this.#nextShieldRho();
22060
- const tag = await tagOf(prover, sk, rho);
22061
- const escrow = shieldEscrowAddress(this.#pool, token, tag);
22062
- 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() };
22063
- await this.#keep(pending);
22064
- const signed = await signVaultTransfer(keychain, v, { chainId, to: escrow, amount });
22065
- return [spendCall(vault, signed), shieldFromEscrowCall(this.#pool, token, tag)];
22066
- }
22067
- });
22068
- if (result.status !== "sent") return result;
22069
- const kept = pending;
22070
- if (kept === null) throw new Error("the shield was sent without its record");
22071
- const note = await shieldedNote(prover, sk, kept.rho, await this.#p.client.getTransactionReceipt({ hash: result.txHash }), this.#pool);
22072
- await this.#addNotes([note]);
22073
- await this.#done(kept.id);
22074
- 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);
22127
+ });
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
+ }
22075
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 };
22076
22174
  }
22077
22175
  /** The smallest note of `token` holding at least `need`, or an error naming the largest. */
22078
22176
  async #pick(token, need, decimals, what) {
@@ -22228,11 +22326,19 @@ var init_private_balance = __esm({
22228
22326
  };
22229
22327
  });
22230
22328
  }
22231
- /** 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
+ */
22232
22333
  unshield(a) {
22233
22334
  return this.#serial(async () => {
22234
22335
  const token = address3(a.token, "token");
22235
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
+ }
22236
22342
  this.#requireRelayer();
22237
22343
  const prover = this.#prover();
22238
22344
  await this.#refresh();
@@ -22240,10 +22346,11 @@ var init_private_balance = __esm({
22240
22346
  const decimals = await tokenDecimals(this.#p.client, token);
22241
22347
  const v = parseUnitsAmount(a.amount, decimals);
22242
22348
  if (v === 0n) throw new RangeError("amount must be greater than zero");
22243
- const recipient = a.destination === void 0 ? await this.#p.vaultFor(token).vaultAddress() : address3(a.destination, "destination");
22244
- if (recipient === "0x0000000000000000000000000000000000000000") throw new RangeError("the destination must not be the zero address");
22245
22349
  const { fee: priced } = await this.#price(token, "unshield");
22246
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");
22247
22354
  const sk = this.#sk();
22248
22355
  const changeRho = this.#spendRho("change", note.commitment);
22249
22356
  const r = await this.#spend(note, (fee) => v + fee, "unshield", async (fee, relayer, deadline, trees) => {
@@ -22254,7 +22361,101 @@ var init_private_balance = __esm({
22254
22361
  const change = changeNote(spend, r.receipt, this.#pool);
22255
22362
  await this.#addNotes(change === null ? [] : [change], [note.commitment]);
22256
22363
  await this.#done(spend.terms.nullifier.slice(2, 18));
22257
- 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));
22258
22459
  });
22259
22460
  }
22260
22461
  /** refresh() for the sweep: one at a time with the operations. Returns what is still in flight or failed. */
@@ -22293,7 +22494,7 @@ var init_private_balance = __esm({
22293
22494
  await this.#addNotes([await shieldedNote(prover, sk, p.rho, await this.#p.client.getTransactionReceipt({ hash: l.transactionHash }), this.#pool)]);
22294
22495
  found = true;
22295
22496
  }
22296
- const s = await this.#p.vaultFor(p.token).state();
22497
+ const s = await (p.source === "creator" ? this.#creator() : this.#p.vaultFor(p.token)).state();
22297
22498
  if (s.nonce === null || s.nonce <= BigInt(p.vaultNonce)) return false;
22298
22499
  if (await tokenBalance(this.#p.client, p.token, p.escrow) > 0n) {
22299
22500
  try {
@@ -22346,7 +22547,7 @@ var init_seed_backup = __esm({
22346
22547
  });
22347
22548
 
22348
22549
  // ../core/src/sessions.ts
22349
- 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";
22350
22551
  import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
22351
22552
  var init_sessions = __esm({
22352
22553
  "../core/src/sessions.ts"() {
@@ -22358,7 +22559,7 @@ var init_sessions = __esm({
22358
22559
  });
22359
22560
 
22360
22561
  // ../core/src/shield.ts
22361
- 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";
22362
22563
  function leafKeyIndex(attempt, j) {
22363
22564
  if (!Number.isInteger(attempt) || attempt < 0 || attempt >= MAX_LEAF_ATTEMPTS) throw new RangeError(`leaf set ${String(attempt)} is out of range`);
22364
22565
  if (!Number.isInteger(j) || j < 0 || j >= LEAF_KEYS_PER_ATTEMPT) throw new RangeError(`leaf ${String(j)} is out of range`);
@@ -22369,12 +22570,12 @@ function recordName(chainId, token, index) {
22369
22570
  }
22370
22571
  function decodeApproveMessage(message) {
22371
22572
  if (message.length !== APPROVE_MESSAGE_LEN) throw new Error("not an approval message");
22372
- const chainId = Number(BigInt(bytesToHex13(message.subarray(20, 52))));
22373
- const pool = getAddress27(bytesToHex13(message.subarray(52, 72)));
22374
- const key = bytesToHex13(message.subarray(72, 104));
22375
- const batchId = BigInt(bytesToHex13(message.subarray(104, 112)));
22376
- const state = bytesToHex13(message.subarray(112, 144));
22377
- 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));
22378
22579
  const again = approvalMessage({ chainId, pool, key, batchId, state, nextKeyRoot });
22379
22580
  if (again.length !== message.length || !again.every((x, i) => x === message[i])) throw new Error("not an approval message");
22380
22581
  return { chainId, pool, key, batchId, state, nextKeyRoot };
@@ -22743,7 +22944,7 @@ var init_shield = __esm({
22743
22944
  depositId: this.#keys.shieldDepositId(this.#config.chainId, this.#token(), index),
22744
22945
  amount,
22745
22946
  refundOwner: await this.#vault.vaultAddress(),
22746
- approvalKeyRoot: bytesToHex13(this.#approvalKeys(record).keyRoot(0n))
22947
+ approvalKeyRoot: bytesToHex14(this.#approvalKeys(record).keyRoot(0n))
22747
22948
  };
22748
22949
  if (depositKey(deposit.depositor, deposit.depositId) !== record.deposit) throw new Error("the deposit key does not match the record");
22749
22950
  const validAfter = 0n;
@@ -22846,7 +23047,7 @@ var init_shield = __esm({
22846
23047
  for (let attempt = 0; attempt < 2; attempt++) {
22847
23048
  let index = -1n;
22848
23049
  for (let i = 0n; i < 64n; i++) {
22849
- if (bytesToHex13(keys.keyRoot(i)) === current.approvalKeyRoot.toLowerCase()) {
23050
+ if (bytesToHex14(keys.keyRoot(i)) === current.approvalKeyRoot.toLowerCase()) {
22850
23051
  index = i;
22851
23052
  break;
22852
23053
  }
@@ -23151,7 +23352,7 @@ function view(r) {
23151
23352
  txHash: r.txHash
23152
23353
  };
23153
23354
  }
23154
- 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;
23155
23356
  var init_unshield = __esm({
23156
23357
  "../core/src/unshield.ts"() {
23157
23358
  "use strict";
@@ -23160,7 +23361,6 @@ var init_unshield = __esm({
23160
23361
  init_amount();
23161
23362
  init_random();
23162
23363
  UNSHIELD_DIR = "unshield";
23163
- UNSHIELD_DEFAULT_WINDOW_HOURS = 6;
23164
23364
  UNSHIELD_MAX_WINDOW_HOURS = 24;
23165
23365
  LEAF_SPEND_GAS = 300000n;
23166
23366
  UnshieldService = class {
@@ -23344,7 +23544,7 @@ import {
23344
23544
  getAddress as getAddress30,
23345
23545
  maxUint256,
23346
23546
  parseAbi as parseAbi4,
23347
- zeroAddress as zeroAddress12
23547
+ zeroAddress as zeroAddress13
23348
23548
  } from "viem";
23349
23549
  var MAX_UINT160, MAX_UINT128, universalRouterAbi, permit2Abi, wrappedErrorAbi;
23350
23550
  var init_wallet_swap = __esm({
@@ -23410,7 +23610,7 @@ var init_src3 = __esm({
23410
23610
  import { randomBytes as randomBytes4 } from "node:crypto";
23411
23611
  import { stat as stat2 } from "node:fs/promises";
23412
23612
  import { join as join3 } from "node:path";
23413
- import { bytesToHex as bytesToHex14 } from "viem";
23613
+ import { bytesToHex as bytesToHex15 } from "viem";
23414
23614
  import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
23415
23615
  async function exists(path) {
23416
23616
  try {
@@ -23448,7 +23648,7 @@ async function openKeys(home, escrow) {
23448
23648
  await ensurePrivateDir(join3(home, WOTS_DIR));
23449
23649
  return new Keys({
23450
23650
  master: master.value,
23451
- feePayer: privateKeyToAccount4(bytesToHex14(fee.value)),
23651
+ feePayer: privateKeyToAccount4(bytesToHex15(fee.value)),
23452
23652
  store: new FileWotsStore(join3(home, WOTS_DIR)),
23453
23653
  folder,
23454
23654
  masterCreated: master.created,
@@ -23647,9 +23847,9 @@ var init_cli_prover = __esm({
23647
23847
  });
23648
23848
 
23649
23849
  // ../../ostra-swap/sdk/src/wasm-prover.ts
23650
- import { bytesToHex as bytesToHex15 } from "viem";
23850
+ import { bytesToHex as bytesToHex16 } from "viem";
23651
23851
  async function sha256Hex(bytes) {
23652
- return bytesToHex15(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)));
23852
+ return bytesToHex16(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)));
23653
23853
  }
23654
23854
  async function wasmProver(glue, bytes, sha2562) {
23655
23855
  const got = await sha256Hex(bytes);
@@ -23811,7 +24011,10 @@ var init_agent = __esm({
23811
24011
  vaultFor,
23812
24012
  sender: this.sender,
23813
24013
  folder: keys.folder.folder(PRIVATE_SWAP_DIR),
23814
- 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
23815
24018
  });
23816
24019
  }
23817
24020
  }
@@ -23836,8 +24039,29 @@ var init_agent = __esm({
23836
24039
  const creatorKeys = await keys.forCreatorVault(config.chainId, config.deployment.token);
23837
24040
  const agent = new _Agent({ config, client, transport, keys, creatorKeys, escrow, log: log2 });
23838
24041
  await agent.#loadSeller();
24042
+ await agent.#moveAutoShield();
23839
24043
  return agent;
23840
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
+ }
23841
24065
  /** Private vault `index` (unshield made it), with its own records and relayer client. */
23842
24066
  async privateVault(index) {
23843
24067
  const known = this.#privates.get(index);
@@ -23948,7 +24172,10 @@ var init_agent = __esm({
23948
24172
  }
23949
24173
  await step2("unshield", () => this.unshield.sweep());
23950
24174
  if (this.swap !== null) await step2("ostra swap orders", () => this.swap.finishPending());
23951
- 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
+ }
23952
24179
  } finally {
23953
24180
  this.#sweeping = false;
23954
24181
  }
@@ -24178,19 +24405,6 @@ function registerTools(server, agent) {
24178
24405
  if (!await agent.sellerReady()) throw new Error("this agent has no seller vault yet; call accept_payments first");
24179
24406
  return agent.seller.withdraw(a.destination ?? await agent.buyer.vaultAddress(), a.amount);
24180
24407
  });
24181
- 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));
24182
- tool(
24183
- "unshield",
24184
- `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).`,
24185
- { leaves: z.number().int().min(1).max(64), windowHours: z.number().min(0).max(UNSHIELD_MAX_WINDOW_HOURS).default(UNSHIELD_DEFAULT_WINDOW_HOURS) },
24186
- (a) => agent.unshield.unshield(a.leaves, a.windowHours)
24187
- );
24188
- 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() }));
24189
- 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));
24190
- tool("private_withdraw", "Sends USDG from one of the agent's private vaults.", { vault: address5, destination: address5, amount: usdg }, async (a) => {
24191
- const ctx = await agent.privateVault(await agent.unshield.indexOf(a.vault));
24192
- return ctx.vault.withdraw(a.destination, a.amount);
24193
- });
24194
24408
  tool(
24195
24409
  "ostra_swap_quote",
24196
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.",
@@ -24211,11 +24425,11 @@ function registerTools(server, agent) {
24211
24425
  );
24212
24426
  tool(
24213
24427
  "private_recover",
24214
- "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.",
24215
24429
  {},
24216
24430
  () => notes().recover()
24217
24431
  );
24218
- 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());
24219
24433
  tool(
24220
24434
  "private_swap",
24221
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.",
@@ -24224,9 +24438,29 @@ function registerTools(server, agent) {
24224
24438
  );
24225
24439
  tool(
24226
24440
  "private_unshield",
24227
- "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.",
24228
- { token: swapToken, amount: units, destination: address5.optional() },
24229
- (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
+ }
24230
24464
  );
24231
24465
  tool("recovery_status", "The vault's guardians, recovery policy and any recovery waiting.", {}, () => agent.buyer.recovery());
24232
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));
@@ -24254,7 +24488,7 @@ function registerTools(server, agent) {
24254
24488
  (a) => lp().sell({ token: a.token, amount: a.amount, ...a.slippageBps === void 0 ? {} : { slippageBps: a.slippageBps }, ...a.referrer === void 0 ? {} : { referrer: a.referrer } })
24255
24489
  );
24256
24490
  tool("token_vault", "The agent's vault for a launched token: address, balance, dividends to claim.", { token: address5 }, (a) => lp().tokenVaultInfo(a.token));
24257
- 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));
24258
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));
24259
24493
  tool(
24260
24494
  "claim_referral",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ostraagent/mcp",
3
- "version": "0.1.4",
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",