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 `.cltc` configures testnet and displays `CLTC`.
|
||||||
- An address ending in `.clc` configures mainnet and displays `CLC`.
|
- An address ending in `.clc` configures mainnet and displays `CLC`.
|
||||||
|
|
||||||
The faucet address must be the registered canonical 40-character short
|
The faucet address itself must be the registered canonical 40-character short
|
||||||
address. Faucet claims also require canonical short addresses; vanity aliases
|
address. Faucet recipients may enter either a canonical short address or a
|
||||||
are not accepted by the claim form.
|
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
|
Point the faucet at its dedicated wallet and provide that wallet's decryption
|
||||||
key:
|
key:
|
||||||
|
|
|
||||||
39
api.php
39
api.php
|
|
@ -51,14 +51,14 @@ try {
|
||||||
json_response(['success' => false, 'error' => 'Request body must be JSON.'], 400);
|
json_response(['success' => false, 'error' => 'Request body must be JSON.'], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
$requestedAddress = normalize_requested_address(
|
$requestedInput = normalize_requested_address(
|
||||||
(string) ($body['address'] ?? ''),
|
(string) ($body['address'] ?? ''),
|
||||||
$network['suffix']
|
$network['suffix']
|
||||||
);
|
);
|
||||||
if ($requestedAddress === null) {
|
if ($requestedInput === null) {
|
||||||
json_response([
|
json_response([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'error' => "Enter a valid registered {$network['network']} wallet address.",
|
'error' => "Enter a valid {$network['network']} wallet or vanity address.",
|
||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,19 +70,38 @@ try {
|
||||||
|
|
||||||
$now = time();
|
$now = time();
|
||||||
$nowMs = (int) floor(microtime(true) * 1000);
|
$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 (
|
$precheck = with_ledger_lock(true, function (array &$ledger) use (
|
||||||
$requestedAddress,
|
$requestedAddress,
|
||||||
$clientIp,
|
$clientIp,
|
||||||
$now,
|
$now,
|
||||||
$nowMs,
|
|
||||||
$config
|
$config
|
||||||
): array {
|
): 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(
|
$addressClaim = active_claim(
|
||||||
$ledger['addresses'],
|
$ledger['addresses'],
|
||||||
$requestedAddress,
|
$requestedAddress,
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="faucet-form">
|
<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">
|
<div class="address-control">
|
||||||
<input
|
<input
|
||||||
id="wallet-address"
|
id="wallet-address"
|
||||||
|
|
@ -79,7 +79,7 @@
|
||||||
>
|
>
|
||||||
<span id="input-symbol" class="input-marker" aria-hidden="true">CLTC</span>
|
<span id="input-symbol" class="input-marker" aria-hidden="true">CLTC</span>
|
||||||
</div>
|
</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">
|
<button type="submit" class="request-button">
|
||||||
<span id="request-label">Request 25 CLTC</span>
|
<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';
|
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));
|
$address = strtolower(trim($address));
|
||||||
$escapedSuffix = preg_quote($suffix, '/');
|
$escapedSuffix = preg_quote($suffix, '/');
|
||||||
$canonical = "[a-f0-9]{40}\\.$escapedSuffix";
|
$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
|
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("#intro-network").textContent = network;
|
||||||
document.querySelector("#input-symbol").textContent = symbol;
|
document.querySelector("#input-symbol").textContent = symbol;
|
||||||
document.querySelector("#address-note").textContent =
|
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 =
|
document.querySelector("#request-label").textContent =
|
||||||
`Request ${displayClaim} ${symbol}`;
|
`Request ${displayClaim} ${symbol}`;
|
||||||
document.querySelector("#rule-amount").textContent = displayClaim;
|
document.querySelector("#rule-amount").textContent = displayClaim;
|
||||||
|
|
@ -115,16 +115,21 @@ form.addEventListener("submit", async (event) => {
|
||||||
const value = address.value.trim();
|
const value = address.value.trim();
|
||||||
const suffix = faucetState.network === "mainnet" ? "clc" : "cltc";
|
const suffix = faucetState.network === "mainnet" ? "clc" : "cltc";
|
||||||
const escapedSuffix = suffix.replace(".", "\\.");
|
const escapedSuffix = suffix.replace(".", "\\.");
|
||||||
const valid = new RegExp(
|
const canonicalAddress = new RegExp(
|
||||||
`^[a-f0-9]{40}\\.${escapedSuffix}$`,
|
`^[a-f0-9]{40}\\.${escapedSuffix}$`,
|
||||||
"i",
|
"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.add("visible");
|
||||||
message.classList.remove("success");
|
message.classList.remove("success");
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
message.textContent =
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue