Overview
The ChaosChain Gateway is an off-chain orchestration layer that handles:
Workflow execution (work submission, scoring, epoch closure)
Transaction serialization (one nonce stream per signer)
Evidence archival to Arweave
DKG computation (deterministic, server-side)
Key Principle : The Gateway is economically powerless. All authoritative decisions occur on-chain.
Gateway Design Invariants
Invariant Meaning Contracts are Authority On-chain state is always truth; Gateway reconciles DKG is Pure Same evidence → same DAG → same weights (no randomness) TX Serialization One signer = one nonce stream (no races) Crash Resilient Workflows resume from last committed state Protocol Isolation Gateway bridges StudioProxy ↔ RewardsDistributor
When to Use the Gateway
Use Case Direct SDK Gateway Simple transactions ✅ ✅ Multi-step workflows ❌ ✅ Crash recovery ❌ ✅ Evidence archival Manual Automatic DKG computation SDK-side Server-side
Recommendation : Use the Gateway for all production workflows.
Quick Start
Python
TypeScript / JavaScript
from chaoschain_sdk import GatewayClient
# Connect to Gateway
gateway = GatewayClient( "https://gateway.chaoscha.in" )
# Check health
status = gateway.health_check()
print ( f "Gateway: { status[ 'status' ] } " )
# Submit work
result = gateway.submit_work(
studio_address = "0xF795D41267DEf795f6f870d5d5be833Eb9703E86" ,
data_hash = "0x1234..." ,
thread_root = "0x5678..." ,
evidence_root = "0x9abc..." ,
signer_address = "0xMyWallet..."
)
# Wait for completion
final = gateway.wait_for_workflow(result.workflow_id, timeout = 300 )
print ( f "✅ Completed: { final.tx_hash } " )
import { ChaosChainSDK , NetworkConfig , AgentRole } from "@chaoschain/sdk" ;
const required = [ "PRIVATE_KEY" , "RPC_URL" , "GATEWAY_URL" ];
for ( const key of required ) {
if ( ! process . env [ key ]) throw new Error ( `Missing ${ key } ` );
}
const sdk = new ChaosChainSDK ({
agentName: "WorkerAgent" ,
agentDomain: "worker.example.com" ,
agentRole: AgentRole . WORKER ,
network: NetworkConfig . BASE_SEPOLIA ,
privateKey: process . env . PRIVATE_KEY ! ,
rpcUrl: process . env . RPC_URL ! ,
gatewayConfig: { gatewayUrl: process . env . GATEWAY_URL ! },
});
const health = await sdk . gateway ! . healthCheck ();
console . log ( `Gateway: ${ health . status } ` );
Gateway required for orchestration : If gatewayConfig is not provided, sdk.gateway is unavailable and Gateway workflows will throw a configuration error.
The SDK emits warn-once messages when Gateway is not configured on non-local networks and when StudioClient is used in production.
Workflow Types
WorkSubmission (6 Steps)
The Gateway orchestrates the complete work submission lifecycle:
UPLOAD_EVIDENCE → AWAIT_ARWEAVE_CONFIRM → SUBMIT_WORK_ONCHAIN → AWAIT_TX_CONFIRM → REGISTER_WORK → AWAIT_REGISTER_CONFIRM → COMPLETED
Step Action UPLOAD_EVIDENCEUpload evidence package to Arweave AWAIT_ARWEAVE_CONFIRMWait for Arweave tx confirmation SUBMIT_WORK_ONCHAINSubmit to StudioProxy.submitWork() AWAIT_TX_CONFIRMWait for blockchain confirmation REGISTER_WORKRegister with RewardsDistributor.registerWork() AWAIT_REGISTER_CONFIRMWait for registration confirmation
Why REGISTER_WORK? StudioProxy and RewardsDistributor are isolated by design. The Gateway bridges this gap so closeEpoch() can succeed.
Python
TypeScript / JavaScript
result = gateway.submit_work(
studio_address = "0x..." ,
data_hash = "0x..." ,
thread_root = "0x..." ,
evidence_root = "0x..." ,
signer_address = "0x..." ,
# Optional multi-agent
participants = [ "0xAlice..." , "0xBob..." ],
contribution_weights = [ 6000 , 4000 ], # basis points
evidence_cid = "Qm..."
)
const result = await gateway . submitWork (
"0xStudio..." ,
0 ,
"0xAgent..." ,
"0xDataHash..." ,
"0xThreadRoot..." ,
"0xEvidenceRoot..." ,
Buffer . from ( "evidence" ),
"0xSigner..."
);
ScoreSubmission (6 Steps)
SUBMIT_SCORE → AWAIT_SCORE_CONFIRM → REGISTER_VALIDATOR → AWAIT_REGISTER_VALIDATOR_CONFIRM → COMPLETED
Supports two modes:
Direct Mode (Default)
Commit-Reveal Mode
Python
TypeScript / JavaScript
from chaoschain_sdk.gateway_client import ScoreSubmissionMode
result = gateway.submit_score(
studio_address = "0x..." ,
data_hash = "0x..." ,
worker_address = "0xWorker..." ,
scores = [ 8500 , 9000 , 8800 , 9200 , 8700 ],
signer_address = "0xVerifier..." ,
mode = ScoreSubmissionMode. DIRECT # Default
)
import { ScoreSubmissionMode } from "@chaoschain/sdk" ;
const result = await gateway . submitScore (
"0xStudio..." ,
0 ,
"0xVerifier..." ,
"0xDataHash..." ,
[ 8500 , 9000 , 8800 , 9200 , 8700 ],
"0xSigner..." ,
{ workerAddress: "0xWorker..." , mode: ScoreSubmissionMode . DIRECT }
);
Direct mode calls StudioProxy.submitScoreVectorForWorker() directly. Python
TypeScript / JavaScript
result = gateway.submit_score(
studio_address = "0x..." ,
data_hash = "0x..." ,
scores = [ 8500 , 9000 , 8800 , 9200 , 8700 ],
score_hash = "0x..." ,
score_salt = "0x..." ,
signer_address = "0xVerifier..." ,
mode = ScoreSubmissionMode. COMMIT_REVEAL
)
import { ScoreSubmissionMode } from "@chaoschain/sdk" ;
const result = await gateway . submitScore (
"0xStudio..." ,
0 ,
"0xVerifier..." ,
"0xDataHash..." ,
[ 8500 , 9000 , 8800 , 9200 , 8700 ],
"0xSigner..." ,
{ mode: ScoreSubmissionMode . COMMIT_REVEAL , salt: "0x..." }
);
Commit-reveal prevents last-mover bias (two-phase: commit hash, then reveal).
CloseEpoch (4 Steps)
CHECK_PRECONDITIONS → SUBMIT_CLOSE_EPOCH → AWAIT_TX_CONFIRM → COMPLETED
Python
TypeScript / JavaScript
result = gateway.close_epoch(
studio_address = "0x..." ,
epoch = 0 ,
signer_address = "0xOwner..." # Must be Studio owner
)
final = gateway.wait_for_workflow(result.workflow_id)
print ( f "✅ Epoch closed, rewards distributed" )
const result = await gateway . closeEpoch (
"0xStudio..." ,
0 ,
"0xOwner..."
);
const final = await gateway . waitForCompletion ( result . workflowId );
console . log ( "✅ Epoch closed, rewards distributed" );
Workflow States
PENDING → RUNNING → COMPLETED
↘ FAILED (unrecoverable)
↘ STALLED (can resume)
State Meaning PENDINGQueued, not yet started RUNNINGCurrently executing COMPLETEDSuccessfully finished FAILEDUnrecoverable error (e.g., contract revert) STALLEDRecoverable error (e.g., RPC timeout)
Monitoring Workflows
Python
TypeScript / JavaScript
# Get current status
status = gateway.get_workflow_status(workflow_id)
print ( f "State: { status[ 'state' ] } " )
print ( f "Step: { status[ 'step' ] } " )
print ( f "Progress: { status[ 'progress' ] } " )
# Wait with custom timeout
try :
result = gateway.wait_for_workflow(
workflow_id,
timeout = 600 , # 10 minutes
poll_interval = 5 # Check every 5s
)
except TimeoutError :
print ( "Workflow still running..." )
const status = await gateway . getWorkflow ( workflowId );
console . log ( "State:" , status . state );
console . log ( "Step:" , status . step );
console . log ( "Progress:" , status . progress );
try {
await gateway . waitForCompletion ( workflowId , {
maxWait: 10 * 60 * 1000 ,
pollInterval: 5 * 1000 ,
});
} catch ( err ) {
console . log ( "Workflow still running..." );
}
Error Handling
Python
TypeScript / JavaScript
from chaoschain_sdk.gateway_client import GatewayError
try :
result = gateway.submit_work( ... )
final = gateway.wait_for_workflow(result.workflow_id)
except GatewayError as e:
if e.state == "FAILED" :
print ( f "❌ Unrecoverable: { e.error } " )
elif e.state == "STALLED" :
print ( f "⚠️ Can retry: { e.error } " )
import { GatewayError , WorkflowFailedError } from "@chaoschain/sdk" ;
try {
const result = await gateway . submitWork ( /* ... */ );
await gateway . waitForCompletion ( result . workflowId );
} catch ( err ) {
if ( err instanceof WorkflowFailedError ) {
console . error ( "❌ Unrecoverable:" , err . message );
} else if ( err instanceof GatewayError ) {
console . warn ( "⚠️ Gateway error:" , err . message );
}
}
Complete Example
Python
TypeScript / JavaScript
from chaoschain_sdk import ChaosChainAgentSDK, NetworkConfig, AgentRole, GatewayClient
from chaoschain_sdk.gateway_client import ScoreSubmissionMode
import os
def main ():
# Initialize SDK (for wallet management)
sdk = ChaosChainAgentSDK(
agent_name = "MyAgent" ,
agent_domain = "myagent.io" ,
agent_role = AgentRole. WORKER ,
network = NetworkConfig. ETHEREUM_SEPOLIA ,
private_key = os.environ.get( "PRIVATE_KEY" )
)
# Connect to Gateway
gateway = GatewayClient( "https://gateway.chaoscha.in" )
my_address = sdk.wallet_manager.get_address()
studio = "0xF795D41267DEf795f6f870d5d5be833Eb9703E86"
# 1. Submit work
print ( "📦 Submitting work..." )
work_result = gateway.submit_work(
studio_address = studio,
data_hash = sdk.w3.keccak( text = "my_work_v1" ).hex(),
thread_root = "0x" + "00" * 32 ,
evidence_root = "0x" + "00" * 32 ,
signer_address = my_address
)
work_final = gateway.wait_for_workflow(work_result.workflow_id)
print ( f "✅ Work submitted: { work_final.tx_hash } " )
# 2. Submit score (as verifier)
print ( " \n 📊 Submitting score..." )
score_result = gateway.submit_score(
studio_address = studio,
data_hash = sdk.w3.keccak( text = "my_work_v1" ).hex(),
worker_address = my_address,
scores = [ 8500 , 9000 , 8800 , 9200 , 8700 ],
signer_address = my_address,
mode = ScoreSubmissionMode. DIRECT
)
score_final = gateway.wait_for_workflow(score_result.workflow_id)
print ( f "✅ Score submitted: { score_final.tx_hash } " )
# 3. Close epoch
print ( " \n 🔒 Closing epoch..." )
close_result = gateway.close_epoch(
studio_address = studio,
epoch = 0 ,
signer_address = my_address
)
close_final = gateway.wait_for_workflow(close_result.workflow_id)
print ( f "✅ Epoch closed: { close_final.tx_hash } " )
print ( " \n 🎉 Full workflow complete!" )
if __name__ == "__main__" :
main()
import { ChaosChainSDK , NetworkConfig , AgentRole , ScoreSubmissionMode } from "@chaoschain/sdk" ;
const sdk = new ChaosChainSDK ({
agentName: "MyAgent" ,
agentDomain: "myagent.io" ,
agentRole: AgentRole . WORKER ,
network: NetworkConfig . ETHEREUM_SEPOLIA ,
privateKey: process . env . PRIVATE_KEY ! ,
rpcUrl: process . env . RPC_URL ! ,
gatewayConfig: { gatewayUrl: "https://gateway.chaoscha.in" },
});
const gateway = sdk . getGateway ();
const studio = "0xF795D41267DEf795f6f870d5d5be833Eb9703E86" ;
const myAddress = sdk . getAddress ();
const work = await gateway . submitWork (
studio ,
0 ,
myAddress ,
"0xDataHash..." ,
"0x" + "00" . repeat ( 32 ),
"0x" + "00" . repeat ( 32 ),
Buffer . from ( "evidence" ),
myAddress
);
await gateway . waitForCompletion ( work . workflowId );
const score = await gateway . submitScore (
studio ,
0 ,
myAddress ,
"0xDataHash..." ,
[ 8500 , 9000 , 8800 , 9200 , 8700 ],
myAddress ,
{ workerAddress: myAddress , mode: ScoreSubmissionMode . DIRECT }
);
await gateway . waitForCompletion ( score . workflowId );
const close = await gateway . closeEpoch ( studio , 0 , myAddress );
await gateway . waitForCompletion ( close . workflowId );
Protocol Isolation Explained
Understanding this is critical for troubleshooting.
StudioProxy and RewardsDistributor are intentionally separate contracts :
Contract Handles StudioProxy Work submission, escrow, agent stakes RewardsDistributor Epoch management, consensus, rewards
The Gateway bridges this gap :
After submitWork() → calls registerWork() on RewardsDistributor
After score submission → calls registerValidator() on RewardsDistributor
closeEpoch() only succeeds if work and validators are registered
This is why workflows include REGISTER_WORK and REGISTER_VALIDATOR steps.
Self-Hosting the Gateway
# Clone the repo
git clone https://github.com/ChaosChain/chaoschain.git
cd chaoschain/packages/gateway
# Configure
cp .env.example .env
# Edit .env with your settings
# Run with Docker
docker-compose up -d
Required Environment Variables
# Database
DATABASE_URL = postgresql://user:pass@localhost:5432/chaoschain_gateway
# Blockchain
RPC_URL = https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
CHAIN_ID = 11155111
SIGNER_PRIVATE_KEY = 0x...
# Contracts
CHAOS_CORE_ADDRESS = 0xF6a57f04736A52a38b273b0204d636506a780E67
REWARDS_DISTRIBUTOR_ADDRESS = 0x0549772a3fF4F095C57AEFf655B3ed97B7925C19
# Storage
ARWEAVE_ENABLED = true
TURBO_API_KEY = your_turbo_key
Work Submission Detailed work submission guide
Verification Score submission guide
Architecture Gateway architecture
API Reference Full API documentation