@effectstream/wallets 0.100.18 → 0.100.19
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/package.json +10 -5
- package/src/effectstream.ts +3 -1
- package/src/evm/viem.ts +73 -0
- package/src/midnight/local.test.ts +76 -1
- package/src/midnight/local.ts +193 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effectstream/wallets",
|
|
3
|
-
"version": "0.100.
|
|
3
|
+
"version": "0.100.19",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"homepage": "https://effectstream.github.io/docs/",
|
|
6
6
|
"repository": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"./midnight-local": "./src/midnight/local.ts"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
|
+
"@effectstream/midnight-contracts": "0.100.19",
|
|
21
22
|
"@lucid-evolution/lucid": "^0.4.29",
|
|
22
23
|
"@lucid-evolution/provider": "^0.1.90",
|
|
23
24
|
"@lucid-evolution/utils": "^0.1.66",
|
|
@@ -26,6 +27,9 @@
|
|
|
26
27
|
"@midnight-ntwrk/wallet-sdk-unshielded-wallet": "^2.1.0"
|
|
27
28
|
},
|
|
28
29
|
"peerDependenciesMeta": {
|
|
30
|
+
"@effectstream/midnight-contracts": {
|
|
31
|
+
"optional": true
|
|
32
|
+
},
|
|
29
33
|
"@lucid-evolution/lucid": {
|
|
30
34
|
"optional": true
|
|
31
35
|
},
|
|
@@ -46,6 +50,7 @@
|
|
|
46
50
|
}
|
|
47
51
|
},
|
|
48
52
|
"devDependencies": {
|
|
53
|
+
"@effectstream/midnight-contracts": "0.100.19",
|
|
49
54
|
"@lucid-evolution/lucid": "^0.4.30",
|
|
50
55
|
"@lucid-evolution/provider": "^0.1.91",
|
|
51
56
|
"@lucid-evolution/utils": "^0.1.66",
|
|
@@ -54,10 +59,10 @@
|
|
|
54
59
|
"@midnight-ntwrk/wallet-sdk-unshielded-wallet": "2.1.0"
|
|
55
60
|
},
|
|
56
61
|
"dependencies": {
|
|
57
|
-
"@effectstream/concise": "0.100.
|
|
58
|
-
"@effectstream/crypto": "0.100.
|
|
59
|
-
"@effectstream/event-client": "0.100.
|
|
60
|
-
"@effectstream/utils": "0.100.
|
|
62
|
+
"@effectstream/concise": "0.100.19",
|
|
63
|
+
"@effectstream/crypto": "0.100.19",
|
|
64
|
+
"@effectstream/event-client": "0.100.19",
|
|
65
|
+
"@effectstream/utils": "0.100.19",
|
|
61
66
|
"@aurowallet/mina-provider": "^1.0.12",
|
|
62
67
|
"@metamask/providers": "^22.1.0",
|
|
63
68
|
"@midnight-ntwrk/dapp-connector-api": "4.0.1",
|
package/src/effectstream.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { type Chain, numberToHex } from "viem";
|
|
|
5
5
|
import { utf8ToHex } from "web3-utils";
|
|
6
6
|
import type { EthersEvmProvider } from "./evm/ethers.ts";
|
|
7
7
|
import type { EvmInjectedProvider } from "./evm/injected.ts";
|
|
8
|
+
import type { ViemEvmProvider } from "./evm/viem.ts";
|
|
8
9
|
import type { AbiItem } from "web3-utils";
|
|
9
10
|
import { type TransactionReceipt, Web3 } from "web3";
|
|
10
11
|
import { createMessageForBatcher } from "@effectstream/concise";
|
|
@@ -189,7 +190,8 @@ export async function sendSelfSequencedTransaction(
|
|
|
189
190
|
|
|
190
191
|
const evmProvider = wallet.provider as
|
|
191
192
|
| EthersEvmProvider
|
|
192
|
-
| EvmInjectedProvider
|
|
193
|
+
| EvmInjectedProvider
|
|
194
|
+
| ViemEvmProvider;
|
|
193
195
|
|
|
194
196
|
const hexData = utf8ToHex(JSON.stringify(conciseData));
|
|
195
197
|
const effectstreamL2Contract = await effectstreamConfig.getEffectstreamL2Contract();
|
package/src/evm/viem.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
createPublicClient,
|
|
2
3
|
createWalletClient,
|
|
3
4
|
http,
|
|
4
5
|
type Chain,
|
|
@@ -119,4 +120,76 @@ export class ViemEvmProvider implements IProvider<ViemApi> {
|
|
|
119
120
|
message,
|
|
120
121
|
});
|
|
121
122
|
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Submit an EIP-1193-shaped transaction request (matching the
|
|
126
|
+
* `EvmInjectedProvider.sendTransaction` shape — all numeric fields are
|
|
127
|
+
* hex strings) by delegating to viem's hoisted `walletClient.sendTransaction`.
|
|
128
|
+
*
|
|
129
|
+
* `account` + `chain` are hoisted on the underlying `WalletClient` at
|
|
130
|
+
* connect time (see `ViemConnector.connectFromPrivateKey`), so we only
|
|
131
|
+
* need to convert the hex-string numeric fields to bigints that viem
|
|
132
|
+
* expects, and pass through `to` / `data` as 0x-strings.
|
|
133
|
+
*
|
|
134
|
+
* Without this method, the high-level `sendTransaction(wallet, …)` helper
|
|
135
|
+
* in `effectstream.ts` rejects with `evmProvider.sendTransaction is not a
|
|
136
|
+
* function` when invoked against a `WalletMode.EvmViem` wallet — blocking
|
|
137
|
+
* any frontend that wants to drive the engine's tx flow via the local-JS
|
|
138
|
+
* wallet (e.g. headless Playwright e2e tests).
|
|
139
|
+
*/
|
|
140
|
+
sendTransaction = async (tx: {
|
|
141
|
+
to?: string;
|
|
142
|
+
from: string;
|
|
143
|
+
gas?: string;
|
|
144
|
+
gasPrice?: string;
|
|
145
|
+
data: string;
|
|
146
|
+
value?: string;
|
|
147
|
+
maxPriorityFeePerGas?: string;
|
|
148
|
+
maxFeePerGas?: string;
|
|
149
|
+
}): Promise<{ txHash: string }> => {
|
|
150
|
+
if (this.account == null) {
|
|
151
|
+
throw new Error("ViemEvmProvider.sendTransaction: account is missing");
|
|
152
|
+
}
|
|
153
|
+
const toBigInt = (h?: string): bigint | undefined =>
|
|
154
|
+
h == null ? undefined : BigInt(h);
|
|
155
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
156
|
+
const hash = await (this.conn.api as any).sendTransaction({
|
|
157
|
+
account: this.account,
|
|
158
|
+
to: tx.to as `0x${string}` | undefined,
|
|
159
|
+
data: tx.data as `0x${string}`,
|
|
160
|
+
value: toBigInt(tx.value),
|
|
161
|
+
gas: toBigInt(tx.gas),
|
|
162
|
+
gasPrice: toBigInt(tx.gasPrice),
|
|
163
|
+
maxFeePerGas: toBigInt(tx.maxFeePerGas),
|
|
164
|
+
maxPriorityFeePerGas: toBigInt(tx.maxPriorityFeePerGas),
|
|
165
|
+
});
|
|
166
|
+
if (typeof hash !== "string") {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`[ViemEvmProvider.sendTransaction] expected string tx hash, got ${typeof hash}`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Wait for the receipt + verify status. viem's sendTransaction returns
|
|
173
|
+
// the hash as soon as the tx is broadcast, NOT once it has mined. If we
|
|
174
|
+
// returned the hash without checking the receipt, a reverted tx would
|
|
175
|
+
// look "successful" to the engine's `wait-receipt` confirmation path,
|
|
176
|
+
// and the indexer would never see a matching event because the contract
|
|
177
|
+
// call reverted on chain. Match the EvmInjected / EthersEvmProvider
|
|
178
|
+
// contract: only return when the tx is mined AND its status is 0x1.
|
|
179
|
+
const chain = (this.conn.api as WalletClient).chain;
|
|
180
|
+
const transport = (this.conn.api as WalletClient).transport;
|
|
181
|
+
const publicClient = createPublicClient({
|
|
182
|
+
chain: chain ?? undefined,
|
|
183
|
+
transport: http((transport as { url?: string })?.url),
|
|
184
|
+
});
|
|
185
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
186
|
+
hash: hash as `0x${string}`,
|
|
187
|
+
});
|
|
188
|
+
if (receipt.status !== "success") {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`[ViemEvmProvider.sendTransaction] tx ${hash} reverted on chain (status=${receipt.status})`,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
return { txHash: hash };
|
|
194
|
+
};
|
|
122
195
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
2
2
|
import { Buffer } from "node:buffer";
|
|
3
3
|
import { AddressType } from "@effectstream/utils";
|
|
4
4
|
import { verifySignature } from "@midnight-ntwrk/ledger-v8";
|
|
@@ -55,6 +55,81 @@ describe("MidnightLocalProvider", () => {
|
|
|
55
55
|
expect(okAgain).toBe(true);
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
+
test("facade mode: networkUrls triggers buildWalletFacade and exposes the full WalletFacade on the api", async () => {
|
|
59
|
+
// Sentinel objects so we can assert the facade-path code actually wired
|
|
60
|
+
// through whatever buildWalletFacade returned.
|
|
61
|
+
const fakeFacade = { __kind: "WalletFacade", shielded: {}, dust: {}, unshielded: {} };
|
|
62
|
+
const fakeShielded = {
|
|
63
|
+
address: {
|
|
64
|
+
coinPublicKeyString: () => "deadbeef-coin-pub-key",
|
|
65
|
+
encryptionPublicKeyString: () => "deadbeef-enc-pub-key",
|
|
66
|
+
},
|
|
67
|
+
balances: {},
|
|
68
|
+
};
|
|
69
|
+
const buildSpy = mock(async (
|
|
70
|
+
networkUrls: unknown,
|
|
71
|
+
seed: string,
|
|
72
|
+
_networkId: unknown,
|
|
73
|
+
_syncMode: unknown,
|
|
74
|
+
) => ({
|
|
75
|
+
wallet: fakeFacade,
|
|
76
|
+
zswapSecretKeys: {} as never,
|
|
77
|
+
walletZswapSecretKeys: {} as never,
|
|
78
|
+
dustSecretKey: {} as never,
|
|
79
|
+
walletDustSecretKey: {} as never,
|
|
80
|
+
dustAddress: `dust-for-${seed.slice(0, 8)}`,
|
|
81
|
+
unshieldedAddress: `unshielded-for-${seed.slice(0, 8)}`,
|
|
82
|
+
unshieldedKeystore: {} as never,
|
|
83
|
+
__echoedNetworkUrls: networkUrls,
|
|
84
|
+
}));
|
|
85
|
+
const getInitialSpy = mock(async (_shielded: unknown) => fakeShielded);
|
|
86
|
+
|
|
87
|
+
mock.module("@effectstream/midnight-contracts/wallet-info", () => ({
|
|
88
|
+
buildWalletFacade: buildSpy,
|
|
89
|
+
getInitialShieldedState: getInitialSpy,
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
const provider = await MidnightLocalConnector.instance().connectFromSeed({
|
|
93
|
+
seed: DETERMINISTIC_SEED,
|
|
94
|
+
networkId: "undeployed",
|
|
95
|
+
networkUrls: {
|
|
96
|
+
indexer: "http://127.0.0.1:8088/api/v3/graphql",
|
|
97
|
+
indexerWS: "ws://127.0.0.1:8088/api/v3/graphql/ws",
|
|
98
|
+
node: "http://127.0.0.1:9944",
|
|
99
|
+
proofServer: "http://127.0.0.1:6300",
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(buildSpy).toHaveBeenCalledTimes(1);
|
|
104
|
+
const [networkUrlsArg, seedArg, networkIdArg, syncModeArg] = buildSpy.mock.calls[0]!;
|
|
105
|
+
expect(seedArg).toBe(DETERMINISTIC_SEED);
|
|
106
|
+
expect(networkIdArg).toBe("undeployed");
|
|
107
|
+
expect(syncModeArg).toBe("all");
|
|
108
|
+
expect(networkUrlsArg).toMatchObject({
|
|
109
|
+
indexer: "http://127.0.0.1:8088/api/v3/graphql",
|
|
110
|
+
indexerWS: "ws://127.0.0.1:8088/api/v3/graphql/ws",
|
|
111
|
+
node: "http://127.0.0.1:9944",
|
|
112
|
+
proofServer: "http://127.0.0.1:6300",
|
|
113
|
+
id: "undeployed",
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const api = provider.getConnection().api as unknown as MidnightLocalApi;
|
|
117
|
+
expect(api.walletFacade).toBe(fakeFacade);
|
|
118
|
+
expect(api.dustAddress).toBe(`dust-for-${DETERMINISTIC_SEED.slice(0, 8)}`);
|
|
119
|
+
expect(api.shieldedAddress).toBe("deadbeef-coin-pub-key");
|
|
120
|
+
|
|
121
|
+
// getShieldedAddresses no longer throws in facade mode.
|
|
122
|
+
await expect(api.getShieldedAddresses()).resolves.toEqual({
|
|
123
|
+
shieldedAddress: "deadbeef-coin-pub-key",
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(provider.getConnection().metadata.displayName).toBe(
|
|
127
|
+
"Midnight (local seed, facade)",
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
mock.restore();
|
|
131
|
+
});
|
|
132
|
+
|
|
58
133
|
test("signMessage round-trips through CryptoManager.Midnight().verifySignature", async () => {
|
|
59
134
|
const provider = await MidnightLocalConnector.instance().connectFromSeed({
|
|
60
135
|
seed: DETERMINISTIC_SEED,
|
package/src/midnight/local.ts
CHANGED
|
@@ -8,20 +8,55 @@ import type {
|
|
|
8
8
|
} from "../IProvider.ts";
|
|
9
9
|
import type { MidnightApi } from "./midnight.ts";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Network endpoints required to bring up the full WalletFacade. Mirrors
|
|
13
|
+
* `NetworkUrls` from `@effectstream/midnight-contracts` but with every field
|
|
14
|
+
* required (so consumers can't accidentally leave one unset).
|
|
15
|
+
*/
|
|
16
|
+
export type MidnightLocalNetworkUrls = {
|
|
17
|
+
/** GraphQL indexer HTTP endpoint. */
|
|
18
|
+
indexer: string;
|
|
19
|
+
/** GraphQL indexer WebSocket endpoint. */
|
|
20
|
+
indexerWS: string;
|
|
21
|
+
/** Midnight node RPC endpoint (used as the wallet relay; http→ws is rewritten internally). */
|
|
22
|
+
node: string;
|
|
23
|
+
/** Proof-server HTTP endpoint. */
|
|
24
|
+
proofServer: string;
|
|
25
|
+
/** Optional network id override; defaults to `connectFromSeed`'s `networkId` arg. */
|
|
26
|
+
id?: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type MidnightLocalSyncMode = "all" | "dust-only";
|
|
30
|
+
|
|
11
31
|
export type MidnightLocalConnectArgs = {
|
|
12
32
|
/** 64-character hex seed. Generated when omitted. */
|
|
13
33
|
seed?: string;
|
|
14
34
|
/** Midnight network id — sets the bech32 prefix on the unshielded address. */
|
|
15
35
|
networkId: string;
|
|
36
|
+
/**
|
|
37
|
+
* Network URLs for the full WalletFacade. When supplied, the connector builds
|
|
38
|
+
* a complete wallet (shielded + dust + unshielded) via
|
|
39
|
+
* `@effectstream/midnight-contracts.buildWalletFacade` and exposes it on
|
|
40
|
+
* `getConnection().api.walletFacade`. Required for shielded tx submission,
|
|
41
|
+
* `balanceAndProveTransaction`, dust fee payment, etc.
|
|
42
|
+
*
|
|
43
|
+
* When omitted, the connector returns the signing-only path (unshielded
|
|
44
|
+
* keystore `signData`) — useful for offline signature flows or signature
|
|
45
|
+
* verification dry runs that don't need to stand up indexer + proof-server.
|
|
46
|
+
*/
|
|
47
|
+
networkUrls?: MidnightLocalNetworkUrls;
|
|
48
|
+
/** Defaults to "all" when `networkUrls` is supplied. Ignored otherwise. */
|
|
49
|
+
syncMode?: MidnightLocalSyncMode;
|
|
16
50
|
};
|
|
17
51
|
|
|
18
52
|
/**
|
|
19
|
-
* Shape exposed on `getConnection().api`.
|
|
53
|
+
* Shape exposed on `getConnection().api`. Mirrors a subset of `ConnectedAPI`
|
|
20
54
|
* that the rest of Effectstream's Midnight code consumes:
|
|
21
|
-
* - `signData` mirrors the dapp-connector-api shape.
|
|
22
|
-
* - `getShieldedAddresses`
|
|
23
|
-
*
|
|
24
|
-
*
|
|
55
|
+
* - `signData` mirrors the dapp-connector-api shape (works in both modes).
|
|
56
|
+
* - `getShieldedAddresses` returns the real shielded coin public key when
|
|
57
|
+
* the full facade is built; throws in signing-only mode.
|
|
58
|
+
* - `walletFacade` / `dustAddress` / `shieldedAddress` are populated only
|
|
59
|
+
* when `networkUrls` was supplied at connect time.
|
|
25
60
|
*/
|
|
26
61
|
export type MidnightLocalApi = {
|
|
27
62
|
signData: (
|
|
@@ -35,6 +70,28 @@ export type MidnightLocalApi = {
|
|
|
35
70
|
verifyingKey: string;
|
|
36
71
|
/** Bech32-encoded unshielded address. */
|
|
37
72
|
unshieldedAddress: string;
|
|
73
|
+
/**
|
|
74
|
+
* Full Midnight `WalletFacade`. Populated only when `networkUrls` was passed
|
|
75
|
+
* to `connectFromSeed`. Cast to
|
|
76
|
+
* `import("@midnight-ntwrk/wallet-sdk-facade").WalletFacade` for typed
|
|
77
|
+
* access. Carries `submitTransaction`, `balanceFinalizedTransaction`,
|
|
78
|
+
* `balanceUnboundTransaction`, `balanceUnprovenTransaction`, `signRecipe`,
|
|
79
|
+
* `signUnprovenTransaction`, `state()`, `waitForSyncedState()`, etc.
|
|
80
|
+
*/
|
|
81
|
+
walletFacade?: unknown;
|
|
82
|
+
/** Bech32-encoded dust address. Populated only in facade mode. */
|
|
83
|
+
dustAddress?: string;
|
|
84
|
+
/** Hex-encoded shielded coin public key. Populated only in facade mode. */
|
|
85
|
+
shieldedAddress?: string;
|
|
86
|
+
/**
|
|
87
|
+
* Escape hatch: the entire `WalletResult` returned by
|
|
88
|
+
* `@effectstream/midnight-contracts.buildWalletFacade`. Exposes the raw
|
|
89
|
+
* secret keys (`zswapSecretKeys`, `dustSecretKey`, `unshieldedKeystore`) that
|
|
90
|
+
* advanced flows (manual tx balancing, custom dust handling) need. Cast to
|
|
91
|
+
* `import("@effectstream/midnight-contracts").WalletResult`.
|
|
92
|
+
* Populated only in facade mode.
|
|
93
|
+
*/
|
|
94
|
+
walletResult?: unknown;
|
|
38
95
|
};
|
|
39
96
|
|
|
40
97
|
export class MidnightLocalConnector {
|
|
@@ -51,27 +108,34 @@ export class MidnightLocalConnector {
|
|
|
51
108
|
/**
|
|
52
109
|
* Build a Midnight wallet identity from a hex seed.
|
|
53
110
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* `
|
|
57
|
-
*
|
|
58
|
-
*
|
|
111
|
+
* Two modes, picked by whether `args.networkUrls` is supplied:
|
|
112
|
+
*
|
|
113
|
+
* - **Facade mode** (`networkUrls` set): builds the full Midnight wallet via
|
|
114
|
+
* `@effectstream/midnight-contracts.buildWalletFacade` — shielded + dust +
|
|
115
|
+
* unshielded sub-wallets, connected to a live indexer + node + proof
|
|
116
|
+
* server. The complete `WalletFacade` is exposed on
|
|
117
|
+
* `getConnection().api.walletFacade`, enabling `submitTransaction`,
|
|
118
|
+
* `balanceAndProveTransaction`, dust fee payment, etc. Requires
|
|
119
|
+
* `@effectstream/midnight-contracts` to be installed (optional peer dep).
|
|
120
|
+
*
|
|
121
|
+
* - **Signing-only mode** (`networkUrls` omitted): derives the unshielded
|
|
122
|
+
* keystore from the seed and exposes a CIP-30-shaped `signData` backed by
|
|
123
|
+
* `unshieldedKeystore.signData(...)`. No network IO. Suitable for offline
|
|
124
|
+
* signature flows and signature-verification dry runs.
|
|
59
125
|
*/
|
|
60
126
|
connectFromSeed = async (
|
|
61
127
|
args: MidnightLocalConnectArgs,
|
|
62
128
|
): Promise<MidnightLocalProvider> => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
);
|
|
74
|
-
});
|
|
129
|
+
if (args.networkUrls != null) {
|
|
130
|
+
return await this.connectWithFacade(args, args.networkUrls);
|
|
131
|
+
}
|
|
132
|
+
return await this.connectSigningOnly(args);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
private connectSigningOnly = async (
|
|
136
|
+
args: MidnightLocalConnectArgs,
|
|
137
|
+
): Promise<MidnightLocalProvider> => {
|
|
138
|
+
const { hdMod, keystoreMod } = await loadKeystoreDeps();
|
|
75
139
|
|
|
76
140
|
const seed = args.seed ?? generateRandomHexSeed(32);
|
|
77
141
|
const unshieldedSeed = deriveUnshieldedSeed(
|
|
@@ -102,7 +166,7 @@ export class MidnightLocalConnector {
|
|
|
102
166
|
},
|
|
103
167
|
getShieldedAddresses: async () => {
|
|
104
168
|
throw new Error(
|
|
105
|
-
"MidnightLocal
|
|
169
|
+
"MidnightLocal signing-only mode does not expose a shielded address. Pass `networkUrls` to connectFromSeed to build the full WalletFacade.",
|
|
106
170
|
);
|
|
107
171
|
},
|
|
108
172
|
seed,
|
|
@@ -126,6 +190,93 @@ export class MidnightLocalConnector {
|
|
|
126
190
|
return this.provider;
|
|
127
191
|
};
|
|
128
192
|
|
|
193
|
+
private connectWithFacade = async (
|
|
194
|
+
args: MidnightLocalConnectArgs,
|
|
195
|
+
networkUrls: MidnightLocalNetworkUrls,
|
|
196
|
+
): Promise<MidnightLocalProvider> => {
|
|
197
|
+
// Use the `/wallet-info` subpath, not the package barrel: the barrel
|
|
198
|
+
// re-exports `deploy.ts` (`node:fs/promises`), which breaks browser bundles
|
|
199
|
+
// of `@effectstream/wallets`.
|
|
200
|
+
const contractsMod = await import(
|
|
201
|
+
"@effectstream/midnight-contracts/wallet-info"
|
|
202
|
+
).catch(() => {
|
|
203
|
+
throw new Error(
|
|
204
|
+
"@effectstream/midnight-contracts is required when `networkUrls` is passed to MidnightLocal.connectFromSeed. Install it as a peer dependency.",
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
const { hdMod, keystoreMod } = await loadKeystoreDeps();
|
|
208
|
+
|
|
209
|
+
const seed = args.seed ?? generateRandomHexSeed(32);
|
|
210
|
+
const unshieldedSeed = deriveUnshieldedSeed(
|
|
211
|
+
hdMod,
|
|
212
|
+
seed,
|
|
213
|
+
hdMod.Roles.NightExternal,
|
|
214
|
+
);
|
|
215
|
+
const unshieldedKeystore = keystoreMod.createKeystore(
|
|
216
|
+
unshieldedSeed,
|
|
217
|
+
args.networkId as never,
|
|
218
|
+
);
|
|
219
|
+
const verifyingKey = String(unshieldedKeystore.getPublicKey());
|
|
220
|
+
|
|
221
|
+
const fullNetworkUrls = {
|
|
222
|
+
indexer: networkUrls.indexer,
|
|
223
|
+
indexerWS: networkUrls.indexerWS,
|
|
224
|
+
node: networkUrls.node,
|
|
225
|
+
proofServer: networkUrls.proofServer,
|
|
226
|
+
id: networkUrls.id ?? args.networkId,
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const walletResult = await contractsMod.buildWalletFacade(
|
|
230
|
+
fullNetworkUrls,
|
|
231
|
+
seed,
|
|
232
|
+
args.networkId as never,
|
|
233
|
+
args.syncMode ?? "all",
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const initialShielded = await contractsMod.getInitialShieldedState(
|
|
237
|
+
walletResult.wallet.shielded,
|
|
238
|
+
);
|
|
239
|
+
const shieldedAddress = initialShielded.address.coinPublicKeyString();
|
|
240
|
+
|
|
241
|
+
const api: MidnightLocalApi = {
|
|
242
|
+
signData: async (data, options) => {
|
|
243
|
+
const bytes =
|
|
244
|
+
options.encoding === "hex"
|
|
245
|
+
? Buffer.from(data, "hex")
|
|
246
|
+
: Buffer.from(data, "utf-8");
|
|
247
|
+
const signature = unshieldedKeystore.signData(bytes);
|
|
248
|
+
return {
|
|
249
|
+
data,
|
|
250
|
+
signature: String(signature),
|
|
251
|
+
verifyingKey,
|
|
252
|
+
};
|
|
253
|
+
},
|
|
254
|
+
getShieldedAddresses: async () => ({ shieldedAddress }),
|
|
255
|
+
seed,
|
|
256
|
+
verifyingKey,
|
|
257
|
+
unshieldedAddress: walletResult.unshieldedAddress,
|
|
258
|
+
walletFacade: walletResult.wallet,
|
|
259
|
+
dustAddress: walletResult.dustAddress,
|
|
260
|
+
shieldedAddress,
|
|
261
|
+
walletResult,
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const conn: ActiveConnection<MidnightApi> = {
|
|
265
|
+
api: api as unknown as MidnightApi,
|
|
266
|
+
metadata: {
|
|
267
|
+
name: "midnight-local",
|
|
268
|
+
displayName: "Midnight (local seed, facade)",
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
this.provider = new MidnightLocalProvider(
|
|
273
|
+
conn,
|
|
274
|
+
walletResult.unshieldedAddress as MidnightAddress,
|
|
275
|
+
unshieldedKeystore,
|
|
276
|
+
);
|
|
277
|
+
return this.provider;
|
|
278
|
+
};
|
|
279
|
+
|
|
129
280
|
getProvider = (): undefined | MidnightLocalProvider => {
|
|
130
281
|
return this.provider;
|
|
131
282
|
};
|
|
@@ -176,6 +327,25 @@ export class MidnightLocalProvider implements IProvider<MidnightApi> {
|
|
|
176
327
|
getUnshieldedKeystore = (): unknown => this.unshieldedKeystore;
|
|
177
328
|
}
|
|
178
329
|
|
|
330
|
+
async function loadKeystoreDeps(): Promise<{
|
|
331
|
+
hdMod: typeof import("@midnight-ntwrk/wallet-sdk-hd");
|
|
332
|
+
keystoreMod: typeof import("@midnight-ntwrk/wallet-sdk-unshielded-wallet");
|
|
333
|
+
}> {
|
|
334
|
+
const hdMod = await import("@midnight-ntwrk/wallet-sdk-hd").catch(() => {
|
|
335
|
+
throw new Error(
|
|
336
|
+
"@midnight-ntwrk/wallet-sdk-hd is required for WalletMode.MidnightLocal.",
|
|
337
|
+
);
|
|
338
|
+
});
|
|
339
|
+
const keystoreMod = await import(
|
|
340
|
+
"@midnight-ntwrk/wallet-sdk-unshielded-wallet"
|
|
341
|
+
).catch(() => {
|
|
342
|
+
throw new Error(
|
|
343
|
+
"@midnight-ntwrk/wallet-sdk-unshielded-wallet is required for WalletMode.MidnightLocal.",
|
|
344
|
+
);
|
|
345
|
+
});
|
|
346
|
+
return { hdMod, keystoreMod };
|
|
347
|
+
}
|
|
348
|
+
|
|
179
349
|
function deriveUnshieldedSeed(
|
|
180
350
|
hdMod: typeof import("@midnight-ntwrk/wallet-sdk-hd"),
|
|
181
351
|
seedHex: string,
|