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

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;
}