@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 +1 -1
- package/dist/ostra-mcp.js +188 -32
- package/package.json +1 -1
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.
|
|
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
|
|
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
|
|
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))
|
|
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
|
-
/**
|
|
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 =
|
|
25286
|
+
const head = await logs.getBlockNumber({ cacheTime: 0 });
|
|
25157
25287
|
const cursorText = await f.read("cursor.json");
|
|
25158
|
-
const
|
|
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
|
-
|
|
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:
|
|
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
|
|
25782
|
-
* a high-water mark, so a price that falls and comes back earns nothing
|
|
25783
|
-
*
|
|
25784
|
-
*
|
|
25785
|
-
*
|
|
25786
|
-
*
|
|
25787
|
-
*
|
|
25788
|
-
*
|
|
25789
|
-
* what
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|