@ostraagent/mcp 0.1.10 → 0.1.12

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.10", 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.12", 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";
@@ -15521,6 +15529,7 @@ async function readBorroPosition(client, stockVault, stock2) {
15521
15529
  listed,
15522
15530
  supply: e.totalSupplyAssets,
15523
15531
  borrowed: e.totalBorrowAssets,
15532
+ borrowedShares: e.totalBorrowShares,
15524
15533
  at: at.timestamp
15525
15534
  };
15526
15535
  }
@@ -15651,21 +15660,54 @@ var init_breaker = __esm({
15651
15660
 
15652
15661
  // ../sdk/src/chain.ts
15653
15662
  import { BaseError as BaseError2, createPublicClient, defineChain, http } from "viem";
15654
- function invalidParams(e) {
15655
- const code = (x) => x.code === -32602;
15656
- return e instanceof BaseError2 ? e.walk(code) !== null : code(e);
15663
+ function laggingRead(e) {
15664
+ const lag = (x) => {
15665
+ const o = x;
15666
+ if (o.code === -32602) return true;
15667
+ return o.code === -32e3 && /unsupported block number/i.test(`${String(o.details ?? "")} ${String(o.message ?? "")}`);
15668
+ };
15669
+ return e instanceof BaseError2 ? e.walk(lag) !== null : lag(e);
15670
+ }
15671
+ function transient(e) {
15672
+ const o = e;
15673
+ if (o === null || typeof o !== "object") return true;
15674
+ if (o.name === "AbortError") return false;
15675
+ if (typeof o.code === "number") return [-1, -32005, -32603, 429, -32007].includes(o.code);
15676
+ if (typeof o.status === "number" && o.status !== 0) return [403, 408, 413, 429, 500, 502, 503, 504].includes(o.status);
15677
+ return true;
15657
15678
  }
15658
- function retryingHttp(url) {
15659
- const base = http(url, { retryCount: 8, retryDelay: 1e3, timeout: 3e4 });
15679
+ function failureText(e, url) {
15680
+ const o = e;
15681
+ const parts = [o?.shortMessage ?? o?.name ?? String(e)];
15682
+ if (typeof o?.code === "number") parts.push(`code ${o.code}`);
15683
+ if (typeof o?.status === "number" && o.status !== 0) parts.push(`HTTP ${o.status}`);
15684
+ if (typeof o?.details === "string" && o.details !== "" && o.details !== parts[0]) parts.push(o.details);
15685
+ return redact(parts.join("; "), url).slice(0, 300);
15686
+ }
15687
+ function retryingHttp(url, o) {
15688
+ const own = o !== void 0;
15689
+ 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 });
15690
+ const retries = o?.retryCount ?? RPC_RETRIES;
15660
15691
  return ((params) => {
15661
15692
  const t = base(params);
15662
15693
  const request = (async (args, options) => {
15663
- for (let attempt = 0; ; attempt++) {
15694
+ let lagging = 0;
15695
+ let failed = 0;
15696
+ for (; ; ) {
15664
15697
  try {
15665
15698
  return await t.request(args, options);
15666
15699
  } catch (e) {
15667
- if (attempt >= INVALID_PARAMS_RETRIES || !IDEMPOTENT_READS.has(args.method) || !invalidParams(e)) throw e;
15668
- await new Promise((r) => setTimeout(r, INVALID_PARAMS_WAIT_MS * 2 ** attempt));
15700
+ let wait;
15701
+ if (lagging < INVALID_PARAMS_RETRIES && IDEMPOTENT_READS.has(args.method) && laggingRead(e)) {
15702
+ wait = INVALID_PARAMS_WAIT_MS * 2 ** lagging;
15703
+ lagging++;
15704
+ } else if (own && failed < retries && transient(e)) {
15705
+ const after = e.headers?.get?.("Retry-After");
15706
+ 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;
15707
+ failed++;
15708
+ } else throw e;
15709
+ o?.onRetry?.({ method: args.method, retry: lagging + failed, waitMs: wait, error: failureText(e, url) });
15710
+ await new Promise((r) => setTimeout(r, wait));
15669
15711
  }
15670
15712
  }
15671
15713
  });
@@ -15682,7 +15724,7 @@ async function assertRobinhoodChain(client) {
15682
15724
  function redact(text3, secret2) {
15683
15725
  return secret2 === "" ? text3 : text3.split(secret2).join("<rpc>");
15684
15726
  }
15685
- var robinhoodChain, IDEMPOTENT_READS, INVALID_PARAMS_RETRIES, INVALID_PARAMS_WAIT_MS;
15727
+ var robinhoodChain, IDEMPOTENT_READS, INVALID_PARAMS_RETRIES, INVALID_PARAMS_WAIT_MS, RPC_RETRIES, RPC_RETRY_WAIT_MS, RPC_TIMEOUT_MS, RETRY_AFTER_MAX_MS;
15686
15728
  var init_chain = __esm({
15687
15729
  "../sdk/src/chain.ts"() {
15688
15730
  "use strict";
@@ -15699,6 +15741,10 @@ var init_chain = __esm({
15699
15741
  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"]);
15700
15742
  INVALID_PARAMS_RETRIES = 4;
15701
15743
  INVALID_PARAMS_WAIT_MS = 1e3;
15744
+ RPC_RETRIES = 8;
15745
+ RPC_RETRY_WAIT_MS = 1e3;
15746
+ RPC_TIMEOUT_MS = 3e4;
15747
+ RETRY_AFTER_MAX_MS = 1e4;
15702
15748
  }
15703
15749
  });
15704
15750
 
@@ -19872,6 +19918,7 @@ var init_browser = __esm({
19872
19918
  "use strict";
19873
19919
  init_define_OSTRA_PACKAGED();
19874
19920
  init_abi();
19921
+ init_agent_card();
19875
19922
  init_aggregator();
19876
19923
  init_airdrop();
19877
19924
  init_backup();
@@ -20248,6 +20295,10 @@ import { decodeFunctionData as decodeFunctionData4, getAddress as getAddress17,
20248
20295
  import { x402Client, x402HTTPClient } from "@x402/core/client";
20249
20296
  import { toClientEvmSigner } from "@x402/evm";
20250
20297
  import { BatchSettlementEvmScheme } from "@x402/evm/batch-settlement/client";
20298
+ function notLandedError(attempts, lastReason) {
20299
+ const reason = lastReason.trim().slice(0, NOT_SENT_MESSAGE_MAX);
20300
+ return new Error(`the relayer could not land the request in ${attempts} attempts${reason === "" ? "" : `; it answered last: ${reason}`}`);
20301
+ }
20251
20302
  function address(value, what) {
20252
20303
  if (typeof value !== "string" || !isAddress6(value, { strict: false })) throw new Error(`${what} is not an address`);
20253
20304
  return getAddress17(value);
@@ -20296,6 +20347,25 @@ function relayerChannelDeposit(needed, vaultBalance, keep = 0n) {
20296
20347
  const want = needed > RELAYER_CHANNEL_DEPOSIT ? needed : RELAYER_CHANNEL_DEPOSIT;
20297
20348
  return want < available ? want : available;
20298
20349
  }
20350
+ function relayerSpendPlan(p) {
20351
+ const rooms = [...p.rooms];
20352
+ let balance = p.balance;
20353
+ let debt = p.debt;
20354
+ for (const a of p.amounts) {
20355
+ const i = debt > 0n ? -1 : rooms.findIndex((r) => r >= a);
20356
+ if (i >= 0) {
20357
+ rooms[i] = rooms[i] - a;
20358
+ continue;
20359
+ }
20360
+ const needed = debt + p.openCost + a;
20361
+ if (balance < needed) return false;
20362
+ const deposit = relayerChannelDeposit(needed, balance);
20363
+ balance -= deposit;
20364
+ debt = 0n;
20365
+ rooms.push(deposit - needed);
20366
+ }
20367
+ return true;
20368
+ }
20299
20369
  function relayerOfferProblem(r, want) {
20300
20370
  if (r.scheme !== "batch-settlement") return `the offer is ${r.scheme}, not batch-settlement`;
20301
20371
  if (r.network !== want.network) return `the offer is on ${r.network}, not ${want.network}`;
@@ -20330,7 +20400,7 @@ function openChannelCall(calls, vault, token) {
20330
20400
  }
20331
20401
  return null;
20332
20402
  }
20333
- var RELAYER_DIR, VAULT_SPEND_GAS, REQUEST_TIMEOUT_MS2, MAX_STEPS, MAX_RESENDS, LANDED_TIMEOUT_MS, OPEN_READ_TRIES, OPEN_READ_INTERVAL_MS, BPS_DENOMINATOR, now, LocalSender, RelayerUnavailableError, PendingSpendUnpaidError, RelayerError, RelayerStore, RelayerClient;
20403
+ var RELAYER_DIR, VAULT_SPEND_GAS, REQUEST_TIMEOUT_MS2, MAX_STEPS, MAX_RESENDS, NOT_SENT_MESSAGE_MAX, LANDED_TIMEOUT_MS, OPEN_READ_TRIES, OPEN_READ_INTERVAL_MS, BPS_DENOMINATOR, now, LocalSender, RelayerUnavailableError, PendingSpendUnpaidError, RelayerError, RelayerStore, RelayerClient;
20334
20404
  var init_relay = __esm({
20335
20405
  "../core/src/relay.ts"() {
20336
20406
  "use strict";
@@ -20344,6 +20414,7 @@ var init_relay = __esm({
20344
20414
  REQUEST_TIMEOUT_MS2 = 24e4;
20345
20415
  MAX_STEPS = 8;
20346
20416
  MAX_RESENDS = 3;
20417
+ NOT_SENT_MESSAGE_MAX = 500;
20347
20418
  LANDED_TIMEOUT_MS = 6e4;
20348
20419
  OPEN_READ_TRIES = 20;
20349
20420
  OPEN_READ_INTERVAL_MS = 500;
@@ -20381,9 +20452,11 @@ var init_relay = __esm({
20381
20452
  }
20382
20453
  };
20383
20454
  PendingSpendUnpaidError = class extends Error {
20384
- constructor(message) {
20455
+ onCredit;
20456
+ constructor(message, onCredit) {
20385
20457
  super(message);
20386
20458
  this.name = "PendingSpendUnpaidError";
20459
+ this.onCredit = onCredit;
20387
20460
  }
20388
20461
  };
20389
20462
  RelayerError = class extends Error {
@@ -20572,6 +20645,55 @@ var init_relay = __esm({
20572
20645
  }
20573
20646
  return null;
20574
20647
  }
20648
+ /**
20649
+ * Read only (nothing is retired, opened or signed): why this vault cannot pay, now, vault spends that reserve `gases`
20650
+ * (each as VaultService.operate reserves it: reserve's amount for that gas), or null when it can. The spends are
20651
+ * played in order as reserve and #ensureChannel would pay them: each from a usable relayer channel with room for it,
20652
+ * or else from a new channel, whose open needs the vault's debt and the open's own spend (#spendBound) on top and
20653
+ * takes RELAYER_CHANNEL_DEPOSIT or what it needs, at most what the vault holds. The vault's credit at the relayer is
20654
+ * not counted: it lowers a price only after the reservation is made. An unpaid open the relayer holds for the vault
20655
+ * is refused here (it is paid before anything else). An operation that sends something first (an approval before the
20656
+ * call it allows) checks this before its first spend, so it does not leave the first done and the rest unpaid.
20657
+ */
20658
+ async shortfall(gases) {
20659
+ const facts = await this.facts();
20660
+ const vault = await this.#buyerVault().vaultAddress();
20661
+ if (await readVault(this.#client, this.#config.deployment, vault) === null) return `the vault ${vault} does not exist yet`;
20662
+ const state = await this.vaultState(vault);
20663
+ if (state.unpaidOpen !== null) return `the relayer holds an unpaid open of the vault's relayer channel ${state.unpaidOpen.channel}: the vault's next relayed operation pays it first`;
20664
+ const amounts = [];
20665
+ for (const g of gases) amounts.push(await this.#reserveAmount(g));
20666
+ const bound2 = await this.#spendBound();
20667
+ const payer = await this.#payer();
20668
+ const rooms = [];
20669
+ for (const c of await this.#records()) {
20670
+ if (c.retired || now() >= c.openedAt + SESSION_MAX_AGE_SECONDS) continue;
20671
+ const free = this.#free(c, null);
20672
+ if (free <= 0n) continue;
20673
+ const ch = await readChannel(this.#client, this.#config.deployment.batchSettlement, c.channel);
20674
+ const usable = payer !== null && getAddress17(c.config.payer) === payer && getAddress17(c.config.receiverAuthorizer) === facts.vault && getAddress17(c.config.receiver) === facts.split && ch.pendingWithdrawal === null;
20675
+ const onChain = ch.balance - BigInt(c.cumulative);
20676
+ if (usable) rooms.push(free < onChain ? free : onChain);
20677
+ }
20678
+ const balance = await tokenBalance(this.#client, this.#config.deployment.token, vault);
20679
+ const plan = (held) => relayerSpendPlan({ amounts, rooms, balance: held, debt: state.debt, openCost: bound2 });
20680
+ if (plan(balance)) return null;
20681
+ let lo = balance;
20682
+ let hi = balance > 0n ? balance * 2n : 1n;
20683
+ while (!plan(hi)) hi *= 2n;
20684
+ while (hi - lo > 1n) {
20685
+ const mid = (lo + hi) / 2n;
20686
+ if (plan(mid)) hi = mid;
20687
+ else lo = mid;
20688
+ }
20689
+ const n = gases.length;
20690
+ return `the vault ${vault} holds ${formatTokenAmount(balance)} USDG: ${n === 1 ? "its relayed transaction needs" : `its ${n} relayed transactions need`} it to hold ${formatTokenAmount(hi)} USDG now (with what its relayer channels have left, and a new channel's open where needed)`;
20691
+ }
20692
+ /** What reserve sets aside for a spend of `gas` (and `extraWei` of ETH it sends): the relayer's price for it, margin and tolerance included. */
20693
+ async #reserveAmount(gas, extraWei = 0n) {
20694
+ const wei = withMargin(gas * await this.#client.getGasPrice()) + extraWei;
20695
+ return maxAcceptedQuote(weiToTokenUnits(wei, await this.#ethUsd(), TOKEN_DECIMALS));
20696
+ }
20575
20697
  /** Base units a transaction of `wei` comes to at the Chainlink price, margin included (the relayer's rule). */
20576
20698
  async #amountFor(wei) {
20577
20699
  return weiToTokenUnits(withMargin(wei), await this.#ethUsd(), TOKEN_DECIMALS);
@@ -20610,8 +20732,7 @@ var init_relay = __esm({
20610
20732
  await (openChannel ?? ((t) => this.#buyerVault().openRelayerChannel(t)))(terms);
20611
20733
  }
20612
20734
  async reserve(gas, openChannel, keep = 0n, extraWei = 0n) {
20613
- const wei = withMargin(gas * await this.#client.getGasPrice()) + extraWei;
20614
- const amount = maxAcceptedQuote(weiToTokenUnits(wei, await this.#ethUsd(), TOKEN_DECIMALS));
20735
+ const amount = await this.#reserveAmount(gas, extraWei);
20615
20736
  for (let attempt = 0; attempt < 2; attempt++) {
20616
20737
  const c = await this.#serial(async () => {
20617
20738
  const found = await this.#channelFor(amount, null);
@@ -20632,6 +20753,7 @@ var init_relay = __esm({
20632
20753
  async #post(calls, extra, headers = {}) {
20633
20754
  const body2 = { calls: calls.map((c) => ({ to: getAddress17(c.to), data: c.data })) };
20634
20755
  if (extra.prepay === true) body2.prepay = true;
20756
+ if (extra.onCredit === true) body2.onCredit = true;
20635
20757
  if (extra.payer !== null) body2.payer = extra.payer;
20636
20758
  if (extra.allowFailedSwaps === true) body2.allowFailedSwaps = true;
20637
20759
  return this.#http("POST", "/v1/relay", JSON.stringify(body2), headers);
@@ -20826,8 +20948,11 @@ var init_relay = __esm({
20826
20948
  }
20827
20949
  async send(calls, options = {}) {
20828
20950
  await this.facts();
20951
+ const credit = options.onCredit === true;
20952
+ const post2 = async () => this.#post(calls, { prepay: options.prepay === true, payer: credit ? null : await this.#payer(), allowFailedSwaps: options.allowFailedSwaps === true, onCredit: credit });
20953
+ let notSent = "";
20829
20954
  for (let resend = 0; resend < MAX_RESENDS; resend++) {
20830
- let answer = await this.#post(calls, { prepay: options.prepay === true, payer: await this.#payer(), allowFailedSwaps: options.allowFailedSwaps === true });
20955
+ let answer = await post2();
20831
20956
  let again = false;
20832
20957
  for (let step2 = 0; step2 < MAX_STEPS && !again; step2++) {
20833
20958
  const b = answer.body;
@@ -20837,17 +20962,19 @@ var init_relay = __esm({
20837
20962
  await this.#landed(hash);
20838
20963
  return hash;
20839
20964
  }
20965
+ 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");
20840
20966
  if (answer.status === 402) {
20841
20967
  answer = await this.#pay(calls, answer, options);
20842
20968
  continue;
20843
20969
  }
20844
20970
  if (answer.status === 403 && (error === "vault_in_debt" || error === "open_unpaid")) {
20845
- if (options.reservation !== void 0 && options.reservation !== null) throw new RelayerError(403, error, String(b?.message ?? error));
20971
+ if (credit || options.reservation !== void 0 && options.reservation !== null) throw new RelayerError(403, error, String(b?.message ?? error));
20846
20972
  await this.#ensureChannel(1n, options.openChannel, null);
20847
- answer = await this.#post(calls, { prepay: options.prepay === true, payer: await this.#payer(), allowFailedSwaps: options.allowFailedSwaps === true });
20973
+ answer = await post2();
20848
20974
  continue;
20849
20975
  }
20850
20976
  if (answer.status === 409 && error === "not_sent") {
20977
+ notSent = typeof b?.message === "string" ? b.message : "";
20851
20978
  again = true;
20852
20979
  continue;
20853
20980
  }
@@ -20866,7 +20993,7 @@ var init_relay = __esm({
20866
20993
  }
20867
20994
  if (!again) throw new Error(`the relayer did not relay the request after ${MAX_STEPS} answers`);
20868
20995
  }
20869
- throw new Error(`the relayer could not land the request in ${MAX_RESENDS} attempts`);
20996
+ throw notLandedError(MAX_RESENDS, notSent);
20870
20997
  }
20871
20998
  /**
20872
20999
  * Relayer channels past SESSION_MAX_AGE_SECONDS stop paying; returns those
@@ -21388,10 +21515,14 @@ var init_vault = __esm({
21388
21515
  * Runs `fn` with what a vault spend needs to be paid. With the relayer: a
21389
21516
  * reservation of unused relayer channel deposit for its price (a channel is
21390
21517
  * opened first if none has room), released once `fn` is done. When a spend
21391
- * signed earlier waits and no relayer channel can pay for it, the local fee
21392
- * payer sends it if it holds the ETH. `moves` is what the spend takes out
21393
- * of this vault: a relayer channel opened from the buyer vault for it
21394
- * leaves that much in the buyer vault.
21518
+ * signed earlier waits and no relayer channel can pay for it (its key is
21519
+ * the one a new channel's open needs), the relayer is asked to send it on
21520
+ * credit, as the vault's debt, and the channel opened next pays that debt
21521
+ * with its first voucher; `fn` then runs as usual and its result names what
21522
+ * was sent first (`sentFirst`). Only when the relayer does not send it so,
21523
+ * the local fee payer sends it if it holds the ETH. `moves` is what the
21524
+ * spend takes out of this vault: a relayer channel opened from the buyer
21525
+ * vault for it leaves that much in the buyer vault.
21395
21526
  */
21396
21527
  async #paidSpend(sender, fn, moves = 0n, gas = VAULT_SPEND_GAS, extraWei = 0n) {
21397
21528
  await this.#finishRecoveryFirst(sender);
@@ -21403,10 +21534,30 @@ var init_vault = __esm({
21403
21534
  reservation = await sender.reserve(gas, sender === this.#sender ? this.#opener() : void 0, this.#paysRelayer() ? moves : 0n, extraWei);
21404
21535
  } catch (err) {
21405
21536
  if (!(err instanceof PendingSpendUnpaidError)) throw err;
21537
+ let refused = "";
21538
+ const credit = err.onCredit;
21539
+ if (sender.relayed && credit !== void 0) {
21540
+ let sentFirst = null;
21541
+ try {
21542
+ sentFirst = await (credit.owner === this ? credit.unqueued() : credit.queued());
21543
+ } catch (e) {
21544
+ if (!(e instanceof RelayerError)) throw e;
21545
+ refused = `; the relayer did not send it on credit (${e.message})`;
21546
+ }
21547
+ if (refused === "") {
21548
+ const again = await sender.reserve(gas, sender === this.#sender ? this.#opener() : void 0, this.#paysRelayer() ? moves : 0n, extraWei);
21549
+ try {
21550
+ const r = await fn(sender, again);
21551
+ return sentFirst !== null && r !== null && typeof r === "object" ? { ...r, sentFirst } : r;
21552
+ } finally {
21553
+ sender.release(again);
21554
+ }
21555
+ }
21556
+ }
21406
21557
  const local = new LocalSender(this.#client, this.#transport, this.#keys.feePayer, this.#keys.escrow);
21407
21558
  const need = gas * await this.#client.getGasPrice() * ETH_HEADROOM + extraWei;
21408
21559
  if (await ethBalance(this.#client, this.#keys.feePayer.address) < need) {
21409
- 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`);
21560
+ 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`);
21410
21561
  }
21411
21562
  return fn(local, null);
21412
21563
  }
@@ -21416,6 +21567,25 @@ var init_vault = __esm({
21416
21567
  sender.release(reservation);
21417
21568
  }
21418
21569
  }
21570
+ /**
21571
+ * Sends the spend signed earlier through the relayer on credit (SendOptions.onCredit): the vault owes its price, which
21572
+ * the first voucher of the channel opened next pays. Returns what was sent, or null when nothing waits any more.
21573
+ */
21574
+ async #sendPendingOnCredit() {
21575
+ const sender = this.#sender;
21576
+ if (!sender.relayed) return null;
21577
+ const vault = await this.vaultAddress();
21578
+ const v = await this.#read(vault);
21579
+ if (v === null) return null;
21580
+ const credit = {
21581
+ relayed: true,
21582
+ payer: () => sender.payer(),
21583
+ send: (calls, o = {}) => sender.send(calls, { ...o, reservation: null, onCredit: true }),
21584
+ reserve: async () => null,
21585
+ release: () => void 0
21586
+ };
21587
+ return this.#sendPending(vault, v, credit, null);
21588
+ }
21419
21589
  /**
21420
21590
  * Version 3 vaults: the spend is run first with a throwaway key (sdk simulateVaultSpend) and refused before it is
21421
21591
  * signed when it would revert, or land as a failure (a SupportRecovery the other vault refuses). A signed spend that
@@ -21645,7 +21815,11 @@ the signed channel open is kept; the next open_channel or withdraw call sends it
21645
21815
  const v = await this.#read(vault);
21646
21816
  if (v === null) throw new Error(this.#missing());
21647
21817
  if (await this.#pending(v) !== null) {
21648
- 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");
21818
+ 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", {
21819
+ owner: this,
21820
+ queued: () => this.#serial(() => this.#sendPendingOnCredit()),
21821
+ unqueued: () => this.#sendPendingOnCredit()
21822
+ });
21649
21823
  }
21650
21824
  await this.#notFrozen(vault, "the vault");
21651
21825
  const balance = await this.#balance(vault);
@@ -22035,6 +22209,21 @@ var init_borro2 = __esm({
22035
22209
  }
22036
22210
  return { buyerVault: buyer, usdgLent: { shares: usdg2.shares.toString(), value: formatUnitsAmount(usdg2.value, TOKEN_DECIMALS) }, stocks };
22037
22211
  }
22212
+ /**
22213
+ * Per Borro stock whose vault of this agent exists: its market, the stock vault's borrow shares, and the market's
22214
+ * borrows and borrow shares accrued to this block, so that any number of borrow shares is valued now as
22215
+ * toAssetsUp(shares, borrowed, borrowedShares) (what fundFromYield takes off as the debt's interest).
22216
+ */
22217
+ async debtBook() {
22218
+ const out = [];
22219
+ for (const s of borroStocks()) {
22220
+ const v = await this.#p.tokenVault(s.token).vaultAddress();
22221
+ if (await readVault(this.#p.client, this.#p.config.deployment, v) === null) continue;
22222
+ const p = await readBorroPosition(this.#p.client, v, s);
22223
+ out.push({ symbol: s.symbol, marketId: s.marketId, borrowShares: p.borrowShares, borrowed: p.borrowed, borrowedShares: p.borrowedShares });
22224
+ }
22225
+ return out;
22226
+ }
22038
22227
  // -------------------------------------------------------------------------
22039
22228
  // Lend and redeem
22040
22229
  // -------------------------------------------------------------------------
@@ -23458,24 +23647,17 @@ function imageUri(value) {
23458
23647
  return value;
23459
23648
  }
23460
23649
  function profileFile(p) {
23461
- const services = [];
23462
- const website = link2(p.website, "website");
23463
- const x = link2(p.x, "x");
23464
- const telegram = link2(p.telegram, "telegram");
23465
- if (website !== void 0) services.push({ name: "web", endpoint: website });
23466
- if (x !== void 0) services.push({ name: "X", endpoint: x });
23467
- if (telegram !== void 0) services.push({ name: "Telegram", endpoint: telegram });
23468
- services.push({ name: "agentWallet", endpoint: `eip155:${ROBINHOOD_CHAIN_ID}:${p.vault}` });
23650
+ const id = p.agentId === void 0 || p.agentId === null ? null : p.agentId;
23651
+ if (id !== null && !/^(0|[1-9][0-9]{0,29})$/.test(id)) throw new RangeError("an agent id is a decimal number");
23469
23652
  const image = imageUri(p.image);
23470
- const description = text2(p.description, "description", 1e3);
23471
23653
  return {
23472
23654
  type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
23473
- name: text2(p.name, "name", 64),
23474
- ...description === void 0 ? {} : { description },
23655
+ name: id === null ? AGENT_NAME : `${AGENT_NAME} #${id}`,
23656
+ description: AGENT_DESCRIPTION,
23475
23657
  ...image === void 0 ? {} : { image },
23476
- services,
23658
+ services: [{ name: "agentWallet", endpoint: `eip155:${ROBINHOOD_CHAIN_ID}:${p.vault}` }],
23477
23659
  active: true,
23478
- registrations: p.agentId === void 0 || p.agentId === null ? [] : [{ agentId: Number(p.agentId), agentRegistry: `eip155:${ROBINHOOD_CHAIN_ID}:${ERC8004_IDENTITY_REGISTRY}` }]
23660
+ registrations: id === null ? [] : [{ agentId: Number(id), agentRegistry: `eip155:${ROBINHOOD_CHAIN_ID}:${ERC8004_IDENTITY_REGISTRY}` }]
23479
23661
  };
23480
23662
  }
23481
23663
  function tokenMetadataFile(m) {
@@ -23514,6 +23696,9 @@ function uploaderUrl(relayerUrl) {
23514
23696
  function uploadJson(relayerUrl, value) {
23515
23697
  return post(`${uploaderUrl(relayerUrl)}/json`, JSON.stringify(value), "application/json");
23516
23698
  }
23699
+ function agentCardUri(relayerUrl, agentId) {
23700
+ return post(`${uploaderUrl(relayerUrl)}/agent-card`, JSON.stringify({ agentId }), "application/json");
23701
+ }
23517
23702
  function uploadImage(relayerUrl, bytes) {
23518
23703
  const base = uploaderUrl(relayerUrl);
23519
23704
  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})`);
@@ -23521,13 +23706,15 @@ function uploadImage(relayerUrl, bytes) {
23521
23706
  if (type === null) throw new RangeError("the image is not a PNG, JPEG, WebP or GIF");
23522
23707
  return post(`${base}/image`, new Blob([new Uint8Array(bytes)], { type }), type);
23523
23708
  }
23524
- var MAX_UPLOAD_IMAGE_BYTES;
23709
+ var MAX_UPLOAD_IMAGE_BYTES, AGENT_NAME, AGENT_DESCRIPTION;
23525
23710
  var init_publish = __esm({
23526
23711
  "../core/src/publish.ts"() {
23527
23712
  "use strict";
23528
23713
  init_define_OSTRA_PACKAGED();
23529
23714
  init_src();
23530
23715
  MAX_UPLOAD_IMAGE_BYTES = 1048576;
23716
+ AGENT_NAME = "Ostra Agent";
23717
+ 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.";
23531
23718
  }
23532
23719
  });
23533
23720
 
@@ -23562,7 +23749,7 @@ function simulatedGas(gasUsed) {
23562
23749
  const g = gasUsed * 5n / 4n + SPEND_GAS_HEADROOM2;
23563
23750
  return g > VAULT_SPEND_GAS ? g : VAULT_SPEND_GAS;
23564
23751
  }
23565
- 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, LaunchpadService;
23752
+ 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;
23566
23753
  var init_launchpad = __esm({
23567
23754
  "../core/src/launchpad.ts"() {
23568
23755
  "use strict";
@@ -23598,8 +23785,11 @@ var init_launchpad = __esm({
23598
23785
  volatility_fee: POOL_SETTING.VOLATILITY_FEE,
23599
23786
  max_tx_move: POOL_SETTING.MAX_TX_MOVE
23600
23787
  };
23788
+ WOTS_GAS_SPREAD = 100000n;
23789
+ STAND_IN_URI = `ipfs://bafkrei${"a".repeat(52)}`;
23601
23790
  LaunchpadService = class {
23602
23791
  #client;
23792
+ #logsClient;
23603
23793
  #transport;
23604
23794
  #config;
23605
23795
  #keys;
@@ -23614,6 +23804,7 @@ var init_launchpad = __esm({
23614
23804
  constructor(p) {
23615
23805
  if (p.creator.kind !== "creator" || p.buyer.kind !== "buyer") throw new Error("the launchpad takes the agent's buyer and creator vaults");
23616
23806
  this.#client = p.client;
23807
+ this.#logsClient = p.logsClient ?? p.client;
23617
23808
  this.#transport = p.transport;
23618
23809
  this.#config = p.config;
23619
23810
  this.#keys = p.keys;
@@ -24402,6 +24593,52 @@ var init_launchpad = __esm({
24402
24593
  const text3 = await this.#folder.read("agent.json");
24403
24594
  return text3 === void 0 ? null : JSON.parse(text3);
24404
24595
  }
24596
+ /**
24597
+ * The agent's registration read from the chain when agent.json does not hold it: an identity its creator vault
24598
+ * registered (the Identity Registry's Registered events from the deployment's first block) and still owns. A
24599
+ * registration that landed after its call stopped (the relayer refused the send and a later operation sent the kept
24600
+ * spend, or the process stopped before agent.json was written) is found here and written to agent.json, so the agent
24601
+ * is never registered twice. An identity someone else registered and sent to the creator vault (an ERC-721 transfer
24602
+ * the vault accepts) is no registration of this agent: with no Registered event of the vault, this is null. A
24603
+ * registration sent again just now is read from its receipt instead (#recordFromReceipt), not from logs that may lag.
24604
+ */
24605
+ async #recoverAgentRecord() {
24606
+ const vault = getAddress24(await this.#creator.vaultAddress());
24607
+ const owned = await this.#client.readContract({ address: ERC8004_IDENTITY_REGISTRY, abi: identityRegistryAbi, functionName: "balanceOf", args: [vault] });
24608
+ if (owned === 0n) return null;
24609
+ const to = await this.#logsClient.getBlockNumber({ cacheTime: 0 });
24610
+ const logs = await getLogsChunked(this.#logsClient, { address: ERC8004_IDENTITY_REGISTRY, event: agentRegisteredEvent, args: { owner: vault }, fromBlock: BigInt(this.#config.deployment.fromBlock), toBlock: to });
24611
+ for (const log2 of logs) {
24612
+ const { agentId, agentURI } = log2.args;
24613
+ const who = await readAgentIdentity(this.#client, agentId);
24614
+ if (who === null || getAddress24(who.owner) !== vault) continue;
24615
+ const record = { agentId: agentId.toString(), uri: agentURI, txHash: log2.transactionHash, at: Math.floor(Date.now() / 1e3) };
24616
+ await this.#folder.write("agent.json", JSON.stringify(record, null, 2));
24617
+ return record;
24618
+ }
24619
+ return null;
24620
+ }
24621
+ /**
24622
+ * The registration transaction `txHash` of the creator vault, read from its receipt (the vault's AgentRegistered
24623
+ * event) and written to agent.json; null when it registered nothing. `uri` is the file it registered with.
24624
+ */
24625
+ async #recordFromReceipt(txHash, uri2) {
24626
+ const vault = getAddress24(await this.#creator.vaultAddress());
24627
+ const receipt = await this.#client.getTransactionReceipt({ hash: txHash });
24628
+ let agentId;
24629
+ for (const log2 of receipt.logs) {
24630
+ if (getAddress24(log2.address) !== vault) continue;
24631
+ try {
24632
+ const e = decodeEventLog3({ abi: ostraVaultAbi, data: log2.data, topics: log2.topics });
24633
+ if (e.eventName === "AgentRegistered") agentId = e.args.agentId.toString();
24634
+ } catch {
24635
+ }
24636
+ }
24637
+ if (agentId === void 0) return null;
24638
+ const record = { agentId, uri: uri2, txHash, at: Math.floor(Date.now() / 1e3) };
24639
+ await this.#folder.write("agent.json", JSON.stringify(record, null, 2));
24640
+ return record;
24641
+ }
24405
24642
  /** 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. */
24406
24643
  async profile(accountText) {
24407
24644
  const account = accountText === void 0 ? await this.#creator.vaultAddress() : address2(accountText, "account");
@@ -24418,32 +24655,21 @@ var init_launchpad = __esm({
24418
24655
  /** 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. */
24419
24656
  async registerAgent(uri2) {
24420
24657
  checkUri(uri2);
24421
- const vault = await this.#identityVault();
24422
- const existing = await this.#agentRecord();
24423
- if (existing !== null) throw new Error(`this agent is registered already as agent ${existing.agentId}: update it with set_agent_uri; nothing was signed`);
24658
+ await this.#identityVault();
24659
+ const existing = await this.#agentRecord() ?? await this.#recoverAgentRecord();
24660
+ if (existing !== null) throw new Error(`this agent is registered already as agent ${existing.agentId}: publish_profile publishes its profile again; nothing was signed`);
24424
24661
  const r = await this.#simulatedSpend(this.#creator, this.#creatorSender, VAULT_ACTION.REGISTER_AGENT, registerAgentParams(uri2), "agent registration");
24425
24662
  if (r.status !== "sent") return r;
24426
- const receipt = await this.#client.getTransactionReceipt({ hash: r.txHash });
24427
- let agentId;
24428
- for (const log2 of receipt.logs) {
24429
- if (getAddress24(log2.address) !== vault) continue;
24430
- try {
24431
- const e = decodeEventLog3({ abi: ostraVaultAbi, data: log2.data, topics: log2.topics });
24432
- if (e.eventName === "AgentRegistered") agentId = e.args.agentId.toString();
24433
- } catch {
24434
- }
24435
- }
24436
- if (agentId === void 0) throw new Error(`the registration ${r.txHash} landed without an AgentRegistered event`);
24437
- const record = { agentId, uri: uri2, txHash: r.txHash, at: Math.floor(Date.now() / 1e3) };
24438
- await this.#folder.write("agent.json", JSON.stringify(record, null, 2));
24439
- return { ...r, agentId };
24663
+ const record = await this.#recordFromReceipt(r.txHash, uri2);
24664
+ if (record === null) throw new Error(`the registration ${r.txHash} landed without an AgentRegistered event`);
24665
+ return { ...r, agentId: record.agentId };
24440
24666
  }
24441
24667
  /** Points the agent's ERC-8004 identity at a new registration file. */
24442
24668
  async setAgentUri(uri2) {
24443
24669
  checkUri(uri2);
24444
24670
  await this.#identityVault();
24445
24671
  const record = await this.#agentRecord();
24446
- if (record === null) throw new Error("this agent has no ERC-8004 identity yet: register_agent first; nothing was signed");
24672
+ if (record === null) throw new Error("this agent has no ERC-8004 identity yet: publish_profile registers it; nothing was signed");
24447
24673
  const r = await this.#simulatedSpend(this.#creator, this.#creatorSender, VAULT_ACTION.SET_AGENT_URI, setAgentUriParams({ agentId: BigInt(record.agentId), uri: uri2 }), "agent registration file");
24448
24674
  if (r.status === "sent") await this.#folder.write("agent.json", JSON.stringify({ ...record, uri: uri2, at: Math.floor(Date.now() / 1e3) }, null, 2));
24449
24675
  return r;
@@ -24469,15 +24695,38 @@ var init_launchpad = __esm({
24469
24695
  if (version < VAULT_VERSION) throw new Error(`the creator vault ${s.vault} runs version ${version}, not ${VAULT_VERSION}; nothing was signed`);
24470
24696
  }
24471
24697
  /**
24472
- * Publishes the agent's profile: builds its ERC-8004 registration file (name, description, image, links, the
24473
- * creator vault as its agentWallet, its registration once it has one), pins it through the uploader, sets it in
24474
- * OstraRegistry, and points the ERC-8004 identity at it too when the agent is registered.
24698
+ * Publishes the agent's Ostra profile, the same for every Ostra agent (profileFile: its name and number, Ostra's
24699
+ * description, its Ostra card, its creator vault as agentWallet), and registers the agent in ERC-8004 when it is not
24700
+ * registered yet: the profile without a number is published, the creator vault registers the agent with it, and the
24701
+ * profile is published again with the agent's number and card, its ERC-8004 identity pointed at it. Called again, it
24702
+ * carries on from where it stopped (what the chain and agent.json hold decides).
24475
24703
  */
24476
- async publishProfile(p) {
24477
- const vault = await this.#identityVault();
24704
+ async publishProfile() {
24705
+ const before = await this.#agentRecord() ?? await this.#recoverAgentRecord();
24706
+ if (before !== null) return { agentId: before.agentId, registration: null, unnumbered: null, ...await this.#publishProfileFile(before) };
24707
+ const first = await this.#publishProfileFile(null);
24708
+ const unnumbered = { uri: first.uri, profile: first.profile };
24709
+ if (first.profile.status !== "sent") {
24710
+ const resent = first.profile.status === "resent_pending" ? first.profile.resent : null;
24711
+ const landed = resent !== null && resent.operation === "vault_spend" && resent.action === VAULT_ACTION.REGISTER_AGENT ? await this.#recordFromReceipt(resent.txHash, first.uri) : null;
24712
+ if (landed === null) return { agentId: null, registration: null, unnumbered, ...first };
24713
+ return { agentId: landed.agentId, registration: null, unnumbered, ...await this.#publishProfileFile(landed) };
24714
+ }
24715
+ const registration = await this.registerAgent(first.uri);
24478
24716
  const record = await this.#agentRecord();
24479
- const file = profileFile({ ...p, vault, agentId: record?.agentId ?? null });
24717
+ if (registration.status !== "sent" || record === null) return { agentId: null, registration, unnumbered, ...first };
24718
+ return { agentId: record.agentId, registration, unnumbered, ...await this.#publishProfileFile(record) };
24719
+ }
24720
+ /**
24721
+ * Builds the profile of the agent (`record`: its registration, or null before it has one), pins it through the
24722
+ * uploader, sets it in OstraRegistry (for the creator vaults of the other pairs it launched from too), and points the
24723
+ * ERC-8004 identity at it when the agent is registered.
24724
+ */
24725
+ async #publishProfileFile(record) {
24726
+ const vault = await this.#identityVault();
24727
+ const agentId = record?.agentId ?? null;
24480
24728
  uploaderUrl(this.#config.relayerUrl);
24729
+ const file = profileFile({ vault, agentId, image: await agentCardUri(this.#config.relayerUrl, agentId) });
24481
24730
  const beforeUri = await this.#profileUri(vault);
24482
24731
  const before = await readIpfsJson(beforeUri);
24483
24732
  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`);
@@ -24503,6 +24752,32 @@ var init_launchpad = __esm({
24503
24752
  if (profile.status !== "sent" || record === null) return { uri: uri2, profile, agentUri: null, pairCreators };
24504
24753
  return { uri: uri2, profile, agentUri: await this.setAgentUri(uri2), pairCreators };
24505
24754
  }
24755
+ /**
24756
+ * Read only: why the creator vault cannot pay for publishRegistrationFile now, or null when it can or does not pay
24757
+ * through the relayer. Its spends (the profile and, once the agent is registered, its agent URI) are simulated as
24758
+ * setProfile and setAgentUri send them, with the URI set now (or one of an ipfs:// URI's length) in place of the
24759
+ * file's, and each is counted at the gas it will be reserved (simulatedGas) with WOTS_GAS_SPREAD added (a simulation
24760
+ * signs with a throwaway key, and a WOTS signature's verification costs more or less with each message);
24761
+ * RelayerClient.shortfall replays their payment. A
24762
+ * spend the creator vault signed earlier and has not sent is refused here: its next operation would send that one
24763
+ * instead of the profile.
24764
+ */
24765
+ async publishShortfall() {
24766
+ if (!(this.#creatorSender instanceof RelayerClient)) return null;
24767
+ const s = await this.#creator.state();
24768
+ if (!s.exists) return `the creator vault ${s.vault} does not exist yet`;
24769
+ if ((await this.#creator.spendSlot())?.pending != null) {
24770
+ return `the creator vault ${s.vault} signed a spend earlier that has not landed: its next operation sends that one first (publish_profile with the same fields does), then publish again`;
24771
+ }
24772
+ const record = await this.#agentRecord();
24773
+ const uri2 = record?.uri ?? await this.#profileUri(s.vault) ?? STAND_IN_URI;
24774
+ const relayer = await this.#creatorSender.payer();
24775
+ const gasOf = async (action, params) => simulatedGas((await simulateVaultSpend(this.#client, this.#config.deployment, { relayer, vault: s.vault, action, params })).gasUsed + WOTS_GAS_SPREAD);
24776
+ const gases = [await gasOf(VAULT_ACTION.SET_PROFILE, setProfileParams(uri2))];
24777
+ if (record !== null) gases.push(await gasOf(VAULT_ACTION.SET_AGENT_URI, setAgentUriParams({ agentId: BigInt(record.agentId), uri: uri2 })));
24778
+ const short = await this.#creatorSender.shortfall(gases);
24779
+ return short === null ? null : `${short}; fund_creator_vault moves USDG into it`;
24780
+ }
24506
24781
  /**
24507
24782
  * Pins `file`, a registration file built from the current one (Ostrapad's listing, pad.ts), and makes it the
24508
24783
  * agent's profile in OstraRegistry and its ERC-8004 registration file when it is registered.
@@ -24809,7 +25084,7 @@ var init_pad2 = __esm({
24809
25084
  publish(offers) {
24810
25085
  return this.#serial(async () => {
24811
25086
  const { agentId, creatorVault } = await this.#p.identity();
24812
- if (agentId === null) throw new Error("this agent is not registered in ERC-8004 yet: publish_profile, then register_agent; nothing was signed");
25087
+ if (agentId === null) throw new Error("this agent is not registered in ERC-8004 yet: publish_profile registers it; nothing was signed");
24813
25088
  const vault = await this.#vault();
24814
25089
  const current = await this.#p.currentFile();
24815
25090
  if (current === null) throw new Error("this agent has no published profile yet: publish_profile first; nothing was signed");
@@ -24818,7 +25093,11 @@ var init_pad2 = __esm({
24818
25093
  if (offers !== null) {
24819
25094
  const entry = padServiceEntry({ provider: vault, offers }, this.#pad, this.#p.config.chainId);
24820
25095
  const digest = padListingDigest(agentId, creatorVault, this.#pad, this.#p.config.chainId);
24821
- if (!await this.#approved(vault, digest)) approval = await this.#approveAlone(digest, "Ostrapad listing approval");
25096
+ if (!await this.#approved(vault, digest)) {
25097
+ const short = this.#p.publishShortfall === void 0 ? null : await this.#p.publishShortfall();
25098
+ if (short !== null) throw new Error(`the creator vault could not publish the listing now, so its approval is not given: ${short}; nothing was signed`);
25099
+ approval = await this.#approveAlone(digest, "Ostrapad listing approval");
25100
+ }
24822
25101
  services.push(entry);
24823
25102
  }
24824
25103
  const out = await this.#p.publishFile({ ...current, services });
@@ -25542,6 +25821,19 @@ var init_pay = __esm({
25542
25821
 
25543
25822
  // ../core/src/raeto.ts
25544
25823
  import { getAddress as getAddress27, hashMessage, isAddress as isAddress13, parseAbi as parseAbi6 } from "viem";
25824
+ function netYieldSince(mark, now3, book) {
25825
+ if (mark.debts === void 0) return null;
25826
+ const high = BigInt(mark.price);
25827
+ const earned = now3.price > high ? BigInt(mark.shares) * (now3.price - high) / now3.unit : 0n;
25828
+ let interest = 0n;
25829
+ for (const d of mark.debts) {
25830
+ const m = book.find((b) => b.marketId.toLowerCase() === d.marketId.toLowerCase());
25831
+ if (m === void 0) return null;
25832
+ const owesNow = toAssetsUp(BigInt(d.borrowShares), m.borrowed, m.borrowedShares);
25833
+ if (owesNow > BigInt(d.debt)) interest += owesNow - BigInt(d.debt);
25834
+ }
25835
+ return { earned, interest };
25836
+ }
25545
25837
  function raetoImages(answer) {
25546
25838
  const out = [];
25547
25839
  const choices = answer?.choices;
@@ -25788,15 +26080,20 @@ var init_raeto = __esm({
25788
26080
  return this.#scrub(await this.#call("POST", "/v1/chat/completions", request, await this.#key(), COMPLETION_TIMEOUT_MS));
25789
26081
  }
25790
26082
  /**
25791
- * What the vault's cash vault shares earn, as credits. Kept here: a mark (the shares, and the highest share price read:
25792
- * a high-water mark, so a price that falls and comes back earns nothing until it passes its high), what is owed (the
25793
- * shares of each mark times the rise of the price over its high, added on every call), and USDG redeemed for it and
25794
- * not sent yet (`ready`), with the vault's spend slot its transfer is signed at. A call adds what the shares earned
25795
- * since the mark, moves the mark to now, sends what was redeemed and not sent first, and else, when what is owed is at
25796
- * least `min` USDG, redeems that from the cash vault and sends what the redeem paid (at most what is owed) to Raeto's
25797
- * payTo. A transfer of `ready` that another operation of the vault sent first (it sends a signed spend before its
25798
- * own) is found at its slot and not paid again. Shares added or removed count from the read after; a full exit leaves
25799
- * what was owed until then. Raeto's terms are checked before anything is redeemed. Calls run one after the other.
26083
+ * What the vault's cash vault shares earn, net of the agent's Borro debts' interest, as credits. Kept here: a mark
26084
+ * (the shares, the highest share price read: a high-water mark, so a price that falls and comes back earns nothing
26085
+ * until it passes its high; and each Borro debt's borrow shares with what they owed), what is owed (netYieldSince,
26086
+ * added on every call: the shares' rise over the high less the interest the marked debts accrued; it can fall below
26087
+ * zero, and nothing is sent until it is back above `min`), and USDG redeemed for it and not sent yet (`ready`), with
26088
+ * the vault's spend slot its transfer is signed at. The interest comes off because the carry strategy borrows USDG
26089
+ * from Borro into this same cash vault: the shares' rise is not earned while the loan behind them owes interest.
26090
+ * A call adds what was earned since the mark, moves the mark to now, sends what was redeemed and not sent first, and
26091
+ * else, when what is owed is at least `min` USDG, redeems that from the cash vault and sends what the redeem paid (at
26092
+ * most what is owed) to Raeto's payTo. A transfer of `ready` that another operation of the vault sent first (it sends
26093
+ * a signed spend before its own) is found at its slot and not paid again. Shares and debts added or removed count
26094
+ * from the read after; a full exit leaves what was owed until then. A mark kept before debts were marked (MCP 0.1.10
26095
+ * and earlier) earns nothing for the time since it, as that time's interest cannot be read back. Raeto's terms are
26096
+ * checked before anything is redeemed. Calls run one after the other.
25800
26097
  */
25801
26098
  fundFromYield(min2 = "1") {
25802
26099
  return this.#serial(async () => {
@@ -25810,20 +26107,25 @@ var init_raeto = __esm({
25810
26107
  return { shares, unit, price };
25811
26108
  };
25812
26109
  const now3 = await read();
26110
+ const book = await this.#p.borro.debtBook();
26111
+ const debts = book.filter((b) => b.borrowShares > 0n).map((b) => ({ marketId: b.marketId, borrowShares: b.borrowShares.toString(), debt: toAssetsUp(b.borrowShares, b.borrowed, b.borrowedShares).toString() }));
25813
26112
  const text3 = await this.#p.folder.read(YIELD_FILE);
25814
26113
  const kept = text3 === void 0 ? null : JSON.parse(text3);
25815
- const usd4 = (x) => formatUnitsAmount(x, TOKEN_DECIMALS);
26114
+ const usd4 = (x) => formatUnitsAmount(x > 0n ? x : 0n, TOKEN_DECIMALS);
25816
26115
  const write = (m) => this.#p.folder.write(YIELD_FILE, JSON.stringify(m));
25817
26116
  if (kept === null || getAddress27(kept.vault) !== target) {
25818
- await write({ vault: target, shares: now3.shares.toString(), price: now3.price.toString(), owed: "0", ready: "0" });
25819
- return { vault: target, owed: "0", sent: "0", note: "the mark is set now: what the shares earn from here is sent as credits by later calls" };
26117
+ await write({ vault: target, shares: now3.shares.toString(), price: now3.price.toString(), owed: "0", ready: "0", debts });
26118
+ return { vault: target, owed: "0", sent: "0", note: "the mark is set now: what the shares earn from here, less the Borro debts' interest, is sent as credits by later calls" };
25820
26119
  }
25821
26120
  const high = BigInt(kept.price);
25822
- let owed = BigInt(kept.owed) + (now3.price > high ? BigInt(kept.shares) * (now3.price - high) / now3.unit : 0n);
26121
+ const net = netYieldSince(kept, now3, book);
26122
+ let owed = BigInt(kept.owed) + (net === null ? 0n : net.earned - net.interest);
25823
26123
  let ready = BigInt(kept.ready);
25824
- const mark = { ...kept, vault: target, shares: now3.shares.toString(), price: (now3.price > high ? now3.price : high).toString(), owed: owed.toString(), ready: ready.toString() };
26124
+ const mark = { ...kept, vault: target, shares: now3.shares.toString(), price: (now3.price > high ? now3.price : high).toString(), owed: owed.toString(), ready: ready.toString(), debts };
25825
26125
  await write(mark);
25826
- if (ready === 0n && (owed === 0n || owed < minimum)) return { vault: target, owed: usd4(owed), sent: "0", note: `under ${min2} USDG: kept for a later call` };
26126
+ const unmarked = net === null ? "the mark did not follow the Borro debts yet (kept by an earlier version): nothing is counted for the time since it, and the mark now follows them; " : "";
26127
+ if (ready === 0n && owed < 0n) return { vault: target, owed: "0", sent: "0", note: `${unmarked}the Borro debts' interest since the mark is more than the shares earned: ${formatUnitsAmount(-owed, TOKEN_DECIMALS)} USDG to make up before anything is sent` };
26128
+ if (ready === 0n && (owed === 0n || owed < minimum)) return { vault: target, owed: usd4(owed), sent: "0", note: `${unmarked}under ${min2} USDG: kept for a later call` };
25827
26129
  const { payTo } = await this.terms();
25828
26130
  if (ready > 0n && mark.readySlot !== void 0) {
25829
26131
  const slot2 = { nonce: BigInt(mark.readySlot.nonce), keyIndex: BigInt(mark.readySlot.keyIndex) };
@@ -25859,7 +26161,7 @@ var init_raeto = __esm({
25859
26161
  ready = paid <= 0n ? 0n : paid < owed ? paid : owed;
25860
26162
  owed -= ready;
25861
26163
  const afterHigh = BigInt(mark.price) > after.price ? BigInt(mark.price) : after.price;
25862
- await write({ vault: target, shares: after.shares.toString(), price: afterHigh.toString(), owed: owed.toString(), ready: ready.toString() });
26164
+ await write({ vault: target, shares: after.shares.toString(), price: afterHigh.toString(), owed: owed.toString(), ready: ready.toString(), debts });
25863
26165
  if (ready === 0n) return { vault: target, owed: usd4(owed), sent: "0", note: "the redeem paid nothing into the vault", redeem };
25864
26166
  }
25865
26167
  const slot = await this.#p.buyer.spendSlot();
@@ -25879,10 +26181,10 @@ var init_raeto = __esm({
25879
26181
  }
25880
26182
  });
25881
26183
 
25882
- // ../../ostra-swap/sdk/src/abi.ts
26184
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/abi.ts
25883
26185
  var ostraRouterAbi, ostraStakeAbi, ostraPrivatePoolAbi, OSTRA_SWAP_ESCROW_CREATION_CODE, OSTRA_SHIELD_ESCROW_CREATION_CODE, ostraQuoteBatchAbi, OSTRA_QUOTE_BATCH_CREATION_CODE;
25884
26186
  var init_abi2 = __esm({
25885
- "../../ostra-swap/sdk/src/abi.ts"() {
26187
+ "../../../../../../srv/apps/ostra-swap/sdk/src/abi.ts"() {
25886
26188
  "use strict";
25887
26189
  init_define_OSTRA_PACKAGED();
25888
26190
  ostraRouterAbi = [
@@ -28747,10 +29049,10 @@ var init_abi2 = __esm({
28747
29049
  }
28748
29050
  });
28749
29051
 
28750
- // ../../ostra-swap/sdk/src/constants.ts
29052
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/constants.ts
28751
29053
  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;
28752
29054
  var init_constants2 = __esm({
28753
- "../../ostra-swap/sdk/src/constants.ts"() {
29055
+ "../../../../../../srv/apps/ostra-swap/sdk/src/constants.ts"() {
28754
29056
  "use strict";
28755
29057
  init_define_OSTRA_PACKAGED();
28756
29058
  LOG_WINDOW_BLOCKS2 = 10000000n;
@@ -28792,11 +29094,11 @@ var init_constants2 = __esm({
28792
29094
  }
28793
29095
  });
28794
29096
 
28795
- // ../../ostra-swap/sdk/src/uniswap.ts
29097
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/uniswap.ts
28796
29098
  import { parseAbi as parseAbi7, parseAbiItem as parseAbiItem6 } from "viem";
28797
29099
  var v3FactoryAbi, v3PoolAbi, quoterV2Abi, poolKeyComponents, v4QuoterAbi, exactInputSingleParam, poolManagerAbi, initializeEvent, erc20Abi, multicall3Abi2;
28798
29100
  var init_uniswap = __esm({
28799
- "../../ostra-swap/sdk/src/uniswap.ts"() {
29101
+ "../../../../../../srv/apps/ostra-swap/sdk/src/uniswap.ts"() {
28800
29102
  "use strict";
28801
29103
  init_define_OSTRA_PACKAGED();
28802
29104
  v3FactoryAbi = parseAbi7(["function getPool(address tokenA, address tokenB, uint24 fee) view returns (address)"]);
@@ -28888,7 +29190,7 @@ var init_uniswap = __esm({
28888
29190
  }
28889
29191
  });
28890
29192
 
28891
- // ../../ostra-swap/sdk/src/legs.ts
29193
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/legs.ts
28892
29194
  import { concatHex, encodeAbiParameters as encodeAbiParameters10, encodePacked as encodePacked4, getAddress as getAddress28, toHex as toHex5 } from "viem";
28893
29195
  function legFor(pool, tokenIn, tokenOut, recipient = MSG_SENDER) {
28894
29196
  const inAddr = getAddress28(tokenIn);
@@ -28925,7 +29227,7 @@ function legFor(pool, tokenIn, tokenOut, recipient = MSG_SENDER) {
28925
29227
  }
28926
29228
  var NO_LEG, byte;
28927
29229
  var init_legs = __esm({
28928
- "../../ostra-swap/sdk/src/legs.ts"() {
29230
+ "../../../../../../srv/apps/ostra-swap/sdk/src/legs.ts"() {
28929
29231
  "use strict";
28930
29232
  init_define_OSTRA_PACKAGED();
28931
29233
  init_constants2();
@@ -28935,7 +29237,7 @@ var init_legs = __esm({
28935
29237
  }
28936
29238
  });
28937
29239
 
28938
- // ../../ostra-swap/sdk/src/pools.ts
29240
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/pools.ts
28939
29241
  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";
28940
29242
  function stateSlot(id, offset) {
28941
29243
  const base = hexToBigInt2(keccak25617(encodeAbiParameters11([{ type: "bytes32" }, { type: "uint256" }], [id, POOLS_SLOT3])));
@@ -28998,7 +29300,7 @@ async function hubPools(client, logsClient, index, token, hub) {
28998
29300
  }
28999
29301
  var POOLS_SLOT3, LIQUIDITY_OFFSET2, PoolIndex;
29000
29302
  var init_pools2 = __esm({
29001
- "../../ostra-swap/sdk/src/pools.ts"() {
29303
+ "../../../../../../srv/apps/ostra-swap/sdk/src/pools.ts"() {
29002
29304
  "use strict";
29003
29305
  init_define_OSTRA_PACKAGED();
29004
29306
  init_constants2();
@@ -29044,7 +29346,7 @@ var init_pools2 = __esm({
29044
29346
  }
29045
29347
  });
29046
29348
 
29047
- // ../../ostra-swap/sdk/src/route.ts
29349
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/route.ts
29048
29350
  import { decodeAbiParameters as decodeAbiParameters5, decodeFunctionResult as decodeFunctionResult4, encodeDeployData as encodeDeployData2, encodeFunctionData as encodeFunctionData13, getAddress as getAddress30 } from "viem";
29049
29351
  function routedToken(token) {
29050
29352
  const t = getAddress30(token);
@@ -29168,7 +29470,7 @@ function routeLegs(route) {
29168
29470
  }
29169
29471
  var NoRouteError, quoteResults;
29170
29472
  var init_route = __esm({
29171
- "../../ostra-swap/sdk/src/route.ts"() {
29473
+ "../../../../../../srv/apps/ostra-swap/sdk/src/route.ts"() {
29172
29474
  "use strict";
29173
29475
  init_define_OSTRA_PACKAGED();
29174
29476
  init_abi2();
@@ -29194,7 +29496,7 @@ var init_route = __esm({
29194
29496
  }
29195
29497
  });
29196
29498
 
29197
- // ../../ostra-swap/sdk/src/orders.ts
29499
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/orders.ts
29198
29500
  import { encodeAbiParameters as encodeAbiParameters12, encodeFunctionData as encodeFunctionData14, getAddress as getAddress31, getContractAddress as getContractAddress4, isAddress as isAddress14, keccak256 as keccak25618, toHex as toHex7 } from "viem";
29199
29501
  function parseSwapDeployment(json) {
29200
29502
  const r = json;
@@ -29248,7 +29550,7 @@ function executeEscrowData(order, route) {
29248
29550
  }
29249
29551
  var ZERO, MAX_UINT32, MAX_UINT64, MAX_STANDING_ORDER_SECONDS, legParam, orderParam;
29250
29552
  var init_orders = __esm({
29251
- "../../ostra-swap/sdk/src/orders.ts"() {
29553
+ "../../../../../../srv/apps/ostra-swap/sdk/src/orders.ts"() {
29252
29554
  "use strict";
29253
29555
  init_define_OSTRA_PACKAGED();
29254
29556
  init_abi2();
@@ -29287,7 +29589,7 @@ var init_orders = __esm({
29287
29589
  }
29288
29590
  });
29289
29591
 
29290
- // ../../ostra-swap/sdk/src/private.ts
29592
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/private.ts
29291
29593
  import { encodeAbiParameters as encodeAbiParameters13, encodeFunctionData as encodeFunctionData15, getAbiItem, getAddress as getAddress32, getContractAddress as getContractAddress5, keccak256 as keccak25619, parseEventLogs, toHex as toHex8 } from "viem";
29292
29594
  function packDigest(elements) {
29293
29595
  if (elements.length !== 8) throw new Error("a digest has 8 field elements");
@@ -29487,7 +29789,7 @@ async function swapOutputNote(prover, sk, outRho, spend, swap, receipt, pool) {
29487
29789
  }
29488
29790
  var FIELD_P, TREE_DEPTH, PRIVATE_SWAP_FEE_BPS, UNSHIELD_DOMAIN, SWAP_DOMAIN, MAX_AMOUNT, MAX_UINT642, ZERO2, noteAddedEvent, PoolTrees;
29489
29791
  var init_private = __esm({
29490
- "../../ostra-swap/sdk/src/private.ts"() {
29792
+ "../../../../../../srv/apps/ostra-swap/sdk/src/private.ts"() {
29491
29793
  "use strict";
29492
29794
  init_define_OSTRA_PACKAGED();
29493
29795
  init_abi2();
@@ -29603,19 +29905,19 @@ var init_private = __esm({
29603
29905
  }
29604
29906
  });
29605
29907
 
29606
- // ../../ostra-swap/sdk/src/reads.ts
29908
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/reads.ts
29607
29909
  import { getAddress as getAddress33 } from "viem";
29608
29910
  var init_reads2 = __esm({
29609
- "../../ostra-swap/sdk/src/reads.ts"() {
29911
+ "../../../../../../srv/apps/ostra-swap/sdk/src/reads.ts"() {
29610
29912
  "use strict";
29611
29913
  init_define_OSTRA_PACKAGED();
29612
29914
  init_abi2();
29613
29915
  }
29614
29916
  });
29615
29917
 
29616
- // ../../ostra-swap/sdk/src/index.ts
29918
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/index.ts
29617
29919
  var init_src2 = __esm({
29618
- "../../ostra-swap/sdk/src/index.ts"() {
29920
+ "../../../../../../srv/apps/ostra-swap/sdk/src/index.ts"() {
29619
29921
  "use strict";
29620
29922
  init_define_OSTRA_PACKAGED();
29621
29923
  init_abi2();
@@ -31292,7 +31594,7 @@ var init_strategy2 = __esm({
31292
31594
  if (who !== null && getAddress37(who.owner) === creatorVault && getAddress37(who.wallet) === creatorVault) return { agentId: id.toString(), creatorVault };
31293
31595
  }
31294
31596
  throw new StrategyGateError(
31295
- `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.`
31597
+ `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.`
31296
31598
  );
31297
31599
  }
31298
31600
  // -------------------------------------------------------------------------------------------------------------------
@@ -32191,7 +32493,7 @@ function refusalOf(reason, outDecimals) {
32191
32493
  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)`;
32192
32494
  }
32193
32495
  }
32194
- 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;
32496
+ 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;
32195
32497
  var init_swap = __esm({
32196
32498
  "../core/src/swap.ts"() {
32197
32499
  "use strict";
@@ -32215,6 +32517,8 @@ var init_swap = __esm({
32215
32517
  POOL_INDEX2 = "pool-index.json";
32216
32518
  ORDER_PREFIX = "order-";
32217
32519
  SPLIT_PREFIX = "split-";
32520
+ DONE_PREFIX = "done-";
32521
+ DONE_ORDERS_KEPT = 100;
32218
32522
  address4 = (text3, what) => {
32219
32523
  if (!/^0x[0-9a-fA-F]{40}$/.test(text3)) throw new RangeError(`${what} is not an address`);
32220
32524
  return getAddress38(text3);
@@ -32427,7 +32731,23 @@ var init_swap = __esm({
32427
32731
  const escrow = escrowAddress(this.#p.swap.router, order);
32428
32732
  const execute = executeEscrowData(order, p.route);
32429
32733
  await p.from.checkSpend(VAULT_ACTION.TRANSFER, transferParams({ to: escrow, amount: p.amountIn }), "the swap's transfer to its escrow", this.#p.sender);
32430
- 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() });
32734
+ await this.#keep({
32735
+ version: 1,
32736
+ hash: orderHash(order),
32737
+ escrow,
32738
+ tokenIn: p.tokenIn,
32739
+ amountIn: p.amountIn.toString(),
32740
+ vaultNonce: v.nonce.toString(),
32741
+ deadline: order.deadline.toString(),
32742
+ execute,
32743
+ at: Date.now(),
32744
+ fromBlock: fromBlock.toString(),
32745
+ tokenOut: p.tokenOut,
32746
+ from: vault,
32747
+ to: p.to,
32748
+ inDecimals: p.inDecimals,
32749
+ outDecimals: p.outDecimals
32750
+ });
32431
32751
  await o.onOrder?.(orderHash(order), fromBlock, escrow);
32432
32752
  const signed = await signVaultTransfer(keychain, v, { chainId, to: escrow, amount: p.amountIn });
32433
32753
  const calls = [spendCall(vault, signed), { to: this.#p.swap.router, data: execute }];
@@ -32456,6 +32776,21 @@ var init_swap = __esm({
32456
32776
  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`
32457
32777
  };
32458
32778
  }
32779
+ await this.#keepDone({
32780
+ version: 1,
32781
+ order: orderHash(placed2),
32782
+ txHash: result.txHash,
32783
+ swapped: outcome.swapped,
32784
+ tokenIn: p.tokenIn,
32785
+ tokenOut: p.tokenOut,
32786
+ amountIn: formatUnitsAmount(p.amountIn, p.inDecimals),
32787
+ amountOut: formatUnitsAmount(outcome.amountOut, p.outDecimals),
32788
+ minOut: formatUnitsAmount(p.route.minOut, p.outDecimals),
32789
+ from: p.fromVault,
32790
+ to: p.to,
32791
+ ...outcome.swapped ? {} : { refusal: outcome.why ?? "OstraRouter gave no reason" },
32792
+ at: Date.now()
32793
+ });
32459
32794
  await this.#p.folder.remove(`${ORDER_PREFIX}${orderHash(placed2)}.json`);
32460
32795
  return {
32461
32796
  status: "sent",
@@ -32792,6 +33127,92 @@ var init_swap = __esm({
32792
33127
  if (swapped) return { swapped, amountOut };
32793
33128
  return { swapped, amountOut: 0n, ...reason === null ? {} : { why: refusalOf(reason, outDecimals) } };
32794
33129
  }
33130
+ /** Keeps a finished order for ostra_swap_orders, and drops the oldest beyond DONE_ORDERS_KEPT. */
33131
+ async #keepDone(d) {
33132
+ await this.#p.folder.ensure();
33133
+ await this.#p.folder.write(`${DONE_PREFIX}${d.order.toLowerCase()}.json`, `${JSON.stringify(d)}
33134
+ `);
33135
+ const names = (await this.#p.folder.list()).filter((n) => n.startsWith(DONE_PREFIX) && n.endsWith(".json"));
33136
+ if (names.length <= DONE_ORDERS_KEPT) return;
33137
+ const dated = [];
33138
+ for (const n of names) {
33139
+ const text3 = await this.#p.folder.read(n);
33140
+ dated.push({ name: n, at: text3 === void 0 ? 0 : Number(JSON.parse(text3).at) });
33141
+ }
33142
+ dated.sort((a, b) => a.at - b.at);
33143
+ for (const x of dated.slice(0, dated.length - DONE_ORDERS_KEPT)) await this.#p.folder.remove(x.name);
33144
+ }
33145
+ /**
33146
+ * Keeps what a kept order did once the sweep finishes it: from the transaction that executed it (`tx`), else from
33147
+ * OstraRouter's events from the record's block. Best effort: an order whose events cannot be read now is not kept
33148
+ * (a record from before 0.1.12 has no block to search from).
33149
+ */
33150
+ async #doneFrom(r, tx) {
33151
+ try {
33152
+ let hash = tx;
33153
+ if (hash === null && r.fromBlock !== void 0) {
33154
+ const toBlock = await this.#p.logsClient.getBlockNumber({ cacheTime: 0 });
33155
+ const logs = await getLogsChunked(this.#p.logsClient, { address: this.#p.swap.router, event: getAbiItem2({ abi: ostraRouterAbi, name: "EscrowExecuted" }), args: { orderHash: r.hash }, fromBlock: BigInt(r.fromBlock), toBlock });
33156
+ const moved = logs.find((l) => l.args.received !== 0n);
33157
+ hash = moved === void 0 ? null : moved.transactionHash;
33158
+ }
33159
+ if (hash === null) return;
33160
+ const inDecimals = r.inDecimals ?? await tokenDecimals(this.#p.client, r.tokenIn);
33161
+ const outDecimals = r.outDecimals ?? (r.tokenOut === void 0 ? 18 : await tokenDecimals(this.#p.client, r.tokenOut));
33162
+ const o = await this.#outcome(hash, r.hash, outDecimals);
33163
+ await this.#keepDone({
33164
+ version: 1,
33165
+ order: r.hash,
33166
+ txHash: hash,
33167
+ swapped: o.swapped,
33168
+ tokenIn: r.tokenIn,
33169
+ tokenOut: r.tokenOut ?? null,
33170
+ amountIn: formatUnitsAmount(BigInt(r.amountIn), inDecimals),
33171
+ amountOut: formatUnitsAmount(o.amountOut, outDecimals),
33172
+ minOut: null,
33173
+ from: r.from ?? null,
33174
+ to: r.to ?? null,
33175
+ ...o.swapped ? {} : { refusal: o.why ?? "OstraRouter gave no reason" },
33176
+ at: Date.now()
33177
+ });
33178
+ } catch {
33179
+ }
33180
+ }
33181
+ /**
33182
+ * The agent's orders kept here: those not finished (signed or about to be, or landed and being finished; oldest
33183
+ * first) and the finished ones (newest first, at most `limit`). With `order`, that order alone in either list.
33184
+ */
33185
+ async orders(o = {}) {
33186
+ const want = o.order?.toLowerCase();
33187
+ const open3 = [];
33188
+ const finished = [];
33189
+ for (const name of await this.#p.folder.list()) {
33190
+ const isOrder = name.startsWith(ORDER_PREFIX);
33191
+ if (!isOrder && !name.startsWith(DONE_PREFIX) || !name.endsWith(".json")) continue;
33192
+ if (want !== void 0 && name.slice((isOrder ? ORDER_PREFIX : DONE_PREFIX).length, -".json".length).toLowerCase() !== want) continue;
33193
+ const text3 = await this.#p.folder.read(name);
33194
+ if (text3 === void 0) continue;
33195
+ if (!isOrder) {
33196
+ finished.push(JSON.parse(text3));
33197
+ continue;
33198
+ }
33199
+ const r = JSON.parse(text3);
33200
+ const inDecimals = r.inDecimals ?? await tokenDecimals(this.#p.client, r.tokenIn);
33201
+ 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 };
33202
+ if (r.fromBlock !== void 0) {
33203
+ const res = await this.orderResult(r.hash, BigInt(r.fromBlock), r.escrow);
33204
+ if (res.spent > 0n || res.received > 0n) {
33205
+ x.state = "landed";
33206
+ x.spent = formatUnitsAmount(res.spent, inDecimals);
33207
+ x.received = formatUnitsAmount(res.received, r.outDecimals ?? 18);
33208
+ }
33209
+ }
33210
+ open3.push(x);
33211
+ }
33212
+ open3.sort((a, b) => a.at - b.at);
33213
+ finished.sort((a, b) => b.at - a.at);
33214
+ return { open: open3, finished: finished.slice(0, o.limit ?? 20) };
33215
+ }
32795
33216
  /**
32796
33217
  * Finishes the orders whose escrow was paid without being executed (a signed spend sent again on its own):
32797
33218
  * executeEscrow sends the input along the order's route, or back to its vault past the deadline. Records of orders
@@ -32808,11 +33229,15 @@ var init_swap = __esm({
32808
33229
  const nonce = (await this.#p.vaultFor(r.tokenIn).state()).nonce;
32809
33230
  const held = await tokenBalance(this.#p.client, r.tokenIn, r.escrow);
32810
33231
  if (held >= BigInt(r.amountIn)) {
32811
- await this.#p.sender.send([{ to: this.#p.swap.router, data: r.execute }]);
33232
+ const tx = await this.#p.sender.send([{ to: this.#p.swap.router, data: r.execute }]);
33233
+ await this.#doneFrom(r, tx);
32812
33234
  await this.#p.folder.remove(name);
32813
33235
  continue;
32814
33236
  }
32815
- if (nonce !== null && nonce > BigInt(r.vaultNonce)) await this.#p.folder.remove(name);
33237
+ if (nonce !== null && nonce > BigInt(r.vaultNonce)) {
33238
+ await this.#doneFrom(r, null);
33239
+ await this.#p.folder.remove(name);
33240
+ }
32816
33241
  } catch (err) {
32817
33242
  failures.push({ order: r.hash, error: err instanceof Error ? err.message : String(err) });
32818
33243
  }
@@ -33676,7 +34101,7 @@ var init_backup_cli = __esm({
33676
34101
  }
33677
34102
  });
33678
34103
 
33679
- // ../../ostra-swap/sdk/src/cli-prover.ts
34104
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/cli-prover.ts
33680
34105
  import { spawn } from "node:child_process";
33681
34106
  function cliProver(binary, timeoutMs = PROVER_TIMEOUT_MS) {
33682
34107
  return (command, request) => new Promise((resolve, reject) => {
@@ -33717,14 +34142,14 @@ function cliProver(binary, timeoutMs = PROVER_TIMEOUT_MS) {
33717
34142
  }
33718
34143
  var PROVER_TIMEOUT_MS;
33719
34144
  var init_cli_prover = __esm({
33720
- "../../ostra-swap/sdk/src/cli-prover.ts"() {
34145
+ "../../../../../../srv/apps/ostra-swap/sdk/src/cli-prover.ts"() {
33721
34146
  "use strict";
33722
34147
  init_define_OSTRA_PACKAGED();
33723
34148
  PROVER_TIMEOUT_MS = 12e4;
33724
34149
  }
33725
34150
  });
33726
34151
 
33727
- // ../../ostra-swap/sdk/src/wasm-prover.ts
34152
+ // ../../../../../../srv/apps/ostra-swap/sdk/src/wasm-prover.ts
33728
34153
  import { bytesToHex as bytesToHex17 } from "viem";
33729
34154
  async function sha256Hex(bytes) {
33730
34155
  return bytesToHex17(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)));
@@ -33760,7 +34185,7 @@ async function wasmProver(glue, bytes, sha2564) {
33760
34185
  }
33761
34186
  var WasmProverTrapped, initialized, trapped;
33762
34187
  var init_wasm_prover = __esm({
33763
- "../../ostra-swap/sdk/src/wasm-prover.ts"() {
34188
+ "../../../../../../srv/apps/ostra-swap/sdk/src/wasm-prover.ts"() {
33764
34189
  "use strict";
33765
34190
  init_define_OSTRA_PACKAGED();
33766
34191
  WasmProverTrapped = class extends Error {
@@ -33881,7 +34306,7 @@ var init_agent = __esm({
33881
34306
  this.creator = new VaultService(client, transport, config, p.creatorKeys, creatorSender, "creator");
33882
34307
  this.creatorRelayer?.attachBuyerVault(this.creator);
33883
34308
  this.creatorChannels = new ChannelService(client, config, p.creatorKeys, this.creator, creatorSender);
33884
- this.launchpad = config.deployment.launchpad === void 0 ? null : new LaunchpadService({ client, transport, config, keys, buyer: this.buyer, buyerSender: this.sender, creator: this.creator, creatorSender });
34309
+ 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 }) }) });
33885
34310
  this.shield = new ShieldService(client, config, keys, this.buyer, this.sender);
33886
34311
  this.creatorShield = new ShieldService(client, config, keys, this.creator, creatorSender);
33887
34312
  this.unshield = new UnshieldService(client, config, keys, this.shield, (index) => this.privateVault(index));
@@ -33955,6 +34380,7 @@ var init_agent = __esm({
33955
34380
  return typeof p2.profile === "object" && p2.profile !== null && !Array.isArray(p2.profile) ? p2.profile : null;
33956
34381
  },
33957
34382
  publishFile: (file) => lp.publishRegistrationFile(file),
34383
+ publishShortfall: () => lp.publishShortfall(),
33958
34384
  upload: (file) => uploadJson(config.relayerUrl, file),
33959
34385
  indexerUrl: config.padIndexerUrl
33960
34386
  });
@@ -34179,6 +34605,12 @@ async function proverConfig(env) {
34179
34605
  }
34180
34606
  return { kind: "wasm", dir, sha256: sha2564 };
34181
34607
  }
34608
+ function swapAnswerMs(env) {
34609
+ const v = env.OSTRA_SWAP_ANSWER_MS;
34610
+ if (v === void 0 || v === "") return DEFAULT_SWAP_ANSWER_MS;
34611
+ 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");
34612
+ return Number(v);
34613
+ }
34182
34614
  function serviceUrl(env, name, fallback) {
34183
34615
  const value = env[name];
34184
34616
  if (value === void 0 || value === "") return fallback;
@@ -34228,6 +34660,7 @@ async function loadConfig(env = process.env) {
34228
34660
  swap,
34229
34661
  logsRpcUrl,
34230
34662
  privateLocalSpends: env.OSTRA_PRIVATE_LOCAL_SPENDS === "yes",
34663
+ swapAnswerMs: swapAnswerMs(env),
34231
34664
  prover,
34232
34665
  chainId: deployment.chainId,
34233
34666
  rpcUrl: rpcUrl ?? ROBINHOOD_PUBLIC_RPC,
@@ -34239,7 +34672,7 @@ async function loadConfig(env = process.env) {
34239
34672
  aiUrl: serviceUrl(env, "OSTRA_AI_URL", services?.aiUrl ?? null)
34240
34673
  };
34241
34674
  }
34242
- var PROVER_WASM_FILES, PAD_INDEXER_URL, ETHEREUM_PUBLIC_RPC;
34675
+ var PROVER_WASM_FILES, PAD_INDEXER_URL, ETHEREUM_PUBLIC_RPC, DEFAULT_SWAP_ANSWER_MS;
34243
34676
  var init_config2 = __esm({
34244
34677
  "src/config.ts"() {
34245
34678
  "use strict";
@@ -34252,10 +34685,12 @@ var init_config2 = __esm({
34252
34685
  PROVER_WASM_FILES = ["ostra_prove.js", "ostra_prove_bg.wasm"];
34253
34686
  PAD_INDEXER_URL = "https://app.ostraagent.xyz";
34254
34687
  ETHEREUM_PUBLIC_RPC = "https://ethereum-rpc.publicnode.com";
34688
+ DEFAULT_SWAP_ANSWER_MS = 4e4;
34255
34689
  }
34256
34690
  });
34257
34691
 
34258
34692
  // src/tools.ts
34693
+ import { randomBytes as randomBytes6 } from "node:crypto";
34259
34694
  import { readFile as readFile6, stat as stat4, writeFile as writeFile3 } from "node:fs/promises";
34260
34695
  import { join as join7 } from "node:path";
34261
34696
  import { z } from "zod";
@@ -34299,7 +34734,79 @@ function registerTools(server, agent) {
34299
34734
  return agent.privateBalance;
34300
34735
  };
34301
34736
  const opt = (key, value) => value === void 0 ? {} : { [key]: value };
34302
- 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]])));
34737
+ const rpcUrls2 = [agent.config.rpcUrl, agent.config.logsRpcUrl, ...agent.config.ethRpcUrl === null ? [] : [agent.config.ethRpcUrl]];
34738
+ const tool = (name, description, shape, fn) => CLOSED_LAUNCHPAD_TOOLS.has(name) ? void 0 : server.registerTool(name, { description, inputSchema: shape }, ((args) => run2(() => fn(args), rpcUrls2)));
34739
+ const answerMs = agent.config.swapAnswerMs;
34740
+ const swaps = /* @__PURE__ */ new Map();
34741
+ const swapView = (r) => ({
34742
+ swap: r.id,
34743
+ what: r.what,
34744
+ tokenIn: r.tokenIn,
34745
+ tokenOut: r.tokenOut,
34746
+ amount: r.amount,
34747
+ startedAt: new Date(r.startedAt).toISOString(),
34748
+ state: r.endedAt === void 0 ? "running" : r.error === void 0 ? "ended" : "failed",
34749
+ // False while its call still waits for it (a client that gave up on that call learns the result here).
34750
+ answeredPending: r.answered,
34751
+ ...r.order === void 0 ? {} : { order: r.order },
34752
+ ...r.split === void 0 ? {} : { split: r.split },
34753
+ ...r.endedAt === void 0 ? {} : { endedAt: new Date(r.endedAt).toISOString() },
34754
+ ...r.result === void 0 ? {} : { result: r.result },
34755
+ ...r.error === void 0 ? {} : { error: r.error }
34756
+ });
34757
+ const boundedSwap = async (a, start) => {
34758
+ const busy = [...swaps.values()].find((x) => x.endedAt === void 0 && x.tokenIn.toLowerCase() === a.tokenIn.toLowerCase());
34759
+ if (busy !== void 0) {
34760
+ 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`);
34761
+ }
34762
+ const r = { id: randomBytes6(8).toString("hex"), ...a, startedAt: Date.now(), answered: false };
34763
+ swaps.set(r.id, r);
34764
+ const work = Promise.resolve().then(() => start({
34765
+ onOrder: async (order) => {
34766
+ r.order = order;
34767
+ },
34768
+ onSplit: async (id) => {
34769
+ r.split = id;
34770
+ }
34771
+ }));
34772
+ let timer;
34773
+ const late = new Promise((resolve) => {
34774
+ timer = setTimeout(() => resolve(null), answerMs);
34775
+ });
34776
+ let first;
34777
+ try {
34778
+ first = await Promise.race([work.then((value) => ({ value })), late]);
34779
+ } catch (err) {
34780
+ swaps.delete(r.id);
34781
+ throw err;
34782
+ } finally {
34783
+ clearTimeout(timer);
34784
+ }
34785
+ if (first !== null) {
34786
+ swaps.delete(r.id);
34787
+ return first.value;
34788
+ }
34789
+ r.answered = true;
34790
+ work.then(
34791
+ (value) => {
34792
+ r.result = value;
34793
+ r.endedAt = Date.now();
34794
+ },
34795
+ (err) => {
34796
+ r.error = rpcUrls2.reduce((text3, url) => redact(text3, url), err instanceof Error ? err.message : String(err));
34797
+ r.endedAt = Date.now();
34798
+ }
34799
+ );
34800
+ const ended = [...swaps.values()].filter((x) => x.endedAt !== void 0).sort((x, y) => x.endedAt - y.endedAt);
34801
+ for (const x of ended.slice(0, Math.max(0, ended.length - ENDED_SWAPS_KEPT))) swaps.delete(x.id);
34802
+ return {
34803
+ status: "pending",
34804
+ swap: r.id,
34805
+ order: r.order ?? null,
34806
+ split: r.split ?? null,
34807
+ 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`}).`
34808
+ };
34809
+ };
34303
34810
  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());
34304
34811
  tool("balance", "The vault's balance, nonce, any operation signed and not landed, the relayer channels, and the creator vault.", {}, async () => ({
34305
34812
  vault: await agent.buyer.status(),
@@ -34393,9 +34900,27 @@ function registerTools(server, agent) {
34393
34900
  );
34394
34901
  tool(
34395
34902
  "ostra_swap",
34396
- "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.",
34903
+ `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.`,
34397
34904
  { tokenIn: swapToken, tokenOut: swapToken, amount: units.describe("in tokenIn"), slippageBps: slippage2, maxImpactBps: maxImpact },
34398
- (a) => ostraSwap().swap({ tokenIn: tokenAddress(a.tokenIn), tokenOut: tokenAddress(a.tokenOut), amount: a.amount, ...opt("slippageBps", a.slippageBps), ...opt("maxImpactBps", a.maxImpactBps) })
34905
+ (a) => {
34906
+ const tokenIn = tokenAddress(a.tokenIn);
34907
+ const tokenOut = tokenAddress(a.tokenOut);
34908
+ return boundedSwap(
34909
+ { what: "the swap", tokenIn, tokenOut, amount: a.amount },
34910
+ (hooks) => ostraSwap().swap({ tokenIn, tokenOut, amount: a.amount, ...opt("slippageBps", a.slippageBps), ...opt("maxImpactBps", a.maxImpactBps) }, hooks)
34911
+ );
34912
+ }
34913
+ );
34914
+ tool(
34915
+ "ostra_swap_orders",
34916
+ "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.",
34917
+ { order: bytes323.optional() },
34918
+ async (a) => {
34919
+ const kept = await ostraSwap().orders(a.order === void 0 ? {} : { order: a.order });
34920
+ const want = a.order?.toLowerCase();
34921
+ const pending = [...swaps.values()].filter((x) => want === void 0 || x.order?.toLowerCase() === want).sort((x, y) => y.startedAt - x.startedAt);
34922
+ return { pending: pending.map(swapView), open: kept.open, finished: kept.finished };
34923
+ }
34399
34924
  );
34400
34925
  tool(
34401
34926
  "ostra_swap_split",
@@ -34560,7 +35085,7 @@ function registerTools(server, agent) {
34560
35085
  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)");
34561
35086
  tool(
34562
35087
  "strategy_plan",
34563
- "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).",
35088
+ "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).",
34564
35089
  { buffer: usdg.optional().describe("USDG kept for payments (default: what the vault spent over 7 days)"), earnings },
34565
35090
  (a) => agent.strategy.plan({
34566
35091
  ...a.buffer === void 0 ? {} : { buffer: parseUsdg(a.buffer) },
@@ -34582,7 +35107,7 @@ function registerTools(server, agent) {
34582
35107
  const bpsLimit = z.number().int().min(1).max(1e4).optional();
34583
35108
  tool(
34584
35109
  "trading",
34585
- `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.`,
35110
+ `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.`,
34586
35111
  {
34587
35112
  action: z.enum(["set", "status", "buy", "sell", "end"]),
34588
35113
  capital: usdg.optional(),
@@ -34612,9 +35137,11 @@ function registerTools(server, agent) {
34612
35137
  const token = tokenAddress(a.token);
34613
35138
  if (a.action === "buy") {
34614
35139
  if (a.amount === "all") throw new RangeError("buy takes an amount of USDG");
34615
- return t.buy({ token, amount: a.amount, ...opt("slippageBps", a.slippageBps) });
35140
+ const amount2 = a.amount;
35141
+ return boundedSwap({ what: "the trading buy", tokenIn: USDG, tokenOut: token, amount: amount2 }, () => t.buy({ token, amount: amount2, ...opt("slippageBps", a.slippageBps) }));
34616
35142
  }
34617
- return t.sell({ token, amount: a.amount, ...opt("slippageBps", a.slippageBps) });
35143
+ const amount = a.amount;
35144
+ return boundedSwap({ what: "the trading sell", tokenIn: token, tokenOut: USDG, amount }, () => t.sell({ token, amount, ...opt("slippageBps", a.slippageBps) }));
34618
35145
  }
34619
35146
  );
34620
35147
  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());
@@ -34624,7 +35151,7 @@ function registerTools(server, agent) {
34624
35151
  };
34625
35152
  tool(
34626
35153
  "raeto",
34627
- "Raeto (raeto.cc): 400+ AI models (text, and image models such as google/gemini-2.5-flash-image and openai/gpt-5-image-mini) through one OpenAI-compatible API, paid in Raeto credits of the vault's own Raeto account. login: signs the USDG vault in as an agent with EIP-1271 (the vault approves Raeto's login message, then withdraws the approval; refused under the withdrawal lock), its API key kept on this machine and never shown (name: the account's name; limitUsd: its spending limit). me: the account's credits. models: the models and prices per token (output: image for the image models). fund: sends `amount` USDG from the vault to Raeto, as credits. fund_yield: sends what the cash vault shares earned since the last call (at least `min`, default 1 USDG), redeemed then sent. complete: a model call, `request` in the OpenAI chat format with Raeto's model id (images it returns are saved as files in the agent's directory).",
35154
+ "Raeto (raeto.cc): 400+ AI models (text, and image models such as google/gemini-2.5-flash-image and openai/gpt-5-image-mini) through one OpenAI-compatible API, paid in Raeto credits of the vault's own Raeto account. login: signs the USDG vault in as an agent with EIP-1271 (the vault approves Raeto's login message, then withdraws the approval; refused under the withdrawal lock), its API key kept on this machine and never shown (name: the account's name; limitUsd: its spending limit). me: the account's credits. models: the models and prices per token (output: image for the image models). fund: sends `amount` USDG from the vault to Raeto, as credits. fund_yield: sends what the cash vault shares earned since the last call, less the interest the agent's Borro loans accrued meanwhile (at least `min`, default 1 USDG), redeemed then sent. complete: a model call, `request` in the OpenAI chat format with Raeto's model id (images it returns are saved as files in the agent's directory).",
34628
35155
  {
34629
35156
  action: z.enum(["login", "me", "models", "fund", "fund_yield", "complete"]),
34630
35157
  name: z.string().min(1).max(64).optional(),
@@ -34683,7 +35210,7 @@ function registerTools(server, agent) {
34683
35210
  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)");
34684
35211
  tool(
34685
35212
  "pad_publish",
34686
- "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. 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.",
35213
+ "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.",
34687
35214
  { action: z.enum(["publish", "unpublish", "status", "bond", "withdraw_bond", "return_bond_box"]), offers: z.array(offer).min(1).max(20).optional(), amount: usdg.optional() },
34688
35215
  async (a) => {
34689
35216
  if (a.action === "publish") {
@@ -34833,17 +35360,9 @@ function registerTools(server, agent) {
34833
35360
  (a) => lp().tokenInfo(a.token)
34834
35361
  );
34835
35362
  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));
34836
- tool(
34837
- "set_profile",
34838
- "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.",
34839
- { uri },
34840
- (a) => lp().setProfile(a.uri)
34841
- );
34842
- 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));
34843
- tool("set_agent_uri", "Points the agent's ERC-8004 identity at a new registration file.", { uri }, (a) => lp().setAgentUri(a.uri));
34844
35363
  tool(
34845
35364
  "upload_image",
34846
- `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.`,
35365
+ `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.`,
34847
35366
  { path: z.string().min(1).max(4096) },
34848
35367
  async (a) => {
34849
35368
  const s = await stat4(a.path);
@@ -34853,9 +35372,9 @@ function registerTools(server, agent) {
34853
35372
  );
34854
35373
  tool(
34855
35374
  "publish_profile",
34856
- "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.",
34857
- { name: z.string().min(1).max(64), ...publicFields },
34858
- (a) => lp().publishProfile(a)
35375
+ `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).`,
35376
+ {},
35377
+ () => lp().publishProfile()
34859
35378
  );
34860
35379
  tool(
34861
35380
  "publish_token_metadata",
@@ -34918,7 +35437,7 @@ function launchOptions(raw) {
34918
35437
  }
34919
35438
  return out;
34920
35439
  }
34921
- 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;
35440
+ 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;
34922
35441
  var init_tools = __esm({
34923
35442
  "src/tools.ts"() {
34924
35443
  "use strict";
@@ -34971,6 +35490,7 @@ var init_tools = __esm({
34971
35490
  "airdrop_fund",
34972
35491
  "airdrop_start"
34973
35492
  ]);
35493
+ ENDED_SWAPS_KEPT = 20;
34974
35494
  BIGINT_FIELDS = /* @__PURE__ */ new Set(["minDividendBalance", "maxBuy", "maxWallet", "graduationThreshold"]);
34975
35495
  ALLOWED_OPTIONS = /* @__PURE__ */ new Set([
34976
35496
  "tiers",