@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.
Files changed (360) hide show
  1. package/README.md +5 -5
  2. package/docs/app-to-agent.md +61 -625
  3. package/package.json +12 -17
  4. package/site/dist/assets/__vite-browser-external-D2U8RZpp.js +1 -0
  5. package/site/dist/assets/embed.worker-CmS57Zti.js +1 -0
  6. package/site/dist/assets/index-Djs8f08m.js +8 -0
  7. package/site/dist/assets/rolldown-runtime-COiEfhsA.js +1 -0
  8. package/site/dist/assets/{transformers.web-BTyOEJ0C.js → transformers.web-CwR_IYQV.js} +1 -1
  9. package/site/dist/index.html +3 -4
  10. package/site/worker.js +115 -338
  11. package/src/agent/agent-card.js +33 -45
  12. package/src/agent/mcp.js +168 -371
  13. package/src/agent/tools.js +2 -1
  14. package/src/agent/view-tools.js +4 -56
  15. package/src/agent/x402.js +182 -369
  16. package/src/core/roles.js +6 -6
  17. package/src/core/shard.js +4 -4
  18. package/src/discover/apps.js +67 -81
  19. package/src/discover/directory.js +2 -2
  20. package/src/market/reactions.js +21 -15
  21. package/src/publish/devnet.js +69 -18
  22. package/src/publish/init.js +1 -1
  23. package/src/publish/ship.js +74 -275
  24. package/src/publish/view.js +52 -53
  25. package/template/README.md +9 -11
  26. package/template/package.json +1 -1
  27. package/plugin/scripts/fx.mjs +0 -266
  28. package/site/dist/assets/AccountNotFoundScreen-C4njXc69-DyVH4CEz.js +0 -1
  29. package/site/dist/assets/AddFundsSelectionScreen-RAoCn7ej-Dz1--SS9.js +0 -31
  30. package/site/dist/assets/Address-H8kD1d9O-BdFZmRwI.js +0 -17
  31. package/site/dist/assets/AffirmativeConsentScreen-BG0MJxku-CxaCAisL.js +0 -6
  32. package/site/dist/assets/AllowlistRejectionScreen-Cae6e1Od-JGk7vndN.js +0 -1
  33. package/site/dist/assets/ApiController-CTLbMCcH.js +0 -5
  34. package/site/dist/assets/ArrowsRightLeftIcon-B8g9adpz.js +0 -1
  35. package/site/dist/assets/AuthenticateWithWalletScreen-CU6hIcFT-CXD9_OP8.js +0 -1
  36. package/site/dist/assets/AwaitingEvmToSolBridgingScreen-D0e_sZMS-BtJCglvU.js +0 -1
  37. package/site/dist/assets/AwaitingExternalEthereumTransferScreen-ur5HL4R7-BgC2TsqH.js +0 -1
  38. package/site/dist/assets/AwaitingPasswordlessCodeScreen-DRHz76mM-B9OzLeEO.js +0 -103
  39. package/site/dist/assets/AwaitingSolToEvmBridgingScreen-CdLRrGD2-C7r5snUL.js +0 -1
  40. package/site/dist/assets/BridgeNetworkSelectionView-CFGjB2nO-BTV0NRio.js +0 -68
  41. package/site/dist/assets/CaptchaScreen-DbpvkIGx-7rSZIuM-.js +0 -1
  42. package/site/dist/assets/CheckCircleIcon-B1l17ioc.js +0 -1
  43. package/site/dist/assets/CheckCircleIcon-BGSI8nKf.js +0 -1
  44. package/site/dist/assets/Checkbox-D1EDeo41-BUfq0w99.js +0 -52
  45. package/site/dist/assets/ChevronDownIcon-B6uJvPHu.js +0 -1
  46. package/site/dist/assets/Chip-CZKIKt9K-r9Nk__0-.js +0 -26
  47. package/site/dist/assets/CoinbaseOnrampStatusScreen-BbyCJDXL-DWWNHxmR.js +0 -16
  48. package/site/dist/assets/ConnectEmailForm-B-VRE0MN-CRQwS0Xu.js +0 -3
  49. package/site/dist/assets/ConnectLedgerScreen-DWHABGot-CZcCt31O.js +0 -10
  50. package/site/dist/assets/ConnectOnlyLandingScreen-CZYx9Bmp-C5IOf-SQ.js +0 -1
  51. package/site/dist/assets/ConnectOnlyStatusScreen-DCRzjxHx-cwg74F3A.js +0 -1
  52. package/site/dist/assets/ConnectOrCreateScreen-CUUYUO07-D28oeK6b.js +0 -1
  53. package/site/dist/assets/ConnectPhoneForm-Dek5xn4C-EEdkKeTp.js +0 -95
  54. package/site/dist/assets/ConnectWalletView-DqI-PTvX-C2mEzukj.js +0 -74
  55. package/site/dist/assets/ConnectionStatusScreen-fUfROm9m-CH_7r8iV.js +0 -7
  56. package/site/dist/assets/ConnectionStatusScreen-fUfROm9m-Z33kis0B.js +0 -1
  57. package/site/dist/assets/ConnectorUtil-DH4NePk5.js +0 -21
  58. package/site/dist/assets/ConstantsUtil-DmLsRHAl.js +0 -198
  59. package/site/dist/assets/CopyToClipboard-i_OQSBJr-_G88JCVR.js +0 -39
  60. package/site/dist/assets/CopyableText-CQapvaMr-CGGfc_zM.js +0 -28
  61. package/site/dist/assets/CrossAppAuthScreen-CLw7ymZq-C9Okoc0V.js +0 -1
  62. package/site/dist/assets/CustomLandingScreenView-Cm_MBSyX-D2l6hdSl.js +0 -22
  63. package/site/dist/assets/DelegatedActionsConsentScreen-DYF2U4y5-Dda3KKqS.js +0 -1
  64. package/site/dist/assets/DelegatedActionsRevokeScreen-bPp6Uwyh-DG8kJPt1.js +0 -1
  65. package/site/dist/assets/DepositAddressAddressStepView-4vdkb55L-CcOUfn6f.js +0 -187
  66. package/site/dist/assets/DepositAddressScreen-DBLRs7aI-DeituDNh.js +0 -8
  67. package/site/dist/assets/DepositAddressV3Screen-CDqIEMQw-FDg6K14-.js +0 -1
  68. package/site/dist/assets/EmailInputForm-Cqo1mda8-BmbxvPHC.js +0 -124
  69. package/site/dist/assets/EmbeddedWalletConnectingScreen-D4T7DUzs-B05CQ9rx.js +0 -1
  70. package/site/dist/assets/EmbeddedWalletCreatedScreen-D7WhW5ve-Cxd1b9XH.js +0 -1
  71. package/site/dist/assets/EmbeddedWalletKeyExportScreen-CqV4NYQ3-CrJy0ViX.js +0 -23
  72. package/site/dist/assets/EmbeddedWalletOnAccountCreateScreen-BoKb3Q2S-Bakaa3zu.js +0 -1
  73. package/site/dist/assets/EmbeddedWalletPasswordUpdateScreen-D3rMlqS7-j78vKrcj.js +0 -1
  74. package/site/dist/assets/EmbeddedWalletPasswordUpdateSplashScreen-BPFkWJeE-D3dr7W9_.js +0 -1
  75. package/site/dist/assets/EnrollTotp-Dibew4dp-BXbYV8nI.js +0 -66
  76. package/site/dist/assets/EnvelopeIcon-DjPofnRI.js +0 -1
  77. package/site/dist/assets/ErrorBanner-BcpGRt0h-BhH6_V-9.js +0 -21
  78. package/site/dist/assets/ErrorMessage-D8VaAP5m-Bs7uX2a9.js +0 -8
  79. package/site/dist/assets/ErrorScreen-DipdTWzE-BgPRgnOb.js +0 -4
  80. package/site/dist/assets/ErrorScreen-DipdTWzE-CvxXC6pz.js +0 -1
  81. package/site/dist/assets/ExclamationCircleIcon-BE-WGrut.js +0 -1
  82. package/site/dist/assets/ExclamationTriangleIcon-Ca1Xlc_f.js +0 -1
  83. package/site/dist/assets/FarcasterConnectStatusScreen-51tjUxqq-De2JvZdV.js +0 -75
  84. package/site/dist/assets/FarcasterSignerStatusScreen-6x-Uh95G-bLp3hXoO.js +0 -28
  85. package/site/dist/assets/FiatOnrampScreen-BAhYeiTI-Jr6D6JiZ.js +0 -491
  86. package/site/dist/assets/FingerPrintIcon-DOCbS93y.js +0 -1
  87. package/site/dist/assets/FundSolWalletWithExternalSolanaWallet-WtLwYaOl-A_G5kfa1.js +0 -1
  88. package/site/dist/assets/FundWalletMethodHeader-BWNkokCT-LPv35wkD.js +0 -1
  89. package/site/dist/assets/FundWithBankDepositScreen-DajkHxBD-0hfGrfvf.js +0 -58
  90. package/site/dist/assets/FundingEditAmountScreen-CpvpiO4M-Dz0s_NWR.js +0 -1
  91. package/site/dist/assets/FundingMethodSelectionScreen-Db7vhRlE-ZGm-3-Io.js +0 -1
  92. package/site/dist/assets/GlobeAltIcon-BBRl2MMn.js +0 -1
  93. package/site/dist/assets/GooglePay-B53WnudL-BGiVpB8y.js +0 -1
  94. package/site/dist/assets/HCaptchaWrapper-CUi9LJsp-CHdfIeD_.js +0 -4
  95. package/site/dist/assets/InAppBrowserLoginNotPossible-BMR9br1T-Cgoiow3m.js +0 -1
  96. package/site/dist/assets/InfoBanner-Cb3p1z12-_YqQmRiu.js +0 -22
  97. package/site/dist/assets/InjectedWalletIcon-DLcYOGDj-tuIrKQHH.js +0 -1
  98. package/site/dist/assets/InstallWalletScreen-DTA8dwLv-BMU_wa61.js +0 -1
  99. package/site/dist/assets/JsonTree-BHzNC-ic-DcVr0YF0.js +0 -34
  100. package/site/dist/assets/LabelXs-oqZNqbm_-CNprGVQM.js +0 -6
  101. package/site/dist/assets/LandingScreen-CGWUoSUG-fH6rsQen.js +0 -1
  102. package/site/dist/assets/Layouts-BMRfo5hw-e-sepi6F.js +0 -77
  103. package/site/dist/assets/Link-BdDilT2T-BdwlRJch.js +0 -40
  104. package/site/dist/assets/LinkConflictScreen-C3ZEPsgl-BXKN2Qm5.js +0 -77
  105. package/site/dist/assets/LinkEmailScreen-dKo9frwc-Cyn7Q8p1.js +0 -1
  106. package/site/dist/assets/LinkPasskeyScreen-zbrl_AI_-CWiqnwLM.js +0 -117
  107. package/site/dist/assets/LinkPasskeyScreen-zbrl_AI_-YmDevJzf.js +0 -1
  108. package/site/dist/assets/LinkPhoneScreen-oIUpfj0w-h7shW-Ay.js +0 -1
  109. package/site/dist/assets/Loader-UAZqxsnw-DJEVblm1.js +0 -62
  110. package/site/dist/assets/LoadingSkeleton-BMsgO5PV-D2atjsP5.js +0 -17
  111. package/site/dist/assets/LockClosedIcon-9EFzWK1C.js +0 -1
  112. package/site/dist/assets/LoginFailedScreen-CN0dJV6U-By3Si77a.js +0 -1
  113. package/site/dist/assets/ManualTransferScreen-CHqcuqbs-BAHPY8rs.js +0 -1
  114. package/site/dist/assets/MfaAuthEnrollmentFlowScreen-Dj7gPTRC-CLjffFG1.js +0 -1
  115. package/site/dist/assets/MfaAuthVerifyFlowScreen-DbFpyWMy-BmQg1Y61.js +0 -1
  116. package/site/dist/assets/MfaEnrollmentFlowScreen-DhLnGgnr-DmBtTtlw.js +0 -1
  117. package/site/dist/assets/MfaVerifyFlowScreen-C_XPjMRP-4a683Bmf.js +0 -1
  118. package/site/dist/assets/ModalFooter-BCuEK_nz-C_71qsaz.js +0 -403
  119. package/site/dist/assets/MoonpayStatusScreen-CzTUtokc-DEHHcEf8.js +0 -16
  120. package/site/dist/assets/OAuthStatusScreen-ufbOa-da-BjAb_WsY.js +0 -1
  121. package/site/dist/assets/OpenLink-CUpJ1mOr-BLWFsPOW.js +0 -39
  122. package/site/dist/assets/PasskeySelectSignupOrLogin-xh2ScC_l-2PJTJxX-.js +0 -1
  123. package/site/dist/assets/PasskeyStatusScreen-Bla8MaOk-DCEg761l.js +0 -6
  124. package/site/dist/assets/PasswordRecoveryScreen-awGXZkQk-8nVjNc8k.js +0 -20
  125. package/site/dist/assets/PhArrowCircleDown-D97G-BOA.js +0 -14
  126. package/site/dist/assets/PhArrowClockwise-DVgyFuez.js +0 -14
  127. package/site/dist/assets/PhArrowDown-87b1BizM.js +0 -14
  128. package/site/dist/assets/PhArrowLeft-Da0MfnbK.js +0 -14
  129. package/site/dist/assets/PhArrowRight-AdrIL4DW.js +0 -14
  130. package/site/dist/assets/PhArrowSquareOut-Bk2CMG3I.js +0 -14
  131. package/site/dist/assets/PhArrowUp-4sgyKPGx.js +0 -14
  132. package/site/dist/assets/PhArrowUpRight-BNghD2zq.js +0 -14
  133. package/site/dist/assets/PhArrowsClockwise-BXRn1GEl.js +0 -14
  134. package/site/dist/assets/PhArrowsDownUp-DM09Jo_3.js +0 -14
  135. package/site/dist/assets/PhArrowsLeftRight-MuKBLUoF.js +0 -14
  136. package/site/dist/assets/PhBank-fHbgCqqZ.js +0 -14
  137. package/site/dist/assets/PhBrowser-2-9m-AkM.js +0 -14
  138. package/site/dist/assets/PhCaretDown-BDNabFuL.js +0 -14
  139. package/site/dist/assets/PhCaretLeft-CI460R-A.js +0 -14
  140. package/site/dist/assets/PhCaretRight-D7ZS3Ls8.js +0 -14
  141. package/site/dist/assets/PhCaretUp-C-iIfyvO.js +0 -14
  142. package/site/dist/assets/PhCheck-DxQXn57r.js +0 -14
  143. package/site/dist/assets/PhCircleHalf-MJZeRBGF.js +0 -14
  144. package/site/dist/assets/PhClock-x3MS3Mwi.js +0 -14
  145. package/site/dist/assets/PhCompass-hYn3HhGN.js +0 -14
  146. package/site/dist/assets/PhCopy-CWNYcABI.js +0 -14
  147. package/site/dist/assets/PhCreditCard-Fy4pzwtt.js +0 -14
  148. package/site/dist/assets/PhCurrencyDollar-BgiaenCi.js +0 -14
  149. package/site/dist/assets/PhDesktop-CxrzCSv5.js +0 -14
  150. package/site/dist/assets/PhDeviceMobile-CsyA5Goi.js +0 -14
  151. package/site/dist/assets/PhDotsThree-D-k1BiYg.js +0 -14
  152. package/site/dist/assets/PhEnvelope-D8kzeNox.js +0 -14
  153. package/site/dist/assets/PhFunnelSimple-CfQs5DAW.js +0 -14
  154. package/site/dist/assets/PhGlobe-DvmSc9_A.js +0 -14
  155. package/site/dist/assets/PhIdentificationCard-CfkZm-5K.js +0 -14
  156. package/site/dist/assets/PhImage-Cf0J2ZfJ.js +0 -14
  157. package/site/dist/assets/PhInfo-Bwi51-2X.js +0 -14
  158. package/site/dist/assets/PhLightbulb-C4FZV7Es.js +0 -14
  159. package/site/dist/assets/PhMagnifyingGlass-C9u8jSCJ.js +0 -14
  160. package/site/dist/assets/PhPaperPlaneRight-zxQVvPLX.js +0 -14
  161. package/site/dist/assets/PhPlus-C4UoW-2n.js +0 -14
  162. package/site/dist/assets/PhPower-CFyWDmgD.js +0 -14
  163. package/site/dist/assets/PhPuzzlePiece-BjoNVfST.js +0 -14
  164. package/site/dist/assets/PhQrCode-CP3tFauY.js +0 -14
  165. package/site/dist/assets/PhQuestion-wOWJBd3G.js +0 -14
  166. package/site/dist/assets/PhQuestionMark-CV0k9C0Y.js +0 -14
  167. package/site/dist/assets/PhSealCheck-vIKzREuN.js +0 -14
  168. package/site/dist/assets/PhSignOut-CxELV-i0.js +0 -14
  169. package/site/dist/assets/PhSpinner-CGDmbzUd.js +0 -14
  170. package/site/dist/assets/PhTrash-C9PD0SAt.js +0 -14
  171. package/site/dist/assets/PhUser-0sWNXQeZ.js +0 -14
  172. package/site/dist/assets/PhVault-6JTskUcC.js +0 -14
  173. package/site/dist/assets/PhWarning-CoKs3gz6.js +0 -14
  174. package/site/dist/assets/PhWarningCircle-BXHw1fCt.js +0 -14
  175. package/site/dist/assets/PhX-DX0SFIqC.js +0 -14
  176. package/site/dist/assets/PhoneIcon-CZ47md80.js +0 -1
  177. package/site/dist/assets/PinInput-DbZ0b1i1-BrTXu3Km.js +0 -247
  178. package/site/dist/assets/PrivyPluginContext-DA-yS5vO-CY60yubk.js +0 -1
  179. package/site/dist/assets/QrCode-DbnZwlkH-CcMrWV5D.js +0 -22
  180. package/site/dist/assets/RecoveryOAuthStatusScreen-CsgtTI2x-DgeDnHOK.js +0 -1
  181. package/site/dist/assets/RecoveryPasswordCreateScreen-Be-qJ-LU-CvJJwLhK.js +0 -1
  182. package/site/dist/assets/RecoverySelectionScreen-D1iGtpmi-CADtGtSV.js +0 -7
  183. package/site/dist/assets/SIWXUtil-Cpiv91-M.js +0 -1
  184. package/site/dist/assets/Screen-D30meWsY-vEusJ7KX.js +0 -175
  185. package/site/dist/assets/ScreenHeader-CHmc4-Lu-BvGTLg53.js +0 -31
  186. package/site/dist/assets/ScreenLayout-OwaQQcxH-cfOTmyl0.js +0 -1
  187. package/site/dist/assets/SelectSourceAsset-CWFog6gm-DxYklZYs.js +0 -140
  188. package/site/dist/assets/SetAutomaticRecoveryScreen-BO_OEqAZ-oeqeo05x.js +0 -14
  189. package/site/dist/assets/SetWalletPasswordForm-DwCTVtAh-BxFee9UA.js +0 -11
  190. package/site/dist/assets/ShieldCheckIcon-eFjsosCA.js +0 -1
  191. package/site/dist/assets/SignRequestScreen-BHKv32KR-z8ixfIma.js +0 -26
  192. package/site/dist/assets/SolanaWalletConnector-C7Lqk-Wz-Bc_lvdiF.js +0 -1
  193. package/site/dist/assets/StackedContainer-B2vaEl56-Do5dXB8-.js +0 -24
  194. package/site/dist/assets/StandardSignAndSendTransactionScreen-CfhIXVtd-DudetZAc.js +0 -20
  195. package/site/dist/assets/Subtitle-CV-2yKE4-DV0o1av3.js +0 -13
  196. package/site/dist/assets/TelegramAuthScreen-UL9zLfO8-Bo3sONvf.js +0 -1
  197. package/site/dist/assets/Title-BnzYV3Is-C3qOPnO_.js +0 -7
  198. package/site/dist/assets/TodoList-DnyULl18-BvxvubbF.js +0 -36
  199. package/site/dist/assets/TransactionDetails-e9nesl30-WWflkOIi.js +0 -109
  200. package/site/dist/assets/TransactionErrorView-BDBNqA_f-CotYKcgf.js +0 -178
  201. package/site/dist/assets/TransferFromWalletScreen-Qvn1Yuno-sVFlWqa8.js +0 -31
  202. package/site/dist/assets/TransferOrBridgeLoadingScreen-Dl1I_iIz-CV2-mY19.js +0 -6
  203. package/site/dist/assets/TurnstileWrapper-B7sNddys-oPbuMTQt.js +0 -1
  204. package/site/dist/assets/UpdateEmailScreen-DVOhPV0H-D_mjSpAy.js +0 -1
  205. package/site/dist/assets/UpdatePhoneScreen-CXRswTNZ-6RLG0ahv.js +0 -1
  206. package/site/dist/assets/UserLimitReachedScreen-5we2DHn8-CaVN0bti.js +0 -1
  207. package/site/dist/assets/Value-DTgR824E-Bp6WGZZm.js +0 -27
  208. package/site/dist/assets/WalletCards-DH1rqayz-D3cOTb9x.js +0 -1
  209. package/site/dist/assets/WalletIcon-DOIZhb2l.js +0 -1
  210. package/site/dist/assets/WalletInfoCard-sq_2wgFg-bOqfl5MA.js +0 -29
  211. package/site/dist/assets/WalletInterstitialScreen-Sh7LfsAh-DWaVkMCA.js +0 -1
  212. package/site/dist/assets/WalletLink-DK5LB5Ha-DjUZvqs-.js +0 -20
  213. package/site/dist/assets/WalletOverflowButton-bfCnp-9T-B6P7DQXA.js +0 -1
  214. package/site/dist/assets/WarningBanner-ZZqCEtZK-Bc2YKZb7.js +0 -21
  215. package/site/dist/assets/__vite-browser-external-NciY4Vu1.js +0 -1
  216. package/site/dist/assets/_esm-BZP3cXTv.js +0 -1
  217. package/site/dist/assets/_esm-ZmzBJpEs.js +0 -6
  218. package/site/dist/assets/accounts-D4AXF17G.js +0 -1
  219. package/site/dist/assets/accounts-IkdFx9nq.js +0 -1
  220. package/site/dist/assets/analytics-mkkvFRju-BRBcadka.js +0 -1
  221. package/site/dist/assets/baseSepolia-Bn1j1Ysw.js +0 -1
  222. package/site/dist/assets/basic-Cz1YiCSk.js +0 -1575
  223. package/site/dist/assets/capitalizeFirstLetter-DmLYqXsO-DtOXx-2n.js +0 -1
  224. package/site/dist/assets/ccip-B1J3oTZx.js +0 -7
  225. package/site/dist/assets/ccip-Bd7261zy.js +0 -1
  226. package/site/dist/assets/ccip-D6EHKpjI.js +0 -1
  227. package/site/dist/assets/chain-scefkYIH.js +0 -5
  228. package/site/dist/assets/check-DHnllI8N.js +0 -1
  229. package/site/dist/assets/chevron-down-1y-r4bGO.js +0 -1
  230. package/site/dist/assets/circle-alert-OwxamXUX.js +0 -1
  231. package/site/dist/assets/circle-check-big-3sZGVC10.js +0 -1
  232. package/site/dist/assets/circle-x-DQjNUV9J.js +0 -1
  233. package/site/dist/assets/client-B-BpRtwY.js +0 -1
  234. package/site/dist/assets/client-BfIR1FvH.js +0 -9
  235. package/site/dist/assets/coinbase-DCPC2L_R-YEuIn3jh.js +0 -3
  236. package/site/dist/assets/copy-DMOX_yn7.js +0 -1
  237. package/site/dist/assets/core-GIEmxmAa.js +0 -12
  238. package/site/dist/assets/createLucideIcon-BsERoFTA.js +0 -1
  239. package/site/dist/assets/defineChain-B-SQDLTG.js +0 -1
  240. package/site/dist/assets/dijkstra-CY95afk4.js +0 -1
  241. package/site/dist/assets/dist-CoS6E-3x.js +0 -1
  242. package/site/dist/assets/dist-Cp6IXeJd.js +0 -1
  243. package/site/dist/assets/dist-CrNxmdZj.js +0 -1
  244. package/site/dist/assets/dist-DYGtf4F0.js +0 -16
  245. package/site/dist/assets/embed.worker-Cnl4VGr3.js +0 -1
  246. package/site/dist/assets/errors-BQ3N-RVt-CZPAWCgC.js +0 -1
  247. package/site/dist/assets/esm-C3DslVJx.js +0 -1
  248. package/site/dist/assets/esm-Ci1ogKuK.js +0 -1
  249. package/site/dist/assets/esm-Co94ENUV.js +0 -37
  250. package/site/dist/assets/esm-bb8SANZh.js +0 -1
  251. package/site/dist/assets/esm-n2lWo7kz.js +0 -1
  252. package/site/dist/assets/ethers-zTjbodk5-Cs648CnD.js +0 -1
  253. package/site/dist/assets/eventemitter3-Dzc_0Uq2.js +0 -1
  254. package/site/dist/assets/external-link-BBPG3rG7.js +0 -1
  255. package/site/dist/assets/farcaster-DPlSjvF5-CzBCmgyL.js +0 -1
  256. package/site/dist/assets/features-CuiHbgEJ.js +0 -2
  257. package/site/dist/assets/fingerprint-pattern-CjLtyIxB.js +0 -1
  258. package/site/dist/assets/floating-ui.react-DmaCfayr.js +0 -1
  259. package/site/dist/assets/floating-ui.react-dom-CaoMF8mP.js +0 -1
  260. package/site/dist/assets/formatErc20TokenAmount-BuPk9xcy-BVqOKQif.js +0 -1
  261. package/site/dist/assets/formatter-CVbp4SlJ.js +0 -1
  262. package/site/dist/assets/get-entropy-details-for-user-dHRH5Gpi-BNiOFXbK.js +0 -1
  263. package/site/dist/assets/getChainName-DjpPdUSc-DUyKDrnp.js +0 -1
  264. package/site/dist/assets/getErc20Balance-jXNpFf0N-q4bV8kQq.js +0 -1
  265. package/site/dist/assets/getErc20TokenInfo-BNTrzLMP-DcnTFrXN.js +0 -1
  266. package/site/dist/assets/getFormattedUsdFromLamports-De3U9GlO-Gx4_wxON.js +0 -1
  267. package/site/dist/assets/getUsdcMintAddress-BVgm9GRo-r2UnDeEy.js +0 -1
  268. package/site/dist/assets/guards-BIW-GiMJ-D3tWTQAR.js +0 -1
  269. package/site/dist/assets/hooks.module-BYZGfwDl.js +0 -1
  270. package/site/dist/assets/hourglass-D_GfQdJr.js +0 -1
  271. package/site/dist/assets/id-UqEwL2BI-CTlvRMA5.js +0 -1
  272. package/site/dist/assets/index-BdEw5wyl-kLbhqLbv.js +0 -1
  273. package/site/dist/assets/index-C2h3EneX.js +0 -8
  274. package/site/dist/assets/index-CWARkn2w-DxuckIFn.js +0 -61
  275. package/site/dist/assets/index-DvMddJcd-CNBb4VUn.js +0 -12
  276. package/site/dist/assets/index-YiCw0Y_x-Czl0tWfF.js +0 -436
  277. package/site/dist/assets/index.browser-B-i-Lj3J.js +0 -1
  278. package/site/dist/assets/index.browser-BMmUcykv.js +0 -1
  279. package/site/dist/assets/index.es-Crd0TyUS.js +0 -2
  280. package/site/dist/assets/index.es-DQbup1WG.js +0 -3
  281. package/site/dist/assets/isPaymentRequestAvailable-Bq1cemEn-Cjf3l05I.js +0 -1
  282. package/site/dist/assets/jsx-runtime-CndCZlyt.js +0 -1
  283. package/site/dist/assets/landmark-BZPqMFEf.js +0 -1
  284. package/site/dist/assets/lib-DGtzi2DK.js +0 -1
  285. package/site/dist/assets/localBatchGatewayRequest-B71J8sQ9.js +0 -29
  286. package/site/dist/assets/lock-DyN_c-ud.js +0 -1
  287. package/site/dist/assets/logger-CsXm3oIv-B0UYpNFI.js +0 -1
  288. package/site/dist/assets/mail-V_mcPb64.js +0 -1
  289. package/site/dist/assets/modal-context-CwmSeFcv-DPWNJrfK.js +0 -1
  290. package/site/dist/assets/native-BKj27wfc.js +0 -1
  291. package/site/dist/assets/ofetch.CWycOUEr-Duby3iHT.js +0 -1
  292. package/site/dist/assets/parseAbi-Csg-8t59.js +0 -2
  293. package/site/dist/assets/parseAbiParameters-BWbIMYe4.js +0 -1
  294. package/site/dist/assets/parseEther-CHS0GNkM.js +0 -1
  295. package/site/dist/assets/pkce-DXF0yKjP-7_VY-d83.js +0 -1
  296. package/site/dist/assets/preload-helper-CRzLMJPS.js +0 -1
  297. package/site/dist/assets/privy-context-81KmsBw2-DiR6-Yq8.js +0 -2
  298. package/site/dist/assets/property-DuZ9DLP4.js +0 -3
  299. package/site/dist/assets/react-BfYA1q0G.js +0 -1
  300. package/site/dist/assets/react-CK2HRI1t.js +0 -1
  301. package/site/dist/assets/react-DxGeY8B8.js +0 -1
  302. package/site/dist/assets/react-auth-sHnipzbS.js +0 -1
  303. package/site/dist/assets/react-dom-BI2Koe4o.js +0 -1
  304. package/site/dist/assets/reservoir-x-nGuZkT-BguVFH3D.js +0 -1
  305. package/site/dist/assets/rolldown-runtime-Bjp52BOI.js +0 -1
  306. package/site/dist/assets/safe-url-D7SRPu33-CkMedGro.js +0 -1
  307. package/site/dist/assets/secp256k1-BeYL8aZl.js +0 -1
  308. package/site/dist/assets/secp256k1-Bh4MSkhz.js +0 -1
  309. package/site/dist/assets/secp256k1-Ci4cqHl0.js +0 -1
  310. package/site/dist/assets/secp256k1-DsxXqbXv.js +0 -1
  311. package/site/dist/assets/secp256k1-N9RUmafO.js +0 -1
  312. package/site/dist/assets/serializeAuthorizationList-DGMik9Wx.js +0 -1
  313. package/site/dist/assets/serializeSignature-CWKpxoab.js +0 -2
  314. package/site/dist/assets/sha2-C6s7zZmb.js +0 -1
  315. package/site/dist/assets/shared-DRZUmnL5-BewE5wK8.js +0 -193
  316. package/site/dist/assets/shared-FM0rljBt-CxLACri_.js +0 -21
  317. package/site/dist/assets/shouldProceedtoEmbeddedWalletCreationFlow-B3vyavrh-BkAKsRNi.js +0 -1
  318. package/site/dist/assets/simulateTransaction-CfuOrae4-PjA_tGjX.js +0 -1
  319. package/site/dist/assets/smartphone-3p8nFSTE.js +0 -1
  320. package/site/dist/assets/src-BBjkfi-Q.js +0 -1
  321. package/site/dist/assets/storage-Ufl800Ne-1qCR2JMO.js +0 -8
  322. package/site/dist/assets/stringify-DqUj9VzO.js +0 -20
  323. package/site/dist/assets/stripe.esm-DEi2D291.js +0 -2
  324. package/site/dist/assets/styles-BsotlekN-Aq27nZGc.js +0 -9
  325. package/site/dist/assets/styles-Cm9H63WO-p_ic_dxE.js +0 -92
  326. package/site/dist/assets/styles-DVyDvTdj-CjsWqAld.js +0 -74
  327. package/site/dist/assets/styles-hslXy6xG-D5QHIrJU.js +0 -164
  328. package/site/dist/assets/telegram-B-JqnkqZ-Dqkm4rDn.js +0 -1
  329. package/site/dist/assets/to-ui-error-DWe-qwS3-Bi1HUQI9.js +0 -4
  330. package/site/dist/assets/toViemAccount-B5iDGq1b-CdFZwv07.js +0 -2
  331. package/site/dist/assets/transaction-BNTP-bFm-M5tJx8vY.js +0 -1
  332. package/site/dist/assets/transfer-6YztDh-t-BVQkTvG-.js +0 -1
  333. package/site/dist/assets/triangle-alert-BG3Qw-Hq.js +0 -1
  334. package/site/dist/assets/twitch-5IOe4sIQ-CaXHFlAu.js +0 -1
  335. package/site/dist/assets/types-4CuOD23i.js +0 -1
  336. package/site/dist/assets/use-headless-crypto-deposit-Yn-QRLB3-CQqeyhGY.js +0 -1
  337. package/site/dist/assets/use-unlink-wallet-BKK4_5Ur-C0xQN_Lp.js +0 -1
  338. package/site/dist/assets/useActiveWallet-B-yZYGc6-BkK6rFBm.js +0 -123
  339. package/site/dist/assets/useGetSolPrice-x7gfUIHJ-CLVXqv6I.js +0 -1
  340. package/site/dist/assets/useGetTokenPrice-CaoUkb79-DAXXs3rs.js +0 -1
  341. package/site/dist/assets/useI18n-C-5n_noF-CGpkgIVy.js +0 -1
  342. package/site/dist/assets/useWalletBalance-CZnP5f_f-Cfd64m4n.js +0 -1
  343. package/site/dist/assets/useWallets-X1ndvJ_U-CAYqg7Za.js +0 -1
  344. package/site/dist/assets/utils-BkR7EQjg.js +0 -1
  345. package/site/dist/assets/utils-DJhBNOZA.js +0 -1
  346. package/site/dist/assets/utils-DdckVfQr.js +0 -1
  347. package/site/dist/assets/w3m-modal-B4H_uiPH.js +0 -1174
  348. package/site/dist/assets/wallet-BNI12IYk.js +0 -1
  349. package/site/dist/assets/wallet-CITSaPlB.js +0 -2
  350. package/site/dist/assets/wallet-connect-BvJtUsDt-CVlSNIXP.js +0 -1
  351. package/site/dist/assets/walletconnect-registry-xxG8XLCb-BpzpBq5s.js +0 -1
  352. package/site/dist/assets/walletconnect-v2-3frQFwbg-CzZ6oybX.js +0 -12
  353. package/site/dist/assets/wui-ux-by-reown-XyHdtV1O.js +0 -1147
  354. package/site/dist/assets/x-gjDrY2rk.js +0 -1
  355. package/src/discover/explore.js +0 -291
  356. package/src/market/settle.js +0 -165
  357. package/src/market/terms.js +0 -200
  358. package/src/publish/cli.js +0 -336
  359. package/src/publish/envelope.js +0 -185
  360. 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/<item id> with CONFIG.paid: 402 + the price (x402 v2); with a valid
17
- // PAYMENT-SIGNATURE header, the facilitator verifies and settles it, then
18
- // the record's KEY is returned
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 paid records are not here. Each is already in the app's committed rows on IPFS,
25
- // encrypted (`paid_ct`, AES-256-GCM), with the sha256 of its plaintext beside it. This worker
26
- // sells only the key, HMAC-SHA256(PAID_SECRET, id): the buyer holds the ciphertext before
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 and IPFS_GATEWAY
31
- // (vars) and PAID_SECRET (a secret).
26
+ // description, fromBlock, schema, repos, paid, facilitator, deployment), and optionally RPC_URL
27
+ // and IPFS_GATEWAY (vars).
32
28
 
33
- import { Fangorn, FangornConfig, SupportedNetworks, repoExcluded } from "@fangorn-network/sdk";
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 appRegistryFromBlock as a string),
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.appRegistryFromBlock ? { appRegistryFromBlock: BigInt(d.appRegistryFromBlock) } : {}),
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, appId: CONFIG.app,
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
- * What the app presents now, kept across runs in R2: each run reads only the updates since
89
- * the last, then asks which repos the app presents: its members' (`presents`), less what it
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().appRegistryFromBlock) : BigInt(state.block) + 1n;
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
- const all = Object.values(state.repos);
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, request = null) {
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), name = msg.params?.name;
188
- const run = async () => {
189
- try { return await t.call(name, msg.params?.arguments ?? {}); }
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, request));
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
- /** A record's key: HMAC-SHA256(PAID_SECRET, id), hex. The same derivation as the sources that
237
- * encrypt (and x402.js's paidKey). */
238
- async function keyFor(env, id) {
239
- if (!env.PAID_SECRET) throw new Error("PAID_SECRET is not set (wrangler pages secret put PAID_SECRET)");
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
- /** The PaymentPayload the facilitator sees: the buyer's signed `payload`, the rest ours. */
313
- const payloadOf = (sale, p, amount) => ({ x402Version: 2, resource: sale.resource, accepted: requirement(sale.P, amount),
314
- payload: p?.payload, ...(sale.bazaar ? { extensions: { bazaar: sale.bazaar } } : {}) });
315
-
316
- async function facilitator(P, path, paymentPayload) {
317
- // A network failure is an answer too: thrown, it would turn a half-settled batch into a 500.
318
- let r;
319
- try {
320
- r = await fetch(`${P.facilitator}/${path}`, {
321
- method: "POST", headers: { "content-type": "application/json" },
322
- body: JSON.stringify({ x402Version: 2, paymentPayload, paymentRequirements: paymentPayload.accepted }),
323
- });
324
- } catch (e) { return { error: `facilitator ${path}: ${e.message}` }; }
325
- const text = await r.text();
326
- try { return JSON.parse(text); } catch { return { error: `facilitator ${path}: HTTP ${r.status} ${text.slice(0, 200)}` }; }
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
- const signatures = (request) => {
330
- const header = request.headers.get("PAYMENT-SIGNATURE");
331
- if (!header) return { error: "PAYMENT-SIGNATURE header is required" };
332
- try { return { payments: [JSON.parse(atob(header))].flat() }; } catch { return { error: "PAYMENT-SIGNATURE is not base64 JSON" }; }
333
- };
334
-
335
- /** One payment for `amount`, verified and settled: `{settled, payer}`, or `{refused}`, the 402 to answer with. */
336
- async function payOnce(request, sale, amount) {
337
- const { payments, error } = signatures(request);
338
- if (error) return { refused: quote(sale, amount, error) };
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
- const id = decodeURIComponent(url.pathname.slice("/paid/".length));
350
- if (!id) return json({ error: "no id" }, 400);
351
- const { settled, refused } = await payOnce(request, keySale(url, false), CONFIG.paid.price);
352
- if (refused) return refused;
353
- return json({ id, key: await keyFor(env, id) }, 200, { "payment-response": b64json(settled) });
354
- }
355
-
356
- // POST /paid {ids}: many keys, one price. 402 with the total; then PAYMENT-SIGNATURE may be one
357
- // payment or a JSON array of them (an agent's vouchers) that add up to it. The only subrequests
358
- // are the facilitator's (2 per payment, 50 on the free plan), hence MAX_PAYMENTS; keys cost nothing.
359
- async function sellBatch(request, env, url) {
360
- const P = CONFIG.paid, sale = keySale(url, true);
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 pr = paymentRequired(sale, X.call, error);
453
- return { isError: true, structuredContent: pr, content: [{ type: "text", text: JSON.stringify(pr) }] };
269
+ const r = { x402Version: 2, error, resource, accepts: [q.requirement] };
270
+ return json({ ...r, ...q.body }, 402, { "payment-required": b64json(r) });
454
271
  };
455
- if (!payment) return required(`${tool.name} is paid: one x402 payment per call in _meta["x402/payment"], or a pass from POST ${origin}/pass`);
456
- const p = payloadOf(sale, payment, X.call);
457
- const verified = await facilitator(X, "verify", p);
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 result = await run();
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 { ...result, _meta: { ...result._meta, "x402/payment-response": settled } };
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", "/openapi.json", "/.well-known/x402"]);
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, { appId: APP, owner: OWNER });
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 private app: its rows sealed on chain, opened by the indexer, served only with a pass or a paid call.
579
- const { sealRow, openRow } = await import("../src/agent/x402.js");
580
- const SECRET = "s3cret";
581
- chain[`${PUB}/vault`] = { records: [rec("v1", TAG, await sealRow(SECRET, { sku: "v1", name: "Velvet chair", about: "a green velvet armchair" }))] };
582
- const sealedAgain = await sealRow(SECRET, { sku: "v1", name: "Velvet chair", about: "a green velvet armchair" });
583
- eq(sealedAgain, chain[`${PUB}/vault`].records[0].payload, "an unchanged row seals to the same bytes (the same CID: not embedded again)");
584
- if (JSON.stringify(sealedAgain).includes("Velvet")) throw new Error("a sealed row must not carry its plaintext");
585
- await publishView({ fangorn, app: APP, schema: CONFIG.schema, store: r2Store(VIEW), embedBatch: encode(env, DOCUMENT), embedder: WORKER_EMBEDDER, log: () => {},
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, init) => {
593
- const { paymentPayload: p } = JSON.parse(init.body);
594
- if (u === "https://fac/verify") return new Response(JSON.stringify({ isValid: true, payer: p.payload.authorization.from }));
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 payment = { x402Version: 2, payload: { signature: "0x", authorization: { from: "0xbuyer", to: CONFIG.private.payTo, value: "1", nonce: "0x1" } } };
600
- const shard = [...objs.keys()].find((k) => k.startsWith("view/cdn/domains/") && k.endsWith("/manifest")).slice("view/".length);
601
- const get = (path, headers = {}) => worker.fetch(new Request(`https://shop.test/${path}`, { headers }), env);
602
- eq([(await get("view/cdn/catalog")).status, (await get(`view/${shard}`)).status], [200, 401], "the catalog is free, the rest of the view is not");
603
- const asMcp = async (params, headers = {}) => (await (await worker.fetch(new Request("https://shop.test/mcp", { method: "POST", headers,
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
- CONFIG = env.CONFIG = { ...env.CONFIG, private: null };
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, membership and exclusions applied every run.
632
- const STRANGER = "0x" + "9".repeat(40);
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 excluded = null, block = 20n;
637
- const r = { chain: { getBlockNumber: async () => block }, fangorn: { getAppId: () => APP, getRegistry: () => ({
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, and keeps members only");
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
- excluded = OWNER;
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, and the indexer scans only new updates and applies membership and exclusions every run");
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
  }