Appearance
dApp builder API
All under /api/v1/dapp. Route file: bz-backend/v1/routes/dapp.routes.js — roughly 24 endpoints.
Product page: dApp builder. Full design: defi-set/DAPP_BUILDER_ARCHITECTURE.md.
Projects
| Method | Path | Purpose |
|---|---|---|
GET | /v1/dapp/list | Available templates or dApp list |
GET | /v1/dapp/projects | The user's projects |
GET | /v1/dapp/project/:projectId | One project |
GET | /v1/dapp/project/:projectId/html | Raw HTML for the preview iframe |
GET | /v1/dapp/project/:projectId/versions | Version history |
POST | /v1/dapp/project/:projectId/iterate | Apply a change |
POST | /v1/dapp/project/:projectId/rollback | Restore a previous version |
DELETE | /v1/dapp/project/:projectId | Delete |
Legacy aliases also exist: GET /v1/dapp/:dappId and POST /v1/dapp/:dappId/iterate.
The catch-all /:dappId is registered last, and that matters
GET /v1/dapp/:dappId sits after /list, /subscription, /projects and the /project/* and /account/* groups. A new single-segment GET route added below it would be shadowed.
Iteration
POST /v1/dapp/project/:projectId/iterate runs v1/services/aiDeveloper.service.js:
| Step | Purpose |
|---|---|
| Complexity check | Reject a request bundling more than ~3 distinct changes |
| Load current version | Iterations modify that user's code, not a template |
| Classify the change | visual / feature / content / contract — picks the model tier and prompt |
| Call the model | Whole current file as context |
| Validate output | HTML must parse, scripts intact, no XSS introduced |
| Save as a new version | currentVersionId advances; prior versions retained |
Validation is the load-bearing step
An LLM asked to change a colour can silently drop a <script> block. Without the check the user's app quietly breaks and the only signal is the preview no longer working.
Why the complexity ceiling exists
"Change the colour, add staking, wire the contract, and add a leaderboard" asks the model to do four things in one pass, and the failure mode is a partially-applied change that looks complete. Rejecting is better than half-applying.
Going on-chain
ERC-4337 via ZeroDev (@zerodev/sdk, @zerodev/ecdsa-validator, @zerodev/permissions, permissionless).
| Method | Path | Step |
|---|---|---|
POST | /v1/dapp/project/:projectId/go-onchain | 1. Start setup; quote the ~$10 cost |
POST | /v1/dapp/account/verify-deposit | 2. Confirm the deposit landed at the counterfactual address |
POST | /v1/dapp/account/:projectId/deploy-account | 3. Deploy the smart account (first UserOp) |
GET | /v1/dapp/account/:projectId | Account state |
POST | /v1/dapp/account/:projectId/top-up | Add paymaster budget |
POST | /v1/dapp/account/:projectId/revoke-session | Revoke Blazpay's session key |
POST | /v1/dapp/account/:projectId/verify-contract | Submit for explorer verification |
GET | /v1/dapp/account/:projectId/verify-contract/:guid | Poll verification status |
The sequence
- Bot quotes the cost (~$10, covering deployment plus roughly 500 user interactions) and states that the user owns the account via their MetaMask.
- User signs a one-time EIP-712 setup message.
- Backend computes the counterfactual smart-account address and asks the user to fund it.
- Deposit verified on-chain.
- Smart account deployed as the first UserOp.
- A session key with tight ZeroDev permissions is registered for Blazpay's automation.
- Paymaster budget topped up with the user's deposit.
aiDeveloper.servicegenerates Solidity;contractDeployer.servicecompiles withsolcand deploys via the session key plus paymaster.
This is the only Blazpay-held key that can move user funds
The session key is scoped by ZeroDev permissions and revocable unilaterally by the user, after which the smart account is entirely self-controlled and Blazpay's automation stops. It is a genuinely different trust posture from the rest of the platform, and the UI should say so at setup — not in a footer.
Withdrawal
Not an endpoint the backend initiates. The frontend has the user sign { action: 'WITHDRAW', amount, to } with their owner key; the backend verifies and constructs the UserOp.
Subscription and quota
| Method | Path | Purpose |
|---|---|---|
GET | /v1/dapp/subscription | Current tier |
GET | /v1/dapp/subscription/status | Usage against the tier |
POST | /v1/dapp/subscription/upgrade | Upgrade |
Iteration counts are per project — iterationsUsed / maxIterations on models/dappProjectModel.js. Tier data in models/dappSubscriptionModel.js.
Data model
| Collection | Contents |
|---|---|
dappProjectModel | Project, version history, currentVersionId, iterationsUsed, maxIterations |
dappSubscriptionModel | Tier and billing |
generatedDappModel | Generated artefacts |
dappModel | Templates or legacy records |
managedAccountModel | ZeroDev smart accounts |
caSca, caActivity | Smart-contract-account records and activity (/api/defi/save-sca, /get-sca/:address) |
Session state
The active project is activeDappProjectId on blazSession, sent to bz-agent with every chat request. That is what makes MODIFY_DAPP, SHOW_DAPP, GO_ONCHAIN and DEPLOY_CONTRACTS selectable at all — without one, the classifier cannot choose them.
Intents
| Intent | Precondition | Action |
|---|---|---|
CREATE_DAPP | — | create_dapp |
MODIFY_DAPP | Active project | create_dapp |
SHOW_DAPP | Active project | create_dapp |
GO_ONCHAIN | Active project | — |
DEPLOY_CONTRACTS | Active project and a smart account | — |
GO_ONCHAIN versus DEPLOY_CONTRACTS
GO_ONCHAIN is first-time smart-account setup. DEPLOY_CONTRACTS is deploying Solidity. Both intent descriptions carry explicit do-NOT-use-for lists because the classifier used to conflate them.
Agent nodes: bz-agent/src/agent/{createDappNode,modifyDappNode,goOnchainNode}.ts. Templates: src/template/{createDapp,modifyDapp,goOnchain}.ts.
Frontend
defi-dex/src/component/defi/swapAI/dapp/DappPreview.tsx — renders the generated HTML in a blob-URL iframe, shows the change summary below the preview, and drives iterate, rollback and go-onchain.
Failure modes
| Symptom | Cause |
|---|---|
| Iteration rejected | Complexity check — more than ~3 distinct changes |
| Iteration applied but the app broke | Output validation should have caught it; check the validator |
| Changes appear then vanish | The original bug — iterations were hitting a shared template rather than the user's code. Should not recur |
MODIFY_DAPP not classified | No activeDappProjectId on the session |
| Deploy fails | Deposit not verified, or paymaster budget exhausted |
| Automation stopped working | The user revoked the session key |
| Contract verification pending forever | Poll GET /verify-contract/:guid; explorer-side delay |
Known limits
- Single-file HTML output, rendered in a blob-URL iframe. Not multi-file React.
- Generated Solidity is not audited. It is compiled and deployed. Routing it through the contract auditor — available in the same chat — is the obvious integration and is not automatic.
- No export. A user cannot download their codebase and self-host.
- Chain coverage is narrower than the trading products. See §12 of the architecture document.