@prism-ing/wallet 0.1.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 (113) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE +21 -0
  3. package/README.md +596 -0
  4. package/SPEC.md +192 -0
  5. package/dist/backends/squads-recovery-backend.d.ts +59 -0
  6. package/dist/backends/squads-recovery-backend.d.ts.map +1 -0
  7. package/dist/backends/squads-recovery-backend.js +81 -0
  8. package/dist/backends/squads-recovery-backend.js.map +1 -0
  9. package/dist/backends/squads-types.d.ts +74 -0
  10. package/dist/backends/squads-types.d.ts.map +1 -0
  11. package/dist/backends/squads-types.js +22 -0
  12. package/dist/backends/squads-types.js.map +1 -0
  13. package/dist/backends/zerodev-policy-mapper.d.ts +41 -0
  14. package/dist/backends/zerodev-policy-mapper.d.ts.map +1 -0
  15. package/dist/backends/zerodev-policy-mapper.js +127 -0
  16. package/dist/backends/zerodev-policy-mapper.js.map +1 -0
  17. package/dist/backends/zerodev-session-backend.d.ts +43 -0
  18. package/dist/backends/zerodev-session-backend.d.ts.map +1 -0
  19. package/dist/backends/zerodev-session-backend.js +63 -0
  20. package/dist/backends/zerodev-session-backend.js.map +1 -0
  21. package/dist/backends/zerodev-types.d.ts +104 -0
  22. package/dist/backends/zerodev-types.d.ts.map +1 -0
  23. package/dist/backends/zerodev-types.js +13 -0
  24. package/dist/backends/zerodev-types.js.map +1 -0
  25. package/dist/create-wallet.d.ts +89 -0
  26. package/dist/create-wallet.d.ts.map +1 -0
  27. package/dist/create-wallet.js +235 -0
  28. package/dist/create-wallet.js.map +1 -0
  29. package/dist/cross-chain.d.ts +64 -0
  30. package/dist/cross-chain.d.ts.map +1 -0
  31. package/dist/cross-chain.js +200 -0
  32. package/dist/cross-chain.js.map +1 -0
  33. package/dist/errors.d.ts +115 -0
  34. package/dist/errors.d.ts.map +1 -0
  35. package/dist/errors.js +97 -0
  36. package/dist/errors.js.map +1 -0
  37. package/dist/index.d.ts +55 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +52 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/internal/base58.d.ts +8 -0
  42. package/dist/internal/base58.d.ts.map +1 -0
  43. package/dist/internal/base58.js +34 -0
  44. package/dist/internal/base58.js.map +1 -0
  45. package/dist/internal/eip712.d.ts +41 -0
  46. package/dist/internal/eip712.d.ts.map +1 -0
  47. package/dist/internal/eip712.js +182 -0
  48. package/dist/internal/eip712.js.map +1 -0
  49. package/dist/internal/file-spend-persistence.d.ts +9 -0
  50. package/dist/internal/file-spend-persistence.d.ts.map +1 -0
  51. package/dist/internal/file-spend-persistence.js +58 -0
  52. package/dist/internal/file-spend-persistence.js.map +1 -0
  53. package/dist/internal/onebalance-client.d.ts +59 -0
  54. package/dist/internal/onebalance-client.d.ts.map +1 -0
  55. package/dist/internal/onebalance-client.js +2 -0
  56. package/dist/internal/onebalance-client.js.map +1 -0
  57. package/dist/internal/onebalance-http-client.d.ts +25 -0
  58. package/dist/internal/onebalance-http-client.d.ts.map +1 -0
  59. package/dist/internal/onebalance-http-client.js +161 -0
  60. package/dist/internal/onebalance-http-client.js.map +1 -0
  61. package/dist/internal/onebalance-types.d.ts +201 -0
  62. package/dist/internal/onebalance-types.d.ts.map +1 -0
  63. package/dist/internal/onebalance-types.js +39 -0
  64. package/dist/internal/onebalance-types.js.map +1 -0
  65. package/dist/internal/platform.d.ts +14 -0
  66. package/dist/internal/platform.d.ts.map +1 -0
  67. package/dist/internal/platform.js +22 -0
  68. package/dist/internal/platform.js.map +1 -0
  69. package/dist/internal/quote-verifier.d.ts +71 -0
  70. package/dist/internal/quote-verifier.d.ts.map +1 -0
  71. package/dist/internal/quote-verifier.js +172 -0
  72. package/dist/internal/quote-verifier.js.map +1 -0
  73. package/dist/internal/recovery-manager.d.ts +29 -0
  74. package/dist/internal/recovery-manager.d.ts.map +1 -0
  75. package/dist/internal/recovery-manager.js +161 -0
  76. package/dist/internal/recovery-manager.js.map +1 -0
  77. package/dist/result.d.ts +132 -0
  78. package/dist/result.d.ts.map +1 -0
  79. package/dist/result.js +114 -0
  80. package/dist/result.js.map +1 -0
  81. package/dist/schemas.d.ts +184 -0
  82. package/dist/schemas.d.ts.map +1 -0
  83. package/dist/schemas.js +76 -0
  84. package/dist/schemas.js.map +1 -0
  85. package/dist/session-keys.d.ts +53 -0
  86. package/dist/session-keys.d.ts.map +1 -0
  87. package/dist/session-keys.js +345 -0
  88. package/dist/session-keys.js.map +1 -0
  89. package/dist/signers/node-signing-backend.d.ts +11 -0
  90. package/dist/signers/node-signing-backend.d.ts.map +1 -0
  91. package/dist/signers/node-signing-backend.js +120 -0
  92. package/dist/signers/node-signing-backend.js.map +1 -0
  93. package/dist/signers/ows-adapter.d.ts +70 -0
  94. package/dist/signers/ows-adapter.d.ts.map +1 -0
  95. package/dist/signers/ows-adapter.js +53 -0
  96. package/dist/signers/ows-adapter.js.map +1 -0
  97. package/dist/signers/ows-signing-backend.d.ts +25 -0
  98. package/dist/signers/ows-signing-backend.d.ts.map +1 -0
  99. package/dist/signers/ows-signing-backend.js +192 -0
  100. package/dist/signers/ows-signing-backend.js.map +1 -0
  101. package/dist/signers/secure-enclave-backend.d.ts +19 -0
  102. package/dist/signers/secure-enclave-backend.d.ts.map +1 -0
  103. package/dist/signers/secure-enclave-backend.js +201 -0
  104. package/dist/signers/secure-enclave-backend.js.map +1 -0
  105. package/dist/signers/secure-enclave-types.d.ts +98 -0
  106. package/dist/signers/secure-enclave-types.d.ts.map +1 -0
  107. package/dist/signers/secure-enclave-types.js +12 -0
  108. package/dist/signers/secure-enclave-types.js.map +1 -0
  109. package/dist/types.d.ts +371 -0
  110. package/dist/types.d.ts.map +1 -0
  111. package/dist/types.js +2 -0
  112. package/dist/types.js.map +1 -0
  113. package/package.json +85 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # @prism-ing/wallet
2
+
3
+ ## Unreleased
4
+
5
+ ### Minor Changes
6
+
7
+ - **Session keys**: `createSessionKeyManager()` creates policy-scoped ephemeral signers for agents. Policies constrain time, assets, amounts, recipients, and chains. Optional `SessionKeyBackend` for on-chain enforcement via ZeroDev permission validators.
8
+ - **Quote verification**: `executeCrossChain()` now verifies OneBalance quotes before signing — checks expiration, asset/amount match, sender integrity, and tamper-proof signature. Configurable via `accountAddress` and `skipVerification` params.
9
+ - **Dynamic recovery challenge**: Recovery proofs are now bound to the specific signer rotation (current + new address) and a 5-minute time window, preventing replay attacks. Replaces the previous static challenge.
10
+ - New error codes: `SESSION_KEY_EXPIRED`, `SESSION_KEY_POLICY_VIOLATION`, `QUOTE_VERIFICATION_FAILED`.
11
+ - New exports: `createSessionKeyManager`, `validateSessionOperation`, `verifyQuoteIntegrity`, `verifyEvmOperation`, `buildRecoveryChallenge`.
12
+
13
+ ## 0.1.0
14
+
15
+ ### Minor Changes
16
+
17
+ - Initial public release: OWS signing (Node.js software signer), OneBalance account abstraction, cross-chain execution, social recovery via guardian rotation.
18
+
19
+ ## 0.0.1
20
+
21
+ - Initial implementation: OWS Node.js software signer, OneBalance account abstraction, recovery manager
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Prism
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,596 @@
1
+ # @prism-ing/wallet
2
+
3
+ Self-custodial smart contract wallet SDK. Hardware-backed key security via iOS Secure Enclave, cross-chain execution via OneBalance Resource Locks, social recovery via ZeroDev (EVM) and Squads V4 (Solana). Built for agents, trading apps, and mobile wallets.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @prism-ing/wallet
9
+ ```
10
+
11
+ Optional peer dependencies (install only what you need):
12
+
13
+ ```bash
14
+ pnpm add @zerodev/sdk # On-chain session key enforcement
15
+ pnpm add @sqds/multisig # Solana social recovery
16
+ pnpm add viem # EVM utilities
17
+ ```
18
+
19
+ ## Quickstart
20
+
21
+ ```ts
22
+ import { createProductionWallet } from '@prism-ing/wallet';
23
+
24
+ const result = await createProductionWallet({
25
+ signer: { walletName: 'my-agent' },
26
+ oneBalanceApiKey: process.env.ONEBALANCE_API_KEY,
27
+ });
28
+
29
+ if (!result.ok) {
30
+ console.error(result.error.code);
31
+ process.exit(1);
32
+ }
33
+
34
+ const wallet = result.value;
35
+ console.log('EVM address:', wallet.evmAddress);
36
+ console.log('Solana address:', wallet.solanaAddress);
37
+
38
+ const balance = await wallet.getBalance();
39
+ console.log(`Balance: $${balance.totalUsd}`);
40
+ ```
41
+
42
+ Same wallet name = same keys = same addresses, every time. Keys persist encrypted at `~/.ows/`.
43
+
44
+ ## Architecture
45
+
46
+ Three systems compose to give you a persistent, cross-chain, recoverable wallet:
47
+
48
+ ```
49
+ Signing Backends (platform-specific key management)
50
+ ├── iOS Secure Enclave: keys encrypted by hardware P-256 key, biometric-gated
51
+ ├── OWS (Node.js): secp256k1 + ed25519 from BIP-39, encrypted at ~/.ows/
52
+ └── Node Software: in-memory signing via @noble/curves (dev/testing)
53
+
54
+ OneBalance (Account Abstraction + Cross-Chain)
55
+ ├── ERC-4337 counterfactual smart account from signer's public key
56
+ ├── Deterministic address: same signer = same smart account
57
+ ├── Resource Locks: instant cross-chain execution without source chain finality
58
+ └── Aggregated balance across all supported chains
59
+
60
+ Smart Contract Wallet (the actual "wallet")
61
+ ├── Signer key authorizes operations, but is NOT the wallet itself
62
+ ├── Funds live in the smart contract, not controlled by the raw key
63
+ ├── Signer can be rotated without moving funds (recovery)
64
+ └── On-chain policy enforcement via ZeroDev permission validators
65
+ ```
66
+
67
+ **Key insight:** The signer key is not the wallet. It authorizes operations on a smart contract. If compromised, guardians can rotate it out without moving funds. This separation is fundamental to every security property.
68
+
69
+ ## Private Key Management
70
+
71
+ ### Security Model by Platform
72
+
73
+ | Property | Node.js (Agent) | iOS (Mobile) |
74
+ |----------|-----------------|--------------|
75
+ | Key storage | Encrypted at `~/.ows/` (AES-256-GCM, scrypt KDF) | Encrypted by Secure Enclave P-256 key, stored in iOS Keychain |
76
+ | Auth | Passphrase or API key | Biometric (Face ID / Touch ID) |
77
+ | Key isolation | In-process only, never serialized | Decrypted in-process only after biometric, never serialized |
78
+ | EVM signing | Software secp256k1 | Software secp256k1, biometric-gated |
79
+ | Solana signing | Software ed25519 | Software ed25519, biometric-gated |
80
+ | Seed phrases | None exposed. OWS encrypts mnemonic internally. | None. Keys are non-exportable. |
81
+
82
+ ### Security Assumptions
83
+
84
+ 1. **Private keys never leave the device in plaintext.** On iOS, keys are encrypted by the Secure Enclave's hardware P-256 key and stored in the Keychain. On Node.js, keys are encrypted with scrypt + AES-256-GCM.
85
+
86
+ 2. **Signing requires authentication.** On iOS, every signing operation triggers Face ID / Touch ID via the `SecureEnclaveNativeBridge`. On Node.js, the OWS vault requires a passphrase or API key to decrypt.
87
+
88
+ 3. **No key material in `OWSKeyPair`.** The `OWSKeyPair` type contains only addresses and a platform identifier. On iOS, it stores a Keychain tag referencing the encrypted key, not the key itself.
89
+
90
+ 4. **The Secure Enclave does not support secp256k1 natively.** iOS uses the Secure Enclave's P-256 key to encrypt/decrypt secp256k1 and ed25519 private keys. Signing happens in-process after biometric-gated decryption, not inside the enclave chip.
91
+
92
+ 5. **OneBalance is a trusted co-signer.** Cross-chain execution relies on OneBalance for quote generation and settlement. The SDK mitigates this trust with [client-side quote verification](#quote-verification) but cannot independently route transactions.
93
+
94
+ ### Agent Mode
95
+
96
+ ```ts
97
+ // First run: creates wallet, encrypts keys at ~/.ows/
98
+ // Every subsequent run: loads same wallet, same addresses
99
+ const result = await createProductionWallet({
100
+ signer: { walletName: 'trading-agent' },
101
+ oneBalanceApiKey: process.env.ONEBALANCE_API_KEY,
102
+ });
103
+ ```
104
+
105
+ With passphrase protection:
106
+ ```ts
107
+ const result = await createProductionWallet({
108
+ signer: {
109
+ walletName: 'treasury',
110
+ passphrase: process.env.WALLET_PASSPHRASE,
111
+ },
112
+ oneBalanceApiKey: process.env.ONEBALANCE_API_KEY,
113
+ });
114
+ ```
115
+
116
+ With OWS agent API key (policy-gated signing):
117
+ ```ts
118
+ const result = await createProductionWallet({
119
+ signer: {
120
+ walletName: 'treasury',
121
+ apiKey: 'ows_key_a1b2c3d4...',
122
+ },
123
+ oneBalanceApiKey: process.env.ONEBALANCE_API_KEY,
124
+ });
125
+ ```
126
+
127
+ ### iOS Mobile App
128
+
129
+ ```ts
130
+ import { createProductionWallet } from '@prism-ing/wallet';
131
+ import { SecureEnclaveModule } from './native/SecureEnclaveModule';
132
+
133
+ const result = await createProductionWallet(
134
+ {
135
+ signer: { walletName: 'user' },
136
+ oneBalanceApiKey: config.oneBalanceApiKey,
137
+ },
138
+ {
139
+ nativeBridge: SecureEnclaveModule, // React Native native module
140
+ biometricPrompt: 'Authorize this trade', // Face ID / Touch ID text
141
+ recoveryBackends: { evm: evmBackend, solana: solanaBackend },
142
+ },
143
+ );
144
+ ```
145
+
146
+ On iOS, `createProductionWallet` detects the platform and auto-selects the Secure Enclave backend when `nativeBridge` is provided. The rest of the stack (OneBalance, cross-chain, recovery) works identically.
147
+
148
+ ### Custom Signer (bring your own keys)
149
+
150
+ Routers and the wallet factory accept any `PrismSigner`. No dependency on `@prism-ing/wallet` for signing:
151
+
152
+ ```ts
153
+ import type { PrismSigner } from '@prism-ing/wallet';
154
+
155
+ const mySigner: PrismSigner = {
156
+ evmAddress: myAddress,
157
+ solanaAddress: mySolanaAddress,
158
+ signMessage: (msg) => mySigningLib.sign(msg),
159
+ signTypedData: (payload) => mySigningLib.signTypedData(payload),
160
+ signTransaction: (tx) => mySigningLib.signSolana(tx),
161
+ };
162
+ ```
163
+
164
+ ## Session Keys
165
+
166
+ Session keys are ephemeral, policy-scoped signers for agents and automated processes. They wrap the root `PrismSigner` and enforce constraints on every signing operation. If a session key is compromised, the blast radius is bounded by its policy.
167
+
168
+ ### How Session Keys Improve Security
169
+
170
+ Without session keys, an agent holds the root signer and can sign anything: any amount, any token, any chain, any recipient. A compromised agent means total loss.
171
+
172
+ With session keys, the agent holds a scoped signer that the SDK restricts before every signing operation:
173
+
174
+ ```
175
+ Root Signer (full authority)
176
+ └── Session Key (policy-restricted)
177
+ ├── Can only trade USDC
178
+ ├── Max $1,000 per operation
179
+ ├── Max $5,000 total
180
+ ├── Only on Base and Arbitrum
181
+ ├── Only to whitelisted addresses
182
+ └── Expires in 1 hour
183
+ ```
184
+
185
+ **Two enforcement layers:**
186
+
187
+ 1. **Client-side (always active):** The session signer checks policy before every `signMessage`, `signTypedData`, and `signTransaction`. Violations throw immediately, preventing the signature from being produced.
188
+
189
+ 2. **On-chain (optional, via ZeroDev):** When a `SessionKeyBackend` is provided, policies are also registered as ZeroDev Kernel v3.1 permission validators. Even if a compromised client bypasses the JS check, the UserOp reverts on-chain.
190
+
191
+ ### Basic Usage
192
+
193
+ ```ts
194
+ import { createSessionKeyManager, validateSessionOperation } from '@prism-ing/wallet';
195
+
196
+ const manager = createSessionKeyManager(wallet.signer);
197
+
198
+ const session = manager.createSessionKey({
199
+ expiresAt: Math.floor(Date.now() / 1000) + 3600, // 1 hour
200
+ allowedAssets: ['ob:usdc'], // USDC only
201
+ maxAmountPerOp: '1000000000', // 1,000 USDC max per swap
202
+ maxTotalAmount: '5000000000', // 5,000 USDC cumulative cap
203
+ allowedChains: [8453, 42161], // Base + Arbitrum only
204
+ allowedRecipients: ['0xabc...'], // Restrict destinations
205
+ });
206
+
207
+ // Pre-validate before execution (optional, fail-fast)
208
+ const violation = validateSessionOperation(session, {
209
+ asset: 'ob:usdc',
210
+ amount: '500000000',
211
+ chainId: 8453,
212
+ });
213
+ if (violation !== undefined) {
214
+ console.error('Policy violation:', violation.code);
215
+ return;
216
+ }
217
+
218
+ // Use the session signer — policy enforced on every sign call
219
+ const result = await executeCrossChain(
220
+ oneBalanceClient,
221
+ session.signer, // <-- scoped signer, not root
222
+ { fromAsset: 'ob:usdc', toAsset: 'ob:eth', amount: '500000000' },
223
+ accounts,
224
+ );
225
+
226
+ // Revoke when the task is complete
227
+ manager.revokeSessionKey(session.sessionId);
228
+ ```
229
+
230
+ ### Policy Dimensions
231
+
232
+ | Dimension | Field | Effect |
233
+ |-----------|-------|--------|
234
+ | Time | `expiresAt` | Session expires after this Unix timestamp (required) |
235
+ | Assets | `allowedAssets` | Only these OneBalance asset IDs can be operated on |
236
+ | Per-op amount | `maxAmountPerOp` | Single operation cannot exceed this (smallest unit) |
237
+ | Cumulative | `maxTotalAmount` | Total spend across all operations is capped |
238
+ | Recipients | `allowedRecipients` | Only these addresses can be destinations |
239
+ | Chains | `allowedChains` | Only these chain IDs are permitted |
240
+
241
+ ### On-Chain Enforcement (ZeroDev)
242
+
243
+ For production agent deployments, client-side enforcement alone is not sufficient. A compromised process could bypass the JS policy checks. On-chain enforcement ensures the smart contract itself rejects unauthorized operations.
244
+
245
+ ```ts
246
+ import { createZeroDevSessionBackend, createSessionKeyManager } from '@prism-ing/wallet';
247
+
248
+ // Create the ZeroDev backend with your own SDK integration
249
+ const sessionBackend = createZeroDevSessionBackend({
250
+ registerOnChain: async (sessionAddress, policies) => {
251
+ // policies is ZeroDevPolicy[] — mapped from SessionKeyPolicy
252
+ // Each policy has a discriminated `type` field:
253
+ // 'timestamp' → validAfter/validUntil
254
+ // 'call' → contract/function/argument restrictions
255
+ // 'gas' → max gas spend per session
256
+ // 'rate_limit' → operations per interval
257
+ //
258
+ // Use @zerodev/permissions to register:
259
+ // 1. toECDSASigner({ signer: sessionPrivateKey })
260
+ // 2. toTimestampPolicy/toCallPolicy/toGasPolicy/toRateLimitPolicy
261
+ // 3. toPermissionValidator(client, { signer, policies, entryPoint, kernelVersion })
262
+ // 4. toInitConfig(validator) for pre-installed permissions
263
+ return txHash;
264
+ },
265
+ revokeOnChain: async (sessionAddress) => {
266
+ // Remove the permission validator from the Kernel account
267
+ return txHash;
268
+ },
269
+ defaultGasBudgetWei: 100_000_000_000_000_000n, // 0.1 ETH
270
+ callPolicyVersion: '0.0.4', // Alchemy bundler compatible
271
+ });
272
+
273
+ // Session keys are now enforced client-side AND on-chain
274
+ const manager = createSessionKeyManager(wallet.signer, sessionBackend);
275
+ ```
276
+
277
+ **Policy mapping details:**
278
+
279
+ The SDK automatically maps `SessionKeyPolicy` to ZeroDev on-chain policies:
280
+
281
+ - `expiresAt` maps to a **timestamp policy** with `validUntil`
282
+ - `allowedAssets` maps to a **call policy** restricting ERC-20 `transfer()` calls to known token contracts
283
+ - `maxAmountPerOp` maps to a `LESS_THAN_OR_EQUAL` parameter condition on the transfer amount
284
+ - `allowedRecipients` maps to `ONE_OF` parameter conditions on the transfer recipient
285
+ - A **gas policy** (default 0.1 ETH) is always included to prevent gas drain
286
+ - A **rate limit policy** is included when amount caps are set (100 ops/hour)
287
+
288
+ Use `mapSessionPolicyToZeroDev(policy)` directly if you need to inspect or customize the mapped policies before registration.
289
+
290
+ ## Social Recovery
291
+
292
+ Recovery rotates the smart contract's authorized signer without moving funds. The original key becomes useless. This works because the signer is not the wallet — it's an authorization key for a smart contract that holds the funds.
293
+
294
+ ### How Recovery Works
295
+
296
+ ```
297
+ Device Lost / Key Compromised
298
+
299
+ Generate new keys (new device, new OWS wallet, or new Secure Enclave key)
300
+
301
+ Guardian authorizes rotation (passkey, second device, or trusted contact)
302
+
303
+ EVM: ZeroDev removeValidator(old) + addValidator(new) on Kernel contract
304
+ Solana: Squads AddMember(new) + RemoveMember(old) on multisig
305
+
306
+ Same wallet address, same funds, new authorized signer
307
+ ```
308
+
309
+ ### Guardian Types
310
+
311
+ | Guardian | How It Works | Best For |
312
+ |----------|-------------|----------|
313
+ | Passkey (iCloud Keychain) | WebAuthn credential signs recovery challenge | Single-device users |
314
+ | Device | Second device's signing key authorizes rotation | Multi-device users |
315
+ | Contact | Trusted contact's EVM address authorizes rotation | High-security setups |
316
+
317
+ ### Setting Up Recovery
318
+
319
+ ```ts
320
+ const recovery = wallet.recover();
321
+
322
+ // Register a passkey guardian (iCloud Keychain)
323
+ const passkey = await recovery.setupPasskey();
324
+
325
+ // Add a second device as guardian
326
+ await recovery.addDeviceGuardian(deviceEvmAddress, deviceSolanaAddress);
327
+
328
+ // Add a trusted contact
329
+ await recovery.addContactGuardian(contactEvmAddress);
330
+ ```
331
+
332
+ ### Performing Recovery
333
+
334
+ ```ts
335
+ // On a new device, with new keys:
336
+ const newWallet = await createProductionWallet({
337
+ signer: { walletName: 'recovered-wallet' },
338
+ oneBalanceApiKey: process.env.ONEBALANCE_API_KEY,
339
+ });
340
+
341
+ const recovery = oldWalletRecoveryManager;
342
+ const recoveryTx = await recovery.initiateRecovery(newWallet.value.signer);
343
+ // Smart contract's authorized signer now points to newWallet.signer
344
+ ```
345
+
346
+ ### EVM Recovery (ZeroDev)
347
+
348
+ The EVM recovery backend rotates the Kernel v3.1 smart account's authorized validator:
349
+
350
+ ```ts
351
+ import type { EvmRecoveryBackend } from '@prism-ing/wallet';
352
+
353
+ const evmBackend: EvmRecoveryBackend = {
354
+ async addGuardian(guardianAddress) {
355
+ // Register guardian on ZeroDev social recovery module
356
+ },
357
+ async rotateValidator(oldSigner, newSigner, guardianProof) {
358
+ // ZeroDev: removeValidator(old) + addValidator(new)
359
+ return txHash;
360
+ },
361
+ };
362
+ ```
363
+
364
+ ### Solana Recovery (Squads V4)
365
+
366
+ The Squads backend manages multisig membership for Solana key recovery:
367
+
368
+ ```ts
369
+ import { createSquadsRecoveryBackend } from '@prism-ing/wallet';
370
+
371
+ const solanaBackend = createSquadsRecoveryBackend({
372
+ bridge: {
373
+ async executeConfigTransaction(actions) {
374
+ // Wraps @sqds/multisig: create config tx → propose → approve → execute
375
+ // Actions: AddMember, RemoveMember, ChangeThreshold
376
+ return txSignature;
377
+ },
378
+ async getThreshold() { return currentThreshold; },
379
+ async getMemberCount() { return currentMemberCount; },
380
+ },
381
+ autoIncrementThreshold: true, // Adding a guardian increases threshold
382
+ });
383
+ ```
384
+
385
+ **Key design decisions:**
386
+
387
+ - `configAuthority: null` — all changes go through the proposal flow (no admin override)
388
+ - `AddMember` executes before `RemoveMember` in the same config transaction, preventing a zero-member state
389
+ - Threshold auto-increments when adding guardians, requiring more consensus for future rotations
390
+ - Guardians get Voter-only permissions; the wallet signer gets full permissions
391
+
392
+ ### Recovery Security
393
+
394
+ Recovery challenges are dynamic and bound to the specific rotation:
395
+
396
+ ```
397
+ challenge = keccak256(
398
+ "prism-recovery-v1" // Domain tag (prevents cross-protocol replay)
399
+ || currentSigner // The signer being rotated away
400
+ || newSigner // The signer being rotated to
401
+ || timeWindow // 5-minute quantized timestamp
402
+ )
403
+ ```
404
+
405
+ This means:
406
+ - A proof for A to B cannot be replayed for A to C (different `newSigner`)
407
+ - A captured proof expires within 5 minutes (time-bounded)
408
+ - A proof for wallet X cannot be used on wallet Y (different `currentSigner`)
409
+
410
+ Use `buildRecoveryChallenge(currentSigner, newSigner)` for custom recovery flows.
411
+
412
+ ## Quote Verification
413
+
414
+ `executeCrossChain` automatically verifies OneBalance quotes before signing. This prevents blind-signing attacks where a compromised or man-in-the-middle API could present malicious EIP-712 payloads.
415
+
416
+ **Checks performed automatically:**
417
+
418
+ 1. Quote has not expired
419
+ 2. OneBalance tamper-proof signature is present
420
+ 3. Origin asset matches the requested `fromAsset`
421
+ 4. Origin amount is within 1% of the requested amount (configurable)
422
+ 5. Destination asset matches the requested `toAsset`
423
+ 6. EVM operation `userOp.sender` matches `accountAddress` (when provided)
424
+
425
+ ```ts
426
+ const result = await executeCrossChain(client, signer, {
427
+ fromAsset: 'ob:usdc',
428
+ toAsset: 'ob:eth',
429
+ amount: '1000000',
430
+ accountAddress: wallet.evmAddress, // enables sender verification
431
+ });
432
+
433
+ if (!result.ok && result.error.code === 'QUOTE_VERIFICATION_FAILED') {
434
+ console.error('Quote integrity failed:', result.error.reason);
435
+ }
436
+ ```
437
+
438
+ **Manual verification** (for custom flows):
439
+
440
+ ```ts
441
+ import { verifyQuoteIntegrity, verifyEvmOperation } from '@prism-ing/wallet';
442
+
443
+ const error = verifyQuoteIntegrity(quote, {
444
+ fromAsset: 'ob:usdc',
445
+ toAsset: 'ob:eth',
446
+ amount: '1000000',
447
+ accountAddress: '0x1234...',
448
+ maxOriginSlippageFraction: 0.005, // 0.5% tolerance (default 1%)
449
+ });
450
+ ```
451
+
452
+ ## Cross-Chain Execution
453
+
454
+ ```ts
455
+ import { executeCrossChain } from '@prism-ing/wallet';
456
+
457
+ const result = await executeCrossChain(
458
+ oneBalanceClient,
459
+ wallet.signer,
460
+ {
461
+ fromAsset: 'ob:usdc',
462
+ toAsset: 'ob:eth',
463
+ amount: '1000000',
464
+ slippageTolerance: 50,
465
+ accountAddress: wallet.evmAddress,
466
+ },
467
+ accounts,
468
+ );
469
+
470
+ if (result.ok) {
471
+ console.log('Quote ID:', result.value.quoteId);
472
+
473
+ // Poll execution status
474
+ const status = await wallet.getTransactionStatus(result.value.quoteId);
475
+ console.log('Status:', status.status); // 'submitted' | 'confirmed' | 'failed'
476
+ }
477
+ ```
478
+
479
+ ## API Reference
480
+
481
+ ### `createProductionWallet(config, options?)`
482
+
483
+ Creates a wallet with platform-appropriate signing + OneBalance. Returns `Result<WalletAccount, WalletError>`.
484
+
485
+ ```ts
486
+ interface WalletConfig {
487
+ signer: SignerConfig;
488
+ oneBalanceApiKey: string;
489
+ accountType?: 'kernel-v3.1-ecdsa';
490
+ chains?: number[];
491
+ recovery?: RecoveryConfig;
492
+ }
493
+
494
+ interface ProductionWalletOptions {
495
+ nativeBridge?: SecureEnclaveNativeBridge; // iOS Secure Enclave
496
+ keyTag?: string; // iOS Keychain tag
497
+ biometricPrompt?: string; // Face ID / Touch ID text
498
+ recoveryBackends?: RecoveryBackends; // Guardian backends
499
+ }
500
+ ```
501
+
502
+ ### `WalletAccount`
503
+
504
+ ```ts
505
+ interface WalletAccount {
506
+ signer: PrismSigner;
507
+ evmAddress: Address;
508
+ solanaAddress: string;
509
+ getBalance(): Promise<UnifiedBalance>;
510
+ deposit(params: DepositParams): Promise<TxResult>;
511
+ getTransactionStatus(quoteId: string): Promise<TxResult>;
512
+ recover(): RecoveryManager;
513
+ }
514
+ ```
515
+
516
+ ### `PrismSigner`
517
+
518
+ The core signing interface. Routers depend on this, not on `@prism-ing/wallet`.
519
+
520
+ ```ts
521
+ interface PrismSigner {
522
+ signMessage(message: Hex): Promise<Hex>;
523
+ signTypedData(payload: TypedDataPayload): Promise<Hex>;
524
+ signTransaction<T>(tx: T): Promise<T>;
525
+ readonly evmAddress: Address;
526
+ readonly solanaAddress: string;
527
+ }
528
+ ```
529
+
530
+ ### Error Handling
531
+
532
+ All operations return `Result<T, WalletError>` instead of throwing:
533
+
534
+ ```ts
535
+ const result = await createProductionWallet(config);
536
+
537
+ if (!result.ok) {
538
+ if (isRetryableError(result.error)) {
539
+ // ONEBALANCE_TIMEOUT, ONEBALANCE_API_ERROR — safe to retry
540
+ }
541
+ if (isUserFacingError(result.error)) {
542
+ // SIGNING_REJECTED, INSUFFICIENT_BALANCE — show to user
543
+ }
544
+ return;
545
+ }
546
+ ```
547
+
548
+ | Code | Retryable | User-Facing | Description |
549
+ |------|-----------|-------------|-------------|
550
+ | `ENCLAVE_UNAVAILABLE` | No | No | Secure Enclave not available on this platform |
551
+ | `ONEBALANCE_TIMEOUT` | Yes | No | OneBalance API did not respond in time |
552
+ | `ONEBALANCE_API_ERROR` | Yes | No | OneBalance API returned an error |
553
+ | `RECOVERY_GUARDIAN_INVALID` | No | Yes | Invalid guardian address |
554
+ | `SIGNING_REJECTED` | No | Yes | User rejected signing (biometric fail / cancel) |
555
+ | `ACCOUNT_NOT_INITIALIZED` | No | No | Operation on uninitialized account |
556
+ | `INSUFFICIENT_BALANCE` | No | Yes | Not enough balance for operation |
557
+ | `KEY_NOT_FOUND` | No | No | Requested key pair not found in signing backend |
558
+ | `VALIDATION_ERROR` | No | Yes | Input failed Zod validation |
559
+ | `SESSION_KEY_EXPIRED` | No | No | Session key has expired or been revoked |
560
+ | `SESSION_KEY_POLICY_VIOLATION` | No | Yes | Operation violates session key policy |
561
+ | `QUOTE_VERIFICATION_FAILED` | No | No | OneBalance quote failed integrity verification |
562
+
563
+ ## Defense-in-Depth Layers
564
+
565
+ ```
566
+ Layer 1: Key Storage
567
+ iOS: Secure Enclave encryption + biometric gate
568
+ Node: OWS scrypt + AES-256-GCM encrypted vault
569
+
570
+ Layer 2: Session Key Policies
571
+ Client-side: JS enforcement before every signature
572
+ On-chain: ZeroDev permission validators (UserOp reverts if violated)
573
+
574
+ Layer 3: Quote Verification
575
+ 6-point client-side integrity check before signing any OneBalance quote
576
+
577
+ Layer 4: Recovery Challenges
578
+ Dynamic keccak256 challenges bound to signer pair + 5-minute time window
579
+
580
+ Layer 5: Smart Contract Validation
581
+ ERC-4337 smart account validates all operations on-chain
582
+ ZeroDev Kernel v3.1 enforces session key policies at contract level
583
+
584
+ Layer 6: Network Resilience
585
+ Exponential backoff with jitter on OneBalance API (3 retries)
586
+ Only retries on timeouts, network errors, and 5xx responses
587
+ ```
588
+
589
+ ## Documentation
590
+
591
+ - [SPEC.md](./SPEC.md) — Design decisions, security model, error codes
592
+ - [CHANGELOG.md](./CHANGELOG.md) — Version history
593
+
594
+ ## License
595
+
596
+ MIT