added vanity address claims
This commit is contained in:
parent
c9dfdd0386
commit
d059af2cea
|
|
@ -71,9 +71,10 @@ but normal installations do not need it.
|
|||
- An address ending in `.cltc` configures testnet and displays `CLTC`.
|
||||
- An address ending in `.clc` configures mainnet and displays `CLC`.
|
||||
|
||||
The faucet address must be the registered canonical 40-character short
|
||||
address. Faucet claims also require canonical short addresses; vanity aliases
|
||||
are not accepted by the claim form.
|
||||
The faucet address itself must be the registered canonical 40-character short
|
||||
address. Faucet recipients may enter either a canonical short address or a
|
||||
registered vanity address. Vanity inputs are resolved through RPC, and the
|
||||
canonical owner address is used for the transfer and 24-hour claim ledger.
|
||||
|
||||
Point the faucet at its dedicated wallet and provide that wallet's decryption
|
||||
key:
|
||||
|
|
|
|||
39
api.php
39
api.php
|
|
@ -51,14 +51,14 @@ try {
|
|||
json_response(['success' => false, 'error' => 'Request body must be JSON.'], 400);
|
||||
}
|
||||
|
||||
$requestedAddress = normalize_requested_address(
|
||||
$requestedInput = normalize_requested_address(
|
||||
(string) ($body['address'] ?? ''),
|
||||
$network['suffix']
|
||||
);
|
||||
if ($requestedAddress === null) {
|
||||
if ($requestedInput === null) {
|
||||
json_response([
|
||||
'success' => false,
|
||||
'error' => "Enter a valid registered {$network['network']} wallet address.",
|
||||
'error' => "Enter a valid {$network['network']} wallet or vanity address.",
|
||||
], 422);
|
||||
}
|
||||
|
||||
|
|
@ -70,19 +70,38 @@ try {
|
|||
|
||||
$now = time();
|
||||
$nowMs = (int) floor(microtime(true) * 1000);
|
||||
$rateLimit = with_ledger_lock(true, function (array &$ledger) use (
|
||||
$nowMs,
|
||||
$config
|
||||
): bool {
|
||||
$lastRequest = (int) $ledger['last_request_timestamp_ms'];
|
||||
if ($nowMs - $lastRequest < $config['minimum_request_interval_ms']) {
|
||||
return false;
|
||||
}
|
||||
$ledger['last_request_timestamp_ms'] = $nowMs;
|
||||
return true;
|
||||
});
|
||||
if (!$rateLimit) {
|
||||
json_response([
|
||||
'success' => false,
|
||||
'error' => 'The faucet is busy. Try again.',
|
||||
], 429);
|
||||
}
|
||||
|
||||
$requestedAddress = resolve_requested_address($requestedInput, $network['suffix']);
|
||||
if ($requestedAddress === null) {
|
||||
json_response([
|
||||
'success' => false,
|
||||
'error' => "The {$network['network']} wallet or vanity address is not registered.",
|
||||
], 422);
|
||||
}
|
||||
|
||||
$precheck = with_ledger_lock(true, function (array &$ledger) use (
|
||||
$requestedAddress,
|
||||
$clientIp,
|
||||
$now,
|
||||
$nowMs,
|
||||
$config
|
||||
): array {
|
||||
$lastRequest = (int) $ledger['last_request_timestamp_ms'];
|
||||
if ($nowMs - $lastRequest < $config['minimum_request_interval_ms']) {
|
||||
return ['allowed' => false, 'status' => 429, 'error' => 'The faucet is busy. Try again.'];
|
||||
}
|
||||
$ledger['last_request_timestamp_ms'] = $nowMs;
|
||||
|
||||
$addressClaim = active_claim(
|
||||
$ledger['addresses'],
|
||||
$requestedAddress,
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
</div>
|
||||
|
||||
<form id="faucet-form">
|
||||
<label for="wallet-address">Registered wallet address</label>
|
||||
<label for="wallet-address">Registered wallet or vanity address</label>
|
||||
<div class="address-control">
|
||||
<input
|
||||
id="wallet-address"
|
||||
|
|
@ -79,7 +79,7 @@
|
|||
>
|
||||
<span id="input-symbol" class="input-marker" aria-hidden="true">CLTC</span>
|
||||
</div>
|
||||
<p id="address-note" class="field-note">The wallet must already be registered on testnet.</p>
|
||||
<p id="address-note" class="field-note">The wallet or vanity address must already be registered on testnet.</p>
|
||||
|
||||
<button type="submit" class="request-button">
|
||||
<span id="request-label">Request 25 CLTC</span>
|
||||
|
|
|
|||
51
lib.php
51
lib.php
|
|
@ -142,12 +142,59 @@ function normalized_client_ip(string $remoteAddress): ?string
|
|||
return substr(bin2hex($packed), 0, 16) . '::/64';
|
||||
}
|
||||
|
||||
function normalize_requested_address(string $address, string $suffix): ?string
|
||||
/**
|
||||
* Normalize a claim input without contacting the node.
|
||||
*
|
||||
* Canonical addresses are returned as-is. Vanity names are left-padded to
|
||||
* the fixed 20-byte payload required by the vanity-owner RPC command.
|
||||
*/
|
||||
function normalize_requested_address(string $address, string $suffix): ?array
|
||||
{
|
||||
$address = strtolower(trim($address));
|
||||
$escapedSuffix = preg_quote($suffix, '/');
|
||||
$canonical = "[a-f0-9]{40}\\.$escapedSuffix";
|
||||
return preg_match("/^$canonical$/", $address) === 1 ? $address : null;
|
||||
if (preg_match("/^$canonical$/", $address) === 1) {
|
||||
return ['type' => 'canonical', 'address' => $address];
|
||||
}
|
||||
|
||||
$separator = strrpos($address, '.');
|
||||
if ($separator === false) {
|
||||
return null;
|
||||
}
|
||||
$payload = substr($address, 0, $separator);
|
||||
$network = substr($address, $separator + 1);
|
||||
if (
|
||||
$network !== $suffix
|
||||
|| preg_match('/^[a-z]{1,20}$/', $payload) !== 1
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'vanity',
|
||||
'address' => str_pad($payload, 20, ' ', STR_PAD_LEFT) . '.' . $suffix,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve either an ordinary short address or registered vanity address to
|
||||
* the canonical short address used by transfers and faucet cooldown records.
|
||||
*/
|
||||
function resolve_requested_address(array $requested, string $suffix): ?string
|
||||
{
|
||||
if ($requested['type'] === 'canonical') {
|
||||
return $requested['address'];
|
||||
}
|
||||
|
||||
$owner = strtolower(trim(faucet_rpc()['client']->vanityOwner($requested['address'])));
|
||||
if ($owner === '' || str_starts_with($owner, 'error:')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$escapedSuffix = preg_quote($suffix, '/');
|
||||
return preg_match("/^[a-f0-9]{40}\\.$escapedSuffix$/", $owner) === 1
|
||||
? $owner
|
||||
: null;
|
||||
}
|
||||
|
||||
function atomic_to_decimal(int $atomic): string
|
||||
|
|
|
|||
13
script.js
13
script.js
|
|
@ -58,7 +58,7 @@ function applyStatus(status) {
|
|||
document.querySelector("#intro-network").textContent = network;
|
||||
document.querySelector("#input-symbol").textContent = symbol;
|
||||
document.querySelector("#address-note").textContent =
|
||||
`The wallet must already be registered on ${network}.`;
|
||||
`The wallet or vanity address must already be registered on ${network}.`;
|
||||
document.querySelector("#request-label").textContent =
|
||||
`Request ${displayClaim} ${symbol}`;
|
||||
document.querySelector("#rule-amount").textContent = displayClaim;
|
||||
|
|
@ -115,16 +115,21 @@ form.addEventListener("submit", async (event) => {
|
|||
const value = address.value.trim();
|
||||
const suffix = faucetState.network === "mainnet" ? "clc" : "cltc";
|
||||
const escapedSuffix = suffix.replace(".", "\\.");
|
||||
const valid = new RegExp(
|
||||
const canonicalAddress = new RegExp(
|
||||
`^[a-f0-9]{40}\\.${escapedSuffix}$`,
|
||||
"i",
|
||||
).test(value);
|
||||
);
|
||||
const vanityAddress = new RegExp(
|
||||
`^[a-z]{1,20}\\.${escapedSuffix}$`,
|
||||
"i",
|
||||
);
|
||||
const valid = canonicalAddress.test(value) || vanityAddress.test(value);
|
||||
|
||||
message.classList.add("visible");
|
||||
message.classList.remove("success");
|
||||
if (!valid) {
|
||||
message.textContent =
|
||||
`Enter a valid registered Contractless wallet address ending in .${suffix}.`;
|
||||
`Enter a valid registered Contractless wallet or vanity address ending in .${suffix}.`;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue