@ostraagent/mcp 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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.11", deployment: { version: 1, chainId: 4663, fromBlock: "91991509", token: "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168", batchSettlement: "0x4020074e9dF2ce1deE5A9C1b5c3f541D02a10003", vaultImplementation: "0xec0ca4A1393D91c42cA809F7F9dd82Bd597E8E58", vaultBeacon: "0xE9F525B9EaeE48d13AEd28fE146F48f2832a18a0", vaultFactory: "0x043a15c514CFE2c797d33520bFF91a5DCdbf2979", poolImplementation: "0xC04d4a222dC1C966640B977608bE29949b0648ec", poolBeacon: "0x30669Ca534921038a531BBE27116d85bB7406fFF", pool: "0x6ea80F2462e5Fd783B0Ec3B2c7BaC96795980CD6", splitFactory: "0xAAf54A483B2c7c2c5ecFc1eDED5ed1619C1bA882", beaconOwner: "0x55533a51d9bd9b06F2D179d919BB31995EF96E5e", registry: "0xcE6Cc1f93744f4090DA1444Bb8590d9206157506", vaultModule: "0x55A89fF907f5F5756470e2ec5Dc5E4E1C54Da770", launchpad: { hook: "0x63A784afb8905452aDceC471a45E555F5caE68cC", fees: "0x8e79671DC9729476614C78aFcf2345D4c1963801", launchpad: "0xfC57162C0e3f9773F97fEa8d276e957d7c43A6C5", creatorSplit: "0x987994dE6A1d5303B9e3F50977cB0321BA4D033B", dispatcher: "0x44Bb65164b2855f8E80B142d5Fb9bDb72E869986", locker: "0xb593812363bAd70Ee381c7B54c4FC82b0BA3A4A7", devBuy: "0x0e1Fd799E136dcfEcad5695B07Ac5DAc5B2fF4bc", vesting: "0xadD9C1995910457312c156E7D8120D1b447E179a", airdrop: "0xBC798D956aF4c2B104FFC6106d5322294Bc103bb", owner: "0x6569E27A95a6A3E92A311B4Cc6600fed56aA50A2" }, breaker: "0x2E33244dE6A64007204f763e9ef81FC33F1a8e01" }, 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.13", deployment: { version: 1, chainId: 4663, fromBlock: "91991509", token: "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168", batchSettlement: "0x4020074e9dF2ce1deE5A9C1b5c3f541D02a10003", vaultImplementation: "0xec0ca4A1393D91c42cA809F7F9dd82Bd597E8E58", vaultBeacon: "0xE9F525B9EaeE48d13AEd28fE146F48f2832a18a0", vaultFactory: "0x043a15c514CFE2c797d33520bFF91a5DCdbf2979", poolImplementation: "0xC04d4a222dC1C966640B977608bE29949b0648ec", poolBeacon: "0x30669Ca534921038a531BBE27116d85bB7406fFF", pool: "0x6ea80F2462e5Fd783B0Ec3B2c7BaC96795980CD6", splitFactory: "0xAAf54A483B2c7c2c5ecFc1eDED5ed1619C1bA882", beaconOwner: "0x55533a51d9bd9b06F2D179d919BB31995EF96E5e", registry: "0xcE6Cc1f93744f4090DA1444Bb8590d9206157506", vaultModule: "0x55A89fF907f5F5756470e2ec5Dc5E4E1C54Da770", launchpad: { hook: "0x63A784afb8905452aDceC471a45E555F5caE68cC", fees: "0x8e79671DC9729476614C78aFcf2345D4c1963801", launchpad: "0xfC57162C0e3f9773F97fEa8d276e957d7c43A6C5", creatorSplit: "0x987994dE6A1d5303B9e3F50977cB0321BA4D033B", dispatcher: "0x44Bb65164b2855f8E80B142d5Fb9bDb72E869986", locker: "0xb593812363bAd70Ee381c7B54c4FC82b0BA3A4A7", devBuy: "0x0e1Fd799E136dcfEcad5695B07Ac5DAc5B2fF4bc", vesting: "0xadD9C1995910457312c156E7D8120D1b447E179a", airdrop: "0xBC798D956aF4c2B104FFC6106d5322294Bc103bb", owner: "0x6569E27A95a6A3E92A311B4Cc6600fed56aA50A2" }, breaker: "0x2E33244dE6A64007204f763e9ef81FC33F1a8e01" }, 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
 
@@ -13581,6 +13581,14 @@ var init_abi = __esm({
13581
13581
  }
13582
13582
  });
13583
13583
 
13584
+ // ../sdk/src/agent-card.ts
13585
+ var init_agent_card = __esm({
13586
+ "../sdk/src/agent-card.ts"() {
13587
+ "use strict";
13588
+ init_define_OSTRA_PACKAGED();
13589
+ }
13590
+ });
13591
+
13584
13592
  // ../sdk/src/hash.ts
13585
13593
  import { keccak_256 } from "@noble/hashes/sha3.js";
13586
13594
  import { sha256 as sha256Hash } from "@noble/hashes/sha2.js";
@@ -15445,7 +15453,7 @@ var init_morpho = __esm({
15445
15453
  });
15446
15454
 
15447
15455
  // ../sdk/src/borro.ts
15448
- import { getAddress as getAddress5, parseAbi as parseAbi2, zeroAddress as zeroAddress4 } from "viem";
15456
+ import { getAbiItem, getAddress as getAddress5, parseAbi as parseAbi2, zeroAddress as zeroAddress4 } from "viem";
15449
15457
  function borroStocks() {
15450
15458
  return BORRO_STOCKS.map((s) => {
15451
15459
  const market2 = { loanToken: USDG, collateralToken: s.vault, oracle: s.oracle, irm: BORRO_IRM, lltv: s.lltv };
@@ -15466,6 +15474,26 @@ function halfLltvMaxDebt(collateral, price, lltv) {
15466
15474
  const value = collateral * price / 10n ** 36n;
15467
15475
  return value * lltv * VAULT_MAX_LTV_OF_LLTV_BPS / (10n ** 18n * 10000n);
15468
15476
  }
15477
+ async function readHashApprovals(client, logsClient, vault, fromBlock) {
15478
+ const version = Number(await client.readContract({ address: vault, abi: ostraVaultAbi, functionName: "VERSION" }));
15479
+ if (version < VAULT_V3) return [];
15480
+ const event = getAbiItem({ abi: ostraVaultModuleAbi, name: "HashApproved" });
15481
+ const logs = await getLogsChunked(logsClient, { address: vault, event, fromBlock, toBlock: await logsClient.getBlockNumber({ cacheTime: 0 }) });
15482
+ const head = await blockTime(client);
15483
+ const hashes = [];
15484
+ for (const l of logs) {
15485
+ const h = l.args.hash.toLowerCase();
15486
+ if (!hashes.includes(h)) hashes.push(h);
15487
+ }
15488
+ const out = [];
15489
+ for (const hash of hashes) {
15490
+ const validFrom = BigInt(
15491
+ await client.readContract({ address: vault, abi: ostraVaultAbi, functionName: "hashValidFrom", args: [hash], blockNumber: head.blockNumber })
15492
+ );
15493
+ if (validFrom !== 0n) out.push({ hash, validFrom, valid: head.timestamp >= validFrom });
15494
+ }
15495
+ return out;
15496
+ }
15469
15497
  async function readVaultGuard(client, vault) {
15470
15498
  const version = Number(await client.readContract({ address: vault, abi: ostraVaultAbi, functionName: "VERSION" }));
15471
15499
  const off = { withdrawal: zeroAddress4, delay: 0, dailyLimit: 0n };
@@ -15652,21 +15680,54 @@ var init_breaker = __esm({
15652
15680
 
15653
15681
  // ../sdk/src/chain.ts
15654
15682
  import { BaseError as BaseError2, createPublicClient, defineChain, http } from "viem";
15655
- function invalidParams(e) {
15656
- const code = (x) => x.code === -32602;
15657
- return e instanceof BaseError2 ? e.walk(code) !== null : code(e);
15683
+ function laggingRead(e) {
15684
+ const lag = (x) => {
15685
+ const o = x;
15686
+ if (o.code === -32602) return true;
15687
+ return o.code === -32e3 && /unsupported block number/i.test(`${String(o.details ?? "")} ${String(o.message ?? "")}`);
15688
+ };
15689
+ return e instanceof BaseError2 ? e.walk(lag) !== null : lag(e);
15690
+ }
15691
+ function transient(e) {
15692
+ const o = e;
15693
+ if (o === null || typeof o !== "object") return true;
15694
+ if (o.name === "AbortError") return false;
15695
+ if (typeof o.code === "number") return [-1, -32005, -32603, 429, -32007].includes(o.code);
15696
+ if (typeof o.status === "number" && o.status !== 0) return [403, 408, 413, 429, 500, 502, 503, 504].includes(o.status);
15697
+ return true;
15658
15698
  }
15659
- function retryingHttp(url) {
15660
- const base = http(url, { retryCount: 8, retryDelay: 1e3, timeout: 3e4 });
15699
+ function failureText(e, url) {
15700
+ const o = e;
15701
+ const parts = [o?.shortMessage ?? o?.name ?? String(e)];
15702
+ if (typeof o?.code === "number") parts.push(`code ${o.code}`);
15703
+ if (typeof o?.status === "number" && o.status !== 0) parts.push(`HTTP ${o.status}`);
15704
+ if (typeof o?.details === "string" && o.details !== "" && o.details !== parts[0]) parts.push(o.details);
15705
+ return redact(parts.join("; "), url).slice(0, 300);
15706
+ }
15707
+ function retryingHttp(url, o) {
15708
+ const own = o !== void 0;
15709
+ const base = own ? http(url, { retryCount: 0, timeout: o.timeoutMs ?? RPC_TIMEOUT_MS }) : http(url, { retryCount: RPC_RETRIES, retryDelay: RPC_RETRY_WAIT_MS, timeout: RPC_TIMEOUT_MS });
15710
+ const retries = o?.retryCount ?? RPC_RETRIES;
15661
15711
  return ((params) => {
15662
15712
  const t = base(params);
15663
15713
  const request = (async (args, options) => {
15664
- for (let attempt = 0; ; attempt++) {
15714
+ let lagging = 0;
15715
+ let failed = 0;
15716
+ for (; ; ) {
15665
15717
  try {
15666
15718
  return await t.request(args, options);
15667
15719
  } catch (e) {
15668
- if (attempt >= INVALID_PARAMS_RETRIES || !IDEMPOTENT_READS.has(args.method) || !invalidParams(e)) throw e;
15669
- await new Promise((r) => setTimeout(r, INVALID_PARAMS_WAIT_MS * 2 ** attempt));
15720
+ let wait;
15721
+ if (lagging < INVALID_PARAMS_RETRIES && IDEMPOTENT_READS.has(args.method) && laggingRead(e)) {
15722
+ wait = INVALID_PARAMS_WAIT_MS * 2 ** lagging;
15723
+ lagging++;
15724
+ } else if (own && failed < retries && transient(e)) {
15725
+ const after = e.headers?.get?.("Retry-After");
15726
+ wait = after !== null && after !== void 0 && /^[0-9]+$/.test(after) ? Math.min(Number(after) * 1e3, RETRY_AFTER_MAX_MS) : RPC_RETRY_WAIT_MS * 2 ** failed;
15727
+ failed++;
15728
+ } else throw e;
15729
+ o?.onRetry?.({ method: args.method, retry: lagging + failed, waitMs: wait, error: failureText(e, url) });
15730
+ await new Promise((r) => setTimeout(r, wait));
15670
15731
  }
15671
15732
  }
15672
15733
  });
@@ -15683,7 +15744,7 @@ async function assertRobinhoodChain(client) {
15683
15744
  function redact(text3, secret2) {
15684
15745
  return secret2 === "" ? text3 : text3.split(secret2).join("<rpc>");
15685
15746
  }
15686
- var robinhoodChain, IDEMPOTENT_READS, INVALID_PARAMS_RETRIES, INVALID_PARAMS_WAIT_MS;
15747
+ var robinhoodChain, IDEMPOTENT_READS, INVALID_PARAMS_RETRIES, INVALID_PARAMS_WAIT_MS, RPC_RETRIES, RPC_RETRY_WAIT_MS, RPC_TIMEOUT_MS, RETRY_AFTER_MAX_MS;
15687
15748
  var init_chain = __esm({
15688
15749
  "../sdk/src/chain.ts"() {
15689
15750
  "use strict";
@@ -15700,6 +15761,10 @@ var init_chain = __esm({
15700
15761
  IDEMPOTENT_READS = /* @__PURE__ */ new Set(["eth_call", "eth_getCode", "eth_getStorageAt", "eth_getBalance", "eth_getBlockByNumber", "eth_getBlockByHash", "eth_blockNumber", "eth_chainId", "eth_getTransactionReceipt", "eth_getTransactionByHash", "eth_getTransactionCount", "eth_gasPrice"]);
15701
15762
  INVALID_PARAMS_RETRIES = 4;
15702
15763
  INVALID_PARAMS_WAIT_MS = 1e3;
15764
+ RPC_RETRIES = 8;
15765
+ RPC_RETRY_WAIT_MS = 1e3;
15766
+ RPC_TIMEOUT_MS = 3e4;
15767
+ RETRY_AFTER_MAX_MS = 1e4;
15703
15768
  }
15704
15769
  });
15705
15770
 
@@ -19135,7 +19200,7 @@ var init_relayer = __esm({
19135
19200
  init_messages();
19136
19201
  init_external_abi();
19137
19202
  RELAYER_MARGIN_BPS = 1000n;
19138
- RELAYER_QUOTE_TOLERANCE_BPS = 200n;
19203
+ RELAYER_QUOTE_TOLERANCE_BPS = 1000n;
19139
19204
  ETH_USD_DEVIATION_BPS = 50n;
19140
19205
  ETH_USD_MAX_AGE_SECONDS = 90000n;
19141
19206
  MAX_RELAY_CALLS = 8;
@@ -19873,6 +19938,7 @@ var init_browser = __esm({
19873
19938
  "use strict";
19874
19939
  init_define_OSTRA_PACKAGED();
19875
19940
  init_abi();
19941
+ init_agent_card();
19876
19942
  init_aggregator();
19877
19943
  init_airdrop();
19878
19944
  init_backup();
@@ -20406,9 +20472,11 @@ var init_relay = __esm({
20406
20472
  }
20407
20473
  };
20408
20474
  PendingSpendUnpaidError = class extends Error {
20409
- constructor(message) {
20475
+ onCredit;
20476
+ constructor(message, onCredit) {
20410
20477
  super(message);
20411
20478
  this.name = "PendingSpendUnpaidError";
20479
+ this.onCredit = onCredit;
20412
20480
  }
20413
20481
  };
20414
20482
  RelayerError = class extends Error {
@@ -20705,6 +20773,7 @@ var init_relay = __esm({
20705
20773
  async #post(calls, extra, headers = {}) {
20706
20774
  const body2 = { calls: calls.map((c) => ({ to: getAddress17(c.to), data: c.data })) };
20707
20775
  if (extra.prepay === true) body2.prepay = true;
20776
+ if (extra.onCredit === true) body2.onCredit = true;
20708
20777
  if (extra.payer !== null) body2.payer = extra.payer;
20709
20778
  if (extra.allowFailedSwaps === true) body2.allowFailedSwaps = true;
20710
20779
  return this.#http("POST", "/v1/relay", JSON.stringify(body2), headers);
@@ -20899,9 +20968,11 @@ var init_relay = __esm({
20899
20968
  }
20900
20969
  async send(calls, options = {}) {
20901
20970
  await this.facts();
20971
+ const credit = options.onCredit === true;
20972
+ const post2 = async () => this.#post(calls, { prepay: options.prepay === true, payer: credit ? null : await this.#payer(), allowFailedSwaps: options.allowFailedSwaps === true, onCredit: credit });
20902
20973
  let notSent = "";
20903
20974
  for (let resend = 0; resend < MAX_RESENDS; resend++) {
20904
- let answer = await this.#post(calls, { prepay: options.prepay === true, payer: await this.#payer(), allowFailedSwaps: options.allowFailedSwaps === true });
20975
+ let answer = await post2();
20905
20976
  let again = false;
20906
20977
  for (let step2 = 0; step2 < MAX_STEPS && !again; step2++) {
20907
20978
  const b = answer.body;
@@ -20911,14 +20982,15 @@ var init_relay = __esm({
20911
20982
  await this.#landed(hash);
20912
20983
  return hash;
20913
20984
  }
20985
+ if (answer.status === 402 && credit) throw new RelayerError(402, "no_credit", "the relayer does not send this spend on credit (it asks for payment); nothing was sent");
20914
20986
  if (answer.status === 402) {
20915
20987
  answer = await this.#pay(calls, answer, options);
20916
20988
  continue;
20917
20989
  }
20918
20990
  if (answer.status === 403 && (error === "vault_in_debt" || error === "open_unpaid")) {
20919
- if (options.reservation !== void 0 && options.reservation !== null) throw new RelayerError(403, error, String(b?.message ?? error));
20991
+ if (credit || options.reservation !== void 0 && options.reservation !== null) throw new RelayerError(403, error, String(b?.message ?? error));
20920
20992
  await this.#ensureChannel(1n, options.openChannel, null);
20921
- answer = await this.#post(calls, { prepay: options.prepay === true, payer: await this.#payer(), allowFailedSwaps: options.allowFailedSwaps === true });
20993
+ answer = await post2();
20922
20994
  continue;
20923
20995
  }
20924
20996
  if (answer.status === 409 && error === "not_sent") {
@@ -21463,10 +21535,14 @@ var init_vault = __esm({
21463
21535
  * Runs `fn` with what a vault spend needs to be paid. With the relayer: a
21464
21536
  * reservation of unused relayer channel deposit for its price (a channel is
21465
21537
  * opened first if none has room), released once `fn` is done. When a spend
21466
- * signed earlier waits and no relayer channel can pay for it, the local fee
21467
- * payer sends it if it holds the ETH. `moves` is what the spend takes out
21468
- * of this vault: a relayer channel opened from the buyer vault for it
21469
- * leaves that much in the buyer vault.
21538
+ * signed earlier waits and no relayer channel can pay for it (its key is
21539
+ * the one a new channel's open needs), the relayer is asked to send it on
21540
+ * credit, as the vault's debt, and the channel opened next pays that debt
21541
+ * with its first voucher; `fn` then runs as usual and its result names what
21542
+ * was sent first (`sentFirst`). Only when the relayer does not send it so,
21543
+ * the local fee payer sends it if it holds the ETH. `moves` is what the
21544
+ * spend takes out of this vault: a relayer channel opened from the buyer
21545
+ * vault for it leaves that much in the buyer vault.
21470
21546
  */
21471
21547
  async #paidSpend(sender, fn, moves = 0n, gas = VAULT_SPEND_GAS, extraWei = 0n) {
21472
21548
  await this.#finishRecoveryFirst(sender);
@@ -21478,10 +21554,30 @@ var init_vault = __esm({
21478
21554
  reservation = await sender.reserve(gas, sender === this.#sender ? this.#opener() : void 0, this.#paysRelayer() ? moves : 0n, extraWei);
21479
21555
  } catch (err) {
21480
21556
  if (!(err instanceof PendingSpendUnpaidError)) throw err;
21557
+ let refused = "";
21558
+ const credit = err.onCredit;
21559
+ if (sender.relayed && credit !== void 0) {
21560
+ let sentFirst = null;
21561
+ try {
21562
+ sentFirst = await (credit.owner === this ? credit.unqueued() : credit.queued());
21563
+ } catch (e) {
21564
+ if (!(e instanceof RelayerError)) throw e;
21565
+ refused = `; the relayer did not send it on credit (${e.message})`;
21566
+ }
21567
+ if (refused === "") {
21568
+ const again = await sender.reserve(gas, sender === this.#sender ? this.#opener() : void 0, this.#paysRelayer() ? moves : 0n, extraWei);
21569
+ try {
21570
+ const r = await fn(sender, again);
21571
+ return sentFirst !== null && r !== null && typeof r === "object" ? { ...r, sentFirst } : r;
21572
+ } finally {
21573
+ sender.release(again);
21574
+ }
21575
+ }
21576
+ }
21481
21577
  const local = new LocalSender(this.#client, this.#transport, this.#keys.feePayer, this.#keys.escrow);
21482
21578
  const need = gas * await this.#client.getGasPrice() * ETH_HEADROOM + extraWei;
21483
21579
  if (await ethBalance(this.#client, this.#keys.feePayer.address) < need) {
21484
- throw new Error(`${err.message}; send at least ${formatEth(need)} ETH to the fee payer ${this.#keys.feePayer.address} and call again: it sends that operation itself`);
21580
+ throw new Error(`${err.message}${refused}; send at least ${formatEth(need)} ETH to the fee payer ${this.#keys.feePayer.address} and call again: it sends that operation itself`);
21485
21581
  }
21486
21582
  return fn(local, null);
21487
21583
  }
@@ -21491,6 +21587,25 @@ var init_vault = __esm({
21491
21587
  sender.release(reservation);
21492
21588
  }
21493
21589
  }
21590
+ /**
21591
+ * Sends the spend signed earlier through the relayer on credit (SendOptions.onCredit): the vault owes its price, which
21592
+ * the first voucher of the channel opened next pays. Returns what was sent, or null when nothing waits any more.
21593
+ */
21594
+ async #sendPendingOnCredit() {
21595
+ const sender = this.#sender;
21596
+ if (!sender.relayed) return null;
21597
+ const vault = await this.vaultAddress();
21598
+ const v = await this.#read(vault);
21599
+ if (v === null) return null;
21600
+ const credit = {
21601
+ relayed: true,
21602
+ payer: () => sender.payer(),
21603
+ send: (calls, o = {}) => sender.send(calls, { ...o, reservation: null, onCredit: true }),
21604
+ reserve: async () => null,
21605
+ release: () => void 0
21606
+ };
21607
+ return this.#sendPending(vault, v, credit, null);
21608
+ }
21494
21609
  /**
21495
21610
  * Version 3 vaults: the spend is run first with a throwaway key (sdk simulateVaultSpend) and refused before it is
21496
21611
  * signed when it would revert, or land as a failure (a SupportRecovery the other vault refuses). A signed spend that
@@ -21720,7 +21835,11 @@ the signed channel open is kept; the next open_channel or withdraw call sends it
21720
21835
  const v = await this.#read(vault);
21721
21836
  if (v === null) throw new Error(this.#missing());
21722
21837
  if (await this.#pending(v) !== null) {
21723
- throw new PendingSpendUnpaidError("a vault operation signed earlier waits to be sent and no channel to the relayer has deposit left to pay for it");
21838
+ throw new PendingSpendUnpaidError("a vault operation signed earlier waits to be sent and no channel to the relayer has deposit left to pay for it", {
21839
+ owner: this,
21840
+ queued: () => this.#serial(() => this.#sendPendingOnCredit()),
21841
+ unqueued: () => this.#sendPendingOnCredit()
21842
+ });
21724
21843
  }
21725
21844
  await this.#notFrozen(vault, "the vault");
21726
21845
  const balance = await this.#balance(vault);
@@ -22292,6 +22411,26 @@ var init_borro2 = __esm({
22292
22411
  const at = await this.#address(vault, what.toUpperCase());
22293
22412
  return { vault: at, ...await readVaultGuard(this.#p.client, at) };
22294
22413
  }
22414
+ /**
22415
+ * The lock's status (guard) with the hashes the vault approves for EIP-1271 now (sdk readHashApprovals): of the vault
22416
+ * of `what`, or, given `otherText`, of any Ostra vault (a guardian watching the vault it guards). Read only.
22417
+ */
22418
+ async lockStatus(what, otherText) {
22419
+ let at;
22420
+ if (otherText === void 0) {
22421
+ const { vault } = this.#vaultOf(what);
22422
+ at = await this.#address(vault, what.toUpperCase());
22423
+ } else {
22424
+ if (!isAddress8(otherText, { strict: false })) throw new RangeError(`${otherText} is not an address`);
22425
+ at = getAddress19(otherText);
22426
+ if (await readVault(this.#p.client, this.#p.config.deployment, at) === null) throw new Error(`${at} is not an Ostra vault`);
22427
+ }
22428
+ const [g, approvals] = await Promise.all([
22429
+ readVaultGuard(this.#p.client, at),
22430
+ readHashApprovals(this.#p.client, this.#p.logsClient ?? this.#p.client, at, BigInt(this.#p.config.deployment.fromBlock))
22431
+ ]);
22432
+ return { vault: at, ...g, approvals };
22433
+ }
22295
22434
  /**
22296
22435
  * Turns the withdrawal lock of the vault of `what` on (a withdrawal address, a delay, a daily limit in the vault's
22297
22436
  * token) or changes it; `withdrawal` "off" turns it off (that waits the delay). The withdrawal address must keep
@@ -22375,11 +22514,27 @@ var init_borro2 = __esm({
22375
22514
  const names = ["cancel", "revoke", "sweep", "redeem", "withdraw collateral"];
22376
22515
  return this.#spend(this.#p.buyer, VAULT_ACTION.GUARD, guardParams(target, call), `guard (${names[call.op] ?? call.op}) of ${target}`);
22377
22516
  }
22378
- /** Approves (or withdraws) `hash` for EIP-1271 on the buyer vault: under the lock an approval waits its delay. */
22517
+ /**
22518
+ * Approves (or withdraws) `hash` for EIP-1271 on the buyer vault: under the lock an approval waits its delay. For this
22519
+ * package's own digests only (Raeto's login message, OstraBreaker's limits), which it builds and checks itself: an
22520
+ * approved hash makes the vault answer isValidSignature for it, and USDG takes such an answer for a permit and for a
22521
+ * transfer authorization, so a hash from anywhere else could hand the vault's funds out. No tool approves a hash it is
22522
+ * given (withdrawApproval only withdraws).
22523
+ */
22379
22524
  async approveHash(hash, approved) {
22380
22525
  if (!/^0x[0-9a-fA-F]{64}$/.test(hash) || /^0x0{64}$/.test(hash)) throw new RangeError("the hash is 32 bytes of 0x hex, not zero");
22381
22526
  return this.#spend(this.#p.buyer, VAULT_ACTION.APPROVE_HASH, approveHashParams({ hash, approved }), approved ? "EIP-1271 approval" : "EIP-1271 approval withdrawal");
22382
22527
  }
22528
+ /**
22529
+ * Withdraws the EIP-1271 approval of `hash` by the vault of `what` ("USDG", the buyer vault, or a Borro stock's
22530
+ * vault): lands when there is none (nothing to do). Withdrawing is at once, under the lock too.
22531
+ */
22532
+ async withdrawApproval(what, hash) {
22533
+ if (!/^0x[0-9a-fA-F]{64}$/.test(hash) || /^0x0{64}$/.test(hash)) throw new RangeError("the hash is 32 bytes of 0x hex, not zero");
22534
+ const { vault } = this.#vaultOf(what);
22535
+ await this.#address(vault, what.toUpperCase());
22536
+ return this.#spend(vault, VAULT_ACTION.APPROVE_HASH, approveHashParams({ hash, approved: false }), `EIP-1271 approval withdrawal (${what.toUpperCase()} vault)`);
22537
+ }
22383
22538
  };
22384
22539
  }
22385
22540
  });
@@ -23548,24 +23703,17 @@ function imageUri(value) {
23548
23703
  return value;
23549
23704
  }
23550
23705
  function profileFile(p) {
23551
- const services = [];
23552
- const website = link2(p.website, "website");
23553
- const x = link2(p.x, "x");
23554
- const telegram = link2(p.telegram, "telegram");
23555
- if (website !== void 0) services.push({ name: "web", endpoint: website });
23556
- if (x !== void 0) services.push({ name: "X", endpoint: x });
23557
- if (telegram !== void 0) services.push({ name: "Telegram", endpoint: telegram });
23558
- services.push({ name: "agentWallet", endpoint: `eip155:${ROBINHOOD_CHAIN_ID}:${p.vault}` });
23706
+ const id = p.agentId === void 0 || p.agentId === null ? null : p.agentId;
23707
+ if (id !== null && !/^(0|[1-9][0-9]{0,29})$/.test(id)) throw new RangeError("an agent id is a decimal number");
23559
23708
  const image = imageUri(p.image);
23560
- const description = text2(p.description, "description", 1e3);
23561
23709
  return {
23562
23710
  type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
23563
- name: text2(p.name, "name", 64),
23564
- ...description === void 0 ? {} : { description },
23711
+ name: id === null ? AGENT_NAME : `${AGENT_NAME} #${id}`,
23712
+ description: AGENT_DESCRIPTION,
23565
23713
  ...image === void 0 ? {} : { image },
23566
- services,
23714
+ services: [{ name: "agentWallet", endpoint: `eip155:${ROBINHOOD_CHAIN_ID}:${p.vault}` }],
23567
23715
  active: true,
23568
- registrations: p.agentId === void 0 || p.agentId === null ? [] : [{ agentId: Number(p.agentId), agentRegistry: `eip155:${ROBINHOOD_CHAIN_ID}:${ERC8004_IDENTITY_REGISTRY}` }]
23716
+ registrations: id === null ? [] : [{ agentId: Number(id), agentRegistry: `eip155:${ROBINHOOD_CHAIN_ID}:${ERC8004_IDENTITY_REGISTRY}` }]
23569
23717
  };
23570
23718
  }
23571
23719
  function tokenMetadataFile(m) {
@@ -23604,6 +23752,9 @@ function uploaderUrl(relayerUrl) {
23604
23752
  function uploadJson(relayerUrl, value) {
23605
23753
  return post(`${uploaderUrl(relayerUrl)}/json`, JSON.stringify(value), "application/json");
23606
23754
  }
23755
+ function agentCardUri(relayerUrl, agentId) {
23756
+ return post(`${uploaderUrl(relayerUrl)}/agent-card`, JSON.stringify({ agentId }), "application/json");
23757
+ }
23607
23758
  function uploadImage(relayerUrl, bytes) {
23608
23759
  const base = uploaderUrl(relayerUrl);
23609
23760
  if (bytes.length === 0 || bytes.length > MAX_UPLOAD_IMAGE_BYTES) throw new RangeError(`the image must be 1 to ${MAX_UPLOAD_IMAGE_BYTES} bytes (it is ${bytes.length})`);
@@ -23611,13 +23762,15 @@ function uploadImage(relayerUrl, bytes) {
23611
23762
  if (type === null) throw new RangeError("the image is not a PNG, JPEG, WebP or GIF");
23612
23763
  return post(`${base}/image`, new Blob([new Uint8Array(bytes)], { type }), type);
23613
23764
  }
23614
- var MAX_UPLOAD_IMAGE_BYTES;
23765
+ var MAX_UPLOAD_IMAGE_BYTES, AGENT_NAME, AGENT_DESCRIPTION;
23615
23766
  var init_publish = __esm({
23616
23767
  "../core/src/publish.ts"() {
23617
23768
  "use strict";
23618
23769
  init_define_OSTRA_PACKAGED();
23619
23770
  init_src();
23620
23771
  MAX_UPLOAD_IMAGE_BYTES = 1048576;
23772
+ AGENT_NAME = "Ostra Agent";
23773
+ AGENT_DESCRIPTION = "An AI agent with an Ostra post-quantum privacy vault on Robinhood Chain. Its earnings and payments stay in its own vault, signed with one-time keys.";
23621
23774
  }
23622
23775
  });
23623
23776
 
@@ -23652,7 +23805,7 @@ function simulatedGas(gasUsed) {
23652
23805
  const g = gasUsed * 5n / 4n + SPEND_GAS_HEADROOM2;
23653
23806
  return g > VAULT_SPEND_GAS ? g : VAULT_SPEND_GAS;
23654
23807
  }
23655
- var LAUNCHPAD_DIR, CREATE_VAULT_GAS2, LAUNCH_GAS_HEADROOM, PAIRS_READ_CONCURRENCY, SPEND_GAS_HEADROOM2, DAY, TOKEN_LIST_MAX, METADATA_MAX_BYTES, launchedEvent, POOL_SETTING_NAMES, WOTS_GAS_SPREAD, STAND_IN_URI, LaunchpadService;
23808
+ var LAUNCHPAD_DIR, CREATE_VAULT_GAS2, LAUNCH_GAS_HEADROOM, PAIRS_READ_CONCURRENCY, SPEND_GAS_HEADROOM2, DAY, LOGS_CATCH_UP_SECONDS, TOKEN_LIST_MAX, METADATA_MAX_BYTES, launchedEvent, POOL_SETTING_NAMES, WOTS_GAS_SPREAD, STAND_IN_URI, LaunchpadService;
23656
23809
  var init_launchpad = __esm({
23657
23810
  "../core/src/launchpad.ts"() {
23658
23811
  "use strict";
@@ -23673,6 +23826,7 @@ var init_launchpad = __esm({
23673
23826
  PAIRS_READ_CONCURRENCY = 6;
23674
23827
  SPEND_GAS_HEADROOM2 = 100000n;
23675
23828
  DAY = 86400;
23829
+ LOGS_CATCH_UP_SECONDS = 30;
23676
23830
  TOKEN_LIST_MAX = 50;
23677
23831
  METADATA_MAX_BYTES = 65536;
23678
23832
  launchedEvent = parseAbiItem5("event Launched(bytes32 indexed poolId, address indexed token, address indexed creatorVault, address quote)");
@@ -23692,6 +23846,7 @@ var init_launchpad = __esm({
23692
23846
  STAND_IN_URI = `ipfs://bafkrei${"a".repeat(52)}`;
23693
23847
  LaunchpadService = class {
23694
23848
  #client;
23849
+ #logsClient;
23695
23850
  #transport;
23696
23851
  #config;
23697
23852
  #keys;
@@ -23706,6 +23861,7 @@ var init_launchpad = __esm({
23706
23861
  constructor(p) {
23707
23862
  if (p.creator.kind !== "creator" || p.buyer.kind !== "buyer") throw new Error("the launchpad takes the agent's buyer and creator vaults");
23708
23863
  this.#client = p.client;
23864
+ this.#logsClient = p.logsClient ?? p.client;
23709
23865
  this.#transport = p.transport;
23710
23866
  this.#config = p.config;
23711
23867
  this.#keys = p.keys;
@@ -24494,6 +24650,71 @@ var init_launchpad = __esm({
24494
24650
  const text3 = await this.#folder.read("agent.json");
24495
24651
  return text3 === void 0 ? null : JSON.parse(text3);
24496
24652
  }
24653
+ /**
24654
+ * The agent's registration read from the chain when agent.json does not hold it: an identity its creator vault
24655
+ * registered (the Identity Registry's Registered events from the deployment's first block) and still owns. A
24656
+ * registration that landed after its call stopped (the relayer refused the send and a later operation sent the kept
24657
+ * spend, or the process stopped before agent.json was written) is found here and written to agent.json, so the agent
24658
+ * is never registered twice. An identity someone else registered and sent to the creator vault (an ERC-721 transfer
24659
+ * the vault accepts) is no registration of this agent: with no Registered event of the vault, this is null. A
24660
+ * registration sent again just now is read from its receipt instead (#recordFromReceipt), not from logs that may lag.
24661
+ * The vault's identities are counted at the chain's head and the events read up to that same block, after the logs
24662
+ * RPC has reached it: a registration the count holds is never missed because the logs RPC was behind (which would
24663
+ * register the agent twice); when the logs RPC does not reach that block soon, this throws before anything is signed.
24664
+ */
24665
+ async #recoverAgentRecord() {
24666
+ const vault = getAddress24(await this.#creator.vaultAddress());
24667
+ const head = await this.#client.getBlockNumber({ cacheTime: 0 });
24668
+ const owned = await this.#client.readContract({ address: ERC8004_IDENTITY_REGISTRY, abi: identityRegistryAbi, functionName: "balanceOf", args: [vault], blockNumber: head });
24669
+ if (owned === 0n) return null;
24670
+ for (let waited = 0; await this.#logsClient.getBlockNumber({ cacheTime: 0 }) < head; waited++) {
24671
+ if (waited >= LOGS_CATCH_UP_SECONDS) throw new Error(`the logs RPC has not reached block ${head} of the chain RPC after ${LOGS_CATCH_UP_SECONDS} seconds, so the creator vault's ERC-8004 registration cannot be read yet: call again shortly; nothing was signed`);
24672
+ await new Promise((r) => setTimeout(r, 1e3));
24673
+ }
24674
+ const logs = await getLogsChunked(this.#logsClient, { address: ERC8004_IDENTITY_REGISTRY, event: agentRegisteredEvent, args: { owner: vault }, fromBlock: BigInt(this.#config.deployment.fromBlock), toBlock: head });
24675
+ for (const log2 of logs) {
24676
+ const { agentId, agentURI } = log2.args;
24677
+ const who = await readAgentIdentity(this.#client, agentId);
24678
+ if (who === null || getAddress24(who.owner) !== vault) continue;
24679
+ const record = { agentId: agentId.toString(), uri: agentURI, txHash: log2.transactionHash, at: Math.floor(Date.now() / 1e3) };
24680
+ await this.#folder.write("agent.json", JSON.stringify(record, null, 2));
24681
+ return record;
24682
+ }
24683
+ return null;
24684
+ }
24685
+ /**
24686
+ * The registration transaction `txHash` of the creator vault, read from its receipt (the vault's AgentRegistered
24687
+ * event) and written to agent.json; null when it registered nothing. The file it registered with is the Identity
24688
+ * Registry's Registered event's agentURI in that receipt (a registration signed by an earlier call carries that
24689
+ * call's file); `uri` only when the receipt has no such event.
24690
+ */
24691
+ async #recordFromReceipt(txHash, uri2) {
24692
+ const vault = getAddress24(await this.#creator.vaultAddress());
24693
+ const receipt = await this.#client.getTransactionReceipt({ hash: txHash });
24694
+ let agentId;
24695
+ for (const log2 of receipt.logs) {
24696
+ if (getAddress24(log2.address) !== vault) continue;
24697
+ try {
24698
+ const e = decodeEventLog3({ abi: ostraVaultAbi, data: log2.data, topics: log2.topics });
24699
+ if (e.eventName === "AgentRegistered") agentId = e.args.agentId.toString();
24700
+ } catch {
24701
+ }
24702
+ }
24703
+ if (agentId === void 0) return null;
24704
+ let registered;
24705
+ for (const log2 of receipt.logs) {
24706
+ if (getAddress24(log2.address) !== getAddress24(ERC8004_IDENTITY_REGISTRY)) continue;
24707
+ try {
24708
+ const e = decodeEventLog3({ abi: [agentRegisteredEvent], data: log2.data, topics: log2.topics });
24709
+ const a = e.args;
24710
+ if (a.agentId.toString() === agentId && getAddress24(a.owner) === vault) registered = a.agentURI;
24711
+ } catch {
24712
+ }
24713
+ }
24714
+ const record = { agentId, uri: registered ?? uri2, txHash, at: Math.floor(Date.now() / 1e3) };
24715
+ await this.#folder.write("agent.json", JSON.stringify(record, null, 2));
24716
+ return record;
24717
+ }
24497
24718
  /** The agent's identity: its creator vault, the profile URI it set and its ERC-8004 agent id; or, given `account`, that account's profile URI. */
24498
24719
  async profile(accountText) {
24499
24720
  const account = accountText === void 0 ? await this.#creator.vaultAddress() : address2(accountText, "account");
@@ -24510,32 +24731,21 @@ var init_launchpad = __esm({
24510
24731
  /** Registers the creator vault as an agent in the ERC-8004 Identity Registry, its registration file at `uri`; the vault owns the identity and is its agentWallet. */
24511
24732
  async registerAgent(uri2) {
24512
24733
  checkUri(uri2);
24513
- const vault = await this.#identityVault();
24514
- const existing = await this.#agentRecord();
24515
- if (existing !== null) throw new Error(`this agent is registered already as agent ${existing.agentId}: update it with set_agent_uri; nothing was signed`);
24734
+ await this.#identityVault();
24735
+ const existing = await this.#agentRecord() ?? await this.#recoverAgentRecord();
24736
+ if (existing !== null) throw new Error(`this agent is registered already as agent ${existing.agentId}: publish_profile publishes its profile again; nothing was signed`);
24516
24737
  const r = await this.#simulatedSpend(this.#creator, this.#creatorSender, VAULT_ACTION.REGISTER_AGENT, registerAgentParams(uri2), "agent registration");
24517
24738
  if (r.status !== "sent") return r;
24518
- const receipt = await this.#client.getTransactionReceipt({ hash: r.txHash });
24519
- let agentId;
24520
- for (const log2 of receipt.logs) {
24521
- if (getAddress24(log2.address) !== vault) continue;
24522
- try {
24523
- const e = decodeEventLog3({ abi: ostraVaultAbi, data: log2.data, topics: log2.topics });
24524
- if (e.eventName === "AgentRegistered") agentId = e.args.agentId.toString();
24525
- } catch {
24526
- }
24527
- }
24528
- if (agentId === void 0) throw new Error(`the registration ${r.txHash} landed without an AgentRegistered event`);
24529
- const record = { agentId, uri: uri2, txHash: r.txHash, at: Math.floor(Date.now() / 1e3) };
24530
- await this.#folder.write("agent.json", JSON.stringify(record, null, 2));
24531
- return { ...r, agentId };
24739
+ const record = await this.#recordFromReceipt(r.txHash, uri2);
24740
+ if (record === null) throw new Error(`the registration ${r.txHash} landed without an AgentRegistered event`);
24741
+ return { ...r, agentId: record.agentId };
24532
24742
  }
24533
24743
  /** Points the agent's ERC-8004 identity at a new registration file. */
24534
24744
  async setAgentUri(uri2) {
24535
24745
  checkUri(uri2);
24536
24746
  await this.#identityVault();
24537
24747
  const record = await this.#agentRecord();
24538
- if (record === null) throw new Error("this agent has no ERC-8004 identity yet: register_agent first; nothing was signed");
24748
+ if (record === null) throw new Error("this agent has no ERC-8004 identity yet: publish_profile registers it; nothing was signed");
24539
24749
  const r = await this.#simulatedSpend(this.#creator, this.#creatorSender, VAULT_ACTION.SET_AGENT_URI, setAgentUriParams({ agentId: BigInt(record.agentId), uri: uri2 }), "agent registration file");
24540
24750
  if (r.status === "sent") await this.#folder.write("agent.json", JSON.stringify({ ...record, uri: uri2, at: Math.floor(Date.now() / 1e3) }, null, 2));
24541
24751
  return r;
@@ -24561,15 +24771,39 @@ var init_launchpad = __esm({
24561
24771
  if (version < VAULT_VERSION) throw new Error(`the creator vault ${s.vault} runs version ${version}, not ${VAULT_VERSION}; nothing was signed`);
24562
24772
  }
24563
24773
  /**
24564
- * Publishes the agent's profile: builds its ERC-8004 registration file (name, description, image, links, the
24565
- * creator vault as its agentWallet, its registration once it has one), pins it through the uploader, sets it in
24566
- * OstraRegistry, and points the ERC-8004 identity at it too when the agent is registered.
24774
+ * Publishes the agent's Ostra profile, the same for every Ostra agent (profileFile: its name and number, Ostra's
24775
+ * description, its Ostra card, its creator vault as agentWallet), and registers the agent in ERC-8004 when it is not
24776
+ * registered yet: the profile without a number is published, the creator vault registers the agent with it, and the
24777
+ * profile is published again with the agent's number and card, its ERC-8004 identity pointed at it. Called again, it
24778
+ * carries on from where it stopped (what the chain and agent.json hold decides).
24567
24779
  */
24568
- async publishProfile(p) {
24569
- const vault = await this.#identityVault();
24780
+ async publishProfile() {
24781
+ const before = await this.#agentRecord() ?? await this.#recoverAgentRecord();
24782
+ if (before !== null) return { agentId: before.agentId, registration: null, unnumbered: null, ...await this.#publishProfileFile(before) };
24783
+ const first = await this.#publishProfileFile(null);
24784
+ const unnumbered = { uri: first.uri, profile: first.profile };
24785
+ if (first.profile.status !== "sent") {
24786
+ const resent = first.profile.resent;
24787
+ const landed = resent.operation === "vault_spend" && resent.action === VAULT_ACTION.REGISTER_AGENT ? await this.#recordFromReceipt(resent.txHash, first.uri) : null;
24788
+ if (landed !== null) return { agentId: landed.agentId, registration: null, unnumbered, ...await this.#publishProfileFile(landed) };
24789
+ const after = await this.#agentRecord() ?? await this.#recoverAgentRecord();
24790
+ if (after !== null) return { agentId: after.agentId, registration: null, unnumbered, ...await this.#publishProfileFile(after) };
24791
+ }
24792
+ const registration = await this.registerAgent(first.uri);
24570
24793
  const record = await this.#agentRecord();
24571
- const file = profileFile({ ...p, vault, agentId: record?.agentId ?? null });
24794
+ if (registration.status !== "sent" || record === null) return { agentId: null, registration, unnumbered, ...first };
24795
+ return { agentId: record.agentId, registration, unnumbered, ...await this.#publishProfileFile(record) };
24796
+ }
24797
+ /**
24798
+ * Builds the profile of the agent (`record`: its registration, or null before it has one), pins it through the
24799
+ * uploader, sets it in OstraRegistry (for the creator vaults of the other pairs it launched from too), and points the
24800
+ * ERC-8004 identity at it when the agent is registered.
24801
+ */
24802
+ async #publishProfileFile(record) {
24803
+ const vault = await this.#identityVault();
24804
+ const agentId = record?.agentId ?? null;
24572
24805
  uploaderUrl(this.#config.relayerUrl);
24806
+ const file = profileFile({ vault, agentId, image: await agentCardUri(this.#config.relayerUrl, agentId) });
24573
24807
  const beforeUri = await this.#profileUri(vault);
24574
24808
  const before = await readIpfsJson(beforeUri);
24575
24809
  if (beforeUri !== null && beforeUri.startsWith("ipfs://") && before === null) throw new Error(`the profile published before (${beforeUri}) cannot be read now, so its Ostrapad listing could not be kept: try again later; nothing was signed`);
@@ -24927,7 +25161,7 @@ var init_pad2 = __esm({
24927
25161
  publish(offers) {
24928
25162
  return this.#serial(async () => {
24929
25163
  const { agentId, creatorVault } = await this.#p.identity();
24930
- if (agentId === null) throw new Error("this agent is not registered in ERC-8004 yet: publish_profile, then register_agent; nothing was signed");
25164
+ if (agentId === null) throw new Error("this agent is not registered in ERC-8004 yet: publish_profile registers it; nothing was signed");
24931
25165
  const vault = await this.#vault();
24932
25166
  const current = await this.#p.currentFile();
24933
25167
  if (current === null) throw new Error("this agent has no published profile yet: publish_profile first; nothing was signed");
@@ -25843,7 +26077,7 @@ var init_raeto = __esm({
25843
26077
  await this.#keepApprovals((await this.#keptApprovals()).filter((h) => h !== hash));
25844
26078
  return { done: true, note };
25845
26079
  }
25846
- return { done: false, note: `${note}; the approval may still read or land: call raeto login again to withdraw it, or vault_approve_hash ${hash} approved false` };
26080
+ return { done: false, note: `${note}; the approval may still read or land: call raeto login again to withdraw it, or vault_revoke_hash ${hash}` };
25847
26081
  }
25848
26082
  /**
25849
26083
  * Signs the vault in to Raeto as an agent (see the file comment): its own Raeto account and API key, kept here. With a
@@ -26024,10 +26258,10 @@ var init_raeto = __esm({
26024
26258
  }
26025
26259
  });
26026
26260
 
26027
- // ../../ostra-swap/sdk/src/abi.ts
26261
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/abi.ts
26028
26262
  var ostraRouterAbi, ostraStakeAbi, ostraPrivatePoolAbi, OSTRA_SWAP_ESCROW_CREATION_CODE, OSTRA_SHIELD_ESCROW_CREATION_CODE, ostraQuoteBatchAbi, OSTRA_QUOTE_BATCH_CREATION_CODE;
26029
26263
  var init_abi2 = __esm({
26030
- "../../ostra-swap/sdk/src/abi.ts"() {
26264
+ "../../../../../../srv/apps/ostra-swap/sdk/src/abi.ts"() {
26031
26265
  "use strict";
26032
26266
  init_define_OSTRA_PACKAGED();
26033
26267
  ostraRouterAbi = [
@@ -28892,10 +29126,10 @@ var init_abi2 = __esm({
28892
29126
  }
28893
29127
  });
28894
29128
 
28895
- // ../../ostra-swap/sdk/src/constants.ts
29129
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/constants.ts
28896
29130
  var LOG_WINDOW_BLOCKS2, USDG2, WETH2, HUBS, ETH, UNISWAP_V3_FACTORY, UNISWAP_V3_QUOTER_V2, V3_FEE_TIERS, UNISWAP_V4_POOL_MANAGER2, UNISWAP_V4_QUOTER, MULTICALL32, OSTRA_HOOK, PONS_HOOK2, ROUTABLE_HOOKS, FEE_TIERS, COMMAND, V4_ACTION, CONTRACT_BALANCE, MSG_SENDER, ADDRESS_THIS, OPEN_DELTA, QUOTE_GAS_LIMIT, QUOTES_PER_CALL, QUOTE_GAS_LIMIT_RETRY, QUOTES_PER_RETRY_CALL, QUOTE_BATCH_OVERHEAD_GAS, V3_MIN_SQRT_PRICE_LIMIT, V3_MAX_SQRT_PRICE_LIMIT, DEFAULT_SLIPPAGE_BPS, MAX_SLIPPAGE_BPS, DEFAULT_ORDER_TTL_SECONDS;
28897
29131
  var init_constants2 = __esm({
28898
- "../../ostra-swap/sdk/src/constants.ts"() {
29132
+ "../../../../../../srv/apps/ostra-swap/sdk/src/constants.ts"() {
28899
29133
  "use strict";
28900
29134
  init_define_OSTRA_PACKAGED();
28901
29135
  LOG_WINDOW_BLOCKS2 = 10000000n;
@@ -28937,11 +29171,11 @@ var init_constants2 = __esm({
28937
29171
  }
28938
29172
  });
28939
29173
 
28940
- // ../../ostra-swap/sdk/src/uniswap.ts
29174
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/uniswap.ts
28941
29175
  import { parseAbi as parseAbi7, parseAbiItem as parseAbiItem6 } from "viem";
28942
29176
  var v3FactoryAbi, v3PoolAbi, quoterV2Abi, poolKeyComponents, v4QuoterAbi, exactInputSingleParam, poolManagerAbi, initializeEvent, erc20Abi, multicall3Abi2;
28943
29177
  var init_uniswap = __esm({
28944
- "../../ostra-swap/sdk/src/uniswap.ts"() {
29178
+ "../../../../../../srv/apps/ostra-swap/sdk/src/uniswap.ts"() {
28945
29179
  "use strict";
28946
29180
  init_define_OSTRA_PACKAGED();
28947
29181
  v3FactoryAbi = parseAbi7(["function getPool(address tokenA, address tokenB, uint24 fee) view returns (address)"]);
@@ -29033,7 +29267,7 @@ var init_uniswap = __esm({
29033
29267
  }
29034
29268
  });
29035
29269
 
29036
- // ../../ostra-swap/sdk/src/legs.ts
29270
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/legs.ts
29037
29271
  import { concatHex, encodeAbiParameters as encodeAbiParameters10, encodePacked as encodePacked4, getAddress as getAddress28, toHex as toHex5 } from "viem";
29038
29272
  function legFor(pool, tokenIn, tokenOut, recipient = MSG_SENDER) {
29039
29273
  const inAddr = getAddress28(tokenIn);
@@ -29070,7 +29304,7 @@ function legFor(pool, tokenIn, tokenOut, recipient = MSG_SENDER) {
29070
29304
  }
29071
29305
  var NO_LEG, byte;
29072
29306
  var init_legs = __esm({
29073
- "../../ostra-swap/sdk/src/legs.ts"() {
29307
+ "../../../../../../srv/apps/ostra-swap/sdk/src/legs.ts"() {
29074
29308
  "use strict";
29075
29309
  init_define_OSTRA_PACKAGED();
29076
29310
  init_constants2();
@@ -29080,7 +29314,7 @@ var init_legs = __esm({
29080
29314
  }
29081
29315
  });
29082
29316
 
29083
- // ../../ostra-swap/sdk/src/pools.ts
29317
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/pools.ts
29084
29318
  import { encodeAbiParameters as encodeAbiParameters11, encodeFunctionData as encodeFunctionData12, decodeFunctionResult as decodeFunctionResult3, getAddress as getAddress29, hexToBigInt as hexToBigInt2, keccak256 as keccak25617, toHex as toHex6, zeroAddress as zeroAddress16 } from "viem";
29085
29319
  function stateSlot(id, offset) {
29086
29320
  const base = hexToBigInt2(keccak25617(encodeAbiParameters11([{ type: "bytes32" }, { type: "uint256" }], [id, POOLS_SLOT3])));
@@ -29143,7 +29377,7 @@ async function hubPools(client, logsClient, index, token, hub) {
29143
29377
  }
29144
29378
  var POOLS_SLOT3, LIQUIDITY_OFFSET2, PoolIndex;
29145
29379
  var init_pools2 = __esm({
29146
- "../../ostra-swap/sdk/src/pools.ts"() {
29380
+ "../../../../../../srv/apps/ostra-swap/sdk/src/pools.ts"() {
29147
29381
  "use strict";
29148
29382
  init_define_OSTRA_PACKAGED();
29149
29383
  init_constants2();
@@ -29189,7 +29423,7 @@ var init_pools2 = __esm({
29189
29423
  }
29190
29424
  });
29191
29425
 
29192
- // ../../ostra-swap/sdk/src/route.ts
29426
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/route.ts
29193
29427
  import { decodeAbiParameters as decodeAbiParameters5, decodeFunctionResult as decodeFunctionResult4, encodeDeployData as encodeDeployData2, encodeFunctionData as encodeFunctionData13, getAddress as getAddress30 } from "viem";
29194
29428
  function routedToken(token) {
29195
29429
  const t = getAddress30(token);
@@ -29313,7 +29547,7 @@ function routeLegs(route) {
29313
29547
  }
29314
29548
  var NoRouteError, quoteResults;
29315
29549
  var init_route = __esm({
29316
- "../../ostra-swap/sdk/src/route.ts"() {
29550
+ "../../../../../../srv/apps/ostra-swap/sdk/src/route.ts"() {
29317
29551
  "use strict";
29318
29552
  init_define_OSTRA_PACKAGED();
29319
29553
  init_abi2();
@@ -29339,7 +29573,7 @@ var init_route = __esm({
29339
29573
  }
29340
29574
  });
29341
29575
 
29342
- // ../../ostra-swap/sdk/src/orders.ts
29576
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/orders.ts
29343
29577
  import { encodeAbiParameters as encodeAbiParameters12, encodeFunctionData as encodeFunctionData14, getAddress as getAddress31, getContractAddress as getContractAddress4, isAddress as isAddress14, keccak256 as keccak25618, toHex as toHex7 } from "viem";
29344
29578
  function parseSwapDeployment(json) {
29345
29579
  const r = json;
@@ -29393,7 +29627,7 @@ function executeEscrowData(order, route) {
29393
29627
  }
29394
29628
  var ZERO, MAX_UINT32, MAX_UINT64, MAX_STANDING_ORDER_SECONDS, legParam, orderParam;
29395
29629
  var init_orders = __esm({
29396
- "../../ostra-swap/sdk/src/orders.ts"() {
29630
+ "../../../../../../srv/apps/ostra-swap/sdk/src/orders.ts"() {
29397
29631
  "use strict";
29398
29632
  init_define_OSTRA_PACKAGED();
29399
29633
  init_abi2();
@@ -29432,8 +29666,8 @@ var init_orders = __esm({
29432
29666
  }
29433
29667
  });
29434
29668
 
29435
- // ../../ostra-swap/sdk/src/private.ts
29436
- import { encodeAbiParameters as encodeAbiParameters13, encodeFunctionData as encodeFunctionData15, getAbiItem, getAddress as getAddress32, getContractAddress as getContractAddress5, keccak256 as keccak25619, parseEventLogs, toHex as toHex8 } from "viem";
29669
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/private.ts
29670
+ import { encodeAbiParameters as encodeAbiParameters13, encodeFunctionData as encodeFunctionData15, getAbiItem as getAbiItem2, getAddress as getAddress32, getContractAddress as getContractAddress5, keccak256 as keccak25619, parseEventLogs, toHex as toHex8 } from "viem";
29437
29671
  function packDigest(elements) {
29438
29672
  if (elements.length !== 8) throw new Error("a digest has 8 field elements");
29439
29673
  let v = 0n;
@@ -29632,7 +29866,7 @@ async function swapOutputNote(prover, sk, outRho, spend, swap, receipt, pool) {
29632
29866
  }
29633
29867
  var FIELD_P, TREE_DEPTH, PRIVATE_SWAP_FEE_BPS, UNSHIELD_DOMAIN, SWAP_DOMAIN, MAX_AMOUNT, MAX_UINT642, ZERO2, noteAddedEvent, PoolTrees;
29634
29868
  var init_private = __esm({
29635
- "../../ostra-swap/sdk/src/private.ts"() {
29869
+ "../../../../../../srv/apps/ostra-swap/sdk/src/private.ts"() {
29636
29870
  "use strict";
29637
29871
  init_define_OSTRA_PACKAGED();
29638
29872
  init_abi2();
@@ -29647,7 +29881,7 @@ var init_private = __esm({
29647
29881
  MAX_AMOUNT = 1n << 128n;
29648
29882
  MAX_UINT642 = (1n << 64n) - 1n;
29649
29883
  ZERO2 = "0x0000000000000000000000000000000000000000";
29650
- noteAddedEvent = getAbiItem({ abi: ostraPrivatePoolAbi, name: "NoteAdded" });
29884
+ noteAddedEvent = getAbiItem2({ abi: ostraPrivatePoolAbi, name: "NoteAdded" });
29651
29885
  PoolTrees = class {
29652
29886
  #data;
29653
29887
  /** The sync running now: syncs run one at a time (each one's rollback assumes no other changes the trees). */
@@ -29748,19 +29982,19 @@ var init_private = __esm({
29748
29982
  }
29749
29983
  });
29750
29984
 
29751
- // ../../ostra-swap/sdk/src/reads.ts
29985
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/reads.ts
29752
29986
  import { getAddress as getAddress33 } from "viem";
29753
29987
  var init_reads2 = __esm({
29754
- "../../ostra-swap/sdk/src/reads.ts"() {
29988
+ "../../../../../../srv/apps/ostra-swap/sdk/src/reads.ts"() {
29755
29989
  "use strict";
29756
29990
  init_define_OSTRA_PACKAGED();
29757
29991
  init_abi2();
29758
29992
  }
29759
29993
  });
29760
29994
 
29761
- // ../../ostra-swap/sdk/src/index.ts
29995
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/index.ts
29762
29996
  var init_src2 = __esm({
29763
- "../../ostra-swap/sdk/src/index.ts"() {
29997
+ "../../../../../../srv/apps/ostra-swap/sdk/src/index.ts"() {
29764
29998
  "use strict";
29765
29999
  init_define_OSTRA_PACKAGED();
29766
30000
  init_abi2();
@@ -31437,7 +31671,7 @@ var init_strategy2 = __esm({
31437
31671
  if (who !== null && getAddress37(who.owner) === creatorVault && getAddress37(who.wallet) === creatorVault) return { agentId: id.toString(), creatorVault };
31438
31672
  }
31439
31673
  throw new StrategyGateError(
31440
- `Ostra's strategies are for agents registered in the ERC-8004 Identity Registry through Ostra: the creator vault ${creatorVault} must own the agent's identity and be its agentWallet${ids.length === 0 ? " (it has none)" : ` (agent ${ids.map(String).join(", ")} is not)`}. Register with register_agent (after publish_profile), then ask again.`
31674
+ `Ostra's strategies are for agents registered in the ERC-8004 Identity Registry through Ostra: the creator vault ${creatorVault} must own the agent's identity and be its agentWallet${ids.length === 0 ? " (it has none)" : ` (agent ${ids.map(String).join(", ")} is not)`}. Register with publish_profile (it registers the agent), then ask again.`
31441
31675
  );
31442
31676
  }
31443
31677
  // -------------------------------------------------------------------------------------------------------------------
@@ -32307,7 +32541,7 @@ var init_strategy2 = __esm({
32307
32541
  });
32308
32542
 
32309
32543
  // ../core/src/swap.ts
32310
- import { decodeErrorResult as decodeErrorResult2, decodeEventLog as decodeEventLog4, getAbiItem as getAbiItem2, getAddress as getAddress38 } from "viem";
32544
+ import { decodeErrorResult as decodeErrorResult2, decodeEventLog as decodeEventLog4, getAbiItem as getAbiItem3, getAddress as getAddress38 } from "viem";
32311
32545
  function refusalOf(reason, outDecimals) {
32312
32546
  if (reason === "0x") return "the swap reverted without a reason (out of gas, or a revert with no data)";
32313
32547
  try {
@@ -32336,7 +32570,7 @@ function refusalOf(reason, outDecimals) {
32336
32570
  return `revert data ${reason.slice(0, 2 + 8 * 2)}${reason.length > 18 ? "\u2026" : ""} (not one of OstraRouter's errors: from a pool or the Universal Router)`;
32337
32571
  }
32338
32572
  }
32339
- var SWAP_DIR, ESCROW_SWAP_GAS, DEFAULT_SWAP_SLIPPAGE_BPS, MAX_SWAP_SLIPPAGE_BPS, DEFAULT_SWAP_MAX_IMPACT_BPS, MAX_SWAP_MAX_IMPACT_BPS, SPLIT_MAX_PARTS, SPLIT_INTERVAL_SECONDS, SPLIT_SLACK_SECONDS, POOL_INDEX2, ORDER_PREFIX, SPLIT_PREFIX, address4, SwapService;
32573
+ var SWAP_DIR, ESCROW_SWAP_GAS, DEFAULT_SWAP_SLIPPAGE_BPS, MAX_SWAP_SLIPPAGE_BPS, DEFAULT_SWAP_MAX_IMPACT_BPS, MAX_SWAP_MAX_IMPACT_BPS, SPLIT_MAX_PARTS, SPLIT_INTERVAL_SECONDS, SPLIT_SLACK_SECONDS, POOL_INDEX2, ORDER_PREFIX, SPLIT_PREFIX, DONE_PREFIX, DONE_ORDERS_KEPT, address4, SwapService;
32340
32574
  var init_swap = __esm({
32341
32575
  "../core/src/swap.ts"() {
32342
32576
  "use strict";
@@ -32360,6 +32594,8 @@ var init_swap = __esm({
32360
32594
  POOL_INDEX2 = "pool-index.json";
32361
32595
  ORDER_PREFIX = "order-";
32362
32596
  SPLIT_PREFIX = "split-";
32597
+ DONE_PREFIX = "done-";
32598
+ DONE_ORDERS_KEPT = 100;
32363
32599
  address4 = (text3, what) => {
32364
32600
  if (!/^0x[0-9a-fA-F]{40}$/.test(text3)) throw new RangeError(`${what} is not an address`);
32365
32601
  return getAddress38(text3);
@@ -32572,7 +32808,23 @@ var init_swap = __esm({
32572
32808
  const escrow = escrowAddress(this.#p.swap.router, order);
32573
32809
  const execute = executeEscrowData(order, p.route);
32574
32810
  await p.from.checkSpend(VAULT_ACTION.TRANSFER, transferParams({ to: escrow, amount: p.amountIn }), "the swap's transfer to its escrow", this.#p.sender);
32575
- await this.#keep({ version: 1, hash: orderHash(order), escrow, tokenIn: p.tokenIn, amountIn: p.amountIn.toString(), vaultNonce: v.nonce.toString(), deadline: order.deadline.toString(), execute, at: Date.now() });
32811
+ await this.#keep({
32812
+ version: 1,
32813
+ hash: orderHash(order),
32814
+ escrow,
32815
+ tokenIn: p.tokenIn,
32816
+ amountIn: p.amountIn.toString(),
32817
+ vaultNonce: v.nonce.toString(),
32818
+ deadline: order.deadline.toString(),
32819
+ execute,
32820
+ at: Date.now(),
32821
+ fromBlock: fromBlock.toString(),
32822
+ tokenOut: p.tokenOut,
32823
+ from: vault,
32824
+ to: p.to,
32825
+ inDecimals: p.inDecimals,
32826
+ outDecimals: p.outDecimals
32827
+ });
32576
32828
  await o.onOrder?.(orderHash(order), fromBlock, escrow);
32577
32829
  const signed = await signVaultTransfer(keychain, v, { chainId, to: escrow, amount: p.amountIn });
32578
32830
  const calls = [spendCall(vault, signed), { to: this.#p.swap.router, data: execute }];
@@ -32601,6 +32853,21 @@ var init_swap = __esm({
32601
32853
  note: `the swap landed in ${result.txHash}; what it paid could not be read now (${e.message.split("\n")[0]}): do not send it again, its order ${orderHash(placed2)} is read again later`
32602
32854
  };
32603
32855
  }
32856
+ await this.#keepDone({
32857
+ version: 1,
32858
+ order: orderHash(placed2),
32859
+ txHash: result.txHash,
32860
+ swapped: outcome.swapped,
32861
+ tokenIn: p.tokenIn,
32862
+ tokenOut: p.tokenOut,
32863
+ amountIn: formatUnitsAmount(p.amountIn, p.inDecimals),
32864
+ amountOut: formatUnitsAmount(outcome.amountOut, p.outDecimals),
32865
+ minOut: formatUnitsAmount(p.route.minOut, p.outDecimals),
32866
+ from: p.fromVault,
32867
+ to: p.to,
32868
+ ...outcome.swapped ? {} : { refusal: outcome.why ?? "OstraRouter gave no reason" },
32869
+ at: Date.now()
32870
+ });
32604
32871
  await this.#p.folder.remove(`${ORDER_PREFIX}${orderHash(placed2)}.json`);
32605
32872
  return {
32606
32873
  status: "sent",
@@ -32891,7 +33158,7 @@ var init_swap = __esm({
32891
33158
  */
32892
33159
  async orderResult(order, fromBlock, escrow) {
32893
33160
  const toBlock = await this.#p.logsClient.getBlockNumber({ cacheTime: 0 });
32894
- const logs = await getLogsChunked(this.#p.logsClient, { address: this.#p.swap.router, event: getAbiItem2({ abi: ostraRouterAbi, name: "EscrowExecuted" }), args: { orderHash: order }, fromBlock, toBlock });
33161
+ const logs = await getLogsChunked(this.#p.logsClient, { address: this.#p.swap.router, event: getAbiItem3({ abi: ostraRouterAbi, name: "EscrowExecuted" }), args: { orderHash: order }, fromBlock, toBlock });
32895
33162
  let spent = 0n;
32896
33163
  let received = 0n;
32897
33164
  let moved = false;
@@ -32937,6 +33204,92 @@ var init_swap = __esm({
32937
33204
  if (swapped) return { swapped, amountOut };
32938
33205
  return { swapped, amountOut: 0n, ...reason === null ? {} : { why: refusalOf(reason, outDecimals) } };
32939
33206
  }
33207
+ /** Keeps a finished order for ostra_swap_orders, and drops the oldest beyond DONE_ORDERS_KEPT. */
33208
+ async #keepDone(d) {
33209
+ await this.#p.folder.ensure();
33210
+ await this.#p.folder.write(`${DONE_PREFIX}${d.order.toLowerCase()}.json`, `${JSON.stringify(d)}
33211
+ `);
33212
+ const names = (await this.#p.folder.list()).filter((n) => n.startsWith(DONE_PREFIX) && n.endsWith(".json"));
33213
+ if (names.length <= DONE_ORDERS_KEPT) return;
33214
+ const dated = [];
33215
+ for (const n of names) {
33216
+ const text3 = await this.#p.folder.read(n);
33217
+ dated.push({ name: n, at: text3 === void 0 ? 0 : Number(JSON.parse(text3).at) });
33218
+ }
33219
+ dated.sort((a, b) => a.at - b.at);
33220
+ for (const x of dated.slice(0, dated.length - DONE_ORDERS_KEPT)) await this.#p.folder.remove(x.name);
33221
+ }
33222
+ /**
33223
+ * Keeps what a kept order did once the sweep finishes it: from the transaction that executed it (`tx`), else from
33224
+ * OstraRouter's events from the record's block. Best effort: an order whose events cannot be read now is not kept
33225
+ * (a record from before 0.1.12 has no block to search from).
33226
+ */
33227
+ async #doneFrom(r, tx) {
33228
+ try {
33229
+ let hash = tx;
33230
+ if (hash === null && r.fromBlock !== void 0) {
33231
+ const toBlock = await this.#p.logsClient.getBlockNumber({ cacheTime: 0 });
33232
+ const logs = await getLogsChunked(this.#p.logsClient, { address: this.#p.swap.router, event: getAbiItem3({ abi: ostraRouterAbi, name: "EscrowExecuted" }), args: { orderHash: r.hash }, fromBlock: BigInt(r.fromBlock), toBlock });
33233
+ const moved = logs.find((l) => l.args.received !== 0n);
33234
+ hash = moved === void 0 ? null : moved.transactionHash;
33235
+ }
33236
+ if (hash === null) return;
33237
+ const inDecimals = r.inDecimals ?? await tokenDecimals(this.#p.client, r.tokenIn);
33238
+ const outDecimals = r.outDecimals ?? (r.tokenOut === void 0 ? 18 : await tokenDecimals(this.#p.client, r.tokenOut));
33239
+ const o = await this.#outcome(hash, r.hash, outDecimals);
33240
+ await this.#keepDone({
33241
+ version: 1,
33242
+ order: r.hash,
33243
+ txHash: hash,
33244
+ swapped: o.swapped,
33245
+ tokenIn: r.tokenIn,
33246
+ tokenOut: r.tokenOut ?? null,
33247
+ amountIn: formatUnitsAmount(BigInt(r.amountIn), inDecimals),
33248
+ amountOut: formatUnitsAmount(o.amountOut, outDecimals),
33249
+ minOut: null,
33250
+ from: r.from ?? null,
33251
+ to: r.to ?? null,
33252
+ ...o.swapped ? {} : { refusal: o.why ?? "OstraRouter gave no reason" },
33253
+ at: Date.now()
33254
+ });
33255
+ } catch {
33256
+ }
33257
+ }
33258
+ /**
33259
+ * The agent's orders kept here: those not finished (signed or about to be, or landed and being finished; oldest
33260
+ * first) and the finished ones (newest first, at most `limit`). With `order`, that order alone in either list.
33261
+ */
33262
+ async orders(o = {}) {
33263
+ const want = o.order?.toLowerCase();
33264
+ const open3 = [];
33265
+ const finished = [];
33266
+ for (const name of await this.#p.folder.list()) {
33267
+ const isOrder = name.startsWith(ORDER_PREFIX);
33268
+ if (!isOrder && !name.startsWith(DONE_PREFIX) || !name.endsWith(".json")) continue;
33269
+ if (want !== void 0 && name.slice((isOrder ? ORDER_PREFIX : DONE_PREFIX).length, -".json".length).toLowerCase() !== want) continue;
33270
+ const text3 = await this.#p.folder.read(name);
33271
+ if (text3 === void 0) continue;
33272
+ if (!isOrder) {
33273
+ finished.push(JSON.parse(text3));
33274
+ continue;
33275
+ }
33276
+ const r = JSON.parse(text3);
33277
+ const inDecimals = r.inDecimals ?? await tokenDecimals(this.#p.client, r.tokenIn);
33278
+ const x = { order: r.hash, tokenIn: r.tokenIn, tokenOut: r.tokenOut ?? null, amountIn: formatUnitsAmount(BigInt(r.amountIn), inDecimals), escrow: r.escrow, state: "open", at: r.at };
33279
+ if (r.fromBlock !== void 0) {
33280
+ const res = await this.orderResult(r.hash, BigInt(r.fromBlock), r.escrow);
33281
+ if (res.spent > 0n || res.received > 0n) {
33282
+ x.state = "landed";
33283
+ x.spent = formatUnitsAmount(res.spent, inDecimals);
33284
+ x.received = formatUnitsAmount(res.received, r.outDecimals ?? 18);
33285
+ }
33286
+ }
33287
+ open3.push(x);
33288
+ }
33289
+ open3.sort((a, b) => a.at - b.at);
33290
+ finished.sort((a, b) => b.at - a.at);
33291
+ return { open: open3, finished: finished.slice(0, o.limit ?? 20) };
33292
+ }
32940
33293
  /**
32941
33294
  * Finishes the orders whose escrow was paid without being executed (a signed spend sent again on its own):
32942
33295
  * executeEscrow sends the input along the order's route, or back to its vault past the deadline. Records of orders
@@ -32953,11 +33306,15 @@ var init_swap = __esm({
32953
33306
  const nonce = (await this.#p.vaultFor(r.tokenIn).state()).nonce;
32954
33307
  const held = await tokenBalance(this.#p.client, r.tokenIn, r.escrow);
32955
33308
  if (held >= BigInt(r.amountIn)) {
32956
- await this.#p.sender.send([{ to: this.#p.swap.router, data: r.execute }]);
33309
+ const tx = await this.#p.sender.send([{ to: this.#p.swap.router, data: r.execute }]);
33310
+ await this.#doneFrom(r, tx);
32957
33311
  await this.#p.folder.remove(name);
32958
33312
  continue;
32959
33313
  }
32960
- if (nonce !== null && nonce > BigInt(r.vaultNonce)) await this.#p.folder.remove(name);
33314
+ if (nonce !== null && nonce > BigInt(r.vaultNonce)) {
33315
+ await this.#doneFrom(r, null);
33316
+ await this.#p.folder.remove(name);
33317
+ }
32961
33318
  } catch (err) {
32962
33319
  failures.push({ order: r.hash, error: err instanceof Error ? err.message : String(err) });
32963
33320
  }
@@ -33821,7 +34178,7 @@ var init_backup_cli = __esm({
33821
34178
  }
33822
34179
  });
33823
34180
 
33824
- // ../../ostra-swap/sdk/src/cli-prover.ts
34181
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/cli-prover.ts
33825
34182
  import { spawn } from "node:child_process";
33826
34183
  function cliProver(binary, timeoutMs = PROVER_TIMEOUT_MS) {
33827
34184
  return (command, request) => new Promise((resolve, reject) => {
@@ -33862,14 +34219,14 @@ function cliProver(binary, timeoutMs = PROVER_TIMEOUT_MS) {
33862
34219
  }
33863
34220
  var PROVER_TIMEOUT_MS;
33864
34221
  var init_cli_prover = __esm({
33865
- "../../ostra-swap/sdk/src/cli-prover.ts"() {
34222
+ "../../../../../../srv/apps/ostra-swap/sdk/src/cli-prover.ts"() {
33866
34223
  "use strict";
33867
34224
  init_define_OSTRA_PACKAGED();
33868
34225
  PROVER_TIMEOUT_MS = 12e4;
33869
34226
  }
33870
34227
  });
33871
34228
 
33872
- // ../../ostra-swap/sdk/src/wasm-prover.ts
34229
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/wasm-prover.ts
33873
34230
  import { bytesToHex as bytesToHex17 } from "viem";
33874
34231
  async function sha256Hex(bytes) {
33875
34232
  return bytesToHex17(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)));
@@ -33905,7 +34262,7 @@ async function wasmProver(glue, bytes, sha2564) {
33905
34262
  }
33906
34263
  var WasmProverTrapped, initialized, trapped;
33907
34264
  var init_wasm_prover = __esm({
33908
- "../../ostra-swap/sdk/src/wasm-prover.ts"() {
34265
+ "../../../../../../srv/apps/ostra-swap/sdk/src/wasm-prover.ts"() {
33909
34266
  "use strict";
33910
34267
  init_define_OSTRA_PACKAGED();
33911
34268
  WasmProverTrapped = class extends Error {
@@ -34026,11 +34383,19 @@ var init_agent = __esm({
34026
34383
  this.creator = new VaultService(client, transport, config, p.creatorKeys, creatorSender, "creator");
34027
34384
  this.creatorRelayer?.attachBuyerVault(this.creator);
34028
34385
  this.creatorChannels = new ChannelService(client, config, p.creatorKeys, this.creator, creatorSender);
34029
- this.launchpad = config.deployment.launchpad === void 0 ? null : new LaunchpadService({ client, transport, config, keys, buyer: this.buyer, buyerSender: this.sender, creator: this.creator, creatorSender });
34386
+ this.launchpad = config.deployment.launchpad === void 0 ? null : new LaunchpadService({ client, transport, config, keys, buyer: this.buyer, buyerSender: this.sender, creator: this.creator, creatorSender, logsClient: createPublicClient2({ chain: robinhoodChain, transport: http2(config.logsRpcUrl, { retryCount: 6, retryDelay: 2e3 }) }) });
34030
34387
  this.shield = new ShieldService(client, config, keys, this.buyer, this.sender);
34031
34388
  this.creatorShield = new ShieldService(client, config, keys, this.creator, creatorSender);
34032
34389
  this.unshield = new UnshieldService(client, config, keys, this.shield, (index) => this.privateVault(index));
34033
- this.borro = new BorroService({ client, config, buyer: this.buyer, tokenVault: (token) => this.vaultFor(token), sender: this.sender, ownVaults: () => this.ownVaults() });
34390
+ this.borro = new BorroService({
34391
+ client,
34392
+ config,
34393
+ buyer: this.buyer,
34394
+ tokenVault: (token) => this.vaultFor(token),
34395
+ sender: this.sender,
34396
+ ownVaults: () => this.ownVaults(),
34397
+ logsClient: createPublicClient2({ chain: robinhoodChain, transport: http2(config.logsRpcUrl, { retryCount: 6, retryDelay: 2e3 }) })
34398
+ });
34034
34399
  this.breaker = new BreakerService({ client, config, buyer: this.buyer, borro: this.borro, sender: this.sender, folder: keys.folder.folder("breaker") });
34035
34400
  this.raeto = config.raetoUrl === null ? null : new RaetoService({ client, buyer: this.buyer, borro: this.borro, folder: keys.folder.folder("raeto"), apiUrl: config.raetoUrl });
34036
34401
  if (config.swap === null) {
@@ -34325,6 +34690,12 @@ async function proverConfig(env) {
34325
34690
  }
34326
34691
  return { kind: "wasm", dir, sha256: sha2564 };
34327
34692
  }
34693
+ function swapAnswerMs(env) {
34694
+ const v = env.OSTRA_SWAP_ANSWER_MS;
34695
+ if (v === void 0 || v === "") return DEFAULT_SWAP_ANSWER_MS;
34696
+ if (!/^[1-9][0-9]{0,5}$/.test(v) || Number(v) > 6e5) throw new Error("OSTRA_SWAP_ANSWER_MS is a whole number of milliseconds from 1 to 600000");
34697
+ return Number(v);
34698
+ }
34328
34699
  function serviceUrl(env, name, fallback) {
34329
34700
  const value = env[name];
34330
34701
  if (value === void 0 || value === "") return fallback;
@@ -34374,6 +34745,7 @@ async function loadConfig(env = process.env) {
34374
34745
  swap,
34375
34746
  logsRpcUrl,
34376
34747
  privateLocalSpends: env.OSTRA_PRIVATE_LOCAL_SPENDS === "yes",
34748
+ swapAnswerMs: swapAnswerMs(env),
34377
34749
  prover,
34378
34750
  chainId: deployment.chainId,
34379
34751
  rpcUrl: rpcUrl ?? ROBINHOOD_PUBLIC_RPC,
@@ -34385,7 +34757,7 @@ async function loadConfig(env = process.env) {
34385
34757
  aiUrl: serviceUrl(env, "OSTRA_AI_URL", services?.aiUrl ?? null)
34386
34758
  };
34387
34759
  }
34388
- var PROVER_WASM_FILES, PAD_INDEXER_URL, ETHEREUM_PUBLIC_RPC;
34760
+ var PROVER_WASM_FILES, PAD_INDEXER_URL, ETHEREUM_PUBLIC_RPC, DEFAULT_SWAP_ANSWER_MS;
34389
34761
  var init_config2 = __esm({
34390
34762
  "src/config.ts"() {
34391
34763
  "use strict";
@@ -34398,10 +34770,12 @@ var init_config2 = __esm({
34398
34770
  PROVER_WASM_FILES = ["ostra_prove.js", "ostra_prove_bg.wasm"];
34399
34771
  PAD_INDEXER_URL = "https://app.ostraagent.xyz";
34400
34772
  ETHEREUM_PUBLIC_RPC = "https://ethereum-rpc.publicnode.com";
34773
+ DEFAULT_SWAP_ANSWER_MS = 4e4;
34401
34774
  }
34402
34775
  });
34403
34776
 
34404
34777
  // src/tools.ts
34778
+ import { randomBytes as randomBytes6 } from "node:crypto";
34405
34779
  import { readFile as readFile6, stat as stat4, writeFile as writeFile3 } from "node:fs/promises";
34406
34780
  import { join as join7 } from "node:path";
34407
34781
  import { z } from "zod";
@@ -34445,7 +34819,79 @@ function registerTools(server, agent) {
34445
34819
  return agent.privateBalance;
34446
34820
  };
34447
34821
  const opt = (key, value) => value === void 0 ? {} : { [key]: value };
34448
- const tool = (name, description, shape, fn) => CLOSED_LAUNCHPAD_TOOLS.has(name) ? void 0 : server.registerTool(name, { description, inputSchema: shape }, ((args) => run2(() => fn(args), [agent.config.rpcUrl, agent.config.logsRpcUrl, ...agent.config.ethRpcUrl === null ? [] : [agent.config.ethRpcUrl]])));
34822
+ const rpcUrls2 = [agent.config.rpcUrl, agent.config.logsRpcUrl, ...agent.config.ethRpcUrl === null ? [] : [agent.config.ethRpcUrl]];
34823
+ const tool = (name, description, shape, fn) => CLOSED_LAUNCHPAD_TOOLS.has(name) ? void 0 : server.registerTool(name, { description, inputSchema: shape }, ((args) => run2(() => fn(args), rpcUrls2)));
34824
+ const answerMs = agent.config.swapAnswerMs;
34825
+ const swaps = /* @__PURE__ */ new Map();
34826
+ const swapView = (r) => ({
34827
+ swap: r.id,
34828
+ what: r.what,
34829
+ tokenIn: r.tokenIn,
34830
+ tokenOut: r.tokenOut,
34831
+ amount: r.amount,
34832
+ startedAt: new Date(r.startedAt).toISOString(),
34833
+ state: r.endedAt === void 0 ? "running" : r.error === void 0 ? "ended" : "failed",
34834
+ // False while its call still waits for it (a client that gave up on that call learns the result here).
34835
+ answeredPending: r.answered,
34836
+ ...r.order === void 0 ? {} : { order: r.order },
34837
+ ...r.split === void 0 ? {} : { split: r.split },
34838
+ ...r.endedAt === void 0 ? {} : { endedAt: new Date(r.endedAt).toISOString() },
34839
+ ...r.result === void 0 ? {} : { result: r.result },
34840
+ ...r.error === void 0 ? {} : { error: r.error }
34841
+ });
34842
+ const boundedSwap = async (a, start) => {
34843
+ const busy = [...swaps.values()].find((x) => x.endedAt === void 0 && x.tokenIn.toLowerCase() === a.tokenIn.toLowerCase());
34844
+ if (busy !== void 0) {
34845
+ throw new Error(`a swap from the agent's vault of ${a.tokenIn} is still running (${busy.what} of ${busy.amount}, begun ${new Date(busy.startedAt).toISOString()}): nothing was signed for this call; ostra_swap_orders shows how that one ends, call again once it has`);
34846
+ }
34847
+ const r = { id: randomBytes6(8).toString("hex"), ...a, startedAt: Date.now(), answered: false };
34848
+ swaps.set(r.id, r);
34849
+ const work = Promise.resolve().then(() => start({
34850
+ onOrder: async (order) => {
34851
+ r.order = order;
34852
+ },
34853
+ onSplit: async (id) => {
34854
+ r.split = id;
34855
+ }
34856
+ }));
34857
+ let timer;
34858
+ const late = new Promise((resolve) => {
34859
+ timer = setTimeout(() => resolve(null), answerMs);
34860
+ });
34861
+ let first;
34862
+ try {
34863
+ first = await Promise.race([work.then((value) => ({ value })), late]);
34864
+ } catch (err) {
34865
+ swaps.delete(r.id);
34866
+ throw err;
34867
+ } finally {
34868
+ clearTimeout(timer);
34869
+ }
34870
+ if (first !== null) {
34871
+ swaps.delete(r.id);
34872
+ return first.value;
34873
+ }
34874
+ r.answered = true;
34875
+ work.then(
34876
+ (value) => {
34877
+ r.result = value;
34878
+ r.endedAt = Date.now();
34879
+ },
34880
+ (err) => {
34881
+ r.error = rpcUrls2.reduce((text3, url) => redact(text3, url), err instanceof Error ? err.message : String(err));
34882
+ r.endedAt = Date.now();
34883
+ }
34884
+ );
34885
+ const ended = [...swaps.values()].filter((x) => x.endedAt !== void 0).sort((x, y) => x.endedAt - y.endedAt);
34886
+ for (const x of ended.slice(0, Math.max(0, ended.length - ENDED_SWAPS_KEPT))) swaps.delete(x.id);
34887
+ return {
34888
+ status: "pending",
34889
+ swap: r.id,
34890
+ order: r.order ?? null,
34891
+ split: r.split ?? null,
34892
+ note: `${a.what} is still running after ${answerMs / 1e3} s and goes on in the MCP server: do not send it again. ostra_swap_orders shows how it ends (swap ${r.id}${r.order === void 0 ? "" : `, order ${r.order}`}${r.split === void 0 ? "" : `; split swap ${r.split}: ostra_swap_split list`}).`
34893
+ };
34894
+ };
34449
34895
  tool("open_vault", "Creates the agent's vault (USDG on Robinhood Chain, post-quantum WOTS keys) or finds it. With the Ostra relayer, its creation is paid later from the vault.", {}, () => agent.buyer.open());
34450
34896
  tool("balance", "The vault's balance, nonce, any operation signed and not landed, the relayer channels, and the creator vault.", {}, async () => ({
34451
34897
  vault: await agent.buyer.status(),
@@ -34539,9 +34985,27 @@ function registerTools(server, agent) {
34539
34985
  );
34540
34986
  tool(
34541
34987
  "ostra_swap",
34542
- "Swaps on Ostra Swap from the agent's vault of tokenIn to its vault of tokenOut, as an escrow order of OstraRouter: the vault signs one transfer into the order's escrow and the relayer executes it in the same transaction. The router pays at least minOut or sends the input back; where the tokens go is fixed by the escrow's address. A swap that moves the price more than maxImpactBps (default 1%) is split into up to 16 parts 60 seconds apart, each its own escrow order quoted again right before it is signed: a part waits while it would move the price more than the limit or pay less than the first part's quote less the slippage. The first part runs now and the next ones while the MCP server runs (ostra_swap_split); what is not swapped by the deadline stays in the vault.",
34988
+ `Swaps on Ostra Swap from the agent's vault of tokenIn to its vault of tokenOut, as an escrow order of OstraRouter: the vault signs one transfer into the order's escrow and the relayer executes it in the same transaction. The router pays at least minOut or sends the input back; where the tokens go is fixed by the escrow's address. A swap that moves the price more than maxImpactBps (default 1%) is split into up to 16 parts 60 seconds apart, each its own escrow order quoted again right before it is signed: a part waits while it would move the price more than the limit or pay less than the first part's quote less the slippage. The first part runs now and the next ones while the MCP server runs (ostra_swap_split); what is not swapped by the deadline stays in the vault. A swap not done within ${answerMs / 1e3} s answers status "pending" and goes on: do not send it again; ostra_swap_orders tells how it ended.`,
34543
34989
  { tokenIn: swapToken, tokenOut: swapToken, amount: units.describe("in tokenIn"), slippageBps: slippage2, maxImpactBps: maxImpact },
34544
- (a) => ostraSwap().swap({ tokenIn: tokenAddress(a.tokenIn), tokenOut: tokenAddress(a.tokenOut), amount: a.amount, ...opt("slippageBps", a.slippageBps), ...opt("maxImpactBps", a.maxImpactBps) })
34990
+ (a) => {
34991
+ const tokenIn = tokenAddress(a.tokenIn);
34992
+ const tokenOut = tokenAddress(a.tokenOut);
34993
+ return boundedSwap(
34994
+ { what: "the swap", tokenIn, tokenOut, amount: a.amount },
34995
+ (hooks) => ostraSwap().swap({ tokenIn, tokenOut, amount: a.amount, ...opt("slippageBps", a.slippageBps), ...opt("maxImpactBps", a.maxImpactBps) }, hooks)
34996
+ );
34997
+ }
34998
+ );
34999
+ tool(
35000
+ "ostra_swap_orders",
35001
+ "The agent's Ostra Swap orders: swaps of this MCP server still running (their call waiting, or answered pending) and those that answered pending and then ended or failed, orders kept and not finished (signed or about to be, or landed and being finished by the MCP server's sweep each minute), and the last finished ones with what they spent and paid (refusal: why one did not swap, its input back in the vault). order: that order alone. Nothing is signed.",
35002
+ { order: bytes323.optional() },
35003
+ async (a) => {
35004
+ const kept = await ostraSwap().orders(a.order === void 0 ? {} : { order: a.order });
35005
+ const want = a.order?.toLowerCase();
35006
+ const pending = [...swaps.values()].filter((x) => want === void 0 || x.order?.toLowerCase() === want).sort((x, y) => y.startedAt - x.startedAt);
35007
+ return { pending: pending.map(swapView), open: kept.open, finished: kept.finished };
35008
+ }
34545
35009
  );
34546
35010
  tool(
34547
35011
  "ostra_swap_split",
@@ -34642,10 +35106,11 @@ function registerTools(server, agent) {
34642
35106
  );
34643
35107
  tool(
34644
35108
  "vault_lock",
34645
- "The withdrawal lock of the vault (USDG) or of the agent's vault of a Borro stock. status: the lock, the daily limit left, the linked vault and the changes waiting. set: turns the lock on or changes it (withdrawal: a wallet whose key is not on this device, or another seed's Ostra vault in the same token that is locked already; never one of this agent's vaults; delaySeconds 129600 to 1209600; dailyLimit in the vault's token): turning it on and tightening are at once, loosening waits the delay. off: turns it off after the delay. cancel: drops the changes still waiting.",
35109
+ "The withdrawal lock of the vault (USDG) or of the agent's vault of a Borro stock. status: the lock, the daily limit left, the linked vault, the changes waiting, and the hashes the vault approves for EIP-1271 (approvals: each hash, from when it counts and whether that time has come; a hash can be a permit or a transfer authorization of the vault's funds; under the lock no flow of this server approves anything, so every hash listed then came from elsewhere and should go; without the lock the list also holds this server's own approvals, such as Ostrapad's, which look alike: vault_revoke_hash withdraws one, a guardian of a locked vault revokes it with guard_vault); with vault, the same for any Ostra vault (a guardian watching the vault it guards). set: turns the lock on or changes it (withdrawal: a wallet whose key is not on this device, or another seed's Ostra vault in the same token that is locked already; never one of this agent's vaults; delaySeconds 129600 to 1209600; dailyLimit in the vault's token): turning it on and tightening are at once, loosening waits the delay. off: turns it off after the delay. cancel: drops the changes still waiting.",
34646
35110
  {
34647
35111
  action: z.enum(["status", "set", "off", "cancel"]),
34648
35112
  asset: borroAsset.optional().describe("USDG (default) or a Borro stock's ticker"),
35113
+ vault: address5.optional().describe("status only: another Ostra vault to read"),
34649
35114
  withdrawal: address5.optional(),
34650
35115
  delaySeconds: z.number().int().min(129600).max(1209600).optional(),
34651
35116
  dailyLimit: units.optional(),
@@ -34653,7 +35118,8 @@ function registerTools(server, agent) {
34653
35118
  },
34654
35119
  async (a) => {
34655
35120
  const asset = a.asset ?? "USDG";
34656
- if (a.action === "status") return agent.borro.guard(asset);
35121
+ if (a.vault !== void 0 && a.action !== "status") throw new RangeError("vault is for status only: set, off and cancel act on this agent's own vaults");
35122
+ if (a.action === "status") return agent.borro.lockStatus(asset, a.vault);
34657
35123
  if (a.action === "cancel") return agent.borro.cancelPending(asset);
34658
35124
  if (a.action === "off") return agent.borro.setLock(asset, "off", 0, "0");
34659
35125
  if (a.withdrawal === void 0 || a.delaySeconds === void 0 || a.dailyLimit === void 0) throw new RangeError("set takes withdrawal, delaySeconds and dailyLimit");
@@ -34677,10 +35143,10 @@ function registerTools(server, agent) {
34677
35143
  (a) => agent.borro.setLink(a.asset ?? "USDG", a.vault)
34678
35144
  );
34679
35145
  tool(
34680
- "vault_approve_hash",
34681
- "Approves a 32-byte hash for EIP-1271 on the vault (isValidSignature answers for it), or withdraws an approval. Under the lock an approval waits the lock's delay; a new lock and every recovery end all approvals.",
34682
- { hash: bytes323, approved: z.boolean() },
34683
- (a) => agent.borro.approveHash(a.hash, a.approved)
35146
+ "vault_revoke_hash",
35147
+ "Withdraws the EIP-1271 approval of a 32-byte hash by the vault (USDG) or by the agent's vault of a Borro stock (vault_lock status lists them); at once, under the lock too; lands as done when there is none. No tool approves a hash it is given: USDG takes the vault's EIP-1271 answer as a permit or a transfer authorization of its funds, so the vault only approves digests this server builds and checks itself (Raeto's login message, OstraBreaker's limits, Ostrapad's actions).",
35148
+ { hash: bytes323, asset: borroAsset.optional().describe("USDG (default) or a Borro stock's ticker") },
35149
+ (a) => agent.borro.withdrawApproval(a.asset ?? "USDG", a.hash)
34684
35150
  );
34685
35151
  tool(
34686
35152
  "guard_vault",
@@ -34706,7 +35172,7 @@ function registerTools(server, agent) {
34706
35172
  const earnings = z.record(z.string().regex(/^[A-Z]{1,8}$/), z.number().int().min(0)).optional().describe("earnings dates the agent knows, unix seconds by stock ticker (without one NVDA stays at most 60% of its LLTV)");
34707
35173
  tool(
34708
35174
  "strategy_plan",
34709
- "Plans what the agent's vaults hold, computed from the chain now: a conservative plan (USDG into a cash vault that passes every check of Strategy 1) and, when a carry of the agent's SPY, QQQ or NVDA may open, a balanced one (Strategy 2: the stock lent to Borro as collateral, USDG borrowed into the cash vault), each with its steps, gas, what it earns in 30 days net of gas, risks, when it stops, and whether OstraBreaker watches (Strategy 3). Every step earns over 3x its gas in 30 days or is left out. The trading capital's cash (trading) stays in the vault. Nothing is signed. Only for an agent registered in ERC-8004 (register_agent).",
35175
+ "Plans what the agent's vaults hold, computed from the chain now: a conservative plan (USDG into a cash vault that passes every check of Strategy 1) and, when a carry of the agent's SPY, QQQ or NVDA may open, a balanced one (Strategy 2: the stock lent to Borro as collateral, USDG borrowed into the cash vault), each with its steps, gas, what it earns in 30 days net of gas, risks, when it stops, and whether OstraBreaker watches (Strategy 3). Every step earns over 3x its gas in 30 days or is left out. The trading capital's cash (trading) stays in the vault. Nothing is signed. Only for an agent registered in ERC-8004 (publish_profile).",
34710
35176
  { buffer: usdg.optional().describe("USDG kept for payments (default: what the vault spent over 7 days)"), earnings },
34711
35177
  (a) => agent.strategy.plan({
34712
35178
  ...a.buffer === void 0 ? {} : { buffer: parseUsdg(a.buffer) },
@@ -34728,7 +35194,7 @@ function registerTools(server, agent) {
34728
35194
  const bpsLimit = z.number().int().min(1).max(1e4).optional();
34729
35195
  tool(
34730
35196
  "trading",
34731
- `The trading capital: USDG of the vault set aside for trades the agent decides, with Ostra's limits (an agent may only tighten them): a position at most 25% of the capital (positionMaxBps) and no larger than a buy whose quote moves the price 1% (maxImpactBps); buys stop for the day after a 5% loss of the capital that day (dailyLossBps) and for the week after 10% that week (weeklyLossBps). set: sets it (capital in USDG, the limits), only with no position open; the plans keep its cash out of the cash vaults. status: its value now (cash, positions at the quote of selling them), whether buys may happen and, with token, the most that position may be. buy: buys token with amount USDG of the capital, refused past those limits. sell: sells amount (or "all") of the capital's token, split like any large swap. end: ends it (positions stay in their vaults, untracked). Trades go through Ostra Swap; only for an agent registered in ERC-8004 (register_agent) to set it or buy.`,
35197
+ `The trading capital: USDG of the vault set aside for trades the agent decides, with Ostra's limits (an agent may only tighten them): a position at most 25% of the capital (positionMaxBps) and no larger than a buy whose quote moves the price 1% (maxImpactBps); buys stop for the day after a 5% loss of the capital that day (dailyLossBps) and for the week after 10% that week (weeklyLossBps). set: sets it (capital in USDG, the limits), only with no position open; the plans keep its cash out of the cash vaults. status: its value now (cash, positions at the quote of selling them), whether buys may happen and, with token, the most that position may be. buy: buys token with amount USDG of the capital, refused past those limits. sell: sells amount (or "all") of the capital's token, split like any large swap. A buy or sell not done in time (${agent.config.swapAnswerMs / 1e3} s) answers status "pending" and goes on: do not send it again; ostra_swap_orders tells how it ended. end: ends it (positions stay in their vaults, untracked). Trades go through Ostra Swap; only for an agent registered in ERC-8004 (publish_profile) to set it or buy.`,
34732
35198
  {
34733
35199
  action: z.enum(["set", "status", "buy", "sell", "end"]),
34734
35200
  capital: usdg.optional(),
@@ -34758,9 +35224,11 @@ function registerTools(server, agent) {
34758
35224
  const token = tokenAddress(a.token);
34759
35225
  if (a.action === "buy") {
34760
35226
  if (a.amount === "all") throw new RangeError("buy takes an amount of USDG");
34761
- return t.buy({ token, amount: a.amount, ...opt("slippageBps", a.slippageBps) });
35227
+ const amount2 = a.amount;
35228
+ return boundedSwap({ what: "the trading buy", tokenIn: USDG, tokenOut: token, amount: amount2 }, () => t.buy({ token, amount: amount2, ...opt("slippageBps", a.slippageBps) }));
34762
35229
  }
34763
- return t.sell({ token, amount: a.amount, ...opt("slippageBps", a.slippageBps) });
35230
+ const amount = a.amount;
35231
+ return boundedSwap({ what: "the trading sell", tokenIn: token, tokenOut: USDG, amount }, () => t.sell({ token, amount, ...opt("slippageBps", a.slippageBps) }));
34764
35232
  }
34765
35233
  );
34766
35234
  tool("strategy_report", "What the strategies earn: yield per day by route at the rates now, the share growth since the first run that held each cash vault, and each carry's distance to liquidation.", {}, () => agent.strategy.report());
@@ -34829,7 +35297,7 @@ function registerTools(server, agent) {
34829
35297
  const files = z.array(z.string().max(200).regex(/^ipfs:\/\/[A-Za-z0-9]+(\/[A-Za-z0-9._-]+)*$/)).max(10).optional().describe("ipfs:// files (upload_image makes one)");
34830
35298
  tool(
34831
35299
  "pad_publish",
34832
- "Ostrapad as a provider. publish: lists offers in the agent's ERC-8004 registration file, paid into its USDG vault (the vault approves the listing once, so a listing is its own); needs publish_profile and register_agent first, and USDG in the creator vault for its relayed transactions (checked before the approval). unpublish: removes the listing. status: the agent's own listing as clients read it. bond / withdraw_bond: a voluntary provider bond (raises the limit per job to the bond, up to 500 USDG; a rejected job costs it 10% of the budget plus the evaluator fee). return_bond_box: returns USDG left in the provider bond box (a deposit whose second step failed) to the USDG vault.",
35300
+ "Ostrapad as a provider. publish: lists offers in the agent's ERC-8004 registration file, paid into its USDG vault (the vault approves the listing once, so a listing is its own); needs publish_profile first (it registers the agent), and USDG in the creator vault for its relayed transactions (checked before the approval). unpublish: removes the listing. status: the agent's own listing as clients read it. bond / withdraw_bond: a voluntary provider bond (raises the limit per job to the bond, up to 500 USDG; a rejected job costs it 10% of the budget plus the evaluator fee). return_bond_box: returns USDG left in the provider bond box (a deposit whose second step failed) to the USDG vault.",
34833
35301
  { action: z.enum(["publish", "unpublish", "status", "bond", "withdraw_bond", "return_bond_box"]), offers: z.array(offer).min(1).max(20).optional(), amount: usdg.optional() },
34834
35302
  async (a) => {
34835
35303
  if (a.action === "publish") {
@@ -34979,17 +35447,9 @@ function registerTools(server, agent) {
34979
35447
  (a) => lp().tokenInfo(a.token)
34980
35448
  );
34981
35449
  tool("profile", "The agent's public identity, its creator vault: the profile URI it set, that profile read from IPFS, and its ERC-8004 agent id; or another account's profile.", { account: address5.optional() }, (a) => lp().profile(a.account));
34982
- tool(
34983
- "set_profile",
34984
- "Sets the agent's profile in OstraRegistry from its creator vault, with a WOTS-signed spend: uri is an ipfs:// (or https://) JSON file with name, description, image and links, ideally an ERC-8004 registration file.",
34985
- { uri },
34986
- (a) => lp().setProfile(a.uri)
34987
- );
34988
- tool("register_agent", "Registers the agent in the ERC-8004 Identity Registry, once: its creator vault owns the identity and is its agentWallet; uri is its ERC-8004 registration file.", { uri }, (a) => lp().registerAgent(a.uri));
34989
- tool("set_agent_uri", "Points the agent's ERC-8004 identity at a new registration file.", { uri }, (a) => lp().setAgentUri(a.uri));
34990
35450
  tool(
34991
35451
  "upload_image",
34992
- `Pins an image file on this machine (PNG, JPEG, WebP or GIF, at most ${MAX_UPLOAD_IMAGE_BYTES} bytes) to IPFS through the Ostra uploader and returns its ipfs:// URI, for a profile or a token's metadata.`,
35452
+ `Pins an image file on this machine (PNG, JPEG, WebP or GIF, at most ${MAX_UPLOAD_IMAGE_BYTES} bytes) to IPFS through the Ostra uploader and returns its ipfs:// URI, for the files of an Ostrapad order or delivery.`,
34993
35453
  { path: z.string().min(1).max(4096) },
34994
35454
  async (a) => {
34995
35455
  const s = await stat4(a.path);
@@ -34999,9 +35459,9 @@ function registerTools(server, agent) {
34999
35459
  );
35000
35460
  tool(
35001
35461
  "publish_profile",
35002
- "Publishes the agent's profile: builds its ERC-8004 registration file (name, description, image, links, its creator vault as agentWallet), pins it to IPFS, sets it in OstraRegistry and, once the agent is registered, points its ERC-8004 identity at it.",
35003
- { name: z.string().min(1).max(64), ...publicFields },
35004
- (a) => lp().publishProfile(a)
35462
+ `Publishes the agent's Ostra profile and registers it in the ERC-8004 Identity Registry, with nothing to fill in: the same for every Ostra agent ("Ostra Agent #<id>", Ostra's description, the Ostra agent card as its image, its creator vault as agentWallet). The first call publishes the profile, registers the agent (its creator vault owns the identity) and publishes the profile again with the agent's number; a later call publishes it again (an Ostrapad listing in it is kept). Needs USDG in the creator vault (fund_creator_vault).`,
35463
+ {},
35464
+ () => lp().publishProfile()
35005
35465
  );
35006
35466
  tool(
35007
35467
  "publish_token_metadata",
@@ -35064,7 +35524,7 @@ function launchOptions(raw) {
35064
35524
  }
35065
35525
  return out;
35066
35526
  }
35067
- var toJson, address5, usdg, units, pair2, slippage2, bytes323, maxImpact, swapToken, tokenAddress, uri, publicFields, AI_DIR, MAX_VIDEO_BYTES, FINAL_JOB, CLOSED_LAUNCHPAD_TOOLS, BIGINT_FIELDS, ALLOWED_OPTIONS;
35527
+ var toJson, address5, usdg, units, pair2, slippage2, bytes323, maxImpact, swapToken, tokenAddress, uri, publicFields, AI_DIR, MAX_VIDEO_BYTES, FINAL_JOB, CLOSED_LAUNCHPAD_TOOLS, ENDED_SWAPS_KEPT, BIGINT_FIELDS, ALLOWED_OPTIONS;
35068
35528
  var init_tools = __esm({
35069
35529
  "src/tools.ts"() {
35070
35530
  "use strict";
@@ -35117,6 +35577,7 @@ var init_tools = __esm({
35117
35577
  "airdrop_fund",
35118
35578
  "airdrop_start"
35119
35579
  ]);
35580
+ ENDED_SWAPS_KEPT = 20;
35120
35581
  BIGINT_FIELDS = /* @__PURE__ */ new Set(["minDividendBalance", "maxBuy", "maxWallet", "graduationThreshold"]);
35121
35582
  ALLOWED_OPTIONS = /* @__PURE__ */ new Set([
35122
35583
  "tiers",