Skip to content

About

Intent Based EVM Task Automated Execution. Prototype , do not use for Production Purposes

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Express Cross-Platform Server

This repository provides a lightweight Express.js service that accepts natural-language task descriptions, converts them into scheduled blockchain actions (EVM calldata / unsigned transactions), manages per-user encrypted wallets, and can delegate signing to an external signer service. It's intended as a secure prototype for automating on-chain actions from human-friendly instructions.

Runs on Linux, macOS, and Windows.

Prerequisites

  • Node.js 18+ (recommended LTS)
  • npm or yarn

Install

cd /path/to/express-crossplatform-server
npm install

Run

  • Start production server:
npm start
  • Start development server (auto-restart with changes):

ZK verification (detailed guide)

This project includes a verifier wrapper at src/zk/verifier.js which uses snarkjs (Groth16) to verify proofs. The /api/users/:userId/wallet/verify-zk endpoint will call this wrapper if snarkjs and a verification key (vkey) are available.

Quick overview of steps to create a verifiable circuit and use the endpoint:

  1. Install tools
# install snarkjs (global or local) and install circom per circom docs
npm install -g snarkjs
# follow https://docs.circom.io/getting-started/installation/ to install circom
  1. Write a simple circuit (example) zk/circuits/preimage.circom

Example circuit (simple preimage check; adapt to your needs):

pragma circom 2.0.0;
include "circomlib/sha256.circom";

template PreimageCheck() {
	signal input preimage[1];
	signal output hash[2];
	component hasher = Sha256(1);
	hasher.in[0] <== preimage[0];
	hash[0] <== hasher.out[0];
	hash[1] <== hasher.out[1];
}

component main = PreimageCheck();
  1. Compile the circuit and produce keys
```bash
npm run dev

Environment variables

  • PORT - TCP port (default 3000)
  • HOST - Host to bind (default 0.0.0.0)

Example

# start on port 4000
PORT=4000 npm start

4) Create witness and proof

```bash
# on Windows (PowerShell)
$env:PORT=4000; npm start
  1. Verify server-side using endpoint

Place zk/vkey.json where the server can read it (or pass its path in vkeyPath query param). Call the API endpoint:

Quick test

```bash
curl http://localhost:3000/

If `snarkjs` is not installed or `zk/vkey.json` is missing, the endpoint will return `501` with guidance. See `src/zk/verifier.js` for the verification wrapper implementation.

Notes & caveats

- Circom and snarkjs are evolving; follow official docs for installation and version compatibility.
- For on-chain verification prefer exporting a Solidity verifier contract via `snarkjs zkey export solidityverifier` and verify proofs on-chain when necessary.
- This guide uses Groth16 as an example; you can adapt to PLONK (snarkjs supports multiple schemes) depending on your threat model and proof size/perf requirements.
curl http://localhost:3000/health
curl http://localhost:3000/hello/Bob

Tasks API

  • Create a task (POST):
curl -X POST http://localhost:3000/api/tasks \
	-H "Content-Type: application/json" \
	-d '{"userId":"0x0123...", "text":"Every 3 times per day on Ethereum do X"}'

Note: When creating a task with POST /api/tasks, the server will ensure the userId has an assigned wallet. If the user does not have a wallet yet, the server will automatically create one and include the wallet metadata in the response under the wallet field. If a wallet already exists the response will include { "exists": true, "address": "0x..." }.

Example response when a wallet is newly created alongside the task:

{
	"task": { /* task object */ },
	"nlp": { /* nlp parse result */ },
	"wallet": {
		"exists": false,
		"address": "0x...",
		"recoverySecret": "<one-time-secret-hex>"
	}
}
  • List tasks for a user (GET):
curl http://localhost:3000/api/users/0x0123.../tasks

Notes

  • userId must be a 32-byte hex string (64 hex chars) optionally prefixed with 0x.
  • The server will attempt to parse the natural language using an LLM if OPENAI_API_KEY is set. Otherwise a simple heuristic parser runs.
  • Execution on blockchains is currently a stub — you must wire in ethers.js / @solana/web3.js and provide credentials/providers for real actions.
  • If the natural language intent maps to an EVM action (for example an ERC-20 transfer), the server will ABI-encode the calldata and store it on the task under the evm field (e.g. { type, contract, data }). This data is ready to be used as the transaction data field for EVM-compatible chains.

Task JSON fields

  • id: internal UUID for the task
  • userId: 32-byte hex identifying the user
  • text: original natural language text
  • action: parsed action or raw text
  • targetBlockchain: e.g. ethereum, solana, or none
  • frequencyPerDay: how many times per day to run
  • totalExecutions: total number of times to run
  • params: parsed parameters from NLP (merged with any overrides from request)
  • contractAddress: optional EVM contract address for calls (0x...)
  • value: optional value to send with transaction (string or number, in human units depending on context)
  • gasLimit: optional gas limit to use for the tx
  • chainId: optional chain id for the target network
  • functionName: optional function name for generic contract calls
  • evm: if generated, an object containing { type, contract, data } where data is the ABI-encoded calldata hex string

You may pass an overrides object in the POST body to explicitly supply contractAddress, value, gasLimit, chainId, or functionName.

Example with overrides

curl -X POST http://localhost:3000/api/tasks \
	-H "Content-Type: application/json" \
	-d '{
		"userId":"0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
		"text":"Transfer tokens to 0x111...",
		"overrides": {
			"contractAddress": "0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
			"value": "0",
			"gasLimit": "100000",
			"chainId": "1"
		}
	}'
	Executor / Signing configuration

	The executor can build unsigned transactions from `evm.executable` (stored on tasks) and optionally sign and broadcast them. Configure the following environment variables when you want the server to sign or send transactions:

	- `RPC_URL` - JSON-RPC endpoint for the target chain (required for signing/broadcast and gas/fee estimation)
	- `PRIVATE_KEY` - private key used to sign transacadd a api route to get user id of a specific usertions (keep this secret!)
	- `SIGN_TRANSACTIONS` - set to `true` to enable automatic signing by the server (requires `PRIVATE_KEY`)
	- `BROADCAST_TRANSACTIONS` - set to `true` to automatically send signed transactions to the network after signing

	Security notes

	- Storing private keys in environment variables is convenient but not secure for production. Prefer a secrets manager or hardware signer.
	- `SIGN_TRANSACTIONS=true` is an explicit opt-in. The server will not sign transactions unless this is enabled and `PRIVATE_KEY` is present.

	Example (sign + broadcast):

	```bash
	export RPC_URL="https://mainnet.infura.io/v3/<KEY>"
	export PRIVATE_KEY="0x...."
	export SIGN_TRANSACTIONS=true
	export BROADCAST_TRANSACTIONS=true
	npm start
	```

	Wallets and authentication

	- The server can now generate a fresh Ethereum-compatible wallet per user. Create one with:

	```bash
	curl -X POST http://localhost:3000/api/users/0x.../wallet
	```
	External signer (recommended)
	--------------------------------

	For production you should avoid placing private keys or KMS logic directly inside the application process. This project includes an `external` signer adapter that talks to an authenticated REST signing service and keeps private keys in a dedicated signing service (HSM/KMS-backed). Use this adapter by setting:

	- `SIGNER_PROVIDER=external`
	- `SIGNER_SERVICE_URL` — base URL for the signer service (e.g. `https://signer.internal`)
	- `SIGNER_SERVICE_KEY` — API key sent as `x-api-key` header (or use mTLS in production)

	The external signer API should implement the following endpoints:

	- `GET /address` -> { address }
	- `POST /signMessage` body `{ message }` -> { signature }
	- `POST /signTransaction` body `{ tx }` -> { signedTx }
	- `POST /sendTransaction` body `{ signedTx }` -> { txHash }

	This keeps signing responsibilities in a hardened service and removes raw private key handling from the server process. A mock local signer server is included in `test/mockExternalSignerServer.js` that the tests use; replace it with a secure signer in production.

	This will return the wallet `address` and a one-time `recoverySecret` value. Store `recoverySecret` securely — it is only shown once.

	Automatic wallet on task creation
	--------------------------------

	As a convenience, when a client creates a task using `POST /api/tasks` the server will automatically create a wallet for the `userId` if one does not already exist. The response will include the created wallet metadata under `wallet`, identical to what `POST /api/users/:userId/wallet` returns. If the wallet already exists, the response will include `{ exists: true, address: '0x...' }` instead.

	- To retrieve the address later:

	```bash
	curl http://localhost:3000/api/users/0x.../wallet/address
	```

	- To sign arbitrary data or a transaction using the user's wallet:

	```bash
	curl -X POST http://localhost:3000/api/users/0x.../wallet/sign \
		-H "Content-Type: application/json" \
		-d '{"recoverySecret":"<secret>", "payload": {"type":"data", "data":"hello"}}'
	```

	Security & ZK proofs

	- Currently, the server authenticates signing requests using the `recoverySecret` (shown once on wallet creation). This is simple but not recommended for high-security production systems.
	- For stronger, privacy-preserving authentication, integrate a proper zero-knowledge proof or an external signer. Libraries like `snarkjs`/`circom` or rollup-native ZK solutions can be used to verify statements without revealing secrets.

	Detailed Architecture & Security Guide
	-------------------------------------

	This section explains the internal design, security considerations, and recommended production changes in detail.

	1) Core components

	- `src/server.js`: Express server that mounts API routers and starts the scheduler.
	- `src/tasksRouter.js`: Accepts natural language tasks and persists them. Uses `src/nlp.js` to parse the intent and `src/evm.js` to generate ABI calldata + executable tx metadata.
	- `src/evm.js`: Encodes ABI calldata for ERC-20 transfers and generic contract calls. Produces an `executable` object with fields required to build an unsigned transaction: `{ to, data, value, gasLimit, chainId }`.
	- `src/scheduler.js`: Schedules and runs tasks according to `frequencyPerDay` and `totalExecutions`.
	- `src/executor.js`: Builds unsigned transactions, optionally signs them using `PRIVATE_KEY` (opt-in via `SIGN_TRANSACTIONS=true`), fetches fee data via `RPC_URL`, and optionally broadcasts when `BROADCAST_TRANSACTIONS=true`.
	- `src/wallets.js` + `src/walletsRouter.js`: Per-user wallet manager creating a fresh wallet per user, encrypting the keystore with a one-time `recoverySecret`, storing an Argon2 hash of the secret, and providing APIs to sign data/txs and perform challenge-response flows.

	2) Wallet creation & recovery flow (how it works)

	- When you POST to `/api/users/:userId/wallet` the server does the following:
		- Generates a fresh Ethereum-compatible wallet (mnemonic/private key).
		- Generates a one-time `recoverySecret` (32 bytes hex) and returns it in the POST response exactly once. The server only stores an Argon2 hash of this secret — it never stores the raw secret.
		- Encrypts the wallet keystore (JSON) using that `recoverySecret` and stores the encrypted keystore in `data/wallets/<userId>.json` along with the Argon2 hash.

	- You must store the `recoverySecret` securely. It is the only way to decrypt/export/rotate/revoke or sign via the server-side signing endpoints which require proof of the secret (or you can sign locally using the keystore client-side).

	3) Challenge-response ownership proof

	- The server supports proving wallet ownership without sending the secret by:
		- `POST /api/users/:userId/wallet/challenge` — server issues a random challenge and stores it with a short TTL.
		- The client (who controls the private key) signs the challenge with the private key and POSTs the signature to `/api/users/:userId/wallet/verify-challenge`.
		- The server recovers the signer address from the signature and compares it to the stored address. This proves possession of the private key without revealing it.

	This is the recommended way to prove ownership. The client must be able to sign with the wallet's private key (decrypt keystore client-side or hold keys externally).

	4) Signing and broadcasting policies

	- The server will NOT sign transactions by default. To enable automatic signing set `SIGN_TRANSACTIONS=true` and provide `PRIVATE_KEY` in the environment. This is an explicit opt-in and intended for controlled environments.
	- When signing is enabled, the executor will use `RPC_URL` to fetch fee data (EIP-1559 or legacy gasPrice), estimate gas if missing, fill nonce (from provider), sign the transaction and optionally broadcast if `BROADCAST_TRANSACTIONS=true`.

Automated server-side signing using user wallets

Below are secure design options, recommended implementation patterns, and operational controls to enable this safely.

Important safety note: decrypting user keystores inside the app process and using the raw private key to sign transactions increases attack surface. Prefer an HSM-backed external signer or an envelope-encryption approach described below.

Options (ordered by security, recommended first)

  • External signer / HSM (recommended):

    • Keep private keys in an HSM or a dedicated signing service. Configure the executor to call the external signer (we already provide an external adapter). Turn on SIGN_TRANSACTIONS=true and set SIGNER_PROVIDER=external.
    • Pros: minimal key exposure, centralized audit, easier rotation and strong auth (mTLS/API keys).
  • Envelope encryption + KMS-managed KEK (recommended if you must sign inside app):

    • Workflow overview:
      1. When a user creates a wallet, instead of keeping only an Argon2 hash of the recoverySecret, the server optionally takes the recoverySecret and encrypts it with a Key-Encryption-Key (KEK) managed by KMS (or HashiCorp Vault). The encrypted blob (encryptedSecret) is stored in the user's wallet record.
      2. At task execution time, the executor requests KMS to decrypt the encryptedSecret (KMS returns the plaintext recoverySecret to the server process temporarily).
      3. The server uses the plaintext recoverySecret to decrypt the user's keystore JSON and obtain a transient in-memory wallet to sign the task's transaction. The plaintext secret and derived private key are zeroed from memory immediately after signing.
      4. Audit logs record the signing event and which task triggered it.
    • Environment & infra required:
      • A KEK in KMS (e.g., KMS_KEY_ID) or Vault transit key.
      • Server IAM role or credentials with limited Decrypt permission for that KMS key.
      • Config flags: ENABLE_USER_WALLET_SIGNING=true (opt-in), KMS_KEY_ID (or equivalent), SIGN_TRANSACTIONS=true.
    • Pros: keys at rest remain encrypted under KMS; the server cannot decrypt without KMS; KMS provides audit logs.
    • Cons: server process still sees the plaintext recoverySecret briefly during signing — protect process/host thoroughly.
  • Store plaintext secrets on server (NOT RECOMMENDED):

    • Acceptable only for development. Do NOT use in production; this stores raw secrets and is a large security risk.

Suggested API & behavior changes (implementation pointers)

  • Opt-in flag: require users to explicitly opt into automated signing for their wallet. Example: POST /api/users/:userId/wallet/enable-automation with body { recoverySecret } — server verifies secret, encrypts it with KMS and stores encryptedSecret on the wallet record.
  • Executor changes: when executing an EVM task for a user and ENABLE_USER_WALLET_SIGNING=true is set and an encryptedSecret exists for that user, the executor should:
    1. Call KMS (or Vault) to decrypt encryptedSecret.
    2. Use wallets.decryptWallet(userId, secret) to obtain an ethers.Wallet instance in memory.
    3. Sign the transaction (use wallet.signTransaction or wallet.signMessage).
    4. Wipe secret and private key variables from memory immediately after use and log the action with an audit entry.

Example environment variables

  • ENABLE_USER_WALLET_SIGNING=true # opt into user-wallet signing feature on server
  • SIGN_TRANSACTIONS=true # allow executor to sign (global switch)
  • KMS_KEY_ID=arn:aws:kms:... # KMS key used to encrypt user secrets

Audit, rotation and revocation

  • Auditing: log every automated signing event with task id, user id, timestamp, and which signer was used. Don't log raw keys or recoverySecret.
  • Rotation: to rotate keys, re-encrypt the user's recoverySecret with a new KEK or rotate the KMS key according to your provider's recommendations.
  • Revocation: provide an API to disable automation for a user and delete their encryptedSecret so future automated signing attempts fail.

Operational recommendations

  • Limit KMS decrypt permissions to only the service role(s) that need them.
  • Use process-level hardening: run the signer/executor on a dedicated host or container isolated from public network and employ regular security scanning.
  • Add rate limiting and fraud detection on signing actions (e.g., maximum value limits or address allow-lists for automated signing).
  • Prefer requiring a secondary approval for high-value or unusual transactions (manual or off-chain approval flow).

Quick start: enable per-user automated signing (example)

The repository includes an opt-in flow for per-user automated signing. The minimal steps to enable it in a test/dev environment are below. For production, prefer the KMS-backed flow described above.

  1. Set required environment variables (example local/dev):
export ENABLE_USER_WALLET_SIGNING=true
export SERVER_MASTER_KEY="your-local-master-passphrase-or-keystore-secret"
export SIGN_TRANSACTIONS=true
export RPC_URL="https://rpc.your-chain.example"
# Optional: broadcast automatically
export BROADCAST_TRANSACTIONS=false
npm start
  1. Create a wallet for the user (if not already present). The response returns a one-time recoverySecret you must keep safe:
curl -s -X POST http://localhost:3000/api/users/0x0123.../wallet | jq .
# Response includes { address, recoverySecret } when newly created
  1. Opt the user into automated signing (server will encrypt the recoverySecret with SERVER_MASTER_KEY):
curl -s -X POST http://localhost:3000/api/users/0x0123.../wallet/enable-automation \
	-H "Content-Type: application/json" \
	-d '{"recoverySecret":"<the-secret-from-step-2>"}' | jq .
# Returns { enabled: true }
  1. Create a task that results in an EVM executable (example):
curl -s -X POST http://localhost:3000/api/tasks \
	-H "Content-Type: application/json" \
	-d '{"userId":"0x0123...","text":"Send 0 ETH to 0x000... every hour on ethereum"}' | jq .
# When the scheduler runs the task, the server will use the user's wallet to sign the tx.
  1. To opt-out and remove the encrypted secret:
curl -s -X POST http://localhost:3000/api/users/0x0123.../wallet/disable-automation | jq .
# Returns { enabled: false }

Testing

  • Run the unit tests (they include automation flow tests):
npm test -- --runInBand

Security reminder

  • Do NOT hard-code SERVER_MASTER_KEY in production environment variables. Use a KMS or Vault to provide a decrypted master secret only to the signing component, or better yet, decrypt user secrets on-demand via the KMS API.

  • The code includes a simple AES-256-GCM encrypted envelope (src/crypto.js) for local testing. In production, replace this with a KMS-backed encryption/decryption flow so the application process never permanently holds a master key.

      Security warning: Storing `PRIVATE_KEY` as an env var is convenient but risky. For production, integrate with one of the following:
    
      - Hardware signer / HSM (recommended): Use an HSM or cloud KMS that signs transactions without exposing raw keys.
      - External signer service: Run a dedicated signer process behind strong access control and audit logging.
      - Secrets manager: Use a secrets manager (AWS KMS/Secrets Manager, HashiCorp Vault) and fetch keys at runtime with strict IAM policies.
    
      5) Wallet rotation, export, and revocation
    
      - `POST /api/users/:userId/wallet/rotate` — requires `recoverySecret`. Creates a brand-new wallet for the user and returns a new `recoverySecret`. This invalidates the old keystore.
      - `POST /api/users/:userId/wallet/export` — requires `recoverySecret`. Returns the encrypted keystore JSON so the user can decrypt it client-side and import the wallet into MetaMask or another wallet.
      - `POST /api/users/:userId/wallet/revoke` — requires `recoverySecret`. Marks the wallet as revoked and deletes the encrypted keystore from server storage. Revoked wallets cannot be used until rotated or re-created.
    
      6) ZK proofs (recommendation + integration points)
    
      The repository includes placeholders and simple challenge-response flows. For privacy-preserving verification with zero-knowledge proofs you can:
    
      - Client: Generate a ZK proof that you know `recoverySecret` or that you control the private key without revealing it (e.g., sign the challenge and create a ZK proof of possession). Libraries: `snarkjs`, `circom`, `zkSync` tooling, or zkVMs.
      - Server: Add a verification endpoint (e.g. `/api/users/:userId/wallet/verify-zk`) that accepts the ZK proof and verifies it using the chosen verifier circuit. Store only the verification result.
    
      High-level approaches:
    
      - Proof of control of key: Use a circuit that proves knowledge of a preimage of an Ethereum address or a signature over the challenge. The server verifies the proof and accepts it without the raw secret/signature.
      - Proof of recoverySecret knowledge: If you must rely on `recoverySecret`, build a circuit that proves knowledge of the secret without sending it. The server verifies the ZK proof and does not learn the secret.
    
      7) Production hardening checklist (recommendations)
    
      - Do not store raw private keys or raw recovery secrets on the server. Keep only Argon2 hashes and encrypted keystores.
      - Use HSMs or external signers for production signing — never inject private keys via env vars in production containers.
      - Add rate limiting, authentication, and authorization to all wallet and task endpoints.
      - Use TLS everywhere and enable secure cookie/session management if you add a web UI.
      - Move `data/` storage to an immutable/external storage (S3 + encryption or a database). The current JSON files are for development only.
      - Add audit logs that record who triggered a signing action and why. Log signatures only as hashes, not raw signed txs.
    
      8) Example end-to-end flow (recommended)
    
      1. Client creates an account in your app and receives `userId` (must be a 32-byte hash here).
      2. Client requests wallet creation: `POST /api/users/:userId/wallet`.
      3. Server returns `recoverySecret` — client stores it in secure local storage or a password manager.
      4. Client asks server for a challenge: `POST /api/users/:userId/wallet/challenge`.
      5. Client signs challenge with local wallet and posts signature to `POST /api/users/:userId/wallet/verify-challenge`.
      6. Once verified, client can ask server to sign transactions (if you enable that) or export keystore and use local signing.
    
      9) File locations & persistence
    
      - Keystore files: `data/wallets/<userId>.json` (contains address, encrypted keystore JSON, Argon2 hash of `recoverySecret`, timestamps, challenge meta, and revoked flag).
      - Tasks: `data/tasks.json` contains all tasks, their parsed params, `evm` metadata and `executable` unsigned tx data, and execution history.
    
      10) Additional developer notes
    
      - The executor supports EVM chains. To add more blockchains, implement a specific executor module (e.g., `src/solanaExecutor.js`) and wire it into `src/executor.js`.
      - The NLP component (`src/nlp.js`) currently uses a heuristic fallback and an optional OpenAI-compatible call. Replace or extend this with better intent-to-action models as needed.
      - Tests: There is a smoke test `scripts/smoke_wallet_test.js` that demonstrates creating a wallet and signing a challenge. Run it after `npm install`.
    
      If you want, I can now:
      - Integrate a KMS/HSM signer plugin (AWS KMS or HashiCorp Vault) so the server never holds raw keys.
      - Add a `verify-zk` endpoint with a simple example circuit + verification using `snarkjs` (will require writing a small Circom circuit and proof generation instructions).
      - Add RBAC + rate-limiting middleware and logging for production readiness.
    
      Implemented follow-ups
      ---------------------
    
      - Signer plugin interface: `src/signer` supports `local` (env PRIVATE_KEY) and `file` (file at `KMS_KEY_PATH`) providers. Set `SIGNER_PROVIDER=file` and `KMS_KEY_PATH` for file-based demo. For production, implement `aws-kms` or `vault` provider in `src/signer/index.js`.
      - Rate limiting middleware: optional `express-rate-limit` integration; if installed it limits requests to 60/min by default. The middleware file is `src/middleware/rateLimiter.js`.
      - API-key admin middleware: `src/middleware/apiKeyAuth.js` can protect sensitive endpoints when `ADMIN_API_KEY` is set.
      - ZK verification endpoint: skeleton endpoint `POST /api/users/:userId/wallet/verify-zk` added. It returns 501 until you wire a verifier (e.g., `snarkjs`).
    
      API Reference (full list of routes)
      ----------------------------------
    
      All endpoints are mounted under `/api`.
    
      Wallets
    
      - `POST /api/users/:userId/wallet`
      	- Create a fresh wallet for `userId` (32-byte hex). Returns `{ address, recoverySecret }` when a new wallet is created; `recoverySecret` is shown once and must be saved by the user.
      	- Body: none
      	- Response: `201` with `{ address, recoverySecret }` or existing `{ address, alreadyExists:true }`.
    
      - `GET /api/users/:userId/wallet/address`
      	- Returns `{ address }` if wallet exists.
    
      - `POST /api/users/:userId/wallet/sign`
      	- Signs data or a transaction using the user's encrypted keystore. Body: `{ recoverySecret, payload }` where `payload` is `{ type:'data', data: <string> }` or `{ type:'tx', tx: { to,data,value,gasLimit,nonce,chainId,... }}`.
      	- Response: `{ result: { signature } }` or `{ result: { signedTx } }`.
    
      - `POST /api/users/:userId/wallet/challenge`
      	- Server issues a challenge string and TTL. Response: `{ challenge, expiresAt }`.
    
      - `POST /api/users/:userId/wallet/verify-challenge`
      	- Verify a signature over the last challenge. Body: `{ signature }`. Response: `{ ok:true, recovered }` on success.
    
      - `POST /api/users/:userId/wallet/verify-zk`
      	- ZK verification endpoint (skeleton). Body: `{ proof, publicSignals }`. Currently returns `501` until you integrate a verifier.
    
      - `POST /api/users/:userId/wallet/export`
      	- Export encrypted keystore. Body: `{ recoverySecret }`. Response: `{ keystore }`.
      	- This endpoint can be protected by `ADMIN_API_KEY` (set in env) — see below.
    
      - `POST /api/users/:userId/wallet/rotate`
      	- Rotate wallet (create a new wallet) for the user. Body: `{ recoverySecret }`. Returns `{ address, recoverySecret }` with the new secret. Can be protected by `ADMIN_API_KEY`.
    
      - `POST /api/users/:userId/wallet/revoke`
      	- Revoke wallet (requires `{ recoverySecret }`). Marks the wallet revoked and removes the keystore. Can be protected by `ADMIN_API_KEY`.
    
      Tasks
    
      - `POST /api/tasks`
      	- Create a natural-language task. Body: `{ userId, text, overrides? }`.
      	- `userId`: 32-byte hex identifying the user. `text`: natural language instruction. `overrides` object may include `contractAddress`, `value`, `gasLimit`, `chainId`, `functionName`, or other params.
      	- The server runs NLP on `text`, produces parsed params, generates `evm` calldata and an `executable` unsigned tx when possible, persists the task, and schedules it.
      	- Response: `201` with `{ task, nlp }`.
    
      - `GET /api/users/:userId/tasks`
      	- List tasks for a user. Response: `{ tasks: [...] }`.
    
      Other
    
      - `GET /` – health/status
      - `GET /health` – returns 200
      - `GET /hello/:name` – example
    
      Admin & protection
    
      - If you set `ADMIN_API_KEY` in the environment, the server will protect sensitive wallet operations (`export`, `rotate`, `revoke`) with `x-api-key` header or `?apiKey=` query param. Set `ADMIN_API_KEY` to a strong secret in production and guard access.
      - Rate limiting: If `express-rate-limit` is installed, the server enables a default rate limiter of 60 requests/min. You can tune it in `src/middleware/rateLimiter.js`.
    
      How to enable KMS/HSM signing in production
      -------------------------------------------
    
      1. Implement a signer provider under `src/signer` named `aws-kms` or `vault` that exposes `getAddress()`, `signTransaction(tx)`, `signMessage(msg)`, and `sendTransaction(signedTx)`.
      2. Set `SIGNER_PROVIDER=aws-kms` (or `vault`) and configure provider-specific environment variables or plugins (e.g., AWS credentials and KMS key id).
      3. Set `SIGN_TRANSACTIONS=true` if the executor should sign automatically, and `BROADCAST_TRANSACTIONS=true` to publish transactions.
    
      Example: file-based signer (demo)
    
      ```bash
      # create a file with a private key
      echo "0x...privatekey..." > kms_key.txt
      export KMS_KEY_PATH=$PWD/kms_key.txt
      export SIGNER_PROVIDER=file
      export SIGN_TRANSACTIONS=true
      export BROADCAST_TRANSACTIONS=false
      npm start
      ```
    

Cross-platform notes

  • The server binds to 0.0.0.0 by default so it works inside containers and on all platforms.
  • Graceful shutdown listens to SIGINT and SIGTERM; a small Windows compatibility shim is included.

Next steps

  • Add more routes or middleware as needed.
  • Use a process manager (PM2, systemd, Docker) for production deployments. Notes and next steps (pick one) Add a Dockerfile and docker-compose.yml.

Run with PM2 (process manager) locally or inside Docker.

Docker (build + run)

# build the image
docker build -t express-crossplatform-server:latest .

# run (binds host:3000 -> container:3000)
docker run -p 3000:3000 -v "$PWD/data":/usr/src/app/data --env PORT=3000 express-crossplatform-server:latest

Using docker-compose

docker-compose up --build -d

PM2

  • The project includes pm2 and an ecosystem.config.js file.
  • Start with PM2 locally (after npm install):
# start with pm2-runtime (recommended for containers)
npx pm2-runtime ecosystem.config.js

# or, for development with pm2 installed globally
# pm2 start ecosystem.config.js

Notes

  • When running inside Docker we use pm2-runtime so PM2 runs in the container foreground and respects container lifecycle.
  • ./data is mounted into the container by the compose file to persist data/tasks.json between runs.

About

Intent Based EVM Task Automated Execution. Prototype , do not use for Production Purposes

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages