Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions .dockerignore

This file was deleted.

18 changes: 2 additions & 16 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,18 +1,4 @@
logs
*.log
npm-debug.log*
.DS_Store
.idea

coverage
node_modules
build
public/static
.env.local
.env.development.local
.env.test.local
.env.production.local

src/config.ts

dev.sh
logs
*.log
Comment thread
greptile-apps[bot] marked this conversation as resolved.
38 changes: 0 additions & 38 deletions Dockerfile

This file was deleted.

122 changes: 66 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,86 +1,96 @@
# [Ecency vision][ecency_vision] – API

### Build instructions
The API proxy service behind [Ecency](https://ecency.com) — routes search,
auth, wallet/portfolio, and private-API traffic to the right backends with
health-aware failover across Hive RPC nodes.

##### Requirements
Implemented in **C# / ASP.NET Core (.NET 10)** under [`dotnet/`](dotnet/). It
replaced the original Node/Express implementation as a verified drop-in
(byte-identical responses across a 300-case differential parity suite; the
history of that migration is in the git log and `dotnet/README.md`). The last
Comment on lines +7 to +10

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align the documented parity case count.

This says the suite has 300 cases, while dotnet/README.md and the latest recorded result state 305 cases. Keep the verification claims consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 7 - 10, Update the parity suite case count in
README.md from 300 to 305, matching dotnet/README.md and the latest recorded
verification result while preserving the surrounding migration and drop-in
compatibility claims.

Node build remains available as the `ecency/api:node-legacy` image tag.

- node ^12.0.0
- yarn
## Quick start

##### Clone
`$ git clone https://github.com/ecency/vision-api`
Requirements: [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
(or just Docker).

`$ cd vision-api`

##### Install dependencies
`$ yarn`
```bash
git clone https://github.com/ecency/vision-api
cd vision-api/dotnet

##### Edit config file or define environment variables
`$ nano src/config.ts`
dotnet build EcencyApi/EcencyApi.csproj # compile
dotnet test EcencyApi.Tests/EcencyApi.Tests.csproj # test suite

##### Environment variables
API_PORT=4000 PRIVATE_API_ADDR=... dotnet run --project EcencyApi -c Release
curl http://localhost:4000/healthcheck.json
```

* `PRIVATE_API_ADDR` - private api endpoint
* `PRIVATE_API_AUTH` - private api auth
* `HIVESIGNER_SECRET` - hivesigner client secret
* `SEARCH_API_ADDR` - hivesearcher api endpoint
* `SEARCH_API_SECRET` - hivesearcher api auth token
* `BLOCKSTREAM_CLIENT_ID` - optional OAuth client identifier used to request temporary access tokens for the Blockstream Explorer Enterprise API (BTC fallback).
* `BLOCKSTREAM_CLIENT_SECRET` - optional OAuth client secret paired with the client identifier for generating Blockstream access tokens.
* `HELIUS_API_KEY` - optional Helius API key added as an extra Solana RPC fallback.
* `ETH_RPC_URLS` / `BNB_RPC_URLS` / `SOL_RPC_URLS` / `BTC_ESPLORA_URLS` - optional comma-separated endpoint lists overriding the built-in public provider pools (see `src/server/chain-providers.ts`).
Or with Docker:

##### Start api in dev
`$ yarn start`
```bash
cd dotnet
docker build -t ecency/api -f Dockerfile .
docker run -it --rm -p 4000:4000 \
-e PRIVATE_API_ADDR=https://api.example.com \
-e PRIVATE_API_AUTH=verysecret \
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
ecency/api
```

##### Pushing new code / Pull requests
## Environment variables

- Make sure to branch off your changes from `main` branch.
- Make sure to run `yarn test` and add tests to your changes.
- Code on!
| variable | purpose |
|---|---|
| `API_PORT` | listen port (default `4000`) |
| `PRIVATE_API_ADDR` | private api endpoint |
| `PRIVATE_API_AUTH` | private api auth (base64-encoded JSON header object) |
| `HIVESIGNER_SECRET` | hivesigner client secret |
| `SEARCH_API_ADDR` | hivesearcher api endpoint |
| `SEARCH_API_SECRET` | hivesearcher api auth token |
| `STRIPE_INTERNAL_SECRET` | shared secret for the Stripe money endpoints (unset = they fail closed) |
| `TURNSTILE_SECRET` | Cloudflare Turnstile secret for account-create captcha |
| `CAPTCHA_MODE` | `hard` (default) or `off` (operator break-glass) |
| `BLOCKSTREAM_CLIENT_ID` / `BLOCKSTREAM_CLIENT_SECRET` | optional Blockstream Enterprise esplora auth (BTC fallback) |
| `HELIUS_API_KEY` | optional Helius API key added as an extra Solana RPC fallback |
| `ETH_RPC_URLS` / `BNB_RPC_URLS` / `SOL_RPC_URLS` / `BTC_ESPLORA_URLS` | optional comma-separated endpoint lists overriding the built-in chain provider pools |
| `Logging__LogLevel__Default` | log level (default `Warning`; set `Information` for per-request logs) |

## Docker
## Swarm

You can use official `ecency/api:latest` image to run Vision locally, deploy it to staging or even production environment. The simplest way is to run it with following command:
Deploy with the example stack file (which also bounds container log size):

```bash
docker run -it --rm -p 3000:3000 ecency/vision:latest
cd dotnet
docker stack deploy -c docker-compose.yml vision-api

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Point the Swarm stack at the published image

In the Swarm path documented here, docker stack deploy does not build the local dotnet/Dockerfile; Docker's stack-deploy docs show it reports Ignoring unsupported options: build and then deploys the service image from the compose file. The referenced dotnet/docker-compose.yml still names ecency/api-csharp:latest, while CI in .github/workflows/main.yml publishes ecency/api:latest and ecency/api:sha-*, so operators following these new instructions can deploy a stale/wrong image or fail to pull instead of deploying the current build.

Useful? React with 👍 / 👎.

```

Configure the instance using following environment variables:

* `PRIVATE_API_ADDR`
* `PRIVATE_API_AUTH`
* `HIVESIGNER_SECRET`
* `SEARCH_API_ADDR`
* `SEARCH_API_SECRET`
* `BLOCKSTREAM_CLIENT_ID`
* `BLOCKSTREAM_CLIENT_SECRET`
* `HELIUS_API_KEY`
* `ETH_RPC_URLS`
* `BNB_RPC_URLS`
* `SOL_RPC_URLS`
* `BTC_ESPLORA_URLS`
## Deployment & rollback

CI (`.github/workflows/main.yml`) tests every PR; on merge to main it builds
the image, pushes `ecency/api:latest` + `ecency/api:sha-<commit>`, and rolls
out by immutable digest. Roll back by redeploying any previous tag:

```bash
docker run -it --rm -p 3000:3000 -e PRIVATE_API_ADDR=https://api.example.com -e PRIVATE_API_AUTH=verysecretpassword ecency/api:latest
docker service update --image ecency/api:sha-<previous-commit> vision_vapi
docker service update --image ecency/api:node-legacy vision_vapi # pre-rewrite Node build
Comment on lines +77 to +78

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Fix the rollback service target.

The stack is deployed as vision-api with an app service, so Docker Swarm will create vision-api_app; neither rollback command targets the service declared by dotnet/docker-compose.yml (vision_vapi). These commands will fail unless another stack defines that service.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 74 - 75, Update both rollback commands in the README
to target the deployed Docker Swarm service vision-api_app instead of
vision_vapi, while preserving the existing image tags and rollback descriptions.

```

### Swarm
## Pushing new code / Pull requests

You can easily deploy a set of vision instances to your production environment, using example `docker-compose.yml` file. Docker Swarm will automatically keep it alive and load balance incoming traffic between the containers:
- Branch off your changes from the `main` branch.
- Run `dotnet test dotnet/EcencyApi.Tests/EcencyApi.Tests.csproj` and add tests
for your changes (the test suite gates every PR in CI).
- Code on!

```bash
docker stack deploy -c docker-compose.yml vision-api
```
## More

`dotnet/README.md` has the full details: architecture and layout, the Hive RPC
failover design, the differential parity harness, benchmark methodology and
results, and how to regenerate the crypto golden vectors.

## Issues

To report a non-critical issue, please file an issue on this GitHub project.

If you find a security issue please report details to: security@ecency.com

We will evaluate the risk and make a patch available before filing the issue.

[//]: # 'LINKS'
[ecency_vision]: https://ecency.com
[ecency_vision]: https://github.com/ecency/vision
3 changes: 0 additions & 3 deletions crowdin.yml

This file was deleted.

45 changes: 0 additions & 45 deletions docker-compose.yml

This file was deleted.

9 changes: 9 additions & 0 deletions dotnet/EcencyApi/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@
builder.Logging.ClearProviders();
builder.Logging.AddSimpleConsole();

// Quiet by default: ASP.NET's per-request Information logging (~4 lines per
// request) fills unbounded docker json-file logs on the origin hosts. Warnings
// and errors only, unless the standard Logging__LogLevel__Default env var is
// set (then configuration rules apply as usual).
if (Environment.GetEnvironmentVariable("Logging__LogLevel__Default") is null)
{
builder.Logging.SetMinimumLevel(LogLevel.Warning);
}

var app = builder.Build();

// Global error handling — the Node app's error middleware sends a plain
Expand Down
18 changes: 14 additions & 4 deletions dotnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,20 @@ request variants (every route × empty/populated/bad-auth bodies, plus error and
fallback probes) at the running Node image and the C# build, then diffs status,
content-type, and body.

The request catalog is generated from the route table in
`EcencyApi/Handlers/Routes.cs` (override with `VAPI_ROUTES_CS`, or point
`VAPI_INDEX_TSX` at a legacy Express `index.tsx`). The Node reference is the
`ecency/api:node-legacy` image — run it with a **restart policy**
(`--restart unless-stopped`): the legacy build crashes outright on the
malformed hs-token-refresh probe, and without auto-restart the remainder of
the capture fails.

```bash
# 1. mock upstream (records every proxied request)
python3 parity/mock_upstream.py &

# 2. run Node reference and C# build against the same mock (see parity/ for env)
# Node on :14000, C# on :14001
# 2. run the node-legacy reference and the C# build against the same mock
# (see parity/ for env) — Node on :14000, C# on :14001

# 3. capture + diff (node2 = second Node run, marks nondeterministic cases loose)
python3 parity/driver.py run node http://127.0.0.1:14000
Expand Down Expand Up @@ -211,6 +219,7 @@ the same defaults, as the Node service (`Config.cs` mirrors `src/config.ts`):
| `HELIUS_API_KEY` | optional extra Solana RPC provider | unset |
| `ETH_RPC_URLS` / `BNB_RPC_URLS` / `SOL_RPC_URLS` / `BTC_ESPLORA_URLS` | comma-separated overrides for the chain provider pools | built-in pools |
| `PUBLIC_DIR` | static assets directory | `<app>/public` |
| `Logging__LogLevel__Default` | log level; unset defaults to `Warning` (set `Information` for per-request logs) | `Warning` |

```bash
API_PORT=4000 \
Expand Down Expand Up @@ -278,8 +287,9 @@ API_PORT=4000 ... dotnet /opt/vapi-csharp/EcencyApi.dll
Only needed if the dhive dependency of the Node service changes:

```bash
# point at a checkout that has the Node service's node_modules installed
VAPI_NODE_MODULES=/path/to/vision-api/node_modules \
# the repo no longer carries Node dependencies; install the two packages anywhere
mkdir -p /tmp/vectors && cd /tmp/vectors && npm install @hiveio/dhive js-base64
VAPI_NODE_MODULES=/tmp/vectors/node_modules \
Comment thread
coderabbitai[bot] marked this conversation as resolved.
node tools/gen-vectors.js > EcencyApi.Tests/fixtures/crypto-vectors.json
dotnet test EcencyApi.Tests/EcencyApi.Tests.csproj
```
7 changes: 7 additions & 0 deletions dotnet/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ services:
- BTC_ESPLORA_URLS
ports:
- "4000:4000"
# Bound container logs so they can never grow unchecked on the host
# (the service also defaults to Warning-level logging).
logging:
driver: json-file
options:
max-size: "20m"
max-file: "3"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
networks:
default: null
deploy:
Expand Down
Binary file added dotnet/parity/__pycache__/driver.cpython-312.pyc
Binary file not shown.
35 changes: 23 additions & 12 deletions dotnet/parity/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
from pathlib import Path

HERE = Path(__file__).parent
# The Express route table drives the catalog. Defaults to the TS source two
# levels up (repo/src/server/index.tsx); override with VAPI_INDEX_TSX.
INDEX_TSX = Path(os.environ.get(
"VAPI_INDEX_TSX", HERE.parent.parent / "src" / "server" / "index.tsx"))
# The route table drives the catalog. Defaults to the C# Routes.cs one level
# up; override with VAPI_ROUTES_CS (or point VAPI_INDEX_TSX at a legacy
# Express index.tsx to test an old Node build).
ROUTES_CS = Path(os.environ.get(
"VAPI_ROUTES_CS", HERE.parent / "EcencyApi" / "Handlers" / "Routes.cs"))
INDEX_TSX = os.environ.get("VAPI_INDEX_TSX")
MOCK = os.environ.get("VAPI_MOCK_URL", "http://127.0.0.1:15999")

# ---------------------------------------------------------------- catalog ---
Expand Down Expand Up @@ -105,15 +107,24 @@


def load_routes():
"""Parse .get/.post route lines out of index.tsx."""
src = INDEX_TSX.read_text()
"""Parse the route table: C# Routes.cs (default) or a legacy index.tsx."""
routes = []
for m in re.finditer(r'\.(get|post)\("(\^?[^"]+?)\$?",', src):
method, path = m.group(1).upper(), m.group(2).lstrip("^")
if path in ("*",):
continue
# substitute :params (incl. regex-constrained ones)
concrete = re.sub(r":(\w+)(\([^)]*\))?", lambda p: PARAM_VALUES.get(p.group(1), "x"), path)
if INDEX_TSX:
src = Path(INDEX_TSX).read_text()
for m in re.finditer(r'\.(get|post)\("(\^?[^"]+?)\$?",', src):
method, path = m.group(1).upper(), m.group(2).lstrip("^")
if path in ("*",):
continue
concrete = re.sub(r":(\w+)(\([^)]*\))?",
lambda p: PARAM_VALUES.get(p.group(1), "x"), path)
routes.append({"method": method, "path": concrete, "template": path})
return routes
src = ROUTES_CS.read_text()
for m in re.finditer(r'app\.Map(Get|Post)\("([^"]+)"', src):
method, path = m.group(1).upper(), m.group(2)
# {param} / {param:regex(...)} -> concrete sample values
concrete = re.sub(r"\{(\w+)[^}]*\}",
lambda p: PARAM_VALUES.get(p.group(1), "x"), path)
routes.append({"method": method, "path": concrete, "template": path})
return routes

Expand Down
Loading
Loading