@fangorn-network/westmarch 2026.10.29 → 2026.11.4-dev
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 +5 -5
- package/docs/app-to-agent.md +61 -625
- package/package.json +12 -17
- package/site/dist/assets/__vite-browser-external-D2U8RZpp.js +1 -0
- package/site/dist/assets/embed.worker-CmS57Zti.js +1 -0
- package/site/dist/assets/index-Djs8f08m.js +8 -0
- package/site/dist/assets/rolldown-runtime-COiEfhsA.js +1 -0
- package/site/dist/assets/{transformers.web-BTyOEJ0C.js → transformers.web-CwR_IYQV.js} +1 -1
- package/site/dist/index.html +3 -4
- package/site/worker.js +115 -338
- package/src/agent/agent-card.js +33 -45
- package/src/agent/mcp.js +168 -371
- package/src/agent/tools.js +2 -1
- package/src/agent/view-tools.js +4 -56
- package/src/agent/x402.js +182 -369
- package/src/core/roles.js +6 -6
- package/src/core/shard.js +4 -4
- package/src/discover/apps.js +67 -81
- package/src/discover/directory.js +2 -2
- package/src/market/reactions.js +21 -15
- package/src/publish/devnet.js +69 -18
- package/src/publish/init.js +1 -1
- package/src/publish/ship.js +74 -275
- package/src/publish/view.js +52 -53
- package/template/README.md +9 -11
- package/template/package.json +1 -1
- package/plugin/scripts/fx.mjs +0 -266
- package/site/dist/assets/AccountNotFoundScreen-C4njXc69-DyVH4CEz.js +0 -1
- package/site/dist/assets/AddFundsSelectionScreen-RAoCn7ej-Dz1--SS9.js +0 -31
- package/site/dist/assets/Address-H8kD1d9O-BdFZmRwI.js +0 -17
- package/site/dist/assets/AffirmativeConsentScreen-BG0MJxku-CxaCAisL.js +0 -6
- package/site/dist/assets/AllowlistRejectionScreen-Cae6e1Od-JGk7vndN.js +0 -1
- package/site/dist/assets/ApiController-CTLbMCcH.js +0 -5
- package/site/dist/assets/ArrowsRightLeftIcon-B8g9adpz.js +0 -1
- package/site/dist/assets/AuthenticateWithWalletScreen-CU6hIcFT-CXD9_OP8.js +0 -1
- package/site/dist/assets/AwaitingEvmToSolBridgingScreen-D0e_sZMS-BtJCglvU.js +0 -1
- package/site/dist/assets/AwaitingExternalEthereumTransferScreen-ur5HL4R7-BgC2TsqH.js +0 -1
- package/site/dist/assets/AwaitingPasswordlessCodeScreen-DRHz76mM-B9OzLeEO.js +0 -103
- package/site/dist/assets/AwaitingSolToEvmBridgingScreen-CdLRrGD2-C7r5snUL.js +0 -1
- package/site/dist/assets/BridgeNetworkSelectionView-CFGjB2nO-BTV0NRio.js +0 -68
- package/site/dist/assets/CaptchaScreen-DbpvkIGx-7rSZIuM-.js +0 -1
- package/site/dist/assets/CheckCircleIcon-B1l17ioc.js +0 -1
- package/site/dist/assets/CheckCircleIcon-BGSI8nKf.js +0 -1
- package/site/dist/assets/Checkbox-D1EDeo41-BUfq0w99.js +0 -52
- package/site/dist/assets/ChevronDownIcon-B6uJvPHu.js +0 -1
- package/site/dist/assets/Chip-CZKIKt9K-r9Nk__0-.js +0 -26
- package/site/dist/assets/CoinbaseOnrampStatusScreen-BbyCJDXL-DWWNHxmR.js +0 -16
- package/site/dist/assets/ConnectEmailForm-B-VRE0MN-CRQwS0Xu.js +0 -3
- package/site/dist/assets/ConnectLedgerScreen-DWHABGot-CZcCt31O.js +0 -10
- package/site/dist/assets/ConnectOnlyLandingScreen-CZYx9Bmp-C5IOf-SQ.js +0 -1
- package/site/dist/assets/ConnectOnlyStatusScreen-DCRzjxHx-cwg74F3A.js +0 -1
- package/site/dist/assets/ConnectOrCreateScreen-CUUYUO07-D28oeK6b.js +0 -1
- package/site/dist/assets/ConnectPhoneForm-Dek5xn4C-EEdkKeTp.js +0 -95
- package/site/dist/assets/ConnectWalletView-DqI-PTvX-C2mEzukj.js +0 -74
- package/site/dist/assets/ConnectionStatusScreen-fUfROm9m-CH_7r8iV.js +0 -7
- package/site/dist/assets/ConnectionStatusScreen-fUfROm9m-Z33kis0B.js +0 -1
- package/site/dist/assets/ConnectorUtil-DH4NePk5.js +0 -21
- package/site/dist/assets/ConstantsUtil-DmLsRHAl.js +0 -198
- package/site/dist/assets/CopyToClipboard-i_OQSBJr-_G88JCVR.js +0 -39
- package/site/dist/assets/CopyableText-CQapvaMr-CGGfc_zM.js +0 -28
- package/site/dist/assets/CrossAppAuthScreen-CLw7ymZq-C9Okoc0V.js +0 -1
- package/site/dist/assets/CustomLandingScreenView-Cm_MBSyX-D2l6hdSl.js +0 -22
- package/site/dist/assets/DelegatedActionsConsentScreen-DYF2U4y5-Dda3KKqS.js +0 -1
- package/site/dist/assets/DelegatedActionsRevokeScreen-bPp6Uwyh-DG8kJPt1.js +0 -1
- package/site/dist/assets/DepositAddressAddressStepView-4vdkb55L-CcOUfn6f.js +0 -187
- package/site/dist/assets/DepositAddressScreen-DBLRs7aI-DeituDNh.js +0 -8
- package/site/dist/assets/DepositAddressV3Screen-CDqIEMQw-FDg6K14-.js +0 -1
- package/site/dist/assets/EmailInputForm-Cqo1mda8-BmbxvPHC.js +0 -124
- package/site/dist/assets/EmbeddedWalletConnectingScreen-D4T7DUzs-B05CQ9rx.js +0 -1
- package/site/dist/assets/EmbeddedWalletCreatedScreen-D7WhW5ve-Cxd1b9XH.js +0 -1
- package/site/dist/assets/EmbeddedWalletKeyExportScreen-CqV4NYQ3-CrJy0ViX.js +0 -23
- package/site/dist/assets/EmbeddedWalletOnAccountCreateScreen-BoKb3Q2S-Bakaa3zu.js +0 -1
- package/site/dist/assets/EmbeddedWalletPasswordUpdateScreen-D3rMlqS7-j78vKrcj.js +0 -1
- package/site/dist/assets/EmbeddedWalletPasswordUpdateSplashScreen-BPFkWJeE-D3dr7W9_.js +0 -1
- package/site/dist/assets/EnrollTotp-Dibew4dp-BXbYV8nI.js +0 -66
- package/site/dist/assets/EnvelopeIcon-DjPofnRI.js +0 -1
- package/site/dist/assets/ErrorBanner-BcpGRt0h-BhH6_V-9.js +0 -21
- package/site/dist/assets/ErrorMessage-D8VaAP5m-Bs7uX2a9.js +0 -8
- package/site/dist/assets/ErrorScreen-DipdTWzE-BgPRgnOb.js +0 -4
- package/site/dist/assets/ErrorScreen-DipdTWzE-CvxXC6pz.js +0 -1
- package/site/dist/assets/ExclamationCircleIcon-BE-WGrut.js +0 -1
- package/site/dist/assets/ExclamationTriangleIcon-Ca1Xlc_f.js +0 -1
- package/site/dist/assets/FarcasterConnectStatusScreen-51tjUxqq-De2JvZdV.js +0 -75
- package/site/dist/assets/FarcasterSignerStatusScreen-6x-Uh95G-bLp3hXoO.js +0 -28
- package/site/dist/assets/FiatOnrampScreen-BAhYeiTI-Jr6D6JiZ.js +0 -491
- package/site/dist/assets/FingerPrintIcon-DOCbS93y.js +0 -1
- package/site/dist/assets/FundSolWalletWithExternalSolanaWallet-WtLwYaOl-A_G5kfa1.js +0 -1
- package/site/dist/assets/FundWalletMethodHeader-BWNkokCT-LPv35wkD.js +0 -1
- package/site/dist/assets/FundWithBankDepositScreen-DajkHxBD-0hfGrfvf.js +0 -58
- package/site/dist/assets/FundingEditAmountScreen-CpvpiO4M-Dz0s_NWR.js +0 -1
- package/site/dist/assets/FundingMethodSelectionScreen-Db7vhRlE-ZGm-3-Io.js +0 -1
- package/site/dist/assets/GlobeAltIcon-BBRl2MMn.js +0 -1
- package/site/dist/assets/GooglePay-B53WnudL-BGiVpB8y.js +0 -1
- package/site/dist/assets/HCaptchaWrapper-CUi9LJsp-CHdfIeD_.js +0 -4
- package/site/dist/assets/InAppBrowserLoginNotPossible-BMR9br1T-Cgoiow3m.js +0 -1
- package/site/dist/assets/InfoBanner-Cb3p1z12-_YqQmRiu.js +0 -22
- package/site/dist/assets/InjectedWalletIcon-DLcYOGDj-tuIrKQHH.js +0 -1
- package/site/dist/assets/InstallWalletScreen-DTA8dwLv-BMU_wa61.js +0 -1
- package/site/dist/assets/JsonTree-BHzNC-ic-DcVr0YF0.js +0 -34
- package/site/dist/assets/LabelXs-oqZNqbm_-CNprGVQM.js +0 -6
- package/site/dist/assets/LandingScreen-CGWUoSUG-fH6rsQen.js +0 -1
- package/site/dist/assets/Layouts-BMRfo5hw-e-sepi6F.js +0 -77
- package/site/dist/assets/Link-BdDilT2T-BdwlRJch.js +0 -40
- package/site/dist/assets/LinkConflictScreen-C3ZEPsgl-BXKN2Qm5.js +0 -77
- package/site/dist/assets/LinkEmailScreen-dKo9frwc-Cyn7Q8p1.js +0 -1
- package/site/dist/assets/LinkPasskeyScreen-zbrl_AI_-CWiqnwLM.js +0 -117
- package/site/dist/assets/LinkPasskeyScreen-zbrl_AI_-YmDevJzf.js +0 -1
- package/site/dist/assets/LinkPhoneScreen-oIUpfj0w-h7shW-Ay.js +0 -1
- package/site/dist/assets/Loader-UAZqxsnw-DJEVblm1.js +0 -62
- package/site/dist/assets/LoadingSkeleton-BMsgO5PV-D2atjsP5.js +0 -17
- package/site/dist/assets/LockClosedIcon-9EFzWK1C.js +0 -1
- package/site/dist/assets/LoginFailedScreen-CN0dJV6U-By3Si77a.js +0 -1
- package/site/dist/assets/ManualTransferScreen-CHqcuqbs-BAHPY8rs.js +0 -1
- package/site/dist/assets/MfaAuthEnrollmentFlowScreen-Dj7gPTRC-CLjffFG1.js +0 -1
- package/site/dist/assets/MfaAuthVerifyFlowScreen-DbFpyWMy-BmQg1Y61.js +0 -1
- package/site/dist/assets/MfaEnrollmentFlowScreen-DhLnGgnr-DmBtTtlw.js +0 -1
- package/site/dist/assets/MfaVerifyFlowScreen-C_XPjMRP-4a683Bmf.js +0 -1
- package/site/dist/assets/ModalFooter-BCuEK_nz-C_71qsaz.js +0 -403
- package/site/dist/assets/MoonpayStatusScreen-CzTUtokc-DEHHcEf8.js +0 -16
- package/site/dist/assets/OAuthStatusScreen-ufbOa-da-BjAb_WsY.js +0 -1
- package/site/dist/assets/OpenLink-CUpJ1mOr-BLWFsPOW.js +0 -39
- package/site/dist/assets/PasskeySelectSignupOrLogin-xh2ScC_l-2PJTJxX-.js +0 -1
- package/site/dist/assets/PasskeyStatusScreen-Bla8MaOk-DCEg761l.js +0 -6
- package/site/dist/assets/PasswordRecoveryScreen-awGXZkQk-8nVjNc8k.js +0 -20
- package/site/dist/assets/PhArrowCircleDown-D97G-BOA.js +0 -14
- package/site/dist/assets/PhArrowClockwise-DVgyFuez.js +0 -14
- package/site/dist/assets/PhArrowDown-87b1BizM.js +0 -14
- package/site/dist/assets/PhArrowLeft-Da0MfnbK.js +0 -14
- package/site/dist/assets/PhArrowRight-AdrIL4DW.js +0 -14
- package/site/dist/assets/PhArrowSquareOut-Bk2CMG3I.js +0 -14
- package/site/dist/assets/PhArrowUp-4sgyKPGx.js +0 -14
- package/site/dist/assets/PhArrowUpRight-BNghD2zq.js +0 -14
- package/site/dist/assets/PhArrowsClockwise-BXRn1GEl.js +0 -14
- package/site/dist/assets/PhArrowsDownUp-DM09Jo_3.js +0 -14
- package/site/dist/assets/PhArrowsLeftRight-MuKBLUoF.js +0 -14
- package/site/dist/assets/PhBank-fHbgCqqZ.js +0 -14
- package/site/dist/assets/PhBrowser-2-9m-AkM.js +0 -14
- package/site/dist/assets/PhCaretDown-BDNabFuL.js +0 -14
- package/site/dist/assets/PhCaretLeft-CI460R-A.js +0 -14
- package/site/dist/assets/PhCaretRight-D7ZS3Ls8.js +0 -14
- package/site/dist/assets/PhCaretUp-C-iIfyvO.js +0 -14
- package/site/dist/assets/PhCheck-DxQXn57r.js +0 -14
- package/site/dist/assets/PhCircleHalf-MJZeRBGF.js +0 -14
- package/site/dist/assets/PhClock-x3MS3Mwi.js +0 -14
- package/site/dist/assets/PhCompass-hYn3HhGN.js +0 -14
- package/site/dist/assets/PhCopy-CWNYcABI.js +0 -14
- package/site/dist/assets/PhCreditCard-Fy4pzwtt.js +0 -14
- package/site/dist/assets/PhCurrencyDollar-BgiaenCi.js +0 -14
- package/site/dist/assets/PhDesktop-CxrzCSv5.js +0 -14
- package/site/dist/assets/PhDeviceMobile-CsyA5Goi.js +0 -14
- package/site/dist/assets/PhDotsThree-D-k1BiYg.js +0 -14
- package/site/dist/assets/PhEnvelope-D8kzeNox.js +0 -14
- package/site/dist/assets/PhFunnelSimple-CfQs5DAW.js +0 -14
- package/site/dist/assets/PhGlobe-DvmSc9_A.js +0 -14
- package/site/dist/assets/PhIdentificationCard-CfkZm-5K.js +0 -14
- package/site/dist/assets/PhImage-Cf0J2ZfJ.js +0 -14
- package/site/dist/assets/PhInfo-Bwi51-2X.js +0 -14
- package/site/dist/assets/PhLightbulb-C4FZV7Es.js +0 -14
- package/site/dist/assets/PhMagnifyingGlass-C9u8jSCJ.js +0 -14
- package/site/dist/assets/PhPaperPlaneRight-zxQVvPLX.js +0 -14
- package/site/dist/assets/PhPlus-C4UoW-2n.js +0 -14
- package/site/dist/assets/PhPower-CFyWDmgD.js +0 -14
- package/site/dist/assets/PhPuzzlePiece-BjoNVfST.js +0 -14
- package/site/dist/assets/PhQrCode-CP3tFauY.js +0 -14
- package/site/dist/assets/PhQuestion-wOWJBd3G.js +0 -14
- package/site/dist/assets/PhQuestionMark-CV0k9C0Y.js +0 -14
- package/site/dist/assets/PhSealCheck-vIKzREuN.js +0 -14
- package/site/dist/assets/PhSignOut-CxELV-i0.js +0 -14
- package/site/dist/assets/PhSpinner-CGDmbzUd.js +0 -14
- package/site/dist/assets/PhTrash-C9PD0SAt.js +0 -14
- package/site/dist/assets/PhUser-0sWNXQeZ.js +0 -14
- package/site/dist/assets/PhVault-6JTskUcC.js +0 -14
- package/site/dist/assets/PhWarning-CoKs3gz6.js +0 -14
- package/site/dist/assets/PhWarningCircle-BXHw1fCt.js +0 -14
- package/site/dist/assets/PhX-DX0SFIqC.js +0 -14
- package/site/dist/assets/PhoneIcon-CZ47md80.js +0 -1
- package/site/dist/assets/PinInput-DbZ0b1i1-BrTXu3Km.js +0 -247
- package/site/dist/assets/PrivyPluginContext-DA-yS5vO-CY60yubk.js +0 -1
- package/site/dist/assets/QrCode-DbnZwlkH-CcMrWV5D.js +0 -22
- package/site/dist/assets/RecoveryOAuthStatusScreen-CsgtTI2x-DgeDnHOK.js +0 -1
- package/site/dist/assets/RecoveryPasswordCreateScreen-Be-qJ-LU-CvJJwLhK.js +0 -1
- package/site/dist/assets/RecoverySelectionScreen-D1iGtpmi-CADtGtSV.js +0 -7
- package/site/dist/assets/SIWXUtil-Cpiv91-M.js +0 -1
- package/site/dist/assets/Screen-D30meWsY-vEusJ7KX.js +0 -175
- package/site/dist/assets/ScreenHeader-CHmc4-Lu-BvGTLg53.js +0 -31
- package/site/dist/assets/ScreenLayout-OwaQQcxH-cfOTmyl0.js +0 -1
- package/site/dist/assets/SelectSourceAsset-CWFog6gm-DxYklZYs.js +0 -140
- package/site/dist/assets/SetAutomaticRecoveryScreen-BO_OEqAZ-oeqeo05x.js +0 -14
- package/site/dist/assets/SetWalletPasswordForm-DwCTVtAh-BxFee9UA.js +0 -11
- package/site/dist/assets/ShieldCheckIcon-eFjsosCA.js +0 -1
- package/site/dist/assets/SignRequestScreen-BHKv32KR-z8ixfIma.js +0 -26
- package/site/dist/assets/SolanaWalletConnector-C7Lqk-Wz-Bc_lvdiF.js +0 -1
- package/site/dist/assets/StackedContainer-B2vaEl56-Do5dXB8-.js +0 -24
- package/site/dist/assets/StandardSignAndSendTransactionScreen-CfhIXVtd-DudetZAc.js +0 -20
- package/site/dist/assets/Subtitle-CV-2yKE4-DV0o1av3.js +0 -13
- package/site/dist/assets/TelegramAuthScreen-UL9zLfO8-Bo3sONvf.js +0 -1
- package/site/dist/assets/Title-BnzYV3Is-C3qOPnO_.js +0 -7
- package/site/dist/assets/TodoList-DnyULl18-BvxvubbF.js +0 -36
- package/site/dist/assets/TransactionDetails-e9nesl30-WWflkOIi.js +0 -109
- package/site/dist/assets/TransactionErrorView-BDBNqA_f-CotYKcgf.js +0 -178
- package/site/dist/assets/TransferFromWalletScreen-Qvn1Yuno-sVFlWqa8.js +0 -31
- package/site/dist/assets/TransferOrBridgeLoadingScreen-Dl1I_iIz-CV2-mY19.js +0 -6
- package/site/dist/assets/TurnstileWrapper-B7sNddys-oPbuMTQt.js +0 -1
- package/site/dist/assets/UpdateEmailScreen-DVOhPV0H-D_mjSpAy.js +0 -1
- package/site/dist/assets/UpdatePhoneScreen-CXRswTNZ-6RLG0ahv.js +0 -1
- package/site/dist/assets/UserLimitReachedScreen-5we2DHn8-CaVN0bti.js +0 -1
- package/site/dist/assets/Value-DTgR824E-Bp6WGZZm.js +0 -27
- package/site/dist/assets/WalletCards-DH1rqayz-D3cOTb9x.js +0 -1
- package/site/dist/assets/WalletIcon-DOIZhb2l.js +0 -1
- package/site/dist/assets/WalletInfoCard-sq_2wgFg-bOqfl5MA.js +0 -29
- package/site/dist/assets/WalletInterstitialScreen-Sh7LfsAh-DWaVkMCA.js +0 -1
- package/site/dist/assets/WalletLink-DK5LB5Ha-DjUZvqs-.js +0 -20
- package/site/dist/assets/WalletOverflowButton-bfCnp-9T-B6P7DQXA.js +0 -1
- package/site/dist/assets/WarningBanner-ZZqCEtZK-Bc2YKZb7.js +0 -21
- package/site/dist/assets/__vite-browser-external-NciY4Vu1.js +0 -1
- package/site/dist/assets/_esm-BZP3cXTv.js +0 -1
- package/site/dist/assets/_esm-ZmzBJpEs.js +0 -6
- package/site/dist/assets/accounts-D4AXF17G.js +0 -1
- package/site/dist/assets/accounts-IkdFx9nq.js +0 -1
- package/site/dist/assets/analytics-mkkvFRju-BRBcadka.js +0 -1
- package/site/dist/assets/baseSepolia-Bn1j1Ysw.js +0 -1
- package/site/dist/assets/basic-Cz1YiCSk.js +0 -1575
- package/site/dist/assets/capitalizeFirstLetter-DmLYqXsO-DtOXx-2n.js +0 -1
- package/site/dist/assets/ccip-B1J3oTZx.js +0 -7
- package/site/dist/assets/ccip-Bd7261zy.js +0 -1
- package/site/dist/assets/ccip-D6EHKpjI.js +0 -1
- package/site/dist/assets/chain-scefkYIH.js +0 -5
- package/site/dist/assets/check-DHnllI8N.js +0 -1
- package/site/dist/assets/chevron-down-1y-r4bGO.js +0 -1
- package/site/dist/assets/circle-alert-OwxamXUX.js +0 -1
- package/site/dist/assets/circle-check-big-3sZGVC10.js +0 -1
- package/site/dist/assets/circle-x-DQjNUV9J.js +0 -1
- package/site/dist/assets/client-B-BpRtwY.js +0 -1
- package/site/dist/assets/client-BfIR1FvH.js +0 -9
- package/site/dist/assets/coinbase-DCPC2L_R-YEuIn3jh.js +0 -3
- package/site/dist/assets/copy-DMOX_yn7.js +0 -1
- package/site/dist/assets/core-GIEmxmAa.js +0 -12
- package/site/dist/assets/createLucideIcon-BsERoFTA.js +0 -1
- package/site/dist/assets/defineChain-B-SQDLTG.js +0 -1
- package/site/dist/assets/dijkstra-CY95afk4.js +0 -1
- package/site/dist/assets/dist-CoS6E-3x.js +0 -1
- package/site/dist/assets/dist-Cp6IXeJd.js +0 -1
- package/site/dist/assets/dist-CrNxmdZj.js +0 -1
- package/site/dist/assets/dist-DYGtf4F0.js +0 -16
- package/site/dist/assets/embed.worker-Cnl4VGr3.js +0 -1
- package/site/dist/assets/errors-BQ3N-RVt-CZPAWCgC.js +0 -1
- package/site/dist/assets/esm-C3DslVJx.js +0 -1
- package/site/dist/assets/esm-Ci1ogKuK.js +0 -1
- package/site/dist/assets/esm-Co94ENUV.js +0 -37
- package/site/dist/assets/esm-bb8SANZh.js +0 -1
- package/site/dist/assets/esm-n2lWo7kz.js +0 -1
- package/site/dist/assets/ethers-zTjbodk5-Cs648CnD.js +0 -1
- package/site/dist/assets/eventemitter3-Dzc_0Uq2.js +0 -1
- package/site/dist/assets/external-link-BBPG3rG7.js +0 -1
- package/site/dist/assets/farcaster-DPlSjvF5-CzBCmgyL.js +0 -1
- package/site/dist/assets/features-CuiHbgEJ.js +0 -2
- package/site/dist/assets/fingerprint-pattern-CjLtyIxB.js +0 -1
- package/site/dist/assets/floating-ui.react-DmaCfayr.js +0 -1
- package/site/dist/assets/floating-ui.react-dom-CaoMF8mP.js +0 -1
- package/site/dist/assets/formatErc20TokenAmount-BuPk9xcy-BVqOKQif.js +0 -1
- package/site/dist/assets/formatter-CVbp4SlJ.js +0 -1
- package/site/dist/assets/get-entropy-details-for-user-dHRH5Gpi-BNiOFXbK.js +0 -1
- package/site/dist/assets/getChainName-DjpPdUSc-DUyKDrnp.js +0 -1
- package/site/dist/assets/getErc20Balance-jXNpFf0N-q4bV8kQq.js +0 -1
- package/site/dist/assets/getErc20TokenInfo-BNTrzLMP-DcnTFrXN.js +0 -1
- package/site/dist/assets/getFormattedUsdFromLamports-De3U9GlO-Gx4_wxON.js +0 -1
- package/site/dist/assets/getUsdcMintAddress-BVgm9GRo-r2UnDeEy.js +0 -1
- package/site/dist/assets/guards-BIW-GiMJ-D3tWTQAR.js +0 -1
- package/site/dist/assets/hooks.module-BYZGfwDl.js +0 -1
- package/site/dist/assets/hourglass-D_GfQdJr.js +0 -1
- package/site/dist/assets/id-UqEwL2BI-CTlvRMA5.js +0 -1
- package/site/dist/assets/index-BdEw5wyl-kLbhqLbv.js +0 -1
- package/site/dist/assets/index-C2h3EneX.js +0 -8
- package/site/dist/assets/index-CWARkn2w-DxuckIFn.js +0 -61
- package/site/dist/assets/index-DvMddJcd-CNBb4VUn.js +0 -12
- package/site/dist/assets/index-YiCw0Y_x-Czl0tWfF.js +0 -436
- package/site/dist/assets/index.browser-B-i-Lj3J.js +0 -1
- package/site/dist/assets/index.browser-BMmUcykv.js +0 -1
- package/site/dist/assets/index.es-Crd0TyUS.js +0 -2
- package/site/dist/assets/index.es-DQbup1WG.js +0 -3
- package/site/dist/assets/isPaymentRequestAvailable-Bq1cemEn-Cjf3l05I.js +0 -1
- package/site/dist/assets/jsx-runtime-CndCZlyt.js +0 -1
- package/site/dist/assets/landmark-BZPqMFEf.js +0 -1
- package/site/dist/assets/lib-DGtzi2DK.js +0 -1
- package/site/dist/assets/localBatchGatewayRequest-B71J8sQ9.js +0 -29
- package/site/dist/assets/lock-DyN_c-ud.js +0 -1
- package/site/dist/assets/logger-CsXm3oIv-B0UYpNFI.js +0 -1
- package/site/dist/assets/mail-V_mcPb64.js +0 -1
- package/site/dist/assets/modal-context-CwmSeFcv-DPWNJrfK.js +0 -1
- package/site/dist/assets/native-BKj27wfc.js +0 -1
- package/site/dist/assets/ofetch.CWycOUEr-Duby3iHT.js +0 -1
- package/site/dist/assets/parseAbi-Csg-8t59.js +0 -2
- package/site/dist/assets/parseAbiParameters-BWbIMYe4.js +0 -1
- package/site/dist/assets/parseEther-CHS0GNkM.js +0 -1
- package/site/dist/assets/pkce-DXF0yKjP-7_VY-d83.js +0 -1
- package/site/dist/assets/preload-helper-CRzLMJPS.js +0 -1
- package/site/dist/assets/privy-context-81KmsBw2-DiR6-Yq8.js +0 -2
- package/site/dist/assets/property-DuZ9DLP4.js +0 -3
- package/site/dist/assets/react-BfYA1q0G.js +0 -1
- package/site/dist/assets/react-CK2HRI1t.js +0 -1
- package/site/dist/assets/react-DxGeY8B8.js +0 -1
- package/site/dist/assets/react-auth-sHnipzbS.js +0 -1
- package/site/dist/assets/react-dom-BI2Koe4o.js +0 -1
- package/site/dist/assets/reservoir-x-nGuZkT-BguVFH3D.js +0 -1
- package/site/dist/assets/rolldown-runtime-Bjp52BOI.js +0 -1
- package/site/dist/assets/safe-url-D7SRPu33-CkMedGro.js +0 -1
- package/site/dist/assets/secp256k1-BeYL8aZl.js +0 -1
- package/site/dist/assets/secp256k1-Bh4MSkhz.js +0 -1
- package/site/dist/assets/secp256k1-Ci4cqHl0.js +0 -1
- package/site/dist/assets/secp256k1-DsxXqbXv.js +0 -1
- package/site/dist/assets/secp256k1-N9RUmafO.js +0 -1
- package/site/dist/assets/serializeAuthorizationList-DGMik9Wx.js +0 -1
- package/site/dist/assets/serializeSignature-CWKpxoab.js +0 -2
- package/site/dist/assets/sha2-C6s7zZmb.js +0 -1
- package/site/dist/assets/shared-DRZUmnL5-BewE5wK8.js +0 -193
- package/site/dist/assets/shared-FM0rljBt-CxLACri_.js +0 -21
- package/site/dist/assets/shouldProceedtoEmbeddedWalletCreationFlow-B3vyavrh-BkAKsRNi.js +0 -1
- package/site/dist/assets/simulateTransaction-CfuOrae4-PjA_tGjX.js +0 -1
- package/site/dist/assets/smartphone-3p8nFSTE.js +0 -1
- package/site/dist/assets/src-BBjkfi-Q.js +0 -1
- package/site/dist/assets/storage-Ufl800Ne-1qCR2JMO.js +0 -8
- package/site/dist/assets/stringify-DqUj9VzO.js +0 -20
- package/site/dist/assets/stripe.esm-DEi2D291.js +0 -2
- package/site/dist/assets/styles-BsotlekN-Aq27nZGc.js +0 -9
- package/site/dist/assets/styles-Cm9H63WO-p_ic_dxE.js +0 -92
- package/site/dist/assets/styles-DVyDvTdj-CjsWqAld.js +0 -74
- package/site/dist/assets/styles-hslXy6xG-D5QHIrJU.js +0 -164
- package/site/dist/assets/telegram-B-JqnkqZ-Dqkm4rDn.js +0 -1
- package/site/dist/assets/to-ui-error-DWe-qwS3-Bi1HUQI9.js +0 -4
- package/site/dist/assets/toViemAccount-B5iDGq1b-CdFZwv07.js +0 -2
- package/site/dist/assets/transaction-BNTP-bFm-M5tJx8vY.js +0 -1
- package/site/dist/assets/transfer-6YztDh-t-BVQkTvG-.js +0 -1
- package/site/dist/assets/triangle-alert-BG3Qw-Hq.js +0 -1
- package/site/dist/assets/twitch-5IOe4sIQ-CaXHFlAu.js +0 -1
- package/site/dist/assets/types-4CuOD23i.js +0 -1
- package/site/dist/assets/use-headless-crypto-deposit-Yn-QRLB3-CQqeyhGY.js +0 -1
- package/site/dist/assets/use-unlink-wallet-BKK4_5Ur-C0xQN_Lp.js +0 -1
- package/site/dist/assets/useActiveWallet-B-yZYGc6-BkK6rFBm.js +0 -123
- package/site/dist/assets/useGetSolPrice-x7gfUIHJ-CLVXqv6I.js +0 -1
- package/site/dist/assets/useGetTokenPrice-CaoUkb79-DAXXs3rs.js +0 -1
- package/site/dist/assets/useI18n-C-5n_noF-CGpkgIVy.js +0 -1
- package/site/dist/assets/useWalletBalance-CZnP5f_f-Cfd64m4n.js +0 -1
- package/site/dist/assets/useWallets-X1ndvJ_U-CAYqg7Za.js +0 -1
- package/site/dist/assets/utils-BkR7EQjg.js +0 -1
- package/site/dist/assets/utils-DJhBNOZA.js +0 -1
- package/site/dist/assets/utils-DdckVfQr.js +0 -1
- package/site/dist/assets/w3m-modal-B4H_uiPH.js +0 -1174
- package/site/dist/assets/wallet-BNI12IYk.js +0 -1
- package/site/dist/assets/wallet-CITSaPlB.js +0 -2
- package/site/dist/assets/wallet-connect-BvJtUsDt-CVlSNIXP.js +0 -1
- package/site/dist/assets/walletconnect-registry-xxG8XLCb-BpzpBq5s.js +0 -1
- package/site/dist/assets/walletconnect-v2-3frQFwbg-CzZ6oybX.js +0 -12
- package/site/dist/assets/wui-ux-by-reown-XyHdtV1O.js +0 -1147
- package/site/dist/assets/x-gjDrY2rk.js +0 -1
- package/src/discover/explore.js +0 -291
- package/src/market/settle.js +0 -165
- package/src/market/terms.js +0 -200
- package/src/publish/cli.js +0 -336
- package/src/publish/envelope.js +0 -185
- package/src/publish/manifest.js +0 -142
package/site/worker.js
CHANGED
|
@@ -13,25 +13,21 @@
|
|
|
13
13
|
// show it inline. Source sites forbid framing and send no CORS, so the
|
|
14
14
|
// page cannot fetch or frame them itself. Only hosts the app's own
|
|
15
15
|
// records link to (each domain's `link_hosts`), only PDFs, cached a day.
|
|
16
|
-
// GET /paid/<
|
|
17
|
-
//
|
|
18
|
-
// the
|
|
19
|
-
// POST /paid {ids} many keys for one price, paid by one payment or several (vouchers).
|
|
20
|
-
// Its 402 carries the Bazaar extension, so the facilitator lists it on
|
|
21
|
-
// the first sale; /openapi.json and /.well-known/x402 name it for x402scan
|
|
16
|
+
// GET /paid/<publisher>/<repo>/<id> with CONFIG.paid: an x402 v2 quote for one record its
|
|
17
|
+
// publisher sells (scheme "fangorn", from the record's signed terms). With a
|
|
18
|
+
// PAYMENT-SIGNATURE header, the facilitator (Barliman) verifies and settles it
|
|
22
19
|
// anything else the static site (the stock page, the card)
|
|
23
20
|
//
|
|
24
|
-
// The
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// paying, so nobody pays for a record that does not exist, and decrypts and checks it after.
|
|
21
|
+
// The app sells nothing of its own. A publisher seals a record's fields and signs its terms
|
|
22
|
+
// (`fangorn commit --paid`); this worker only quotes them. After paying, the buyer proves it to
|
|
23
|
+
// the relay and asks the record's key server for the key: nothing secret passes through here.
|
|
28
24
|
//
|
|
29
25
|
// Bindings: VIEW (R2), AI (Workers AI), ASSETS (the site), CONFIG (a JSON var: app, name,
|
|
30
|
-
// description, fromBlock, schema, repos, paid, deployment), and optionally RPC_URL
|
|
31
|
-
//
|
|
26
|
+
// description, fromBlock, schema, repos, paid, facilitator, deployment), and optionally RPC_URL
|
|
27
|
+
// and IPFS_GATEWAY (vars).
|
|
32
28
|
|
|
33
|
-
import { Fangorn, FangornConfig, SupportedNetworks
|
|
34
|
-
import { createPublicClient, http } from "viem";
|
|
29
|
+
import { Fangorn, FangornConfig, SupportedNetworks } from "@fangorn-network/sdk";
|
|
30
|
+
import { createPublicClient, http, parseAbi } from "viem";
|
|
35
31
|
import { generatePrivateKey } from "viem/accounts";
|
|
36
32
|
import { EMBED_MODEL, DOCUMENT, QUERY, matryoshka } from "../src/core/embed.js";
|
|
37
33
|
import { configure, resetShard } from "../src/core/shard.js";
|
|
@@ -74,35 +70,28 @@ export async function takeLock(bucket, now = Date.now()) {
|
|
|
74
70
|
const network = () => SupportedNetworks[CONFIG.network] ?? FangornConfig;
|
|
75
71
|
|
|
76
72
|
function reader(env) {
|
|
77
|
-
// CONFIG.deployment: another deployment's addresses (and
|
|
73
|
+
// CONFIG.deployment: another deployment's addresses (and fromBlock as a string),
|
|
78
74
|
// for a testnet fork or a local chain. Absent, the network's.
|
|
79
75
|
const d = CONFIG.deployment ?? {};
|
|
80
|
-
const config = { ...network(), ...d, ...(d.
|
|
76
|
+
const config = { ...network(), ...d, ...(d.fromBlock != null ? { fromBlock: BigInt(d.fromBlock) } : {}),
|
|
81
77
|
...(env.RPC_URL ? { rpcUrl: env.RPC_URL } : {}) };
|
|
82
|
-
const fangorn = Fangorn.create({ privateKey: generatePrivateKey(), config,
|
|
78
|
+
const fangorn = Fangorn.create({ privateKey: generatePrivateKey(), config,
|
|
83
79
|
storage: { signedUrl: env.IPFS_GATEWAY ? { gateway: env.IPFS_GATEWAY } : {} } });
|
|
84
80
|
return { fangorn, chain: createPublicClient({ chain: config.chain, transport: http(config.rpcUrl) }) };
|
|
85
81
|
}
|
|
86
82
|
|
|
87
83
|
/**
|
|
88
|
-
*
|
|
89
|
-
* the last
|
|
90
|
-
* excludes. Neither membership nor exclusions move a head, so they are read, not followed.
|
|
91
|
-
* ponytail: reads every publisher's updates, and 4 contract reads per repo per run; follow
|
|
92
|
-
* the app's members and ExclusionChanged events when an app holds enough repos to show it.
|
|
84
|
+
* Every repo at its latest head, kept across runs in R2: each run reads only the updates
|
|
85
|
+
* since the last. publishView narrows it to the repos the app lists.
|
|
93
86
|
*/
|
|
94
87
|
export async function presented({ fangorn, chain }, state) {
|
|
95
88
|
const to = await chain.getBlockNumber();
|
|
96
|
-
const from = state.block == null ? BigInt(CONFIG.fromBlock ?? network().
|
|
97
|
-
const app = fangorn.getAppId();
|
|
89
|
+
const from = state.block == null ? BigInt(CONFIG.fromBlock ?? network().fromBlock) : BigInt(state.block) + 1n;
|
|
98
90
|
const registry = fangorn.getRegistry();
|
|
99
91
|
if (from <= to) for (const e of await registry.log({}, from, to))
|
|
100
92
|
state.repos[`${e.publisher.toLowerCase()}/${e.repo}`] = { owner: e.publisher, repo: e.repo, root: e.head };
|
|
101
93
|
state.block = String(to);
|
|
102
|
-
|
|
103
|
-
const kept = await Promise.all(all.map(async (r) => await registry.presents(app, r.owner)
|
|
104
|
-
&& !(await repoExcluded(registry, app, { publisher: r.owner, repo: r.repo }))));
|
|
105
|
-
return all.filter((_, i) => kept[i]);
|
|
94
|
+
return Object.values(state.repos);
|
|
106
95
|
}
|
|
107
96
|
|
|
108
97
|
export async function index(env, { log = console.log } = {}) {
|
|
@@ -112,12 +101,9 @@ export async function index(env, { log = console.log } = {}) {
|
|
|
112
101
|
try {
|
|
113
102
|
const state = (await (await env.VIEW.get(STATE))?.json()) ?? { block: null, repos: {} };
|
|
114
103
|
const r = reader(env);
|
|
115
|
-
// Imported here: x402.js's self-check imports this module, and a cycle at load would deadlock it.
|
|
116
|
-
const { openRow } = await import("../src/agent/x402.js");
|
|
117
104
|
const report = await publishView({
|
|
118
105
|
fangorn: r.fangorn, app: CONFIG.app, schema: CONFIG.schema, repos: CONFIG.repos, store: r2Store(env.VIEW),
|
|
119
106
|
presented: await presented(r, state), embedBatch: encode(env, DOCUMENT), embedder: await embedderOf(env), log,
|
|
120
|
-
...(CONFIG.private ? { open: (p) => openRow(env.PAID_SECRET, p) } : {}),
|
|
121
107
|
});
|
|
122
108
|
await env.VIEW.put(STATE, JSON.stringify(state));
|
|
123
109
|
log(`index: through block ${state.block}, ${Object.keys(report).length} domain(s) changed`);
|
|
@@ -167,7 +153,7 @@ async function tools(env, origin) {
|
|
|
167
153
|
return mcp.tools;
|
|
168
154
|
}
|
|
169
155
|
|
|
170
|
-
export async function mcpCall(env, origin, msg
|
|
156
|
+
export async function mcpCall(env, origin, msg) {
|
|
171
157
|
const ok = (result) => ({ jsonrpc: "2.0", id: msg.id, result });
|
|
172
158
|
switch (msg.method) {
|
|
173
159
|
case "initialize": return ok({
|
|
@@ -177,21 +163,11 @@ export async function mcpCall(env, origin, msg, request = null) {
|
|
|
177
163
|
instructions: `${CONFIG.description ?? ""} Call describe first, then search.`.trim(),
|
|
178
164
|
});
|
|
179
165
|
case "ping": return ok({});
|
|
180
|
-
case "tools/list": {
|
|
181
|
-
const t = await tools(env, origin), X = CONFIG.private;
|
|
182
|
-
const price = X && `${Number(X.call) / 10 ** (X.decimals ?? 6)} ${X.symbol ?? "USDC"}`;
|
|
183
|
-
return ok({ tools: (t ? t.tools : []).map((d) => !X || FREE_TOOLS.has(d.name) ? d
|
|
184
|
-
: { ...d, description: `${d.description} Paid: ${price} a call over x402, or a pass from POST ${origin}/pass.` }) });
|
|
185
|
-
}
|
|
166
|
+
case "tools/list": return ok({ tools: (await tools(env, origin))?.tools ?? [] });
|
|
186
167
|
case "tools/call": {
|
|
187
|
-
const t = await tools(env, origin)
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
catch (e) { return { isError: true, content: [{ type: "text", text: e?.message ?? String(e) }] }; }
|
|
191
|
-
};
|
|
192
|
-
const tool = t.tools.find((d) => d.name === name);
|
|
193
|
-
if (!CONFIG.private || !tool || FREE_TOOLS.has(name) || (request && await passOf(env, request))) return ok(await run());
|
|
194
|
-
return ok(await paidCall(origin, tool, msg.params?._meta?.["x402/payment"], run));
|
|
168
|
+
const t = await tools(env, origin);
|
|
169
|
+
try { return ok(await t.call(msg.params?.name, msg.params?.arguments ?? {})); }
|
|
170
|
+
catch (e) { return ok({ isError: true, content: [{ type: "text", text: e?.message ?? String(e) }] }); }
|
|
195
171
|
}
|
|
196
172
|
default: return { jsonrpc: "2.0", id: msg.id, error: { code: -32601, message: `unknown method ${msg.method}` } };
|
|
197
173
|
}
|
|
@@ -203,7 +179,7 @@ async function serveMcp(request, env, url) {
|
|
|
203
179
|
try { msg = await request.json(); } catch { return json({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "not JSON" } }, 400); }
|
|
204
180
|
if (Array.isArray(msg)) return json({ jsonrpc: "2.0", id: null, error: { code: -32600, message: "one message per request" } }, 400);
|
|
205
181
|
if (msg.id === undefined) return new Response(null, { status: 202, headers: CORS }); // a notification
|
|
206
|
-
return json(await mcpCall(env, url.origin, msg
|
|
182
|
+
return json(await mcpCall(env, url.origin, msg));
|
|
207
183
|
}
|
|
208
184
|
|
|
209
185
|
const CORS = { "access-control-allow-origin": "*", "access-control-allow-headers": "PAYMENT-SIGNATURE, authorization, content-type",
|
|
@@ -233,240 +209,82 @@ async function doc(request, env, url) {
|
|
|
233
209
|
return res;
|
|
234
210
|
}
|
|
235
211
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const k = await crypto.subtle.importKey("raw", new TextEncoder().encode(env.PAID_SECRET), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
241
|
-
const mac = new Uint8Array(await crypto.subtle.sign("HMAC", k, new TextEncoder().encode(id)));
|
|
242
|
-
return [...mac].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
243
|
-
}
|
|
244
|
-
const keysFor = async (env, ids) => Object.fromEntries(await Promise.all(ids.map(async (id) => [id, await keyFor(env, id)])));
|
|
245
|
-
|
|
246
|
-
// ── x402 v2 ───────────────────────────────────────────────────────────────────
|
|
247
|
-
// Over HTTP the 402's PaymentRequired is in the PAYMENT-REQUIRED header (base64 JSON) and in the
|
|
248
|
-
// body, the buyer pays in PAYMENT-SIGNATURE, the settlement comes back in PAYMENT-RESPONSE. Over
|
|
249
|
-
// MCP (a private app's tools) the same objects ride in the tool result and `_meta`.
|
|
250
|
-
// Only a payment's `payload` (the signed EIP-3009 authorization) is taken from the buyer: what
|
|
251
|
-
// goes to the facilitator is rebuilt here around it, so `accepted`, `resource` and the Bazaar
|
|
252
|
-
// extension are always this worker's, including on vouchers signed weeks ago.
|
|
253
|
-
//
|
|
254
|
-
// A sale is what one route sells: its terms `P` (CONFIG.paid or CONFIG.private: network, asset,
|
|
255
|
-
// payTo, facilitator, the token's EIP-712 name and version), what a catalog says it is
|
|
256
|
-
// (`resource`), and its Bazaar entry, if the facilitator should list it.
|
|
212
|
+
// ── paid records ─────────────────────────────────────────────────────────────
|
|
213
|
+
// x402 v2 over HTTP: the 402's PaymentRequired in the PAYMENT-REQUIRED header (base64 JSON) and the
|
|
214
|
+
// body, the buyer's payment in PAYMENT-SIGNATURE, the settlement back in PAYMENT-RESPONSE. Only the
|
|
215
|
+
// payment's `payload` is taken from the buyer: the requirement sent to the facilitator is ours.
|
|
257
216
|
|
|
258
|
-
const MAX_BATCH = 1000, MAX_PAYMENTS = 10;
|
|
259
217
|
const b64json = (o) => btoa(JSON.stringify(o));
|
|
260
|
-
const requirement = (P, amount) => ({ scheme: "exact", network: P.network, amount: String(amount), asset: P.asset,
|
|
261
|
-
payTo: P.payTo, maxTimeoutSeconds: 120, extra: { name: P.name, version: P.version } });
|
|
262
|
-
const resource = (url, description) => ({ url, mimeType: "application/json", serviceName: CONFIG.name, description });
|
|
263
|
-
const lead = (P) => (P.description ? `${P.description}. ` : "");
|
|
264
|
-
|
|
265
|
-
const SCHEMA = "https://json-schema.org/draft/2020-12/schema";
|
|
266
|
-
const OUTPUT = { type: "object", required: ["type"], properties: { type: { type: "string" }, example: { type: "object" } } };
|
|
267
|
-
/** A Bazaar entry for a POST route taking JSON. */
|
|
268
|
-
const httpBazaar = (body, bodySchema, example) => ({
|
|
269
|
-
info: { input: { type: "http", method: "POST", bodyType: "json", body }, output: { type: "json", example } },
|
|
270
|
-
schema: { $schema: SCHEMA, type: "object", required: ["input"], properties: {
|
|
271
|
-
input: { type: "object", required: ["type", "method", "bodyType", "body"], additionalProperties: false, properties: {
|
|
272
|
-
type: { type: "string", const: "http" }, method: { type: "string", enum: ["POST"] }, bodyType: { type: "string", enum: ["json"] }, body: bodySchema } },
|
|
273
|
-
output: OUTPUT } },
|
|
274
|
-
});
|
|
275
|
-
/** A Bazaar entry for one MCP tool: listed by (the server's URL, its name). */
|
|
276
|
-
const mcpBazaar = (tool) => ({
|
|
277
|
-
info: { input: { type: "mcp", toolName: tool.name, description: tool.description, inputSchema: tool.inputSchema, transport: "streamable-http" } },
|
|
278
|
-
schema: { $schema: SCHEMA, type: "object", required: ["input"], properties: {
|
|
279
|
-
input: { type: "object", required: ["type", "toolName", "inputSchema"], additionalProperties: false, properties: {
|
|
280
|
-
type: { type: "string", const: "mcp" }, toolName: { type: "string" }, description: { type: "string" },
|
|
281
|
-
transport: { type: "string", enum: ["streamable-http", "sse"] }, inputSchema: { type: "object" }, example: { type: "object" } } },
|
|
282
|
-
output: OUTPUT } },
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
// The keys to sealed records. Only POST /paid is listed: GET /paid/<id>'s URL differs per record,
|
|
286
|
-
// and each would be listed apart.
|
|
287
|
-
const KEYS_BAZAAR = httpBazaar({ ids: ["<record id>"] },
|
|
288
|
-
{ type: "object", required: ["ids"], properties: { ids: { type: "array", items: { type: "string" }, minItems: 1, maxItems: MAX_BATCH } } },
|
|
289
|
-
{ keys: { "<record id>": "<hex key>" }, missing: [], receipts: [], paid: [] });
|
|
290
|
-
const keySale = (url, listed) => ({ P: CONFIG.paid, bazaar: listed ? KEYS_BAZAAR : null,
|
|
291
|
-
resource: resource(url.href, `${lead(CONFIG.paid)}Sells the keys to ${CONFIG.name}'s sealed records (AES-256-GCM, in its rows as `
|
|
292
|
-
+ `paid_ct). Find records and their ids with the MCP server at ${url.origin}/mcp.`) });
|
|
293
|
-
|
|
294
|
-
// A private app's pass, and its tools one call at a time.
|
|
295
|
-
const PASS_BAZAAR = httpBazaar({}, { type: "object" }, { token: "<pass>", expires: 1900000000 });
|
|
296
|
-
const days = (s) => Math.round(s / 86400);
|
|
297
|
-
const passSale = (url) => ({ P: CONFIG.private, bazaar: PASS_BAZAAR,
|
|
298
|
-
resource: resource(url.href, `${lead(CONFIG.private)}A ${days(CONFIG.private.pass.seconds)}-day pass to ${CONFIG.name}: every tool of its MCP server `
|
|
299
|
-
+ `at ${url.origin}/mcp and its view, with Authorization: Bearer <token>.`) });
|
|
300
|
-
const callSale = (origin, tool) => ({ P: CONFIG.private, bazaar: mcpBazaar(tool),
|
|
301
|
-
resource: resource(`${origin}/mcp`, `${lead(CONFIG.private)}${CONFIG.name}'s ${tool.name} tool, one call. A pass (POST ${origin}/pass) covers every call.`) });
|
|
302
|
-
|
|
303
|
-
const paymentRequired = (sale, amount, error) => ({ x402Version: 2, error, resource: sale.resource, accepts: [requirement(sale.P, amount)],
|
|
304
|
-
...(sale.bazaar ? { extensions: { bazaar: sale.bazaar } } : {}) });
|
|
305
|
-
|
|
306
|
-
/** 402 with the price of `amount`; `more` rides in the body only. */
|
|
307
|
-
function quote(sale, amount, error, more = {}) {
|
|
308
|
-
const required = paymentRequired(sale, amount, error);
|
|
309
|
-
return json({ ...required, ...more }, 402, { "payment-required": b64json(required) });
|
|
310
|
-
}
|
|
311
218
|
|
|
312
|
-
/**
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
219
|
+
/**
|
|
220
|
+
* A record's quote: `{ requirement, body }`, or null when its publisher doesn't sell it.
|
|
221
|
+
* `read(publisher, repo, id)` → the record's payload, or null. `chain`: chainId, registry, usdc and
|
|
222
|
+
* its EIP-712 name and version. The facilitator's /supported names the referrer the buyer signs in.
|
|
223
|
+
*/
|
|
224
|
+
export async function quoteOf({ publisher, repo, id, read, chain, facilitator }) {
|
|
225
|
+
const payload = await read(publisher, repo, id);
|
|
226
|
+
const t = payload?.["@terms"];
|
|
227
|
+
if (!t?.terms || !t.signature) return null;
|
|
228
|
+
const fields = Object.fromEntries(Object.entries(payload).filter(([, v]) => v?.["@type"] === "handle" && v.gadget === "field-v1"));
|
|
229
|
+
const generation = Object.values(fields)[0]?.generation;
|
|
230
|
+
if (generation == null) return null;
|
|
231
|
+
const deposit = await read(publisher, repo, `@deposit/${generation}`);
|
|
232
|
+
if (!deposit) return null;
|
|
233
|
+
const network = `eip155:${chain.chainId}`;
|
|
234
|
+
const kind = (await (await fetch(`${facilitator}/supported`)).json()).kinds?.find((k) => k.scheme === "fangorn" && k.network === network);
|
|
235
|
+
if (!kind?.extra?.referrer) throw new Error(`${facilitator} does not settle scheme "fangorn" on ${network}`);
|
|
236
|
+
return {
|
|
237
|
+
requirement: { scheme: "fangorn", network, amount: String(t.terms.price), asset: chain.usdc, payTo: chain.registry, maxTimeoutSeconds: 300,
|
|
238
|
+
extra: { terms: t.terms, signature: t.signature, referrer: kind.extra.referrer, name: chain.usdcName, version: chain.usdcVersion } },
|
|
239
|
+
body: { record: { publisher, repo, id }, fields, deposit, relay: facilitator },
|
|
240
|
+
};
|
|
327
241
|
}
|
|
328
242
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if (payments.length !== 1) return { refused: quote(sale, amount, "one payment, not several") };
|
|
340
|
-
const payment = payloadOf(sale, payments[0], amount);
|
|
341
|
-
const verified = await facilitator(sale.P, "verify", payment);
|
|
342
|
-
if (!verified.isValid) return { refused: quote(sale, amount, verified.invalidReason ?? verified.error ?? "payment did not verify") };
|
|
343
|
-
const settled = await facilitator(sale.P, "settle", payment);
|
|
344
|
-
if (!settled.success) return { refused: quote(sale, amount, settled.errorReason ?? settled.error ?? "settlement failed") };
|
|
345
|
-
return { settled, payer: settled.payer ?? payment.payload?.authorization?.from };
|
|
243
|
+
let chainInfo = null;
|
|
244
|
+
/** The SettlementRegistry and its USDC (address, EIP-712 name and version), read once per isolate. */
|
|
245
|
+
function chainOf(fangorn) {
|
|
246
|
+
return (chainInfo ??= (async () => {
|
|
247
|
+
const c = fangorn.getConfig(), registry = fangorn.getSettlementRegistry().getAddress();
|
|
248
|
+
const pub = createPublicClient({ transport: http(c.rpcUrl) });
|
|
249
|
+
const usdc = await pub.readContract({ address: registry, abi: parseAbi(["function usdc() view returns (address)"]), functionName: "usdc" });
|
|
250
|
+
const str = (fn, fallback) => pub.readContract({ address: usdc, abi: parseAbi([`function ${fn}() view returns (string)`]), functionName: fn }).catch(() => fallback);
|
|
251
|
+
return { chainId: c.chain.id, registry, usdc, usdcName: await str("name", "USDC"), usdcVersion: await str("version", "2") };
|
|
252
|
+
})().catch((e) => { chainInfo = null; throw e; }));
|
|
346
253
|
}
|
|
347
254
|
|
|
348
255
|
async function sell(request, env, url) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
const
|
|
352
|
-
if (
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const
|
|
361
|
-
const { payments, error } = signatures(request);
|
|
362
|
-
let ids;
|
|
363
|
-
try { ids = [...new Set((await request.json()).ids)].filter((x) => typeof x === "string"); } catch { ids = null; }
|
|
364
|
-
if (!ids?.length || ids.length > MAX_BATCH) {
|
|
365
|
-
// A probe (x402scan, a Bazaar crawler) posts no body: it gets the price of one record.
|
|
366
|
-
if (error) return quote(sale, P.price, `${error}; the body is {"ids": [...]}, ${P.price} per id`);
|
|
367
|
-
return json({ error: `body must be {"ids": [...]}, between 1 and ${MAX_BATCH} ids per purchase` }, 400);
|
|
368
|
-
}
|
|
369
|
-
const found = ids, missing = [];
|
|
370
|
-
const total = BigInt(P.price) * BigInt(found.length);
|
|
371
|
-
const refuse = (error, more = {}) => quote(sale, total, error, { found, missing, ...more });
|
|
372
|
-
if (error) return refuse(error);
|
|
373
|
-
if (payments.length > MAX_PAYMENTS) return refuse(`at most ${MAX_PAYMENTS} payments per purchase`);
|
|
374
|
-
const auths = payments.map((p) => p?.payload?.authorization ?? {});
|
|
375
|
-
if (new Set(auths.map((a) => a.nonce)).size !== auths.length) return refuse("a payment is repeated");
|
|
376
|
-
if (auths.some((a) => String(a.to).toLowerCase() !== P.payTo.toLowerCase())) return refuse("a payment is not to this app");
|
|
377
|
-
let sum = 0n;
|
|
378
|
-
try { for (const a of auths) sum += BigInt(a.value); } catch { return refuse("a payment has no amount"); }
|
|
379
|
-
if (sum !== total) return refuse(`payments add up to ${sum}, the price is ${total}`);
|
|
380
|
-
const signed = payments.map((p, i) => payloadOf(sale, p, auths[i].value));
|
|
381
|
-
for (const [i, p] of signed.entries()) {
|
|
382
|
-
const v = await facilitator(P, "verify", p);
|
|
383
|
-
if (!v.isValid) return refuse(`payment ${i + 1}: ${v.invalidReason ?? v.error ?? "did not verify"}`);
|
|
384
|
-
}
|
|
385
|
-
// Settle one by one. A spent voucher (or one the wallet can no longer cover) passes verify
|
|
386
|
-
// and fails here. If that happens after others settled, the buyer still gets what the
|
|
387
|
-
// settled money pays for, never more: a spent voucher riding along with a fresh one buys
|
|
388
|
-
// only what the fresh one pays for. `paid` and `rejected` name the vouchers (by nonce) so
|
|
389
|
-
// the buyer's stack can drop them either way.
|
|
390
|
-
const receipts = [];
|
|
391
|
-
let settled = 0n;
|
|
392
|
-
for (const [i, p] of signed.entries()) {
|
|
393
|
-
const s = await facilitator(P, "settle", p);
|
|
394
|
-
if (!s.success) {
|
|
395
|
-
const error = `payment ${i + 1}: ${s.errorReason ?? s.error ?? "settlement failed"}`;
|
|
396
|
-
if (!receipts.length) return refuse(error, { rejected: [auths[i].nonce] });
|
|
397
|
-
const n = Number(settled / BigInt(P.price));
|
|
398
|
-
return json({ keys: await keysFor(env, found.slice(0, n)), missing, undelivered: found.slice(n), receipts, error,
|
|
399
|
-
paid: auths.slice(0, i).map((a) => a.nonce), rejected: [auths[i].nonce] });
|
|
400
|
-
}
|
|
401
|
-
receipts.push(s);
|
|
402
|
-
settled += BigInt(auths[i].value);
|
|
403
|
-
}
|
|
404
|
-
return json({ keys: await keysFor(env, found), missing, receipts, paid: auths.map((a) => a.nonce) });
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// ── a private app ─────────────────────────────────────────────────────────────
|
|
408
|
-
// CONFIG.private: the rows are sealed on chain (x402.js's sealRow), opened by the cron with
|
|
409
|
-
// PAID_SECRET, and the view is served only with access: a pass bought at POST /pass (one x402
|
|
410
|
-
// payment, a token good for `pass.seconds`), or, on /mcp, one x402 payment per tool call. What a
|
|
411
|
-
// stranger needs to decide whether to pay stays free: the card, the catalog (views, counts) and
|
|
412
|
-
// the describe tool.
|
|
413
|
-
// ponytail: a pass is a signed token, not a record: it cannot be revoked before it expires, and
|
|
414
|
-
// changing PAID_SECRET voids every pass (and every key). A list of revoked payers in R2 if that matters.
|
|
415
|
-
|
|
416
|
-
const FREE_VIEW = new Set(["cdn/catalog"]), FREE_TOOLS = new Set(["describe"]);
|
|
417
|
-
const b64url = (bytes) => btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
418
|
-
const unb64url = (s) => Uint8Array.from(atob(s.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0));
|
|
419
|
-
const passKey = (env) => crypto.subtle.importKey("raw", new TextEncoder().encode(`${env.PAID_SECRET}:pass`), { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"]);
|
|
420
|
-
|
|
421
|
-
async function issuePass(env, payer, exp) {
|
|
422
|
-
const claims = b64url(new TextEncoder().encode(JSON.stringify({ app: CONFIG.app, sub: payer, exp })));
|
|
423
|
-
return `${claims}.${b64url(new Uint8Array(await crypto.subtle.sign("HMAC", await passKey(env), new TextEncoder().encode(claims))))}`;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/** The pass a request carries (Authorization: Bearer), if it is this app's and unexpired. */
|
|
427
|
-
async function passOf(env, request) {
|
|
428
|
-
const m = /^Bearer ([\w-]+)\.([\w-]+)$/.exec(request.headers.get("authorization") ?? "");
|
|
429
|
-
if (!m || !env.PAID_SECRET) return null;
|
|
430
|
-
try {
|
|
431
|
-
if (!await crypto.subtle.verify("HMAC", await passKey(env), unb64url(m[2]), new TextEncoder().encode(m[1]))) return null;
|
|
432
|
-
const c = JSON.parse(new TextDecoder().decode(unb64url(m[1])));
|
|
433
|
-
return c.app === CONFIG.app && c.exp > Date.now() / 1000 ? c : null;
|
|
434
|
-
} catch { return null; }
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// POST /pass: one payment, a pass. A probe posts nothing and gets the price.
|
|
438
|
-
async function sellPass(request, env, url) {
|
|
439
|
-
const X = CONFIG.private;
|
|
440
|
-
const { settled, payer, refused } = await payOnce(request, passSale(url), X.pass.price);
|
|
441
|
-
if (refused) return refused;
|
|
442
|
-
const expires = Math.floor(Date.now() / 1000) + X.pass.seconds;
|
|
443
|
-
return json({ token: await issuePass(env, payer, expires), expires,
|
|
444
|
-
use: `Authorization: Bearer <token> on ${url.origin}/mcp and ${url.origin}/view/…` }, 200, { "payment-response": b64json(settled) });
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
/** A paid tool call over x402's MCP transport: verify, run, settle. A call that fails is not
|
|
448
|
-
* charged (its payment is never settled), and a settlement that fails returns no content. */
|
|
449
|
-
async function paidCall(origin, tool, payment, run) {
|
|
450
|
-
const X = CONFIG.private, sale = callSale(origin, tool);
|
|
256
|
+
if (!CONFIG.facilitator) return json({ error: "this app names no facilitator (CONFIG.facilitator)" }, 503);
|
|
257
|
+
const [publisher, repo, ...rest] = url.pathname.slice("/paid/".length).split("/").map(decodeURIComponent);
|
|
258
|
+
const id = rest.join("/");
|
|
259
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(publisher ?? "") || !repo || !id) return json({ error: "GET /paid/<publisher>/<repo>/<record id>" }, 400);
|
|
260
|
+
const { fangorn } = reader(env);
|
|
261
|
+
const read = async (p, r, i) => (await fangorn.engine.readRecord(p, r, i).catch(() => null))?.payload ?? null;
|
|
262
|
+
let q;
|
|
263
|
+
try { q = await quoteOf({ publisher, repo, id, read, chain: await chainOf(fangorn), facilitator: CONFIG.facilitator }); }
|
|
264
|
+
catch (e) { return json({ error: e.message }, 502); }
|
|
265
|
+
if (!q) return json({ error: "not for sale" }, 404);
|
|
266
|
+
|
|
267
|
+
const resource = { url: url.href, mimeType: "application/json", serviceName: CONFIG.name, description: `${CONFIG.name}: ${id}` };
|
|
451
268
|
const required = (error) => {
|
|
452
|
-
const
|
|
453
|
-
return {
|
|
269
|
+
const r = { x402Version: 2, error, resource, accepts: [q.requirement] };
|
|
270
|
+
return json({ ...r, ...q.body }, 402, { "payment-required": b64json(r) });
|
|
454
271
|
};
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
272
|
+
const header = request.headers.get("PAYMENT-SIGNATURE");
|
|
273
|
+
if (!header) return required("payment required");
|
|
274
|
+
let payment;
|
|
275
|
+
try { payment = JSON.parse(atob(header)); } catch { return required("PAYMENT-SIGNATURE is not base64 JSON"); }
|
|
276
|
+
const paymentPayload = { x402Version: 2, resource, accepted: q.requirement, payload: payment?.payload };
|
|
277
|
+
const call = (path) => fetch(`${CONFIG.facilitator}/${path}`, { method: "POST", headers: { "content-type": "application/json" },
|
|
278
|
+
body: JSON.stringify({ x402Version: 2, paymentPayload, paymentRequirements: q.requirement }) })
|
|
279
|
+
.then((r) => r.json()).catch((e) => ({ error: `facilitator ${path}: ${e.message}` }));
|
|
280
|
+
const verified = await call("verify");
|
|
458
281
|
if (!verified.isValid) return required(verified.invalidReason ?? verified.error ?? "payment did not verify");
|
|
459
|
-
const
|
|
460
|
-
// view-tools answers a miss ("not in a loaded view") with {error} in the text, not isError.
|
|
461
|
-
let missed = false;
|
|
462
|
-
try { missed = !!JSON.parse(result.content?.[0]?.text ?? "{}").error; } catch { /* not JSON: an answer */ }
|
|
463
|
-
if (result.isError || missed) return result;
|
|
464
|
-
const settled = await facilitator(X, "settle", p);
|
|
282
|
+
const settled = await call("settle");
|
|
465
283
|
if (!settled.success) return required(settled.errorReason ?? settled.error ?? "settlement failed");
|
|
466
|
-
return { ...
|
|
284
|
+
return json({ ...q.body, settlement: settled }, 200, { "payment-response": b64json(settled) });
|
|
467
285
|
}
|
|
468
286
|
|
|
469
|
-
const BY_ORIGIN = new Set(["/.well-known/agent-card.json"
|
|
287
|
+
const BY_ORIGIN = new Set(["/.well-known/agent-card.json"]);
|
|
470
288
|
|
|
471
289
|
const worker = {
|
|
472
290
|
async fetch(request, env) {
|
|
@@ -474,16 +292,9 @@ const worker = {
|
|
|
474
292
|
const url = new URL(request.url);
|
|
475
293
|
const p = url.pathname;
|
|
476
294
|
if (request.method === "OPTIONS") return new Response(null, { status: 204, headers: CORS });
|
|
477
|
-
if (p.startsWith("/view/"))
|
|
478
|
-
const path = p.slice("/view/".length);
|
|
479
|
-
if (CONFIG.private && !FREE_VIEW.has(path) && !await passOf(env, request))
|
|
480
|
-
return json({ error: `${CONFIG.name}'s data is private: send Authorization: Bearer <pass>, from POST ${url.origin}/pass (x402)` }, 401);
|
|
481
|
-
return viewFile(env, path);
|
|
482
|
-
}
|
|
483
|
-
if (p === "/pass" && request.method === "POST") return CONFIG.private ? sellPass(request, env, url) : json({ error: "nothing is sold here" }, 404);
|
|
295
|
+
if (p.startsWith("/view/")) return viewFile(env, p.slice("/view/".length));
|
|
484
296
|
if (p === "/mcp") return serveMcp(request, env, url);
|
|
485
297
|
if (p === "/doc") return doc(request, env, url);
|
|
486
|
-
if (p === "/paid" && request.method === "POST") return CONFIG.paid ? sellBatch(request, env, url) : json({ error: "nothing is sold here" }, 404);
|
|
487
298
|
if (p.startsWith("/paid/")) return CONFIG.paid ? sell(request, env, url) : json({ error: "nothing is sold here" }, 404);
|
|
488
299
|
|
|
489
300
|
let res = await env.ASSETS.fetch(request);
|
|
@@ -552,7 +363,7 @@ if (typeof process !== "undefined" && import.meta.url === `file://${process.argv
|
|
|
552
363
|
rec("c1", TAG, { sku: "a1", name: "Oak table", about: "a solid oak dining table", doc: "https://docs.example/oak.pdf" }),
|
|
553
364
|
rec("c2", TAG, { sku: "b2", name: "Brass lamp", about: "a brass reading lamp" })] },
|
|
554
365
|
};
|
|
555
|
-
const fangorn = localFangorn(chain
|
|
366
|
+
const fangorn = localFangorn(chain);
|
|
556
367
|
await publishView({ fangorn, app: APP, schema: CONFIG.schema, store: r2Store(VIEW), embedBatch: encode(env, DOCUMENT), embedder: WORKER_EMBEDDER, log: () => {} });
|
|
557
368
|
eq([calls[0].model, calls[0].text[0].startsWith(DOCUMENT)], [AI_MODEL, true], "documents go to the AI binding with the document prompt");
|
|
558
369
|
|
|
@@ -563,8 +374,11 @@ if (typeof process !== "undefined" && import.meta.url === `file://${process.argv
|
|
|
563
374
|
for (const t of ["describe", "search", "get", "similar", "count", "browse"]) if (!names.includes(t)) throw new Error(`missing tool ${t}: ${names}`);
|
|
564
375
|
const described = JSON.parse((await call("tools/call", { name: "describe", arguments: {} })).result.content[0].text);
|
|
565
376
|
eq(described.views.map((v) => v.rows), [2], "describe reads the catalog from R2");
|
|
377
|
+
eq(described.views[0].repos.map((r) => [r.publisher, r.repo, r.rows]), [[PUB, "shop", 2]], "describe names each repo's publisher");
|
|
566
378
|
const found = JSON.parse((await call("tools/call", { name: "search", arguments: { query: "reading lamp", limit: 1 } })).result.content[0].text);
|
|
567
|
-
eq([found.ranked_by, found.hits[0].title], ["meaning", "Brass lamp"], "search ranks by meaning, the query embedded by the same runtime");
|
|
379
|
+
eq([found.ranked_by, found.hits[0].title, found.hits[0].publisher], ["meaning", "Brass lamp", PUB], "search ranks by meaning, the query embedded by the same runtime; a hit names its publisher");
|
|
380
|
+
const theirs = JSON.parse((await call("tools/call", { name: "search", arguments: { query: "lamp", where: { owner: OWNER } } })).result.content[0].text);
|
|
381
|
+
eq(theirs.hits.length, 0, "search narrows to one publisher with where.owner");
|
|
568
382
|
if (!calls.at(-1).text[0].startsWith(QUERY)) throw new Error("the query goes to the AI binding with the query prompt");
|
|
569
383
|
eq((await (await worker.fetch(new Request("https://shop.test/mcp", { method: "POST", body: '{"jsonrpc":"2.0","method":"notifications/initialized"}' }), env)).status), 202, "a notification is accepted, not answered");
|
|
570
384
|
|
|
@@ -575,77 +389,40 @@ if (typeof process !== "undefined" && import.meta.url === `file://${process.argv
|
|
|
575
389
|
eq((await worker.fetch(new Request("https://shop.test/doc?u=https://evil.example/x.pdf"), env)).status, 403, "/doc refuses a host no record links to");
|
|
576
390
|
eq((await worker.fetch(new Request("https://shop.test/view/cdn/nope"), env)).status, 404, "a missing file is a 404");
|
|
577
391
|
|
|
578
|
-
// A
|
|
579
|
-
const
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
const
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
open: (p) => openRow(SECRET, p) });
|
|
587
|
-
env.PAID_SECRET = SECRET;
|
|
588
|
-
CONFIG = env.CONFIG = { ...env.CONFIG, private: { network: "eip155:84532", asset: `0x${"aa".repeat(20)}`, payTo: `0x${"5e".repeat(20)}`,
|
|
589
|
-
facilitator: "https://fac", name: "USDC", version: "2", pass: { price: "1000000", seconds: 86400 }, call: "10000" } };
|
|
590
|
-
const settles = [];
|
|
392
|
+
// A paid record's quote: its signed terms as the requirement, its handles and deposit beside it.
|
|
393
|
+
const H = (b) => `0x${b.repeat(32)}`;
|
|
394
|
+
const terms = { terms: { publisher: PUB, repo: H("22"), key: H("33"), price: "10000", shareBps: 500, generation: "0", deadline: "2000000000" }, signature: "0x12" };
|
|
395
|
+
const handle = { "@type": "handle", gadget: "field-v1", generation: "0", cid: "bafy" };
|
|
396
|
+
const records = { [`${PUB}/shop/a1`]: { name: "Oak", body: handle, "@terms": terms }, [`${PUB}/shop/@deposit/0`]: { "@type": "deposit", sealed: "0x99" },
|
|
397
|
+
[`${PUB}/shop/free`]: { name: "Free" } };
|
|
398
|
+
const read = async (p, r, i) => records[`${p}/${r}/${i}`] ?? null;
|
|
399
|
+
const chainInfo = { chainId: 84532, registry: `0x${"e0".repeat(20)}`, usdc: `0x${"aa".repeat(20)}`, usdcName: "USDC", usdcVersion: "2" };
|
|
591
400
|
const realFetch = globalThis.fetch;
|
|
592
|
-
globalThis.fetch = async (u
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
settles.push(p);
|
|
596
|
-
return new Response(JSON.stringify({ success: true, transaction: `0x${settles.length}`, network: "eip155:84532", payer: p.payload.authorization.from }));
|
|
597
|
-
};
|
|
401
|
+
globalThis.fetch = async (u) => (u === "https://fac/supported"
|
|
402
|
+
? new Response(JSON.stringify({ kinds: [{ x402Version: 2, scheme: "fangorn", network: "eip155:84532", extra: { referrer: `0x${"fe".repeat(20)}` } }] }))
|
|
403
|
+
: realFetch(u));
|
|
598
404
|
try {
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
eq([(
|
|
603
|
-
|
|
604
|
-
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/call", params }) }), env)).json()).result;
|
|
605
|
-
const search = { name: "search", arguments: { query: "velvet armchair", limit: 1 } };
|
|
606
|
-
const ask = await asMcp(search);
|
|
607
|
-
eq([ask.isError, ask.structuredContent.accepts[0].amount, ask.structuredContent.extensions.bazaar.info.input.toolName, JSON.parse(ask.content[0].text).x402Version],
|
|
608
|
-
[true, "10000", "search", 2], "a paid tool without payment answers with x402's PaymentRequired, listed as an MCP tool");
|
|
609
|
-
eq(JSON.parse((await asMcp({ name: "describe", arguments: {} })).content[0].text).views[0].rows, 3, "describe stays free, and counts the sealed row");
|
|
610
|
-
if (!(await call("tools/list")).result.tools.find((t) => t.name === "search").description.includes("Paid: 0.01 USDC")) throw new Error("tools/list names the price");
|
|
611
|
-
|
|
612
|
-
const quote402 = await worker.fetch(new Request("https://shop.test/pass", { method: "POST" }), env);
|
|
613
|
-
const pr = JSON.parse(atob(quote402.headers.get("payment-required")));
|
|
614
|
-
eq([quote402.status, pr.accepts[0].amount, pr.extensions.bazaar.info.input.method], [402, "1000000", "POST"], "a pass is quoted over x402, Bazaar entry and all");
|
|
615
|
-
const bought = await (await worker.fetch(new Request("https://shop.test/pass", { method: "POST", headers: { "PAYMENT-SIGNATURE": btoa(JSON.stringify(payment)) } }), env)).json();
|
|
616
|
-
const auth = { authorization: `Bearer ${bought.token}` };
|
|
617
|
-
eq([(await get(`view/${shard}`, auth)).status, settles.length], [200, 1], "a pass opens the view, paid once");
|
|
618
|
-
const withPass = JSON.parse((await asMcp(search, auth)).content[0].text);
|
|
619
|
-
eq([withPass.hits[0].title, settles.length], ["Velvet chair", 1], "a pass covers tool calls, and the sealed row was opened and embedded");
|
|
620
|
-
const forged = `${bought.token.split(".")[0]}.${"A".repeat(43)}`;
|
|
621
|
-
eq((await get(`view/${shard}`, { authorization: `Bearer ${forged}` })).status, 401, "a forged pass is refused");
|
|
622
|
-
|
|
623
|
-
const paidOnce = await asMcp({ ...search, _meta: { "x402/payment": payment } });
|
|
624
|
-
eq([JSON.parse(paidOnce.content[0].text).hits[0].title, paidOnce._meta["x402/payment-response"].transaction, settles.at(-1).resource.url],
|
|
625
|
-
["Velvet chair", "0x2", "https://shop.test/mcp"], "one call, one payment, settled after it ran");
|
|
626
|
-
const failed = await asMcp({ name: "get", arguments: { id: "no-such-row" }, _meta: { "x402/payment": payment } });
|
|
627
|
-
eq([!!JSON.parse(failed.content[0].text).error, !!failed._meta, settles.length], [true, false, 2], "a call that misses is not charged");
|
|
405
|
+
const q = await quoteOf({ publisher: PUB, repo: "shop", id: "a1", read, chain: chainInfo, facilitator: "https://fac" });
|
|
406
|
+
eq([q.requirement.scheme, q.requirement.amount, q.requirement.payTo, q.requirement.extra.referrer, q.requirement.extra.terms.key],
|
|
407
|
+
["fangorn", "10000", chainInfo.registry, `0x${"fe".repeat(20)}`, H("33")], "the requirement is the record's signed terms, paid to the registry, with the facilitator's referrer");
|
|
408
|
+
eq([Object.keys(q.body.fields), q.body.deposit.sealed, q.body.relay], [["body"], "0x99", "https://fac"], "the body carries the sealed fields' handles, the deposit and the relay");
|
|
409
|
+
eq(await quoteOf({ publisher: PUB, repo: "shop", id: "free", read, chain: chainInfo, facilitator: "https://fac" }), null, "a record with no terms is not for sale");
|
|
628
410
|
} finally { globalThis.fetch = realFetch; }
|
|
629
|
-
|
|
411
|
+
eq((await worker.fetch(new Request(`https://shop.test/paid/${PUB}/shop/a1`), env)).status, 404, "an app without `paid` sells nothing");
|
|
630
412
|
|
|
631
|
-
// The incremental scan: only updates since the last run
|
|
632
|
-
const
|
|
633
|
-
const logs = [{ publisher: PUB, repo: "shop", head: "0x01", t: 10n }, { publisher: OWNER, repo: "notes", head: "0x02", t: 12n },
|
|
634
|
-
{ publisher: STRANGER, repo: "spam", head: "0x04", t: 14n }];
|
|
413
|
+
// The incremental scan: only updates since the last run.
|
|
414
|
+
const logs = [{ publisher: PUB, repo: "shop", head: "0x01", t: 10n }, { publisher: OWNER, repo: "notes", head: "0x02", t: 12n }];
|
|
635
415
|
const asked = [];
|
|
636
|
-
let
|
|
637
|
-
const r = { chain: { getBlockNumber: async () => block }, fangorn: {
|
|
416
|
+
let block = 20n;
|
|
417
|
+
const r = { chain: { getBlockNumber: async () => block }, fangorn: { getRegistry: () => ({
|
|
638
418
|
log: async (_f, from, to) => { asked.push([from, to]); return logs.filter((l) => l.t >= from && l.t <= to); },
|
|
639
|
-
presents: async (_app, p) => p !== STRANGER,
|
|
640
|
-
exclusionCount: async (_app, x) => (excluded && x.publisher === excluded && !x.repo ? 1 : 0),
|
|
641
419
|
}) } };
|
|
642
420
|
CONFIG = { ...CONFIG, fromBlock: "5" };
|
|
643
421
|
const state = { block: null, repos: {} };
|
|
644
|
-
eq((await presented(r, state)).map((t) => t.repo).sort(), ["notes", "shop"], "the first run scans from the app's fromBlock
|
|
422
|
+
eq((await presented(r, state)).map((t) => t.repo).sort(), ["notes", "shop"], "the first run scans from the app's fromBlock");
|
|
645
423
|
logs.push({ publisher: PUB, repo: "shop", head: "0x03", t: 25n });
|
|
646
424
|
block = 30n;
|
|
647
|
-
|
|
648
|
-
eq((await presented(r, state)).map((t) => [t.repo, t.root]), [["shop", "0x03"]], "the next reads only what is new, and drops what the app excludes");
|
|
425
|
+
eq((await presented(r, state)).map((t) => [t.repo, t.root]).sort(), [["notes", "0x02"], ["shop", "0x03"]], "the next reads only what is new");
|
|
649
426
|
eq(asked.map((a) => a.map(String)), [["5", "20"], ["21", "30"]], "each run starts where the last ended");
|
|
650
|
-
console.log("worker.js self-check ok — the lens embeds through the AI binding, /mcp serves the view's tools from R2 with queries embedded by the same runtime, the view and /doc are served from R2,
|
|
427
|
+
console.log("worker.js self-check ok — the lens embeds through the AI binding, /mcp serves the view's tools from R2 with queries embedded by the same runtime, the view and /doc are served from R2, a paid record is quoted from its signed terms, and the indexer scans only new updates");
|
|
651
428
|
}
|