strictencode
Version:
Deterministic binary encoding for RGB protocol compliance - JavaScript implementation of StrictEncode
693 lines (583 loc) โข 22.2 kB
JavaScript
/**
* StrictEncode CLI - Command line interface for deterministic binary encoding
*
* Provides interactive encoding/decoding capabilities for the StrictEncode library
* with extensive examples and RGB20 contract support.
*
* @author RGB Community
* @license Apache-2.0
*/
import { StrictEncoder, RGB20Encoder, RGB20_TYPE_IDS } from './index.js';
import { readFileSync } from 'fs';
import { createHash } from 'crypto';
const args = process.argv.slice(2);
const command = args[0];
/**
* Color codes for terminal output
*/
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m',
gray: '\x1b[90m'
};
const c = (color, text) => `${colors[color]}${text}${colors.reset}`;
/**
* Show help information
*/
function showHelp() {
console.log(`
${c('cyan', '๐ง StrictEncode CLI')} - Deterministic binary encoding for RGB protocol
${c('bright', 'USAGE:')}
strictencode <command> [options] [arguments]
${c('bright', 'COMMANDS:')}
${c('green', 'Basic Encoding:')}
u8 <value> Encode 8-bit unsigned integer (0-255)
u16 <value> Encode 16-bit unsigned integer (0-65535)
u32 <value> Encode 32-bit unsigned integer (0-4294967295)
u64 <value> Encode 64-bit unsigned integer (0-18446744073709551615)
bool <true|false> Encode boolean value
string <text> Encode UTF-8 string with LEB128 length
leb128 <value> Encode variable-length integer
${c('green', 'Collection Encoding:')}
vec <type> <val1,val2> Encode vector of values
option <type> <value> Encode optional value (use 'null' for None)
${c('green', 'RGB20 Encoding:')}
asset-spec Encode RGB20 AssetSpec structure
contract-terms Encode RGB20 ContractTerms structure
amount <value> Encode RGB20 token amount
genesis Create RGB20 genesis structure
${c('green', 'Utilities:')}
combine <hex1> <hex2> Combine hex encodings
examples Show comprehensive examples
interactive Start interactive mode
test-vectors Show specification test vectors
${c('green', 'Help:')}
help, -h, --help Show this help message
version, -v, --version Show version information
${c('bright', 'EXAMPLES:')}
${c('gray', '# Basic types')}
strictencode u32 1000000
strictencode string "Hello RGB"
strictencode leb128 200
${c('gray', '# RGB20 asset specification')}
strictencode asset-spec --ticker BTC --name Bitcoin --precision 8
${c('gray', '# Interactive exploration')}
strictencode interactive
strictencode examples
${c('bright', 'OPTIONS:')}
--hex Output as hex string (default)
--bytes Output as byte array
--json Output as JSON object
--verbose Show encoding breakdown
--no-color Disable colored output
For more examples: ${c('blue', 'strictencode examples')}
Interactive mode: ${c('blue', 'strictencode interactive')}
`);
}
/**
* Show version information
*/
function showVersion() {
const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
console.log(`strictencode v${pkg.version}`);
console.log('Deterministic binary encoding for RGB protocol compliance');
}
/**
* Parse command line options
*/
function parseOptions(args) {
const options = {
format: 'hex',
verbose: false,
color: true
};
const filtered = args.filter(arg => {
if (arg === '--hex') { options.format = 'hex'; return false; }
if (arg === '--bytes') { options.format = 'bytes'; return false; }
if (arg === '--json') { options.format = 'json'; return false; }
if (arg === '--verbose') { options.verbose = true; return false; }
if (arg === '--no-color') { options.color = false; return false; }
return true;
});
return { options, args: filtered };
}
/**
* Format output based on options
*/
function formatOutput(encoder, options, description = '') {
const hex = encoder.toHex();
const bytes = encoder.toBytes();
if (!options.color) {
Object.keys(colors).forEach(key => colors[key] = '');
}
if (options.verbose && description) {
console.log(c('yellow', `๐ ${description}`));
}
switch (options.format) {
case 'bytes':
console.log(Array.from(bytes).join(' '));
break;
case 'json':
console.log(JSON.stringify({
hex: hex,
bytes: Array.from(bytes),
length: bytes.length,
description: description
}, null, 2));
break;
default: // hex
console.log(hex);
break;
}
if (options.verbose) {
console.log(c('gray', `Length: ${bytes.length} bytes`));
if (hex.length <= 32) {
console.log(c('gray', `Bytes: [${Array.from(bytes).join(', ')}]`));
}
}
}
/**
* Show comprehensive examples
*/
function showExamples() {
console.log(`
${c('cyan', '๐ StrictEncode Examples')}
${c('bright', '๐ข Basic Integer Encoding:')}
strictencode u8 255 # ${c('green', 'ff')}
strictencode u16 65535 # ${c('green', 'ffff')} (little-endian)
strictencode u32 1000000 # ${c('green', '40420f00')} (little-endian)
strictencode u64 1000000 # ${c('green', '40420f0000000000')} (little-endian)
${c('bright', 'โ
Boolean Encoding:')}
strictencode bool true # ${c('green', '01')}
strictencode bool false # ${c('green', '00')}
${c('bright', '๐ค String Encoding:')}
strictencode string "RGB" # ${c('green', '03524742')} (length + UTF-8)
strictencode string "Hello" # ${c('green', '0548656c6c6f')}
strictencode string "" # ${c('green', '00')} (empty string)
${c('bright', '๐ LEB128 Variable-Length Encoding:')}
strictencode leb128 7 # ${c('green', '07')} (single byte)
strictencode leb128 127 # ${c('green', '7f')} (max single byte)
strictencode leb128 128 # ${c('green', '8001')} (multi-byte)
strictencode leb128 200 # ${c('green', 'c801')} (multi-byte)
${c('bright', '๐ฆ Collection Encoding:')}
strictencode vec u8 1,2,3 # ${c('green', '03010203')} (length + items)
strictencode option string "test" # ${c('green', '010474657374')} (Some)
strictencode option string null # ${c('green', '00')} (None)
${c('bright', '๐ RGB20 Asset Specification:')}
strictencode asset-spec --ticker NIATCKR --name "NIA asset name" --precision 8
# ${c('green', '074e494154434b520e4e4941206173736574206e616d650800')}
${c('bright', '๐ RGB20 Contract Terms:')}
strictencode contract-terms --text "NIA terms"
# ${c('green', '094e4941207465726d7300')}
${c('bright', '๐ฐ RGB20 Token Amount:')}
strictencode amount 1000000 # ${c('green', '40420f0000000000')} (u64 little-endian)
${c('bright', '๐๏ธ RGB20 Complete Genesis:')}
strictencode genesis \\
--ticker TESTCOIN \\
--name "Test Coin" \\
--precision 8 \\
--terms "Test contract" \\
--amount 21000000 \\
--utxo "abc123...def:0"
${c('bright', '๐ง Utility Commands:')}
strictencode combine 03524742 0548656c6c6f # Combine hex strings
strictencode test-vectors # Show specification test cases
strictencode interactive # Interactive exploration mode
${c('bright', 'โก Advanced Options:')}
strictencode u32 1000000 --verbose # Show encoding breakdown
strictencode string "test" --bytes # Output as byte array
strictencode amount 1000000 --json # Output as JSON
strictencode examples --no-color # Disable colors
${c('bright', '๐ฏ Real-World RGB20 Example:')}
${c('gray', '# Create a complete RGB20 token')}
strictencode genesis \\
--ticker BTC \\
--name "Bitcoin" \\
--precision 8 \\
--terms "Peer-to-peer electronic cash system" \\
--amount 2100000000000000 \\
--utxo "6a12c58f92d73cd8a685c55b3f0e7d5e2b4a1c23456789abcdef0123456789ab:0"
${c('bright', '๐งช Specification Test Vectors:')}
strictencode test-vectors # Shows all test cases from specification
Try: ${c('blue', 'strictencode interactive')} for hands-on exploration!
`);
}
/**
* Show specification test vectors
*/
function showTestVectors() {
console.log(`
${c('cyan', '๐งช StrictEncode Specification Test Vectors')}
${c('bright', 'From STRICT-ENCODE-SPECIFICATION.md:')}
${c('yellow', 'Basic Types:')}
u8(255) โ ff
u16(65535) โ ffff
u32(1000000) โ 40420f00
u64(1000000) โ 40420f0000000000
bool(true) โ 01
bool(false) โ 00
${c('yellow', 'LEB128 Encoding:')}
leb128(7) โ 07
leb128(127) โ 7f
leb128(128) โ 8001
leb128(200) โ c801
${c('yellow', 'String Encoding:')}
"RGB" โ 03524742
"NIATCKR" โ 074e494154434b52
"" โ 00
${c('yellow', 'RGB20 Test Case:')}
AssetSpec {
ticker: "NIATCKR",
name: "NIA asset name",
precision: 8,
details: null
}
โ 074e494154434b520e4e4941206173736574206e616d650800
ContractTerms {
text: "NIA terms",
media: null
}
โ 094e4941207465726d7300
Amount(1000000)
โ 40420f0000000000
${c('bright', 'Validate any test vector:')}
strictencode string "NIATCKR" # Should output: 074e494154434b52
strictencode leb128 200 # Should output: c801
strictencode u32 1000000 # Should output: 40420f00
`);
}
/**
* Interactive mode
*/
async function interactiveMode() {
console.log(`
${c('cyan', '๐ฎ StrictEncode Interactive Mode')}
Type commands to explore encoding. Examples:
โข ${c('green', 'u32 1000000')}
โข ${c('green', 'string "Hello RGB"')}
โข ${c('green', 'asset-spec --ticker BTC --name Bitcoin --precision 8')}
โข ${c('green', 'examples')} - show all examples
โข ${c('green', 'help')} - show help
โข ${c('green', 'exit')} - quit
`);
const readline = await import('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: c('blue', 'strictencode> ')
});
rl.prompt();
rl.on('line', (line) => {
const input = line.trim();
if (!input) {
rl.prompt();
return;
}
if (input === 'exit' || input === 'quit') {
console.log(c('yellow', 'Goodbye! ๐'));
rl.close();
return;
}
try {
const args = input.split(' ').filter(Boolean);
processCommand(args[0], args.slice(1));
} catch (error) {
console.log(c('red', `Error: ${error.message}`));
}
console.log();
rl.prompt();
});
rl.on('close', () => {
process.exit(0);
});
}
/**
* Process individual commands
*/
function processCommand(cmd, args) {
const { options, args: filteredArgs } = parseOptions(args);
const encoder = new StrictEncoder();
let description = '';
try {
switch (cmd) {
case 'u8':
if (!filteredArgs[0]) throw new Error('u8 requires a value (0-255)');
const u8Val = parseInt(filteredArgs[0]);
encoder.encodeU8(u8Val);
description = `u8(${u8Val})`;
break;
case 'u16':
if (!filteredArgs[0]) throw new Error('u16 requires a value (0-65535)');
const u16Val = parseInt(filteredArgs[0]);
encoder.encodeU16(u16Val);
description = `u16(${u16Val}) little-endian`;
break;
case 'u32':
if (!filteredArgs[0]) throw new Error('u32 requires a value (0-4294967295)');
const u32Val = parseInt(filteredArgs[0]);
encoder.encodeU32(u32Val);
description = `u32(${u32Val}) little-endian`;
break;
case 'u64':
if (!filteredArgs[0]) throw new Error('u64 requires a value');
const u64Val = filteredArgs[0];
encoder.encodeU64(u64Val);
description = `u64(${u64Val}) little-endian`;
break;
case 'bool':
if (!filteredArgs[0]) throw new Error('bool requires true or false');
const boolVal = filteredArgs[0] === 'true';
encoder.encodeBool(boolVal);
description = `bool(${boolVal})`;
break;
case 'string':
if (!filteredArgs[0]) throw new Error('string requires text');
const strVal = filteredArgs.join(' ');
encoder.encodeString(strVal);
description = `string("${strVal}") with LEB128 length`;
break;
case 'leb128':
if (!filteredArgs[0]) throw new Error('leb128 requires a value');
const lebVal = parseInt(filteredArgs[0]);
encoder.encodeLeb128(lebVal);
description = `leb128(${lebVal}) variable-length`;
break;
case 'vec':
if (filteredArgs.length < 2) throw new Error('vec requires type and values');
const vecType = filteredArgs[0];
const vecValues = filteredArgs[1].split(',');
encoder.encodeVec(vecValues, function(item) {
switch (vecType) {
case 'u8': this.encodeU8(parseInt(item)); break;
case 'string': this.encodeString(item); break;
default: throw new Error(`Unsupported vec type: ${vecType}`);
}
});
description = `Vec<${vecType}>[${vecValues.join(',')}]`;
break;
case 'option':
if (filteredArgs.length < 2) throw new Error('option requires type and value');
const optType = filteredArgs[0];
const optValue = filteredArgs[1] === 'null' ? null : filteredArgs.slice(1).join(' ');
encoder.encodeOption(optValue, function(value) {
switch (optType) {
case 'string': this.encodeString(value); break;
case 'u32': this.encodeU32(parseInt(value)); break;
default: throw new Error(`Unsupported option type: ${optType}`);
}
});
description = `Option<${optType}>(${optValue === null ? 'None' : optValue})`;
break;
case 'asset-spec':
return processAssetSpec(filteredArgs, options);
case 'contract-terms':
return processContractTerms(filteredArgs, options);
case 'amount':
if (!filteredArgs[0]) throw new Error('amount requires a value');
const amountHex = RGB20Encoder.encodeAmount(filteredArgs[0]);
console.log(amountHex);
if (options.verbose) {
console.log(c('gray', `RGB20 Amount(${filteredArgs[0]}) as u64 little-endian`));
}
return;
case 'genesis':
return processGenesis(filteredArgs, options);
case 'combine':
if (filteredArgs.length < 2) throw new Error('combine requires at least 2 hex strings');
const combined = filteredArgs.join('');
console.log(combined);
if (options.verbose) {
console.log(c('gray', `Combined ${filteredArgs.length} hex strings`));
}
return;
case 'examples':
showExamples();
return;
case 'test-vectors':
showTestVectors();
return;
case 'interactive':
interactiveMode();
return;
case 'help':
case '-h':
case '--help':
showHelp();
return;
case 'version':
case '-v':
case '--version':
showVersion();
return;
default:
throw new Error(`Unknown command: ${cmd}. Use 'help' for available commands.`);
}
formatOutput(encoder, options, description);
} catch (error) {
console.log(c('red', `Error: ${error.message}`));
if (error.message.includes('requires')) {
console.log(c('gray', `Try: strictencode help`));
}
}
}
/**
* Process asset-spec command
*/
function processAssetSpec(args, options) {
const spec = {
ticker: null,
name: null,
precision: 8,
details: null
};
for (let i = 0; i < args.length; i++) {
if (args[i] === '--ticker' && args[i + 1]) {
spec.ticker = args[i + 1];
i++;
} else if (args[i] === '--name' && args[i + 1]) {
spec.name = args[i + 1];
i++;
} else if (args[i] === '--precision' && args[i + 1]) {
spec.precision = parseInt(args[i + 1]);
i++;
} else if (args[i] === '--details' && args[i + 1]) {
spec.details = args[i + 1];
i++;
}
}
if (!spec.ticker) spec.ticker = 'DEMO';
if (!spec.name) spec.name = 'Demo Token';
const encoded = RGB20Encoder.encodeAssetSpec(spec);
console.log(encoded);
if (options.verbose) {
console.log(c('yellow', '๐ RGB20 AssetSpec breakdown:'));
console.log(c('gray', ` Ticker: "${spec.ticker}"`));
console.log(c('gray', ` Name: "${spec.name}"`));
console.log(c('gray', ` Precision: ${spec.precision}`));
console.log(c('gray', ` Details: ${spec.details || 'None'}`));
}
}
/**
* Process contract-terms command
*/
function processContractTerms(args, options) {
const terms = {
text: null,
media: null
};
for (let i = 0; i < args.length; i++) {
if (args[i] === '--text' && args[i + 1]) {
terms.text = args[i + 1];
i++;
} else if (args[i] === '--media' && args[i + 1]) {
terms.media = args[i + 1];
i++;
}
}
if (!terms.text) terms.text = 'Demo contract terms';
const encoded = RGB20Encoder.encodeContractTerms(terms);
console.log(encoded);
if (options.verbose) {
console.log(c('yellow', '๐ RGB20 ContractTerms breakdown:'));
console.log(c('gray', ` Text: "${terms.text}"`));
console.log(c('gray', ` Media: ${terms.media || 'None'}`));
}
}
/**
* Process genesis command
*/
function processGenesis(args, options) {
const config = {
ticker: 'DEMO',
name: 'Demo Token',
precision: 8,
terms: 'Demo contract terms',
amount: 1000000,
utxo: 'abc123def456789abc123def456789abc123def456789abc123def456789:0'
};
for (let i = 0; i < args.length; i++) {
if (args[i] === '--ticker' && args[i + 1]) {
config.ticker = args[i + 1];
i++;
} else if (args[i] === '--name' && args[i + 1]) {
config.name = args[i + 1];
i++;
} else if (args[i] === '--precision' && args[i + 1]) {
config.precision = parseInt(args[i + 1]);
i++;
} else if (args[i] === '--terms' && args[i + 1]) {
config.terms = args[i + 1];
i++;
} else if (args[i] === '--amount' && args[i + 1]) {
config.amount = args[i + 1];
i++;
} else if (args[i] === '--utxo' && args[i + 1]) {
config.utxo = args[i + 1];
i++;
}
}
const genesis = RGB20Encoder.createGenesis({
assetSpec: {
ticker: config.ticker,
name: config.name,
precision: config.precision,
details: null
},
contractTerms: {
text: config.terms,
media: null
},
amount: config.amount,
utxo: config.utxo
});
if (options.format === 'json') {
console.log(JSON.stringify(genesis, null, 2));
} else {
// Create contract ID hash
const serialized = JSON.stringify(genesis, Object.keys(genesis).sort());
const contractId = createHash('sha256').update(serialized).digest('hex');
console.log(c('bright', '๐๏ธ RGB20 Genesis Structure:'));
console.log(JSON.stringify(genesis, null, 2));
console.log();
console.log(c('bright', '๐ Contract ID (SHA-256):'));
console.log(contractId);
if (options.verbose) {
console.log();
console.log(c('yellow', '๐ Components:'));
console.log(c('gray', ` AssetSpec: ${genesis.global_state[RGB20_TYPE_IDS.ASSET_SPEC]}`));
console.log(c('gray', ` ContractTerms: ${genesis.global_state[RGB20_TYPE_IDS.CONTRACT_TERMS]}`));
console.log(c('gray', ` Amount: ${genesis.global_state[RGB20_TYPE_IDS.AMOUNT]}`));
}
}
}
/**
* Main entry point
*/
function main() {
if (!command || command === 'help' || command === '-h' || command === '--help') {
showHelp();
return;
}
if (command === 'version' || command === '-v' || command === '--version') {
showVersion();
return;
}
processCommand(command, args.slice(1));
}
// Handle uncaught errors gracefully
process.on('uncaughtException', (error) => {
console.error(c('red', `Fatal error: ${error.message}`));
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error(c('red', `Unhandled rejection: ${reason}`));
process.exit(1);
});
main();