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