Wire discovery and probing for LNURL mints, add rate-limited POST /api/index for user submissions, and optionally announce confirmed state to relays. Co-authored-by: Cursor <cursoragent@cursor.com>
169 lines
5.8 KiB
TypeScript
169 lines
5.8 KiB
TypeScript
/**
|
|
* pnpm seed: ingest the initial mint list, run one probe cycle, run one discovery
|
|
* backfill, print a summary table.
|
|
*
|
|
* The list below is every mint URL carried in a kind 38172 announcement on the old
|
|
* site's relay pool at the time of the rebuild (see NOTES.md: the old project has no
|
|
* hardcoded list of its own, its directory is built live from Nostr). Seeding is only
|
|
* a warm start, discovery finds everything here on its own and keeps finding more.
|
|
*/
|
|
import { closeDb, getDb } from './db.ts';
|
|
import { closePool, runDiscovery } from './discovery.ts';
|
|
import { log } from './log.ts';
|
|
import { insertLnurlIfNew, insertMintIfNew } from './mints.ts';
|
|
import { probeAll } from './probe.ts';
|
|
import { listMints } from './queries.ts';
|
|
|
|
const SEED_MINTS = [
|
|
'https://21mint.me',
|
|
'https://bitcointxoko.com/cashu/api/v1/dMk78c5aR7uhHzcqH3Bwqp',
|
|
'https://cashu.boats',
|
|
'https://cashu.mineracks.com',
|
|
'https://forge.flashapp.me',
|
|
'https://lnb.bolverker.com/cashu/api/v1/GZjPXyQXsgnZEgKZsLRQyy',
|
|
'https://lnwallet.app/cashu/api/v1/9tZLEVyknrtaGxeBjEecc4',
|
|
'https://bb.mountainlake.io/cashu/api/v1/WpczZwvNES7wMa8ForKGmh',
|
|
'https://mint.0xchat.com',
|
|
'https://mint.28waves.com',
|
|
'https://mint.belgianbitcoinembassy.org',
|
|
'https://mint.btcforplebs.com',
|
|
'https://mint.cashu.chat',
|
|
'https://mint.coinos.io',
|
|
'https://mint.endfiat.money',
|
|
'https://mint.fredix.xyz',
|
|
'https://mint.gitvid.net',
|
|
'https://mint.gwoq.com',
|
|
'https://mint.kashir.xyz',
|
|
'https://mint.lnfast.com',
|
|
'https://mint.lnserver.com',
|
|
'https://mint.lnvoltz.com',
|
|
'https://mint.lnw.cash',
|
|
'https://mint.lnwallet.app',
|
|
'https://mint.mikelcalvo.net',
|
|
'https://mint.minibits.cash/Bitcoin',
|
|
'https://mint.mineracks.com',
|
|
'https://mint.mountainlake.io',
|
|
'https://mint.nostrich.cc',
|
|
'https://mint.quixote.cash/cashu/api/v1/TX6nZECe8r6hjDxtdA6N5b',
|
|
'https://mint.sharegap.net',
|
|
'https://mint.westernbtc.com',
|
|
'https://mint2.nutmix.cash',
|
|
'https://obsessedjerky7.lnbits.com/cashu/api/v1/L4bNfiBtTyhEpZWdt6QTb4',
|
|
'https://smilemoji.cash:3338',
|
|
'https://stpaulinternet.net/cashu/api/v1/TTcmiEDSWmkefef3XqERKv',
|
|
'https://kashu.me',
|
|
];
|
|
|
|
/**
|
|
* LNURL mints, seeded by URL alone.
|
|
*
|
|
* Short for the obvious reason: `kind:38174` is a proposed kind (see
|
|
* `docs/KIND-LNURL-MINT.md`) and there are no announcements on the network yet, so
|
|
* unlike Cashu — where this list was read off real 38172 events — there is nothing to
|
|
* read it off. This is the reference instance the ecosystem was built against, and
|
|
* discovery takes over the moment operators start announcing.
|
|
*
|
|
* Seeded with no status: `insertLnurlIfNew` writes `unknown` and the first probe cycle
|
|
* decides, exactly as it does for a Cashu mint. Being on this list is not a claim that
|
|
* a mint is up, and is not an endorsement of it.
|
|
*/
|
|
const SEED_LNURL = [
|
|
'https://lnurl.21mint.me',
|
|
];
|
|
|
|
function pad(s: string, width: number): string {
|
|
return s.length > width ? `${s.slice(0, width - 1)}…` : s.padEnd(width);
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const db = await getDb();
|
|
log.info('seeding', { db: db.label });
|
|
|
|
let added = 0;
|
|
for (const url of SEED_MINTS) if (await insertMintIfNew(url)) added++;
|
|
|
|
let addedLnurl = 0;
|
|
for (const url of SEED_LNURL) if (await insertLnurlIfNew(url)) addedLnurl++;
|
|
|
|
log.info('seed list ingested', {
|
|
listed: SEED_MINTS.length + SEED_LNURL.length,
|
|
added: added + addedLnurl,
|
|
lnurl: addedLnurl,
|
|
});
|
|
|
|
await probeAll();
|
|
const discovery = await runDiscovery(true);
|
|
|
|
// Discovery almost always turns up mints the seed list did not have. Probe those too,
|
|
// otherwise they sit at status 'unknown' with no metadata.
|
|
if (discovery.newMints.length > 0) {
|
|
log.info('probing newly discovered mints', { count: discovery.newMints.length });
|
|
await probeAll();
|
|
}
|
|
|
|
const listed = await listMints();
|
|
const width = { host: 44, type: 9, status: 10, score: 7, reviews: 8, rating: 7 };
|
|
|
|
console.log('');
|
|
console.log(
|
|
[
|
|
pad('MINT', width.host),
|
|
pad('TYPE', width.type),
|
|
pad('STATUS', width.status),
|
|
pad('SCORE', width.score),
|
|
pad('REVIEWS', width.reviews),
|
|
pad('RATING', width.rating),
|
|
'VERSION',
|
|
].join(' '),
|
|
);
|
|
console.log('-'.repeat(120));
|
|
|
|
for (const m of listed) {
|
|
console.log(
|
|
[
|
|
pad(m.host, width.host),
|
|
pad(m.type, width.type),
|
|
pad(m.status, width.status),
|
|
pad(m.score.toFixed(3), width.score),
|
|
pad(String(m.review_count), width.reviews),
|
|
pad(m.rating_avg === null ? '-' : m.rating_avg.toFixed(1), width.rating),
|
|
m.version ?? '-',
|
|
].join(' '),
|
|
);
|
|
}
|
|
|
|
const mints = listed.filter((m) => m.type === 'cashu');
|
|
const federations = listed.filter((m) => m.type === 'fedimint');
|
|
const lnurlMints = listed.filter((m) => m.type === 'lnurl');
|
|
const count = (rows: typeof listed, status: string) =>
|
|
rows.filter((m) => m.status === status).length;
|
|
const reviews = listed.reduce((sum, m) => sum + m.review_count, 0);
|
|
|
|
console.log('-'.repeat(120));
|
|
console.log(
|
|
`${mints.length} mints (${count(mints, 'online')} online, ${count(mints, 'offline')} offline)`,
|
|
);
|
|
/*
|
|
* Federations are counted with `announced` called out rather than folded into the
|
|
* offline total. It is the honest word for "nothing checks this one", and rolling it
|
|
* into either of the other two would be the first place the site started claiming to
|
|
* know something it does not.
|
|
*/
|
|
console.log(
|
|
`${federations.length} federations (${count(federations, 'online')} confirmed up, ` +
|
|
`${count(federations, 'offline')} reported down, ` +
|
|
`${count(federations, 'announced')} announced only)`,
|
|
);
|
|
console.log(
|
|
`${lnurlMints.length} LNURL mints (${count(lnurlMints, 'online')} online, ` +
|
|
`${count(lnurlMints, 'offline')} offline)`,
|
|
);
|
|
console.log(`${reviews} reviews indexed`);
|
|
|
|
closePool();
|
|
await closeDb();
|
|
process.exit(0);
|
|
}
|
|
|
|
void main();
|