Connector Open-Source Code
Browse connector files locally. Exchange API keys stay on your device.
ui/exchange.php
<?php
// /opt/nuxvision_connector/ui/exchange.php
declare(strict_types=1);
require_once __DIR__ . '/common.php';
/* =========================================================
CONFIG
========================================================= */
const NV_BASE_DEFAULT = 'https://nuxvision.com/api/v1';
/* -------------------- local helpers -------------------- */
function current_instance_id(): int {
$id = 0;
if (isset($_GET['instance_id'])) $id = to_int($_GET['instance_id'], 0);
if (isset($_POST['instance_id'])) $id = to_int($_POST['instance_id'], $id);
if ($id <= 0) $id = nv_instance_id_from_session();
return $id > 0 ? $id : 0;
}
function cfg_defaults(): array {
return [
'nuxvision' => [
'base_url' => NV_BASE_DEFAULT,
'api_key' => '',
'timeout' => 8,
],
'exchange' => [
// name is auto-detected from NuxVision instance
'name' => '',
'api_key' => '',
'api_secret' => '',
],
];
}
function load_config(int $instanceId): array {
$d = cfg_defaults();
if ($instanceId <= 0) return $d;
$path = nv_instance_config_path($instanceId);
if (!is_file($path)) return $d;
$cfg = @require $path;
if (!is_array($cfg)) return $d;
foreach (['nuxvision','exchange'] as $k) {
if (isset($cfg[$k]) && is_array($cfg[$k])) {
$d[$k] = array_replace($d[$k], $cfg[$k]);
}
}
// lock base_url to prod default
$d['nuxvision']['base_url'] = NV_BASE_DEFAULT;
// never use exchange base_url from UI
if (isset($d['exchange']) && is_array($d['exchange'])) {
$d['exchange']['base_url'] = '';
}
return $d;
}
function save_config(int $instanceId, array $cfg): bool {
if ($instanceId <= 0) return false;
$dir = nv_instance_dir($instanceId);
if (!is_dir($dir)) @mkdir($dir, 0775, true);
$path = nv_instance_config_path($instanceId);
// Enforce invariants
if (!isset($cfg['exchange']) || !is_array($cfg['exchange'])) $cfg['exchange'] = [];
$cfg['exchange']['base_url'] = '';
if (!isset($cfg['nuxvision']) || !is_array($cfg['nuxvision'])) $cfg['nuxvision'] = [];
$cfg['nuxvision']['base_url'] = NV_BASE_DEFAULT;
$out = [
'nuxvision' => (array)($cfg['nuxvision'] ?? []),
'exchange' => (array)($cfg['exchange'] ?? []),
];
$export = var_export($out, true);
$php = "<?php\n// generated by connector UI\nreturn {$export};\n";
return (bool)@file_put_contents($path, $php, LOCK_EX);
}
/* -------------------- temp form persistence (for Test) -------------------- */
function nv_exchange_tmp_key(int $instanceId): string {
return 'nv_exchange_form_' . $instanceId;
}
function nv_exchange_tmp_set(int $instanceId, string $apiKey, string $apiSecret): void {
if ($instanceId <= 0) return;
if (!isset($_SESSION) || !is_array($_SESSION)) return;
$_SESSION[nv_exchange_tmp_key($instanceId)] = [
'api_key' => $apiKey,
'api_secret' => $apiSecret,
];
}
function nv_exchange_tmp_pop(int $instanceId): ?array {
if ($instanceId <= 0) return null;
if (!isset($_SESSION) || !is_array($_SESSION)) return null;
$k = nv_exchange_tmp_key($instanceId);
if (!isset($_SESSION[$k]) || !is_array($_SESSION[$k])) return null;
$v = $_SESSION[$k];
unset($_SESSION[$k]);
return $v;
}
/* -------------------- NV helpers -------------------- */
function nv_detect_exchange_name(string $apiKey, int $instanceId): array {
$apiKey = trim($apiKey);
if ($apiKey === '' || $instanceId <= 0) {
return ['ok' => false, 'name' => '', 'err' => 'missing_params', 'http' => 0, 'raw' => ''];
}
$url = rtrim(NV_BASE_DEFAULT, '/') . '/instance_list.php';
[$code, $curlErr, $body, $json] = curl_json($url, [
'Accept' => 'application/json',
'X-API-KEY' => $apiKey,
], 8);
if ($curlErr !== '') {
return ['ok' => false, 'name' => '', 'err' => $curlErr, 'http' => (int)$code, 'raw' => is_string($body) ? substr($body, 0, 200) : ''];
}
if ($code < 200 || $code >= 400 || !is_array($json) || empty($json['ok']) || !isset($json['instances']) || !is_array($json['instances'])) {
return ['ok' => false, 'name' => '', 'err' => 'http_error', 'http' => (int)$code, 'raw' => is_string($body) ? substr($body, 0, 200) : ''];
}
foreach ($json['instances'] as $row) {
if (!is_array($row)) continue;
$id = (int)($row['id'] ?? 0);
if ($id !== $instanceId) continue;
$ex = strtolower(trim((string)($row['exchange'] ?? '')));
if ($ex === '') {
return ['ok' => false, 'name' => '', 'err' => 'missing_exchange', 'http' => (int)$code, 'raw' => ''];
}
return ['ok' => true, 'name' => $ex, 'err' => '', 'http' => (int)$code, 'raw' => ''];
}
return ['ok' => false, 'name' => '', 'err' => 'instance_not_found', 'http' => (int)$code, 'raw' => ''];
}
/* -------------------- Exchange adapter test -------------------- */
function exchange_adapter_load(string $name): array {
$name = strtolower(trim($name));
if ($name === '') return ['ok' => false, 'err' => 'missing_exchange_name', 'adapter' => null];
// UI dir = /opt/nuxvision_connector/ui, adapters = /opt/nuxvision_connector/exchanges
$path = realpath(__DIR__ . '/../exchanges/' . $name . '.php');
if (!$path || !is_file($path)) {
return ['ok' => false, 'err' => 'adapter_not_found', 'adapter' => null];
}
$adapter = @require $path;
if (!is_array($adapter)) {
return ['ok' => false, 'err' => 'adapter_invalid', 'adapter' => null];
}
return ['ok' => true, 'err' => '', 'adapter' => $adapter];
}
function exchange_test_keys(string $exchangeName, array $cfgExchange): array {
$load = exchange_adapter_load($exchangeName);
if (empty($load['ok'])) {
return ['ok' => false, 'err' => (string)($load['err'] ?? 'adapter_load_failed')];
}
$ad = $load['adapter'];
if (!is_array($ad) || !isset($ad['wallet']) || !is_callable($ad['wallet'])) {
return ['ok' => false, 'err' => 'wallet_not_supported'];
}
// Never force base_url from UI
$cfgExchange['base_url'] = '';
$r = $ad['wallet']($cfgExchange);
// Expect standard structure from your adapters: ['ok'=>bool,'code'=>int,'json'=>..., 'raw'=>...]
$okHttp = !empty($r['ok']);
$code = (int)($r['code'] ?? 0);
$json = $r['json'] ?? null;
// Bitkub-specific: if JSON has "error" and it's not 0, treat as failure
if (is_array($json) && array_key_exists('error', $json)) {
$e = (int)$json['error'];
if ($e !== 0) {
return ['ok' => false, 'err' => 'exchange_error_' . $e, 'code' => $code];
}
}
if (!$okHttp) {
return ['ok' => false, 'err' => 'http_or_auth_failed', 'code' => $code];
}
return ['ok' => true, 'err' => '', 'code' => $code];
}
/* =========================================================
PAGE LOGIC
========================================================= */
$instanceId = current_instance_id();
if ($instanceId <= 0) {
nv_flash_set('err', 'Missing instance_id. Please complete Step 1 first.');
header('Location: ./nuxvision.php');
exit;
}
nv_set_instance_in_session($instanceId);
$cfg = load_config($instanceId);
// If we just came back from "Test", restore the typed values (without saving to disk)
$tmp = nv_exchange_tmp_pop($instanceId);
if (is_array($tmp)) {
if (array_key_exists('api_key', $tmp)) $cfg['exchange']['api_key'] = (string)$tmp['api_key'];
if (array_key_exists('api_secret', $tmp)) $cfg['exchange']['api_secret'] = (string)$tmp['api_secret'];
$cfg['exchange']['base_url'] = '';
}
// Ensure Step 1 exists
$nvKey = trim((string)($cfg['nuxvision']['api_key'] ?? ''));
if ($nvKey === '') {
nv_flash_set('err', 'NuxVision config is missing. Please complete Step 1 first.');
header('Location: ./nuxvision.php?instance_id=' . urlencode((string)$instanceId));
exit;
}
// Auto-detect exchange name from NuxVision instance
$det = nv_detect_exchange_name($nvKey, $instanceId);
if (!empty($det['ok'])) {
$cfg['exchange']['name'] = (string)$det['name'];
} else {
if (trim((string)($cfg['exchange']['name'] ?? '')) === '') {
nv_flash_set('warn', 'Could not detect exchange name from NuxVision (you can still save credentials).');
}
}
// Handle POST actions
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST') {
$do = (string)($_POST['do'] ?? 'save'); // default to save if missing
$apiKey = trim((string)($_POST['api_key'] ?? ''));
$apiSecret = trim((string)($_POST['api_secret'] ?? ''));
// Update in-memory cfg from form
$cfg['exchange']['api_key'] = $apiKey;
$cfg['exchange']['api_secret'] = $apiSecret;
$cfg['exchange']['base_url'] = '';
// Refresh exchange name on any action (best-effort)
$det2 = nv_detect_exchange_name($nvKey, $instanceId);
if (!empty($det2['ok'])) {
$cfg['exchange']['name'] = (string)$det2['name'];
}
$exchangeName = strtolower(trim((string)($cfg['exchange']['name'] ?? '')));
if ($do === 'test') {
// Persist typed values for the redirect so the form doesn't "lose" them
nv_exchange_tmp_set($instanceId, $apiKey, $apiSecret);
if ($apiKey === '' || $apiSecret === '') {
nv_flash_set('err', 'Please enter both API key and API secret before testing.');
} elseif ($exchangeName === '') {
nv_flash_set('err', 'Exchange name is missing (cannot load adapter).');
} else {
$t = exchange_test_keys($exchangeName, [
'api_key' => $apiKey,
'api_secret' => $apiSecret,
'base_url' => '',
]);
if (!empty($t['ok'])) {
nv_flash_set('ok', 'Test successful: exchange API credentials are valid.');
} else {
$code = (int)($t['code'] ?? 0);
$msg = 'Test failed';
if ($exchangeName !== '') $msg .= ' (' . strtoupper($exchangeName) . ')';
if ($code > 0) $msg .= ' • HTTP ' . $code;
if (!empty($t['err'])) $msg .= ' • ' . (string)$t['err'];
nv_flash_set('err', $msg);
}
}
header('Location: ./exchange.php?instance_id=' . urlencode((string)$instanceId));
exit;
}
// Default: save
if ($apiKey === '' || $apiSecret === '') {
nv_flash_set('err', 'Please enter both API key and API secret.');
} else {
if (save_config($instanceId, $cfg)) {
nv_flash_set('ok', 'Saved.');
header('Location: ./finish.php?instance_id=' . urlencode((string)$instanceId));
exit;
}
nv_flash_set('err', 'Failed to write config.php (permissions).');
}
}
/* =========================================================
RENDER
========================================================= */
render_header('Exchange', 'Step 2 — Exchange');
render_flash();
$exName = strtoupper(trim((string)($cfg['exchange']['name'] ?? '')));
if ($exName === '') $exName = 'UNKNOWN';
?>
<div class="nv-card">
<div class="nv-titlebar">
<div class="d-flex align-items-center gap-2">
<strong>Exchange credentials</strong>
<span class="nv-muted">Instance <?=h((string)$instanceId)?> • <?=h($exName)?></span>
</div>
<a class="btn btn-soft btn-sm" href="./index.php" title="Home">
<i class="bi bi-house me-1"></i>Home
</a>
</div>
<div class="nv-pad">
<form method="post" action="./exchange.php?instance_id=<?=h((string)$instanceId)?>">
<input type="hidden" name="instance_id" value="<?=h((string)$instanceId)?>">
<input type="hidden" name="do" value="save">
<div class="row g-3">
<div class="col-12">
<label class="form-label">Exchange</label>
<input class="form-control" value="<?=h($exName)?>" readonly>
<div class="nv-muted small mt-1">Detected from your NuxVision instance.</div>
</div>
<div class="col-12 col-md-6">
<label class="form-label">API key</label>
<input class="form-control" name="api_key" value="<?=h((string)($cfg['exchange']['api_key'] ?? ''))?>">
</div>
<div class="col-12 col-md-6">
<label class="form-label">API secret</label>
<input class="form-control" name="api_secret" value="<?=h((string)($cfg['exchange']['api_secret'] ?? ''))?>">
</div>
</div>
<div class="d-flex flex-wrap gap-2 justify-content-between mt-4">
<a class="btn btn-soft" href="./nuxvision.php?instance_id=<?=h((string)$instanceId)?>">
<i class="bi bi-arrow-left me-1"></i>Back
</a>
<div class="d-flex gap-2">
<button class="btn btn-soft" type="submit" onclick="this.form.do.value='test'">
<i class="bi bi-shield-check me-1"></i>Test API keys
</button>
<button class="btn btn-accent" type="submit" onclick="this.form.do.value='save'">
<i class="bi bi-check2-circle me-1"></i>Continue to Finish
</button>
</div>
</div>
</form>
</div>
</div>
<?php render_footer(); ?>