Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.6.1",
"version": "0.7.1",
"workspace": [
"./packages/**/*",
"./e2e/**/*",
Expand Down
169 changes: 110 additions & 59 deletions deno.lock

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions e2e/client/node/e2e-tests/e2e.midnight.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,16 @@ import { assertIsContractAddress } from "@midnight-ntwrk/midnight-js-utils";
import { blockWatcher } from "@e2e/engine";
import { setNetworkId } from "@midnight-ntwrk/midnight-js-network-id";
import type { Client } from "pg";
import { readMidnightContract } from "@effectstream/midnight-contracts/read-contract";
import {
readMidnightContract,
buildWalletFacade,
getInitialShieldedState,
syncAndWaitForFunds,
type NetworkUrls as MidnightNetworkUrls,
type WalletResult,
} from "@effectstream/midnight-contracts/wallet-info";
import {
midnightNetworkConfig,
} from "@effectstream/midnight-contracts/midnight-env";
type NetworkUrls,
} from "@effectstream/midnight-contracts";
import {
type FinalizedTransaction,
Transaction as LedgerTransaction,
Expand Down Expand Up @@ -219,11 +218,12 @@ const buildWalletAndWaitForFunds = async (
{ indexer, indexerWS, node, proofServer }: Config,
seed: string,
): Promise<WalletResult> => {
const networkUrls: MidnightNetworkUrls = {
const networkUrls: Required<NetworkUrls> = {
indexer,
indexerWS,
node,
proofServer,
id: midnightNetworkConfig.id,
};

const walletResult = await buildWalletFacade(
Expand Down Expand Up @@ -549,6 +549,7 @@ async function testDelegatedBalancing(
// Build Party A wallet
// We don't need funds for Party A since they are delegating payment!
const networkUrls = {
id: midnightNetworkConfig.id,
indexer: config.indexer,
indexerWS: config.indexerWS,
node: config.node,
Expand Down
6 changes: 4 additions & 2 deletions packages/batcher/adapters/midnight-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,13 @@ import {
buildWalletFacade,
getInitialDustState,
getInitialShieldedState,
type NetworkUrls,
registerNightForDust,
syncAndWaitForFunds,
waitForDustFunds,
type NetworkUrls as MidnightNetworkUrls,
type WalletResult,
} from "@effectstream/midnight-contracts/wallet-info";
} from "@effectstream/midnight-contracts";
import type { NetworkId as WalletNetworkId } from "@midnight-ntwrk/wallet-sdk-abstractions";
import { CompiledContract } from "@midnight-ntwrk/compact-js";

Expand Down Expand Up @@ -167,7 +168,8 @@ export class MidnightAdapter<TContract> implements BlockchainAdapter<MidnightBat
} else {
console.log("🔗 Building Midnight wallet (modular SDK)...");

const networkUrls: MidnightNetworkUrls = {
const networkUrls: Required<NetworkUrls> = {
id: this.walletNetworkId,
indexer: this.config.indexer,
indexerWS: this.config.indexerWS,
node: this.config.node,
Expand Down
7 changes: 4 additions & 3 deletions packages/batcher/adapters/midnight-balancing-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import {
syncAndWaitForFunds,
type WalletResult,
waitForDustFunds,
type NetworkUrls as MidnightNetworkUrls,
} from "@effectstream/midnight-contracts/wallet-info";
type NetworkUrls,
} from "@effectstream/midnight-contracts";
import { setNetworkId } from "@midnight-ntwrk/midnight-js-network-id";
import { indexerPublicDataProvider } from "@midnight-ntwrk/midnight-js-indexer-public-data-provider";
import type { NetworkId as WalletNetworkId } from "@midnight-ntwrk/wallet-sdk-abstractions";
Expand Down Expand Up @@ -154,7 +154,8 @@ export class MidnightBalancingAdapter implements BlockchainAdapter<DelegatedBatc
} else {
console.log("🔗 Building Midnight Balancing Adapter wallet...");

const networkUrls: MidnightNetworkUrls = {
const networkUrls: Required<NetworkUrls> = {
id: this.walletNetworkId,
indexer: this.config.indexer,
indexerWS: this.config.indexerWS,
node: this.config.node,
Expand Down
2 changes: 2 additions & 0 deletions packages/chains/midnight-contracts/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"version": "0.3.0",
"license": "MIT",
"exports": {
".": "./src/mod.ts",
"./types": "./src/types.ts",
"./read-contract": "./src/read-contract.ts",
"./deploy": "./src/deploy.ts",
"./wallet-info": "./src/get-wallet-info.ts",
Expand Down
100 changes: 100 additions & 0 deletions packages/chains/midnight-contracts/src/build-wallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import * as log from "@std/log";
import { Buffer } from "node:buffer";

import type { NetworkId } from "@midnight-ntwrk/wallet-sdk-abstractions";
import { shieldedToken } from "@midnight-ntwrk/ledger-v7";
import type { WalletFacade } from "@midnight-ntwrk/wallet-sdk-facade";

import {
buildWalletFacade,
getInitialShieldedState,
resolveWalletSyncTimeoutMs,
syncAndWaitForFunds,
safeStringifyProgress
} from "./get-wallet-info.ts";

import type { WalletResult, NetworkUrls } from "./types.ts";
import type { InitialOwner } from "./types.ts";

// ============================================================================
// Wallet Facade
// ============================================================================

/**
* Build wallet and wait for funds
*/
export async function buildWalletAndWaitForFunds(
networkUrls: Required<NetworkUrls>,
seed: string,
networkId: NetworkId.NetworkId
): Promise<WalletResult> {
log.info("Building wallet using modular SDK");
const result = await buildWalletFacade(networkUrls, seed, networkId);

const initialState = await getInitialShieldedState(result.wallet.shielded);
const address = initialState.address.coinPublicKeyString();
log.info(`Wallet seed: ${seed}`);
log.info(`Wallet address: ${address}`);
log.info(`Dust address: ${result.dustAddress}`);

let balance = initialState.balances[shieldedToken().tag] ?? 0n;
console.log("initialState", safeStringifyProgress(initialState));
const syncTimeoutMs = resolveWalletSyncTimeoutMs();
if (balance === 0n) {
const skipWait =
Deno.env.get("MIDNIGHT_SKIP_WAIT_FOR_FUNDS")?.toLowerCase() === "true";
log.info("Wallet shielded balance: 0");
log.info(
`Waiting to receive tokens... (timeout ${syncTimeoutMs}ms${skipWait ? ", skip on timeout enabled" : ""})`
);
try {
const { shieldedBalance, unshieldedBalance } = await syncAndWaitForFunds(
result.wallet
);
balance = shieldedBalance;
if (unshieldedBalance > 0n) {
log.info(`Unshielded balance available: ${unshieldedBalance}`);
}
} catch (e) {
if (skipWait) {
log.warn(
`Skipping wait for shielded funds after timeout: ${(e as Error).message}`
);
} else {
throw e;
}
}
}
log.info(`Wallet balance: ${balance}`);

// Dust syncing is handled by syncAndWaitForFunds above
return result;
}



// ============================================================================
// Contract Deployment Helpers
// ============================================================================

/**
* Extract initial owner from wallet for contracts that need it (e.g., EIP-20)
*/
export async function extractInitialOwnerFromWallet(
wallet: WalletFacade
): Promise<InitialOwner> {
const initialState = await getInitialShieldedState(wallet.shielded);
const coinPubHex = initialState.address.coinPublicKeyString();
const encPubHex = initialState.address.encryptionPublicKeyString();
log.info(`Extracting initial owner from wallet keys (hex): coin=${coinPubHex}`);
log.info(`Encryption key (hex): ${encPubHex}`);

const coinBytes = Buffer.from(coinPubHex, "hex");
const encBytes = Buffer.from(encPubHex, "hex");

return {
is_left: true,
left: { bytes: coinBytes },
right: { bytes: encBytes.subarray(0, 32) },
};
}
20 changes: 20 additions & 0 deletions packages/chains/midnight-contracts/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// ============================================================================
// Constants
// ============================================================================

export const CONSTANTS = {
/** Transaction TTL duration in milliseconds (1 hour) */
TTL_DURATION_MS: 60 * 60 * 1000,

/** Wallet sync progress logging throttle interval */
WALLET_SYNC_THROTTLE_MS: 10_000,

/** Wallet sync timeout (5 minutes) */
WALLET_SYNC_TIMEOUT_MS: 300_000,

/** Additional fee overhead for dust transactions (in smallest unit) */
DUST_FEE_OVERHEAD: 300_000_000_000_000n,

/** Fee blocks margin for dust wallet */
DUST_FEE_BLOCKS_MARGIN: 5,
} as const;
Loading