Initial Commit
Some checks failed
Build and Test / build (16.x) (push) Failing after 12s
Build and Test / build (18.x) (push) Failing after 6s
Build and Test / build (20.x) (push) Failing after 6s

This commit is contained in:
2025-11-05 15:25:20 -05:00
commit 98ab2e05a7
25 changed files with 1953 additions and 0 deletions

21
src/block.ts Normal file
View File

@@ -0,0 +1,21 @@
/**
* Block module for querying Dragonchain blocks
*/
import { DragonchainClient } from './client';
import { Block } from './types';
export class BlockClient {
private client: DragonchainClient;
constructor(client: DragonchainClient) {
this.client = client;
}
/**
* Gets a block by ID
*/
async get(blockId: string): Promise<Block> {
return this.client.get<Block>(`/api/v1/block/${blockId}`);
}
}

207
src/client.ts Normal file
View File

@@ -0,0 +1,207 @@
/**
* HTTP Client with HMAC-SHA256 authentication for Dragonchain
*/
import * as crypto from 'crypto';
import * as https from 'https';
import * as http from 'http';
import { URL } from 'url';
export interface ClientConfig {
publicId: string;
authKeyId: string;
authKey: string;
baseURL: string;
timeout?: number;
}
export class DragonchainClient {
private readonly publicId: string;
private readonly authKeyId: string;
private readonly authKey: string;
private readonly baseURL: string;
private readonly timeout: number;
constructor(config: ClientConfig) {
this.publicId = config.publicId;
this.authKeyId = config.authKeyId;
this.authKey = config.authKey;
this.baseURL = config.baseURL.replace(/\/$/, ''); // Remove trailing slash
this.timeout = config.timeout || 30000; // Default 30 seconds
}
/**
* Creates HMAC message string for signing
*/
private createHmacMessage(
method: string,
path: string,
timestamp: string,
contentType: string,
body: Buffer
): string {
// Hash the body content
const contentHash = crypto.createHash('sha256').update(body).digest();
const b64Content = contentHash.toString('base64');
// Format: METHOD\nPATH\nPUBLIC_ID\nTIMESTAMP\nCONTENT_TYPE\nBASE64_CONTENT_HASH
return [method.toUpperCase(), path, this.publicId, timestamp, contentType, b64Content].join(
'\n'
);
}
/**
* Creates HMAC signature
*/
private createHmac(message: string): string {
const hmac = crypto.createHmac('sha256', this.authKey);
hmac.update(message);
return hmac.digest('base64');
}
/**
* Generates the Authorization header
*/
private generateAuthHeader(
method: string,
path: string,
timestamp: string,
contentType: string,
body: Buffer
): string {
const message = this.createHmacMessage(method, path, timestamp, contentType, body);
const signature = this.createHmac(message);
return `DC1-HMAC-SHA256 ${this.authKeyId}:${signature}`;
}
/**
* Performs an HTTP request
*/
private async doRequest<T>(
method: string,
path: string,
contentType: string,
body: unknown,
responseType?: 'json' | 'buffer'
): Promise<T> {
let bodyBuffer: Buffer;
// Prepare request body
if (body === null || body === undefined) {
bodyBuffer = Buffer.from('');
} else if (Buffer.isBuffer(body)) {
bodyBuffer = body;
} else if (typeof body === 'string') {
bodyBuffer = Buffer.from(body);
} else {
bodyBuffer = Buffer.from(JSON.stringify(body));
if (!contentType) {
contentType = 'application/json';
}
}
// Generate authentication headers
const timestamp = Math.floor(Date.now() / 1000).toString();
const authHeader = this.generateAuthHeader(method, path, timestamp, contentType, bodyBuffer);
// Parse URL
const fullURL = `${this.baseURL}${path}`;
const parsedURL = new URL(fullURL);
const isHttps = parsedURL.protocol === 'https:';
// Prepare request options
const options: https.RequestOptions = {
hostname: parsedURL.hostname,
port: parsedURL.port || (isHttps ? 443 : 80),
path: parsedURL.pathname + parsedURL.search,
method: method.toUpperCase(),
headers: {
Authorization: authHeader,
Dragonchain: this.publicId,
Timestamp: timestamp,
...(contentType && { 'Content-Type': contentType }),
'Content-Length': bodyBuffer.length,
},
timeout: this.timeout,
};
return new Promise<T>((resolve, reject) => {
const httpModule = isHttps ? https : http;
const req = httpModule.request(options, (res) => {
const chunks: Buffer[] = [];
res.on('data', (chunk: Buffer) => {
chunks.push(chunk);
});
res.on('end', () => {
const responseBody = Buffer.concat(chunks);
// Check for errors
if (res.statusCode && res.statusCode >= 400) {
const errorMessage = responseBody.toString('utf8').trim();
reject(new Error(`API error (status ${res.statusCode}): ${errorMessage}`));
return;
}
// Return response
if (responseType === 'buffer') {
resolve(responseBody as T);
} else if (responseBody.length > 0) {
try {
const parsed = JSON.parse(responseBody.toString('utf8')) as T;
resolve(parsed);
} catch (error) {
reject(new Error(`Failed to parse response: ${(error as Error).message}`));
}
} else {
resolve({} as T);
}
});
});
req.on('error', (error) => {
reject(new Error(`Request failed: ${error.message}`));
});
req.on('timeout', () => {
req.destroy();
reject(new Error(`Request timeout after ${this.timeout}ms`));
});
// Write body and end request
if (bodyBuffer.length > 0) {
req.write(bodyBuffer);
}
req.end();
});
}
/**
* Performs a GET request
*/
public async get<T>(path: string): Promise<T> {
return this.doRequest<T>('GET', path, '', null);
}
/**
* Performs a POST request
*/
public async post<T>(path: string, contentType: string, body: unknown): Promise<T> {
return this.doRequest<T>('POST', path, contentType, body);
}
/**
* Performs a PUT request
*/
public async put<T>(path: string, contentType: string, body: unknown): Promise<T> {
return this.doRequest<T>('PUT', path, contentType, body);
}
/**
* Performs a DELETE request
*/
public async delete<T>(path: string): Promise<T> {
return this.doRequest<T>('DELETE', path, '', null);
}
}

73
src/contract.ts Normal file
View File

@@ -0,0 +1,73 @@
/**
* Contract module for managing Dragonchain smart contracts
*/
import * as fs from 'fs';
import { DragonchainClient } from './client';
import {
ContentType,
SmartContractCreateRequest,
SmartContractUpdateRequest,
SmartContract,
ListResponse,
SuccessResponse,
} from './types';
export class ContractClient {
private client: DragonchainClient;
constructor(client: DragonchainClient) {
this.client = client;
}
/**
* Creates a new smart contract
*/
async create(request: SmartContractCreateRequest): Promise<SmartContract> {
return this.client.post<SmartContract>('/api/v1/contract', ContentType.JSON, request);
}
/**
* Gets a smart contract by ID
*/
async get(contractId: string): Promise<SmartContract> {
return this.client.get<SmartContract>(`/api/v1/contract/${contractId}`);
}
/**
* Lists all smart contracts
*/
async list(): Promise<ListResponse> {
return this.client.get<ListResponse>('/api/v1/contract');
}
/**
* Updates a smart contract
*/
async update(contractId: string, request: SmartContractUpdateRequest): Promise<SuccessResponse> {
return this.client.put<SuccessResponse>(
`/api/v1/contract/${contractId}`,
ContentType.JSON,
request
);
}
/**
* Uploads smart contract code
*/
async upload(contractId: string, filePath: string): Promise<SuccessResponse> {
const fileContent = fs.readFileSync(filePath);
return this.client.put<SuccessResponse>(
`/api/v1/contract/${contractId}/upload`,
ContentType.OCTET_STREAM,
fileContent
);
}
/**
* Deletes a smart contract
*/
async delete(contractId: string): Promise<SuccessResponse> {
return this.client.delete<SuccessResponse>(`/api/v1/contract/${contractId}`);
}
}

109
src/credentials.ts Normal file
View File

@@ -0,0 +1,109 @@
/**
* Credentials module for loading and managing Dragonchain configuration
*/
import * as fs from 'fs';
import * as path from 'path';
import * as yaml from 'js-yaml';
import { homedir } from 'os';
/**
* Configuration for a single Dragonchain
*/
export interface ChainConfig {
name: string;
publicId: string;
authKeyId: string;
authKey: string;
endpoint: string;
}
/**
* Complete configuration structure
*/
export interface Config {
default: string;
chains: ChainConfig[];
}
/**
* Expands file paths with home directory and environment variables
*/
function expandPath(filePath: string): string {
// Expand environment variables
let expanded = filePath.replace(/\$\{([^}]+)\}/g, (_, variable: string) => {
return process.env[variable] || '';
});
// Handle tilde for home directory
if (expanded.startsWith('~/')) {
expanded = path.join(homedir(), expanded.slice(2));
} else if (expanded === '~') {
expanded = homedir();
}
return expanded;
}
/**
* Loads configuration from a YAML file
*/
export function loadConfig(filePath: string): Config {
const expandedPath = expandPath(filePath);
if (!fs.existsSync(expandedPath)) {
throw new Error(`Config file not found: ${expandedPath}`);
}
const fileContent = fs.readFileSync(expandedPath, 'utf8');
const config = yaml.load(fileContent) as Config;
if (!config.chains || !Array.isArray(config.chains)) {
throw new Error('Invalid config: missing or invalid chains array');
}
return config;
}
/**
* Loads configuration from a YAML string
*/
export function loadConfigFromString(yamlContent: string): Config {
const config = yaml.load(yamlContent) as Config;
if (!config.chains || !Array.isArray(config.chains)) {
throw new Error('Invalid config: missing or invalid chains array');
}
return config;
}
/**
* Gets the default chain configuration
*/
export function getDefaultChain(config: Config): ChainConfig | undefined {
return config.chains.find((chain) => chain.publicId === config.default);
}
/**
* Gets a chain configuration by public ID
*/
export function getChainByPublicId(config: Config, publicId: string): ChainConfig | undefined {
return config.chains.find((chain) => chain.publicId === publicId);
}
/**
* Lists all chain names in the configuration
*/
export function listChains(config: Config): string[] {
return config.chains.map((chain) => chain.name);
}
/**
* Saves configuration to a YAML file
*/
export function saveConfig(config: Config, filePath: string): void {
const expandedPath = expandPath(filePath);
const yamlContent = yaml.dump(config);
fs.writeFileSync(expandedPath, yamlContent, 'utf8');
}

77
src/index.ts Normal file
View File

@@ -0,0 +1,77 @@
/**
* Dragonchain SDK for Node.js and TypeScript
*
* Official SDK for interacting with Dragonchain blockchain nodes
*/
import { DragonchainClient, ClientConfig } from './client';
import { TransactionClient } from './transaction';
import { TransactionTypeClient } from './transactionType';
import { ContractClient } from './contract';
import { BlockClient } from './block';
import { SystemClient } from './system';
/**
* Main Dragonchain SDK class
*/
export class DragonchainSDK {
private client: DragonchainClient;
public readonly transaction: TransactionClient;
public readonly transactionType: TransactionTypeClient;
public readonly contract: ContractClient;
public readonly block: BlockClient;
public readonly system: SystemClient;
/**
* Creates a new Dragonchain SDK instance
*
* @param publicId - Your Dragonchain public ID
* @param authKeyId - Your authentication key ID
* @param authKey - Your authentication key
* @param baseURL - The base URL of your Dragonchain node
* @param timeout - Optional request timeout in milliseconds (default: 30000)
*/
constructor(
publicId: string,
authKeyId: string,
authKey: string,
baseURL: string,
timeout?: number
) {
const config: ClientConfig = {
publicId,
authKeyId,
authKey,
baseURL,
timeout,
};
this.client = new DragonchainClient(config);
this.transaction = new TransactionClient(this.client);
this.transactionType = new TransactionTypeClient(this.client);
this.contract = new ContractClient(this.client);
this.block = new BlockClient(this.client);
this.system = new SystemClient(this.client);
}
/**
* Gets the underlying HTTP client
*/
public getClient(): DragonchainClient {
return this.client;
}
}
// Export all types and modules
export * from './types';
export * from './credentials';
export { DragonchainClient, ClientConfig } from './client';
export { TransactionClient } from './transaction';
export { TransactionTypeClient } from './transactionType';
export { ContractClient } from './contract';
export { BlockClient } from './block';
export { SystemClient } from './system';
// Default export
export default DragonchainSDK;

28
src/system.ts Normal file
View File

@@ -0,0 +1,28 @@
/**
* System module for Dragonchain system operations
*/
import { DragonchainClient } from './client';
import { SystemStatus } from './types';
export class SystemClient {
private client: DragonchainClient;
constructor(client: DragonchainClient) {
this.client = client;
}
/**
* Checks system health
*/
async health(): Promise<void> {
await this.client.get<void>('/api/v1/health');
}
/**
* Gets system status
*/
async status(): Promise<SystemStatus> {
return this.client.get<SystemStatus>('/api/v1/status');
}
}

58
src/transaction.ts Normal file
View File

@@ -0,0 +1,58 @@
/**
* Transaction module for managing Dragonchain transactions
*/
import { DragonchainClient } from './client';
import {
ContentType,
TransactionCreateRequest,
TransactionCreateResponse,
TransactionBulkRequest,
TransactionBulkResponse,
Transaction,
ListTransactionsResponse,
} from './types';
export class TransactionClient {
private client: DragonchainClient;
constructor(client: DragonchainClient) {
this.client = client;
}
/**
* Creates a new transaction
*/
async create(request: TransactionCreateRequest): Promise<TransactionCreateResponse> {
return this.client.post<TransactionCreateResponse>(
'/api/v1/transaction',
ContentType.JSON,
request
);
}
/**
* Creates multiple transactions in bulk
*/
async createBulk(request: TransactionBulkRequest): Promise<TransactionBulkResponse> {
return this.client.post<TransactionBulkResponse>(
'/api/v1/transaction/bulk',
ContentType.JSON,
request
);
}
/**
* Gets a transaction by ID
*/
async get(transactionId: string): Promise<Transaction> {
return this.client.get<Transaction>(`/api/v1/transaction/${transactionId}`);
}
/**
* Lists all transactions
*/
async list(): Promise<ListTransactionsResponse> {
return this.client.get<ListTransactionsResponse>('/api/v1/transaction/');
}
}

53
src/transactionType.ts Normal file
View File

@@ -0,0 +1,53 @@
/**
* Transaction Type module for managing Dragonchain transaction types
*/
import { DragonchainClient } from './client';
import {
ContentType,
TransactionTypeCreateRequest,
TransactionTypeCreateResponse,
TransactionType,
TransactionListResponse,
SuccessResponse,
} from './types';
export class TransactionTypeClient {
private client: DragonchainClient;
constructor(client: DragonchainClient) {
this.client = client;
}
/**
* Creates a new transaction type
*/
async create(request: TransactionTypeCreateRequest): Promise<TransactionTypeCreateResponse> {
return this.client.post<TransactionTypeCreateResponse>(
'/api/v1/transaction-type',
ContentType.JSON,
request
);
}
/**
* Gets a transaction type by name
*/
async get(txnType: string): Promise<TransactionType> {
return this.client.get<TransactionType>(`/api/v1/transaction-type/${txnType}`);
}
/**
* Lists all transaction types
*/
async list(): Promise<TransactionListResponse> {
return this.client.get<TransactionListResponse>('/api/v1/transaction-types');
}
/**
* Deletes a transaction type
*/
async delete(txnType: string): Promise<SuccessResponse> {
return this.client.delete<SuccessResponse>(`/api/v1/transaction-type/${txnType}`);
}
}

162
src/types.ts Normal file
View File

@@ -0,0 +1,162 @@
/**
* Type definitions for Dragonchain SDK
*/
export const ContentType = {
JSON: 'application/json',
OCTET_STREAM: 'application/octet-stream',
} as const;
// Transaction Types
export interface TransactionCreateRequest {
version?: string;
txn_type: string;
payload: string | Record<string, unknown>;
tag?: string;
}
export interface TransactionCreateResponse {
transaction_id: string;
}
export interface TransactionBulkRequest {
transactions: TransactionCreateRequest[];
}
export interface TransactionBulkResponse {
transaction_ids: string[];
}
export interface TransactionHeader {
tag: string;
dc_id: string;
txn_id: string;
invoker: string;
block_id: string;
txn_type: string;
timestamp: string;
}
export interface TransactionProof {
full: string;
stripped: string;
}
export interface Transaction {
version: string;
header: TransactionHeader;
proof: TransactionProof;
payload: string;
}
export interface ListTransactionsResponse {
transactions: Transaction[];
}
// Transaction Type Types
export interface TransactionTypeCreateRequest {
version?: string;
txn_type: string;
}
export interface TransactionTypeCreateResponse {
success: boolean;
}
export interface TransactionType {
version: string;
created: number;
modified: number;
txn_type: string;
contract_id?: string;
custom_indexes: unknown[];
active_since_block: string;
}
export interface TransactionListResponse {
transactionTypes: TransactionType[];
}
// Smart Contract Types
export interface SmartContractCreateRequest {
environment: string;
transactionType: string;
executionOrder: string;
environmentVariables?: Record<string, string>;
secrets?: Record<string, string>;
}
export interface SmartContractUpdateRequest {
version?: string;
enabled: boolean;
environmentVariables?: Record<string, string>;
secrets?: Record<string, string>;
}
export interface SmartContractExecutionInfo {
type: string;
executablePath?: string;
executableWorkingDirectory?: string;
executableHash?: string;
}
export interface SmartContract {
id: string;
created: number;
modified: number;
version: string;
environment: string;
transactionType: string;
executionOrder: string;
executionInfo?: SmartContractExecutionInfo;
envVars: Record<string, string>;
secrets: string[];
}
// Block Types
export interface BlockProof {
scheme: string;
proof: string;
nonce?: number;
}
export interface Block {
version: string;
block_id: string;
timestamp: string;
prev_id: string;
prev_proof: string;
transactions: string[];
proof: BlockProof;
}
// System Types
export interface SystemStatus {
id: string;
level: number;
url: string;
hashAlgo: string;
scheme: string;
version: string;
encryptionAlgo: string;
indexingEnabled: boolean;
// Level 5 Node specific fields
funded?: string;
broadcastInterval?: string;
network?: string;
interchainWallet?: string;
}
// Generic Response Types
export interface SuccessResponse {
success: boolean;
}
export interface ErrorResponse {
error: string;
}
export interface ListResponse {
items: unknown[];
total_count: number;
}