@fangorn-network/westmarch 0.0.19-dev → 0.1.0
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 +35 -5
- package/docs/app-to-agent.md +113 -620
- package/package.json +18 -18
- 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-CkP1WhkV.js +8 -0
- package/site/dist/assets/roles-BDPDWrwo.js +1 -0
- package/site/dist/assets/rolldown-runtime-COiEfhsA.js +1 -0
- package/site/dist/assets/transformers.web-CUZUFjAK.js +49 -0
- package/site/dist/assets/transformers.web-CwR_IYQV.js +40 -0
- package/site/dist/index.html +6 -7
- package/site/feed.js +16 -1
- package/site/no-model.js +4 -0
- package/site/worker.js +382 -59
- package/src/agent/agent-card.js +51 -37
- package/src/agent/mcp.js +173 -500
- package/src/agent/tools.js +3 -2
- package/src/agent/ui.js +1 -1
- package/src/agent/view-tools.js +11 -30
- package/src/agent/x402.js +194 -85
- package/src/core/embed.js +56 -53
- package/src/core/embed.worker.js +1 -1
- package/src/core/rank.js +1 -1
- package/src/core/roles.js +7 -7
- package/src/core/shard.js +12 -8
- package/src/discover/apps.js +103 -67
- package/src/discover/corpora.js +1 -1
- package/src/discover/directory.js +3 -3
- package/src/market/cohort.js +1 -1
- package/src/market/demand.js +1 -1
- package/src/market/reactions.js +24 -18
- package/src/publish/devnet.js +146 -0
- package/src/publish/graph.js +1 -1
- package/src/publish/init.js +74 -0
- package/src/publish/lint.js +3 -2
- package/src/publish/ship.js +433 -287
- package/src/publish/view-cli.js +14 -10
- package/src/publish/view.js +485 -255
- package/src/taste/steam.js +1 -1
- package/src/taste/taste-doc.js +4 -3
- package/src/taste/taste.js +3 -3
- package/src/taste/vault.js +4 -2
- package/template/README.md +48 -0
- package/template/app.json +27 -0
- package/template/eval/goals.md +7 -0
- package/template/eval/golden.jsonl +0 -0
- package/template/gitignore +3 -0
- package/template/package.json +13 -0
- package/template/wrangler.json +12 -0
- package/plugin/scripts/fx.mjs +0 -268
- package/site/dist/assets/AccountNotFoundScreen-C4njXc69-BOfmiSH1.js +0 -1
- package/site/dist/assets/AddFundsSelectionScreen-RAoCn7ej-B6uvYyqa.js +0 -31
- package/site/dist/assets/Address-H8kD1d9O-DcHv7A8C.js +0 -17
- package/site/dist/assets/AffirmativeConsentScreen-BG0MJxku-v2wz-N-s.js +0 -6
- package/site/dist/assets/AllowlistRejectionScreen-Cae6e1Od-DhVy9iGN.js +0 -1
- package/site/dist/assets/ApiController-BbhXeqTV.js +0 -5
- package/site/dist/assets/ArrowsRightLeftIcon-cp60yobq.js +0 -1
- package/site/dist/assets/AuthenticateWithWalletScreen-CU6hIcFT-B0eT1zE6.js +0 -1
- package/site/dist/assets/AwaitingEvmToSolBridgingScreen-D0e_sZMS-DL5PH-v_.js +0 -1
- package/site/dist/assets/AwaitingExternalEthereumTransferScreen-ur5HL4R7-n1BsRPqc.js +0 -1
- package/site/dist/assets/AwaitingPasswordlessCodeScreen-DRHz76mM-BVl2BJDY.js +0 -103
- package/site/dist/assets/AwaitingSolToEvmBridgingScreen-CdLRrGD2-BD-OZUQb.js +0 -1
- package/site/dist/assets/BridgeNetworkSelectionView-CFGjB2nO-CaYrzRje.js +0 -68
- package/site/dist/assets/CaptchaScreen-DbpvkIGx-BQJ7inx1.js +0 -1
- package/site/dist/assets/CheckCircleIcon-BM409mGI.js +0 -1
- package/site/dist/assets/CheckCircleIcon-NCOQc0Gs.js +0 -1
- package/site/dist/assets/Checkbox-D1EDeo41-rHlvsZzP.js +0 -52
- package/site/dist/assets/ChevronDownIcon-CW-4MSpE.js +0 -1
- package/site/dist/assets/Chip-CZKIKt9K-CutJG010.js +0 -26
- package/site/dist/assets/CoinbaseOnrampStatusScreen-BbyCJDXL-DDaMC7cl.js +0 -16
- package/site/dist/assets/ConnectEmailForm-B-VRE0MN-DVSf4tFJ.js +0 -3
- package/site/dist/assets/ConnectLedgerScreen-DWHABGot-h0uYIUi6.js +0 -10
- package/site/dist/assets/ConnectOnlyLandingScreen-CZYx9Bmp-DP19hJq5.js +0 -1
- package/site/dist/assets/ConnectOnlyStatusScreen-DCRzjxHx-DHvijlTx.js +0 -1
- package/site/dist/assets/ConnectOrCreateScreen-CUUYUO07-ewmXYPTQ.js +0 -1
- package/site/dist/assets/ConnectPhoneForm-Dek5xn4C-DUYdmVtr.js +0 -95
- package/site/dist/assets/ConnectWalletView-DqI-PTvX-RobGErVW.js +0 -74
- package/site/dist/assets/ConnectionStatusScreen-fUfROm9m-Bbiq3hY6.js +0 -1
- package/site/dist/assets/ConnectionStatusScreen-fUfROm9m-DPkOGNWl.js +0 -7
- package/site/dist/assets/ConnectorUtil-BO1L6VvV.js +0 -21
- package/site/dist/assets/ConstantsUtil-DmLsRHAl.js +0 -198
- package/site/dist/assets/CopyToClipboard-i_OQSBJr-DujUHNzW.js +0 -39
- package/site/dist/assets/CopyableText-CQapvaMr-DMIdpDaU.js +0 -28
- package/site/dist/assets/CrossAppAuthScreen-CLw7ymZq-DbGy4QyA.js +0 -1
- package/site/dist/assets/CustomLandingScreenView-Cm_MBSyX-B3WqJZBh.js +0 -22
- package/site/dist/assets/DelegatedActionsConsentScreen-DYF2U4y5-CmUDoS0C.js +0 -1
- package/site/dist/assets/DelegatedActionsRevokeScreen-bPp6Uwyh-fcO51q5H.js +0 -1
- package/site/dist/assets/DepositAddressAddressStepView-4vdkb55L-ClAb_j1B.js +0 -187
- package/site/dist/assets/DepositAddressScreen-DBLRs7aI-CN1P_KY9.js +0 -8
- package/site/dist/assets/DepositAddressV3Screen-CDqIEMQw-BtDZmuEN.js +0 -1
- package/site/dist/assets/EmailInputForm-Cqo1mda8-B2NG3K5V.js +0 -124
- package/site/dist/assets/EmbeddedWalletConnectingScreen-D4T7DUzs-D3pExuaW.js +0 -1
- package/site/dist/assets/EmbeddedWalletCreatedScreen-D7WhW5ve-CLsJbPYQ.js +0 -1
- package/site/dist/assets/EmbeddedWalletKeyExportScreen-CqV4NYQ3-DI6bu-oi.js +0 -23
- package/site/dist/assets/EmbeddedWalletOnAccountCreateScreen-BoKb3Q2S-BQ9Ygr0P.js +0 -1
- package/site/dist/assets/EmbeddedWalletPasswordUpdateScreen-D3rMlqS7-CxrfLr5q.js +0 -1
- package/site/dist/assets/EmbeddedWalletPasswordUpdateSplashScreen-BPFkWJeE-DHZRGoTN.js +0 -1
- package/site/dist/assets/EnrollTotp-Dibew4dp-7gVtJ1Jt.js +0 -66
- package/site/dist/assets/EnvelopeIcon-Dh2vSVj4.js +0 -1
- package/site/dist/assets/ErrorBanner-BcpGRt0h-BKOYTyCi.js +0 -21
- package/site/dist/assets/ErrorMessage-D8VaAP5m-CrZOnUyg.js +0 -8
- package/site/dist/assets/ErrorScreen-DipdTWzE-BT8TtRTx.js +0 -4
- package/site/dist/assets/ErrorScreen-DipdTWzE-DAam41Xl.js +0 -1
- package/site/dist/assets/ExclamationCircleIcon-DUA2wMfO.js +0 -1
- package/site/dist/assets/ExclamationTriangleIcon-eZfaqcFq.js +0 -1
- package/site/dist/assets/FarcasterConnectStatusScreen-51tjUxqq-BPCBi37Q.js +0 -75
- package/site/dist/assets/FarcasterSignerStatusScreen-6x-Uh95G-XCmWpnUd.js +0 -28
- package/site/dist/assets/FiatOnrampScreen-BAhYeiTI-DJRcUh2N.js +0 -491
- package/site/dist/assets/FingerPrintIcon-Dm6kxmg7.js +0 -1
- package/site/dist/assets/FundSolWalletWithExternalSolanaWallet-WtLwYaOl-BfNVSTb1.js +0 -1
- package/site/dist/assets/FundWalletMethodHeader-BWNkokCT-BQdbJ5Bb.js +0 -1
- package/site/dist/assets/FundWithBankDepositScreen-DajkHxBD-BfvMcHb6.js +0 -58
- package/site/dist/assets/FundingEditAmountScreen-CpvpiO4M-VBfbahnA.js +0 -1
- package/site/dist/assets/FundingMethodSelectionScreen-Db7vhRlE-CMuBYf64.js +0 -1
- package/site/dist/assets/GlobeAltIcon-vqrVV5ho.js +0 -1
- package/site/dist/assets/GooglePay-B53WnudL-BX5_WtCx.js +0 -1
- package/site/dist/assets/HCaptchaWrapper-CUi9LJsp-D-I8j7NO.js +0 -4
- package/site/dist/assets/InAppBrowserLoginNotPossible-BMR9br1T-BY3j50El.js +0 -1
- package/site/dist/assets/InfoBanner-Cb3p1z12-DLBl_Ywy.js +0 -22
- package/site/dist/assets/InjectedWalletIcon-DLcYOGDj-DLSV_NjP.js +0 -1
- package/site/dist/assets/InstallWalletScreen-DTA8dwLv-CxreyXoD.js +0 -1
- package/site/dist/assets/JsonTree-BHzNC-ic-DcNcbSoM.js +0 -34
- package/site/dist/assets/LabelXs-oqZNqbm_-iafrj25N.js +0 -6
- package/site/dist/assets/LandingScreen-CGWUoSUG-Q7hgXKvM.js +0 -1
- package/site/dist/assets/Layouts-BMRfo5hw-CeCOyCLK.js +0 -77
- package/site/dist/assets/Link-BdDilT2T-BEDEdHuS.js +0 -40
- package/site/dist/assets/LinkConflictScreen-C3ZEPsgl-DK1z_v7y.js +0 -77
- package/site/dist/assets/LinkEmailScreen-dKo9frwc-BYYpJ5-6.js +0 -1
- package/site/dist/assets/LinkPasskeyScreen-zbrl_AI_-uhub9C26.js +0 -1
- package/site/dist/assets/LinkPasskeyScreen-zbrl_AI_-uid22VTN.js +0 -117
- package/site/dist/assets/LinkPhoneScreen-oIUpfj0w-CVQibNi9.js +0 -1
- package/site/dist/assets/Loader-UAZqxsnw-Cy7hEE2f.js +0 -62
- package/site/dist/assets/LoadingSkeleton-BMsgO5PV-CiEyS5PZ.js +0 -17
- package/site/dist/assets/LockClosedIcon-BgY_5v_Z.js +0 -1
- package/site/dist/assets/LoginFailedScreen-CN0dJV6U-DhUMiJZJ.js +0 -1
- package/site/dist/assets/ManualTransferScreen-CHqcuqbs-DMygAXMU.js +0 -1
- package/site/dist/assets/MfaAuthEnrollmentFlowScreen-Dj7gPTRC-_1GFUSIg.js +0 -1
- package/site/dist/assets/MfaAuthVerifyFlowScreen-DbFpyWMy-DLnBJ5b8.js +0 -1
- package/site/dist/assets/MfaEnrollmentFlowScreen-DhLnGgnr-BUJY1lzs.js +0 -1
- package/site/dist/assets/MfaVerifyFlowScreen-C_XPjMRP-25l5fVun.js +0 -1
- package/site/dist/assets/ModalFooter-BCuEK_nz-c-khvG4Y.js +0 -403
- package/site/dist/assets/MoonpayStatusScreen-CzTUtokc-BRFN2eXr.js +0 -16
- package/site/dist/assets/OAuthStatusScreen-ufbOa-da-DL30vu07.js +0 -1
- package/site/dist/assets/OpenLink-CUpJ1mOr-DfAvJRyK.js +0 -39
- package/site/dist/assets/PasskeySelectSignupOrLogin-xh2ScC_l-DtxhRQyw.js +0 -1
- package/site/dist/assets/PasskeyStatusScreen-Bla8MaOk-CNfu8ujT.js +0 -6
- package/site/dist/assets/PasswordRecoveryScreen-awGXZkQk-BR6z6xE8.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-CvIiPwYQ.js +0 -1
- package/site/dist/assets/PinInput-DbZ0b1i1-CDXZBdQx.js +0 -247
- package/site/dist/assets/PrivyPluginContext-DA-yS5vO-CtQWBiV2.js +0 -1
- package/site/dist/assets/QrCode-DbnZwlkH-BymHrKY7.js +0 -22
- package/site/dist/assets/RecoveryOAuthStatusScreen-CsgtTI2x-Dro1btv5.js +0 -1
- package/site/dist/assets/RecoveryPasswordCreateScreen-Be-qJ-LU-C1q1hiaN.js +0 -1
- package/site/dist/assets/RecoverySelectionScreen-D1iGtpmi-C21D20gD.js +0 -7
- package/site/dist/assets/SIWXUtil-CHZVugix.js +0 -1
- package/site/dist/assets/Screen-D30meWsY-CCeVNuWs.js +0 -175
- package/site/dist/assets/ScreenHeader-CHmc4-Lu-DjyrH7IK.js +0 -31
- package/site/dist/assets/ScreenLayout-OwaQQcxH-CrQM6kvA.js +0 -1
- package/site/dist/assets/SelectSourceAsset-CWFog6gm-RKjBsB6m.js +0 -140
- package/site/dist/assets/SetAutomaticRecoveryScreen-BO_OEqAZ-BcWHJoEG.js +0 -14
- package/site/dist/assets/SetWalletPasswordForm-DwCTVtAh-kq1zvA7d.js +0 -11
- package/site/dist/assets/ShieldCheckIcon-o6RHYev1.js +0 -1
- package/site/dist/assets/SignRequestScreen-BHKv32KR-iQCaBkcz.js +0 -26
- package/site/dist/assets/SolanaWalletConnector-C7Lqk-Wz-BUdJJ0ep.js +0 -1
- package/site/dist/assets/StackedContainer-B2vaEl56-eQCM2cmv.js +0 -24
- package/site/dist/assets/StandardSignAndSendTransactionScreen-CfhIXVtd-nART-xUU.js +0 -20
- package/site/dist/assets/Subtitle-CV-2yKE4-zxWw2m1Q.js +0 -13
- package/site/dist/assets/TelegramAuthScreen-UL9zLfO8-Dw9geyK3.js +0 -1
- package/site/dist/assets/Title-BnzYV3Is-B_JpTatT.js +0 -7
- package/site/dist/assets/TodoList-DnyULl18-C8Eyy28X.js +0 -36
- package/site/dist/assets/TransactionDetails-e9nesl30-DeEL1JQS.js +0 -109
- package/site/dist/assets/TransactionErrorView-BDBNqA_f-Dffuo7I4.js +0 -178
- package/site/dist/assets/TransferFromWalletScreen-Qvn1Yuno-CWLWUiHy.js +0 -31
- package/site/dist/assets/TransferOrBridgeLoadingScreen-Dl1I_iIz-Bo-tm6wr.js +0 -6
- package/site/dist/assets/TurnstileWrapper-B7sNddys-Jwy1tJtm.js +0 -1
- package/site/dist/assets/UpdateEmailScreen-DVOhPV0H-gFbSIUhh.js +0 -1
- package/site/dist/assets/UpdatePhoneScreen-CXRswTNZ-Coy4E1NQ.js +0 -1
- package/site/dist/assets/UserLimitReachedScreen-5we2DHn8-D7m70C4b.js +0 -1
- package/site/dist/assets/Value-DTgR824E-BRuIGiec.js +0 -27
- package/site/dist/assets/WalletCards-DH1rqayz-Bgp9Fsgs.js +0 -1
- package/site/dist/assets/WalletIcon-gKf0gTw6.js +0 -1
- package/site/dist/assets/WalletInfoCard-sq_2wgFg-ISBsQt8p.js +0 -29
- package/site/dist/assets/WalletInterstitialScreen-Sh7LfsAh-DG-RuO-F.js +0 -1
- package/site/dist/assets/WalletLink-DK5LB5Ha-CXBqCHf3.js +0 -20
- package/site/dist/assets/WalletOverflowButton-bfCnp-9T-DP0-PoZL.js +0 -1
- package/site/dist/assets/WarningBanner-ZZqCEtZK-2eU4Qo74.js +0 -21
- package/site/dist/assets/__vite-browser-external-CitKSmYk.js +0 -1
- package/site/dist/assets/_esm-CJe2F7LL.js +0 -7
- package/site/dist/assets/_esm-zhBBffjV.js +0 -1
- package/site/dist/assets/accounts-Bq8t1wAD.js +0 -1
- package/site/dist/assets/accounts-D96UKWkC.js +0 -1
- package/site/dist/assets/analytics-mkkvFRju-BRBcadka.js +0 -1
- package/site/dist/assets/baseSepolia-DX-qGsik.js +0 -1
- package/site/dist/assets/basic-MlZ7f4s9.js +0 -1575
- package/site/dist/assets/capitalizeFirstLetter-DmLYqXsO-DtOXx-2n.js +0 -1
- package/site/dist/assets/ccip-BI9LWBBg.js +0 -1
- package/site/dist/assets/ccip-C-59SNEF.js +0 -7
- package/site/dist/assets/ccip-y08wK9du.js +0 -1
- package/site/dist/assets/chain-DM3oH6Px.js +0 -8
- package/site/dist/assets/check-BPgcwEPP.js +0 -1
- package/site/dist/assets/chevron-down-BSsbXkK-.js +0 -1
- package/site/dist/assets/circle-alert-DWaeLZNj.js +0 -1
- package/site/dist/assets/circle-check-big-Chb1ivw9.js +0 -1
- package/site/dist/assets/circle-x-AV9Cet0R.js +0 -1
- package/site/dist/assets/client-D0IV9lV9.js +0 -2
- package/site/dist/assets/client-Dsa7_3V3.js +0 -9
- package/site/dist/assets/coinbase-DCPC2L_R-Rs9nVCfm.js +0 -3
- package/site/dist/assets/copy-Ckr0AkL6.js +0 -1
- package/site/dist/assets/core-zQ1tZtVZ.js +0 -12
- package/site/dist/assets/createLucideIcon-IPW50ckY.js +0 -1
- package/site/dist/assets/defineChain-CiuCXUZW.js +0 -1
- package/site/dist/assets/dijkstra-Drfl93V4.js +0 -1
- package/site/dist/assets/dist-BxQqwnue.js +0 -16
- package/site/dist/assets/dist-Cm7pNAtL.js +0 -1
- package/site/dist/assets/dist-CoS6E-3x.js +0 -1
- package/site/dist/assets/dist-Dl07uGq1.js +0 -1
- package/site/dist/assets/embed.worker-ugGhEqCV.js +0 -1
- package/site/dist/assets/errors-BQ3N-RVt-BLqjTPNR.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-DXsETz3H.js +0 -1
- package/site/dist/assets/esm-n2lWo7kz.js +0 -1
- package/site/dist/assets/esm-vs5jNx_Y.js +0 -37
- package/site/dist/assets/ethers-zTjbodk5-B7BBFFKf.js +0 -1
- package/site/dist/assets/eventemitter3-CL76jPsW.js +0 -1
- package/site/dist/assets/external-link-D-uehwM2.js +0 -1
- package/site/dist/assets/farcaster-DPlSjvF5-BqTvDXBR.js +0 -1
- package/site/dist/assets/features-CYXRa3_m.js +0 -2
- package/site/dist/assets/fingerprint-pattern-D8ytA16Q.js +0 -1
- package/site/dist/assets/floating-ui.react-dom-DKWOL9UG.js +0 -1
- package/site/dist/assets/floating-ui.react-xRkl9O3u.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-Dukf7_kJ.js +0 -1
- package/site/dist/assets/getChainName-DjpPdUSc-DUyKDrnp.js +0 -1
- package/site/dist/assets/getErc20Balance-jXNpFf0N-V2btxzrJ.js +0 -1
- package/site/dist/assets/getErc20TokenInfo-BNTrzLMP-CtCwQuGA.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-CHfXo8XI.js +0 -1
- package/site/dist/assets/hooks.module-BYZGfwDl.js +0 -1
- package/site/dist/assets/hourglass-CPG1HZmf.js +0 -1
- package/site/dist/assets/id-UqEwL2BI-CdKXapd-.js +0 -1
- package/site/dist/assets/index-BdEw5wyl-ChSJvPZk.js +0 -1
- package/site/dist/assets/index-CKdAF_dK.js +0 -8
- package/site/dist/assets/index-CWARkn2w-Cezl_uBr.js +0 -61
- package/site/dist/assets/index-DvMddJcd-D-4mRLKK.js +0 -12
- package/site/dist/assets/index-YiCw0Y_x-BX1zsiqG.js +0 -437
- 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-BmqyoBEM.js +0 -2
- package/site/dist/assets/index.es-C1v1or--.js +0 -3
- package/site/dist/assets/isPaymentRequestAvailable-Bq1cemEn-By6r0dVB.js +0 -1
- package/site/dist/assets/jsx-runtime-BbDfbRii.js +0 -1
- package/site/dist/assets/landmark-CH6LV6F0.js +0 -1
- package/site/dist/assets/lib-BsS8z8Lj.js +0 -1
- package/site/dist/assets/localBatchGatewayRequest-5YxrERBj.js +0 -32
- package/site/dist/assets/lock-DcskI4Bb.js +0 -1
- package/site/dist/assets/logger-CsXm3oIv-B0UYpNFI.js +0 -1
- package/site/dist/assets/mail-Bs0r5yOi.js +0 -1
- package/site/dist/assets/modal-context-CwmSeFcv-DruX9J1E.js +0 -1
- package/site/dist/assets/native-BKj27wfc.js +0 -1
- package/site/dist/assets/ofetch.CWycOUEr-CHp-crOQ.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-CGP3gnf2.js +0 -1
- package/site/dist/assets/pkce-DXF0yKjP-Bqz4wAsq.js +0 -1
- package/site/dist/assets/preload-helper-BaNbYf_w.js +0 -1
- package/site/dist/assets/privy-context-81KmsBw2-CjWGzyg7.js +0 -2
- package/site/dist/assets/property-DuZ9DLP4.js +0 -3
- package/site/dist/assets/react-DCXkXSrv.js +0 -1
- package/site/dist/assets/react-DQnMl2Xv.js +0 -1
- package/site/dist/assets/react-DrJ0MmMO.js +0 -1
- package/site/dist/assets/react-auth-sHnipzbS.js +0 -1
- package/site/dist/assets/react-dom-5dW7aKoP.js +0 -1
- package/site/dist/assets/reservoir-x-nGuZkT-BMX127i6.js +0 -1
- package/site/dist/assets/roles-32M1l9jI.js +0 -1
- package/site/dist/assets/rolldown-runtime-BcKkbAw3.js +0 -1
- package/site/dist/assets/safe-url-D7SRPu33-CkMedGro.js +0 -1
- package/site/dist/assets/secp256k1-C4nzcPo-.js +0 -1
- package/site/dist/assets/secp256k1-D92aGNnU.js +0 -1
- package/site/dist/assets/secp256k1-DmbwyFau.js +0 -1
- package/site/dist/assets/secp256k1-_MleGpqL.js +0 -1
- package/site/dist/assets/secp256k1-xl2og-wO.js +0 -1
- package/site/dist/assets/serializeAuthorizationList-CX8hGL56.js +0 -1
- package/site/dist/assets/serializeSignature-DfxkPnu-.js +0 -2
- package/site/dist/assets/sha2-BU_QyRTx.js +0 -1
- package/site/dist/assets/shared-DRZUmnL5-xqAUARWo.js +0 -193
- package/site/dist/assets/shared-FM0rljBt--Avg6q2R.js +0 -21
- package/site/dist/assets/shouldProceedtoEmbeddedWalletCreationFlow-B3vyavrh-ByFOTrQK.js +0 -1
- package/site/dist/assets/simulateTransaction-CfuOrae4-CMFGDgT_.js +0 -1
- package/site/dist/assets/smartphone-CcEZ-XAy.js +0 -1
- package/site/dist/assets/src-BBjkfi-Q.js +0 -1
- package/site/dist/assets/storage-Ufl800Ne-EalZTH9q.js +0 -9
- package/site/dist/assets/stringify-BlDm7tuc.js +0 -21
- package/site/dist/assets/stripe.esm-CZRDpc-N.js +0 -2
- package/site/dist/assets/styles-BsotlekN-CXEB4Ud9.js +0 -9
- package/site/dist/assets/styles-Cm9H63WO-D90SsXOs.js +0 -92
- package/site/dist/assets/styles-DVyDvTdj-BTABn2K8.js +0 -74
- package/site/dist/assets/styles-hslXy6xG-D-49b1rk.js +0 -164
- package/site/dist/assets/telegram-B-JqnkqZ-RBoY3PLx.js +0 -1
- package/site/dist/assets/to-ui-error-DWe-qwS3-DgCO-Y70.js +0 -4
- package/site/dist/assets/toViemAccount-B5iDGq1b-DZ1MBqUW.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/transformers.web-D4RvOP4-.js +0 -40
- package/site/dist/assets/transformers.web-D8W1u_I2.js +0 -49
- package/site/dist/assets/triangle-alert-CWjYH75H.js +0 -1
- package/site/dist/assets/twitch-5IOe4sIQ-lDtgk0os.js +0 -1
- package/site/dist/assets/types-4CuOD23i.js +0 -1
- package/site/dist/assets/use-headless-crypto-deposit-Yn-QRLB3-Dn6_8w-Y.js +0 -1
- package/site/dist/assets/use-unlink-wallet-BKK4_5Ur-DgsQ75lh.js +0 -1
- package/site/dist/assets/useActiveWallet-B-yZYGc6-B6JWREbk.js +0 -123
- package/site/dist/assets/useGetSolPrice-x7gfUIHJ-D1hmQfpA.js +0 -1
- package/site/dist/assets/useGetTokenPrice-CaoUkb79-ZnbuJ-vP.js +0 -1
- package/site/dist/assets/useI18n-C-5n_noF-BcqC76yj.js +0 -1
- package/site/dist/assets/useWalletBalance-CZnP5f_f-L3V0jDjL.js +0 -1
- package/site/dist/assets/useWallets-X1ndvJ_U-CLwUcevw.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-BMubXAoG.js +0 -1174
- package/site/dist/assets/wallet-Bn85Sg2W.js +0 -1
- package/site/dist/assets/wallet-connect-BvJtUsDt-Y03hhqFN.js +0 -1
- package/site/dist/assets/wallet-uSKqQsgP.js +0 -2
- package/site/dist/assets/walletconnect-registry-xxG8XLCb-D1SSFvMp.js +0 -1
- package/site/dist/assets/walletconnect-v2-3frQFwbg-Dv6JY_ko.js +0 -12
- package/site/dist/assets/wui-ux-by-reown-fVu0QkI4.js +0 -1147
- package/site/dist/assets/x-rlSp9U7W.js +0 -1
- package/src/discover/explore.js +0 -271
- 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
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{t as e}from"./createLucideIcon-IPW50ckY.js";var t=e(`x`,[[`path`,{d:`M18 6 6 18`,key:`1bl5f8`}],[`path`,{d:`m6 6 12 12`,key:`d8bk6v`}]]);export{t};
|
package/src/discover/explore.js
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
// The Fangorn explorer: every verified app, what it is about, and what the
|
|
2
|
-
// ERC-8004 registries say about it — read live, with no bake of its own.
|
|
3
|
-
//
|
|
4
|
-
// Everything here is already published by someone else:
|
|
5
|
-
// AppRegistry `AppAgentChanged` which apps exist and where their cards are
|
|
6
|
-
// each card name, tools, views; checked against the chain
|
|
7
|
-
// each view's catalog rows, domains, and coverage centroids (what it is about)
|
|
8
|
-
// ERC-8004 identity `Registered` the agent minted for the app
|
|
9
|
-
// ERC-8004 reputation feedback count and average per agent
|
|
10
|
-
//
|
|
11
|
-
// Both registries are scanned from their first event in 10M-block windows, in
|
|
12
|
-
// parallel, so the page and the MCP tool read the same chain the same way, and
|
|
13
|
-
// a new app shows up on the next read.
|
|
14
|
-
//
|
|
15
|
-
// `client` is a viem PublicClient (getLogs, readContract): passed in, so this
|
|
16
|
-
// runs in a tab and in node, and the self-check needs no chain.
|
|
17
|
-
//
|
|
18
|
-
// ponytail: windows grow with the chain (1 + 8 at block 316M, ~0.2s). Index
|
|
19
|
-
// the logs somewhere if that ever gets slow.
|
|
20
|
-
|
|
21
|
-
import { parseAbi, parseAbiItem } from "viem";
|
|
22
|
-
import { APP_EXTENSION } from "./apps.js";
|
|
23
|
-
import { survey, comparable } from "./directory.js";
|
|
24
|
-
import { rankDomains, zFloor, lexScore } from "../core/rank.js";
|
|
25
|
-
|
|
26
|
-
export const CHAIN = {
|
|
27
|
-
chainId: 421614,
|
|
28
|
-
appRegistry: "0x11d228c4774af3d9cae3b4b6874a12576a1a83ec",
|
|
29
|
-
identity: "0x8004A818BFB912233c491871b3d84c89A494BD9e",
|
|
30
|
-
reputation: "0x8004B663056A597Dffe9eCcC1965A193B7388713",
|
|
31
|
-
// Each registry's first event (2026-10-05): nothing to read before it.
|
|
32
|
-
// Scanning from 0 instead was 32 windows per registry, ~5s of a 7.7s load.
|
|
33
|
-
appRegistryFrom: 311_639_205n,
|
|
34
|
-
identityFrom: 241_557_787n,
|
|
35
|
-
scan: (agentId) => `https://8004scan.io/agents/arbitrum-sepolia/${agentId}`,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const APP_AGENT_CHANGED = parseAbiItem("event AppAgentChanged(bytes32 indexed app_id, string agent_uri)");
|
|
39
|
-
const REGISTERED = parseAbiItem("event Registered(uint256 indexed agentId, string agentURI, address indexed owner)");
|
|
40
|
-
const APPS = parseAbi(["function getAppOwner(bytes32) view returns (address)"]);
|
|
41
|
-
const REP = parseAbi([
|
|
42
|
-
"function getClients(uint256) view returns (address[])",
|
|
43
|
-
"function getSummary(uint256, address[], string, string) view returns (uint64, int128, uint8)",
|
|
44
|
-
]);
|
|
45
|
-
|
|
46
|
-
/** A link someone else wrote, shown to a reader: https or nothing. */
|
|
47
|
-
const https = (u) => { try { const x = new URL(String(u)); return x.protocol === "https:" ? x.href : null; } catch { return null; } };
|
|
48
|
-
const slug = (name) => String(name).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "").slice(0, 24) || "app";
|
|
49
|
-
|
|
50
|
-
/** A card is listed only if it names this chain's AppRegistry and the app that
|
|
51
|
-
* bound it — the checks `discoverApp` makes. The binding itself is the log we
|
|
52
|
-
* read it from: we scanned to `latest`, so the last write per app is current. */
|
|
53
|
-
export function verifyCard(card, appId, chain = CHAIN) {
|
|
54
|
-
const p = card?.capabilities?.extensions?.find((e) => e.uri === APP_EXTENSION)?.params;
|
|
55
|
-
if (!p) return "no fangorn app extension";
|
|
56
|
-
if (String(p.appId).toLowerCase() !== appId.toLowerCase()) return "card names another app";
|
|
57
|
-
if (p.chainId !== chain.chainId || String(p.appRegistry).toLowerCase() !== chain.appRegistry.toLowerCase()) return "card is for another registry";
|
|
58
|
-
if (!/^\d+$/.test(String(p.fromBlock))) return "bad fromBlock";
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* The agent an app registered. `fangorn app agent` mints it and then binds the
|
|
64
|
-
* card, from the owner's wallet, so it is the owner's latest `Registered` at or
|
|
65
|
-
* before the binding.
|
|
66
|
-
*
|
|
67
|
-
* ponytail: paired by owner and block, not by reading the registration file
|
|
68
|
-
* (IPFS gateways are too slow and flaky for a page load). An owner that
|
|
69
|
-
* rebinds with --skip-register after minting another app's agent gets that
|
|
70
|
-
* agent; fetch tokenURI and match its A2A endpoint if that starts happening.
|
|
71
|
-
*/
|
|
72
|
-
export function pairAgent(registered, owner, bindBlock) {
|
|
73
|
-
let best = null;
|
|
74
|
-
for (const r of registered) {
|
|
75
|
-
if (r.owner.toLowerCase() !== owner.toLowerCase() || r.block > bindBlock) continue;
|
|
76
|
-
if (!best || r.block > best.block || (r.block === best.block && r.agentId > best.agentId)) best = r;
|
|
77
|
-
}
|
|
78
|
-
return best?.agentId ?? null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Retried: dropping a verified app over a blip hides it from everyone who
|
|
82
|
-
* loads the page in that second. Seen 2026-09-28: headless Chrome in WSL got
|
|
83
|
-
* intermittent net::ERR_SSL_PROTOCOL_ERROR from one pages.dev host (curl never
|
|
84
|
-
* did); three tries do not always get past that one. */
|
|
85
|
-
async function json(url, timeoutMs = 8000, tries = 3) {
|
|
86
|
-
try {
|
|
87
|
-
const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
88
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
89
|
-
return await res.json();
|
|
90
|
-
} catch (e) {
|
|
91
|
-
if (tries <= 1) throw e;
|
|
92
|
-
await new Promise((ok) => setTimeout(ok, 600 * (4 - tries))); // 600ms, then 1.2s
|
|
93
|
-
return json(url, timeoutMs, tries - 1);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async function reputation(client, agentId, chain) {
|
|
98
|
-
try {
|
|
99
|
-
const clients = await client.readContract({ address: chain.reputation, abi: REP, functionName: "getClients", args: [agentId] });
|
|
100
|
-
if (!clients.length) return { feedback: 0, average: null };
|
|
101
|
-
const [count, value, decimals] = await client.readContract({ address: chain.reputation, abi: REP, functionName: "getSummary", args: [agentId, clients, "", ""] });
|
|
102
|
-
return { feedback: Number(count), average: Number(value) / 10 ** decimals, clients: clients.length };
|
|
103
|
-
} catch (e) {
|
|
104
|
-
return { error: e?.shortMessage ?? e?.message ?? String(e) };
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// The public Arbitrum Sepolia RPC refuses an address+event getLogs spanning more
|
|
109
|
-
// than 10M blocks (from 2026-10; one call from 0 worked before).
|
|
110
|
-
const LOG_WINDOW = 10_000_000n;
|
|
111
|
-
|
|
112
|
-
async function getLogsSince(client, start, filter) {
|
|
113
|
-
const head = await client.getBlockNumber();
|
|
114
|
-
const windows = [];
|
|
115
|
-
for (let from = start; from <= head; from += LOG_WINDOW) {
|
|
116
|
-
const to = from + LOG_WINDOW - 1n < head ? from + LOG_WINDOW - 1n : head;
|
|
117
|
-
windows.push(client.getLogs({ ...filter, fromBlock: from, toBlock: to }));
|
|
118
|
-
}
|
|
119
|
-
return (await Promise.all(windows)).flat(); // in window order: oldest first still holds
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Every verified app, with its 8004 agent, reputation, and catalog.
|
|
124
|
-
* Unverified apps are dropped, not listed; `dropped` says why, for debugging.
|
|
125
|
-
*
|
|
126
|
-
* `onUpdate({ apps, pending })`, if given, is called once the bindings are read
|
|
127
|
-
* and again as each app lands or drops, so a page can show apps as they arrive.
|
|
128
|
-
* `pending` counts the bound apps not yet settled.
|
|
129
|
-
*/
|
|
130
|
-
export async function readDirectory(client, { chain = CHAIN, onUpdate = () => {} } = {}) {
|
|
131
|
-
const [bindLogs, regLogs] = await Promise.all([
|
|
132
|
-
getLogsSince(client, chain.appRegistryFrom ?? 0n, { address: chain.appRegistry, event: APP_AGENT_CHANGED }),
|
|
133
|
-
getLogsSince(client, chain.identityFrom ?? 0n, { address: chain.identity, event: REGISTERED }),
|
|
134
|
-
]);
|
|
135
|
-
const registered = regLogs.map((l) => ({ agentId: l.args.agentId, owner: l.args.owner, block: l.blockNumber }));
|
|
136
|
-
const latest = new Map(); // logs come oldest first: the last write wins
|
|
137
|
-
for (const l of bindLogs) latest.set(l.args.app_id, { uri: l.args.agent_uri, block: l.blockNumber });
|
|
138
|
-
const bound = [...latest].filter(([, { uri }]) => uri); // an empty uri is unbound on purpose
|
|
139
|
-
|
|
140
|
-
// One pipeline per app, so the quick ones need not wait on Quorum's 1.7 MB
|
|
141
|
-
// catalog. Only a card failure drops an app; an owner or catalog that fails
|
|
142
|
-
// leaves it listed with less to say about it.
|
|
143
|
-
const apps = [], dropped = [];
|
|
144
|
-
let pending = bound.length;
|
|
145
|
-
const settle = () => { pending--; apps.sort((a, b) => a.name.localeCompare(b.name)); onUpdate({ apps: [...apps], pending }); };
|
|
146
|
-
onUpdate({ apps: [], pending });
|
|
147
|
-
await Promise.all(bound.map(async ([appId, { uri, block }]) => {
|
|
148
|
-
let card;
|
|
149
|
-
try {
|
|
150
|
-
if (!/^https:\/\//.test(uri)) throw new Error("card is not https");
|
|
151
|
-
card = await json(uri);
|
|
152
|
-
const why = verifyCard(card, appId, chain);
|
|
153
|
-
if (why) throw new Error(why);
|
|
154
|
-
} catch (e) { dropped.push({ card: uri, why: e?.message ?? String(e) }); return settle(); }
|
|
155
|
-
|
|
156
|
-
const p = card.capabilities.extensions.find((e) => e.uri === APP_EXTENSION).params;
|
|
157
|
-
const [[owner, agentId, rep], { corpora, unreachable }] = await Promise.all([
|
|
158
|
-
client.readContract({ address: chain.appRegistry, abi: APPS, functionName: "getAppOwner", args: [appId] }).catch(() => null)
|
|
159
|
-
.then(async (owner) => {
|
|
160
|
-
const agentId = owner && pairAgent(registered, owner, block);
|
|
161
|
-
return [owner, agentId, agentId == null ? null : await reputation(client, agentId, chain)];
|
|
162
|
-
}),
|
|
163
|
-
survey((p.views ?? []).filter((v) => /^https:\/\//.test(v))),
|
|
164
|
-
]);
|
|
165
|
-
const name = typeof card.name === "string" && card.name.trim() ? card.name.trim() : `App ${appId.slice(0, 10)}`;
|
|
166
|
-
apps.push({
|
|
167
|
-
app: slug(name), name, description: typeof card.description === "string" ? card.description : "",
|
|
168
|
-
site: https(card.url), card: uri, appId, owner,
|
|
169
|
-
boundAt: Number(block), fromBlock: Number(p.fromBlock),
|
|
170
|
-
tools: (card.skills ?? []).map((s) => s.id),
|
|
171
|
-
agent: agentId == null ? null : { id: Number(agentId), scan: chain.scan(agentId) },
|
|
172
|
-
reputation: rep,
|
|
173
|
-
// null, not 0, when no catalog answered: "0 records" would claim it was counted.
|
|
174
|
-
rows: corpora.length ? corpora.reduce((n, c) => n + (c.rows || 0), 0) : null,
|
|
175
|
-
unreachable: unreachable.length,
|
|
176
|
-
domains: corpora.map((c) => ({ name: c.domain, description: c.description, rows: c.rows, coverage: c.coverage, model: c.model })),
|
|
177
|
-
});
|
|
178
|
-
settle();
|
|
179
|
-
}));
|
|
180
|
-
return { apps, dropped };
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/** A calibration knob, not a law: nomic-embed-text-v1.5 at 256d, 5 apps
|
|
184
|
-
* (2026-09-28). Every on-topic top app scored >= 0.545 ("a cozy game" ->
|
|
185
|
-
* Sidequest), every off-topic top <= 0.52 ("bitcoin" -> Audius). Re-measure
|
|
186
|
-
* with the explorer's check when the model or the network changes much. */
|
|
187
|
-
export const MIN_AFFINITY = 0.53;
|
|
188
|
-
|
|
189
|
-
// ponytail: a short English list, enough for questions put to a directory.
|
|
190
|
-
const FILLER = new Set("the and for are was were has have had not but with from that this these those what which where when who whom whose why how can could would should will shall may might must about into onto over under than then there their them they you your our any all some more most much many app apps find show tell learn know want need good best reputable".split(" "));
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Rank a directory against a question. An app scores as its best domain's
|
|
194
|
-
* coverage affinity (what its data is about, not what its blurb says). With no
|
|
195
|
-
* `qv` (no embedder yet), name and description are matched by words.
|
|
196
|
-
*
|
|
197
|
-
* `relevant`: mean + 1σ of this query's scores, and at least MIN_AFFINITY.
|
|
198
|
-
* Unlike findCorpora the top app is NOT always relevant: "which app has stock
|
|
199
|
-
* prices" must be able to answer "none", or the explorer vouches for a
|
|
200
|
-
* government-records app on a finance question.
|
|
201
|
-
*/
|
|
202
|
-
export function rankApps(apps, query, { qv = null, model = null } = {}) {
|
|
203
|
-
const q = String(query ?? "").trim().toLowerCase();
|
|
204
|
-
if (!q) return apps.map((a) => ({ ...a, score: null, relevant: null, ranked_by: null }));
|
|
205
|
-
// Filler dropped, or "where can I learn about X" matches every blurb with "about" in it.
|
|
206
|
-
const content = q.split(/[^\p{L}\p{N}]+/u).filter((w) => w.length > 2 && !FILLER.has(w)).join(" ");
|
|
207
|
-
const words = (a) => (content ? lexScore({ text: `${a.name} ${a.description} ${a.domains.map((d) => d.description ?? "").join(" ")}` }, content) : 0);
|
|
208
|
-
let scored;
|
|
209
|
-
if (qv) {
|
|
210
|
-
scored = apps.map((a) => {
|
|
211
|
-
const doms = rankDomains(a.domains.filter((d) => comparable(d, model)), qv);
|
|
212
|
-
const best = doms.find((d) => d.affinity != null);
|
|
213
|
-
// No coverage (its catalog did not answer, or predates coverage):
|
|
214
|
-
// its own blurb, by words, rather than silently ranking it last.
|
|
215
|
-
if (!best) return { ...a, score: null, matched: null, ranked_by: "words", wordHit: words(a) > 0 };
|
|
216
|
-
return { ...a, score: best.affinity, matched: best.name, ranked_by: "meaning" };
|
|
217
|
-
});
|
|
218
|
-
} else {
|
|
219
|
-
scored = apps.map((a) => ({ ...a, score: words(a), ranked_by: "words" }));
|
|
220
|
-
}
|
|
221
|
-
scored.sort((a, b) => (b.score ?? -Infinity) - (a.score ?? -Infinity));
|
|
222
|
-
const s = scored.filter((a) => a.ranked_by === "meaning").map((a) => a.score);
|
|
223
|
-
const floor = qv ? Math.max(zFloor(s), MIN_AFFINITY) : 1e-9;
|
|
224
|
-
return scored.map(({ wordHit, ...a }) => ({ ...a, relevant: wordHit ?? (a.score != null && a.score >= floor) }));
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/** What a caller sees: the ranking minus the centroids. Quorum alone has 29
|
|
228
|
-
* domains, so an app reports how many, and which one matched. */
|
|
229
|
-
export const brief = ({ domains, ...a }) => ({ ...a, domains: domains.length });
|
|
230
|
-
|
|
231
|
-
// ── self-check: `node src/discover/explore.js` ──────────────────────────────────
|
|
232
|
-
if (typeof process !== "undefined" && import.meta.url === `file://${process.argv[1]}` && process.argv[1].endsWith("/explore.js")) {
|
|
233
|
-
const j = (x) => JSON.stringify(x, (_, v) => (typeof v === "bigint" ? `${v}n` : v));
|
|
234
|
-
const eq = (a, b, m) => { if (j(a) !== j(b)) throw new Error(`${m}: ${j(a)} != ${j(b)}`); };
|
|
235
|
-
const O = "0x00000000000000000000000000000000000000aa", P = "0x00000000000000000000000000000000000000bb";
|
|
236
|
-
const reg = [
|
|
237
|
-
{ agentId: 1n, owner: O, block: 10n }, { agentId: 2n, owner: P, block: 11n },
|
|
238
|
-
{ agentId: 3n, owner: O, block: 20n }, { agentId: 4n, owner: O, block: 99n },
|
|
239
|
-
];
|
|
240
|
-
eq(pairAgent(reg, O, 21n), 3n, "owner's latest agent at or before the binding");
|
|
241
|
-
eq(pairAgent(reg, P.toUpperCase().replace("0X", "0x"), 50n), 2n, "owner compared case-insensitively");
|
|
242
|
-
eq(pairAgent(reg, O, 5n), null, "no agent before the binding");
|
|
243
|
-
|
|
244
|
-
const A = "0x" + "a".repeat(64);
|
|
245
|
-
const card = (params) => ({ capabilities: { extensions: [{ uri: APP_EXTENSION, params }] } });
|
|
246
|
-
const good = { appId: A, chainId: CHAIN.chainId, appRegistry: CHAIN.appRegistry.toUpperCase().replace("0X", "0x"), fromBlock: "7" };
|
|
247
|
-
eq(verifyCard(card(good), A), null, "a good card verifies");
|
|
248
|
-
eq(verifyCard(card({ ...good, appId: "0x" + "b".repeat(64) }), A), "card names another app", "wrong app");
|
|
249
|
-
eq(verifyCard(card({ ...good, chainId: 1 }), A), "card is for another registry", "wrong chain");
|
|
250
|
-
eq(verifyCard({}, A), "no fangorn app extension", "no extension");
|
|
251
|
-
|
|
252
|
-
const cov = (v) => ({ vectors: [v] });
|
|
253
|
-
const apps = [
|
|
254
|
-
{ name: "Tunes", description: "music tracks", domains: [{ name: "tracks", coverage: cov([1, 0, 0]) }] },
|
|
255
|
-
{ name: "Sky", description: "weather alerts", domains: [{ name: "alerts", coverage: cov([0, 1, 0]) }, { name: "old", coverage: null }] },
|
|
256
|
-
{ name: "Law", description: "meetings", domains: [{ name: "minutes", coverage: cov([0, 0, 1]) }] },
|
|
257
|
-
{ name: "Empty", description: "", domains: [] },
|
|
258
|
-
];
|
|
259
|
-
const r = rankApps(apps, "rain", { qv: [0.1, 1, 0] });
|
|
260
|
-
eq(r.map((a) => a.name), ["Sky", "Tunes", "Law", "Empty"], "ranked by best domain affinity, unscored last");
|
|
261
|
-
eq([r[0].relevant, r[0].matched, r[3].relevant], [true, "alerts", false], "top is relevant, unscored is not");
|
|
262
|
-
const blind = rankApps([{ ...apps[1], domains: [] }, apps[0], apps[2]], "weather alerts", { qv: [0.1, 1, 0] });
|
|
263
|
-
eq(blind.map((a) => [a.name, a.relevant, a.ranked_by]).at(-1), ["Sky", true, "words"], "an app with no coverage still matches on its own words");
|
|
264
|
-
eq(rankApps([{ ...apps[1], description: "all about the sky", domains: [] }], "where can I learn about music", { qv: [1, 0, 0] })[0].relevant, false, "filler words never match");
|
|
265
|
-
eq(rankApps(apps, "stock prices", { qv: [-1, -1, -1] }).filter((a) => a.relevant).length, 0, "a question no app covers matches none");
|
|
266
|
-
eq(rankApps(apps, "music").map((a) => [a.name, a.relevant]).slice(0, 2), [["Tunes", true], ["Sky", false]], "word fallback");
|
|
267
|
-
eq(rankApps(apps, "").map((a) => a.score), [null, null, null, null], "no query: listed, not ranked");
|
|
268
|
-
eq([https("javascript:alert(1)"), https("http://x.test"), https("https://x.test/a")], [null, null, "https://x.test/a"], "only https links reach a reader");
|
|
269
|
-
eq(brief({ name: "x", domains: [{ name: "d", coverage: 1 }] }), { name: "x", domains: 1 }, "brief drops centroids");
|
|
270
|
-
console.log("explore.js self-check ok — cards verified, agents paired by owner and block, apps ranked by coverage or words");
|
|
271
|
-
}
|
package/src/market/settle.js
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
// Encrypting a file into the access worker, from Node.
|
|
2
|
-
//
|
|
3
|
-
// This is the publisher's byte path, and it is the half of publishing that no
|
|
4
|
-
// relay can do for you: the DEK is generated here, sealed to the worker's public
|
|
5
|
-
// key here, and the plaintext never leaves the machine that owns it. A relay
|
|
6
|
-
// that offered to do this step would be a relay that had your file.
|
|
7
|
-
//
|
|
8
|
-
// The browser has its own copy of this walk (an app's own encrypt.js, over
|
|
9
|
-
// File.slice instead of fs.read). They must agree byte for byte — they share
|
|
10
|
-
// `pack`, `aadFor` and `chunkKey` from envelope.js precisely so that the only
|
|
11
|
-
// thing that differs between them is how bytes are read off a disk.
|
|
12
|
-
import { open } from "node:fs/promises";
|
|
13
|
-
import { createHash } from "node:crypto";
|
|
14
|
-
import { seal } from "@fangorn-network/sdk";
|
|
15
|
-
import { bytesToHex } from "viem";
|
|
16
|
-
import {
|
|
17
|
-
CHUNK_SIZE, NONCE_LEN, aadFor, chunkKey, deleteResource,
|
|
18
|
-
getWorkerPubkey, pack, putChunk, unpack,
|
|
19
|
-
} from "../publish/envelope.js";
|
|
20
|
-
|
|
21
|
-
export { CHUNK_SIZE, aadFor, chunkKey, deleteResource, getWorkerPubkey };
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Envelope-encrypt the file at `file` chunk by chunk and upload each
|
|
25
|
-
* {ciphertext, sealed DEK} to the access worker's R2. Reads one chunk at a time,
|
|
26
|
-
* so peak memory is ~2x chunkSize no matter how big the file is.
|
|
27
|
-
*
|
|
28
|
-
* @returns { plaintextHash, chunks, size, chunkSize } — the hash to commit
|
|
29
|
-
* on-chain, the chunk count the buyer reassembles, and the plaintext
|
|
30
|
-
* byte geometry a streaming player needs to map a Range request onto a
|
|
31
|
-
* chunk (chunk i starts at i * chunkSize).
|
|
32
|
-
*/
|
|
33
|
-
export async function encryptAndUpload({ file, resourceId, workerUrl, uploadToken, chunkSize = CHUNK_SIZE, onProgress }) {
|
|
34
|
-
const dek = crypto.getRandomValues(new Uint8Array(32));
|
|
35
|
-
const aesKey = await crypto.subtle.importKey("raw", dek, "AES-GCM", false, ["encrypt"]);
|
|
36
|
-
const sealedDek = bytesToHex(seal(dek, await getWorkerPubkey(workerUrl), resourceId));
|
|
37
|
-
|
|
38
|
-
const fh = await open(file, "r");
|
|
39
|
-
try {
|
|
40
|
-
const { size } = await fh.stat();
|
|
41
|
-
const chunks = Math.max(1, Math.ceil(size / chunkSize));
|
|
42
|
-
const hash = createHash("sha256");
|
|
43
|
-
const buf = Buffer.allocUnsafe(Math.min(size, chunkSize) || 1);
|
|
44
|
-
|
|
45
|
-
for (let i = 0; i < chunks; i++) {
|
|
46
|
-
const { bytesRead } = await fh.read(buf, 0, buf.length, i * chunkSize);
|
|
47
|
-
const plain = buf.subarray(0, bytesRead);
|
|
48
|
-
hash.update(plain);
|
|
49
|
-
|
|
50
|
-
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LEN));
|
|
51
|
-
const aesCt = new Uint8Array(
|
|
52
|
-
await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce, additionalData: aadFor(i) }, aesKey, await pack(plain)),
|
|
53
|
-
);
|
|
54
|
-
const body = new Uint8Array(NONCE_LEN + aesCt.length);
|
|
55
|
-
body.set(nonce, 0);
|
|
56
|
-
body.set(aesCt, NONCE_LEN);
|
|
57
|
-
|
|
58
|
-
await putChunk({ workerUrl, uploadToken, key: chunkKey(resourceId, i), body, sealedDek });
|
|
59
|
-
onProgress?.(i + 1, chunks);
|
|
60
|
-
}
|
|
61
|
-
return { plaintextHash: `0x${hash.digest("hex")}`, chunks, size, chunkSize };
|
|
62
|
-
} finally {
|
|
63
|
-
await fh.close();
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// ── self-check: `node src/market/settle.js` — no network, no worker ─────────────
|
|
68
|
-
//
|
|
69
|
-
// It decrypts with bare AES-GCM rather than importing a buyer, because what is
|
|
70
|
-
// under test here is the ENCRYPTOR: chunk addressing, the AAD index binding, the
|
|
71
|
-
// ragged tail, and that pack/unpack round trip. An app's full publisher→buyer
|
|
72
|
-
// round trip belongs in that app, against its own buyer.
|
|
73
|
-
if (import.meta.url === `file://${process.argv[1]}` && process.argv[1].endsWith("/settle.js")) {
|
|
74
|
-
const { writeFileSync, rmSync } = await import("node:fs");
|
|
75
|
-
const { unseal } = await import("@fangorn-network/sdk");
|
|
76
|
-
const { x25519 } = await import("@noble/curves/ed25519");
|
|
77
|
-
const { hexToBytes, keccak256 } = await import("viem");
|
|
78
|
-
|
|
79
|
-
const CHUNK = 1000;
|
|
80
|
-
const tmp = `/tmp/westmarch-settle-${process.pid}.bin`;
|
|
81
|
-
const workerSecret = crypto.getRandomValues(new Uint8Array(32));
|
|
82
|
-
const workerPubkey = x25519.getPublicKey(workerSecret);
|
|
83
|
-
const r2 = new Map();
|
|
84
|
-
let sealedSeen = null;
|
|
85
|
-
const realFetch = globalThis.fetch;
|
|
86
|
-
globalThis.fetch = async (url, init = {}) => {
|
|
87
|
-
const u = String(url);
|
|
88
|
-
if (u.endsWith("/pubkey")) return { ok: true, json: async () => ({ pubkey: bytesToHex(workerPubkey) }) };
|
|
89
|
-
if (u.includes("/upload/")) {
|
|
90
|
-
const key = u.split("/upload/")[1];
|
|
91
|
-
if (init.method === "DELETE") return r2.delete(key), { ok: true };
|
|
92
|
-
sealedSeen = init.headers["X-Sealed-Dek"];
|
|
93
|
-
r2.set(key, new Uint8Array(init.body));
|
|
94
|
-
return { ok: true };
|
|
95
|
-
}
|
|
96
|
-
return { ok: false, status: 404 };
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const rid = keccak256(new Uint8Array([1]));
|
|
100
|
-
/** Decrypt one stored chunk the way any buyer must. */
|
|
101
|
-
const read = async (i) => {
|
|
102
|
-
const dek = unseal(hexToBytes(sealedSeen), workerSecret, rid);
|
|
103
|
-
const key = await crypto.subtle.importKey("raw", dek, "AES-GCM", false, ["decrypt"]);
|
|
104
|
-
const body = r2.get(chunkKey(rid, i));
|
|
105
|
-
return unpack(new Uint8Array(await crypto.subtle.decrypt(
|
|
106
|
-
{ name: "AES-GCM", iv: body.subarray(0, NONCE_LEN), additionalData: aadFor(i) },
|
|
107
|
-
key, body.subarray(NONCE_LEN),
|
|
108
|
-
)));
|
|
109
|
-
};
|
|
110
|
-
const same = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
// Random bytes: 2.5 chunks → a ragged tail, and gzip must LOSE, so these
|
|
114
|
-
// exercise pack()'s store-raw branch.
|
|
115
|
-
const bytes = crypto.getRandomValues(new Uint8Array(2500));
|
|
116
|
-
writeFileSync(tmp, bytes);
|
|
117
|
-
const out = await encryptAndUpload({ file: tmp, resourceId: rid, workerUrl: "http://stub", chunkSize: CHUNK });
|
|
118
|
-
if (out.chunks !== 3) throw new Error(`chunk count: got ${out.chunks}, want 3`);
|
|
119
|
-
if (out.plaintextHash !== `0x${createHash("sha256").update(bytes).digest("hex")}`) throw new Error("streamed hash != whole-file sha256");
|
|
120
|
-
if (!r2.has(rid)) throw new Error("chunk 0 must land on the resourceId — that is where /access looks");
|
|
121
|
-
if (r2.size !== 3) throw new Error(`chunk keys collide: ${r2.size} objects for 3 chunks`);
|
|
122
|
-
// nonce(12) + flag(1) + ct(500) + tag(16)
|
|
123
|
-
if (r2.get(chunkKey(rid, 2)).length !== 12 + 1 + 500 + 16) throw new Error("ragged last chunk is the wrong size");
|
|
124
|
-
|
|
125
|
-
const back = new Uint8Array([...await read(0), ...await read(1), ...await read(2)]);
|
|
126
|
-
if (!same(back, bytes)) throw new Error("round trip corrupted the file");
|
|
127
|
-
|
|
128
|
-
// AAD binds each chunk to its index: a chunk decrypted at the wrong index
|
|
129
|
-
// must FAIL, not silently hand back scrambled bytes.
|
|
130
|
-
let swallowed = false;
|
|
131
|
-
try {
|
|
132
|
-
const dek = unseal(hexToBytes(sealedSeen), workerSecret, rid);
|
|
133
|
-
const key = await crypto.subtle.importKey("raw", dek, "AES-GCM", false, ["decrypt"]);
|
|
134
|
-
const body = r2.get(chunkKey(rid, 0));
|
|
135
|
-
await crypto.subtle.decrypt({ name: "AES-GCM", iv: body.subarray(0, NONCE_LEN), additionalData: aadFor(1) }, key, body.subarray(NONCE_LEN));
|
|
136
|
-
swallowed = true;
|
|
137
|
-
} catch { /* expected */ }
|
|
138
|
-
if (swallowed) throw new Error("a chunk decrypted under the wrong index — AAD binding is broken");
|
|
139
|
-
|
|
140
|
-
// Compressible payload: pack()'s other branch. Stored bytes must come in
|
|
141
|
-
// well under the plaintext and still round trip.
|
|
142
|
-
r2.clear();
|
|
143
|
-
const text = new Uint8Array(3000).fill(65);
|
|
144
|
-
writeFileSync(tmp, text);
|
|
145
|
-
const packed = await encryptAndUpload({ file: tmp, resourceId: rid, workerUrl: "http://stub", chunkSize: CHUNK });
|
|
146
|
-
const stored = [...r2.values()].reduce((n, b) => n + b.length, 0);
|
|
147
|
-
if (stored > text.length / 2) throw new Error(`compressible payload barely shrank: ${stored} of ${text.length}`);
|
|
148
|
-
const gzBack = new Uint8Array([...await read(0), ...await read(1), ...await read(2)]);
|
|
149
|
-
if (!same(gzBack, text)) throw new Error("gzip round trip corrupted the file");
|
|
150
|
-
|
|
151
|
-
// Delete frees every chunk, and a worker that refuses RAISES rather than
|
|
152
|
-
// reporting freed space the operator is still billed for.
|
|
153
|
-
if (await deleteResource({ workerUrl: "http://stub", uploadToken: "t", resourceId: rid, chunks: packed.chunks }) !== packed.chunks) throw new Error("deleteResource miscounted");
|
|
154
|
-
if (r2.size !== 0) throw new Error(`delete left ${r2.size} chunk(s) behind`);
|
|
155
|
-
globalThis.fetch = async () => ({ ok: false, status: 401, statusText: "Unauthorized" });
|
|
156
|
-
let quiet = false;
|
|
157
|
-
try { await deleteResource({ workerUrl: "http://stub", resourceId: rid, chunks: 2 }); quiet = true; } catch { /* expected */ }
|
|
158
|
-
if (quiet) throw new Error("a refused delete was swallowed — the ledger would free bytes still in R2");
|
|
159
|
-
|
|
160
|
-
console.log(`settle.js self-check ok — round trip, hash, ragged tail, AAD index binding, gzip ${stored}/${text.length} bytes, delete`);
|
|
161
|
-
} finally {
|
|
162
|
-
globalThis.fetch = realFetch;
|
|
163
|
-
rmSync(tmp, { force: true });
|
|
164
|
-
}
|
|
165
|
-
}
|
package/src/market/terms.js
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
// Who gets paid, and how much, when something sells.
|
|
2
|
-
//
|
|
3
|
-
// An app owner registers terms; publishers join under them; a buyer pays once.
|
|
4
|
-
// This file is the arithmetic in the middle, and it is the only revenue
|
|
5
|
-
// mechanism in the whole architecture — discovery cannot be charged for (the
|
|
6
|
-
// query never leaves the client and the index holds 4 KB per corpus), so a take
|
|
7
|
-
// at settlement is the business model or there isn't one.
|
|
8
|
-
//
|
|
9
|
-
// Two layers, because they are set by different people at different times:
|
|
10
|
-
//
|
|
11
|
-
// app terms the owner's cut, fixed when the app is registered and hashed so
|
|
12
|
-
// a publisher can prove what they agreed to
|
|
13
|
-
// lineage what a publisher owes UPSTREAM, declared per resource when they
|
|
14
|
-
// publish. Paid out of the publisher's share, never the app's:
|
|
15
|
-
// a derivative's author pays their sources, and the app's cut does
|
|
16
|
-
// not shrink because someone built on someone else.
|
|
17
|
-
//
|
|
18
|
-
// That second layer is the point. An agent buys three corpora, synthesises a
|
|
19
|
-
// fourth, publishes it with lineage naming the three, and every later sale of
|
|
20
|
-
// the derivative pays back up the chain. Knowledge in, concept out, concept
|
|
21
|
-
// becomes knowledge someone else builds on — with the money following the
|
|
22
|
-
// citations instead of stopping at the last hop.
|
|
23
|
-
//
|
|
24
|
-
// ponytail: BigInt base units, one level of lineage, no recursion. A derivative
|
|
25
|
-
// OF a derivative declares its own immediate sources and each hop settles on its
|
|
26
|
-
// own sale — walking the tree at settlement time would mean fetching a stranger's
|
|
27
|
-
// terms mid-payment, and a payment that depends on someone else's server being
|
|
28
|
-
// up is a payment that fails.
|
|
29
|
-
|
|
30
|
-
const BPS = 10000n;
|
|
31
|
-
const ADDR = /^0x[0-9a-fA-F]{40}$/;
|
|
32
|
-
|
|
33
|
-
const addr = (a) => {
|
|
34
|
-
const s = String(a ?? "").toLowerCase();
|
|
35
|
-
if (!ADDR.test(s)) throw new Error(`not an address: ${a}`);
|
|
36
|
-
return s;
|
|
37
|
-
};
|
|
38
|
-
const bps = (n, what) => {
|
|
39
|
-
const v = BigInt(n ?? 0);
|
|
40
|
-
if (v < 0n || v > BPS) throw new Error(`${what} must be 0..10000 bps, got ${n}`);
|
|
41
|
-
return v;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* An app's terms, canonical.
|
|
46
|
-
*
|
|
47
|
-
* Canonical because the hash is the thing a publisher agrees to and a buyer can
|
|
48
|
-
* check: same terms must produce the same bytes on any machine, in any key
|
|
49
|
-
* order, forever. So: fixed field order, lowercase addresses, decimal strings
|
|
50
|
-
* for numbers (JSON has no integers), and nothing else in the object.
|
|
51
|
-
*/
|
|
52
|
-
export function appTerms({ appId, owner, appBps = 0, currency = "USDC", chainId = 421614 }) {
|
|
53
|
-
if (!appId) throw new Error("terms need an appId");
|
|
54
|
-
return {
|
|
55
|
-
v: 1,
|
|
56
|
-
appId: String(appId),
|
|
57
|
-
owner: addr(owner),
|
|
58
|
-
appBps: String(bps(appBps, "appBps")),
|
|
59
|
-
currency: String(currency),
|
|
60
|
-
chainId: Number(chainId),
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Canonical bytes → sha-256. Async because that is the only hash both node and
|
|
65
|
-
* a browser have without a dependency. */
|
|
66
|
-
export async function termsHash(t) {
|
|
67
|
-
const canon = JSON.stringify(appTerms(t));
|
|
68
|
-
const d = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canon));
|
|
69
|
-
return `0x${[...new Uint8Array(d)].map((b) => b.toString(16).padStart(2, "0")).join("")}`;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** What a publisher owes upstream for one resource. Deduped by payee — the same
|
|
73
|
-
* source cited twice is one debt, not two, and merging is the only reading that
|
|
74
|
-
* cannot be gamed by repeating an entry. */
|
|
75
|
-
export function lineage(entries = []) {
|
|
76
|
-
const by = new Map();
|
|
77
|
-
for (const e of entries) {
|
|
78
|
-
const to = addr(e.to);
|
|
79
|
-
by.set(to, { to, bps: (by.get(to)?.bps ?? 0n) + bps(e.bps, "lineage bps"), note: e.note ?? by.get(to)?.note ?? "" });
|
|
80
|
-
}
|
|
81
|
-
const out = [...by.values()];
|
|
82
|
-
const total = out.reduce((s, e) => s + e.bps, 0n);
|
|
83
|
-
if (total > BPS) throw new Error(`lineage owes ${total} bps of the publisher's share — more than all of it`);
|
|
84
|
-
return out.map((e) => ({ to: e.to, bps: String(e.bps), ...(e.note ? { note: e.note } : {}) }));
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* One payment → the payouts it becomes.
|
|
89
|
-
*
|
|
90
|
-
* Integer base units end to end, and the dust is given to the publisher rather
|
|
91
|
-
* than rounded away: the payouts MUST sum to exactly what the buyer paid, or
|
|
92
|
-
* settlement is inventing or destroying money. Every share rounds down; the
|
|
93
|
-
* publisher absorbs the difference, which is at most one base unit per payee.
|
|
94
|
-
*/
|
|
95
|
-
export function splitPayment(amount, { terms, publisher, lineage: up = [] } = {}) {
|
|
96
|
-
const total = BigInt(amount);
|
|
97
|
-
if (total < 0n) throw new Error("amount must not be negative");
|
|
98
|
-
const t = appTerms(terms);
|
|
99
|
-
const pub = addr(publisher);
|
|
100
|
-
|
|
101
|
-
const payouts = [];
|
|
102
|
-
const appCut = (total * BigInt(t.appBps)) / BPS;
|
|
103
|
-
if (appCut > 0n) payouts.push({ to: t.owner, amount: appCut, why: `app ${t.appId} (${t.appBps} bps)` });
|
|
104
|
-
|
|
105
|
-
// Everything the app did not take is the publisher's, and upstream is paid
|
|
106
|
-
// out of that — so a publisher who cites nobody keeps it all, and one who
|
|
107
|
-
// builds on three sources pays them from their own half.
|
|
108
|
-
const publisherPool = total - appCut;
|
|
109
|
-
let owed = 0n;
|
|
110
|
-
for (const e of lineage(up)) {
|
|
111
|
-
const cut = (publisherPool * BigInt(e.bps)) / BPS;
|
|
112
|
-
if (cut === 0n) continue;
|
|
113
|
-
owed += cut;
|
|
114
|
-
payouts.push({ to: e.to, amount: cut, why: `upstream ${e.note || e.to.slice(0, 10)} (${e.bps} bps of the publisher's share)` });
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const keeps = publisherPool - owed;
|
|
118
|
-
payouts.push({ to: pub, amount: keeps, why: "publisher" });
|
|
119
|
-
|
|
120
|
-
// Merge payees — an app owner who is also the publisher gets one payout, not
|
|
121
|
-
// two, and a downstream reconciliation that sees the same address twice has
|
|
122
|
-
// no way to tell a split from a double-pay.
|
|
123
|
-
const by = new Map();
|
|
124
|
-
for (const p of payouts) {
|
|
125
|
-
const at = by.get(p.to);
|
|
126
|
-
by.set(p.to, at ? { ...at, amount: at.amount + p.amount, why: `${at.why} + ${p.why}` } : p);
|
|
127
|
-
}
|
|
128
|
-
const merged = [...by.values()];
|
|
129
|
-
const sum = merged.reduce((s, p) => s + p.amount, 0n);
|
|
130
|
-
if (sum !== total) throw new Error(`payouts sum to ${sum}, buyer paid ${total}`);
|
|
131
|
-
return merged;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/** Human-readable, for a receipt or a quote. 6-decimal base units — every asset
|
|
135
|
-
* this settles on is 6-decimal, and a bare "50000" on a buy button is not a
|
|
136
|
-
* price anyone reads. */
|
|
137
|
-
export const label = (base, currency = "USDC") => `${(Number(base) / 1e6).toFixed(2)} ${currency}`;
|
|
138
|
-
|
|
139
|
-
// ── self-check: `node src/market/terms.js` ────────────────────────────────────
|
|
140
|
-
if (typeof process !== "undefined" && import.meta.url === `file://${process.argv[1]}` && process.argv[1].endsWith("/terms.js")) {
|
|
141
|
-
const A = "0x" + "a".repeat(40); // app owner
|
|
142
|
-
const P = "0x" + "b".repeat(40); // publisher
|
|
143
|
-
const S1 = "0x" + "c".repeat(40); // a source
|
|
144
|
-
const S2 = "0x" + "d".repeat(40); // another
|
|
145
|
-
const T = { appId: "fangorn.tv", owner: A, appBps: 1000 };
|
|
146
|
-
const at = (ps, to) => ps.find((p) => p.to === to)?.amount ?? 0n;
|
|
147
|
-
|
|
148
|
-
// The plain case: 10% to the app, the rest to whoever published it.
|
|
149
|
-
let ps = splitPayment(1_000_000n, { terms: T, publisher: P });
|
|
150
|
-
if (at(ps, A) !== 100_000n || at(ps, P) !== 900_000n) throw new Error(`plain split wrong: ${JSON.stringify(ps, (k, v) => typeof v === "bigint" ? String(v) : v)}`);
|
|
151
|
-
|
|
152
|
-
// A derivative: two sources, 20% and 5% OF THE PUBLISHER'S SHARE. The app's
|
|
153
|
-
// cut must not move — that is the whole reason lineage comes out of the
|
|
154
|
-
// publisher's half.
|
|
155
|
-
ps = splitPayment(1_000_000n, { terms: T, publisher: P, lineage: [{ to: S1, bps: 2000 }, { to: S2, bps: 500 }] });
|
|
156
|
-
if (at(ps, A) !== 100_000n) throw new Error("the app's cut must not shrink because the publisher cited someone");
|
|
157
|
-
if (at(ps, S1) !== 180_000n || at(ps, S2) !== 45_000n) throw new Error(`upstream is a share of 900000: ${at(ps, S1)}/${at(ps, S2)}`);
|
|
158
|
-
if (at(ps, P) !== 675_000n) throw new Error(`publisher keeps the rest: ${at(ps, P)}`);
|
|
159
|
-
|
|
160
|
-
// Money is neither created nor destroyed, at any amount.
|
|
161
|
-
for (const amt of [0n, 1n, 3n, 7n, 999n, 1_000_001n, 123_456_789n]) {
|
|
162
|
-
const out = splitPayment(amt, { terms: T, publisher: P, lineage: [{ to: S1, bps: 3333 }, { to: S2, bps: 3333 }] });
|
|
163
|
-
const sum = out.reduce((s, p) => s + p.amount, 0n);
|
|
164
|
-
if (sum !== amt) throw new Error(`${amt} split into ${sum}`);
|
|
165
|
-
if (out.some((p) => p.amount < 0n)) throw new Error(`negative payout at ${amt}`);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Dust goes to the publisher, never to rounding.
|
|
169
|
-
ps = splitPayment(7n, { terms: { ...T, appBps: 3333 }, publisher: P });
|
|
170
|
-
if (at(ps, A) !== 2n || at(ps, P) !== 5n) throw new Error(`dust must land on the publisher: ${JSON.stringify(ps.map((p) => [p.to.slice(0, 4), String(p.amount)]))}`);
|
|
171
|
-
|
|
172
|
-
// One payee, one payout: an app owner who publishes under their own app.
|
|
173
|
-
ps = splitPayment(1_000_000n, { terms: T, publisher: A });
|
|
174
|
-
if (ps.length !== 1 || at(ps, A) !== 1_000_000n) throw new Error("the same address must be paid once, not twice");
|
|
175
|
-
|
|
176
|
-
// Citing the same source twice is one debt.
|
|
177
|
-
ps = splitPayment(1_000_000n, { terms: T, publisher: P, lineage: [{ to: S1, bps: 1000 }, { to: S1, bps: 1000 }] });
|
|
178
|
-
if (at(ps, S1) !== 180_000n) throw new Error(`a repeated citation must merge, not double-pay: ${at(ps, S1)}`);
|
|
179
|
-
|
|
180
|
-
// Terms are refused, not silently clamped.
|
|
181
|
-
for (const bad of [{ ...T, appBps: 10001 }, { ...T, appBps: -1 }, { ...T, owner: "0xnope" }, { appId: "", owner: A }]) {
|
|
182
|
-
let threw = false;
|
|
183
|
-
try { splitPayment(1n, { terms: bad, publisher: P }); } catch { threw = true; }
|
|
184
|
-
if (!threw) throw new Error(`bad terms must throw: ${JSON.stringify(bad)}`);
|
|
185
|
-
}
|
|
186
|
-
let threw = false;
|
|
187
|
-
try { lineage([{ to: S1, bps: 6000 }, { to: S2, bps: 6000 }]); } catch { threw = true; }
|
|
188
|
-
if (!threw) throw new Error("a publisher cannot owe more than they have");
|
|
189
|
-
|
|
190
|
-
// The hash is what a publisher agreed to: stable across key order, and it
|
|
191
|
-
// moves the moment a single term does.
|
|
192
|
-
const h1 = await termsHash(T);
|
|
193
|
-
const h2 = await termsHash({ owner: A, appBps: 1000, appId: "fangorn.tv" });
|
|
194
|
-
if (h1 !== h2) throw new Error("key order must not change the hash");
|
|
195
|
-
if (h1 === await termsHash({ ...T, appBps: 1001 })) throw new Error("a changed cut must change the hash");
|
|
196
|
-
if (!/^0x[0-9a-f]{64}$/.test(h1)) throw new Error(`hash shape: ${h1}`);
|
|
197
|
-
|
|
198
|
-
console.log("terms.js self-check ok — app cut fixed, lineage paid from the publisher's share, payouts always sum to what was paid, "
|
|
199
|
-
+ "dust to the publisher, repeated payees merged, bad terms refused, terms hash canonical");
|
|
200
|
-
}
|