diff --git a/bunny_js/README.md b/bunny_js/README.md index a7463a9b8..a3aa464b1 100644 --- a/bunny_js/README.md +++ b/bunny_js/README.md @@ -11,9 +11,13 @@ Four Bunny Edge Scripts, each deployed as **one bundled file**: All four are live. Three of them are **sellers** — unpaid requests get the honest `402` + `Payment-Required` challenge; retry with the payment attached settles and returns the -result. The channel state lives in the shared S3 bucket, claimed by `scw_js`'s -`llmx402cron` (the edge has no cron), which is also why `x402_facilitator/` keeps only -`walletreportcron`. +result. The sellers' 402 lists **two schemes**: batch-settlement channel entries first +(the preferred way to pay), plus an appended **exact alternative** — one EIP-3009 signature +per call, flat $0.01 USDC on Base mainnet — for stock clients without channel wiring; the +flat price is decoupled from the channel prices (see `withExactAlternative` in +[`src/x402_server.ts`](./src/x402_server.ts) for the fee rationale). The channel state lives +in the shared S3 bucket, claimed by `scw_js`'s `llmx402cron` (the edge has no cron), which is +also why `x402_facilitator/` keeps only `walletreportcron`. The sellers are ERC-8004-registered agents on Base. Every origin serves: diff --git a/bunny_js/src/discovery.ts b/bunny_js/src/discovery.ts index 4b488eb95..9197c4eae 100644 --- a/bunny_js/src/discovery.ts +++ b/bunny_js/src/discovery.ts @@ -40,7 +40,7 @@ export function buildRootPage(input: RootPageInput): string { - + ${escapeHtml(input.title)} diff --git a/bunny_js/src/favicon.ts b/bunny_js/src/favicon.ts index 6693e3ff5..922b31605 100644 --- a/bunny_js/src/favicon.ts +++ b/bunny_js/src/favicon.ts @@ -1,3 +1,3 @@ export const faviconBase64 = - "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAQABAADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDLopTSV9EeSJS0UUxBSUtFABRRRQAUUUUAJRS4ooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACkpaKAEopaKACilpKACiiigAoopcUAJRRRQAUUUUAFJS0UAFFFFACUUtFABRRRQAlFLRQAUUUUAFJS0UAFFFFABRRRQAlLRRQAUUUUAFFFFABRRRQAlFLRQAUUUUAFFFFABRRRQAlFLRQAUUUUAJS0lLQAUUUUAFFFFABRRRQAUUUUAFJS0UAJRS0UAFFFFACUtFFACUUtFACUtFFABRRRQAUUUUAJRS0UAJS0UUAFFFFABRRRQAUUUUAFJS0UAJRS0UAJRS0UAFFFKKQAaSlNFACUUUUwCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKWgBKKWkoAKKKKACiiigAooooAWkoooAKKKWgBKKKKAFpKWg0AJRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRS0AJRRRQAUUUtACUUGigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKWiigAooopABpKKKYBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFGaKKAFpKKKACiiigAooooAKKKWgApKWigBKKKKACilooAKQ0tJQAUUUUAFFFFABRRRQAUUUUAFFFLQAlFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFLQAlLRRQAlFFFABRS0lABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUtJQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUooAKSlNFIBKKKKYBRRRQAUUUUAFFFFABRRRQAUUUtABSUtJQAUUUUAFFFFABRRRQAUUUUAFFFFABRRS0AJRRRQAUUtJQAUUUtABRRRQAUUUUAFJS0lABRRS0AJS0UUAJRRRigAopaMUAJRS0UAJRRRQAUUUUAFFFFAAKWiigBKKKWgBKKKKACiiigAoopcUAFJS0lABRRS0AJRS0lABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAAKWikoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigApaSloAKKKKQCUUUUwCiiigAooooAKKKKAClpKWgBKKKKACiiigAooooAKKKKACiiloASilpKACilooASilpDQAUUUUAFBopaAEopaKAEopaKAEopaSgBaSlooAKSlpKACiiigApaSloAKKSloAM0UlLQAUlLSUAFLSUtABSUtJQAUUUUAFFFFABRRRQMKKKKAFooooEFJS0lAC0UUUAFJS0lABRRRQAUtJRQAUUUUALSUtIaACiiigApaKKACkpaKAEooooAWikooAKKKKACiiigAooooAKKKKAClpKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKWkooAKKKKACiiigAooooAKKKWgBKWilFACUUUUgEooopgFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUtJRQAtJS0lABRRRQAZooooAKKKKACiiigApaSigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACilooASloooAKSlpKACiiigAooooAKKWigBKKWkoAWikpaACkpaSgAooooAKKKKACiiigAooooAKWkpaACkpaKAEooooGFLSUUCCiiigAooooAKKWigBKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooASloooAKKKKACiiigBaKSloAKKKKQCUUUUwCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKBhRRRQIKKWkoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAopaKAEooooAWkoooAWkpaMUAJRS0UAJRRRQAtFJS0AFFFFACUUUUAFFLRQAlFFFABRRRQAUUUUAFFFFABS0lFAC0lLRQAlFFFABRRRQAUUUYoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKWkNABRRRQAUUUUAFFFFABRRRQAUtJSigBKWiikAlFFFMAooooAKKKKACiiigAooooAKWkpaACiikoAWkpaSgAopcUlABS0lLQAlFFFABRRRQAUUUUAFFGKKACiiigAooooAKKKKACilooASloooASilooASiiigAoopaACiiigAopKKACilpKACiiigYtFJS0hBSGlooASiilpgJRRRQAUUUUAFFFFAC0UUlAC0lLSUAFFFFABRRRQAUUUUAFFFFAC0lFFABRRRQAUUUtACUtJRQAtJS0lABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUALSUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABS0lKKACiiikAlFLSUwCiiigAooooAKKKKACiiigAooooAKKKKACiiigBaKKKAEopaSgAooooAKKKKACiiigBaSiigAooooAKKKKACiiloAKKKKACiiigAooooASiiigAopaKAEoopaAEopaSgBaSiigAooooGFLSUtAgpKKKACjNFFABRRRQAUUUUAFFFFAC0GiigBKKKKACiiigAooooAKKWjFACUUUUAFFFFABRRRQMKKKKBBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRS0AFFFFIAopKKYBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAC0lFLQAlFLSUALmkoooAKKKKACiiigAooooAKWkooAWkpaKAEopaSgAooooAWiiigAooooAKKKKAEooooAWiiigBKWkooAWkpaSgAooooAKKWigBKKWkNABRRRQAUUUYoAKKWigBKWkpaACkoooAWkoooAKKKKACiiigAooooAWikooAWkpaSgAopaSgAooooAKKWkoAKKKKACiiigAoopaACkpaSgAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAClpKUUAFFFFIBKKKKYBRRRQAopKWkoAKKKKACiiigAooooAKKKKAClpKKACiiigAooooAKKKKACiiigAooooAKKKKAFopKXNABSUtJQAUUUUAFFFFABRRRQAUUUUAFFLSUAFFFFABRRRQAUUUUAFLRRQAUUUlAC0lLRQAlLRRQAUUUGgBKKKWgBKKKKACiiigYUUuKSgQUUUUAFFFFABRRRQAUUUUAFFBooAWkpaQ0AFFFFABRRRQAUUUUAFFFFABRRRQAUUtJQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAwpwpKKBBRRRSASiiimAUUUUAFFFFABRRRQAUUUUAFLSUtABSUUUAFFLRQAlFFFABRRRQAUUUUAFFFFABRRS0AFFFFACUUtJQAtJS0lABRRRQAUUUUALRSUtACUUUUAFFFFABRS0lABRRS0AJRRRQAtFFFAAaSlpKAFooooASilpKACiiloASilpKACilpKBhRRRQIWkoooAKWkooAKKKKAFopKWgAoopDQAUUUUALRSUtACUUtJQAUUUUDCiiigQUtFFABSUtJQAtJRRQAUUUUAFFLSUAFFFFABRRRQAUUUUAFFFFABRRS0AJRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUtJS0AFFFFIBKKWkpgFFFFABRRRQAUUtJQAUUUUAFFFFABRRS0AFFFJQAUUUUAFLSUUAFFFFABRS0UAJRS0hoAWikpaAEooooAUUUUUAFJS0UAJRSmkoAKKKKACiiigYUUUtAgooooAKKKKACiiigAooooAKSlooAKSlooASilpKACiiloASiiigBaSiigAopaKAEooooAKKKKAFpKWkoAKKKKAFpKKKAFoNFFACUtJRQAtJS0UAJRS0mKACilooASiiigBaSiigAooooAKKKKAFpDRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUtJS0AFFFFIBKKKWmAlFFFABRRRQAUUUtACUUUUAFFLSUAFLSUUALSUtJQAUUUUAFFFFABRRRQAUUUUAGaKKKAClpKKACiiigAopaKACiikoAKKKWgAooooAKKKKACiikNAC0UlLQAUUlFAC0lLRQAUUUUAFFFJQAtFFFABSUUUALRSZpc0AJRRRQAUtAooAKKKKACkpaQ0ALSUUUAFFFFABRRRQAUUUtABRRSUAFFFFABRRRQAtFJS0AIaKKKACiiloASiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigApaKKACiiikAUlFFMAooooAKKKKACilooASiiigAooooAWiiigAoNFFACUUUUAFFFFABQaKWgBKKKKACiiigAooooAKWkooAKKKKAFpKKKACiiigBaSlooAKQ0tJQAUUUUALSUUtACUUUUALRRRQAUlLRQAUlFFAC0lLRQAlFFFABRRRQAUUUUAFFFFAC0UUUAJRRRQAUUUUAFFFFABRRRQAUtJS0AFJS0UAJRS0lABRRRQAUtFIaACiiigAooooAKKKKACiiigApaSloASiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAFpKKKACiiigAooooAKKKKACiiigAooooAKWkooGLRRRQIKKKKQCUUUUwCiiigAooooAKKKKACiiigAoopaACikpaACg0UlABRRSigBKKWigBKKWkoAKKKKACiiigAooooAKKKKACiiigAooooAKKKWgAooooAKKKKAEopaSgAooooAKKKKACiiigBaKKKAEopaSgBRRSUUAFFLSUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFLRQAlFFFABRRRQMWkpaKBBSUtJQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUALSUUUAFFFFABRS0UAJRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUtJS0AKaSlNJSAKDRRTASiiigAooooAKKBRQAUUUUAFLRRQAUlLSUAFFFFABS0lFAC0UlFAC0lLQaAEooooAKKKKACiiigAooooAKKKKACiiloASlopKAClpKKAFopKKAFpKKWgBBS0UUAJRRRQAUUUUALRRRQAUlLSUAFFFFABRRRQAUUlFADiCMZBGelJV/UohFbaaQPv2oY/99tVCkndXGFFFFMQUUUUAFFFFABRRRQAtJRRQAUUUUALSUUtACUuaSigBaSiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKWigBKWkpaAA0UUUgCiiimAlFFLQAlFFFAC0lFFABRRRQAUUUUAFFFFABRRS0AFJS0lABRRRQAtFJS0AJRRS0AJRRRQAtJRRQAUUUUAFFFFABS0lLQAUlLRQAlFFFABRRS0AJS0UUAFFFJQAUUUUAFFFFAC0lFFABRRRQAUUUUAFFFSRxGVtoZAf9tgo/M0AR0YrSh0SefBF1YID/fu0H9a2NO8ET3s6q2q6YoPa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iigAooooAKKKKAFpKKKACiiigAooooAWkoooAKKKKACiiigBaSiloASiiigAooooAKKKKACiiigAooooAKKKKACiiigAopaSgAooooAKKKKACiiigAooooAKKKKACiiigAooooAKWkpaACkpaKQCUUUUwCiiigAooooAKKKKACilpKACiiigAooooAKKWigBKWkpaACg0UlABRRS0AJRS0lABRRRQAUUtFABSUtFACUUUtABSUUUDCiiigQUUUUALQaSigApaSjNABRRRQAUUUUAFFFFABRRRQAUUUUAFKBSYq3p9jNqV7HawDLuevYDuTSbsrsaVzT8NaEdYvN0gItYiDIf7x7KK9PjRY41RFCqowAOgFVtO0+HTLKO1gGEQcnux7k1YmlSCF5ZGCogLMx6AV5dWo6kjrhHlRT1bVIdJsXuZiMjhEzyzdhXlF9ezahdyXM7bpHOT7ew9qu+INak1rUDJysCfLEnoPU+5rJrtoUeRXe5z1J8zCiiiugzCiiigAooooAKKKKAOq8Ia/8AYbgWNy3+jyn5WJ+43+B/z3r0QHNeIivQ/CGv/bIBY3T5njHyMTy6/wCI/wA964cTRt7yOilPozT8Q6JHrNkVAC3CZMT+/ofY15ZLG8MjRyIUdSQynqD6V7XXI+MdBFxC2pWy/vYx+9UfxL6/UfyqcPWs+Vjqwvqjz+kzSmkr0DmFooooAKKKKACiiigAooooAKKKKACiiigAoopaAEopaSgAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigApaKKAEooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAFoopRQAlJS0lIYUUUUxBRRRQAUUtFACUUUUAFFFFABRRiloAKSiigBaKSloAKSlooASiiigAooooAWiiigBKKKKAFoopKAFoNFFACUtFFACUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUoFADkVnYKoJYnAA716d4a0JdIs98oBupRmQ/3f9kVj+DvD+Nup3Sf9cFI/8e/wrtOlefia13yo6KUOrFzXn/jHxAbqZtOtX/cRn96wP329PoP51t+Lde/s6zNrbvi6mGMjqi9z9fSvN8VWGo/bYVZ/ZQlFLRXcc4lFLilEbnorH8KLgNop/kyf883/AO+TS+RL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U7pWBEUQPLn/AA9TVzUtRt9LsZbu6cJHGMn1J7AepNeJa5rdxrupSXc/yqfljjzwi9hV4ah7SV3sTWqcqstytqOoXGqX0t3dOXlkOT6D0A9hVSiivZSSVkee3cKKKKYgooooAWikpc0AFJS5pKACiiigAooooAKKKKACiiigAooooAKKKKACiiigDT0a68m58pj8knH0PaujPIrilJByDyK62yuBc2iSdyOfr3rCpHW5rB9DndStvs14wAwjfMtU66PWrYS2nmAfNGc/h3rnK0hK6IkrMKWkpRVkigV2fgXTd002oyLwn7uP69z/AE/GuPijaWRI0Us7EKoHcmvXtMsU07T4bVMfu1wT6nufzrlxM7Rsa0o3dy52rgfHep+bcxadG3yxfPJ/vHoPy/nXb3t3HY2c1zKcJEpY/wCFeO3dw93dS3Ehy8jFj+NYYWF5cz6GtaVlYhooor0TlCiiigAooooAKKKKACiiigAooooAKKKKACiiigAoopaAEooooAKKKKACiilNACUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFLSUooAKKKKQCUUUUwCiiigAooooAKKKKAFopKWgApKWkoAKKWigBKKKKAFpKWigAooooAKSlFFABRSUtABSUtJQAUUUUALRSUtACUUUUAFFFFABRRRQAUUUooAmtbmS0uoriI4eNgy/UV7BZXUd9ZQ3UX3JUDD29q8aruvAup74JdOkPzR/vI8+h6j8/51yYqF48yNqMrOx1GpWKajp09pJjEi4B9D2P515BNC8ErxSKVdGKsD2Ir2mvPPG+m/ZtSW8RcR3A+b2cdfzH9aywtSz5WXWj1OUpRRSgV6BzGxoNvukknI+78q/XvW27BEZieAMmq+nW/wBnsIkIwxG4/U1DrM3lWDqD80hCj+tcz96RstInO3MxuLh5W6sajpKBXRsYnsXw+cP4ThA/hkcH/vquoJABJOAOprzb4e+IbSxhn069nWEM/mRO5wvTBGe3SrXjfxjAbN9M0yZZGlGJpozkKvdQfU/y/TyKlGUqzSR3wqJU0znPGfiQ65qXkwOfsUBIjx0c92/w9vrXMUlFepTgoR5UcUpOTuwooorQkKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAWkNFFABRRRQAtbOgz/NJAe43D+v9Kxans5zbXUcvYHn6VM1dDi7M62RQ6FSMgjBrj54jBM8Z/hYiuyHIB7Guf12DZcJMBw4wfqP8isabs7Gs1pcyaUUlOUFiABkmtzE6nwTpf2nUWvZBmO3+77uen5dfyr0TGKzNA03+zNGggYYkI3yf7x6/wCFaE8yW8Ek0hwkalmPsK8qtNzmdkFyxON8eaptSLTYzycSS49Ow/r+VcLVrUr2TUdRnu5PvSNnHoOw/KqtejShyRSOacrsKKKK1ICiiigAooooAKKKWgBKKKKACiiigAooooAKKKKACiiigAooooAKKWkoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoopRQMKKKKQhKKKKYBRRRQAUUUUAFFFGKACilooAKKSloAKKKSgAooooAWiiigBKKKKAClpKWgApKKKACiiigAooooAKKKKACiiloGJRS0UCEooooAKWkooAXNXNMvn07UYLpM5jbJHqO4/KqVKKTV1Yadnc9rhlSeFJY23I6hlPqDWdr+mDVNImgAzIBvjP+0On+H41k+CNT+06c1lIf3lufl91P+B/pXV15Mk6czsTUoniDAqxBGCOxq1p0H2m9jQj5c5b6Ctbxhpn2DWGlRcRXGXGOx7j+v403QLfbHJcEfe+Vf616XOnG6OTl96xs9q53XJvMuViHRB+proZGCIWJwAMmuOnlM07yH+Jiaikru5c3pYhopTSV0GQZooooAKKKKACiiigAooooAKKKWgBKKKKACiiigAooooAKKKKACiiigAooooAKKKKACkoooAWiiigAooooA6vS5/PsIyeoG0/hSarb+fYvgfMnzD8P8ms7QZ8SyQHow3D6/5/lW+QCMGuWXuyN1qjh66Hwhpv2/WFkdcw248xvc9h+f8AKsW9tzbXkkXQA8fSvS/Cel/2do6F1xNP+8f29B+X86qvU5YaEU43kb3auU8b6n9n05bJD+8uDlvZR/if611LsFUknAA5NeSa9qR1XVprgHMYOyMf7I6fn1/GuTDQ5p37G9WVlYzTSUUV6ZyBRRRQAUUUUAFFFFABS0lFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAwooooEFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUCgBaKKKQBSUtJTAKKKKACiiigApaKSgBaKSloASilpKACiiigAooooAWiiigBKKKKACiiigAopaTFABRRRQAUtJRQAUUUtACUtFFABRRRQAlFFFABRRRQAUUUtAGnoWonS9VhuMny87ZB6qev8Aj+FesKwZQwIIIyCK8VFek+D9T+3aSIHbMtt8h917H+n4VxYuGnMjooy6FvxJpY1TS2QD97Gd6f1H5VgRQLbxLEowFGBXbEZFctqUH2e6ZcfKeVrGlN25S5LW5h6zceTYsoPzSfKP61zFXtWuvtN6wU/u4/lX+pqhXoU1ZHNJ3YUUUVZIUUUUAFFFFABRRRQAUtJRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFACUUUUALRRRQAUUUUATWk5trmOUfwnJ+neuxQh1BByCMg1xFdLoV151v5DH54+nutY1Vpc0g+hqQaNFqWp27yLlYzucf3gOg/PFdqOBxWbpFt5Vv5pHzP0+laROBXnVZuTsdMYpanPeMdT+xaM0KNiW4+QY7L3P8AT8a8zrZ8Tan/AGnrErI2YYv3cf0HU/iaxq9ChDkgc1SV2JRRRW5mFFFFABRRRQAUUUUAFFFLQAlFFFABRRRQAUUUUAFFFFABRRRQMKKWkoEFFFFABRRRQAUUUUAFFFFABS0lLQAlFFFABRRRQAUUUUAFFFFABRRS0AFFFFIBKKKKYBRRRQAUUUUAFFFFABS0lFAC0lFFABRRS0AJRS0UAFJS0UAJRRRQAUUUUAFLmkooAKKKKACiiigApaSloAKSlooAKKKKAEooooAKKKKAFpKWkoAUVv8Ag+aePxBCkILLICsg/wBnufwrAr0HwPpnkWL37r+8n+VPZB/if5VhXklB3LppuR1lc/4sXbpDSx/65eFx6d66A1zesT+fcMmcog2j+tefSXvXOqex5tSVb1C2+y3jxgfL1X6VVr1k7o4mJRRRTAKKKKACiiigBaSlpKACiiigAooooAKKKKACiilxQAlFFFABRRRQAUUUUAJRS0UAFdL4X8IT+I1lmacW9tG23ft3Fm9AMj865qux8HeMINAt5bS7hkeB38xWjwSpxg8HtxWNdzUPc3NKai5e8ZnijwxN4cuYgZRNBMD5cm3ByOoIrBrpPGHigeI7qFYImitYM7A/3mJ6k+n0rm6dHn5Fz7inZS90KKKK1ICiiigAq9o5kGqwbMkbvm/3e/6VRrovD9qI42uWHzPwv0/z/KoqP3WVFanpSABAFGFAwPpWd4hlng0G7ktx+8CfkOhP4DJqbS7kT2oB+8nyn+lXZEWSNkdQysMEHuK8n4ZanZujxKitDW9PbTNVntiuFByh9VPT/PtWca9eLTV0cT0YUUUVQgooooAKKKKACiiigAooooAKKKKACiiigAopaSgAooooAKKKKACiiigAooooAKKKKBhRRRQIKWkooAKKKKBhRRRQIKKKKACiiigAoopaACiiigANFFFIBKKU0lMAooooAKKKKACiiigAooooAKKKKAClpKWgAoopKAClpKWgAooooASilpKACiiloASilpKACilpKACilpMUALRRRQAUUUUAJRRRQMKKKKACgUtKBQIt6bYvqOoQ2qdZGwT6Dufyr16CFLeCOGNdqIoVR6AVx/gbTNscuoSLyTsj/qf6fnXZ15uJnzS5TqoxsrlDW9RTS9KmuWxuA2oPVj0/x/CuYhmW4hWUdHGap+OtT+0X0djGf3cAy/ux/wABVXQLjdbNAx5jOR9D/k1pTpWhzEud5WJNctRJbeco+aPr9K5qu2lUSRMjDhgQa4qRGjlZG6qSDXRSeljOohtFFFbGYUUUUAFFFFABRS0lABRRRQAUUUtABSUtFABRRQaAEooooAKKKKACiiigBRQaVFLsFVSzMcAAZJNd1pPw1urq2WbULr7KWGREi7mA9znA/WsqlWNP4mXGDlscHRXba38Or3T4GuLGb7XGoyybcOB7DvXFcjg06dSNRXixShKO4lFFFaEhRS0lABRRQKAJIITPPHEvVmxXZRRiKJUUYVRgCsDQYN9y0x6IOPqf8muhPArnqvWxrBdSWx1dLDVreCRsJP8AKx9PQ/nXZZrxu+ufPvZJAeAcL9BXpvh3Uv7T0eGZjmVfkk+o/wARg1zV6VkpGtOd7oy/G2mfadOW9jX95bn5vdD/AIHH6155ivapolmheJ1DI6lWB7g15Fqlg+m6jNauD8jfKfVex/KtcLO65WRVjrco0UppK7DAKKKKAFFFJRQAUUUUALRRRQAlFFFABS0lLQAUlLSUAFFFFABRRRQAUUUUAFFFFABRRS0AJRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAtFFAoAKKKKQBSUUUwCiiigAooooAKKKKACiiigAooooAKWiigBKKDRQAUUUUALRRRQAUlFFABS0lLQ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JPAAptdr4N0Ddt1S6T5R/qFPf/a/wrOpNQV2VGLk7Gz4Z0IaVaebMo+1Sj5v9kf3f8a36MYrnfFeu/2XYmCB8XcwwuOqL3b/AA/+tXme9VmdekImN4x18TyNplq/7tD++YfxN/d+g/nXGmgkk5PNJXp04KEbI5JS5ncKKKK0JCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKWiigApKKKACiiigAooooAKKKKACiiigApaSigBaSiigAooooAKKKKACiiigAooooAKKKKACiiigAoopaAEooooAKKKKAClFJSigYUUtJSEJRRRTAKKKKACiiigAooooAKWkpaACiiigApKWigBKKKXFABRRRQAUlLRQAlFFFAC0UUUAJRRRQAtFFFABSUtJQAUUUUALRRRQAlLRRQAUUUUAFFFFACGilooASilxSUAFLSUtABSUtJQAUUUUAFFFFABRRRQB1HhDxB9guPsVy+LaU/Kx6I3+B/z3r0YHNeI16F4Q8Qfa4Rp9y37+Mfu2J++o7fUfy/GuHE0ftI6KU/ss19f0WPWbAxkATplon9D6fQ15ZPDJbyvFKhR0OGU9Qa9orlfF+gfbLc39sn+kRj94oH319fqP89qjD1uV8r2HVhfVHnlJQaK9E5gooooAKKKKAClpKu6Zp02qX0drCOWPLdlHcmk3ZXY0rmn4Y0A6veebMp+yRHLn++f7v+NenIqogVQFUDAAHSq9jZQ6fZx20C7Y0GB6k9yfc1NNNHBC8srBY0UszHsBXl1ajqSOuEeVFPWNUg0mwe5mPThF7s3YV5PfX02oXkl1O2ZJDk+g9APar3iHW5Nb1Aycrbx/LEh7D1Puaya7aFLkV3uc9SfMwoooroMwooooAKKKKACiiigAooooAKKWigBKKWigBKKWigBKKKKACiiigApaSloASiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigApRSUooAXNJRRSASig0UwCiiigAooooAKKKKACilooASiiigAooooAKWkpaACiiigApKWigAooooAKSiigApaTFLQAUUUlAC0lLSUAFLSUtABRRRQAlLRRQAUUUUAFFFFABRRRQAUlLRQAUUUUAFFFFACUUtJQAUUUUAFFFFABUkE0lvMk0TlJEIZWHY1HS0PXQD1fQNYj1iwWUYEyfLKnof8DWseRXkWkanNpN8lzFyBw6Z4Yeleq2V7Df2kdzA26Nxke3sfevLr0uSV1sddOfMjgvF3h/7DP9utk/0aQ/Oo/gb/A/57Vy1e1XFvFdW7wTIHjcbWU9xXlOu6PLo9+0LAmJuYn/ALy/4104atzLle5lVhZ3Rl0UUV1mIUopKUUASRxPLIqIpZmOAB1Jr0/w5oaaPZDcAbmQZlb09h7CsjwhoIijXUrlf3jD9ypHQf3v8K7GvPxNa/uo6aULasWuB8Y+IPtMjabav+5Q/vWH8bDt9B/OtrxXr39mWn2aB/8ASphwR1RfX/D/AOtXmxOTTw1G/vsVWp0Q2iiiu85wooooAKKKKACiiigApaKKAEpaKSgBaKSigAzS0lFAC0UlFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABS0lFABRRRQAUUUUAFFFFAwooooAKKKKBBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQMKWkpRQIKKKKQBSUUUwCiiigApaSloASilpKAFopKWgApKKKACiiigApaSigBaSlpKAFooooAKSlpKACiiigApaSigAooooAWkpaKAEpaSloAKKDSUALRRRQAUhpaSgAoopaACiiigAooooAKTNLRQAUUlFAC0lFFAC0UUlAC0UlLQAUUUUAKDXQ+F9eOl3fkTt/osp+Yn+A+v8AjXO0ucGonBTVmVF2d0e2AggEdDWbrekRaxYPbvhZB80b/wB1v8KwPBuv+co0y5f51H7lj3H938K7GvLlF05nWmpo8Wubaa0uHgnQpIhwwNRV6J4x0L7bam/t0/0iEfOAPvr/AIj/AD2rzzFelSqKcbnLOPKxK6XwroH9p3X2m4X/AESI8g/8tG9Pp61iafYT6nfR2sC5ZzyeyjuTXrllaRWNnFbQriONdo/xrLEVeVWW5VKF3dk4AAwBgVS1fU4tJ097mXkjhVHVm7CrrMFUsxAAGST2rzzxHq39raa86f6hL3y4uOqhOv4nJrjpQ55a7G85cqOdvbyW/vJLmdsySHJ9vYe1QUmaK9VKysjjCiiimAUUUUAFFFFABRRSmgBKKWkoAWkoooAWkopaAEopaSgAooooAKKKKACilooASilooASiiigAooooAKKKKACiig0ALSUUUAFFFGKACiiigAooooGFFFFAgooooAKKKKBhRS0UCEopaKAEooooAKKKKACiiigAoFFAoAWiiikAlFFFMAooooAKKKWgApKWkoAKKBS0AJRRRQAUUUUAFFFFABRS0UAFFFFABRRRQAUUUUAJRRRQAtFFFABRRRQAUlFFABRRRQAtFIKWgAooooAKKKKACiiigAooooAKKKKACkpaSgAopaKAEoNFFAC0UUUAFJS0lABRRRQA+KV4ZUkjYq6EMpHUEV6xoOrprOmJOMCVfllUdm/wPWvJK0dF1ifRb0TxfMh4kjJwHH+PvWFelzx03NKc+V6nrxGaxLvwjpF5cGZ4XRm6iJ9oP4Vb0zWbLVoRJbSgtj5o24ZfqK0M15t5QfY6rKRTsNJstMjK2kCx56t1Y/UmrmKXsSeAOp9Kx9U1u3it3js9R09bnpmWcYX34zk0kpSYNqKMHxr4h8hG0u1f94w/fMOw/u/4/wD66wfs2fAouCcAX4UD1+Sp4/Cct9MXOs6W0kjZZjd7iSe/Su+/4Q23PhBdGaf5gfN8/HHmdc49O30rq9pCkkkY8kp3Z49ilrprzwc9oxB1nSj7NcbT+VZE+kvDnN/pz4/uXIP9K7I1Iy2Zi4tbmfRQRtYjIOO4OaK0JCiiigAooooAKKKKBhRRRQIKKWkoAKKKKADNLSUtABSUUUAFFFFAC0UZooAKSlooAKSlpKAFpKKKACiiigAooooAKKKKAFopKM0AFFFFABRRRQAUUUUAFFLSUAFFFFABS0UhoAWikpaAEopaSgAooooAKKKKACloooAKKU9aSkAlFFFMAooooAKKKKAFpKKKAClpKKACiiigAoopaAEopaKACiiigApKKKAFopKWgApKWkoAKKWigBKKXFFACUUUUAFFFFAC0UUUAFFFFABSUtFABRRRQAUUUUAFFFFABRRRQAmaKWkoAWikpaACiiigAooooAKSlooASilooASiiigByO8bh0Yqw6EHBFaKeIdXjXauo3GPd8/zrMoqXFPdDu0W7nVL68GLm8nlH915CR+XSqmaKKaSWwXErul164/4VkyNKTL532QN32dcf988VwtannH/AIRXyc8fbt2P+2dZVYKVr9yoSauZmaM0UVqQFFFFMAooooAKKKWgBKKWigAooooAKSlpKACiiloASiiigAooooAKKKKACiijFAC0UlLQAUlLSUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABS0lLQAUUUUAJRRRQAUtJS0AJRS0lABRRRQAUUUUAFFFFAC0UUUAFFFFIBKKKKYBRRRQAUUUUAFFLRQAlFFFABRRRQAtFFJQAtFFFABSUUUAFFFLQAlFLRQAUlLSUAFLRRQAUlFFABRRRQAUUUUAFLSUUAFLRRQAlFLRQAUUUUAFFFJQAtFFFABRRRQAUlFFABRS0UAFFFIaADNFFFABRRRQAZopaKAEoopaAEopaSgAooooAKm84fYvIwc+Zv8A0x/jUNFABRRRQAUUUUAFFFFABRRmigAozRS0AFFFFABSUtJQAtJRRQAtJS0UAJRRRQAUUUUAFFLSUAFLSUUAFFFFABRRRQAUUUUAFFFFABRRRQMKKKKBBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABS0lFABRRRQAUUUUAFFFLQAUUUUAFFFFIBKKKKYBRRRQAUUUUALSUUtACUUUUAFFLSUAFFFFAC0UlLQAlFFFABRRS0AFFFFACUUtFABSUtJQAUUtJQAUUUUAFFFFABRRRQAtFFFABRRSUALRSUtABSUtJQAtFIKWgAoopKACiiigAooooAWkoooAKKKKACiiigBaQ0UGgApaSigBaSiigAooooAKKKKACiiigAooooAKKKKACiiloAKSiigAooooAKKKKACiiloAKKKKAEpaKKACkoooAKKWigBKKKKACiiigAopaSgAooooAKKKKACiiigAooooAKWkpaACkpaKAEopaKAEooooAWkopaAEopaSgAooooAKKKKACiiigAooooAKUUlKKACiiikAlFFFMAooooAKKKKACiiigAoopaACkpaSgAopaSgAooooAWkoooAKWkpaACiiigAooooAKSlpKACiiigAopaKACkpaKAEooooAKWiigBKKWkoAKWiigApKWigBKWiigApKWkoAKKKKACiiigApaKKACiiigAooooAKSlooASilpKAClpKWgApKWigBKKWkNABRRRQAUUUtACUUUUDCiilxQISlpKKACiiigAopaSgAooooAKWkFLQAUUUUAJS0UUAFFFFACUUUUAFFLSUAFFFFABRRRigAooooAKKKKACiiigAooooAKWikNABRRRQAUUUUAFFLRQAUUUUAJRRRQAUUUUAFFFFABRRRQAUtJSigAooopAFJRRTAKKKKACiiigAooooAKKKKACiiigBaSlooASiiigAooooAKKKKACiiigAooooAKKKKACiiigAopaKACiiigBKWiigBKWkpaACiiigAooooAKKKKACiiigAooooASilpKAClpKKAFooooAKKKKACiiigAozRRQAlFLSUAFFLRQAUUUUAFJS0lABRRS0AFFFFABSGlooASlpKWgBKKWigBKKKKACilooASiiloABRSUtABSUUtACUuaSigBc0lFFABS0UlAC0UZooASiiigAzS0lFABRRRQAUUUUAFFFFABRRRQMWkoooEFFFFABRRRQAtFFJQAUUUUAFFFFABRRRQAUUUUAFFFFABS0UUAFFFFIBKKWkpgFFFFABRRRQAYopaSgAopcUlABQKKKAFpKWigBKKKWgBKKKKACiiloASiiigAopaSgAooooAKKKKACiiigBaKKKACiiigApKWigApKWigBKKKKACloooAKKKKAEpaKKACkpaKAEopaKAAUUUUAFFFFABRRRQAUUUUAJRS0UAFFFFABRRRQAUUUUAFFFFABRRSUALSUUUAFFFFAC0lLSUALSUtJQAtFFFACGloooAKSlooASilooASilooASlpKKAFpKKKACiiigAooooAKKKWgBKKKKACiiigAooooAKKKKACiiigAopaKAEooooAKKKKACiiigAooooAKKKKACiiigAooooAWikpaACiiikMSilpKYgooooAKKWigBKKKKACiiloAKSlpKAFopKKAFooooASiiigBaKKKAA0lLSUAFFFFABRRRQAUUUYoAKWiigBKWiigAooooAKKKKACigUUAFJilpKAFooooAKKKKACkpaSgApaSloAKKSigBaKKKACiiigAooooAKKKKAEooooAWkpaKACiiigAooooAKKSloAKSlpKAFpKKKACiiloAKKKKACkpaKACkpaSgBaSiigBaSlpKAFopKKAFoopDQAUUUUAFFFFABRRS0AJRRRQAUUUUALSUtFACUUUUAFFFFABRRRQAUtJRQAtJRRQAUUUUAFFFFABRRRQAtJS0lABRRRQAUUUUAFFFFABS0UUAFFLSUgCkoopgFFFFAC0lLSUAFFFFABRRS0AJRS0lABRRRQAtFJS0AFJS0UAFJS0UAFJS0UAJRS0UAFJS0lAC0UlLQAUUUUAJmlpKKAFooooAKKKKACiiigApKKKAFpKWigBKKWigBKKKWgAooooAQ0UUUALSUUYoAKWiigAooooAKKKQ0AFFFLQAUUUlAC5pKKKACiiigAooooAWiiigApKWkoAKKKWgAooooAKSlooAKSlpKACiiloASloooAKSlooAKKKKAEooooAKKKKACiiigBaSlpKACiiigAooooAKKKKACiiigAooooAKKKKBhRRRQIKKKKACilooASiiigAooooAKKKKACiiigApRSUUALRRRQAUUUUAIaKDRQAUUUUAFAoooAWkpaSgAoFFFAC0UUUAFFJRQAUUUUAFLSUtABRRRQAUlLRQAlLmkooAKKKWgBKWiigAooooASloooAKKKKACiiigAooooAKKKKACiikoAWiiigAooooAKKKKAEopaSgBaKSloAKKKKACiiigAooooAKSlooAKSlooASloooAKKKKAEoFFLQAUUUUAFJQaKAFpKWkoAKWig0AFFJS0AFJQaKACiiigApc0lFAC0UlLQAUUUUAJRS0UAJS0UUAFFFFACUUtJQAtJRRQAUUUUAFFLSUAFFFLQAUUUhoAKKKKACiiigAooooAWikooAKKKKACiiigAooooAKKKKAClpKKAFooooAKKKKQAaSg0UwCiiigYUUUUCCiiloASilooAKKKSgAooooAWkoooAKWkpaAEpaKKACiiigBKKWkoAKWkpaACkNLRQAlFFLQAlFLRQAlFLRQAUUlLQAUUUUAFFFFABRRRQAUUUUAFFFFACUtFFABSUtFACUUuKSgBaKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigApKWigBKKKKACilooAKKKKAEopaKAEpaKKAEopaSgApaSloAKKKKACkpaKACiiigAooooASiiigBaSiloASiiigAooooAKKKKAFooooAKSlooASiiigAopcUUAJRS0lABRRRQAUUtFACUUUUAFFFFABRRRQAUUUUAFLSUtABRRRQAhopaSgAooooAKKWigBKKWkoAKWkooAKKKKACilooASiiigAoopaACiiigAooooAKSlooASiiigAooooAKKKKAFpKKKACijFFABRRRQAtFFFABSUUUALRRSUALRSUtABRRRQAUUUUAFJS0UAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAJS0lLQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFJS0lAC0lFFAC0UUUAFFFFABRRRQAUUUUAFFJS0AJRS0lABRRRQAUUCloASilooASilooASilxRQAUUUUAFJS0UAFFFFABSGlpKACiiigAooooAKKKKAFpKKKACiiloASijFLQAUUUUAFFKaSgApKKKACiiigBaKKKACkpaKAEopaKAEooooAKKKKACiiigApaSloAKKKKACkpaSgApaSloASiiigAopaKAEopaKAEpaSigBaSlooASilooASilooAKMUUUAFJS0UAJRS0YoASlpKKAFooooAKSlooAKKKSgBaSiigApaSloAKSlooAKKKKACiiigApKWigAooooAKKKKACiiigAooooAKKKKADNFJS0AFFFFABRRRQAUmKWigApKWigBKWkooAWkzRRQAtFFFACUUtFACUUtJQAUUUUAFFFFABRRRQAtFJS0AFJRRQAUUUUAFFFFABS0lFABRRRQAUUUtACUUUUAFFFFABRRRQAUUUUALRRRQAUCilFACUUUUgEopaSmAUUUUAFGaKKAClpKWgAooooAKSlooASilooASlxRRQAlLSUtABRRRQAUlLRQAlFLSUAFFLiigBKWiigAoopKACiiigApaSigBaKKKACiiigAooooAKKKKACkpaKACiiigAooooAKSlooAKKKKACiiigBKKWigAooooAKKKKACiiigAooooAKKKKACiiigBKWiigAooooASiiigAooooAWkpaTFABRRRQAUtJS0AFFFFACUUtFABRRRQAUUUUAFJRRQAUtJS0AFFFFACUUUUAFFLSUAFLSUtACUUUtACUUUUAFFFFAwooooEFFFFABRRRQAUUUUAFFKKKAEooooAKKKKAClpKWgAopKUUAFJRRSGFFFFMQUUUUAFFFFAC0lFFABS0lFABRRRQAtFJRQAtFJS0AFFFFABSUtFABSUtJQAUUUUALmkoooAWiikoAKKKKACiiigBaSiigAFLSUtABRRRQAUZopKAFooooAKKKKACiiigAooooAKKKKACiiigAooooAKSlooASloooAKSlpKADNFFFABRRS0AFJS0UAFFFFABRRRQAUUUUABpKKWgBKKWigAooooAKSiigBaSlpDQAUtJS0AFFFJQAtFFFABRRRQAlFFFABRRS0AFJRRQAUUUUAFFFLQAlFLSUAFFFFABRRRQAUUUtACUUtJQAUUUUDCiiigQUUUUALSUtJQAUUUUAFFFFABRRRQAUtFFAAetFFFIBKKKKYBRRRQAUUUUAFFFFABRRRQAUUtFACUUtJQAUUUUAFFFFABRRRQAUUtFACUUUtABRRRQAlFFFABRRRQAUUUUDFpKKKBBS0UUAFFFFABSUtIaAClpKWgAooozQAUUUlAC0UUUAJS0lFAC0lFLQAUUUlAC0UUUAIaWkooAWkpaKAEooooAKWkpaACiiigAoopKAFopKKAFpKWigAFFFFACUUtFABRRSUALRSCloAKKKKACikzS0AFJS0lABRRRQAtFJRQAUUUtABSUtIaACiiigAooooAKKKKACiiigAooooAKKKKAClpKWgBKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigApaKKAA0UlLQAlFFFABRRRQAUUUUAFFFFABRRRQAtJRRQAUUUUAFFFFABRRRQMKKKKBC0UlFABRRRQAUUUUAFFFFABRRRQAtJS0lABRRRQAUtJRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAxaSiigBaQ0UUCCiiigYUtJRQAtFJS0CEooooAXNJRRQMKKKKACg0UUCCiiigYtFFFAhKKKKACiiigYUUUUCCiiigAooooAKKKKACiiigBaSiigApaSigBaSiigAooooAKKKKACiiigAooooAKKKKACiiigApaSigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAClpKKAFopKWkAlFFGKYBRRRigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiijFABRRS0AJRRRQAUUUUAFFFFABRRRQAUUtFACUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUuKAEooooAKKKKACiiigAoopaAEpaKKAEpaKKAEopaKAEopaTFABRS4ooASiiigAooooAM0UUUAFFFFAwooooEFLRRQAlFLRQAlFFFABRRRQAUUUtACUUtJQAUUUUAFLSUtACUUtJQAUUUUAFFFFABRRS0AJRRRQAUUUtACUUUtACUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFLQAlFLRQAlKKSnYpDP/9k="; -export const faviconContentType = "image/jpeg"; + "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"; +export const faviconContentType = "image/png"; diff --git a/bunny_js/src/llm_routes.ts b/bunny_js/src/llm_routes.ts index cc56c0624..72392d285 100644 --- a/bunny_js/src/llm_routes.ts +++ b/bunny_js/src/llm_routes.ts @@ -36,20 +36,26 @@ import { MAX_PRICE_ATOMIC, getSettleAmount, } from "./llm_schemas.js"; -import { isTestnet } from "@fretchen/chain-utils"; +import { isTestnet, type SettlementTokenInfo } from "@fretchen/chain-utils"; import { logger } from "./logger.js"; import { offeredStablecoins, resolvePaidStablecoin } from "./stablecoin_pricing.js"; import { toResponse } from "./response.js"; import { createLLMResourceServer, + EXACT_ALTERNATIVE_AMOUNT_ATOMIC, + EXACT_ALTERNATIVE_NETWORK, createBatchSettlementPaymentRequirements, create402Response, + createExactAlternative, + createResourceServer, extractPaymentPayload, createSettlementHeaders, + formatParallelPrices, getBatchSettlementNetworks, LLM_MAX_TIMEOUT_SECONDS, formatUsdcAtomicAsDecimalUsd, releaseLock, + withExactAlternative, type SdkPaymentPayload, type SdkPaymentRequirements, } from "./x402_server.js"; @@ -103,10 +109,9 @@ const ROOT_PAGE = buildRootPage({ }, ], price: [ - `POST / — metered by real token usage, at most $${formatUsdcAtomicAsDecimalUsd(MAX_PRICE_ATOMIC.USDC)} per request`, + `POST / — metered per request, ceiling ${formatParallelPrices(MAX_PRICE_ATOMIC)}; or exact: $${formatUsdcAtomicAsDecimalUsd(EXACT_ALTERNATIVE_AMOUNT_ATOMIC)} flat (USDC, Base mainnet)`, ], - payment: - "x402 (HTTP 402): no account, no API key. An unpaid request answers 402 with the machine-readable Payment-Required challenge — exact terms (scheme batch-settlement, network, token, ceiling) are in the challenge, then retry the same request with the payment attached.", + payment: `x402 (HTTP 402): no account, no API key. The 402 lists both schemes — batch-settlement channels first (the preferred way to pay: metered by real token usage under the ceiling) and an exact alternative at the end (one EIP-3009 signature per call, unmetered, for clients without channel wiring). Pay in USDC (Base or Optimism) or EURC/EURe (Base); the challenge carries every token's per-network terms. Retry the same request with the payment attached.`, tryIt: `curl -X POST '${openapiSpec.servers[0].url}/' -H 'Content-Type: application/json' -d '{"model":"${advertisedModelIds()[0]}","messages":[{"role":"user","content":"ping"}]}'\n# → HTTP 402 + Payment-Required header: the terms. No wallet needed for this first look.\n# Testnet note: on Base Sepolia the payment settles and the completion is a mocked answer — a free end-to-end test.`, errors: "402 = unpaid or invalid payment (challenge in the Payment-Required header); 400 = invalid request (OpenAI-shaped error body); 404 = unknown model. Response schemas: openapi.json.", @@ -249,7 +254,12 @@ export async function handleLLMRequest(request: Request): Promise { tags: ["llm", "chat", "x402"], iconUrl: `${openapiSpec.servers[0].url}/favicon.png`, }); - return toResponse(create402Response(paymentRequirements)); + // Append the exact alternative AFTER the batch entries — order is the contract (see + // withExactAlternative): channel buyers keep their preferred entries, channel-less + // clients fall through to the exact one. + return toResponse( + create402Response(withExactAlternative(paymentRequirements, receiverAddressForChallenge)), + ); } // OpenAI chat-completions request body — everything we do not consume is FORWARDED to the @@ -345,74 +355,115 @@ export async function handleLLMRequest(request: Request): Promise { ); } - let resourceServer: ReturnType["resourceServer"]; - let scheme: ReturnType["scheme"]; - try { - ({ resourceServer, scheme } = createLLMResourceServer(receiverAddress)); - } catch (err) { - logger.error({ err }, "Failed to configure batch-settlement resource server"); - return jsonError(500, "Server configuration error"); - } - // Non-null: the unpaid case already returned the 402 challenge above, before validation. const paymentPayload = paymentPayloadEarly; + const accepted = (paymentPayload as Record)["accepted"] as + Record | undefined; + const clientNetwork = accepted?.["network"] as string | undefined; - const clientNetwork = (paymentPayload as Record)["accepted"] - ? (((paymentPayload as Record)["accepted"] as Record)[ - "network" - ] as string | undefined) - : undefined; + // ─── Scheme fork ──────────────────────────────────────────────────────────────────── + // The 402 lists the batch-settlement entries first and the exact alternative last (see + // withExactAlternative); the payment names which one it pays against. The exact path is + // channel-free — no enhancePaymentRequirements, no channel lock — which is also why an + // invalid exact payment answers a plain 402 below instead of the batch path's corrective + // resync (there is no channel state to resync). + const isExact = accepted?.["scheme"] === "exact"; - if (!clientNetwork || !getBatchSettlementNetworks().includes(clientNetwork)) { - return jsonError(402, "Unsupported or missing network for batch-settlement payment"); - } + let resourceServer: ReturnType["resourceServer"]; + let paymentRequirements: SdkPaymentRequirements; + let useMock: boolean; + let coin: SettlementTokenInfo; + + if (isExact) { + // The exact entry was offered on exactly one (network, token); anything else is a + // caller paying against terms we never issued. + const exactRequirements = createExactAlternative(receiverAddress); + if ( + clientNetwork !== EXACT_ALTERNATIVE_NETWORK || + accepted?.["asset"] !== exactRequirements.asset + ) { + return jsonError( + 402, + `Unsupported exact payment terms. This endpoint's exact scheme accepts ${EXACT_ALTERNATIVE_NETWORK} and ${exactRequirements.asset} only`, + ); + } + // Mainnet-only offer, so the testnet mock branch cannot apply; only an explicit + // useDummyData still selects the stand-in. + useMock = useDummyData === true; + // Guaranteed by the terms check above (the asset IS ours), but stay symmetric with the + // batch branch rather than trust an invariant across two modules. + const resolvedCoin = resolvePaidStablecoin(EXACT_ALTERNATIVE_NETWORK, exactRequirements.asset); + if (!resolvedCoin) { + logger.error( + { network: EXACT_ALTERNATIVE_NETWORK, asset: exactRequirements.asset }, + "Config error: exact-alternative asset not offered", + ); + return jsonError(500, "Server configuration error"); + } + coin = resolvedCoin; + resourceServer = createResourceServer(); + paymentRequirements = exactRequirements; + } else { + let scheme: ReturnType["scheme"]; + try { + ({ resourceServer, scheme } = createLLMResourceServer(receiverAddress)); + } catch (err) { + logger.error({ err }, "Failed to configure batch-settlement resource server"); + return jsonError(500, "Server configuration error"); + } - // SECURITY: testnet must never reach the real Mistral API (scw parity). An explicit - // `useDummyData: false` on a testnet network is always a caller error — reject loudly. - if (isTestnet(clientNetwork) && useDummyData === false) { - return jsonError( - 400, - "Real inference is not available on testnet — this network always returns a mocked LLM response. Omit useDummyData or set it to true, or use a mainnet network for a real completion.", - ); - } - // Never spend real inference budget on a valueless testnet payment (scw parity). - const useMock = useDummyData === true || isTestnet(clientNetwork); + if (!clientNetwork || !getBatchSettlementNetworks().includes(clientNetwork)) { + return jsonError(402, "Unsupported or missing network for batch-settlement payment"); + } - // Price the stablecoin the buyer's channel is in, not a fixed one (scw parity). - const accepted = (paymentPayload as Record)["accepted"] as - Record | undefined; - const coin = resolvePaidStablecoin(clientNetwork, accepted?.["asset"]); - if (!coin) { - logger.warn({ clientNetwork, asset: accepted?.["asset"] }, "Payment asset not offered"); - return jsonError( - 402, - `Unsupported payment asset on ${clientNetwork} — offered: ${offeredStablecoins(clientNetwork) - .map((offered) => `${offered.symbol} ${offered.address}`) - .join(", ")}`, - ); - } - const baseRequirements: SdkPaymentRequirements = { - scheme: "batch-settlement", - network: clientNetwork as `${string}:${string}`, - amount: MAX_PRICE_ATOMIC[coin.symbol], - asset: coin.address, - payTo: receiverAddress, - maxTimeoutSeconds: LLM_MAX_TIMEOUT_SECONDS, - extra: { name: coin.name, version: coin.version }, - }; - // Must be the SAME enhanced requirements the client signed its channelConfig against — - // a raw object here makes the facilitator reject every deposit with - // receiver_authorizer_mismatch (scw parity; confirmed via a real Base Sepolia run). - const paymentRequirements = await scheme.enhancePaymentRequirements( - baseRequirements, - { - x402Version: 2, + // SECURITY: testnet must never reach the real Mistral API (scw parity). An explicit + // `useDummyData: false` on a testnet network is always a caller error — reject loudly. + if (isTestnet(clientNetwork) && useDummyData === false) { + return jsonError( + 400, + "Real inference is not available on testnet — this network always returns a mocked LLM response. Omit useDummyData or set it to true, or use a mainnet network for a real completion.", + ); + } + // Never spend real inference budget on a valueless testnet payment (scw parity). + useMock = useDummyData === true || isTestnet(clientNetwork); + + // Price the stablecoin the buyer's channel is in, not a fixed one (scw parity). + const resolvedCoin = resolvePaidStablecoin(clientNetwork, accepted?.["asset"]); + if (!resolvedCoin) { + logger.warn({ clientNetwork, asset: accepted?.["asset"] }, "Payment asset not offered"); + return jsonError( + 402, + `Unsupported payment asset on ${clientNetwork} — offered: ${offeredStablecoins( + clientNetwork, + ) + .map((offered) => `${offered.symbol} ${offered.address}`) + .join(", ")}`, + ); + } + coin = resolvedCoin; + const baseRequirements: SdkPaymentRequirements = { scheme: "batch-settlement", network: clientNetwork as `${string}:${string}`, - extra: baseRequirements.extra, - }, - [], - ); + amount: MAX_PRICE_ATOMIC[coin.symbol], + asset: coin.address, + payTo: receiverAddress, + maxTimeoutSeconds: LLM_MAX_TIMEOUT_SECONDS, + extra: { name: coin.name, version: coin.version }, + }; + // Must be the SAME enhanced requirements the client signed its channelConfig against — + // a raw object here makes the facilitator reject every deposit with + // receiver_authorizer_mismatch (scw parity; confirmed via a real Base Sepolia run). + paymentRequirements = await scheme.enhancePaymentRequirements( + baseRequirements, + { + x402Version: 2, + scheme: "batch-settlement", + network: clientNetwork as `${string}:${string}`, + extra: baseRequirements.extra, + }, + [], + ); + } let verification: { isValid: boolean; @@ -431,13 +482,21 @@ export async function handleLLMRequest(request: Request): Promise { } if (!verification.isValid) { - // Through createPaymentRequiredResponse (not a hand-rolled body) so the SDK's corrective - // enrichment runs — channelState/voucherState for cumulative mismatches, which the client - // SDK's processCorrectivePaymentRequired needs to resync and retry (scw parity). logger.warn( { reason: verification.invalidReason, message: verification.invalidMessage }, "Payment verification failed", ); + if (isExact) { + // Plain 402, not the batch corrective re-challenge: exact holds no channel state to + // resync — the client retries with a fresh signature against the challenge it has. + return jsonError( + 402, + `Payment verification failed: ${verification.invalidReason ?? "unknown"}`, + ); + } + // Through createPaymentRequiredResponse (not a hand-rolled body) so the SDK's corrective + // enrichment runs — channelState/voucherState for cumulative mismatches, which the client + // SDK's processCorrectivePaymentRequired needs to resync and retry (scw parity). const paymentRequired = await resourceServer.createPaymentRequiredResponse( [paymentRequirements], { @@ -485,16 +544,22 @@ export async function handleLLMRequest(request: Request): Promise { logger.error({ err: error }, "Error during answer generation"); // Verified but never settled: release the channel lock, or the next message waits out // LLM_MAX_TIMEOUT_SECONDS (the lock TTL) behind `channel_busy` for an answer that was - // never charged. - await releaseLock(resourceServer, paymentPayload as SdkPaymentPayload, paymentRequirements); + // never charged. Exact holds no lock, so there is nothing to release. + if (!isExact) { + await releaseLock(resourceServer, paymentPayload as SdkPaymentPayload, paymentRequirements); + } return jsonError(500, (error as Error).message); } // Settling with a DIFFERENT (smaller, real-usage) amount than verifyPayment() used is // intentional (scw parity — see getSettleAmount() in llm_schemas.ts): handleBeforeSettle - // only checks against the voucher's signed ceiling, not against what verify saw. - const settleAmount = getSettleAmount(llmData.usage, coin.symbol); - const settleRequirements = { ...paymentRequirements, amount: settleAmount }; + // only checks against the voucher's signed ceiling, not against what verify saw. The + // exact alternative has no ceiling concept — its price is the flat per-call amount, so + // the settle amount equals the verified one. + const settleRequirements = isExact + ? paymentRequirements + : { ...paymentRequirements, amount: getSettleAmount(llmData.usage, coin.symbol) }; + const settleAmount = settleRequirements.amount; try { const settlement = await resourceServer.settlePayment( diff --git a/bunny_js/src/mcp_routes.ts b/bunny_js/src/mcp_routes.ts index 29d187938..75eb1439d 100644 --- a/bunny_js/src/mcp_routes.ts +++ b/bunny_js/src/mcp_routes.ts @@ -54,19 +54,23 @@ const ROOT_PAGE = buildRootPage({ }, { href: "https://web-agent.fretchen.eu/", - label: "web_search — the web seller's page, spec and terms", + label: + "the web seller — home of the web_search tool; it also serves GET /fetch (single-page fetch, one URL's clean machine-readable content), a REST route priced on its page", }, { href: `https://mcp-agent.fretchen.eu/${AGENT_REGISTRATION_PATH}`, label: "this origin's registration document (domain control proof for both agents)", }, ], - price: MCP_TOOLS.map( - (tool) => - `${tool.name} — priced at the seller it relays to; the unpaid call's tool result carries the exact 402 terms`, - ), + price: [ + ...MCP_TOOLS.map( + (tool) => + `${tool.name} — priced at the seller it relays to; the unpaid call's tool result carries the exact 402 terms`, + ), + "GET /fetch — single-page fetch, the web seller's most-called route: REST only (no MCP tool — call the web seller directly), priced on its page", + ], payment: - "per tool, via x402 at the relaying seller: no account, no API key. initialize and tools/list are free; an unpaid tools/call returns the 402 demands as structuredContent in an isError tool result — retry with the payment attached.", + "per tool, via x402 at the relaying seller: no account, no API key. Each seller's 402 lists both schemes — batch-settlement channels first (USDC, or EURC/EURe on Base) and an exact alternative (one signature per call, flat in USDC on Base mainnet); the challenge carries every token's terms. initialize and tools/list are free; an unpaid tools/call returns the 402 demands as structuredContent in an isError tool result — retry with the payment attached.", tryIt: `curl -X POST '${MCP_SERVICE.endpoint}' -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'\n# → the tools and their input schemas, free (as is initialize). tools/call carries the x402 payment.`, errors: "JSON-RPC errors: -32601 unknown method (a stateless server: initialize, tools/list, tools/call only), -32602 unknown tool. GET /mcp is 405 Allow: POST (the optional GET stream is not offered). Unpaid tools/call is NOT an error — it is a tools result with isError and the 402 demands in structuredContent.", diff --git a/bunny_js/src/openapi.llm.json b/bunny_js/src/openapi.llm.json index 6dc1b498b..ab7dde8a5 100644 --- a/bunny_js/src/openapi.llm.json +++ b/bunny_js/src/openapi.llm.json @@ -2,9 +2,9 @@ "openapi": "3.1.0", "info": { "title": "Fretchen LLM Inference — pay-per-call chat completions for agents", - "description": "OpenAI-shaped chat completions from Mistral models, sold per request to autonomous agents via x402 batch-settlement payment channels — no account, no API key. Metered by real token usage under a fixed per-request ceiling; settled in USDC (Base or Optimism), EURC or EURe (Base), while Base Sepolia settles for free and answers with a mocked completion.", + "description": "OpenAI-shaped chat completions from Mistral models, sold per request to autonomous agents via x402 — batch-settlement channels (metered by real token usage under a per-request ceiling; USDC, EURC or EURe, the euro tokens on Base) or a flat per-call exact alternative (unmetered, USDC on Base mainnet). No account, no API key; Base Sepolia settles for free and answers with a mocked completion.", "version": "1.0.0", - "x-guidance": "OpenAI chat-completions body. POST / with { model, messages: [{ role, content }, ...] } and no payment header to receive a 402 with x402 batch-settlement payment requirements (accepts[]). Open/top up a payment channel per the requirements, retry with the payment header, and the service returns a standard OpenAI chat.completion object. Streaming (stream: true) is not supported. Each message is metered and settled up to a per-message price ceiling; the real cost is usage-derived and typically lower. Other standard OpenAI chat params (temperature, top_p, stop, seed, the penalties, response_format) are forwarded to the upstream model and work as normal; they are not enumerated here because the upstream owns that contract. The exceptions are stream, n and max_tokens, which are rejected with a 400 rather than ignored, because each would move cost past the fixed per-message ceiling this endpoint meters against. Tool calling is supported (see x-capabilities): send OpenAI-shaped tools, and a tool call comes back as choices[].message.tool_calls with finish_reason: \"tool_calls\" and content: null. Execute it yourself and send the result back as a role:\"tool\" message with the matching tool_call_id — this endpoint never calls a tool on your behalf, and accepts no callback or MCP server URL. Note: payment uses x402 batch-settlement (a stateful channel), so a stock OpenAI SDK cannot pay this endpoint without batch-settlement client wiring — the OpenAI shape is for body legibility, not drop-in SDK use.", + "x-guidance": "OpenAI chat-completions body. POST / with { model, messages: [{ role, content }, ...] } and no payment header to receive a 402 with x402 batch-settlement payment requirements (accepts[]). Open/top up a payment channel per the requirements, retry with the payment header, and the service returns a standard OpenAI chat.completion object. Streaming (stream: true) is not supported. Each message is metered and settled up to a per-message price ceiling; the real cost is usage-derived and typically lower. Other standard OpenAI chat params (temperature, top_p, stop, seed, the penalties, response_format) are forwarded to the upstream model and work as normal; they are not enumerated here because the upstream owns that contract. The exceptions are stream, n and max_tokens, which are rejected with a 400 rather than ignored, because each would move cost past the fixed per-message ceiling this endpoint meters against. Tool calling is supported (see x-capabilities): send OpenAI-shaped tools, and a tool call comes back as choices[].message.tool_calls with finish_reason: \"tool_calls\" and content: null. Execute it yourself and send the result back as a role:\"tool\" message with the matching tool_call_id — this endpoint never calls a tool on your behalf, and accepts no callback or MCP server URL. Note: payment uses x402 batch-settlement (a stateful channel), so a stock OpenAI SDK cannot pay this endpoint without batch-settlement client wiring — the OpenAI shape is for body legibility, not drop-in SDK use. After the channel entries, the 402 also carries one exact-scheme alternative: a single EIP-3009 signature per call, flat 0.01 USDC on Base mainnet, unmetered — the usage-derived settlement and the per-message ceiling apply to channel payments only — for clients that cannot open channels at all. The live challenge is the source of truth for its terms.", "contact": { "name": "fretchen", "url": "https://www.fretchen.eu", diff --git a/bunny_js/src/openapi.search.json b/bunny_js/src/openapi.search.json index fe2f87558..d148e061d 100644 --- a/bunny_js/src/openapi.search.json +++ b/bunny_js/src/openapi.search.json @@ -2,9 +2,9 @@ "openapi": "3.1.0", "info": { "title": "Fretchen Web Retrieval — pay-per-call web access for agents", - "description": "Web search and single-page fetch for agents, paid per call via x402 batch-settlement payment channels in USDC (or EURC on Base).", + "description": "Web search and single-page fetch for agents, paid per call via x402 — batch-settlement channels in USDC, EURC or EURe (the euro tokens on Base, USDC also on Optimism), or a flat per-call exact alternative in USDC on Base mainnet.", "version": "1.0.0", - "x-guidance": "GET /search?q=... or GET /fetch?url=... with no payment header to receive a 402 carrying x402 batch-settlement payment requirements (accepts[]). Open or top up a payment channel per those requirements, retry with the payment header, and the service returns JSON. Payment travels in the header only — these are GET routes, so there is no body fallback. Both routes bill onto the SAME channel as llm-agent.fretchen.eu when the payer, voucher signer and receiver match, so an agent already chatting there pays for web access without a second deposit. Prices are fixed per route and settled only when the call succeeds: an upstream failure returns 500 and takes no money. Mainnet only — testnet USDC is free and these routes spend real money on the caller's behalf.", + "x-guidance": "GET /search?q=... or GET /fetch?url=... with no payment header to receive a 402 carrying x402 batch-settlement payment requirements (accepts[]). Open or top up a payment channel per those requirements, retry with the payment header, and the service returns JSON. Payment travels in the header only — these are GET routes, so there is no body fallback. Both routes bill onto the SAME channel as llm-agent.fretchen.eu when the payer, voucher signer and receiver match, so an agent already chatting there pays for web access without a second deposit. Prices are fixed per route and settled only when the call succeeds: an upstream failure returns 500 and takes no money. Base Sepolia is offered for end-to-end testing: a testnet payment settles in free test tokens and the answer is a marked stand-in (no web search or fetch is performed) — the metered upstreams run for mainnet payments only. After the channel entries, the 402 also carries one exact-scheme alternative: a single EIP-3009 signature per call, flat 0.01 USDC on Base mainnet, priced independently of the channel prices — for clients that cannot open batch-settlement channels. The live challenge is the source of truth for its terms.", "contact": { "name": "fretchen", "url": "https://www.fretchen.eu", diff --git a/bunny_js/src/search_stand_in.ts b/bunny_js/src/search_stand_in.ts new file mode 100644 index 000000000..cfb6f5322 --- /dev/null +++ b/bunny_js/src/search_stand_in.ts @@ -0,0 +1,66 @@ +/** + * The testnet stand-ins for the web seller's two routes — the twin's counterpart of + * llm_service's mock LLM (scw parity in intent, not in shape). + * + * A paid request on a testnet network (Base Sepolia) exercises the FULL x402 machinery — + * challenge, validation, channel verification, settlement, the Payment-Response header — + * but must never touch the metered upstreams: Brave bills per query and /fetch is + * egress, and a testnet payment is free. `run()` in searchapi_routes.ts therefore swaps + * these in for searchWeb/fetchExternalHtml whenever the payment's network is a testnet. + * + * The output types are the SERVICES' own return types (SearchResult / FetchedPage), so the + * rest of the response path is untouched. Content rules, deliberately strict: + * - deterministic — same input, same bytes; no clock, no randomness, no drift; + * - loud — every string carries the MOCKER marker, so no caller can mistake a stand-in + * for real data (a mock that looks real is worse than no mock); + * - echoing — the input query/url appears in the output, so the caller sees their own + * request flow through the whole pipeline. + */ + +import type { SearchResult } from "./search_service.js"; +import type { FetchedPage } from "./web_fetch_service.js"; + +/** The marker every stand-in string carries — tests pin the exact phrase. */ +export const TESTNET_MOCK_MARKER = "testnet mock"; + +const note = + "This is a testnet mock answer: your payment's challenge, verification, settlement and " + + "response all ran, but no real web search was performed. Pay on a mainnet network " + + "(eip155:10 or eip155:8453) for real results."; + +export function standInSearch(query: string): { results: SearchResult[] } { + return { + results: [ + { + url: "https://web-agent.fretchen.eu/mock-result-1", + title: `testnet mock result for "${query}"`, + text: note, + }, + { + url: "https://web-agent.fretchen.eu/mock-result-2", + title: "testnet mock: payment pipeline verified", + text: `Your query "${query}" reached the seller and a signed, settled payment was accepted for it.`, + }, + { + url: "https://web-agent.fretchen.eu/mock-result-3", + title: "testnet mock: how to get real results", + text: "Aim the same client at a mainnet network entry of the 402 challenge and the live upstream runs.", + }, + ], + }; +} + +export function standInFetch(url: string): FetchedPage { + return { + finalUrl: url, + contentType: "text/html; charset=utf-8", + html: `\n\ntestnet mock page: ${escapeHtml( + url, + )}\n\n

testnet mock page

\n

${note}

\n

You requested ${escapeHtml( + url, + )}; it was never fetched.

\n\n`, + }; +} + +const escapeHtml = (text: string): string => + text.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); diff --git a/bunny_js/src/searchapi_routes.ts b/bunny_js/src/searchapi_routes.ts index d311f1827..f3921aebd 100644 --- a/bunny_js/src/searchapi_routes.ts +++ b/bunny_js/src/searchapi_routes.ts @@ -12,9 +12,11 @@ * fails after it never settles, orphaning the lock for MAX_TIMEOUT_SECONDS). */ import { logger } from "./logger.js"; +import { isTestnet } from "@fretchen/chain-utils"; import { offeredStablecoins, resolvePaidStablecoin } from "./stablecoin_pricing.js"; import { searchWeb, QueryError } from "./search_service.js"; import { fetchExternalHtml, FetchUrlError, parseHttpsUrl } from "./web_fetch_service.js"; +import { standInFetch, standInSearch } from "./search_stand_in.js"; import { DESCRIPTION, FetchQuerySchema, @@ -37,24 +39,33 @@ import openapiSpec from "./openapi.search.json" with { type: "json" }; import { buildSearchBazaarExtension } from "./bazaar_discovery.js"; import { toResponse } from "./response.js"; import { + EXACT_ALTERNATIVE_AMOUNT_ATOMIC, + EXACT_ALTERNATIVE_NETWORK, create402Response, createBatchSettlementPaymentRequirements, + createExactAlternative, createLLMResourceServer, + createResourceServer, createSettlementHeaders, extractPaymentPayload, + formatParallelPrices, + formatUsdcAtomicAsDecimalUsd, + getBatchSettlementNetworks, releaseLock, + withExactAlternative, type SdkPaymentPayload, type SdkPaymentRequirements, } from "./x402_server.js"; /** - * Mainnet only, unlike the chat, which also accepts Base Sepolia. Testnet USDC is free, and - * both routes spend real money on the caller's behalf — Brave bills per query, and /fetch is - * egress. Advertising a testnet would hand anyone a metered API for nothing. The cost is - * that the paid path cannot be exercised on a testnet at all: at $0.001 a call, a real - * run is cheaper than the setup it replaces. (Copied from scw_js/search_api.ts.) + * The batch-settlement networks, mainnet PLUS Base Sepolia — unlike the scw original, which + * was mainnet-only. Adverting a testnet is safe now BECAUSE the paid path never touches the + * metered upstreams there: a testnet payment gets the marked stand-in answers instead + * (search_stand_in.ts), while the full challenge/verify/settle machinery runs for real on + * free test tokens — the try-before-buy the agents asked for. Metered Brave/egress only + * run for mainnet payments; the regression test pins that forever. */ -const MAINNET_NETWORKS = ["eip155:10", "eip155:8453"]; +const OFFERED_NETWORKS = getBatchSettlementNetworks(); /** * Advertised and verified lock TTL, a quarter of the chat's LLM_MAX_TIMEOUT_SECONDS. It @@ -102,18 +113,12 @@ const ROOT_PAGE = buildRootPage({ label: "stateless MCP endpoint — same tools as JSON-RPC 2.0 over POST (web_search)", }, ], - price: ROUTES.map((route) => { - const info = ( - openapiSpec.paths as Record< - string, - Record - > - )[`/${route}`].get["x-payment-info"]; - return `GET /${route} — $${info?.price.amount ?? "?"} per call`; - }), - payment: - "x402 (HTTP 402): no account, no API key. An unpaid request answers 402 with the machine-readable Payment-Required challenge — exact terms (scheme batch-settlement, network, token, amount) are in the challenge, then retry the same request with the payment attached.", - tryIt: `curl '${SERVICE_URL}/search?q=erc-8004'\n# → HTTP 402 + Payment-Required header: the terms. No wallet needed for this first look.`, + price: ROUTES.map( + (route) => + `GET /${route} — channel call: ${formatParallelPrices(PRICE_ATOMIC[route])}; or exact: $${formatUsdcAtomicAsDecimalUsd(EXACT_ALTERNATIVE_AMOUNT_ATOMIC)} flat (USDC, Base mainnet)`, + ), + payment: `x402 (HTTP 402): no account, no API key. The 402 lists both schemes — batch-settlement channels first (the preferred way to pay) and an exact alternative at the end (one EIP-3009 signature per call, for clients without channel wiring). Pay in USDC (Base or Optimism) or EURC/EURe (Base); the challenge carries every token's per-network terms. Retry the same request with the payment attached.`, + tryIt: `curl '${SERVICE_URL}/search?q=erc-8004'\n# → HTTP 402 + Payment-Required header: the terms. No wallet needed for this first look.\n# Testnet note: a payment on Base Sepolia settles in free test tokens and the answer is a marked mock — the full flow, real results on mainnet only.`, errors: "402 = unpaid (challenge in the Payment-Required header); 404 = no such route; everything else is standard HTTP. Response schemas: openapi.json.", }); @@ -165,7 +170,16 @@ function parseArgs(route: Route, queryParams: Record): Args { return { route, url: parseHttpsUrl(parsed.data.url).toString() }; } -function run(args: Args): Promise { +/** + * The upstream call for one paid request — the ONE place the metered services are touched, + * which is why the testnet gate lives here and nowhere else: a testnet payment gets the + * marked stand-in answers (search_stand_in.ts) and the metered upstreams never run. + */ +async function run(args: Args, useMock: boolean): Promise { + if (useMock) { + logger.info({ route: args.route }, "Serving stand-in answer (testnet payment)"); + return args.route === "search" ? standInSearch(args.q) : standInFetch(args.url); + } return args.route === "search" ? searchWeb(args.q) : fetchExternalHtml(args.url); } @@ -191,8 +205,17 @@ function acceptedNetwork(payload: Record): string | null { return typeof network === "string" ? network : null; } +/** The scheme the payer built their payload against, or null if they named none. */ +function acceptedScheme(payload: Record): string | null { + const accepted = payload["accepted"] as Record | undefined; + const scheme = accepted?.["scheme"]; + return typeof scheme === "string" ? scheme : null; +} + /** * Serve one route against a payment: verify, run, and settle **only if the run succeeded**. + * The batch-settlement path below is the original; payments carrying the exact alternative + * (the last accepts entry of the 402, see x402_server.ts) take their own, channel-free flow. * * Settle-after-success is what makes an upstream failure free for the caller. It leaves a * verify without a matching settle, which holds the channel lock for MAX_TIMEOUT_SECONDS — @@ -203,13 +226,124 @@ async function servePaid( args: Args, payload: Record, receiverAddress: `0x${string}`, +): Promise { + if (acceptedScheme(payload) === "exact") { + return serveExact(route, args, payload, receiverAddress); + } + return serveBatchSettlement(route, args, payload, receiverAddress); +} + +/** + * The exact alternative's paid flow: one EIP-3009 signature per call, Base mainnet USDC, + * flat EXACT_ALTERNATIVE_AMOUNT_ATOMIC (see x402_server.ts for why the price is what it is). + * Deliberately resembles the batch flow (verify → run → settle-on-success → Payment-Response) + * while touching NONE of the channel machinery: no enhancePaymentRequirements, no channel + * lock, no releaseLock — there is no state for any of it to act on. + */ +async function serveExact( + route: Route, + args: Args, + payload: Record, + receiverAddress: `0x${string}`, +): Promise { + // The exact entry was offered on exactly one network/token; anything else is the caller + // paying against terms we never issued. + const network = acceptedNetwork(payload); + const paidAsset = (payload["accepted"] as Record | undefined)?.["asset"]; + const exactRequirements = createExactAlternative(receiverAddress); + if (network !== EXACT_ALTERNATIVE_NETWORK || paidAsset !== exactRequirements.asset) { + return jsonResponse(402, { + error: `Unsupported exact payment terms. This endpoint's exact scheme accepts ${ + EXACT_ALTERNATIVE_NETWORK + } and ${exactRequirements.asset} only`, + }); + } + + const resourceServer = createResourceServer(); + + let verification: { + isValid: boolean; + invalidReason?: string; + invalidMessage?: string; + payer?: string; + }; + try { + verification = await resourceServer.verifyPayment( + payload as SdkPaymentPayload, + exactRequirements, + ); + } catch (err) { + logger.error({ err }, "Payment verification error"); + return jsonResponse(402, { error: `Payment verification failed: ${(err as Error).message}` }); + } + + if (!verification.isValid) { + logger.warn( + { reason: verification.invalidReason, message: verification.invalidMessage }, + "Payment verification failed", + ); + // A plain 402, not the batch path's corrective re-challenge: exact has no channel state + // to resync — the client retries with a fresh signature against the challenge it has. + return jsonResponse(402, { + error: `Payment verification failed: ${verification.invalidReason ?? "unknown"}`, + ...(verification.payer ? { payer: verification.payer } : {}), + }); + } + + let data: unknown; + try { + // Exact is offered on Base mainnet only — the metered upstream always runs there. + data = await run(args, false); + } catch (err) { + // Nothing settled and nothing to release — exact holds no locks. + logger.warn({ err, route }, "Run failed after exact verify"); + return errorResponseFor(err); + } + + let settlement: Record & { success: boolean; errorReason?: string }; + try { + settlement = await resourceServer.settlePayment( + payload as SdkPaymentPayload, + exactRequirements, + ); + } catch (err) { + logger.error({ err }, "Settlement error"); + return jsonResponse(402, { error: `Settlement failed: ${(err as Error).message}` }); + } + + if (!settlement.success) { + logger.error({ settlement }, "Settlement failed"); + return jsonResponse(402, { + error: `Settlement failed: ${settlement.errorReason ?? "unknown"}`, + }); + } + + logger.info( + { route, network, scheme: "exact", amount: exactRequirements.amount }, + "Served and settled", + ); + return new Response(JSON.stringify(data), { + status: 200, + headers: { ...CORS_HEADERS, ...createSettlementHeaders(settlement) }, + }); +} + +/** The original batch-settlement flow (channels, vouchers, shared locks with the chat). */ +async function serveBatchSettlement( + route: Route, + args: Args, + payload: Record, + receiverAddress: `0x${string}`, ): Promise { const network = acceptedNetwork(payload); - if (!network || !MAINNET_NETWORKS.includes(network)) { + if (!network || !OFFERED_NETWORKS.includes(network)) { return jsonResponse(402, { - error: `Unsupported or missing network. This endpoint accepts ${MAINNET_NETWORKS.join(", ")}`, + error: `Unsupported or missing network. This endpoint accepts ${OFFERED_NETWORKS.join(", ")}`, }); } + // A testnet payment settles free test tokens and answers with the marked stand-in + // (see run()); the metered upstreams run for mainnet payments only. + const useMock = isTestnet(network); // Price the stablecoin the buyer's channel is in (see sc_llm_x402.ts) — checked before any // server setup, since an unoffered asset is the caller's error, not ours. @@ -295,7 +429,7 @@ async function servePaid( let data: unknown; try { - data = await run(args); + data = await run(args, useMock); } catch (err) { // Nothing is settled: an upstream that failed is not something to charge for. But verify took // the channel lock, which the chat shares — release it, or the user's next message is @@ -353,7 +487,7 @@ async function challenge(route: Route, receiverAddress: `0x${string}`): Promise< price: PRICE_ATOMIC[route], payTo: receiverAddress, scheme, - networks: MAINNET_NETWORKS, + networks: OFFERED_NETWORKS, maxTimeoutSeconds: MAX_TIMEOUT_SECONDS, // One bazaar block per route: the block names the method and the query parameters, and the // two routes sell different things at different prices. (Copied.) @@ -362,7 +496,10 @@ async function challenge(route: Route, receiverAddress: `0x${string}`): Promise< tags: ["search", "web", "x402"], iconUrl: `${openapiSpec.servers[0].url}/favicon.png`, }); - return toResponse(create402Response(paymentRequirements)); + // Append the exact alternative AFTER the batch entries — order is the contract (see + // withExactAlternative): channel buyers keep their preferred entries, channel-less + // clients fall through to the exact one. + return toResponse(create402Response(withExactAlternative(paymentRequirements, receiverAddress))); } function isHexAddress(addr: unknown): addr is `0x${string}` { diff --git a/bunny_js/src/x402_server.ts b/bunny_js/src/x402_server.ts index 9155796fa..368521859 100644 --- a/bunny_js/src/x402_server.ts +++ b/bunny_js/src/x402_server.ts @@ -491,6 +491,63 @@ export async function createBatchSettlementPaymentRequirements({ }; } +/** + * The exact-scheme alternative the sellers append to their batch-settlement 402s. + * + * Why it exists: a stock x402 client can sign an EIP-3009 authorization but cannot open + * batch-settlement channels — for those callers the channel-only challenge was unpayable. + * Terms are deliberately the flattest possible: one network (Base mainnet), one token + * (USDC), one price per call (10000 atomic = $0.01). + * + * The price IS the facilitator's per-settle fee (0.01), so a settled exact call nets the + * seller zero minus the upstream cost — an accepted trade for the simplicity it buys + * (decided 2026-11). It also means an exact price below $0.01 would lose money on every + * call, which is why /fetch quotes 10000 under exact despite quoting 1000 under + * batch-settlement — the two schemes price independently. + */ +export const EXACT_ALTERNATIVE_NETWORK = "eip155:8453" as const; +export const EXACT_ALTERNATIVE_AMOUNT_ATOMIC = "10000"; + +/** + * The one accepts entry the exact alternative consists of — also the per-call requirements + * the paid path verifies and settles against (single source; see genimg_x402_token.ts for + * the production exact flow this mirrors). + */ +export function createExactAlternative(payTo: string): SdkPaymentRequirements { + const usdc = offeredStablecoins(EXACT_ALTERNATIVE_NETWORK).find((coin) => coin.symbol === "USDC"); + if (!usdc) { + // Config error (chain-utils' token list changed), not a caller error — throws into the + // 402 path's existing try/catch and surfaces as the same "Server configuration error". + throw new Error("USDC is not offered on the exact-alternative network"); + } + return { + scheme: "exact", + network: EXACT_ALTERNATIVE_NETWORK as `${string}:${string}`, + amount: EXACT_ALTERNATIVE_AMOUNT_ATOMIC, + asset: usdc.address, + payTo, + maxTimeoutSeconds: 60, + extra: tokenExtra(usdc), + }; +} + +/** + * Appends the exact alternative AFTER the batch-settlement entries — the order is the + * contract: existing channel buyers read accepts[0..n-1] as the preferred (EURe-first) + * way to pay, and a client that cannot wire batch-settlement falls through to the exact + * entry at the end. The extensions stay the batch builder's own (Permit2/bazaar). + */ +export function withExactAlternative< + T extends { + x402Version: number; + resource: { url: string }; + accepts: unknown[]; + extensions?: Record; + }, +>(challenge: T, payTo: string): T { + return { ...challenge, accepts: [...challenge.accepts, createExactAlternative(payTo)] }; +} + export interface PaymentRequirementsOptions { resourceUrl: string; description: string; @@ -687,6 +744,22 @@ export function formatUsdcAtomicAsDecimalUsd(atomicAmount: string, decimals: big return trimmedFraction ? `${whole}.${trimmedFraction}` : whole.toString(); } +/** + * A PriceList as the root pages display it: every accepted token priced in its own units, in + * the seller's preference order (EURe, EURC, USDC on Base — the same order the 402 lists), + * € for the euro tokens and $ for USDC, decimals read from the token registry so EURe's + * 18-decimal prices format correctly. Pages built with this can never quote fewer tokens + * (or a different order) than the challenge does. + */ +export function formatParallelPrices(price: PriceList): string { + return offeredStablecoins("eip155:8453") + .map((coin) => { + const display = formatUsdcAtomicAsDecimalUsd(price[coin.symbol], BigInt(coin.decimals)); + return `${coin.symbol === "USDC" ? "$" : "€"}${display} in ${coin.symbol}`; + }) + .join(", "); +} + export function createSettlementHeaders( settlementResult: Record, ): Record { diff --git a/bunny_js/test/llm.test.ts b/bunny_js/test/llm.test.ts index 046f63051..66f7e0cc3 100644 --- a/bunny_js/test/llm.test.ts +++ b/bunny_js/test/llm.test.ts @@ -8,6 +8,9 @@ const { mockCreateLLMResourceServer, mockCreateBatchSettlementPaymentRequirements, mockCreate402Response, + mockCreateResourceServer, + mockExactVerifyPayment, + mockExactSettlePayment, mockExtractPaymentPayload, mockCreateSettlementHeaders, mockGetBatchSettlementNetworks, @@ -63,6 +66,10 @@ const { mockCreateLLMResourceServer: vi.fn(), mockCreateBatchSettlementPaymentRequirements: vi.fn(), mockCreate402Response: vi.fn(), + // The exact alternative's server (createResourceServer — no channels, no scheme object). + mockCreateResourceServer: vi.fn(), + mockExactVerifyPayment: vi.fn(), + mockExactSettlePayment: vi.fn(), mockExtractPaymentPayload: vi.fn(), mockCreateSettlementHeaders: vi.fn(), mockGetBatchSettlementNetworks: vi.fn(), @@ -89,28 +96,65 @@ vi.mock("../src/llm_service.js", () => ({ advertisedModelIds: () => ["mistral-large-latest"], })); -vi.mock("../src/x402_server.js", () => ({ - createLLMResourceServer: mockCreateLLMResourceServer, - createBatchSettlementPaymentRequirements: mockCreateBatchSettlementPaymentRequirements, - create402Response: mockCreate402Response, - extractPaymentPayload: mockExtractPaymentPayload, - createSettlementHeaders: mockCreateSettlementHeaders, - getBatchSettlementNetworks: mockGetBatchSettlementNetworks, - releaseLock: mockReleaseLock, - // Real value (the scw-verbatim advertised maxTimeoutSeconds / lock TTL), imported by - // llm_routes.ts — see the constant's twin of this comment there. - LLM_MAX_TIMEOUT_SECONDS: 120, - // Real implementation (pure bigint math, no side effects) — keep in sync with - // x402_server.ts's actual formatUsdcAtomicAsDecimalUsd. - formatUsdcAtomicAsDecimalUsd: (atomicAmount: string) => { - const atomic = BigInt(atomicAmount); - const divisor = 1_000_000n; - const whole = atomic / divisor; - const fraction = (atomic % divisor).toString().padStart(6, "0"); - const trimmedFraction = fraction.replace(/0+$/, ""); - return trimmedFraction ? `${whole}.${trimmedFraction}` : whole.toString(); - }, -})); +vi.mock("../src/x402_server.js", async () => { + // The real registry — which asset is offered where is part of what these tests check, so + // the exact-alternative helpers mirror the real implementation against it. + const { USDC_ADDRESSES } = await import("@fretchen/chain-utils"); + const exactAlternativeEntry = (payTo: string) => ({ + scheme: "exact", + network: "eip155:8453", + amount: "10000", + asset: USDC_ADDRESSES["eip155:8453"], + payTo, + maxTimeoutSeconds: 60, + extra: { name: "USD Coin", version: "2" }, + }); + return { + createLLMResourceServer: mockCreateLLMResourceServer, + createBatchSettlementPaymentRequirements: mockCreateBatchSettlementPaymentRequirements, + create402Response: mockCreate402Response, + extractPaymentPayload: mockExtractPaymentPayload, + createSettlementHeaders: mockCreateSettlementHeaders, + getBatchSettlementNetworks: mockGetBatchSettlementNetworks, + releaseLock: mockReleaseLock, + // Real values, as the routes import them. + EXACT_ALTERNATIVE_NETWORK: "eip155:8453", + EXACT_ALTERNATIVE_AMOUNT_ATOMIC: "10000", + // Real semantics for the pure helpers — the tests below pin the entry they produce. + createExactAlternative: exactAlternativeEntry, + withExactAlternative: (challenge: { accepts: unknown[] }, payTo: string) => ({ + ...challenge, + accepts: [...(challenge.accepts ?? []), exactAlternativeEntry(payTo)], + }), + createResourceServer: mockCreateResourceServer, + // Real value (the scw-verbatim advertised maxTimeoutSeconds / lock TTL), imported by + // llm_routes.ts — see the constant's twin of this comment there. + LLM_MAX_TIMEOUT_SECONDS: 120, + // Real implementation (pure bigint math, no side effects) — keep in sync with + // x402_server.ts's actual formatUsdcAtomicAsDecimalUsd. + formatUsdcAtomicAsDecimalUsd: (atomicAmount: string) => { + const atomic = BigInt(atomicAmount); + const divisor = 1_000_000n; + const whole = atomic / divisor; + const fraction = (atomic % divisor).toString().padStart(6, "0"); + const trimmedFraction = fraction.replace(/0+$/, ""); + return trimmedFraction ? `${whole}.${trimmedFraction}` : whole.toString(); + }, + // Real semantics, EURe-first as the production preference orders it: every token priced + // in its own units, so the page can never quote fewer tokens than the challenge. + formatParallelPrices: (price: { EURe: string; EURC: string; USDC: string }) => { + const fmt = (atomic: string, decimals: bigint, symbol: "EURe" | "EURC" | "USDC") => + `${symbol === "USDC" ? "$" : "€"}${(() => { + const a = BigInt(atomic); + const div = 10n ** decimals; + const whole = a / div; + const frac = (a % div).toString().padStart(Number(decimals), "0").replace(/0+$/, ""); + return frac ? `${whole}.${frac}` : whole.toString(); + })()} in ${symbol}`; + return `${fmt(price.EURe, 18n, "EURe")}, ${fmt(price.EURC, 6n, "EURC")}, ${fmt(price.USDC, 6n, "USDC")}`; + }, + }; +}); // The real stablecoin registry — which asset a seller prices is the thing under test here. vi.mock("@fretchen/chain-utils", async (importOriginal) => ({ @@ -240,6 +284,18 @@ describe("sc_llm_x402", () => { transaction: "", network: "eip155:84532", }); + // The exact alternative's server: same shapes, its own mocks, so the two schemes' call + // paths can be asserted apart. + mockCreateResourceServer.mockImplementation(() => ({ + verifyPayment: mockExactVerifyPayment, + settlePayment: mockExactSettlePayment, + })); + mockExactVerifyPayment.mockResolvedValue({ isValid: true, payer: "0xpayer" }); + mockExactSettlePayment.mockResolvedValue({ + success: true, + transaction: "", + network: "eip155:8453", + }); }); describe("basic request validation", () => { @@ -375,11 +431,11 @@ describe("sc_llm_x402", () => { it("serves the favicon as raw bytes on GET /favicon.png", async () => { const res = await handleLLMRequest(makeEvent({ httpMethod: "GET", path: "/favicon.png" })); expect(res.status).toBe(200); - expect(res.headers.get("content-type")).toBe("image/jpeg"); + expect(res.headers.get("content-type")).toBe("image/png"); const bytes = new Uint8Array(await res.arrayBuffer()); expect(bytes.length).toBeGreaterThan(0); - expect(bytes[0]).toBe(0xff); - expect(bytes[1]).toBe(0xd8); + expect(bytes[0]).toBe(0x89); + expect(bytes[1]).toBe(0x50); }); it("answers a HEAD /favicon.png probe with 200 and an image content-type", async () => { @@ -388,7 +444,7 @@ describe("sc_llm_x402", () => { {}, ); expect(res.statusCode).toBe(200); - expect(res.headers["content-type"]).toBe("image/jpeg"); + expect(res.headers["content-type"]).toBe("image/png"); expect(res.body).toBe(""); }); @@ -1403,4 +1459,120 @@ describe("sc_llm_x402", () => { expect(mockVerifyPayment).not.toHaveBeenCalled(); }); }); + + describe("the exact alternative", () => { + const exactPayload = { + accepted: { + scheme: "exact", + network: "eip155:8453", + asset: USDC_ADDRESSES["eip155:8453"], + }, + payload: { transferWithAuthorization: "0xsigned" }, + }; + + /** One accepts entry appended AFTER the batch-settlement ones — the ordering is the + * contract (channel buyers read their entries first, channel-less clients fall + * through); the mock's batch accepts are empty, so appended-last also means + * index-0-of-one here. */ + it("appends the exact alternative after the batch-settlement accepts", async () => { + const res = await handle(makeEvent() as never, {}); + + expect(res.statusCode).toBe(402); + const challenge = (mockCreate402Response.mock.calls[0] as unknown[])[0] as { + accepts: Record[]; + }; + const exactEntries = challenge.accepts.filter((entry) => entry["scheme"] === "exact"); + expect(exactEntries).toHaveLength(1); + expect(challenge.accepts[challenge.accepts.length - 1]).toEqual({ + scheme: "exact", + network: "eip155:8453", + amount: "10000", + asset: USDC_ADDRESSES["eip155:8453"], + payTo: VALID_ADDRESS, + maxTimeoutSeconds: 60, + extra: { name: "USD Coin", version: "2" }, + }); + }); + + it("serves and settles an exact payment at the flat price, metering bypassed", async () => { + mockExtractPaymentPayload.mockReturnValue(exactPayload); + + const res = await handle(makeEvent() as never, {}); + + expect(res.statusCode).toBe(200); + // The exact server, for both payment steps; the batch pipeline never runs. + expect(mockCreateResourceServer).toHaveBeenCalledOnce(); + expect(mockExactVerifyPayment).toHaveBeenCalledTimes(1); + expect(mockExactVerifyPayment).toHaveBeenCalledWith( + exactPayload, + expect.objectContaining({ + scheme: "exact", + network: "eip155:8453", + amount: "10000", + asset: USDC_ADDRESSES["eip155:8453"], + payTo: VALID_ADDRESS, + }), + ); + // Flat price: the settle amount is exactly what was verified, NOT the usage-derived + // getSettleAmount of the batch path. + expect(mockExactSettlePayment).toHaveBeenCalledTimes(1); + expect((mockExactSettlePayment.mock.calls[0] as unknown[])[1]).toEqual( + expect.objectContaining({ scheme: "exact", amount: "10000" }), + ); + expect(res.headers?.["Payment-Response"] ?? res.headers?.["payment-response"]).toBe( + "encoded", + ); + expect(mockCreateLLMResourceServer).not.toHaveBeenCalled(); + expect(mockEnhancePaymentRequirements).not.toHaveBeenCalled(); + expect(mockVerifyPayment).not.toHaveBeenCalled(); + expect(mockSettlePayment).not.toHaveBeenCalled(); + // Mistral really ran (mainnet offer — no mock completion unless asked). + expect(mockCallLLMAPI).toHaveBeenCalledTimes(1); + expect(mockCallLLMAPI.mock.calls[0][1]).toBe(false); // useMock + }); + + it("rejects an exact payment naming a network it never offered", async () => { + mockExtractPaymentPayload.mockReturnValue({ + accepted: { + scheme: "exact", + network: "eip155:84532", // Base Sepolia — batch-settlement testnet, never exact + asset: USDC_ADDRESSES["eip155:84532"], + }, + payload: { transferWithAuthorization: "0xsigned" }, + }); + + const res = await handle(makeEvent() as never, {}); + + expect(res.statusCode).toBe(402); + expect(mockExactVerifyPayment).not.toHaveBeenCalled(); + expect(mockExactSettlePayment).not.toHaveBeenCalled(); + expect(mockCallLLMAPI).not.toHaveBeenCalled(); + }); + + it("answers an invalid exact payment with a plain 402, not the batch corrective", async () => { + mockExtractPaymentPayload.mockReturnValue(exactPayload); + mockExactVerifyPayment.mockResolvedValue({ isValid: false, invalidReason: "expired" }); + + const res = await handle(makeEvent() as never, {}); + + expect(res.statusCode).toBe(402); + expect(JSON.parse(res.body).error).toMatch(/expired/); + // No channel state to resync — no SDK corrective re-challenge. + expect(mockCreatePaymentRequiredResponse).not.toHaveBeenCalled(); + expect(mockExactSettlePayment).not.toHaveBeenCalled(); + expect(mockCallLLMAPI).not.toHaveBeenCalled(); + }); + + it("does not settle an exact payment when the model call fails", async () => { + mockExtractPaymentPayload.mockReturnValue(exactPayload); + mockCallLLMAPI.mockRejectedValue(new Error("mistral down")); + + const res = await handle(makeEvent() as never, {}); + + expect(res.statusCode).toBe(500); + expect(mockExactSettlePayment).not.toHaveBeenCalled(); + // Exact holds no lock, so the batch path's lock release must not run either. + expect(mockReleaseLock).not.toHaveBeenCalled(); + }); + }); }); diff --git a/bunny_js/test/mcp.test.ts b/bunny_js/test/mcp.test.ts index 2fc276c9d..133cb827b 100644 --- a/bunny_js/test/mcp.test.ts +++ b/bunny_js/test/mcp.test.ts @@ -231,6 +231,10 @@ describe("mcp_routes (stateless paid MCP endpoint)", () => { // the free first look: initialize and tools/list respond unpaid. expect(body).toContain("https://llm-agent.fretchen.eu/"); expect(body).toContain("https://web-agent.fretchen.eu/"); + // /fetch is REST-only (no MCP tool) — the page must still name the seller's + // most-called route and where it lives, or an MCP-only agent never finds it. + expect(body).toContain("GET /fetch"); + expect(body).toContain("single-page fetch"); expect(body).toContain(".well-known/agent-registration.json"); expect(body).toContain("https://mcp-agent.fretchen.eu/mcp"); expect(body).toContain("tools/list"); @@ -339,7 +343,7 @@ describe("mcp_routes (stateless paid MCP endpoint)", () => { // ════════════════════════════════════════════════════════════════════════════ describe("tools/call — the payment gate (no free path, by the real handlers)", () => { - it("answers an unpaid web_search with the real mainnet-only demands and never touches Brave", async () => { + it("answers an unpaid web_search with the real demands and never touches Brave", async () => { const res = await rpc({ jsonrpc: "2.0", id: 3, @@ -355,11 +359,11 @@ describe("mcp_routes (stateless paid MCP endpoint)", () => { expect(result.isError).toBe(true); expect(result.structuredContent.x402Version).toBe(2); expect(result.structuredContent.accepts.length).toBeGreaterThan(0); - // The security pin, on the real challenge path: search is mainnet-only — testnet - // coins must never buy metered Brave queries. A translation layer that minted its - // own accepts broke this once; it cannot anymore. + // Mainnets plus Base Sepolia — testnet is offered because a testnet payment buys the + // MARKED STAND-IN, never metered Brave. A translation layer that minted its own + // accepts broke the network pin once; it cannot anymore. for (const accept of result.structuredContent.accepts) { - expect(["eip155:10", "eip155:8453"]).toContain(accept.network); + expect(["eip155:10", "eip155:8453", "eip155:84532"]).toContain(accept.network); } expect(mockSearchWeb).not.toHaveBeenCalled(); }); @@ -425,7 +429,7 @@ describe("mcp_routes (stateless paid MCP endpoint)", () => { expect(result._meta["x402/payment-response"]).toMatchObject({ success: true }); }); - it("rejects a paid web_search on testnet with the handler's own network refusal", async () => { + it("runs and settles a paid web_search on testnet via the handler's own stand-in rule", async () => { const res = await rpc( { jsonrpc: "2.0", @@ -437,15 +441,21 @@ describe("mcp_routes (stateless paid MCP endpoint)", () => { ); if ("error" in res) throw new Error(`expected result, got: ${JSON.stringify(res).slice(0, 300)}`); + expect(mockVerifyPayment).toHaveBeenCalledTimes(1); + expect(mockSettlePayment).toHaveBeenCalledTimes(1); + // The REAL testnet-stand-in rule, exercised through MCP: a worthless-coin payment + // must buy the marked mock answer, never metered Brave. + expect(mockSearchWeb).not.toHaveBeenCalled(); const result = res.result as { - isError: boolean; - structuredContent: { error: string }; + isError?: boolean; + structuredContent: { results: Array<{ title: string; text: string }> }; + _meta: Record; }; - expect(result.isError).toBe(true); - expect(result.structuredContent.error).toContain("Unsupported or missing network"); - // Refused before anything metered runs. - expect(mockSearchWeb).not.toHaveBeenCalled(); - expect(mockVerifyPayment).not.toHaveBeenCalled(); + expect(result.isError).not.toBe(true); + expect(result.structuredContent.results[0].title).toContain("testnet mock"); + expect(result.structuredContent.results[0].text).toContain("testnet mock"); + // The settlement receipt, decoded from the handler's Payment-Response header. + expect(result._meta["x402/payment-response"]).toMatchObject({ success: true }); }); it("runs and settles a paid web_search on mainnet and returns the receipt", async () => { diff --git a/bunny_js/test/routes.test.ts b/bunny_js/test/routes.test.ts index bb8972b9c..8b38d0838 100644 --- a/bunny_js/test/routes.test.ts +++ b/bunny_js/test/routes.test.ts @@ -7,6 +7,7 @@ import { describe, test, expect, vi, beforeEach, afterEach } from "vitest"; // being asserted is that even a CORRECT signature is ignored. import { privateKeyToAccount } from "viem/accounts"; import { USDC_ADDRESSES, EURC_ADDRESSES } from "@fretchen/chain-utils"; +import { TESTNET_MOCK_MARKER } from "../src/search_stand_in.js"; // DNS is stubbed so these stay hermetic: /fetch resolves every hostname before connecting, and a // test that needs a working resolver fails on an offline machine for reasons that have nothing to @@ -29,6 +30,9 @@ const { mockCreatePaymentRequiredResponse, mockEnhancePaymentRequirements, mockReleaseLock, + mockCreateResourceServer, + mockExactVerifyPayment, + mockExactSettlePayment, } = vi.hoisted(() => ({ mockCreateLLMResourceServer: vi.fn(), mockCreateBatchSettlementPaymentRequirements: vi.fn(), @@ -40,16 +44,69 @@ const { mockCreatePaymentRequiredResponse: vi.fn(), mockEnhancePaymentRequirements: vi.fn(), mockReleaseLock: vi.fn(), + // The exact alternative's server (createResourceServer — no channels, no scheme object). + mockCreateResourceServer: vi.fn(), + mockExactVerifyPayment: vi.fn(), + mockExactSettlePayment: vi.fn(), })); -vi.mock("../src/x402_server.js", () => ({ - createLLMResourceServer: mockCreateLLMResourceServer, - createBatchSettlementPaymentRequirements: mockCreateBatchSettlementPaymentRequirements, - create402Response: mockCreate402Response, - extractPaymentPayload: mockExtractPaymentPayload, - createSettlementHeaders: mockCreateSettlementHeaders, - releaseLock: mockReleaseLock, -})); +vi.mock("../src/x402_server.js", async () => { + // The real registry — which asset is offered where is part of what these tests check, so + // the exact-alternative helpers mirror the real implementation against it. + const { USDC_ADDRESSES } = await import("@fretchen/chain-utils"); + const exactAlternativeEntry = (payTo: string) => ({ + scheme: "exact", + network: "eip155:8453", + amount: "10000", + asset: USDC_ADDRESSES["eip155:8453"], + payTo, + maxTimeoutSeconds: 60, + extra: { name: "USD Coin", version: "2" }, + }); + return { + createLLMResourceServer: mockCreateLLMResourceServer, + createBatchSettlementPaymentRequirements: mockCreateBatchSettlementPaymentRequirements, + create402Response: mockCreate402Response, + extractPaymentPayload: mockExtractPaymentPayload, + createSettlementHeaders: mockCreateSettlementHeaders, + releaseLock: mockReleaseLock, + // Real values, as the routes import them. + EXACT_ALTERNATIVE_NETWORK: "eip155:8453", + EXACT_ALTERNATIVE_AMOUNT_ATOMIC: "10000", + // Real value (x402_server.ts's batch networks — mainnets plus the testnet mock path). + getBatchSettlementNetworks: () => ["eip155:10", "eip155:8453", "eip155:84532"], + // Real implementation (pure bigint math, no side effects) — keep in sync with + // x402_server.ts's actual formatUsdcAtomicAsDecimalUsd. + formatUsdcAtomicAsDecimalUsd: (atomicAmount: string) => { + const atomic = BigInt(atomicAmount); + const divisor = 1_000_000n; + const whole = atomic / divisor; + const fraction = (atomic % divisor).toString().padStart(6, "0"); + const trimmedFraction = fraction.replace(/0+$/, ""); + return trimmedFraction ? `${whole}.${trimmedFraction}` : whole.toString(); + }, + // Real semantics, EURe-first as the production preference orders it: every token priced + // in its own units, so the page can never quote fewer tokens than the challenge. + formatParallelPrices: (price: { EURe: string; EURC: string; USDC: string }) => { + const fmt = (atomic: string, decimals: bigint, symbol: "EURe" | "EURC" | "USDC") => + `${symbol === "USDC" ? "$" : "€"}${(() => { + const a = BigInt(atomic); + const div = 10n ** decimals; + const whole = a / div; + const frac = (a % div).toString().padStart(Number(decimals), "0").replace(/0+$/, ""); + return frac ? `${whole}.${frac}` : whole.toString(); + })()} in ${symbol}`; + return `${fmt(price.EURe, 18n, "EURe")}, ${fmt(price.EURC, 6n, "EURC")}, ${fmt(price.USDC, 6n, "USDC")}`; + }, + // Real semantics for the pure helpers — the tests below pin the entry they produce. + createExactAlternative: exactAlternativeEntry, + withExactAlternative: (challenge: { accepts: unknown[] }, payTo: string) => ({ + ...challenge, + accepts: [...(challenge.accepts ?? []), exactAlternativeEntry(payTo)], + }), + createResourceServer: mockCreateResourceServer, + }; +}); /** The seller's own address — `NFT_WALLET_PUBLIC_KEY`, shared with the chat so both land on one * payment channel. Anvil account #2, standing in for it. */ @@ -180,6 +237,18 @@ beforeEach(async () => { mockExtractPaymentPayload.mockReturnValue(null); mockVerifyPayment.mockResolvedValue({ isValid: true, payer: "0xpayer" }); mockSettlePayment.mockResolvedValue({ success: true, transaction: "", network: "eip155:10" }); + // The exact alternative's server: same shapes, its own mocks, so the two schemes' call + // paths can be asserted apart. + mockCreateResourceServer.mockImplementation(() => ({ + verifyPayment: mockExactVerifyPayment, + settlePayment: mockExactSettlePayment, + })); + mockExactVerifyPayment.mockResolvedValue({ isValid: true, payer: "0xpayer" }); + mockExactSettlePayment.mockResolvedValue({ + success: true, + transaction: "", + network: "eip155:8453", + }); mockCreatePaymentRequiredResponse.mockResolvedValue({ x402Version: 2, error: "channel_busy", @@ -509,7 +578,7 @@ describe("discovery", () => { const res = await handleRequest(discoveryRequest("GET", "favicon.png")); expect(res.status).toBe(200); - expect(res.headers.get("content-type")).toBe("image/jpeg"); + expect(res.headers.get("content-type")).toBe("image/png"); expect((await res.arrayBuffer()).byteLength).toBeGreaterThan(0); }); @@ -541,9 +610,15 @@ describe("discovery", () => { expect(res.body).toContain(".well-known/agent-registration.json"); expect(res.body).toContain(".well-known/oasf.json"); expect(res.body).toContain("https://mcp-agent.fretchen.eu/mcp"); - // Prices restated on the page must be the spec's x-payment-info values. - expect(res.body).toContain("GET /search — $0.01 per call"); - expect(res.body).toContain("GET /fetch — $0.001 per call"); + // Prices on the page are generated from PRICE_ATOMIC — every accepted token in its + // own units, EURe first as the seller's preference orders it — plus the flat exact + // alternative; never a single-token restatement. + expect(res.body).toContain( + "GET /search — channel call: €0.01 in EURe, €0.01 in EURC, $0.01 in USDC; or exact: $0.01 flat (USDC, Base mainnet)", + ); + expect(res.body).toContain( + "GET /fetch — channel call: €0.001 in EURe, €0.001 in EURC, $0.001 in USDC; or exact: $0.01 flat (USDC, Base mainnet)", + ); expect(res.body).toContain("curl 'https://web-agent.fretchen.eu/search?q=erc-8004'"); expect(res.body).toContain("402"); expect(mockCreateBatchSettlementPaymentRequirements).not.toHaveBeenCalled(); @@ -567,9 +642,15 @@ describe("the 402 challenge", () => { ])("quotes %s at %s atomic units", async (route, amount) => { const res = await handle(makeRequest("GET", route, { auth: null })); - const { accepts } = JSON.parse(res.body) as { accepts: { amount: string }[] }; + const { accepts } = JSON.parse(res.body) as { + accepts: { amount: string; scheme?: string }[]; + }; expect(res.statusCode).toBe(402); - expect(accepts.every((entry) => entry.amount === amount)).toBe(true); + // The route's own price applies to every entry except the exact alternative (whose + // flat per-call price is pinned in its own test below). + const routeEntries = accepts.filter((entry) => entry.scheme !== "exact"); + expect(routeEntries.length).toBeGreaterThan(0); + expect(routeEntries.every((entry) => entry.amount === amount)).toBe(true); }); /** @@ -588,12 +669,26 @@ describe("the 402 challenge", () => { }); /** Testnet USDC is free and both routes spend real money, so a testnet payment would buy - * metered Brave queries for nothing. */ - test("offers mainnet only", async () => { + * metered Brave queries for nothing. The exact alternative is Base MAINNET — it joins the + * mainnet-only fold rather than widening it. */ + test("offers the batch networks: mainnets plus the testnet mock path", async () => { const res = await handle(makeRequest("GET", "search", { auth: null })); - const { accepts } = JSON.parse(res.body) as { accepts: { network: string }[] }; - expect(accepts.map((entry) => entry.network).sort()).toEqual(["eip155:10", "eip155:8453"]); + const { accepts } = JSON.parse(res.body) as { accepts: { network: string; scheme?: string }[] }; + expect( + accepts + .filter((entry) => entry.scheme !== "exact") + .map((entry) => entry.network) + .sort(), + ).toEqual(["eip155:10", "eip155:8453", "eip155:84532"]); + expect( + accepts.every( + (entry) => + entry.network === "eip155:10" || + entry.network === "eip155:8453" || + entry.network === "eip155:84532", + ), + ).toBe(true); }); test("quotes the route, not the query string, as the resource", async () => { @@ -816,7 +911,9 @@ describe("the paid path", () => { }); test("refuses a payment on a network it does not sell on", async () => { - mockExtractPaymentPayload.mockReturnValue(payment("eip155:84532")); + // Optimism Sepolia: in no network list here — not batch (no contract deployment there) + // and not the exact alternative (Base mainnet only). The refusal stays a plain 402. + mockExtractPaymentPayload.mockReturnValue(payment("eip155:11155420")); const res = await handle(makeRequest("GET", "search", { auth: null, query: { q: "x402" } })); @@ -859,3 +956,189 @@ describe("the paid path", () => { expect(mockSettlePayment).toHaveBeenCalled(); }); }); + +/** + * The exact alternative: the accepts entry appended AFTER the batch-settlement entries — + * one EIP-3009 signature per call, flat $0.01 USDC on Base mainnet, no channels. It exists + * for stock x402 clients that cannot open channels at all. + */ +describe("the exact alternative", () => { + const exactPayload = { + accepted: { + scheme: "exact", + network: "eip155:8453", + asset: USDC_ADDRESSES["eip155:8453"], + }, + payload: { transferWithAuthorization: "0xsigned" }, + }; + + /** One accepts entry, appended last, exact terms; the batch entries stay untouched ahead + * of it — the ordering IS the contract (channel buyers read their entries first). */ + test("appends one exact entry after the batch-settlement entries", async () => { + for (const route of ["search", "fetch"] as const) { + const res = await handle(makeRequest("GET", route, { auth: null })); + + const { accepts } = JSON.parse(res.body) as { + accepts: Record[]; + }; + const exactEntries = accepts.filter((entry) => entry["scheme"] === "exact"); + expect(exactEntries).toHaveLength(1); + expect(accepts[accepts.length - 1]["scheme"]).toBe("exact"); + expect(exactEntries[0]).toEqual({ + scheme: "exact", + network: "eip155:8453", + amount: "10000", + asset: USDC_ADDRESSES["eip155:8453"], + payTo: RECEIVER, + maxTimeoutSeconds: 60, + extra: { name: "USD Coin", version: "2" }, + }); + } + }); + + test("serves and settles an exact payment without touching channel machinery", async () => { + mockExtractPaymentPayload.mockReturnValue(exactPayload); + + const res = await handle(makeRequest("GET", "search", { auth: null, query: { q: "x402" } })); + + expect(res.statusCode).toBe(200); + // The exact server, for both payment steps; the batch server is never built. + expect(mockCreateResourceServer).toHaveBeenCalledOnce(); + expect(mockExactVerifyPayment).toHaveBeenCalledTimes(1); + expect(mockExactVerifyPayment).toHaveBeenCalledWith( + exactPayload, + expect.objectContaining({ + scheme: "exact", + network: "eip155:8453", + amount: "10000", + asset: USDC_ADDRESSES["eip155:8453"], + payTo: RECEIVER, + maxTimeoutSeconds: 60, + }), + ); + // Flat price: settle verifies and settles the same amount (no metering, no enhancement). + expect(mockExactSettlePayment).toHaveBeenCalledTimes(1); + expect((mockExactSettlePayment.mock.calls[0] as unknown[])[1]).toEqual( + expect.objectContaining({ scheme: "exact", amount: "10000" }), + ); + expect(res.headers["payment-response"]).toBe("encoded"); + // None of the channel pipeline may run. + expect(mockCreateLLMResourceServer).not.toHaveBeenCalled(); + expect(mockEnhancePaymentRequirements).not.toHaveBeenCalled(); + expect(mockVerifyPayment).not.toHaveBeenCalled(); + expect(mockSettlePayment).not.toHaveBeenCalled(); + expect(fetch).toHaveBeenCalledTimes(1); // the paid Brave call did run + }); + + test("rejects an exact payment on network terms it never offered", async () => { + mockExtractPaymentPayload.mockReturnValue({ + accepted: { + scheme: "exact", + network: "eip155:10", // Optimism USDC — offered for batch-settlement, never for exact + asset: USDC_ADDRESSES["eip155:10"], + }, + payload: { transferWithAuthorization: "0xsigned" }, + }); + + const res = await handle(makeRequest("GET", "search", { auth: null, query: { q: "x402" } })); + + expect(res.statusCode).toBe(402); + expect(mockExactVerifyPayment).not.toHaveBeenCalled(); + expect(mockExactSettlePayment).not.toHaveBeenCalled(); + expect(fetch).not.toHaveBeenCalled(); + }); + + test("answers an invalid exact payment with a plain 402, not the batch corrective", async () => { + mockExtractPaymentPayload.mockReturnValue(exactPayload); + mockExactVerifyPayment.mockResolvedValue({ isValid: false, invalidReason: "expired" }); + + const res = await handle(makeRequest("GET", "search", { auth: null, query: { q: "x402" } })); + + expect(res.statusCode).toBe(402); + expect(JSON.parse(res.body).error).toMatch(/expired/); + // No channel state to resync — so no SDK corrective and no advert of the failed payment. + expect(mockCreatePaymentRequiredResponse).not.toHaveBeenCalled(); + expect(mockExactSettlePayment).not.toHaveBeenCalled(); + expect(fetch).not.toHaveBeenCalled(); + }); + + test("does not settle an exact payment when the upstream fails", async () => { + mockExtractPaymentPayload.mockReturnValue(exactPayload); + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue({ ok: false, status: 502, statusText: "Bad Gateway" }), + ); + + const res = await handle(makeRequest("GET", "search", { auth: null, query: { q: "x402" } })); + + expect(res.statusCode).toBe(500); + expect(mockExactSettlePayment).not.toHaveBeenCalled(); + // Exact holds no lock, so the batch path's lock release must not run either. + expect(mockReleaseLock).not.toHaveBeenCalled(); + }); +}); + +/** + * The testnet stand-in: a paid request on Base Sepolia runs the FULL x402 machinery — + * challenge, validation, channel verify, settle-on-success, the Payment-Response header — + * but answers with the marked stand-in (search_stand_in.ts) instead of the metered + * upstreams. The invariant is one-sided and absolute: testnet money (free) must never + * buy Brave queries or /fetch egress; mainnet money must always get the real thing. + */ +describe("the testnet stand-in", () => { + beforeEach(() => { + mockExtractPaymentPayload.mockReturnValue(payment("eip155:84532")); + }); + + test("serves and settles a paid /search with the marked stand-in, upstream untouched", async () => { + const res = await handle(makeRequest("GET", "search", { auth: null, query: { q: "x402" } })); + + expect(res.statusCode).toBe(200); + expect(mockVerifyPayment).toHaveBeenCalledTimes(1); + expect(mockSettlePayment).toHaveBeenCalledTimes(1); + expect(res.headers["payment-response"]).toBe("encoded"); + // The security invariant: testnet money never reaches Brave. + expect(fetch).not.toHaveBeenCalled(); + + const body = JSON.parse(res.body) as { + results: { title: string; text: string; url: string }[]; + }; + expect(body.results).toHaveLength(3); + for (const result of body.results) { + expect(result.title).toContain(TESTNET_MOCK_MARKER); + expect(result.url).toContain("web-agent.fretchen.eu"); + } + // Echo: the caller's own query arrives in the stand-in answer. + expect(body.results[0].title).toContain("x402"); + }); + + test("serves and settles a paid /fetch with the marked stand-in, upstream untouched", async () => { + const res = await handle( + makeRequest("GET", "fetch", { auth: null, query: { url: "https://example.com/" } }), + ); + + expect(res.statusCode).toBe(200); + expect(mockVerifyPayment).toHaveBeenCalledTimes(1); + expect(mockSettlePayment).toHaveBeenCalledTimes(1); + expect(res.headers["payment-response"]).toBe("encoded"); + // The security invariant: testnet money never triggers egress. + expect(fetch).not.toHaveBeenCalled(); + + const body = JSON.parse(res.body) as { finalUrl: string; html: string; contentType: string }; + expect(body.finalUrl).toBe("https://example.com/"); + expect(body.html).toContain(TESTNET_MOCK_MARKER); + expect(body.html).toContain("no real web"); // loud it is + }); + + /** The other side of the invariant, pinned where it is defined: the same caller, the same + * route, only the network flipped — and the metered upstream runs. */ + test("flips back to the real upstream for a mainnet payment", async () => { + mockExtractPaymentPayload.mockReturnValue(payment("eip155:10")); + + const res = await handle(makeRequest("GET", "search", { auth: null, query: { q: "x402" } })); + + expect(res.statusCode).toBe(200); + expect(fetch).toHaveBeenCalledTimes(1); // the paid Brave call did run + expect(JSON.stringify(res.body)).not.toContain(TESTNET_MOCK_MARKER); + }); +}); diff --git a/scw_js/discovery.ts b/scw_js/discovery.ts index af0e5fc48..f08885767 100644 --- a/scw_js/discovery.ts +++ b/scw_js/discovery.ts @@ -43,7 +43,7 @@ export function buildRootPage(input: RootPageInput): string { - + ${escapeHtml(input.title)} diff --git a/scw_js/favicon.ts b/scw_js/favicon.ts index 6693e3ff5..922b31605 100644 --- a/scw_js/favicon.ts +++ b/scw_js/favicon.ts @@ -1,3 +1,3 @@ export const faviconBase64 = - "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAQABAADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDLopTSV9EeSJS0UUxBSUtFABRRRQAUUUUAJRS4ooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACkpaKAEopaKACilpKACiiigAoopcUAJRRRQAUUUUAFJS0UAFFFFACUUtFABRRRQAlFLRQAUUUUAFJS0UAFFFFABRRRQAlLRRQAUUUUAFFFFABRRRQAlFLRQAUUUUAFFFFABRRRQAlFLRQAUUUUAJS0lLQAUUUUAFFFFABRRRQAUUUUAFJS0UAJRS0UAFFFFACUtFFACUUtFACUtFFABRRRQAUUUUAJRS0UAJS0UUAFFFFABRRRQAUUUUAFJS0UAJRS0UAJRS0UAFFFKKQAaSlNFACUUUUwCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKWgBKKWkoAKKKKACiiigAooooAWkoooAKKKWgBKKKKAFpKWg0AJRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRS0AJRRRQAUUUtACUUGigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKWiigAooopABpKKKYBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFGaKKAFpKKKACiiigAooooAKKKWgApKWigBKKKKACilooAKQ0tJQAUUUUAFFFFABRRRQAUUUUAFFFLQAlFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFLQAlLRRQAlFFFABRS0lABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUtJQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUooAKSlNFIBKKKKYBRRRQAUUUUAFFFFABRRRQAUUUtABSUtJQAUUUUAFFFFABRRRQAUUUUAFFFFABRRS0AJRRRQAUUtJQAUUUtABRRRQAUUUUAFJS0lABRRS0AJS0UUAJRRRigAopaMUAJRS0UAJRRRQAUUUUAFFFFAAKWiigBKKKWgBKKKKACiiigAoopcUAFJS0lABRRS0AJRS0lABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAAKWikoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigApaSloAKKKKQCUUUUwCiiigAooooAKKKKAClpKWgBKKKKACiiigAooooAKKKKACiiloASilpKACilooASilpDQAUUUUAFBopaAEopaKAEopaKAEopaSgBaSlooAKSlpKACiiigApaSloAKKSloAM0UlLQAUlLSUAFLSUtABSUtJQAUUUUAFFFFABRRRQMKKKKAFooooEFJS0lAC0UUUAFJS0lABRRRQAUtJRQAUUUUALSUtIaACiiigApaKKACkpaKAEooooAWikooAKKKKACiiigAooooAKKKKAClpKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKWkooAKKKKACiiigAooooAKKKWgBKWilFACUUUUgEooopgFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUtJRQAtJS0lABRRRQAZooooAKKKKACiiigApaSigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACilooASloooAKSlpKACiiigAooooAKKWigBKKWkoAWikpaACkpaSgAooooAKKKKACiiigAooooAKWkpaACkpaKAEooooGFLSUUCCiiigAooooAKKWigBKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooASloooAKKKKACiiigBaKSloAKKKKQCUUUUwCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKBhRRRQIKKWkoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAopaKAEooooAWkoooAWkpaMUAJRS0UAJRRRQAtFJS0AFFFFACUUUUAFFLRQAlFFFABRRRQAUUUUAFFFFABS0lFAC0lLRQAlFFFABRRRQAUUUYoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKWkNABRRRQAUUUUAFFFFABRRRQAUtJSigBKWiikAlFFFMAooooAKKKKACiiigAooooAKWkpaACiikoAWkpaSgAopcUlABS0lLQAlFFFABRRRQAUUUUAFFGKKACiiigAooooAKKKKACilooASloooASilooASiiigAoopaACiiigAopKKACilpKACiiigYtFJS0hBSGlooASiilpgJRRRQAUUUUAFFFFAC0UUlAC0lLSUAFFFFABRRRQAUUUUAFFFFAC0lFFABRRRQAUUUtACUtJRQAtJS0lABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUALSUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABS0lKKACiiikAlFLSUwCiiigAooooAKKKKACiiigAooooAKKKKACiiigBaKKKAEopaSgAooooAKKKKACiiigBaSiigAooooAKKKKACiiloAKKKKACiiigAooooASiiigAopaKAEoopaAEopaSgBaSiigAooooGFLSUtAgpKKKACjNFFABRRRQAUUUUAFFFFAC0GiigBKKKKACiiigAooooAKKWjFACUUUUAFFFFABRRRQMKKKKBBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRS0AFFFFIAopKKYBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAC0lFLQAlFLSUALmkoooAKKKKACiiigAooooAKWkooAWkpaKAEopaSgAooooAWiiigAooooAKKKKAEooooAWiiigBKWkooAWkpaSgAooooAKKWigBKKWkNABRRRQAUUUYoAKKWigBKWkpaACkoooAWkoooAKKKKACiiigAooooAWikooAWkpaSgAopaSgAooooAKKWkoAKKKKACiiigAoopaACkpaSgAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAClpKUUAFFFFIBKKKKYBRRRQAopKWkoAKKKKACiiigAooooAKKKKAClpKKACiiigAooooAKKKKACiiigAooooAKKKKAFopKXNABSUtJQAUUUUAFFFFABRRRQAUUUUAFFLSUAFFFFABRRRQAUUUUAFLRRQAUUUlAC0lLRQAlLRRQAUUUGgBKKKWgBKKKKACiiigYUUuKSgQUUUUAFFFFABRRRQAUUUUAFFBooAWkpaQ0AFFFFABRRRQAUUUUAFFFFABRRRQAUUtJQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAwpwpKKBBRRRSASiiimAUUUUAFFFFABRRRQAUUUUAFLSUtABSUUUAFFLRQAlFFFABRRRQAUUUUAFFFFABRRS0AFFFFACUUtJQAtJS0lABRRRQAUUUUALRSUtACUUUUAFFFFABRS0lABRRS0AJRRRQAtFFFAAaSlpKAFooooASilpKACiiloASilpKACilpKBhRRRQIWkoooAKWkooAKKKKAFopKWgAoopDQAUUUUALRSUtACUUtJQAUUUUDCiiigQUtFFABSUtJQAtJRRQAUUUUAFFLSUAFFFFABRRRQAUUUUAFFFFABRRS0AJRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUtJS0AFFFFIBKKWkpgFFFFABRRRQAUUtJQAUUUUAFFFFABRRS0AFFFJQAUUUUAFLSUUAFFFFABRS0UAJRS0hoAWikpaAEooooAUUUUUAFJS0UAJRSmkoAKKKKACiiigYUUUtAgooooAKKKKACiiigAooooAKSlooAKSlooASilpKACiiloASiiigBaSiigAopaKAEooooAKKKKAFpKWkoAKKKKAFpKKKAFoNFFACUtJRQAtJS0UAJRS0mKACilooASiiigBaSiigAooooAKKKKAFpDRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUtJS0AFFFFIBKKKWmAlFFFABRRRQAUUUtACUUUUAFFLSUAFLSUUALSUtJQAUUUUAFFFFABRRRQAUUUUAGaKKKAClpKKACiiigAopaKACiikoAKKKWgAooooAKKKKACiikNAC0UlLQAUUlFAC0lLRQAUUUUAFFFJQAtFFFABSUUUALRSZpc0AJRRRQAUtAooAKKKKACkpaQ0ALSUUUAFFFFABRRRQAUUUtABRRSUAFFFFABRRRQAtFJS0AIaKKKACiiloASiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigApaKKACiiikAUlFFMAooooAKKKKACilooASiiigAooooAWiiigAoNFFACUUUUAFFFFABQaKWgBKKKKACiiigAooooAKWkooAKKKKAFpKKKACiiigBaSlooAKQ0tJQAUUUUALSUUtACUUUUALRRRQAUlLRQAUlFFAC0lLRQAlFFFABRRRQAUUUUAFFFFAC0UUUAJRRRQAUUUUAFFFFABRRRQAUtJS0AFJS0UAJRS0lABRRRQAUtFIaACiiigAooooAKKKKACiiigApaSloASiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAFpKKKACiiigAooooAKKKKACiiigAooooAKWkooGLRRRQIKKKKQCUUUUwCiiigAooooAKKKKACiiigAoopaACikpaACg0UlABRRSigBKKWigBKKWkoAKKKKACiiigAooooAKKKKACiiigAooooAKKKWgAooooAKKKKAEopaSgAooooAKKKKACiiigBaKKKAEopaSgBRRSUUAFFLSUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFLRQAlFFFABRRRQMWkpaKBBSUtJQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUALSUUUAFFFFABRS0UAJRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUtJS0AKaSlNJSAKDRRTASiiigAooooAKKBRQAUUUUAFLRRQAUlLSUAFFFFABS0lFAC0UlFAC0lLQaAEooooAKKKKACiiigAooooAKKKKACiiloASlopKAClpKKAFopKKAFpKKWgBBS0UUAJRRRQAUUUUALRRRQAUlLSUAFFFFABRRRQAUUlFADiCMZBGelJV/UohFbaaQPv2oY/99tVCkndXGFFFFMQUUUUAFFFFABRRRQAtJRRQAUUUUALSUUtACUuaSigBaSiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKWigBKWkpaAA0UUUgCiiimAlFFLQAlFFFAC0lFFABRRRQAUUUUAFFFFABRRS0AFJS0lABRRRQAtFJS0AJRRS0AJRRRQAtJRRQAUUUUAFFFFABS0lLQAUlLRQAlFFFABRRS0AJS0UUAFFFJQAUUUUAFFFFAC0lFFABRRRQAUUUUAFFFSRxGVtoZAf9tgo/M0AR0YrSh0SefBF1YID/fu0H9a2NO8ET3s6q2q6YoPa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iigAooooAKKKKAFpKKKACiiigAooooAWkoooAKKKKACiiigBaSiloASiiigAooooAKKKKACiiigAooooAKKKKACiiigAopaSgAooooAKKKKACiiigAooooAKKKKACiiigAooooAKWkpaACkpaKQCUUUUwCiiigAooooAKKKKACilpKACiiigAooooAKKWigBKWkpaACg0UlABRRS0AJRS0lABRRRQAUUtFABSUtFACUUUtABSUUUDCiiigQUUUUALQaSigApaSjNABRRRQAUUUUAFFFFABRRRQAUUUUAFKBSYq3p9jNqV7HawDLuevYDuTSbsrsaVzT8NaEdYvN0gItYiDIf7x7KK9PjRY41RFCqowAOgFVtO0+HTLKO1gGEQcnux7k1YmlSCF5ZGCogLMx6AV5dWo6kjrhHlRT1bVIdJsXuZiMjhEzyzdhXlF9ezahdyXM7bpHOT7ew9qu+INak1rUDJysCfLEnoPU+5rJrtoUeRXe5z1J8zCiiiugzCiiigAooooAKKKKAOq8Ia/8AYbgWNy3+jyn5WJ+43+B/z3r0QHNeIivQ/CGv/bIBY3T5njHyMTy6/wCI/wA964cTRt7yOilPozT8Q6JHrNkVAC3CZMT+/ofY15ZLG8MjRyIUdSQynqD6V7XXI+MdBFxC2pWy/vYx+9UfxL6/UfyqcPWs+Vjqwvqjz+kzSmkr0DmFooooAKKKKACiiigAooooAKKKKACiiigAoopaAEopaSgAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigApaKKAEooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAFoopRQAlJS0lIYUUUUxBRRRQAUUtFACUUUUAFFFFABRRiloAKSiigBaKSloAKSlooASiiigAooooAWiiigBKKKKAFoopKAFoNFFACUtFFACUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUoFADkVnYKoJYnAA716d4a0JdIs98oBupRmQ/3f9kVj+DvD+Nup3Sf9cFI/8e/wrtOlefia13yo6KUOrFzXn/jHxAbqZtOtX/cRn96wP329PoP51t+Lde/s6zNrbvi6mGMjqi9z9fSvN8VWGo/bYVZ/ZQlFLRXcc4lFLilEbnorH8KLgNop/kyf883/AO+TS+RL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U7pWBEUQPLn/AA9TVzUtRt9LsZbu6cJHGMn1J7AepNeJa5rdxrupSXc/yqfljjzwi9hV4ah7SV3sTWqcqstytqOoXGqX0t3dOXlkOT6D0A9hVSiivZSSVkee3cKKKKYgooooAWikpc0AFJS5pKACiiigAooooAKKKKACiiigAooooAKKKKACiiigDT0a68m58pj8knH0PaujPIrilJByDyK62yuBc2iSdyOfr3rCpHW5rB9DndStvs14wAwjfMtU66PWrYS2nmAfNGc/h3rnK0hK6IkrMKWkpRVkigV2fgXTd002oyLwn7uP69z/AE/GuPijaWRI0Us7EKoHcmvXtMsU07T4bVMfu1wT6nufzrlxM7Rsa0o3dy52rgfHep+bcxadG3yxfPJ/vHoPy/nXb3t3HY2c1zKcJEpY/wCFeO3dw93dS3Ehy8jFj+NYYWF5cz6GtaVlYhooor0TlCiiigAooooAKKKKACiiigAooooAKKKKACiiigAoopaAEooooAKKKKACiilNACUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFLSUooAKKKKQCUUUUwCiiigAooooAKKKKAFopKWgApKWkoAKKWigBKKKKAFpKWigAooooAKSlFFABRSUtABSUtJQAUUUUALRSUtACUUUUAFFFFABRRRQAUUUooAmtbmS0uoriI4eNgy/UV7BZXUd9ZQ3UX3JUDD29q8aruvAup74JdOkPzR/vI8+h6j8/51yYqF48yNqMrOx1GpWKajp09pJjEi4B9D2P515BNC8ErxSKVdGKsD2Ir2mvPPG+m/ZtSW8RcR3A+b2cdfzH9aywtSz5WXWj1OUpRRSgV6BzGxoNvukknI+78q/XvW27BEZieAMmq+nW/wBnsIkIwxG4/U1DrM3lWDqD80hCj+tcz96RstInO3MxuLh5W6sajpKBXRsYnsXw+cP4ThA/hkcH/vquoJABJOAOprzb4e+IbSxhn069nWEM/mRO5wvTBGe3SrXjfxjAbN9M0yZZGlGJpozkKvdQfU/y/TyKlGUqzSR3wqJU0znPGfiQ65qXkwOfsUBIjx0c92/w9vrXMUlFepTgoR5UcUpOTuwooorQkKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAWkNFFABRRRQAtbOgz/NJAe43D+v9Kxans5zbXUcvYHn6VM1dDi7M62RQ6FSMgjBrj54jBM8Z/hYiuyHIB7Guf12DZcJMBw4wfqP8isabs7Gs1pcyaUUlOUFiABkmtzE6nwTpf2nUWvZBmO3+77uen5dfyr0TGKzNA03+zNGggYYkI3yf7x6/wCFaE8yW8Ek0hwkalmPsK8qtNzmdkFyxON8eaptSLTYzycSS49Ow/r+VcLVrUr2TUdRnu5PvSNnHoOw/KqtejShyRSOacrsKKKK1ICiiigAooooAKKKWgBKKKKACiiigAooooAKKKKACiiigAooooAKKWkoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoopRQMKKKKQhKKKKYBRRRQAUUUUAFFFGKACilooAKKSloAKKKSgAooooAWiiigBKKKKAClpKWgApKKKACiiigAooooAKKKKACiiloGJRS0UCEooooAKWkooAXNXNMvn07UYLpM5jbJHqO4/KqVKKTV1Yadnc9rhlSeFJY23I6hlPqDWdr+mDVNImgAzIBvjP+0On+H41k+CNT+06c1lIf3lufl91P+B/pXV15Mk6czsTUoniDAqxBGCOxq1p0H2m9jQj5c5b6Ctbxhpn2DWGlRcRXGXGOx7j+v403QLfbHJcEfe+Vf616XOnG6OTl96xs9q53XJvMuViHRB+proZGCIWJwAMmuOnlM07yH+Jiaikru5c3pYhopTSV0GQZooooAKKKKACiiigAooooAKKKWgBKKKKACiiigAooooAKKKKACiiigAooooAKKKKACkoooAWiiigAooooA6vS5/PsIyeoG0/hSarb+fYvgfMnzD8P8ms7QZ8SyQHow3D6/5/lW+QCMGuWXuyN1qjh66Hwhpv2/WFkdcw248xvc9h+f8AKsW9tzbXkkXQA8fSvS/Cel/2do6F1xNP+8f29B+X86qvU5YaEU43kb3auU8b6n9n05bJD+8uDlvZR/if611LsFUknAA5NeSa9qR1XVprgHMYOyMf7I6fn1/GuTDQ5p37G9WVlYzTSUUV6ZyBRRRQAUUUUAFFFFABS0lFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAwooooEFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUCgBaKKKQBSUtJTAKKKKACiiigApaKSgBaKSloASilpKACiiigAooooAWiiigBKKKKACiiigAopaTFABRRRQAUtJRQAUUUtACUtFFABRRRQAlFFFABRRRQAUUUtAGnoWonS9VhuMny87ZB6qev8Aj+FesKwZQwIIIyCK8VFek+D9T+3aSIHbMtt8h917H+n4VxYuGnMjooy6FvxJpY1TS2QD97Gd6f1H5VgRQLbxLEowFGBXbEZFctqUH2e6ZcfKeVrGlN25S5LW5h6zceTYsoPzSfKP61zFXtWuvtN6wU/u4/lX+pqhXoU1ZHNJ3YUUUVZIUUUUAFFFFABRRRQAUtJRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFACUUUUALRRRQAUUUUATWk5trmOUfwnJ+neuxQh1BByCMg1xFdLoV151v5DH54+nutY1Vpc0g+hqQaNFqWp27yLlYzucf3gOg/PFdqOBxWbpFt5Vv5pHzP0+laROBXnVZuTsdMYpanPeMdT+xaM0KNiW4+QY7L3P8AT8a8zrZ8Tan/AGnrErI2YYv3cf0HU/iaxq9ChDkgc1SV2JRRRW5mFFFFABRRRQAUUUUAFFFLQAlFFFABRRRQAUUUUAFFFFABRRRQMKKWkoEFFFFABRRRQAUUUUAFFFFABS0lLQAlFFFABRRRQAUUUUAFFFFABRRS0AFFFFIBKKKKYBRRRQAUUUUAFFFFABS0lFAC0lFFABRRS0AJRS0UAFJS0UAJRRRQAUUUUAFLmkooAKKKKACiiigApaSloAKSlooAKKKKAEooooAKKKKAFpKWkoAUVv8Ag+aePxBCkILLICsg/wBnufwrAr0HwPpnkWL37r+8n+VPZB/if5VhXklB3LppuR1lc/4sXbpDSx/65eFx6d66A1zesT+fcMmcog2j+tefSXvXOqex5tSVb1C2+y3jxgfL1X6VVr1k7o4mJRRRTAKKKKACiiigBaSlpKACiiigAooooAKKKKACiilxQAlFFFABRRRQAUUUUAJRS0UAFdL4X8IT+I1lmacW9tG23ft3Fm9AMj865qux8HeMINAt5bS7hkeB38xWjwSpxg8HtxWNdzUPc3NKai5e8ZnijwxN4cuYgZRNBMD5cm3ByOoIrBrpPGHigeI7qFYImitYM7A/3mJ6k+n0rm6dHn5Fz7inZS90KKKK1ICiiigAq9o5kGqwbMkbvm/3e/6VRrovD9qI42uWHzPwv0/z/KoqP3WVFanpSABAFGFAwPpWd4hlng0G7ktx+8CfkOhP4DJqbS7kT2oB+8nyn+lXZEWSNkdQysMEHuK8n4ZanZujxKitDW9PbTNVntiuFByh9VPT/PtWca9eLTV0cT0YUUUVQgooooAKKKKACiiigAooooAKKKKACiiigAopaSgAooooAKKKKACiiigAooooAKKKKBhRRRQIKWkooAKKKKBhRRRQIKKKKACiiigAoopaACiiigANFFFIBKKU0lMAooooAKKKKACiiigAooooAKKKKAClpKWgAoopKAClpKWgAooooASilpKACiiloASilpKACilpKACilpMUALRRRQAUUUUAJRRRQMKKKKACgUtKBQIt6bYvqOoQ2qdZGwT6Dufyr16CFLeCOGNdqIoVR6AVx/gbTNscuoSLyTsj/qf6fnXZ15uJnzS5TqoxsrlDW9RTS9KmuWxuA2oPVj0/x/CuYhmW4hWUdHGap+OtT+0X0djGf3cAy/ux/wABVXQLjdbNAx5jOR9D/k1pTpWhzEud5WJNctRJbeco+aPr9K5qu2lUSRMjDhgQa4qRGjlZG6qSDXRSeljOohtFFFbGYUUUUAFFFFABRS0lABRRRQAUUUtABSUtFABRRQaAEooooAKKKKACiiigBRQaVFLsFVSzMcAAZJNd1pPw1urq2WbULr7KWGREi7mA9znA/WsqlWNP4mXGDlscHRXba38Or3T4GuLGb7XGoyybcOB7DvXFcjg06dSNRXixShKO4lFFFaEhRS0lABRRQKAJIITPPHEvVmxXZRRiKJUUYVRgCsDQYN9y0x6IOPqf8muhPArnqvWxrBdSWx1dLDVreCRsJP8AKx9PQ/nXZZrxu+ufPvZJAeAcL9BXpvh3Uv7T0eGZjmVfkk+o/wARg1zV6VkpGtOd7oy/G2mfadOW9jX95bn5vdD/AIHH6155ivapolmheJ1DI6lWB7g15Fqlg+m6jNauD8jfKfVex/KtcLO65WRVjrco0UppK7DAKKKKAFFFJRQAUUUUALRRRQAlFFFABS0lLQAUlLSUAFFFFABRRRQAUUUUAFFFFABRRS0AJRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAtFFAoAKKKKQBSUUUwCiiigAooooAKKKKACiiigAooooAKWiigBKKDRQAUUUUALRRRQAUlFFABS0lLQ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JPAAptdr4N0Ddt1S6T5R/qFPf/a/wrOpNQV2VGLk7Gz4Z0IaVaebMo+1Sj5v9kf3f8a36MYrnfFeu/2XYmCB8XcwwuOqL3b/AA/+tXme9VmdekImN4x18TyNplq/7tD++YfxN/d+g/nXGmgkk5PNJXp04KEbI5JS5ncKKKK0JCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKWiigApKKKACiiigAooooAKKKKACiiigApaSigBaSiigAooooAKKKKACiiigAooooAKKKKACiiigAoopaAEooooAKKKKAClFJSigYUUtJSEJRRRTAKKKKACiiigAooooAKWkpaACiiigApKWigBKKKXFABRRRQAUlLRQAlFFFAC0UUUAJRRRQAtFFFABSUtJQAUUUUALRRRQAlLRRQAUUUUAFFFFACGilooASilxSUAFLSUtABSUtJQAUUUUAFFFFABRRRQB1HhDxB9guPsVy+LaU/Kx6I3+B/z3r0YHNeI16F4Q8Qfa4Rp9y37+Mfu2J++o7fUfy/GuHE0ftI6KU/ss19f0WPWbAxkATplon9D6fQ15ZPDJbyvFKhR0OGU9Qa9orlfF+gfbLc39sn+kRj94oH319fqP89qjD1uV8r2HVhfVHnlJQaK9E5gooooAKKKKAClpKu6Zp02qX0drCOWPLdlHcmk3ZXY0rmn4Y0A6veebMp+yRHLn++f7v+NenIqogVQFUDAAHSq9jZQ6fZx20C7Y0GB6k9yfc1NNNHBC8srBY0UszHsBXl1ajqSOuEeVFPWNUg0mwe5mPThF7s3YV5PfX02oXkl1O2ZJDk+g9APar3iHW5Nb1Aycrbx/LEh7D1Puaya7aFLkV3uc9SfMwoooroMwooooAKKKKACiiigAooooAKKWigBKKWigBKKWigBKKKKACiiigApaSloASiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigApRSUooAXNJRRSASig0UwCiiigAooooAKKKKACilooASiiigAooooAKWkpaACiiigApKWigAooooAKSiigApaTFLQAUUUlAC0lLSUAFLSUtABRRRQAlLRRQAUUUUAFFFFABRRRQAUlLRQAUUUUAFFFFACUUtJQAUUUUAFFFFABUkE0lvMk0TlJEIZWHY1HS0PXQD1fQNYj1iwWUYEyfLKnof8DWseRXkWkanNpN8lzFyBw6Z4Yeleq2V7Df2kdzA26Nxke3sfevLr0uSV1sddOfMjgvF3h/7DP9utk/0aQ/Oo/gb/A/57Vy1e1XFvFdW7wTIHjcbWU9xXlOu6PLo9+0LAmJuYn/ALy/4104atzLle5lVhZ3Rl0UUV1mIUopKUUASRxPLIqIpZmOAB1Jr0/w5oaaPZDcAbmQZlb09h7CsjwhoIijXUrlf3jD9ypHQf3v8K7GvPxNa/uo6aULasWuB8Y+IPtMjabav+5Q/vWH8bDt9B/OtrxXr39mWn2aB/8ASphwR1RfX/D/AOtXmxOTTw1G/vsVWp0Q2iiiu85wooooAKKKKACiiigApaKKAEpaKSgBaKSigAzS0lFAC0UlFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABS0lFABRRRQAUUUUAFFFFAwooooAKKKKBBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQMKWkpRQIKKKKQBSUUUwCiiigApaSloASilpKAFopKWgApKKKACiiigApaSigBaSlpKAFooooAKSlpKACiiigApaSigAooooAWkpaKAEpaSloAKKDSUALRRRQAUhpaSgAoopaACiiigAooooAKTNLRQAUUlFAC0lFFAC0UUlAC0UlLQAUUUUAKDXQ+F9eOl3fkTt/osp+Yn+A+v8AjXO0ucGonBTVmVF2d0e2AggEdDWbrekRaxYPbvhZB80b/wB1v8KwPBuv+co0y5f51H7lj3H938K7GvLlF05nWmpo8Wubaa0uHgnQpIhwwNRV6J4x0L7bam/t0/0iEfOAPvr/AIj/AD2rzzFelSqKcbnLOPKxK6XwroH9p3X2m4X/AESI8g/8tG9Pp61iafYT6nfR2sC5ZzyeyjuTXrllaRWNnFbQriONdo/xrLEVeVWW5VKF3dk4AAwBgVS1fU4tJ097mXkjhVHVm7CrrMFUsxAAGST2rzzxHq39raa86f6hL3y4uOqhOv4nJrjpQ55a7G85cqOdvbyW/vJLmdsySHJ9vYe1QUmaK9VKysjjCiiimAUUUUAFFFFABRRSmgBKKWkoAWkoooAWkopaAEopaSgAooooAKKKKACilooASilooASiiigAooooAKKKKACiig0ALSUUUAFFFGKACiiigAooooGFFFFAgooooAKKKKBhRS0UCEopaKAEooooAKKKKACiiigAoFFAoAWiiikAlFFFMAooooAKKKWgApKWkoAKKBS0AJRRRQAUUUUAFFFFABRS0UAFFFFABRRRQAUUUUAJRRRQAtFFFABRRRQAUlFFABRRRQAtFIKWgAooooAKKKKACiiigAooooAKKKKACkpaSgAopaKAEoNFFAC0UUUAFJS0lABRRRQA+KV4ZUkjYq6EMpHUEV6xoOrprOmJOMCVfllUdm/wPWvJK0dF1ifRb0TxfMh4kjJwHH+PvWFelzx03NKc+V6nrxGaxLvwjpF5cGZ4XRm6iJ9oP4Vb0zWbLVoRJbSgtj5o24ZfqK0M15t5QfY6rKRTsNJstMjK2kCx56t1Y/UmrmKXsSeAOp9Kx9U1u3it3js9R09bnpmWcYX34zk0kpSYNqKMHxr4h8hG0u1f94w/fMOw/u/4/wD66wfs2fAouCcAX4UD1+Sp4/Cct9MXOs6W0kjZZjd7iSe/Su+/4Q23PhBdGaf5gfN8/HHmdc49O30rq9pCkkkY8kp3Z49ilrprzwc9oxB1nSj7NcbT+VZE+kvDnN/pz4/uXIP9K7I1Iy2Zi4tbmfRQRtYjIOO4OaK0JCiiigAooooAKKKKBhRRRQIKKWkoAKKKKADNLSUtABSUUUAFFFFAC0UZooAKSlooAKSlpKAFpKKKACiiigAooooAKKKKAFopKM0AFFFFABRRRQAUUUUAFFLSUAFFFFABS0UhoAWikpaAEopaSgAooooAKKKKACloooAKKU9aSkAlFFFMAooooAKKKKAFpKKKAClpKKACiiigAoopaAEopaKACiiigApKKKAFopKWgApKWkoAKKWigBKKXFFACUUUUAFFFFAC0UUUAFFFFABSUtFABRRRQAUUUUAFFFFABRRRQAmaKWkoAWikpaACiiigAooooAKSlooASilooASiiigByO8bh0Yqw6EHBFaKeIdXjXauo3GPd8/zrMoqXFPdDu0W7nVL68GLm8nlH915CR+XSqmaKKaSWwXErul164/4VkyNKTL532QN32dcf988VwtannH/AIRXyc8fbt2P+2dZVYKVr9yoSauZmaM0UVqQFFFFMAooooAKKKWgBKKWigAooooAKSlpKACiiloASiiigAooooAKKKKACiijFAC0UlLQAUlLSUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABS0lLQAUUUUAJRRRQAUtJS0AJRS0lABRRRQAUUUUAFFFFAC0UUUAFFFFIBKKKKYBRRRQAUUUUAFFLRQAlFFFABRRRQAtFFJQAtFFFABSUUUAFFFLQAlFLRQAUlLSUAFLRRQAUlFFABRRRQAUUUUAFLSUUAFLRRQAlFLRQAUUUUAFFFJQAtFFFABRRRQAUlFFABRS0UAFFFIaADNFFFABRRRQAZopaKAEoopaAEopaSgAooooAKm84fYvIwc+Zv8A0x/jUNFABRRRQAUUUUAFFFFABRRmigAozRS0AFFFFABSUtJQAtJRRQAtJS0UAJRRRQAUUUUAFFLSUAFLSUUAFFFFABRRRQAUUUUAFFFFABRRRQMKKKKBBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABS0lFABRRRQAUUUUAFFFLQAUUUUAFFFFIBKKKKYBRRRQAUUUUALSUUtACUUUUAFFLSUAFFFFAC0UlLQAlFFFABRRS0AFFFFACUUtFABSUtJQAUUtJQAUUUUAFFFFABRRRQAtFFFABRRSUALRSUtABSUtJQAtFIKWgAoopKACiiigAooooAWkoooAKKKKACiiigBaQ0UGgApaSigBaSiigAooooAKKKKACiiigAooooAKKKKACiiloAKSiigAooooAKKKKACiiloAKKKKAEpaKKACkoooAKKWigBKKKKACiiigAopaSgAooooAKKKKACiiigAooooAKWkpaACkpaKAEopaKAEooooAWkopaAEopaSgAooooAKKKKACiiigAooooAKUUlKKACiiikAlFFFMAooooAKKKKACiiigAoopaACkpaSgAopaSgAooooAWkoooAKWkpaACiiigAooooAKSlpKACiiigAopaKACkpaKAEooooAKWiigBKKWkoAKWiigApKWigBKWiigApKWkoAKKKKACiiigApaKKACiiigAooooAKSlooASilpKAClpKWgApKWigBKKWkNABRRRQAUUUtACUUUUDCiilxQISlpKKACiiigAopaSgAooooAKWkFLQAUUUUAJS0UUAFFFFACUUUUAFFLSUAFFFFABRRRigAooooAKKKKACiiigAooooAKWikNABRRRQAUUUUAFFLRQAUUUUAJRRRQAUUUUAFFFFABRRRQAUtJSigAooopAFJRRTAKKKKACiiigAooooAKKKKACiiigBaSlooASiiigAooooAKKKKACiiigAooooAKKKKACiiigAopaKACiiigBKWiigBKWkpaACiiigAooooAKKKKACiiigAooooASilpKAClpKKAFooooAKKKKACiiigAozRRQAlFLSUAFFLRQAUUUUAFJS0lABRRS0AFFFFABSGlooASlpKWgBKKWigBKKKKACilooASiiloABRSUtABSUUtACUuaSigBc0lFFABS0UlAC0UZooASiiigAzS0lFABRRRQAUUUUAFFFFABRRRQMWkoooEFFFFABRRRQAtFFJQAUUUUAFFFFABRRRQAUUUUAFFFFABS0UUAFFFFIBKKWkpgFFFFABRRRQAYopaSgAopcUlABQKKKAFpKWigBKKKWgBKKKKACiiloASiiigAopaSgAooooAKKKKACiiigBaKKKACiiigApKWigApKWigBKKKKACloooAKKKKAEpaKKACkpaKAEopaKAAUUUUAFFFFABRRRQAUUUUAJRS0UAFFFFABRRRQAUUUUAFFFFABRRSUALSUUUAFFFFAC0lLSUALSUtJQAtFFFACGloooAKSlooASilooASilooASlpKKAFpKKKACiiigAooooAKKKWgBKKKKACiiigAooooAKKKKACiiigAopaKAEooooAKKKKACiiigAooooAKKKKACiiigAooooAWikpaACiiikMSilpKYgooooAKKWigBKKKKACiiloAKSlpKAFopKKAFooooASiiigBaKKKAA0lLSUAFFFFABRRRQAUUUYoAKWiigBKWiigAooooAKKKKACigUUAFJilpKAFooooAKKKKACkpaSgApaSloAKKSigBaKKKACiiigAooooAKKKKAEooooAWkpaKACiiigAooooAKKSloAKSlpKAFpKKKACiiloAKKKKACkpaKACkpaSgBaSiigBaSlpKAFopKKAFoopDQAUUUUAFFFFABRRS0AJRRRQAUUUUALSUtFACUUUUAFFFFABRRRQAUtJRQAtJRRQAUUUUAFFFFABRRRQAtJS0lABRRRQAUUUUAFFFFABS0UUAFFLSUgCkoopgFFFFAC0lLSUAFFFFABRRS0AJRS0lABRRRQAtFJS0AFJS0UAFJS0UAFJS0UAJRS0UAFJS0lAC0UlLQAUUUUAJmlpKKAFooooAKKKKACiiigApKKKAFpKWigBKKWigBKKKWgAooooAQ0UUUALSUUYoAKWiigAooooAKKKQ0AFFFLQAUUUlAC5pKKKACiiigAooooAWiiigApKWkoAKKKWgAooooAKSlooAKSlpKACiiloASloooAKSlooAKKKKAEooooAKKKKACiiigBaSlpKACiiigAooooAKKKKACiiigAooooAKKKKBhRRRQIKKKKACilooASiiigAooooAKKKKACiiigApRSUUALRRRQAUUUUAIaKDRQAUUUUAFAoooAWkpaSgAoFFFAC0UUUAFFJRQAUUUUAFLSUtABRRRQAUlLRQAlLmkooAKKKWgBKWiigAooooASloooAKKKKACiiigAooooAKKKKACiikoAWiiigAooooAKKKKAEopaSgBaKSloAKKKKACiiigAooooAKSlooAKSlooASloooAKKKKAEoFFLQAUUUUAFJQaKAFpKWkoAKWig0AFFJS0AFJQaKACiiigApc0lFAC0UlLQAUUUUAJRS0UAJS0UUAFFFFACUUtJQAtJRRQAUUUUAFFLSUAFFFLQAUUUhoAKKKKACiiigAooooAWikooAKKKKACiiigAooooAKKKKAClpKKAFooooAKKKKQAaSg0UwCiiigYUUUUCCiiloASilooAKKKSgAooooAWkoooAKWkpaAEpaKKACiiigBKKWkoAKWkpaACkNLRQAlFFLQAlFLRQAlFLRQAUUlLQAUUUUAFFFFABRRRQAUUUUAFFFFACUtFFABSUtFACUUuKSgBaKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigApKWigBKKKKACilooAKKKKAEopaKAEpaKKAEopaSgApaSloAKKKKACkpaKACiiigAooooASiiigBaSiloASiiigAooooAKKKKAFooooAKSlooASiiigAopcUUAJRS0lABRRRQAUUtFACUUUUAFFFFABRRRQAUUUUAFLSUtABRRRQAhopaSgAooooAKKWigBKKWkoAKWkooAKKKKACilooASiiigAoopaACiiigAooooAKSlooASiiigAooooAKKKKAFpKKKACijFFABRRRQAtFFFABSUUUALRRSUALRSUtABRRRQAUUUUAFJS0UAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAJS0lLQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFJS0lAC0lFFAC0UUUAFFFFABRRRQAUUUUAFFJS0AJRS0lABRRRQAUUCloASilooASilooASilxRQAUUUUAFJS0UAFFFFABSGlpKACiiigAooooAKKKKAFpKKKACiiloASijFLQAUUUUAFFKaSgApKKKACiiigBaKKKACkpaKAEopaKAEooooAKKKKACiiigApaSloAKKKKACkpaSgApaSloASiiigAopaKAEopaKAEpaSigBaSlooASilooASilooAKMUUUAFJS0UAJRS0YoASlpKKAFooooAKSlooAKKKSgBaSiigApaSloAKSlooAKKKKACiiigApKWigAooooAKKKKACiiigAooooAKKKKADNFJS0AFFFFABRRRQAUmKWigApKWigBKWkooAWkzRRQAtFFFACUUtFACUUtJQAUUUUAFFFFABRRRQAtFJS0AFJRRQAUUUUAFFFFABS0lFABRRRQAUUUtACUUUUAFFFFABRRRQAUUUUALRRRQAUCilFACUUUUgEopaSmAUUUUAFGaKKAClpKWgAooooAKSlooASilooASlxRRQAlLSUtABRRRQAUlLRQAlFLSUAFFLiigBKWiigAoopKACiiigApaSigBaKKKACiiigAooooAKKKKACkpaKACiiigAooooAKSlooAKKKKACiiigBKKWigAooooAKKKKACiiigAooooAKKKKACiiigBKWiigAooooASiiigAooooAWkpaTFABRRRQAUtJS0AFFFFACUUtFABRRRQAUUUUAFJRRQAUtJS0AFFFFACUUUUAFFLSUAFLSUtACUUUtACUUUUAFFFFAwooooEFFFFABRRRQAUUUUAFFKKKAEooooAKKKKAClpKWgAopKUUAFJRRSGFFFFMQUUUUAFFFFAC0lFFABS0lFABRRRQAtFJRQAtFJS0AFFFFABSUtFABSUtJQAUUUUALmkoooAWiikoAKKKKACiiigBaSiigAFLSUtABRRRQAUZopKAFooooAKKKKACiiigAooooAKKKKACiiigAooooAKSlooASloooAKSlpKADNFFFABRRS0AFJS0UAFFFFABRRRQAUUUUABpKKWgBKKWigAooooAKSiigBaSlpDQAUtJS0AFFFJQAtFFFABRRRQAlFFFABRRS0AFJRRQAUUUUAFFFLQAlFLSUAFFFFABRRRQAUUUtACUUtJQAUUUUDCiiigQUUUUALSUtJQAUUUUAFFFFABRRRQAUtFFAAetFFFIBKKKKYBRRRQAUUUUAFFFFABRRRQAUUtFACUUtJQAUUUUAFFFFABRRRQAUUtFACUUUtABRRRQAlFFFABRRRQAUUUUDFpKKKBBS0UUAFFFFABSUtIaAClpKWgAooozQAUUUlAC0UUUAJS0lFAC0lFLQAUUUlAC0UUUAIaWkooAWkpaKAEooooAKWkpaACiiigAoopKAFopKKAFpKWigAFFFFACUUtFABRRSUALRSCloAKKKKACikzS0AFJS0lABRRRQAtFJRQAUUUtABSUtIaACiiigAooooAKKKKACiiigAooooAKKKKAClpKWgBKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigApaKKAA0UlLQAlFFFABRRRQAUUUUAFFFFABRRRQAtJRRQAUUUUAFFFFABRRRQMKKKKBC0UlFABRRRQAUUUUAFFFFABRRRQAtJS0lABRRRQAUtJRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAxaSiigBaQ0UUCCiiigYUtJRQAtFJS0CEooooAXNJRRQMKKKKACg0UUCCiiigYtFFFAhKKKKACiiigYUUUUCCiiigAooooAKKKKACiiigBaSiigApaSigBaSiigAooooAKKKKACiiigAooooAKKKKACiiigApaSigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAClpKKAFopKWkAlFFGKYBRRRigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiijFABRRS0AJRRRQAUUUUAFFFFABRRRQAUUtFACUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUuKAEooooAKKKKACiiigAoopaAEpaKKAEpaKKAEopaKAEopaTFABRS4ooASiiigAooooAM0UUUAFFFFAwooooEFLRRQAlFLRQAlFFFABRRRQAUUUtACUUtJQAUUUUAFLSUtACUUtJQAUUUUAFFFFABRRS0AJRRRQAUUUtACUUUtACUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFLQAlFLRQAlKKSnYpDP/9k="; -export const faviconContentType = "image/jpeg"; + "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"; +export const faviconContentType = "image/png"; diff --git a/scw_js/genimg_x402_token.ts b/scw_js/genimg_x402_token.ts index fcdca287f..d131e94a2 100644 --- a/scw_js/genimg_x402_token.ts +++ b/scw_js/genimg_x402_token.ts @@ -33,8 +33,9 @@ import { createResourceServer, createPaymentRequirements, create402Response, - extractPaymentPayload, createSettlementHeaders, + extractPaymentPayload, + formatParallelPrices, type SdkPaymentPayload, type SdkPaymentRequirements, } from "./x402_server.js"; @@ -88,16 +89,9 @@ const ROOT_PAGE = buildRootPage({ }, ], price: [ - ((): string => { - // Generated from the committed spec, not restated — the page and the 402 share it. - const info = ( - openapiSpec.paths as Record< - string, - Record - > - )["/"].post["x-payment-info"]; - return `POST /imagegen — $${info?.price.amount ?? "?"} per image (USDC, EURC or EURe; each token's price is set in its own units)`; - })(), + // Generated from the committed price list — every accepted token in its own units, + // EURe first as the seller's preference orders it; never a single-token restatement. + `POST /imagegen — ${formatParallelPrices(PRICE_ATOMIC)} per image`, ], payment: "x402 (HTTP 402), exact scheme — one signature moves the money per call: no account, no API key, no approve() needed. An unpaid request answers 402 with the machine-readable Payment-Required challenge — exact terms (network, token, amount) are in the challenge, then retry the same request with the payment attached.", diff --git a/scw_js/test/genimg_x402_token.test.ts b/scw_js/test/genimg_x402_token.test.ts index 0b7905651..b03d98f4f 100644 --- a/scw_js/test/genimg_x402_token.test.ts +++ b/scw_js/test/genimg_x402_token.test.ts @@ -612,14 +612,14 @@ describe("genimg_x402_token.js - x402 v2 Token Payment Tests", () => { const response = await handle(event, {}); expect(response.statusCode).toBe(200); - expect(response.headers["Content-Type"]).toBe("image/jpeg"); + expect(response.headers["Content-Type"]).toBe("image/png"); expect(response.headers["Access-Control-Allow-Origin"]).toBe("*"); expect(response.isBase64Encoded).toBe(true); - // Valid base64 that decodes to a JPEG (SOI marker 0xFFD8). + // Valid base64 that decodes to a PNG (signature 0x8950). const bytes = Buffer.from(response.body, "base64"); expect(bytes.length).toBeGreaterThan(0); - expect(bytes[0]).toBe(0xff); - expect(bytes[1]).toBe(0xd8); + expect(bytes[0]).toBe(0x89); + expect(bytes[1]).toBe(0x50); }); test("should answer a HEAD /favicon.png probe with 200 and an image content-type", async () => { @@ -633,7 +633,7 @@ describe("genimg_x402_token.js - x402 v2 Token Payment Tests", () => { const response = await handle(event, {}); expect(response.statusCode).toBe(200); - expect(response.headers["Content-Type"]).toBe("image/jpeg"); + expect(response.headers["Content-Type"]).toBe("image/png"); expect(response.body).toBe(""); }); @@ -666,7 +666,11 @@ describe("genimg_x402_token.js - x402 v2 Token Payment Tests", () => { expect(response.body).toContain(".well-known/agent-registration.json"); expect(response.body).toContain(".well-known/oasf.json"); // The page price is the spec's x-payment-info value, not a hand-restated one. - expect(response.body).toContain("POST /imagegen — $0.07 per image"); + // Generated per-token prices (EURe first, each token's own units) — nominal EUR + // and USD prices differ here, which is exactly why the page must quote both. + expect(response.body).toContain( + "POST /imagegen — €0.06 in EURe, €0.06 in EURC, $0.07 in USDC per image", + ); expect(response.body).toContain( "curl -X POST 'https://imagegen-agent.fretchen.eu/imagegen'", ); diff --git a/scw_js/x402_server.ts b/scw_js/x402_server.ts index 9155796fa..d8df04d10 100644 --- a/scw_js/x402_server.ts +++ b/scw_js/x402_server.ts @@ -687,6 +687,22 @@ export function formatUsdcAtomicAsDecimalUsd(atomicAmount: string, decimals: big return trimmedFraction ? `${whole}.${trimmedFraction}` : whole.toString(); } +/** + * A PriceList as the root pages display it: every accepted token priced in its own units, in + * the seller's preference order (EURe, EURC, USDC on Base — the same order the 402 lists), + * € for the euro tokens and $ for USDC, decimals read from the token registry so EURe's + * 18-decimal prices format correctly. Pages built with this can never quote fewer tokens + * (or a different order) than the challenge does. (Twin of bunny_js/x402_server.ts.) + */ +export function formatParallelPrices(price: PriceList): string { + return offeredStablecoins("eip155:8453") + .map((coin) => { + const display = formatUsdcAtomicAsDecimalUsd(price[coin.symbol], BigInt(coin.decimals)); + return `${coin.symbol === "USDC" ? "$" : "€"}${display} in ${coin.symbol}`; + }) + .join(", "); +} + export function createSettlementHeaders( settlementResult: Record, ): Record {