Skip to content

Commit 4f19550

Browse files
committed
feat: add bundler
1 parent 4e014b2 commit 4f19550

File tree

14 files changed

+670
-1
lines changed

14 files changed

+670
-1
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
name: Build and push Bundler Docker image
2+
permissions:
3+
contents: read
4+
on:
5+
push:
6+
branches:
7+
- main
8+
tags:
9+
- "*"
10+
pull_request:
11+
branches:
12+
- main
13+
workflow_dispatch:
14+
15+
concurrency:
16+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
17+
cancel-in-progress: true
18+
19+
jobs:
20+
build-push-image:
21+
name: Build and push Bundler Docker image
22+
runs-on: [matterlabs-ci-runner]
23+
# Only run docker build/push for the main repository, not forks
24+
# Allow manual dispatch from any repository
25+
if: github.repository == 'matter-labs/zksync-sso'
26+
steps:
27+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
28+
with:
29+
submodules: recursive
30+
31+
- name: Set git SHA
32+
id: git_sha
33+
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
34+
35+
- name: Set Docker tag
36+
id: docker_tag
37+
run: |
38+
ts=$(date +%s%N | cut -b1-13)
39+
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
40+
echo "tag=${{ steps.git_sha.outputs.sha_short }}-${ts}" >> $GITHUB_OUTPUT
41+
elif [[ "${{ github.ref }}" == refs/tags/* ]]; then
42+
echo "tag=$(echo ${GITHUB_REF#refs/tags/})" >> $GITHUB_OUTPUT
43+
elif [[ "${{ github.event_name }}" == "pull_request" ]]; then
44+
echo "tag=none" >> $GITHUB_OUTPUT
45+
else
46+
echo "Unsupported event ${GITHUB_EVENT_NAME} or ref ${GITHUB_REF}, only refs/heads/, refs/tags/, pull_request, and workflow_dispatch are supported."
47+
exit 1
48+
fi
49+
50+
- name: Set up Docker Buildx
51+
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.0
52+
53+
- name: Log in to Docker Hub
54+
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
55+
with:
56+
username: ${{ secrets.DOCKERHUB_USER }}
57+
password: ${{ secrets.DOCKERHUB_TOKEN }}
58+
59+
- name: Login to GAR
60+
run: |
61+
gcloud auth configure-docker us-docker.pkg.dev -q
62+
63+
- name: Build and push bundler image
64+
id: docker_build_bundler
65+
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
66+
with:
67+
context: packages/bundler
68+
file: packages/bundler/Dockerfile
69+
push: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')) || github.event_name == 'workflow_dispatch' }}
70+
tags: |
71+
matterlabs/sso-bundler:${{ steps.docker_tag.outputs.tag }}
72+
matterlabs/sso-bundler:latest
73+
us-docker.pkg.dev/matterlabs-infra/matterlabs-docker/sso-bundler:${{ steps.docker_tag.outputs.tag }}
74+
us-docker.pkg.dev/matterlabs-infra/matterlabs-docker/sso-bundler:latest
75+
76+
- name: Print image digest to summary
77+
run: |
78+
echo "Bundler Image tag: ${{ steps.docker_tag.outputs.tag }}" >> $GITHUB_STEP_SUMMARY

cspell-config/cspell-misc.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,7 @@ wght
4545
// packages/auther-server-api
4646
rustup
4747
distroless
48-
debian
48+
debian
49+
50+
// packages/bundler
51+
pimlico

packages/bundler/.env.example

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# ERC-4337 Bundler Configuration
2+
3+
# Private key for executing user operations (required for production)
4+
# Default: Anvil rich account #0
5+
EXECUTOR_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
6+
7+
# Private key for utility operations (required for production)
8+
# Default: Anvil rich account #1
9+
UTILITY_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
10+
11+
# RPC URL for the blockchain network (required for production)
12+
# Default: Local anvil instance
13+
RPC_URL=http://localhost:8545
14+
15+
# For production (testnet/mainnet), replace with actual values:
16+
# EXECUTOR_PRIVATE_KEY=0x...
17+
# UTILITY_PRIVATE_KEY=0x...
18+
# RPC_URL=https://sepolia.drpc.org

packages/bundler/Dockerfile

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Stage 1: Build TypeScript
2+
FROM node:22-slim AS builder
3+
4+
WORKDIR /app
5+
6+
# Copy package files
7+
COPY package.json ./
8+
COPY tsconfig.json ./
9+
10+
# Install dependencies
11+
RUN npm install
12+
13+
# Copy source files
14+
COPY src/ ./src/
15+
16+
# Build TypeScript
17+
RUN npm run build
18+
19+
# Stage 2: Production runtime
20+
FROM node:22-slim AS production
21+
22+
# Install @pimlico/alto globally
23+
RUN npm install -g @pimlico/alto@0.0.19
24+
25+
WORKDIR /app
26+
27+
# Copy package.json for production dependencies
28+
COPY package.json ./
29+
30+
# Install production dependencies only
31+
RUN npm install --omit=dev && npm cache clean --force
32+
33+
# Copy built files from builder
34+
COPY --from=builder /app/dist ./dist
35+
36+
# Environment defaults (can be overridden at runtime)
37+
ENV EXECUTOR_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
38+
ENV UTILITY_PRIVATE_KEY="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
39+
ENV RPC_URL="http://localhost:8545"
40+
41+
# Healthcheck on Alto bundler port (internal)
42+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
43+
CMD ["node", "-e", "require('http').get('http://localhost:4338/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"]
44+
45+
# Expose only the CORS proxy port (4338 is internal)
46+
EXPOSE 4337
47+
48+
# Start the bundler
49+
CMD ["node", "dist/index.js"]

packages/bundler/README.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Bundler
2+
3+
ERC-4337 bundler service with CORS proxy for ZKsync SSO. Built on
4+
[@pimlico/alto](https://github.com/pimlicolabs/alto).
5+
6+
## Features
7+
8+
- **Alto Bundler**: ERC-4337 compliant bundler on port 4338
9+
- **CORS Proxy**: Browser-friendly proxy on port 4337
10+
- **Environment Validation**: Zod-based config with sensible defaults
11+
- **Docker Ready**: Multi-stage build with production-optimized image
12+
13+
## Ports
14+
15+
- **4337**: CORS proxy (accessible from browsers)
16+
- **4338**: Alto bundler API (internal)
17+
18+
## Quick Start
19+
20+
### Local Development
21+
22+
Works out of the box with local Anvil:
23+
24+
```bash
25+
# Install dependencies
26+
pnpm install
27+
28+
# Start bundler (uses default anvil keys)
29+
pnpm dev
30+
```
31+
32+
The bundler will start with default configuration for local development:
33+
34+
- Anvil rich account keys
35+
- RPC: `http://localhost:8545`
36+
- EntryPoint: `0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108`
37+
38+
### Production
39+
40+
Create a `.env` file:
41+
42+
```bash
43+
EXECUTOR_PRIVATE_KEY=0x...
44+
UTILITY_PRIVATE_KEY=0x...
45+
RPC_URL=https://sepolia.drpc.org
46+
```
47+
48+
Then start:
49+
50+
```bash
51+
pnpm start
52+
```
53+
54+
## Docker Usage
55+
56+
### Build
57+
58+
```bash
59+
docker build -t sso-bundler packages/bundler
60+
```
61+
62+
### Run
63+
64+
```bash
65+
docker run -p 4337:4337 -p 4338:4338 \
66+
-e EXECUTOR_PRIVATE_KEY=0x... \
67+
-e UTILITY_PRIVATE_KEY=0x... \
68+
-e RPC_URL=https://sepolia.drpc.org \
69+
sso-bundler
70+
```
71+
72+
### Pre-built Image
73+
74+
```bash
75+
docker pull matterlabs/sso-bundler:latest
76+
77+
docker run -p 4337:4337 -p 4338:4338 \
78+
-e EXECUTOR_PRIVATE_KEY=0x... \
79+
-e UTILITY_PRIVATE_KEY=0x... \
80+
-e RPC_URL=https://sepolia.drpc.org \
81+
matterlabs/sso-bundler:latest
82+
```
83+
84+
## Environment Variables
85+
86+
| Variable | Description | Default (Local Dev) |
87+
| ---------------------- | ----------------------------------------- | ----------------------- |
88+
| `EXECUTOR_PRIVATE_KEY` | Private key for executing user operations | Anvil account #0 |
89+
| `UTILITY_PRIVATE_KEY` | Private key for utility operations | Anvil account #1 |
90+
| `RPC_URL` | RPC endpoint for blockchain network | `http://localhost:8545` |
91+
92+
## Configuration
93+
94+
The bundler generates an Alto config file at runtime with these fixed values:
95+
96+
- **EntryPoint**: `0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108`
97+
- **Port**: 4338
98+
- **Safe Mode**: false
99+
100+
## API Usage
101+
102+
Send ERC-4337 user operations to: `http://localhost:4337`
103+
104+
The CORS proxy automatically forwards requests to Alto bundler and adds
105+
appropriate CORS headers for browser compatibility.
106+
107+
## Development
108+
109+
```bash
110+
# Install dependencies
111+
pnpm install
112+
113+
# Build TypeScript
114+
pnpm build
115+
116+
# Run in development mode with hot reload
117+
pnpm dev
118+
119+
# Run compiled version
120+
pnpm start
121+
```
122+
123+
## License
124+
125+
MIT

packages/bundler/alto-config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"entrypoints": "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
3+
"executor-private-keys": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
4+
"utility-private-key": "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d",
5+
"rpc-url": "http://localhost:8545",
6+
"port": 4338,
7+
"safe-mode": false
8+
}

packages/bundler/package.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "bundler",
3+
"version": "0.0.1",
4+
"description": "ERC-4337 bundler with CORS proxy for zkSync SSO",
5+
"type": "module",
6+
"main": "dist/index.js",
7+
"scripts": {
8+
"build": "tsc",
9+
"typecheck": "tsc --noEmit",
10+
"dev": "node --import tsx src/index.ts",
11+
"start": "node dist/index.js"
12+
},
13+
"keywords": [
14+
"zksync",
15+
"account-abstraction",
16+
"ERC4337",
17+
"bundler",
18+
"alto"
19+
],
20+
"author": "Matter Labs <zksync.io>",
21+
"license": "MIT",
22+
"dependencies": {
23+
"@pimlico/alto": "^0.0.19",
24+
"dotenv": "^16.4.7",
25+
"zod": "^3.24.1"
26+
},
27+
"devDependencies": {
28+
"@types/node": "^22.10.1",
29+
"tsx": "^4.19.2",
30+
"typescript": "^5.6.2"
31+
}
32+
}

packages/bundler/project.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "bundler",
3+
"tags": ["type:package"],
4+
"targets": {
5+
"build": {
6+
"executor": "nx:run-script",
7+
"options": {
8+
"cwd": "packages/bundler",
9+
"script": "build"
10+
}
11+
},
12+
"dev": {
13+
"executor": "nx:run-script",
14+
"options": {
15+
"cwd": "packages/bundler",
16+
"script": "dev"
17+
}
18+
}
19+
}
20+
}

0 commit comments

Comments
 (0)