@effectstream/midnight-contracts 0.104.1 → 0.200.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/providers.ts CHANGED
@@ -3,11 +3,15 @@
3
3
  // ============================================================================
4
4
 
5
5
  import { Buffer } from "node:buffer";
6
- import type { ZswapSecretKeys, DustSecretKey, CoinPublicKey, EncPublicKey, FinalizedTransaction, TransactionId } from "@midnight-ntwrk/ledger-v8";
6
+ import * as path from "node:path";
7
+ import type { ZswapSecretKeys, DustSecretKey, CoinPublicKey, EncPublicKey, FinalizedTransaction, TransactionId } from "@midnightntwrk/ledger-v9";
7
8
  import { httpClientProofProvider } from "@midnight-ntwrk/midnight-js-http-client-proof-provider";
8
9
  import { indexerPublicDataProvider } from "@midnight-ntwrk/midnight-js-indexer-public-data-provider";
9
10
  import { levelPrivateStateProvider } from "@midnight-ntwrk/midnight-js-level-private-state-provider";
10
- import { NodeZkConfigProvider } from "@midnight-ntwrk/midnight-js-node-zk-config-provider";
11
+ import {
12
+ NodeZkConfigProvider,
13
+ nodeZkConfigRegistry,
14
+ } from "@midnight-ntwrk/midnight-js-node-zk-config-provider";
11
15
  import type { WalletProvider, MidnightProvider, MidnightProviders, UnboundTransaction } from "@midnight-ntwrk/midnight-js-types";
12
16
  import type { WalletFacade } from "@midnightntwrk/wallet-sdk-facade";
13
17
  import type { NetworkUrls } from "./types.ts";
@@ -27,7 +31,7 @@ function createTtl(): Date {
27
31
  * Create wallet and midnight provider adapter for WalletFacade
28
32
  *
29
33
  * Implements the WalletProvider and MidnightProvider interfaces
30
- * as defined in @midnight-ntwrk/midnight-js-types v3.x
34
+ * as defined in @midnight-ntwrk/midnight-js-types v5.
31
35
  */
32
36
  function createWalletAndMidnightProvider(
33
37
  wallet: WalletFacade,
@@ -51,13 +55,15 @@ function createWalletAndMidnightProvider(
51
55
  ttl?: Date
52
56
  ): Promise<FinalizedTransaction> {
53
57
  console.log("✅ Balancing transaction", tx);
54
- const bound = tx.bind();
55
- const finalizedTransactionRecipe = await wallet.balanceFinalizedTransaction(bound, {
58
+ const unboundTransactionRecipe = await wallet.balanceUnboundTransaction(tx, {
56
59
  shieldedSecretKeys: zswapSecretKeys,
57
60
  dustSecretKey: dustSecretKey,
58
61
  }, { ttl: ttl ?? createTtl() } );
59
- const x = await wallet.signRecipe(finalizedTransactionRecipe, (payload) => unshieldedKeystore.signData(payload));
60
- return wallet.finalizeRecipe(x);
62
+ const signedRecipe = await wallet.signRecipe(
63
+ unboundTransactionRecipe,
64
+ (payload) => unshieldedKeystore.signDataAsync(payload),
65
+ );
66
+ return wallet.finalizeRecipe(signedRecipe);
61
67
  },
62
68
  submitTx(tx: FinalizedTransaction): Promise<TransactionId> {
63
69
  console.log("✅ Submitting transaction", tx);
@@ -69,7 +75,7 @@ function createWalletAndMidnightProvider(
69
75
  /**
70
76
  * Configure all providers needed for contract deployment
71
77
  */
72
- export function configureMidnightNodeProviders(
78
+ export async function configureMidnightNodeProviders(
73
79
  wallet: WalletFacade,
74
80
  zswapSecretKeys: ZswapSecretKeys,
75
81
  walletZswapSecretKeys: ZswapSecretKeys,
@@ -79,7 +85,7 @@ function createWalletAndMidnightProvider(
79
85
  privateStateStoreName: string,
80
86
  zkConfigPath: string,
81
87
  unshieldedKeystore: UnshieldedKeystore
82
- ): MidnightProviders {
88
+ ): Promise<MidnightProviders> {
83
89
  const signingKeyStoreName = `${privateStateStoreName}-signing-keys`;
84
90
  const walletAndMidnightProvider = createWalletAndMidnightProvider(
85
91
  wallet,
@@ -89,7 +95,13 @@ function createWalletAndMidnightProvider(
89
95
  walletDustSecretKey,
90
96
  unshieldedKeystore
91
97
  );
92
- const zkConfigProvider = new NodeZkConfigProvider(zkConfigPath);
98
+ const zkConfigProvider = new NodeZkConfigProvider(zkConfigPath, {
99
+ verify: "require",
100
+ });
101
+ // Midnight.js 5 resolves proof artifacts through a registry so transactions
102
+ // with cross-contract calls can select bundles by verifier key. Search from
103
+ // the managed-artifact parent to include sibling compiled contracts.
104
+ const zkConfigRegistry = await nodeZkConfigRegistry(path.dirname(zkConfigPath));
93
105
  return {
94
106
  // For deployment, we use full private state config because we may need to verify
95
107
  // the deployed contract state. For batcher/transaction submission use cases,
@@ -109,8 +121,11 @@ function createWalletAndMidnightProvider(
109
121
  networkUrls.indexerWS
110
122
  ),
111
123
  zkConfigProvider,
112
- proofProvider: httpClientProofProvider(networkUrls.proofServer, zkConfigProvider),
124
+ proofProvider: httpClientProofProvider(
125
+ networkUrls.proofServer,
126
+ zkConfigRegistry,
127
+ ),
113
128
  walletProvider: walletAndMidnightProvider,
114
129
  midnightProvider: walletAndMidnightProvider,
115
130
  };
116
- }
131
+ }
package/src/types.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { ZswapSecretKeys, DustSecretKey } from "@midnight-ntwrk/ledger-v8";
1
+ import type { ZswapSecretKeys, DustSecretKey } from "@midnightntwrk/ledger-v9";
2
+ import type { NetworkId } from "@midnightntwrk/wallet-sdk-abstractions";
2
3
  import type { WalletFacade } from "@midnightntwrk/wallet-sdk-facade";
3
4
  import type { UnshieldedKeystore } from "@midnightntwrk/wallet-sdk-unshielded-wallet";
4
5
 
@@ -61,10 +62,10 @@ export interface DeployConfig {
61
62
  */
62
63
  export interface NetworkUrls {
63
64
  /** Optional network ID override */
64
- id?: string;
65
- /** GraphQL indexer HTTP endpoint (default: http://127.0.0.1:8088/api/v3/graphql)*/
65
+ id?: NetworkId.NetworkId;
66
+ /** GraphQL indexer HTTP endpoint (default: http://127.0.0.1:8088/api/v4/graphql)*/
66
67
  indexer?: string;
67
- /** GraphQL indexer WebSocket endpoint (default: ws://127.0.0.1:8088/api/v3/graphql/ws)*/
68
+ /** GraphQL indexer WebSocket endpoint (default: ws://127.0.0.1:8088/api/v4/graphql/ws)*/
68
69
  indexerWS?: string;
69
70
  /** Midnight node RPC endpoint (default: http://127.0.0.1:9944)*/
70
71
  node?: string;
@@ -91,4 +92,4 @@ export interface WalletResult {
91
92
  dustAddress: string;
92
93
  unshieldedAddress: string;
93
94
  unshieldedKeystore: UnshieldedKeystore;
94
- }
95
+ }
@@ -0,0 +1,223 @@
1
+ import { pathToFileURL } from "node:url";
2
+
3
+ import { nativeToken } from "@midnightntwrk/ledger-v9";
4
+ import type { NetworkId } from "@midnightntwrk/wallet-sdk-abstractions";
5
+ import type { WalletFacade } from "@midnightntwrk/wallet-sdk-facade";
6
+ import * as Rx from "rxjs";
7
+
8
+ import { deployMidnightContract } from "../src/deploy.ts";
9
+ import {
10
+ buildWalletFacade,
11
+ registerNightForDust,
12
+ resolveFacadeDustAvailableCoins,
13
+ syncAndWaitForFunds,
14
+ } from "../src/get-wallet-info.ts";
15
+ import type { NetworkUrls, WalletResult } from "../src/types.ts";
16
+
17
+ const networkId = "undeployed" as NetworkId.NetworkId;
18
+ const timeoutMs = Number(process.env.MIDNIGHT_WALLET_SYNC_TIMEOUT_MS ?? "600000");
19
+ const fundingAmount = BigInt(process.env.M3_FUNDING_AMOUNT ?? "10000000000000");
20
+ const sourceSeed = process.env.M3_SOURCE_SEED ??
21
+ "0000000000000000000000000000000000000000000000000000000000000001";
22
+ const recipientSeed = process.env.M3_RECIPIENT_SEED ??
23
+ "00000000000000000000000000000000000000000000000000000000000000a3";
24
+ const contractBaseDir = requiredEnv("M3_CONTRACT_BASE_DIR");
25
+ const contractModule = requiredEnv("M3_CONTRACT_MODULE");
26
+
27
+ const networkUrls: Required<NetworkUrls> = {
28
+ id: networkId,
29
+ indexer: requiredEnv("MIDNIGHT_INDEXER_HTTP"),
30
+ indexerWS: requiredEnv("MIDNIGHT_INDEXER_WS"),
31
+ node: requiredEnv("MIDNIGHT_NODE_HTTP"),
32
+ proofServer: requiredEnv("MIDNIGHT_PROOF_SERVER_URL"),
33
+ };
34
+
35
+ const wallets: WalletResult[] = [];
36
+ let recipientWasPassedToDeploy = false;
37
+
38
+ try {
39
+ checkpoint("integration-start", {
40
+ runtime: `bun-${Bun.version}`,
41
+ networkId,
42
+ endpoints: networkUrls,
43
+ fundingAmount: fundingAmount.toString(),
44
+ });
45
+
46
+ const source = await buildWalletFacade(networkUrls, sourceSeed, networkId);
47
+ wallets.push(source);
48
+ await syncAndWaitForFunds(source.wallet, { timeoutMs });
49
+ const sourceState = await waitForFacadeState(
50
+ source.wallet,
51
+ "source wallet with NIGHT and spendable DUST",
52
+ (state) =>
53
+ allStrictlyComplete(state) &&
54
+ unshieldedNight(state) >= fundingAmount &&
55
+ resolveFacadeDustAvailableCoins(state) >= 1,
56
+ );
57
+ checkpoint("source-wallet-ready", stateEvidence(sourceState));
58
+
59
+ const recipient = await buildWalletFacade(networkUrls, recipientSeed, networkId);
60
+ wallets.push(recipient);
61
+ await syncAndWaitForFunds(recipient.wallet, { timeoutMs });
62
+ const recipientBefore = await waitForFacadeState(
63
+ recipient.wallet,
64
+ "fresh recipient wallet fully synchronized",
65
+ allStrictlyComplete,
66
+ );
67
+ if (unshieldedNight(recipientBefore) !== 0n) {
68
+ throw new Error("recipient seed is not fresh: expected zero NIGHT before funding");
69
+ }
70
+ if (resolveFacadeDustAvailableCoins(recipientBefore) !== 0) {
71
+ throw new Error("recipient seed is not fresh: expected zero spendable DUST before registration");
72
+ }
73
+ checkpoint("recipient-wallet-strictly-complete", stateEvidence(recipientBefore));
74
+
75
+ const receiverAddress = await recipient.wallet.unshielded.getAddress();
76
+ const fundingRecipe = await source.wallet.transferTransaction(
77
+ [{
78
+ type: "unshielded",
79
+ outputs: [{
80
+ type: nativeToken().raw,
81
+ receiverAddress,
82
+ amount: fundingAmount,
83
+ }],
84
+ }],
85
+ {
86
+ shieldedSecretKeys: source.zswapSecretKeys,
87
+ dustSecretKey: source.dustSecretKey,
88
+ },
89
+ { ttl: new Date(Date.now() + 30 * 60 * 1_000) },
90
+ );
91
+ if (fundingRecipe.type !== "UNPROVEN_TRANSACTION") {
92
+ throw new Error(`unexpected funding recipe type: ${fundingRecipe.type}`);
93
+ }
94
+ const signedFundingRecipe = await source.wallet.signRecipe(
95
+ fundingRecipe,
96
+ (payload) => source.unshieldedKeystore.signDataAsync(payload),
97
+ );
98
+ const fundingTx = await source.wallet.finalizeRecipe(signedFundingRecipe);
99
+ const fundingTxId = await source.wallet.submitTransaction(fundingTx);
100
+ checkpoint("fresh-wallet-funded", { fundingTxId: String(fundingTxId) });
101
+
102
+ const fundedState = await waitForFacadeState(
103
+ recipient.wallet,
104
+ "fresh recipient NIGHT UTXO",
105
+ (state) =>
106
+ allStrictlyComplete(state) &&
107
+ unshieldedNight(state) >= fundingAmount &&
108
+ state.unshielded.availableCoins.some(
109
+ (coin: { meta: { registeredForDustGeneration: boolean } }) =>
110
+ coin.meta.registeredForDustGeneration === false,
111
+ ),
112
+ );
113
+ checkpoint("recipient-funded-and-unregistered", stateEvidence(fundedState));
114
+
115
+ if (!await registerNightForDust(recipient)) {
116
+ throw new Error("fresh recipient NIGHT-to-DUST registration failed");
117
+ }
118
+ const registeredState = await waitForFacadeState(
119
+ recipient.wallet,
120
+ "registered wallet with spendable DUST",
121
+ (state) =>
122
+ allStrictlyComplete(state) && resolveFacadeDustAvailableCoins(state) >= 1,
123
+ );
124
+ checkpoint("recipient-dust-registered", stateEvidence(registeredState));
125
+
126
+ const fixture = await import(pathToFileURL(contractModule).href) as {
127
+ Counter: { Contract: unknown };
128
+ witnesses: unknown;
129
+ };
130
+ recipientWasPassedToDeploy = true;
131
+ const contractAddress = await deployMidnightContract(
132
+ {
133
+ contractName: "contract-counter",
134
+ contractFileName: "contract-counter.json",
135
+ contractClass: fixture.Counter.Contract,
136
+ witnesses: fixture.witnesses,
137
+ privateStateId: "counterPrivateState",
138
+ initialPrivateState: { privateCounter: 0 },
139
+ privateStateStoreName: "m3-counter-private-state",
140
+ baseDir: contractBaseDir,
141
+ },
142
+ networkUrls,
143
+ undefined,
144
+ { walletResult: recipient },
145
+ );
146
+ if (!contractAddress) throw new Error("deployment returned an empty contract address");
147
+ checkpoint("dust-fee-deployment-finalized", {
148
+ contractAddress,
149
+ networkId,
150
+ dustCoinsBeforeDeployment: resolveFacadeDustAvailableCoins(registeredState),
151
+ });
152
+ checkpoint("integration-pass", { contractAddress, fundingTxId: String(fundingTxId) });
153
+ } finally {
154
+ await Promise.allSettled(
155
+ wallets.map((entry, index) =>
156
+ index === 1 && recipientWasPassedToDeploy
157
+ ? Promise.resolve()
158
+ : entry.wallet.stop()
159
+ ),
160
+ );
161
+ }
162
+
163
+ function allStrictlyComplete(state: any): boolean {
164
+ return state.shielded.progress.isStrictlyComplete() &&
165
+ state.unshielded.progress.isStrictlyComplete() &&
166
+ state.dust.progress.isStrictlyComplete();
167
+ }
168
+
169
+ function unshieldedNight(state: any): bigint {
170
+ return state.unshielded.balances[nativeToken().raw] ?? 0n;
171
+ }
172
+
173
+ function stateEvidence(state: any): Record<string, unknown> {
174
+ return {
175
+ strictlyComplete: {
176
+ shielded: state.shielded.progress.isStrictlyComplete(),
177
+ unshielded: state.unshielded.progress.isStrictlyComplete(),
178
+ dust: state.dust.progress.isStrictlyComplete(),
179
+ },
180
+ unshieldedNight: unshieldedNight(state).toString(),
181
+ unregisteredNightCoins: state.unshielded.availableCoins.filter(
182
+ (coin: { meta: { registeredForDustGeneration: boolean } }) =>
183
+ coin.meta.registeredForDustGeneration === false,
184
+ ).length,
185
+ dustAvailableCoins: resolveFacadeDustAvailableCoins(state),
186
+ };
187
+ }
188
+
189
+ function waitForFacadeState(
190
+ wallet: WalletFacade,
191
+ description: string,
192
+ predicate: (state: any) => boolean,
193
+ ): Promise<any> {
194
+ let latest = "no state emitted";
195
+ return Rx.firstValueFrom(
196
+ wallet.state().pipe(
197
+ Rx.tap((state) => {
198
+ latest = JSON.stringify(stateEvidence(state));
199
+ }),
200
+ Rx.filter(predicate),
201
+ Rx.timeout({
202
+ first: timeoutMs,
203
+ with: () => Rx.throwError(
204
+ () => new Error(`timeout waiting for ${description}; latest=${latest}`),
205
+ ),
206
+ }),
207
+ ),
208
+ );
209
+ }
210
+
211
+ function checkpoint(name: string, details: Record<string, unknown> = {}): void {
212
+ console.log(JSON.stringify({
213
+ timestamp: new Date().toISOString(),
214
+ checkpoint: name,
215
+ ...details,
216
+ }));
217
+ }
218
+
219
+ function requiredEnv(name: string): string {
220
+ const value = process.env[name];
221
+ if (!value) throw new Error(`missing required environment variable ${name}`);
222
+ return value;
223
+ }
@@ -0,0 +1,219 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { realpathSync } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import {
5
+ ZswapChainState,
6
+ addressFromKey,
7
+ sampleSigningKey,
8
+ signData,
9
+ signatureVerifyingKey,
10
+ verifySignature,
11
+ type SignatureKind,
12
+ } from "@midnightntwrk/ledger-v9";
13
+ import { WalletFacade } from "@midnightntwrk/wallet-sdk-facade";
14
+ import * as Rx from "rxjs";
15
+
16
+ import {
17
+ registerNightForDust,
18
+ resolveFacadeDustAvailableCoins,
19
+ resolveFacadeDustFundsReadiness,
20
+ waitForDustFunds,
21
+ } from "../src/get-wallet-info.ts";
22
+ import { defaultMidnightNetworkConfig } from "../src/midnight-env.ts";
23
+ import type { WalletResult } from "../src/types.ts";
24
+
25
+ describe("ledger-v9 compatibility traps", () => {
26
+ for (const kind of ["schnorr", "ecdsa"] satisfies SignatureKind[]) {
27
+ test(`${kind} signatures keep their tagged key and signature shapes`, () => {
28
+ const signingKey = sampleSigningKey(kind);
29
+ const verifyingKey = signatureVerifyingKey(signingKey);
30
+ const payload = new TextEncoder().encode(`effectstream-${kind}`);
31
+ const signature = signData(signingKey, payload);
32
+
33
+ expect(signingKey.tag).toBe(kind);
34
+ expect(verifyingKey.tag).toBe(kind);
35
+ expect(signature.tag).toBe(kind);
36
+ expect(signingKey.value).toMatch(/^[0-9a-f]+$/i);
37
+ expect(verifyingKey.value).toMatch(/^[0-9a-f]+$/i);
38
+ expect(signature.value).toMatch(/^[0-9a-f]+$/i);
39
+ expect(verifySignature(verifyingKey, payload, signature)).toBe(true);
40
+ expect(addressFromKey(verifyingKey)).toBeTruthy();
41
+ });
42
+ }
43
+
44
+ test("postBlockUpdate accepts the required retentionDuration", () => {
45
+ const updated = new ZswapChainState().postBlockUpdate(
46
+ new Date("2026-08-23T00:00:00.000Z"),
47
+ 3_600n,
48
+ );
49
+ expect(updated).toBeInstanceOf(ZswapChainState);
50
+ });
51
+ });
52
+
53
+ describe("wallet-v2 migration", () => {
54
+ test("wallet node client and Polkadot share one BN constructor", () => {
55
+ const nodeClientRoot = realpathSync(
56
+ `${import.meta.dir}/../node_modules/@midnightntwrk/wallet-sdk-node-client`,
57
+ );
58
+ const nodeClientRequire = createRequire(`${nodeClientRoot}/package.json`);
59
+ const polkadotUtilRequire = createRequire(
60
+ nodeClientRequire.resolve("@polkadot/util/package.json"),
61
+ );
62
+ const NodeClientBN = nodeClientRequire("bn.js");
63
+ const PolkadotBN = polkadotUtilRequire("bn.js");
64
+ const blockNumber = new PolkadotBN(7);
65
+
66
+ expect(nodeClientRequire("bn.js/package.json").version).toBe("5.2.3");
67
+ expect(polkadotUtilRequire("bn.js/package.json").version).toBe("5.2.3");
68
+ expect(PolkadotBN).toBe(NodeClientBN);
69
+ expect(blockNumber).toBeInstanceOf(NodeClientBN);
70
+ expect(blockNumber.toString()).toBe("7");
71
+ });
72
+
73
+ test("WalletFacade exposes the async init entry point", () => {
74
+ expect(typeof WalletFacade.init).toBe("function");
75
+ });
76
+
77
+ test("DUST readiness counts spendable coins, not accrued balance", () => {
78
+ expect(
79
+ resolveFacadeDustAvailableCoins({
80
+ dust: { availableCoins: [], balance: () => 99n },
81
+ }),
82
+ ).toBe(0);
83
+ expect(
84
+ resolveFacadeDustAvailableCoins({
85
+ dust: { availableCoins: [{}], balance: () => 0n },
86
+ }),
87
+ ).toBe(1);
88
+ });
89
+
90
+ test("DUST readiness carries available coins when aggregate balance is zero", async () => {
91
+ const complete = { isStrictlyComplete: () => true };
92
+ const dustState = {
93
+ progress: complete,
94
+ availableCoins: [{ generatedNow: 10n }],
95
+ balance: () => 0n,
96
+ };
97
+ const wallet = { dust: { state: Rx.of(dustState) } } as unknown as WalletFacade;
98
+
99
+ const funds = await waitForDustFunds(wallet, {
100
+ timeoutMs: 100,
101
+ waitNonZero: true,
102
+ skipCatchUp: true,
103
+ dustPollIntervalMs: 0,
104
+ });
105
+
106
+ expect(funds).toEqual({
107
+ balance: 0n,
108
+ availableCoins: 1,
109
+ spendableCoins: 1,
110
+ ready: true,
111
+ });
112
+ expect(resolveFacadeDustFundsReadiness(dustState, 11n).ready).toBe(false);
113
+ expect(resolveFacadeDustFundsReadiness(dustState, 10n).ready).toBe(true);
114
+ });
115
+
116
+ test("registration finalizes the returned UnprovenTransactionRecipe", async () => {
117
+ const calls: string[] = [];
118
+ const signingKey = sampleSigningKey("schnorr");
119
+ const verifyingKey = signatureVerifyingKey(signingKey);
120
+ const recipe = {
121
+ type: "UNPROVEN_TRANSACTION" as const,
122
+ transaction: {},
123
+ };
124
+ const complete = { isStrictlyComplete: () => true };
125
+ const dustState = {
126
+ progress: complete,
127
+ availableCoins: [{}],
128
+ balance: () => 1n,
129
+ };
130
+ const facadeState = {
131
+ dust: dustState,
132
+ unshielded: {
133
+ progress: complete,
134
+ availableCoins: [
135
+ { meta: { registeredForDustGeneration: false } },
136
+ ],
137
+ },
138
+ };
139
+ const wallet = {
140
+ state: () => Rx.of(facadeState),
141
+ dust: { state: Rx.of(dustState) },
142
+ estimateRegistration: async () => {
143
+ calls.push("estimate");
144
+ return { fee: 5n, dustGenerationEstimations: [] };
145
+ },
146
+ waitForGeneratedDust: async (
147
+ _coins: readonly unknown[],
148
+ requiredAmount: bigint,
149
+ ) => {
150
+ calls.push("wait-generated");
151
+ expect(requiredAmount).toBe(5n);
152
+ },
153
+ registerNightUtxosForDustGeneration: async (
154
+ _coins: readonly unknown[],
155
+ key: unknown,
156
+ sign: (payload: Uint8Array) => Promise<unknown>,
157
+ ) => {
158
+ calls.push("register");
159
+ expect(key).toEqual(verifyingKey);
160
+ await sign(new Uint8Array([1, 2, 3]));
161
+ return recipe;
162
+ },
163
+ finalizeRecipe: async (received: unknown) => {
164
+ calls.push("finalize");
165
+ expect(received).toBe(recipe);
166
+ return { finalized: true };
167
+ },
168
+ submitTransaction: async () => {
169
+ calls.push("submit");
170
+ return "tx-id";
171
+ },
172
+ };
173
+ const walletResult = {
174
+ wallet,
175
+ unshieldedKeystore: {
176
+ getPublicKey: () => verifyingKey,
177
+ signDataAsync: async (payload: Uint8Array) =>
178
+ signData(signingKey, payload),
179
+ },
180
+ } as unknown as WalletResult;
181
+
182
+ expect(await registerNightForDust(walletResult)).toBe(true);
183
+ expect(calls).toEqual([
184
+ "estimate",
185
+ "wait-generated",
186
+ "register",
187
+ "finalize",
188
+ "submit",
189
+ ]);
190
+ });
191
+ });
192
+
193
+ describe("network defaults", () => {
194
+ test("undeployed uses local GraphQL v4 endpoints", () => {
195
+ const config = defaultMidnightNetworkConfig("undeployed");
196
+ expect(config.indexer).toBe("http://127.0.0.1:8088/api/v4/graphql");
197
+ expect(config.indexerWS).toBe("ws://127.0.0.1:8088/api/v4/graphql/ws");
198
+ });
199
+
200
+ for (const networkId of [
201
+ "mainnet",
202
+ "testnet",
203
+ "devnet",
204
+ "qanet",
205
+ "preview",
206
+ "preprod",
207
+ "stagenet",
208
+ "future-network",
209
+ ]) {
210
+ test(`preserves deployed network ID ${networkId}`, () => {
211
+ const config = defaultMidnightNetworkConfig(networkId);
212
+ expect(config.networkId).toBe(networkId);
213
+ expect(config.indexer).toBe(
214
+ `https://indexer.${networkId}.midnight.network/api/v4/graphql`,
215
+ );
216
+ expect(config.node).toBe(`https://rpc.${networkId}.midnight.network`);
217
+ });
218
+ }
219
+ });
@@ -0,0 +1,27 @@
1
+ import {
2
+ ZswapChainState,
3
+ type Signature,
4
+ type SignatureVerifyingKey,
5
+ type SigningKey,
6
+ } from "@midnightntwrk/ledger-v9";
7
+
8
+ const signingKey: SigningKey = { tag: "schnorr", value: "00" };
9
+ const verifyingKey: SignatureVerifyingKey = { tag: "ecdsa", value: "00" };
10
+ const signature: Signature = { tag: "schnorr", value: "00" };
11
+ void [signingKey, verifyingKey, signature];
12
+
13
+ // The legacy string representation must stay rejected by ledger-v9 types.
14
+ // @ts-expect-error ledger-v9 signing keys are tagged values
15
+ const legacySigningKey: SigningKey = "00";
16
+ // @ts-expect-error ledger-v9 verifying keys are tagged values
17
+ const legacyVerifyingKey: SignatureVerifyingKey = "00";
18
+ // @ts-expect-error ledger-v9 signatures are tagged values
19
+ const legacySignature: Signature = "00";
20
+ void [legacySigningKey, legacyVerifyingKey, legacySignature];
21
+
22
+ if (false) {
23
+ const state = new ZswapChainState();
24
+ state.postBlockUpdate(new Date(), 60n);
25
+ // @ts-expect-error ledger-v9 requires retentionDuration
26
+ state.postBlockUpdate(new Date());
27
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "types": ["bun-types"]
6
+ },
7
+ "include": ["src/**/*.ts", "test/**/*.ts"]
8
+ }