@ostraagent/mcp 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -84,7 +84,7 @@ All settings are optional.
84
84
  | `OSTRA_ETH_RPC_URL` | `https://ethereum-rpc.publicnode.com` | An Ethereum mainnet RPC, for Chainlink's USDe / USD feed the strategies read before lending to a cash vault that can lend against USDe. `off`: no cash goes into such a vault. Never printed |
85
85
  | `OSTRA_RAETO_URL` | `https://api.raeto.cc` | Raeto's API. `off` turns the `raeto` tool off |
86
86
  | `OSTRA_PAD_INDEXER_URL` | `https://app.ostraagent.xyz` | Ostra's Ostrapad index, for `pad_search` by words. `off` turns that search off (a listing by agent id is still read from the chain) |
87
- | `OSTRA_IPFS_GATEWAYS` | Pinata's gateway, then ipfs.io | The IPFS gateways profiles and Ostrapad's files are read through, comma-separated, in the order they are tried (each URL ending in `/`). A raw CID is checked against the bytes, whichever gateway answers |
87
+ | `OSTRA_IPFS_GATEWAYS` | The Ostra uploader (`https://relayer.ostraagent.xyz/v1/ipfs/`), then Pinata's gateway, then ipfs.io | The IPFS gateways profiles and Ostrapad's files are read through, comma-separated, in the order they are tried (each URL ending in `/`). A raw CID is checked against the bytes, whichever gateway answers |
88
88
  | `OSTRA_DEPLOYMENT` | Ostra on Robinhood Chain mainnet | Path of another deployment record (JSON). With it, the four services default to off |
89
89
 
90
90
  On a local fork of mainnet (`OSTRA_RPC_URL` pointing at it), set the four services to `off`: they act on mainnet.
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.9", 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.11", deployment: { version: 1, chainId: 4663, fromBlock: "91991509", token: "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168", batchSettlement: "0x4020074e9dF2ce1deE5A9C1b5c3f541D02a10003", vaultImplementation: "0xec0ca4A1393D91c42cA809F7F9dd82Bd597E8E58", vaultBeacon: "0xE9F525B9EaeE48d13AEd28fE146F48f2832a18a0", vaultFactory: "0x043a15c514CFE2c797d33520bFF91a5DCdbf2979", poolImplementation: "0xC04d4a222dC1C966640B977608bE29949b0648ec", poolBeacon: "0x30669Ca534921038a531BBE27116d85bB7406fFF", pool: "0x6ea80F2462e5Fd783B0Ec3B2c7BaC96795980CD6", splitFactory: "0xAAf54A483B2c7c2c5ecFc1eDED5ed1619C1bA882", beaconOwner: "0x55533a51d9bd9b06F2D179d919BB31995EF96E5e", registry: "0xcE6Cc1f93744f4090DA1444Bb8590d9206157506", vaultModule: "0x55A89fF907f5F5756470e2ec5Dc5E4E1C54Da770", launchpad: { hook: "0x63A784afb8905452aDceC471a45E555F5caE68cC", fees: "0x8e79671DC9729476614C78aFcf2345D4c1963801", launchpad: "0xfC57162C0e3f9773F97fEa8d276e957d7c43A6C5", creatorSplit: "0x987994dE6A1d5303B9e3F50977cB0321BA4D033B", dispatcher: "0x44Bb65164b2855f8E80B142d5Fb9bDb72E869986", locker: "0xb593812363bAd70Ee381c7B54c4FC82b0BA3A4A7", devBuy: "0x0e1Fd799E136dcfEcad5695B07Ac5DAc5B2fF4bc", vesting: "0xadD9C1995910457312c156E7D8120D1b447E179a", airdrop: "0xBC798D956aF4c2B104FFC6106d5322294Bc103bb", owner: "0x6569E27A95a6A3E92A311B4Cc6600fed56aA50A2" }, breaker: "0x2E33244dE6A64007204f763e9ef81FC33F1a8e01" }, swapDeployment: { chainId: 4663, fromBlock: "94940585", router: "0x5b9686f384A5Fb768B2360cF0B893F0d1E960B01", stake: "0xaDA4FaB9c8aB9165bf4d6cD9d4Da6d6b32Bb4BC6", buyback: "0xd86083031b7Af66ecD5C9c5e90D4C6d8E661F3f5", spendVerifier: "0x0a0E28a81DE6289e5044950163FfE95b37180e3B", privatePool: "0xe4250895Da2499602Cee4e178Fb46A0cF39F5b76" }, prover: { wasm: "0xd5f3069334ab49a2f955e4b4be9493b80c3f7b2a85f4ff2073a327ef5fbc2747", glue: "0x4bc8bef7c755c83daaf833623f85473a20a7c40b390d8d639d9e4353618a0537" }, relayerUrl: "https://relayer.ostraagent.xyz", keeperUrl: "https://keeper.ostraagent.xyz", aggregatorUrl: "https://aggregator.ostraagent.xyz", aiUrl: "https://ai.ostraagent.xyz" };
22
22
  }
23
23
  });
24
24
 
@@ -15521,6 +15521,7 @@ async function readBorroPosition(client, stockVault, stock2) {
15521
15521
  listed,
15522
15522
  supply: e.totalSupplyAssets,
15523
15523
  borrowed: e.totalBorrowAssets,
15524
+ borrowedShares: e.totalBorrowShares,
15524
15525
  at: at.timestamp
15525
15526
  };
15526
15527
  }
@@ -20248,6 +20249,10 @@ import { decodeFunctionData as decodeFunctionData4, getAddress as getAddress17,
20248
20249
  import { x402Client, x402HTTPClient } from "@x402/core/client";
20249
20250
  import { toClientEvmSigner } from "@x402/evm";
20250
20251
  import { BatchSettlementEvmScheme } from "@x402/evm/batch-settlement/client";
20252
+ function notLandedError(attempts, lastReason) {
20253
+ const reason = lastReason.trim().slice(0, NOT_SENT_MESSAGE_MAX);
20254
+ return new Error(`the relayer could not land the request in ${attempts} attempts${reason === "" ? "" : `; it answered last: ${reason}`}`);
20255
+ }
20251
20256
  function address(value, what) {
20252
20257
  if (typeof value !== "string" || !isAddress6(value, { strict: false })) throw new Error(`${what} is not an address`);
20253
20258
  return getAddress17(value);
@@ -20296,6 +20301,25 @@ function relayerChannelDeposit(needed, vaultBalance, keep = 0n) {
20296
20301
  const want = needed > RELAYER_CHANNEL_DEPOSIT ? needed : RELAYER_CHANNEL_DEPOSIT;
20297
20302
  return want < available ? want : available;
20298
20303
  }
20304
+ function relayerSpendPlan(p) {
20305
+ const rooms = [...p.rooms];
20306
+ let balance = p.balance;
20307
+ let debt = p.debt;
20308
+ for (const a of p.amounts) {
20309
+ const i = debt > 0n ? -1 : rooms.findIndex((r) => r >= a);
20310
+ if (i >= 0) {
20311
+ rooms[i] = rooms[i] - a;
20312
+ continue;
20313
+ }
20314
+ const needed = debt + p.openCost + a;
20315
+ if (balance < needed) return false;
20316
+ const deposit = relayerChannelDeposit(needed, balance);
20317
+ balance -= deposit;
20318
+ debt = 0n;
20319
+ rooms.push(deposit - needed);
20320
+ }
20321
+ return true;
20322
+ }
20299
20323
  function relayerOfferProblem(r, want) {
20300
20324
  if (r.scheme !== "batch-settlement") return `the offer is ${r.scheme}, not batch-settlement`;
20301
20325
  if (r.network !== want.network) return `the offer is on ${r.network}, not ${want.network}`;
@@ -20330,7 +20354,7 @@ function openChannelCall(calls, vault, token) {
20330
20354
  }
20331
20355
  return null;
20332
20356
  }
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;
20357
+ 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
20358
  var init_relay = __esm({
20335
20359
  "../core/src/relay.ts"() {
20336
20360
  "use strict";
@@ -20344,6 +20368,7 @@ var init_relay = __esm({
20344
20368
  REQUEST_TIMEOUT_MS2 = 24e4;
20345
20369
  MAX_STEPS = 8;
20346
20370
  MAX_RESENDS = 3;
20371
+ NOT_SENT_MESSAGE_MAX = 500;
20347
20372
  LANDED_TIMEOUT_MS = 6e4;
20348
20373
  OPEN_READ_TRIES = 20;
20349
20374
  OPEN_READ_INTERVAL_MS = 500;
@@ -20572,6 +20597,55 @@ var init_relay = __esm({
20572
20597
  }
20573
20598
  return null;
20574
20599
  }
20600
+ /**
20601
+ * Read only (nothing is retired, opened or signed): why this vault cannot pay, now, vault spends that reserve `gases`
20602
+ * (each as VaultService.operate reserves it: reserve's amount for that gas), or null when it can. The spends are
20603
+ * played in order as reserve and #ensureChannel would pay them: each from a usable relayer channel with room for it,
20604
+ * or else from a new channel, whose open needs the vault's debt and the open's own spend (#spendBound) on top and
20605
+ * takes RELAYER_CHANNEL_DEPOSIT or what it needs, at most what the vault holds. The vault's credit at the relayer is
20606
+ * not counted: it lowers a price only after the reservation is made. An unpaid open the relayer holds for the vault
20607
+ * is refused here (it is paid before anything else). An operation that sends something first (an approval before the
20608
+ * call it allows) checks this before its first spend, so it does not leave the first done and the rest unpaid.
20609
+ */
20610
+ async shortfall(gases) {
20611
+ const facts = await this.facts();
20612
+ const vault = await this.#buyerVault().vaultAddress();
20613
+ if (await readVault(this.#client, this.#config.deployment, vault) === null) return `the vault ${vault} does not exist yet`;
20614
+ const state = await this.vaultState(vault);
20615
+ 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`;
20616
+ const amounts = [];
20617
+ for (const g of gases) amounts.push(await this.#reserveAmount(g));
20618
+ const bound2 = await this.#spendBound();
20619
+ const payer = await this.#payer();
20620
+ const rooms = [];
20621
+ for (const c of await this.#records()) {
20622
+ if (c.retired || now() >= c.openedAt + SESSION_MAX_AGE_SECONDS) continue;
20623
+ const free = this.#free(c, null);
20624
+ if (free <= 0n) continue;
20625
+ const ch = await readChannel(this.#client, this.#config.deployment.batchSettlement, c.channel);
20626
+ const usable = payer !== null && getAddress17(c.config.payer) === payer && getAddress17(c.config.receiverAuthorizer) === facts.vault && getAddress17(c.config.receiver) === facts.split && ch.pendingWithdrawal === null;
20627
+ const onChain = ch.balance - BigInt(c.cumulative);
20628
+ if (usable) rooms.push(free < onChain ? free : onChain);
20629
+ }
20630
+ const balance = await tokenBalance(this.#client, this.#config.deployment.token, vault);
20631
+ const plan = (held) => relayerSpendPlan({ amounts, rooms, balance: held, debt: state.debt, openCost: bound2 });
20632
+ if (plan(balance)) return null;
20633
+ let lo = balance;
20634
+ let hi = balance > 0n ? balance * 2n : 1n;
20635
+ while (!plan(hi)) hi *= 2n;
20636
+ while (hi - lo > 1n) {
20637
+ const mid = (lo + hi) / 2n;
20638
+ if (plan(mid)) hi = mid;
20639
+ else lo = mid;
20640
+ }
20641
+ const n = gases.length;
20642
+ 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)`;
20643
+ }
20644
+ /** 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. */
20645
+ async #reserveAmount(gas, extraWei = 0n) {
20646
+ const wei = withMargin(gas * await this.#client.getGasPrice()) + extraWei;
20647
+ return maxAcceptedQuote(weiToTokenUnits(wei, await this.#ethUsd(), TOKEN_DECIMALS));
20648
+ }
20575
20649
  /** Base units a transaction of `wei` comes to at the Chainlink price, margin included (the relayer's rule). */
20576
20650
  async #amountFor(wei) {
20577
20651
  return weiToTokenUnits(withMargin(wei), await this.#ethUsd(), TOKEN_DECIMALS);
@@ -20610,8 +20684,7 @@ var init_relay = __esm({
20610
20684
  await (openChannel ?? ((t) => this.#buyerVault().openRelayerChannel(t)))(terms);
20611
20685
  }
20612
20686
  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));
20687
+ const amount = await this.#reserveAmount(gas, extraWei);
20615
20688
  for (let attempt = 0; attempt < 2; attempt++) {
20616
20689
  const c = await this.#serial(async () => {
20617
20690
  const found = await this.#channelFor(amount, null);
@@ -20826,6 +20899,7 @@ var init_relay = __esm({
20826
20899
  }
20827
20900
  async send(calls, options = {}) {
20828
20901
  await this.facts();
20902
+ let notSent = "";
20829
20903
  for (let resend = 0; resend < MAX_RESENDS; resend++) {
20830
20904
  let answer = await this.#post(calls, { prepay: options.prepay === true, payer: await this.#payer(), allowFailedSwaps: options.allowFailedSwaps === true });
20831
20905
  let again = false;
@@ -20848,6 +20922,7 @@ var init_relay = __esm({
20848
20922
  continue;
20849
20923
  }
20850
20924
  if (answer.status === 409 && error === "not_sent") {
20925
+ notSent = typeof b?.message === "string" ? b.message : "";
20851
20926
  again = true;
20852
20927
  continue;
20853
20928
  }
@@ -20866,7 +20941,7 @@ var init_relay = __esm({
20866
20941
  }
20867
20942
  if (!again) throw new Error(`the relayer did not relay the request after ${MAX_STEPS} answers`);
20868
20943
  }
20869
- throw new Error(`the relayer could not land the request in ${MAX_RESENDS} attempts`);
20944
+ throw notLandedError(MAX_RESENDS, notSent);
20870
20945
  }
20871
20946
  /**
20872
20947
  * Relayer channels past SESSION_MAX_AGE_SECONDS stop paying; returns those
@@ -22035,6 +22110,21 @@ var init_borro2 = __esm({
22035
22110
  }
22036
22111
  return { buyerVault: buyer, usdgLent: { shares: usdg2.shares.toString(), value: formatUnitsAmount(usdg2.value, TOKEN_DECIMALS) }, stocks };
22037
22112
  }
22113
+ /**
22114
+ * Per Borro stock whose vault of this agent exists: its market, the stock vault's borrow shares, and the market's
22115
+ * borrows and borrow shares accrued to this block, so that any number of borrow shares is valued now as
22116
+ * toAssetsUp(shares, borrowed, borrowedShares) (what fundFromYield takes off as the debt's interest).
22117
+ */
22118
+ async debtBook() {
22119
+ const out = [];
22120
+ for (const s of borroStocks()) {
22121
+ const v = await this.#p.tokenVault(s.token).vaultAddress();
22122
+ if (await readVault(this.#p.client, this.#p.config.deployment, v) === null) continue;
22123
+ const p = await readBorroPosition(this.#p.client, v, s);
22124
+ out.push({ symbol: s.symbol, marketId: s.marketId, borrowShares: p.borrowShares, borrowed: p.borrowed, borrowedShares: p.borrowedShares });
22125
+ }
22126
+ return out;
22127
+ }
22038
22128
  // -------------------------------------------------------------------------
22039
22129
  // Lend and redeem
22040
22130
  // -------------------------------------------------------------------------
@@ -23030,7 +23120,7 @@ var init_ipfs = __esm({
23030
23120
  "use strict";
23031
23121
  init_define_OSTRA_PACKAGED();
23032
23122
  init_src();
23033
- IPFS_GATEWAYS = ["https://gateway.pinata.cloud/ipfs/", "https://ipfs.io/ipfs/"];
23123
+ IPFS_GATEWAYS = ["https://relayer.ostraagent.xyz/v1/ipfs/", "https://gateway.pinata.cloud/ipfs/", "https://ipfs.io/ipfs/"];
23034
23124
  IPFS_GATEWAY_TIMEOUT_MS = 2e4;
23035
23125
  }
23036
23126
  });
@@ -23562,7 +23652,7 @@ function simulatedGas(gasUsed) {
23562
23652
  const g = gasUsed * 5n / 4n + SPEND_GAS_HEADROOM2;
23563
23653
  return g > VAULT_SPEND_GAS ? g : VAULT_SPEND_GAS;
23564
23654
  }
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;
23655
+ var LAUNCHPAD_DIR, CREATE_VAULT_GAS2, LAUNCH_GAS_HEADROOM, PAIRS_READ_CONCURRENCY, SPEND_GAS_HEADROOM2, DAY, TOKEN_LIST_MAX, METADATA_MAX_BYTES, launchedEvent, POOL_SETTING_NAMES, WOTS_GAS_SPREAD, STAND_IN_URI, LaunchpadService;
23566
23656
  var init_launchpad = __esm({
23567
23657
  "../core/src/launchpad.ts"() {
23568
23658
  "use strict";
@@ -23598,6 +23688,8 @@ var init_launchpad = __esm({
23598
23688
  volatility_fee: POOL_SETTING.VOLATILITY_FEE,
23599
23689
  max_tx_move: POOL_SETTING.MAX_TX_MOVE
23600
23690
  };
23691
+ WOTS_GAS_SPREAD = 100000n;
23692
+ STAND_IN_URI = `ipfs://bafkrei${"a".repeat(52)}`;
23601
23693
  LaunchpadService = class {
23602
23694
  #client;
23603
23695
  #transport;
@@ -24503,6 +24595,32 @@ var init_launchpad = __esm({
24503
24595
  if (profile.status !== "sent" || record === null) return { uri: uri2, profile, agentUri: null, pairCreators };
24504
24596
  return { uri: uri2, profile, agentUri: await this.setAgentUri(uri2), pairCreators };
24505
24597
  }
24598
+ /**
24599
+ * Read only: why the creator vault cannot pay for publishRegistrationFile now, or null when it can or does not pay
24600
+ * through the relayer. Its spends (the profile and, once the agent is registered, its agent URI) are simulated as
24601
+ * setProfile and setAgentUri send them, with the URI set now (or one of an ipfs:// URI's length) in place of the
24602
+ * file's, and each is counted at the gas it will be reserved (simulatedGas) with WOTS_GAS_SPREAD added (a simulation
24603
+ * signs with a throwaway key, and a WOTS signature's verification costs more or less with each message);
24604
+ * RelayerClient.shortfall replays their payment. A
24605
+ * spend the creator vault signed earlier and has not sent is refused here: its next operation would send that one
24606
+ * instead of the profile.
24607
+ */
24608
+ async publishShortfall() {
24609
+ if (!(this.#creatorSender instanceof RelayerClient)) return null;
24610
+ const s = await this.#creator.state();
24611
+ if (!s.exists) return `the creator vault ${s.vault} does not exist yet`;
24612
+ if ((await this.#creator.spendSlot())?.pending != null) {
24613
+ 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`;
24614
+ }
24615
+ const record = await this.#agentRecord();
24616
+ const uri2 = record?.uri ?? await this.#profileUri(s.vault) ?? STAND_IN_URI;
24617
+ const relayer = await this.#creatorSender.payer();
24618
+ const gasOf = async (action, params) => simulatedGas((await simulateVaultSpend(this.#client, this.#config.deployment, { relayer, vault: s.vault, action, params })).gasUsed + WOTS_GAS_SPREAD);
24619
+ const gases = [await gasOf(VAULT_ACTION.SET_PROFILE, setProfileParams(uri2))];
24620
+ if (record !== null) gases.push(await gasOf(VAULT_ACTION.SET_AGENT_URI, setAgentUriParams({ agentId: BigInt(record.agentId), uri: uri2 })));
24621
+ const short = await this.#creatorSender.shortfall(gases);
24622
+ return short === null ? null : `${short}; fund_creator_vault moves USDG into it`;
24623
+ }
24506
24624
  /**
24507
24625
  * Pins `file`, a registration file built from the current one (Ostrapad's listing, pad.ts), and makes it the
24508
24626
  * agent's profile in OstraRegistry and its ERC-8004 registration file when it is registered.
@@ -24569,7 +24687,7 @@ function jobView(j) {
24569
24687
  deliverable: j.deliverable === ZERO324 ? null : padCidFromDigest(j.deliverable)
24570
24688
  };
24571
24689
  }
24572
- var PAD_DIR, PAD_READ_MAX_BYTES, READ_TIMEOUT_MS, SPEND_GAS_HEADROOM3, EIP1271_MAGIC2, erc1271Abi2, tokenUriAbi, ZERO324, PadResentError, PadService;
24690
+ var PAD_DIR, PAD_READ_MAX_BYTES, READ_TIMEOUT_MS, SPEND_GAS_HEADROOM3, PAD_DISCOVER_RESCAN_BLOCKS, PAD_CURSOR_VERSION, EIP1271_MAGIC2, erc1271Abi2, tokenUriAbi, ZERO324, PadResentError, PadService;
24573
24691
  var init_pad2 = __esm({
24574
24692
  "../core/src/pad.ts"() {
24575
24693
  "use strict";
@@ -24583,6 +24701,8 @@ var init_pad2 = __esm({
24583
24701
  PAD_READ_MAX_BYTES = 65536;
24584
24702
  READ_TIMEOUT_MS = 2e4;
24585
24703
  SPEND_GAS_HEADROOM3 = 100000n;
24704
+ PAD_DISCOVER_RESCAN_BLOCKS = 3000n;
24705
+ PAD_CURSOR_VERSION = 2;
24586
24706
  EIP1271_MAGIC2 = "0x1626ba7e";
24587
24707
  erc1271Abi2 = [
24588
24708
  { type: "function", name: "isValidSignature", stateMutability: "view", inputs: [{ name: "hash", type: "bytes32" }, { name: "signature", type: "bytes" }], outputs: [{ name: "", type: "bytes4" }] }
@@ -24816,7 +24936,11 @@ var init_pad2 = __esm({
24816
24936
  if (offers !== null) {
24817
24937
  const entry = padServiceEntry({ provider: vault, offers }, this.#pad, this.#p.config.chainId);
24818
24938
  const digest = padListingDigest(agentId, creatorVault, this.#pad, this.#p.config.chainId);
24819
- if (!await this.#approved(vault, digest)) approval = await this.#approveAlone(digest, "Ostrapad listing approval");
24939
+ if (!await this.#approved(vault, digest)) {
24940
+ const short = this.#p.publishShortfall === void 0 ? null : await this.#p.publishShortfall();
24941
+ 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`);
24942
+ approval = await this.#approveAlone(digest, "Ostrapad listing approval");
24943
+ }
24820
24944
  services.push(entry);
24821
24945
  }
24822
24946
  const out = await this.#p.publishFile({ ...current, services });
@@ -25148,14 +25272,21 @@ var init_pad2 = __esm({
25148
25272
  // -------------------------------------------------------------------------------------------------------------
25149
25273
  // Reads and the sweep
25150
25274
  // -------------------------------------------------------------------------------------------------------------
25151
- /** Finds jobs addressed to this agent's vault (as provider, drawn evaluator or panel member) and records them. */
25275
+ /**
25276
+ * Finds jobs addressed to this agent's vault (as provider, drawn evaluator or panel member) and records them. The
25277
+ * logs are read up to the logs RPC's own head (another RPC's head can be ahead of the blocks it has logs for: those
25278
+ * blocks would be passed over for good), and the last PAD_DISCOVER_RESCAN_BLOCKS are read again on the next pass,
25279
+ * as the evaluator bot does (keeper pad-evaluator.ts). A cursor written before 0.1.10 (no version: its head came
25280
+ * from the other RPC) is not trusted: the scan starts again from OstraPad's deployment block, once.
25281
+ */
25152
25282
  async #discover(vault) {
25153
25283
  const f = this.#folder();
25154
25284
  await f.ensure();
25155
25285
  const logs = this.#p.logsClient ?? this.#p.client;
25156
- const head = (await this.#p.client.getBlock({ blockTag: "latest" })).number;
25286
+ const head = await logs.getBlockNumber({ cacheTime: 0 });
25157
25287
  const cursorText = await f.read("cursor.json");
25158
- const from = cursorText === void 0 ? OSTRAPAD_FROM_BLOCK : BigInt(JSON.parse(cursorText).next);
25288
+ const cursor = cursorText === void 0 ? null : JSON.parse(cursorText);
25289
+ const from = cursor === null || cursor.version !== PAD_CURSOR_VERSION ? OSTRAPAD_FROM_BLOCK : BigInt(cursor.next);
25159
25290
  if (from > head) return;
25160
25291
  const found = /* @__PURE__ */ new Map();
25161
25292
  for (const l of await getLogsChunked(logs, { address: this.#pad, event: PAD_EVENTS.JobCreated, args: { provider: vault }, fromBlock: from, toBlock: head })) found.set(l.args.jobId, "provider");
@@ -25165,7 +25296,8 @@ var init_pad2 = __esm({
25165
25296
  if (a.panel.some((m) => getAddress25(m) === vault) && !found.has(a.jobId)) found.set(a.jobId, "evaluator");
25166
25297
  }
25167
25298
  for (const [id, role] of found) if (await this.#record(id) === null) await this.#save({ id, role, steps: {}, at: Math.floor(Date.now() / 1e3) });
25168
- await f.write("cursor.json", JSON.stringify({ next: (head + 1n).toString() }));
25299
+ const rescan = head + 1n - PAD_DISCOVER_RESCAN_BLOCKS;
25300
+ await f.write("cursor.json", JSON.stringify({ version: PAD_CURSOR_VERSION, next: (rescan > from ? rescan : from).toString() }));
25169
25301
  }
25170
25302
  /** The jobs this agent is a party to, with their state on chain and what can be done next; `jobId`: one job, with its order and delivery files. */
25171
25303
  async jobs(a = {}) {
@@ -25193,7 +25325,7 @@ var init_pad2 = __esm({
25193
25325
  const r = await this.#record(id);
25194
25326
  if (r?.terms !== void 0) return r.terms.description;
25195
25327
  const logs = this.#p.logsClient ?? this.#p.client;
25196
- const found = await getLogsChunked(logs, { address: this.#pad, event: PAD_EVENTS.JobCreated, args: { jobId: id }, fromBlock: OSTRAPAD_FROM_BLOCK, toBlock: (await this.#p.client.getBlock({ blockTag: "latest" })).number });
25328
+ const found = await getLogsChunked(logs, { address: this.#pad, event: PAD_EVENTS.JobCreated, args: { jobId: id }, fromBlock: OSTRAPAD_FROM_BLOCK, toBlock: await logs.getBlockNumber({ cacheTime: 0 }) });
25197
25329
  const log2 = found[0];
25198
25330
  if (log2 === void 0) return null;
25199
25331
  return (await padTermsFromTransaction(this.#p.client, log2.transactionHash, id, this.#pad))?.description ?? null;
@@ -25532,6 +25664,19 @@ var init_pay = __esm({
25532
25664
 
25533
25665
  // ../core/src/raeto.ts
25534
25666
  import { getAddress as getAddress27, hashMessage, isAddress as isAddress13, parseAbi as parseAbi6 } from "viem";
25667
+ function netYieldSince(mark, now3, book) {
25668
+ if (mark.debts === void 0) return null;
25669
+ const high = BigInt(mark.price);
25670
+ const earned = now3.price > high ? BigInt(mark.shares) * (now3.price - high) / now3.unit : 0n;
25671
+ let interest = 0n;
25672
+ for (const d of mark.debts) {
25673
+ const m = book.find((b) => b.marketId.toLowerCase() === d.marketId.toLowerCase());
25674
+ if (m === void 0) return null;
25675
+ const owesNow = toAssetsUp(BigInt(d.borrowShares), m.borrowed, m.borrowedShares);
25676
+ if (owesNow > BigInt(d.debt)) interest += owesNow - BigInt(d.debt);
25677
+ }
25678
+ return { earned, interest };
25679
+ }
25535
25680
  function raetoImages(answer) {
25536
25681
  const out = [];
25537
25682
  const choices = answer?.choices;
@@ -25778,15 +25923,20 @@ var init_raeto = __esm({
25778
25923
  return this.#scrub(await this.#call("POST", "/v1/chat/completions", request, await this.#key(), COMPLETION_TIMEOUT_MS));
25779
25924
  }
25780
25925
  /**
25781
- * What the vault's cash vault shares earn, as credits. Kept here: a mark (the shares, and the highest share price read:
25782
- * a high-water mark, so a price that falls and comes back earns nothing until it passes its high), what is owed (the
25783
- * shares of each mark times the rise of the price over its high, added on every call), and USDG redeemed for it and
25784
- * not sent yet (`ready`), with the vault's spend slot its transfer is signed at. A call adds what the shares earned
25785
- * since the mark, moves the mark to now, sends what was redeemed and not sent first, and else, when what is owed is at
25786
- * least `min` USDG, redeems that from the cash vault and sends what the redeem paid (at most what is owed) to Raeto's
25787
- * payTo. A transfer of `ready` that another operation of the vault sent first (it sends a signed spend before its
25788
- * own) is found at its slot and not paid again. Shares added or removed count from the read after; a full exit leaves
25789
- * what was owed until then. Raeto's terms are checked before anything is redeemed. Calls run one after the other.
25926
+ * What the vault's cash vault shares earn, net of the agent's Borro debts' interest, as credits. Kept here: a mark
25927
+ * (the shares, the highest share price read: a high-water mark, so a price that falls and comes back earns nothing
25928
+ * until it passes its high; and each Borro debt's borrow shares with what they owed), what is owed (netYieldSince,
25929
+ * added on every call: the shares' rise over the high less the interest the marked debts accrued; it can fall below
25930
+ * zero, and nothing is sent until it is back above `min`), and USDG redeemed for it and not sent yet (`ready`), with
25931
+ * the vault's spend slot its transfer is signed at. The interest comes off because the carry strategy borrows USDG
25932
+ * from Borro into this same cash vault: the shares' rise is not earned while the loan behind them owes interest.
25933
+ * A call adds what was earned since the mark, moves the mark to now, sends what was redeemed and not sent first, and
25934
+ * else, when what is owed is at least `min` USDG, redeems that from the cash vault and sends what the redeem paid (at
25935
+ * most what is owed) to Raeto's payTo. A transfer of `ready` that another operation of the vault sent first (it sends
25936
+ * a signed spend before its own) is found at its slot and not paid again. Shares and debts added or removed count
25937
+ * from the read after; a full exit leaves what was owed until then. A mark kept before debts were marked (MCP 0.1.10
25938
+ * and earlier) earns nothing for the time since it, as that time's interest cannot be read back. Raeto's terms are
25939
+ * checked before anything is redeemed. Calls run one after the other.
25790
25940
  */
25791
25941
  fundFromYield(min2 = "1") {
25792
25942
  return this.#serial(async () => {
@@ -25800,20 +25950,25 @@ var init_raeto = __esm({
25800
25950
  return { shares, unit, price };
25801
25951
  };
25802
25952
  const now3 = await read();
25953
+ const book = await this.#p.borro.debtBook();
25954
+ 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() }));
25803
25955
  const text3 = await this.#p.folder.read(YIELD_FILE);
25804
25956
  const kept = text3 === void 0 ? null : JSON.parse(text3);
25805
- const usd4 = (x) => formatUnitsAmount(x, TOKEN_DECIMALS);
25957
+ const usd4 = (x) => formatUnitsAmount(x > 0n ? x : 0n, TOKEN_DECIMALS);
25806
25958
  const write = (m) => this.#p.folder.write(YIELD_FILE, JSON.stringify(m));
25807
25959
  if (kept === null || getAddress27(kept.vault) !== target) {
25808
- await write({ vault: target, shares: now3.shares.toString(), price: now3.price.toString(), owed: "0", ready: "0" });
25809
- 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" };
25960
+ await write({ vault: target, shares: now3.shares.toString(), price: now3.price.toString(), owed: "0", ready: "0", debts });
25961
+ 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" };
25810
25962
  }
25811
25963
  const high = BigInt(kept.price);
25812
- let owed = BigInt(kept.owed) + (now3.price > high ? BigInt(kept.shares) * (now3.price - high) / now3.unit : 0n);
25964
+ const net = netYieldSince(kept, now3, book);
25965
+ let owed = BigInt(kept.owed) + (net === null ? 0n : net.earned - net.interest);
25813
25966
  let ready = BigInt(kept.ready);
25814
- const mark = { ...kept, vault: target, shares: now3.shares.toString(), price: (now3.price > high ? now3.price : high).toString(), owed: owed.toString(), ready: ready.toString() };
25967
+ const mark = { ...kept, vault: target, shares: now3.shares.toString(), price: (now3.price > high ? now3.price : high).toString(), owed: owed.toString(), ready: ready.toString(), debts };
25815
25968
  await write(mark);
25816
- if (ready === 0n && (owed === 0n || owed < minimum)) return { vault: target, owed: usd4(owed), sent: "0", note: `under ${min2} USDG: kept for a later call` };
25969
+ 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; " : "";
25970
+ 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` };
25971
+ 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` };
25817
25972
  const { payTo } = await this.terms();
25818
25973
  if (ready > 0n && mark.readySlot !== void 0) {
25819
25974
  const slot2 = { nonce: BigInt(mark.readySlot.nonce), keyIndex: BigInt(mark.readySlot.keyIndex) };
@@ -25849,7 +26004,7 @@ var init_raeto = __esm({
25849
26004
  ready = paid <= 0n ? 0n : paid < owed ? paid : owed;
25850
26005
  owed -= ready;
25851
26006
  const afterHigh = BigInt(mark.price) > after.price ? BigInt(mark.price) : after.price;
25852
- await write({ vault: target, shares: after.shares.toString(), price: afterHigh.toString(), owed: owed.toString(), ready: ready.toString() });
26007
+ await write({ vault: target, shares: after.shares.toString(), price: afterHigh.toString(), owed: owed.toString(), ready: ready.toString(), debts });
25853
26008
  if (ready === 0n) return { vault: target, owed: usd4(owed), sent: "0", note: "the redeem paid nothing into the vault", redeem };
25854
26009
  }
25855
26010
  const slot = await this.#p.buyer.spendSlot();
@@ -33945,6 +34100,7 @@ var init_agent = __esm({
33945
34100
  return typeof p2.profile === "object" && p2.profile !== null && !Array.isArray(p2.profile) ? p2.profile : null;
33946
34101
  },
33947
34102
  publishFile: (file) => lp.publishRegistrationFile(file),
34103
+ publishShortfall: () => lp.publishShortfall(),
33948
34104
  upload: (file) => uploadJson(config.relayerUrl, file),
33949
34105
  indexerUrl: config.padIndexerUrl
33950
34106
  });
@@ -34614,7 +34770,7 @@ function registerTools(server, agent) {
34614
34770
  };
34615
34771
  tool(
34616
34772
  "raeto",
34617
- "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).",
34773
+ "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).",
34618
34774
  {
34619
34775
  action: z.enum(["login", "me", "models", "fund", "fund_yield", "complete"]),
34620
34776
  name: z.string().min(1).max(64).optional(),
@@ -34673,7 +34829,7 @@ function registerTools(server, agent) {
34673
34829
  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)");
34674
34830
  tool(
34675
34831
  "pad_publish",
34676
- "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.",
34832
+ "Ostrapad as a provider. publish: lists offers in the agent's ERC-8004 registration file, paid into its USDG vault (the vault approves the listing once, so a listing is its own); needs publish_profile and register_agent first, and USDG in the creator vault for its relayed transactions (checked before the approval). unpublish: removes the listing. status: the agent's own listing as clients read it. bond / withdraw_bond: a voluntary provider bond (raises the limit per job to the bond, up to 500 USDG; a rejected job costs it 10% of the budget plus the evaluator fee). return_bond_box: returns USDG left in the provider bond box (a deposit whose second step failed) to the USDG vault.",
34677
34833
  { action: z.enum(["publish", "unpublish", "status", "bond", "withdraw_bond", "return_bond_box"]), offers: z.array(offer).min(1).max(20).optional(), amount: usdg.optional() },
34678
34834
  async (a) => {
34679
34835
  if (a.action === "publish") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ostraagent/mcp",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Ostra Agent's MCP server (stdio): the post-quantum privacy vault for AI agents. One vault to pay, earn, work and hire on Ostrapad, and stay private on Robinhood Chain, with no ETH (the Ostra relayer sends its transactions, paid in USDG).",
5
5
  "keywords": [
6
6
  "mcp",