mirror of
https://github.com/lux-tel/websites.git
synced 2026-10-30 20:31:36 +00:00
743 lines
21 KiB
TypeScript
743 lines
21 KiB
TypeScript
import path from 'path';
|
|
import { isProduction } from '@/lib/env';
|
|
import type * as BetterSql3 from 'better-sqlite3-multiple-ciphers';
|
|
import Database from 'better-sqlite3-multiple-ciphers';
|
|
import type { Address } from 'viem';
|
|
|
|
export enum TABLE {
|
|
TRANSACTIONS = 'transactions',
|
|
CODE = 'code',
|
|
WALLET = 'wallet',
|
|
OPERATOR = 'operator',
|
|
DISCORD = 'discord',
|
|
TELEGRAM = 'telegram',
|
|
FAUCET = 'faucet',
|
|
}
|
|
|
|
export enum FAUCET_TABLE {
|
|
OPERATOR_LAST_UPDATE = 'operator_last_update',
|
|
}
|
|
|
|
export enum TRANSACTIONS_TABLE {
|
|
HASH = 'hash',
|
|
TARGET = 'target',
|
|
AMOUNT = 'amount',
|
|
TIMESTAMP = 'timestamp',
|
|
DISCORD = 'discord',
|
|
TELEGRAM = 'telegram',
|
|
OPERATOR = 'operator',
|
|
CODE = 'code',
|
|
WALLET = 'wallet',
|
|
ETHHASH = 'ethhash',
|
|
ETHAMOUNT = 'ethamount',
|
|
}
|
|
|
|
export enum OPERATOR_TABLE {
|
|
ID = 'id',
|
|
}
|
|
|
|
export enum DISCORD_TABLE {
|
|
ID = 'id',
|
|
}
|
|
|
|
export enum TELEGRAM_TABLE {
|
|
ID = 'id',
|
|
}
|
|
|
|
export enum WALLET_TABLE {
|
|
ID = 'id',
|
|
NOTE = 'note',
|
|
}
|
|
|
|
export enum CODE_TABLE {
|
|
ID = 'id',
|
|
WALLET = 'wallet',
|
|
DRIP = 'drip',
|
|
MAXUSES = 'maxuses',
|
|
}
|
|
|
|
export interface TransactionsRow {
|
|
hash: string;
|
|
to: string;
|
|
amount: number;
|
|
timestamp: number;
|
|
discord?: string;
|
|
telegram?: string;
|
|
operator?: string;
|
|
wallet?: string;
|
|
code?: string;
|
|
ethhash?: string;
|
|
ethamount?: string;
|
|
}
|
|
|
|
export const openDatabase = (fileMustExist = true): BetterSql3.Database => {
|
|
const dbSecretKey = process.env.FAUCET_DB_SECRET_KEY;
|
|
if (!dbSecretKey) {
|
|
throw new Error('Faucet database secret key is required');
|
|
}
|
|
|
|
const dbOptions: BetterSql3.Options = {
|
|
nativeBinding: path.join(
|
|
'./',
|
|
'node_modules',
|
|
'better-sqlite3-multiple-ciphers',
|
|
'build',
|
|
'Release',
|
|
'better_sqlite3.node'
|
|
),
|
|
fileMustExist,
|
|
};
|
|
|
|
if (!isProduction) {
|
|
dbOptions.verbose = console.log;
|
|
}
|
|
|
|
return new Database('db/faucet.sqlite', dbOptions);
|
|
};
|
|
|
|
export const setupDatababse = () => {
|
|
const db = openDatabase(false);
|
|
|
|
db.prepare(
|
|
`CREATE TABLE IF NOT EXISTS ${TABLE.FAUCET} (
|
|
${FAUCET_TABLE.OPERATOR_LAST_UPDATE} INTEGER
|
|
)`
|
|
).run();
|
|
|
|
db.prepare(
|
|
`CREATE TABLE IF NOT EXISTS ${TABLE.OPERATOR} (
|
|
${OPERATOR_TABLE.ID} TEXT PRIMARY KEY
|
|
)`
|
|
).run();
|
|
|
|
db.prepare(
|
|
`CREATE TABLE IF NOT EXISTS ${TABLE.DISCORD} (
|
|
${DISCORD_TABLE.ID} TEXT PRIMARY KEY
|
|
)`
|
|
).run();
|
|
|
|
db.prepare(
|
|
`CREATE TABLE IF NOT EXISTS ${TABLE.TELEGRAM} (
|
|
${TELEGRAM_TABLE.ID} TEXT PRIMARY KEY
|
|
)`
|
|
).run();
|
|
|
|
db.prepare(
|
|
`CREATE TABLE IF NOT EXISTS ${TABLE.WALLET} (
|
|
${WALLET_TABLE.ID} TEXT PRIMARY KEY,
|
|
${WALLET_TABLE.NOTE} TEXT
|
|
)`
|
|
).run();
|
|
|
|
db.prepare(
|
|
`CREATE TABLE IF NOT EXISTS ${TABLE.CODE} (
|
|
${CODE_TABLE.ID} TEXT PRIMARY KEY,
|
|
${CODE_TABLE.WALLET} TEXT,
|
|
${CODE_TABLE.DRIP} TEXT,
|
|
${CODE_TABLE.MAXUSES} INTEGER
|
|
)`
|
|
).run();
|
|
|
|
db.prepare(
|
|
`CREATE TABLE IF NOT EXISTS ${TABLE.TRANSACTIONS} (
|
|
${TRANSACTIONS_TABLE.HASH} TEXT NOT NULL PRIMARY KEY,
|
|
${TRANSACTIONS_TABLE.TARGET} TEXT NOT NULL,
|
|
${TRANSACTIONS_TABLE.AMOUNT} TEXT NOT NULL,
|
|
${TRANSACTIONS_TABLE.TIMESTAMP} INTEGER NOT NULL,
|
|
${TRANSACTIONS_TABLE.DISCORD} TEXT,
|
|
${TRANSACTIONS_TABLE.TELEGRAM} TEXT,
|
|
${TRANSACTIONS_TABLE.OPERATOR} TEXT,
|
|
${TRANSACTIONS_TABLE.WALLET} TEXT,
|
|
${TRANSACTIONS_TABLE.CODE} TEXT,
|
|
${TRANSACTIONS_TABLE.ETHHASH} TEXT,
|
|
${TRANSACTIONS_TABLE.ETHAMOUNT} TEXT
|
|
);`
|
|
).run();
|
|
|
|
/* db.prepare(
|
|
`ALTER TABLE ${TABLE.TRANSACTIONS} ADD COLUMN ${TRANSACTIONS_TABLE.ETHHASH} TEXT;`
|
|
).run();
|
|
db.prepare(
|
|
`ALTER TABLE ${TABLE.TRANSACTIONS} ADD COLUMN ${TRANSACTIONS_TABLE.ETHAMOUNT} TEXT;`
|
|
).run(); */
|
|
// db.prepare(
|
|
// `ALTER TABLE ${TABLE.TRANSACTIONS} ADD COLUMN ${TRANSACTIONS_TABLE.WALLET} TEXT;`
|
|
// ).run();
|
|
// db.prepare(`ALTER TABLE ${TABLE.TRANSACTIONS} ADD COLUMN ${TRANSACTIONS_TABLE.CODE} TEXT;`).run();
|
|
|
|
db.prepare(
|
|
`CREATE INDEX IF NOT EXISTS discord_index ON ${TABLE.TRANSACTIONS} (${TRANSACTIONS_TABLE.DISCORD}, ${TRANSACTIONS_TABLE.TIMESTAMP}) WHERE ${TRANSACTIONS_TABLE.DISCORD} IS NOT NULL;`
|
|
).run();
|
|
|
|
db.prepare(
|
|
`CREATE INDEX IF NOT EXISTS telegram_index ON ${TABLE.TRANSACTIONS} (${TRANSACTIONS_TABLE.TELEGRAM}, ${TRANSACTIONS_TABLE.TIMESTAMP}) WHERE ${TRANSACTIONS_TABLE.TELEGRAM} IS NOT NULL;`
|
|
).run();
|
|
|
|
db.prepare(
|
|
`CREATE INDEX IF NOT EXISTS operator_index ON ${TABLE.TRANSACTIONS} (${TRANSACTIONS_TABLE.OPERATOR}, ${TRANSACTIONS_TABLE.TIMESTAMP}) WHERE ${TRANSACTIONS_TABLE.OPERATOR} IS NOT NULL;`
|
|
).run();
|
|
|
|
db.prepare(
|
|
`CREATE INDEX IF NOT EXISTS operator_index_wallet ON ${TABLE.TRANSACTIONS} (${TRANSACTIONS_TABLE.WALLET}, ${TRANSACTIONS_TABLE.TIMESTAMP}) WHERE ${TRANSACTIONS_TABLE.WALLET} IS NOT NULL;`
|
|
).run();
|
|
|
|
db.prepare(
|
|
`CREATE INDEX IF NOT EXISTS operator_index_code ON ${TABLE.TRANSACTIONS} (${TRANSACTIONS_TABLE.TARGET}, ${TRANSACTIONS_TABLE.TIMESTAMP}, ${TRANSACTIONS_TABLE.CODE}) WHERE ${TRANSACTIONS_TABLE.CODE} IS NOT NULL;`
|
|
).run();
|
|
|
|
db.close();
|
|
};
|
|
|
|
interface DiscordIdExport {
|
|
id: string;
|
|
name: string;
|
|
roles: Array<string>;
|
|
joined_at: string;
|
|
}
|
|
|
|
interface TelegramIdExport {
|
|
id: number;
|
|
username: string | null;
|
|
status: string;
|
|
}
|
|
|
|
interface OperatorAddressExport {
|
|
address: string;
|
|
destination: string;
|
|
share: string;
|
|
}
|
|
|
|
export const importDiscordIds = (discordExport: Array<DiscordIdExport>) => {
|
|
let db: BetterSql3.Database | undefined;
|
|
|
|
try {
|
|
const db = openDatabase();
|
|
|
|
const insert = db.prepare(`INSERT INTO ${TABLE.DISCORD} (${DISCORD_TABLE.ID}) VALUES (?)`);
|
|
|
|
const transaction = db.transaction((discordExport: Array<DiscordIdExport>) => {
|
|
for (const { id } of discordExport) {
|
|
insert.run(id);
|
|
}
|
|
});
|
|
|
|
transaction(discordExport);
|
|
} catch (error) {
|
|
console.error('Failed to import Discord IDs', error);
|
|
} finally {
|
|
if (db) {
|
|
db.close();
|
|
}
|
|
}
|
|
};
|
|
|
|
export const importTelegramIds = (telegramExport: Array<TelegramIdExport>) => {
|
|
let db: BetterSql3.Database | undefined;
|
|
|
|
try {
|
|
const db = openDatabase();
|
|
|
|
const insert = db.prepare(`INSERT INTO ${TABLE.TELEGRAM} (${TELEGRAM_TABLE.ID}) VALUES (?)`);
|
|
|
|
const transaction = db.transaction((telegramExport: Array<TelegramIdExport>) => {
|
|
for (const { id } of telegramExport) {
|
|
insert.run(BigInt(id).toString());
|
|
}
|
|
});
|
|
|
|
transaction(telegramExport);
|
|
} catch (error) {
|
|
console.error('Failed to import Telegram IDs', error);
|
|
} finally {
|
|
if (db) {
|
|
db.close();
|
|
}
|
|
}
|
|
};
|
|
|
|
export const parseCSV = (csv: string) => {
|
|
try {
|
|
const lines = csv.split('\n');
|
|
|
|
if (!lines[0]) {
|
|
throw new Error('CSV is empty');
|
|
}
|
|
|
|
const headers = lines[0].split(',');
|
|
|
|
return lines.slice(1).map((line) => {
|
|
const values = line.split(',');
|
|
if (values.length !== headers.length) {
|
|
throw new Error('CSV has inconsistent columns');
|
|
}
|
|
return headers.reduce((acc: Record<string, string>, header, index) => {
|
|
acc[header] = values[index]!;
|
|
return acc;
|
|
}, {});
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to parse CSV', error);
|
|
return [];
|
|
}
|
|
};
|
|
|
|
export const importOperatorIds = (operatorExport: Array<string>) => {
|
|
const filtered = Array.from(new Set(operatorExport));
|
|
|
|
let db: BetterSql3.Database | undefined;
|
|
|
|
try {
|
|
const db = openDatabase();
|
|
|
|
const insert = db.prepare(`INSERT INTO ${TABLE.OPERATOR} (${OPERATOR_TABLE.ID}) VALUES (?)`);
|
|
|
|
const transaction = db.transaction((operatorExport: Array<string>) => {
|
|
for (const destination of operatorExport) {
|
|
insert.run(`0X${destination}`);
|
|
}
|
|
});
|
|
|
|
transaction(filtered);
|
|
} catch (error) {
|
|
console.error('Failed to import Operator IDs', error);
|
|
} finally {
|
|
if (db) {
|
|
db.close();
|
|
}
|
|
}
|
|
};
|
|
|
|
type CountType<F extends string> = Record<`count(${F})`, number>;
|
|
|
|
/**
|
|
* Checks if the given row has a count value greater than zero for the specified countField.
|
|
* @param row The row object containing the count value.
|
|
* @param countField The field name for the count value.
|
|
*
|
|
* @returns A boolean indicating whether the count value exists and is greater than zero.
|
|
*/
|
|
export const hasCount = <F extends string>(row: CountType<F>, countField: F) => {
|
|
const count = row[`count(${countField})`];
|
|
return count && count > 0;
|
|
};
|
|
|
|
type IdTableParams = {
|
|
db: BetterSql3.Database;
|
|
source: TABLE.DISCORD | TABLE.TELEGRAM | TABLE.OPERATOR | TABLE.WALLET | TABLE.CODE;
|
|
id: string;
|
|
};
|
|
|
|
/**
|
|
* Checks if the given ID exists in the specified table of the database.
|
|
*
|
|
* @param db The database instance.
|
|
* @param source The name of the table to search in.
|
|
* @param id The ID to check for existence.
|
|
* @returns A boolean indicating whether the ID exists in the table.
|
|
*/
|
|
export function idIsInTable({ db, source, id }: IdTableParams) {
|
|
const field = 'id';
|
|
const row = db
|
|
.prepare<string>(`SELECT count(${field}) FROM ${source} WHERE ${field} = ?`)
|
|
.get(id) as CountType<typeof field>;
|
|
|
|
return hasCount(row, field);
|
|
}
|
|
|
|
/**
|
|
* Checks if a recent transaction exists for the given id in the specified source.
|
|
*
|
|
* @param db The database connection.
|
|
* @param source The name of the source.
|
|
* @param id The id to check for recent transactions.
|
|
* @returns A boolean indicating whether a recent transaction exists.
|
|
*/
|
|
export function hasRecentTransaction({
|
|
db,
|
|
source,
|
|
id,
|
|
hoursBetweenTransactions,
|
|
}: IdTableParams & { hoursBetweenTransactions?: number }) {
|
|
const lastTransactionCutoff = hoursBetweenTransactions
|
|
? Date.now() - hoursBetweenTransactions * 60 * 60 * 1000
|
|
: 0;
|
|
|
|
const row = db
|
|
.prepare<[IdTableParams['id'], TransactionsRow['timestamp']]>(
|
|
`SELECT count(${source}) FROM ${TABLE.TRANSACTIONS} WHERE ${source} = ? AND timestamp > ?`
|
|
)
|
|
.get(id, lastTransactionCutoff) as CountType<typeof source>;
|
|
|
|
return hasCount(row, source);
|
|
}
|
|
|
|
export type TransactionHistory = {
|
|
[TRANSACTIONS_TABLE.TIMESTAMP]: number;
|
|
[TRANSACTIONS_TABLE.HASH]: string;
|
|
[TRANSACTIONS_TABLE.AMOUNT]: string;
|
|
[TRANSACTIONS_TABLE.ETHHASH]?: string;
|
|
[TRANSACTIONS_TABLE.ETHAMOUNT]?: string;
|
|
};
|
|
|
|
export function getTransactionHistory({
|
|
db,
|
|
address,
|
|
}: {
|
|
db: BetterSql3.Database;
|
|
address: Address;
|
|
}): Array<TransactionHistory> {
|
|
return db
|
|
.prepare<string>(
|
|
`SELECT ${TRANSACTIONS_TABLE.TIMESTAMP}, ${TRANSACTIONS_TABLE.HASH}, ${TRANSACTIONS_TABLE.AMOUNT}, ${TRANSACTIONS_TABLE.ETHHASH}, ${TRANSACTIONS_TABLE.ETHAMOUNT} FROM ${TABLE.TRANSACTIONS} WHERE ${TRANSACTIONS_TABLE.TARGET} = ? ORDER BY ${TRANSACTIONS_TABLE.TIMESTAMP} DESC`
|
|
)
|
|
.all(address) as Array<TransactionHistory>;
|
|
}
|
|
|
|
type CodeExistsParams = {
|
|
db: BetterSql3.Database;
|
|
code: string;
|
|
};
|
|
|
|
/**
|
|
* Checks if the given referral code exists.
|
|
* @param props Code exists props.
|
|
* @param props.db The database instance.
|
|
* @param props.code The code to check for existence.
|
|
* @returns A boolean indicating whether the code exists in the table.
|
|
*/
|
|
export function codeExists({ db, code }: CodeExistsParams) {
|
|
return idIsInTable({ db, source: TABLE.CODE, id: code });
|
|
}
|
|
|
|
type CodeDetailParams = CodeExistsParams;
|
|
|
|
type CodeDetails = {
|
|
id: string;
|
|
wallet?: string;
|
|
drip?: string;
|
|
maxuses?: number;
|
|
};
|
|
|
|
/**
|
|
* Get a referral codes details.
|
|
* @param props Get referral code details props.
|
|
* @param props.db The database instance.
|
|
* @param props.code The code to get details for.
|
|
* @returns The referral code details.
|
|
*/
|
|
export function getReferralCodeDetails({ db, code }: CodeDetailParams): CodeDetails | undefined {
|
|
return db
|
|
.prepare<string>(
|
|
`SELECT ${CODE_TABLE.ID}, ${CODE_TABLE.WALLET}, ${CODE_TABLE.DRIP}, ${CODE_TABLE.MAXUSES} FROM ${TABLE.CODE} WHERE ${CODE_TABLE.ID} = ?`
|
|
)
|
|
.all(code)[0] as CodeDetails;
|
|
}
|
|
|
|
type CodeUseTransactionHistory = TransactionHistory & { target: Address };
|
|
|
|
/**
|
|
* Get a referral code's transaction history. This is all the times a code was used.
|
|
* @param props Get code transaction history props.
|
|
* @param props.db The database instance.
|
|
* @param props.code The code to get history for.
|
|
* @returns A list of transactions for a referral code.
|
|
*/
|
|
export function getCodeUseTransactionHistory({
|
|
db,
|
|
code,
|
|
}: {
|
|
db: BetterSql3.Database;
|
|
code: string;
|
|
}): Array<CodeUseTransactionHistory> {
|
|
return db
|
|
.prepare<string>(
|
|
`SELECT ${TRANSACTIONS_TABLE.TIMESTAMP}, ${TRANSACTIONS_TABLE.HASH}, ${TRANSACTIONS_TABLE.AMOUNT}, ${TRANSACTIONS_TABLE.ETHHASH}, ${TRANSACTIONS_TABLE.ETHAMOUNT}, ${TRANSACTIONS_TABLE.TARGET} FROM ${TABLE.TRANSACTIONS} WHERE ${TRANSACTIONS_TABLE.CODE} = ? ORDER BY ${TRANSACTIONS_TABLE.TIMESTAMP} DESC`
|
|
)
|
|
.all(code) as Array<CodeUseTransactionHistory>;
|
|
}
|
|
|
|
/**
|
|
* Determines if the parameters for generating a number of codes are sufficient for generating
|
|
* the requested number of codes given the available character set and code length.
|
|
* @param numCodes The number of codes to generate.
|
|
* @param codeLength The length of the codes.
|
|
* @param characters The character set to use to create the codes.
|
|
* @returns If the parameters for generating a number of codes is sufficient
|
|
*/
|
|
function canCreateRequestedCodes(numCodes: number, codeLength: number, characters: string) {
|
|
const numUniqueCombinations = Math.pow(characters.length, codeLength);
|
|
return numUniqueCombinations >= numCodes;
|
|
}
|
|
|
|
const charsets = {
|
|
alphanumeric: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
|
|
safeAlphanumeric: 'ABCDEFGHJKLMNPRSTUVXYZ0123456789',
|
|
custom: '',
|
|
} as const;
|
|
|
|
type GenerateReferralCodesParams = {
|
|
codeLength: number;
|
|
charset: keyof typeof charsets;
|
|
numberOfCodes: number;
|
|
prefix?: string;
|
|
suffix?: string;
|
|
customCharset?: string;
|
|
existingCodes: Array<string>;
|
|
};
|
|
|
|
/**
|
|
* Generate random unique referral codes.
|
|
* @param props Referral code generator props
|
|
* @param props.codeLength The length of the codes.
|
|
* @param props.charset The characters to use in creating the codes.
|
|
* @param props.numberOfCodes The number of codes to create.
|
|
* @param props.prefix The prefix to use at the start of the code. NOTE: this reduces the number of random characters in the code.
|
|
* @param props.suffix The suffix to use at the end of the code. NOTE: this reduces the number of random characters in the code.
|
|
* @param props.customCharset A custom charset to use if {@link charset} is set to {@link charsets.custom}.
|
|
* @param props.existingCodes A list of existing referral codes to prevent duplication.
|
|
* @returns The generated referral codes.
|
|
*/
|
|
function generateRandomReferralCodes({
|
|
codeLength,
|
|
charset,
|
|
numberOfCodes,
|
|
prefix,
|
|
suffix,
|
|
customCharset,
|
|
existingCodes,
|
|
}: GenerateReferralCodesParams) {
|
|
const codes = new Set<string>();
|
|
const codeRandomLength = codeLength - (prefix?.length ?? 0) - (suffix?.length ?? 0);
|
|
|
|
const validChars = charset === 'custom' && customCharset ? customCharset : charsets[charset];
|
|
|
|
if (!canCreateRequestedCodes(numberOfCodes, codeRandomLength, validChars)) {
|
|
throw new Error(
|
|
'Unable to generate the requested codes. Too many number of codes, too short length, or too short charset.'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 1. Generate random codes until to the target is reached (internal do-while loop).
|
|
* 2. Remove non-unique codes.
|
|
* 3. If more codes are needed to reach the target, repeat.
|
|
*/
|
|
do {
|
|
/**
|
|
* Keeps generating random codes until the number of codes in the set (unique) is equal to the
|
|
* goal numberOfCodes.
|
|
*/
|
|
do {
|
|
const code = `${prefix ?? ''}${generateRandomReferralCode(validChars, codeRandomLength)}${suffix ?? ''}`;
|
|
codes.add(code);
|
|
} while (codes.size < numberOfCodes);
|
|
|
|
/**
|
|
* Remove any codes from the set that already exist
|
|
*/
|
|
existingCodes.forEach((str) => {
|
|
if (codes.has(str)) {
|
|
codes.delete(str);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* If any codes have been removed due to a uniqueness collision, keep generating
|
|
* the codes up to the number of codes again and repeat until all the codes are generated and
|
|
* unique
|
|
*/
|
|
} while (codes.size < numberOfCodes);
|
|
|
|
return codes;
|
|
}
|
|
|
|
/**
|
|
* Generate a random referral code.
|
|
* @param charset The character set to generate the code from.
|
|
* @param codeLength The length of the coed to generate.
|
|
* @returns A randomly generated code.
|
|
*/
|
|
function generateRandomReferralCode(charset: string, codeLength: number) {
|
|
let code = '';
|
|
for (let i = 0; i < codeLength; i++) {
|
|
const randomIndex = Math.floor(Math.random() * charset.length);
|
|
code += charset[randomIndex];
|
|
}
|
|
return code;
|
|
}
|
|
|
|
type AddReferralCodesParams = {
|
|
codes: Set<string>;
|
|
creatorWallet?: string;
|
|
maxUses?: number;
|
|
drip?: bigint;
|
|
};
|
|
|
|
/**
|
|
* Get existing referral codes from the database
|
|
* @param db The database instance
|
|
*/
|
|
function getExistingReferralCodes(db: BetterSql3.Database) {
|
|
const rows = db.prepare(`SELECT ${CODE_TABLE.ID} FROM ${TABLE.CODE}`).all() as Array<{
|
|
id: string;
|
|
}>;
|
|
return rows.flatMap((row) => row.id);
|
|
}
|
|
|
|
/**
|
|
* Add referral codes to the database
|
|
* @param props Referral code props
|
|
* @param props.codes The codes to add
|
|
* @param props.maxUses The maximum number of uses the codes can have
|
|
* @param props.drip The amount of tokens the code will set to be released
|
|
* @param props.creatorWallet The wallet the code was created with (if it was created for a wallet)
|
|
*/
|
|
function addReferralCodes({ codes, maxUses, drip, creatorWallet }: AddReferralCodesParams) {
|
|
let db: BetterSql3.Database | undefined;
|
|
|
|
try {
|
|
const db = openDatabase();
|
|
|
|
const insert = db.prepare(
|
|
`INSERT INTO ${TABLE.CODE} (${CODE_TABLE.ID}, ${CODE_TABLE.WALLET}, ${CODE_TABLE.DRIP}, ${CODE_TABLE.MAXUSES}) VALUES (?, ?, ?, ?)`
|
|
);
|
|
|
|
const transaction = db.transaction((newCodes: Set<string>) => {
|
|
for (const code of newCodes) {
|
|
insert.run(code, creatorWallet, drip?.toString(), maxUses);
|
|
}
|
|
});
|
|
|
|
transaction(codes);
|
|
} catch (error) {
|
|
console.error('Failed to add referral codes', error);
|
|
} finally {
|
|
if (db) {
|
|
db.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
type CreateReferralCodesParams = {
|
|
db: BetterSql3.Database;
|
|
generateParams: Omit<GenerateReferralCodesParams, 'existingCodes'>;
|
|
addParams: Omit<AddReferralCodesParams, 'codes'>;
|
|
};
|
|
|
|
/**
|
|
* Create referral codes
|
|
* @param props Create referral code props
|
|
* @param props.db The database instance
|
|
* @param props.generateParams params for {@link generateRandomReferralCodes}
|
|
* @param props.addParams params for {@link getExistingReferralCodes}
|
|
*/
|
|
export function createReferralCodes({
|
|
db,
|
|
generateParams,
|
|
addParams,
|
|
}: CreateReferralCodesParams): Set<string> {
|
|
try {
|
|
const existingCodes = getExistingReferralCodes(db);
|
|
const codes = generateRandomReferralCodes({ ...generateParams, existingCodes });
|
|
addReferralCodes({ ...addParams, codes });
|
|
return codes;
|
|
} catch (error) {
|
|
console.error('Failed to creat referral codes', error);
|
|
return new Set<string>();
|
|
}
|
|
}
|
|
|
|
type CreateWalletReferralCodesParams = {
|
|
params: Array<AddReferralCodesParams>;
|
|
};
|
|
|
|
export function createWalletReferralCodes({ params }: CreateWalletReferralCodesParams) {
|
|
try {
|
|
for (const { codes, maxUses, drip, creatorWallet } of params) {
|
|
addReferralCodes({ codes, maxUses, drip, creatorWallet });
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to creat referral codes', error);
|
|
}
|
|
}
|
|
|
|
// const test: Array<{ address: Address; uses: number; creatorWallet: Address }> = [];
|
|
//
|
|
// export function createWalletCodes() {
|
|
// const params: Array<AddReferralCodesParams> = test.map(({ address, uses, creatorWallet }) => ({
|
|
// codes: new Set([
|
|
// encodeAddressToHashId(
|
|
// address,
|
|
// process.env.NEXT_PUBLIC_SALT && !Number.isInteger(process.env.NEXT_PUBLIC_SALT)
|
|
// ? Number.parseInt(process.env.NEXT_PUBLIC_SALT)
|
|
// : undefined,
|
|
// process.env.NEXT_PUBLIC_PEPPER
|
|
// ),
|
|
// ]),
|
|
// creatorWallet: creatorWallet ?? address,
|
|
// maxUses: uses,
|
|
// drip: 2000_000000000n,
|
|
// }));
|
|
// createWalletReferralCodes({ params });
|
|
// }
|
|
|
|
/*
|
|
interface NodeOperatorScoreResponse {
|
|
global_score: number;
|
|
total_snapshots: number;
|
|
wallets: {
|
|
address: {
|
|
percent: number;
|
|
score: number;
|
|
snapshots: number;
|
|
};
|
|
};
|
|
}
|
|
|
|
function didOperatorTableUpdateRecently(db: BetterSql3.Database) {
|
|
const minutesBetweenOperatorTableUpdates =
|
|
process.env.FAUCET_MINUTES_BETWEEN_OPERATOR_TABLE_UPDATES;
|
|
|
|
const operatorTableTimestampCutoff = minutesBetweenOperatorTableUpdates
|
|
? Date.now() - parseInt(minutesBetweenOperatorTableUpdates) * 60 * 1000
|
|
: 0;
|
|
|
|
if (!operatorTableTimestampCutoff) return false;
|
|
|
|
const lastUpdate = db
|
|
.prepare(`SELECT ${FAUCET_TABLE.OPERATOR_LAST_UPDATE} FROM ${TABLE.FAUCET}`)
|
|
.get() as { [FAUCET_TABLE.OPERATOR_LAST_UPDATE]: number };
|
|
|
|
console.log('lastUpdate', lastUpdate);
|
|
|
|
if (!lastUpdate || !lastUpdate[FAUCET_TABLE.OPERATOR_LAST_UPDATE]) {
|
|
return false;
|
|
}
|
|
|
|
return lastUpdate[FAUCET_TABLE.OPERATOR_LAST_UPDATE] > operatorTableTimestampCutoff;
|
|
}
|
|
|
|
async function updateNodeOperatorTable({ db }: { db: BetterSql3.Database }) {
|
|
const nodeOperatorRes = await fetch('/oxen-swap');
|
|
|
|
if (!nodeOperatorRes.ok) {
|
|
console.error('Failed to fetch node operator data');
|
|
return;
|
|
}
|
|
|
|
const { wallets } = (await nodeOperatorRes.json()) as NodeOperatorScoreResponse;
|
|
|
|
const walletAddresses = Object.keys(wallets);
|
|
|
|
const insert = db.prepare(`INSERT INTO ${TABLE.OPERATOR} (${OPERATOR_TABLE.ID}) VALUES (?)`);
|
|
|
|
const transaction = db.transaction((walletAddresses: Array<string>) => {
|
|
for (const address of walletAddresses) {
|
|
insert.run(address);
|
|
}
|
|
});
|
|
|
|
transaction(walletAddresses);
|
|
}
|
|
*/
|