Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions apps/pwa/src/moshpit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ export {
parseTldList, tldRejection, normalizeMode, resolutionPreference,
} from "./lib/moshpit-name.mjs";

/**
* The largest number this column will accept.
*
* Not a policy cap. What an ending's names cost is the operator's call -- the
* defaults ($2 a name, $5 an ending) are a starting point, not a ceiling, and
* an ending somebody wants seven figures for is their business. This exists
* only so a fat finger or a hostile client cannot park Infinity, a NaN or 1e300
* in a column that later gets charged.
*
* It replaced a $1,000,000 bound, which was low enough to be a policy decision
* nobody had made.
*/
export const MAX_LISTING_PRICE_USD = 1_000_000_000;

const COLS = `tld, user_id, owner_email, alias_of, price_usd, created_at`;

export async function getTld(tld) {
Expand Down Expand Up @@ -334,12 +348,11 @@ export async function setTldPrice({ tld: tldInput, userId, priceUsd }) {
// NaN/Infinity would be stored verbatim and then charged; a negative or
// zero price would let anyone drain the namespace for free.
if (!Number.isFinite(price) || price <= 0) return { ok: false, error: "price must be a positive number" };
// Not capped at MAX_CHILD_PRICE_USD here on purpose. PRD 0005 R3 caps the
// annual child price at $1.99, but that requirement arrives with terms,
// renewals and the ledger, and today this same column also carries prices
// set before any cap existed. The forms default to the cap and hint at it;
// enforcing it is a migration, not a validation tweak.
if (price > 1_000_000) return { ok: false, error: "price is implausibly large" };
// Not capped at MAX_CHILD_PRICE_USD on purpose, and the forms no longer
// pretend otherwise: $2 is what a new ending defaults to, not the most it
// may charge. PRD 0005 R3's annual cap arrives with terms, renewals and the
// ledger; this column already carries prices set before any cap existed.
if (price > MAX_LISTING_PRICE_USD) return { ok: false, error: "price is implausibly large" };
price = Math.round(price * 100) / 100;
}

Expand Down Expand Up @@ -773,7 +786,7 @@ function* chunksOf(list, size) {
function normalizePrice(value) {
if (value === null || value === undefined || String(value).trim() === "") return null;
const price = Number(value);
if (!Number.isFinite(price) || price <= 0 || price > 1_000_000) return undefined;
if (!Number.isFinite(price) || price <= 0 || price > MAX_LISTING_PRICE_USD) return undefined;
return Math.round(price * 100) / 100;
}

Expand Down
6 changes: 3 additions & 3 deletions apps/pwa/src/routes/moshpit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {
listTldsForUser,
listTldsNotOwnedBy,
MAX_BULK_TLDS,
MAX_CHILD_PRICE_USD,
MAX_LISTING_PRICE_USD,
normalizeLabel,
normalizeMode,
normalizePinKind,
Expand Down Expand Up @@ -632,9 +632,9 @@ moshpitRouter.get("/api/moshpit/resolve", async (req, res) => {
const claimDefaults = (req) => `
<div class="pit-defaults">
<label>Price each
<span class="pit-dot">$</span><input name="price_usd" type="number" min="0.01" step="0.01" max="${MAX_CHILD_PRICE_USD}"
<span class="pit-dot">$</span><input name="price_usd" type="number" min="0.01" step="0.01" max="${MAX_LISTING_PRICE_USD}"
value="${DEFAULT_TLD_PRICE_USD}" placeholder="unlisted" autocomplete="off"
aria-label="price per name, in dollars — clear it to keep them off the market"></label>
aria-label="price per name, in dollars — what you charge is yours to set; clear it to keep them off the market"></label>
<label>Point at
<span class="pit-dot">.</span><input name="alias_of" placeholder="nothing"
autocomplete="off" spellcheck="false" aria-label="an ending you already hold"></label>
Expand Down
19 changes: 18 additions & 1 deletion apps/pwa/test/moshpit-sales.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,31 @@ test("moshpit name sales", { skip: installed ? false : "pwa dependencies not ins
});

await t.test("a price must be a positive, plausible number", async () => {
for (const bad of [0, -5, "abc", Infinity, NaN, 5_000_000]) {
// The ceiling is an overflow guard, not a policy price: what an operator
// charges is their call, so only nonsense is refused.
for (const bad of [0, -5, "abc", Infinity, NaN, 5_000_000_000]) {
const r = await m.setTldPrice({ tld: "whatever", userId: SELLER, priceUsd: bad });
assert.equal(r.ok, false, `${bad} should be refused`);
}
// A zero or negative price would let anyone drain the namespace for free.
assert.equal((await m.getTldWithPrice("whatever")).price_usd, null);
});

await t.test("an operator may ask seven figures for a name", async () => {
// $2 is what a new ending defaults to, not the most it may charge — the
// form used to cap the input at the default and call it a rule.
const r = await m.setTldPrice({ tld: "whatever", userId: SELLER, priceUsd: 1_000_000 });
assert.equal(r.ok, true, r.error);
assert.equal(r.priceUsd, 1_000_000);

const quoted = await m.quoteName({ tld: "whatever", label: "expensive", buyerId: BUYER });
assert.equal(quoted.ok, true, "and a buyer can be quoted it");
assert.equal(quoted.priceUsd, 1_000_000);

await m.setTldPrice({ tld: "whatever", userId: SELLER, priceUsd: null });
assert.equal((await m.getTldWithPrice("whatever")).price_usd, null);
});

await t.test("listing sets a price, rounded to cents", async () => {
const r = await m.setTldPrice({ tld: "whatever", userId: SELLER, priceUsd: "12.345" });
assert.equal(r.ok, true);
Expand Down
Loading