PORTAL v2

Wallet and ENS

Portal uses Ethereum-style signatures in several different places. They are related, but they do not all mean “connect a browser wallet”.

Identity Surfaces

SurfaceKey materialPurpose
Tunnel identityLocal identity.json secp256k1 private key, or BIP-39 mnemonic plus derivation pathSigns SIWE lease registration challenges
Relay identityRelay IDENTITY_PATH/identity.json secp256k1 private key, or BIP-39 mnemonic plus derivation pathSigns relay descriptors, lease access tokens, and ENS base-domain address
Relay admin tokenADMIN_TOKENSigns in to /admin and authorizes relay policy changes
Agent walletOptional browser wallet allowlistReads loopback agent status through /agent/status
ENS gasless DNSDNSSEC plus ENS1 ... TXT recordsLets ENS-aware clients resolve the relay domain and lease hostnames to Portal identities

Tunnel SIWE Registration

Tunnel registration always uses a SIWE challenge internally:

  1. The tunnel creates or loads a local identity from identity.json.
  2. The tunnel asks the relay for /sdk/register/challenge.
  3. The relay returns a SIWE message with statement Register a portal lease.
  4. The tunnel signs that message with the local identity private key using Ethereum personal_sign semantics.
  5. The relay verifies the signature and returns a lease-scoped access token.
  6. The access token is used for renew, unregister, reverse connect, keyless signing access, and UDP backhaul authentication.

This does not require MetaMask or a user wallet. It is accountless identity proof based on the local tunnel key. identity.json may store a raw private_key, or a BIP-39 mnemonic with derivation_path such as m/44'/60'/0'/0/0.

There is no --auth siwe flag. The current CLI command is:

portal expose 3000 --name myapp

Use a stable identity path when the lease identity must survive working directory changes:

portal expose 3000 
  --name myapp 
  --identity-path ~/.config/portal/myapp.identity.json

The public lease name is a single DNS label such as myapp. It is not an ENS name such as alice.eth.

Relay Admin Token Login

The relay admin API uses a configured token. Set ADMIN_TOKEN to a long random value before exposing the admin UI or policy API.

Example:

ADMIN_TOKEN=$(openssl rand -hex 32)

Admin token flow:

  1. POST /api/admin/auth/login with { "token": "<admin-token>" }.
  2. The relay returns an access_token.
  3. Admin endpoints require Authorization: Bearer <access_token>.

The token returned by login is the configured admin token; browser logout clears the local stored token.

Agent Wallet Login

The local agent also exposes SIWE wallet auth endpoints:

/agent/auth/challenge
/agent/auth/login
/agent/auth/logout
/agent/auth/status

Agent wallet access is intentionally narrow:

  • agent.allowed_wallets restricts which wallet addresses can sign in.
  • when allowed_wallets is empty, any wallet can sign in to the loopback auth endpoint.
  • wallet-authenticated requests can read /agent/status.
  • config mutation, tunnel changes, relay changes, shutdown, and multi-hop edits still require the bearer token in <state_dir>/agent-endpoint.json.

Example:

[agent]
allowed_wallets = ["0x1234567890abcdef1234567890abcdef12345678"]

See Portal Agent for the control API details.

ENS Gasless DNS Import

ENS gasless DNS import is optional relay-side DNS automation. It is separate from tunnel registration and admin token login.

When enabled, Portal uses the configured DNS provider to:

  • enable or inspect DNSSEC for the relay base domain
  • publish ENS1 ... TXT records for the base domain
  • publish ENS1 ... TXT records for lease hostnames
  • keep A records for lease hostnames in sync with the relay public IPv4
  • remove lease hostname records when leases unregister or expire

Portal writes TXT values in this shape:

ENS1 0x238A8F792dFA6033814B18618aD4100654aeef01 <address>

The base-domain address is the relay identity address. Lease hostname addresses come from the tunnel identity that registered each lease.

ENS gasless automation does not perform an onchain ENS claim transaction. It only prepares DNSSEC-backed DNS records for ENS-aware clients.

Enable ENS Gasless

Requirements:

  • public relay domain, not localhost
  • ACME_DNS_PROVIDER=cloudflare, gcloud, route53, or vultr
  • provider credentials with DNS write access
  • ENS_GASLESS_ENABLED=true
  • DNSSEC active at the parent zone

Hetzner and Njalla are supported for managed ACME DNS automation, but not for ENS gasless automation because Portal does not automate DNSSEC signing for those providers.

Example:

PORTAL_URL=https://portal.example.com
IDENTITY_PATH=/portal-certs
ACME_DNS_PROVIDER=cloudflare
CLOUDFLARE_TOKEN=cf_xxxxxxxxxxxxxxxxx
ENS_GASLESS_ENABLED=true

The same provider is used for ACME DNS-01, managed A records, ECH HTTPS records, DNSSEC, and ENS TXT records. If manual fullchain.pem and privatekey.pem already exist under IDENTITY_PATH, Portal keeps using those certificate files and still uses the provider for ENS/DNS automation.

DNSSEC And Registrar State

DNSSEC has two sides:

  • the DNS provider signs the hosted zone
  • the registrar publishes the DS record at the parent zone

Portal can automate provider-side setup for supported providers. It cannot always publish the registrar-side DS record. If /sdk/domain reports a pending DNSSEC state and a ds_record, copy that DS record into the registrar’s DNSSEC settings and wait for propagation.

Check ENS Status

The relay exposes ENS status through /sdk/domain:

curl https://portal.example.com/sdk/domain

Relevant response fields:

FieldMeaning
ens.enabledENS gasless automation is enabled for a non-local relay domain
ens.verifiedPortal considers DNSSEC active and the last sync successful
ens.providerDNS provider used for automation
ens.addressBase-domain ENS address, usually the relay identity address
ens.dnssec_stateProvider DNSSEC state
ens.ds_recordDS record that may need registrar publication
ens.messageProvider-specific DNSSEC guidance
ens.last_errorLast ENS/DNS sync error

The relay frontend shows an ENS verified badge when ens.verified is true.

DNS checks:

dig +short DS portal.example.com
dig +short TXT portal.example.com
dig +short TXT myapp.portal.example.com

Expected TXT records start with ENS1.

Troubleshooting

ENS_GASLESS_ENABLED=true fails at startup:

  • set ACME_DNS_PROVIDER
  • provide the provider credentials
  • use a public PORTAL_URL, not localhost

ens.verified stays false:

  • publish the DS record at the registrar
  • wait for DNSSEC propagation
  • check ens.last_error from /sdk/domain
  • confirm the provider token can edit DNS records

A lease hostname has no ENS TXT record:

  • confirm the tunnel is registered and not expired
  • confirm the hostname is under the relay base domain
  • check relay logs for ensure ens gasless txt or provider errors

Next Steps