added wallet registration endpoint

This commit is contained in:
viraladmin 2026-07-29 12:05:54 -06:00
parent 69722b9bba
commit 7ca99356fb
4 changed files with 86 additions and 5 deletions

View File

@ -156,6 +156,7 @@ All responses use JSON. Successful responses contain `success: true` and a
| `GET` | `/api/v1/addresses/validate?address=...` | Validate a registered canonical address |
| `GET` | `/api/v1/addresses/vanity/resolve?address=...` | Resolve a vanity address to its owner |
| `GET` | `/api/v1/addresses/registration?address=...` | Check wallet registration |
| `POST` | `/api/v1/addresses/register` | Submit a locally signed wallet registration |
| `GET` | `/api/v1/balances/base?coin=CLTC&address=...` | Return one base-coin balance |
| `GET` | `/api/v1/balances?address=...` | Return every balance owned by an address |
| `GET` | `/api/v1/transactions/lookup?txid=...` | Return a confirmed transaction |
@ -184,6 +185,27 @@ Transaction broadcasting accepts:
The API never creates or signs a transaction for the caller. It only forwards
the complete signed transaction through `Client::submitTransaction()`.
Wallet registration accepts:
```json
{
"address": "40-character-address.cltc",
"public_key": "1794-character-public-key-hex",
"signature": "1332-character-registration-signature-hex"
}
```
The authenticated address and public key must match the registration body.
The API forwards the registration through `Client::registerWallet()`; it never
creates the registration signature.
Browser extensions must also be included in `API_CORS_ORIGINS` using their
exact origin:
```text
API_CORS_ORIGINS=https://wallet.example.com,chrome-extension://extension-id
```
## Reliability
Each RPC request still opens one connection, makes one request, receives one
@ -479,7 +501,7 @@ GET /api/v1/governance/proposals?proposal_key=...
This returns the node's proposal, vote, implementation, and activation state as
JSON.
The public API does not currently expose wallet-registration transaction
creation. A browser wallet must create and sign registration data locally
before an appropriate broadcast path can submit it. The API never registers a
The public API does not create wallet-registration signatures. A browser
wallet must create and sign registration data locally before submitting it
through `/api/v1/addresses/register`. The API never registers a
wallet using credentials owned by the API operator.

View File

@ -3,7 +3,7 @@
APP_ENV=production
# Give every endpoint a unique name. Separate endpoints with commas.
CONTRACTLESS_RPC_NODES=node-1=contractless.dev:50050
CONTRACTLESS_RPC_NODES=node-1=127.0.0.1:50050,node-2=192.0.2.10:50050
CONTRACTLESS_RPC_TIMEOUT=10
# An endpoint enters a temporary cooldown after this many consecutive

View File

@ -132,6 +132,62 @@ try {
),
]);
});
$router->post(
'/api/v1/addresses/register',
static function () use ($application, $requestCredentials): never {
$body = Request::json();
$address = Request::canonicalAddress(
Request::bodyString($body, 'address', 45),
);
$publicKeyHex = strtolower(
Request::bodyString($body, 'public_key', 1_794),
);
$signature = Request::signature(
Request::bodyString($body, 'signature', 1_332),
);
if (
strlen($publicKeyHex) !== 1_794
|| !ctype_xdigit($publicKeyHex)
) {
throw new HttpException(
422,
'The public key must contain 1,794 hexadecimal characters.',
);
}
if (!hash_equals($requestCredentials->address, $address)) {
throw new HttpException(
403,
'A wallet may only submit its own registration.',
);
}
$publicKey = hex2bin($publicKeyHex);
if (
$publicKey === false
|| !hash_equals(
$requestCredentials->handshakeProof->publicKey,
$publicKey,
)
) {
throw new HttpException(
403,
'The registration public key does not match the authenticated wallet.',
);
}
JsonResponse::success([
'address' => $address,
'registered' => RpcReplyDecoder::registrationStatus(
$application->client->registerWallet(
$address,
$publicKey,
$signature,
),
),
]);
},
);
$router->get('/api/v1/balances/base', static function () use ($application): never {
$address = Request::canonicalAddress(Request::queryString('address', 45));
$coin = strtoupper(Request::queryString('coin', 15));

View File

@ -26,7 +26,10 @@ final class RateLimiter
'limit' => $this->config->messageVerificationQuota,
];
}
if ($path === '/api/v1/transactions/broadcast') {
if (
$path === '/api/v1/transactions/broadcast'
|| $path === '/api/v1/addresses/register'
) {
$limits[] = [
'scope' => 'broadcast',
'limit' => $this->config->broadcastQuota,