`;
}
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 `
duly.swiftbotlab.workers.dev/i/1-3
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
Waiting for the client to pay
Base
NEAR Intents
${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.
`;
}
/** The scroll story: three scenes on one stage. Without JavaScript all three stand in a column. */
const HOW_STAGE = `
New invoiceNumber 0003
${icon('key')}Signed in with a passkeyno wallet, no NEAR
${icon('lock')}Client and line itemsencrypted on this device
Invoice 1-31.00 USDC
${icon('link')}duly.swiftbotlab.workers.dev/i/1-3#…Copy link
You 80%0.80
Tax 20%0.20
Subcontractoroptional
${icon('file')}invoices.csv
Invoice number
Status
Received
Set aside
INV-0003
Paid
1.00
0.20
`;
const HOW = `
One link out, one payment in, books sorted
Your client never needs an account. You never install a wallet or buy gas.
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.
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.
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))}.
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.
PaymentClient pays 1.00 USDC for invoice 1-3 from Base, through the pay page
${txOf(['base', 'pay3', 'to a 1Click deposit address'])}
31.8 s
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'])}
Merchant 0.80Tax 0.20
${liveLine}
${icon('term')}Check it without trusting this page${copyBtn(cmd, 'command')}
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.
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.
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.
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.
`;
}
/** 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.'}
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.` : ''}