@miden-sdk/miden-wallet-adapter-miden 0.16.2 → 0.16.3
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/dist/adapter.js.map +1 -1
- package/dist/conformance.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +9 -4
- package/__tests__/adapter.test.ts +0 -594
- package/__tests__/conformance.test.ts +0 -131
- package/adapter.ts +0 -449
- package/conformance.ts +0 -219
- package/dist/__tests__/adapter.test.d.ts +0 -1
- package/dist/__tests__/adapter.test.js +0 -464
- package/dist/__tests__/adapter.test.js.map +0 -1
- package/dist/__tests__/conformance.test.d.ts +0 -1
- package/dist/__tests__/conformance.test.js +0 -97
- package/dist/__tests__/conformance.test.js.map +0 -1
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -8
- package/dist/vitest.config.js.map +0 -1
- package/docs/README.md +0 -20
- package/docs/classes/MidenWalletAdapter.md +0 -714
- package/docs/interfaces/MidenWallet.md +0 -506
- package/docs/interfaces/MidenWalletAdapterConfig.md +0 -13
- package/docs/interfaces/MidenWalletEvents.md +0 -55
- package/docs/interfaces/MidenWindow.md +0 -5217
- package/docs/variables/MidenWalletName.md +0 -9
- package/index.ts +0 -2
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -8
package/conformance.ts
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
// `EventEmitter` comes via the base package rather than `eventemitter3` directly:
|
|
2
|
-
// base re-exports it and is a declared dependency here, eventemitter3 is not.
|
|
3
|
-
import type { EventEmitter } from "@miden-sdk/miden-wallet-adapter-base";
|
|
4
|
-
|
|
5
|
-
import type { MidenWallet, MidenWalletEvents } from "./adapter";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* A conformance suite for `MidenWallet` implementations.
|
|
9
|
-
*
|
|
10
|
-
* ## Why this exists
|
|
11
|
-
*
|
|
12
|
-
* `createAccount` has been on the published `MidenWallet` interface since
|
|
13
|
-
* adapter 0.13.2. No wallet provider implements it — not the browser
|
|
14
|
-
* extension, not the mobile injection script, not the Tauri provider — and
|
|
15
|
-
* there is no wire message for it. A dApp calling it gets
|
|
16
|
-
* `TypeError: wallet.createAccount is not a function`.
|
|
17
|
-
*
|
|
18
|
-
* That shipped, and stayed shipped, because both sides of the contract were
|
|
19
|
-
* green: this repo's tests inject a `vi.fn()` bag as `window.midenWallet`, and
|
|
20
|
-
* the wallet's tests mock this package wholesale. Each side asserted against a
|
|
21
|
-
* shape it invented.
|
|
22
|
-
*
|
|
23
|
-
* A suite written *here* cannot fix that. Whatever reference provider this
|
|
24
|
-
* repo authors gets updated in the same commit that adds a method, so it can
|
|
25
|
-
* never detect a missing one. The detection has to happen where the real
|
|
26
|
-
* implementations live. So this module ships the *instrument*, and the wallet
|
|
27
|
-
* runs it against its real providers.
|
|
28
|
-
*
|
|
29
|
-
* ## Two halves, deliberately separate
|
|
30
|
-
*
|
|
31
|
-
* {@link getSurfaceCases} asks only whether each method exists. It needs no
|
|
32
|
-
* connection, no network and no state, so it is safe to run against providers
|
|
33
|
-
* that are plain injected JavaScript — which is what the mobile and Tauri
|
|
34
|
-
* providers are, and where the remaining gaps live.
|
|
35
|
-
*
|
|
36
|
-
* {@link getBehaviorCases} exercises round-trips and needs a live, connected
|
|
37
|
-
* provider. Running it against an un-connected provider proves nothing.
|
|
38
|
-
*
|
|
39
|
-
* @packageDocumentation
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
/** Members `MidenWallet` inherits from EventEmitter rather than declaring. */
|
|
43
|
-
type InheritedKeys = keyof EventEmitter<MidenWalletEvents>;
|
|
44
|
-
|
|
45
|
-
/** Members `MidenWallet` declares itself. */
|
|
46
|
-
type OwnKeys = Exclude<keyof MidenWallet, InheritedKeys>;
|
|
47
|
-
|
|
48
|
-
/** The subset of {@link OwnKeys} that are callable — i.e. not `address`/`publicKey`. */
|
|
49
|
-
type MethodKeys = {
|
|
50
|
-
[K in OwnKeys]-?: NonNullable<MidenWallet[K]> extends (
|
|
51
|
-
...args: never[]
|
|
52
|
-
) => unknown
|
|
53
|
-
? K
|
|
54
|
-
: never;
|
|
55
|
-
}[OwnKeys];
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Every method a `MidenWallet` must implement.
|
|
59
|
-
*
|
|
60
|
-
* Type-locked in both directions below: a method added to the interface
|
|
61
|
-
* without being added here fails to compile, and a name here that is not on
|
|
62
|
-
* the interface fails to compile too. The list cannot silently drift from the
|
|
63
|
-
* contract it describes.
|
|
64
|
-
*/
|
|
65
|
-
export const MIDEN_WALLET_METHODS = [
|
|
66
|
-
"connect",
|
|
67
|
-
"disconnect",
|
|
68
|
-
"signBytes",
|
|
69
|
-
"requestSend",
|
|
70
|
-
"requestConsume",
|
|
71
|
-
"requestTransaction",
|
|
72
|
-
"requestAssets",
|
|
73
|
-
"requestGuardianInfo",
|
|
74
|
-
"requestPrivateNotes",
|
|
75
|
-
"requestConsumableNotes",
|
|
76
|
-
"importPrivateNote",
|
|
77
|
-
"waitForTransaction",
|
|
78
|
-
"createAccount",
|
|
79
|
-
] as const satisfies readonly MethodKeys[];
|
|
80
|
-
|
|
81
|
-
/** Compile error if the interface gains a method this list does not name. */
|
|
82
|
-
type Unlisted = Exclude<MethodKeys, (typeof MIDEN_WALLET_METHODS)[number]>;
|
|
83
|
-
const _exhaustive: [Unlisted] extends [never]
|
|
84
|
-
? true
|
|
85
|
-
: ["MIDEN_WALLET_METHODS is missing", Unlisted] = true;
|
|
86
|
-
void _exhaustive;
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Identifies this module as the real implementation.
|
|
90
|
-
*
|
|
91
|
-
* The wallet mocks this package in a dozen test files. If a conformance run
|
|
92
|
-
* ever lands inside a suite that mocks it — or someone later adds a
|
|
93
|
-
* `moduleNameMapper` — the imported suite would become a no-op that still
|
|
94
|
-
* reports green: the identical failure mode this whole fixture exists to
|
|
95
|
-
* prevent, one layer up. Assert on this before running any case.
|
|
96
|
-
*/
|
|
97
|
-
export const CONFORMANCE_BUILD = {
|
|
98
|
-
real: true,
|
|
99
|
-
methodCount: MIDEN_WALLET_METHODS.length,
|
|
100
|
-
} as const;
|
|
101
|
-
|
|
102
|
-
/** One assertion, named so a failure says what is wrong without a stack trace. */
|
|
103
|
-
export interface ConformanceCase {
|
|
104
|
-
readonly name: string;
|
|
105
|
-
/** Throws on failure; returns normally on success. */
|
|
106
|
-
readonly run: () => void | Promise<void>;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Anything shaped enough to probe. Deliberately not `MidenWallet`: the point is to test providers that may not satisfy it. */
|
|
110
|
-
export type ProviderLike = Record<string, unknown>;
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Shape-only checks: does the provider expose every method?
|
|
114
|
-
*
|
|
115
|
-
* Safe against untyped, runtime-only providers — no connection or state
|
|
116
|
-
* required. This is the half that catches a provider missing a method the
|
|
117
|
-
* interface promises.
|
|
118
|
-
*/
|
|
119
|
-
export function getSurfaceCases(provider: ProviderLike): ConformanceCase[] {
|
|
120
|
-
return MIDEN_WALLET_METHODS.map((method) => ({
|
|
121
|
-
name: `implements ${method}()`,
|
|
122
|
-
run: () => {
|
|
123
|
-
const value = provider[method];
|
|
124
|
-
if (typeof value !== "function") {
|
|
125
|
-
throw new Error(
|
|
126
|
-
`MidenWallet.${method} is ${value === undefined ? "missing" : `a ${typeof value}`}. ` +
|
|
127
|
-
`Every method on the published MidenWallet interface must exist on the provider; ` +
|
|
128
|
-
`a dApp calling this one gets "TypeError: wallet.${method} is not a function".`
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
}));
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Round-trip checks against a live, connected provider.
|
|
137
|
-
*
|
|
138
|
-
* Unlike {@link getSurfaceCases} these need real state, so a provider obtained
|
|
139
|
-
* by evaluating an injection script — with no wallet behind it — will fail
|
|
140
|
-
* them for reasons that say nothing about conformance. Run the surface half
|
|
141
|
-
* against those.
|
|
142
|
-
*/
|
|
143
|
-
export function getBehaviorCases(provider: ProviderLike): ConformanceCase[] {
|
|
144
|
-
const call = async <T>(method: string, ...args: unknown[]): Promise<T> => {
|
|
145
|
-
const fn = provider[method];
|
|
146
|
-
if (typeof fn !== "function") {
|
|
147
|
-
throw new Error(`MidenWallet.${method} is not a function`);
|
|
148
|
-
}
|
|
149
|
-
return (await (fn as (...a: unknown[]) => Promise<T>).apply(
|
|
150
|
-
provider,
|
|
151
|
-
args
|
|
152
|
-
)) as T;
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
return [
|
|
156
|
-
{
|
|
157
|
-
name: "requestAssets() resolves to { assets: [...] }",
|
|
158
|
-
run: async () => {
|
|
159
|
-
const result = await call<{ assets: unknown[] }>("requestAssets");
|
|
160
|
-
if (!result || !Array.isArray(result.assets)) {
|
|
161
|
-
throw new Error(
|
|
162
|
-
`requestAssets() must resolve to { assets: Asset[] }, got ${JSON.stringify(result)}`
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
},
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
name: "requestConsumableNotes() resolves to { consumableNotes: [...] }",
|
|
169
|
-
run: async () => {
|
|
170
|
-
const result = await call<{ consumableNotes: unknown[] }>(
|
|
171
|
-
"requestConsumableNotes"
|
|
172
|
-
);
|
|
173
|
-
if (!result || !Array.isArray(result.consumableNotes)) {
|
|
174
|
-
throw new Error(
|
|
175
|
-
`requestConsumableNotes() must resolve to { consumableNotes: InputNoteDetails[] }, got ${JSON.stringify(result)}`
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
name: "requestGuardianInfo() resolves to { guardianInfo }",
|
|
182
|
-
run: async () => {
|
|
183
|
-
const result = await call<{ guardianInfo: unknown }>(
|
|
184
|
-
"requestGuardianInfo"
|
|
185
|
-
);
|
|
186
|
-
if (!result || result.guardianInfo === undefined) {
|
|
187
|
-
throw new Error(
|
|
188
|
-
`requestGuardianInfo() must resolve to { guardianInfo: GuardianInfo }, got ${JSON.stringify(result)}`
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
];
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Runs cases and collects every failure rather than stopping at the first.
|
|
198
|
-
*
|
|
199
|
-
* A provider missing four methods should report four failures, not one — the
|
|
200
|
-
* point is to see the whole gap in a single run.
|
|
201
|
-
*/
|
|
202
|
-
export async function runConformance(
|
|
203
|
-
cases: ConformanceCase[]
|
|
204
|
-
): Promise<{ passed: string[]; failed: { name: string; error: string }[] }> {
|
|
205
|
-
const passed: string[] = [];
|
|
206
|
-
const failed: { name: string; error: string }[] = [];
|
|
207
|
-
for (const testCase of cases) {
|
|
208
|
-
try {
|
|
209
|
-
await testCase.run();
|
|
210
|
-
passed.push(testCase.name);
|
|
211
|
-
} catch (error) {
|
|
212
|
-
failed.push({
|
|
213
|
-
name: testCase.name,
|
|
214
|
-
error: error instanceof Error ? error.message : String(error),
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
return { passed, failed };
|
|
219
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,464 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
import { WalletNotConnectedError, WalletTransactionError, WalletReadyState, WalletNotReadyError, WalletConnectionError, PrivateDataPermission, WalletAdapterNetwork, } from "@miden-sdk/miden-wallet-adapter-base";
|
|
3
|
-
// Mock @miden-sdk/miden-sdk before importing adapter
|
|
4
|
-
vi.mock("@miden-sdk/miden-sdk", () => ({
|
|
5
|
-
Note: {
|
|
6
|
-
deserialize: vi.fn((bytes) => ({ mockNote: true, bytes })),
|
|
7
|
-
},
|
|
8
|
-
}));
|
|
9
|
-
import { Note } from "@miden-sdk/miden-sdk";
|
|
10
|
-
import { MidenWalletAdapter } from "../adapter";
|
|
11
|
-
function createMockWallet(overrides = {}) {
|
|
12
|
-
return {
|
|
13
|
-
address: "0xmockaddress",
|
|
14
|
-
publicKey: new Uint8Array([1, 2, 3]),
|
|
15
|
-
connect: vi.fn(),
|
|
16
|
-
disconnect: vi.fn(),
|
|
17
|
-
requestSend: vi.fn().mockResolvedValue({ transactionId: "tx-send-1" }),
|
|
18
|
-
requestConsume: vi
|
|
19
|
-
.fn()
|
|
20
|
-
.mockResolvedValue({ transactionId: "tx-consume-1" }),
|
|
21
|
-
requestTransaction: vi
|
|
22
|
-
.fn()
|
|
23
|
-
.mockResolvedValue({ transactionId: "tx-custom-1" }),
|
|
24
|
-
requestAssets: vi
|
|
25
|
-
.fn()
|
|
26
|
-
.mockResolvedValue({ assets: [{ faucetId: "f1", amount: "100" }] }),
|
|
27
|
-
requestGuardianInfo: vi.fn().mockResolvedValue({
|
|
28
|
-
guardianInfo: {
|
|
29
|
-
isGuardianAccount: false,
|
|
30
|
-
guardianEndpoint: null,
|
|
31
|
-
guardianProvider: null,
|
|
32
|
-
guardianSyncStatus: null,
|
|
33
|
-
},
|
|
34
|
-
}),
|
|
35
|
-
requestPrivateNotes: vi
|
|
36
|
-
.fn()
|
|
37
|
-
.mockResolvedValue({ privateNotes: [{ noteId: "n1" }] }),
|
|
38
|
-
signBytes: vi
|
|
39
|
-
.fn()
|
|
40
|
-
.mockResolvedValue({ signature: new Uint8Array([9, 8, 7]) }),
|
|
41
|
-
importPrivateNote: vi.fn().mockResolvedValue({ noteId: "imported-note-1" }),
|
|
42
|
-
requestConsumableNotes: vi
|
|
43
|
-
.fn()
|
|
44
|
-
.mockResolvedValue({ consumableNotes: [{ noteId: "cn1" }] }),
|
|
45
|
-
createAccount: vi.fn().mockResolvedValue({ accountId: "account-123" }),
|
|
46
|
-
waitForTransaction: vi.fn(),
|
|
47
|
-
...overrides,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
// Helper to set up a connected adapter by injecting mock wallet via window
|
|
51
|
-
async function createConnectedAdapter() {
|
|
52
|
-
const mockWallet = createMockWallet();
|
|
53
|
-
window.midenWallet = mockWallet;
|
|
54
|
-
const adapter = new MidenWalletAdapter();
|
|
55
|
-
// Force readyState to Installed so connect works
|
|
56
|
-
adapter.readyState = WalletReadyState.Installed;
|
|
57
|
-
await adapter.connect(PrivateDataPermission.UponRequest, WalletAdapterNetwork.Testnet);
|
|
58
|
-
return { adapter, mockWallet };
|
|
59
|
-
}
|
|
60
|
-
describe("MidenWalletAdapter", () => {
|
|
61
|
-
beforeEach(() => {
|
|
62
|
-
vi.clearAllMocks();
|
|
63
|
-
delete window.midenWallet;
|
|
64
|
-
delete window.miden;
|
|
65
|
-
});
|
|
66
|
-
describe("constructor", () => {
|
|
67
|
-
it("initializes with null address and not connecting", () => {
|
|
68
|
-
const adapter = new MidenWalletAdapter();
|
|
69
|
-
expect(adapter.address).toBeNull();
|
|
70
|
-
expect(adapter.publicKey).toBeNull();
|
|
71
|
-
expect(adapter.connecting).toBe(false);
|
|
72
|
-
expect(adapter.connected).toBe(false);
|
|
73
|
-
});
|
|
74
|
-
it("sets readyState to NotDetected when wallet is not present", () => {
|
|
75
|
-
const adapter = new MidenWalletAdapter();
|
|
76
|
-
expect(adapter.readyState).toBe(WalletReadyState.NotDetected);
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
describe("not connected errors", () => {
|
|
80
|
-
const methods = [
|
|
81
|
-
{
|
|
82
|
-
name: "requestSend",
|
|
83
|
-
call: (a) => a.requestSend({
|
|
84
|
-
senderAddress: "s",
|
|
85
|
-
recipientAddress: "r",
|
|
86
|
-
faucetId: "f",
|
|
87
|
-
noteType: "public",
|
|
88
|
-
amount: 1,
|
|
89
|
-
}),
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
name: "requestConsume",
|
|
93
|
-
call: (a) => a.requestConsume({
|
|
94
|
-
faucetId: "f",
|
|
95
|
-
noteId: "n",
|
|
96
|
-
noteType: "public",
|
|
97
|
-
amount: 1,
|
|
98
|
-
}),
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
name: "requestTransaction",
|
|
102
|
-
call: (a) => a.requestTransaction({ type: "send", payload: {} }),
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
name: "requestAssets",
|
|
106
|
-
call: (a) => a.requestAssets(),
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
name: "requestGuardianInfo",
|
|
110
|
-
call: (a) => a.requestGuardianInfo(),
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
name: "requestPrivateNotes",
|
|
114
|
-
call: (a) => a.requestPrivateNotes("All"),
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
name: "signBytes",
|
|
118
|
-
call: (a) => a.signBytes(new Uint8Array([1]), "word"),
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
name: "importPrivateNote",
|
|
122
|
-
call: (a) => a.importPrivateNote(new Uint8Array([1])),
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
name: "requestConsumableNotes",
|
|
126
|
-
call: (a) => a.requestConsumableNotes(),
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
name: "waitForTransaction",
|
|
130
|
-
call: (a) => a.waitForTransaction("tx-1"),
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
name: "createAccount",
|
|
134
|
-
call: (a) => a.createAccount(),
|
|
135
|
-
},
|
|
136
|
-
];
|
|
137
|
-
for (const { name, call } of methods) {
|
|
138
|
-
it(`${name} throws WalletNotConnectedError when not connected`, async () => {
|
|
139
|
-
const adapter = new MidenWalletAdapter();
|
|
140
|
-
const errorHandler = vi.fn();
|
|
141
|
-
adapter.on("error", errorHandler);
|
|
142
|
-
await expect(call(adapter)).rejects.toThrow(WalletNotConnectedError);
|
|
143
|
-
expect(errorHandler).toHaveBeenCalledWith(expect.any(WalletNotConnectedError));
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
describe("connected operations", () => {
|
|
148
|
-
it("requestSend delegates to wallet and returns transactionId", async () => {
|
|
149
|
-
const { adapter, mockWallet } = await createConnectedAdapter();
|
|
150
|
-
const tx = {
|
|
151
|
-
senderAddress: "s",
|
|
152
|
-
recipientAddress: "r",
|
|
153
|
-
faucetId: "f",
|
|
154
|
-
noteType: "public",
|
|
155
|
-
amount: 100,
|
|
156
|
-
};
|
|
157
|
-
const result = await adapter.requestSend(tx);
|
|
158
|
-
expect(mockWallet.requestSend).toHaveBeenCalledWith(tx);
|
|
159
|
-
expect(result).toBe("tx-send-1");
|
|
160
|
-
});
|
|
161
|
-
it("requestConsume delegates to wallet and returns transactionId", async () => {
|
|
162
|
-
const { adapter, mockWallet } = await createConnectedAdapter();
|
|
163
|
-
const tx = {
|
|
164
|
-
faucetId: "f",
|
|
165
|
-
noteId: "n",
|
|
166
|
-
noteType: "public",
|
|
167
|
-
amount: 50,
|
|
168
|
-
};
|
|
169
|
-
const result = await adapter.requestConsume(tx);
|
|
170
|
-
expect(mockWallet.requestConsume).toHaveBeenCalledWith(tx);
|
|
171
|
-
expect(result).toBe("tx-consume-1");
|
|
172
|
-
});
|
|
173
|
-
it("requestSend rejects (does not resolve undefined) when the wallet returns no transactionId (0xMiden/wallet#314)", async () => {
|
|
174
|
-
const { adapter, mockWallet } = await createConnectedAdapter();
|
|
175
|
-
// The wallet resolved "successfully" but without a transaction id — meaning
|
|
176
|
-
// the transaction was never submitted; the dApp must see a rejection.
|
|
177
|
-
mockWallet.requestSend.mockResolvedValue({});
|
|
178
|
-
const errorHandler = vi.fn();
|
|
179
|
-
adapter.on("error", errorHandler);
|
|
180
|
-
await expect(adapter.requestSend({
|
|
181
|
-
senderAddress: "s",
|
|
182
|
-
recipientAddress: "r",
|
|
183
|
-
faucetId: "f",
|
|
184
|
-
noteType: "public",
|
|
185
|
-
amount: 1,
|
|
186
|
-
})).rejects.toThrow(WalletTransactionError);
|
|
187
|
-
expect(errorHandler).toHaveBeenCalled();
|
|
188
|
-
});
|
|
189
|
-
it("requestConsume rejects when the wallet returns no transactionId (0xMiden/wallet#314)", async () => {
|
|
190
|
-
const { adapter, mockWallet } = await createConnectedAdapter();
|
|
191
|
-
mockWallet.requestConsume.mockResolvedValue({ transactionId: undefined });
|
|
192
|
-
await expect(adapter.requestConsume({
|
|
193
|
-
faucetId: "f",
|
|
194
|
-
noteId: "n",
|
|
195
|
-
noteType: "public",
|
|
196
|
-
amount: 1,
|
|
197
|
-
})).rejects.toThrow(WalletTransactionError);
|
|
198
|
-
});
|
|
199
|
-
it("requestTransaction (custom) rejects when the wallet returns no transactionId (0xMiden/wallet#314)", async () => {
|
|
200
|
-
const { adapter, mockWallet } = await createConnectedAdapter();
|
|
201
|
-
mockWallet.requestTransaction.mockResolvedValue({});
|
|
202
|
-
await expect(adapter.requestTransaction({
|
|
203
|
-
type: "custom",
|
|
204
|
-
payload: {},
|
|
205
|
-
})).rejects.toThrow(WalletTransactionError);
|
|
206
|
-
expect(mockWallet.requestTransaction).toHaveBeenCalled();
|
|
207
|
-
});
|
|
208
|
-
it("requestTransaction routes a Consume transaction to wallet.requestConsume (issue #88)", async () => {
|
|
209
|
-
const { adapter, mockWallet } = await createConnectedAdapter();
|
|
210
|
-
const payload = {
|
|
211
|
-
faucetId: "f",
|
|
212
|
-
noteId: "n",
|
|
213
|
-
noteType: "public",
|
|
214
|
-
amount: 50,
|
|
215
|
-
};
|
|
216
|
-
const result = await adapter.requestTransaction({
|
|
217
|
-
type: "consume",
|
|
218
|
-
payload,
|
|
219
|
-
});
|
|
220
|
-
expect(mockWallet.requestConsume).toHaveBeenCalledWith(payload);
|
|
221
|
-
expect(mockWallet.requestTransaction).not.toHaveBeenCalled();
|
|
222
|
-
expect(result).toBe("tx-consume-1");
|
|
223
|
-
});
|
|
224
|
-
it("requestTransaction routes a Send transaction to wallet.requestSend", async () => {
|
|
225
|
-
const { adapter, mockWallet } = await createConnectedAdapter();
|
|
226
|
-
const payload = {
|
|
227
|
-
senderAddress: "s",
|
|
228
|
-
recipientAddress: "r",
|
|
229
|
-
faucetId: "f",
|
|
230
|
-
noteType: "public",
|
|
231
|
-
amount: 100,
|
|
232
|
-
};
|
|
233
|
-
const result = await adapter.requestTransaction({
|
|
234
|
-
type: "send",
|
|
235
|
-
payload,
|
|
236
|
-
});
|
|
237
|
-
expect(mockWallet.requestSend).toHaveBeenCalledWith(payload);
|
|
238
|
-
expect(mockWallet.requestTransaction).not.toHaveBeenCalled();
|
|
239
|
-
expect(result).toBe("tx-send-1");
|
|
240
|
-
});
|
|
241
|
-
it("requestTransaction forwards a Custom transaction to wallet.requestTransaction", async () => {
|
|
242
|
-
const { adapter, mockWallet } = await createConnectedAdapter();
|
|
243
|
-
const tx = {
|
|
244
|
-
type: "custom",
|
|
245
|
-
payload: {
|
|
246
|
-
address: "addr",
|
|
247
|
-
recipientAddress: "r",
|
|
248
|
-
transactionRequest: "base64-request",
|
|
249
|
-
},
|
|
250
|
-
};
|
|
251
|
-
const result = await adapter.requestTransaction(tx);
|
|
252
|
-
expect(mockWallet.requestTransaction).toHaveBeenCalledWith(tx);
|
|
253
|
-
expect(result).toBe("tx-custom-1");
|
|
254
|
-
});
|
|
255
|
-
it("requestTransaction forwards a bare (typeless) payload to wallet.requestTransaction", async () => {
|
|
256
|
-
const { adapter, mockWallet } = await createConnectedAdapter();
|
|
257
|
-
const tx = {
|
|
258
|
-
address: "addr",
|
|
259
|
-
transactionRequest: "base64-request",
|
|
260
|
-
};
|
|
261
|
-
const result = await adapter.requestTransaction(tx);
|
|
262
|
-
expect(mockWallet.requestTransaction).toHaveBeenCalledWith(tx);
|
|
263
|
-
expect(result).toBe("tx-custom-1");
|
|
264
|
-
});
|
|
265
|
-
it("requestAssets returns assets array", async () => {
|
|
266
|
-
const { adapter } = await createConnectedAdapter();
|
|
267
|
-
const assets = await adapter.requestAssets();
|
|
268
|
-
expect(assets).toEqual([{ faucetId: "f1", amount: "100" }]);
|
|
269
|
-
});
|
|
270
|
-
it("requestGuardianInfo unwraps the provider result", async () => {
|
|
271
|
-
const { adapter, mockWallet } = await createConnectedAdapter();
|
|
272
|
-
mockWallet.requestGuardianInfo.mockResolvedValue({
|
|
273
|
-
guardianInfo: {
|
|
274
|
-
isGuardianAccount: true,
|
|
275
|
-
guardianEndpoint: "https://g",
|
|
276
|
-
guardianProvider: "gateway",
|
|
277
|
-
guardianSyncStatus: "in-sync",
|
|
278
|
-
},
|
|
279
|
-
});
|
|
280
|
-
const guardianInfo = await adapter.requestGuardianInfo();
|
|
281
|
-
expect(guardianInfo).toEqual({
|
|
282
|
-
isGuardianAccount: true,
|
|
283
|
-
guardianEndpoint: "https://g",
|
|
284
|
-
guardianProvider: "gateway",
|
|
285
|
-
guardianSyncStatus: "in-sync",
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
it("requestGuardianInfo emits error and wraps failure", async () => {
|
|
289
|
-
const { adapter, mockWallet } = await createConnectedAdapter();
|
|
290
|
-
mockWallet.requestGuardianInfo.mockRejectedValue(new Error("network error"));
|
|
291
|
-
const errorHandler = vi.fn();
|
|
292
|
-
adapter.on("error", errorHandler);
|
|
293
|
-
await expect(adapter.requestGuardianInfo()).rejects.toThrow(WalletTransactionError);
|
|
294
|
-
expect(errorHandler).toHaveBeenCalledWith(expect.any(WalletTransactionError));
|
|
295
|
-
});
|
|
296
|
-
it("requestPrivateNotes returns notes array", async () => {
|
|
297
|
-
const { adapter } = await createConnectedAdapter();
|
|
298
|
-
const notes = await adapter.requestPrivateNotes("All");
|
|
299
|
-
expect(notes).toEqual([{ noteId: "n1" }]);
|
|
300
|
-
});
|
|
301
|
-
it("signBytes returns signature", async () => {
|
|
302
|
-
const { adapter } = await createConnectedAdapter();
|
|
303
|
-
const sig = await adapter.signBytes(new Uint8Array([1, 2]), "word");
|
|
304
|
-
expect(sig).toEqual(new Uint8Array([9, 8, 7]));
|
|
305
|
-
});
|
|
306
|
-
it("importPrivateNote returns noteId", async () => {
|
|
307
|
-
const { adapter } = await createConnectedAdapter();
|
|
308
|
-
const noteId = await adapter.importPrivateNote(new Uint8Array([1]));
|
|
309
|
-
expect(noteId).toBe("imported-note-1");
|
|
310
|
-
});
|
|
311
|
-
it("requestConsumableNotes returns consumable notes", async () => {
|
|
312
|
-
const { adapter } = await createConnectedAdapter();
|
|
313
|
-
const notes = await adapter.requestConsumableNotes();
|
|
314
|
-
expect(notes).toEqual([{ noteId: "cn1" }]);
|
|
315
|
-
});
|
|
316
|
-
it("createAccount returns accountId without params", async () => {
|
|
317
|
-
const { adapter, mockWallet } = await createConnectedAdapter();
|
|
318
|
-
const result = await adapter.createAccount();
|
|
319
|
-
expect(mockWallet.createAccount).toHaveBeenCalledWith(undefined);
|
|
320
|
-
expect(result).toBe("account-123");
|
|
321
|
-
});
|
|
322
|
-
it("createAccount returns accountId with params", async () => {
|
|
323
|
-
const { adapter, mockWallet } = await createConnectedAdapter();
|
|
324
|
-
const params = {
|
|
325
|
-
accountType: "RegularAccountUpdatableCode",
|
|
326
|
-
storageMode: "private",
|
|
327
|
-
};
|
|
328
|
-
const result = await adapter.createAccount(params);
|
|
329
|
-
expect(mockWallet.createAccount).toHaveBeenCalledWith(params);
|
|
330
|
-
expect(result).toBe("account-123");
|
|
331
|
-
});
|
|
332
|
-
it("createAccount passes customComponents to wallet", async () => {
|
|
333
|
-
const { adapter, mockWallet } = await createConnectedAdapter();
|
|
334
|
-
const component = new Uint8Array([1, 2, 3]);
|
|
335
|
-
const params = { customComponents: [component] };
|
|
336
|
-
const result = await adapter.createAccount(params);
|
|
337
|
-
expect(mockWallet.createAccount).toHaveBeenCalledWith(params);
|
|
338
|
-
expect(result).toBe("account-123");
|
|
339
|
-
});
|
|
340
|
-
});
|
|
341
|
-
describe("waitForTransaction", () => {
|
|
342
|
-
it("success path: deserializes notes and returns TransactionOutput", async () => {
|
|
343
|
-
// base64 of [1,2,3] = "AQID"
|
|
344
|
-
const { adapter, mockWallet } = await createConnectedAdapter();
|
|
345
|
-
mockWallet.waitForTransaction.mockResolvedValue({
|
|
346
|
-
txHash: "hash-123",
|
|
347
|
-
outputNotes: ["AQID", "BAUG"],
|
|
348
|
-
});
|
|
349
|
-
const result = await adapter.waitForTransaction("tx-1");
|
|
350
|
-
expect(result.txHash).toBe("hash-123");
|
|
351
|
-
expect(result.outputNotes).toHaveLength(2);
|
|
352
|
-
expect(Note.deserialize).toHaveBeenCalledTimes(2);
|
|
353
|
-
});
|
|
354
|
-
it("error path: throws WalletTransactionError on errorMessage", async () => {
|
|
355
|
-
const { adapter, mockWallet } = await createConnectedAdapter();
|
|
356
|
-
mockWallet.waitForTransaction.mockResolvedValue({
|
|
357
|
-
errorMessage: "tx failed",
|
|
358
|
-
});
|
|
359
|
-
const errorHandler = vi.fn();
|
|
360
|
-
adapter.on("error", errorHandler);
|
|
361
|
-
await expect(adapter.waitForTransaction("tx-1")).rejects.toThrow(WalletTransactionError);
|
|
362
|
-
expect(errorHandler).toHaveBeenCalled();
|
|
363
|
-
});
|
|
364
|
-
});
|
|
365
|
-
describe("connect", () => {
|
|
366
|
-
it("throws WalletNotReadyError if readyState is not Installed", async () => {
|
|
367
|
-
const adapter = new MidenWalletAdapter();
|
|
368
|
-
const errorHandler = vi.fn();
|
|
369
|
-
adapter.on("error", errorHandler);
|
|
370
|
-
await expect(adapter.connect(PrivateDataPermission.UponRequest, WalletAdapterNetwork.Testnet)).rejects.toThrow(WalletNotReadyError);
|
|
371
|
-
});
|
|
372
|
-
it("connects successfully and emits connect event", async () => {
|
|
373
|
-
const mockWallet = createMockWallet();
|
|
374
|
-
window.midenWallet = mockWallet;
|
|
375
|
-
const adapter = new MidenWalletAdapter();
|
|
376
|
-
adapter.readyState = WalletReadyState.Installed;
|
|
377
|
-
const connectHandler = vi.fn();
|
|
378
|
-
adapter.on("connect", connectHandler);
|
|
379
|
-
await adapter.connect(PrivateDataPermission.UponRequest, WalletAdapterNetwork.Testnet);
|
|
380
|
-
expect(adapter.address).toBe("0xmockaddress");
|
|
381
|
-
expect(adapter.publicKey).toEqual(new Uint8Array([1, 2, 3]));
|
|
382
|
-
expect(adapter.connected).toBe(true);
|
|
383
|
-
expect(adapter.connecting).toBe(false);
|
|
384
|
-
expect(connectHandler).toHaveBeenCalledWith("0xmockaddress");
|
|
385
|
-
});
|
|
386
|
-
it("throws WalletConnectionError if wallet has no address after connect", async () => {
|
|
387
|
-
const mockWallet = createMockWallet({ address: undefined });
|
|
388
|
-
window.midenWallet = mockWallet;
|
|
389
|
-
const adapter = new MidenWalletAdapter();
|
|
390
|
-
adapter.readyState = WalletReadyState.Installed;
|
|
391
|
-
await expect(adapter.connect(PrivateDataPermission.UponRequest, WalletAdapterNetwork.Testnet)).rejects.toThrow(WalletConnectionError);
|
|
392
|
-
});
|
|
393
|
-
it("re-drives the handshake and re-emits `connect` when called while already connected, repopulating the current account (0xMiden/wallet#470)", async () => {
|
|
394
|
-
const { adapter, mockWallet } = await createConnectedAdapter();
|
|
395
|
-
const connectHandler = vi.fn();
|
|
396
|
-
adapter.on("connect", connectHandler);
|
|
397
|
-
// The in-app browser preserves this adapter's JS context across a
|
|
398
|
-
// park/restore, so a re-opened dApp finds `connected === true`. The
|
|
399
|
-
// old early-return made a second connect() a silent no-op, so a
|
|
400
|
-
// re-mounted consumer that re-subscribed to events never got its
|
|
401
|
-
// account back ("Connect Wallet works only once"). A repeat connect()
|
|
402
|
-
// must re-drive the wallet handshake — which returns the current
|
|
403
|
-
// account — and re-emit `connect` so the consumer repopulates.
|
|
404
|
-
await adapter.connect(PrivateDataPermission.UponRequest, WalletAdapterNetwork.Testnet);
|
|
405
|
-
expect(mockWallet.connect).toHaveBeenCalledTimes(2);
|
|
406
|
-
expect(connectHandler).toHaveBeenCalledWith("0xmockaddress");
|
|
407
|
-
expect(adapter.connected).toBe(true);
|
|
408
|
-
expect(adapter.address).toBe("0xmockaddress");
|
|
409
|
-
});
|
|
410
|
-
it("ignores a concurrent connect() while one is still in flight", async () => {
|
|
411
|
-
let releaseFirstConnect = () => { };
|
|
412
|
-
const mockWallet = createMockWallet({
|
|
413
|
-
connect: vi.fn(() => new Promise((resolve) => {
|
|
414
|
-
releaseFirstConnect = resolve;
|
|
415
|
-
})),
|
|
416
|
-
});
|
|
417
|
-
window.midenWallet = mockWallet;
|
|
418
|
-
const adapter = new MidenWalletAdapter();
|
|
419
|
-
adapter.readyState = WalletReadyState.Installed;
|
|
420
|
-
// First connect stays in flight (its wallet.connect() promise is pending).
|
|
421
|
-
const first = adapter.connect(PrivateDataPermission.UponRequest, WalletAdapterNetwork.Testnet);
|
|
422
|
-
// A second call while `connecting` is true must be a guarded no-op.
|
|
423
|
-
await adapter.connect(PrivateDataPermission.UponRequest, WalletAdapterNetwork.Testnet);
|
|
424
|
-
expect(mockWallet.connect).toHaveBeenCalledTimes(1);
|
|
425
|
-
releaseFirstConnect();
|
|
426
|
-
await first;
|
|
427
|
-
expect(adapter.connected).toBe(true);
|
|
428
|
-
});
|
|
429
|
-
});
|
|
430
|
-
describe("disconnect", () => {
|
|
431
|
-
it("clears address, publicKey, and emits disconnect", async () => {
|
|
432
|
-
const { adapter } = await createConnectedAdapter();
|
|
433
|
-
const disconnectHandler = vi.fn();
|
|
434
|
-
adapter.on("disconnect", disconnectHandler);
|
|
435
|
-
await adapter.disconnect();
|
|
436
|
-
expect(adapter.address).toBeNull();
|
|
437
|
-
expect(adapter.publicKey).toBeNull();
|
|
438
|
-
expect(adapter.connected).toBe(false);
|
|
439
|
-
expect(disconnectHandler).toHaveBeenCalled();
|
|
440
|
-
});
|
|
441
|
-
it("emits error if wallet.disconnect fails", async () => {
|
|
442
|
-
const { adapter, mockWallet } = await createConnectedAdapter();
|
|
443
|
-
mockWallet.disconnect.mockRejectedValue(new Error("disconnect failed"));
|
|
444
|
-
const errorHandler = vi.fn();
|
|
445
|
-
adapter.on("error", errorHandler);
|
|
446
|
-
await adapter.disconnect();
|
|
447
|
-
expect(errorHandler).toHaveBeenCalled();
|
|
448
|
-
});
|
|
449
|
-
});
|
|
450
|
-
describe("error wrapping", () => {
|
|
451
|
-
it("wraps wallet method failures in WalletTransactionError", async () => {
|
|
452
|
-
const { adapter, mockWallet } = await createConnectedAdapter();
|
|
453
|
-
mockWallet.requestSend.mockRejectedValue(new Error("network error"));
|
|
454
|
-
await expect(adapter.requestSend({
|
|
455
|
-
senderAddress: "s",
|
|
456
|
-
recipientAddress: "r",
|
|
457
|
-
faucetId: "f",
|
|
458
|
-
noteType: "public",
|
|
459
|
-
amount: 1,
|
|
460
|
-
})).rejects.toThrow(WalletTransactionError);
|
|
461
|
-
});
|
|
462
|
-
});
|
|
463
|
-
});
|
|
464
|
-
//# sourceMappingURL=adapter.test.js.map
|