Initial commit: smart contract templates for bash, go, python, and typescript

This commit is contained in:
2026-03-17 19:59:47 -04:00
commit 0634e66469
35 changed files with 3794 additions and 0 deletions

57
.gitignore vendored Executable file
View File

@@ -0,0 +1,57 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual environments
venv/
ENV/
env/
.venv/
# Node
node_modules/
# Go
smart-contract
# Generated proto files
*_pb2.py
*_pb2_grpc.py
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# Claude
.claude/
# Test config (contains credentials)
test-config.yaml
# OS
.DS_Store
Thumbs.db
# Logs
*.log

197
README.md Executable file
View File

@@ -0,0 +1,197 @@
# Dragonchain Smart Contract Templates
This repository contains templates for creating smart contract clients that connect to Dragonchain Prime via gRPC.
## Available Templates
| Template | Language | Directory |
|----------|----------|-----------|
| Go | Go 1.21+ | [go/](./go/) |
| Python | Python 3.10+ | [python/](./python/) |
| TypeScript | Node.js 18+ | [typescript/](./typescript/) |
## How It Works
Smart contracts connect to the Dragonchain Prime server using a bi-directional gRPC stream. The server sends transaction requests to your smart contract, which processes them and sends back responses.
```
┌─────────────────┐ ┌─────────────────────┐
│ │ SmartContractRequest │ │
│ Dragonchain │ ──────────────────────► │ Smart Contract │
│ Prime Server │ │ Client │
│ │ ◄────────────────────── │ │
│ │ SmartContractResponse │ │
└─────────────────┘ └─────────────────────┘
```
### Request Flow
1. Your client connects to the server with API key and smart contract ID
2. The server sends `SmartContractRequest` messages containing:
- `transaction_id` - Unique identifier for correlation
- `transaction_json` - The transaction data to process
- `env_vars` - Environment variables
- `secrets` - Secret values (API keys, credentials)
3. Your client processes the transaction and sends back `SmartContractResponse`:
- `transaction_id` - Same ID from the request
- `result_json` - Your processing result as JSON
- `logs` - Any logs captured during execution
- `output_to_chain` - Whether to persist the result
- `error` - Error message if processing failed
## Quick Start
### 1. Choose a Template
Pick the template for your preferred language:
```bash
# For Go
cp -r go /path/to/my-smart-contract
# For Python
cp -r python /path/to/my-smart-contract
# For TypeScript/JavaScript
cp -r typescript /path/to/my-smart-contract
```
### 2. Configure
Edit `config.yaml` with your credentials:
```yaml
server_address: "your-server:50051"
smart_contract_id: "your-smart-contract-id"
api_key: "your-api-key"
```
### 3. Implement Your Logic
Each template has a `Process` (Go), `process` (Python/TypeScript) function. Implement your business logic there:
**Go** (`cmd/main.go`):
```go
func Process(ctx context.Context, txJSON string, envVars, secrets map[string]string) ProcessResult {
// Your logic here
return ProcessResult{Data: result, OutputToChain: true}
}
```
**Python** (`main.py`):
```python
def process(tx_json: str, env_vars: dict, secrets: dict) -> ProcessResult:
# Your logic here
return ProcessResult(data=result, output_to_chain=True)
```
**TypeScript** (`src/main.ts`):
```typescript
async function process(txJson: string, envVars: Record<string, string>, secrets: Record<string, string>): Promise<ProcessResult> {
// Your logic here
return { data: result, outputToChain: true };
}
```
### 4. Build and Run
Each template includes a Makefile:
```bash
# Install dependencies
make setup
# Generate proto code (Go and Python only)
make proto
# Build
make build
# Run
make run
```
## Configuration Options
| Option | Description | Default |
|--------|-------------|---------|
| `server_address` | gRPC server address (host:port) | 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` | Reconnection delay | `5` |
| `max_reconnect_attempts` | Max retries (0 = infinite) | `0` |
## Environment Variables
Your smart contract receives these 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 passed separately from environment variables and are prefixed with `SC_SECRET_`. Access them via the `secrets` parameter in your process function.
## Concurrent Processing
All templates support concurrent transaction processing. Configure the number of workers with `num_workers` in your config file. Each incoming request is processed in parallel up to the worker limit.
## Reconnection
All templates include automatic reconnection logic. If the connection is lost, the client will attempt to reconnect with exponential backoff based on `reconnect_delay_seconds`. Set `max_reconnect_attempts` to limit retry attempts (0 = infinite).
## Docker Examples
Each template README includes a Docker example. General pattern:
```dockerfile
# Build stage
FROM <base-image> AS builder
COPY . .
RUN <build-commands>
# Runtime stage
FROM <runtime-image>
COPY --from=builder <artifacts> .
CMD [<run-command>]
```
## Development
### Prerequisites
**Go:**
- Go 1.21+
- protoc
- protoc-gen-go, protoc-gen-go-grpc
**Python:**
- Python 3.10+
- pip
**TypeScript:**
- Node.js 18+
- npm
### Testing
Each template supports testing. Add tests and run:
```bash
make test
```
## License
[Your License Here]

27
bash/.gitignore vendored Normal file
View File

@@ -0,0 +1,27 @@
# Python (infrastructure runtime)
__pycache__/
*.py[cod]
*$py.class
# Virtual environments
venv/
ENV/
env/
.venv/
# Generated proto files
*_pb2.py
*_pb2_grpc.py
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# Test config (contains credentials)
test-config.yaml
# Logs
*.log

53
bash/Makefile Normal file
View File

@@ -0,0 +1,53 @@
.PHONY: proto run clean setup venv test deps check
# Generate Python code from proto files (for the gRPC infrastructure)
proto:
python -m grpc_tools.protoc \
-I./proto \
--python_out=. \
--grpc_python_out=. \
proto/remote_sc.proto
# Create virtual environment and install dependencies
setup: venv
. venv/bin/activate && pip install -r requirements.txt
@echo ""
@echo "Setup complete. Activate the virtual environment with:"
@echo " source venv/bin/activate"
# Create virtual environment
venv:
python3 -m venv venv
# Run the smart contract
run:
python main.py --config config.yaml
# Clean generated files
clean:
rm -f *_pb2.py *_pb2_grpc.py
rm -rf __pycache__
rm -rf venv
# Run tests
test:
bash -n process.sh
@echo "Syntax check passed"
@echo "Running process.sh with sample transaction..."
echo '{"version":"1","header":{"tag":"test","dc_id":"test-dc","txn_id":"test-txn-123","block_id":"1","txn_type":"test","timestamp":"2024-01-01T00:00:00Z","invoker":"test-user"},"payload":{"action":"test","value":42}}' | \
bash process.sh "$$(cat /dev/stdin)" 2>/dev/null && echo "Test passed" || echo "Test failed"
# Install dependencies (without venv)
deps:
pip install -r requirements.txt
# Check that required tools are available
check:
@command -v python3 >/dev/null 2>&1 || { echo "python3 is required but not installed"; exit 1; }
@command -v bash >/dev/null 2>&1 || { echo "bash is required but not installed"; exit 1; }
@command -v jq >/dev/null 2>&1 || { echo "jq is required but not installed"; exit 1; }
@echo "All required tools are available"
# Format process.sh with shfmt (if available)
format:
@command -v shfmt >/dev/null 2>&1 && shfmt -w process.sh || echo "shfmt not installed, skipping format"

243
bash/README.md Normal file
View File

@@ -0,0 +1,243 @@
# Bash Smart Contract Template
A Bash-based smart contract client for Dragonchain Prime that connects via gRPC.
This template uses a thin Python gRPC infrastructure layer to handle the network protocol, while your smart contract logic lives entirely in `process.sh`.
## Prerequisites
- Bash 4.0+
- Python 3.10+ (for gRPC infrastructure)
- pip
- jq (for JSON processing in bash)
## Quick Start
1. **Copy this template** to create your smart contract:
```bash
cp -r bash /path/to/my-smart-contract
cd /path/to/my-smart-contract
```
2. **Set up the environment**:
```bash
make setup
source venv/bin/activate
```
Or without make:
```bash
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```
3. **Generate the protobuf code**:
```bash
make proto
```
4. **Configure your connection** by editing `config.yaml`:
```yaml
server_address: "your-dragonchain-server:50051"
smart_contract_id: "your-smart-contract-id"
api_key: "your-api-key"
```
5. **Implement your smart contract logic** in `process.sh`.
6. **Run**:
```bash
python main.py --config config.yaml
```
## Configuration
| Field | Description | Default |
|-------|-------------|---------|
| `server_address` | gRPC server address | 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` | Delay between reconnection attempts | `5` |
| `max_reconnect_attempts` | Max reconnect attempts (0 = infinite) | `0` |
## Implementing Your Smart Contract
Edit `process.sh`. The script receives the transaction JSON as its first argument (`$1`) and must output a JSON result to stdout.
### Interface
**Input:**
- `$1` - Transaction JSON string
- Environment variables - Server env vars and secrets are exported
**Output (stdout):**
```json
{
"data": { "your": "result" },
"output_to_chain": true,
"error": ""
}
```
**Logs (stderr):** Anything written to stderr is captured and returned as logs.
**Exit code:** 0 = success, non-zero = error (stderr used as error message).
### Example
```bash
#!/usr/bin/env bash
set -euo pipefail
TX_JSON="$1"
# Parse transaction fields with jq
TXN_ID=$(echo "$TX_JSON" | jq -r '.header.txn_id')
TXN_TYPE=$(echo "$TX_JSON" | jq -r '.header.txn_type')
PAYLOAD=$(echo "$TX_JSON" | jq -c '.payload')
# Access environment variables
SC_NAME="${SMART_CONTRACT_NAME:-}"
DC_ID="${DRAGONCHAIN_ID:-}"
# Access secrets
MY_SECRET="${SC_SECRET_MY_SECRET:-}"
# Log to stderr
echo "Processing transaction $TXN_ID" >&2
# Process based on payload action
ACTION=$(echo "$TX_JSON" | jq -r '.payload.action // empty')
case "$ACTION" in
create)
RESULT='{"status": "created"}'
;;
update)
RESULT='{"status": "updated"}'
;;
*)
RESULT='{"status": "unknown"}'
;;
esac
# Output result as JSON
jq -n --argjson result "$RESULT" '{
"data": $result,
"output_to_chain": true,
"error": ""
}'
```
### Transaction Structure
The transaction JSON passed to your script has this format:
```json
{
"version": "1",
"header": {
"tag": "my-tag",
"dc_id": "dragonchain-id",
"txn_id": "transaction-id",
"block_id": "block-id",
"txn_type": "my-type",
"timestamp": "2024-01-01T00:00:00Z",
"invoker": "user-id"
},
"payload": {
"your": "custom data"
}
}
```
### 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 exported as environment variables with keys prefixed by `SC_SECRET_`.
## Project Structure
```
.
├── main.py # gRPC infrastructure (do not modify)
├── process.sh # Your smart contract logic (modify this)
├── proto/
│ └── remote_sc.proto # gRPC service definition
├── config.yaml # Configuration file
├── requirements.txt # Python dependencies (for infrastructure)
├── Makefile # Build commands
└── README.md # This file
```
### File Descriptions
- **`process.sh`** - Your smart contract logic. This is the only file you need to modify for most use cases.
- **`main.py`** - gRPC client infrastructure that invokes `process.sh` for each transaction. You typically don't need to modify this file.
## Make Commands
```bash
make setup # Create venv and install dependencies
make proto # Generate Python code from proto files
make run # Run with default config
make test # Syntax check and sample run of process.sh
make clean # Remove generated files and venv
make deps # Install dependencies (no venv)
make check # Verify required tools (python3, bash, jq)
make format # Format process.sh with shfmt (if installed)
```
## Concurrent Processing
The client uses a thread pool to process multiple transactions concurrently. Each worker invokes a separate instance of `process.sh`. The number of workers is configurable via `num_workers` in the config file.
## Error Handling
- Return errors by setting the `error` field in your JSON output, or exit with a non-zero code
- Anything written to stderr is captured as logs
- The client automatically handles reconnection on connection failures
## Docker
Example `Dockerfile`:
```dockerfile
FROM python:3.11-slim
RUN apt-get update && apt-get install -y --no-install-recommends jq bash && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN chmod +x process.sh
RUN python -m grpc_tools.protoc \
-I./proto \
--python_out=. \
--grpc_python_out=. \
proto/remote_sc.proto
CMD ["python", "main.py", "--config", "config.yaml"]
```
## License
[Your License Here]

24
bash/config.yaml Normal file
View File

@@ -0,0 +1,24 @@
# Smart Contract Client Configuration
# Copy this file and fill in your values
# The gRPC server address to connect to
server_address: "localhost:50051"
# Your smart contract ID (provided by Dragonchain)
smart_contract_id: "your-smart-contract-id"
# API key for authentication (provided by Dragonchain)
api_key: "your-api-key"
# Whether to use TLS for the connection
use_tls: false
# Path to TLS certificate (required if use_tls is true)
# tls_cert_path: "/path/to/cert.pem"
# Number of worker threads for processing transactions concurrently
num_workers: 10
# Reconnect settings
reconnect_delay_seconds: 5
max_reconnect_attempts: 0 # 0 = infinite retries

89
bash/process.sh Executable file
View File

@@ -0,0 +1,89 @@
#!/usr/bin/env bash
# =============================================================================
# Smart Contract Processing Logic
#
# This file contains the transaction processing logic for your smart contract.
# Modify this script to implement your business logic.
#
# Input:
# $1 - Transaction JSON string
# Environment - Server env vars and secrets are exported as environment variables
#
# Output (stdout):
# A JSON object with the following fields:
# {
# "data": { ... }, // Your result data (any JSON object)
# "output_to_chain": true, // Whether to persist the result on chain
# "error": "" // Error message (empty string if no error)
# }
#
# Logs (stderr):
# Anything written to stderr is captured and sent back as logs.
#
# Exit code:
# 0 = success (stdout is parsed as JSON result)
# non-zero = error (stderr is used as the error message)
# =============================================================================
set -euo pipefail
# Transaction JSON is passed as the first argument
TX_JSON="$1"
# =============================================================================
# TODO: Implement your smart contract logic here
# =============================================================================
#
# Parse transaction fields using jq:
# TXN_ID=$(echo "$TX_JSON" | jq -r '.header.txn_id')
# TXN_TYPE=$(echo "$TX_JSON" | jq -r '.header.txn_type')
# PAYLOAD=$(echo "$TX_JSON" | jq '.payload')
#
# Access environment variables (set by the server):
# echo "Smart Contract Name: $SMART_CONTRACT_NAME" >&2
# echo "Dragonchain ID: $DRAGONCHAIN_ID" >&2
#
# Access secrets (set by the server):
# MY_SECRET="${SC_SECRET_MY_SECRET:-}"
#
# Process based on payload action:
# ACTION=$(echo "$TX_JSON" | jq -r '.payload.action // empty')
# case "$ACTION" in
# create)
# # Handle create operation
# ;;
# update)
# # Handle update operation
# ;;
# *)
# echo '{"data": null, "output_to_chain": false, "error": "Unknown action: '"$ACTION"'"}'
# exit 0
# ;;
# esac
#
# Log messages to stderr (captured and sent back with response):
# echo "Processing transaction..." >&2
# Parse transaction data
TXN_ID=$(echo "$TX_JSON" | jq -r '.header.txn_id')
TXN_TYPE=$(echo "$TX_JSON" | jq -r '.header.txn_type')
PAYLOAD=$(echo "$TX_JSON" | jq -c '.payload')
echo "Processing transaction $TXN_ID (type: $TXN_TYPE)" >&2
# Default implementation: echo back the transaction
jq -n \
--arg txn_id "$TXN_ID" \
--arg txn_type "$TXN_TYPE" \
--argjson payload "$PAYLOAD" \
'{
"data": {
"status": "processed",
"transaction_id": $txn_id,
"txn_type": $txn_type,
"payload": $payload,
"message": "Transaction processed successfully"
},
"output_to_chain": true,
"error": ""
}'

View File

@@ -0,0 +1,48 @@
syntax = "proto3";
package remote_sc;
// SmartContractService defines the bi-directional streaming service for remote
// smart contract execution. External workers connect to this service to receive
// execution tasks and send back results.
service SmartContractService {
// Run establishes a bi-directional stream. The server sends SmartContractRequest
// messages to the client for execution, and the client sends back
// SmartContractResponse messages with results.
rpc Run(stream SmartContractResponse) returns (stream SmartContractRequest);
}
// SmartContractRequest is sent from the server to the connected worker
// to request execution of a smart contract.
message SmartContractRequest {
// Unique identifier for this execution request, used to correlate responses
string transaction_id = 1;
// Full transaction JSON to be processed by the smart contract
string transaction_json = 2;
// Environment variables to set for the smart contract execution
map<string, string> env_vars = 3;
// Secrets to be made available to the smart contract
map<string, string> secrets = 4;
}
// SmartContractResponse is sent from the worker back to the server
// with the results of smart contract execution.
message SmartContractResponse {
// The transaction_id from the original request, for correlation
string transaction_id = 1;
// The result data from the smart contract execution as JSON
string result_json = 2;
// Logs captured during smart contract execution
string logs = 3;
// Whether to persist the output to the chain
bool output_to_chain = 4;
// Error message if execution failed
string error = 5;
}

22
go/.gitignore vendored Executable file
View File

@@ -0,0 +1,22 @@
# Binary
smart-contract
# Generated proto files
proto/*.pb.go
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# Test config (contains credentials)
test-config.yaml
# OS
.DS_Store
Thumbs.db
# Logs
*.log

45
go/Makefile Executable file
View File

@@ -0,0 +1,45 @@
SHELL := /bin/bash
.PHONY: proto build run clean tools test deps fix-package-name
# Generate Go code from proto files
proto:
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
proto/remote_sc.proto
# Build the smart contract binary
build:
go build -o smart-contract .
# Run the smart contract
run:
go run . -config config.yaml
# Clean build artifacts
clean:
rm -f smart-contract
# Install required tools
tools:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
# Run tests
test:
go test -v ./...
# Download dependencies
deps:
go mod download
go mod tidy
# Fix package name based on current working directory
fix-package-name:
@NEW_MODULE=$$(go list -m 2>/dev/null || echo "$$(basename $$(dirname $$(pwd)))/$$(basename $$(pwd))"); \
if [ -z "$$NEW_MODULE" ]; then \
echo "Could not determine module name. Please run 'go mod init <module-name>' first."; \
exit 1; \
fi; \
echo "Updating package name to: $$NEW_MODULE"; \
find . -type f -name "*.go" ! -path "./proto/*" -exec sed -i 's|github.com/your-org/smart-contract|'$$NEW_MODULE'|g' {} +; \
echo "Done. Run 'make proto' to generate proto files, then 'make build' to build."

192
go/README.md Executable file
View File

@@ -0,0 +1,192 @@
# Go Smart Contract Template
A Go-based smart contract client for Dragonchain Prime that connects via gRPC.
## Prerequisites
- Go 1.21 or later
- Protocol Buffers compiler (`protoc`)
- Go protobuf plugins
### Installing Tools
```bash
# Install protoc (Ubuntu/Debian)
sudo apt install -y protobuf-compiler
# Install protoc (macOS)
brew install protobuf
# Install Go protobuf plugins
make tools
```
## Quick Start
1. **Copy this template** to create your smart contract:
```bash
cp -r go /path/to/my-smart-contract
cd /path/to/my-smart-contract
```
2. **Initialize your Go module and update package names**:
```bash
go mod init github.com/your-org/your-project
make fix-package-name
```
3. **Generate the protobuf code**:
```bash
make proto
```
4. **Configure your connection** by editing `config.yaml`:
```yaml
server_address: "your-dragonchain-server:50051"
smart_contract_id: "your-smart-contract-id"
api_key: "your-api-key"
```
5. **Implement your smart contract logic** in `process.go` by modifying the `Process` function.
6. **Build and run**:
```bash
make build
./smart-contract -config config.yaml
```
## Configuration
| Field | Description | Default |
|-------|-------------|---------|
| `server_address` | gRPC server address | 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` | Delay between reconnection attempts | `5` |
| `max_reconnect_attempts` | Max reconnect attempts (0 = infinite) | `0` |
## Implementing Your Smart Contract
Edit the `Process` function in `process.go`:
```go
func Process(ctx context.Context, txJSON string, envVars, secrets map[string]string) ProcessResult {
// Parse the transaction
var tx Transaction
if err := json.Unmarshal([]byte(txJSON), &tx); err != nil {
return ProcessResult{Error: fmt.Errorf("failed to parse transaction: %w", err)}
}
// Access transaction data
txnId := tx.Header.TxnId
txnType := tx.Header.TxnType
payload := tx.Payload
// Access environment variables
scName := envVars["SMART_CONTRACT_NAME"]
dcID := envVars["DRAGONCHAIN_ID"]
// Access secrets
mySecret := secrets["SC_SECRET_MY_SECRET"]
// Implement your logic here
result := map[string]any{
"status": "success",
"data": "your result data",
}
return ProcessResult{
Data: result,
OutputToChain: true, // Set to true to persist result on chain
Error: nil,
}
}
```
### Transaction Structure
The `Transaction` struct in `process.go` matches the Dragonchain transaction format:
```go
type Transaction struct {
Version string `json:"version"`
Header TransactionHeader `json:"header"`
Payload map[string]any `json:"payload"`
}
type TransactionHeader struct {
Tag string `json:"tag"`
DcId string `json:"dc_id"`
TxnId string `json:"txn_id"`
BlockId string `json:"block_id"`
TxnType string `json:"txn_type"`
Timestamp string `json:"timestamp"`
Invoker string `json:"invoker"`
}
```
### 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` map with keys prefixed by `SC_SECRET_`.
## Project Structure
```
.
├── main.go # Client infrastructure (do not modify)
├── process.go # Your smart contract logic (modify this)
├── proto/
│ └── remote_sc.proto # gRPC service definition
├── config.yaml # Configuration file
├── go.mod # Go module definition
├── Makefile # Build commands
└── README.md # This file
```
### File Descriptions
- **`process.go`** - Contains the `Process` function where you implement your smart contract logic. This is the only file you need to modify for most use cases.
- **`main.go`** - Contains the gRPC client infrastructure, connection handling, and worker pool. You typically don't need to modify this file.
## Make Commands
```bash
make proto # Generate Go code from proto files
make build # Build the binary
make run # Run with default config
make test # Run tests
make clean # Remove build artifacts
make tools # Install required tools
make deps # Download dependencies
make fix-package-name # Update package name based on go.mod
```
## Concurrent Processing
The client uses a worker pool pattern to process multiple transactions concurrently. The number of workers is configurable via `num_workers` in the config file.
## Error Handling
- Return errors from the `Process` function to report failures to the server
- The client automatically handles reconnection on connection failures
- Logs are captured and sent back with the response
## License
[Your License Here]

24
go/config.yaml Executable file
View File

@@ -0,0 +1,24 @@
# Smart Contract Client Configuration
# Copy this file and fill in your values
# The gRPC server address to connect to
server_address: "localhost:50051"
# Your smart contract ID (provided by Dragonchain)
smart_contract_id: "your-smart-contract-id"
# API key for authentication (provided by Dragonchain)
api_key: "your-api-key"
# Whether to use TLS for the connection
use_tls: false
# Path to TLS certificate (required if use_tls is true)
# tls_cert_path: "/path/to/cert.pem"
# Number of worker goroutines for processing transactions concurrently
num_workers: 10
# Reconnect settings
reconnect_delay_seconds: 5
max_reconnect_attempts: 0 # 0 = infinite retries

18
go/go.mod Executable file
View File

@@ -0,0 +1,18 @@
module github.com/your-org/smart-contract
go 1.24.0
toolchain go1.24.5
require (
google.golang.org/grpc v1.78.0
google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v3 v3.0.1
)
require (
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
)

40
go/go.sum Executable file
View File

@@ -0,0 +1,40 @@
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

311
go/main.go Executable file
View File

@@ -0,0 +1,311 @@
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"io"
"log"
"os"
"os/signal"
"sync"
"syscall"
"time"
pb "github.com/your-org/smart-contract/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata"
"gopkg.in/yaml.v3"
)
// =============================================================================
// Configuration and Client Infrastructure
// Do not modify this file unless you need to customize the client behavior.
// Implement your smart contract logic in process.go instead.
// =============================================================================
// Config holds the client configuration loaded from YAML
type Config struct {
ServerAddress string `yaml:"server_address"`
SmartContractID string `yaml:"smart_contract_id"`
APIKey string `yaml:"api_key"`
UseTLS bool `yaml:"use_tls"`
TLSCertPath string `yaml:"tls_cert_path"`
NumWorkers int `yaml:"num_workers"`
ReconnectDelaySecs int `yaml:"reconnect_delay_seconds"`
MaxReconnectAttempts int `yaml:"max_reconnect_attempts"`
}
// Client manages the gRPC connection and request processing
type Client struct {
config *Config
conn *grpc.ClientConn
grpcClient pb.SmartContractServiceClient
workChan chan *pb.SmartContractRequest
wg sync.WaitGroup
logger *log.Logger
}
// NewClient creates a new smart contract client
func NewClient(config *Config) *Client {
return &Client{
config: config,
workChan: make(chan *pb.SmartContractRequest, config.NumWorkers*2),
logger: log.New(os.Stdout, "[SC-Client] ", log.LstdFlags|log.Lmicroseconds),
}
}
// Connect establishes a connection to the gRPC server
func (c *Client) Connect() error {
var opts []grpc.DialOption
if c.config.UseTLS {
creds, err := credentials.NewClientTLSFromFile(c.config.TLSCertPath, "")
if err != nil {
return fmt.Errorf("failed to load TLS credentials: %w", err)
}
opts = append(opts, grpc.WithTransportCredentials(creds))
} else {
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
}
conn, err := grpc.NewClient(c.config.ServerAddress, opts...)
if err != nil {
return fmt.Errorf("failed to connect to server: %w", err)
}
c.conn = conn
c.grpcClient = pb.NewSmartContractServiceClient(conn)
c.logger.Printf("Connected to server at %s", c.config.ServerAddress)
return nil
}
// Close closes the gRPC connection
func (c *Client) Close() error {
if c.conn != nil {
return c.conn.Close()
}
return nil
}
// Run starts the client and processes incoming requests
func (c *Client) Run(ctx context.Context) error {
// Create metadata with authentication headers
md := metadata.Pairs(
"x-api-key", c.config.APIKey,
"x-smart-contract-id", c.config.SmartContractID,
)
ctx = metadata.NewOutgoingContext(ctx, md)
// Establish the bi-directional stream
stream, err := c.grpcClient.Run(ctx)
if err != nil {
return fmt.Errorf("failed to establish stream: %w", err)
}
c.logger.Printf("Stream established, starting %d workers", c.config.NumWorkers)
// Channel to collect responses from workers
responseChan := make(chan *pb.SmartContractResponse, c.config.NumWorkers*2)
errChan := make(chan error, 1)
// Start worker goroutines
for i := 0; i < c.config.NumWorkers; i++ {
c.wg.Add(1)
go c.worker(ctx, responseChan)
}
// Goroutine to send responses back to server
go func() {
for resp := range responseChan {
if err := stream.Send(resp); err != nil {
c.logger.Printf("Error sending response: %v", err)
select {
case errChan <- err:
default:
}
return
}
}
}()
// Main loop: receive requests and dispatch to workers
for {
req, err := stream.Recv()
if err == io.EOF {
c.logger.Println("Server closed the stream")
break
}
if err != nil {
return fmt.Errorf("error receiving request: %w", err)
}
c.logger.Printf("Received request: transaction_id=%s", req.TransactionId)
select {
case c.workChan <- req:
case <-ctx.Done():
return ctx.Err()
}
}
// Cleanup
close(c.workChan)
c.wg.Wait()
close(responseChan)
return nil
}
// worker processes requests from the work channel
func (c *Client) worker(ctx context.Context, responseChan chan<- *pb.SmartContractResponse) {
defer c.wg.Done()
for {
select {
case req, ok := <-c.workChan:
if !ok {
return
}
c.processRequest(ctx, req, responseChan)
case <-ctx.Done():
return
}
}
}
// processRequest handles a single request
func (c *Client) processRequest(ctx context.Context, req *pb.SmartContractRequest, responseChan chan<- *pb.SmartContractResponse) {
// Capture logs (in production, you might want a more sophisticated logging approach)
var logs string
// Call the user-defined Process function
result := Process(ctx, req.TransactionJson, req.EnvVars, req.Secrets)
// Build the response
resp := &pb.SmartContractResponse{
TransactionId: req.TransactionId,
OutputToChain: result.OutputToChain,
Logs: logs,
}
if result.Error != nil {
resp.Error = result.Error.Error()
c.logger.Printf("Error processing transaction %s: %v", req.TransactionId, result.Error)
} else {
// Marshal the result data to JSON
resultJSON, err := json.Marshal(result.Data)
if err != nil {
resp.Error = fmt.Sprintf("failed to marshal result: %v", err)
c.logger.Printf("Error marshaling result for transaction %s: %v", req.TransactionId, err)
} else {
resp.ResultJson = string(resultJSON)
c.logger.Printf("Successfully processed transaction %s", req.TransactionId)
}
}
select {
case responseChan <- resp:
case <-ctx.Done():
}
}
// LoadConfig loads configuration from a YAML file
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}
config := &Config{
NumWorkers: 10,
ReconnectDelaySecs: 5,
}
if err := yaml.Unmarshal(data, config); err != nil {
return nil, fmt.Errorf("failed to parse config file: %w", err)
}
// Validate required fields
if config.ServerAddress == "" {
return nil, fmt.Errorf("server_address is required")
}
if config.SmartContractID == "" {
return nil, fmt.Errorf("smart_contract_id is required")
}
if config.APIKey == "" {
return nil, fmt.Errorf("api_key is required")
}
return config, nil
}
func main() {
configPath := flag.String("config", "config.yaml", "Path to configuration file")
flag.Parse()
// Load configuration
config, err := LoadConfig(*configPath)
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
// Create client
client := NewClient(config)
// Setup signal handling for graceful shutdown
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-sigChan
log.Printf("Received signal %v, shutting down...", sig)
cancel()
}()
// Connection loop with reconnection logic
attempts := 0
for {
if err := client.Connect(); err != nil {
log.Printf("Connection failed: %v", err)
} else {
attempts = 0
if err := client.Run(ctx); err != nil {
if ctx.Err() != nil {
log.Println("Shutdown requested")
break
}
log.Printf("Stream error: %v", err)
}
}
_ = client.Close()
// Check if we should stop reconnecting
if ctx.Err() != nil {
break
}
attempts++
if config.MaxReconnectAttempts > 0 && attempts >= config.MaxReconnectAttempts {
log.Printf("Max reconnection attempts (%d) reached, exiting", config.MaxReconnectAttempts)
break
}
delay := time.Duration(config.ReconnectDelaySecs) * time.Second
log.Printf("Reconnecting in %v (attempt %d)...", delay, attempts)
select {
case <-time.After(delay):
case <-ctx.Done():
break
}
}
log.Println("Client shut down")
}

100
go/process.go Executable file
View File

@@ -0,0 +1,100 @@
package main
import (
"context"
"encoding/json"
"fmt"
)
// =============================================================================
// SMART CONTRACT IMPLEMENTATION - MODIFY THIS FILE
// =============================================================================
// Transaction represents the parsed transaction from the server.
// Customize this struct to match your transaction payload structure.
type Transaction struct {
Version string `json:"version"`
Header TransactionHeader `json:"header"`
Payload map[string]any `json:"payload"`
}
// TransactionHeader contains transaction metadata from Dragonchain.
type TransactionHeader struct {
Tag string `json:"tag"`
DcId string `json:"dc_id"`
TxnId string `json:"txn_id"`
BlockId string `json:"block_id"`
TxnType string `json:"txn_type"`
Timestamp string `json:"timestamp"`
Invoker string `json:"invoker"`
}
// ProcessResult is the result returned from the Process function.
type ProcessResult struct {
Data map[string]any
OutputToChain bool
Error error
}
// Process is the main function that handles incoming transactions.
// Implement your smart contract logic here.
//
// Parameters:
// - ctx: Context for cancellation and timeouts
// - txJSON: Raw transaction JSON string
// - envVars: Environment variables passed from the server
// - secrets: Secrets passed from the server (e.g., API keys, credentials)
//
// Returns:
// - ProcessResult containing the result data, whether to output to chain, and any error
func Process(ctx context.Context, txJSON string, envVars, secrets map[string]string) ProcessResult {
// Parse the transaction JSON
var tx Transaction
if err := json.Unmarshal([]byte(txJSON), &tx); err != nil {
return ProcessResult{
Error: fmt.Errorf("failed to parse transaction: %w", err),
}
}
// ==========================================================================
// TODO: Implement your smart contract logic here
// ==========================================================================
//
// Example: Access transaction data
// txnId := tx.Header.TxnId
// txnType := tx.Header.TxnType
// payload := tx.Payload
//
// Example: Access environment variables
// scName := envVars["SMART_CONTRACT_NAME"]
// dcID := envVars["DRAGONCHAIN_ID"]
//
// Example: Access secrets
// apiKey := secrets["SC_SECRET_MY_API_KEY"]
//
// Example: Process based on payload action
// action, _ := tx.Payload["action"].(string)
// switch action {
// case "create":
// // Handle create operation
// case "update":
// // Handle update operation
// default:
// return ProcessResult{Error: fmt.Errorf("unknown action: %s", action)}
// }
// Default implementation: echo back the transaction
result := map[string]any{
"status": "processed",
"transaction_id": tx.Header.TxnId,
"txn_type": tx.Header.TxnType,
"payload": tx.Payload,
"message": "Transaction processed successfully",
}
return ProcessResult{
Data: result,
OutputToChain: true,
Error: nil,
}
}

50
go/proto/remote_sc.proto Executable file
View File

@@ -0,0 +1,50 @@
syntax = "proto3";
package remote_sc;
option go_package = "git.dragonchain.com/dragonchain/sc-templates/go/proto";
// SmartContractService defines the bi-directional streaming service for remote
// smart contract execution. External workers connect to this service to receive
// execution tasks and send back results.
service SmartContractService {
// Run establishes a bi-directional stream. The server sends SmartContractRequest
// messages to the client for execution, and the client sends back
// SmartContractResponse messages with results.
rpc Run(stream SmartContractResponse) returns (stream SmartContractRequest);
}
// SmartContractRequest is sent from the server to the connected worker
// to request execution of a smart contract.
message SmartContractRequest {
// Unique identifier for this execution request, used to correlate responses
string transaction_id = 1;
// Full transaction JSON to be processed by the smart contract
string transaction_json = 2;
// Environment variables to set for the smart contract execution
map<string, string> env_vars = 3;
// Secrets to be made available to the smart contract
map<string, string> secrets = 4;
}
// SmartContractResponse is sent from the worker back to the server
// with the results of smart contract execution.
message SmartContractResponse {
// The transaction_id from the original request, for correlation
string transaction_id = 1;
// The result data from the smart contract execution as JSON
string result_json = 2;
// Logs captured during smart contract execution
string logs = 3;
// Whether to persist the output to the chain
bool output_to_chain = 4;
// Error message if execution failed
string error = 5;
}

40
python/Makefile Executable file
View File

@@ -0,0 +1,40 @@
.PHONY: proto run clean setup venv test
# Generate Python code from proto files
proto:
python -m grpc_tools.protoc \
-I./proto \
--python_out=. \
--grpc_python_out=. \
proto/remote_sc.proto
# Create virtual environment and install dependencies
setup: venv
. venv/bin/activate && pip install -r requirements.txt
# Create virtual environment
venv:
python3 -m venv venv
# Run the smart contract
run:
python main.py --config config.yaml
# Clean generated files
clean:
rm -f *_pb2.py *_pb2_grpc.py
rm -rf __pycache__
rm -rf venv
# Run tests
test:
python -m pytest tests/ -v
# Install dependencies (without venv)
deps:
pip install -r requirements.txt
# Format code
format:
black .
isort .

205
python/README.md Executable file
View File

@@ -0,0 +1,205 @@
# Python Smart Contract Template
A Python-based smart contract client for Dragonchain Prime that connects via gRPC.
## Prerequisites
- Python 3.10 or later
- pip
## Quick Start
1. **Copy this template** to create your smart contract:
```bash
cp -r python /path/to/my-smart-contract
cd /path/to/my-smart-contract
```
2. **Set up the environment**:
```bash
make setup
source venv/bin/activate
```
Or without make:
```bash
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```
3. **Generate the protobuf code**:
```bash
make proto
```
4. **Configure your connection** by editing `config.yaml`:
```yaml
server_address: "your-dragonchain-server:50051"
smart_contract_id: "your-smart-contract-id"
api_key: "your-api-key"
```
5. **Implement your smart contract logic** in `process.py` by modifying the `process` function.
6. **Run**:
```bash
python main.py --config config.yaml
```
## Configuration
| Field | Description | Default |
|-------|-------------|---------|
| `server_address` | gRPC server address | 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` | Delay between reconnection attempts | `5` |
| `max_reconnect_attempts` | Max reconnect attempts (0 = infinite) | `0` |
## Implementing Your Smart Contract
Edit the `process` function in `process.py`:
```python
def process(
tx_json: str,
env_vars: dict[str, str],
secrets: dict[str, str],
) -> ProcessResult:
# Parse the transaction
tx = Transaction.from_json(tx_json)
# Access transaction data
txn_id = tx.header.txn_id
txn_type = tx.header.txn_type
payload = tx.payload
# Access environment variables
sc_name = env_vars.get("SMART_CONTRACT_NAME")
dc_id = env_vars.get("DRAGONCHAIN_ID")
# Access secrets
my_secret = secrets.get("SC_SECRET_MY_SECRET")
# Implement your logic here
result = {
"status": "success",
"data": "your result data",
}
return ProcessResult(
data=result,
output_to_chain=True, # Set to True to persist result on chain
error=None,
)
```
### Transaction Structure
The `Transaction` class in `process.py` matches the Dragonchain transaction format:
```python
@dataclass
class Transaction:
version: str
header: TransactionHeader
payload: dict[str, Any]
@dataclass
class TransactionHeader:
tag: str
dc_id: str
txn_id: str
block_id: str
txn_type: str
timestamp: str
invoker: str
```
### 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` dict with keys prefixed by `SC_SECRET_`.
## Project Structure
```
.
├── main.py # Client infrastructure (do not modify)
├── process.py # Your smart contract logic (modify this)
├── proto/
│ └── remote_sc.proto # gRPC service definition
├── config.yaml # Configuration file
├── requirements.txt # Python dependencies
├── Makefile # Build commands
└── README.md # This file
```
### File Descriptions
- **`process.py`** - Contains the `process` function where you implement your smart contract logic. This is the only file you need to modify for most use cases.
- **`main.py`** - Contains the gRPC client infrastructure, connection handling, and worker pool. You typically don't need to modify this file.
## Make Commands
```bash
make setup # Create venv and install dependencies
make proto # Generate Python code from proto files
make run # Run with default config
make test # Run tests
make clean # Remove generated files and venv
make deps # Install dependencies (no venv)
make format # Format code with black and isort
```
## Concurrent Processing
The client uses a thread pool to process multiple transactions concurrently. The number of workers is configurable via `num_workers` in the config file.
## Error Handling
- Return errors from the `process` function via `ProcessResult.error` to report failures
- The client automatically handles reconnection on connection failures
- Logs are captured and sent back with the response
## Docker
Example `Dockerfile`:
```dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN python -m grpc_tools.protoc \
-I./proto \
--python_out=. \
--grpc_python_out=. \
proto/remote_sc.proto
CMD ["python", "main.py", "--config", "config.yaml"]
```
## License
[Your License Here]

24
python/config.yaml Executable file
View File

@@ -0,0 +1,24 @@
# Smart Contract Client Configuration
# Copy this file and fill in your values
# The gRPC server address to connect to
server_address: "localhost:50051"
# Your smart contract ID (provided by Dragonchain)
smart_contract_id: "your-smart-contract-id"
# API key for authentication (provided by Dragonchain)
api_key: "your-api-key"
# Whether to use TLS for the connection
use_tls: false
# Path to TLS certificate (required if use_tls is true)
# tls_cert_path: "/path/to/cert.pem"
# Number of worker threads for processing transactions concurrently
num_workers: 10
# Reconnect settings
reconnect_delay_seconds: 5
max_reconnect_attempts: 0 # 0 = infinite retries

297
python/main.py Executable file
View File

@@ -0,0 +1,297 @@
#!/usr/bin/env python3
"""
Dragonchain Smart Contract Client
A gRPC client that connects to Dragonchain Prime server to process
smart contract transactions.
Do not modify this file unless you need to customize the client behavior.
Implement your smart contract logic in process.py instead.
"""
import argparse
import json
import logging
import queue
import signal
import sys
import threading
import time
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass
from typing import Any, Optional
import grpc
import yaml
import remote_sc_pb2 as pb
import remote_sc_pb2_grpc as pb_grpc
from process import ProcessResult, process
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger("SmartContract")
# =============================================================================
# Configuration and Client Infrastructure
# Do not modify this file unless you need to customize the client behavior.
# Implement your smart contract logic in process.py instead.
# =============================================================================
@dataclass
class Config:
"""Client configuration loaded from YAML."""
server_address: str
smart_contract_id: str
api_key: str
use_tls: bool = False
tls_cert_path: Optional[str] = None
num_workers: int = 10
reconnect_delay_seconds: int = 5
max_reconnect_attempts: int = 0 # 0 = infinite
class SmartContractClient:
"""gRPC client for smart contract execution."""
def __init__(self, config: Config):
self.config = config
self.channel: Optional[grpc.Channel] = None
self.stub: Optional[pb_grpc.SmartContractServiceStub] = None
self.running = False
self.work_queue: queue.Queue = queue.Queue()
self.response_queue: queue.Queue = queue.Queue()
self.executor: Optional[ThreadPoolExecutor] = None
def connect(self) -> bool:
"""Establish connection to the gRPC server."""
try:
if self.config.use_tls:
if not self.config.tls_cert_path:
logger.error("TLS enabled but no certificate path provided")
return False
with open(self.config.tls_cert_path, "rb") as f:
creds = grpc.ssl_channel_credentials(f.read())
self.channel = grpc.secure_channel(self.config.server_address, creds)
else:
self.channel = grpc.insecure_channel(self.config.server_address)
self.stub = pb_grpc.SmartContractServiceStub(self.channel)
logger.info(f"Connected to server at {self.config.server_address}")
return True
except Exception as e:
logger.error(f"Failed to connect: {e}")
return False
def close(self):
"""Close the gRPC connection."""
if self.channel:
self.channel.close()
self.channel = None
self.stub = None
def _response_generator(self):
"""Generator that yields responses from the response queue."""
while self.running:
try:
response = self.response_queue.get(timeout=1.0)
if response is None:
break
yield response
except queue.Empty:
continue
def _process_request(self, request: pb.SmartContractRequest):
"""Process a single request and queue the response."""
logs = ""
try:
result = process(
tx_json=request.transaction_json,
env_vars=dict(request.env_vars),
secrets=dict(request.secrets),
)
response = pb.SmartContractResponse(
transaction_id=request.transaction_id,
output_to_chain=result.output_to_chain,
logs=logs,
)
if result.error:
response.error = result.error
logger.error(
f"Error processing transaction {request.transaction_id}: {result.error}"
)
else:
response.result_json = json.dumps(result.data) if result.data else "{}"
logger.info(f"Successfully processed transaction {request.transaction_id}")
except Exception as e:
response = pb.SmartContractResponse(
transaction_id=request.transaction_id,
error=str(e),
logs=logs,
)
logger.exception(f"Exception processing transaction {request.transaction_id}")
self.response_queue.put(response)
def _worker(self):
"""Worker thread that processes requests from the queue."""
while self.running:
try:
request = self.work_queue.get(timeout=1.0)
if request is None:
break
self._process_request(request)
except queue.Empty:
continue
def run(self) -> bool:
"""Run the client and process incoming requests."""
if not self.stub:
logger.error("Not connected to server")
return False
self.running = True
self.executor = ThreadPoolExecutor(max_workers=self.config.num_workers)
# Start worker threads
workers = []
for _ in range(self.config.num_workers):
future = self.executor.submit(self._worker)
workers.append(future)
logger.info(f"Started {self.config.num_workers} worker threads")
# Create metadata for authentication
metadata = [
("x-api-key", self.config.api_key),
("x-smart-contract-id", self.config.smart_contract_id),
]
try:
# Establish bi-directional stream
stream = self.stub.Run(self._response_generator(), metadata=metadata)
logger.info("Stream established, waiting for requests...")
# Receive and dispatch requests
for request in stream:
if not self.running:
break
logger.info(f"Received request: transaction_id={request.transaction_id}")
self.work_queue.put(request)
logger.info("Server closed the stream")
return True
except grpc.RpcError as e:
logger.error(f"gRPC error: {e.code()} - {e.details()}")
return False
except Exception as e:
logger.exception(f"Error in run loop: {e}")
return False
finally:
self.running = False
# Signal workers to stop
for _ in range(self.config.num_workers):
self.work_queue.put(None)
self.response_queue.put(None)
# Wait for workers to finish
if self.executor:
self.executor.shutdown(wait=True)
def stop(self):
"""Stop the client gracefully."""
logger.info("Stopping client...")
self.running = False
def load_config(path: str) -> Config:
"""Load configuration from a YAML file."""
with open(path, "r") as f:
data = yaml.safe_load(f)
# Validate required fields
required = ["server_address", "smart_contract_id", "api_key"]
for field in required:
if field not in data or not data[field]:
raise ValueError(f"Missing required config field: {field}")
return Config(
server_address=data["server_address"],
smart_contract_id=data["smart_contract_id"],
api_key=data["api_key"],
use_tls=data.get("use_tls", False),
tls_cert_path=data.get("tls_cert_path"),
num_workers=data.get("num_workers", 10),
reconnect_delay_seconds=data.get("reconnect_delay_seconds", 5),
max_reconnect_attempts=data.get("max_reconnect_attempts", 0),
)
def main():
parser = argparse.ArgumentParser(description="Dragonchain Smart Contract Client")
parser.add_argument(
"--config",
"-c",
default="config.yaml",
help="Path to configuration file",
)
args = parser.parse_args()
# Load configuration
try:
config = load_config(args.config)
except Exception as e:
logger.error(f"Failed to load config: {e}")
sys.exit(1)
# Create client
client = SmartContractClient(config)
# Setup signal handling for graceful shutdown
def signal_handler(signum, frame):
logger.info(f"Received signal {signum}, shutting down...")
client.stop()
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# Connection loop with reconnection logic
attempts = 0
while True:
if client.connect():
attempts = 0
if not client.run():
if not client.running:
logger.info("Shutdown requested")
break
client.close()
attempts += 1
if config.max_reconnect_attempts > 0 and attempts >= config.max_reconnect_attempts:
logger.error(f"Max reconnection attempts ({config.max_reconnect_attempts}) reached")
break
delay = config.reconnect_delay_seconds
logger.info(f"Reconnecting in {delay} seconds (attempt {attempts})...")
time.sleep(delay)
logger.info("Client shut down")
if __name__ == "__main__":
main()

6
python/package-lock.json generated Executable file
View File

@@ -0,0 +1,6 @@
{
"name": "python",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

140
python/process.py Executable file
View File

@@ -0,0 +1,140 @@
"""
Smart Contract Processing Logic
This file contains the transaction processing logic for your smart contract.
Modify the `process` function to implement your business logic.
"""
import json
from dataclasses import dataclass
from typing import Any, Optional
# =============================================================================
# SMART CONTRACT IMPLEMENTATION - MODIFY THIS FILE
# =============================================================================
@dataclass
class TransactionHeader:
"""Transaction metadata from Dragonchain."""
tag: str
dc_id: str
txn_id: str
block_id: str
txn_type: str
timestamp: str
invoker: str
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "TransactionHeader":
"""Parse header from dictionary."""
return cls(
tag=data.get("tag", ""),
dc_id=data.get("dc_id", ""),
txn_id=data.get("txn_id", ""),
block_id=data.get("block_id", ""),
txn_type=data.get("txn_type", ""),
timestamp=data.get("timestamp", ""),
invoker=data.get("invoker", ""),
)
@dataclass
class Transaction:
"""
Parsed transaction from the server.
Customize this class to match your transaction payload structure.
"""
version: str
header: TransactionHeader
payload: dict[str, Any]
@classmethod
def from_json(cls, json_str: str) -> "Transaction":
"""Parse a transaction from JSON string."""
data = json.loads(json_str)
return cls(
version=data.get("version", ""),
header=TransactionHeader.from_dict(data.get("header", {})),
payload=data.get("payload", {}),
)
@dataclass
class ProcessResult:
"""Result from the process function."""
data: Optional[dict[str, Any]] = None
output_to_chain: bool = True
error: Optional[str] = None
def process(
tx_json: str,
env_vars: dict[str, str],
secrets: dict[str, str],
) -> ProcessResult:
"""
Process an incoming transaction.
Implement your smart contract logic here.
Args:
tx_json: Raw transaction JSON string
env_vars: Environment variables passed from the server
secrets: Secrets passed from the server (e.g., API keys, credentials)
Returns:
ProcessResult containing the result data, whether to output to chain, and any error
"""
try:
# Parse the transaction JSON
tx = Transaction.from_json(tx_json)
except json.JSONDecodeError as e:
return ProcessResult(error=f"Failed to parse transaction: {e}")
# ==========================================================================
# TODO: Implement your smart contract logic here
# ==========================================================================
#
# Example: Access transaction data
# txn_id = tx.header.txn_id
# txn_type = tx.header.txn_type
# payload = tx.payload
#
# Example: Access environment variables
# sc_name = env_vars.get("SMART_CONTRACT_NAME")
# dc_id = env_vars.get("DRAGONCHAIN_ID")
#
# Example: Access secrets
# api_key = secrets.get("SC_SECRET_MY_API_KEY")
#
# Example: Process based on payload action
# action = tx.payload.get("action")
# if action == "create":
# # Handle create operation
# pass
# elif action == "update":
# # Handle update operation
# pass
# else:
# return ProcessResult(error=f"Unknown action: {action}")
# Default implementation: echo back the transaction
result = {
"status": "processed",
"transaction_id": tx.header.txn_id,
"txn_type": tx.header.txn_type,
"payload": tx.payload,
"message": "Transaction processed successfully",
}
return ProcessResult(
data=result,
output_to_chain=True,
error=None,
)

48
python/proto/remote_sc.proto Executable file
View File

@@ -0,0 +1,48 @@
syntax = "proto3";
package remote_sc;
// SmartContractService defines the bi-directional streaming service for remote
// smart contract execution. External workers connect to this service to receive
// execution tasks and send back results.
service SmartContractService {
// Run establishes a bi-directional stream. The server sends SmartContractRequest
// messages to the client for execution, and the client sends back
// SmartContractResponse messages with results.
rpc Run(stream SmartContractResponse) returns (stream SmartContractRequest);
}
// SmartContractRequest is sent from the server to the connected worker
// to request execution of a smart contract.
message SmartContractRequest {
// Unique identifier for this execution request, used to correlate responses
string transaction_id = 1;
// Full transaction JSON to be processed by the smart contract
string transaction_json = 2;
// Environment variables to set for the smart contract execution
map<string, string> env_vars = 3;
// Secrets to be made available to the smart contract
map<string, string> secrets = 4;
}
// SmartContractResponse is sent from the worker back to the server
// with the results of smart contract execution.
message SmartContractResponse {
// The transaction_id from the original request, for correlation
string transaction_id = 1;
// The result data from the smart contract execution as JSON
string result_json = 2;
// Logs captured during smart contract execution
string logs = 3;
// Whether to persist the output to the chain
bool output_to_chain = 4;
// Error message if execution failed
string error = 5;
}

4
python/requirements.txt Executable file
View File

@@ -0,0 +1,4 @@
grpcio>=1.60.0
grpcio-tools>=1.60.0
protobuf>=4.25.0
pyyaml>=6.0

28
typescript/.gitignore vendored Executable file
View File

@@ -0,0 +1,28 @@
# Dependencies
node_modules/
# Build output
dist/
# Generated proto files
src/proto/
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# Test config (contains credentials)
test-config.yaml
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# OS
.DS_Store
Thumbs.db

38
typescript/Makefile Executable file
View File

@@ -0,0 +1,38 @@
.PHONY: proto build run clean setup test
# Generate TypeScript types from proto files
proto:
npm run proto
# Install dependencies
setup:
npm install
# Build the TypeScript code
build:
npm run build
# Run the smart contract (production)
run: build
npm start -- --config config.yaml
# Run in development mode (with ts-node)
dev:
npm run dev -- --config config.yaml
# Clean build artifacts
clean:
npm run clean
rm -rf node_modules
# Run tests
test:
npm test
# Lint code
lint:
npm run lint
# Format code
format:
npm run format

220
typescript/README.md Executable file
View File

@@ -0,0 +1,220 @@
# 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
1. **Copy this template** to create your smart contract:
```bash
cp -r typescript /path/to/my-smart-contract
cd /path/to/my-smart-contract
```
2. **Install dependencies**:
```bash
npm install
```
3. **Configure your connection** by editing `config.yaml`:
```yaml
server_address: "your-dragonchain-server:50051"
smart_contract_id: "your-smart-contract-id"
api_key: "your-api-key"
```
4. **Implement your smart contract logic** in `src/process.ts` by modifying the `processTransaction` function.
5. **Build and run**:
```bash
npm run build
npm start -- --config config.yaml
```
Or run in development mode:
```bash
npm run dev -- --config config.yaml
```
## Configuration
| Field | Description | Default |
|-------|-------------|---------|
| `server_address` | gRPC server address | 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` | Delay between reconnection attempts | `5` |
| `max_reconnect_attempts` | Max reconnect attempts (0 = infinite) | `0` |
## Implementing Your Smart Contract
Edit the `processTransaction` function in `src/process.ts`:
```typescript
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:
```typescript
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 the `processTransaction` function 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
```bash
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
```bash
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 `processTransaction` function via `ProcessResult.error` to report failures
- The client automatically handles reconnection on connection failures
- Logs are captured and sent back with the response
## Docker
Example `Dockerfile`:
```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:
1. Write your code in `src/process.js` instead of `src/process.ts`
2. Skip the build step and run directly:
```bash
node src/main.js --config config.yaml
```
## License
[Your License Here]

24
typescript/config.yaml Executable file
View File

@@ -0,0 +1,24 @@
# Smart Contract Client Configuration
# Copy this file and fill in your values
# The gRPC server address to connect to
server_address: "localhost:50051"
# Your smart contract ID (provided by Dragonchain)
smart_contract_id: "your-smart-contract-id"
# API key for authentication (provided by Dragonchain)
api_key: "your-api-key"
# Whether to use TLS for the connection
use_tls: false
# Path to TLS certificate (required if use_tls is true)
# tls_cert_path: "/path/to/cert.pem"
# Number of concurrent workers for processing transactions
num_workers: 10
# Reconnect settings
reconnect_delay_seconds: 5
max_reconnect_attempts: 0 # 0 = infinite retries

603
typescript/package-lock.json generated Executable file
View File

@@ -0,0 +1,603 @@
{
"name": "smart-contract",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "smart-contract",
"version": "1.0.0",
"dependencies": {
"@grpc/grpc-js": "^1.9.0",
"@grpc/proto-loader": "^0.7.0",
"js-yaml": "^4.1.0"
},
"devDependencies": {
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.0.0",
"ts-node": "^10.9.0",
"typescript": "^5.3.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@grpc/grpc-js": {
"version": "1.14.3",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz",
"integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==",
"license": "Apache-2.0",
"dependencies": {
"@grpc/proto-loader": "^0.8.0",
"@js-sdsl/ordered-map": "^4.4.2"
},
"engines": {
"node": ">=12.10.0"
}
},
"node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz",
"integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==",
"license": "Apache-2.0",
"dependencies": {
"lodash.camelcase": "^4.3.0",
"long": "^5.0.0",
"protobufjs": "^7.5.3",
"yargs": "^17.7.2"
},
"bin": {
"proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@grpc/proto-loader": {
"version": "0.7.15",
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz",
"integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==",
"license": "Apache-2.0",
"dependencies": {
"lodash.camelcase": "^4.3.0",
"long": "^5.0.0",
"protobufjs": "^7.2.5",
"yargs": "^17.7.2"
},
"bin": {
"proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@js-sdsl/ordered-map": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
"integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/js-sdsl"
}
},
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/codegen": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/eventemitter": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/fetch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.1",
"@protobufjs/inquire": "^1.1.0"
}
},
"node_modules/@protobufjs/float": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/inquire": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/utf8": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
"license": "BSD-3-Clause"
},
"node_modules/@tsconfig/node10": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
"integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/js-yaml": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.19.30",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz",
"integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/acorn-walk": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.11.0"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true,
"license": "MIT"
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/diff": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
"license": "MIT"
},
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
},
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true,
"license": "ISC"
},
"node_modules/protobufjs": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
"integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.4",
"@protobufjs/eventemitter": "^1.1.0",
"@protobufjs/fetch": "^1.1.0",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.0",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.0",
"@types/node": ">=13.7.0",
"long": "^5.0.0"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/ts-node": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.2",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
},
"bin": {
"ts-node": "dist/bin.js",
"ts-node-cwd": "dist/bin-cwd.js",
"ts-node-esm": "dist/bin-esm.js",
"ts-node-script": "dist/bin-script.js",
"ts-node-transpile-only": "dist/bin-transpile.js",
"ts-script": "dist/bin-script-deprecated.js"
},
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
"@types/node": "*",
"typescript": ">=2.7"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"@swc/wasm": {
"optional": true
}
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT"
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true,
"license": "MIT"
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"license": "MIT",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
}
}
}

29
typescript/package.json Executable file
View File

@@ -0,0 +1,29 @@
{
"name": "smart-contract",
"version": "1.0.0",
"description": "Dragonchain Smart Contract Client",
"main": "dist/main.js",
"scripts": {
"build": "tsc",
"start": "node dist/main.js",
"dev": "ts-node src/main.ts",
"proto": "proto-loader-gen-types --grpcLib=@grpc/grpc-js --outDir=src/proto proto/remote_sc.proto",
"clean": "rm -rf dist",
"lint": "eslint src/**/*.ts",
"format": "prettier --write src/**/*.ts"
},
"dependencies": {
"@grpc/grpc-js": "^1.9.0",
"@grpc/proto-loader": "^0.7.0",
"js-yaml": "^4.1.0"
},
"devDependencies": {
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.0.0",
"typescript": "^5.3.0",
"ts-node": "^10.9.0"
},
"engines": {
"node": ">=18.0.0"
}
}

View File

@@ -0,0 +1,48 @@
syntax = "proto3";
package remote_sc;
// SmartContractService defines the bi-directional streaming service for remote
// smart contract execution. External workers connect to this service to receive
// execution tasks and send back results.
service SmartContractService {
// Run establishes a bi-directional stream. The server sends SmartContractRequest
// messages to the client for execution, and the client sends back
// SmartContractResponse messages with results.
rpc Run(stream SmartContractResponse) returns (stream SmartContractRequest);
}
// SmartContractRequest is sent from the server to the connected worker
// to request execution of a smart contract.
message SmartContractRequest {
// Unique identifier for this execution request, used to correlate responses
string transaction_id = 1;
// Full transaction JSON to be processed by the smart contract
string transaction_json = 2;
// Environment variables to set for the smart contract execution
map<string, string> env_vars = 3;
// Secrets to be made available to the smart contract
map<string, string> secrets = 4;
}
// SmartContractResponse is sent from the worker back to the server
// with the results of smart contract execution.
message SmartContractResponse {
// The transaction_id from the original request, for correlation
string transaction_id = 1;
// The result data from the smart contract execution as JSON
string result_json = 2;
// Logs captured during smart contract execution
string logs = 3;
// Whether to persist the output to the chain
bool output_to_chain = 4;
// Error message if execution failed
string error = 5;
}

369
typescript/src/main.ts Executable file
View File

@@ -0,0 +1,369 @@
/**
* Dragonchain Smart Contract Client
*
* A gRPC client that connects to Dragonchain Prime server to process
* smart contract transactions.
*
* Do not modify this file unless you need to customize the client behavior.
* Implement your smart contract logic in process.ts instead.
*/
import * as grpc from "@grpc/grpc-js";
import * as protoLoader from "@grpc/proto-loader";
import * as fs from "fs";
import * as path from "path";
import * as yaml from "js-yaml";
import { ProcessResult, processTransaction } from "./process";
// Load proto definition
const PROTO_PATH = path.join(__dirname, "../proto/remote_sc.proto");
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: false,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition) as any;
const SmartContractService = protoDescriptor.remote_sc.SmartContractService;
// =============================================================================
// Configuration and Client Infrastructure
// Do not modify this file unless you need to customize the client behavior.
// Implement your smart contract logic in process.ts instead.
// =============================================================================
interface Config {
serverAddress: string;
smartContractId: string;
apiKey: string;
useTls: boolean;
tlsCertPath?: string;
numWorkers: number;
reconnectDelaySeconds: number;
maxReconnectAttempts: number;
}
interface SmartContractRequest {
transactionId: string;
transactionJson: string;
envVars: Record<string, string>;
secrets: Record<string, string>;
}
interface SmartContractResponse {
transactionId: string;
resultJson: string;
logs: string;
outputToChain: boolean;
error: string;
}
class SmartContractClient {
private config: Config;
private client: any;
private running: boolean = false;
private workQueue: SmartContractRequest[] = [];
private processing: Set<string> = new Set();
private stream: any;
constructor(config: Config) {
this.config = config;
}
/**
* Connect to the gRPC server.
*/
connect(): boolean {
try {
let credentials: grpc.ChannelCredentials;
if (this.config.useTls) {
if (!this.config.tlsCertPath) {
console.error("[SC-Client] TLS enabled but no certificate path provided");
return false;
}
const rootCert = fs.readFileSync(this.config.tlsCertPath);
credentials = grpc.credentials.createSsl(rootCert);
} else {
credentials = grpc.credentials.createInsecure();
}
this.client = new SmartContractService(
this.config.serverAddress,
credentials
);
console.log(`[SC-Client] Connected to server at ${this.config.serverAddress}`);
return true;
} catch (e) {
console.error(`[SC-Client] Failed to connect: ${e}`);
return false;
}
}
/**
* Close the gRPC connection.
*/
close(): void {
if (this.stream) {
this.stream.end();
this.stream = null;
}
if (this.client) {
grpc.closeClient(this.client);
this.client = null;
}
}
/**
* Process a single request.
*/
private async processRequest(request: SmartContractRequest): Promise<SmartContractResponse> {
const logs = "";
try {
const result = await processTransaction(
request.transactionJson,
request.envVars,
request.secrets
);
const response: SmartContractResponse = {
transactionId: request.transactionId,
resultJson: result.data ? JSON.stringify(result.data) : "{}",
logs,
outputToChain: result.outputToChain,
error: result.error || "",
};
if (result.error) {
console.error(
`[SC-Client] Error processing transaction ${request.transactionId}: ${result.error}`
);
} else {
console.log(
`[SC-Client] Successfully processed transaction ${request.transactionId}`
);
}
return response;
} catch (e) {
console.error(
`[SC-Client] Exception processing transaction ${request.transactionId}: ${e}`
);
return {
transactionId: request.transactionId,
resultJson: "",
logs,
outputToChain: false,
error: String(e),
};
}
}
/**
* Run the client and process incoming requests.
*/
async run(): Promise<boolean> {
if (!this.client) {
console.error("[SC-Client] Not connected to server");
return false;
}
this.running = true;
// Create metadata for authentication
const metadata = new grpc.Metadata();
metadata.add("x-api-key", this.config.apiKey);
metadata.add("x-smart-contract-id", this.config.smartContractId);
return new Promise((resolve) => {
// Establish bi-directional stream
this.stream = this.client.Run(metadata);
console.log(
`[SC-Client] Stream established, ready to process requests (workers: ${this.config.numWorkers})`
);
// Handle incoming requests
this.stream.on("data", async (request: SmartContractRequest) => {
if (!this.running) return;
console.log(
`[SC-Client] Received request: transaction_id=${request.transactionId}`
);
// Process with concurrency limit
if (this.processing.size >= this.config.numWorkers) {
this.workQueue.push(request);
} else {
this.startProcessing(request);
}
});
this.stream.on("end", () => {
console.log("[SC-Client] Server closed the stream");
this.running = false;
resolve(true);
});
this.stream.on("error", (err: grpc.ServiceError) => {
console.error(`[SC-Client] Stream error: ${err.code} - ${err.message}`);
this.running = false;
resolve(false);
});
});
}
/**
* Start processing a request with concurrency tracking.
*/
private async startProcessing(request: SmartContractRequest): Promise<void> {
this.processing.add(request.transactionId);
try {
const response = await this.processRequest(request);
if (this.stream && this.running) {
this.stream.write(response);
}
} finally {
this.processing.delete(request.transactionId);
// Process next queued request if any
if (this.workQueue.length > 0 && this.running) {
const next = this.workQueue.shift()!;
this.startProcessing(next);
}
}
}
/**
* Stop the client gracefully.
*/
stop(): void {
console.log("[SC-Client] Stopping client...");
this.running = false;
}
}
// =============================================================================
// Configuration Loading
// =============================================================================
interface RawConfig {
server_address: string;
smart_contract_id: string;
api_key: string;
use_tls?: boolean;
tls_cert_path?: string;
num_workers?: number;
reconnect_delay_seconds?: number;
max_reconnect_attempts?: number;
}
function loadConfig(configPath: string): Config {
const content = fs.readFileSync(configPath, "utf8");
const raw = yaml.load(content) as RawConfig;
// Validate required fields
const required = ["server_address", "smart_contract_id", "api_key"];
for (const field of required) {
if (!(field in raw) || !raw[field as keyof RawConfig]) {
throw new Error(`Missing required config field: ${field}`);
}
}
return {
serverAddress: raw.server_address,
smartContractId: raw.smart_contract_id,
apiKey: raw.api_key,
useTls: raw.use_tls ?? false,
tlsCertPath: raw.tls_cert_path,
numWorkers: raw.num_workers ?? 10,
reconnectDelaySeconds: raw.reconnect_delay_seconds ?? 5,
maxReconnectAttempts: raw.max_reconnect_attempts ?? 0,
};
}
// =============================================================================
// Main Entry Point
// =============================================================================
async function main(): Promise<void> {
// Parse command line arguments
const args = process.argv.slice(2);
let configPath = "config.yaml";
for (let i = 0; i < args.length; i++) {
if (args[i] === "--config" || args[i] === "-c") {
configPath = args[i + 1];
i++;
}
}
// Load configuration
let config: Config;
try {
config = loadConfig(configPath);
} catch (e) {
console.error(`[SC-Client] Failed to load config: ${e}`);
process.exit(1);
}
// Create client
const client = new SmartContractClient(config);
// Setup signal handling for graceful shutdown
const shutdown = () => {
console.log("[SC-Client] Received shutdown signal...");
client.stop();
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
// Connection loop with reconnection logic
let attempts = 0;
while (true) {
if (client.connect()) {
attempts = 0;
const success = await client.run();
if (!success) {
// Check if it was a graceful shutdown
client.close();
break;
}
}
client.close();
attempts++;
if (
config.maxReconnectAttempts > 0 &&
attempts >= config.maxReconnectAttempts
) {
console.error(
`[SC-Client] Max reconnection attempts (${config.maxReconnectAttempts}) reached`
);
break;
}
const delay = config.reconnectDelaySeconds;
console.log(
`[SC-Client] Reconnecting in ${delay} seconds (attempt ${attempts})...`
);
await new Promise((resolve) => setTimeout(resolve, delay * 1000));
}
console.log("[SC-Client] Client shut down");
}
main().catch((e) => {
console.error(`[SC-Client] Fatal error: ${e}`);
process.exit(1);
});

112
typescript/src/process.ts Executable file
View File

@@ -0,0 +1,112 @@
/**
* Smart Contract Processing Logic
*
* This file contains the transaction processing logic for your smart contract.
* Modify the `processTransaction` function to implement your business logic.
*/
// =============================================================================
// SMART CONTRACT IMPLEMENTATION - MODIFY THIS FILE
// =============================================================================
/**
* Transaction header metadata from Dragonchain.
*/
export interface TransactionHeader {
tag: string;
dc_id: string;
txn_id: string;
block_id: string;
txn_type: string;
timestamp: string;
invoker: string;
}
/**
* Parsed transaction from the server.
* Customize this interface to match your transaction payload structure.
*/
export interface Transaction {
version: string;
header: TransactionHeader;
payload: Record<string, unknown>;
}
/**
* Result from the processTransaction function.
*/
export interface ProcessResult {
data?: Record<string, unknown>;
outputToChain: boolean;
error?: string;
}
/**
* Process an incoming transaction.
*
* Implement your smart contract logic here.
*
* @param txJson - Raw transaction JSON string
* @param envVars - Environment variables passed from the server
* @param secrets - Secrets passed from the server (e.g., API keys, credentials)
* @returns ProcessResult containing the result data, whether to output to chain, and any error
*/
export async function processTransaction(
txJson: string,
envVars: Record<string, string>,
secrets: Record<string, string>
): Promise<ProcessResult> {
// Parse the transaction JSON
let tx: Transaction;
try {
tx = JSON.parse(txJson);
} catch (e) {
return {
outputToChain: false,
error: `Failed to parse transaction: ${e}`,
};
}
// ==========================================================================
// TODO: Implement your smart contract logic here
// ==========================================================================
//
// Example: Access transaction data
// const txnId = tx.header.txn_id;
// const txnType = tx.header.txn_type;
// const payload = tx.payload;
//
// Example: Access environment variables
// const scName = envVars["SMART_CONTRACT_NAME"];
// const dcId = envVars["DRAGONCHAIN_ID"];
//
// Example: Access secrets
// const apiKey = secrets["SC_SECRET_MY_API_KEY"];
//
// Example: Process based on payload action
// const action = tx.payload.action as string;
// switch (action) {
// case "create":
// // Handle create operation
// break;
// case "update":
// // Handle update operation
// break;
// default:
// return { outputToChain: false, error: `Unknown action: ${action}` };
// }
// Default implementation: echo back the transaction
const result = {
status: "processed",
transaction_id: tx.header.txn_id,
txn_type: tx.header.txn_type,
payload: tx.payload,
message: "Transaction processed successfully",
};
return {
data: result,
outputToChain: true,
};
}

19
typescript/tsconfig.json Executable file
View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}