Skip to content

feat: devnet (moutai) support + evo-sdk 4.2.0-dev.2 - #304

Merged
PastaPastaPasta merged 9 commits into
stagingfrom
feat/devnet-moutai-support
Aug 27, 2026
Merged

feat: devnet (moutai) support + evo-sdk 4.2.0-dev.2#304
PastaPastaPasta merged 9 commits into
stagingfrom
feat/devnet-moutai-support

Conversation

@PastaPastaPasta

@PastaPastaPasta PastaPastaPasta commented Aug 27, 2026

Copy link
Copy Markdown
Owner

Plan A of PLAN_DEVNET_STAGING.md: bump the SDK, teach the app and the operational scripts to talk to a Dash devnet, and add a /devnet build of the staging commit.

The provisioning half of the plan (A4/A5) could not be executed — moutai is currently unreadable from the WASM SDK. Details and evidence below. The code that would do it is here and ready; .env.devnet carries the wiring with the contract and identity slots left blank rather than filled with invented ids.

A1 — evo-sdk 4.2.0-dev.2

Pinned exact, mirroring #295. Verified before landing:

  • Byte-array regression check (memory evo-sdk-4-1-byte-array-write-regression). Serialized a like create transition built through Document.fromObject and hexdumped it: the 32-byte postId lands as … 49 64 | 0a 20 | <32 raw bytes>Value::Bytes, correct. Negative control: the same document built with new Document({ properties }) still produces the broken 15 20 02… (Value::Array of U64) marker and the raw bytes are absent, so the check itself is sound and dev.2 has not regressed.
  • Real testnet write smoke on the staging contract 9oDC6xdg8WRixTD2j3FCBq3vtsrf6bRGjXSJbhtFoma9 as bot0 (GENx4QYZ4UT3KGDbtxc2E7pvECfbf8AP8kk33ULcAPXr): created a bookmark, read it back with postId bytes intact, then deleted it. This is the gate that chore: bump @dashevo/evo-sdk to 4.2.0-dev.1 #295's regression slipped past.
  • npm run lint and npm run build clean.

A2 — devnet in the app

NEXT_PUBLIC_NETWORK=devnet now builds an SDK against an explicit DAPI address pool for the devnet named by NEXT_PUBLIC_DEVNET_NAME. The typed new EvoSDK({ network: 'devnet', … }) constructor is used rather than EvoSDK.devnetTrusted(), because that factory takes no addresses and a devnet publishes no masternode list to discover them from.

The important structural change is that network splits in two:

helper answers devnet value
getConfiguredNetwork() what the SDK connects to 'devnet'
keyNetwork() what address/WIF prefixes apply 'testnet'

Devnets reuse testnet's address and WIF version bytes — moutai's own Insight reports "network":"testnet". Every key-derivation, signer-matching and secure-storage call site moves to keyNetwork(); only SDK construction ever sees 'devnet'. Without this the eleven existing (process.env.NEXT_PUBLIC_NETWORK as 'testnet' | 'mainnet') || 'testnet' casts would have quietly passed the string 'devnet' into key derivation.

DPNS_CONTRACT_ID and the Insight base URL become overridable per deployment.

A3–A5 — scripts

  • New scripts/sdk-env.mjs replaces the hardcoded EvoSDK.testnetTrusted in all six operational scripts. NETWORK (plus DEVNET_NAME, DAPI_ADDRESSES, QUORUM_URL, read from .env.devnet when absent from the environment) picks the chain. Testnet remains the default, so every existing invocation is byte-for-byte unchanged — verified with node scripts/provision-test-identity.mjs --check-balances.
  • provision-test-identity.mjs gains a devnet mode that refuses InstantSend asset-lock proofs. On the devnet build rs-dapi's bloom matcher never forwards the IS lock, so an InstantSend-funded lock silently burns the funds (feat: register funded identities via a ChainLock asset lock dashpay/platform#4399). The devnet path is ChainLock only: fund the address printed by --gen-asset-lock-key, pass the outpoint back as --funding-outpoint <txid>:<vout>, and the script polls Insight for the funding tx's block height and DAPI getStatus().chain.coreChainLockedHeight for the lock (600s budget) before building AssetLockProof.createChainAssetLockProof. Every one-shot asset-lock key is appended to the gitignored .devnet-locks.local before anything is broadcast, so funds are never stranded behind a key that only lived in memory.
  • register-test-contracts.mjs can clone from another chain (--source-network, --from-social, --from-profile) and now carries the source contract's tokens block across. tokenCost lives inside the document schemas and always followed them, so dropping the token configuration left the copy costing a token that did not exist. The clone goes through DataContract.fromJSON rather than new DataContract({ schemas, tokens }): the constructor's tokens option only accepts live TokenConfiguration handles (a plain object read from JSON is rejected with "JS object constructor name mismatch"), and the JSON form additionally round-trips groups, config, keywords and description, so the copy is faithful rather than schemas-only. publishContract asserts the published contract came back with the token count it was given. set-yapp-price.mjs takes --contract and a seed-derived --owner-index so the same price can be set on a copy.

A6 — /devnet deployment

npm run build:devnet sources the checked-in .env.devnet and builds with BASE_PATH=/devnet, mirroring build:testing. deploy.yml rebuilds the staging checkout a second time with it, so /devnet always tracks the staging commit. Storage scoping is automatic via base path (devnet: prefix), so it is isolated from /, /staging and /testing — confirmed in the built bundle. The step is continue-on-error: the devnet chain is disposable and re-genesises without notice, and a broken /devnet must never hold back the /staging deploy.

Moutai facts (verified live 2026-08-27)

  • DAPI https://seed-{1..5}.moutai.networks.dash.org:1443, valid public TLS, reachable.
  • getStatus: dapi/drive 4.2.0-dev.2, tenderdash 1.7.0, drive protocol 14, chain id dash-devnet-moutai, platform height ~17 (re-genesised today), coreChainLockedHeight 40550.
  • Insight https://insight.moutai.networks.dash.org/insight-api works and reports "network":"testnet". Faucet address yhJHMkBAT2TF6D8GHc4v9bMfBh3V2Z6meg holds ~247k DASH.

What could not be completed, and why

Moutai cannot currently be read by the WASM SDK at all, which blocks identity provisioning (A4) and contract registration (A5) — both need identities.fetch / contracts.fetch / a nonce before they can write anything.

Three independent findings, each reproduced against evo-sdk 4.2.0-dev.2:

  1. proofs: false is not a fallback. The first proofless query panics inside rs-sdk: packages/rs-sdk/src/platform/query.rs:232 — not implemented: queries without proofs are not supported yet. So the plan's non-trusted + proofs:false shape is not viable.
  2. trusted: false with proofs on is refused outright: Context provider error: Non-trusted mode is not supported in WASM. Please construct a WasmTrustedContext via prefetchMainnet/prefetchTestnet/prefetchDevnet/prefetchLocal.
  3. Which leaves a trusted context — prefetched over HTTP from a quorum service. EvoSDK.devnetTrusted('moutai') defaults to https://quorums.moutai.networks.dash.org, which is NXDOMAIN. No alternative host serves it either (checked moutai.networks.dash.org, quorum.moutai…, the seed hosts on 443 and 1443, insight, and the faucet).

I did confirm the plumbing works, so this is purely a missing service and not an SDK gap: pointing quorumUrl at a local HTTP server showed the SDK requesting exactly three paths — GET /quorums, GET /previous, GET /masternodes — in that order, and mirroring testnet's real responses got it past prefetch. Producing moutai's own quorum and masternode data needs Core RPC (quorum listextended / protx list), which on moutai is VPN-only; only :1443 is publicly open.

Hence NEXT_PUBLIC_QUORUM_URL in .env.devnet and in scripts/sdk-env.mjsthat is the one thing standing between this branch and a working /devnet. Once a sidecar exists (dashmate can produce the data), the follow-up is mechanical and fully scripted:

NETWORK=devnet node scripts/provision-test-identity.mjs 9 --asset-lock-key-file <f> --funding-outpoint <txid>:<vout>   # maker
NETWORK=devnet node scripts/provision-test-identity.mjs 0 …                                                            # bot0
NETWORK=devnet node scripts/provision-test-identity.mjs 1 …                                                            # bot1
NETWORK=devnet node scripts/register-test-contracts.mjs --source-network testnet \
  --from-social 9oDC6xdg8WRixTD2j3FCBq3vtsrf6bRGjXSJbhtFoma9 --owner <makerId> --owner-index 9
node scripts/set-yapp-price.mjs --contract <newSocialId> --owner <makerId> --owner-index 9

Two smaller deviations from the plan, both forced:

  • The plan's funding source was the faucet private key committed in dashpay/dash-network-configs/devnet-moutai.yml, fetched at runtime. That repository is not publicly readable (raw.githubusercontent.com returns 404 unauthenticated; it resolves only with an authenticated gh api call), so an unattended runtime fetch is not possible. Separately, neither @dashevo/evo-sdk nor this repo has a Dash Core transaction builder, so spending that UTXO would mean a new dependency. The script therefore takes the funding outpoint from the operator instead, which works with the moutai MultiFaucet's web UI as-is. No key material was printed, stored, or committed at any point.
  • .env.devnet ships with the contract and identity slots commented out rather than populated. They are public data and would have been checked in, but nothing was registered, so there is nothing truthful to put there yet.

Validation

check result
npm run lint clean (pre-existing warnings only)
npm run build clean
npm run build:devnet clean; output confirms /devnet base path, devnet: storage scope and the moutai DAPI pool baked in
byte-array regression check pass, with negative control
testnet write smoke (create/read/delete) pass
provision-test-identity.mjs --check-balances on testnet pass — default path unchanged after the sdk-env refactor
contract clone with token block built, signed and accepted by Drive's state-transition validation on testnet; fails only on balance (a 16-doctype contract carrying the YAPP token costs ~93,000,100,000 credits, more than the e2e bots hold)
devnet profile round-trip not run — blocked on the missing quorum service
app dev-server smoke against .env.devnet not run — same blocker; every read fails at SDK connect

Review pass

Two findings from review were fixed on this branch rather than left for the PR:

  • contexts/sdk-context.tsx hardcoded network: 'testnet'. It is the app-wide SDK bootstrap and normally wins the race against the on-demand callers, so on a /devnet build every useSdk() consumer (store, checkout, blog, DPNS, homepage) would have read testnet through the shared singleton until something else forced a reinit. Pre-existing code that the keyNetwork() sweep did not match, because it never used the cast pattern being replaced.
  • evoSdkService.initialize() compared only network and contractId when deciding to reuse the existing instance, so a devnet config differing solely in its address pool or quorum URL would have been silently ignored.

🤖 Generated with Claude Code

Pins the exact dev.2 build, which carries the protocol-14 additions Layer 2 needs (refersTo / ReferencedEntityNotFound / timeRange / requiredSince).

Verified before landing: the standing byte-array regression check (memory evo-sdk-4-1-byte-array-write-regression) serializes a like create transition built through Document.fromObject and the byte field lands as 0a 20 + 32 raw bytes (Value::Bytes); the new Document({properties}) negative control still produces the broken 15 20 02 marker, so the check itself is sound. Followed by a real testnet write on the staging contract as bot0: bookmark create, read-back with postId bytes intact, delete.
NEXT_PUBLIC_NETWORK=devnet now builds an EvoSDK against an explicit DAPI address pool (NEXT_PUBLIC_DAPI_ADDRESSES) for the devnet named by NEXT_PUBLIC_DEVNET_NAME. The typed constructor is used rather than EvoSDK.devnetTrusted() because that factory has no addresses parameter, and devnets publish no masternode list to discover them from.

Proof verification is not optional on devnet: wasm-sdk 4.2.0-dev.2 panics on proofs:false ('queries without proofs are not supported yet') and rejects non-trusted proof verification outright ('Non-trusted mode is not supported in WASM'). Every devnet read therefore needs a trusted context, prefetched from a quorum service at NEXT_PUBLIC_QUORUM_URL (/quorums, /previous and /masternodes); the built-in default host quorums.<devnetName>.networks.dash.org does not exist for moutai.

Devnets reuse testnet address and WIF prefixes, so network now splits in two: getConfiguredNetwork() is what the SDK connects to, keyNetwork() is what key material is encoded for and collapses devnet to testnet. Every key-derivation, signer-matching and secure-storage call site moves to keyNetwork(); only SDK construction sees 'devnet'. DPNS_CONTRACT_ID and the Insight base URL become overridable per deployment.
scripts/sdk-env.mjs replaces the hardcoded EvoSDK.testnetTrusted in the six operational scripts. NETWORK (plus DEVNET_NAME, DAPI_ADDRESSES, QUORUM_URL, read from .env.devnet when not in the environment) selects the chain; testnet stays the default, so every existing invocation is unchanged.

provision-test-identity gains a devnet mode. InstantSend asset-lock proofs are refused there: rs-dapi on the devnet build never forwards the IS lock, so an InstantSend-funded lock silently burns the funds (dashpay/platform#4399). Instead the asset-lock address is funded out of band and handed back as --funding-outpoint <txid>:<vout>; the script polls Insight for the funding tx's block height and DAPI getStatus for coreChainLockedHeight, with a 600s budget, then builds a ChainLock proof. --gen-asset-lock-key now prints the P2PKH address to pay, and every one-shot asset-lock key is appended to the gitignored .devnet-locks.local before anything is broadcast.

register-test-contracts can clone from another chain (--source-network, --from-social, --from-profile) and now carries the source contract's tokens block across. tokenCost lives in the document schemas and always followed them, so dropping the token configuration left the copy costing a token that did not exist. set-yapp-price takes --contract and a seed-derived --owner-index so the same price can be set on a copy.
npm run build:devnet sources the checked-in .env.devnet and builds with BASE_PATH=/devnet, mirroring build:testing. deploy.yml rebuilds the staging checkout a second time with it, so /devnet always tracks the staging commit; client-side storage is scoped by base path, so it stays isolated from /, /staging and /testing.

The step is continue-on-error: the devnet chain is disposable and re-genesises without notice, and a broken /devnet must never hold back the /staging deploy. .env.devnet carries the moutai wiring (DAPI pool, insight, devnet name) and documents that NEXT_PUBLIC_QUORUM_URL must be filled in before /devnet can read anything.
@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f9796d34-e591-4169-8056-0d781aca5c74

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 27, 2026

Copy link
Copy Markdown

Deploying yappr with  Cloudflare Pages  Cloudflare Pages

Latest commit: a7fe12d
Status: ✅  Deploy successful!
Preview URL: https://3fff712b.yappr.pages.dev
Branch Preview URL: https://feat-devnet-moutai-support.yappr.pages.dev

View logs

SdkProvider hardcoded network: 'testnet'. It wraps the whole app and usually wins the race against the on-demand callers, so on a /devnet build every useSdk() consumer — the store, checkout, blog, DPNS and homepage paths — would have read testnet through the shared singleton until some later caller forced a reinit.

evoSdkService.initialize() also compared only network and contractId when deciding whether the existing instance could be reused, so a devnet config differing solely in its address pool or quorum URL would have been silently ignored; it now compares the whole config.
…ock survives

new DataContract({ schemas, tokens }) only accepts live TokenConfiguration handles, so the tokens option is unusable for anything read back from JSON, and the schemas-only form silently dropped tokens, groups, config, keywords and description. DataContract.fromJSON round-trips all of it, so the copy is a faithful clone; publishContract now also asserts the published contract came back with the token count it was given.

Verified against testnet: the re-owned JSON builds, signs and passes Drive's state transition validation, failing only on the owner's balance (a 16-doctype contract with the YAPP token costs ~93,000,100,000 credits and the e2e bots hold less).

Also: connectSdk resolved the network twice, insightUrl stripped its trailing slash on two separate paths, keyNetwork carried a parameter no caller passed, and owner-keys.mjs reported 'not found on testnet' regardless of the network it had connected to.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 27, 2026

Copy link
Copy Markdown

Deploying yappr-v2 with  Cloudflare Pages  Cloudflare Pages

Latest commit: a7fe12d
Status: ✅  Deploy successful!
Preview URL: https://43532637.yappr-v2.pages.dev
Branch Preview URL: https://feat-devnet-moutai-support.yappr-v2.pages.dev

View logs

@thepastaclaw

thepastaclaw commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

🔍 Review in progress — actively reviewing now (commit a7fe12d)
Stage: Codex precheck starting
ETA: complete ~14:05 UTC (median 19m across 30 recent reviews)
Running 14m · Last checked: 2026-08-27 14:00 UTC

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Preliminary review — Codex only

The network split and contract-cloning changes are structurally sound, but four in-scope blockers remain. The ChainLock provisioner reads a nonexistent SDK field, devnet payment polling targets testnet, the cloned contract owner cannot access token moderation, and the generated price-setting command silently switches back to testnet.
Source: Codex reviewer backend model was not provided in the supplied evidence; final Claude verifier backend model was not exposed to this agent; orchestration-only and not reviewer evidence: openclaw-agent/cliproxy/gpt-5.6-sol.

Validated blockers were found in the Codex precheck. Opus is deferred until a fresh Codex revalidation clears the blocker gate.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet: not run (deferred by blocker gate)

🔴 4 blocking

2 additional finding(s) omitted (not in diff).

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `scripts/provision-test-identity.mjs`:
- [BLOCKING] scripts/provision-test-identity.mjs:204-205: Read the SDK's snake-case ChainLock height
  `sdk.system.status()` returns the WASM `StatusResponse`. In the installed `@dashevo/wasm-sdk` 4.2.0-dev.2 declaration and runtime implementation, `StatusChain` exposes `core_chain_locked_height`; it has no `coreChainLockedHeight` getter. This expression therefore always falls back to zero, so every `--funding-outpoint` run waits for 600 seconds and times out even after the funding block is chain-locked, preventing the new devnet identity provisioning path from completing.

In `lib/constants.ts`:
- [BLOCKING] lib/constants.ts:10-12: Configure the token authority per deployment
  The cloned token configuration authorizes `ContractOwner` for freeze, unfreeze, destruction, emergency actions, and price changes. On devnet that owner is the newly provisioned maker identity, whose ID differs from the hard-coded testnet authority because it is derived from a different asset-lock outpoint. `app/settings/page.tsx` compares the logged-in identity against this constant, so the actual devnet contract owner cannot see or use the moderation controls. Make the authority publicly configurable and populate the corresponding `.env.devnet` value alongside the maker and contract IDs after provisioning.

In `lib/services/insight-api-service.ts`:
- [BLOCKING] lib/services/insight-api-service.ts:132-137: Route devnet tDASH polling to the configured Insight API
  Devnet uses testnet address prefixes and consequently the existing `tdash:` payment scheme, but this helper always converts that scheme to `testnet`. `use-dash-transaction-watcher.ts` passes the returned network explicitly to `waitForUtxo`, overriding its new `getConfiguredNetwork()` default. A `/devnet` checkout therefore polls the testnet Insight host instead of `NEXT_PUBLIC_INSIGHT_API_URL`, so native devnet payments are never detected.

In `scripts/register-test-contracts.mjs`:
- [BLOCKING] scripts/register-test-contracts.mjs:226-232: Preserve the target network in the generated price command
  The documented `NETWORK=devnet node scripts/register-test-contracts.mjs ...` assignment applies only to the registration process and does not persist in the shell. The emitted follow-up command omits `NETWORK`, while `set-yapp-price.mjs` defaults to testnet through `sdk-env.mjs`. Following the generated instructions therefore connects to testnet and cannot fetch or update the newly published devnet contract, leaving the cloned YAPP token without its required direct-purchase price.

Comment thread scripts/provision-test-identity.mjs Outdated
Comment on lines +226 to +232
if (published.NEXT_PUBLIC_YAPPR_CONTRACT_ID) {
console.log('');
console.log('Then set the YAPP direct-purchase price on the new social contract:');
console.log(
` node scripts/set-yapp-price.mjs --contract ${published.NEXT_PUBLIC_YAPPR_CONTRACT_ID}` +
` --owner ${ownerId} --owner-index ${args.ownerIndex}`
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔴 Blocking: Preserve the target network in the generated price command

The documented NETWORK=devnet node scripts/register-test-contracts.mjs ... assignment applies only to the registration process and does not persist in the shell. The emitted follow-up command omits NETWORK, while set-yapp-price.mjs defaults to testnet through sdk-env.mjs. Following the generated instructions therefore connects to testnet and cannot fetch or update the newly published devnet contract, leaving the cloned YAPP token without its required direct-purchase price.

Suggested change
if (published.NEXT_PUBLIC_YAPPR_CONTRACT_ID) {
console.log('');
console.log('Then set the YAPP direct-purchase price on the new social contract:');
console.log(
` node scripts/set-yapp-price.mjs --contract ${published.NEXT_PUBLIC_YAPPR_CONTRACT_ID}` +
` --owner ${ownerId} --owner-index ${args.ownerIndex}`
);
console.log(
` NETWORK=${network()} node scripts/set-yapp-price.mjs --contract ${published.NEXT_PUBLIC_YAPPR_CONTRACT_ID}` +
` --owner ${ownerId} --owner-index ${args.ownerIndex}`
);

source: ['codex']

…g requires

Faucets pay plain P2PKH, but Platform rejects such outpoints ('Funding transaction must have an Asset Lock Special Transaction Payload'). build-asset-lock.mjs spends the faucet UTXO into a DIP-2 type-8 transaction shaped per v4.2-dev's validate_asset_lock_transaction_structure_v0 (OP_RETURN burn output, P2PKH credit output in the payload, proof outpoint txid:0). Note: moutai's nodes run a low -maxtxfee, so the fee is 500 duffs.

.env.devnet now carries the live moutai state: v3-draft social contract (with refersTo + YAPP price set), profile clone, maker + bot identities registered 2026-08-27 via ChainLock asset locks.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Preliminary review — Codex only

The SDK network/key-network split and static devnet deployment are directionally sound, but eight in-scope blockers remain. All four previously verified blockers are still present, and the current head also selects testnet identities during bare devnet script runs, drops devnet between the new asset-lock commands, and exposes features backed only by testnet contract IDs. Source: Codex reviewer backend model was not provided in the supplied evidence; final Claude verifier backend model was not exposed to this agent; orchestration-only and not reviewer evidence: openclaw-agent/cliproxy/gpt-5.6-sol.

Validated blockers were found in the Codex precheck. Opus is deferred until a fresh Codex revalidation clears the blocker gate.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet: not run (deferred by blocker gate)

🔴 7 blocking

4 additional finding(s) omitted (not in diff).

2 carried-forward finding(s) already raised on this PR; not re-posting as new inline comments.

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `lib/constants.ts`:
- [BLOCKING] lib/constants.ts:10-12: Configure the token authority per deployment
  The cloned token authorizes its `ContractOwner` to freeze, unfreeze, destroy frozen balances, and perform the other administrative actions. The devnet contract maker recorded in `.env.devnet` is `DuqE3...`, but this UI gate remains fixed to the testnet authority `hbGEc...`. Because `app/settings/page.tsx` uses this constant to render the moderation section, the actual devnet contract owner cannot access those controls. Make the authority deployment-configurable and set `NEXT_PUBLIC_YAPP_TOKEN_AUTHORITY_ID` to the devnet maker identity in `.env.devnet`.
- [BLOCKING] lib/constants.ts:22-43: Do not route devnet features through testnet contract IDs
  The devnet environment configures only the social and profile contracts, while the DM, storefront, encrypted-backup, key-exchange, vault, auth-vault, blog, and Pollr constants remain nonempty testnet fallbacks. The `/devnet` export still exposes messages, store, checkout, blog, polls, and vault-backed authentication, so their services query IDs that do not exist on moutai. Preloading merely logs several missing-contract failures, and checks such as `authVaultService.isConfigured()` incorrectly report that the fallback contract is available. Provision and configure devnet copies of these contracts, or make these values empty on devnet and gate the corresponding routes and controls.

In `lib/services/insight-api-service.ts`:
- [BLOCKING] lib/services/insight-api-service.ts:132-137: Route devnet tDASH polling to the configured Insight API
  Devnet addresses use the testnet prefix and therefore retain the `tdash:` payment scheme, but this helper always maps that scheme to the SDK network `testnet`. `use-dash-transaction-watcher.ts` passes the result explicitly to `waitForUtxo`, overriding its deployment-aware default. A `/devnet` checkout consequently polls the testnet Insight host instead of `NEXT_PUBLIC_INSIGHT_API_URL`, so native devnet payments are never detected.

In `scripts/derive-identities.mjs`:
- [BLOCKING] scripts/derive-identities.mjs:137-140: Load the devnet identity pool for devnet script runs
  `loadIdentityIds()` always falls back to `.env.testing`, even when `NETWORK=devnet`. The new `sdk-env.mjs` reads `.env.devnet` lazily but does not export its values into `process.env`, so a bare devnet invocation still loads the testnet IDs `GENx...` and `BSHD...` instead of the devnet IDs checked into `.env.devnet`. As a result, `provision-test-identity.mjs --check-balances`, `verify-poll-interop.mjs`, bot-based owner resolution, and the default registration owner query testnet identities through the devnet SDK unless the operator separately exports `E2E_IDENTITY_IDS`, contradicting the documented bare `NETWORK=devnet` workflow.

In `scripts/build-asset-lock.mjs`:
- [BLOCKING] scripts/build-asset-lock.mjs:19-23: Keep devnet selected for the provisioning command
  The first command's `NETWORK=devnet` assignment is process-local and does not persist after `build-asset-lock.mjs` exits. The immediately following provisioning command omits it, so `provision-test-identity.mjs` defaults to testnet and attempts to use a moutai asset-lock outpoint against testnet. The saved key permits recovery and retry, but the newly documented end-to-end provisioning sequence cannot complete as written.

In `scripts/provision-test-identity.mjs`:
- [BLOCKING] scripts/provision-test-identity.mjs:204-205: Read the SDK's snake-case ChainLock height
  (existing thread: https://github.com/PastaPastaPasta/yappr/pull/304#discussion_r3868677011)
  `sdk.system.status()` directly returns the WASM `StatusResponse`. In the installed `@dashevo/wasm-sdk` 4.2.0-dev.2 declaration, `StatusResponse.chain` is a `StatusChain` whose accessor is `core_chain_locked_height`; there is no `coreChainLockedHeight` accessor. The current expression therefore always falls back to zero, so automatic `--funding-outpoint` provisioning waits for the full 600-second timeout even after the funding block has been chain-locked.

In `scripts/register-test-contracts.mjs`:
- [BLOCKING] scripts/register-test-contracts.mjs:226-232: Preserve the target network in the generated price command
  (existing thread: https://github.com/PastaPastaPasta/yappr/pull/304#discussion_r3868677032)
  The `NETWORK=devnet` assignment used to invoke contract registration applies only to that process. The generated follow-up command omits `NETWORK`, while `set-yapp-price.mjs` defaults to testnet through `sdk-env.mjs`. Following the printed reset/provisioning workflow therefore looks for the newly published contract on testnet and leaves the cloned devnet YAPP token without its direct-purchase price.

Comment on lines +19 to +23
* Run:
* NETWORK=devnet node scripts/build-asset-lock.mjs --key-file <keyfile> --outpoint <txid>:<vout>
* then:
* node scripts/provision-test-identity.mjs <idx> --asset-lock-key-file <keyfile> \
* --funding-outpoint <printedTxid>:0 [--chain-lock <height>]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔴 Blocking: Keep devnet selected for the provisioning command

The first command's NETWORK=devnet assignment is process-local and does not persist after build-asset-lock.mjs exits. The immediately following provisioning command omits it, so provision-test-identity.mjs defaults to testnet and attempts to use a moutai asset-lock outpoint against testnet. The saved key permits recovery and retry, but the newly documented end-to-end provisioning sequence cannot complete as written.

Suggested change
* Run:
* NETWORK=devnet node scripts/build-asset-lock.mjs --key-file <keyfile> --outpoint <txid>:<vout>
* then:
* node scripts/provision-test-identity.mjs <idx> --asset-lock-key-file <keyfile> \
* --funding-outpoint <printedTxid>:0 [--chain-lock <height>]
* Run:
* NETWORK=devnet node scripts/build-asset-lock.mjs --key-file <keyfile> --outpoint <txid>:<vout>
* then:
* NETWORK=devnet node scripts/provision-test-identity.mjs <idx> --asset-lock-key-file <keyfile> \
* --funding-outpoint <printedTxid>:0 [--chain-lock <height>]

source: ['codex']

1) The chain-lock wait read coreChainLockedHeight off the live StatusChain class, which only exposes the snake_case getter - the value was always undefined, so every --funding-outpoint run timed out at 600s (this, not status caching, was the provisioning failure; verified live: camelCase=undefined, snake_case=41272). Accept both shapes. 2) YAPP_TOKEN_AUTHORITY_ID is now env-overridable and .env.devnet points it at the devnet contract owner so moderation controls gate correctly per deployment. 3) getNetworkFromScheme resolves tdash: to the configured network (devnets share testnet prefixes), so /devnet polls its own Insight host instead of testnet's. 4) register-test-contracts' emitted follow-up command now carries NETWORK= so the price step targets the network the contract was published on.
@PastaPastaPasta

Copy link
Copy Markdown
Owner Author

Triage of the four review findings — all four are valid, all fixed in 8aff4f7:

1. snake_case ChainLock height — valid, and it explains a live incident. Verified against moutai: status.chain.coreChainLockedHeight = undefined while status.chain.core_chain_locked_height = 41272 on the same response. This (not status caching, as the earlier provisioning notes guessed) is why every --funding-outpoint run timed out at 600s; provisioning only succeeded via the --chain-lock bypass. The wait now reads the snake_case getter with a camelCase fallback for plain-object shapes.

2. Token authority per deployment — valid, fixed. YAPP_TOKEN_AUTHORITY_ID is now NEXT_PUBLIC_YAPP_TOKEN_AUTHORITY_ID-overridable, and .env.devnet sets it to the devnet contract owner (DuqE3z…gKGA) so the settings moderation gate works there.

3. tdash: → testnet hardcode — valid, fixed. getNetworkFromScheme now resolves tdash: through getConfiguredNetwork() (devnets share testnet prefixes), so a /devnet build polls its configured Insight host.

4. Emitted price command loses NETWORK — valid, fixed. The follow-up command now carries NETWORK=<network>.

Validation: lint + build clean; snake_case getter verified live against moutai.


🤖 Posted autonomously by Claude on behalf of pasta.

…y pool

Optional contract constants now use ?? so .env.devnet can explicitly blank the eight contracts that have no moutai copy (DM, storefront, key backup, key exchange, vault, auth vault, blog, pollr) - preload skips them and every isConfigured() gate fails closed instead of querying testnet ids that do not exist on this chain. loadIdentityIds() picks .env.devnet under NETWORK=devnet (env var still wins), so bot-based scripts stop silently using the testnet pool. build-asset-lock's doc header repeats NETWORK=devnet on the provisioning command since the assignment is process-local.
@PastaPastaPasta

Copy link
Copy Markdown
Owner Author

Second review round triage — four of the seven findings were already fixed in 8aff4f7 (this round reviewed adfe64a): token authority (env-overridable, .env.devnet set), tdash: Insight routing, the snake_case ChainLock getter, and the price command's NETWORK prefix — see the earlier triage comment for details.

The three new findings are all valid, fixed in the follow-up commit:

Testnet fallbacks on /devnet — fixed. The eight optional contract constants switched from || to ??, and .env.devnet now explicitly blanks them: the preload's existing truthiness guards skip empty ids and every isConfigured()-style gate fails closed, so DM/store/blog/polls/vault features report unavailable on /devnet instead of querying ids that don't exist on moutai. Each lights up by provisioning a devnet copy and filling in its id.

loadIdentityIds() ignoring NETWORK=devnet — fixed. It now reads .env.devnet under NETWORK=devnet (the env var still wins). Verified: a bare NETWORK=devnet run returns the two moutai bot identities.

build-asset-lock's doc header — fixed. The provisioning example now repeats NETWORK=devnet, with a note that the assignment is process-local.

Validation: lint + build clean; devnet identity loading verified.


🤖 Posted autonomously by Claude on behalf of pasta.

@PastaPastaPasta
PastaPastaPasta merged commit 6ab0cec into staging Aug 27, 2026
3 checks passed
@PastaPastaPasta
PastaPastaPasta deleted the feat/devnet-moutai-support branch August 27, 2026 13:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants