added wallet registration endpoint
This commit is contained in:
parent
69722b9bba
commit
7ca99356fb
28
README.md
28
README.md
|
|
@ -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/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/vanity/resolve?address=...` | Resolve a vanity address to its owner |
|
||||||
| `GET` | `/api/v1/addresses/registration?address=...` | Check wallet registration |
|
| `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/base?coin=CLTC&address=...` | Return one base-coin balance |
|
||||||
| `GET` | `/api/v1/balances?address=...` | Return every balance owned by an address |
|
| `GET` | `/api/v1/balances?address=...` | Return every balance owned by an address |
|
||||||
| `GET` | `/api/v1/transactions/lookup?txid=...` | Return a confirmed transaction |
|
| `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 API never creates or signs a transaction for the caller. It only forwards
|
||||||
the complete signed transaction through `Client::submitTransaction()`.
|
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
|
## Reliability
|
||||||
|
|
||||||
Each RPC request still opens one connection, makes one request, receives one
|
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
|
This returns the node's proposal, vote, implementation, and activation state as
|
||||||
JSON.
|
JSON.
|
||||||
|
|
||||||
The public API does not currently expose wallet-registration transaction
|
The public API does not create wallet-registration signatures. A browser
|
||||||
creation. A browser wallet must create and sign registration data locally
|
wallet must create and sign registration data locally before submitting it
|
||||||
before an appropriate broadcast path can submit it. The API never registers a
|
through `/api/v1/addresses/register`. The API never registers a
|
||||||
wallet using credentials owned by the API operator.
|
wallet using credentials owned by the API operator.
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
APP_ENV=production
|
APP_ENV=production
|
||||||
|
|
||||||
# Give every endpoint a unique name. Separate endpoints with commas.
|
# 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
|
CONTRACTLESS_RPC_TIMEOUT=10
|
||||||
|
|
||||||
# An endpoint enters a temporary cooldown after this many consecutive
|
# An endpoint enters a temporary cooldown after this many consecutive
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
$router->get('/api/v1/balances/base', static function () use ($application): never {
|
||||||
$address = Request::canonicalAddress(Request::queryString('address', 45));
|
$address = Request::canonicalAddress(Request::queryString('address', 45));
|
||||||
$coin = strtoupper(Request::queryString('coin', 15));
|
$coin = strtoupper(Request::queryString('coin', 15));
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,10 @@ final class RateLimiter
|
||||||
'limit' => $this->config->messageVerificationQuota,
|
'limit' => $this->config->messageVerificationQuota,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
if ($path === '/api/v1/transactions/broadcast') {
|
if (
|
||||||
|
$path === '/api/v1/transactions/broadcast'
|
||||||
|
|| $path === '/api/v1/addresses/register'
|
||||||
|
) {
|
||||||
$limits[] = [
|
$limits[] = [
|
||||||
'scope' => 'broadcast',
|
'scope' => 'broadcast',
|
||||||
'limit' => $this->config->broadcastQuota,
|
'limit' => $this->config->broadcastQuota,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue