Files
2026-09-06 03:24:58 -07:00

347 lines
8.7 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
CHAIN="${1:?Usage: $0 <chain-name> <symbol> <mainnet-id> <testnet-id> <devnet-id>}"
SYMBOL="${2:?symbol required}"
MAINNET_ID="${3:?mainnet chain ID required}"
TESTNET_ID="${4:?testnet chain ID required}"
DEVNET_ID="${5:?devnet chain ID required}"
ORG="${CHAIN}-labs"
UPPER=$(echo "$CHAIN" | tr '[:lower:]' '[:upper:]')
TITLE=$(echo "$CHAIN" | sed 's/./\U&/')
DIR="$HOME/work/$CHAIN"
echo "Creating $TITLE L2 stack at $DIR"
echo " Symbol: $SYMBOL"
echo " Chain IDs: $MAINNET_ID / $TESTNET_ID / $DEVNET_ID"
echo " GitHub org: $ORG"
echo ""
mkdir -p "$DIR"
# ─── 1. EVM ──────────────────────────────────────────────
mkdir -p "$DIR/evm"
cat > "$DIR/evm/main.go" << GOEOF
package main
import (
"fmt"
"os"
"github.com/luxfi/node/version"
"github.com/luxfi/evm/plugin/evm"
// Precompiles — uncomment what you need
// _ "github.com/luxfi/precompile/mldsa"
// _ "github.com/luxfi/precompile/frost"
// _ "github.com/luxfi/precompile/cggmp21"
// _ "github.com/luxfi/precompile/dex"
// _ "github.com/luxfi/precompile/blake3"
)
const Version = "0.1.0"
func main() {
if len(os.Args) > 1 && (os.Args[1] == "version" || os.Args[1] == "--version") {
fmt.Printf("${CHAIN}-evm %s (luxd %s)\n", Version, version.Current)
os.Exit(0)
}
evm.Run()
}
GOEOF
cat > "$DIR/evm/go.mod" << GOEOF
module github.com/${ORG}/evm
go 1.26.1
require (
github.com/luxfi/node v1.0.9
github.com/luxfi/evm v1.0.7
)
GOEOF
cat > "$DIR/evm/Dockerfile" << 'DEOF'
FROM golang:1.26.4-alpine AS build
RUN apk add --no-cache gcc musl-dev git
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=1 go build -trimpath -ldflags="-s -w" -o /node .
FROM alpine:3.21
RUN apk add --no-cache ca-certificates curl && adduser -D -u 10001 node
COPY --from=build /node /usr/local/bin/node
USER node
ENTRYPOINT ["node"]
DEOF
# ─── 2. CLI ──────────────────────────────────────────────
mkdir -p "$DIR/cli/cmd" "$DIR/cli/internal/deploy"
cat > "$DIR/cli/main.go" << GOEOF
package main
import (
"fmt"
"os"
"github.com/${ORG}/cli/cmd"
)
func main() {
if err := cmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
GOEOF
cat > "$DIR/cli/go.mod" << GOEOF
module github.com/${ORG}/cli
go 1.26.1
GOEOF
# ─── 3. SDK ──────────────────────────────────────────────
mkdir -p "$DIR/sdk/src"
cat > "$DIR/sdk/package.json" << JSONEOF
{
"name": "@${ORG}/sdk",
"version": "0.1.0",
"description": "${TITLE} Network SDK",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"type": "module",
"scripts": {
"build": "tsc",
"test": "vitest run"
},
"dependencies": {
"ethers": "^6.13.0",
"viem": "^2.21.0"
},
"devDependencies": {
"typescript": "^5.7.0",
"vitest": "^3.0.0"
}
}
JSONEOF
cat > "$DIR/sdk/src/index.ts" << TSEOF
export const CHAINS = {
devnet: { id: ${DEVNET_ID}, rpc: 'https://rpc.dev.${CHAIN}.network' },
testnet: { id: ${TESTNET_ID}, rpc: 'https://rpc.test.${CHAIN}.network' },
mainnet: { id: ${MAINNET_ID}, rpc: 'https://rpc.${CHAIN}.network' },
} as const;
export const NATIVE = { name: '${TITLE}', symbol: '${SYMBOL}', decimals: 18 } as const;
export type ChainName = keyof typeof CHAINS;
TSEOF
cat > "$DIR/sdk/tsconfig.json" << 'TSEOF'
{
"compilerOptions": {
"target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext",
"declaration": true, "outDir": "dist", "rootDir": "src", "strict": true
},
"include": ["src"]
}
TSEOF
# ─── 4. Contracts ────────────────────────────────────────
mkdir -p "$DIR/contracts/src" "$DIR/contracts/script" "$DIR/contracts/test"
cat > "$DIR/contracts/foundry.toml" << 'FEOF'
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc = "0.8.31"
optimizer = true
optimizer_runs = 200
evm_version = "shanghai"
FEOF
cat > "$DIR/contracts/src/Token.sol" << 'SEOF'
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
contract Token is ERC20, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
constructor(string memory name_, string memory symbol_, address admin)
ERC20(name_, symbol_)
{
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(MINTER_ROLE, admin);
}
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
_mint(to, amount);
}
}
SEOF
# ─── 5. Genesis ──────────────────────────────────────────
mkdir -p "$DIR/genesis"
for env_id in "mainnet:${MAINNET_ID}" "testnet:${TESTNET_ID}" "devnet:${DEVNET_ID}"; do
IFS=: read env cid <<< "$env_id"
cat > "$DIR/genesis/${env}.json" << GEOF
{
"config": {
"chainId": ${cid},
"homesteadBlock": 0, "eip155Block": 0, "eip158Block": 0,
"byzantiumBlock": 0, "constantinopleBlock": 0, "petersburgBlock": 0,
"istanbulBlock": 0, "muirGlacierBlock": 0, "subnetEVMTimestamp": 0
},
"nonce": "0x0",
"timestamp": "0x0",
"gasLimit": "0x1312D00",
"difficulty": "0x0",
"alloc": {
"0x0000000000000000000000000000000000000000": {
"balance": "0x204FCE5E3E25026110000000"
}
}
}
GEOF
done
# ─── 6. Exchange (stub — clone from lux/exchange apps/) ──
mkdir -p "$DIR/exchange"
cat > "$DIR/exchange/README.md" << EEOF
# ${TITLE} Exchange
Clone apps from ~/work/lux/exchange:
\`\`\`bash
cp -r ~/work/lux/exchange/apps .
pnpm install
\`\`\`
Apps consume @l.x/* packages from npm. No local pkgs/ needed.
EEOF
# ─── 7. Universe (K8s) ──────────────────────────────────
mkdir -p "$DIR/universe/k8s/base" "$DIR/universe/k8s/devnet" "$DIR/universe/k8s/testnet" "$DIR/universe/k8s/mainnet"
cat > "$DIR/universe/k8s/base/node.yaml" << 'KEOF'
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: node
spec:
serviceName: node-headless
replicas: 5
selector:
matchLabels:
app: node
template:
metadata:
labels:
app: node
spec:
containers:
- name: node
image: IMAGE_PLACEHOLDER
ports:
- {containerPort: 9630, name: staking}
- {containerPort: 9631, name: rpc}
volumeMounts:
- {name: data, mountPath: /data}
volumeClaimTemplates:
- metadata: {name: data}
spec:
accessModes: [ReadWriteOnce]
resources: {requests: {storage: 50Gi}}
KEOF
cat > "$DIR/universe/k8s/base/services.yaml" << 'KEOF'
# Explorer — use lux/explorer image with env branding
apiVersion: apps/v1
kind: Deployment
metadata:
name: explorer
spec:
replicas: 1
selector: {matchLabels: {app: explorer}}
template:
metadata: {labels: {app: explorer}}
spec:
containers:
- name: explorer
image: us-docker.pkg.dev/liquidity-devnet/backend/zoo-explorer:latest
env:
- {name: COIN, value: SYMBOL_PLACEHOLDER}
- {name: NETWORK, value: NAME_PLACEHOLDER}
- {name: DISABLE_INDEXER, value: "false"}
ports: [{containerPort: 4000}]
resources:
requests: {memory: 1Gi}
limits: {memory: 4Gi}
---
# MPC — upstream luxfi/mpc image
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mpc-node
spec:
serviceName: mpc
replicas: 1
selector: {matchLabels: {app: mpc-node}}
template:
metadata: {labels: {app: mpc-node}}
spec:
containers:
- name: mpc
image: ghcr.io/luxfi/mpc:latest
env:
- {name: MPC_ORG, value: NAME_PLACEHOLDER}
- {name: MPC_BRAND_NAME, value: NAME_PLACEHOLDER}
ports: [{containerPort: 8081}]
---
# KMS — upstream luxfi/kms image
apiVersion: apps/v1
kind: Deployment
metadata:
name: kms
spec:
replicas: 1
selector: {matchLabels: {app: kms}}
template:
metadata: {labels: {app: kms}}
spec:
containers:
- name: kms
image: ghcr.io/luxfi/kms:latest
env:
- {name: KMS_ORG, value: NAME_PLACEHOLDER}
ports: [{containerPort: 8443}]
---
# Bridge — upstream luxfi/bridge image
apiVersion: apps/v1
kind: Deployment
metadata:
name: bridge
spec:
replicas: 1
selector: {matchLabels: {app: bridge}}
template:
metadata: {labels: {app: bridge}}
spec:
containers:
- name: bridge
image: ghcr.io/luxfi/bridge:latest
ports: [{containerPort: 8080}]
KEOF
echo "Template created at $DIR"
echo ""
echo "Repos:"
ls -d "$DIR"/*/