diff --git a/package-lock.json b/package-lock.json index 05f1043..738636d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@coinbase/x402": "^2.1.0", "@modelcontextprotocol/server": "^2.0.0", + "@x402/avm": "^2.28.0", "@x402/core": "^2.27.0", "@x402/evm": "^2.27.0", "@x402/express": "^2.27.0", @@ -31,6 +32,108 @@ "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", "license": "MIT" }, + "node_modules/@algorandfoundation/algokit-utils": { + "version": "10.0.0-alpha.46", + "resolved": "https://registry.npmjs.org/@algorandfoundation/algokit-utils/-/algokit-utils-10.0.0-alpha.46.tgz", + "integrity": "sha512-Lx56ET6RmrDCFGwx6qyIr7YRX3Q3MyKgqkNeumLkFHXVkqMnXnK1aLeEbV59EOK8UHQ5UPJofSj1zvRMm9Du7w==", + "license": "MIT", + "dependencies": { + "@algorandfoundation/xhd-wallet-api": "2.0.0-canary.1", + "@noble/curves": "^2.0.1", + "@noble/ed25519": "^3.0.0", + "@noble/hashes": "^2.0.1", + "algorand-msgpack": "^1.1.0", + "buffer": "^6.0.3", + "dotenv": "^16.4.5", + "hi-base32": "^0.5.1", + "json-bigint": "^1.0.0", + "tweetnacl": "^1.0.3", + "vlq": "^2.0.4" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@algorandfoundation/algokit-utils/node_modules/@noble/curves": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.4.0.tgz", + "integrity": "sha512-P4/62zrgfH33CneE3Dn4WhJVA22YUU0eR51wKIan4NVRvwsA0YnPTwWGpNbpuacSujmSFLvyzpyuR30+fbq2Ew==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.4.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@algorandfoundation/algokit-utils/node_modules/@noble/hashes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@algorandfoundation/xhd-wallet-api": { + "version": "2.0.0-canary.1", + "resolved": "https://registry.npmjs.org/@algorandfoundation/xhd-wallet-api/-/xhd-wallet-api-2.0.0-canary.1.tgz", + "integrity": "sha512-U6rSWMO6FKDLvMxWtfKyW5ctqSy2NrONWTU/jOA+BX0SGru0GPXsxwzFdcPa5w+X055V/xVVsr8eA3xA+m/5jA==", + "license": "Apache-2.0", + "dependencies": { + "@noble/ciphers": "^2.0.0", + "@noble/curves": "^2.0.0", + "@noble/hashes": "^2.0.0", + "ajv": "^8.17.1", + "algo-msgpack-with-bigint": "^2.1.1", + "bn.js": "^5.2.2" + } + }, + "node_modules/@algorandfoundation/xhd-wallet-api/node_modules/@noble/ciphers": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.4.0.tgz", + "integrity": "sha512-AnjFn0Jv92laAkvMrghlFZq4qQCIN/4DxFV/eooqtC2YTjB7kBeLMS2T9KJX4Dn+ZVXLOwK0lSgqDtx9gvxtiw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@algorandfoundation/xhd-wallet-api/node_modules/@noble/curves": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.4.0.tgz", + "integrity": "sha512-P4/62zrgfH33CneE3Dn4WhJVA22YUU0eR51wKIan4NVRvwsA0YnPTwWGpNbpuacSujmSFLvyzpyuR30+fbq2Ew==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.4.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@algorandfoundation/xhd-wallet-api/node_modules/@noble/hashes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@coinbase/cdp-sdk": { "version": "1.56.0", "resolved": "https://registry.npmjs.org/@coinbase/cdp-sdk/-/cdp-sdk-1.56.0.tgz", @@ -184,6 +287,15 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@noble/ed25519": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-3.2.0.tgz", + "integrity": "sha512-criDgRlnUA09hchYrTy/JUWPIEap5rZxQe6wDWzRx51oWWpDRcUpuNzlgPxDJaOK6AsW9c0wKcj3rKRv6t+bPQ==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -1221,6 +1333,34 @@ } } }, + "node_modules/@x402/avm": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/@x402/avm/-/avm-2.28.0.tgz", + "integrity": "sha512-ww6EPNQvWKsQdjDFzAA2w15rGDFo+5sRZk9iFerLIMM0URa/A70nPoh4FerH9Bss/lBjUcKG01wYI+i6HBK8/g==", + "license": "Apache-2.0", + "dependencies": { + "@algorandfoundation/algokit-utils": "10.0.0-alpha.46", + "@x402/core": "~2.28.0" + } + }, + "node_modules/@x402/avm/node_modules/@x402/core": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/@x402/core/-/core-2.28.0.tgz", + "integrity": "sha512-Da/7zKxCKqpBoB4Msc9tFgkAv7tJTD8nG+DSSm+QdXAyJS4PQsmgVSKe17rDJTtlu0pwNbLcOje6MyQ6kaDkig==", + "license": "Apache-2.0", + "dependencies": { + "zod": "^3.24.2" + } + }, + "node_modules/@x402/avm/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@x402/core": { "version": "2.27.0", "resolved": "https://registry.npmjs.org/@x402/core/-/core-2.27.0.tgz", @@ -1469,6 +1609,24 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/algo-msgpack-with-bigint": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/algo-msgpack-with-bigint/-/algo-msgpack-with-bigint-2.1.1.tgz", + "integrity": "sha512-F1tGh056XczEaEAqu7s+hlZUDWwOBT70Eq0lfMpBP2YguSQVyxRbprLq5rELXKQOyOaixTWYhMeMQMzP0U5FoQ==", + "license": "ISC", + "engines": { + "node": ">= 10" + } + }, + "node_modules/algorand-msgpack": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/algorand-msgpack/-/algorand-msgpack-1.1.0.tgz", + "integrity": "sha512-08k7pBQnkaUB5p+jL7f1TRaUIlTSDE0cesFu1mD7llLao+1cAhtvvZmGE3OnisTd0xOn118QMw74SRqddqaYvw==", + "license": "ISC", + "engines": { + "node": ">= 14" + } + }, "node_modules/apg-js": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/apg-js/-/apg-js-4.4.0.tgz", @@ -1517,6 +1675,26 @@ "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", @@ -1526,6 +1704,12 @@ "node": "*" } }, + "node_modules/bn.js": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.5.tgz", + "integrity": "sha512-Vq886eXykuP5E6HcKSSStP3bJgrE6In5WKxVUvJ8XGpWWYs2xZHWqUwzCtGgEtBcxyd57KBFDPFoUfNzdaHCNg==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "1.20.8", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.8.tgz", @@ -1559,6 +1743,30 @@ "base-x": "^5.0.0" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -2071,6 +2279,12 @@ "node": ">= 0.4" } }, + "node_modules/hi-base32": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/hi-base32/-/hi-base32-0.5.1.tgz", + "integrity": "sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA==", + "license": "MIT" + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -2139,6 +2353,26 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -2196,6 +2430,15 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -2782,6 +3025,12 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/vlq": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-2.0.4.tgz", + "integrity": "sha512-aodjPa2wPQFkra1G8CzJBTHXhgk3EVSwxSWXNPr1fgdFLUb8kvLV1iEb6rFgasIsjP82HWI6dsb5Io26DDnasA==", + "license": "MIT" + }, "node_modules/ws": { "version": "8.21.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", diff --git a/package.json b/package.json index 67dc53c..ef06245 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "@coinbase/x402": "^2.1.0", "@modelcontextprotocol/server": "^2.0.0", + "@x402/avm": "^2.28.0", "@x402/core": "^2.27.0", "@x402/evm": "^2.27.0", "@x402/express": "^2.27.0", diff --git a/render.yaml b/render.yaml index d3668bd..62c28cc 100644 --- a/render.yaml +++ b/render.yaml @@ -16,6 +16,8 @@ services: sync: false - key: X402_PAY_TO_SOLANA sync: false + - key: ALGORAND_PAY_TO + sync: false - key: X402_NETWORK sync: false - key: X402_CHECK_PRICE diff --git a/server.js b/server.js index ce8eab1..95930f3 100644 --- a/server.js +++ b/server.js @@ -4,6 +4,7 @@ const path = require('path'); const { paymentMiddleware } = require('@x402/express'); const { x402ResourceServer, HTTPFacilitatorClient } = require('@x402/core/server'); const { ExactXrplScheme } = require('@x402/xrpl/exact/server'); +const { ExactAvmScheme } = require('@x402/avm/exact/server'); const { createXrplFacilitator, XRPL, X402_SOURCE_TAG } = require('./xrpl-facilitator'); const { ExactEvmScheme } = require('@x402/evm/exact/server'); const { ExactSvmScheme } = require('@x402/svm/exact/server'); @@ -79,6 +80,7 @@ function mirrorChallengeIntoBody(req, res, next) { next(); } +const ALGORAND = 'algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8='; const x402PayTo = process.env.X402_PAY_TO; if (x402PayTo) { const x402Network = process.env.X402_NETWORK || 'base-sepolia'; @@ -104,6 +106,23 @@ if (x402PayTo) { const usingCdp = Boolean(process.env.CDP_API_KEY_ID && process.env.CDP_API_KEY_SECRET); const facilitators = []; if (usingCdp) facilitators.push(new HTTPFacilitatorClient(createFacilitatorConfig(process.env.CDP_API_KEY_ID, process.env.CDP_API_KEY_SECRET))); + + // USDC on Algorand (ASA 31566704), the same dollar price, settled by GoPlausible's public facilitator (it also pays + // the Algorand fee). Mainnet only, and only with ALGORAND_PAY_TO: an Algorand account with the USDC opt-in; "off" + // turns it off. Put before the URL facilitator, offering only Algorand, so that one never claims Algorand. + const algorandPayTo = x402Caip2Network !== CAIP2_NETWORKS.base || !(process.env.ALGORAND_PAY_TO || '').trim() || process.env.ALGORAND_PAY_TO.trim() === 'off' ? null + : process.env.ALGORAND_PAY_TO.trim(); + if (algorandPayTo) { + const goplausible = new HTTPFacilitatorClient({ url: process.env.ALGORAND_FACILITATOR_URL || 'https://facilitator.goplausible.xyz' }); + facilitators.push({ + verify: (...args) => goplausible.verify(...args), + settle: (...args) => goplausible.settle(...args), + async getSupported() { + const supported = await goplausible.getSupported(); + return { ...supported, kinds: supported.kinds.filter((k) => String(k.network).startsWith('algorand:')) }; + } + }); + } facilitators.push(new HTTPFacilitatorClient({ url: x402FacilitatorUrl })); // RLUSD on the XRP Ledger (mainnet only), the same dollar price: XRPL_PAY_TO, else Frits's account (it has the @@ -116,6 +135,7 @@ if (x402PayTo) { x402Server.register('eip155:*', new ExactEvmScheme()); if (x402PayToSolana) x402Server.register(x402SolanaNetwork, new ExactSvmScheme()); if (xrplPayTo) x402Server.register(XRPL, new ExactXrplScheme()); + if (algorandPayTo) x402Server.register(ALGORAND, new ExactAvmScheme()); // Diagnostic logging only — doesn't change behavior. The 402 response a client // sees on verify/settle failure carries no detail, so log the real reason here. @@ -185,7 +205,8 @@ if (x402PayTo) { const acceptsFor = (price) => [ { scheme: 'exact', price, network: x402Caip2Network, payTo: x402PayTo }, ...(x402PayToSolana ? [{ scheme: 'exact', price, network: x402SolanaNetwork, payTo: x402PayToSolana }] : []), - ...(xrplPayTo ? [{ scheme: 'exact', price, network: XRPL, payTo: xrplPayTo, extra: { invoiceId: 'plaintext.fizzl.eu', sourceTag: X402_SOURCE_TAG } }] : []) + ...(xrplPayTo ? [{ scheme: 'exact', price, network: XRPL, payTo: xrplPayTo, extra: { invoiceId: 'plaintext.fizzl.eu', sourceTag: X402_SOURCE_TAG } }] : []), + ...(algorandPayTo ? [{ scheme: 'exact', price, network: ALGORAND, payTo: algorandPayTo }] : []) ]; // MPP (mpp-pay.js): the same Base USDC payment for agents that speak MPP, through the @@ -231,7 +252,7 @@ if (x402PayTo) { } }, x402Server)))); - const networks = [x402Caip2Network, ...(x402PayToSolana ? [x402SolanaNetwork] : [])].join(' + '); + const networks = [x402Caip2Network, ...(x402PayToSolana ? [x402SolanaNetwork] : []), ...(xrplPayTo ? [XRPL] : []), ...(algorandPayTo ? [ALGORAND] : [])].join(' + '); console.log(`x402 paywall enabled for /api/check-wallet and /api/explain on ${networks} using facilitator ${usingCdp ? `Coinbase CDP (fallback ${x402FacilitatorUrl})` : x402FacilitatorUrl}${mpp ? ', MPP on' : ''}`); } else { console.log('x402 paywall disabled: set X402_PAY_TO in your environment to enable it.'); diff --git a/test/algorand.test.js b/test/algorand.test.js new file mode 100644 index 0000000..d58023f --- /dev/null +++ b/test/algorand.test.js @@ -0,0 +1,69 @@ +// Paying in USDC on Algorand: offered on both paid routes only with ALGORAND_PAY_TO (mainnet), settled by the +// Algorand facilitator (GoPlausible in production, a stand-in here), never by the URL facilitator. +const test = require('node:test'); +const assert = require('node:assert/strict'); +const http = require('node:http'); +const path = require('node:path'); +const { spawn } = require('node:child_process'); +const { usdOf } = require('../usage-log'); + +const ALGORAND = 'algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8='; +const PAY_TO = 'SGLTUPAC7TKGKNNXKNPQ2QZCC7NJSLAKYZ7O7NOGGAPXWBFZTOLTPMSPPI'; +const FEE_PAYER = 'ZMFK2OI7ZBD2U27ISERZC4S6LKM6WMFJPZQ4MYNJDZ2VNBNMBA67RA22AA'; + +// A facilitator that says it supports the given networks. +function standInFacilitator(kinds) { + return new Promise((ok) => { + const server = http.createServer((req, res) => { + res.setHeader('content-type', 'application/json'); + if (req.url.endsWith('/supported')) return res.end(JSON.stringify({ kinds, extensions: [], signers: {} })); + res.end(JSON.stringify({ isValid: false, success: false })); + }).listen(0, () => ok(server)); + }); +} + +// Starts server.js with the env, asks a paid route for its 402 and returns the payment options. +async function acceptsWith(env) { + const payai = await standInFacilitator([{ x402Version: 2, scheme: 'exact', network: 'eip155:8453' }, { x402Version: 2, scheme: 'exact', network: ALGORAND, extra: { feePayer: 'NOT-THIS-ONE' } }]); + const algo = await standInFacilitator([{ x402Version: 2, scheme: 'exact', network: ALGORAND, extra: { feePayer: FEE_PAYER } }, { x402Version: 2, scheme: 'exact', network: 'eip155:8453' }]); + const port = 20000 + Math.floor(Math.random() * 20000); + const child = spawn(process.execPath, ['server.js'], { + cwd: path.join(__dirname, '..'), + env: { PATH: process.env.PATH, PORT: String(port), X402_PAY_TO: '0x6B0F4651eD42893ab58139938175E4a69f175F25', X402_NETWORK: 'base', XRPL_PAY_TO: 'off', X402_FACILITATOR_URL: `http://127.0.0.1:${payai.address().port}`, ALGORAND_FACILITATOR_URL: `http://127.0.0.1:${algo.address().port}`, ...env }, + stdio: 'ignore', + }); + try { + for (let i = 0; i < 50; i++) { + try { + const res = await fetch(`http://127.0.0.1:${port}/api/check-wallet`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' }); + if (res.status === 402) return JSON.parse(Buffer.from(res.headers.get('payment-required'), 'base64').toString()).accepts; + } catch { /* not up yet */ } + await new Promise((r) => setTimeout(r, 200)); + } + throw new Error('no 402'); + } finally { + child.kill(); + payai.close(); + algo.close(); + } +} + +test('with ALGORAND_PAY_TO: $0.04 in USDC (ASA 31566704) on Algorand, with the Algorand facilitator\'s fee payer', async () => { + const accepts = await acceptsWith({ ALGORAND_PAY_TO: PAY_TO }); + const algo = accepts.find((a) => a.network === ALGORAND); + assert.ok(algo, 'Algorand offered'); + assert.equal(algo.payTo, PAY_TO); + assert.equal(algo.asset, '31566704'); + assert.equal(algo.amount, '40000'); + assert.equal(algo.extra.feePayer, FEE_PAYER, 'not the URL facilitator\'s'); + assert.ok(accepts.some((a) => a.network === 'eip155:8453'), 'Base still offered'); +}); + +test('without ALGORAND_PAY_TO, or "off": no Algorand', async () => { + assert.ok(!(await acceptsWith({})).some((a) => a.network === ALGORAND)); + assert.ok(!(await acceptsWith({ ALGORAND_PAY_TO: 'off' })).some((a) => a.network === ALGORAND)); +}); + +test('usage log: Algorand USDC counts in dollars', () => { + assert.equal(usdOf({ network: ALGORAND, asset: '31566704', amount: '40000' }), 0.04); +}); diff --git a/usage-log.js b/usage-log.js index 1813e26..52b9b66 100644 --- a/usage-log.js +++ b/usage-log.js @@ -45,6 +45,7 @@ const NETWORK_NAMES = { 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1': 'solana-devnet', 'eip155:4217': 'Tempo', 'xrpl:0': 'xrpl', + 'algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=': 'algorand', 'xrpl:1': 'xrpl-testnet', }; const RLUSD_HEX = '524C555344000000000000000000000000000000';