// POST /api/send {"tx"}: sends a wallet-signed buy or activation. Only transactions that mint from // one of our machines, co-signed by us, are carried. The app resends the same bytes every few // seconds until the transaction confirms or expires; the network ignores duplicates. import { getSignatureFromTransaction, getTransactionDecoder } from '@solana/kit' import { CardError, MAX_TX_BASE64, relay } from '../../server/card.ts' import { ConfigError, configFailure, contextFor, gate, type CardEnv } from '../../server/env.ts' import { failure, json, readBody } from '../../server/http.ts' // The same bytes sent again within a few seconds are answered from memory, so one client repeating // a transaction can't turn each repeat into a call to our RPC. Short on purpose: the app resends // every ~6 s and each resend must reach the network, and an "expired" stays remembered no longer // either (the relay only says it once the blockhash is really gone, but a wrong one must not stick). const SENT_TTL_MS = 4_000 const RECENT_CAP = 2_000 const recent = new Map() function remember(key: string, entry: { until: number; signature?: string; refusal?: CardError }) { if (recent.size >= RECENT_CAP) for (const k of recent.keys()) if (recent.size >= RECENT_CAP * 0.9) recent.delete(k) recent.set(key, entry) } /** Remembered by its signature, not its text: the same transaction can be written in base64 several ways. */ function keyOf(tx: unknown): string | null { if (typeof tx !== 'string' || tx.length > MAX_TX_BASE64) return null try { return getSignatureFromTransaction(getTransactionDecoder().decode(Uint8Array.from(atob(tx), (c) => c.charCodeAt(0)))) } catch { return null } } export const onRequestPost: PagesFunction = async ({ request, env }) => { const body = await readBody(request, ['tx']) if (!body) return json({ error: 'Expected {"tx"}.', code: 'bad-request' }, 400) // First sends plus a resend every 6 s for whatever is still pending. const refused = await gate(request, env, 'send', 150, 'memory') if (refused) return refused const tx = body.tx const key = keyOf(tx) const hit = key ? recent.get(key) : undefined if (hit && hit.until > Date.now()) return hit.refusal ? failure(hit.refusal, 'send') : json({ signature: hit.signature }) try { const sent = await relay(await contextFor(env), tx) if (key) remember(key, { until: Date.now() + SENT_TTL_MS, signature: sent.signature }) console.log('sent', sent.signature) return json(sent) } catch (err) { if (key && err instanceof CardError && err.code === 'expired') remember(key, { until: Date.now() + SENT_TTL_MS, refusal: err }) return err instanceof ConfigError ? configFailure(err) : failure(err, 'send') } }