diff --git a/README.md b/README.md index 38394e0..cf95655 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,51 @@ const result = await response.json(); The wallet can generate the handshake proof when it is unlocked and reuse it for later API calls. It does not send its private key or wallet decryption key. +### Test Tool + +The CLI-only test tool sends an existing wallet handshake proof through the +same headers a browser wallet will use. Set the credentials in the current +shell: + +```bash +export CONTRACTLESS_TEST_ADDRESS="$(jq -r '.short_address' /path/to/wallet)" +export CONTRACTLESS_TEST_PUBLIC_KEY="$(jq -r '.public_key' /path/to/wallet)" +``` + +Generate the handshake signature with the Contractless `sign_message` tool: + +```bash +./sign_message "aced" +``` + +Enter the same wallet path and decryption key. Copy only the hexadecimal value +printed after `signature:`: + +```bash +export CONTRACTLESS_TEST_SIGNATURE='1332-character-signature-from-sign-message' +``` + +`sign_message` and the node handshake both sign the Skein-256 hash of the exact +text `aced`, so this produces the proof expected by the API. + +Then test the default `/api/v1/height` route: + +```bash +php tools/test_api.php https://api.contractless.dev +``` + +An alternative GET route may be provided: + +```bash +php tools/test_api.php \ + https://api.contractless.dev \ + /api/v1/network +``` + +The tool never accepts or loads a private key. It tests the exact public proof +that applications will submit to the API and returns a nonzero exit status +when the HTTP request fails. + ## Initial Endpoints All responses use JSON. Successful responses contain `success: true` and a diff --git a/tools/test_api.php b/tools/test_api.php new file mode 100644 index 0000000..5fbe7e9 --- /dev/null +++ b/tools/test_api.php @@ -0,0 +1,99 @@ + [ + 'method' => 'GET', + 'header' => implode("\r\n", [ + 'Accept: application/json', + 'X-Contractless-Address: ' . $address, + 'X-Contractless-Public-Key: ' . $publicKey, + 'X-Contractless-Signature: ' . $signature, + ]), + 'ignore_errors' => true, + 'timeout' => 20, + ], + ]); + $body = file_get_contents($baseUrl . $route, false, $context); + if ($body === false) { + throw new RuntimeException('The API request could not be completed.'); + } + + $responseHeaders = $http_response_header ?? []; + $statusLine = (string) ($responseHeaders[0] ?? ''); + if (preg_match('/\s(\d{3})\s/', $statusLine, $match) !== 1) { + throw new RuntimeException('The API returned an invalid HTTP response.'); + } + + $status = (int) $match[1]; + echo "HTTP $status\n"; + try { + $decoded = json_decode($body, true, flags: JSON_THROW_ON_ERROR); + echo json_encode( + $decoded, + JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR, + ) . PHP_EOL; + } catch (JsonException) { + echo $body . PHP_EOL; + } + + exit($status >= 200 && $status < 300 ? 0 : 1); +} catch (Throwable $error) { + fwrite(STDERR, 'API test failed: ' . $error->getMessage() . PHP_EOL); + exit(1); +}