conet-l0d — Linux runtime for web3://
Maturity: Under development.
conet-l0d is the Linux server/client runtime for the
web3:// Application Protocol. It publishes
local services through CoNET Layer Minus and opens remote wallet-addressed
services as local endpoints.
Product overview: web3:// wallet-addressed applications
Protocol contract: web3:// Application Protocol
Source: CoNET-project/CoNET-L0D
Crate design: whitepaper
Runtime roles
| Role | Command/config | Use |
|---|---|---|
| Persistent-stream server (L1 overlay hub primary) | --proxyDuplex HOST:PORT / [[l0.proxy_duplex]] |
Publish a persistent bidirectional TCP service |
| Persistent-stream client (L1 overlay spoke primary) | --clientDuplex web3://HOST:PORT / l0.client_duplex |
Expose remotes through 127.0.0.1; the same logical port may map to several remotes |
| Request/response server | --proxy HOST:PORT / [[l0.proxies]] |
Publish a bounded local request/response service (not the L1 geth/beacon primary path) |
| Signed Web gateway | gateway + [gateway] |
Map signed GET/HEAD requests to a loopback HTTP origin |
Linux servers and Linux clients can use this binary. Windows, macOS, Android,
iOS, and browser applications implement the same protocol in client code and
do not require conet-l0d.
Build
git clone https://github.com/CoNET-project/CoNET-L0D.git
cd CoNET-L0D
cargo test
cargo build --release
sudo install -m 0755 target/release/conet-l0d /usr/local/sbin/conet-l0d
Validate the example before enabling any route:
conet-l0d check-config --config config/conet-l0d.example.toml
Commands
| Command | Purpose |
|---|---|
check-config |
Validate TOML without opening a network session |
resolve |
Parse a web3:// locator and show a configured mapping when available |
start |
Run configured server proxies and client endpoints |
gateway |
Run the signed GET/HEAD application gateway |
status |
Print recorded runtime state |
stop |
Signal the recorded process and clean runtime state |
teardown |
Remove stale daemon-owned runtime state |
conet-l0d resolve \
'web3://0x1111111111111111111111111111111111111111:9443' \
--config /etc/conet-l0d.toml
conet-l0d start --config /etc/conet-l0d.toml
conet-l0d status --config /etc/conet-l0d.toml
conet-l0d stop --config /etc/conet-l0d.toml
CLI endpoint flags override the corresponding endpoint lists:
conet-l0d start \
--config /etc/conet-l0d.toml \
--proxyDuplex 127.0.0.1:9443
conet-l0d start \
--config /etc/conet-l0d.toml \
--clientDuplex 'web3://ExactPeer.web3:9443'
--proxy and --proxyDuplex are repeatable. --clientDuplex is repeatable.
The same logical port may map to several remotes.
--debugSI (optional SI URL, domain, or route key id) pins exclusive/temp
mailbox B for laboratory isolation. Without the flag, mailbox B is a random
on-chain SI. Entry A/C stays random and must not equal B.
Locator
The application endpoint grammar is:
web3://0x<40-hex>:<port>
web3://<exact-tag>.web3:<port>
Examples:
web3://0x1111111111111111111111111111111111111111:443
web3://ExampleMerchant.web3:9443
An exact BeamioTag match is required. Never select a non-exact search result
or results[0].
Base configuration
Copy config/conet-l0d.example.toml and keep L0 disabled until the real
wallet identities and key files are configured:
state_path = "/run/conet-l0d/state.json"
[identity]
locator = "web3://0x1111111111111111111111111111111111111111/app"
[l0]
enabled = false
rpc = "https://rpc1.conet.network"
address_pgp = "0x684b0ac760cEE9c9b85de36d69746420648Cf9e2"
route_register_url = "https://beamio.app/api/regiestChatRoute"
si_pool_from_contract = true
# Optional overrides only when si_pool_from_contract = false:
# entries = []
# listen_entries = []
SI entry discovery (GuardianNodesInfoV6)
Default discovery does not depend on a static TOML entries /
listen_entries list.
With si_pool_from_contract = true (default), startup pages
GuardianNodesInfoV6 (0xBC6b53065b5647261396d002bDBA0d3396E0722f) via
l0.rpc (rpc1 or publicrpc) using getAllNodes — the same path as the
client UI / SilentPass: load all nodes → local pool → periodic refresh.
When listen / POST / an l0_connect pipe needs an SI entry:
- Random-pick a candidate from the pool.
- Soft-qualify with a short TCP
:80probe tohttp://{domain}.conet.network. - Hand the URL back to the caller; on failure (including SI
pool_full) cool the host down and pick another.
Do not invent a domain. Outbound application delivery uses an entry distinct
from destination mailbox B; mailbox receive uses another healthy entry
distinct from B. Static entries / listen_entries remain only as an
explicit override when si_pool_from_contract = false. Gateway mode still
uses its own listen_entries / post_entries (independent of the overlay
SI pool).
Live occupancy commands are l0_listen and l0_connect only
(listenKind: "l0"). Do not send the retired command: "mining" +
listenKind: "l0" variant. Chat offers still use mining +
listenKind: "chat" (proxy hubs need this for inbound duplex_offer;
pure --clientDuplex spokes no longer open a static Chat SSE). Never send
mining + listenKind: "duplex", and never treat SI duplex_* /
p2p_stream_* / listenKind: "l1p2p" as current SI.
Server profile
Publish a local request/response service:
[l0]
enabled = true
# identity, key files, and existing entries omitted
[[l0.proxies]]
host = "127.0.0.1"
port = 8080
Publish a persistent bidirectional service:
[[l0.proxy_duplex]]
host = "127.0.0.1"
port = 9443
The host and port identify the local origin. Keep that service on loopback or a private network unless it is intentionally public by another route.
Each accepted duplex connection receives its own session, temporary communication identity, queue, occupied line, and upstream TCP socket. Several connections may use the same logical port, but they do not share socket or identity state.
Client profile
Open remote duplex services as local TCP endpoints:
[l0]
enabled = true
# identity, key files, and existing entries omitted
client_duplex = [
"web3://ExactPeer.web3:9443",
"web3://0x2222222222222222222222222222222222222222:7443",
]
The same logical port may map to several remotes. Use two entries for two
hubs on :8400, or :8400 and :4200 for two services. The same
(host, PORT) listed twice is rejected.
The Linux runtime binds each line on 127.0.0.1. The preferred listener is
the logical port. If that port is already occupied, the runtime walks
port + 10000, port + 20000, and so on. An optional @LOCAL suffix is a
Linux bind pin only — it is not part of the public web3:// locator
contract. Prefer the protocol form web3://0x…:8400. Use @18400 only when
the local application must hard-code a loopback port. Read the startup log
or status and configure the local application with the actual endpoint.
@LOCAL does not change the offer. web3://0x66dC…:8400@18400 still
offers remote service 8400. If this host also publishes --proxyDuplex
on 8400, inbound offers to this billing wallet use the proxy origin.
Outbound client sessions to another wallet's 8400 must not attach
that proxy drain. A current Linux implementation still keys
maybe_start_proxy_drain by the logical port integer; that is a runtime
defect, not a reason to put @18400 into the public URI.
One host may be both a client and a published wallet. Outbound
client_duplex lists only the wallets that host must join. Inbound
proxy_duplex publishes the local origin so any conforming client can
dial this host's billing EOA. The host does not add those callers to
client_duplex and does not dial them back. Laboratory evidence:
L1 overlay lab.
Wallet and key roles
The runtime separates:
| Role | Purpose |
|---|---|
| Application target EOA | Stable destination in the web3:// locator |
| Routing EOA + PGP | Communication identity registered through AddressPGP |
| Billing EOA | Signs and settles paid mailbox/channel commands |
| Temporary line identity | Isolates one accepted duplex connection |
For a server profile:
[l0]
billing_eoa = "0x..."
billing_eth_key_file = "/etc/conet-l0d/billing.eth"
billing_pgp_file = "/etc/conet-l0d/billing-pgp.asc"
routing_eoa = "0x..."
routing_key_file = "/etc/conet-l0d/routing-pgp.asc"
routing_eth_key_file = "/etc/conet-l0d/routing.eth"
mailbox_route_pgp_file = "/etc/conet-l0d/mailbox-route.asc"
Keep secret files local, mode 0600, and outside the repository. Do not pass
private keys in shell arguments, environment variables, logs, or service
unit text.
Multi-port servers may use [[l0.channels]] to assign one communication
identity per logical port. This avoids competing exclusive receive sessions
for one routing wallet.
Persistent-stream bootstrap
The implemented sequence is:
- Accept one local TCP connection.
- Create and register a temporary communication route for that line.
- Wait until AddressPGP returns the expected route key.
- Establish the temporary receive session.
- Send a signed
duplex_offer, optionally with the first application bytes. - The remote server validates billing identity, target, and logical port.
- The remote server opens exactly one configured upstream socket.
duplex_acceptis mailbox work to the initiator temp mailbox (inner user PGP, noNoPush). SI copies it onto idlel0_listenbyuserPgpKeyId.- The initiator occupies the responder mailbox; only then the responder reverse-occupies. Occupying the initiator SSE first drops the accept.
- If the proxy temporary line fails, the hub posts
duplex_rejectto the client L0d request listen and does not retry that wallet. The client application reconnects with a new offer. - Both sides carry ordered encrypted frames on the occupied pipes.
- Close or retry that line independently.
Registration HTTP 200 is queue admission only. AddressPGP visibility and
the receive session must be ready before application bytes are treated as
deliverable.
The runtime uses existing Layer Minus listen, routing, and occupied-line primitives. It does not require a new SI command family.
Signed gateway
Use a dedicated config for the gateway:
[gateway]
rpc = "https://rpc1.conet.network"
upstream = "http://127.0.0.1:8080"
listen_entries = []
post_entries = []
routing_eoa = "0x..."
routing_key_file = "/etc/conet-web3-gateway/gateway-pgp.asc"
routing_eth_key_file = "/etc/conet-web3-gateway/gateway.eth"
mailbox_route_pgp_file = "/etc/conet-web3-gateway/mailbox-route.asc"
allowed_methods = ["GET", "HEAD"]
max_body_bytes = 8388608
request_timeout_seconds = 15
Then run:
conet-l0d gateway --config /etc/conet-l0d-gateway.toml
The gateway validates request type, expiry, target, method, path, and EIP-191 signer; connects only to its configured loopback origin; and encrypts the response to the caller's registered user PGP key.
systemd
The repository contains:
systemd/conet-l0d.servicesystemd/conet-l0d-gateway.service
Install the binary, config, and restricted key files before enabling a unit. Use a dedicated Unix user where practical. The daemon must not run as the same account that owns validator keys or unrelated production secrets.
Verification
Check evidence at each layer:
| Layer | Evidence |
|---|---|
| Configuration | check-config succeeds |
| Identity | exact EOA/tag and AddressPGP route are visible |
| Entry path | SI pool (or override list) returns a healthy entry that accepts the encrypted request |
| Session | offer and accept refer to the expected wallet, port, and session |
| Data | frame counters continue in both directions |
| Origin | configured local service accepts the upstream connection |
| Application | correlated decrypted response or application protocol handshake succeeds |
A running process or entry HTTP 200 alone is not an end-to-end test.
Laboratory evidence that unmodified geth / Prysm can peer through this
runtime by wallet locator (not public IP:port) is recorded in
L1 overlay lab — wallet-addressed geth / Prysm.
That page is a lab review, not the public L1 join path.
Failure order
- Validate the exact locator and wallet identity.
- Confirm the routing and mailbox PGP records.
- Confirm the SI pool refreshed from GuardianNodesInfoV6 (or that static override entries are existing, healthy Guardians when the pool is off).
- Confirm the local client endpoint or server origin is listening.
- Match offer, accept, and session IDs.
- Confirm encrypted frames flow both ways.
- Inspect the origin application's own protocol result.
Do not convert a timeout or routing failure into a successful empty result.