arnacon-sdk
Version:
A comprehensive SDK for deploying and managing Arnacon smart contracts across multiple networks
429 lines (408 loc) • 18.8 kB
JavaScript
require('dotenv').config();
const fs = require('fs');
const path = require('path');
const ArnaconSDK = require('./index');
function print(msg) {
process.stdout.write(String(msg) + '\n');
}
function printError(msg) {
process.stderr.write(String(msg) + '\n');
}
function exitWithError(message, code = 1) {
printError(`Error: ${message}`);
process.exit(code);
}
function getFlagValue(names, argv) {
for (const name of names) {
const idx = argv.indexOf(name);
if (idx !== -1) {
if (idx + 1 >= argv.length || argv[idx + 1].startsWith('-')) return '';
return argv[idx + 1];
}
// Support --key=value style
const withEq = argv.find(arg => arg.startsWith(name + '='));
if (withEq) return withEq.slice(name.length + 1);
}
return undefined;
}
function flagExists(name, argv) {
return argv.includes(name);
}
function parseJsonInput(input) {
if (!input) return undefined;
try {
const abs = path.isAbsolute(input) ? input : path.join(process.cwd(), input);
if (fs.existsSync(abs) && fs.statSync(abs).isFile()) {
const content = fs.readFileSync(abs, 'utf8');
return JSON.parse(content);
}
} catch (_) {
// fallthrough to try parsing as JSON
}
try {
return JSON.parse(input);
} catch (e) {
exitWithError(`Failed to parse JSON input. Provide a valid JSON string or a path to a JSON file. Received: ${input}`);
}
}
function usage() {
print(`Arnacon CLI
Usage:
arnacon --private-key <hex> --rpc-url <url> <command> [options]
Auth options (or use env SECNUM_PRIVATE_KEY / RPC_URL):
-k, --private-key <hex> Private key (0x...)
-r, --rpc-url <url> JSON-RPC URL
Commands:
deploy-contracts [--tld <tld>]
deploy-gsm
register-sp --name <serviceProviderName>
purchase-name --name <serviceProviderName> [--days <n>]
get-owner --name <ensName>
get-ens-owner --name <ensName>
get-wrapper-owner --name <ensName>
register-subdomain --owner <address> --label <label> --name <serviceProviderName>
get-second-level-controller
get-second-level-interactor
get-product-registry --name <serviceProviderName>
get-product-types --name <serviceProviderName>
get-all-product-types --name <serviceProviderName>
get-all-products --name <serviceProviderName>
get-products-from-contract --address <nftContract>
get-nft-contracts --name <serviceProviderName>
create-product-type --name <serviceProviderName> --type <productType> --info <jsonOrPath>
create-product --ens <ensName> --type <productType> --info <jsonOrPath>
batch-create-product --ens <ensName> --info <jsonArrayOrPath> --types <jsonArrayOrComma>
create-provision --name <serviceProviderName> --identifier <id> --key <key> --info <jsonOrPath>
create-record --name <serviceProviderName> --key <key> --value <value>
get-data --ens <ensName>
get-provision --name <serviceProviderName> --identifier <id>
get-expiry --name <ensName>
get-resolver --ens <ensName>
get-ttl --ens <ensName>
get-registered-names --address <address>
get-all-addresses
get-address --contract <contractName>
Examples:
arnacon -k 0xabc -r https://rpc.test deploy-contracts --tld global
arnacon -k 0xabc -r https://rpc.test register-sp --name cellact
arnacon -k 0xabc -r https://rpc.test create-provision --name cellact --identifier sbc --key sbc_v_1 --info provision.json
`);
}
async function main() {
const argv = process.argv.slice(2);
if (argv.length === 0 || flagExists('-h', argv) || flagExists('--help', argv)) {
usage();
process.exit(0);
}
const privateKey = getFlagValue(['--private-key', '-k'], argv) || process.env.SECNUM_PRIVATE_KEY;
const rpcUrl = getFlagValue(['--rpc-url', '-r'], argv) || process.env.RPC_URL;
if (!privateKey) exitWithError('Missing private key. Use --private-key or set SECNUM_PRIVATE_KEY');
if (!rpcUrl) exitWithError('Missing RPC URL. Use --rpc-url or set RPC_URL');
const sdk = new ArnaconSDK(privateKey, rpcUrl);
// Find command as the first token that is not a flag or a flag's value
function findCommandIndex(tokens) {
let i = 0;
while (i < tokens.length) {
const t = tokens[i];
if (t.startsWith('-')) {
// --flag=value style
if (t.includes('=')) {
i += 1;
continue;
}
// Skip the next token if it's a value (doesn't start with '-')
if (i + 1 < tokens.length && !tokens[i + 1].startsWith('-')) {
i += 2;
} else {
i += 1;
}
continue;
}
return i;
}
return -1;
}
const commandIdx = findCommandIndex(argv);
if (commandIdx === -1) {
usage();
process.exit(1);
}
const command = argv[commandIdx];
const args = argv.slice(commandIdx + 1);
try {
switch (command) {
case 'deploy-contracts': {
const tld = getFlagValue(['--tld'], args) || 'global';
print(`Deploying core contracts for TLD: ${tld} ...`);
const res = await sdk.deployContracts(tld);
const count = res && typeof res === 'object' ? Object.keys(res).length : 0;
print(`✅ Core contracts deployed (${count}) for .${tld}`);
print(JSON.stringify(res, null, 2));
break;
}
case 'deploy-gsm': {
print('Deploying GSM contracts ...');
const res = await sdk.deployGSM();
const count = res && typeof res === 'object' ? Object.keys(res).length : 0;
print(`✅ GSM contracts deployed (${count})`);
print(JSON.stringify(res, null, 2));
break;
}
case 'register-sp': {
const name = getFlagValue(['--name'], args);
if (!name) exitWithError('Missing --name');
const res = await sdk.registerAsServiceProvider(name);
print(`✅ Service provider registered: ${name}`);
print(JSON.stringify(res, null, 2));
break;
}
case 'purchase-name': {
const name = getFlagValue(['--name'], args);
const daysStr = getFlagValue(['--days'], args);
const days = daysStr ? Number(daysStr) : 365;
if (!name) exitWithError('Missing --name');
print(`Purchasing name ${name} for ${days} days ...`);
const res = await sdk.purchaseName(name, days);
print(`✅ Purchased name: ${name} for ${days} days`);
print(JSON.stringify(res, null, 2));
break;
}
case 'get-owner': {
const name = getFlagValue(['--name'], args);
if (!name) exitWithError('Missing --name');
const res = await sdk.getOwner(name);
print(`Owner of ${name}: ${res}`);
break;
}
case 'get-ens-owner': {
const name = getFlagValue(['--name'], args);
if (!name) exitWithError('Missing --name');
const res = await sdk.getEnsOwner(name);
print(`ENS owner of ${name}: ${res}`);
break;
}
case 'get-wrapper-owner': {
const name = getFlagValue(['--name'], args);
if (!name) exitWithError('Missing --name');
const res = await sdk.getWrapperOwner(name);
print(`Wrapper owner of ${name}: ${res}`);
break;
}
case 'register-subdomain': {
const owner = getFlagValue(['--owner'], args);
const label = getFlagValue(['--label'], args);
const name = getFlagValue(['--name'], args);
if (!owner || !label || !name) exitWithError('Missing --owner, --label, or --name');
const res = await sdk.registerSubdomain(owner, label, name);
print(`✅ Subdomain registered: ${label}.${name} → ${owner}`);
print(JSON.stringify(res, null, 2));
break;
}
case 'get-second-level-controller': {
const res = await sdk.getSecondLevelController();
print(`Second level controller: ${res}`);
break;
}
case 'get-second-level-interactor': {
const res = await sdk.getSecondLevelInteractor();
print(`Second level interactor: ${res}`);
break;
}
case 'get-product-registry': {
const name = getFlagValue(['--name'], args);
if (!name) exitWithError('Missing --name');
const res = await sdk.getProductRegistry(name);
print(`Product registry for ${name}:`);
print(JSON.stringify(res, null, 2));
break;
}
case 'get-product-types': {
const name = getFlagValue(['--name'], args);
if (!name) exitWithError('Missing --name');
const res = await sdk.getProductTypes(name);
const count = Array.isArray(res) ? res.length : 0;
print(`Product types for ${name}: ${count}`);
print(JSON.stringify(res, null, 2));
break;
}
case 'get-all-products': {
const name = getFlagValue(['--name'], args);
if (!name) exitWithError('Missing --name');
const res = await sdk.getAllProducts(name);
const count = Array.isArray(res) ? res.length : (res && typeof res === 'object' ? Object.keys(res).length : 0);
print(`All products for ${name}: ${count}`);
print(JSON.stringify(res, null, 2));
break;
}
case 'get-all-product-types': {
const name = getFlagValue(['--name'], args);
if (!name) exitWithError('Missing --name');
const res = await sdk.getAllProductTypesWithMetadata(name);
const count = Array.isArray(res) ? res.length : (res && typeof res === 'object' ? Object.keys(res).length : 0);
print(`All product types with metadata for ${name}: ${count}`);
print(JSON.stringify(res, null, 2));
break;
}
case 'get-products-from-contract': {
const address = getFlagValue(['--address'], args);
if (!address) exitWithError('Missing --address');
const res = await sdk.getProductsFromContract(address);
const count = Array.isArray(res) ? res.length : 0;
print(`Products from contract ${address}: ${count}`);
print(JSON.stringify(res, null, 2));
break;
}
case 'get-nft-contracts': {
const name = getFlagValue(['--name'], args);
if (!name) exitWithError('Missing --name');
const res = await sdk.getNFTContracts(name);
const count = Array.isArray(res) ? res.length : 0;
print(`NFT contracts for ${name}: ${count}`);
print(JSON.stringify(res, null, 2));
break;
}
case 'create-product-type': {
const name = getFlagValue(['--name'], args);
const type = getFlagValue(['--type'], args);
const infoRaw = getFlagValue(['--info'], args);
if (!name || !type || !infoRaw) exitWithError('Missing --name, --type, or --info');
const info = parseJsonInput(infoRaw);
const res = await sdk.createProductType(name, type, info);
print(`✅ Created product type '${type}' for ${name}`);
print(JSON.stringify(res, null, 2));
break;
}
case 'create-product': {
const ens = getFlagValue(['--ens'], args);
const type = getFlagValue(['--type'], args);
const infoRaw = getFlagValue(['--info'], args);
if (!ens || !type || !infoRaw) exitWithError('Missing --ens, --type, or --info');
const info = parseJsonInput(infoRaw);
const res = await sdk.createProduct(ens, info, type);
print(`✅ Created product on ${ens} (type: ${type})`);
print(JSON.stringify(res, null, 2));
break;
}
case 'batch-create-product': {
const ens = getFlagValue(['--ens'], args);
const infoRaw = getFlagValue(['--info'], args);
const typesRaw = getFlagValue(['--types'], args);
if (!ens || !infoRaw || !typesRaw) exitWithError('Missing --ens, --info, or --types');
const productInfos = parseJsonInput(infoRaw);
let productTypes;
try {
// Try JSON first
productTypes = parseJsonInput(typesRaw);
} catch (_) {
// Fallback comma-separated
productTypes = String(typesRaw).split(',').map(s => s.trim()).filter(Boolean);
}
if (!Array.isArray(productInfos) || !Array.isArray(productTypes)) {
exitWithError('--info and --types must be arrays');
}
const res = await sdk.batchCreateProduct(ens, productInfos, productTypes);
print(`✅ Batch created ${productInfos.length} products on ${ens}`);
print(JSON.stringify(res, null, 2));
break;
}
case 'create-provision': {
const name = getFlagValue(['--name'], args);
const identifier = getFlagValue(['--identifier'], args);
const key = getFlagValue(['--key'], args);
const infoRaw = getFlagValue(['--info'], args);
if (!name || !identifier || !key || !infoRaw) {
exitWithError('Missing --name, --identifier, --key, or --info');
}
const info = parseJsonInput(infoRaw);
const res = await sdk.createProvisionAndUpdateResolver(key, info, name, identifier);
print(`✅ Provision created for ${name} [${identifier}] (key: ${key})`);
print(JSON.stringify(res, null, 2));
break;
}
case 'create-record': {
const name = getFlagValue(['--name'], args);
const key = getFlagValue(['--key'], args);
const value = getFlagValue(['--value'], args);
if (!name || !key || typeof value === 'undefined') exitWithError('Missing --name, --key, or --value');
const res = await sdk.createRecord(key, value, name);
print(`✅ Record created for ${name}: ${key}=${value}`);
print(JSON.stringify(res, null, 2));
break;
}
case 'get-data': {
const ens = getFlagValue(['--ens'], args);
if (!ens) exitWithError('Missing --ens');
const res = await sdk.getData(ens);
print(`Data for ${ens}:`);
print(JSON.stringify(res, null, 2));
break;
}
case 'get-provision': {
const name = getFlagValue(['--name'], args);
const identifier = getFlagValue(['--identifier'], args);
if (!name || !identifier) exitWithError('Missing --name or --identifier');
const res = await sdk.getProvision(name, identifier);
print(`Provision for ${name} [${identifier}]:`);
print(JSON.stringify(res, null, 2));
break;
}
case 'get-expiry': {
const name = getFlagValue(['--name'], args);
if (!name) exitWithError('Missing --name');
const res = await sdk.getExpiry(name);
const date = new Date(res * 1000);
print("Expiry date: " + date.toLocaleString());
break;
}
case 'get-resolver': {
const ens = getFlagValue(['--ens'], args);
if (!ens) exitWithError('Missing --ens');
const res = await sdk.getResolver(ens);
if (typeof res === 'string') {
print(`Resolver for ${ens}: ${res}`);
} else {
print(`Resolver for ${ens}:`);
}
print(JSON.stringify(res, null, 2));
break;
}
case 'get-ttl': {
const ens = getFlagValue(['--ens'], args);
if (!ens) exitWithError('Missing --ens');
const res = await sdk.getTTL(ens);
print(`TTL for ${ens}: ${String(res)}`);
break;
}
case 'get-registered-names': {
const address = getFlagValue(['--address'], args);
if (!address) exitWithError('Missing --address');
const res = await sdk.getRegisteredNames(address);
const count = Array.isArray(res) ? res.length : 0;
print(`Registered names for ${address}: ${count}`);
print(JSON.stringify(res, null, 2));
break;
}
case 'get-all-addresses': {
const res = sdk.getAllContractAddresses();
const count = res && typeof res === 'object' ? Object.keys(res).length : 0;
print(`All deployed addresses (${count}):`);
print(JSON.stringify(res, null, 2));
break;
}
case 'get-address': {
const contract = getFlagValue(['--contract'], args);
if (!contract) exitWithError('Missing --contract');
const res = sdk.getContractAddress(contract);
print(`Address for ${contract}: ${String(res || 'not found')}`);
break;
}
default:
usage();
process.exit(1);
}
} catch (err) {
exitWithError(err && err.message ? err.message : String(err));
}
}
main();