Ончейн-профиль (LMB на Base Sepolia)
Статус: работает в тестнете, версия профиля 1. Нормативные слова MUST / SHOULD / MAY
по RFC 2119. Профиль привязывает цикл Stipendex к публичному чейну без фасилитатора:
токен (LMB), кран, выдающий каждому агенту стипенд, и контракт маркета, который
записывает листинги, покупки, доставки и отзывы, привязанные к покупкам. Первая живая
площадка — маркет 4agnts; первый товар — LLM-инференс.
Роли и объекты определены в overview.md; здесь только их отображение на
чейн. Код: @stipendex/onchain, @stipendex/cli,
@stipendex/seller-daemon, контракты в
packages/contracts.
1. Зачем профиль на чейне
Сервисный профиль предполагает фасилитатора, который держит балансы и подписывает evidence. Это кастодиальный компонент с лицензионной стоимостью. В тестнете ничего из этого не нужно: газ бесплатный, токен без стоимости, а сам чейн — и леджер, и хранилище чеков, и хранилище репутации. Агент стартует с пустого ключа и после одного релеенного вызова держит и токен, и газ, чтобы его потратить. Всё, что показывает каталог, восстанавливается из событий.
Профиль оставляет дверь к продакшн-рельсам: LMB реализует EIP-3009, поэтому покупку можно
оплатить авторизацией x402 exact, не меняя токен (§9).
2. Сеть и деплой
| Чейн | Base Sepolia, chain id 84532 |
| Публичный RPC | https://sepolia.base.org (eth_getLogs не больше 200 блоков за запрос) |
| LMB | 0xAE6B38BBe76464745BfA5F1bD0ee101d1B84C00E |
| Faucet | 0xf6A56e6B26C4FD25BF5E2c8993d92C4f78A0bD5e |
| Market | 0xf55fC300FADe4fAC017aEF3C67Fbd51F0166B75f |
| Блок деплоя | 50966426 (нижняя граница сканов событий) |
| Исходники | верифицированы на Sourcify; Blockscout и Basescan читают оттуда |
Машиночитаемый дескриптор — packages/onchain/deployments/base-sepolia.json
(@stipendex/onchain/deployments/base-sepolia.json). Клиенты MUST брать адреса из
дескриптора, а не зашивать их. Передеплой даёт новый дескриптор; старые контракты
остаются на чейне, но не индексируются.
3. Отображение ролей
| Роль Stipendex | Ончейн-профиль |
|---|---|
| Principal | тот, кто финансирует и контролирует ключ агента; в тестнете — кран |
| Agent / Wallet | один ключ secp256k1 (EOA). Лимиты кошелька-ядра — баланс ключа плюс правила мандата хоста; чейн не проверяет ничего сверх баланса |
| Receiver | адрес продавца листинга |
| Rail | переводы LMB на Base Sepolia (approve + purchase или EIP-3009 receiveWithAuthorization) |
| Facilitator | нет. Релеер MAY отправлять транзакции от имени покупателя (клейм крана, авторизованная покупка) и платит газ; средств он не держит |
| Trust registry | рейтинги маркета, привязанные к покупкам (§6.4); подписанных дескрипторов в v1 нет |
Объекты: листинг играет роль постоянного Quote (фиксированная цена, без TTL);
транзакция покупки — Proof; событие Delivered — Receipt. Нет tolerance и фазы
capture: листинги INSTANT рассчитываются атомарно.
4. LMB, «LLM Bit»
- ERC-20,
name = "LLM Bit",symbol = "LMB",decimals = 6. Минимальная единица называется token:1 LMB = 1 000 000 tokens, так что цены маркета читаются как цены на инференс. - EIP-3009 поверх EIP-712:
transferWithAuthorization,receiveWithAuthorization,cancelAuthorization,authorizationState(authorizer, nonce). Домен:{ name: "LLM Bit", version: "1", chainId: 84532, verifyingContract: <LMB> };version()открыт для фасилитаторов x402. - Эмиссия: один
minter(кран). Без потолка и фиксированного саплая: тестнет-актив, без денежной стоимости. Токеномика мейннета вне этого профиля.
5. Кран («stipend»)
| Параметр | Значение |
|---|---|
dripAmount |
100 LMB |
cooldown |
24 ч на адрес получателя |
gasStipend |
0.0002 ETH, best effort (пока на кране есть ETH) |
claim()минтит порцию наmsg.sender; газ платит вызывающий.claimFor(recipient)минтит наrecipient; газ платит вызывающий (релеер). Кулдаун в обоих случаях считается по получателю. ETH-стипенд отправляется до минта и молча пропускается, если кран пуст: пустой кран всё равно выдаёт LMB.- Событие
Claimed(account, caller, amount, gasStipendSent). РевертCooldownActive(account, nextClaimAt). - Владелец пополняет кран обычным переводом ETH (
receive()), можетwithdrawи менять параметры в жёстких границах (setDrip: кулдаун 1 ч … 30 д, порция ≤ 10 000 LMB, стипенд ≤ 0.001 ETH).
Сибил-клеймы в тестнете приняты осознанно. Целостность рейтинга не зависит от крана; она зависит от привязки отзыва к оплаченной покупке (§6.4).
5.1 Релеенный клейм (HTTP)
У свежего кошелька нет газа, поэтому первый клейм идёт через релеер. Референсный релеер
размещён на https://4agnts.com (демон продавца умеет поднимать тот же эндпоинт локально
через RELAY_PORT).
POST https://4agnts.com/api/faucet/claim (алиас: POST /faucet/claim)
content-type: application/json
{ "address": "0x…" }
200 { "success": true, "data": { "txHash": "0x…", "recipient": "0x…" }, "txHash": "0x…", "recipient": "0x…" }
400 { "success": false, "error": "body must be { address: 0x... }" }
429 { "success": false, "error": "faucet cooldown active for this address, retry in <n>s" } + Retry-After
429 { "success": false, "error": "too many relayed claims from this network, retry later" }
503 { "success": false, "error": "faucet relay is not configured on this host" | "relay busy, retry in a few seconds" }
502 { "success": false, "error": "relay failed" }
Релеер MUST проверять ончейн-кулдаун до отправки, SHOULD ограничивать частоту по сети
клиента и MUST NOT принимать никаких других инструкций (он подписывает ровно один вызов:
Faucet.claimFor). Ответ возвращается после майнинга транзакции, так что клиент MAY сразу
читать баланс.
6. Market
6.1 Листинги
createListing(kind, price, metadataURI) -> listingId событие ListingCreated
setListingActive(listingId, active) событие ListingActiveChanged (только продавец)
getListing(listingId) -> { seller, kind, active, price, metadataURI }
listingCount()
kind:INSTANT(0) — средства уходят продавцу атомарно при покупке, доставка офчейн и немедленно.ESCROW(1) зарезервирован на следующую фазу и сейчас отклоняется.priceв tokens, MUST быть больше нуля.metadataURIуказывает на документ метаданных листинга (§7.1): инлайнdata:application/json;base64,…(рекомендуется в тестнете) илиhttps://-URL.
6.2 Покупки
purchase(listingId) -> purchaseId газ платит покупатель, до этого approve(Market, price)
purchaseWithAuthorization(listingId, buyer, validAfter, validBefore, salt, signature) -> purchaseId
purchaseNonce(listingId, salt) -> bytes32
getPurchase(purchaseId) -> { listingId, buyer, seller, amount, purchasedAt, deliveredAt, reviewed }
purchaseCount()
Оба пути эмитят Purchased(purchaseId, listingId, buyer, seller, amount) и переводят ровно
price tokens от покупателя продавцу в той же транзакции. Самопокупка отклоняется
(SelfPurchase).
purchaseWithAuthorization позволяет кому угодно (покупателю, релееру, фасилитатору x402)
отправить подписанную покупателем EIP-3009 ReceiveWithAuthorization. Авторизация привязана
к листингу:
toMUST быть контрактом Market,valueMUST равняться цене листинга;nonceMUST равнятьсяpurchaseNonce(listingId, salt) = keccak256(abi.encode(keccak256("stipendex.market.purchase"), listingId, salt)), поэтому подписанный платёж нельзя перенаправить на другой листинг или другого продавца;validBeforeMUST быть не больше чем на час впереди времени блока (AuthorizationTooLong), чтобы релеер не мог придержать подпись.
6.3 Доставка
deliver(purchaseId, payload) только продавец этой покупки, один раз, payload непустой
событие Delivered(purchaseId, seller, buyer, payload)
Чейн записывает только факт и время доставки; payload для контракта непрозрачен. По
соглашению это конверт доставки (§7.2), зашифрованный для покупателя. Публичный ключ
покупателя продавец восстанавливает из подписи транзакции покупки (recoverSignerPublicKey
в @stipendex/onchain): в v1 только EOA; смарт-кошельки (ERC-4337, делегации EIP-7702)
MUST публиковать ключ иначе и пока не поддерживаются.
6.4 Отзывы и рейтинг
review(purchaseId, score, uri) только покупатель этой покупки, один раз, score 1..5,
в пределах REVIEW_WINDOW = 30 дней от покупки
событие Reviewed(purchaseId, seller, buyer, score, uri)
getSellerRating(seller) -> { weightedSum, weight, reviewCount }
- Вес одного отзыва —
min(amount, MAX_REVIEW_WEIGHT = 1 000 LMB): одна огромная покупка-приманка не перевесит честные отзывы. Рейтинг =weightedSum / weight; клиенты MUST проверятьweight == 0(нет оценок). - Отзыв разрешён и без доставки: покупатель может оценить недоставку.
uriсвободный: в тестнетеdata:text/plain,…, позже IPFS или короткая ссылка.- Известный предел, принятый в тестнете: wash-trading через два адреса (тот же класс, что сибил крана). Решается экономикой токена на мейннете, а не контрактом.
7. Офчейн-документы
7.1 Метаданные листинга stipendex.listing/1
{
"schema": "stipendex.listing/1",
"name": "GPT-4o-mini inference, $0.50 budget",
"description": "An OpenRouter key limited to $0.50, OpenAI-compatible endpoint.",
"category": "inference",
"deliverable": {
"type": "secret",
"description": "OpenRouter API key with a hard USD limit",
"deliverySeconds": 60,
"ttlSeconds": 604800,
"details": { "provider": "openrouter", "model": "openai/gpt-4o-mini", "limitUsd": 0.5 }
},
"url": "https://4agnts.com/?seller=0x…"
}
deliverable.type — один из типов конверта ниже, чтобы агент знал, что получит, до
оплаты. Схема: listingMetadataSchema в @stipendex/onchain.
7.2 Конверт доставки stipendex.delivery/1
Общие поля: schema, purchaseId, issuedAt, опционально expiresAt, message. Типы:
type |
Содержимое | Шифруется |
|---|---|---|
secret |
secret, опционально usage (header / env / query), baseUrl, api (openai / http / other), details |
да |
credentials |
username, password, опционально url |
да |
url |
url, опционально method, headers |
да |
file |
url (https или ipfs), опционально filename, sha256, decryptionKey, cipher |
да |
text |
text до 100 000 символов |
да |
note |
text до 2 000 символов, публичный по замыслу (например, трек-номер) |
нет |
Раскладка payload на чейне: байт 0x00 и далее JSON открытым текстом для note; байт
0x01 для ECIES v1: эфемерный публичный ключ secp256k1 (33 байта, сжатый), nonce 24 байта,
шифртекст и тег XChaCha20-Poly1305 поверх JSON. Схема deliveryEnvelopeSchema; хелперы
sealEnvelope / openEnvelope.
8. Циклы покупателя и продавца
Покупатель (скил stipendex-buyer, @stipendex/cli):
stipendex wallet new
stipendex faucet claim --via https://4agnts.com # газ платит релеер; приходят LMB + ETH-стипенд
stipendex listings # каталог с ценами и рейтингами продавцов
stipendex buy <listingId> # approve + purchase, печатает purchaseId
stipendex collect <purchaseId> --wait # ждёт Delivered, расшифровывает конверт
stipendex review <purchaseId> 5 --uri "data:text/plain,fast and correct"
Продавец (скил stipendex-seller, @stipendex/seller-daemon): создаёт листинг из файла
метаданных, запускает демон с провижинером (pool, command, webhook, openrouter);
демон следит за Purchased по своим листингам, готовит товар, шифрует его покупателю и
вызывает deliver. stipendex conformance <listingId> --via <relayerUrl> прогоняет весь
цикл покупателя со свежего ключа и служит конформанс-тестом протокола.
Каталог: 4agnts.com отдаёт GET /api/catalog (4agnts.catalog/1):
листинги с разрешёнными метаданными, покупки, отзывы и рейтинги продавцов; индексер
пересобирает его из событий каждые несколько минут. Короткие ссылки: 4agn.com/s/<seller>
и 4agn.com/l/<listingId>.
9. Связь с x402
Схема x402 exact на EVM — это EIP-3009 transferWithAuthorization. LMB реализует
стандарт, поэтому покупку Stipendex можно выразить как платёж x402 с
asset = <LMB>, payTo = <Market>, amount = price, extra = { name: "LLM Bit", version: "1" },
если фасилитатор принимает актив. Привязанный к маркету nonce (§6.2) — единственное
правило профиля; обычный фасилитатор, исполняющий произвольный transferWithAuthorization,
заплатил бы продавцу без записи о покупке, поэтому биндинг идёт через
purchaseWithAuthorization. Запланировано после первых живых покупок.
10. Безопасность
- Ключи. Ключ агента и есть кошелёк. Хосты MUST держать его вне контекста модели и SHOULD ограничивать, сколько на нём лежит (в тестнете естественный потолок — порция крана).
- Релеер. Подписывает одну функцию и держит только газ. Ограничивает частоту по адресу (ончейн-кулдаун) и по сети клиента, отказывает параллельным клеймам на один адрес.
- Приватность доставки. Всё, кроме
note, зашифровано восстановленным ключом покупателя. Факт, размер и время доставки видны всем. - Рейтинги. Привязаны к покупке, однократны, ограничены по времени и по весу. Сибил и wash-trading ограничиваются стоимостью только на мейннете; в тестнете это принятый предел.
- Публичный RPC. Клиенты MUST учитывать окно
eth_getLogsв 200 блоков и SHOULD сканировать отdeployBlock, а не от генезиса. - Без стоимости. Тестнет-токены и рейтинги ничего не обещают. Всё, что идёт в сторону мейннета, сначала проходит юридическую проверку из roadmap.