diff --git a/cardano-effectstream-local/README.md b/cardano-effectstream-local/README.md new file mode 100644 index 000000000..f4c0f9cbb --- /dev/null +++ b/cardano-effectstream-local/README.md @@ -0,0 +1,20 @@ +IMPORTANT +- Provide a dolos binary in ./cardano-effectstream-local/packages/cardano/dolos +- Provide a yaci-cli binary in $HOME/.yaci-cli/yaci-cli + +```sh +deno install --allow-scripts && ./patch.sh +deno task -f @minimal-cardano/midnight-contract-counter-basic contract:compile +deno task -f @minimal-cardano/node dev +``` + + +This type of log is expected. +This means both midnight and utxorpc are syncing, and generating effectstream blocks. +``` +00:15:13 INFO effectstream-sync: [Midnight:undeployed] Fetching blocks from 11 to 11. +00:15:13 INFO effectstream-sync-ntp-mainNtp: [26] +00:15:13 INFO effectstream-sync: [UTXORPC] Fetching blocks from 78 to 78. +00:15:13 INFO effectstream-sync-block-merge: producing block 26 +00:15:13 INFO effectstream-sync-block-merge: finalized block 26 @ 0x62909d... | {"mainNtp":[26,26],"parallelUtxoRpc":[77,77],"parallelMidnight":[10 +``` \ No newline at end of file diff --git a/cardano-effectstream-local/packages/cardano/.gitignore b/cardano-effectstream-local/packages/cardano/.gitignore new file mode 100644 index 000000000..186bb303a --- /dev/null +++ b/cardano-effectstream-local/packages/cardano/.gitignore @@ -0,0 +1,4 @@ +data/ +dolos.socket +yaci-cli.history +logs/ diff --git a/cardano-effectstream-local/packages/cardano/config/application.properties b/cardano-effectstream-local/packages/cardano/config/application.properties new file mode 100644 index 000000000..b56712acc --- /dev/null +++ b/cardano-effectstream-local/packages/cardano/config/application.properties @@ -0,0 +1,34 @@ +spring.config.import=optional:file:./config/node.properties,optional:file:./config/download.properties + +#admin endpoint ports +server.port=10000 + +#Set the path to the directory where all yaci-cli related files are stored including cardano-node binary. +#Default is the user_home/.yaci-cli +#yaci.cli.home=/Users/satya/yacicli + +ogmios.enabled=false +kupo.enabled=false +yaci.store.enabled=false + +yaci.store.mode=native + +bp.create.enabled=true + +## Default ports +#ogmios.port=1337 +#kupo.port=1442 +#yaci.store.port=8080 +#socat.port=3333 +#prometheus.port=12798 + + +###################################################### +#To configure an external database for Yaci Store (Indexer), +# uncomment the following properties and provide the required values +#Only PostgreSQL is supported for now for external database +###################################################### + +#yaci.store.db.url=jdbc:postgresql://localhost:5433/yaci_indexer?currentSchema=dev +#yaci.store.db.username=user +#yaci.store.db.password= diff --git a/cardano-effectstream-local/packages/cardano/config/download.properties b/cardano-effectstream-local/packages/cardano/config/download.properties new file mode 100644 index 000000000..aa40db064 --- /dev/null +++ b/cardano-effectstream-local/packages/cardano/config/download.properties @@ -0,0 +1,12 @@ +#Please specify either the version or the full url for the following components +node.version=10.1.2 +ogmios.version=6.9.0 +kupo.version=2.9.0 +yaci.store.version=0.1.1-graalvm-preview1 +yaci.store.jar.version=0.1.0 + +#node.url= +#ogmios.url= +#kupo.url= +#yaci.store.url= +#yaci.store.jar.url= diff --git a/cardano-effectstream-local/packages/cardano/config/node.properties b/cardano-effectstream-local/packages/cardano/config/node.properties new file mode 100644 index 000000000..4fabd1a64 --- /dev/null +++ b/cardano-effectstream-local/packages/cardano/config/node.properties @@ -0,0 +1,99 @@ +#protocolMagic=42 +#maxKESEvolutions=60 +#securityParam=80 +#slotsPerKESPeriod=129600 +#updateQuorum=1 +#peerSharing=true + +## Shelley Genesis +#maxLovelaceSupply=45000000000000000 +#poolPledgeInfluence=0 +#decentralisationParam=0 +#eMax=18 +#keyDeposit=2000000 +#maxBlockBodySize=65536 +#maxBlockHeaderSize=1100 +#maxTxSize=16384 +#minFeeA=44 +#minFeeB=155381 +#minPoolCost=340000000 +#minUTxOValue=1000000 +#nOpt=100 +#poolDeposit=500000000 + +#protocolMajorVer=8 +#protocolMinorVer=0 +#monetaryExpansionRate=0.003f +#treasuryGrowthRate=0.20f + +##Default addresses +#initialAddresses[0].address=addr_test1qzx9hu8j4ah3auytk0mwcupd69hpc52t0cw39a65ndrah86djs784u92a3m5w475w3w35tyd6v3qumkze80j8a6h5tuqq5xe8y +#initialAddresses[0].balance=450000000 +#initialAddresses[0].staked=true +# +#initialAddresses[1].address=addr_test1qqwpl7h3g84mhr36wpetk904p7fchx2vst0z696lxk8ujsjyruqwmlsm344gfux3nsj6njyzj3ppvrqtt36cp9xyydzqzumz82 +#initialAddresses[1].balance=250000000 +#initialAddresses[1].staked=false + +##Alonzo +#collateralPercentage=150 +#prMem=5.77e-2 +#prSteps=7.21e-5 +#lovelacePerUTxOWord=34482 +#maxBlockExUnitsMem=62000000 +#maxBlockExUnitsSteps=20000000000 +#maxCollateralInputs=3 +#maxTxExUnitsMem=14000000 +#maxTxExUnitsSteps=10000000000 +#maxValueSize=5000 + +##Conway +#pvtcommitteeNormal=0.51f +#pvtCommitteeNoConfidence=0.51f +#pvtHardForkInitiation=0.51f +#pvtMotionNoConfidence=0.51f +#pvtPPSecurityGroup=0.51f + +#dvtMotionNoConfidence=0.51f +#dvtCommitteeNormal=0.51f +#dvtCommitteeNoConfidence=0.51f +#dvtUpdateToConstitution=0.51f +#dvtHardForkInitiation=0.51f +#dvtPPNetworkGroup=0.51f +#dvtPPEconomicGroup=0.51f +#dvtPPTechnicalGroup=0.51f +#dvtPPGovGroup=0.51f +#dvtTreasuryWithdrawal=0.51f + +#committeeMinSize=0 +#committeeMaxTermLength=200 +#govActionLifetime=10 +#govActionDeposit=1000000000 +#dRepDeposit=2000000 +#dRepActivity=20 + +#constitutionScript=7713eb6a46b67bfa1ca082f2b410b0a4e502237d03f7a0b7cbf1b025 +#constitutionUrl=https://devkit.yaci.xyz/constitution.json +#constitutionDataHash=f89cc2469ce31c3dfda2f3e0b56c5c8b4ee4f0e5f66c30a3f12a95298b01179e + +## CC Members +#ccMembers[0].hash=scriptHash-8fc13431159fdda66347a38c55105d50d77d67abc1c368b876d52ad1 +#ccMembers[0].term=340 + +######################################################################################################## +# Workaround for : https://github.com/bloxbean/yaci-devkit/issues/65 +# +# The following parameters are enabled for a V2 cost model-related issue where there are 10 extra elements if the devnet +# is started with the Conway era at epoch 0. The following parameters are enabled to configure the Conway era hard fork (HF) at epoch 1. +# The network will start in the Babbage era and then hard fork (HF) to the Conway era at epoch 1. + +# The shiftStartTimeBehind=true flag is enabled to shift the start time of the network to a time behind the current time by adjusting security parameter +# which changes the stability window. This is to speed up the process of reaching the Conway era. +# +# This should only be done in a development environment because if the stability window is larger than the epoch length, the reward/treasury calculations will be incorrect or ignored. +# Therefore, for a real multi-node network, you should start the network at the current time and allow it to reach the Conway era at epoch 1. +# So, the shiftStartTimeBehind flag should be "false" for non-development / multi-node networks. +# +######################################################################################################### +conwayHardForkAtEpoch=1 +shiftStartTimeBehind=true diff --git a/cardano-effectstream-local/packages/cardano/deno.json b/cardano-effectstream-local/packages/cardano/deno.json new file mode 100644 index 000000000..7f5361464 --- /dev/null +++ b/cardano-effectstream-local/packages/cardano/deno.json @@ -0,0 +1,20 @@ +{ + "name": "@minimal-cardano/cardano-contracts", + "version": "0.3.0", + "license": "MIT", + "exports": {}, + "tasks": { + "dolos:exists": "test -f ./dolos", + "devkit:start": "deno run -A --node-modules-dir npm:@bloxbean/yaci-devkit up", + "devkit:wait": "wait-on tcp:3001", + "dolos:fill-template": "deno run -A ./fill-template.ts", + // rm -rf is required because of this issue: https://github.com/txpipe/dolos/issues/398 + // TODO we need to provide newer versions of dolos binaries. + "dolos:start": "rm -rf ./data ./dolos.socket && ./dolos bootstrap relay && ./dolos daemon", + "dolos:wait": "wait-on tcp:50051" // utxorpc port + }, + "imports": { + "@txpipe/dolos": "npm:@txpipe/dolos@0.19.1", + "toml": "jsr:@std/toml" + } +} diff --git a/cardano-effectstream-local/packages/cardano/dolos.template.toml b/cardano-effectstream-local/packages/cardano/dolos.template.toml new file mode 100644 index 000000000..c84536bcd --- /dev/null +++ b/cardano-effectstream-local/packages/cardano/dolos.template.toml @@ -0,0 +1,56 @@ +[upstream] +peer_address = "localhost:3001" +network_magic = 42 +is_testnet = true + +[storage] +version = "v3" +path = "data" +max_wal_history = 10000 + +[genesis] +# note: filled programmatically + +[sync] +pull_batch_size = 100 +sync_limit = "NoLimit" + +[submit] + +[serve.grpc] +listen_address = "[::]:50051" +permissive_cors = true + +[serve.minibf] +listen_address = "[::]:3000" +permissive_cors = true + +[serve.trp] +listen_address = "[::]:8000" +max_optimize_rounds = 10 +permissive_cors = true + +[chain] +type = "cardano" +custom_utxos = [] + +[chain.track] +account_state = false +asset_state = false +pool_state = true +epoch_state = true +drep_state = true +proposal_logs = true +tx_logs = true +account_logs = true +pool_logs = true +epoch_logs = true +datum_state = false + +[logging] +max_level = "INFO" +include_tokio = false +include_pallas = false +include_grpc = false +include_trp = false +include_minibf = false diff --git a/cardano-effectstream-local/packages/cardano/dolos.toml b/cardano-effectstream-local/packages/cardano/dolos.toml new file mode 100644 index 000000000..a6ba45045 --- /dev/null +++ b/cardano-effectstream-local/packages/cardano/dolos.toml @@ -0,0 +1,60 @@ + +[upstream] +peer_address = "localhost:3001" +network_magic = 42 +is_testnet = true + +[storage] +version = "v3" +path = "data" +max_wal_history = 10000 + +[genesis] +byron_path = "./temp/byron-genesis.json" +shelley_path = "./temp/shelley-genesis.json" +alonzo_path = "./temp/alonzo-genesis2.json" +conway_path = "./temp/conway-genesis.json" + +[sync] +pull_batch_size = 100 +sync_limit = "NoLimit" + +[submit] + +[serve.grpc] +listen_address = "[::]:50051" +permissive_cors = true + +[serve.minibf] +listen_address = "[::]:3000" +permissive_cors = true + +[serve.trp] +listen_address = "[::]:8000" +max_optimize_rounds = 10 +permissive_cors = true + +[chain] +type = "cardano" +custom_utxos = [] + +[chain.track] +account_state = false +asset_state = false +pool_state = true +epoch_state = true +drep_state = true +proposal_logs = true +tx_logs = true +account_logs = true +pool_logs = true +epoch_logs = true +datum_state = false + +[logging] +max_level = "INFO" +include_tokio = false +include_pallas = false +include_grpc = false +include_trp = false +include_minibf = false diff --git a/cardano-effectstream-local/packages/cardano/fill-template.ts b/cardano-effectstream-local/packages/cardano/fill-template.ts new file mode 100644 index 000000000..2369f2c44 --- /dev/null +++ b/cardano-effectstream-local/packages/cardano/fill-template.ts @@ -0,0 +1,58 @@ +import { parse as parseToml, stringify as stringifyToml } from "toml"; +import fs from "node:fs/promises"; + +const TEMP_DIR = "./temp"; +const TEMPLATE_FILE = "./dolos.template.toml"; +const FINAL_TOML = "./dolos.toml"; +const BASE_URL = (hostname: string, port: number) => + `http://${hostname}:${port}/local-cluster/api/admin/devnet`; +const GENESIS_ENDPOINTS = { + byron: BASE_URL("localhost", 10000) + "/genesis/byron", + shelley: BASE_URL("localhost", 10000) + "/genesis/shelley", + alonzo: BASE_URL("localhost", 10000) + "/genesis/alonzo", + conway: BASE_URL("localhost", 10000) + "/genesis/conway", +}; + +async function fetchAndSaveGenesis( + type: keyof typeof GENESIS_ENDPOINTS, +): Promise { + const response = await fetch(GENESIS_ENDPOINTS[type]); + if (!response.ok) { + throw new Error(`Failed to fetch ${type} genesis: ${response.statusText}`); + } + + const json = await response.json(); + const filePath = `${TEMP_DIR}/${type}-genesis.json`; + + await fs.mkdir(TEMP_DIR, { recursive: true }); + await Deno.writeTextFile(filePath, JSON.stringify(json, null, 2)); + + return filePath; +} + +async function updateDolosConfig() { + // Fetch and save all genesis files + const paths = await Promise.all( + Object.keys(GENESIS_ENDPOINTS).map((type) => + fetchAndSaveGenesis(type as keyof typeof GENESIS_ENDPOINTS) + ), + ); + + // Read the template file + const templateContent = await Deno.readTextFile(TEMPLATE_FILE); + const config = parseToml(templateContent); + + // Update genesis paths + config.genesis = { + byron_path: paths[0], + shelley_path: paths[1], + alonzo_path: "./temp/alonzo-genesis2.json", // https://github.com/txpipe/pallas/issues/296#issuecomment-2547962797 + conway_path: paths[3], + }; + + // Write updated config back to file + await Deno.writeTextFile(FINAL_TOML, stringifyToml(config)); +} + +// Execute the update +await updateDolosConfig(); diff --git a/cardano-effectstream-local/packages/cardano/package.json b/cardano-effectstream-local/packages/cardano/package.json new file mode 100644 index 000000000..c0b0ee59f --- /dev/null +++ b/cardano-effectstream-local/packages/cardano/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "wait-on": "8.0.3" + } +} diff --git a/cardano-effectstream-local/packages/cardano/temp/.gitignore b/cardano-effectstream-local/packages/cardano/temp/.gitignore new file mode 100644 index 000000000..546a03934 --- /dev/null +++ b/cardano-effectstream-local/packages/cardano/temp/.gitignore @@ -0,0 +1,2 @@ +*.json +!./alonzo-genesis2.json \ No newline at end of file diff --git a/cardano-effectstream-local/packages/midnight/.gitignore b/cardano-effectstream-local/packages/midnight/.gitignore new file mode 100644 index 000000000..7426c9f70 --- /dev/null +++ b/cardano-effectstream-local/packages/midnight/.gitignore @@ -0,0 +1,3 @@ +*.undeployed.json +midnight-level-db* +compactc \ No newline at end of file diff --git a/cardano-effectstream-local/packages/midnight/contract-counter-deploy.ts b/cardano-effectstream-local/packages/midnight/contract-counter-deploy.ts new file mode 100644 index 000000000..58c893902 --- /dev/null +++ b/cardano-effectstream-local/packages/midnight/contract-counter-deploy.ts @@ -0,0 +1,30 @@ +import { deployMidnightContract, type DeployConfig } from "@effectstream/midnight-contracts/deploy"; +import { midnightNetworkConfig } from "@effectstream/midnight-contracts/midnight-env"; +import { + Counter, + type CounterPrivateState, + witnesses, +} from "./contract-counter/src/index.ts"; + +const config: DeployConfig = { + contractName: "contract-counter", + contractFileName: "contract-counter.json", + contractClass: Counter.Contract, + witnesses, + privateStateId: "counterPrivateState", + initialPrivateState: { privateCounter: 0 } as CounterPrivateState, + privateStateStoreName: "counter-private-state", +}; + + +console.log("Deploying contract with network config:", midnightNetworkConfig); + +deployMidnightContract(config, midnightNetworkConfig) + .then(() => { + console.log("Deployment successful"); + Deno.exit(0); + }) + .catch((e) => { + console.error("Unhandled error:", e); + Deno.exit(1); + }); diff --git a/cardano-effectstream-local/packages/midnight/contract-counter/.gitignore b/cardano-effectstream-local/packages/midnight/contract-counter/.gitignore new file mode 100644 index 000000000..4d50e3839 --- /dev/null +++ b/cardano-effectstream-local/packages/midnight/contract-counter/.gitignore @@ -0,0 +1,3 @@ +dist +src/managed +reports \ No newline at end of file diff --git a/cardano-effectstream-local/packages/midnight/contract-counter/README.md b/cardano-effectstream-local/packages/midnight/contract-counter/README.md new file mode 100644 index 000000000..70b9d1190 --- /dev/null +++ b/cardano-effectstream-local/packages/midnight/contract-counter/README.md @@ -0,0 +1,13 @@ +## Counter Example + +The Counter example demonstrates how to build, compile, and interact with a +simple smart contract on the Midnight blockchain using the Compact language. The +contract maintains a public counter on the ledger and provides a single +operation to increment its value. This example guides you through setting up the +development environment, compiling the Compact contract, building the TypeScript +interface, and running a command-line DApp to deploy and interact with the +contract on the Midnight Testnet. It is designed as an introductory project to +help developers get started with Midnight's privacy-focused smart contract +platform and +tooling [In detail: the counter contract](https://docs.midnight.network/develop/tutorial/building/contract-details), +[Counter DApp README](https://github.com/midnightntwrk/example-counter/blob/main/README.md). diff --git a/cardano-effectstream-local/packages/midnight/contract-counter/convert-js.ts b/cardano-effectstream-local/packages/midnight/contract-counter/convert-js.ts new file mode 100644 index 000000000..6382510a5 --- /dev/null +++ b/cardano-effectstream-local/packages/midnight/contract-counter/convert-js.ts @@ -0,0 +1,33 @@ +// Convert js files to ts files. +// +// This is a temporal solution for converting the .cjs file to .ts +// This is a partial conversion and might fail for some cases. +// The goal is doing the minimal conversion to make it a valid ts file. +// We expect the compiler to generate the .mjs or .ts eventually. +export async function convertJS(jsPath: string, tsPath: string) { + const jsFile = await Deno.readTextFile(jsPath); + const output = jsFile + // 1. Remove 'use strict'. + .replace(/^["']use strict["'];?/, "") + // 2. Replace const r = require('lib') with import * as r from 'lib'. + .replace( + /(const|let|var) (\w+) = require\(['"](.*)["']\);?/g, + "import * as $2 from '$3';", + ) + // replace exports.foo = foo; with export { foo }; + .replace(/exports\.(\w+) = (\w+);?/g, "export { $1 };"); + + await Deno.writeTextFile(tsPath, output); + console.log(`Converted ${jsPath} to ${tsPath}`); +} + +if (import.meta.main) { + const args = Deno.args; + if (args.length !== 2) { + console.error( + "Usage: deno run -A convert-js.ts ", + ); + Deno.exit(1); + } + await convertJS(args[0], args[1]); +} diff --git a/cardano-effectstream-local/packages/midnight/contract-counter/deno.json b/cardano-effectstream-local/packages/midnight/contract-counter/deno.json new file mode 100644 index 000000000..95fdd7614 --- /dev/null +++ b/cardano-effectstream-local/packages/midnight/contract-counter/deno.json @@ -0,0 +1,17 @@ +{ + "name": "@minimal-cardano/midnight-contract-counter-basic", + "exports": { + ".": "./src/_index.ts", + "./witnesses": "./src/witnesses.ts", + "./contract": "./src/managed/contract/index.js" + }, + "tasks": { + "compact": "compact compile +0.28.0 src/counter.compact src/managed/", + "contract:compile": "deno task compact" + }, + "imports": { + "vitest": "npm:vitest@^3.2.4", + "@midnight-ntwrk/compact-runtime": "npm:@midnight-ntwrk/compact-runtime@0.14.0", + "@midnight-ntwrk/compact-js": "npm:@midnight-ntwrk/compact-js@2.4.0" + } + } \ No newline at end of file diff --git a/cardano-effectstream-local/packages/midnight/contract-counter/package.json b/cardano-effectstream-local/packages/midnight/contract-counter/package.json new file mode 100644 index 000000000..08a964ae6 --- /dev/null +++ b/cardano-effectstream-local/packages/midnight/contract-counter/package.json @@ -0,0 +1,26 @@ +{ + "name": "@midnight-ntwrk/counter-contract", + "version": "0.3.0", + "license": "Apache-2.0", + "private": true, + "type": "module", + "main": "dist/index.js", + "module": "dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "require": "./dist/index.js", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "scripts": { + "compact": "compactc src/counter.compact src/managed/counter", + "test": "vitest run", + "test:compile": "npm run compact && vitest run", + "build": "rm -rf dist && tsc --project tsconfig.build.json && cp -Rf ./src/managed ./dist/managed && cp ./src/counter.compact ./dist", + "lint": "eslint src", + "typecheck": "tsc -p tsconfig.json --noEmit" + } +} diff --git a/cardano-effectstream-local/packages/midnight/contract-counter/src/counter.compact b/cardano-effectstream-local/packages/midnight/contract-counter/src/counter.compact new file mode 100644 index 000000000..daf99aa09 --- /dev/null +++ b/cardano-effectstream-local/packages/midnight/contract-counter/src/counter.compact @@ -0,0 +1,11 @@ +pragma language_version >= 0.18.0; + +import CompactStandardLibrary; + +// public state +export ledger round: Counter; + +// transition function changing public state +export circuit increment(): [] { + round.increment(1); +} diff --git a/cardano-effectstream-local/packages/midnight/contract-counter/src/index.ts b/cardano-effectstream-local/packages/midnight/contract-counter/src/index.ts new file mode 100644 index 000000000..d5acd964e --- /dev/null +++ b/cardano-effectstream-local/packages/midnight/contract-counter/src/index.ts @@ -0,0 +1,4 @@ +export * as Counter from "./managed/contract/index.js"; +export * from "./witnesses.ts"; +export type { CounterPrivateState } from "./witnesses.ts"; +export { witnesses } from "./witnesses.ts"; diff --git a/cardano-effectstream-local/packages/midnight/contract-counter/src/witnesses.ts b/cardano-effectstream-local/packages/midnight/contract-counter/src/witnesses.ts new file mode 100644 index 000000000..ce54fb72c --- /dev/null +++ b/cardano-effectstream-local/packages/midnight/contract-counter/src/witnesses.ts @@ -0,0 +1,5 @@ +export type CounterPrivateState = { + privateCounter: number; +}; + +export const witnesses = {}; diff --git a/cardano-effectstream-local/packages/midnight/contract-counter/tsconfig.build.json b/cardano-effectstream-local/packages/midnight/contract-counter/tsconfig.build.json new file mode 100644 index 000000000..f1132509e --- /dev/null +++ b/cardano-effectstream-local/packages/midnight/contract-counter/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["src/test/**/*.ts"], + "compilerOptions": {} +} diff --git a/cardano-effectstream-local/packages/midnight/contract-counter/tsconfig.json b/cardano-effectstream-local/packages/midnight/contract-counter/tsconfig.json new file mode 100644 index 000000000..98b9edf76 --- /dev/null +++ b/cardano-effectstream-local/packages/midnight/contract-counter/tsconfig.json @@ -0,0 +1,23 @@ +{ + "include": ["src/**/*.ts"], + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "declaration": true, + "lib": ["ESNext"], + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "node", + "allowJs": true, + "forceConsistentCasingInFileNames": true, + "allowImportingTsExtensions": true, + "noEmit": true, + "noImplicitAny": true, + "strict": true, + "isolatedModules": true, + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "skipLibCheck": true + } +} diff --git a/cardano-effectstream-local/packages/midnight/deno.json b/cardano-effectstream-local/packages/midnight/deno.json new file mode 100644 index 000000000..3cd1fc0e9 --- /dev/null +++ b/cardano-effectstream-local/packages/midnight/deno.json @@ -0,0 +1,43 @@ +{ + "name": "@minimal-cardano/midnight-contracts", + "version": "0.3.0", + "exports": { + "./counter": "./contract-counter/src/index.ts", + }, + "tasks": { + "midnight-node:start": "MIDNIGHT_STORAGE_PASSWORD=yourpasswordmypassword CFG_PRESET=dev deno run -A --unstable-detect-cjs @effectstream/npm-midnight-node --dev --rpc-port 9944 --state-pruning archive --blocks-pruning archive --public-addr /ip4/127.0.0.1 --unsafe-rpc-external", + "midnight-node:wait": "wait-on tcp:9944", + "midnight-indexer:start": "RUST_BACKTRACE=1 LEDGER_NETWORK_ID=\"Undeployed\" SUBSTRATE_NODE_WS_URL=\"ws://localhost:9944\" APP__INFRA__SECRET=$(openssl rand -hex 32 | tr 'a-f' 'A-F') FEATURES_WALLET_ENABLED=\"true\" APP__INFRA__NODE__URL=\"ws://localhost:9944\" deno run -A --unstable-detect-cjs @effectstream/npm-midnight-indexer --binary --clean", + "midnight-indexer:wait": "wait-on tcp:8088", + "midnight-proof-server:start": "RUST_BACKTRACE=full SUBSTRATE_NODE_WS_URL=\"ws://localhost:9944\" deno run -A --unstable-detect-cjs @effectstream/npm-midnight-proof-server", + "midnight-proof-server:wait": "wait-on tcp:6300", + "midnight-contract:clean": "rm -rf midnight-level-db contract-eip-20.undeployed.json contract-counter.undeployed.json", + "midnight-contract:deploy": "deno task contract-counter:deploy", + "contract-counter:deploy": "deno --unstable-detect-cjs -A contract-counter-deploy.ts", + "faucet": "deno run -A faucet.ts" + }, + "imports": { + "@std/path": "jsr:@std/path", + "@std/fs": "jsr:@std/fs", + "rxjs": "npm:rxjs@^7.8.2", + "ws": "npm:ws", + "@midnight-ntwrk/compact-runtime": "npm:@midnight-ntwrk/compact-runtime@0.14.0", + "@midnight-ntwrk/wallet-sdk-address-format": "npm:@midnight-ntwrk/wallet-sdk-address-format@3.0.0", + "@midnight-ntwrk/midnight-js-network-id": "npm:@midnight-ntwrk/midnight-js-network-id@3.0.0", + "@midnight-ntwrk/midnight-js-types": "npm:@midnight-ntwrk/midnight-js-types@3.0.0", + "@midnight-ntwrk/midnight-js-utils": "npm:@midnight-ntwrk/midnight-js-utils@3.0.0", + "@midnight-ntwrk/midnight-js-contracts": "npm:@midnight-ntwrk/midnight-js-contracts@3.0.0", + "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "npm:@midnight-ntwrk/midnight-js-indexer-public-data-provider@3.0.0", + "@midnight-ntwrk/midnight-js-http-client-proof-provider": "npm:@midnight-ntwrk/midnight-js-http-client-proof-provider@3.0.0", + "@midnight-ntwrk/midnight-js-node-zk-config-provider": "npm:@midnight-ntwrk/midnight-js-node-zk-config-provider@3.0.0", + "@midnight-ntwrk/midnight-js-level-private-state-provider": "npm:@midnight-ntwrk/midnight-js-level-private-state-provider@3.0.0", + "@midnight-ntwrk/wallet-sdk-abstractions": "npm:@midnight-ntwrk/wallet-sdk-abstractions@1.0.0", + "@midnight-ntwrk/wallet-sdk-facade": "npm:@midnight-ntwrk/wallet-sdk-facade@1.0.0", + "@midnight-ntwrk/wallet-sdk-hd": "npm:@midnight-ntwrk/wallet-sdk-hd@3.0.0", + "@midnight-ntwrk/wallet-sdk-shielded": "npm:@midnight-ntwrk/wallet-sdk-shielded@1.0.0", + "@midnight-ntwrk/wallet-sdk-shielded/v1": "npm:@midnight-ntwrk/wallet-sdk-shielded@1.0.0/v1", + "@midnight-ntwrk/wallet-sdk-dust-wallet": "npm:@midnight-ntwrk/wallet-sdk-dust-wallet@1.0.0", + "@midnight-ntwrk/wallet-sdk-unshielded-wallet": "npm:@midnight-ntwrk/wallet-sdk-unshielded-wallet@1.0.0", + "@midnight-ntwrk/ledger-v7": "npm:@midnight-ntwrk/ledger-v7@7.0.0" + } +} diff --git a/cardano-effectstream-local/packages/midnight/faucet.ts b/cardano-effectstream-local/packages/midnight/faucet.ts new file mode 100644 index 000000000..c29876522 --- /dev/null +++ b/cardano-effectstream-local/packages/midnight/faucet.ts @@ -0,0 +1,894 @@ +import { setNetworkId } from "@midnight-ntwrk/midnight-js-network-id"; +import { Buffer } from "node:buffer"; +import * as Rx from "rxjs"; +import { HDWallet, Roles } from "@midnight-ntwrk/wallet-sdk-hd"; +import { UnprovenTransactionRecipe, WalletFacade } from "@midnight-ntwrk/wallet-sdk-facade"; +import { ShieldedWallet } from "@midnight-ntwrk/wallet-sdk-shielded"; +import { DustWallet } from "@midnight-ntwrk/wallet-sdk-dust-wallet"; +import { + createKeystore, + InMemoryTransactionHistoryStorage, + PublicKey, + type UnshieldedKeystore, + UnshieldedWallet, +} from "@midnight-ntwrk/wallet-sdk-unshielded-wallet"; +import { + DustSecretKey, + LedgerParameters, + nativeToken, + shieldedToken, + UnprovenTransaction, + ZswapSecretKeys, +} from "@midnight-ntwrk/ledger-v7"; +import { NetworkId } from "@midnight-ntwrk/wallet-sdk-abstractions"; +import type { DefaultV1Configuration } from "@midnight-ntwrk/wallet-sdk-shielded/v1"; + +/** + * This script transfers 10.0 dust from the default midnight wallet to a given address. + * This works only on the local undeployed network. + * + * This is useful to pass dust to Lace wallets in the browser for testing purposes. + * + * Usage: + * MIDNIGHT_ADDRESS=mn_addr_undeployed1k7dst6qphntqmypwa4mhyltk794wx4lt07kherlc9y6clu5swssxqr9xe4z7txy8rscldhec7nmm47ujccf7syky0wz86jwahhkfd3mvq9wu8qx deno run -A faucet.ts + */ + +// ============================================================================ +// Constants +// ============================================================================ + +/** Transaction TTL duration in milliseconds (1 hour) */ +const TTL_DURATION_MS = 60 * 60 * 1000; + +/** Additional fee overhead for dust transactions (in smallest unit) */ +const DUST_FEE_OVERHEAD = 300_000_000_000_000n; + +/** Fee blocks margin for dust wallet (overridable via MIDNIGHT_DUST_FEE_BLOCKS_MARGIN) */ +const DUST_FEE_BLOCKS_MARGIN = 5; + +/** Wallet sync progress logging throttle interval */ +const WALLET_SYNC_THROTTLE_MS = 10_000; + +/** Wallet sync timeout (5 minutes) */ +const WALLET_SYNC_TIMEOUT_MS = 300_000; + +const GENESIS_MINT_WALLET_SEED = + "0000000000000000000000000000000000000000000000000000000000000001"; + +// ============================================================================ +// Types +// ============================================================================ + +interface Config { + readonly indexer: string; + readonly indexerWS: string; + readonly node: string; + readonly proofServer: string; +} + +const DEFAULT_NETWORK_URLS: Required = { + indexer: "http://127.0.0.1:8088/api/v3/graphql", + indexerWS: "ws://127.0.0.1:8088/api/v3/graphql/ws", + node: "http://127.0.0.1:9944", + proofServer: "http://127.0.0.1:6300", +}; + +export interface WalletResult { + wallet: WalletFacade; + zswapSecretKeys: ZswapSecretKeys; + walletZswapSecretKeys: ZswapSecretKeys; + dustSecretKey: DustSecretKey; + walletDustSecretKey: DustSecretKey; + dustAddress: string; + unshieldedAddress: string; + unshieldedKeystore: UnshieldedKeystore; +} + +// ============================================================================ +// Key Derivation +// ============================================================================ + +export type DerivationRole = + | typeof Roles.Zswap + | typeof Roles.Dust + | typeof Roles.NightExternal; + +export function deriveSeedForRole( + seed: string, + role: DerivationRole, +): Uint8Array { + const seedBuffer = Buffer.from(seed, "hex"); + const hdWalletResult = HDWallet.fromSeed(seedBuffer); + + if (hdWalletResult.type !== "seedOk") { + throw new Error(`Failed to create HD wallet: ${hdWalletResult.type}`); + } + + const derivationResult = hdWalletResult.hdWallet + .selectAccount(0) + .selectRole(role) + .deriveKeyAt(0); + + if (derivationResult.type === "keyOutOfBounds") { + throw new Error(`Key derivation out of bounds for role: ${role}`); + } + + return Buffer.from(derivationResult.key); +} + +// ============================================================================ +// Wallet Configuration +// ============================================================================ + +/** + * Create wallet configuration for the modular Midnight SDK + */ +export function createWalletConfiguration( + networkUrls: Required, + networkId: NetworkId.NetworkId, +): DefaultV1Configuration { + return { + indexerClientConnection: { + indexerHttpUrl: networkUrls.indexer, + indexerWsUrl: networkUrls.indexerWS, + }, + provingServerUrl: new URL(networkUrls.proofServer), + relayURL: new URL(networkUrls.node.replace("http", "ws")), + networkId: networkId, + }; +} + +export function buildShieldedWallet( + config: DefaultV1Configuration, + seed: Uint8Array, +): ReturnType["startWithShieldedSeed"]> { + const shieldedBuilder = ShieldedWallet(config); + return shieldedBuilder.startWithShieldedSeed(seed); +} + +export function buildDustWallet( + config: DefaultV1Configuration, + seed: Uint8Array, +): ReturnType["startWithSeed"]> { + const legacyLedgerParams = LedgerParameters.initialParameters(); + const resolvedFeeBlocksMargin = resolveDustFeeBlocksMargin(); + const resolvedFeeOverhead = resolveDustFeeOverhead(); + const dustConfig = { + ...config, + costParameters: { + additionalFeeOverhead: resolvedFeeOverhead, + feeBlocksMargin: resolvedFeeBlocksMargin, + }, + }; + const dustBuilder = DustWallet(dustConfig); + const dustParameters = legacyLedgerParams.dust; + + return dustBuilder.startWithSeed(seed, dustParameters); +} + +export function buildUnshieldedWallet( + networkUrls: Required, + seed: Uint8Array, + networkId: NetworkId.NetworkId, +): ReturnType["startWithPublicKey"]> { + const keystore = createKeystore(seed, networkId); + const publicKey = PublicKey.fromKeyStore(keystore); + + return UnshieldedWallet({ + networkId, + indexerClientConnection: { + indexerHttpUrl: networkUrls.indexer, + indexerWsUrl: networkUrls.indexerWS, + }, + txHistoryStorage: new InMemoryTransactionHistoryStorage(), + } as any).startWithPublicKey(publicKey); +} + +/** + * Build a complete wallet facade with shielded, unshielded, and dust wallets + */ +export async function buildWalletFacade( + networkUrls: Required, + seed: string, + networkId: NetworkId.NetworkId, +): Promise { + const shieldedSeed = deriveSeedForRole(seed, Roles.Zswap); + const dustSeed = deriveSeedForRole(seed, Roles.Dust); + const unshieldedSeed = deriveSeedForRole(seed, Roles.NightExternal); + + const walletConfig = createWalletConfiguration(networkUrls, networkId); + + const shieldedWallet = buildShieldedWallet(walletConfig, shieldedSeed); + const dustWallet = buildDustWallet(walletConfig, dustSeed); + const unshieldedWallet = buildUnshieldedWallet( + networkUrls, + unshieldedSeed, + networkId, + ); + + const unshieldedKeystore = createKeystore(unshieldedSeed, networkId); + const unshieldedAddress = unshieldedKeystore.getBech32Address().asString(); + + const wallet = new WalletFacade( + shieldedWallet as any, + unshieldedWallet as any, + dustWallet, + ); + + const zswapSecretKeys = ZswapSecretKeys.fromSeed(shieldedSeed); + const walletZswapSecretKeys = ZswapSecretKeys.fromSeed(shieldedSeed); + const dustSecretKey = DustSecretKey.fromSeed(dustSeed); + const walletDustSecretKey = DustSecretKey.fromSeed(dustSeed); + + await wallet.start(walletZswapSecretKeys, walletDustSecretKey); + + const dustState = await Rx.firstValueFrom(dustWallet.state) as any; + + return { + wallet, + zswapSecretKeys, + walletZswapSecretKeys, + dustSecretKey, + walletDustSecretKey, + dustAddress: dustState.dustAddress, + unshieldedAddress, + unshieldedKeystore, + }; +} + +export interface ShieldedWalletState { + address: { + coinPublicKeyString(): string; + encryptionPublicKeyString(): string; + }; + balances: Record; +} + +export function getInitialShieldedState( + shieldedWallet: any, +): Promise { + return Rx.firstValueFrom(shieldedWallet.state); +} + +/** + * Resolve sync timeout from env or default. + */ +export function resolveWalletSyncTimeoutMs(): number { + const envValue = Deno.env.get("MIDNIGHT_WALLET_SYNC_TIMEOUT_MS"); + if (!envValue) return WALLET_SYNC_TIMEOUT_MS; + const parsed = Number(envValue); + if (Number.isFinite(parsed) && parsed > 0) return parsed; + console.warn( + `Invalid MIDNIGHT_WALLET_SYNC_TIMEOUT_MS="${envValue}", using default ${WALLET_SYNC_TIMEOUT_MS}ms`, + ); + return WALLET_SYNC_TIMEOUT_MS; +} + +const resolveDustFeeBlocksMargin = (): number => { + const envValue = Deno.env.get("MIDNIGHT_DUST_FEE_BLOCKS_MARGIN"); + if (!envValue) return DUST_FEE_BLOCKS_MARGIN; + const parsed = Number(envValue); + if (Number.isFinite(parsed) && parsed >= 0) return Math.floor(parsed); + console.warn( + `Invalid MIDNIGHT_DUST_FEE_BLOCKS_MARGIN="${envValue}", using default ${DUST_FEE_BLOCKS_MARGIN}`, + ); + return DUST_FEE_BLOCKS_MARGIN; +}; + +const resolveDustFeeOverhead = (): bigint => { + const envValue = Deno.env.get("MIDNIGHT_DUST_FEE_OVERHEAD"); + if (!envValue) return DUST_FEE_OVERHEAD; + try { + return BigInt(envValue); + } catch (_error) { + console.warn( + `Invalid MIDNIGHT_DUST_FEE_OVERHEAD="${envValue}", using default ${DUST_FEE_OVERHEAD}`, + ); + return DUST_FEE_OVERHEAD; + } +}; + +const resolveNativeTokenId = (): string => { + const token = nativeToken() as unknown as { raw?: string }; + if (typeof token === "string") return token; + if (token && typeof token.raw === "string") return token.raw; + return String(token); +}; + +const sumUnshieldedBalances = ( + balances: Map | Record | undefined, +): bigint => { + if (!balances) return 0n; + if (balances instanceof Map) { + return Array.from(balances.values()).reduce( + (acc, v) => acc + (v ?? 0n), + 0n, + ); + } + return Object.values(balances).reduce((acc, v) => acc + (v ?? 0n), 0n); +}; + +const resolveUnshieldedTokenId = async ( + wallet: WalletFacade, +): Promise => { + const state = await Rx.firstValueFrom(wallet.state()); + const balances = (state as any).unshielded?.balances as + | Map + | Record + | undefined; + if (balances) { + const keys = balances instanceof Map + ? Array.from(balances.keys()) + : Object.keys(balances); + const preferred = resolveNativeTokenId(); + if (keys.includes(preferred)) return preferred; + if (keys.length > 0) return keys[0]; + } + return resolveNativeTokenId(); +}; + +/** + * Wait for wallet to be synced and funded + */ +export async function syncAndWaitForFunds( + wallet: WalletFacade, + options?: { timeoutMs?: number; waitNonZero?: boolean; logLabel?: string }, +): Promise< + { shieldedBalance: bigint; unshieldedBalance: bigint; dustBalance: bigint } +> { + const logPrefix = options?.logLabel ? `[${options.logLabel}] ` : ""; + console.info( + `${logPrefix}Waiting for wallet to sync and receive funds (shielded/dust)...`, + ); + + const syncTimeoutMs = options?.timeoutMs ?? resolveWalletSyncTimeoutMs(); + const waitNonZero = options?.waitNonZero ?? false; + let latestState: any = null; + const periodicLogger = setInterval(() => { + if (!latestState) return; + const shieldedSynced = + latestState.shielded.state.progress.isStrictlyComplete() || + (latestState.isSynced ?? false); + const dustSynced = latestState.dust.state.progress.isStrictlyComplete() || + (latestState.isSynced ?? false); + const unshieldedSynced = latestState.unshielded?.syncProgress?.synced ?? + (latestState.isSynced ?? false); + const shieldedBalances = latestState.shielded?.balances ?? {}; + const balanceKeys = Object.keys(shieldedBalances); + + const unshieldedBalanceLog = sumUnshieldedBalances( + latestState.unshielded?.balances, + ); + + console.info( + `${logPrefix}[wait] shielded=${shieldedSynced}, unshielded=${unshieldedSynced}, dust=${dustSynced} | shieldedKeys: [${ + balanceKeys.join(", ") + }] | unshieldedBalance: ${unshieldedBalanceLog}`, + ); + }, WALLET_SYNC_THROTTLE_MS); + + let state: any; + try { + state = await Rx.firstValueFrom( + wallet.state().pipe( + Rx.throttleTime(WALLET_SYNC_THROTTLE_MS), + Rx.tap((state: any) => { + latestState = state; + const isSynced = state.isSynced ?? false; + const shieldedSynced = + state.shielded.state.progress.isStrictlyComplete() || isSynced; + const dustSynced = state.dust.state.progress.isStrictlyComplete() || + isSynced; + const unshieldedSynced = state.unshielded?.syncProgress?.synced ?? + isSynced; + const tokenRaw = shieldedToken().raw; + const tokenTag = shieldedToken().tag; + const shieldedBalance = state.shielded.balances[tokenRaw] ?? 0n; + const keys = Object.keys(state.shielded.balances); + + const unshieldedBalanceLog = sumUnshieldedBalances( + state.unshielded?.balances, + ); + + console.info( + `${logPrefix}Wallet sync progress: shielded=${shieldedSynced}, unshielded=${unshieldedSynced}, dust=${dustSynced} (isSynced: ${isSynced})`, + ); + console.info( + `${logPrefix}Balance check: tokenRaw=${tokenRaw}, tokenTag=${tokenTag}, shieldedBal=${shieldedBalance}, unshieldedBal=${unshieldedBalanceLog}, availableKeys=[${ + keys.join(", ") + }]`, + ); + }), + Rx.filter((state: any) => { + const isSynced = state.isSynced ?? false; + const shieldedSynced = + state.shielded.state.progress.isStrictlyComplete() || isSynced; + const dustSynced = state.dust.state.progress.isStrictlyComplete() || + isSynced; + const unshieldedSynced = state.unshielded?.syncProgress?.synced ?? + isSynced; + + if (!shieldedSynced || !dustSynced || !unshieldedSynced) return false; + + if (waitNonZero) { + const shieldedBalance = + state.shielded.balances[shieldedToken().raw] ?? 0n; + + const unshieldedBalanceCheck = sumUnshieldedBalances( + state.unshielded?.balances, + ); + + if (shieldedBalance > 0n || unshieldedBalanceCheck > 0n) { + return true; + } + + return false; + } + + return true; + }), + Rx.tap(() => console.info(`${logPrefix}Wallet sync complete`)), + Rx.timeout({ + each: syncTimeoutMs, + with: () => + Rx.throwError( + () => new Error(`Wallet sync timeout after ${syncTimeoutMs}ms`), + ), + }), + ), + ); + } finally { + clearInterval(periodicLogger); + } + + const tokenObj = shieldedToken(); + const tokenId = tokenObj.raw; + + const shieldedBalance = (state as any).shielded.balances[tokenId] ?? 0n; + + // Handle unshielded balances + const unshieldedBalances = (state as any).unshielded?.balances as + | Map + | Record + | undefined; + + const unshieldedBalance = sumUnshieldedBalances(unshieldedBalances); + + let dustBalance = 0n; + try { + dustBalance = await waitForDustFunds(wallet, { + timeoutMs: syncTimeoutMs, + waitNonZero, + }); + } catch (_err) { + console.warn( + "Dust wallet did not report funds within timeout; continuing with dustBalance=0", + ); + } + + return { shieldedBalance, unshieldedBalance, dustBalance }; +} + +export async function waitForUnshieldedFunds( + wallet: WalletFacade, + options?: { timeoutMs?: number }, +): Promise { + console.info("Waiting for unshielded wallet funds..."); + const syncTimeoutMs = options?.timeoutMs ?? resolveWalletSyncTimeoutMs(); + + const balance = await Rx.firstValueFrom( + wallet.state().pipe( + Rx.throttleTime(WALLET_SYNC_THROTTLE_MS), + Rx.filter((state: any) => { + const isSynced = state.isSynced ?? false; + return state.unshielded?.syncProgress?.synced ?? isSynced; + }), + Rx.map((state: any) => sumUnshieldedBalances(state.unshielded?.balances)), + Rx.filter((value: bigint) => value > 0n), + Rx.timeout({ + each: syncTimeoutMs, + with: () => + Rx.throwError( + () => + new Error( + `Unshielded wallet sync timeout after ${syncTimeoutMs}ms`, + ), + ), + }), + ), + ); + + return balance; +} + +/** + * Wait for dust wallet sync and return dust balance if available. + */ +export async function waitForDustFunds( + wallet: WalletFacade, + optionsOrTimeout?: number | { timeoutMs?: number; waitNonZero?: boolean }, +): Promise { + console.info("Waiting for dust wallet to sync and receive funds..."); + + const options = typeof optionsOrTimeout === "number" + ? { timeoutMs: optionsOrTimeout } + : optionsOrTimeout; + + const syncTimeoutMs = options?.timeoutMs ?? resolveWalletSyncTimeoutMs(); + const waitNonZero = options?.waitNonZero ?? false; + + const dustWallet = (wallet as any).dust; + if (!dustWallet || !dustWallet.state) { + console.warn("Dust wallet state not available; skipping dust balance wait."); + return 0n; + } + + const dustBalance = (await Rx.firstValueFrom( + dustWallet.state.pipe( + Rx.throttleTime(WALLET_SYNC_THROTTLE_MS), + Rx.tap((state: any) => { + try { + const progress = (state as any).state?.progress; + const complete = progress?.isCompleteWithin?.(0n); + console.info( + `Dust wallet sync progress: complete=${complete ?? "unknown"}`, + ); + } catch (_err) { + } + }), + Rx.filter((state: any) => { + try { + const progress = (state as any).state?.progress; + return progress?.isCompleteWithin?.(0n) === true; + } catch (_err) { + return false; + } + }), + Rx.map((state: any) => { + try { + if (typeof state.walletBalance === "function") { + return state.walletBalance(new Date()); + } + const balances = state.balances; + if (balances) { + return Object.values(balances).reduce( + (acc: bigint, v) => acc + BigInt((v as any) ?? 0), + 0n, + ); + } + } catch (_err) { + } + return 0n; + }), + Rx.timeout({ + each: syncTimeoutMs, + with: () => + Rx.throwError( + () => + new Error(`Dust wallet sync timeout after ${syncTimeoutMs}ms`), + ), + }), + Rx.filter((balance: bigint) => !waitNonZero || balance > 0n), + Rx.tap((balance: bigint) => { + if (balance > 0n) console.info(`Dust wallet balance: ${balance}`); + }), + ), + )) as bigint; + + return dustBalance; +} + +/** + * Register unshielded Night UTXOs for dust generation. + */ +export async function registerNightForDust( + walletResult: WalletResult, +): Promise { + console.info( + "Checking for unshielded Night UTXOs to register for dust generation...", + ); + + const state = await Rx.firstValueFrom( + walletResult.wallet.state().pipe( + Rx.filter((s: any) => s.isSynced), + ), + ); + + const unregisteredNightUtxos = + (state as any).unshielded?.availableCoins?.filter( + (coin: any) => coin.meta.registeredForDustGeneration === false, + ) ?? []; + + if (unregisteredNightUtxos.length === 0) { + console.info("No unregistered unshielded Night UTXOs available."); + const dustBalance = await waitForDustFunds(walletResult.wallet, { + timeoutMs: 5000, + }); + return dustBalance > 0n; + } + + console.info( + `Found ${unregisteredNightUtxos.length} unregistered Night UTXOs. Registering for dust...`, + ); + + try { + const recipe: UnprovenTransactionRecipe = await walletResult.wallet + .registerNightUtxosForDustGeneration( + unregisteredNightUtxos, + walletResult.unshieldedKeystore.getPublicKey(), + (payload: Uint8Array) => + walletResult.unshieldedKeystore.signData(payload), + ); + + const signedRecipe: UnprovenTransaction = await walletResult.wallet.signUnprovenTransaction(recipe.transaction, (payload: Uint8Array) => + walletResult.unshieldedKeystore.signData(payload), + ); + + console.info("Submitting dust registration transaction..."); + const txId = await walletResult.wallet.submitTransaction( + await walletResult.wallet.finalizeTransaction(signedRecipe), + ); + console.info(`Dust registration submitted with tx id: ${txId}`); + + console.info("Waiting for dust to be generated..."); + await Rx.firstValueFrom( + walletResult.wallet.state().pipe( + Rx.throttleTime(WALLET_SYNC_THROTTLE_MS), + Rx.tap((s: any) => { + const dustBalance = s.dust?.walletBalance?.(new Date()) ?? 0n; + console.info(`Current dust balance: ${dustBalance}`); + }), + Rx.filter((s: any) => (s.dust?.walletBalance?.(new Date()) ?? 0n) > 0n), + Rx.timeout({ + each: resolveWalletSyncTimeoutMs(), + with: () => + Rx.throwError(() => + new Error("Timeout waiting for dust generation") + ), + }), + ), + ); + + console.info("Dust registration complete!"); + return true; + } catch (e) { + console.error( + `Failed to register Night UTXOs for dust: ${ + e instanceof Error ? e.message : String(e) + }`, + ); + return false; + } +} + +const resolveNetworkUrls = (): Required => ({ + indexer: Deno.env.get("MIDNIGHT_INDEXER_URL") || DEFAULT_NETWORK_URLS.indexer, + indexerWS: Deno.env.get("MIDNIGHT_INDEXER_WS_URL") || + DEFAULT_NETWORK_URLS.indexerWS, + node: Deno.env.get("MIDNIGHT_NODE_URL") || DEFAULT_NETWORK_URLS.node, + proofServer: Deno.env.get("MIDNIGHT_PROOF_SERVER_URL") || + DEFAULT_NETWORK_URLS.proofServer, +}); + +const resolveNetworkId = (): NetworkId.NetworkId => { + const networkIdRaw = Deno.env.get("MIDNIGHT_NETWORK_ID") || "undeployed"; + switch (networkIdRaw.toLowerCase()) { + case "undeployed": + return NetworkId.NetworkId.Undeployed; + case "testnet": + case "testnet-02": + return NetworkId.NetworkId.TestNet; + case "devnet": + case "qanet": + return NetworkId.NetworkId.DevNet; + case "preview": + console.info( + "Using preview network (addresses will have mn_addr_preview prefix)", + ); + return "preview" as NetworkId.NetworkId; + default: + console.warn( + `Unknown network ID "${networkIdRaw}", using as-is. Valid values: undeployed, testnet, devnet, preview`, + ); + return networkIdRaw as NetworkId.NetworkId; + } +}; + +const transfer = async ( + walletResult: WalletResult, + receiverAddress: string, + tokenId: string, + amount: bigint = 10_000_000_000n, +): Promise => { + console.log( + `Transferring ${amount} to ${receiverAddress} (tokenId=${tokenId})`, + ); + + try { + const ttl = new Date(Date.now() + TTL_DURATION_MS); + const recipe = await walletResult.wallet.transferTransaction( + [{ + type: "unshielded", + outputs: [{ + amount, + type: tokenId, + receiverAddress, + }], + }], + { + shieldedSecretKeys: walletResult.walletZswapSecretKeys, + dustSecretKey: walletResult.walletDustSecretKey, + }, + { ttl }, + ); + console.log("✓ Transfer transaction created"); + + const signSegment = (payload: Uint8Array) => + walletResult.unshieldedKeystore.signData(payload); + + const x: UnprovenTransaction = await walletResult.wallet.signUnprovenTransaction(recipe.transaction, (payload: Uint8Array) => + walletResult.unshieldedKeystore.signData(payload), + ); + + // let signedRecipe = recipe as typeof recipe; + // if (recipe.type === "TransactionToProve") { + // const signedTx = await walletResult.wallet.signTransaction( + // recipe.transaction, + // signSegment, + // ); + // signedRecipe = { ...recipe, transaction: signedTx }; + // } else if (recipe.type === "BalanceTransactionToProve") { + // const signedTx = await walletResult.wallet.signTransaction( + // recipe.transactionToProve, + // signSegment, + // ); + // signedRecipe = { ...recipe, transactionToProve: signedTx }; + // } else if (recipe.type === "NothingToProve") { + // const signedTx = await walletResult.wallet.signTransaction( + // recipe.transaction as any, + // signSegment, + // ); + // signedRecipe = { ...recipe, transaction: signedTx }; + // } + console.log("✓ Transfer transaction signed"); + + const finalizedTx = await walletResult.wallet.finalizeTransaction( + x, + ); + console.log("✓ Transfer transaction finalized"); + + const txId = await walletResult.wallet.submitTransaction(finalizedTx); + console.log({ txId }); + console.log( + `✅ Successfully transferred Night tokens to ${receiverAddress}`, + ); + return String(txId); + } catch (error) { + console.error("❌ Error during transfer:", error); + throw error; + } +}; + +export const faucet = async ( + receiverAddresses: string | string[], + seed: string = GENESIS_MINT_WALLET_SEED, +): Promise => { + let wallet: WalletFacade | null = null; + + const targets = Array.isArray(receiverAddresses) + ? receiverAddresses + : [receiverAddresses]; + const maxRetries = 5; + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + const networkUrls = resolveNetworkUrls(); + const networkId = resolveNetworkId(); + setNetworkId(networkId); + console.log( + `🔗 Building wallet with genesis seed for standalone mode... (attempt ${attempt})`, + ); + + const walletResult = await buildWalletFacade( + networkUrls, + seed, + networkId, + ); + wallet = walletResult.wallet; + console.log("✅ Wallet built successfully"); + + const initialState = await getInitialShieldedState(wallet.shielded); + console.log( + `Wallet address: ${initialState.address.coinPublicKeyString()}`, + ); + console.log(`Unshielded address: ${walletResult.unshieldedAddress}`); + console.log(`Dust address: ${walletResult.dustAddress}`); + + let { shieldedBalance, unshieldedBalance, dustBalance } = + await syncAndWaitForFunds(wallet, { + waitNonZero: false, + logLabel: "faucet", + }); + console.log(`Shielded balance: ${shieldedBalance}`); + console.log(`Unshielded balance: ${unshieldedBalance}`); + console.log(`Dust balance: ${dustBalance}`); + + if (unshieldedBalance === 0n) { + try { + unshieldedBalance = await waitForUnshieldedFunds(wallet, { + timeoutMs: resolveWalletSyncTimeoutMs(), + }); + console.log(`Unshielded balance (post-wait): ${unshieldedBalance}`); + } catch (error) { + throw new Error( + `Unshielded balance is 0; cannot transfer NIGHT. ${ + error instanceof Error ? error.message : String(error) + }`, + ); + } + } + + if (dustBalance === 0n && unshieldedBalance > 0n) { + const registered = await registerNightForDust(walletResult); + if (registered) { + try { + dustBalance = await waitForDustFunds(wallet, { + timeoutMs: resolveWalletSyncTimeoutMs(), + }); + console.log(`Dust balance (post-registration): ${dustBalance}`); + } catch (_error) { + console.warn("Dust still not available after registration; continuing"); + } + } + } + + let i = 1; + while (targets.length > 0) { + const receiverAddress = targets[0]; + const tokenId = await resolveUnshieldedTokenId(walletResult.wallet); + console.log(`Using unshielded token id: ${tokenId}`); + await transfer(walletResult, receiverAddress, tokenId, 1_000_000_000n); + targets.splice(targets.indexOf(receiverAddress), 1); + console.log( + `✅ Successfully transferred Night tokens to [${i} of ${targets.length}] (attempt ${attempt}) ${receiverAddress}`, + ); + i += 1; + } + console.log("✅ Successfully transferred Night tokens to all wallets"); + break; + } catch (error) { + console.error("❌ Error during join and mint process (0x2)", error); + console.error( + "❌ Error:", + error instanceof Error ? error.message : error, + ); + } + } + + if (wallet) { + try { + await wallet.stop(); + console.log("🧹 Wallet closed successfully"); + } catch (error) { + console.error("❌ Error closing wallet:", error); + } + } +}; + +if (import.meta.main) { + const midnightAddress = Deno.env.get("MIDNIGHT_ADDRESS"); + if (!midnightAddress) { + console.error("❌ MIDNIGHT_ADDRESS environment variable is not set"); + console.error( + "Example: MIDNIGHT_ADDRESS=mn_addr_undeployed1k7dst6qphntqmypwa4mhyltk794wx4lt07kherlc9y6clu5swssxqr9xe4z7txy8rscldhec7nmm47ujccf7syky0wz86jwahhkfd3mvq9wu8qx deno run -A faucet.ts", + ); + Deno.exit(1); + } + try { + await faucet(midnightAddress); + Deno.exit(0); + } catch (error) { + console.error("❌ Error during faucet process:", error); + Deno.exit(1); + } +} diff --git a/cardano-effectstream-local/packages/node/.gitignore b/cardano-effectstream-local/packages/node/.gitignore new file mode 100644 index 000000000..c5a0e57c9 --- /dev/null +++ b/cardano-effectstream-local/packages/node/.gitignore @@ -0,0 +1,2 @@ +tmux.conf +install.sh \ No newline at end of file diff --git a/cardano-effectstream-local/packages/node/deno.json b/cardano-effectstream-local/packages/node/deno.json new file mode 100644 index 000000000..aec747881 --- /dev/null +++ b/cardano-effectstream-local/packages/node/deno.json @@ -0,0 +1,46 @@ +{ + "name": "@minimal-cardano/node", + "version": "0.3.23", + "license": "MIT", + "exports": { + ".": "./main.ts" + }, + "tasks": { + // separate from node:start so we can type-check before starting other services + "check": "deno check --unstable-raw-imports main.ts", + // Open chrome://inspect to see the inspector + "node:start": "deno run -A --inspect --unstable-raw-imports main.ts", + "dev": "deno task -f @effectstream/tui clean && NODE_ENV=development deno run -A --check --unstable-raw-imports start-script.ts" + // "test": "deno run -A --unstable-raw-imports --check scripts/e2e.test.ts" + }, + "imports": { + "viem": "npm:viem@2.37.3", + "@sinclair/typebox": "npm:@sinclair/typebox@^0.34.30", + "effection": "npm:effection@^3.5.0", + // Why do i need midnight stuff? + "@midnight-ntwrk/dapp-connector-api": "npm:@midnight-ntwrk/dapp-connector-api@^3.0.0", + + + "@midnight-ntwrk/compact-runtime": "npm:@midnight-ntwrk/compact-runtime@^0.9.0", + "@midnight-ntwrk/ledger-v7": "npm:@midnight-ntwrk/ledger-v7@7.0.0", + "@midnight-ntwrk/midnight-js-network-id": "npm:@midnight-ntwrk/midnight-js-network-id@3.0.0", + "@midnight-ntwrk/midnight-js-types": "npm:@midnight-ntwrk/midnight-js-types@3.0.0", + "@midnight-ntwrk/midnight-js-utils": "npm:@midnight-ntwrk/midnight-js-utils@3.0.0", + "@midnight-ntwrk/midnight-js-contracts": "npm:@midnight-ntwrk/midnight-js-contracts@3.0.0", + "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "npm:@midnight-ntwrk/midnight-js-indexer-public-data-provider@3.0.0", + "@midnight-ntwrk/midnight-js-http-client-proof-provider": "npm:@midnight-ntwrk/midnight-js-http-client-proof-provider@3.0.0", + "@midnight-ntwrk/midnight-js-node-zk-config-provider": "npm:@midnight-ntwrk/midnight-js-node-zk-config-provider@3.0.0", + "@midnight-ntwrk/midnight-js-level-private-state-provider": "npm:@midnight-ntwrk/midnight-js-level-private-state-provider@3.0.0", + "@midnight-ntwrk/wallet-sdk-abstractions": "npm:@midnight-ntwrk/wallet-sdk-abstractions@1.0.0", + "@midnight-ntwrk/wallet-sdk-facade": "npm:@midnight-ntwrk/wallet-sdk-facade@1.0.0", + "@midnight-ntwrk/wallet-sdk-hd": "npm:@midnight-ntwrk/wallet-sdk-hd@3.0.0", + "@midnight-ntwrk/wallet-sdk-shielded": "npm:@midnight-ntwrk/wallet-sdk-shielded@1.0.0", + "@midnight-ntwrk/wallet-sdk-shielded/v1": "npm:@midnight-ntwrk/wallet-sdk-shielded@1.0.0/v1", + "@midnight-ntwrk/wallet-sdk-dust-wallet": "npm:@midnight-ntwrk/wallet-sdk-dust-wallet@1.0.0", + "@midnight-ntwrk/wallet-sdk-unshielded-wallet": "npm:@midnight-ntwrk/wallet-sdk-unshielded-wallet@1.0.0", + "@midnight-ntwrk/zswap": "npm:@midnight-ntwrk/zswap@^4.0.0" + }, + "compilerOptions": { + "strict": true + } +} \ No newline at end of file diff --git a/cardano-effectstream-local/packages/node/main.ts b/cardano-effectstream-local/packages/node/main.ts new file mode 100644 index 000000000..0fdf7be5b --- /dev/null +++ b/cardano-effectstream-local/packages/node/main.ts @@ -0,0 +1,189 @@ +import { + init, + start, + type StartConfigApiRouter, + type StartConfigGameStateTransitions, +} from "@effectstream/runtime"; +import { main, suspend } from "effection"; +import { + toSyncProtocolWithNetwork, + withEffectstreamStaticConfig, +} from "@effectstream/config"; +import { + ConfigBuilder, + ConfigNetworkType, + ConfigSyncProtocolType, +} from "@effectstream/config"; +import { Type } from "@sinclair/typebox"; +import type { GrammarDefinition } from "@effectstream/concise"; +import type { SyncStateUpdateStream } from "@effectstream/coroutine"; +import { PaimaSTM } from "@effectstream/sm"; +import type { BaseStfInput } from "@effectstream/sm"; +import { + midnightNetworkConfig, +} from "@effectstream/midnight-contracts/midnight-env"; +import { PrimitiveTypeMidnightGeneric, PrimitiveTypeUtxorpcGeneric } from "@effectstream/sm/builtin"; +import * as CounterContract from "@minimal-cardano/midnight-contract-counter-basic/contract"; +import { readMidnightContract } from "@effectstream/midnight-contracts/read-contract"; + +const grammar = { + my_action_name: [ + ["input", Type.String()], + ], +} as const satisfies GrammarDefinition; + +const response = await fetch("http://localhost:3000/blocks/latest"); +const yaciDevKitStartTime = new Date().getTime() - ((await response.json()).time * 1000); +console.log("yaciDevKitStartTime", yaciDevKitStartTime); + +const counterAddress = readMidnightContract( + "contract-counter", + { networkId: midnightNetworkConfig.id }, +).contractAddress; + +export const localhostConfig = new ConfigBuilder() + .setNamespace( + (builder) => builder.setSecurityNamespace("minimal-node"), + ) + .buildNetworks((builder) => + builder + .addNetwork({ + name: "ntp", + type: ConfigNetworkType.NTP, + startTime: new Date().getTime(), + blockTimeMS: 1000, + }) + .addNetwork({ + name: "yaci", + type: ConfigNetworkType.CARDANO, + nodeUrl: "http://127.0.0.1:10000", // yaci-devkit default URL + network: "yaci", + }) + .addNetwork({ + name: "midnight", + type: ConfigNetworkType.MIDNIGHT, + networkId: midnightNetworkConfig.id, + nodeUrl: midnightNetworkConfig.node, + }) + ) + .buildDeployments((builder) => builder).buildSyncProtocols((builder) => + builder + .addMain( + (networks) => networks.ntp, + (network, deployments) => ({ + name: "mainNtp", + type: ConfigSyncProtocolType.NTP_MAIN, + chainUri: "", + startBlockHeight: 1, + pollingInterval: 1000, + }), + ) + .addParallel( + (networks) => (networks as any).yaci, + (network, deployments) => ({ + name: "parallelUtxoRpc", + type: ConfigSyncProtocolType.CARDANO_UTXORPC_PARALLEL, + rpcUrl: "http://127.0.0.1:50051", // dolos utxorpc address + startSlot: 1, + delayMs: yaciDevKitStartTime || 0, + pollingInterval: 1000, + headers: { + 'x-rpc-key': 'dev' + } + }), + ) + .addParallel( + (networks) => (networks as any).midnight, + (network, deployments) => ({ + name: "parallelMidnight", + type: ConfigSyncProtocolType.MIDNIGHT_PARALLEL, + startBlockHeight: 1, + pollingInterval: 1000, + delayMs: 18000, + indexer: midnightNetworkConfig.indexer, + indexerWs: midnightNetworkConfig.indexerWS, + }), + ) + ) + .buildPrimitives((builder) => + builder + .addPrimitive( + (syncProtocols) => (syncProtocols as any).parallelMidnight, + (network, deployments, syncProtocol) => ({ + name: "MidnightContractState", + type: PrimitiveTypeMidnightGeneric, + startBlockHeight: 1, + contractAddress: counterAddress, + stateMachinePrefix: "midnightContractState", + contract: { ledger: CounterContract.ledger }, + networkId: midnightNetworkConfig.id, + }), + ) + .addPrimitive( + (syncProtocols) => (syncProtocols as any).parallelUtxoRpc, + (network, deployments, syncProtocol) => ({ + name: "UtxoRpcGeneric", + type: PrimitiveTypeUtxorpcGeneric, + startBlockHeight: 1, + stateMachinePrefix: "cardano-utxo-rpc-generic", + predicate: { + match: { + cardano: { + has_address: { + exact_address: "cD0ktC/NQ3j7hUmyY1iMF3lu2gFFPU+MCRxVFYw=" + } + } + } + }, + }), + ) + ) + .build(); + +const stm = new PaimaSTM(grammar); +stm.addStateTransition("my_action_name", function* (data) { + console.log("--------------------------------"); + console.log("State Transition Function"); + console.log("Input Data:", data.parsedInput); + console.log("--------------------------------"); + + return; +}); + +const gameStateTransitions: StartConfigGameStateTransitions = function* ( + _blockHeight: number, + input: BaseStfInput, +): SyncStateUpdateStream { + yield* stm.processInput(input); +}; + +export const apiRouter: StartConfigApiRouter = async function ( + server: any, // fastify.FastifyInstance, + dbConn: any, // Pool, +): Promise { + server.get("/fetch-primitive-accounting", async () => { + const result = await dbConn.query( + `SELECT * FROM effectstream.primitive_accounting`, + ); + return result.rows; + }); +}; + +main(function* () { + yield* init(); + console.log("Starting EffectStream Node"); + + yield* withEffectstreamStaticConfig(localhostConfig, function* () { + yield* start({ + appName: "minimal-client", + appVersion: "1.0.0", + syncInfo: toSyncProtocolWithNetwork(localhostConfig), + gameStateTransitions, + migrations: undefined, + apiRouter, + grammar, + }); + }); + + yield* suspend(); +}); diff --git a/cardano-effectstream-local/packages/node/mod.ts b/cardano-effectstream-local/packages/node/mod.ts new file mode 100644 index 000000000..e69de29bb diff --git a/cardano-effectstream-local/packages/node/start-script.ts b/cardano-effectstream-local/packages/node/start-script.ts new file mode 100644 index 000000000..2f8d120d5 --- /dev/null +++ b/cardano-effectstream-local/packages/node/start-script.ts @@ -0,0 +1,39 @@ +import { + OrchestratorConfig, + start, +} from "@effectstream/orchestrator"; +import { ComponentNames } from "@effectstream/log"; +import { Value } from "@sinclair/typebox/value"; +import { launchCardano } from "@effectstream/orchestrator/start-cardano"; +import { launchMidnight } from "@effectstream/orchestrator/start-midnight"; + +const config = Value.Parse(OrchestratorConfig, { + packageName: "@effectstream", + logs: "stdout", + processes: { + // Launch Dev DB & Collector + [ComponentNames.EFFECTSTREAM_PGLITE]: true, + [ComponentNames.COLLECTOR]: false, + [ComponentNames.TMUX]: false, + [ComponentNames.TUI]: false, + }, + + // Launch my processes + processesToLaunch: [ + ...launchCardano("@minimal-cardano/cardano-contracts"), + ...launchMidnight("@minimal-cardano/midnight-contracts"), + ], + // Launch the Batcher with our PaimaL2 Contract + // batcher: { + // batchIntervalMs: 100, + // paimaL2Address: contractAddressesEvmMain()["chain31337"][ + // "PaimaL2ContractModule#MyPaimaL2Contract" + // ], + // paimaSyncProtocolName: "parallelEvmRPC_fast", + // batcherPrivateKey:` + // "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", + // chainName: "hardhat", + // }, +}); + +await start(config); diff --git a/deno.json b/deno.json index c69c736a0..3272b0740 100644 --- a/deno.json +++ b/deno.json @@ -3,7 +3,8 @@ "workspace": [ "./packages/**/*", "./e2e/**/*", - "./docs/**/*" + "./docs/**/*", + "./cardano-effectstream-local/**/*" ], "nodeModulesDir": "auto", "tasks": { diff --git a/deno.lock b/deno.lock index b06b2962d..36eb124bc 100644 --- a/deno.lock +++ b/deno.lock @@ -76,6 +76,8 @@ "npm:@metamask/providers@^22.1.0": "22.1.1_webextension-polyfill@0.10.0", "npm:@midnight-ntwrk/compact-js@2.4.0": "2.4.0_effect@3.19.15", "npm:@midnight-ntwrk/compact-runtime@0.14.0": "0.14.0", + "npm:@midnight-ntwrk/compact-runtime@0.9": "0.9.0", + "npm:@midnight-ntwrk/dapp-connector-api@3": "3.0.0_rxjs@7.8.2", "npm:@midnight-ntwrk/dapp-connector-api@4.0.0": "4.0.0", "npm:@midnight-ntwrk/ledger-v7@7.0.0": "7.0.0", "npm:@midnight-ntwrk/ledger@4.0.0": "4.0.0", @@ -98,6 +100,7 @@ "npm:@midnight-ntwrk/wallet-sdk-shielded@1.0.0": "1.0.0_ws@8.19.0__bufferutil@4.1.0__utf-8-validate@5.0.10", "npm:@midnight-ntwrk/wallet-sdk-unshielded-wallet@1.0.0": "1.0.0_ws@8.18.1", "npm:@midnight-ntwrk/wallet@5.0.0": "5.0.0_rxjs@7.8.2_@midnight-ntwrk+zswap@4.0.0_ws@8.19.0__bufferutil@4.1.0__utf-8-validate@5.0.10", + "npm:@midnight-ntwrk/zswap@4": "4.0.0", "npm:@midnight-ntwrk/zswap@4.0.0": "4.0.0", "npm:@nomicfoundation/hardhat-errors@3.0.1": "3.0.1", "npm:@nomicfoundation/hardhat-ignition-viem@3.0.2": "3.0.2_@nomicfoundation+hardhat-ignition@3.0.7__@nomicfoundation+hardhat-verify@3.0.9___hardhat@3.0.4____zod@3.25.76___zod@3.25.76__hardhat@3.0.4___zod@3.25.76_@nomicfoundation+hardhat-verify@3.0.9__hardhat@3.0.4___zod@3.25.76__zod@3.25.76_@nomicfoundation+hardhat-viem@3.0.0__hardhat@3.0.4___zod@3.25.76__viem@2.23.10___typescript@5.6.3___ws@8.18.1__typescript@5.6.3_@nomicfoundation+ignition-core@3.0.2_hardhat@3.0.4__zod@3.25.76_viem@2.23.10__typescript@5.6.3__ws@8.18.1_typescript@5.6.3", @@ -131,6 +134,7 @@ "npm:@polkadot/util@^13.4.3": "13.5.9", "npm:@sinclair/typebox@*": "0.34.41", "npm:@sinclair/typebox@0.34.41": "0.34.41", + "npm:@sinclair/typebox@~0.34.30": "0.34.41", "npm:@subsquid/ss58-codec@^1.2.3": "1.2.3", "npm:@txpipe/dolos@0.19.1": "0.19.1", "npm:@types/react@18.3.1": "18.3.1", @@ -3829,7 +3833,7 @@ "integrity": "sha512-bdf8yUeVyjndHTLzUIhuvHuFTIhHhL3Y9NJ7uTIdVfhN0wsgHECB+Bedh+5fv4KRjEfPiIINPgddhpVZ8Y0kaA==", "dependencies": [ "@effect/platform@0.94.2_effect@3.19.15", - "@midnight-ntwrk/compact-runtime", + "@midnight-ntwrk/compact-runtime@0.14.0", "@midnight-ntwrk/ledger-v7", "@midnight-ntwrk/platform-js", "effect", @@ -3844,6 +3848,22 @@ "object-inspect" ] }, + "@midnight-ntwrk/compact-runtime@0.9.0": { + "integrity": "sha512-Dsms+SWRWlaTIay4SO7ITxTJvo8lgEKkMss+dQnIZDnEoCEO5lHTSj4TckBAvyYSWm4KElN3tir/MVm5hHgfDA==", + "dependencies": [ + "@midnight-ntwrk/onchain-runtime", + "@types/object-inspect", + "object-inspect" + ] + }, + "@midnight-ntwrk/dapp-connector-api@3.0.0_rxjs@7.8.2": { + "integrity": "sha512-A/AhsNjibrOSaUiPhQhxANeW9178uYnzeBFBf30YCvyvWg0YWCbhQfc7dRpiL7iBoX3Sxt9mtf+Svx4ZygLajg==", + "dependencies": [ + "@midnight-ntwrk/wallet-api", + "@midnight-ntwrk/zswap", + "ts-custom-error" + ] + }, "@midnight-ntwrk/dapp-connector-api@4.0.0": { "integrity": "sha512-XiAesY32uPPDYOk6MXquO1lDsUhe7J/CMbMOIfEgjwDgXfkLvvKFMfNz672O1aSJYkqm1I65o2XjPXNnwloQtA==" }, @@ -3938,6 +3958,9 @@ "@midnight-ntwrk/onchain-runtime-v2@2.0.0": { "integrity": "sha512-9LfU3xJDI4aWcElE4ZxsGLKKAjj/7+O6nepvqMngojc+YLmlpR29ju4C+/isDbBk/4m/9m9fq89d8qIvX8Y4/Q==" }, + "@midnight-ntwrk/onchain-runtime@0.3.0": { + "integrity": "sha512-vZWPoz7MhXO5UgWZET50g7APOcT5dbAfADDcrSreeOagV8lj7JJzlnYIIhrcUaMOv+NHWxPaUcJUYUkmu9LoTA==" + }, "@midnight-ntwrk/platform-js@2.2.0_effect@3.19.15": { "integrity": "sha512-HWwS7bWnkRvufHowxutk4QA+nlLK0UVjxFCIfCkxVKLEO5UzgrfIUx2mY+P6BFnzGJZ0R6tZDTyJ2jh1Go258w==", "dependencies": [ @@ -20471,6 +20494,74 @@ }, "workspace": { "members": { + "cardano-effectstream-local/packages/cardano": { + "dependencies": [ + "jsr:@std/toml@*", + "npm:@txpipe/dolos@0.19.1" + ], + "packageJson": { + "dependencies": [ + "npm:wait-on@8.0.3" + ] + } + }, + "cardano-effectstream-local/packages/midnight": { + "dependencies": [ + "jsr:@std/fs@*", + "jsr:@std/path@*", + "npm:@midnight-ntwrk/compact-runtime@0.14.0", + "npm:@midnight-ntwrk/ledger-v7@7.0.0", + "npm:@midnight-ntwrk/midnight-js-contracts@3.0.0", + "npm:@midnight-ntwrk/midnight-js-http-client-proof-provider@3.0.0", + "npm:@midnight-ntwrk/midnight-js-indexer-public-data-provider@3.0.0", + "npm:@midnight-ntwrk/midnight-js-level-private-state-provider@3.0.0", + "npm:@midnight-ntwrk/midnight-js-network-id@3.0.0", + "npm:@midnight-ntwrk/midnight-js-node-zk-config-provider@3.0.0", + "npm:@midnight-ntwrk/midnight-js-types@3.0.0", + "npm:@midnight-ntwrk/midnight-js-utils@3.0.0", + "npm:@midnight-ntwrk/wallet-sdk-abstractions@1.0.0", + "npm:@midnight-ntwrk/wallet-sdk-address-format@3.0.0", + "npm:@midnight-ntwrk/wallet-sdk-dust-wallet@1.0.0", + "npm:@midnight-ntwrk/wallet-sdk-facade@1.0.0", + "npm:@midnight-ntwrk/wallet-sdk-hd@3.0.0", + "npm:@midnight-ntwrk/wallet-sdk-shielded@1.0.0", + "npm:@midnight-ntwrk/wallet-sdk-unshielded-wallet@1.0.0", + "npm:rxjs@^7.8.2", + "npm:ws@*" + ] + }, + "cardano-effectstream-local/packages/midnight/contract-counter": { + "dependencies": [ + "npm:@midnight-ntwrk/compact-js@2.4.0", + "npm:@midnight-ntwrk/compact-runtime@0.14.0", + "npm:vitest@^3.2.4" + ] + }, + "cardano-effectstream-local/packages/node": { + "dependencies": [ + "npm:@midnight-ntwrk/compact-runtime@0.9", + "npm:@midnight-ntwrk/dapp-connector-api@3", + "npm:@midnight-ntwrk/ledger-v7@7.0.0", + "npm:@midnight-ntwrk/midnight-js-contracts@3.0.0", + "npm:@midnight-ntwrk/midnight-js-http-client-proof-provider@3.0.0", + "npm:@midnight-ntwrk/midnight-js-indexer-public-data-provider@3.0.0", + "npm:@midnight-ntwrk/midnight-js-level-private-state-provider@3.0.0", + "npm:@midnight-ntwrk/midnight-js-network-id@3.0.0", + "npm:@midnight-ntwrk/midnight-js-node-zk-config-provider@3.0.0", + "npm:@midnight-ntwrk/midnight-js-types@3.0.0", + "npm:@midnight-ntwrk/midnight-js-utils@3.0.0", + "npm:@midnight-ntwrk/wallet-sdk-abstractions@1.0.0", + "npm:@midnight-ntwrk/wallet-sdk-dust-wallet@1.0.0", + "npm:@midnight-ntwrk/wallet-sdk-facade@1.0.0", + "npm:@midnight-ntwrk/wallet-sdk-hd@3.0.0", + "npm:@midnight-ntwrk/wallet-sdk-shielded@1.0.0", + "npm:@midnight-ntwrk/wallet-sdk-unshielded-wallet@1.0.0", + "npm:@midnight-ntwrk/zswap@4", + "npm:@sinclair/typebox@~0.34.30", + "npm:effection@^3.5.0", + "npm:viem@2.37.3" + ] + }, "docs/site": { "packageJson": { "dependencies": [ @@ -21058,7 +21149,6 @@ "npm:@coderspirit/nominal@^4.1.1", "npm:@foxglove/crc@^1.0.1", "npm:@metamask/providers@^22.1.0", - "npm:@midnight-ntwrk/dapp-connector-api@4.0.0", "npm:@perawallet/connect@^1.4.2", "npm:@polkadot/extension-dapp@~0.58.10", "npm:@polkadot/extension-inject@~0.58.10", @@ -21082,7 +21172,6 @@ "npm:mina-signer@^3.0.7", "npm:mqtt-pattern@^2.1.0", "npm:mqtt@^5.14.0", - "npm:semver@^7.7.3", "npm:viem@2.37.3", "npm:web3-utils@^4.3.3", "npm:web3@^4.16.0"