@chipi-stack/backend 14.4.0 → 14.7.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.
package/dist/wallets.js CHANGED
@@ -1,28 +1,740 @@
1
1
  'use strict';
2
2
 
3
- var shared = require('@chipi-stack/shared');
4
- var CryptoES2 = require('crypto-es');
5
3
  var starknet = require('starknet');
4
+ var shared = require('@chipi-stack/shared');
5
+ var CryptoES = require('crypto-es');
6
+ var crypto = require('crypto');
6
7
 
7
8
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
9
 
9
- var CryptoES2__default = /*#__PURE__*/_interopDefault(CryptoES2);
10
+ var CryptoES__default = /*#__PURE__*/_interopDefault(CryptoES);
11
+
12
+ var __defProp = Object.defineProperty;
13
+ var __getOwnPropNames = Object.getOwnPropertyNames;
14
+ var __esm = (fn, res) => function __init() {
15
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
16
+ };
17
+ var __export = (target, all) => {
18
+ for (var name in all)
19
+ __defProp(target, name, { get: all[name], enumerable: true });
20
+ };
21
+ function poseidon(values) {
22
+ const felts = values.map((v) => typeof v === "bigint" ? "0x" + v.toString(16) : v);
23
+ return BigInt(starknet.hash.computePoseidonHashOnElements(felts));
24
+ }
25
+ function ownerCommitment(kindTag, pubkey) {
26
+ return poseidon([kindTag, ...pubkey]);
27
+ }
28
+ function computeAddressSalt(primaryKind, pubkey) {
29
+ if (pubkey.length === 0) {
30
+ throw new Error(
31
+ "computeAddressSalt: pubkey must contain at least one felt \u2014 empty signer material is rejected to prevent silent address mismatch."
32
+ );
33
+ }
34
+ const kindTag = BigInt(starknet.shortString.encodeShortString(primaryKind));
35
+ const pubkeyHash = ownerCommitment(kindTag, pubkey);
36
+ return poseidon([kindTag, pubkeyHash]);
37
+ }
38
+ function computeShhhAddress(input) {
39
+ const {
40
+ classHash,
41
+ primaryKind,
42
+ pubkey,
43
+ verifierClassHash,
44
+ label = "primary",
45
+ deployerAddress = 0n
46
+ } = input;
47
+ const salt = computeAddressSalt(primaryKind, pubkey);
48
+ const kindTag = BigInt(starknet.shortString.encodeShortString(primaryKind));
49
+ const labelFelt = BigInt(starknet.shortString.encodeShortString(label));
50
+ const ctor = [
51
+ kindTag,
52
+ verifierClassHash,
53
+ BigInt(pubkey.length),
54
+ ...pubkey,
55
+ labelFelt
56
+ ];
57
+ const addressHex = starknet.hash.calculateContractAddressFromHash(
58
+ "0x" + salt.toString(16),
59
+ "0x" + classHash.toString(16),
60
+ ctor.map((x) => typeof x === "bigint" ? "0x" + x.toString(16) : x),
61
+ "0x" + deployerAddress.toString(16)
62
+ );
63
+ return BigInt(addressHex);
64
+ }
65
+ var init_compute_wallet_address = __esm({
66
+ "src/shhh/compute-wallet-address.ts"() {
67
+ }
68
+ });
69
+ function toBigInt(x) {
70
+ if (typeof x === "bigint") return x;
71
+ if (typeof x === "number") return BigInt(x);
72
+ return BigInt(x);
73
+ }
74
+ function poseidonSpan(values) {
75
+ const felts = values.map((v) => typeof v === "bigint" ? "0x" + v.toString(16) : v);
76
+ return BigInt(starknet.hash.computePoseidonHashOnElements(felts));
77
+ }
78
+ function hashCall(call) {
79
+ const calldataFelts = starknet.CallData.toHex(
80
+ Array.isArray(call.calldata) ? call.calldata : [call.calldata]
81
+ );
82
+ const calldataItems = calldataFelts.map((x) => BigInt(x));
83
+ const calldataHash = poseidonSpan(calldataItems);
84
+ return poseidonSpan([
85
+ CALL_TYPE_HASH_REV1,
86
+ toBigInt(call.contractAddress),
87
+ toBigInt(starknet.hash.getSelectorFromName(call.entrypoint)),
88
+ calldataHash
89
+ ]);
90
+ }
91
+ function hashCalls(calls) {
92
+ const callHashes = calls.map(hashCall);
93
+ return poseidonSpan(callHashes);
94
+ }
95
+ function hashStarknetDomain(chainIdFelt) {
96
+ return poseidonSpan([
97
+ STARKNET_DOMAIN_TYPE_HASH_REV1,
98
+ BigInt(OE_DOMAIN_NAME),
99
+ OE_DOMAIN_VERSION,
100
+ toBigInt(chainIdFelt),
101
+ OE_DOMAIN_REVISION
102
+ ]);
103
+ }
104
+ function hashOutsideExecutionStruct(oe) {
105
+ const callsArrayHash = hashCalls(oe.calls);
106
+ return poseidonSpan([
107
+ OUTSIDE_EXECUTION_TYPE_HASH_REV1,
108
+ toBigInt(oe.caller),
109
+ toBigInt(oe.nonce),
110
+ oe.execute_after,
111
+ oe.execute_before,
112
+ callsArrayHash
113
+ ]);
114
+ }
115
+ function computeSnip12Hash(oe, contractAddress, chainIdFelt) {
116
+ const domainHash = hashStarknetDomain(chainIdFelt);
117
+ const structHash = hashOutsideExecutionStruct(oe);
118
+ return poseidonSpan([
119
+ BigInt(STARKNET_MESSAGE_PREFIX),
120
+ domainHash,
121
+ toBigInt(contractAddress),
122
+ structHash
123
+ ]);
124
+ }
125
+ function snip12HashToHexAsciiBytes(h) {
126
+ const hex = h.toString(16).padStart(64, "0");
127
+ return new TextEncoder().encode(hex);
128
+ }
129
+ var OUTSIDE_EXECUTION_TYPE_HASH_REV1, CALL_TYPE_HASH_REV1, STARKNET_DOMAIN_TYPE_HASH_REV1, STARKNET_MESSAGE_PREFIX, OE_DOMAIN_NAME, OE_DOMAIN_VERSION, OE_DOMAIN_REVISION, SIG_VERSION_V2_SNIP12, SIG_VERSION_V2_THRESHOLD;
130
+ var init_snip12_hash = __esm({
131
+ "src/shhh/snip12-hash.ts"() {
132
+ OUTSIDE_EXECUTION_TYPE_HASH_REV1 = 0x5a4b49e17039355cd95d1f0981d75901191d1319b1f4b05a9a791d218d7e0cn;
133
+ CALL_TYPE_HASH_REV1 = 0x3635c7f2a7ba93844c0d064e18e487f35ab90f7c39d00f186a781fc3f0c2ca9n;
134
+ STARKNET_DOMAIN_TYPE_HASH_REV1 = 0x1ff2f602e42168014d405a94f75e8a93d640751d71d16311266e140d8b0a210n;
135
+ STARKNET_MESSAGE_PREFIX = starknet.shortString.encodeShortString("StarkNet Message");
136
+ OE_DOMAIN_NAME = starknet.shortString.encodeShortString("Account.execute_from_outside");
137
+ OE_DOMAIN_VERSION = 2n;
138
+ OE_DOMAIN_REVISION = 1n;
139
+ starknet.shortString.encodeShortString("V1_HEX_ASCII");
140
+ SIG_VERSION_V2_SNIP12 = starknet.shortString.encodeShortString("V2_SNIP12");
141
+ SIG_VERSION_V2_THRESHOLD = starknet.shortString.encodeShortString("V2_THRESHOLD");
142
+ BigInt(
143
+ "0x1d1144bb2138366ff28d8e9ab57456b1d332ac42196230c3a602003c89872"
144
+ );
145
+ BigInt(
146
+ "0x94c5a761f34b25a4e603c651ac0e1fc4fad9cdb5517f7fa1bb54044c7e5ef8"
147
+ );
148
+ }
149
+ });
150
+ var init_threshold = __esm({
151
+ "src/shhh/threshold.ts"() {
152
+ init_snip12_hash();
153
+ BigInt(SIG_VERSION_V2_SNIP12);
154
+ BigInt(SIG_VERSION_V2_THRESHOLD);
155
+ }
156
+ });
157
+ function buildOutsideExecution(params) {
158
+ const nowSec = BigInt(Math.floor(Date.now() / 1e3));
159
+ const after = params.executeAfter ?? nowSec - BigInt(OE_EXECUTE_AFTER_CLOCK_SKEW_BUFFER);
160
+ const before = params.executeBefore ?? nowSec + BigInt(MAX_OE_VALIDITY_WINDOW_SECONDS) - BigInt(OE_EXECUTE_AFTER_CLOCK_SKEW_BUFFER);
161
+ if (before <= after) {
162
+ throw new Error(
163
+ `executeBefore (${before}) must be strictly greater than executeAfter (${after}).`
164
+ );
165
+ }
166
+ if (before - after > BigInt(MAX_OE_VALIDITY_WINDOW_SECONDS)) {
167
+ throw new Error(
168
+ `OE validity window ${before - after}s exceeds max ${MAX_OE_VALIDITY_WINDOW_SECONDS}s (audit M-2). Adjust executeBefore.`
169
+ );
170
+ }
171
+ return {
172
+ caller: params.caller ?? ANY_CALLER_FELT,
173
+ nonce: params.nonce,
174
+ execute_after: after,
175
+ execute_before: before,
176
+ calls: params.calls
177
+ };
178
+ }
179
+ function computeOeHash(oe, accountAddress, chainId) {
180
+ return computeSnip12Hash(oe, accountAddress, chainId);
181
+ }
182
+ function wrapV2Snip12Envelope(params) {
183
+ const tag = typeof params.kindTag === "bigint" ? params.kindTag : BigInt(starknet.shortString.encodeShortString(params.kindTag));
184
+ return [
185
+ BigInt(SIG_VERSION_V2_SNIP12),
186
+ params.ownerId,
187
+ tag,
188
+ ...params.payload.map((v) => typeof v === "bigint" ? v : BigInt(v))
189
+ ];
190
+ }
191
+ var ANY_CALLER_FELT, MAX_OE_VALIDITY_WINDOW_SECONDS, OE_EXECUTE_AFTER_CLOCK_SKEW_BUFFER;
192
+ var init_oe = __esm({
193
+ "src/shhh/oe.ts"() {
194
+ init_snip12_hash();
195
+ init_threshold();
196
+ ANY_CALLER_FELT = BigInt(starknet.shortString.encodeShortString("ANY_CALLER"));
197
+ MAX_OE_VALIDITY_WINDOW_SECONDS = 2 * 60 * 60;
198
+ OE_EXECUTE_AFTER_CLOCK_SKEW_BUFFER = 60;
199
+ }
200
+ });
201
+ async function ensureGaragaInit() {
202
+ if (garagaInitPromise) return garagaInitPromise;
203
+ const attempt = (async () => {
204
+ const garaga = await import('garaga');
205
+ await garaga.init();
206
+ return garaga;
207
+ })();
208
+ garagaInitPromise = attempt;
209
+ try {
210
+ return await attempt;
211
+ } catch (err) {
212
+ if (garagaInitPromise === attempt) garagaInitPromise = null;
213
+ throw err;
214
+ }
215
+ }
216
+ function bytesToLeU256(bytes) {
217
+ if (bytes.length !== 32) {
218
+ throw new Error(`Ed25519 value must be exactly 32 bytes; got ${bytes.length}.`);
219
+ }
220
+ let low = 0n;
221
+ let high = 0n;
222
+ for (let i = 0; i < 16; i++) low += BigInt(bytes[i]) << BigInt(8 * i);
223
+ for (let i = 0; i < 16; i++) high += BigInt(bytes[16 + i]) << BigInt(8 * i);
224
+ return { low, high };
225
+ }
226
+ function bytesToLeBigInt(bytes) {
227
+ let x = 0n;
228
+ for (let i = bytes.length - 1; i >= 0; i--) x = x << 8n | BigInt(bytes[i]);
229
+ return x;
230
+ }
231
+ function ed25519SignedBytes(messageHash) {
232
+ return snip12HashToHexAsciiBytes(messageHash);
233
+ }
234
+ function ed25519PubkeyFelts(publicKey) {
235
+ const { low, high } = bytesToLeU256(publicKey);
236
+ return [low, high];
237
+ }
238
+ async function buildEd25519Envelope(params) {
239
+ const ownerIndex = params.ownerIndex ?? 0n;
240
+ if (ownerIndex < 0n || ownerIndex >= 0x100000000n) {
241
+ throw new Error(
242
+ `buildEd25519Envelope: ownerIndex ${ownerIndex} is out of u32 range [0, 2^32). owner_id is the u32 INDEX into the wallet's owner_set (0 for primary, 1 for second, etc.); do NOT pass an owner_commitment or pubkey here.`
243
+ );
244
+ }
245
+ if (params.privateKey.length !== 32) {
246
+ throw new Error(
247
+ `Ed25519 private key must be exactly 32 bytes; got ${params.privateKey.length}.`
248
+ );
249
+ }
250
+ const ed = await import('@noble/ed25519');
251
+ const publicKey = await ed.getPublicKeyAsync(params.privateKey);
252
+ const signedBytes = ed25519SignedBytes(params.messageHash);
253
+ const signature = await ed.signAsync(signedBytes, params.privateKey);
254
+ return assembleEd25519Envelope({
255
+ R: signature.slice(0, 32),
256
+ s: signature.slice(32, 64),
257
+ publicKey,
258
+ signedBytes,
259
+ ownerIndex
260
+ });
261
+ }
262
+ async function assembleEd25519Envelope(parts) {
263
+ const garaga = await ensureGaragaInit();
264
+ const payload = garaga.eddsaCalldataBuilder(
265
+ bytesToLeBigInt(parts.R),
266
+ bytesToLeBigInt(parts.s),
267
+ bytesToLeBigInt(parts.publicKey),
268
+ parts.signedBytes,
269
+ false
270
+ );
271
+ return wrapV2Snip12Envelope({
272
+ ownerId: parts.ownerIndex,
273
+ kindTag: ED25519_KIND_TAG,
274
+ payload
275
+ });
276
+ }
277
+ var ED25519_KIND_TAG, garagaInitPromise;
278
+ var init_ed25519 = __esm({
279
+ "src/shhh/signers/ed25519.ts"() {
280
+ init_compute_wallet_address();
281
+ init_oe();
282
+ init_snip12_hash();
283
+ ED25519_KIND_TAG = "ED25519";
284
+ BigInt(starknet.shortString.encodeShortString(ED25519_KIND_TAG));
285
+ garagaInitPromise = null;
286
+ }
287
+ });
288
+ var encryptPrivateKey, decryptPrivateKey;
289
+ var init_encryption = __esm({
290
+ "src/lib/encryption.ts"() {
291
+ encryptPrivateKey = (privateKey, password) => {
292
+ if (!privateKey || !password) {
293
+ throw new Error("Private key and password are required");
294
+ }
295
+ return CryptoES__default.default.AES.encrypt(privateKey, password).toString();
296
+ };
297
+ decryptPrivateKey = (encryptedPrivateKey, password) => {
298
+ if (!encryptedPrivateKey || !password) {
299
+ throw new Error("Encrypted private key and password are required");
300
+ }
301
+ try {
302
+ const bytes = CryptoES__default.default.AES.decrypt(encryptedPrivateKey, password);
303
+ const decrypted = bytes.toString(CryptoES__default.default.enc.Utf8);
304
+ if (!decrypted) {
305
+ throw new Error("Decryption resulted in empty string");
306
+ }
307
+ return decrypted;
308
+ } catch (error) {
309
+ if (error instanceof Error) {
310
+ throw error;
311
+ }
312
+ throw new Error(`Decryption failed: ${error}`);
313
+ }
314
+ };
315
+ }
316
+ });
317
+
318
+ // src/encryption.ts
319
+ var init_encryption2 = __esm({
320
+ "src/encryption.ts"() {
321
+ init_encryption();
322
+ }
323
+ });
324
+ function buildStarkEnvelope(params) {
325
+ const pkHex = "0x" + (typeof params.privateKey === "bigint" ? params.privateKey : BigInt(params.privateKey)).toString(16).padStart(64, "0");
326
+ const sig = starknet.ec.starkCurve.sign("0x" + params.messageHash.toString(16), pkHex);
327
+ const r = BigInt("0x" + sig.r.toString(16));
328
+ const s = BigInt("0x" + sig.s.toString(16));
329
+ const ownerIndex = params.ownerIndex ?? 0n;
330
+ if (ownerIndex < 0n || ownerIndex >= 0x100000000n) {
331
+ throw new Error(
332
+ `buildStarkEnvelope: ownerIndex ${ownerIndex} is out of u32 range [0, 2^32). owner_id is the u32 INDEX into the wallet's owner_set (0 for primary, 1 for second, etc.); do NOT pass an owner_commitment or pubkey here.`
333
+ );
334
+ }
335
+ return wrapV2Snip12Envelope({
336
+ ownerId: ownerIndex,
337
+ kindTag: STARK_KIND_TAG,
338
+ payload: [r, s]
339
+ });
340
+ }
341
+ var STARK_KIND_TAG;
342
+ var init_stark = __esm({
343
+ "src/shhh/signers/stark.ts"() {
344
+ init_compute_wallet_address();
345
+ init_oe();
346
+ STARK_KIND_TAG = "STARK";
347
+ BigInt(starknet.shortString.encodeShortString(STARK_KIND_TAG));
348
+ }
349
+ });
350
+
351
+ // src/shhh/execute-paymaster-raw.ts
352
+ var execute_paymaster_raw_exports = {};
353
+ __export(execute_paymaster_raw_exports, {
354
+ executeShhhSponsoredRaw: () => executeShhhSponsoredRaw,
355
+ resolveShhhChainId: () => resolveShhhChainId,
356
+ serializeExecuteFromOutsideV2Calldata: () => serializeExecuteFromOutsideV2Calldata
357
+ });
358
+ function resolveShhhChainId(params) {
359
+ if (params.override !== void 0) {
360
+ return typeof params.override === "bigint" ? "0x" + params.override.toString(16) : params.override;
361
+ }
362
+ const url = (params.baseUrl ?? "").toLowerCase();
363
+ if (url.includes("sepolia")) return SN_SEPOLIA_CHAIN_ID;
364
+ return SN_MAIN_CHAIN_ID;
365
+ }
366
+ function serializeExecuteFromOutsideV2Calldata(params) {
367
+ const out = [];
368
+ const oe = params.outsideExecution;
369
+ const toBig = (v) => typeof v === "bigint" ? v : BigInt(v);
370
+ out.push(toBig(oe.caller));
371
+ out.push(toBig(oe.nonce));
372
+ out.push(oe.execute_after);
373
+ out.push(oe.execute_before);
374
+ out.push(BigInt(oe.calls.length));
375
+ for (const call of oe.calls) {
376
+ out.push(BigInt(call.contractAddress));
377
+ out.push(BigInt(starknet.hash.getSelectorFromName(call.entrypoint)));
378
+ const callData = Array.isArray(call.calldata) ? call.calldata : [];
379
+ out.push(BigInt(callData.length));
380
+ for (const datum of callData) {
381
+ out.push(typeof datum === "bigint" ? datum : BigInt(datum));
382
+ }
383
+ }
384
+ out.push(BigInt(params.signature.length));
385
+ for (const sigFelt of params.signature) {
386
+ out.push(sigFelt);
387
+ }
388
+ return out.map((v) => "0x" + v.toString(16));
389
+ }
390
+ function newOeNonce() {
391
+ const tsMs = BigInt(Date.now());
392
+ const rand = BigInt(Math.floor(Math.random() * 16777215));
393
+ return tsMs << 24n | rand;
394
+ }
395
+ async function executeShhhSponsoredRaw(params) {
396
+ const signerKind = params.signerKind ?? "STARK";
397
+ const SDK_SHIPPED_OE_KINDS = ["STARK", "ED25519"];
398
+ if (!SDK_SHIPPED_OE_KINDS.includes(signerKind)) {
399
+ throw new Error(
400
+ `SHHH signerKind="${signerKind}" is not yet shipped from the SDK (Wave B ships STARK + ED25519). Wait for the follow-up PR that adds external-rooted (MetaMask, Phantom, passkey, Apple) signing.`
401
+ );
402
+ }
403
+ const privateKeyHex = decryptPrivateKey(params.wallet.encryptedPrivateKey, params.encryptKey);
404
+ if (!privateKeyHex) {
405
+ throw new Error(
406
+ "Failed to decrypt private key for SHHH OE. Verify the encryptKey matches the one used at wallet creation."
407
+ );
408
+ }
409
+ const oe = buildOutsideExecution({
410
+ nonce: params.nonce ?? newOeNonce(),
411
+ calls: params.calls,
412
+ caller: ANY_CALLER_FELT
413
+ });
414
+ const chainId = resolveShhhChainId({
415
+ override: params.chainId,
416
+ baseUrl: params.client.baseUrl
417
+ });
418
+ const messageHash = computeOeHash(oe, params.wallet.publicKey, chainId);
419
+ let signature;
420
+ if (signerKind === "STARK") {
421
+ signature = buildStarkEnvelope({
422
+ privateKey: privateKeyHex,
423
+ messageHash
424
+ });
425
+ } else {
426
+ const edKeyHex = privateKeyHex.startsWith("0x") ? privateKeyHex.slice(2) : privateKeyHex;
427
+ if (edKeyHex.length !== 64) {
428
+ throw new Error(
429
+ `SHHH ED25519 private key is ${edKeyHex.length} hex chars; expected 64 (32 raw bytes). The stored ciphertext does not look like a Chipi-held Ed25519 key \u2014 check wallet provenance.`
430
+ );
431
+ }
432
+ const edPrivKey = Uint8Array.from(Buffer.from(edKeyHex, "hex"));
433
+ signature = await buildEd25519Envelope({
434
+ privateKey: edPrivKey,
435
+ messageHash
436
+ });
437
+ }
438
+ const calldata = serializeExecuteFromOutsideV2Calldata({
439
+ outsideExecution: oe,
440
+ signature
441
+ });
442
+ const response = await params.client.post({
443
+ endpoint: "/transactions/execute-sponsored-raw",
444
+ bearerToken: params.bearerToken,
445
+ body: {
446
+ accountAddress: params.wallet.publicKey,
447
+ calldata,
448
+ signerKind,
449
+ ...params.externalUserId ? { externalUserId: params.externalUserId } : {}
450
+ }
451
+ });
452
+ if (!response || !response.transactionHash) {
453
+ throw new Error(
454
+ "execute-sponsored-raw response missing transactionHash. Check the chipi-back logs for upstream paymaster errors."
455
+ );
456
+ }
457
+ return response.transactionHash;
458
+ }
459
+ var SN_MAIN_CHAIN_ID, SN_SEPOLIA_CHAIN_ID;
460
+ var init_execute_paymaster_raw = __esm({
461
+ "src/shhh/execute-paymaster-raw.ts"() {
462
+ init_encryption2();
463
+ init_oe();
464
+ init_stark();
465
+ init_ed25519();
466
+ SN_MAIN_CHAIN_ID = "0x534e5f4d41494e";
467
+ SN_SEPOLIA_CHAIN_ID = "0x534e5f5345504f4c4941";
468
+ }
469
+ });
10
470
 
11
471
  // src/wallets.ts
12
- var encryptPrivateKey = (privateKey, password) => {
13
- if (!privateKey || !password) {
14
- throw new Error("Private key and password are required");
472
+ init_compute_wallet_address();
473
+ init_ed25519();
474
+ init_encryption2();
475
+
476
+ // src/migrate-to-shhh.ts
477
+ init_encryption2();
478
+
479
+ // src/execute-paymaster-transaction.ts
480
+ init_encryption2();
481
+ function normalizeSignature(signature) {
482
+ if (Array.isArray(signature)) {
483
+ if (signature.length < 2) {
484
+ throw new Error(`Array signature must have at least 2 elements, got ${signature.length}`);
485
+ }
486
+ const r = signature[0];
487
+ const s = signature[1];
488
+ if (r == null || s == null) {
489
+ throw new Error(`Signature elements must not be null/undefined. Got r=${r}, s=${s}`);
490
+ }
491
+ return { r: String(r), s: String(s) };
492
+ }
493
+ if (signature && typeof signature === "object" && "r" in signature && "s" in signature) {
494
+ const sig = signature;
495
+ if (sig.r == null || sig.s == null) {
496
+ throw new Error(`Object signature has null/undefined fields. Got r=${sig.r}, s=${sig.s}`);
497
+ }
498
+ return {
499
+ r: String(sig.r),
500
+ s: String(sig.s)
501
+ };
502
+ }
503
+ throw new Error("Unrecognized signature format from account.signMessage()");
504
+ }
505
+
506
+ // src/execute-paymaster-transaction.ts
507
+ var executePaymasterTransaction = async ({
508
+ params,
509
+ bearerToken,
510
+ client
511
+ }) => {
512
+ try {
513
+ const { encryptKey, wallet, calls, saveToDatabase, usePasskey } = params;
514
+ if (!encryptKey) {
515
+ throw new Error(
516
+ usePasskey ? "encryptKey is required when using passkey. The passkey authentication should have provided the encryptKey." : "encryptKey is required for transaction execution"
517
+ );
518
+ }
519
+ const walletType = wallet.walletType ?? "READY";
520
+ if (walletType === "SHHH") {
521
+ const { executeShhhSponsoredRaw: executeShhhSponsoredRaw2 } = await Promise.resolve().then(() => (init_execute_paymaster_raw(), execute_paymaster_raw_exports));
522
+ const shhhSignerKind = (wallet.signerKind ?? "STARK") || "STARK";
523
+ return await executeShhhSponsoredRaw2({
524
+ client,
525
+ bearerToken,
526
+ wallet: {
527
+ publicKey: wallet.publicKey,
528
+ encryptedPrivateKey: wallet.encryptedPrivateKey,
529
+ walletType: wallet.walletType
530
+ },
531
+ encryptKey,
532
+ calls,
533
+ signerKind: shhhSignerKind
534
+ });
535
+ }
536
+ const isKnownType = Object.prototype.hasOwnProperty.call(shared.WALLET_RPC_ENDPOINTS, walletType) && Object.prototype.hasOwnProperty.call(shared.WALLET_CLASS_HASHES, walletType);
537
+ const rpcUrl = isKnownType ? shared.WALLET_RPC_ENDPOINTS[walletType] : shared.WALLET_RPC_ENDPOINTS["CHIPI"];
538
+ const accountClassHash = isKnownType ? shared.WALLET_CLASS_HASHES[walletType] : shared.WALLET_CLASS_HASHES["CHIPI"];
539
+ let privateKeyDecrypted;
540
+ try {
541
+ privateKeyDecrypted = decryptPrivateKey(wallet.encryptedPrivateKey, encryptKey);
542
+ } catch {
543
+ throw new Error("Failed to decrypt private key. Verify the encryptKey is correct.");
544
+ }
545
+ const provider = new starknet.RpcProvider({ nodeUrl: rpcUrl });
546
+ const account = new starknet.Account({
547
+ provider,
548
+ address: wallet.publicKey,
549
+ signer: privateKeyDecrypted
550
+ });
551
+ const prepareResponse = await client.post({
552
+ endpoint: "/transactions/prepare-typed-data",
553
+ bearerToken,
554
+ body: {
555
+ publicKey: wallet.publicKey,
556
+ calls,
557
+ accountClassHash
558
+ }
559
+ });
560
+ if (!prepareResponse || typeof prepareResponse !== "object") {
561
+ throw new Error(
562
+ "Invalid response from /transactions/prepare-typed-data: response is not an object"
563
+ );
564
+ }
565
+ let typedData2;
566
+ let resolvedWalletType;
567
+ if ("typedData" in prepareResponse) {
568
+ const resp = prepareResponse;
569
+ typedData2 = resp.typedData;
570
+ if (resp.walletType && Object.prototype.hasOwnProperty.call(shared.WALLET_CLASS_HASHES, resp.walletType)) {
571
+ resolvedWalletType = resp.walletType;
572
+ }
573
+ } else if (prepareResponse && "types" in prepareResponse && "message" in prepareResponse) {
574
+ typedData2 = prepareResponse;
575
+ } else {
576
+ const apiMessage2 = prepareResponse && typeof prepareResponse.message === "string" ? prepareResponse.message : void 0;
577
+ throw new Error(
578
+ apiMessage2 ?? "Invalid response from /transactions/prepare-typed-data: typedData is missing. This may occur for older STARKNET wallets that don't support paymaster transactions. Please ensure the wallet is a CHIPI wallet type."
579
+ );
580
+ }
581
+ if (!typedData2 || typeof typedData2 !== "object" || !("types" in typedData2) || !("message" in typedData2)) {
582
+ throw new Error(
583
+ "Invalid typedData received from backend: missing 'types' or 'message' field. This may occur for older STARKNET wallets that don't support paymaster transactions."
584
+ );
585
+ }
586
+ const userSignature = await account.signMessage(typedData2);
587
+ const normalizedSig = normalizeSignature(userSignature);
588
+ const result = await client.post({
589
+ endpoint: "/transactions/execute-sponsored-transaction",
590
+ bearerToken,
591
+ body: {
592
+ publicKey: wallet.publicKey,
593
+ typedData: typedData2,
594
+ userSignature: normalizedSig,
595
+ saveToDatabase,
596
+ walletType: resolvedWalletType ?? walletType
597
+ }
598
+ });
599
+ if (!result || typeof result !== "object") {
600
+ throw new Error(
601
+ "Invalid response from /transactions/execute-sponsored-transaction: response is not an object"
602
+ );
603
+ }
604
+ const apiMessage = typeof result.message === "string" ? result.message : void 0;
605
+ if (typeof result.transactionHash !== "string" || result.transactionHash.trim().length === 0) {
606
+ throw new Error(apiMessage ?? "Response does not contain transaction hash");
607
+ }
608
+ return result.transactionHash;
609
+ } catch (error) {
610
+ console.error("Error sending transaction with paymaster", error);
611
+ throw error;
15
612
  }
16
- return CryptoES2__default.default.AES.encrypt(privateKey, password).toString();
17
613
  };
614
+ BigInt(
615
+ starknet.shortString.encodeShortString("SHHH_BOOTSTRAP_V8_4")
616
+ );
617
+ var UPGRADE_ENTRYPOINT = "upgrade";
618
+ var BOOTSTRAP_ENTRYPOINT = "bootstrap_from_sessions";
619
+ function _asHex(x) {
620
+ return typeof x === "bigint" ? "0x" + x.toString(16) : x;
621
+ }
622
+ function labelToFelt(label) {
623
+ if (label.length === 0) return 0n;
624
+ for (let i = 0; i < label.length; i++) {
625
+ if (label.charCodeAt(i) > 127) {
626
+ throw new RangeError(
627
+ `migrate label must be ASCII-only (starknet.js short-string encoder rejects non-ASCII)`
628
+ );
629
+ }
630
+ }
631
+ if (label.length > 31) {
632
+ throw new RangeError(
633
+ `migrate label must fit in felt252 short-string (\u226431 ASCII characters); got ${label.length}`
634
+ );
635
+ }
636
+ return BigInt(starknet.shortString.encodeShortString(label));
637
+ }
638
+ function buildMigrationCalls(params) {
639
+ const upgradeCall = {
640
+ contractAddress: _asHex(params.legacyAccountAddress),
641
+ entrypoint: UPGRADE_ENTRYPOINT,
642
+ calldata: [_asHex(params.shhhAccountClassHash)]
643
+ };
644
+ const labelFelt = labelToFelt(params.label ?? "migrated");
645
+ const bootstrapCall = {
646
+ contractAddress: _asHex(params.legacyAccountAddress),
647
+ entrypoint: BOOTSTRAP_ENTRYPOINT,
648
+ calldata: [
649
+ _asHex(params.legacyPublicKey),
650
+ _asHex(params.starkVerifierClassHash),
651
+ "0x" + labelFelt.toString(16)
652
+ ]
653
+ };
654
+ return [upgradeCall, bootstrapCall];
655
+ }
656
+
657
+ // src/migrate-to-shhh.ts
658
+ async function migrateChipiWalletToShhh(params) {
659
+ const prepare = await params.client.post({
660
+ endpoint: "/chipi-wallets/prepare-migrate-to-shhh",
661
+ bearerToken: params.bearerToken,
662
+ body: {
663
+ walletId: params.walletId,
664
+ externalUserId: params.externalUserId
665
+ }
666
+ });
667
+ if (prepare.migrationPath === "ALREADY_MIGRATED") {
668
+ return {
669
+ kind: "ALREADY_MIGRATED",
670
+ walletAddress: prepare.walletAddress,
671
+ onChainClassHash: prepare.onChainClassHash,
672
+ primaryKind: prepare.primaryKind
673
+ };
674
+ }
675
+ if (prepare.migrationPath === "STRANDED") {
676
+ throw new Error(
677
+ "SHHH stranded-state recovery is not yet shipped in the SDK client. The wallet is at V8.4 class hash but primary_kind == 0, which means a prior migration completed `upgrade(SHHH)` but the `bootstrap_from_sessions` call silently reverted. Recovery requires `bootstrap_from_sessions_signed`; see `buildBootstrapSignedCall` in @chipi-stack/backend/src/shhh/migrate.ts."
678
+ );
679
+ }
680
+ if (prepare.migrationPath !== "HAPPY") {
681
+ throw new Error(
682
+ "SHHH migration: unexpected migrationPath " + JSON.stringify(prepare.migrationPath) + " returned by chipi-back. This SDK build only handles HAPPY / STRANDED / ALREADY_MIGRATED; upgrade @chipi-stack/backend to a version that knows this path."
683
+ );
684
+ }
685
+ const privateKeyHex = decryptPrivateKey(params.wallet.encryptedPrivateKey, params.encryptKey);
686
+ if (!privateKeyHex) {
687
+ throw new Error(
688
+ "Failed to decrypt STARK private key for migration. Verify the encryptKey matches the one used at wallet creation."
689
+ );
690
+ }
691
+ const starkKeyPub = BigInt(starknet.ec.starkCurve.getStarkKey(privateKeyHex));
692
+ const calls = buildMigrationCalls({
693
+ legacyAccountAddress: params.wallet.publicKey,
694
+ legacyPublicKey: starkKeyPub,
695
+ shhhAccountClassHash: BigInt(prepare.shhhClassHash),
696
+ starkVerifierClassHash: BigInt(prepare.shhhVerifierClassHash),
697
+ label: prepare.label
698
+ });
699
+ const transactionHash = await executePaymasterTransaction({
700
+ params: {
701
+ encryptKey: params.encryptKey,
702
+ wallet: {
703
+ publicKey: params.wallet.publicKey,
704
+ encryptedPrivateKey: params.wallet.encryptedPrivateKey,
705
+ // Explicit walletType: the wallet IS still CHIPI v29 at sign
706
+ // time. The upgrade(SHHH) inside the multicall changes the
707
+ // class hash mid-tx; that's after signature verification.
708
+ walletType: "CHIPI"
709
+ },
710
+ calls,
711
+ // Persist the migration tx so it shows up in the user's tx
712
+ // history. The on-chain receipt is the load-bearing artifact;
713
+ // the DB row is convenience surfacing.
714
+ saveToDatabase: true
715
+ },
716
+ bearerToken: params.bearerToken,
717
+ client: params.client
718
+ });
719
+ const execute = await params.client.post({
720
+ endpoint: "/chipi-wallets/execute-migrate-to-shhh",
721
+ bearerToken: params.bearerToken,
722
+ body: {
723
+ walletId: params.walletId,
724
+ externalUserId: params.externalUserId,
725
+ transactionHash
726
+ }
727
+ });
728
+ return { kind: "MIGRATED", ...execute };
729
+ }
18
730
 
19
731
  // src/wallets.ts
20
732
  var ChipiWallets = class {
21
733
  constructor(client) {
22
734
  this.client = client;
23
735
  this.getPrivateKeyAX = () => {
24
- const privateKeyBytes = CryptoES2__default.default.lib.WordArray.random(32);
25
- const privateKey = privateKeyBytes.toString(CryptoES2__default.default.enc.Hex);
736
+ const privateKeyBytes = CryptoES__default.default.lib.WordArray.random(32);
737
+ const privateKey = privateKeyBytes.toString(CryptoES__default.default.enc.Hex);
26
738
  const fullPrivateKey = `0x${privateKey}`;
27
739
  const maxStarknetValue = BigInt(
28
740
  "0x800000000000000000000000000000000000000000000000000000000000000"
@@ -31,6 +743,20 @@ var ChipiWallets = class {
31
743
  return `0x${privateKeyBigInt.toString(16)}`;
32
744
  };
33
745
  }
746
+ /**
747
+ * Create a new wallet on Starknet (gasless, paymaster-sponsored).
748
+ *
749
+ * **PIN is weak — not recommended for production.** `encryptKey` here
750
+ * doubles as the only thing decrypting the wallet's private key. A
751
+ * user-typed PIN is short, low-entropy, and trivially compromised
752
+ * by shoulder-surfing or phishing. Production embedded-wallet apps
753
+ * should derive `encryptKey` from a platform passkey
754
+ * (`@chipi-stack/chipi-passkey` `getWalletEncryptKey` for the legacy
755
+ * PRF flow, or — preferred — `createShhhPasskey` for new SHHH
756
+ * V8.4 wallets where the P-256 key never leaves the platform
757
+ * authenticator at all). PIN remains only as a last-resort
758
+ * recovery surface for users without WebAuthn / biometrics.
759
+ */
34
760
  async createWallet(params) {
35
761
  try {
36
762
  const {
@@ -38,8 +764,25 @@ var ChipiWallets = class {
38
764
  externalUserId,
39
765
  bearerToken,
40
766
  userId,
41
- walletType = "CHIPI",
42
- // Default to CHIPI wallet
767
+ // P2.2 default flip (re-applied 2026-05-29 after #270 revert and
768
+ // the post-revert SHHH bug-fix run #271–#296). New wallets
769
+ // default to SHHH V8.4 instead of legacy CHIPI v29 — pluggable-
770
+ // signer Starknet account, guardian recovery, threshold support
771
+ // out of the box. Existing wallets unaffected; this only
772
+ // changes the default for callers that omit `walletType`.
773
+ // Callers who explicitly pass `walletType: "CHIPI"` or
774
+ // `"READY"` continue producing those.
775
+ //
776
+ // Server-side override: chipi-back's CHIPI_SHHH_DEFAULT env
777
+ // var (chipi-back#267) lets ops force the legacy default back
778
+ // for callers that bypass the SDK and POST to /chipi-wallets
779
+ // directly without walletType.
780
+ //
781
+ // signerKind under the SHHH branch defaults to "STARK" further
782
+ // down — same kind the original #267 P2.2 used and the same
783
+ // kind every mainnet smoke since 2026-05-26 has been signing
784
+ // under.
785
+ walletType = "SHHH",
43
786
  classHash: customClassHash,
44
787
  usePasskey,
45
788
  chain
@@ -52,18 +795,115 @@ var ChipiWallets = class {
52
795
  }
53
796
  throw new Error("encryptKey is required for wallet creation");
54
797
  }
798
+ const isShhh = walletType === "SHHH";
799
+ const effectiveSignerKind = isShhh ? params.signerKind ?? "STARK" : void 0;
800
+ const SDK_SHIPPED_SHHH_KINDS = [
801
+ "STARK",
802
+ "ED25519",
803
+ "WEBAUTHN_P256",
804
+ "EIP191_SECP256K1"
805
+ ];
806
+ if (isShhh && !SDK_SHIPPED_SHHH_KINDS.includes(
807
+ effectiveSignerKind
808
+ )) {
809
+ throw new Error(
810
+ `SHHH signerKind=${effectiveSignerKind} is not yet shipped from the SDK (supports STARK, ED25519, WEBAUTHN_P256, EIP191_SECP256K1). JWT_ES256_APPLE_SUB and others remain deferred to their follow-up PRs (OAuth / Apple-Developer context).`
811
+ );
812
+ }
55
813
  const rpcUrl = walletType === "READY" ? shared.WALLET_RPC_ENDPOINTS.READY : shared.WALLET_RPC_ENDPOINTS.CHIPI;
56
814
  const provider = new starknet.RpcProvider({ nodeUrl: rpcUrl });
57
815
  const privateKeyAX = this.getPrivateKeyAX();
58
816
  const starkKeyPubAX = starknet.ec.starkCurve.getStarkKey(privateKeyAX);
59
- const constructorCallData = this.buildConstructorCallData(walletType, starkKeyPubAX);
60
- const accountClassHash = customClassHash ?? (walletType === "READY" ? shared.WALLET_CLASS_HASHES.READY : shared.WALLET_CLASS_HASHES.CHIPI);
61
- const publicKey = starknet.hash.calculateContractAddressFromHash(
62
- starkKeyPubAX,
63
- accountClassHash,
64
- constructorCallData,
65
- 0
66
- );
817
+ let constructorCallData;
818
+ let accountClassHash;
819
+ let publicKey;
820
+ let deploymentSalt;
821
+ let shhhOwnerPubkeyFelts;
822
+ let shhhKeyForEncryption = privateKeyAX;
823
+ if (isShhh) {
824
+ let pubkeyFelts;
825
+ let shhhPrivateKeyHex;
826
+ if (effectiveSignerKind === "STARK") {
827
+ pubkeyFelts = [BigInt(starkKeyPubAX)];
828
+ shhhPrivateKeyHex = privateKeyAX;
829
+ } else if (effectiveSignerKind === "ED25519") {
830
+ if (params.ownerPubkeyFelts && params.ownerPubkeyFelts.length > 0) {
831
+ if (params.ownerPubkeyFelts.length !== 2) {
832
+ throw new Error(
833
+ `SHHH ED25519 ownerPubkeyFelts must be [low_u128, high_u128] (2 felts); got ${params.ownerPubkeyFelts.length}.`
834
+ );
835
+ }
836
+ pubkeyFelts = params.ownerPubkeyFelts.map((s) => BigInt(s));
837
+ shhhPrivateKeyHex = "";
838
+ } else {
839
+ const edPrivKey = crypto.randomBytes(32);
840
+ const ed = await import('@noble/ed25519');
841
+ const edPubKey = await ed.getPublicKeyAsync(edPrivKey);
842
+ pubkeyFelts = ed25519PubkeyFelts(edPubKey);
843
+ shhhPrivateKeyHex = Buffer.from(edPrivKey).toString("hex");
844
+ }
845
+ } else if (effectiveSignerKind === "WEBAUTHN_P256" || effectiveSignerKind === "EIP191_SECP256K1") {
846
+ if (!params.ownerPubkeyFelts || params.ownerPubkeyFelts.length !== 4) {
847
+ throw new Error(
848
+ `SHHH ${effectiveSignerKind} ownerPubkeyFelts must be [x_low, x_high, y_low, y_high] (4 felts); got ${params.ownerPubkeyFelts?.length ?? 0}.`
849
+ );
850
+ }
851
+ pubkeyFelts = params.ownerPubkeyFelts.map((s) => BigInt(s));
852
+ shhhPrivateKeyHex = "";
853
+ } else {
854
+ throw new Error(
855
+ `SHHH signerKind=${effectiveSignerKind} reached the address-derivation block without an implementation.`
856
+ );
857
+ }
858
+ shhhOwnerPubkeyFelts = pubkeyFelts.map((f) => "0x" + f.toString(16));
859
+ shhhKeyForEncryption = shhhPrivateKeyHex;
860
+ const verifierClassHash = BigInt(
861
+ shared.SHHH_VERIFIER_CLASS_HASHES[effectiveSignerKind]
862
+ );
863
+ const shhhClassHash = customClassHash ?? shared.WALLET_CLASS_HASHES.SHHH;
864
+ const computedAddress = computeShhhAddress({
865
+ classHash: BigInt(shhhClassHash),
866
+ primaryKind: effectiveSignerKind,
867
+ pubkey: pubkeyFelts,
868
+ verifierClassHash
869
+ });
870
+ publicKey = "0x" + computedAddress.toString(16);
871
+ accountClassHash = shhhClassHash;
872
+ const labelFelt = BigInt(starknet.shortString.encodeShortString("primary"));
873
+ const kindTagFelt = BigInt(starknet.shortString.encodeShortString(effectiveSignerKind));
874
+ constructorCallData = [
875
+ kindTagFelt.toString(),
876
+ verifierClassHash.toString(),
877
+ pubkeyFelts.length.toString(),
878
+ ...pubkeyFelts.map((f) => f.toString()),
879
+ labelFelt.toString()
880
+ ];
881
+ const salt = (() => {
882
+ const ownerCommit = BigInt(
883
+ starknet.hash.computePoseidonHashOnElements([
884
+ "0x" + kindTagFelt.toString(16),
885
+ ...pubkeyFelts.map((f) => "0x" + f.toString(16))
886
+ ])
887
+ );
888
+ return BigInt(
889
+ starknet.hash.computePoseidonHashOnElements([
890
+ "0x" + kindTagFelt.toString(16),
891
+ "0x" + ownerCommit.toString(16)
892
+ ])
893
+ );
894
+ })();
895
+ deploymentSalt = "0x" + salt.toString(16);
896
+ } else {
897
+ constructorCallData = this.buildConstructorCallData(walletType, starkKeyPubAX);
898
+ accountClassHash = customClassHash ?? (walletType === "READY" ? shared.WALLET_CLASS_HASHES.READY : shared.WALLET_CLASS_HASHES.CHIPI);
899
+ publicKey = starknet.hash.calculateContractAddressFromHash(
900
+ starkKeyPubAX,
901
+ accountClassHash,
902
+ constructorCallData,
903
+ 0
904
+ );
905
+ deploymentSalt = starkKeyPubAX;
906
+ }
67
907
  const account = new starknet.Account({ provider, address: publicKey, signer: privateKeyAX });
68
908
  const typedDataResponse = await this.client.post({
69
909
  endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}/prepare-creation`,
@@ -73,19 +913,41 @@ var ChipiWallets = class {
73
913
  publicKey,
74
914
  walletType,
75
915
  starkKeyPubAX,
76
- ...customClassHash && { classHash: customClassHash }
916
+ ...customClassHash && { classHash: customClassHash },
917
+ // SHHH: tell chipi-back which signer kind + pubkey felts
918
+ // produced this deployment so it can validate against the
919
+ // expected SHHH constructor shape and populate the new
920
+ // wallet row columns.
921
+ ...isShhh && {
922
+ signerKind: effectiveSignerKind,
923
+ ownerPubkeyFelts: shhhOwnerPubkeyFelts
924
+ }
77
925
  }
78
926
  });
79
- const { typedData, accountClassHash: accountClassHashResponse } = typedDataResponse;
80
- const userSignature = await account.signMessage(typedData);
927
+ const { typedData: typedData2, accountClassHash: accountClassHashResponse } = typedDataResponse;
928
+ let userSignaturePayload;
929
+ if (isShhh) {
930
+ userSignaturePayload = {
931
+ r: "0x0",
932
+ s: "0x0",
933
+ recovery: 0
934
+ };
935
+ } else {
936
+ const userSignature = await account.signMessage(typedData2);
937
+ userSignaturePayload = {
938
+ r: userSignature.r.toString(),
939
+ s: userSignature.s.toString(),
940
+ recovery: userSignature.recovery
941
+ };
942
+ }
81
943
  const deploymentData = {
82
944
  class_hash: accountClassHashResponse,
83
- salt: starkKeyPubAX,
945
+ salt: deploymentSalt,
84
946
  unique: `${starknet.num.toHex(0)}`,
85
947
  calldata: constructorCallData.map((value) => starknet.num.toHex(value))
86
948
  };
87
- const encryptedPrivateKey = encryptPrivateKey(privateKeyAX, encryptKey);
88
- const encryptedPrivateKeyBackup = params.encryptKeyBackup ? encryptPrivateKey(privateKeyAX, params.encryptKeyBackup) : void 0;
949
+ const encryptedPrivateKey = shhhKeyForEncryption === "" ? void 0 : encryptPrivateKey(shhhKeyForEncryption, encryptKey);
950
+ const encryptedPrivateKeyBackup = params.encryptKeyBackup && shhhKeyForEncryption !== "" ? encryptPrivateKey(shhhKeyForEncryption, params.encryptKeyBackup) : void 0;
89
951
  const executeTransactionResponse = await this.client.post({
90
952
  endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}`,
91
953
  bearerToken,
@@ -95,13 +957,21 @@ var ChipiWallets = class {
95
957
  publicKey,
96
958
  walletType,
97
959
  chain,
98
- userSignature: {
99
- r: userSignature.r.toString(),
100
- s: userSignature.s.toString(),
101
- recovery: userSignature.recovery
960
+ userSignature: userSignaturePayload,
961
+ typedData: typedData2,
962
+ ...encryptedPrivateKey !== void 0 && { encryptedPrivateKey },
963
+ // SHHH: write the signer kind to the new wallet row.
964
+ ...isShhh && { signerKind: effectiveSignerKind },
965
+ // External-rooted SHHH (passkey / MetaMask / Phantom): no
966
+ // Chipi-held key, so chipi-back needs the pubkey felts (address
967
+ // derivation) + `ownerPublicKey` as the credential source it
968
+ // stores (encryptedPrivateKey stays null). Canonical
969
+ // ownerPublicKey = comma-joined felts. STARK / Chipi-held kinds
970
+ // are untouched — encryptedPrivateKey is their credential source.
971
+ ...isShhh && encryptedPrivateKey === void 0 && shhhOwnerPubkeyFelts && {
972
+ ownerPubkeyFelts: shhhOwnerPubkeyFelts,
973
+ ownerPublicKey: shhhOwnerPubkeyFelts.join(",")
102
974
  },
103
- typedData,
104
- encryptedPrivateKey,
105
975
  ...encryptedPrivateKeyBackup && { encryptedPrivateKeyBackup },
106
976
  ...params.authMethod && { authMethod: params.authMethod },
107
977
  ...params.credentialId && { credentialId: params.credentialId },
@@ -235,6 +1105,31 @@ var ChipiWallets = class {
235
1105
  body: params
236
1106
  });
237
1107
  }
1108
+ /**
1109
+ * Migrate a CHIPI v29 wallet to SHHH V8.4 in-place at the same
1110
+ * on-chain address. Composes the two-phase chipi-back endpoint:
1111
+ * prepare returns the path decision + parameters, then this method
1112
+ * builds the [upgrade, bootstrap_from_sessions] multicall locally,
1113
+ * signs the V8.4 OE with the wallet's STARK key (decrypted via
1114
+ * the user's encryptKey), and sends the signed calldata to the
1115
+ * execute endpoint. Server forwards verbatim to the paymaster.
1116
+ *
1117
+ * Returns `{kind: "ALREADY_MIGRATED"}` if the wallet is already
1118
+ * SHHH on chain (DB row gets patched server-side; no on-chain tx
1119
+ * fires); otherwise `{kind: "MIGRATED", transactionHash, ...}`.
1120
+ *
1121
+ * Throws on STRANDED-state recovery, UNDEPLOYED wallets, and
1122
+ * other deferred-feature paths with actionable error messages.
1123
+ *
1124
+ * Endpoints contract shipped in chipi-back task #28 (PR #272).
1125
+ */
1126
+ async migrateToShhh(params, bearerToken) {
1127
+ return migrateChipiWalletToShhh({
1128
+ client: this.client,
1129
+ bearerToken,
1130
+ ...params
1131
+ });
1132
+ }
238
1133
  };
239
1134
 
240
1135
  exports.ChipiWallets = ChipiWallets;