// Server-rendered first paint. A reader without JavaScript, or a tool that judges a // page by its HTML, sees what Duly is, the mainnet evidence and how to try it. When // the app's script runs it keeps the landing as it is and replaces invoice pages with // the interactive version. Everything here is pure, so it is tested without a network. // Every value that is not a literal goes through esc(), including data from the chain. import { MAINNET as E, NEARBLOCKS_TX, NEARBLOCKS_API_TX, BASESCAN_TX, BLOCKSCOUT_TX } from '../../evidence/mainnet.js'; import { formatAmount } from '../../web/lib/money.js'; export const REPO = 'https://code.swiftbotlab.workers.dev/duly'; const ID = /^\d{1,10}-\d{1,10}$/; const HASH = /^[1-9A-HJ-NP-Za-km-z]{43,44}$/; const EVM_HASH = /^0x[0-9a-fA-F]{64}$/; /** The same headers web/_headers gives static files; Worker responses do not get those. */ export const SECURITY_HEADERS = { 'content-security-policy': "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self' https://1click.chaindefuser.com https://free.rpc.fastnear.com https://rpc.mainnet.fastnear.com https://rpc.mainnet.near.org https://test.rpc.fastnear.com https://rpc.testnet.fastnear.com https://rpc.testnet.near.org; base-uri 'none'; form-action 'none'; frame-ancestors 'none'", 'referrer-policy': 'no-referrer', 'x-content-type-options': 'nosniff', 'permissions-policy': 'publickey-credentials-get=(self), publickey-credentials-create=(self)', }; export function esc(v) { return String(v ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]); } const usdc = (u) => { try { return formatAmount(BigInt(u)); } catch { return '?'; } }; const shortHash = (h) => `${h.slice(0, 8)}…${h.slice(-6)}`; const utc = (ms) => { const n = Number(ms); return n ? new Date(n).toISOString().replace('T', ' ').replace(/\.\d+Z$/, ' UTC') : ''; }; export const STATUS_WORDS = { open: 'Not paid yet', overdue: 'Overdue', partial: 'Part paid', paid: 'Paid', partially_refunded: 'Part refunded', refunded: 'Refunded', cancelled: 'Cancelled', }; const STATUS_KIND = { open: 'pending', overdue: 'bad', partial: 'pending', paid: 'good', partially_refunded: 'neutral', refunded: 'neutral', cancelled: 'neutral' }; const nearTx = (key) => E.near.find((t) => t.key === key).hash; const baseTx = (key) => E.base.find((t) => t.key === key).hash; // One icon set: 16 px grid, 1.6 stroke, decorative only. // One icon set: 16 px grid, 1.6 stroke, decorative only. const ICONS = { copy: '', ext: '', check: '', lock: '', term: '', file: '', replay: '', sun: '', moon: '', key: '', link: '', }; const icon = (name) => ``; const copyBtn = (value, what) => ``; /** A transaction as a receipt row: network, short hash linked to the explorer, what it did, copy. */ function txRow(net, hash, what, explorer = 'https://nearblocks.io') { const near = net === 'near'; const url = near ? `${explorer}/txns/${hash}` : BASESCAN_TX(hash); const alt = near ? '' : `Blockscout`; return `
  • ${near ? 'NEAR' : 'Base'}${esc(shortHash(hash))}${what ? `${esc(what)}` : ''}${alt}${copyBtn(hash, 'transaction hash')}${icon('ext')}
  • `; } const i3 = () => E.invoices.find((i) => i.id === '1-3'); const keptOf = (inv) => { try { return usdc(BigInt(inv.paid) - BigInt(inv.setAside)); } catch { return '?'; } }; const sentOf = (key) => { const t = E.base.find((b) => b.key === key); try { return formatAmount(BigInt(t.amount), { min: 6 }); } catch { return '?'; } }; // Chains the pay page offers in the picker, named in type (no permitted logo set carries all of them). const CHAINS = ['Base', 'Arbitrum', 'Optimism', 'Polygon', 'Gnosis', 'NEAR']; /** Hero: the client's pay window for invoice 1-3, exactly as the contract recorded it. Served finished * (Paid). With motion allowed, app.js plays the payment once: Base picked, the route pulses Base -> * NEAR Intents -> dulypay.near while the clock runs to the real 31.8 s, the stamp lands, the packets fill. */ export function heroCardHtml() { const inv = i3(); const secs = E.secondsToClose['1-3']; const settle = nearTx('settle3'); return `

    Invoice 1-3

    Number 0003, NEAR mainnet

    ${esc(STATUS_WORDS[inv.status] || inv.status)}

    ${esc(usdc(inv.amount))}USDC

    Billed to
    ${icon('lock')}Private, encrypted on device
    Merchant account
    ${esc(shortHash(E.merchant))}

    Paid with USDC on

      ${CHAINS.map((c) => `${esc(c)}`).join('')}

    ${esc(sentOf('pay3'))} USDC sent from Base

    1. Base
    2. NEAR Intents
    3. ${esc(E.contract)}

    ${esc(String(secs))} sfrom the Base block to invoice 1-3 closed on NEAR

    Merchant ${esc(keptOf(inv))} USDC
    Tax set-aside ${esc(usdc(inv.setAside))} USDC

    Settled on NEAR by ft_on_transfer. Both shares split in the same receipt, no server in the path.

    ${esc(shortHash(settle))}${copyBtn(settle, 'settlement hash')}
    `; } /** The scroll story: three scenes on one stage. Without JavaScript all three stand in a column. */ const HOW_STAGE = ``; const HOW = `

    One link out, one payment in, books sorted

    Your client never needs an account. You never install a wallet or buy gas.

    1. Send a link

      Sign in with your fingerprint or face, add line items and share the link. Client names and line items are encrypted on your device; only the amount, dates and a fingerprint of the document go on-chain.

    2. Your client pays from any chain

      Any major token on any major chain. NEAR Intents converts it to USDC and delivers it with the invoice number attached, so the payment closes the invoice on-chain.

    3. Shares land in their own balances

      Tax and subcontractor shares move into their own balances in the same step. Export a CSV your accountant can open.

    ${HOW_STAGE}
    `; /** The mainnet evidence: the one event (invoice 1-3) held large, every other movement as a ledger row. */ const MOVES = [ { tag: 'Payout', amt: '0.10 USDC', what: 'Merchant withdraws 0.10 USDC to Base; gas paid by the relayer, payout started by the contract', txs: [['near', 'withdraw', 'withdraw'], ['base', 'payout', 'lands on Base']] }, { tag: 'Refund', amt: '0.05 USDC', what: 'Merchant refunds 0.05 USDC of invoice 1-1 to the address recorded at payment', txs: [['near', 'refund', 'refund'], ['base', 'refundOut', 'back with the payer']] }, { tag: 'Payment', amt: '0.25 USDC', what: 'Invoice 1-1, 0.25 USDC, paid from Base by script; 0.05 set aside', txs: [['near', 'settle1', 'ft_on_transfer'], ['base', 'pay1', 'from Base']] }, { tag: 'Deploy', what: 'Contract deployed and initialised', txs: [['near', 'deploy', 'deploy and init']] }, { tag: 'Setup', what: 'Merchant account created by the relayer. The merchant then registers, sets the 20% tax share and issues invoices 1-1 to 1-4 by signing; the relayer pays every fee. The account holds 1 yoctoNEAR and never bought gas', txs: [['near', 'account', 'account created'], ['near', 'register', 'register'], ['near', 'shares', 'set_shares, 20% tax'], ['near', 'issue1', 'issue invoice 1-1'], ['near', 'issue2', 'issue invoice 1-2'], ['near', 'issue3', 'issue invoice 1-3'], ['near', 'issue4', 'issue invoice 1-4']], }, ]; const txOf = ([net, key, what]) => txRow(net, net === 'near' ? nearTx(key) : baseTx(key), what); export function evidenceHtml(live) { const total = E.near.length + E.base.length; const liveLine = live ? `

    Read from the chain at ${esc(utc(live.at))}: ${esc(live.invoices)} invoices, ${esc(usdc(live.received))} USDC received in total. The contract holds ${esc(usdc(live.held))} USDC and owes ${esc(usdc(live.liabilities))}.

    ` : ''; const moves = MOVES.map( (r) => `
  • ${esc(r.tag)}${r.amt ? `${esc(r.amt)}` : ''}

    ${esc(r.what)}

      ${r.txs.map(txOf).join('')}
  • `, ).join('\n'); const cmd = `git clone ${REPO} && cd duly && node scripts/verify-mainnet.mjs`; return `

    Proof on NEAR mainnet

    On ${esc(E.date)} a client paid 1.00 USDC from Base. The payment itself closed invoice 1-3 on NEAR 31.8 seconds later, with 0.20 USDC already moved to the tax set-aside. No server marked it paid. Amounts were kept tiny and all of the money was ours.

    Contract
    ${esc(E.contract)}${copyBtn(E.contract, 'contract name')}
    Code hash
    ${esc(E.codeHash)}${copyBtn(E.codeHash, 'code hash')}
    Transactions
    ${esc(total)} on NEAR and Base

    Invoice 1-3

    1.00 USDCPaid
    1. PaymentClient pays 1.00 USDC for invoice 1-3 from Base, through the pay page

        ${txOf(['base', 'pay3', 'to a 1Click deposit address'])}
    2. SettlementNEAR Intents delivers 1.00 USDC by ft_transfer_call. ft_on_transfer closes invoice 1-3 and moves 0.20 USDC to the tax set-aside in the same receipt, 31.8 s after the Base block

        ${txOf(['near', 'settle3', 'ft_on_transfer'])}
    ${liveLine}
    ${icon('term')}Check it without trusting this page${copyBtn(cmd, 'command')}
    $ ${esc(cmd).replace(' && ', ' && \\\n  ').replace(' && node', ' && \\\n  node')}

    Re-checks all ${esc(total)} transactions, the code hash, the agent's key and the invoice state against public NEAR and Base RPCs, with no keys. Explorer pages block some automated readers; the same data is in JSON at api.nearblocks.io and on Blockscout.

      ${moves}
    `; } function tryItHtml() { return `

    Four ways to check Duly yourself

    Start with test money in the browser, pay a real invoice for about 10 cents, or re-run everything from a terminal.

    NEAR testnet

    No wallet, no passkey, no money

    1. Open /test/app and press Start.
    2. Press New invoice, fill it in and press Issue invoice and get link.
    3. In Settings keep the Tax 20% row and press Save.
    4. Open the invoice, press Open as client, then Simulate the client paying: you see a PAID stamp and the settlement transaction.
    5. Money shows your balance and the tax jar, split in that transaction. Download CSV gives one row per invoice.

    This is the real contract on testnet with worthless test USDC; only the cross-chain conversion is simulated, because NEAR Intents has no testnet.

    NEAR mainnet, optional

    Real money, about 10 cents

    Invoice 1-4 is open for 0.10 USDC until 2026-11-30. Open it in a browser, pick USDC on Arbitrum, Optimism, Polygon, Gnosis or NEAR (on Base and Ethereum 0.10 is below the route minimum), enter your own address for refunds, press See the price, then Get payment address. The page checks the NEAR Intents quote signature before it shows an address. When the money lands the invoice closes on-chain by itself.

    The first payment closes it; anything paid after that is recorded as extra and stays refundable.

    Terminal

    From a terminal

    node scripts/demo-testnet.mjs runs the whole loop on testnet in about a minute and prints every transaction; node scripts/verify-mainnet.mjs re-checks the mainnet run.

    `; } const REAL = `

    What is real and what is simulated

    Mainnet runs the whole path with real USDC. Test mode runs the same contract on testnet; only the cross-chain leg is simulated.

    PieceLive (mainnet)Test mode (testnet)
    Contract: invoices, set-asides, refundsReal: dulypay.near, unaudited; new invoices 0.50 to 25 USDCReal: dulypay.testnet
    Gas sponsorship (NEP-366 meta transactions)RealReal
    Client payment from another chainReal, through the NEAR Intents 1Click APISimulated: the Simulate button sends test USDC
    Subcontractor share at settlementSame code; the mainnet run used a tax share onlyReal: 20% tax and 10% subcontractor in one transaction
    Collections agentPlain rules on a timer with a function-call key; no language model
    `; // QR code of the test app's address, drawn once with the app's own generator (web/vendor/qrcode.js), // so a judge on a desktop can open the test app on a phone. Regenerate if the host changes. const TEST_APP_URL = 'https://duly.swiftbotlab.workers.dev/test/app'; const TEST_APP_QR = { n: 33, d: 'M0 0h7v1h-7zM8 0h1v1h-1zM10 0h2v1h-2zM13 0h4v1h-4zM18 0h1v1h-1zM22 0h2v1h-2zM26 0h7v1h-7zM0 1h1v1h-1zM6 1h1v1h-1zM9 1h2v1h-2zM12 1h3v1h-3zM16 1h1v1h-1zM18 1h3v1h-3zM24 1h1v1h-1zM26 1h1v1h-1zM32 1h1v1h-1zM0 2h1v1h-1zM2 2h3v1h-3zM6 2h1v1h-1zM9 2h1v1h-1zM12 2h1v1h-1zM14 2h1v1h-1zM16 2h3v1h-3zM20 2h3v1h-3zM24 2h1v1h-1zM26 2h1v1h-1zM28 2h3v1h-3zM32 2h1v1h-1zM0 3h1v1h-1zM2 3h3v1h-3zM6 3h1v1h-1zM8 3h5v1h-5zM16 3h2v1h-2zM21 3h2v1h-2zM24 3h1v1h-1zM26 3h1v1h-1zM28 3h3v1h-3zM32 3h1v1h-1zM0 4h1v1h-1zM2 4h3v1h-3zM6 4h1v1h-1zM8 4h2v1h-2zM11 4h1v1h-1zM13 4h1v1h-1zM16 4h2v1h-2zM19 4h1v1h-1zM23 4h1v1h-1zM26 4h1v1h-1zM28 4h3v1h-3zM32 4h1v1h-1zM0 5h1v1h-1zM6 5h1v1h-1zM8 5h2v1h-2zM13 5h1v1h-1zM15 5h1v1h-1zM20 5h2v1h-2zM24 5h1v1h-1zM26 5h1v1h-1zM32 5h1v1h-1zM0 6h7v1h-7zM8 6h1v1h-1zM10 6h1v1h-1zM12 6h1v1h-1zM14 6h1v1h-1zM16 6h1v1h-1zM18 6h1v1h-1zM20 6h1v1h-1zM22 6h1v1h-1zM24 6h1v1h-1zM26 6h7v1h-7zM8 7h1v1h-1zM10 7h1v1h-1zM12 7h1v1h-1zM14 7h1v1h-1zM17 7h1v1h-1zM19 7h3v1h-3zM23 7h1v1h-1zM0 8h1v1h-1zM4 8h1v1h-1zM6 8h3v1h-3zM10 8h2v1h-2zM15 8h1v1h-1zM17 8h1v1h-1zM21 8h2v1h-2zM24 8h6v1h-6zM32 8h1v1h-1zM1 9h1v1h-1zM3 9h3v1h-3zM8 9h1v1h-1zM10 9h1v1h-1zM12 9h1v1h-1zM15 9h4v1h-4zM21 9h5v1h-5zM29 9h3v1h-3zM2 10h1v1h-1zM6 10h1v1h-1zM9 10h2v1h-2zM12 10h5v1h-5zM18 10h1v1h-1zM20 10h1v1h-1zM23 10h3v1h-3zM29 10h1v1h-1zM31 10h1v1h-1zM1 11h1v1h-1zM3 11h1v1h-1zM5 11h1v1h-1zM8 11h1v1h-1zM10 11h4v1h-4zM16 11h1v1h-1zM19 11h2v1h-2zM22 11h2v1h-2zM25 11h1v1h-1zM32 11h1v1h-1zM1 12h1v1h-1zM3 12h4v1h-4zM10 12h1v1h-1zM12 12h2v1h-2zM16 12h1v1h-1zM22 12h2v1h-2zM26 12h1v1h-1zM28 12h2v1h-2zM1 13h4v1h-4zM7 13h2v1h-2zM14 13h1v1h-1zM18 13h3v1h-3zM23 13h1v1h-1zM27 13h1v1h-1zM29 13h2v1h-2zM0 14h2v1h-2zM5 14h7v1h-7zM13 14h3v1h-3zM17 14h1v1h-1zM20 14h1v1h-1zM23 14h7v1h-7zM31 14h1v1h-1zM1 15h1v1h-1zM3 15h1v1h-1zM9 15h1v1h-1zM12 15h7v1h-7zM21 15h1v1h-1zM23 15h1v1h-1zM25 15h2v1h-2zM28 15h1v1h-1zM1 16h1v1h-1zM3 16h1v1h-1zM5 16h4v1h-4zM12 16h3v1h-3zM16 16h2v1h-2zM19 16h4v1h-4zM26 16h1v1h-1zM28 16h1v1h-1zM31 16h1v1h-1zM0 17h3v1h-3zM7 17h2v1h-2zM13 17h4v1h-4zM18 17h1v1h-1zM23 17h1v1h-1zM25 17h1v1h-1zM29 17h3v1h-3zM1 18h2v1h-2zM5 18h2v1h-2zM11 18h1v1h-1zM13 18h1v1h-1zM15 18h1v1h-1zM17 18h1v1h-1zM21 18h3v1h-3zM27 18h1v1h-1zM29 18h1v1h-1zM0 19h3v1h-3zM4 19h1v1h-1zM7 19h2v1h-2zM11 19h1v1h-1zM13 19h2v1h-2zM17 19h3v1h-3zM21 19h1v1h-1zM23 19h5v1h-5zM31 19h2v1h-2zM0 20h1v1h-1zM3 20h1v1h-1zM6 20h2v1h-2zM10 20h1v1h-1zM14 20h2v1h-2zM18 20h3v1h-3zM23 20h2v1h-2zM26 20h4v1h-4zM31 20h2v1h-2zM0 21h2v1h-2zM4 21h2v1h-2zM7 21h2v1h-2zM11 21h2v1h-2zM16 21h1v1h-1zM19 21h1v1h-1zM21 21h3v1h-3zM27 21h1v1h-1zM29 21h3v1h-3zM2 22h1v1h-1zM5 22h4v1h-4zM11 22h2v1h-2zM14 22h1v1h-1zM16 22h1v1h-1zM19 22h1v1h-1zM21 22h5v1h-5zM31 22h1v1h-1zM2 23h2v1h-2zM5 23h1v1h-1zM7 23h3v1h-3zM13 23h1v1h-1zM17 23h1v1h-1zM19 23h3v1h-3zM24 23h1v1h-1zM31 23h1v1h-1zM0 24h3v1h-3zM4 24h1v1h-1zM6 24h3v1h-3zM10 24h1v1h-1zM12 24h2v1h-2zM15 24h1v1h-1zM17 24h2v1h-2zM20 24h10v1h-10zM8 25h1v1h-1zM10 25h1v1h-1zM14 25h5v1h-5zM21 25h1v1h-1zM24 25h1v1h-1zM28 25h1v1h-1zM30 25h1v1h-1zM0 26h7v1h-7zM8 26h3v1h-3zM12 26h4v1h-4zM18 26h1v1h-1zM24 26h1v1h-1zM26 26h1v1h-1zM28 26h2v1h-2zM31 26h1v1h-1zM0 27h1v1h-1zM6 27h1v1h-1zM10 27h1v1h-1zM13 27h1v1h-1zM20 27h1v1h-1zM22 27h1v1h-1zM24 27h1v1h-1zM28 27h1v1h-1zM32 27h1v1h-1zM0 28h1v1h-1zM2 28h3v1h-3zM6 28h1v1h-1zM8 28h5v1h-5zM16 28h1v1h-1zM19 28h1v1h-1zM22 28h1v1h-1zM24 28h6v1h-6zM31 28h2v1h-2zM0 29h1v1h-1zM2 29h3v1h-3zM6 29h1v1h-1zM9 29h1v1h-1zM17 29h4v1h-4zM23 29h2v1h-2zM26 29h6v1h-6zM0 30h1v1h-1zM2 30h3v1h-3zM6 30h1v1h-1zM10 30h1v1h-1zM12 30h4v1h-4zM18 30h1v1h-1zM20 30h1v1h-1zM25 30h6v1h-6zM0 31h1v1h-1zM6 31h1v1h-1zM10 31h1v1h-1zM15 31h3v1h-3zM21 31h5v1h-5zM29 31h1v1h-1zM0 32h7v1h-7zM8 32h1v1h-1zM10 32h3v1h-3zM16 32h2v1h-2zM19 32h1v1h-1zM21 32h2v1h-2zM24 32h3v1h-3zM32 32h1v1h-1z' }; export function landingHtml({ test = false, live = null } = {}) { const base = test ? '/test' : ''; const total = E.near.length + E.base.length; return `
    ${test ? '

    Test mode. No real money.

    ' : `

    Live on NEAR mainnet, ${esc(total)} transactions you can re-check

    `}

    Invoicing for freelancers, paid from any chain

    Your client pays with any major token on any major chain. You receive USDC on NEAR, and the payment itself closes the invoice on-chain and sets the tax and subcontractor shares aside in the same step.

    No wallet to install, no gas to buy. Shares split on-chain.

    ${heroCardHtml()}
    ${HOW} ${evidenceHtml(live)} ${tryItHtml()} ${REAL}
    `; } /** The chain's view of one invoice. The document key never reaches the server, so names and line items stay out. */ export function invoiceHtml({ id, test = false, inv, payments = [], verified = null }) { const base = test ? '/test' : ''; const explorer = test ? 'https://testnet.nearblocks.io' : 'https://nearblocks.io'; const contract = test ? 'dulypay.testnet' : E.contract; const head = `${test ? '
    Test mode. No real money.
    ' : ''}
    Duly
    `; if (!ID.test(String(id)) || !inv) { return `
    ${head}

    Invoice ${esc(id)}

    ${inv === null ? 'No invoice with this number exists on the contract.' : 'The NEAR chain did not answer just now. Reload this page, or ask the chain directly as shown below.'}

    ${checkHtml(id, contract, explorer, test)}
    `; } const paid = ['paid', 'partially_refunded'].includes(inv.status); const status = esc(STATUS_WORDS[inv.status] || inv.status); const chip = paid ? '
    PAID
    ' : `${status}`; const big = (v) => { try { return BigInt(v) > 0n; } catch { return false; } }; // The watcher's records, plus the run in evidence/mainnet.js for the invoices it covers. const known = !test && E.invoices.find((i) => i.id === id && i.settledBy); const fromEvidence = known ? [{ near_tx: nearTx(known.settledBy), origin_tx: baseTx(known.paidFrom) }] : []; const settle = [...payments, ...fromEvidence] .filter((p) => typeof p.near_tx === 'string' && HASH.test(p.near_tx)) .filter((p, i, all) => all.findIndex((q) => q.near_tx === p.near_tx) === i); const sample = !test && E.invoices.find((i) => i.id === id && i.sample); const open = big(inv.due_now) && !['cancelled'].includes(inv.status); const payList = (inv.payments || []) .map((p) => `

    ${esc(usdc(p.amount))} USDC

    ${esc(utc(p.at_ms))}, NEAR block ${esc(p.block)}

    Refunds go to ${esc(String(p.refund_to || '').split(':').slice(0, 2).join(' ') || 'the payer')}

    `) .join(''); const txList = settle .map((p) => `${typeof p.origin_tx === 'string' && EVM_HASH.test(p.origin_tx) ? txRow('base', p.origin_tx, 'client payment') : ''}${txRow('near', p.near_tx, 'settlement', explorer)}`) .join(''); return `
    ${head}
    ${verified === false ? '' : ''}

    Invoice ${esc(String(inv.number).padStart(4, '0'))}

    Number ${esc(inv.id)}, issued by account ${esc(shortHash(String(inv.merchant)))}

    ${chip}

    Status

    ${status}

    Issued

    ${esc(utc(inv.issued_at_ms))}

    Due

    ${esc(utc(inv.due_at_ms))}

    Total${esc(usdc(inv.amount))} USDC
    Received${esc(usdc(inv.paid))}
    Set aside at settlement (tax, subcontractors)${esc(usdc(inv.set_aside))}
    ${big(inv.refunded) ? `
    Refunded${esc(usdc(inv.refunded))}
    ` : ''} ${big(inv.excess) ? `
    Extra paid by client, refundable${esc(usdc(inv.excess))}
    ` : ''} ${open ? `
    Still due${esc(usdc(inv.due_now))}
    ` : ''}

    Client name and line items are encrypted. They open only in a browser, with the key in the part of the link after #, which never reaches a server. Everything above is read from contract ${esc(contract)} on NEAR${test ? ' testnet' : ''}.

    ${payList || txList ? `

    Payment record

    ${payList ? `
    ${payList}
    ` : ''}${txList ? `
      ${txList}
    ` : ''}
    ` : ''} ${open ? `

    Pay this invoice

    Open this page in a browser with JavaScript: it asks NEAR Intents for a signed quote, checks the signature and the amount, and only then shows a deposit address. ${sample ? `This is the public sample invoice. 1Click quoted 0.10 USDC from ${esc(E.sampleRoutes.join(', '))}; on Base and Ethereum it is below the route minimum. The first payment closes it; later payments are recorded as extra and stay refundable.` : ''}

    ` : ''} ${checkHtml(id, contract, explorer, test)}
    `; } function checkHtml(id, contract, explorer, test) { const args = btoa(JSON.stringify({ id: String(id) })); const rpcUrl = test ? 'https://test.rpc.fastnear.com' : 'https://free.rpc.fastnear.com'; return `
    Check this invoice yourself

    Ask the chain directly, without anything of ours in the path:

    curl -s ${esc(rpcUrl)} -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"query","params":{"request_type":"call_function","finality":"final","account_id":"${esc(contract)}","method_name":"get_invoice","args_base64":"${esc(args)}"}}'

    Open the contract on the NEAR explorer

    `; } /** Puts server-rendered markup into the app root of index.html. */ export function injectApp(indexHtml, inner, { title, description } = {}) { const marker = /
    [\s\S]*?<\/div>\s*
    `
    ${inner}
    \n
    [\s\S]*?<\/noscript>/, ''); if (title) out = out.replace(/[^<]*<\/title>/, () => `<title>${esc(title)}`); if (description) out = out.replace(//, () => ``); return out; } /** Which server-rendered page a path gets, if any. */ export function pageFor(pathname) { const test = pathname === '/test' || pathname.startsWith('/test/'); const path = pathname.replace(/^\/test(?=\/|$)/, '') || '/'; if (path === '/' || path === '/index.html') return { kind: 'landing', test }; const m = path.match(/^\/i\/(\d{1,10}-\d{1,10})$/); if (m) return { kind: 'invoice', test, id: m[1] }; return null; }