Oracle3
Open-source trading engine and MCP server for prediction markets. Fee-aware no-arbitrage trading across related contracts, live on Kalshi, Polymarket and Solana or on paper.
An account for your agents. You set the rules; they pay within them, never beyond. No raw keys, no shared cards.
FinanceTypeScript · JavaScriptGitHub @d-hindersOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Agent-first wallet infrastructure for the autonomous economy
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
owner: "@d-hinders" status: current covers: • .nvmrc • .env.example • docker-compose.yml • package.json • packages/backend/package.json • packages/frontend/package.json • packages/sdk/package.json • packages/mcp/package.json • packages/mcp-server/package.json • packages/signer/package.json • packages/connect/package.json • packages/demo-merchant-mcp/package.json • .github/workflows/publish.yml • scripts/release-bump.mjs last-verified: "2026-09-19"Read the full README ↗
owner: "@d-hinders" status: current covers:
Haven is an agentic stablecoin payment wallet. Users create a Haven account, add funds to a Haven wallet, and give AI agents constrained spending ability through agent rules and budgets.
Haven is non-custodial smart account software. User funds stay in a user-controlled MetaMask Hybrid DeleGator smart account, shown in product copy as a Haven wallet. An agent's spending authority is a delegation you sign — a budget with audited caveat enforcers (period budget with native refill, optional recipient pin, expiry) that the DelegationManager enforces on-chain at redemption. Haven helps users configure that authority, construct payment payloads, relay independently signed transactions, and understand activity. It does not hold user or agent private keys, make API credentials sufficient to spend, or make discretionary transfer decisions — and it holds no key that can change an account's signer set or redeem a delegation you did not sign.
Read /for-agents.md on the Haven host your user gave you — or
the copy in this repository
if you do not have that host yet.
Your user creates the account and the passkey: those are theirs, they need a
human, and you should never ask for their password. You can do everything else
— including running the connector command from the setup prompt they paste you,
and managing the account from the shell with @haven_ai/cli.
Not to be confused with
AGENTS.md. That file is for contributors to this repository — build, test and review conventions for changing Haven itself. It is not a guide for an agent that wants to USE Haven to pay for something; this section is.
Agents should not be wallets. They should be payment actors with constrained authority.
User -> Haven wallet / Hybrid DeleGator (funds and signer authority)
Agent -> Haven credential (identity) + delegate signing key (payment signatures)
Haven -> UI, API, pre-checks, relay, receipts, status
Signed delegation + caveat enforcers -> On-chain agent budget enforcement
API auth is identity. Signature is authority. On-chain delegation state is enforcement.
Haven runs one live on-chain policy rail: the delegation rail (epic #821).
Direct payments and erc7710 x402 settlement move funds account→recipient directly,
with no funding leg (the EIP-3009 x402 fallback funds the agent's delegate first, by
redeeming the same delegation); there is no approval queue. A payment over
the budget is normally refused by Haven's budget pre-check (a typed 403), and
one outside the recipient pin or expiry — or over budget when that read fails
open — reverts during gas estimation, rather than queueing for a human.
The legacy Safe + AllowanceModule rail is RETIRED (epic #1440), not frozen: nothing can
enter it, nothing on it can spend, and its execution machinery is deleted. The closure
sequence is recorded once in the
decision log
and is not restated here (#2640).
Existing Safe rows are untouched and still readable to a direct database query, but
since #2413 no account, agent or dashboard surface displays them — balances, tokens and agents no
longer render there. Transaction history is the named exception: GET /transactions carries no rail
predicate on either its account or its agent list, so legacy names still appear in its picklists. Any residual AllowanceModule permission remains on-chain and outside Haven until
the Safe owner revokes it externally. The Smart Sessions session rail is
likewise retired (#834). See
docs/security/delegation-rail-security-model.md,
docs/product/account-recovery.md for what a legacy
Safe owner can still do, and your non-custody exit path.
This is a TypeScript monorepo:
| Package | Description |
|---|---|
packages/backend | Fastify API for auth, Haven wallets, agents, payments, x402/MPP, receipts, the Fortnox accounting feed, and OpenAPI (the legacy approval queue is gone entirely — its route was deregistered and its table dropped by #2055) |
packages/frontend | Next.js dashboard for Haven accounts, Haven wallets, agent rules, connect-agent handoff, and activity |
packages/core | @haven_ai/core — the shared kernel: pure domain types and helpers (chain facts, delegation statuses, client release data) used by the backend and frontend. Workspace-private, never published |
packages/ui | @haven_ai/ui — the shared design system: v2 tokens, the Tailwind preset and the primitives the ops console consumes. Workspace-private, never published |
packages/ops | @haven/ops — the private operations console. Reads through the ops backend surface (#3509); every sign-in and read is audited server-side. Ships dark: off unless every OPS_* variable is set |
packages/sdk | @haven_ai/sdk for direct agent integrations, tool definitions, x402/MPP quote/pay/resume helpers, and payment state handling |
packages/connect | @haven_ai/connect — the one-command connector the dashboard hands out: installs the MCP runtime and generates the agent signing key locally, registering only the public address. Writes owner-only credential files under ~/.haven/agents/ |
packages/mcp | @haven_ai/mcp local stdio MCP server that reads a local credential file and signs locally |
packages/mcp-server | @haven_ai/mcp-server hosted/keyless Streamable HTTP MCP server that constructs and relays but never signs |
packages/signer | @haven_ai/signer local edge signer used with hosted MCP; it holds the delegate key locally and exposes sign-only tools |
packages/cli | @haven_ai/cli terminal-native, scriptable parallel to the dashboard (login, reads, backend-only management) |
packages/demo-merchant-mcp | Internal x402 demo merchant MCP server for Base USDC test purchases and Swedish invoice output |
.nvmrc; CI and the
Docker images run the same major. (The published agent-runtime packages
declare a lower engines floor, >=22 — see
mcp-runtime-compatibility.) With nvm
or fnm, run nvm use / fnm use in the repo
root to match it automatically (otherwise npm install warns EBADENGINE).git clone https://github.com/d-hinders/Haven-AI.git
cd Haven-AI
npm install
cp .env.example .env
Edit .env and fill in the required values:
| Variable | Required | Description |
|---|---|---|
DATABASE_URL | Yes | PostgreSQL connection string (default works with Docker) |
JWT_SECRET | Yes | Secret for dashboard auth tokens; use a long random string in production |
RPC_URL_BASE | No | Base RPC (default: https://mainnet.base.org) |
ETH_GETLOGS_MAX_BLOCK_RANGE | No | Widest eth_getLogs block range the RPC provider serves (Alchemy free plan: 10). The x402 settlement sweepers never ask for more; unset, they learn the limit from the provider's refusal |
RELAYER_PRIVATE_KEY | Yes for on-chain execution | EOA private key that pays gas for relayed transactions; it cannot access user funds |
GNOSISSCAN_API_KEY | No | History only (decision (c), #3635): the chain-100 explorer history read; nothing runs on Gnosis |
BASESCAN_API_KEY | No | Base explorer API key when using an Etherscan-style Base source; Base currently defaults to Blockscout for transactions |
BLOCKSCOUT_API_KEY | No | Sent on the Base / Base Sepolia Blockscout history reads; without it Blockscout may refuse (403) anonymous requests from a datacenter IP |
ALCHEMY_HISTORY_API_KEY | No | When set, Base / Base Sepolia transaction history is read through Alchemy's transfers API instead of Blockscout (whose public API refuses Railway with a Cloudflare challenge). Use a separate Alchemy app from the RPC one |
COINGECKO_API_KEY | No | Token price lookups |
FRONTEND_URL | No | Backend CORS/link base (default: http://localhost:3000) |
NEXT_PUBLIC_API_URL | No | Frontend backend URL override (default through local rewrite: http://localhost:3001) |
HAVEN_HOSTED_MCP_URL | Outside production (local dev can skip it with --local) | Backend: the hosted MCP URL handed to the connector and dashboard (falls back to NEXT_PUBLIC_HAVEN_MCP_URL, then to the production-only default) |
Setting up the relayer wallet:
The relayer is a throwaway EOA that pays gas for on-chain agent payments. It never has access to user funds — it just submits transactions. Generate one:
node -e "const{ethers}=require('ethers');const w=ethers.Wallet.createRandom();console.log('Address:',w.address);console.log('Key:',w.privateKey)"
Fund the relayer with the native token for each chain you plan to use:
Put the private key in RELAYER_PRIVATE_KEY.
Make sure Docker Desktop is running:
npm run docker:up
npm run dev
One onboarding path, no transaction. Signup provisions a counterfactual passkey-owned Hybrid DeleGator (
POST /accounts/hybrid) — zero transactions and no gas, so no faucet is needed to get an address. You need native gas tokens only if you later send from the account yourself. Creating or importing a Safe is retired (#1984) and answers HTTP 410; there is no deploy modal.
The credential contains an agent API key and a delegate signing key. Haven stores only the API-key hash/prefix and never stores the delegate private key. If the API key is exposed or lost, use Payment credentials on the agent detail page to rotate it; the new key is shown once and the old key stops working. If the delegate signing key is exposed or lost, a delegation-rail agent is re-keyed, not replaced: the same agent gets a new signing key and a new API key, keeping its id, name, history and the remainder of its budget — see Replacing an agent's signing key. Legacy AllowanceModule agents are historical records only in the dashboard, without payment-credential, pause/resume, re-key, or revoke controls there. Owners manage any remaining Safe permission outside Haven where they have access; replacement agents use the live delegation flow.
Most users connect an agent through the dashboard's Connect your agent flow. The hosted MCP path sends only the API key to the hosted MCP endpoint; the delegate signing key stays local with the runtime or @haven_ai/signer.
Developers can also integrate directly with @haven_ai/sdk. The SDK wraps direct payments, quote-first x402/MPP flows, and ready-made tool definitions for Claude/OpenAI-style tool calling.
npm install @haven_ai/sdk@alpha
import { HavenClient } from '@haven_ai/sdk'
const haven = new HavenClient({
apiKey: 'sk_agent_xxx', // from Haven dashboard
delegateKey: '0x...', // delegate signing key held by the agent runtime
baseUrl: 'http://localhost:3001',
})
const result = await haven.pay({
token: 'USDC',
amount: '5.00',
to: '0xrecipient...',
})
console.log(result.txHash) // 0x...
console.log(result.explorerUrl) // https://basescan.org/tx/0x... (sepolia.basescan.org on Base Sepolia)
The SDK ships with pre-built tool definitions for Claude and OpenAI:
import { HavenClient, havenTools } from '@haven_ai/sdk'
const haven = new HavenClient({ apiKey, delegateKey })
const tools = havenTools.claude() // or havenTools.openai()
// When your model returns a Haven tool call:
const toolCall = { name: 'get_allowances', input: {} }
const result = await haven.executeTool(toolCall.name, toolCall.input)
if (toolCall.name === 'get_allowances') {
// Use this for budget, remaining amount, reset-period, or "what can I spend?" questions.
}
See packages/sdk/README.md for the full SDK reference, payment state machine, x402/MPP helpers, and error handling.
| Path | Package | Use when | Custody boundary |
|---|---|---|---|
| Hosted MCP | @haven_ai/mcp-server + @haven_ai/signer | You want one hosted URL across agent runtimes | Hosted server receives API identity only; local signer holds the delegate key |
| Local MCP | @haven_ai/mcp | You want a local stdio server beside the agent runtime | Local process reads the credential file and signs locally |
| Direct SDK | @haven_ai/sdk | You are writing custom TypeScript agent code | Your runtime holds the delegate key and calls Haven with signed payloads |
After creating an agent, test payments through the hosted MCP:
Use the dashboard's Connect your agent Done step. It creates runtime-specific snippets and one-click deep links where supported. The hosted snippets include the API key only; they do not include the delegate signing key. The local signer or runtime secret store handles signing.
Agent runtime
-> SDK / local MCP / hosted MCP
-> Haven API (identity, policy mirror, construct, relay, status)
-> Signed delegation + caveat enforcers (on-chain budget enforcement)
-> Haven wallet / Hybrid DeleGator (user funds)
{ token: "USDC", amount: "5.00", to: "0x..." }| Step | Endpoint | What happens |
|---|---|---|
| 1. Create intent | POST /payments | Haven validates, selects the budget delegation, returns typed data to sign |
| 2. Sign & submit | POST /payments/:id/sign | Agent signs the typed data, Haven verifies and submits the sponsored UserOperation |
| 3. Check status | GET /payments/:id | Poll until confirmed / failed |
All endpoints authenticate with Authorization: Bearer sk_agent_xxx. Authentication is not payment authority: executable transfers still require the agent-held delegate signature and the on-chain delegation it redeems against.
An account on the retired Safe rail (execution_rail='allowance_module') gets HTTP 410 from all of these, fail-closed with nothing written (#1986).
For x402 and MPP, the SDK and MCP tools use quote/pay/resume flows. The preferred merchant scheme is ERC-7710 direct settlement — one leg, account→merchant, no funding hop. Where a merchant's facilitator cannot redeem a delegation chain (still most of them today), the EIP-3009 bridge (#946) transiently funds the agent EOA from the budget delegation so it can sign a standard X-PAYMENT header; that path deliberately reintroduces a bounded funding leg, which is why the sweep and delegate-balance monitoring machinery is kept. Production merchant facilitation, acquiring, fiat/card rails, settlement, swaps, yield, and advice are not current Haven production surfaces.
Independent layers keep the API and signing boundaries separate:
| Layer | What it does | Where it lives |
|---|---|---|
| Hybrid DeleGator smart account | User-managed signer set (passkey and/or wallet), holds all funds | On-chain |
| Signed delegation + caveat enforcers | Per-agent period budget with native refill, optional recipient pin, expiry — checked at redemption | On-chain |
| Delegate signing key | Signs payment payloads within the delegated budget | Agent/runtime/user environment |
| Haven policy mirror | Pre-checks, audit trail, status, and copy — a mirror, never the real spend control | Haven backend |
| Credential scoping | API-key identity, prefix display, rotation, and revocation state | Haven backend |
If Haven is compromised, API keys alone cannot sign transactions. For a live delegation agent, an account owner can stop its budget or replace its signing key in Haven (pausing only blocks payments through Haven), and can revoke the underlying on-chain authority directly without needing Haven — see the independent exit path. The dashboard exposes no pause or revoke controls for legacy Safe records; any residual Safe permission must be revoked externally by the Safe owner.
Approver (Safe owner) management is retired (#1988/#1989, epic #1440). Haven never signed an owner change and no longer constructs one: the backend routes and the Settings surface are both deleted. Owners of a legacy Safe manage its owner set directly through Safe's own interfaces with their own key — which was always true, and is the reason removing Haven's builder takes nothing away that Haven was the only source of. Haven no longer relays an owner-signed Safe transaction either: #1989 deleted the screen that composed one, and #2847 deleted POST /safe/exec, the route that relayed it for gas. A wallet-owned Safe loses nothing (sign at app.safe.global); a passkey-owned Safe currently has no self-serve way to move funds out, because Haven's passkey Safe signer is a custom WebAuthn scheme Safe's own interfaces cannot drive. The epic's Base-mainnet census found no passkey-owned Safe, which is why this was accepted as a narrowing — but a census is not a proof and it does not cover non-mainnet accounts. Full user-facing wording: docs/product/account-recovery.md.
| Key | Who holds it | What it can do |
|---|---|---|
| Account signer (passkey / wallet) | User device or wallet/hardware environment | Full account control: add or remove signers, grant and revoke agent budgets. On a legacy Safe, the equivalent is the Safe owner key |
| Delegate private key | Your agent | Sign payment intents within the delegated budget only |
| Agent API key | Your agent | Authenticate with Haven API; no signing ability |
| Hosted MCP bearer token | Agent runtime config | Same API identity role as the agent API key |
| Relayer key | Haven server | Pay gas for independently valid signed transactions; no fund access |
Haven never holds account signer keys, Safe owner keys, or delegate private keys.
For architecture constraints around custody, transfer-service risk, relaying, x402/merchant demos, fiat/card rails, swaps, and investment advice, use docs/regulatory/casp-risk-guardrails.md as the required perimeter guardrail.
Agent endpoints use Bearer token auth:
Authorization: Bearer sk_agent_xxx
Dashboard endpoints use the signed-in user's JWT. The OpenAPI contract is served at /openapi.json.
| Surface | Auth | Examples |
|---|---|---|
| Dashboard auth | None/JWT | /auth/signup, /auth/login, /auth/me |
| Haven wallets | JWT | /user/accounts (list/rename/re-default/unlink), balances and account views. The whole /user/safes* prefix and PUT /user/safe answer 410 naming their replacement (#2914) — the Safe vocabulary is retired from the wire. Creating or importing an account is separately retired — POST /user/accounts, /user/accounts/deploy, /safe/deploy and PUT /user/account all answer 410 (#1984). Create a Haven account on the delegation rail with POST /accounts/hybrid. |
| Agents | JWT | /agents, /agents/:id, /agents/:id/pause, /agents/:id/resume, /agents/:id/revoke, /agents/:id/rotate-key |
| Agent payments | API key | /payments, /payments/:id/sign, /payments/:id, /payments |
| Agent info | API key | /machine-payments/agent, /machine-payments/allowances, /machine-payments/receipts, /machine-payments/:id/status, resume-state endpoints |
| x402 | API key or protocol challenge | /x402 (the legacy internal mpp_demo flow at /demo/mpp/* and POST /machine-payments/authorize is retired — the latter now refuses with HTTP 410, #1328) |
| Activity | JWT | /agent-activity/* for payments, MCP tool calls, and last activity |
/agents/:id/allowances and /agents/:id/allowances/:tokenAddress are retired
410 tombstones. Live agent budgets are signed delegation grants, not allowance
rows.
POST /payments
{
"token": "USDC",
"amount": "5.00",
"to": "0xrecipient..."
}
Sign sign_data.typed_data verbatim, never the bare hash. The account validates the typed data, not the 4337 hash; @haven_ai/sdk and the MCP signer do this for you.
There is no over-budget approval queue on the delegation rail. A request the remaining period budget cannot cover is refused 403 delegation_budget_exceeded at a pre-check before anything is built (#3503); one to a recipient outside the grant's pin, or outside its validity window, finds no active delegation and is refused 403; anything that still reaches the chain out of policy reverts during on-chain gas estimation — none becomes a pending approval. There is no 202 on this route: #2055 dropped the approval_requests table, so no row is left to replay, and #2105 removed the response from the published contract. Do not keep a pending_approval branch alive.
{
"payment_id": "uuid",
"status": "pending_signature",
"expires_at": "2025-01-01T00:10:00Z",
"sign_data": {
"hash": "0x...",
"signature_scheme": "eip712_userop",
"typed_data": { "domain": {}, "types": {}, "primaryType": "PackedUserOperation", "message": {} },
"components": {
"account": "0x...",
"token": "0x...",
"to": "0x...",
"amount": "5000000000000000000"
},
"instructions": "Sign sign_data.typed_data with your delegate key using EIP-712 (signTypedData)..."
}
}
POST /payments/:id/sign
{
"signature": "0x...65_byte_signature"
}
Response on success:
{
"payment_id": "uuid",
"status": "confirmed",
"tx_hash": "0x...",
"chain_id": 8453,
"explorer_url": "https://basescan.org/tx/0x..."
}
| Command | What it does |
|---|---|
npm run dev | Start backend + frontend in dev mode |
npm run build | Build core, SDK, connect, MCP packages, signer, CLI, backend, frontend and the ops console |
npm run test | Run workspace tests where configured |
npm run typecheck | Run workspace type checks |
npm run preflight | Run the gates CI will run on your diff, derived from the workflow files (#3150). Start here before pushing. preflight:all ignores the diff and runs every gate; preflight:list prints the plan without running it |
npm run quality | Typecheck and unit tests across workspaces, then the root build chain. Despite the name this is not the pre-push gate list — it covers none of the ratchets (lint:*, check:*, the coupling gates), and the root build omits two packages. Use preflight |
npm run docker:up | Start PostgreSQL container |
npm run docker:down | Stop PostgreSQL container |
npm run docker:logs | Tail PostgreSQL logs |
Haven-AI/
|-- ABOUT_HAVEN.md # Product and architecture mental model
|-- docs/ # architecture, product/UX, contributing, operations, regulatory docs (see docs/README.md)
|-- packages/
| |-- backend/ # Fastify API, database migrations, relaying, OpenAPI
| |-- frontend/ # Next.js dashboard and connect-agent UX
| |-- core/ # @haven_ai/core — shared domain types and helpers (workspace-private)
| |-- ui/ # @haven_ai/ui — v2 tokens, Tailwind preset, shared primitives (workspace-private)
| |-- ops/ # @haven/ops — private operations console (ships dark)
| |-- sdk/ # @haven_ai/sdk
| |-- connect/ # @haven_ai/connect — one-command connector: installs the MCP runtime, generates the signing key locally
| |-- mcp/ # Local stdio MCP server; signs locally from a credential file
| |-- mcp-server/ # Hosted/keyless Streamable HTTP MCP server
| |-- signer/ # Local edge signer paired with hosted MCP
| |-- cli/ # @haven_ai/cli terminal parallel to the dashboard
| |-- demo-merchant-mcp/ # Internal x402 merchant MCP demo
| `-- qa-agent/ # Internal QA harness (not published)
|-- .env.example # Environment variable template
`-- docker-compose.yml # PostgreSQL for local dev
Base (chainId: 8453)
| Token | Symbol | Decimals | Address |
|---|---|---|---|
| ETH | ETH | 18 | Native |
| USDC | USDC | 6 | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
Base Sepolia (chainId: 84532, dev/QA)
| Token | Symbol | Decimals | Address |
|---|---|---|---|
| ETH | ETH | 18 | Native |
| USDC | USDC | 6 | 0x036CbD53842c5426634e7929541eC2318f3dCF7e |
Agent budgets are enforced on USDC: the period enforcer is ERC-20 only. Gnosis Chain (chain 100) is not a Haven network.
@safe-global/protocol-kitHaven runs in production on Vercel (frontend) and Railway (backend + Postgres). Two long-lived branches deploy: dev auto-deploys to the shared dev environment, and main auto-deploys to production. The five published npm packages (@haven_ai/sdk, signer, mcp, connect, cli) are also published automatically from main on a version bump — see Releasing npm packages. For the dev environment's setup and env vars, see docs/operations/dev-environment.md.
All changes go through pull requests — no direct pushes to main or dev. Feature work flows feature/* → dev → main; the dev-gate workflow only lets dev or hotfix/* merge into main.
dev → make your changesdev on GitHubpreview/<name> (PR previews are opt-in, dev-environment.md § PR previews are opt-in); Vercel's status on the branch's commit carries the URLdev and auto-deploys to the dev environmentdev → main with a separate PR (the only normal path into main); merging triggers automatic production deploys to Vercel + Railway (~2 min). Emergency fixes can use a hotfix/* branch straight into main.A Vercel preview (a preview/* branch) points at the dev Railway backend, like the dev host. You can test most frontend changes directly against it — no local setup needed beyond the branch.
Vercel previews share the deployed dev backend, so a PR's backend changes can't be tested through a preview. Run the backend locally before opening the PR:
# 1. Start the local Postgres
npm run docker:up
# 2. In one terminal — run the backend on :3001
npm run dev -w packages/backend
# 3. In another terminal — run the frontend on :3000
# (set NEXT_PUBLIC_API_URL=http://localhost:3001 in your .env)
npm run dev -w packages/frontend
Test the full flow locally, then push and open the PR. Once merged, watch the Railway deploy logs to confirm the change deployed cleanly in prod.
Collaborators have Viewer access to the Railway project — you can see services, deploy logs, and runtime logs, but not change env vars. If a deploy fails or behaves unexpectedly:
dev host deploy on every push that changes the frontend; a PR gets a preview only from a preview/* branchIf you need an env var changed in Railway or a secret rotated, ping the project owner.
The five npx-installed packages — @haven_ai/sdk, @haven_ai/signer, @haven_ai/mcp, @haven_ai/connect, @haven_ai/cli — are published to npm automatically. You never run npm publish by hand.
# 1. Bump all published packages atomically (versions, cross-package pins,
# and source version constants) and verify the connect bundle.
npm run release:bump -- <new-version> # e.g. 0.1.17-alpha.0
# 2. Commit on a release branch and open the PR into `dev` — NOT `main`.
# `dev-gate` only lets `dev` or `hotfix/*` into `main`, so a release/*
# branch aimed at `main` fails by design. Get it green, merge to `dev`.
# 3. Promote `dev → main` (a merge commit, never a squash). Publishing fires
# on THIS step, not on the dev merge — the promotion stays a human step.
On the promotion to main, the Publish packages workflow (.github/workflows/publish.yml) rebuilds dist in dependency order and publishes only the packages whose package.json version is not yet on npm. The dist-tag is derived from the version: a prerelease like 0.1.17-alpha.0 publishes under --tag alpha, a stable 0.2.0 under latest. The connector install command the dashboard hands out names @alpha unless that deployment sets HAVEN_CONNECTOR_CHANNEL (#2422; production leaves it unset), so the prerelease line is what real users get.
main → alpha/latest). The same workflow also fires on a package-touching push to dev and publishes a 0.0.0-dev.<YYYYMMDDHHMM>.<shortsha> snapshot of all five packages under the dev dist-tag — built by the ordinary bump script in a throwaway CI tree, with nothing committed. 0.0.0- sorts below every real version, so no ^0.1.x range resolves to a snapshot by accident. A snapshot can reach neither alpha nor latest, and the main path refuses a 0.0.0-dev.* version; the enforcement points are named in the workflow's header comment. Loop and owner steps: docs/operations/package-dev-channel.md. One file rather than two because npm's trusted-publisher entries are pinned to the workflow filename.id-token: write and upgrades npm to ≥ 11.5.1; npm then authenticates the short-lived GitHub Actions OIDC token against a trusted publisher configured per package on npm (pointing at d-hinders/Haven-AI + workflow publish.yml, environment field empty — see the bullet below for why that matters). Nothing to leak or rotate on this path, and it's exempt from the 2FA one-time-password prompt that blocks token-based publishes.latest is a SECOND job with a DIFFERENT credential (#2647): OIDC authorises npm publish and nothing else, so npm dist-tag add cannot ride on it — putting it in the publish job failed every tag move with E401 on the 0.1.35-alpha.0 release, leaving a release behind . A separate job holds a granular npm token in the -only GitHub Environment, and has no , so it can move a tag and cannot publish. It has to be a separate job: an is job-level, and adding one to the publish job would break its trusted-publisher entries (registered with an empty environment field). , and it expires — npm caps write-enabled granular tokens at 90 days, this one on , after which releases publish and silently stops moving. Two consequences worth reading: a promotion can be (published, unmoved), and the durable answer is to stop needing two dist-tags at all.Private — not open source.
packages/qa-agent | Internal QA harness for the dev environment: deterministic money-flow scenarios and dev seeding (not published) |
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID | No | WalletConnect project id; injected wallet connectors can still work without it |
latestalphapromote-tagsmainnpm-production-tagsid-token: writeenvironment:latestlatestE422) unless each package.json declares a repository.url (with the monorepo directory) matching the repo — each published package must carry this.d-hinders / Haven-AI / publish.yml), and (2) give its package.json a repository block. Skipping either makes the first publish fail — on auth (EOTP/OIDC) or provenance (E422) respectively.mcp-server (Docker → Railway), backend, and frontend (Vercel/Railway) deploy from branches directly — main to production and dev to the shared dev environment.scripts/README.md.