Parity pass on the other three language templates. Same guarantees as go/: survive server restart, client restart, half-open TCP, and long outages; rejoin and drain prime-side backlog on reconnect, without the user writing any of this in process.*. python/main.py: - grpc.keepalive_time_ms=10000, keepalive_timeout_ms=3000, keepalive_permit_without_calls=1 on the channel. Half-open TCP is detected within ~13s instead of the OS default ~2h. - Exponential backoff with jitter; max_backoff_seconds config ceiling (default 120). Attempts counter resets after a session runs healthy for 60s so transient restarts don't escalate the delay. - chain_id added as a required config field and sent as the x-chain-id gRPC metadata header (prime rejects streams without it). typescript/src/main.ts: - Same keepalive options on the @grpc/grpc-js client. - Same exponential backoff + jitter logic. - chain_id added to Config + metadata. bash/: - Config + README updated. The bash template uses Python's main.py as its runtime, so the behavioural changes above flow through without a separate main per language. Docs: each README gains a "Durability guarantees" section so contract authors see the invariants without reading the runtime code.
7.0 KiB
Executable File
TypeScript Smart Contract Template
A TypeScript/JavaScript-based smart contract client for Dragonchain Prime that connects via gRPC.
Prerequisites
- Node.js 18 or later
- npm
Quick Start
-
Copy this template to create your smart contract:
cp -r typescript /path/to/my-smart-contract cd /path/to/my-smart-contract -
Install dependencies:
npm install -
Configure your connection by editing
config.yaml:server_address: "your-dragonchain-server:50051" chain_id: "your-chain-public-id" smart_contract_id: "your-smart-contract-id" api_key: "your-api-key" -
Implement your smart contract logic in
src/process.tsby modifying theprocessTransactionfunction. -
Build and run:
npm run build npm start -- --config config.yamlOr run in development mode:
npm run dev -- --config config.yaml
Configuration
| Field | Description | Default |
|---|---|---|
server_address |
gRPC server address | Required |
chain_id |
Public chain id the SC is registered on (sent as x-chain-id metadata) |
Required |
smart_contract_id |
Your smart contract ID | Required |
api_key |
API key for authentication | Required |
use_tls |
Enable TLS encryption | false |
tls_cert_path |
Path to TLS certificate | - |
num_workers |
Concurrent transaction processors | 10 |
reconnect_delay_seconds |
Base delay for exponential backoff between reconnect attempts | 3 |
max_backoff_seconds |
Ceiling for the exponential backoff | 120 |
max_reconnect_attempts |
Max reconnect attempts (0 = infinite, recommended) | 0 |
Durability guarantees (provided by src/main.ts, no work for you)
- Server restart, update, crash, or network blip → the client auto-reconnects and resumes processing. Transactions observed while the stream was down stay queued on the Dragonchain Prime side and are delivered (oldest first) on reconnect.
- Client restart or long outage → when this process comes back up (minutes, hours, months later), it rejoins the stream and prime re-delivers every still-pending transaction that should have invoked it.
- Half-open TCP (silent peer, resumed laptop, corporate NAT dropping idle flows) is detected within ~13 seconds via gRPC keepalive and triggers a reconnect. No dangling ghost streams.
- Reconnect storms are avoided: exponential backoff with jitter means many clients reconnecting after a server restart don't all slam
accept()at the same instant. The timer resets after a stream has been healthy for 60 seconds.
These are invariants of the template — you do not add any of this in src/process.ts.
Implementing Your Smart Contract
Edit the processTransaction function in src/process.ts:
export async function processTransaction(
txJson: string,
envVars: Record<string, string>,
secrets: Record<string, string>
): Promise<ProcessResult> {
// Parse the transaction
const tx: Transaction = JSON.parse(txJson);
// Access transaction data
const txnId = tx.header.txn_id;
const txnType = tx.header.txn_type;
const payload = tx.payload;
// Access environment variables
const scName = envVars["SMART_CONTRACT_NAME"];
const dcId = envVars["DRAGONCHAIN_ID"];
// Access secrets
const mySecret = secrets["SC_SECRET_MY_SECRET"];
// Implement your logic here
const result = {
status: "success",
data: "your result data",
};
return {
data: result,
outputToChain: true, // Set to true to persist result on chain
};
}
Transaction Structure
The Transaction interface in src/process.ts matches the Dragonchain transaction format:
interface Transaction {
version: string;
header: TransactionHeader;
payload: Record<string, unknown>;
}
interface TransactionHeader {
tag: string;
dc_id: string;
txn_id: string;
block_id: string;
txn_type: string;
timestamp: string;
invoker: string;
}
Available Environment Variables
| Variable | Description |
|---|---|
TZ |
Timezone |
ENVIRONMENT |
Deployment environment |
INTERNAL_ID |
Internal identifier |
DRAGONCHAIN_ID |
Dragonchain ID |
DRAGONCHAIN_ENDPOINT |
Dragonchain API endpoint |
SMART_CONTRACT_ID |
This smart contract's ID |
SMART_CONTRACT_NAME |
This smart contract's name |
SC_ENV_* |
Custom environment variables |
Secrets
Secrets are provided in the secrets object with keys prefixed by SC_SECRET_.
Project Structure
.
├── src/
│ ├── main.ts # Client infrastructure (do not modify)
│ └── process.ts # Your smart contract logic (modify this)
├── proto/
│ └── remote_sc.proto # gRPC service definition
├── config.yaml # Configuration file
├── package.json # Node.js dependencies
├── tsconfig.json # TypeScript configuration
├── Makefile # Build commands
└── README.md # This file
File Descriptions
src/process.ts- Contains theprocessTransactionfunction where you implement your smart contract logic. This is the only file you need to modify for most use cases.src/main.ts- Contains the gRPC client infrastructure, connection handling, and worker pool. You typically don't need to modify this file.
NPM Scripts
npm install # Install dependencies
npm run build # Compile TypeScript to JavaScript
npm start # Run the compiled application
npm run dev # Run with ts-node (development)
npm run proto # Generate TypeScript types from proto
npm run clean # Remove build artifacts
npm run lint # Lint the code
npm run format # Format code with prettier
Make Commands
make setup # Install dependencies
make proto # Generate TypeScript types from proto
make build # Build TypeScript
make run # Build and run
make dev # Run in development mode
make clean # Remove build artifacts
make lint # Lint code
make format # Format code
Concurrent Processing
The client uses async/await with a concurrency limit to process multiple transactions simultaneously. The number of concurrent workers is configurable via num_workers in the config file.
Error Handling
- Return errors from the
processTransactionfunction viaProcessResult.errorto report failures - The client automatically handles reconnection on connection failures
- Logs are captured and sent back with the response
Docker
Example Dockerfile:
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
CMD ["node", "dist/main.js", "--config", "config.yaml"]
Using with JavaScript
If you prefer plain JavaScript instead of TypeScript:
- Write your code in
src/process.jsinstead ofsrc/process.ts - Skip the build step and run directly:
node src/main.js --config config.yaml
License
[Your License Here]