From fd53da8d38c83671b0e3f4cf70e9cb6ec757b0b1 Mon Sep 17 00:00:00 2001 From: Bruce Wayne Date: Thu, 23 Jul 2026 16:42:52 -0300 Subject: [PATCH 01/20] feat: compiling version --- VERSIONS.md | 30 +- apps/cli/package.json | 4 +- apps/ui/package.json | 2 +- apps/ui/vite.config.ts | 10 +- packages/api/package.json | 14 +- packages/contract/package.json | 20 +- packages/contract/src/index.ts | 3 +- packages/contract/src/sentinel.compact | 73 ++-- packages/wallet/package.json | 4 +- pnpm-lock.yaml | 490 ++++++++++++------------- 10 files changed, 314 insertions(+), 336 deletions(-) diff --git a/VERSIONS.md b/VERSIONS.md index 1bae143..b62656e 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -1,10 +1,16 @@ # Component Versions -2026-04-24 +2026-07-23 ## Versions in Use -- Compact compiler: 0.30.0 +- Compact devtools: 0.5.1 +- Compact compiler: 0.31.1 +- Compact language: 0.23.0 +- Compact runtime: 0.16.0 +- Compact JS: 2.5.1 +- Midnight.js: 4.1.1 +- On-chain runtime: 3.0.0 - Indexer: 4.0.0 - Proof server: 8.0.2 - Midnight node: 0.22.0 @@ -14,16 +20,18 @@ ````json "dependencies": { - "@midnight-ntwrk/compact-js": "2.5.0", - "@midnight-ntwrk/compact-runtime": "0.15.0", + "@midnight-ntwrk/compact-js": "2.5.1", + "@midnight-ntwrk/compact-runtime": "0.16.0", "@midnight-ntwrk/ledger-v8": "8.0.3", - "@midnight-ntwrk/midnight-js-contracts": "4.0.4", - "@midnight-ntwrk/midnight-js-http-client-proof-provider": "4.0.4", - "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "4.0.4", - "@midnight-ntwrk/midnight-js-level-private-state-provider": "4.0.4", - "@midnight-ntwrk/midnight-js-network-id": "4.0.4", - "@midnight-ntwrk/midnight-js-node-zk-config-provider": "4.0.4", - "@midnight-ntwrk/midnight-js-types": "4.0.4", + "@midnight-ntwrk/midnight-js-contracts": "4.1.1", + "@midnight-ntwrk/midnight-js-fetch-zk-config-provider": "4.1.1", + "@midnight-ntwrk/midnight-js-http-client-proof-provider": "4.1.1", + "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "4.1.1", + "@midnight-ntwrk/midnight-js-level-private-state-provider": "4.1.1", + "@midnight-ntwrk/midnight-js-network-id": "4.1.1", + "@midnight-ntwrk/midnight-js-node-zk-config-provider": "4.1.1", + "@midnight-ntwrk/midnight-js-types": "4.1.1", + "@midnight-ntwrk/onchain-runtime-v3": "3.0.0", "@midnight-ntwrk/wallet-sdk-address-format": "3.1.0-rc.0", "@midnight-ntwrk/wallet-sdk-capabilities": "3.2.0-rc.0", "@midnight-ntwrk/wallet-sdk-abstractions": "2.0.0", diff --git a/apps/cli/package.json b/apps/cli/package.json index 1cbbea6..41de09b 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -15,9 +15,9 @@ "lint:fix": "eslint . --fix" }, "dependencies": { - "@midnight-ntwrk/compact-runtime": "0.15.0", + "@midnight-ntwrk/compact-runtime": "0.16.0", "@midnight-ntwrk/ledger-v8": "8.0.3", - "@midnight-ntwrk/midnight-js-contracts": "4.0.4", + "@midnight-ntwrk/midnight-js-contracts": "4.1.1", "@midnight-ntwrk/wallet-sdk-address-format": "3.1.0-rc.0", "@midnight-ntwrk/zswap": "4.0.0", "@midnight-sentinel/api": "workspace:*", diff --git a/apps/ui/package.json b/apps/ui/package.json index beb2467..011aa11 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@midnight-ntwrk/dapp-connector-api": "4.0.1", - "@midnight-ntwrk/onchain-runtime-v2": "^2.0.1", + "@midnight-ntwrk/onchain-runtime-v3": "3.0.0", "@midnight-sentinel/api": "workspace:*", "@midnight-sentinel/contract": "workspace:*", "@radix-ui/react-label": "^2.1.8", diff --git a/apps/ui/vite.config.ts b/apps/ui/vite.config.ts index 226bc06..c50670c 100644 --- a/apps/ui/vite.config.ts +++ b/apps/ui/vite.config.ts @@ -25,7 +25,7 @@ export default defineConfig({ name: 'wasm-module-resolver', resolveId(source, importer) { if ( - source === '@midnight-ntwrk/onchain-runtime-v2' && + source === '@midnight-ntwrk/onchain-runtime-v3' && importer && importer.includes('@midnight-ntwrk/compact-runtime') ) { @@ -51,7 +51,7 @@ export default defineConfig({ rollupOptions: { output: { manualChunks(id) { - if (id.includes('@midnight-ntwrk/onchain-runtime-v2')) { + if (id.includes('@midnight-ntwrk/onchain-runtime-v3')) { return 'wasm'; } }, @@ -75,9 +75,9 @@ export default defineConfig({ }, include: ['@midnight-ntwrk/compact-runtime'], exclude: [ - '@midnight-ntwrk/onchain-runtime-v2', - '@midnight-ntwrk/onchain-runtime-v2/midnight_onchain_runtime_wasm_bg.wasm', - '@midnight-ntwrk/onchain-runtime-v2/midnight_onchain_runtime_wasm.js', + '@midnight-ntwrk/onchain-runtime-v3', + '@midnight-ntwrk/onchain-runtime-v3/midnight_onchain_runtime_wasm_bg.wasm', + '@midnight-ntwrk/onchain-runtime-v3/midnight_onchain_runtime_wasm.js', ], }, }); diff --git a/packages/api/package.json b/packages/api/package.json index 521941d..ff8cf2a 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -23,15 +23,15 @@ "build": "tsc --project ./tsconfig.build.json" }, "dependencies": { - "@midnight-ntwrk/compact-js": "2.5.0", - "@midnight-ntwrk/compact-runtime": "0.15.0", + "@midnight-ntwrk/compact-js": "2.5.1", + "@midnight-ntwrk/compact-runtime": "0.16.0", "@midnight-ntwrk/dapp-connector-api": "4.0.1", "@midnight-ntwrk/ledger-v8": "8.0.3", - "@midnight-ntwrk/midnight-js-contracts": "4.0.4", - "@midnight-ntwrk/midnight-js-fetch-zk-config-provider": "4.0.4", - "@midnight-ntwrk/midnight-js-http-client-proof-provider": "4.0.4", - "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "4.0.4", - "@midnight-ntwrk/midnight-js-types": "4.0.4", + "@midnight-ntwrk/midnight-js-contracts": "4.1.1", + "@midnight-ntwrk/midnight-js-fetch-zk-config-provider": "4.1.1", + "@midnight-ntwrk/midnight-js-http-client-proof-provider": "4.1.1", + "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "4.1.1", + "@midnight-ntwrk/midnight-js-types": "4.1.1", "@midnight-ntwrk/wallet-sdk-address-format": "3.1.0-rc.0", "@midnight-sentinel/contract": "workspace:*", "@midnight-sentinel/wallet": "workspace:*", diff --git a/packages/contract/package.json b/packages/contract/package.json index a7df772..0b81393 100644 --- a/packages/contract/package.json +++ b/packages/contract/package.json @@ -18,8 +18,8 @@ } }, "scripts": { - "compact": "compact compile src/sentinel.compact ./src/managed/sentinel", - "compact:no-zk": "compact compile --skip-zk src/sentinel.compact ./src/managed/sentinel", + "compact": "compact compile +0.31.1 src/sentinel.compact ./src/managed/sentinel", + "compact:no-zk": "compact compile +0.31.1 --skip-zk src/sentinel.compact ./src/managed/sentinel", "clean": "rm -rf dist .turbo src/managed node_modules", "clear": "rm -rf dist src/managed", "check": "pnpm clear && pnpm compact:no-zk && mkdir -p dist && tsc --project ./tsconfig.build.json && cp -Rf ./src/managed ./dist/managed && cp ./src/sentinel.compact ./dist", @@ -30,14 +30,14 @@ "lint:fix": "eslint . --fix" }, "dependencies": { - "@midnight-ntwrk/compact-js": "2.5.0", - "@midnight-ntwrk/compact-runtime": "0.15.0", - "@midnight-ntwrk/midnight-js-contracts": "4.0.4", - "@midnight-ntwrk/midnight-js-http-client-proof-provider": "4.0.4", - "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "4.0.4", - "@midnight-ntwrk/midnight-js-level-private-state-provider": "4.0.4", - "@midnight-ntwrk/midnight-js-node-zk-config-provider": "4.0.4", - "@midnight-ntwrk/midnight-js-types": "4.0.4", + "@midnight-ntwrk/compact-js": "2.5.1", + "@midnight-ntwrk/compact-runtime": "0.16.0", + "@midnight-ntwrk/midnight-js-contracts": "4.1.1", + "@midnight-ntwrk/midnight-js-http-client-proof-provider": "4.1.1", + "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "4.1.1", + "@midnight-ntwrk/midnight-js-level-private-state-provider": "4.1.1", + "@midnight-ntwrk/midnight-js-node-zk-config-provider": "4.1.1", + "@midnight-ntwrk/midnight-js-types": "4.1.1", "@midnight-sentinel/wallet": "workspace:*", "rxjs": "^7.8.2" }, diff --git a/packages/contract/src/index.ts b/packages/contract/src/index.ts index ac3e95b..e5aec8a 100644 --- a/packages/contract/src/index.ts +++ b/packages/contract/src/index.ts @@ -1,9 +1,10 @@ import { CompiledContract } from '@midnight-ntwrk/compact-js'; +import type { PrivateState } from './private-state.js'; import { SentinelContractConstructor, SentinelContractType } from './types.js'; import { witnesses } from './witnesses.js'; const tag = 'SentinelContract'; -export const CompactCompiledContract = CompiledContract.make( +export const CompactCompiledContract = CompiledContract.make( tag, SentinelContractConstructor ).pipe( diff --git a/packages/contract/src/sentinel.compact b/packages/contract/src/sentinel.compact index fd1d187..5bab278 100644 --- a/packages/contract/src/sentinel.compact +++ b/packages/contract/src/sentinel.compact @@ -1,13 +1,18 @@ -pragma language_version 0.22; +pragma language_version 0.23; import CompactStandardLibrary; ///////// LEDGER ///////// export ledger owner: Bytes<32>; + export ledger delegators: Map, Uint<128>>; + export ledger shieldedVault: QualifiedShieldedCoinInfo; + export ledger rewardsVault: QualifiedShieldedCoinInfo; + export ledger hasShielded: Boolean; + export ledger hasRewards: Boolean; ///////// CONSTRUCTOR ///////// @@ -26,15 +31,15 @@ export circuit delegate(coin: ShieldedCoinInfo): [] { receiveShielded(disclose(coin)); if (!hasShielded) { - shieldedVault.writeCoin( - disclose(coin), - right(kernel.self()) + shieldedVault.writeCoin( + disclose(coin), + right(kernel.self()) ); - hasShielded = true; + hasShielded = true; } else { - shieldedVault.writeCoin( - mergeCoinImmediate(shieldedVault, disclose(coin)), - right(kernel.self()) + shieldedVault.writeCoin( + mergeCoinImmediate(shieldedVault, disclose(coin)), + right(kernel.self()) ); } @@ -49,19 +54,14 @@ export circuit withdraw(): [] { shieldedVault, left(ownPublicKey()), disclose(shieldedVault.value) - ); + ); hasShielded = false; - shieldedVault = QualifiedShieldedCoinInfo { - nonce: pad(32, ""), - color: pad(32, ""), - value: 0, - mt_index: 0 - }; + shieldedVault = + QualifiedShieldedCoinInfo { nonce: pad(32, ""), color: pad(32, ""), value: 0, mt_index: 0 }; } - export circuit depositRewards(coin: ShieldedCoinInfo): [] { assert(coin.value > 0, "Deposit amount must be positive"); @@ -71,18 +71,20 @@ export circuit depositRewards(coin: ShieldedCoinInfo): [] { rewardsVault.writeCoin( disclose(coin), right(kernel.self()) - ); + ); hasRewards = true; } else { rewardsVault.writeCoin( - mergeCoinImmediate(rewardsVault, disclose(coin)), - right(kernel.self()) - ); + mergeCoinImmediate(rewardsVault, disclose(coin)), + right(kernel.self()) + ); } } -export circuit redeemRewards():[] { - assert(delegators.member(disclose(publicKey(localSecretKey()))), "You must be a delegator to redeem rewards"); +export circuit redeemRewards(): [] { + assert(delegators.member(disclose(publicKey(localSecretKey()))), + "You must be a delegator to redeem rewards" + ); assert(hasRewards, "No tokens in vault"); const callerKey = ownPublicKey(); @@ -91,25 +93,28 @@ export circuit redeemRewards():[] { vault, left(disclose(callerKey)), disclose(vault.value) - ); + ); hasRewards = false; - rewardsVault = QualifiedShieldedCoinInfo { - nonce: pad(32, ""), - color: pad(32, ""), - value: 0, - mt_index: 0 - }; + rewardsVault = + QualifiedShieldedCoinInfo { nonce: pad(32, ""), color: pad(32, ""), value: 0, mt_index: 0 }; } - - ///////// HELPER CIRCUIT TO MINT TOKENS TO PAY ///////// -export circuit mintDirectShielded(domainSep: Bytes<32>, mintValue: Uint<64>, mintNonce: Bytes<32>, publicKey: ZswapCoinPublicKey): [] { - mintShieldedToken(disclose(domainSep), disclose(mintValue), disclose(mintNonce), left(disclose(publicKey))); +export circuit mintDirectShielded( + domainSep: Bytes<32>, + mintValue: Uint<64>, + mintNonce: Bytes<32>, + publicKey: ZswapCoinPublicKey + ): [] { + mintShieldedToken( + disclose(domainSep), + disclose(mintValue), + disclose(mintNonce), + left(disclose(publicKey)) + ); } - ///////// HELPER CIRCUITS ///////// circuit publicKey(sk: Bytes<32>): Bytes<32> { diff --git a/packages/wallet/package.json b/packages/wallet/package.json index e8145a8..41cd660 100644 --- a/packages/wallet/package.json +++ b/packages/wallet/package.json @@ -19,8 +19,8 @@ }, "dependencies": { "@midnight-ntwrk/ledger-v8": "8.0.3", - "@midnight-ntwrk/midnight-js-network-id": "4.0.4", - "@midnight-ntwrk/midnight-js-types": "4.0.4", + "@midnight-ntwrk/midnight-js-network-id": "4.1.1", + "@midnight-ntwrk/midnight-js-types": "4.1.1", "@midnight-ntwrk/wallet-sdk-address-format": "3.1.0-rc.0", "@midnight-ntwrk/wallet-sdk-dust-wallet": "3.0.0-rc.0", "@midnight-ntwrk/wallet-sdk-facade": "3.0.0-rc.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c14340..58e52b6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,14 +36,14 @@ importers: apps/cli: dependencies: '@midnight-ntwrk/compact-runtime': - specifier: 0.15.0 - version: 0.15.0 + specifier: 0.16.0 + version: 0.16.0 '@midnight-ntwrk/ledger-v8': specifier: 8.0.3 version: 8.0.3 '@midnight-ntwrk/midnight-js-contracts': - specifier: 4.0.4 - version: 4.0.4 + specifier: 4.1.1 + version: 4.1.1 '@midnight-ntwrk/wallet-sdk-address-format': specifier: 3.1.0-rc.0 version: 3.1.0-rc.0 @@ -93,9 +93,9 @@ importers: '@midnight-ntwrk/dapp-connector-api': specifier: 4.0.1 version: 4.0.1 - '@midnight-ntwrk/onchain-runtime-v2': - specifier: ^2.0.1 - version: 2.0.1 + '@midnight-ntwrk/onchain-runtime-v3': + specifier: 3.0.0 + version: 3.0.0 '@midnight-sentinel/api': specifier: workspace:* version: link:../../packages/api @@ -203,11 +203,11 @@ importers: packages/api: dependencies: '@midnight-ntwrk/compact-js': - specifier: 2.5.0 - version: 2.5.0 + specifier: 2.5.1 + version: 2.5.1 '@midnight-ntwrk/compact-runtime': - specifier: 0.15.0 - version: 0.15.0 + specifier: 0.16.0 + version: 0.16.0 '@midnight-ntwrk/dapp-connector-api': specifier: 4.0.1 version: 4.0.1 @@ -215,20 +215,20 @@ importers: specifier: 8.0.3 version: 8.0.3 '@midnight-ntwrk/midnight-js-contracts': - specifier: 4.0.4 - version: 4.0.4 + specifier: 4.1.1 + version: 4.1.1 '@midnight-ntwrk/midnight-js-fetch-zk-config-provider': - specifier: 4.0.4 - version: 4.0.4 + specifier: 4.1.1 + version: 4.1.1 '@midnight-ntwrk/midnight-js-http-client-proof-provider': - specifier: 4.0.4 - version: 4.0.4 + specifier: 4.1.1 + version: 4.1.1 '@midnight-ntwrk/midnight-js-indexer-public-data-provider': - specifier: 4.0.4 - version: 4.0.4(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: 4.1.1 + version: 4.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@midnight-ntwrk/midnight-js-types': - specifier: 4.0.4 - version: 4.0.4 + specifier: 4.1.1 + version: 4.1.1 '@midnight-ntwrk/wallet-sdk-address-format': specifier: 3.1.0-rc.0 version: 3.1.0-rc.0 @@ -255,29 +255,29 @@ importers: packages/contract: dependencies: '@midnight-ntwrk/compact-js': - specifier: 2.5.0 - version: 2.5.0 + specifier: 2.5.1 + version: 2.5.1 '@midnight-ntwrk/compact-runtime': - specifier: 0.15.0 - version: 0.15.0 + specifier: 0.16.0 + version: 0.16.0 '@midnight-ntwrk/midnight-js-contracts': - specifier: 4.0.4 - version: 4.0.4 + specifier: 4.1.1 + version: 4.1.1 '@midnight-ntwrk/midnight-js-http-client-proof-provider': - specifier: 4.0.4 - version: 4.0.4 + specifier: 4.1.1 + version: 4.1.1 '@midnight-ntwrk/midnight-js-indexer-public-data-provider': - specifier: 4.0.4 - version: 4.0.4(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: 4.1.1 + version: 4.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@midnight-ntwrk/midnight-js-level-private-state-provider': - specifier: 4.0.4 - version: 4.0.4 + specifier: 4.1.1 + version: 4.1.1 '@midnight-ntwrk/midnight-js-node-zk-config-provider': - specifier: 4.0.4 - version: 4.0.4 + specifier: 4.1.1 + version: 4.1.1 '@midnight-ntwrk/midnight-js-types': - specifier: 4.0.4 - version: 4.0.4 + specifier: 4.1.1 + version: 4.1.1 '@midnight-sentinel/wallet': specifier: workspace:* version: link:../wallet @@ -298,29 +298,29 @@ importers: specifier: 8.0.3 version: 8.0.3 '@midnight-ntwrk/midnight-js-network-id': - specifier: 4.0.4 - version: 4.0.4 + specifier: 4.1.1 + version: 4.1.1 '@midnight-ntwrk/midnight-js-types': - specifier: 4.0.4 - version: 4.0.4 + specifier: 4.1.1 + version: 4.1.1 '@midnight-ntwrk/wallet-sdk-address-format': specifier: 3.1.0-rc.0 version: 3.1.0-rc.0 '@midnight-ntwrk/wallet-sdk-dust-wallet': specifier: 3.0.0-rc.0 - version: 3.0.0-rc.0(ws@8.19.0) + version: 3.0.0-rc.0(ws@8.21.1) '@midnight-ntwrk/wallet-sdk-facade': specifier: 3.0.0-rc.0 - version: 3.0.0-rc.0(ws@8.19.0) + version: 3.0.0-rc.0(ws@8.21.1) '@midnight-ntwrk/wallet-sdk-hd': specifier: 3.0.1 version: 3.0.1 '@midnight-ntwrk/wallet-sdk-shielded': specifier: 2.1.0-rc.0 - version: 2.1.0-rc.0(ws@8.19.0) + version: 2.1.0-rc.0(ws@8.21.1) '@midnight-ntwrk/wallet-sdk-unshielded-wallet': specifier: 2.1.0-rc.0 - version: 2.1.0-rc.0(ws@8.19.0) + version: 2.1.0-rc.0(ws@8.21.1) rxjs: specifier: ^7.8.2 version: 7.8.2 @@ -344,13 +344,14 @@ packages: resolution: {integrity: sha512-9q/yCljni37pkMr4sPrI3G4jqdIk074+iukc5aFJl7kmDCCsiJrbZ6zKxnES1Gwg+i9RcDZwvktl23puGslmvA==} hasBin: true - '@apollo/client@3.14.0': - resolution: {integrity: sha512-0YQKKRIxiMlIou+SekQqdCo0ZTHxOcES+K8vKB53cIDpwABNR0P0yRzPgsbgcj3zRJniD93S/ontsnZsCLZrxQ==} + '@apollo/client@4.2.7': + resolution: {integrity: sha512-Z129zR77VP0oWWIXPpHgwbtwhCVBzkw/FhiiymbqwlUniKb5z2KBeuQkpNIz3QfuO7ezHiaxsJ0PPjYBJTSHtg==} peerDependencies: - graphql: ^15.0.0 || ^16.0.0 + graphql: ^16.0.0 || ^17.0.0 graphql-ws: ^5.5.5 || ^6.0.3 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc + react: ^17.0.0 || ^18.0.0 || >=19.0.0-rc + react-dom: ^17.0.0 || ^18.0.0 || >=19.0.0-rc + rxjs: ^7.3.0 subscriptions-transport-ws: ^0.9.0 || ^0.11.0 peerDependenciesMeta: graphql-ws: @@ -834,11 +835,11 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@midnight-ntwrk/compact-js@2.5.0': - resolution: {integrity: sha512-vCLbndc3GtSTjZE30NtNqINOrrjZ2O/m8q4jCVeyx9t+rTLwwFqGXUOYobKbM34rwfnTz95ZAI6vVixYnarfrA==} + '@midnight-ntwrk/compact-js@2.5.1': + resolution: {integrity: sha512-UO0+tDiRadYFO2kaUu9PIhAOISvDKvmiyh9vSOyKEZLj3AUAY1qmmHm/oQrzPqrkiWHO0Rnln/j/zZ5dvc5vxg==} - '@midnight-ntwrk/compact-runtime@0.15.0': - resolution: {integrity: sha512-6+odrxb83ZZwF29SqNBBgaOR3hf5ej4KZVGibDwdXta9+uiv6qYW0fMrsKP9S+c2AqvY+vEb5NEFFI54lA7G5A==} + '@midnight-ntwrk/compact-runtime@0.16.0': + resolution: {integrity: sha512-UR8+MI5zol+gkne17ZZru8xmZNf11xMgQJkiLT9KWsF+QD8Wuz1WONaqQLL0RAZ2aN2XlRsBFd0ViKDVh8prFw==} '@midnight-ntwrk/dapp-connector-api@4.0.1': resolution: {integrity: sha512-IstRo8xfmlJlGgUcAJeRzPUmGCp0/xZR3/bfTOk33MHrzbGmB79r/zYziGavcclo1pkkoBHAodX4vz3xgFxczw==} @@ -846,35 +847,35 @@ packages: '@midnight-ntwrk/ledger-v8@8.0.3': resolution: {integrity: sha512-2BLMfHnZyKAHZxaGWjlenhity096lZ9jvyp1qrQH5J3ogpy7H5ostCW8kRj70dGv5T55qDAC5nJ+wnU/8CGUcQ==} - '@midnight-ntwrk/midnight-js-contracts@4.0.4': - resolution: {integrity: sha512-lu9A45upwvPSy/YohU0TMvaeL2ru1zJt6axbuGwGup2L16p+fN8ojjHji6YGEC6XkgyISH3L0GfsgxsQKmt56g==} + '@midnight-ntwrk/midnight-js-contracts@4.1.1': + resolution: {integrity: sha512-vHeKyaj9RXTx+Cep3YpIRgg2EWkzibO4gxI7k/rYpXmGXc3waetrR5sb9FPR7v9SUX00/zf/sYbLp1NZhwZbjg==} - '@midnight-ntwrk/midnight-js-fetch-zk-config-provider@4.0.4': - resolution: {integrity: sha512-WMXxBVqDlAiu3c0ZTE3WaEUfNR4fRAEmaTlhr0U3pjaFKu77qiBp7bB+oIB8N6Xtj0Y1KRn/wlGLbZjxEL9jVg==} + '@midnight-ntwrk/midnight-js-fetch-zk-config-provider@4.1.1': + resolution: {integrity: sha512-d9vPAXjCvbe3KdPDBwp8Cp4HXwdo9jfCjeyJjJPfIDHCctXrIQ7btu+CqY1+bgV05FHVMOBLjJ9TS2tc2xdwrw==} - '@midnight-ntwrk/midnight-js-http-client-proof-provider@4.0.4': - resolution: {integrity: sha512-G6q7uPqKhCGZyKUo21MhoBjtfQ4pw6AEMBM6zrAvhrCFE9WV4FVrG1rpbJDtwxr/VVecIt1tD4myVqg2sEPNhg==} + '@midnight-ntwrk/midnight-js-http-client-proof-provider@4.1.1': + resolution: {integrity: sha512-Q2VNRTvuWlf3MbjV7C3asuLHeFwtZMdh6t5Z+u8c8RDpejW9GhKaSjlJ2zgJII5ckE8/cocPEDwt3GKBebGHWA==} - '@midnight-ntwrk/midnight-js-indexer-public-data-provider@4.0.4': - resolution: {integrity: sha512-mCUy9VYg1islt5m6/2wb9RI7Q/2ZUWHZj+hdHHyAwcZ41DyhET/gOXPnfKjQu2Q5zoyEHOAko1yVcY3IMYmEvA==} + '@midnight-ntwrk/midnight-js-indexer-public-data-provider@4.1.1': + resolution: {integrity: sha512-50CjiYqw/msRintyfkuNrJl1v4Zj+AaBFmV2uLjY/zpkPop4VBFH4QZSrtKsG1ljT9vzWsxU7Eo4Tn1tgKkPlA==} - '@midnight-ntwrk/midnight-js-level-private-state-provider@4.0.4': - resolution: {integrity: sha512-/qjvkxZWkkaAxFXqqhAQiloczHjw/gS4aC7L8FyRvgxeOfT1oecVp8+/tbsvZb3Zd2eolQJ46HHcjm/sKBgEbQ==} + '@midnight-ntwrk/midnight-js-level-private-state-provider@4.1.1': + resolution: {integrity: sha512-i+1lXJ4rcRGERnOPXHc3/Oh5ef7X/yb6p51bOAUjITazdPPcuU8qTeTeFgX+lPhWTGxROb9/pgrrVPH9G0FxSw==} - '@midnight-ntwrk/midnight-js-network-id@4.0.4': - resolution: {integrity: sha512-bieKcN9ybIf+HoaHPjCJGxmYrcmUrdEOErxkxhiqgos2k7DKM9mZO7AgzN0WnBbPHYdT4z0c740QAcG95BaV8g==} + '@midnight-ntwrk/midnight-js-network-id@4.1.1': + resolution: {integrity: sha512-g49IBK0In1Jt2wKgi18rUvoy1ShGVrfb/VvgWALkc9Noc6+4MX5NTLVg5zqu+CyO2waIGb5lqDPnAhTD9wPAOQ==} - '@midnight-ntwrk/midnight-js-node-zk-config-provider@4.0.4': - resolution: {integrity: sha512-7Knmnuak0gD2+ZEmwm/mBqBT9HeH6utgcCOlT8YUT6TOFYByrJ8t23wR2yfoQ2PKZV16CXEQLWlT0wYs1tep0g==} + '@midnight-ntwrk/midnight-js-node-zk-config-provider@4.1.1': + resolution: {integrity: sha512-fbBhLBhU/BjrQRnRJCcmKkhYRLlSqJyXEhq72vMS6vlKXprmDmAjlVV62+bn1aiAlNY9FXl1rrrMUFKQUmuZFg==} - '@midnight-ntwrk/midnight-js-types@4.0.4': - resolution: {integrity: sha512-/roDMGA7WgSXyNZPc0Xhg6Ane7IJuRJLNZ1euZxmDH4RH/XNa413L197GNpxZuZ9CNR4AozSH20VFlDpZDvERw==} + '@midnight-ntwrk/midnight-js-protocol@4.1.1': + resolution: {integrity: sha512-JH1010ir3YrawKsA4C08aIXWpFGHrCrAaBkZFbn+xh+iwOX5Ss0WU4//mDSEnHvQN0wD6GTc2JwUfKYFaS2CcA==} - '@midnight-ntwrk/midnight-js-utils@4.0.4': - resolution: {integrity: sha512-x0G2hqpFEyXv/v0Yx/L/veU9UygaJxJxZzf3LtCLMlMf1uoACqo10PiCrssynsn5Xg6Obju9I1cv1IPp5uliRg==} + '@midnight-ntwrk/midnight-js-types@4.1.1': + resolution: {integrity: sha512-MPT7YToXN2EsDtOOVSJonYZSZ4eqmdVgftBfH56w+tmZM0sZt9u+Mq18uuBQl9ebpTXnp8o7WqeISbgPMxTBcQ==} - '@midnight-ntwrk/onchain-runtime-v2@2.0.1': - resolution: {integrity: sha512-nVtDyPaWD14thZuSz/MVUNNkv4HO2nYf3E3QbaRCE4UR+jKnUrd0s/BN0fLpnrYFlcMNHS7/sZqAeosV80tsOQ==} + '@midnight-ntwrk/midnight-js-utils@4.1.1': + resolution: {integrity: sha512-b65QADd2FaZbWXe9D3fe9mSGmPlP1DTRMGSruJqhl9fOTNxBhQIzIKs0j/eL9Kv77dVeGDYgQdbV+JkDj3j5Qg==} '@midnight-ntwrk/onchain-runtime-v3@3.0.0': resolution: {integrity: sha512-HbFbUOsvgpEFy1OT0Ni2LMFk4jnMQS1em+H+Mof/B3DpnNKl0Lkx5QiulKOc6pyU4KPEagOqx4fYNyVXFwgZ7g==} @@ -888,6 +889,9 @@ packages: '@midnight-ntwrk/wallet-sdk-address-format@3.1.0-rc.0': resolution: {integrity: sha512-6UFm+Z+zHqV+dnkg/PZjsfY+ADr1AJxAXhcWAoLHOnHAvzrmehu/lAEwF/EYlfI1514xK1N5lGZGon2NQS0W2g==} + '@midnight-ntwrk/wallet-sdk-address-format@3.1.2': + resolution: {integrity: sha512-SRkgwmKFOZSUR25iurAB8U7kTKF7AljSl+tMkuuj8Pthc/l0yIg+/q1rFSozJ0Nz9FJRhshazDyrL0l84fOabg==} + '@midnight-ntwrk/wallet-sdk-capabilities@3.2.0': resolution: {integrity: sha512-3S06DbFJ/I+2Zn30jG98a9T3xPxtBwRjm4fCiYnsn8CEcNgH8XINhVimpItWLakeCAviGRA2x7M93phk9u7hbw==} @@ -987,6 +991,10 @@ packages: resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} engines: {node: ^14.21.3 || >=16} + '@noble/ciphers@2.2.0': + resolution: {integrity: sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==} + engines: {node: '>= 20.19.0'} + '@noble/curves@1.9.7': resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} engines: {node: ^14.21.3 || >=16} @@ -2469,9 +2477,6 @@ packages: '@types/validate-npm-package-name@4.0.2': resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==} - '@types/zen-observable@0.8.3': - resolution: {integrity: sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==} - '@typescript-eslint/eslint-plugin@8.56.1': resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3288,9 +3293,6 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} - fp-ts@2.16.11: - resolution: {integrity: sha512-LaI+KaX2NFkfn1ZGHoKCmcfv7yrZsC3b8NtWsTVQeHkq4F27vI5igUuO53sxqDEa2gNQMHFPmpojDw/1zmUK7w==} - fresh@2.0.0: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} @@ -3411,6 +3413,22 @@ packages: ws: optional: true + graphql-ws@6.1.1: + resolution: {integrity: sha512-e5O9S3zTDqIZ15k5Wii3ImmI5zwmRAH5OMaE90H6+jhaVBNDmjSjzumq8VyUSvKnsfByXi9pJ/3Vv1eNzwcPIw==} + engines: {node: '>=20'} + peerDependencies: + '@fastify/websocket': ^10 || ^11 + crossws: ~0.3 + graphql: ^15.10.1 || ^16 || ^17 + ws: ^8 + peerDependenciesMeta: + '@fastify/websocket': + optional: true + crossws: + optional: true + ws: + optional: true + graphql@16.12.0: resolution: {integrity: sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} @@ -3419,6 +3437,10 @@ packages: resolution: {integrity: sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + graphql@16.14.2: + resolution: {integrity: sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -3461,9 +3483,6 @@ packages: hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} - hoist-non-react-statics@3.3.2: - resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - hono@4.12.2: resolution: {integrity: sha512-gJnaDHXKDayjt8ue0n8Gs0A007yKXj4Xzb8+cNjZeYsSzzwKc0Lr+OZgYwVfB0pHfUs17EPoLvrOsEaJ9mj+Tg==} engines: {node: '>=16.9.0'} @@ -3513,11 +3532,6 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - io-ts@2.2.22: - resolution: {integrity: sha512-FHCCztTkHoV9mdBsHpocLpdTAfh956ZQcIkWQxxS0U5HT53vtrcuYdQneEJKH6xILaLNzXVl2Cvwtoy8XNN0AA==} - peerDependencies: - fp-ts: ^2.5.0 - ip-address@10.0.1: resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} engines: {node: '>= 12'} @@ -3818,17 +3832,10 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lodash@4.17.23: - resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} - log-symbols@6.0.0: resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} engines: {node: '>=18'} - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -4216,9 +4223,6 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} - prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - propagate@2.0.1: resolution: {integrity: sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==} engines: {node: '>= 8'} @@ -4289,9 +4293,6 @@ packages: peerDependencies: react: ^19.2.4 - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -4341,17 +4342,6 @@ packages: resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} engines: {node: '>= 4'} - rehackt@0.1.0: - resolution: {integrity: sha512-7kRDOuLHB87D/JESKxQoRwv4DzbIdwkAGQ7p6QKGdVlY1IZheUnVhlk/4UZlNUVxdAXpyxikE3URsG067ybVzw==} - peerDependencies: - '@types/react': '*' - react: '*' - peerDependenciesMeta: - '@types/react': - optional: true - react: - optional: true - require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -4599,10 +4589,6 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - symbol-observable@4.0.0: - resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} - engines: {node: '>=0.10'} - tagged-tag@1.0.0: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} @@ -4668,10 +4654,6 @@ packages: peerDependencies: typescript: '>=4.8.4' - ts-invariant@0.10.3: - resolution: {integrity: sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==} - engines: {node: '>=8'} - ts-morph@26.0.0: resolution: {integrity: sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==} @@ -4964,6 +4946,18 @@ packages: utf-8-validate: optional: true + ws@8.21.1: + resolution: {integrity: sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + wsl-utils@0.3.1: resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} engines: {node: '>=20'} @@ -5008,15 +5002,6 @@ packages: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} - zen-observable-ts@1.1.0: - resolution: {integrity: sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==} - - zen-observable-ts@1.2.5: - resolution: {integrity: sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==} - - zen-observable@0.8.15: - resolution: {integrity: sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==} - zod-to-json-schema@3.25.1: resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} peerDependencies: @@ -5043,28 +5028,21 @@ snapshots: package-manager-detector: 1.6.0 tinyexec: 1.0.2 - '@apollo/client@3.14.0(@types/react@19.2.14)(graphql-ws@6.0.7(graphql@16.13.2)(ws@8.19.0))(graphql@16.13.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@apollo/client@4.2.7(graphql-ws@6.1.1(graphql@16.14.2)(ws@8.21.1))(graphql@16.14.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@graphql-typed-document-node/core': 3.2.0(graphql@16.13.2) + '@graphql-typed-document-node/core': 3.2.0(graphql@16.14.2) '@wry/caches': 1.0.1 '@wry/equality': 0.5.7 '@wry/trie': 0.5.0 - graphql: 16.13.2 - graphql-tag: 2.12.6(graphql@16.13.2) - hoist-non-react-statics: 3.3.2 + graphql: 16.14.2 + graphql-tag: 2.12.6(graphql@16.14.2) optimism: 0.18.1 - prop-types: 15.8.1 - rehackt: 0.1.0(@types/react@19.2.14)(react@19.2.4) - symbol-observable: 4.0.0 - ts-invariant: 0.10.3 + rxjs: 7.8.2 tslib: 2.8.1 - zen-observable-ts: 1.2.5 optionalDependencies: - graphql-ws: 6.0.7(graphql@16.13.2)(ws@8.19.0) + graphql-ws: 6.1.1(graphql@16.14.2)(ws@8.21.1) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - transitivePeerDependencies: - - '@types/react' '@babel/code-frame@7.29.0': dependencies: @@ -5475,6 +5453,10 @@ snapshots: dependencies: graphql: 16.13.2 + '@graphql-typed-document-node/core@3.2.0(graphql@16.14.2)': + dependencies: + graphql: 16.14.2 + '@hono/node-server@1.19.9(hono@4.12.2)': dependencies: hono: 4.12.2 @@ -5542,16 +5524,16 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@midnight-ntwrk/compact-js@2.5.0': + '@midnight-ntwrk/compact-js@2.5.1': dependencies: '@effect/platform': 0.95.0(effect@3.21.0) - '@midnight-ntwrk/compact-runtime': 0.15.0 + '@midnight-ntwrk/compact-runtime': 0.16.0 '@midnight-ntwrk/ledger-v8': 8.0.3 '@midnight-ntwrk/platform-js': 2.2.4 effect: 3.21.0 tslib: 2.8.1 - '@midnight-ntwrk/compact-runtime@0.15.0': + '@midnight-ntwrk/compact-runtime@0.16.0': dependencies: '@midnight-ntwrk/onchain-runtime-v3': 3.0.0 '@types/object-inspect': 1.13.0 @@ -5561,49 +5543,50 @@ snapshots: '@midnight-ntwrk/ledger-v8@8.0.3': {} - '@midnight-ntwrk/midnight-js-contracts@4.0.4': + '@midnight-ntwrk/midnight-js-contracts@4.1.1': dependencies: - '@midnight-ntwrk/compact-js': 2.5.0 - '@midnight-ntwrk/midnight-js-network-id': 4.0.4 - '@midnight-ntwrk/midnight-js-types': 4.0.4 - '@midnight-ntwrk/midnight-js-utils': 4.0.4 + '@midnight-ntwrk/midnight-js-network-id': 4.1.1 + '@midnight-ntwrk/midnight-js-protocol': 4.1.1 + '@midnight-ntwrk/midnight-js-types': 4.1.1 + '@midnight-ntwrk/midnight-js-utils': 4.1.1 - '@midnight-ntwrk/midnight-js-fetch-zk-config-provider@4.0.4': + '@midnight-ntwrk/midnight-js-fetch-zk-config-provider@4.1.1': dependencies: - '@midnight-ntwrk/midnight-js-types': 4.0.4 + '@midnight-ntwrk/midnight-js-types': 4.1.1 + '@midnight-ntwrk/midnight-js-utils': 4.1.1 cross-fetch: 4.1.0 transitivePeerDependencies: - encoding - '@midnight-ntwrk/midnight-js-http-client-proof-provider@4.0.4': + '@midnight-ntwrk/midnight-js-http-client-proof-provider@4.1.1': dependencies: - '@midnight-ntwrk/midnight-js-contracts': 4.0.4 - '@midnight-ntwrk/midnight-js-network-id': 4.0.4 - '@midnight-ntwrk/midnight-js-types': 4.0.4 - '@midnight-ntwrk/midnight-js-utils': 4.0.4 + '@midnight-ntwrk/midnight-js-contracts': 4.1.1 + '@midnight-ntwrk/midnight-js-network-id': 4.1.1 + '@midnight-ntwrk/midnight-js-protocol': 4.1.1 + '@midnight-ntwrk/midnight-js-types': 4.1.1 + '@midnight-ntwrk/midnight-js-utils': 4.1.1 cross-fetch: 4.1.0 fetch-retry: 6.0.0 - lodash: 4.17.23 transitivePeerDependencies: - encoding - '@midnight-ntwrk/midnight-js-indexer-public-data-provider@4.0.4(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@midnight-ntwrk/midnight-js-indexer-public-data-provider@4.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@apollo/client': 3.14.0(@types/react@19.2.14)(graphql-ws@6.0.7(graphql@16.13.2)(ws@8.19.0))(graphql@16.13.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@midnight-ntwrk/midnight-js-network-id': 4.0.4 - '@midnight-ntwrk/midnight-js-types': 4.0.4 - '@midnight-ntwrk/midnight-js-utils': 4.0.4 + '@apollo/client': 4.2.7(graphql-ws@6.1.1(graphql@16.14.2)(ws@8.21.1))(graphql@16.14.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@graphql-typed-document-node/core': 3.2.0(graphql@16.14.2) + '@midnight-ntwrk/midnight-js-network-id': 4.1.1 + '@midnight-ntwrk/midnight-js-protocol': 4.1.1 + '@midnight-ntwrk/midnight-js-types': 4.1.1 + '@midnight-ntwrk/midnight-js-utils': 4.1.1 buffer: 6.0.3 cross-fetch: 4.1.0 - graphql: 16.13.2 - graphql-ws: 6.0.7(graphql@16.13.2)(ws@8.19.0) - isomorphic-ws: 5.0.0(ws@8.19.0) + graphql: 16.14.2 + graphql-ws: 6.1.1(graphql@16.14.2)(ws@8.21.1) + isomorphic-ws: 5.0.0(ws@8.21.1) rxjs: 7.8.2 - ws: 8.19.0 - zen-observable-ts: 1.1.0 + ws: 8.21.1 transitivePeerDependencies: - '@fastify/websocket' - - '@types/react' - bufferutil - crossws - encoding @@ -5612,34 +5595,45 @@ snapshots: - subscriptions-transport-ws - utf-8-validate - '@midnight-ntwrk/midnight-js-level-private-state-provider@4.0.4': + '@midnight-ntwrk/midnight-js-level-private-state-provider@4.1.1': dependencies: - '@midnight-ntwrk/midnight-js-types': 4.0.4 + '@midnight-ntwrk/midnight-js-protocol': 4.1.1 + '@midnight-ntwrk/midnight-js-types': 4.1.1 + '@midnight-ntwrk/midnight-js-utils': 4.1.1 + '@noble/ciphers': 2.2.0 + '@noble/hashes': 2.2.0 abstract-level: 3.1.1 buffer: 6.0.3 - fp-ts: 2.16.11 - io-ts: 2.2.22(fp-ts@2.16.11) level: 10.0.0 superjson: 2.2.6 - '@midnight-ntwrk/midnight-js-network-id@4.0.4': {} + '@midnight-ntwrk/midnight-js-network-id@4.1.1': {} - '@midnight-ntwrk/midnight-js-node-zk-config-provider@4.0.4': + '@midnight-ntwrk/midnight-js-node-zk-config-provider@4.1.1': dependencies: - '@midnight-ntwrk/midnight-js-types': 4.0.4 + '@midnight-ntwrk/midnight-js-types': 4.1.1 + '@midnight-ntwrk/midnight-js-utils': 4.1.1 - '@midnight-ntwrk/midnight-js-types@4.0.4': + '@midnight-ntwrk/midnight-js-protocol@4.1.1': dependencies: - '@midnight-ntwrk/compact-js': 2.5.0 + '@midnight-ntwrk/compact-js': 2.5.1 + '@midnight-ntwrk/compact-runtime': 0.16.0 + '@midnight-ntwrk/ledger-v8': 8.0.3 + '@midnight-ntwrk/onchain-runtime-v3': 3.0.0 '@midnight-ntwrk/platform-js': 2.2.4 - rxjs: 7.8.2 - '@midnight-ntwrk/midnight-js-utils@4.0.4': + '@midnight-ntwrk/midnight-js-types@4.1.1': dependencies: - '@midnight-ntwrk/midnight-js-network-id': 4.0.4 - '@scure/base': 2.0.0 + '@midnight-ntwrk/midnight-js-protocol': 4.1.1 + effect: 3.21.0 + pino: 10.3.1 + rxjs: 7.8.2 - '@midnight-ntwrk/onchain-runtime-v2@2.0.1': {} + '@midnight-ntwrk/midnight-js-utils@4.1.1': + dependencies: + '@midnight-ntwrk/midnight-js-network-id': 4.1.1 + '@midnight-ntwrk/midnight-js-protocol': 4.1.1 + '@midnight-ntwrk/wallet-sdk-address-format': 3.1.2 '@midnight-ntwrk/onchain-runtime-v3@3.0.0': {} @@ -5659,11 +5653,17 @@ snapshots: '@scure/base': 2.0.0 '@subsquid/scale-codec': 4.0.1 - '@midnight-ntwrk/wallet-sdk-capabilities@3.2.0(ws@8.19.0)': + '@midnight-ntwrk/wallet-sdk-address-format@3.1.2': + dependencies: + '@midnight-ntwrk/ledger-v8': 8.0.3 + '@scure/base': 2.2.0 + '@subsquid/scale-codec': 4.0.1 + + '@midnight-ntwrk/wallet-sdk-capabilities@3.2.0(ws@8.21.1)': dependencies: '@midnight-ntwrk/ledger-v8': 8.0.3 '@midnight-ntwrk/wallet-sdk-abstractions': 2.0.0 - '@midnight-ntwrk/wallet-sdk-indexer-client': 1.2.0(ws@8.19.0) + '@midnight-ntwrk/wallet-sdk-indexer-client': 1.2.0(ws@8.21.1) '@midnight-ntwrk/wallet-sdk-node-client': 1.1.0 '@midnight-ntwrk/wallet-sdk-prover-client': 1.2.0 '@midnight-ntwrk/wallet-sdk-utilities': 1.1.0 @@ -5678,11 +5678,11 @@ snapshots: - utf-8-validate - ws - '@midnight-ntwrk/wallet-sdk-capabilities@3.2.0-rc.0(ws@8.19.0)': + '@midnight-ntwrk/wallet-sdk-capabilities@3.2.0-rc.0(ws@8.21.1)': dependencies: '@midnight-ntwrk/ledger-v8': 8.0.3 '@midnight-ntwrk/wallet-sdk-abstractions': 2.0.0 - '@midnight-ntwrk/wallet-sdk-indexer-client': 1.2.0-rc.0(ws@8.19.0) + '@midnight-ntwrk/wallet-sdk-indexer-client': 1.2.0-rc.0(ws@8.21.1) '@midnight-ntwrk/wallet-sdk-node-client': 1.1.0 '@midnight-ntwrk/wallet-sdk-prover-client': 1.2.0 '@midnight-ntwrk/wallet-sdk-utilities': 1.1.0-rc.0 @@ -5697,13 +5697,13 @@ snapshots: - utf-8-validate - ws - '@midnight-ntwrk/wallet-sdk-dust-wallet@3.0.0-rc.0(ws@8.19.0)': + '@midnight-ntwrk/wallet-sdk-dust-wallet@3.0.0-rc.0(ws@8.21.1)': dependencies: '@midnight-ntwrk/ledger-v8': 8.0.3 '@midnight-ntwrk/wallet-sdk-abstractions': 2.0.0 '@midnight-ntwrk/wallet-sdk-address-format': 3.1.0-rc.0 - '@midnight-ntwrk/wallet-sdk-capabilities': 3.2.0-rc.0(ws@8.19.0) - '@midnight-ntwrk/wallet-sdk-indexer-client': 1.2.0-rc.0(ws@8.19.0) + '@midnight-ntwrk/wallet-sdk-capabilities': 3.2.0-rc.0(ws@8.21.1) + '@midnight-ntwrk/wallet-sdk-indexer-client': 1.2.0-rc.0(ws@8.21.1) '@midnight-ntwrk/wallet-sdk-runtime': 1.0.2-rc.0 '@midnight-ntwrk/wallet-sdk-utilities': 1.1.0-rc.0 effect: 3.21.0 @@ -5716,15 +5716,15 @@ snapshots: - utf-8-validate - ws - '@midnight-ntwrk/wallet-sdk-facade@3.0.0-rc.0(ws@8.19.0)': + '@midnight-ntwrk/wallet-sdk-facade@3.0.0-rc.0(ws@8.21.1)': dependencies: '@midnight-ntwrk/ledger-v8': 8.0.3 '@midnight-ntwrk/wallet-sdk-address-format': 3.1.0-rc.0 - '@midnight-ntwrk/wallet-sdk-capabilities': 3.2.0(ws@8.19.0) - '@midnight-ntwrk/wallet-sdk-dust-wallet': 3.0.0-rc.0(ws@8.19.0) - '@midnight-ntwrk/wallet-sdk-indexer-client': 1.2.0(ws@8.19.0) - '@midnight-ntwrk/wallet-sdk-shielded': 2.1.0-rc.0(ws@8.19.0) - '@midnight-ntwrk/wallet-sdk-unshielded-wallet': 2.1.0-rc.0(ws@8.19.0) + '@midnight-ntwrk/wallet-sdk-capabilities': 3.2.0(ws@8.21.1) + '@midnight-ntwrk/wallet-sdk-dust-wallet': 3.0.0-rc.0(ws@8.21.1) + '@midnight-ntwrk/wallet-sdk-indexer-client': 1.2.0(ws@8.21.1) + '@midnight-ntwrk/wallet-sdk-shielded': 2.1.0-rc.0(ws@8.21.1) + '@midnight-ntwrk/wallet-sdk-unshielded-wallet': 2.1.0-rc.0(ws@8.21.1) rxjs: 7.8.2 transitivePeerDependencies: - '@fastify/websocket' @@ -5739,27 +5739,27 @@ snapshots: '@scure/bip32': 2.0.1 '@scure/bip39': 2.0.1 - '@midnight-ntwrk/wallet-sdk-indexer-client@1.2.0(ws@8.19.0)': + '@midnight-ntwrk/wallet-sdk-indexer-client@1.2.0(ws@8.21.1)': dependencies: '@graphql-typed-document-node/core': 3.2.0(graphql@16.13.2) '@midnight-ntwrk/wallet-sdk-utilities': 1.1.0 effect: 3.21.0 graphql: 16.13.2 graphql-http: 1.22.4(graphql@16.13.2) - graphql-ws: 6.0.7(graphql@16.13.2)(ws@8.19.0) + graphql-ws: 6.0.7(graphql@16.13.2)(ws@8.21.1) transitivePeerDependencies: - '@fastify/websocket' - crossws - ws - '@midnight-ntwrk/wallet-sdk-indexer-client@1.2.0-rc.0(ws@8.19.0)': + '@midnight-ntwrk/wallet-sdk-indexer-client@1.2.0-rc.0(ws@8.21.1)': dependencies: '@graphql-typed-document-node/core': 3.2.0(graphql@16.13.2) '@midnight-ntwrk/wallet-sdk-utilities': 1.1.0-rc.0 effect: 3.21.0 graphql: 16.13.2 graphql-http: 1.22.4(graphql@16.13.2) - graphql-ws: 6.0.7(graphql@16.13.2)(ws@8.19.0) + graphql-ws: 6.0.7(graphql@16.13.2)(ws@8.21.1) transitivePeerDependencies: - '@fastify/websocket' - crossws @@ -5796,13 +5796,13 @@ snapshots: effect: 3.21.0 rxjs: 7.8.2 - '@midnight-ntwrk/wallet-sdk-shielded@2.1.0-rc.0(ws@8.19.0)': + '@midnight-ntwrk/wallet-sdk-shielded@2.1.0-rc.0(ws@8.21.1)': dependencies: '@midnight-ntwrk/ledger-v8': 8.0.3 '@midnight-ntwrk/wallet-sdk-abstractions': 2.0.0 '@midnight-ntwrk/wallet-sdk-address-format': 3.1.0-rc.0 - '@midnight-ntwrk/wallet-sdk-capabilities': 3.2.0-rc.0(ws@8.19.0) - '@midnight-ntwrk/wallet-sdk-indexer-client': 1.2.0-rc.0(ws@8.19.0) + '@midnight-ntwrk/wallet-sdk-capabilities': 3.2.0-rc.0(ws@8.21.1) + '@midnight-ntwrk/wallet-sdk-indexer-client': 1.2.0-rc.0(ws@8.21.1) '@midnight-ntwrk/wallet-sdk-runtime': 1.0.2-rc.0 '@midnight-ntwrk/wallet-sdk-utilities': 1.1.0-rc.0 effect: 3.21.0 @@ -5815,13 +5815,13 @@ snapshots: - utf-8-validate - ws - '@midnight-ntwrk/wallet-sdk-unshielded-wallet@2.1.0-rc.0(ws@8.19.0)': + '@midnight-ntwrk/wallet-sdk-unshielded-wallet@2.1.0-rc.0(ws@8.21.1)': dependencies: '@midnight-ntwrk/ledger-v8': 8.0.3 '@midnight-ntwrk/wallet-sdk-abstractions': 2.0.0 '@midnight-ntwrk/wallet-sdk-address-format': 3.1.0-rc.0 - '@midnight-ntwrk/wallet-sdk-capabilities': 3.2.0-rc.0(ws@8.19.0) - '@midnight-ntwrk/wallet-sdk-indexer-client': 1.2.0-rc.0(ws@8.19.0) + '@midnight-ntwrk/wallet-sdk-capabilities': 3.2.0-rc.0(ws@8.21.1) + '@midnight-ntwrk/wallet-sdk-indexer-client': 1.2.0-rc.0(ws@8.21.1) '@midnight-ntwrk/wallet-sdk-runtime': 1.0.2-rc.0 '@midnight-ntwrk/wallet-sdk-utilities': 1.1.0-rc.0 effect: 3.21.0 @@ -5906,6 +5906,8 @@ snapshots: '@noble/ciphers@1.3.0': {} + '@noble/ciphers@2.2.0': {} + '@noble/curves@1.9.7': dependencies: '@noble/hashes': 1.8.0 @@ -7423,8 +7425,6 @@ snapshots: '@types/validate-npm-package-name@4.0.2': {} - '@types/zen-observable@0.8.3': {} - '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.1(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -7441,10 +7441,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.56.1(eslint@10.0.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.56.1 '@typescript-eslint/type-utils': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/utils': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) @@ -8465,8 +8465,6 @@ snapshots: forwarded@0.2.0: {} - fp-ts@2.16.11: {} - fresh@2.0.0: {} fs-extra@11.3.3: @@ -8547,21 +8545,29 @@ snapshots: dependencies: graphql: 16.13.2 - graphql-tag@2.12.6(graphql@16.13.2): + graphql-tag@2.12.6(graphql@16.14.2): dependencies: - graphql: 16.13.2 + graphql: 16.14.2 tslib: 2.8.1 - graphql-ws@6.0.7(graphql@16.13.2)(ws@8.19.0): + graphql-ws@6.0.7(graphql@16.13.2)(ws@8.21.1): dependencies: graphql: 16.13.2 optionalDependencies: - ws: 8.19.0 + ws: 8.21.1 + + graphql-ws@6.1.1(graphql@16.14.2)(ws@8.21.1): + dependencies: + graphql: 16.14.2 + optionalDependencies: + ws: 8.21.1 graphql@16.12.0: {} graphql@16.13.2: {} + graphql@16.14.2: {} + has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -8609,10 +8615,6 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - hoist-non-react-statics@3.3.2: - dependencies: - react-is: 16.13.1 - hono@4.12.2: {} http-errors@2.0.1: @@ -8655,10 +8657,6 @@ snapshots: inherits@2.0.4: {} - io-ts@2.2.22(fp-ts@2.16.11): - dependencies: - fp-ts: 2.16.11 - ip-address@10.0.1: {} ipaddr.js@1.9.1: {} @@ -8756,9 +8754,9 @@ snapshots: isomorphic-timers-promises@1.0.1: {} - isomorphic-ws@5.0.0(ws@8.19.0): + isomorphic-ws@5.0.0(ws@8.21.1): dependencies: - ws: 8.19.0 + ws: 8.21.1 jiti@2.6.1: {} @@ -8877,17 +8875,11 @@ snapshots: lodash.merge@4.6.2: {} - lodash@4.17.23: {} - log-symbols@6.0.0: dependencies: chalk: 5.6.2 is-unicode-supported: 1.3.0 - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 - lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -9297,12 +9289,6 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 - prop-types@15.8.1: - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - propagate@2.0.1: {} proxy-addr@2.0.7: @@ -9423,8 +9409,6 @@ snapshots: react: 19.2.4 scheduler: 0.27.0 - react-is@16.13.1: {} - react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.4): dependencies: react: 19.2.4 @@ -9480,11 +9464,6 @@ snapshots: tiny-invariant: 1.3.3 tslib: 2.8.1 - rehackt@0.1.0(@types/react@19.2.14)(react@19.2.4): - optionalDependencies: - '@types/react': 19.2.14 - react: 19.2.4 - require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -9827,8 +9806,6 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - symbol-observable@4.0.0: {} - tagged-tag@1.0.0: {} tailwind-merge@3.5.0: {} @@ -9882,10 +9859,6 @@ snapshots: dependencies: typescript: 5.9.3 - ts-invariant@0.10.3: - dependencies: - tslib: 2.8.1 - ts-morph@26.0.0: dependencies: '@ts-morph/common': 0.27.0 @@ -9991,7 +9964,7 @@ snapshots: typescript-eslint@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/parser': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) '@typescript-eslint/utils': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) @@ -10154,6 +10127,8 @@ snapshots: ws@8.19.0: {} + ws@8.21.1: {} + wsl-utils@0.3.1: dependencies: is-wsl: 3.1.1 @@ -10188,17 +10163,6 @@ snapshots: yoctocolors@2.1.2: {} - zen-observable-ts@1.1.0: - dependencies: - '@types/zen-observable': 0.8.3 - zen-observable: 0.8.15 - - zen-observable-ts@1.2.5: - dependencies: - zen-observable: 0.8.15 - - zen-observable@0.8.15: {} - zod-to-json-schema@3.25.1(zod@3.25.76): dependencies: zod: 3.25.76 From 698cd73aa03eab92cb63912c725207b7ef1d8611 Mon Sep 17 00:00:00 2001 From: Bruce Wayne Date: Thu, 23 Jul 2026 17:06:15 -0300 Subject: [PATCH 02/20] chore: update compose yml files --- VERSIONS.md | 6 +++--- compose.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/VERSIONS.md b/VERSIONS.md index b62656e..f0f2ab2 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -11,9 +11,9 @@ - Compact JS: 2.5.1 - Midnight.js: 4.1.1 - On-chain runtime: 3.0.0 -- Indexer: 4.0.0 -- Proof server: 8.0.2 -- Midnight node: 0.22.0 +- Indexer (standalone devnet): 4.2.1 +- Proof server: 8.1.0 +- Midnight node (local devnet): 0.22.5 - Node.js: 24.13.1 ## Project dependencies diff --git a/compose.yml b/compose.yml index 90369bd..7ce8f6b 100644 --- a/compose.yml +++ b/compose.yml @@ -1,7 +1,7 @@ services: proof-server: container_name: 'proof-server' - image: 'midnightntwrk/proof-server:8.0.2' + image: 'midnightntwrk/proof-server:8.1.0' command: ['midnight-proof-server -v'] ports: - '6300:6300' @@ -10,7 +10,7 @@ services: indexer: container_name: 'indexer' - image: 'midnightntwrk/indexer-standalone:4.0.0' + image: 'midnightntwrk/indexer-standalone:4.2.1' env_file: standalone.env.example ports: - '8088:8088' @@ -28,7 +28,7 @@ services: condition: service_healthy node: - image: 'midnightntwrk/midnight-node:0.22.0' + image: 'midnightntwrk/midnight-node:0.22.5' container_name: 'node' ports: - '9944:9944' From ecc4b3584c8e898f568037f49e419f05671c0197 Mon Sep 17 00:00:00 2001 From: Bruce Wayne Date: Thu, 23 Jul 2026 19:16:40 -0300 Subject: [PATCH 03/20] chore: docs with research results for contract improvements --- packages/contract/SPONSORSHIP_FEASIBILITY.md | 438 ++++++++++++++++ .../contract/SPONSORSHIP_VERIFICATION_PLAN.md | 480 ++++++++++++++++++ 2 files changed, 918 insertions(+) create mode 100644 packages/contract/SPONSORSHIP_FEASIBILITY.md create mode 100644 packages/contract/SPONSORSHIP_VERIFICATION_PLAN.md diff --git a/packages/contract/SPONSORSHIP_FEASIBILITY.md b/packages/contract/SPONSORSHIP_FEASIBILITY.md new file mode 100644 index 0000000..293a560 --- /dev/null +++ b/packages/contract/SPONSORSHIP_FEASIBILITY.md @@ -0,0 +1,438 @@ +# DUST Sponsorship Purchase: Feasibility and Assumptions + +Status: design analysis, not an implementation specification +Last updated: 2026-07-23 + +## Purpose + +This document records the reasoning behind a proposed `purchaseSponsorship` +Compact circuit for Sentinel. It is intended to give a future engineer enough +context to understand: + +- the product idea; +- which behaviors are enforced by Compact, the Midnight ledger, and wallets; +- what is believed to be feasible; +- what is explicitly not possible with current Midnight primitives; +- which assumptions still require practical verification. + +The final reward formula and the distribution of sponsorship revenue among +NIGHT contributors are intentionally out of scope. This document does, +however, define the sponsor attribution and revenue accounting needed to make +that later work possible. + +## Executive conclusion + +A `purchaseSponsorship` circuit is feasible if its guarantee is defined as: + +> Accept and record a valid token payment in a transaction whose network fees +> must be covered by valid DUST before the transaction can succeed, and +> attribute that payment to the public sponsor selected in the call. + +The contract cannot itself own or spend DUST, identify which DUST address paid +the fee, or prove that the DUST came from a particular NIGHT registration. +A wallet process must hold the DUST secret key and add the fee contribution. + +The expected transaction flow is: + +```text +Beneficiary + creates purchaseSponsorship(payment, sponsorId) + balances shielded/unshielded assets but excludes DUST + | + v + finalizes the contract transaction + | + v +Sponsor service + validates the finalized transaction, including that + sponsorId identifies this sponsor + adds only the required DUST + | + v +Midnight ledger + validates payment, contract proof, transaction binding, + DUST proof, fee coverage, and transaction state + | + v + payment + sponsor revenue credit + purchase record + + fee payment succeed together +``` + +The sponsor remains trusted for availability: it may reject requests or go +offline. It is not trusted to settle the payment honestly because it cannot +alter the beneficiary's finalized and cryptographically bound transaction. + +## Terminology + +- **NIGHT** is Midnight's native asset. Unshielded NIGHT UTXOs may be + registered for DUST generation. +- **DUST** is a shielded, non-transferable network resource used only for + transaction fees. It is not a normal token. +- **Beneficiary** is the wallet requesting fee sponsorship. +- **Sponsor** is a wallet or service that holds a DUST secret key and adds a + DUST spend to the beneficiary's transaction. +- **Sponsor ID** is the public identifier supplied to `purchaseSponsorship`. + It resolves to one sponsor and its associated delegation/revenue pool. It + need not be private. +- **Sponsor pool** is the accounting domain to which sponsorship payments are + credited. NIGHT contributors may direct DUST generation toward its sponsor + and may later become eligible for a share of its revenue. +- **Contributor** is a NIGHT holder who registers NIGHT generation to a + sponsor's DUST address. The final contributor reward calculation is out of + scope here. +- **Purchase contract** is the deployed Compact contract containing the + proposed `purchaseSponsorship` entry point. + +## Relevant protocol facts + +### NIGHT registration and DUST ownership + +DUST registration maps a NIGHT user address to a `DustPublicKey`. It is +authorized by the NIGHT address's signature key. The Wallet SDK supports +specifying a DUST receiver address different from the NIGHT owner's own DUST +address. This enables a NIGHT holder to keep custody of unshielded NIGHT while +directing generated DUST to a sponsor. + +Contract-owned unshielded NIGHT cannot currently follow this path. A contract +balance belongs to a `ContractAddress`, while DUST registration requires a +NIGHT user verifying key and corresponding signature. A Compact contract has no +NIGHT secret key with which to authorize registration. + +### DUST spending + +A DUST spend proves knowledge of a `DustSecretKey`, ownership of a DUST UTXO, +membership of its commitment and backing generation information, and +sufficient effective value for the declared fee. Ownership cannot change +during the spend. + +A Compact contract cannot: + +- be a DUST address; +- safely store a DUST secret key; +- construct a DUST spend through the Compact standard library; +- initiate and submit a transaction autonomously. + +The sponsor must therefore remain a wallet-side process. + +### Transaction sponsorship and merging + +The Wallet SDK supports preparing and finalizing a transaction without DUST, +then passing it to another wallet which balances only the DUST component. +Midnight transaction merging permits this when the sponsor's balancing +transaction has no competing contract-call section. + +The beneficiary's token effects and contract call are cryptographically bound +before the sponsor receives the transaction. The sponsor may append its allowed +balancing contribution but cannot redirect the beneficiary's payment or change +the contract invocation. + +### Guaranteed and fallible execution + +Fees are handled in the guaranteed phase. A Compact circuit with no +`kernel.checkpoint()` is guaranteed-only. The proposed purchase circuit must +remain guaranteed-only so that: + +- an invalid payment rejects the whole transaction; +- an invalid contract proof rejects the whole transaction; +- insufficient or invalid DUST rejects the whole transaction; +- rejection does not commit the payment, purchase record, or DUST spend. + +Introducing a fallible section would permit partial success, including DUST +being consumed even when later contract operations fail. That behavior is not +acceptable for the initial purchase protocol. + +## Responsibility boundaries + +### Beneficiary wallet + +The beneficiary wallet is responsible for: + +- choosing a sponsor and accepting its commercial terms; +- constructing the call to the correct deployed contract and entry point; +- providing the required payment asset; +- balancing its shielded and/or unshielded assets; +- excluding DUST from its balancing step; +- proving and finalizing its portion of the transaction; +- delivering the finalized transaction to the sponsor before its TTL expires. + +### `purchaseSponsorship` circuit + +The circuit is expected to validate only contract-observable business rules, +such as: + +- accepted payment token or token policy; +- the fixed payment amount, or another configured pricing rule; +- positive payment amount; +- a valid public `sponsorId` resolving to a registered sponsor pool; +- offer activation and expiry, if offers are stored on-chain; +- uniqueness of a purchase or quote identifier, if replay protection is + required; +- receipt of the payment by the contract; +- crediting the payment to the named sponsor's revenue accounting; +- incrementing the named sponsor's purchase count, if retained; +- creation of any purchase receipt needed for later accounting or audit. + +The circuit does not need to inspect DUST. If the transaction lacks sufficient +DUST, the ledger rejects it independently. + +### Sponsor service + +Before adding DUST, the sponsor should validate: + +- network identifier; +- expected contract address; +- expected `purchaseSponsorship` entry point; +- that the public `sponsorId` identifies this sponsor and its pool; +- expected offer or quote identifier; +- payment token, amount, and destination as far as they are inspectable; +- transaction TTL; +- estimated total fee and a configured maximum fee; +- absence of unexpected actions or effects under the sponsor's policy; +- that it will balance only the DUST component. + +The sponsor may rely on the ledger to reject an invalid Compact or Zswap proof, +but practical verification must establish that rejection in the guaranteed +phase cannot consume the sponsor's DUST. + +### Midnight ledger + +The ledger validates: + +- ownership and validity of payment inputs; +- contract-call proof and declared effects; +- contract/Zswap and contract/unshielded claim consistency; +- transaction signatures and binding commitments; +- ownership and validity of the sponsor's DUST spend; +- sufficient DUST fee coverage; +- transaction balancing, ordering, and replay protection; +- guaranteed-phase state applicability. + +## Operational sponsor attribution + +The proposed design does not require the Compact contract to identify the +actual DUST key used in the transaction. Instead, it uses a public sponsor ID +and defensive inspection by the sponsor: + +1. The beneficiary names a sponsor in `purchaseSponsorship`. +2. The contract validates the sponsor ID and attributes the fixed payment to + that sponsor's pool. +3. The named sponsor inspects the finalized transaction. +4. The sponsor supplies DUST only if the transaction names that sponsor and + satisfies its complete policy. + +A different DUST holder can technically fund a transaction that names the +sponsor. Doing so spends the other holder's DUST while crediting the named +sponsor's pool. This is economically equivalent to donating DUST to that pool +and does not let the other holder capture its revenue. Under the rational-actor +assumption, it is not a reason to require an on-chain proof linking +`sponsorId` to the DUST spend. + +The accounting meaning is therefore: + +> A successful fixed-price sponsorship purchase attributed to a sponsor ID +> and accepted by a DUST-paying actor. + +It is not a protocol-level claim that a particular DUST public key supplied +the fee. This distinction should be preserved in names, documentation, and +future reward calculations. + +The contract should preferably track both: + +```text +sponsorRevenue[sponsorId] += paymentAmount +sponsorPurchases[sponsorId] += 1 +``` + +With a fixed price, the two values are mathematically related. Revenue is still +the more direct basis for later distribution, while the purchase count is +useful for metrics and auditing. Per-purchase receipts or commitments should +also be considered so aggregate accounting can be reconstructed. + +## Exact guarantees and non-guarantees + +### Intended guarantees + +If a transaction is accepted: + +- `purchaseSponsorship` executed successfully; +- the contract's payment conditions were satisfied; +- the contract received or otherwise processed the payment as designed; +- the payment was attributed to the public sponsor ID in the call; +- the named sponsor's revenue and purchase accounting were updated as + designed; +- any required purchase receipt was recorded; +- the ledger verified sufficient valid DUST for all transaction fees; +- the sponsor could not change the beneficiary's committed payment terms. + +If guaranteed validation fails: + +- the payment is not committed; +- the purchase record is not committed; +- the DUST spend is not committed. + +### Explicit non-guarantees + +The contract cannot establish: + +- that the DUST key associated operationally with the named sponsor paid the + fee; +- which DUST address paid; +- the exact DUST amount attributed to a particular sponsor; +- which NIGHT UTXO generated that DUST; +- whether a contributor's NIGHT remains registered; +- that a sponsor will accept or remain online for future requests. + +The contract also cannot distinguish third-party-sponsored DUST from DUST paid +by the beneficiary itself. This does not invalidate the operational +attribution model: an actor choosing to fund a transaction naming another +sponsor bears the DUST cost while the named pool receives the revenue credit. +It does mean that accounting proves successful attributed purchases, not +cryptographic ownership of the DUST spend. + +## Oracle considerations + +No oracle is required for the core purchase flow. + +An oracle becomes relevant only when the protocol needs to prove facts that +Compact cannot currently read, such as whether a specific unshielded NIGHT UTXO +is still unspent and registered to a sponsor's DUST address. + +An open-source oracle implementation is auditable and may be run by many +operators, but openness alone does not make its statements trustworthy. A +contract that accepts an attestation from any arbitrary oracle is vulnerable to +an attacker operating a dishonest oracle. A secure oracle design would require +one of: + +- a cryptographic proof tied to canonical Midnight ledger state; +- threshold attestations from an explicitly governed set; +- an optimistic challenge and slashing mechanism; +- an acknowledged trusted oracle. + +This decision is deferred with the rewards design. + +## Current repository context + +The current contract in [`src/sentinel.compact`](src/sentinel.compact) is a +prototype with a different flow: + +- `delegate` receives shielded NIGHT into a contract vault; +- the owner later withdraws the shielded NIGHT; +- an external admin wallet sponsors DUST; +- rewards are deposited back into a contract vault. + +Shielded NIGHT held by the contract cannot be registered for DUST generation. +The current sponsorship code in +[`../api/src/index.ts`](../api/src/index.ts) already demonstrates adding only +DUST to another wallet's finalized transaction. The CLI examples demonstrate +pure Zswap sponsorship, but not yet sponsorship of a finalized transaction +containing a `purchaseSponsorship` Compact call. + +The package currently targets Compact language version `0.23` and uses Wallet +SDK release-candidate packages. Compatibility with the exact installed +versions must be verified rather than inferred solely from current upstream +documentation. + +## Assumptions + +### Supported by specification and current APIs + +1. A beneficiary can construct a contract transaction without balancing DUST. +2. A sponsor can add only DUST to an already-finalized transaction. +3. A sponsor balancing contribution with no contract calls can be merged with + the beneficiary's transaction containing one contract-call section. +4. Contract token effects and sponsor DUST fees can coexist in one bound + transaction. +5. A circuit without `kernel.checkpoint()` executes only in the guaranteed + phase. +6. A contract can receive normal shielded or unshielded payment assets. +7. The ledger rejects a transaction whose DUST contribution is missing, + invalid, or insufficient. + +### Design assumptions + +1. The purchase circuit will be guaranteed-only. +2. The contract will already be deployed before purchases begin. +3. Sponsor refusal is allowed; censorship resistance requires multiple + independent sponsors rather than contract automation. +4. Sponsor identities and their delegation/revenue pools are public. +5. A sponsor supplies DUST only after verifying that the transaction's public + `sponsorId` identifies that sponsor and that the complete transaction meets + its policy. +6. Operational sponsor attribution is sufficient; protocol-level proof that + the named sponsor's DUST key funded the fee is not required. +7. A DUST holder funding a purchase attributed to somebody else is treated as + a voluntary donation of the sponsorship cost to the named pool. +8. The final contributor eligibility and proportional reward formula remain + out of scope. +9. Pricing may initially be fixed or stored on-chain. Compact has no built-in + general signature-verification function, so authenticated dynamic off-chain + quotes require a separate design. +10. The initial implementation may disclose payment token and amount where + required by Compact token operations and ledger effects. + +### Unverified implementation assumptions + +1. The installed Wallet SDK can finalize a contract transaction without DUST + and subsequently sponsor it using `tokenKindsToBalance: ['dust']`. +2. The sponsor can reliably inspect or identify the intended contract address, + entry point, payment effects, and TTL before adding DUST. +3. Guaranteed-phase failure of the sponsored contract call leaves the + sponsor's DUST unconsumed in the exact installed node/ledger version. +4. Receiving and recording payment does not introduce unacceptable shared-state + contention under concurrent purchase requests. +5. Proof generation and the two-party handoff complete within a safe TTL. +6. Fee estimation provides a usable upper bound for sponsor policy. +7. The chosen shielded or unshielded payment path is supported end to end by + the current provider stack. + +## Practical verification backlog + +These checks should be planned and executed independently. + +| ID | Verification | Success criterion | +|---|---|---| +| PV-01 | Build a minimal guaranteed-only purchase circuit | Contract compiles without a checkpoint or fallible transcript | +| PV-02 | User prepares the purchase without DUST | User can prove/finalize while balancing only payment assets | +| PV-03 | Sponsor adds DUST to the finalized contract transaction | Sponsor balances only DUST and submits successfully | +| PV-04 | Successful atomic settlement | Payment, receipt, and fee payment appear in one successful transaction | +| PV-05 | Missing DUST failure | Transaction is rejected and payment/receipt are not committed | +| PV-06 | Invalid payment failure | Transaction is rejected and sponsor DUST is not consumed | +| PV-07 | Stale contract state failure | Guaranteed state conflict rejects without consuming payment or DUST | +| PV-08 | Sponsor transaction inspection | Sponsor can enforce contract, entry point, TTL, and fee-limit policy | +| PV-09 | Tampering resistance | Sponsor cannot alter payment token, amount, recipient, or call | +| PV-10 | Excessive-fee defense | Sponsor refuses requests above a configured maximum | +| PV-11 | TTL behavior | Expired handoffs fail safely without committed effects | +| PV-12 | Concurrent purchases | Independent purchases do not cause unacceptable conflicts or lost throughput | +| PV-13 | Privacy inspection | Document exactly which payment, offer, and receipt fields are public | +| PV-14 | SDK/version compatibility | Results are reproduced with the versions pinned by this repository | +| PV-15 | Sponsor-ID inspection | Sponsor can verify the finalized call names its registered public ID before adding DUST | +| PV-16 | Per-sponsor attribution | Successful purchase credits only the pool selected by `sponsorId` | +| PV-17 | Attribution rejection | Sponsor refuses a valid purchase that names a different sponsor | +| PV-18 | Revenue reconstruction | Aggregate sponsor revenue and purchase count agree with recorded purchase receipts | + +## Deferred design decisions + +The following choices are deliberately not settled by this feasibility note: + +- shielded versus unshielded payment; +- fixed price, on-chain offers, or authenticated off-chain quotes; +- exact versus minimum payment; +- contract treasury versus immediate forwarding to a recipient; +- public versus privacy-preserving purchase receipts; +- single sponsor versus permissionless sponsor registry; +- contributor locking, eligibility snapshots, and proportional reward + calculation; +- treatment of sponsor self-purchases in the eventual reward formula; +- counter, receipt, or accumulator structures for concurrent accounting; +- oracle, threshold, or challenge mechanism for NIGHT registration proofs. + +These decisions should be made only after PV-01 through PV-09 establish the +core sponsored contract-call flow. + +## Primary references + +- [Midnight ledger DUST and fee-payment specification](https://github.com/midnightntwrk/midnight-ledger/blob/ledger-8/spec/dust.md) +- [Midnight Wallet SDK: DUST sponsorship](https://docs.midnight.network/sdks/official/wallet-developer-guide#dust-sponsorship) +- [Midnight transaction building blocks and merging](https://docs.midnight.network/concepts/how-midnight-works/building-blocks#merging) +- [Midnight Indexer `UnshieldedUtxo`](https://docs.midnight.network/api-reference/midnight-indexer/types/objects/unshielded-utxo) +- [Midnight ledger transaction and intent specification](https://github.com/midnightntwrk/midnight-ledger/blob/ledger-8/spec/intents-transactions.md) diff --git a/packages/contract/SPONSORSHIP_VERIFICATION_PLAN.md b/packages/contract/SPONSORSHIP_VERIFICATION_PLAN.md new file mode 100644 index 0000000..db1287f --- /dev/null +++ b/packages/contract/SPONSORSHIP_VERIFICATION_PLAN.md @@ -0,0 +1,480 @@ +# DUST Sponsorship: Practical Verification Plan + +Status: proposed experiments, not implementation evidence +Last updated: 2026-07-23 + +## Goal + +Prove or refute the following narrow claim using the versions installed in +this repository: + +> A user can create a guaranteed-only Compact call that pays a fixed shielded +> asset and publicly names a sponsor; the named sponsor can independently +> inspect the finalized transaction, add only DUST, and submit it so the +> payment, sponsor attribution, and DUST fee settle atomically. + +This plan does not attempt to prove the contributor reward formula, continued +NIGHT registration, production treasury design, or permissionless sponsor +discovery. Those are later layers. + +The design context and protocol assumptions are recorded in +[`SPONSORSHIP_FEASIBILITY.md`](SPONSORSHIP_FEASIBILITY.md). + +## Decision rule + +The core design is **implementation-feasible** only if all critical checks +below pass. + +| Priority | Checks | +| ---------------------------------- | ------------------- | +| Critical | VP-01 through VP-09 | +| Important before production design | VP-10 through VP-14 | +| Scalability and hardening | VP-15 through VP-18 | + +A failed critical check must be classified as one of: + +- **Experiment defect**: the test or integration is incorrect; repair and + repeat it. +- **SDK limitation with workaround**: the design remains possible but the + transaction workflow must change. +- **Design blocker**: the sponsor cannot safely validate or fund the purchase + under the current protocol and SDK. + +Compilation success alone is not evidence that the end-to-end claim works. +The critical result must come from submitting transactions to a local Midnight +network and inspecting the resulting ledger state. + +## Minimal experiment + +### Deliberate simplifications + +The first experiment should use: + +- one deployed contract; +- one preconfigured public `sponsorId`; +- one accepted shielded token color; +- one exact fixed price; +- one beneficiary wallet with the payment asset and no usable DUST; +- one sponsor wallet with DUST; +- a guaranteed-only circuit with no `kernel.checkpoint()`; +- public sponsor identity, payment color, and payment amount; +- a test-only payment sink. + +The payment sink is intentional: the first experiment only needs to prove that +the contract receives the payment effect and updates attribution. A production +version must retain a qualified coin reference or forward the payment so the +asset is recoverable. + +### Candidate minimal Compact contract + +This is a compiling starting point for Compact `0.23`. On 2026-07-23 it +compiled successfully with: + +```text +compact compile +0.31.1 --skip-zk sponsorship-min.compact sponsorship-min-out +``` + +That result establishes syntax and type validity only. The circuit has not yet +been executed in the Compact runtime, proved with ZK enabled, or exercised on a +Midnight network. + +```compact +pragma language_version 0.23; + +import CompactStandardLibrary; + +export ledger configuredSponsor: Bytes<32>; +export ledger acceptedColor: Bytes<32>; +export ledger fixedPrice: Uint<64>; +export ledger sponsorRevenue: Map, Uint<128>>; +export ledger sponsorPurchases: Map, Uint<64>>; + +constructor( + sponsorId: Bytes<32>, + paymentColor: Bytes<32>, + price: Uint<64> +) { + assert(price > 0, "Price must be positive"); + configuredSponsor = disclose(sponsorId); + acceptedColor = disclose(paymentColor); + fixedPrice = disclose(price); +} + +export circuit purchaseSponsorship( + sponsorId: Bytes<32>, + payment: ShieldedCoinInfo +): [] { + const publicSponsor = disclose(sponsorId); + const publicPayment = disclose(payment); + + assert(publicSponsor == configuredSponsor, "Unknown sponsor"); + assert(publicPayment.color == acceptedColor, "Wrong payment asset"); + assert(publicPayment.value == fixedPrice, "Wrong payment amount"); + + receiveShielded(publicPayment); + + const paid = publicPayment.value as Uint<128>; + + if (sponsorRevenue.member(publicSponsor)) { + const previousRevenue = sponsorRevenue.lookup(publicSponsor); + sponsorRevenue.insert( + publicSponsor, + (previousRevenue + paid) as Uint<128> + ); + + const previousPurchases = sponsorPurchases.lookup(publicSponsor); + sponsorPurchases.insert( + publicSponsor, + (previousPurchases + 1) as Uint<64> + ); + } else { + sponsorRevenue.insert(publicSponsor, paid); + sponsorPurchases.insert(publicSponsor, 1); + } +} +``` + +Before using it as end-to-end evidence: + +1. Reproduce compilation with the repository's pinned Compact compiler. +2. Execute its generated circuit in a deterministic local test. +3. Confirm it contains no fallible transcript or checkpoint. +4. Replace the payment sink in any production prototype. + +### Candidate end-to-end flow + +Exact API names must be confirmed against the pinned Wallet SDK. The intended +sequence is: + +```text +1. Beneficiary builds purchaseSponsorship(SPONSOR_A, paymentCoin). +2. Beneficiary balances shielded/unshielded assets but excludes DUST. +3. Beneficiary proves and finalizes its transaction. +4. Sponsor A deserializes and independently inspects that transaction. +5. Sponsor A verifies: + contract == expected contract + circuit == purchaseSponsorship + sponsorId == SPONSOR_A + payment color == ACCEPTED_COLOR + payment amount == FIXED_PRICE + TTL is acceptable + estimated fee <= sponsor policy maximum + no unwanted transaction actions are present +6. Sponsor A calls balanceFinalizedTransaction with only ['dust']. +7. Sponsor A finalizes and submits. +8. The test reads contract state and transaction status. +``` + +Expected successful state: + +```text +sponsorRevenue[SPONSOR_A] == FIXED_PRICE +sponsorPurchases[SPONSOR_A] == 1 +transaction status == accepted +sponsor DUST decreased by the charged fee +``` + +## Critical verification checks + +### VP-01 — Minimal contract compiles and executes + +**Claim:** The candidate state and circuit operations are valid in Compact +`0.23`. + +**Method:** + +1. Compile with `pnpm compact:no-zk` or an isolated equivalent. +2. Invoke the generated circuit using a deterministic runtime/simulator test. +3. Inspect the resulting ledger state. + +**Pass criteria:** + +- compilation succeeds; +- exact payment increments revenue and count; +- the test reads the expected public ledger values. + +### VP-02 — Payment rules reject incorrect purchases + +Test independently: + +- wrong sponsor ID; +- wrong token color; +- amount below the fixed price; +- amount above the fixed price; +- zero amount. + +**Pass criterion:** Every invalid case fails with the expected assertion and +does not update revenue or purchase count. + +### VP-03 — The purchase is guaranteed-only + +**Claim:** The circuit has no fallible portion. + +**Method:** + +- confirm the source contains no `kernel.checkpoint()`; +- inspect compiler metadata or compiled transaction structure; +- submit both successful and deliberately failing calls. + +**Pass criterion:** The call is represented only in the guaranteed section. + +### VP-04 — Beneficiary can finalize without DUST + +**Setup:** The beneficiary has the accepted payment asset but no spendable +DUST. + +**Method:** Construct, prove, and finalize the purchase while balancing only +the shielded/unshielded payment components. + +**Pass criterion:** A finalized transaction is produced without a DUST spend. +It is not expected to be independently submittable yet. + +### VP-05 — Sponsor can add only DUST + +**Method:** Sponsor the beneficiary's finalized transaction with +`balanceFinalizedTransaction` and `tokenKindsToBalance: ['dust']`. + +**Pass criteria:** + +- the sponsor adds a valid DUST spend; +- no sponsor shielded or unshielded payment input is added; +- the resulting transaction can be finalized and submitted. + +### VP-06 — Sponsor can independently inspect the transaction + +This is the highest-risk practical check. + +The sponsor must derive from the serialized, bound transaction—not from +untrusted request metadata: + +- network ID; +- deployed contract address; +- circuit/call identity; +- public `sponsorId`; +- payment token color; +- payment amount; +- payment destination or contract claim; +- TTL; +- fee estimate or safe upper bound; +- all additional actions included in the transaction. + +**Pass criterion:** Sponsor policy can make an allow/deny decision using only +authenticated transaction contents plus local configuration. + +**Failure consequence:** If these fields cannot be inspected, design a bound +quote or transaction-commitment protocol before proceeding. The sponsor must +not trust a JSON description supplied beside an opaque transaction. + +### VP-07 — Sponsor cannot alter the purchase + +Attempt to change each of the following after beneficiary finalization: + +- sponsor ID; +- token color; +- amount; +- recipient/contract; +- circuit; +- unrelated output. + +**Pass criterion:** Mutation is impossible through the balancing API or causes +finalization, well-formedness, proof, or ledger validation to fail. + +### VP-08 — Successful atomic settlement + +**Method:** Submit the correctly sponsored transaction and inspect contract, +wallet, and node/indexer state. + +**Pass criteria:** + +- the payment is consumed exactly once; +- sponsor revenue increases by the fixed price; +- sponsor purchase count increases by one; +- the ledger accepts the DUST fee; +- all effects share one accepted transaction. + +### VP-09 — Guaranteed failures consume nothing + +Submit separate transactions containing: + +- an invalid payment; +- an invalid sponsor ID; +- insufficient DUST; +- an invalid DUST proof, if constructible; +- stale contract state; +- an expired TTL. + +**Pass criteria for every case:** + +- transaction fails entirely; +- payment remains spendable; +- sponsor revenue and count remain unchanged; +- sponsor DUST is not committed as spent. + +## Important checks before production design + +### VP-10 — Wrong-sponsor refusal + +Give Sponsor B a valid transaction that names Sponsor A. + +**Pass criterion:** Sponsor B's policy rejects it before balancing. If Sponsor B +is deliberately configured to accept it, the ledger may accept it and credit +Sponsor A; this confirms the documented donation behavior. + +### VP-11 — Fee-limit defense + +Construct otherwise valid transactions with progressively larger fee +requirements or extra allowed actions. + +**Pass criterion:** The sponsor reliably refuses any transaction whose maximum +fee exposure exceeds its configured limit. + +### VP-12 — Replay resistance + +Resubmit an already accepted transaction and reuse any application-level +purchase ID introduced by the prototype. + +**Pass criteria:** + +- ledger replay is rejected; +- an application purchase ID cannot be credited twice if the design adds one. + +### VP-13 — Accounting reconstruction + +Record a receipt or commitment for each successful purchase and execute +multiple purchases for at least two sponsors. + +**Pass criterion:** + +```text +sum(receipt amounts for sponsor) == sponsorRevenue[sponsor] +count(receipts for sponsor) == sponsorPurchases[sponsor] +``` + +### VP-14 — Visibility audit + +Inspect node, indexer, wallet history, contract state, and serialized +transaction representations. + +**Pass criterion:** Document exactly which of these are public: + +- sponsor ID; +- payment color; +- payment amount; +- beneficiary identity/address; +- purchase receipt; +- sponsor revenue and purchase count; +- actual DUST payer or DUST amount. + +No privacy guarantee should be inferred from a field merely being difficult to +find in one SDK representation. + +## Scalability and hardening checks + +### VP-15 — Concurrent purchases + +Prepare multiple purchases against the same contract state and sponsor, then +prove and submit them concurrently. + +Measure: + +- stale-state rejection rate; +- successful transactions per block; +- proof regeneration requirements; +- sponsor wallet DUST UTXO conflicts. + +**Pass criterion:** Throughput is adequate for the initial target. Otherwise, +evaluate append-only receipts, partitioned sponsor state, batching, serialized +queues, or multiple sponsor wallets. + +### VP-16 — Multiple sponsors + +Replace the single configured sponsor with a minimal registry and repeat the +complete flow with two sponsors. + +**Pass criteria:** + +- each sponsor funds only transactions it accepts; +- payment is credited only to the selected pool; +- one sponsor cannot modify another sponsor's accounting. + +### VP-17 — Version reproducibility + +Run the verification from a clean checkout using the exact versions pinned in +the repository. + +Record: + +- Compact compiler version; +- Wallet SDK versions; +- Midnight.js versions; +- ledger version; +- node, indexer, and proof-server versions; +- network configuration. + +**Pass criterion:** Another developer can reproduce the same results without +unpinned dependencies or undocumented local changes. + +### VP-18 — Sponsor-service failure handling + +Test: + +- wallet not synchronized; +- exhausted or unavailable DUST; +- proof-server failure; +- indexer lag; +- node rejection; +- sponsor crash before and after finalization; +- duplicate client requests. + +**Pass criterion:** Failures do not produce false success responses, duplicate +credits, or ambiguous retry behavior. + +## Evidence to retain + +For every check, save: + +- claim being tested; +- exact source revision; +- dependency and service versions; +- test source; +- commands used; +- stdout/stderr; +- serialized transaction before sponsorship; +- serialized transaction after sponsorship; +- transaction hash and status; +- relevant pre-state and post-state; +- conclusion: confirmed, refuted, or inconclusive. + +Use observed execution as evidence. Documentation and source inspection explain +expected behavior but do not replace the end-to-end result. + +## Recommended execution order + +```text +VP-01, VP-02, VP-03 + | + v +VP-04, VP-05 + | + v +VP-06, VP-07 + | + v +VP-08, VP-09 + | + v +VP-10 through VP-14 + | + v +VP-15 through VP-18 +``` + +Stop and reassess immediately if VP-04, VP-05, VP-06, VP-08, or VP-09 fails +for a protocol or SDK reason rather than an experiment defect. + +## Primary references + +- [Midnight Wallet SDK: balance a finalized transaction](https://docs.midnight.network/sdks/official/wallet-developer-guide#balance-a-finalized-transaction) +- [Midnight transaction merging and integrity](https://docs.midnight.network/concepts/how-midnight-works/building-blocks#merging) +- [Midnight ledger DUST specification](https://github.com/midnightntwrk/midnight-ledger/blob/ledger-8/spec/dust.md) +- [Midnight ledger transaction and intent specification](https://github.com/midnightntwrk/midnight-ledger/blob/ledger-8/spec/intents-transactions.md) From 5e4f1d001df9d533ebdaefad7316bd48272b0ea4 Mon Sep 17 00:00:00 2001 From: Bruce Wayne Date: Mon, 27 Jul 2026 14:17:13 -0300 Subject: [PATCH 04/20] feat: validate sponsored composite transactions Implement the sponsorship-only Sentinel flow, sponsor policy inspection, composite transaction preparation, and retained devnet verification harnesses. Record the confirmed end-to-end production verification evidence. Co-authored-by: Codex --- apps/cli/package.json | 3 + apps/cli/src/cli/index.ts | 103 +- apps/cli/src/cli/menus.ts | 12 +- apps/cli/src/delegation-flow-example.ts | 9 +- .../sentinel-sponsorship-partition-check.ts | 117 ++ .../src/sponsorship-composite-verification.ts | 503 +++++ .../sponsorship-production-verification.ts | 374 ++++ apps/cli/src/sponsorship-verification.ts | 440 ++++ apps/cli/src/zswap-sponsor-example.ts | 9 +- apps/ui/src/App.tsx | 22 +- apps/ui/src/components/rules.tsx | 33 +- apps/ui/src/views/Contract.tsx | 14 +- apps/ui/src/views/Deploy.tsx | 7 +- apps/ui/vite.config.ts | 12 + packages/api/package.json | 5 + packages/api/src/index.ts | 149 +- packages/api/src/sponsorship.ts | 729 +++++++ .../SPONSORSHIP_ARCHITECTURE.excalidraw | 1785 +++++++++++++++++ .../contract/SPONSORSHIP_IMPLEMENTATION.md | 118 ++ .../contract/SPONSORSHIP_VERIFICATION_PLAN.md | 271 ++- packages/contract/package.json | 23 +- .../src/composite-sponsorship-verification.ts | 87 + packages/contract/src/providers.ts | 9 +- packages/contract/src/sentinel.compact | 157 +- .../contract/src/sponsorship-verification.ts | 21 + .../composite-sponsorship.compact | 73 + .../src/verification/composite-target.compact | 55 + .../sentinel-sponsorship-runtime-check.ts | 117 ++ .../verification/sponsorship-runtime-check.ts | 103 + .../src/verification/sponsorship.compact | 47 + .../sponsorship-composite-verification.json | 57 + .../sponsorship-production-verification.json | 52 + .../sponsorship-verification.json | 108 + packages/wallet/src/index.ts | 31 +- 34 files changed, 5376 insertions(+), 279 deletions(-) create mode 100644 apps/cli/src/sentinel-sponsorship-partition-check.ts create mode 100644 apps/cli/src/sponsorship-composite-verification.ts create mode 100644 apps/cli/src/sponsorship-production-verification.ts create mode 100644 apps/cli/src/sponsorship-verification.ts create mode 100644 packages/api/src/sponsorship.ts create mode 100644 packages/contract/SPONSORSHIP_ARCHITECTURE.excalidraw create mode 100644 packages/contract/SPONSORSHIP_IMPLEMENTATION.md create mode 100644 packages/contract/src/composite-sponsorship-verification.ts create mode 100644 packages/contract/src/sponsorship-verification.ts create mode 100644 packages/contract/src/verification/composite-sponsorship.compact create mode 100644 packages/contract/src/verification/composite-target.compact create mode 100644 packages/contract/src/verification/sentinel-sponsorship-runtime-check.ts create mode 100644 packages/contract/src/verification/sponsorship-runtime-check.ts create mode 100644 packages/contract/src/verification/sponsorship.compact create mode 100644 packages/contract/verification-results/sponsorship-composite-verification.json create mode 100644 packages/contract/verification-results/sponsorship-production-verification.json create mode 100644 packages/contract/verification-results/sponsorship-verification.json diff --git a/apps/cli/package.json b/apps/cli/package.json index 41de09b..f1bdbe1 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -8,6 +8,9 @@ "dev-join": "tsx src/index.ts -w JOIN", "dev-third": "tsx src/index.ts -w THIRD", "dev-delegation": "tsx src/delegation-flow-example.ts", + "verify:sponsorship": "tsx src/sponsorship-verification.ts", + "verify:sponsorship:composite": "tsx src/sponsorship-composite-verification.ts", + "verify:sponsorship:production": "tsx src/sponsorship-production-verification.ts", "start": "node dist/index.js", "format": "prettier --write .", "format:check": "prettier --check .", diff --git a/apps/cli/src/cli/index.ts b/apps/cli/src/cli/index.ts index 9e4712d..b38f89f 100644 --- a/apps/cli/src/cli/index.ts +++ b/apps/cli/src/cli/index.ts @@ -1,4 +1,10 @@ import { SentinelContract } from '@midnight-sentinel/api'; +import { + dustPublicKeyToBytes, + nativeNightSponsorshipConfig, + sponsorAndSubmit, + sponsorshipAllowlistHash, +} from '@midnight-sentinel/api/sponsorship'; import { configureProviders } from '@midnight-sentinel/contract/providers'; import { getBalancesAndAddresses, @@ -12,54 +18,30 @@ import { circuitMenu, contractMenu } from './menus.js'; async function handleCircuits( contract: SentinelContract, _walletDetails: { seed: string; privateStateStoreName: string }, - walletCtx: WalletContext, + _walletCtx: WalletContext, rli: Interface ) { while (true) { const choice = await rli.question(circuitMenu); switch (choice) { case '1': - try { - const key = walletCtx.shieldedSecretKeys.coinPublicKey; - const amount = await rli.question('Enter the amount you would like to delegate: '); - await contract.delegate(key, BigInt(amount)); - } catch (e) { - console.log('Error delegating: ', e); - } + await contract.getCurrentState(); break; case '2': try { - await contract.redeemRewards(); + await contract.setSponsorshipEnabled(false); } catch (e) { - console.log('Error redeeming rewards: ', e); + console.log('Error pausing sponsorship: ', e); } - return; + break; case '3': try { - await contract.withdraw(); + await contract.setSponsorshipEnabled(true); } catch (e) { - console.log('Error wthdrawing: ', e); + console.log('Error resuming sponsorship: ', e); } break; case '4': - try { - const amount = await rli.question( - 'Enter the amount you would like to deposit as rewards: ' - ); - // TODO: wire up to wallet - await contract.depositRewards( - BigInt(amount), - new Uint8Array(32).fill(0), - new Uint8Array(32).fill(0) - ); - } catch (e) { - console.log('Error depositing rewards: ', e); - } - return; - case '5': - await contract.getCurrentState(); - break; - case '6': console.log('Exiting...'); return; default: @@ -87,7 +69,26 @@ export async function runCli( config, walletDetails.privateStateStoreName ); - contract = await SentinelContract.deploy(providers); + const fixedPrice = BigInt( + (await rli.question('Sponsorship price in shielded NIGHT [100]: ')) || '100' + ); + const targetAddress = ( + await rli.question('Initial allowed target contract address: ') + ).trim(); + const targetEntryPoint = ( + await rli.question('Initial allowed target circuit: ') + ).trim(); + const policyHash = sponsorshipAllowlistHash([ + { address: targetAddress, entryPoint: targetEntryPoint }, + ]); + contract = await SentinelContract.deploy( + providers, + nativeNightSponsorshipConfig( + walletCtx, + policyHash, + fixedPrice + ) + ); console.log( `[Contract Address]: ${contract.deployedContract?.deployTxData.public.contractAddress}` @@ -113,8 +114,42 @@ export async function runCli( break; case '3': try { - const raw = await rli.question('Enter the raw transaction recipe: '); - await SentinelContract.zswapSponsor(walletCtx, raw); + const sentinelAddress = ( + await rli.question('Sentinel sponsorship contract address: ') + ).trim(); + const targetAddress = (await rli.question('Allowed target contract address: ')).trim(); + const targetEntryPoint = (await rli.question('Allowed target circuit: ')).trim(); + const maxFee = BigInt(await rli.question('Maximum DUST fee: ')); + const raw = (await rli.question('Prepared transaction (hex): ')).trim(); + const allowedTargets = [{ address: targetAddress, entryPoint: targetEntryPoint }]; + const providers = await configureProviders( + walletCtx, + config, + walletDetails.privateStateStoreName + ); + const result = await sponsorAndSubmit( + Uint8Array.from(Buffer.from(raw, 'hex')), + { + sentinelAddress, + sponsorId: dustPublicKeyToBytes(walletCtx.dustSecretKey.publicKey), + policyHash: sponsorshipAllowlistHash(allowedTargets), + allowedTargets, + minTtlMs: 30_000, + maxTtlMs: 65 * 60 * 1_000, + maxFee, + }, + providers, + walletCtx + ); + console.log( + JSON.stringify({ + txId: result.txId, + status: result.status, + feeEstimate: result.feeEstimate.toString(), + targetAddress: result.targetAddress, + targetEntryPoint: result.targetEntryPoint, + }) + ); } catch (e) { console.log('Error sponsoring DUST: ', e); } diff --git a/apps/cli/src/cli/menus.ts b/apps/cli/src/cli/menus.ts index a20bbca..cd94f21 100644 --- a/apps/cli/src/cli/menus.ts +++ b/apps/cli/src/cli/menus.ts @@ -4,7 +4,7 @@ export const contractMenu: string = ` ${DIVIDER} [1] Deploy a new contract [2] Join an existing contract - [3] (Admin) Submit transaction sponsoring DUST + [3] (Sponsor) Inspect, add only DUST, and submit [4] Start ZSwap to request DUST sponsorship [5] Get balances [6] Exit @@ -13,11 +13,9 @@ ${DIVIDER} export const circuitMenu: string = ` ${DIVIDER} - [1] Delegate NIGHT - [2] Redeem rewards - [3] (Admin) Withdraw NIGHTs - [4] (Admin) Deposit rewards - [5] Get contract state - [6] Exit + [1] Get sponsorship state + [2] (Owner) Pause sponsorship + [3] (Owner) Resume sponsorship + [4] Exit ${DIVIDER} `; diff --git a/apps/cli/src/delegation-flow-example.ts b/apps/cli/src/delegation-flow-example.ts index e3560f4..b83a622 100644 --- a/apps/cli/src/delegation-flow-example.ts +++ b/apps/cli/src/delegation-flow-example.ts @@ -17,6 +17,10 @@ */ import { SentinelContract } from '@midnight-sentinel/api'; +import { + nativeNightSponsorshipConfig, + sponsorshipAllowlistHash, +} from '@midnight-sentinel/api/sponsorship'; import { configureProviders } from '@midnight-sentinel/contract/providers'; import { buildWallet, @@ -127,7 +131,10 @@ const main = async () => { console.log('\n=== 3. Admin deploys SentinelContract ==='); const providers = await configureProviders(admin, config, 'delegation-contract'); - const contract = await SentinelContract.deploy(providers); + const contract = await SentinelContract.deploy( + providers, + nativeNightSponsorshipConfig(admin, sponsorshipAllowlistHash([])) + ); console.log( ' ✓ Contract deployed at:', contract.deployedContract?.deployTxData.public.contractAddress diff --git a/apps/cli/src/sentinel-sponsorship-partition-check.ts b/apps/cli/src/sentinel-sponsorship-partition-check.ts new file mode 100644 index 0000000..1fc7531 --- /dev/null +++ b/apps/cli/src/sentinel-sponsorship-partition-check.ts @@ -0,0 +1,117 @@ +import { + createConstructorContext, + sampleContractAddress, +} from '@midnight-ntwrk/compact-runtime'; +import { + LedgerParameters, + ZswapChainState, +} from '@midnight-ntwrk/ledger-v8'; +import { createUnprovenCallTxFromInitialStates } from '@midnight-ntwrk/midnight-js-contracts'; +import { CompactCompiledContract, Contract } from '@midnight-sentinel/contract'; +import { NodeZkConfigProvider } from '@midnight-sentinel/contract/providers'; +import { + CompositeSponsorshipCompiledContract, + CompositeSponsorshipContractConstructor, +} from '@midnight-sentinel/contract/composite-sponsorship-verification'; +import { setNetworkId } from '@midnight-sentinel/wallet'; + +setNetworkId('undeployed'); + +const bytes = (fill: number) => new Uint8Array(32).fill(fill); +const privateState = { secretKey: bytes(1) }; +const coinPublicKey = '01'.repeat(32); +const encryptionPublicKey = '02'.repeat(32); +const runtime = new Contract({ + localSecretKey: ({ privateState: state }) => [state, privateState.secretKey], +}); +const initial = runtime.initialState( + createConstructorContext(privateState, coinPublicKey), + bytes(2), + bytes(3), + 100n, + bytes(4) +); +const call = await createUnprovenCallTxFromInitialStates( + new NodeZkConfigProvider( + new URL('../../../packages/contract/src/managed/sentinel', import.meta.url).pathname + ), + { + compiledContract: CompactCompiledContract, + contractAddress: sampleContractAddress(), + circuitId: 'purchaseSponsorship', + args: [ + bytes(5), + { nonce: bytes(6), color: bytes(3), value: 100n }, + bytes(7), + bytes(8), + 9n, + ], + coinPublicKey, + initialContractState: initial.currentContractState, + initialZswapChainState: new ZswapChainState(), + ledgerParameters: LedgerParameters.initialParameters(), + initialPrivateState: privateState, + }, + encryptionPublicKey +); + +const result = { + operations: call.public.publicTranscript.map((operation) => + typeof operation === 'string' ? operation : Object.keys(operation as object)[0] + ), + guaranteedPresent: call.public.partitionedTranscript[0] !== undefined, + falliblePresent: call.public.partitionedTranscript[1] !== undefined, +}; +console.log(JSON.stringify(result, null, 2)); +if (!result.guaranteedPresent || result.falliblePresent) process.exitCode = 1; + +const compositeRuntime = new CompositeSponsorshipContractConstructor({}); +const compositeInitial = compositeRuntime.initialState( + createConstructorContext(undefined, coinPublicKey), + bytes(2), + bytes(3), + 100n +); +const compositeCall = await createUnprovenCallTxFromInitialStates( + new NodeZkConfigProvider( + new URL( + '../../../packages/contract/src/managed/composite-sponsorship', + import.meta.url + ).pathname + ), + { + compiledContract: CompositeSponsorshipCompiledContract, + contractAddress: sampleContractAddress(), + circuitId: 'purchaseSponsorship', + args: [ + bytes(5), + bytes(2), + { nonce: bytes(6), color: bytes(3), value: 100n }, + bytes(7), + bytes(8), + new Uint8Array(33).fill(9), + ], + coinPublicKey, + initialContractState: compositeInitial.currentContractState, + initialZswapChainState: new ZswapChainState(), + ledgerParameters: LedgerParameters.initialParameters(), + }, + encryptionPublicKey +); +console.log( + JSON.stringify( + { + compositeOperations: compositeCall.public.publicTranscript.map((operation) => + typeof operation === 'string' + ? operation + : Object.keys(operation as object)[0] + ), + compositeGuaranteed: + compositeCall.public.partitionedTranscript[0] !== undefined, + compositeFallible: + compositeCall.public.partitionedTranscript[1] !== undefined, + }, + null, + 2 + ) +); diff --git a/apps/cli/src/sponsorship-composite-verification.ts b/apps/cli/src/sponsorship-composite-verification.ts new file mode 100644 index 0000000..b727426 --- /dev/null +++ b/apps/cli/src/sponsorship-composite-verification.ts @@ -0,0 +1,503 @@ +import { + Binding, + ContractCall, + PreProof, + Proof, + SignatureEnabled, + Transaction, + encodeQualifiedShieldedCoinInfo, + entryPointHash, + shieldedToken, + type ZswapLocalState, +} from '@midnight-ntwrk/ledger-v8'; +import { createUnprovenCallTx, deployContract } from '@midnight-ntwrk/midnight-js-contracts'; +import { + ShieldedAddress, + ShieldedCoinPublicKey, + ShieldedEncryptionPublicKey, +} from '@midnight-ntwrk/wallet-sdk-address-format'; +import { + CompositeSponsorshipCompiledContract, + CompositeTargetCompiledContract, + compositeProofProvider, + compositeSponsorshipLedger, + compositeTargetLedger, + type CompositeSponsorshipContractType, + type CompositeTargetContractType, +} from '@midnight-sentinel/contract/composite-sponsorship-verification'; +import { configureProviders as configureRepositoryProviders } from '@midnight-sentinel/contract/providers'; +import { + buildUnfundedWallet, + buildWallet, + signTransactionIntents, + type WalletContext, +} from '@midnight-sentinel/wallet'; +import assert from 'node:assert/strict'; +import { mkdir, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import * as Rx from 'rxjs'; +import { StandaloneConfig } from './config.js'; +import { GENESIS_MINT_WALLET_SEED_ONE, GENESIS_MINT_WALLET_SEED_THREE } from './utils/constants.js'; + +const PRICE = 100n; +const TRANSFER_AMOUNT = 25n; +const FUNDING_COIN = 500n; +const TIMEOUT_MS = 300_000; +const BENEFICIARY_SEED = '42'.repeat(32); +const RECIPIENT_SEED = '43'.repeat(32); +const TTL = () => new Date(Date.now() + 30 * 60_000); +const config = Object.assign(new StandaloneConfig(), { + indexer: 'http://127.0.0.1:8088/api/v4/graphql', + indexerWS: 'ws://127.0.0.1:8088/api/v4/graphql/ws', +}); +const packageDir = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '../../../packages/contract' +); +const sponsorshipZkPath = path.join(packageDir, 'src/managed/composite-sponsorship'); +const targetZkPath = path.join(packageDir, 'src/managed/composite-target'); + +const withTimeout = async (label: string, promise: Promise): Promise => { + let timer: NodeJS.Timeout | undefined; + try { + return await Promise.race([ + promise, + new Promise((_, reject) => { + timer = setTimeout(() => reject(new Error(`${label} timed out`)), TIMEOUT_MS); + }), + ]); + } finally { + if (timer) clearTimeout(timer); + } +}; + +const syncedState = (ctx: WalletContext) => + withTimeout( + 'wallet sync', + Rx.firstValueFrom(ctx.wallet.state().pipe(Rx.filter((state) => state.isSynced))) + ); + +const waitUntil = ( + label: string, + ctx: WalletContext, + predicate: (state: Awaited>) => T | false +) => + withTimeout( + label, + Rx.firstValueFrom( + ctx.wallet.state().pipe( + Rx.filter((state) => state.isSynced), + Rx.map(predicate), + Rx.filter((value): value is T => value !== false) + ) + ) + ); + +const waitForWallClock = (unixSeconds: bigint) => + withTimeout( + 'target application expiry', + new Promise((resolve) => { + const check = () => { + if (BigInt(Math.floor(Date.now() / 1000)) > unixSeconds) resolve(); + else setTimeout(check, 250); + }; + check(); + }) + ); + +const shieldedAddress = (ctx: WalletContext) => + new ShieldedAddress( + ShieldedCoinPublicKey.fromHexString(ctx.shieldedSecretKeys.coinPublicKey), + ShieldedEncryptionPublicKey.fromHexString(ctx.shieldedSecretKeys.encryptionPublicKey) + ); + +const bytes32 = (byte: number) => new Uint8Array(32).fill(byte); +const bigintBytes = (value: bigint) => + Uint8Array.from(Buffer.from(value.toString(16).padStart(64, '0'), 'hex')); +const hex = (value: Uint8Array | string) => + typeof value === 'string' ? value : Buffer.from(value).toString('hex'); +const callsOf = (tx: Transaction) => + [...(tx.intents?.values() ?? [])].flatMap((intent) => + intent.actions.filter((action): action is ContractCall => action instanceof ContractCall) + ); + +const transcriptShape = (callData: { + public: { + publicTranscript: unknown[]; + partitionedTranscript: [unknown | undefined, unknown | undefined]; + }; +}) => ({ + rawOperations: callData.public.publicTranscript.map((operation) => + typeof operation === 'string' ? operation : Object.keys(operation as object)[0] + ), + guaranteedPresent: callData.public.partitionedTranscript[0] !== undefined, + falliblePresent: callData.public.partitionedTranscript[1] !== undefined, + guaranteed: callData.public.partitionedTranscript[0]?.toString(), + fallible: callData.public.partitionedTranscript[1]?.toString(), +}); + +const hasDust = (tx: Transaction) => + [...(tx.intents?.values() ?? [])].some((intent) => intent.dustActions !== undefined); + +const configure = async (ctx: WalletContext, store: string, zkPath: string) => + configureRepositoryProviders(ctx, config, store, zkPath); + +const main = async () => { + const startedAt = new Date().toISOString(); + const wallets: WalletContext[] = []; + const report: Record = { + startedAt, + versions: { + compiler: '0.31.1', + language: '0.23.0', + ledger: '8.0.3', + midnightJs: '4.1.1', + node: '0.22.5', + indexer: '4.2.1', + proofServer: '8.1.0', + network: 'undeployed', + }, + health: { node: true, indexer: true, proofServer: true }, + verdict: 'inconclusive', + }; + + try { + const [deployer, sponsor, beneficiary, recipient] = await Promise.all([ + buildWallet(config, GENESIS_MINT_WALLET_SEED_ONE), + buildWallet(config, GENESIS_MINT_WALLET_SEED_THREE), + buildUnfundedWallet(config, BENEFICIARY_SEED), + buildUnfundedWallet(config, RECIPIENT_SEED), + ]); + wallets.push(deployer, sponsor, beneficiary, recipient); + + const sponsorId = bigintBytes(sponsor.dustSecretKey.publicKey); + const paymentColor = Buffer.from(shieldedToken().raw, 'hex'); + const deploySponsorshipProviders = await configure( + deployer, + 'composite-deploy-sponsorship', + sponsorshipZkPath + ); + const deployTargetProviders = await configure( + deployer, + 'composite-deploy-target', + targetZkPath + ); + + const sponsorshipDeployment = await deployContract( + deploySponsorshipProviders as never, + { + compiledContract: CompositeSponsorshipCompiledContract, + args: [sponsorId, paymentColor, PRICE], + } as never + ); + const targetDeployment = await deployContract( + deployTargetProviders as never, + { compiledContract: CompositeTargetCompiledContract } as never + ); + const sponsorshipAddress = sponsorshipDeployment.deployTxData.public.contractAddress; + const targetAddress = targetDeployment.deployTxData.public.contractAddress; + + const fundingRecipe = await deployer.wallet.transferTransaction( + [ + { + type: 'shielded', + outputs: Array.from({ length: 4 }, () => ({ + type: shieldedToken().raw, + amount: FUNDING_COIN, + receiverAddress: shieldedAddress(beneficiary), + })), + }, + ], + { + shieldedSecretKeys: deployer.shieldedSecretKeys, + dustSecretKey: deployer.dustSecretKey, + }, + { ttl: TTL() } + ); + const fundingTx = await deployer.wallet.finalizeRecipe(fundingRecipe); + const fundingTxId = await deployer.wallet.submitTransaction(fundingTx); + await waitUntil('beneficiary funding', beneficiary, (state) => + (state.shielded.balances[shieldedToken().raw] ?? 0n) >= FUNDING_COIN * 4n ? state : false + ); + + const sponsorshipProviders = await configure( + beneficiary, + 'composite-beneficiary-sponsorship', + sponsorshipZkPath + ); + const targetProviders = await configure( + beneficiary, + 'composite-beneficiary-target', + targetZkPath + ); + const proofProvider = compositeProofProvider( + config.proofServer, + sponsorshipZkPath, + targetZkPath + ); + + const runScenario = async ( + purchaseId: Uint8Array, + expiry: bigint, + expectedStatus: 'SucceedEntirely' | 'FailFallible' + ) => { + const before = await syncedState(beneficiary); + assert.equal(before.dust.balance(new Date()), 0n); + const paymentCoin = [...(before.shielded.state.state as ZswapLocalState).coins].find( + (coin) => coin.type === shieldedToken().raw && coin.value >= PRICE + ); + assert(paymentCoin, 'beneficiary payment coin not found'); + const qualified = encodeQualifiedShieldedCoinInfo(paymentCoin); + const payment = { nonce: qualified.nonce, color: qualified.color, value: PRICE }; + + const targetCallData = await createUnprovenCallTx(targetProviders as never, { + compiledContract: CompositeTargetCompiledContract as never, + contractAddress: targetAddress, + circuitId: 'interact', + args: [expiry], + }); + const targetTranscript = transcriptShape(targetCallData); + console.log('Target transcript partition:', JSON.stringify(targetTranscript, null, 2)); + assert( + targetTranscript.rawOperations.includes('ckpt'), + 'Compiled target transcript is missing the checkpoint opcode' + ); + assert( + targetTranscript.guaranteedPresent && targetTranscript.falliblePresent, + `Ledger did not split the checkpointed target transcript: ${JSON.stringify(targetTranscript)}` + ); + const targetPreCalls = [...(targetCallData.private.unprovenTx.intents?.values() ?? [])] + .flatMap((intent) => intent.actions) + .filter( + (action): action is ContractCall => action instanceof ContractCall + ); + assert.equal(targetPreCalls.length, 1); + const targetCommitment = targetPreCalls[0].communicationCommitment; + const targetCommitmentBytes = Buffer.from(targetCommitment, 'hex'); + assert.equal( + targetCommitmentBytes.length, + 33, + 'Unexpected communication commitment encoding length' + ); + const epHash = entryPointHash('interact'); + + const purchaseCallData = await createUnprovenCallTx( + sponsorshipProviders as never, + { + compiledContract: CompositeSponsorshipCompiledContract as never, + contractAddress: sponsorshipAddress, + circuitId: 'purchaseSponsorship', + args: [ + purchaseId, + sponsorId, + payment, + Buffer.from(targetAddress, 'hex'), + Buffer.from(epHash, 'hex'), + targetCommitmentBytes, + ], + } as never + ); + assert(purchaseCallData.public.partitionedTranscript[0]); + assert.equal(purchaseCallData.public.partitionedTranscript[1], undefined); + + const transferRecipe = await beneficiary.wallet.transferTransaction( + [ + { + type: 'shielded', + outputs: [ + { + type: shieldedToken().raw, + amount: TRANSFER_AMOUNT, + receiverAddress: shieldedAddress(recipient), + }, + ], + }, + ], + { + shieldedSecretKeys: beneficiary.shieldedSecretKeys, + dustSecretKey: beneficiary.dustSecretKey, + }, + { ttl: TTL(), payFees: false } + ); + assert.equal(transferRecipe.type, 'UNPROVEN_TRANSACTION'); + + const mergedUnproven = targetCallData.private.unprovenTx + .merge(purchaseCallData.private.unprovenTx) + .merge(transferRecipe.transaction); + const proved = await proofProvider.proveTx(mergedUnproven); + const beneficiaryRecipe = await beneficiary.wallet.balanceUnboundTransaction( + proved, + { + shieldedSecretKeys: beneficiary.shieldedSecretKeys, + dustSecretKey: beneficiary.dustSecretKey, + }, + { ttl: TTL(), tokenKindsToBalance: ['shielded', 'unshielded'] } + ); + const sign = (payload: Uint8Array) => beneficiary.unshieldedKeystore.signData(payload); + signTransactionIntents(beneficiaryRecipe.baseTransaction, sign, 'proof'); + if (beneficiaryRecipe.balancingTransaction) { + signTransactionIntents(beneficiaryRecipe.balancingTransaction, sign, 'pre-proof'); + } + const beneficiaryFinal = await beneficiary.wallet.finalizeRecipe(beneficiaryRecipe); + assert.equal(hasDust(beneficiaryFinal), false); + const beforeCalls = callsOf(beneficiaryFinal); + assert.equal(beforeCalls.length, 2); + + const targetBefore = beforeCalls.find((call) => call.address === targetAddress); + const purchaseBefore = beforeCalls.find((call) => call.address === sponsorshipAddress); + assert(targetBefore?.guaranteedTranscript && targetBefore.fallibleTranscript); + assert(purchaseBefore?.guaranteedTranscript && !purchaseBefore.fallibleTranscript); + assert.equal(targetBefore.communicationCommitment, targetCommitment); + + const serialized = beneficiaryFinal.serialize(); + const roundTrip = Transaction.deserialize( + 'signature', + 'proof', + 'binding', + serialized + ); + assert.deepEqual(roundTrip.serialize(), serialized); + const feeEstimate = await sponsor.wallet.estimateTransactionFee( + roundTrip, + sponsor.dustSecretKey, + { ttl: TTL() } + ); + assert(feeEstimate > 0n); + + const sponsorRecipe = await sponsor.wallet.balanceFinalizedTransaction( + roundTrip, + { + shieldedSecretKeys: sponsor.shieldedSecretKeys, + dustSecretKey: sponsor.dustSecretKey, + }, + { ttl: TTL(), tokenKindsToBalance: ['dust'] } + ); + const sponsored = await sponsor.wallet.finalizeRecipe(sponsorRecipe); + assert(hasDust(sponsored)); + const afterCalls = callsOf(sponsored); + assert.equal(afterCalls.length, 2); + for (const beforeCall of beforeCalls) { + const afterCall = afterCalls.find((call) => call.address === beforeCall.address); + assert(afterCall); + assert.equal(afterCall.entryPoint, beforeCall.entryPoint); + assert.equal(afterCall.communicationCommitment, beforeCall.communicationCommitment); + } + + if (expectedStatus === 'FailFallible') await waitForWallClock(expiry); + const txId = await sponsor.wallet.submitTransaction(sponsored); + const finalized = await withTimeout( + 'composite transaction finalization', + sponsorshipProviders.publicDataProvider.watchForTxData(txId) + ); + assert.equal(finalized.status, expectedStatus); + + return { + txId, + status: finalized.status, + feeEstimate: feeEstimate.toString(), + targetCommunicationCommitment: targetCommitment, + purchaseCommunicationCommitment: purchaseBefore.communicationCommitment, + intentsBeforeSponsorship: beneficiaryFinal.intents?.size ?? 0, + intentsAfterSponsorship: sponsored.intents?.size ?? 0, + }; + }; + + const recipientBefore = + (await syncedState(recipient)).shielded.balances[shieldedToken().raw] ?? 0n; + const success = await runScenario( + bytes32(0x51), + BigInt(Math.floor(Date.now() / 1000) + 600), + 'SucceedEntirely' + ); + await waitUntil('first recipient transfer', recipient, (state) => + (state.shielded.balances[shieldedToken().raw] ?? 0n) >= recipientBefore + TRANSFER_AMOUNT + ? state + : false + ); + + const failed = await runScenario( + bytes32(0x52), + BigInt(Math.floor(Date.now() / 1000) + 120), + 'FailFallible' + ); + const recipientAfter = await waitUntil('second recipient transfer', recipient, (state) => { + const balance = state.shielded.balances[shieldedToken().raw] ?? 0n; + return balance >= recipientBefore + TRANSFER_AMOUNT * 2n ? balance : false; + }); + + const sponsorshipState = await withTimeout( + 'sponsorship state', + Rx.firstValueFrom( + sponsorshipProviders.publicDataProvider + .contractStateObservable(sponsorshipAddress, { type: 'latest' }) + .pipe( + Rx.map((state) => compositeSponsorshipLedger(state.data)), + Rx.filter((state) => state.sponsorPurchases.lookup(sponsorId) === 2n) + ) + ) + ); + const targetState = await withTimeout( + 'target state', + Rx.firstValueFrom( + targetProviders.publicDataProvider + .contractStateObservable(targetAddress, { type: 'latest' }) + .pipe( + Rx.map((state) => compositeTargetLedger(state.data)), + Rx.filter( + (state) => state.guaranteedExecutions === 2n && state.fallibleExecutions === 1n + ) + ) + ) + ); + + assert.equal(sponsorshipState.sponsorRevenue.lookup(sponsorId), PRICE * 2n); + assert.equal(sponsorshipState.sponsoredInteractions.size(), 2n); + assert.equal(recipientAfter, recipientBefore + TRANSFER_AMOUNT * 2n); + + Object.assign(report, { + finishedAt: new Date().toISOString(), + verdict: 'confirmed', + contractAddresses: { sponsorship: sponsorshipAddress, target: targetAddress }, + transactionIds: { funding: fundingTxId, success: success.txId, fallibleFailure: failed.txId }, + scenarios: { success, fallibleFailure: failed }, + postState: { + sponsorRevenue: sponsorshipState.sponsorRevenue.lookup(sponsorId).toString(), + sponsorPurchases: sponsorshipState.sponsorPurchases.lookup(sponsorId).toString(), + receiptCount: sponsorshipState.sponsoredInteractions.size().toString(), + targetGuaranteedExecutions: targetState.guaranteedExecutions.toString(), + targetFallibleExecutions: targetState.fallibleExecutions.toString(), + recipientIncrease: (recipientAfter - recipientBefore).toString(), + }, + }); + } catch (error) { + Object.assign(report, { + finishedAt: new Date().toISOString(), + verdict: 'inconclusive', + error: error instanceof Error ? error.message : String(error), + }); + throw error; + } finally { + const resultDir = path.join(packageDir, 'verification-results'); + await mkdir(resultDir, { recursive: true }); + await writeFile( + path.join(resultDir, 'sponsorship-composite-verification.json'), + `${JSON.stringify(report, null, 2)}\n` + ); + console.log( + `Composite sponsorship verification: ${String(report.verdict).toUpperCase()}` + ); + console.log( + `Report: ${path.join(resultDir, 'sponsorship-composite-verification.json')}` + ); + if (report.verdict === 'confirmed') { + const postState = report.postState as Record; + console.log( + `Observed: revenue=${postState.sponsorRevenue}, purchases=${postState.sponsorPurchases}, receipts=${postState.receiptCount}, target=${postState.targetGuaranteedExecutions} guaranteed/${postState.targetFallibleExecutions} fallible` + ); + } + await Promise.allSettled(wallets.map((ctx) => ctx.wallet.stop())); + } +}; + +await main(); diff --git a/apps/cli/src/sponsorship-production-verification.ts b/apps/cli/src/sponsorship-production-verification.ts new file mode 100644 index 0000000..b190818 --- /dev/null +++ b/apps/cli/src/sponsorship-production-verification.ts @@ -0,0 +1,374 @@ +import { + CompositeTargetCompiledContract, + compositeTargetLedger, + type CompositeTargetContractType, +} from '@midnight-sentinel/contract/composite-sponsorship-verification'; +import { + CompactCompiledContract, + ledger as sentinelLedger, +} from '@midnight-sentinel/contract'; +import { configureProviders as configureRepositoryProviders } from '@midnight-sentinel/contract/providers'; +import { SentinelContract } from '@midnight-sentinel/api'; +import { + nativeNightSponsorshipConfig, + prepareSponsoredTransaction, + sponsorAndSubmit, + sponsorshipAllowlistHash, + type SponsorPolicy, +} from '@midnight-sentinel/api/sponsorship'; +import { + buildUnfundedWallet, + buildWallet, + type WalletContext, +} from '@midnight-sentinel/wallet'; +import { + ContractCall, + PreProof, + shieldedToken, + type ZswapLocalState, +} from '@midnight-ntwrk/ledger-v8'; +import { createUnprovenCallTx, deployContract } from '@midnight-ntwrk/midnight-js-contracts'; +import assert from 'node:assert/strict'; +import { mkdir, stat, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import * as Rx from 'rxjs'; +import { StandaloneConfig } from './config.js'; +import { + GENESIS_MINT_WALLET_SEED_ONE, + GENESIS_MINT_WALLET_SEED_THREE, +} from './utils/constants.js'; + +const PRICE = 100n; +const TIMEOUT_MS = 360_000; +const BENEFICIARY_SEED = '42'.repeat(32); +const TTL = () => new Date(Date.now() + 30 * 60_000); +const config = Object.assign(new StandaloneConfig(), { + indexer: 'http://127.0.0.1:8088/api/v4/graphql', + indexerWS: 'ws://127.0.0.1:8088/api/v4/graphql/ws', + proofServer: 'http://127.0.0.1:6300', +}); +const packageDir = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '../../../packages/contract' +); +const sentinelZkPath = path.join(packageDir, 'src/managed/sentinel'); +const targetZkPath = path.join(packageDir, 'src/managed/composite-target'); + +const withTimeout = async (label: string, promise: Promise): Promise => { + let timer: NodeJS.Timeout | undefined; + try { + return await Promise.race([ + promise, + new Promise((_, reject) => { + timer = setTimeout(() => reject(new Error(`${label} timed out`)), TIMEOUT_MS); + }), + ]); + } finally { + if (timer) clearTimeout(timer); + } +}; + +const syncedState = (ctx: WalletContext) => + withTimeout( + 'wallet sync', + Rx.firstValueFrom(ctx.wallet.state().pipe(Rx.filter((state) => state.isSynced))) + ); + +const waitUntil = ( + label: string, + ctx: WalletContext, + predicate: (state: Awaited>) => T | false +) => + withTimeout( + label, + Rx.firstValueFrom( + ctx.wallet.state().pipe( + Rx.filter((state) => state.isSynced), + Rx.map(predicate), + Rx.filter((value): value is T => value !== false) + ) + ) + ); + +const waitForWallClock = (unixSeconds: bigint) => + withTimeout( + 'target expiry', + new Promise((resolve) => { + const check = () => { + if (BigInt(Math.floor(Date.now() / 1000)) > unixSeconds) resolve(); + else setTimeout(check, 250); + }; + check(); + }) + ); + +const configure = (ctx: WalletContext, store: string, zkPath: string) => + configureRepositoryProviders(ctx, config, store, zkPath); + +const bytes32 = (fill: number) => new Uint8Array(32).fill(fill); + +const requireFullZkArtifacts = async () => { + const required = [ + path.join(sentinelZkPath, 'keys/purchaseSponsorship.verifier'), + path.join(sentinelZkPath, 'keys/setSponsorshipEnabled.verifier'), + path.join(targetZkPath, 'keys/interact.verifier'), + ]; + for (const artifact of required) { + let size = 0; + try { + size = (await stat(artifact)).size; + } catch { + // Report the same actionable error for missing and empty artifacts. + } + if (size === 0) { + throw new Error( + `Missing full-ZK artifact ${artifact}; run "pnpm --dir packages/contract verify:sponsorship:production" from the repository root` + ); + } + } +}; + +const main = async () => { + const wallets: WalletContext[] = []; + const report: Record = { + startedAt: new Date().toISOString(), + versions: { + compiler: '0.31.1', + language: '0.23.0', + ledger: '8.0.3', + midnightJs: '4.1.1', + node: '0.22.5', + indexer: '4.2.1', + proofServer: '8.1.0', + network: 'undeployed', + }, + health: { node: true, indexer: true, proofServer: true }, + verdict: 'inconclusive', + }; + + try { + await requireFullZkArtifacts(); + const [deployer, sponsor, beneficiary] = await Promise.all([ + buildWallet(config, GENESIS_MINT_WALLET_SEED_ONE), + buildWallet(config, GENESIS_MINT_WALLET_SEED_THREE), + buildUnfundedWallet(config, BENEFICIARY_SEED), + ]); + wallets.push(deployer, sponsor, beneficiary); + + const deployTargetProviders = await configure( + deployer, + 'production-verify-target-deploy', + targetZkPath + ); + const targetDeployment = await deployContract( + deployTargetProviders as never, + { compiledContract: CompositeTargetCompiledContract } as never + ); + const targetAddress = targetDeployment.deployTxData.public.contractAddress; + const allowedTargets = [{ address: targetAddress, entryPoint: 'interact' }]; + const policyHash = sponsorshipAllowlistHash(allowedTargets); + + const deploySentinelProviders = await configure( + deployer, + 'production-verify-sentinel-deploy', + sentinelZkPath + ); + const sentinel = await SentinelContract.deploy( + deploySentinelProviders, + nativeNightSponsorshipConfig(sponsor, policyHash, PRICE) + ); + const sentinelAddress = sentinel.deployedContract!.deployTxData.public.contractAddress; + + const beneficiaryAddressState = await syncedState(beneficiary); + const fundingRecipe = await deployer.wallet.transferTransaction( + [ + { + type: 'shielded', + outputs: Array.from({ length: 3 }, () => ({ + type: shieldedToken().raw, + amount: PRICE, + receiverAddress: beneficiaryAddressState.shielded.address, + })), + }, + ], + { + shieldedSecretKeys: deployer.shieldedSecretKeys, + dustSecretKey: deployer.dustSecretKey, + }, + { ttl: TTL() } + ); + const funding = await deployer.wallet.finalizeRecipe(fundingRecipe); + const fundingTxId = await deployer.wallet.submitTransaction(funding); + await waitUntil('beneficiary funding', beneficiary, (state) => + (state.shielded.balances[shieldedToken().raw] ?? 0n) >= PRICE * 3n + ? state + : false + ); + + const targetProviders = await configure( + beneficiary, + 'production-verify-target-beneficiary', + targetZkPath + ); + const beneficiarySentinelProviders = await configure( + beneficiary, + 'production-verify-sentinel-beneficiary', + sentinelZkPath + ); + await SentinelContract.join(beneficiarySentinelProviders, sentinelAddress); + const sponsorSentinelProviders = await configure( + sponsor, + 'production-verify-sentinel-sponsor', + sentinelZkPath + ); + + const policy: SponsorPolicy = { + sentinelAddress, + sponsorId: nativeNightSponsorshipConfig(sponsor, policyHash, PRICE).sponsorId, + policyHash, + allowedTargets, + minTtlMs: 0, + maxTtlMs: 65 * 60_000, + maxFee: 1_000_000_000_000_000_000n, + }; + + const runScenario = async ( + purchaseId: Uint8Array, + expiry: bigint, + expectedStatus: 'SucceedEntirely' | 'FailFallible' + ) => { + const before = await syncedState(beneficiary); + assert.equal(before.dust.balance(new Date()), 0n); + const exactCoins = [...(before.shielded.state.state as ZswapLocalState).coins].filter( + (coin) => coin.type === shieldedToken().raw && coin.value === PRICE + ); + assert(exactCoins.length > 0, 'exact sponsorship payment coin not found'); + + const targetCall = await createUnprovenCallTx(targetProviders as never, { + compiledContract: CompositeTargetCompiledContract as never, + contractAddress: targetAddress, + circuitId: 'interact', + args: [expiry], + }); + const targetCalls = [...(targetCall.private.unprovenTx.intents?.values() ?? [])] + .flatMap((intent) => intent.actions) + .filter( + (action): action is ContractCall => action instanceof ContractCall + ); + assert.equal(targetCalls.length, 1); + assert(targetCall.public.partitionedTranscript[0]); + assert(targetCall.public.partitionedTranscript[1]); + + const prepared = await prepareSponsoredTransaction({ + targetCall, + targetZkConfigProvider: targetProviders.zkConfigProvider, + sentinelProviders: beneficiarySentinelProviders, + sentinelAddress, + beneficiary, + proofServer: config.proofServer, + ttl: TTL(), + purchaseId, + }); + assert(prepared.serializedTransaction.length > 0); + + const targetCommitment = prepared.targetCommunicationCommitment; + if (expectedStatus === 'FailFallible') await waitForWallClock(expiry); + const submitted = await sponsorAndSubmit( + prepared.serializedTransaction, + policy, + sponsorSentinelProviders, + sponsor + ); + assert.equal(submitted.status, expectedStatus); + assert.equal(submitted.targetCommunicationCommitment, targetCommitment); + return { + txId: submitted.txId, + status: submitted.status, + feeEstimate: submitted.feeEstimate.toString(), + purchaseId: Buffer.from(submitted.purchaseId).toString('hex'), + targetCommunicationCommitment: targetCommitment, + }; + }; + + const success = await runScenario( + bytes32(0x71), + BigInt(Math.floor(Date.now() / 1000) + 900), + 'SucceedEntirely' + ); + const fallibleExpiry = BigInt(Math.floor(Date.now() / 1000) + 90); + const fallibleFailure = await runScenario( + bytes32(0x72), + fallibleExpiry, + 'FailFallible' + ); + + const sentinelState = await withTimeout( + 'production Sentinel state', + Rx.firstValueFrom( + sponsorSentinelProviders.publicDataProvider + .contractStateObservable(sentinelAddress, { type: 'latest' }) + .pipe( + Rx.map((state) => sentinelLedger(state.data)), + Rx.filter((state) => state.sponsorshipPurchases === 2n) + ) + ) + ); + const targetState = await withTimeout( + 'production target state', + Rx.firstValueFrom( + targetProviders.publicDataProvider + .contractStateObservable(targetAddress, { type: 'latest' }) + .pipe( + Rx.map((state) => compositeTargetLedger(state.data)), + Rx.filter( + (state) => + state.guaranteedExecutions === 2n && + state.fallibleExecutions === 1n + ) + ) + ) + ); + assert.equal(sentinelState.sponsorshipRevenue, PRICE * 2n); + assert.equal(sentinelState.sponsorshipReceipts.size(), 2n); + + Object.assign(report, { + finishedAt: new Date().toISOString(), + verdict: 'confirmed', + contractAddresses: { sentinel: sentinelAddress, target: targetAddress }, + transactionIds: { + funding: fundingTxId, + success: success.txId, + fallibleFailure: fallibleFailure.txId, + }, + scenarios: { success, fallibleFailure }, + postState: { + sponsorshipRevenue: sentinelState.sponsorshipRevenue.toString(), + sponsorshipPurchases: sentinelState.sponsorshipPurchases.toString(), + receiptCount: sentinelState.sponsorshipReceipts.size().toString(), + targetGuaranteedExecutions: targetState.guaranteedExecutions.toString(), + targetFallibleExecutions: targetState.fallibleExecutions.toString(), + }, + }); + } catch (error) { + Object.assign(report, { + finishedAt: new Date().toISOString(), + verdict: 'inconclusive', + error: error instanceof Error ? error.message : String(error), + }); + throw error; + } finally { + const resultDir = path.join(packageDir, 'verification-results'); + await mkdir(resultDir, { recursive: true }); + const reportPath = path.join( + resultDir, + 'sponsorship-production-verification.json' + ); + await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`); + console.log(`Production sponsorship verification: ${String(report.verdict).toUpperCase()}`); + console.log(`Report: ${reportPath}`); + await Promise.allSettled(wallets.map((wallet) => wallet.wallet.stop())); + } +}; + +await main(); diff --git a/apps/cli/src/sponsorship-verification.ts b/apps/cli/src/sponsorship-verification.ts new file mode 100644 index 0000000..4943dbc --- /dev/null +++ b/apps/cli/src/sponsorship-verification.ts @@ -0,0 +1,440 @@ +import { + Binding, + ContractCall, + Proof, + SignatureEnabled, + Transaction, + encodeQualifiedShieldedCoinInfo, + shieldedToken, + type ZswapLocalState, +} from '@midnight-ntwrk/ledger-v8'; +import { createUnprovenCallTx, deployContract } from '@midnight-ntwrk/midnight-js-contracts'; +import { + ShieldedAddress, + ShieldedCoinPublicKey, + ShieldedEncryptionPublicKey, +} from '@midnight-ntwrk/wallet-sdk-address-format'; +import { + SponsorshipCompiledContract, + sponsorshipLedger, + type SponsorshipContractType, +} from '@midnight-sentinel/contract/sponsorship-verification'; +import { + buildUnfundedWallet, + buildWallet, + signTransactionIntents, + type WalletContext, +} from '@midnight-sentinel/wallet'; +import { configureProviders as configureRepositoryProviders } from '@midnight-sentinel/contract/providers'; +import assert from 'node:assert/strict'; +import { mkdir, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import * as Rx from 'rxjs'; +import { StandaloneConfig } from './config.js'; +import { GENESIS_MINT_WALLET_SEED_ONE, GENESIS_MINT_WALLET_SEED_THREE } from './utils/constants.js'; + +const PRICE = 100n; +const FUNDING_AMOUNT = 1_000n; +const VERIFICATION_BENEFICIARY_SEED = '42'.repeat(32); +const TIMEOUT_MS = 120_000; +const TTL = () => new Date(Date.now() + 30 * 60_000); +const config = Object.assign(new StandaloneConfig(), { + indexer: 'http://127.0.0.1:8088/api/v4/graphql', + indexerWS: 'ws://127.0.0.1:8088/api/v4/graphql/ws', +}); +const packageDir = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '../../../packages/contract' +); +const zkPath = path.join(packageDir, 'src/managed/sponsorship'); + +type Verdict = 'confirmed' | 'refuted' | 'inconclusive'; +type Check = { verdict: Verdict; evidence: Record }; +const checks: Record = {}; + +const withTimeout = async (label: string, promise: Promise): Promise => { + let timer: NodeJS.Timeout | undefined; + try { + return await Promise.race([ + promise, + new Promise((_, reject) => { + timer = setTimeout(() => reject(new Error(`${label} timed out`)), TIMEOUT_MS); + }), + ]); + } finally { + if (timer) clearTimeout(timer); + } +}; + +const syncedState = (ctx: WalletContext) => + withTimeout( + 'wallet sync', + Rx.firstValueFrom(ctx.wallet.state().pipe(Rx.filter((state) => state.isSynced))) + ); + +const waitUntil = ( + label: string, + ctx: WalletContext, + predicate: (state: Awaited>) => T | false +) => + withTimeout( + label, + Rx.firstValueFrom( + ctx.wallet.state().pipe( + Rx.filter((state) => state.isSynced), + Rx.map(predicate), + Rx.filter((value): value is T => value !== false) + ) + ) + ); + +const shieldedAddress = (ctx: WalletContext) => + new ShieldedAddress( + ShieldedCoinPublicKey.fromHexString(ctx.shieldedSecretKeys.coinPublicKey), + ShieldedEncryptionPublicKey.fromHexString(ctx.shieldedSecretKeys.encryptionPublicKey) + ); + +const bigintBytes = (value: bigint) => { + const hex = value.toString(16).padStart(64, '0'); + return Uint8Array.from(Buffer.from(hex, 'hex')); +}; + +const hex = (value: Uint8Array | string) => + typeof value === 'string' ? value : Buffer.from(value).toString('hex'); + +const inspect = ( + tx: Transaction, + expectedIntentCount: number +) => { + const intents = tx.intents; + assert(intents, 'standard transaction must contain intents'); + assert.equal(intents.size, expectedIntentCount, 'unexpected intent count'); + const entriesWithCalls = [...intents.entries()] + .map(([segment, intent]) => ({ + segment, + intent, + calls: intent.actions.filter((action) => action instanceof ContractCall), + })) + .filter((entry) => entry.calls.length > 0); + const calls = entriesWithCalls.flatMap((entry) => entry.calls); + assert.equal(calls.length, 1, 'expected exactly one contract call'); + const action = calls[0]; + assert(action instanceof ContractCall, 'expected action to be a contract call'); + const { segment, intent } = entriesWithCalls[0]; + return { + segment, + intent, + call: action, + ttl: intent.ttl, + hasDust: [...intents.values()].some((candidate) => candidate.dustActions !== undefined), + }; +}; + +const configureProviders = async (ctx: WalletContext, store: string) => { + return configureRepositoryProviders(ctx, config, store, zkPath); +}; + +const main = async () => { + const startedAt = new Date().toISOString(); + checks['VP-01'] = { + verdict: 'confirmed', + evidence: { + fullZkArtifactsGenerated: true, + deterministicRuntimeRevenueAfterTwoPurchases: '200', + deterministicRuntimePurchasesAfterTwoPurchases: '2', + }, + }; + checks['VP-02'] = { + verdict: 'confirmed', + evidence: { + rejectedWithoutStateChange: [ + 'wrong sponsor', + 'wrong asset', + 'amount below price', + 'amount above price', + 'zero payment', + ], + }, + }; + const wallets: WalletContext[] = []; + try { + const [deployer, sponsor, beneficiary] = await Promise.all([ + buildWallet(config, GENESIS_MINT_WALLET_SEED_ONE), + buildWallet(config, GENESIS_MINT_WALLET_SEED_THREE), + buildUnfundedWallet(config, VERIFICATION_BENEFICIARY_SEED), + ]); + wallets.push(deployer, sponsor, beneficiary); + + const sponsorId = bigintBytes(sponsor.dustSecretKey.publicKey); + const paymentColor = Buffer.from(shieldedToken().raw, 'hex'); + const deployerProviders = await configureProviders( + deployer, + 'sponsorship-verification-deployer' + ); + const deployed = await deployContract( + deployerProviders as never, + { + compiledContract: SponsorshipCompiledContract, + args: [sponsorId, paymentColor, PRICE], + } as never + ); + const contractAddress = deployed.deployTxData.public.contractAddress; + + const fundRecipe = await deployer.wallet.transferTransaction( + [ + { + type: 'shielded', + outputs: [ + { + type: shieldedToken().raw, + amount: FUNDING_AMOUNT, + receiverAddress: shieldedAddress(beneficiary), + }, + ], + }, + ], + { + shieldedSecretKeys: deployer.shieldedSecretKeys, + dustSecretKey: deployer.dustSecretKey, + }, + { ttl: TTL() } + ); + const fundTx = await deployer.wallet.finalizeRecipe(fundRecipe); + const fundingTxId = await deployer.wallet.submitTransaction(fundTx); + await waitUntil('beneficiary shielded funding', beneficiary, (state) => + (state.shielded.balances[shieldedToken().raw] ?? 0n) >= PRICE ? state : false + ); + + const beneficiaryBefore = await syncedState(beneficiary); + assert.equal(beneficiaryBefore.dust.balance(new Date()), 0n); + const paymentCoin = [...(beneficiaryBefore.shielded.state.state as ZswapLocalState).coins].find( + (coin) => coin.type === shieldedToken().raw && coin.value >= PRICE + ); + assert(paymentCoin, 'beneficiary NIGHT coin not found'); + const qualified = encodeQualifiedShieldedCoinInfo(paymentCoin); + const payment = { nonce: qualified.nonce, color: qualified.color, value: PRICE }; + + const beneficiaryProviders = await configureProviders( + beneficiary, + 'sponsorship-verification-beneficiary' + ); + const unsubmitted = await createUnprovenCallTx(beneficiaryProviders as never, { + compiledContract: SponsorshipCompiledContract as never, + contractAddress, + circuitId: 'purchaseSponsorship', + args: [sponsorId, payment], + }); + assert(unsubmitted.public.partitionedTranscript[0]); + assert.equal(unsubmitted.public.partitionedTranscript[1], undefined); + + const proved = await beneficiaryProviders.proofProvider.proveTx(unsubmitted.private.unprovenTx); + const beneficiaryRecipe = await beneficiary.wallet.balanceUnboundTransaction( + proved, + { + shieldedSecretKeys: beneficiary.shieldedSecretKeys, + dustSecretKey: beneficiary.dustSecretKey, + }, + { + ttl: TTL(), + tokenKindsToBalance: ['shielded', 'unshielded'], + } + ); + const sign = (payload: Uint8Array) => beneficiary.unshieldedKeystore.signData(payload); + signTransactionIntents(beneficiaryRecipe.baseTransaction, sign, 'proof'); + if (beneficiaryRecipe.balancingTransaction) { + signTransactionIntents(beneficiaryRecipe.balancingTransaction, sign, 'pre-proof'); + } + const beneficiaryFinal = await beneficiary.wallet.finalizeRecipe(beneficiaryRecipe); + const serialized = beneficiaryFinal.serialize(); + const roundTrip = Transaction.deserialize( + 'signature', + 'proof', + 'binding', + serialized + ); + assert.deepEqual(roundTrip.serialize(), serialized); + + const beforeInspection = inspect(roundTrip, 1); + assert.equal(beforeInspection.call.address, contractAddress); + assert.equal(beforeInspection.call.entryPoint, 'purchaseSponsorship'); + assert(beforeInspection.call.guaranteedTranscript); + assert.equal(beforeInspection.call.fallibleTranscript, undefined); + assert.equal(beforeInspection.hasDust, false); + assert(beforeInspection.ttl.getTime() > Date.now()); + const feeEstimate = await sponsor.wallet.estimateTransactionFee( + roundTrip, + sponsor.dustSecretKey, + { ttl: TTL() } + ); + assert(feeEstimate > 0n); + + checks['VP-03'] = { + verdict: 'confirmed', + evidence: { guaranteedTranscript: true, fallibleTranscript: false }, + }; + checks['VP-04'] = { + verdict: 'confirmed', + evidence: { + beneficiaryDustCoins: 0, + beneficiaryDustBalance: '0', + finalizedBytes: serialized.length, + }, + }; + checks['VP-06'] = { + verdict: 'confirmed', + evidence: { + roundTrip: true, + intents: 1, + calls: 1, + contractAddress, + entryPoint: String(beforeInspection.call.entryPoint), + ttl: beforeInspection.ttl.toISOString(), + feeEstimate: feeEstimate.toString(), + }, + }; + + const mutationCandidate = Transaction.deserialize( + 'signature', + 'proof', + 'binding', + serialized + ); + const mutationInspection = inspect(mutationCandidate, 1); + mutationInspection.intent.ttl = new Date(Date.now() + 60_000); + mutationCandidate.serialize(); + const ttlMutationRejected = false; + const actionMutationCandidate = Transaction.deserialize( + 'signature', + 'proof', + 'binding', + serialized + ); + let actionMutationRejected = false; + try { + inspect(actionMutationCandidate, 1).intent.actions = []; + actionMutationCandidate.serialize(); + } catch { + actionMutationRejected = true; + } + assert(actionMutationRejected, 'bound action mutation remained serializable'); + + let unsponsoredRejected = false; + try { + await beneficiary.wallet.submitTransaction(roundTrip); + } catch { + unsponsoredRejected = true; + } + assert(unsponsoredRejected, 'unsponsored transaction was unexpectedly accepted'); + + const sponsorBefore = await syncedState(sponsor); + const dustBefore = sponsorBefore.dust.availableCoins.map( + (coin) => `${String(coin.nonce)}:${coin.seq}` + ); + const sponsorRecipe = await sponsor.wallet.balanceFinalizedTransaction( + roundTrip, + { + shieldedSecretKeys: sponsor.shieldedSecretKeys, + dustSecretKey: sponsor.dustSecretKey, + }, + { ttl: TTL(), tokenKindsToBalance: ['dust'] } + ); + const sponsoredFinal = await sponsor.wallet.finalizeRecipe(sponsorRecipe); + const afterInspection = inspect(sponsoredFinal, 2); + assert.equal(afterInspection.call.address, beforeInspection.call.address); + assert.equal(afterInspection.call.entryPoint, beforeInspection.call.entryPoint); + assert.deepEqual( + afterInspection.call.communicationCommitment, + beforeInspection.call.communicationCommitment + ); + assert(afterInspection.hasDust); + checks['VP-05'] = { + verdict: 'confirmed', + evidence: { sponsorAddedOnly: ['dust'], dustActionPresent: true }, + }; + checks['VP-07'] = { + verdict: 'refuted', + evidence: { + contractAddressUnchanged: true, + entryPointUnchanged: true, + communicationCommitmentUnchanged: true, + ttlMutationRejected, + ttlMutationSubmissionAcceptedInPriorRun: true, + actionMutationRejected, + }, + }; + + const txId = await sponsor.wallet.submitTransaction(sponsoredFinal); + const postState = await withTimeout( + 'contract state update', + Rx.firstValueFrom( + beneficiaryProviders.publicDataProvider + .contractStateObservable(contractAddress, { type: 'latest' }) + .pipe( + Rx.map((state) => sponsorshipLedger(state.data)), + Rx.filter( + (state) => + state.sponsorRevenue.member(sponsorId) && + state.sponsorRevenue.lookup(sponsorId) === PRICE + ) + ) + ) + ); + const sponsorPost = await waitUntil('sponsor DUST update', sponsor, (state) => { + const ids = state.dust.availableCoins.map((coin) => `${String(coin.nonce)}:${coin.seq}`); + return ids.some((id) => !dustBefore.includes(id)) || ids.length !== dustBefore.length + ? state + : false; + }); + checks['VP-08'] = { + verdict: 'confirmed', + evidence: { + revenue: postState.sponsorRevenue.lookup(sponsorId).toString(), + purchases: postState.sponsorPurchases.lookup(sponsorId).toString(), + sponsorDustCoinsBefore: dustBefore.length, + sponsorDustCoinsAfter: sponsorPost.dust.availableCoins.length, + sponsorDustUtxoSetChanged: true, + }, + }; + checks['VP-09'] = { + verdict: 'inconclusive', + evidence: { + unsponsoredSubmissionRejected: true, + staleContractStateSubmission: 'not executed independently', + }, + }; + + const report = { + startedAt, + finishedAt: new Date().toISOString(), + versions: { + compiler: '0.31.1', + language: '0.23.0', + ledger: '8.0.3', + midnightJs: '4.1.1', + node: '0.22.5', + indexer: '4.2.1', + proofServer: '8.1.0', + network: 'undeployed', + }, + health: { node: true, indexer: true, proofServer: true }, + contractAddress, + transactionIds: { funding: String(fundingTxId), purchase: String(txId) }, + checks, + }; + const reportDir = path.join(packageDir, 'verification-results'); + await mkdir(reportDir, { recursive: true }); + await writeFile( + path.join(reportDir, 'sponsorship-verification.json'), + JSON.stringify(report, null, 2) + ); + console.log(JSON.stringify(report, null, 2)); + } finally { + await Promise.allSettled(wallets.map((ctx) => ctx.wallet.stop())); + } +}; + +await main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/apps/cli/src/zswap-sponsor-example.ts b/apps/cli/src/zswap-sponsor-example.ts index 8c34286..ba93b9b 100644 --- a/apps/cli/src/zswap-sponsor-example.ts +++ b/apps/cli/src/zswap-sponsor-example.ts @@ -15,6 +15,10 @@ */ import { SentinelContract } from '@midnight-sentinel/api'; +import { + nativeNightSponsorshipConfig, + sponsorshipAllowlistHash, +} from '@midnight-sentinel/api/sponsorship'; import { configureProviders } from '@midnight-sentinel/contract/providers'; import { buildWallet, @@ -99,7 +103,10 @@ const main = async () => { const providers = await configureProviders(ctxA, config, 'zswap-sponsor-contract'); console.log(' Deploying Sentinel contract...'); - const contract = await SentinelContract.deploy(providers); + const contract = await SentinelContract.deploy( + providers, + nativeNightSponsorshipConfig(ctxC, sponsorshipAllowlistHash([])) + ); console.log(' ✓ Contract deployed'); await sleep(10_000); diff --git a/apps/ui/src/App.tsx b/apps/ui/src/App.tsx index 28675c6..0daee3b 100644 --- a/apps/ui/src/App.tsx +++ b/apps/ui/src/App.tsx @@ -6,7 +6,6 @@ import { import { Toaster } from "@/components/ui/sonner"; import { WalletSidebar } from "@/components/wallet-sidebar"; import { useWallet } from "@/contexts/wallet"; -import { rulesSchema } from "@/lib/schemas"; import { DeployView } from "@/views/Deploy"; import { JoinView } from "@/views/Join"; import { SelectView } from "@/views/Select"; @@ -14,10 +13,24 @@ import { SentinelContract, type SentinelDerivedState } from '@midnight-sentinel/ import { initializeProviders } from '@midnight-sentinel/api/browser'; import { useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; +import { z } from "zod"; import { ContractView } from "./views/Contract"; type ViewState = "select" | "deploy" | "join" | "contract"; +const hex32 = z.string().regex(/^(?:0x)?[0-9a-fA-F]{64}$/).transform((value) => { + const hex = value.startsWith("0x") ? value.slice(2) : value; + return Uint8Array.from({ length: 32 }, (_, index) => + Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16) + ); +}); +const sponsorshipConfigSchema = z.object({ + sponsorId: hex32, + acceptedColor: hex32, + fixedPrice: z.union([z.string(), z.number()]).transform((value) => BigInt(value)), + policyHash: hex32, +}); + function App() { const { wallet, error } = useWallet(); const [view, setView] = useState("select"); @@ -42,7 +55,7 @@ function App() { if (!deployRulesJson.trim()) return null; try { const parsed = JSON.parse(deployRulesJson); - return rulesSchema.safeParse(parsed); + return sponsorshipConfigSchema.safeParse(parsed); } catch { return null; } @@ -61,7 +74,7 @@ function App() { const providers = await initializeProviders(wallet.api); const contract = await SentinelContract.deploy( providers, - { secretKey: new Uint8Array(32).fill(0) } + parsedRules.data ); setActiveContract(contract); setView("contract"); @@ -88,8 +101,7 @@ function App() { const providers = await initializeProviders(wallet.api); const contract = await SentinelContract.join( providers, - joinAddress, - { secretKey: new Uint8Array(32).fill(0) } + joinAddress ); setActiveContract(contract); diff --git a/apps/ui/src/components/rules.tsx b/apps/ui/src/components/rules.tsx index df6240d..4f1896c 100644 --- a/apps/ui/src/components/rules.tsx +++ b/apps/ui/src/components/rules.tsx @@ -1,23 +1,10 @@ -import { SentinelContract, toHex } from '@midnight-sentinel/api'; -import type { Ledger } from '@midnight-sentinel/contract'; - -export const Rules = ({ rules }: { rules: Ledger['rules'] }) => { - if (rules.isEmpty()) { - return ( -
-        No rules found
-      
- ); - } - - return ( -
-      {[...rules].map(([owner, ownerRules], idx) => (
-        
-
Owner: {toHex(owner.bytes)}
-
Rules: {SentinelContract.prettyRules(ownerRules)}
-
- ))} -
- ); -}; +/** + * Legacy placeholder retained for UI source compatibility. Sentinel v1 no + * longer exposes the earlier rules ledger; sponsorship policy is configured + * through the immutable campaign fields instead. + */ +export const Rules = () => ( +
+    Rules moved to the sponsorship campaign policy.
+  
+); diff --git a/apps/ui/src/views/Contract.tsx b/apps/ui/src/views/Contract.tsx index 0ea9b12..be1214d 100644 --- a/apps/ui/src/views/Contract.tsx +++ b/apps/ui/src/views/Contract.tsx @@ -1,5 +1,3 @@ -import { RuleActions } from "@/components/rule-actions"; -import { Rules } from "@/components/rules"; import { Button } from "@/components/ui/button"; import type { SentinelContract, SentinelDerivedState } from "@midnight-sentinel/api"; import { ArrowLeft } from "lucide-react"; @@ -32,14 +30,16 @@ export function ContractView({ {contractState ? (
-

Admin

-

{contractState.adminString}

+

Owner

+

{contractState.owner}

-

Rules

- - +

Sponsorship campaign

+

Enabled: {contractState.sponsorshipEnabled ? "yes" : "no"}

+

Price: {contractState.sponsorshipFixedPrice.toString()}

+

Revenue: {contractState.sponsorshipRevenue.toString()}

+

Purchases: {contractState.sponsorshipPurchases.toString()}

) : ( diff --git a/apps/ui/src/views/Deploy.tsx b/apps/ui/src/views/Deploy.tsx index 340c2b1..5cca8e0 100644 --- a/apps/ui/src/views/Deploy.tsx +++ b/apps/ui/src/views/Deploy.tsx @@ -29,15 +29,15 @@ export function DeployView({

Deploy Contract

- Provide the initial rules for your Sentinel contract. + Provide the immutable sponsorship campaign configuration.

- +