@pasosdejesus/m 0.23.1 → 0.25.0

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.
Files changed (200) hide show
  1. package/dist/CHANGELOG.md +45 -1
  2. package/dist/e2e/full-wallet.d.ts +41 -0
  3. package/dist/e2e/full-wallet.d.ts.map +1 -0
  4. package/dist/e2e/full-wallet.js +138 -0
  5. package/dist/e2e/full-wallet.js.map +1 -0
  6. package/dist/e2e/full-wallet.test.d.ts +2 -0
  7. package/dist/e2e/full-wallet.test.d.ts.map +1 -0
  8. package/dist/e2e/full-wallet.test.js +37 -0
  9. package/dist/e2e/full-wallet.test.js.map +1 -0
  10. package/dist/e2e/index.d.ts +4 -0
  11. package/dist/e2e/index.d.ts.map +1 -1
  12. package/dist/e2e/index.js +2 -0
  13. package/dist/e2e/index.js.map +1 -1
  14. package/dist/e2e/local-node.d.ts +22 -0
  15. package/dist/e2e/local-node.d.ts.map +1 -0
  16. package/dist/e2e/local-node.js +144 -0
  17. package/dist/e2e/local-node.js.map +1 -0
  18. package/dist/e2e/local-node.test.d.ts +2 -0
  19. package/dist/e2e/local-node.test.d.ts.map +1 -0
  20. package/dist/e2e/local-node.test.js +43 -0
  21. package/dist/e2e/local-node.test.js.map +1 -0
  22. package/dist/i18n/locales/cli.d.ts.map +1 -1
  23. package/dist/i18n/locales/cli.js +2 -0
  24. package/dist/i18n/locales/cli.js.map +1 -1
  25. package/dist/m.js +7 -1
  26. package/dist/m.js.map +1 -1
  27. package/dist/tasks/e2e-summary.d.ts +13 -0
  28. package/dist/tasks/e2e-summary.d.ts.map +1 -0
  29. package/dist/tasks/e2e-summary.js +117 -0
  30. package/dist/tasks/e2e-summary.js.map +1 -0
  31. package/dist/tasks/e2e.d.ts +29 -0
  32. package/dist/tasks/e2e.d.ts.map +1 -1
  33. package/dist/tasks/e2e.js +130 -36
  34. package/dist/tasks/e2e.js.map +1 -1
  35. package/dist/tasks/e2e.test.d.ts +2 -0
  36. package/dist/tasks/e2e.test.d.ts.map +1 -0
  37. package/dist/tasks/e2e.test.js +108 -0
  38. package/dist/tasks/e2e.test.js.map +1 -0
  39. package/dist/tasks/wallet.d.ts.map +1 -1
  40. package/dist/tasks/wallet.js +14 -25
  41. package/dist/tasks/wallet.js.map +1 -1
  42. package/dist/test-utils/index.d.ts +1 -0
  43. package/dist/test-utils/index.d.ts.map +1 -1
  44. package/dist/test-utils/index.js +1 -0
  45. package/dist/test-utils/index.js.map +1 -1
  46. package/dist/test-utils/wallet-mocks.d.ts +44 -0
  47. package/dist/test-utils/wallet-mocks.d.ts.map +1 -0
  48. package/dist/test-utils/wallet-mocks.js +114 -0
  49. package/dist/test-utils/wallet-mocks.js.map +1 -0
  50. package/dist/test-utils/wallet-mocks.test.d.ts +2 -0
  51. package/dist/test-utils/wallet-mocks.test.d.ts.map +1 -0
  52. package/dist/test-utils/wallet-mocks.test.js +55 -0
  53. package/dist/test-utils/wallet-mocks.test.js.map +1 -0
  54. package/dist/test-utils/wallet-setup.d.ts +2 -0
  55. package/dist/test-utils/wallet-setup.d.ts.map +1 -0
  56. package/dist/test-utils/wallet-setup.js +23 -0
  57. package/dist/test-utils/wallet-setup.js.map +1 -0
  58. package/dist/test-utils/wallet-setup.test.d.ts +2 -0
  59. package/dist/test-utils/wallet-setup.test.d.ts.map +1 -0
  60. package/dist/test-utils/wallet-setup.test.js +18 -0
  61. package/dist/test-utils/wallet-setup.test.js.map +1 -0
  62. package/dist/wallet/biometric.d.ts +33 -0
  63. package/dist/wallet/biometric.d.ts.map +1 -0
  64. package/dist/wallet/biometric.js +88 -0
  65. package/dist/wallet/biometric.js.map +1 -0
  66. package/dist/wallet/biometric.test.d.ts +2 -0
  67. package/dist/wallet/biometric.test.d.ts.map +1 -0
  68. package/dist/wallet/biometric.test.js +44 -0
  69. package/dist/wallet/biometric.test.js.map +1 -0
  70. package/dist/wallet/crypto.d.ts +42 -0
  71. package/dist/wallet/crypto.d.ts.map +1 -0
  72. package/dist/wallet/crypto.js +127 -0
  73. package/dist/wallet/crypto.js.map +1 -0
  74. package/dist/wallet/crypto.test.d.ts +2 -0
  75. package/dist/wallet/crypto.test.d.ts.map +1 -0
  76. package/dist/wallet/crypto.test.js +34 -0
  77. package/dist/wallet/crypto.test.js.map +1 -0
  78. package/dist/wallet/destinations.d.ts +20 -0
  79. package/dist/wallet/destinations.d.ts.map +1 -0
  80. package/dist/wallet/destinations.js +98 -0
  81. package/dist/wallet/destinations.js.map +1 -0
  82. package/dist/wallet/destinations.test.d.ts +2 -0
  83. package/dist/wallet/destinations.test.d.ts.map +1 -0
  84. package/dist/wallet/destinations.test.js +34 -0
  85. package/dist/wallet/destinations.test.js.map +1 -0
  86. package/dist/wallet/external-provider.d.ts +33 -0
  87. package/dist/wallet/external-provider.d.ts.map +1 -0
  88. package/dist/wallet/external-provider.js +108 -0
  89. package/dist/wallet/external-provider.js.map +1 -0
  90. package/dist/wallet/external-provider.test.d.ts +2 -0
  91. package/dist/wallet/external-provider.test.d.ts.map +1 -0
  92. package/dist/wallet/external-provider.test.js +44 -0
  93. package/dist/wallet/external-provider.test.js.map +1 -0
  94. package/dist/wallet/index.d.ts +14 -0
  95. package/dist/wallet/index.d.ts.map +1 -0
  96. package/dist/wallet/index.js +24 -0
  97. package/dist/wallet/index.js.map +1 -0
  98. package/dist/wallet/indexeddb.test.d.ts +2 -0
  99. package/dist/wallet/indexeddb.test.d.ts.map +1 -0
  100. package/dist/wallet/indexeddb.test.js +28 -0
  101. package/dist/wallet/indexeddb.test.js.map +1 -0
  102. package/dist/wallet/kdf.test.d.ts +2 -0
  103. package/dist/wallet/kdf.test.d.ts.map +1 -0
  104. package/dist/wallet/kdf.test.js +54 -0
  105. package/dist/wallet/kdf.test.js.map +1 -0
  106. package/dist/wallet/next/components/InAppWalletSetup.d.ts +11 -0
  107. package/dist/wallet/next/components/InAppWalletSetup.d.ts.map +1 -0
  108. package/dist/wallet/next/components/InAppWalletSetup.js +55 -0
  109. package/dist/wallet/next/components/InAppWalletSetup.js.map +1 -0
  110. package/dist/wallet/next/components/InAppWalletUnlock.d.ts +9 -0
  111. package/dist/wallet/next/components/InAppWalletUnlock.d.ts.map +1 -0
  112. package/dist/wallet/next/components/InAppWalletUnlock.js +29 -0
  113. package/dist/wallet/next/components/InAppWalletUnlock.js.map +1 -0
  114. package/dist/wallet/next/components.test.d.ts +2 -0
  115. package/dist/wallet/next/components.test.d.ts.map +1 -0
  116. package/dist/wallet/next/components.test.js +122 -0
  117. package/dist/wallet/next/components.test.js.map +1 -0
  118. package/dist/wallet/next/i18n.d.ts +63 -0
  119. package/dist/wallet/next/i18n.d.ts.map +1 -0
  120. package/dist/wallet/next/i18n.js +64 -0
  121. package/dist/wallet/next/i18n.js.map +1 -0
  122. package/dist/wallet/next/index.d.ts +7 -0
  123. package/dist/wallet/next/index.d.ts.map +1 -0
  124. package/dist/wallet/next/index.js +12 -0
  125. package/dist/wallet/next/index.js.map +1 -0
  126. package/dist/wallet/next/namespaces.d.ts +17 -0
  127. package/dist/wallet/next/namespaces.d.ts.map +1 -0
  128. package/dist/wallet/next/namespaces.js +17 -0
  129. package/dist/wallet/next/namespaces.js.map +1 -0
  130. package/dist/wallet/next/useInAppWallet.d.ts +55 -0
  131. package/dist/wallet/next/useInAppWallet.d.ts.map +1 -0
  132. package/dist/wallet/next/useInAppWallet.js +261 -0
  133. package/dist/wallet/next/useInAppWallet.js.map +1 -0
  134. package/dist/wallet/next/useInAppWallet.test.d.ts +2 -0
  135. package/dist/wallet/next/useInAppWallet.test.d.ts.map +1 -0
  136. package/dist/wallet/next/useInAppWallet.test.js +261 -0
  137. package/dist/wallet/next/useInAppWallet.test.js.map +1 -0
  138. package/dist/wallet/provider.d.ts +38 -0
  139. package/dist/wallet/provider.d.ts.map +1 -0
  140. package/dist/wallet/provider.js +285 -0
  141. package/dist/wallet/provider.js.map +1 -0
  142. package/dist/wallet/provider.test.d.ts +2 -0
  143. package/dist/wallet/provider.test.d.ts.map +1 -0
  144. package/dist/wallet/provider.test.js +170 -0
  145. package/dist/wallet/provider.test.js.map +1 -0
  146. package/dist/wallet/signer.d.ts +55 -0
  147. package/dist/wallet/signer.d.ts.map +1 -0
  148. package/dist/wallet/signer.js +91 -0
  149. package/dist/wallet/signer.js.map +1 -0
  150. package/dist/wallet/signer.test.d.ts +2 -0
  151. package/dist/wallet/signer.test.d.ts.map +1 -0
  152. package/dist/wallet/signer.test.js +62 -0
  153. package/dist/wallet/signer.test.js.map +1 -0
  154. package/dist/wallet/siwe.d.ts +2 -0
  155. package/dist/wallet/siwe.d.ts.map +1 -0
  156. package/dist/wallet/siwe.js +5 -0
  157. package/dist/wallet/siwe.js.map +1 -0
  158. package/dist/wallet/storage/file.d.ts +19 -0
  159. package/dist/wallet/storage/file.d.ts.map +1 -0
  160. package/dist/wallet/storage/file.js +48 -0
  161. package/dist/wallet/storage/file.js.map +1 -0
  162. package/dist/wallet/storage/idb.d.ts +25 -0
  163. package/dist/wallet/storage/idb.d.ts.map +1 -0
  164. package/dist/wallet/storage/idb.js +58 -0
  165. package/dist/wallet/storage/idb.js.map +1 -0
  166. package/dist/wallet/storage/index.d.ts +5 -0
  167. package/dist/wallet/storage/index.d.ts.map +1 -0
  168. package/dist/wallet/storage/index.js +4 -0
  169. package/dist/wallet/storage/index.js.map +1 -0
  170. package/dist/wallet/storage/indexeddb.d.ts +8 -0
  171. package/dist/wallet/storage/indexeddb.d.ts.map +1 -0
  172. package/dist/wallet/storage/indexeddb.js +18 -0
  173. package/dist/wallet/storage/indexeddb.js.map +1 -0
  174. package/dist/wallet/storage/memory.d.ts +10 -0
  175. package/dist/wallet/storage/memory.d.ts.map +1 -0
  176. package/dist/wallet/storage/memory.js +19 -0
  177. package/dist/wallet/storage/memory.js.map +1 -0
  178. package/dist/wallet/storage.test.d.ts +2 -0
  179. package/dist/wallet/storage.test.d.ts.map +1 -0
  180. package/dist/wallet/storage.test.js +103 -0
  181. package/dist/wallet/storage.test.js.map +1 -0
  182. package/dist/wallet/types.d.ts +69 -0
  183. package/dist/wallet/types.d.ts.map +1 -0
  184. package/dist/wallet/types.js +6 -0
  185. package/dist/wallet/types.js.map +1 -0
  186. package/dist/wallet/wallet.d.ts +81 -0
  187. package/dist/wallet/wallet.d.ts.map +1 -0
  188. package/dist/wallet/wallet.js +358 -0
  189. package/dist/wallet/wallet.js.map +1 -0
  190. package/dist/wallet/wallet.test.d.ts +2 -0
  191. package/dist/wallet/wallet.test.d.ts.map +1 -0
  192. package/dist/wallet/wallet.test.js +150 -0
  193. package/dist/wallet/wallet.test.js.map +1 -0
  194. package/dist/wallet/web-authn.d.ts +78 -0
  195. package/dist/wallet/web-authn.d.ts.map +1 -0
  196. package/dist/wallet/web-authn.js +250 -0
  197. package/dist/wallet/web-authn.js.map +1 -0
  198. package/package.json +18 -4
  199. package/src/e2e/README.md +67 -0
  200. package/src/wallet/README.md +119 -0
@@ -0,0 +1,150 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import { verifyMessage } from 'viem';
3
+ import { createWallet, deleteWallet, getWalletInfo, hasWallet, importWallet, isUnlocked, isValidPassword, lockWallet, signMessage, setWalletChain, unlockWallet, } from './wallet.js';
4
+ import { MemoryStorage } from './storage/memory.js';
5
+ import { KDF_ITERATIONS_FLOOR, encryptSecret } from './crypto.js';
6
+ import { privateKeyFromMnemonic, isValidMnemonic } from './signer.js';
7
+ import { signSIWE } from './siwe.js';
8
+ const HARDHAT_MNEMONIC = 'test test test test test test test test test test test junk';
9
+ const HARDHAT_ADDRESS = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266';
10
+ const password = '12345678';
11
+ describe('wallet', () => {
12
+ let storage;
13
+ beforeEach(async () => {
14
+ storage = new MemoryStorage();
15
+ await lockWallet();
16
+ });
17
+ it('creates a wallet with a fresh mnemonic and stores it encrypted', async () => {
18
+ const { walletInfo, mnemonic } = await createWallet({ password: password, storage });
19
+ expect(mnemonic.split(' ')).toHaveLength(12);
20
+ expect(walletInfo.address).toMatch(/^0x[0-9a-fA-F]{40}$/);
21
+ expect(walletInfo.chain).toBe('celoSepolia');
22
+ expect(await hasWallet(storage)).toBe(true);
23
+ expect(isUnlocked()).toBe(true);
24
+ const record = await storage.get();
25
+ expect(record?.cipher.data).not.toContain(mnemonic);
26
+ expect(record?.address).toBe(walletInfo.address);
27
+ });
28
+ it('rejects a password shorter than 8 characters', async () => {
29
+ await expect(createWallet({ password: '123', storage })).rejects.toThrow(/at least 8 characters/i);
30
+ await expect(createWallet({ password: 'short', storage })).rejects.toThrow(/at least 8 characters/i);
31
+ await expect(createWallet({ password: '123456', storage })).rejects.toThrow(/at least 8 characters/i);
32
+ });
33
+ it('upgrades the work factor of an old record when it is unlocked', async () => {
34
+ const legacy = new MemoryStorage();
35
+ const secret = await encryptSecret(JSON.stringify({ v: 1, privateKey: privateKeyFromMnemonic(HARDHAT_MNEMONIC) }), password, 1000);
36
+ await legacy.set({
37
+ version: 1,
38
+ address: HARDHAT_ADDRESS,
39
+ chain: 'celoSepolia',
40
+ createdAt: Date.now(),
41
+ kdf: secret.kdf,
42
+ cipher: secret.cipher,
43
+ });
44
+ expect((await unlockWallet(password, legacy)).address).toBe(HARDHAT_ADDRESS);
45
+ const upgraded = await legacy.get();
46
+ expect(upgraded.kdf.iterations).toBeGreaterThanOrEqual(KDF_ITERATIONS_FLOOR);
47
+ await lockWallet();
48
+ expect((await unlockWallet(password, legacy)).address).toBe(HARDHAT_ADDRESS);
49
+ });
50
+ it('accepts a passphrase of 8+ characters and unlocks with it', async () => {
51
+ const { walletInfo } = await createWallet({ password: 'correct horse', storage });
52
+ expect(walletInfo.address).toMatch(/^0x[a-fA-F0-9]{40}$/);
53
+ await lockWallet();
54
+ const unlocked = await unlockWallet('correct horse', storage);
55
+ expect(unlocked.address).toBe(walletInfo.address);
56
+ });
57
+ it('isValidPassword accepts 8+ character secrets only', () => {
58
+ expect(isValidPassword('12345678')).toBe(true);
59
+ expect(isValidPassword('correct horse')).toBe(true);
60
+ expect(isValidPassword('123456')).toBe(false);
61
+ expect(isValidPassword('short')).toBe(false);
62
+ expect(isValidPassword(' short ')).toBe(false);
63
+ });
64
+ it('imports a known mnemonic and derives the expected address', async () => {
65
+ const info = await importWallet({ mnemonic: HARDHAT_MNEMONIC, password: password, storage });
66
+ expect(info.address).toBe(HARDHAT_ADDRESS);
67
+ await lockWallet();
68
+ expect(await hasWallet(storage)).toBe(true);
69
+ const unlocked = await unlockWallet(password, storage);
70
+ expect(unlocked.address).toBe(HARDHAT_ADDRESS);
71
+ });
72
+ it('rejects a mnemonic that is not BIP39', async () => {
73
+ const typo = 'test test test test test test test test test test test tset';
74
+ await expect(importWallet({ mnemonic: typo, password: password, storage })).rejects.toThrow(/invalid-mnemonic/);
75
+ expect(await hasWallet(storage)).toBe(false);
76
+ const badChecksum = 'test test test test test test test test test test test test';
77
+ await expect(importWallet({ mnemonic: badChecksum, password: password, storage })).rejects.toThrow(/invalid-mnemonic/);
78
+ await expect(importWallet({ mnemonic: HARDHAT_MNEMONIC.split(' ').slice(0, 11).join(' '), password: password, storage })).rejects.toThrow(/invalid-mnemonic/);
79
+ });
80
+ it('accepts the phrases it generates and a valid 24-word phrase', async () => {
81
+ const { mnemonic } = await createWallet({ password: password, storage });
82
+ expect(await isValidMnemonic(mnemonic)).toBe(true);
83
+ expect(await isValidMnemonic(` ${HARDHAT_MNEMONIC.toUpperCase()} `)).toBe(true);
84
+ expect(await isValidMnemonic('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art')).toBe(true);
85
+ expect(await isValidMnemonic('not a mnemonic at all')).toBe(false);
86
+ });
87
+ it('reports the wallet info without unlocking', async () => {
88
+ await importWallet({ mnemonic: HARDHAT_MNEMONIC, password: password, storage });
89
+ await lockWallet();
90
+ const info = await getWalletInfo(storage);
91
+ expect(info?.address).toBe(HARDHAT_ADDRESS);
92
+ expect(isUnlocked()).toBe(false);
93
+ });
94
+ it('rejects a wrong password when unlocking', async () => {
95
+ await importWallet({ mnemonic: HARDHAT_MNEMONIC, password: password, storage });
96
+ await lockWallet();
97
+ await expect(unlockWallet('99999999', storage)).rejects.toThrow(/Wrong password/i);
98
+ });
99
+ it('signs messages only while unlocked', async () => {
100
+ await importWallet({ mnemonic: HARDHAT_MNEMONIC, password: password, storage });
101
+ const signature = await signMessage('hello');
102
+ expect(signature).toMatch(/^0x[0-9a-f]{130}$/i);
103
+ expect(await verifyMessage({ address: HARDHAT_ADDRESS, message: 'hello', signature })).toBe(true);
104
+ await lockWallet();
105
+ await expect(signMessage('hello')).rejects.toThrow(/locked/i);
106
+ });
107
+ it('signs SIWE messages that verify against the wallet address', async () => {
108
+ await importWallet({ mnemonic: HARDHAT_MNEMONIC, password: password, storage });
109
+ const message = [
110
+ 'learn.tg:9001 wants you to sign in with your Ethereum account:',
111
+ HARDHAT_ADDRESS,
112
+ '',
113
+ 'Sign in to Learn Through Games.',
114
+ '',
115
+ 'URI: https://learn.tg:9001',
116
+ 'Version: 1',
117
+ 'Chain ID: 11142220',
118
+ 'Nonce: 12345678',
119
+ 'Issued At: 2026-09-15T00:00:00.000Z',
120
+ ].join('\n');
121
+ const signature = await signSIWE(message);
122
+ expect(await verifyMessage({ address: HARDHAT_ADDRESS, message, signature })).toBe(true);
123
+ });
124
+ it('switches the declared chain without invalidating the secret', async () => {
125
+ await importWallet({ mnemonic: HARDHAT_MNEMONIC, password: password, storage });
126
+ expect((await getWalletInfo(storage))?.chain).toBe('celoSepolia');
127
+ const info = await setWalletChain('celo', storage);
128
+ expect(info.chain).toBe('celo');
129
+ expect((await getWalletInfo(storage))?.chain).toBe('celo');
130
+ expect(await signMessage('hola')).toMatch(/^0x/);
131
+ await lockWallet();
132
+ const unlocked = await unlockWallet(password, storage);
133
+ expect(unlocked.address).toBe(HARDHAT_ADDRESS);
134
+ expect(unlocked.chain).toBe('celo');
135
+ });
136
+ it('rejects an unsupported chain and a switch with no wallet', async () => {
137
+ await importWallet({ mnemonic: HARDHAT_MNEMONIC, password: password, storage });
138
+ await expect(setWalletChain('optimism', storage)).rejects.toThrow(/unsupported chain/i);
139
+ await expect(setWalletChain('celo', new MemoryStorage())).rejects.toThrow(/no in-app wallet/i);
140
+ });
141
+ it('deletes the wallet and locks it', async () => {
142
+ await importWallet({ mnemonic: HARDHAT_MNEMONIC, password: password, storage });
143
+ await deleteWallet(storage);
144
+ expect(await hasWallet(storage)).toBe(false);
145
+ expect(await getWalletInfo(storage)).toBeNull();
146
+ expect(isUnlocked()).toBe(false);
147
+ await expect(unlockWallet(password, storage)).rejects.toThrow(/no in-app wallet/i);
148
+ });
149
+ });
150
+ //# sourceMappingURL=wallet.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wallet.test.js","sourceRoot":"","sources":["../../src/wallet/wallet.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ /** Overrides the WebAuthn relying-party display name. */
2
+ export declare function setRpName(name: string): void;
3
+ /**
4
+ * Ventana de gracia de la verificación del usuario (decisión del operador,
5
+ * 2026-09-20). Una vez que el usuario verificó con el dispositivo (gesto L1 o
6
+ * desbloqueo L2), un movimiento de fondos **a un destino ya conocido** no vuelve a
7
+ * pedir el gesto dentro de esta ventana. Es deliberadamente corta: 15 minutos.
8
+ *
9
+ * Además, un **destino nuevo siempre exige el gesto** (ver `destinations.ts`),
10
+ * aunque la ventana esté abierta: eso frena el drenaje automatizado/hostil a una
11
+ * dirección que el usuario nunca usó.
12
+ *
13
+ * Compromiso explícito: en un dispositivo desbloqueado y robado, dentro de la
14
+ * ventana se puede mover dinero a direcciones ya usadas sin gesto. La clave/PIN
15
+ * nunca se guarda y los 12 palabras siguen siendo la recuperación.
16
+ */
17
+ export declare const USER_VERIFICATION_GRACE_MS: number;
18
+ /** Records that the user just verified (called on a successful assertion/PRF). */
19
+ export declare function markUserVerified(now?: number): void;
20
+ /** Forgets the verification (called when the wallet locks or is deleted). */
21
+ export declare function clearUserVerification(): void;
22
+ /** Whether a user verification happened within the grace window. */
23
+ export declare function hasRecentUserVerification(windowMs?: number, now?: number): boolean;
24
+ export interface PlatformSupport {
25
+ /** `navigator.credentials` with WebAuthn is present at all. */
26
+ webauthn: boolean;
27
+ /** The device can verify the user (Face ID, fingerprint, device password). */
28
+ userVerifying: boolean;
29
+ /** Whether the engine advertises the `prf` extension (may be unknown). */
30
+ prf: boolean | null;
31
+ }
32
+ export declare function toArrayBuffer(bytes: Uint8Array): ArrayBuffer;
33
+ /** Reports what the device can do, never throwing. */
34
+ export declare function detectPlatformSupport(): Promise<PlatformSupport>;
35
+ export interface PrfCredential {
36
+ credentialId: string;
37
+ /** The authenticator accepted the `prf` extension for this credential. */
38
+ prfEnabled: boolean;
39
+ }
40
+ /**
41
+ * Creates a discoverable platform credential with the `prf` extension. The
42
+ * credential is stored by the password manager / device, exactly like a passkey,
43
+ * and can be removed by the user from there.
44
+ */
45
+ export declare function createPrfCredential(userName: string): Promise<PrfCredential>;
46
+ /**
47
+ * Evaluates the PRF for `salt` and returns its 32-byte secret, asking the user to
48
+ * verify (Face ID / fingerprint / device password). Returns `null` when the
49
+ * authenticator does not provide a result instead of throwing.
50
+ */
51
+ export declare function evaluatePrf(credentialId: string, salt: Uint8Array): Promise<Uint8Array | null>;
52
+ /**
53
+ * Layer L1: asks the user to verify **with the device** (Face ID / fingerprint /
54
+ * device password) before a sensitive operation, and returns the assertion. It does not
55
+ * need the `prf` extension: any user-verified assertion is enough. Throws
56
+ * `no-webauthn` when there is no platform authenticator and `NotAllowedError` when
57
+ * the user cancels.
58
+ */
59
+ export declare function assertUserVerification(credentialId: string, { timeoutMs }?: {
60
+ timeoutMs?: number;
61
+ }): Promise<void>;
62
+ /** HKDF(PRF secret) → AES-GCM key that wraps the wallet key. */ export declare function deriveWrappingKey(prfSecret: Uint8Array, hkdfSalt: Uint8Array): Promise<CryptoKey>;
63
+ /**
64
+ * Tells the authenticator that a passkey is gone (R-#246 §14 item 1, adopted from
65
+ * Rabby: `signalUnknownCredential`). Without it, disabling the biometric unlock or
66
+ * deleting the wallet left the passkey orphaned in the OS list until the user
67
+ * removed it by hand.
68
+ *
69
+ * Best effort: the API only exists in Chrome 132+ and must never break the caller.
70
+ */
71
+ export declare function signalUnknownCredential(credentialId: string, rpId?: string): Promise<void>;
72
+ /**
73
+ * True when a WebAuthn/signing failure means "the user cancelled" (R-#246 §14
74
+ * item 4). We used to match only `NotAllowedError`, so an abort surfaced as an
75
+ * unknown error.
76
+ */
77
+ export declare function isUserCancelledError(error: unknown): boolean;
78
+ //# sourceMappingURL=web-authn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-authn.d.ts","sourceRoot":"","sources":["../../src/wallet/web-authn.ts"],"names":[],"mappings":"AAeA,yDAAyD;AACzD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAE5C;AAID;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,0BAA0B,QAAiB,CAAA;AAIxD,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAmB,GAAG,IAAI,CAE/D;AAED,6EAA6E;AAC7E,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,oEAAoE;AACpE,wBAAgB,yBAAyB,CACvC,QAAQ,GAAE,MAAmC,EAC7C,GAAG,GAAE,MAAmB,GACvB,OAAO,CAET;AAED,MAAM,WAAW,eAAe;IAC9B,+DAA+D;IAC/D,QAAQ,EAAE,OAAO,CAAA;IACjB,8EAA8E;IAC9E,aAAa,EAAE,OAAO,CAAA;IACtB,0EAA0E;IAC1E,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;CACpB;AASD,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAE5D;AAUD,sDAAsD;AACtD,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,eAAe,CAAC,CAmCtE;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,0EAA0E;IAC1E,UAAU,EAAE,OAAO,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAsClF;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CA2BpG;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,MAAM,EACpB,EAAE,SAAsB,EAAE,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACtD,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED,gEAAgE,CAAA,wBAAsB,iBAAiB,CACrG,SAAS,EAAE,UAAU,EACrB,QAAQ,EAAE,UAAU,GACnB,OAAO,CAAC,SAAS,CAAC,CAkBpB;AAQD;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,YAAY,EAAE,MAAM,EACpB,IAAI,GAAE,MAAyB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAK5D"}
@@ -0,0 +1,250 @@
1
+ /**
2
+ * WebAuthn helpers for the in-app wallet (layers L1/L2 of
3
+ * https://github.com/pasosdeJesus/learn.tg/issues/246).
4
+ *
5
+ * Measured on real phones (2026-09-18): `PublicKeyCredential` and the `prf`
6
+ * extension exist in Chrome/Brave for Android and in Safari (18+), and do **not**
7
+ * exist in the in-app browsers of Rabby, MetaMask, OneKey and OKX. Every function
8
+ * here is therefore optional and must degrade silently: nothing may throw just
9
+ * because the device has no platform authenticator.
10
+ */
11
+ import { fromBase64, toBase64 } from './crypto.js';
12
+ const TIMEOUT_MS = 120_000;
13
+ /** Relying-party display name (a consumer overrides it with `setRpName`). */
14
+ let rpName = 'Pasos de Jesús';
15
+ /** Overrides the WebAuthn relying-party display name. */
16
+ export function setRpName(name) {
17
+ if (name)
18
+ rpName = name;
19
+ }
20
+ /** HKDF info string: separates this use of a PRF secret from any other. */
21
+ const HKDF_INFO = 'learn.tg/pdj-wallet/prf/v1';
22
+ /**
23
+ * Ventana de gracia de la verificación del usuario (decisión del operador,
24
+ * 2026-09-20). Una vez que el usuario verificó con el dispositivo (gesto L1 o
25
+ * desbloqueo L2), un movimiento de fondos **a un destino ya conocido** no vuelve a
26
+ * pedir el gesto dentro de esta ventana. Es deliberadamente corta: 15 minutos.
27
+ *
28
+ * Además, un **destino nuevo siempre exige el gesto** (ver `destinations.ts`),
29
+ * aunque la ventana esté abierta: eso frena el drenaje automatizado/hostil a una
30
+ * dirección que el usuario nunca usó.
31
+ *
32
+ * Compromiso explícito: en un dispositivo desbloqueado y robado, dentro de la
33
+ * ventana se puede mover dinero a direcciones ya usadas sin gesto. La clave/PIN
34
+ * nunca se guarda y los 12 palabras siguen siendo la recuperación.
35
+ */
36
+ export const USER_VERIFICATION_GRACE_MS = 15 * 60 * 1000;
37
+ let lastUserVerificationAt = 0;
38
+ /** Records that the user just verified (called on a successful assertion/PRF). */
39
+ export function markUserVerified(now = Date.now()) {
40
+ lastUserVerificationAt = now;
41
+ }
42
+ /** Forgets the verification (called when the wallet locks or is deleted). */
43
+ export function clearUserVerification() {
44
+ lastUserVerificationAt = 0;
45
+ }
46
+ /** Whether a user verification happened within the grace window. */
47
+ export function hasRecentUserVerification(windowMs = USER_VERIFICATION_GRACE_MS, now = Date.now()) {
48
+ return lastUserVerificationAt > 0 && now - lastUserVerificationAt < windowMs;
49
+ }
50
+ function credentials() {
51
+ const c = globalThis.navigator;
52
+ return c?.credentials ?? null;
53
+ }
54
+ export function toArrayBuffer(bytes) {
55
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
56
+ }
57
+ function randomBytes(length) {
58
+ const cryptoObj = globalThis.crypto;
59
+ if (!cryptoObj?.getRandomValues)
60
+ throw new Error('crypto.getRandomValues is not available');
61
+ const bytes = new Uint8Array(length);
62
+ cryptoObj.getRandomValues(bytes);
63
+ return bytes;
64
+ }
65
+ /** Reports what the device can do, never throwing. */
66
+ export async function detectPlatformSupport() {
67
+ const hasPublicKey = typeof globalThis.PublicKeyCredential !== 'undefined';
68
+ const container = credentials();
69
+ if (!hasPublicKey || !container?.create || !container.get) {
70
+ return { webauthn: false, userVerifying: false, prf: null };
71
+ }
72
+ let userVerifying = false;
73
+ const ask = PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable;
74
+ if (typeof ask === 'function') {
75
+ try {
76
+ userVerifying = await ask();
77
+ }
78
+ catch {
79
+ userVerifying = false;
80
+ }
81
+ }
82
+ let prf = null;
83
+ const caps = PublicKeyCredential.getClientCapabilities;
84
+ if (typeof caps === 'function') {
85
+ try {
86
+ const result = await caps();
87
+ // Chrome 133+ exposes 'extension:prf'; older builds expose 'extension:hmacCreateSecret'.
88
+ const value = result['extension:prf'] ?? result['extension:hmacCreateSecret'];
89
+ if (typeof value === 'boolean')
90
+ prf = value;
91
+ }
92
+ catch {
93
+ prf = null;
94
+ }
95
+ }
96
+ return { webauthn: true, userVerifying, prf };
97
+ }
98
+ /**
99
+ * Creates a discoverable platform credential with the `prf` extension. The
100
+ * credential is stored by the password manager / device, exactly like a passkey,
101
+ * and can be removed by the user from there.
102
+ */
103
+ export async function createPrfCredential(userName) {
104
+ const container = credentials();
105
+ if (!container?.create)
106
+ throw new Error('WebAuthn is not available on this device');
107
+ const credential = (await container.create({
108
+ publicKey: {
109
+ challenge: toArrayBuffer(randomBytes(32)),
110
+ rp: { name: rpName },
111
+ user: {
112
+ id: toArrayBuffer(randomBytes(16)),
113
+ name: userName,
114
+ displayName: userName,
115
+ },
116
+ pubKeyCredParams: [
117
+ { type: 'public-key', alg: -7 },
118
+ { type: 'public-key', alg: -257 },
119
+ ],
120
+ authenticatorSelection: {
121
+ authenticatorAttachment: 'platform',
122
+ residentKey: 'required',
123
+ userVerification: 'required',
124
+ },
125
+ timeout: TIMEOUT_MS,
126
+ attestation: 'none',
127
+ extensions: { prf: {} },
128
+ },
129
+ }));
130
+ if (!credential)
131
+ throw new Error('The browser did not return a credential');
132
+ const results = credential.getClientExtensionResults();
133
+ return {
134
+ credentialId: toBase64(new Uint8Array(credential.rawId)),
135
+ prfEnabled: results?.prf?.enabled === true,
136
+ };
137
+ }
138
+ /**
139
+ * Evaluates the PRF for `salt` and returns its 32-byte secret, asking the user to
140
+ * verify (Face ID / fingerprint / device password). Returns `null` when the
141
+ * authenticator does not provide a result instead of throwing.
142
+ */
143
+ export async function evaluatePrf(credentialId, salt) {
144
+ const container = credentials();
145
+ if (!container?.get)
146
+ throw new Error('WebAuthn is not available on this device');
147
+ const assertion = (await container.get({
148
+ publicKey: {
149
+ challenge: toArrayBuffer(randomBytes(32)),
150
+ allowCredentials: [
151
+ {
152
+ type: 'public-key',
153
+ id: toArrayBuffer(fromBase64(credentialId)),
154
+ transports: ['internal', 'hybrid'],
155
+ },
156
+ ],
157
+ userVerification: 'required',
158
+ timeout: TIMEOUT_MS,
159
+ extensions: { prf: { eval: { first: toArrayBuffer(salt) } } },
160
+ },
161
+ }));
162
+ if (!assertion)
163
+ return null;
164
+ markUserVerified();
165
+ const results = assertion.getClientExtensionResults();
166
+ const first = results?.prf?.results?.first;
167
+ return first ? new Uint8Array(first) : null;
168
+ }
169
+ /**
170
+ * Layer L1: asks the user to verify **with the device** (Face ID / fingerprint /
171
+ * device password) before a sensitive operation, and returns the assertion. It does not
172
+ * need the `prf` extension: any user-verified assertion is enough. Throws
173
+ * `no-webauthn` when there is no platform authenticator and `NotAllowedError` when
174
+ * the user cancels.
175
+ */
176
+ export async function assertUserVerification(credentialId, { timeoutMs = TIMEOUT_MS } = {}) {
177
+ const container = credentials();
178
+ if (!container?.get)
179
+ throw new Error('no-webauthn');
180
+ const assertion = (await container.get({
181
+ publicKey: {
182
+ challenge: toArrayBuffer(randomBytes(32)),
183
+ allowCredentials: [
184
+ {
185
+ type: 'public-key',
186
+ id: toArrayBuffer(fromBase64(credentialId)),
187
+ transports: ['internal', 'hybrid'],
188
+ },
189
+ ],
190
+ userVerification: 'required',
191
+ timeout: timeoutMs,
192
+ },
193
+ }));
194
+ if (!assertion)
195
+ throw new Error('auth-failed');
196
+ markUserVerified();
197
+ }
198
+ /** HKDF(PRF secret) → AES-GCM key that wraps the wallet key. */ export async function deriveWrappingKey(prfSecret, hkdfSalt) {
199
+ const subtle = globalThis.crypto?.subtle;
200
+ if (!subtle)
201
+ throw new Error('Web Crypto (crypto.subtle) is not available');
202
+ const material = await subtle.importKey('raw', toArrayBuffer(prfSecret), 'HKDF', false, [
203
+ 'deriveKey',
204
+ ]);
205
+ return subtle.deriveKey({
206
+ name: 'HKDF',
207
+ hash: 'SHA-256',
208
+ salt: toArrayBuffer(hkdfSalt),
209
+ info: toArrayBuffer(new TextEncoder().encode(HKDF_INFO)),
210
+ }, material, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);
211
+ }
212
+ /** rpId used by the passkeys of this wallet (the page's host). */
213
+ function relyingPartyId() {
214
+ const location = globalThis.location;
215
+ return location?.hostname || 'localhost';
216
+ }
217
+ /**
218
+ * Tells the authenticator that a passkey is gone (R-#246 §14 item 1, adopted from
219
+ * Rabby: `signalUnknownCredential`). Without it, disabling the biometric unlock or
220
+ * deleting the wallet left the passkey orphaned in the OS list until the user
221
+ * removed it by hand.
222
+ *
223
+ * Best effort: the API only exists in Chrome 132+ and must never break the caller.
224
+ */
225
+ export async function signalUnknownCredential(credentialId, rpId = relyingPartyId()) {
226
+ if (!credentialId)
227
+ return;
228
+ const signal = globalThis.PublicKeyCredential?.signalUnknownCredential;
229
+ if (typeof signal !== 'function')
230
+ return;
231
+ try {
232
+ await signal({ rpId, credentialId });
233
+ }
234
+ catch {
235
+ // el passkey queda huérfano, como antes de este cambio
236
+ }
237
+ }
238
+ /**
239
+ * True when a WebAuthn/signing failure means "the user cancelled" (R-#246 §14
240
+ * item 4). We used to match only `NotAllowedError`, so an abort surfaced as an
241
+ * unknown error.
242
+ */
243
+ export function isUserCancelledError(error) {
244
+ const name = error?.name?.toLowerCase() ?? '';
245
+ if (name === 'notallowederror' || name === 'aborterror')
246
+ return true;
247
+ const message = error instanceof Error ? error.message : String(error ?? '');
248
+ return /cancel|abort|notallowed/i.test(message);
249
+ }
250
+ //# sourceMappingURL=web-authn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-authn.js","sourceRoot":"","sources":["../../src/wallet/web-authn.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pasosdejesus/m",
3
- "version": "0.23.1",
3
+ "version": "0.25.0",
4
4
  "description": "Motor para crear y gestionar proyectos al estilo Pasos de Jesus.",
5
5
  "author": "",
6
6
  "license": "ISC",
@@ -103,6 +103,18 @@
103
103
  "./e2e": {
104
104
  "import": "./dist/e2e/index.js",
105
105
  "types": "./dist/e2e/index.d.ts"
106
+ },
107
+ "./wallet": {
108
+ "import": "./dist/wallet/index.js",
109
+ "types": "./dist/wallet/index.d.ts"
110
+ },
111
+ "./wallet/next": {
112
+ "import": "./dist/wallet/next/index.js",
113
+ "types": "./dist/wallet/next/index.d.ts"
114
+ },
115
+ "./wallet/storage": {
116
+ "import": "./dist/wallet/storage/index.js",
117
+ "types": "./dist/wallet/storage/index.d.ts"
106
118
  }
107
119
  },
108
120
  "bin": {
@@ -118,7 +130,8 @@
118
130
  "src/shadcn_components/README.md",
119
131
  "src/test-utils/README.md",
120
132
  "src/blockchain/README.md",
121
- "src/e2e/README.md"
133
+ "src/e2e/README.md",
134
+ "src/wallet/README.md"
122
135
  ],
123
136
  "dependencies": {
124
137
  "dotenv": "^17.4.2",
@@ -127,8 +140,8 @@
127
140
  "picocolors": "^1.1.1"
128
141
  },
129
142
  "optionalDependencies": {
130
- "@pasosdejesus/edr": "0.1.2",
131
- "@pasosdejesus/contract-test": "0.1.4"
143
+ "@pasosdejesus/contract-test": "0.1.4",
144
+ "@pasosdejesus/edr": "0.1.2"
132
145
  },
133
146
  "devDependencies": {
134
147
  "@radix-ui/react-accordion": "^1.2.12",
@@ -161,6 +174,7 @@
161
174
  "autoprefixer": "^10.5.0",
162
175
  "class-variance-authority": "^0.7.1",
163
176
  "clsx": "^2.1.1",
177
+ "fake-indexeddb": "^6.2.5",
164
178
  "jsdom": "^29.1.1",
165
179
  "kysely": "^0.28.17",
166
180
  "kysely-codegen": "^0.20.0",
package/src/e2e/README.md CHANGED
@@ -100,6 +100,73 @@ const page = await newPage(browser, address, timeout, true)
100
100
  // GoodDollarClaimButton, wallet interactions now work
101
101
  ```
102
102
 
103
+ ### Real-signature wallet (`m/REQ/14`)
104
+
105
+ ```ts
106
+ installRealSigner(page, { privateKey, name?, typedDataName? }): Promise<RealSigner>
107
+ injectEIP6963(page, { address, privateKey?, chainId?, walletName? }): Promise<void> // real signer + realSigning
108
+ setupFullWalletMock(page, { privateKey, address?, chainId?, rpcUrl?, ... }): Promise<FullWalletMock>
109
+ fullWalletMockCode(opts): string
110
+ ```
111
+
112
+ `injectEIP6963` with `privateKey` makes `personal_sign`/`eth_signTypedData_v4` return real
113
+ EIP-191/EIP-712 signatures; without it, the mock keeps the fake `0xabab…`. `setupFullWalletMock`
114
+ adds real `eth_sendTransaction`: with `rpcUrl` (opt-in) it signs in Node and broadcasts the raw
115
+ transaction, and forwards reads (`eth_call`, `eth_getBalance`, `eth_getTransactionReceipt`, …) to
116
+ that RPC. **It spends real funds**, so point it at a test network.
117
+
118
+ ```js
119
+ // Real signature, fake broadcast (no rpcUrl)
120
+ await setupFullWalletMock(page, { privateKey: process.env.PRIVATE_KEY })
121
+
122
+ // Real signature + real broadcast against Celo Sepolia (spends funds)
123
+ await setupFullWalletMock(page, { privateKey: process.env.PRIVATE_KEY, rpcUrl: process.env.RPC_URL })
124
+ ```
125
+
126
+ ### Local EVM node (`m/REQ/14`)
127
+
128
+ ```ts
129
+ startLocalRpc({ chainId?, accounts? }): Promise<LocalRpc> // { url, chainId, accounts, close() }
130
+ ```
131
+
132
+ Hermetic E2E: a JSON-RPC node over `@pasosdejesus/edr` (optional dependency, loaded
133
+ dynamically). The wallet signs for real and the node executes and confirms — no external
134
+ network, no funds. Works from viem in Node **and** from a page with `setupFullWalletMock`.
135
+
136
+ A runnable fixture lives in `packages/m/e2e/` (a minimal donation page + spec):
137
+
138
+ ```sh
139
+ cd packages/m
140
+ pnpm add -D puppeteer-core # consumer devDependency, not bundled with m
141
+ CHROME_PATH=/usr/local/bin/chromium node e2e/specs/donate.spec.mjs
142
+ ```
143
+
144
+ Lessons that cost time here (see also learn.tg's `doc/e2e-testing.md`):
145
+
146
+ - **Chrome needs headroom**: this VM already runs `ulimit -d` at 12G; if a cold Chrome
147
+ crashes, raise it (`/bin/ksh -c 'ulimit -d 7340032; exec …'`).
148
+ - **Avoid `page.click` + `waitForFunction`** for a click handler that awaits the wallet:
149
+ that pair hit `Runtime.callFunctionOn timed out` (protocolTimeout). Do the click **and**
150
+ the wait inside one `page.evaluate(async () => { … })` (the spec does exactly that).
151
+ - **Receipt hashes**: the local node re-signs, so `setupFullWalletMock` re-keys the receipt
152
+ under `keccak256(rawTransaction)` (the hash viem waits for).
153
+
154
+ ### Runner: retry and summary (`m/REQ/49` P3/P4)
155
+
156
+ ```sh
157
+ ./bin/m test:e2e --retry 2 --retry-delay 15000 # re-run failed specs (fresh WALLET_INDEX each)
158
+ ./bin/m test:e2e:summary /tmp/site.log /tmp/local.log # compare two runs
159
+ ```
160
+
161
+ - `--retry <N>` (default 0, or `E2E_RETRIES`): after the first pass, each failed spec is re-run
162
+ on its own up to N times; a spec is green **only by exit code**.
163
+ - `--retry-delay <ms>` (default 15000): pause before each retry round.
164
+ - `E2E_LOG_DIR` (default `/tmp`): the run writes `<dir>/e2e-<timestamp>.log` with one
165
+ `[e2e-result] <file> <ok|fail|skip>` line per spec per attempt (the machine-readable format).
166
+ - `test:e2e:summary <log…>` reads those lines (or the human format for older logs) and prints a
167
+ row per spec, one column per log, marking `<-- difiere` when a spec's state changes between
168
+ runs (environmental vs product failure).
169
+
103
170
  ## Environment Variables
104
171
 
105
172
  See [doc/e2e.md](../../../../doc/e2e.md) for full reference.