@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/index.d.mts +1581 -7
- package/dist/index.d.ts +1581 -7
- package/dist/index.js +2493 -321
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2411 -317
- package/dist/index.mjs.map +1 -1
- package/dist/skuPurchases.js +413 -19
- package/dist/skuPurchases.js.map +1 -1
- package/dist/skuPurchases.mjs +413 -19
- package/dist/skuPurchases.mjs.map +1 -1
- package/dist/transactions.js +413 -19
- package/dist/transactions.js.map +1 -1
- package/dist/transactions.mjs +413 -19
- package/dist/transactions.mjs.map +1 -1
- package/dist/wallets-CYCLyyXY.d.mts +221 -0
- package/dist/wallets-DxKnh21Q.d.ts +221 -0
- package/dist/wallets.d.mts +3 -56
- package/dist/wallets.d.ts +3 -56
- package/dist/wallets.js +926 -31
- package/dist/wallets.js.map +1 -1
- package/dist/wallets.mjs +926 -31
- package/dist/wallets.mjs.map +1 -1
- package/package.json +10 -5
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var starknet = require('starknet');
|
|
3
4
|
var shared = require('@chipi-stack/shared');
|
|
4
5
|
var CryptoES = require('crypto-es');
|
|
5
|
-
var
|
|
6
|
+
var crypto = require('crypto');
|
|
6
7
|
var types = require('@chipi-stack/types');
|
|
7
8
|
var core = require('@chipi-stack/core');
|
|
8
9
|
|
|
@@ -10,7 +11,649 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
10
11
|
|
|
11
12
|
var CryptoES__default = /*#__PURE__*/_interopDefault(CryptoES);
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
var __defProp = Object.defineProperty;
|
|
15
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
16
|
+
var __esm = (fn, res) => function __init() {
|
|
17
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
18
|
+
};
|
|
19
|
+
var __export = (target, all) => {
|
|
20
|
+
for (var name in all)
|
|
21
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
22
|
+
};
|
|
23
|
+
function poseidon(values) {
|
|
24
|
+
const felts = values.map((v) => typeof v === "bigint" ? "0x" + v.toString(16) : v);
|
|
25
|
+
return BigInt(starknet.hash.computePoseidonHashOnElements(felts));
|
|
26
|
+
}
|
|
27
|
+
function ownerCommitment(kindTag, pubkey) {
|
|
28
|
+
return poseidon([kindTag, ...pubkey]);
|
|
29
|
+
}
|
|
30
|
+
function computeAddressSalt(primaryKind, pubkey) {
|
|
31
|
+
if (pubkey.length === 0) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
"computeAddressSalt: pubkey must contain at least one felt \u2014 empty signer material is rejected to prevent silent address mismatch."
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
const kindTag = BigInt(starknet.shortString.encodeShortString(primaryKind));
|
|
37
|
+
const pubkeyHash = ownerCommitment(kindTag, pubkey);
|
|
38
|
+
return poseidon([kindTag, pubkeyHash]);
|
|
39
|
+
}
|
|
40
|
+
function computeShhhAddress(input) {
|
|
41
|
+
const {
|
|
42
|
+
classHash,
|
|
43
|
+
primaryKind,
|
|
44
|
+
pubkey,
|
|
45
|
+
verifierClassHash,
|
|
46
|
+
label = "primary",
|
|
47
|
+
deployerAddress = 0n
|
|
48
|
+
} = input;
|
|
49
|
+
const salt = computeAddressSalt(primaryKind, pubkey);
|
|
50
|
+
const kindTag = BigInt(starknet.shortString.encodeShortString(primaryKind));
|
|
51
|
+
const labelFelt = BigInt(starknet.shortString.encodeShortString(label));
|
|
52
|
+
const ctor = [
|
|
53
|
+
kindTag,
|
|
54
|
+
verifierClassHash,
|
|
55
|
+
BigInt(pubkey.length),
|
|
56
|
+
...pubkey,
|
|
57
|
+
labelFelt
|
|
58
|
+
];
|
|
59
|
+
const addressHex = starknet.hash.calculateContractAddressFromHash(
|
|
60
|
+
"0x" + salt.toString(16),
|
|
61
|
+
"0x" + classHash.toString(16),
|
|
62
|
+
ctor.map((x) => typeof x === "bigint" ? "0x" + x.toString(16) : x),
|
|
63
|
+
"0x" + deployerAddress.toString(16)
|
|
64
|
+
);
|
|
65
|
+
return BigInt(addressHex);
|
|
66
|
+
}
|
|
67
|
+
var init_compute_wallet_address = __esm({
|
|
68
|
+
"src/shhh/compute-wallet-address.ts"() {
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
function computeISignerId() {
|
|
72
|
+
return BigInt(starknet.hash.starknetKeccak(exports.ISIGNER_CANONICAL_LABEL));
|
|
73
|
+
}
|
|
74
|
+
function toBigInt(x) {
|
|
75
|
+
if (typeof x === "bigint") return x;
|
|
76
|
+
if (typeof x === "number") return BigInt(x);
|
|
77
|
+
return BigInt(x);
|
|
78
|
+
}
|
|
79
|
+
function poseidonSpan(values) {
|
|
80
|
+
const felts = values.map((v) => typeof v === "bigint" ? "0x" + v.toString(16) : v);
|
|
81
|
+
return BigInt(starknet.hash.computePoseidonHashOnElements(felts));
|
|
82
|
+
}
|
|
83
|
+
function hashCall(call) {
|
|
84
|
+
const calldataFelts = starknet.CallData.toHex(
|
|
85
|
+
Array.isArray(call.calldata) ? call.calldata : [call.calldata]
|
|
86
|
+
);
|
|
87
|
+
const calldataItems = calldataFelts.map((x) => BigInt(x));
|
|
88
|
+
const calldataHash = poseidonSpan(calldataItems);
|
|
89
|
+
return poseidonSpan([
|
|
90
|
+
CALL_TYPE_HASH_REV1,
|
|
91
|
+
toBigInt(call.contractAddress),
|
|
92
|
+
toBigInt(starknet.hash.getSelectorFromName(call.entrypoint)),
|
|
93
|
+
calldataHash
|
|
94
|
+
]);
|
|
95
|
+
}
|
|
96
|
+
function hashCalls(calls) {
|
|
97
|
+
const callHashes = calls.map(hashCall);
|
|
98
|
+
return poseidonSpan(callHashes);
|
|
99
|
+
}
|
|
100
|
+
function hashStarknetDomain(chainIdFelt) {
|
|
101
|
+
return poseidonSpan([
|
|
102
|
+
STARKNET_DOMAIN_TYPE_HASH_REV1,
|
|
103
|
+
BigInt(exports.OE_DOMAIN_NAME),
|
|
104
|
+
exports.OE_DOMAIN_VERSION,
|
|
105
|
+
toBigInt(chainIdFelt),
|
|
106
|
+
exports.OE_DOMAIN_REVISION
|
|
107
|
+
]);
|
|
108
|
+
}
|
|
109
|
+
function hashOutsideExecutionStruct(oe) {
|
|
110
|
+
const callsArrayHash = hashCalls(oe.calls);
|
|
111
|
+
return poseidonSpan([
|
|
112
|
+
OUTSIDE_EXECUTION_TYPE_HASH_REV1,
|
|
113
|
+
toBigInt(oe.caller),
|
|
114
|
+
toBigInt(oe.nonce),
|
|
115
|
+
oe.execute_after,
|
|
116
|
+
oe.execute_before,
|
|
117
|
+
callsArrayHash
|
|
118
|
+
]);
|
|
119
|
+
}
|
|
120
|
+
function computeSnip12Hash(oe, contractAddress, chainIdFelt) {
|
|
121
|
+
const domainHash = hashStarknetDomain(chainIdFelt);
|
|
122
|
+
const structHash = hashOutsideExecutionStruct(oe);
|
|
123
|
+
return poseidonSpan([
|
|
124
|
+
BigInt(exports.STARKNET_MESSAGE_PREFIX),
|
|
125
|
+
domainHash,
|
|
126
|
+
toBigInt(contractAddress),
|
|
127
|
+
structHash
|
|
128
|
+
]);
|
|
129
|
+
}
|
|
130
|
+
function snip12HashToHexAsciiBytes(h) {
|
|
131
|
+
const hex = h.toString(16).padStart(64, "0");
|
|
132
|
+
return new TextEncoder().encode(hex);
|
|
133
|
+
}
|
|
134
|
+
var OUTSIDE_EXECUTION_TYPE_HASH_REV1, CALL_TYPE_HASH_REV1, STARKNET_DOMAIN_TYPE_HASH_REV1; exports.STARKNET_MESSAGE_PREFIX = void 0; exports.OE_DOMAIN_NAME = void 0; exports.OE_DOMAIN_VERSION = void 0; exports.OE_DOMAIN_REVISION = void 0; exports.SIG_VERSION_V1_HEX_ASCII = void 0; exports.SIG_VERSION_V2_SNIP12 = void 0; exports.SIG_VERSION_V2_THRESHOLD = void 0; exports.ISRC9_V2_ID = void 0; exports.ISIGNER_ID = void 0; exports.ISIGNER_CANONICAL_LABEL = void 0;
|
|
135
|
+
var init_snip12_hash = __esm({
|
|
136
|
+
"src/shhh/snip12-hash.ts"() {
|
|
137
|
+
OUTSIDE_EXECUTION_TYPE_HASH_REV1 = 0x5a4b49e17039355cd95d1f0981d75901191d1319b1f4b05a9a791d218d7e0cn;
|
|
138
|
+
CALL_TYPE_HASH_REV1 = 0x3635c7f2a7ba93844c0d064e18e487f35ab90f7c39d00f186a781fc3f0c2ca9n;
|
|
139
|
+
STARKNET_DOMAIN_TYPE_HASH_REV1 = 0x1ff2f602e42168014d405a94f75e8a93d640751d71d16311266e140d8b0a210n;
|
|
140
|
+
exports.STARKNET_MESSAGE_PREFIX = starknet.shortString.encodeShortString("StarkNet Message");
|
|
141
|
+
exports.OE_DOMAIN_NAME = starknet.shortString.encodeShortString("Account.execute_from_outside");
|
|
142
|
+
exports.OE_DOMAIN_VERSION = 2n;
|
|
143
|
+
exports.OE_DOMAIN_REVISION = 1n;
|
|
144
|
+
exports.SIG_VERSION_V1_HEX_ASCII = starknet.shortString.encodeShortString("V1_HEX_ASCII");
|
|
145
|
+
exports.SIG_VERSION_V2_SNIP12 = starknet.shortString.encodeShortString("V2_SNIP12");
|
|
146
|
+
exports.SIG_VERSION_V2_THRESHOLD = starknet.shortString.encodeShortString("V2_THRESHOLD");
|
|
147
|
+
exports.ISRC9_V2_ID = BigInt(
|
|
148
|
+
"0x1d1144bb2138366ff28d8e9ab57456b1d332ac42196230c3a602003c89872"
|
|
149
|
+
);
|
|
150
|
+
exports.ISIGNER_ID = BigInt(
|
|
151
|
+
"0x94c5a761f34b25a4e603c651ac0e1fc4fad9cdb5517f7fa1bb54044c7e5ef8"
|
|
152
|
+
);
|
|
153
|
+
exports.ISIGNER_CANONICAL_LABEL = "ISigner_V1";
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
function assertValidV2Snip12Envelope(envelope) {
|
|
157
|
+
if (envelope.length < 2) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`THRESH: inner envelope must have at least 2 felts (tag + owner_id); got ${envelope.length}`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
const tag = envelope[0];
|
|
163
|
+
if (tag !== V2_SNIP12_TAG) {
|
|
164
|
+
if (tag === V2_THRESHOLD_TAG) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
"THRESH: nested threshold envelopes are not allowed; inner envelopes must be V2_SNIP12"
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
const decoded = _tryDecodeShortString(tag);
|
|
170
|
+
throw new Error(
|
|
171
|
+
`THRESH: inner envelope tag must be V2_SNIP12; got ${decoded ?? "0x" + tag.toString(16)}`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
return envelope[1];
|
|
175
|
+
}
|
|
176
|
+
function _tryDecodeShortString(felt) {
|
|
177
|
+
try {
|
|
178
|
+
return starknet.shortString.decodeShortString("0x" + felt.toString(16));
|
|
179
|
+
} catch {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function buildThresholdEnvelope(args) {
|
|
184
|
+
const { innerEnvelopes } = args;
|
|
185
|
+
if (innerEnvelopes.length < exports.MIN_THRESHOLD_OWNERS) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`THRESH: need at least ${exports.MIN_THRESHOLD_OWNERS} inner envelopes; got ${innerEnvelopes.length}. Use wrap_v2_snip12_envelope for single-owner OEs.`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
const seenOwners = /* @__PURE__ */ new Set();
|
|
191
|
+
const out = [V2_THRESHOLD_TAG, BigInt(innerEnvelopes.length)];
|
|
192
|
+
for (let i = 0; i < innerEnvelopes.length; i++) {
|
|
193
|
+
const env = innerEnvelopes[i];
|
|
194
|
+
const ownerId = assertValidV2Snip12Envelope(env);
|
|
195
|
+
const key = ownerId.toString();
|
|
196
|
+
if (seenOwners.has(key)) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`THRESH: duplicate owner_id ${"0x" + ownerId.toString(16)} at inner envelope ${i}`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
seenOwners.add(key);
|
|
202
|
+
out.push(BigInt(env.length));
|
|
203
|
+
for (const felt of env) {
|
|
204
|
+
if (felt < 0n) {
|
|
205
|
+
throw new RangeError(
|
|
206
|
+
`THRESH: envelope ${i} contains negative felt (cannot serialize as felt252)`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
out.push(felt);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
function parseThresholdEnvelope(envelope) {
|
|
215
|
+
if (envelope.length < 2) {
|
|
216
|
+
throw new Error(`THRESH: envelope too short (need at least [tag, n]); got ${envelope.length}`);
|
|
217
|
+
}
|
|
218
|
+
if (envelope[0] !== V2_THRESHOLD_TAG) {
|
|
219
|
+
throw new Error(`THRESH: envelope tag must be V2_THRESHOLD; got 0x${envelope[0].toString(16)}`);
|
|
220
|
+
}
|
|
221
|
+
const n = Number(envelope[1]);
|
|
222
|
+
if (!Number.isInteger(n) || n < exports.MIN_THRESHOLD_OWNERS) {
|
|
223
|
+
throw new Error(`THRESH: n must be an integer >= ${exports.MIN_THRESHOLD_OWNERS}; got ${envelope[1]}`);
|
|
224
|
+
}
|
|
225
|
+
const seenOwners = /* @__PURE__ */ new Set();
|
|
226
|
+
const innerEnvelopes = [];
|
|
227
|
+
let cursor = 2;
|
|
228
|
+
for (let i = 0; i < n; i++) {
|
|
229
|
+
if (cursor >= envelope.length) {
|
|
230
|
+
throw new Error(`THRESH: envelope truncated at inner ${i} (no length felt)`);
|
|
231
|
+
}
|
|
232
|
+
const envLen = Number(envelope[cursor]);
|
|
233
|
+
cursor += 1;
|
|
234
|
+
if (!Number.isInteger(envLen) || envLen < 2) {
|
|
235
|
+
throw new Error(`THRESH: inner ${i} env_len must be >= 2; got ${envLen}`);
|
|
236
|
+
}
|
|
237
|
+
if (cursor + envLen > envelope.length) {
|
|
238
|
+
throw new Error(
|
|
239
|
+
`THRESH: inner ${i} would overflow envelope (env_len ${envLen}, cursor ${cursor}, total ${envelope.length})`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
const inner = envelope.slice(cursor, cursor + envLen);
|
|
243
|
+
for (const felt of inner) {
|
|
244
|
+
if (felt < 0n) {
|
|
245
|
+
throw new RangeError(
|
|
246
|
+
`THRESH: inner ${i} contains negative felt (cannot serialize as felt252)`
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const ownerId = assertValidV2Snip12Envelope(inner);
|
|
251
|
+
const key = ownerId.toString();
|
|
252
|
+
if (seenOwners.has(key)) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
`THRESH: duplicate owner_id ${"0x" + ownerId.toString(16)} at inner envelope ${i}`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
seenOwners.add(key);
|
|
258
|
+
innerEnvelopes.push(inner);
|
|
259
|
+
cursor += envLen;
|
|
260
|
+
}
|
|
261
|
+
if (cursor !== envelope.length) {
|
|
262
|
+
throw new Error(
|
|
263
|
+
`THRESH: trailing bytes after inner ${n - 1} (cursor ${cursor}, length ${envelope.length})`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
return { innerEnvelopes };
|
|
267
|
+
}
|
|
268
|
+
function estimateThresholdGasOverhead(owners) {
|
|
269
|
+
if (owners.length < exports.MIN_THRESHOLD_OWNERS) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
`THRESH: need at least ${exports.MIN_THRESHOLD_OWNERS} owners to estimate threshold gas; got ${owners.length}`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
let total = 0;
|
|
275
|
+
for (const { signerKind } of owners) {
|
|
276
|
+
const overhead = shared.SHHH_KIND_GAS_OVERHEAD[signerKind];
|
|
277
|
+
if (overhead === void 0) {
|
|
278
|
+
throw new Error(
|
|
279
|
+
`THRESH: unknown signer kind ${signerKind} \u2014 missing from SHHH_KIND_GAS_OVERHEAD`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
total += overhead;
|
|
283
|
+
}
|
|
284
|
+
return total;
|
|
285
|
+
}
|
|
286
|
+
var V2_SNIP12_TAG, V2_THRESHOLD_TAG; exports.MIN_THRESHOLD_OWNERS = void 0;
|
|
287
|
+
var init_threshold = __esm({
|
|
288
|
+
"src/shhh/threshold.ts"() {
|
|
289
|
+
init_snip12_hash();
|
|
290
|
+
V2_SNIP12_TAG = BigInt(exports.SIG_VERSION_V2_SNIP12);
|
|
291
|
+
V2_THRESHOLD_TAG = BigInt(exports.SIG_VERSION_V2_THRESHOLD);
|
|
292
|
+
exports.MIN_THRESHOLD_OWNERS = 2;
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// src/shhh/oe.ts
|
|
297
|
+
var oe_exports = {};
|
|
298
|
+
__export(oe_exports, {
|
|
299
|
+
ANY_CALLER_FELT: () => exports.ANY_CALLER_FELT,
|
|
300
|
+
MAX_OE_VALIDITY_WINDOW_SECONDS: () => exports.MAX_OE_VALIDITY_WINDOW_SECONDS,
|
|
301
|
+
OE_EXECUTE_AFTER_CLOCK_SKEW_BUFFER: () => OE_EXECUTE_AFTER_CLOCK_SKEW_BUFFER,
|
|
302
|
+
buildOutsideExecution: () => buildOutsideExecution,
|
|
303
|
+
computeOeHash: () => computeOeHash,
|
|
304
|
+
wrapV2Snip12Envelope: () => wrapV2Snip12Envelope,
|
|
305
|
+
wrapV2ThresholdEnvelope: () => wrapV2ThresholdEnvelope
|
|
306
|
+
});
|
|
307
|
+
function buildOutsideExecution(params) {
|
|
308
|
+
const nowSec = BigInt(Math.floor(Date.now() / 1e3));
|
|
309
|
+
const after = params.executeAfter ?? nowSec - BigInt(OE_EXECUTE_AFTER_CLOCK_SKEW_BUFFER);
|
|
310
|
+
const before = params.executeBefore ?? nowSec + BigInt(exports.MAX_OE_VALIDITY_WINDOW_SECONDS) - BigInt(OE_EXECUTE_AFTER_CLOCK_SKEW_BUFFER);
|
|
311
|
+
if (before <= after) {
|
|
312
|
+
throw new Error(
|
|
313
|
+
`executeBefore (${before}) must be strictly greater than executeAfter (${after}).`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
if (before - after > BigInt(exports.MAX_OE_VALIDITY_WINDOW_SECONDS)) {
|
|
317
|
+
throw new Error(
|
|
318
|
+
`OE validity window ${before - after}s exceeds max ${exports.MAX_OE_VALIDITY_WINDOW_SECONDS}s (audit M-2). Adjust executeBefore.`
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
caller: params.caller ?? exports.ANY_CALLER_FELT,
|
|
323
|
+
nonce: params.nonce,
|
|
324
|
+
execute_after: after,
|
|
325
|
+
execute_before: before,
|
|
326
|
+
calls: params.calls
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function computeOeHash(oe, accountAddress, chainId) {
|
|
330
|
+
return computeSnip12Hash(oe, accountAddress, chainId);
|
|
331
|
+
}
|
|
332
|
+
function wrapV2Snip12Envelope(params) {
|
|
333
|
+
const tag = typeof params.kindTag === "bigint" ? params.kindTag : BigInt(starknet.shortString.encodeShortString(params.kindTag));
|
|
334
|
+
return [
|
|
335
|
+
BigInt(exports.SIG_VERSION_V2_SNIP12),
|
|
336
|
+
params.ownerId,
|
|
337
|
+
tag,
|
|
338
|
+
...params.payload.map((v) => typeof v === "bigint" ? v : BigInt(v))
|
|
339
|
+
];
|
|
340
|
+
}
|
|
341
|
+
function wrapV2ThresholdEnvelope(innerEnvelopes) {
|
|
342
|
+
return buildThresholdEnvelope({ innerEnvelopes });
|
|
343
|
+
}
|
|
344
|
+
exports.ANY_CALLER_FELT = void 0; exports.MAX_OE_VALIDITY_WINDOW_SECONDS = void 0; var OE_EXECUTE_AFTER_CLOCK_SKEW_BUFFER;
|
|
345
|
+
var init_oe = __esm({
|
|
346
|
+
"src/shhh/oe.ts"() {
|
|
347
|
+
init_snip12_hash();
|
|
348
|
+
init_threshold();
|
|
349
|
+
exports.ANY_CALLER_FELT = BigInt(starknet.shortString.encodeShortString("ANY_CALLER"));
|
|
350
|
+
exports.MAX_OE_VALIDITY_WINDOW_SECONDS = 2 * 60 * 60;
|
|
351
|
+
OE_EXECUTE_AFTER_CLOCK_SKEW_BUFFER = 60;
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
async function ensureGaragaInit() {
|
|
355
|
+
if (garagaInitPromise) return garagaInitPromise;
|
|
356
|
+
const attempt = (async () => {
|
|
357
|
+
const garaga = await import('garaga');
|
|
358
|
+
await garaga.init();
|
|
359
|
+
return garaga;
|
|
360
|
+
})();
|
|
361
|
+
garagaInitPromise = attempt;
|
|
362
|
+
try {
|
|
363
|
+
return await attempt;
|
|
364
|
+
} catch (err) {
|
|
365
|
+
if (garagaInitPromise === attempt) garagaInitPromise = null;
|
|
366
|
+
throw err;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function bytesToLeU256(bytes) {
|
|
370
|
+
if (bytes.length !== 32) {
|
|
371
|
+
throw new Error(`Ed25519 value must be exactly 32 bytes; got ${bytes.length}.`);
|
|
372
|
+
}
|
|
373
|
+
let low = 0n;
|
|
374
|
+
let high = 0n;
|
|
375
|
+
for (let i = 0; i < 16; i++) low += BigInt(bytes[i]) << BigInt(8 * i);
|
|
376
|
+
for (let i = 0; i < 16; i++) high += BigInt(bytes[16 + i]) << BigInt(8 * i);
|
|
377
|
+
return { low, high };
|
|
378
|
+
}
|
|
379
|
+
function bytesToLeBigInt(bytes) {
|
|
380
|
+
let x = 0n;
|
|
381
|
+
for (let i = bytes.length - 1; i >= 0; i--) x = x << 8n | BigInt(bytes[i]);
|
|
382
|
+
return x;
|
|
383
|
+
}
|
|
384
|
+
function ed25519SignedBytes(messageHash) {
|
|
385
|
+
return snip12HashToHexAsciiBytes(messageHash);
|
|
386
|
+
}
|
|
387
|
+
function ed25519OwnerId(publicKey) {
|
|
388
|
+
const { low, high } = bytesToLeU256(publicKey);
|
|
389
|
+
return ownerCommitment(ED25519_KIND_TAG_FELT, [low, high]);
|
|
390
|
+
}
|
|
391
|
+
function ed25519PubkeyFelts(publicKey) {
|
|
392
|
+
const { low, high } = bytesToLeU256(publicKey);
|
|
393
|
+
return [low, high];
|
|
394
|
+
}
|
|
395
|
+
async function buildEd25519Envelope(params) {
|
|
396
|
+
const ownerIndex = params.ownerIndex ?? 0n;
|
|
397
|
+
if (ownerIndex < 0n || ownerIndex >= 0x100000000n) {
|
|
398
|
+
throw new Error(
|
|
399
|
+
`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.`
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
if (params.privateKey.length !== 32) {
|
|
403
|
+
throw new Error(
|
|
404
|
+
`Ed25519 private key must be exactly 32 bytes; got ${params.privateKey.length}.`
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
const ed = await import('@noble/ed25519');
|
|
408
|
+
const publicKey = await ed.getPublicKeyAsync(params.privateKey);
|
|
409
|
+
const signedBytes = ed25519SignedBytes(params.messageHash);
|
|
410
|
+
const signature = await ed.signAsync(signedBytes, params.privateKey);
|
|
411
|
+
return assembleEd25519Envelope({
|
|
412
|
+
R: signature.slice(0, 32),
|
|
413
|
+
s: signature.slice(32, 64),
|
|
414
|
+
publicKey,
|
|
415
|
+
signedBytes,
|
|
416
|
+
ownerIndex
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
async function buildEd25519EnvelopeFromSignature(params) {
|
|
420
|
+
const ownerIndex = params.ownerIndex ?? 0n;
|
|
421
|
+
if (ownerIndex < 0n || ownerIndex >= 0x100000000n) {
|
|
422
|
+
throw new Error(
|
|
423
|
+
`buildEd25519EnvelopeFromSignature: 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); do NOT pass an owner_commitment or pubkey here.`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
if (params.signature.length !== 64) {
|
|
427
|
+
throw new Error(
|
|
428
|
+
`Ed25519 signature must be exactly 64 bytes (R\u2016s); got ${params.signature.length}.`
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
if (params.publicKey.length !== 32) {
|
|
432
|
+
throw new Error(`Ed25519 public key must be exactly 32 bytes; got ${params.publicKey.length}.`);
|
|
433
|
+
}
|
|
434
|
+
const signedBytes = ed25519SignedBytes(params.messageHash);
|
|
435
|
+
return assembleEd25519Envelope({
|
|
436
|
+
R: params.signature.slice(0, 32),
|
|
437
|
+
s: params.signature.slice(32, 64),
|
|
438
|
+
publicKey: params.publicKey,
|
|
439
|
+
signedBytes,
|
|
440
|
+
ownerIndex
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
async function assembleEd25519Envelope(parts) {
|
|
444
|
+
const garaga = await ensureGaragaInit();
|
|
445
|
+
const payload = garaga.eddsaCalldataBuilder(
|
|
446
|
+
bytesToLeBigInt(parts.R),
|
|
447
|
+
bytesToLeBigInt(parts.s),
|
|
448
|
+
bytesToLeBigInt(parts.publicKey),
|
|
449
|
+
parts.signedBytes,
|
|
450
|
+
false
|
|
451
|
+
);
|
|
452
|
+
return wrapV2Snip12Envelope({
|
|
453
|
+
ownerId: parts.ownerIndex,
|
|
454
|
+
kindTag: ED25519_KIND_TAG,
|
|
455
|
+
payload
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
var ED25519_KIND_TAG, ED25519_KIND_TAG_FELT, garagaInitPromise;
|
|
459
|
+
var init_ed25519 = __esm({
|
|
460
|
+
"src/shhh/signers/ed25519.ts"() {
|
|
461
|
+
init_compute_wallet_address();
|
|
462
|
+
init_oe();
|
|
463
|
+
init_snip12_hash();
|
|
464
|
+
ED25519_KIND_TAG = "ED25519";
|
|
465
|
+
ED25519_KIND_TAG_FELT = BigInt(starknet.shortString.encodeShortString(ED25519_KIND_TAG));
|
|
466
|
+
garagaInitPromise = null;
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
exports.encryptPrivateKey = void 0; exports.decryptPrivateKey = void 0;
|
|
470
|
+
var init_encryption = __esm({
|
|
471
|
+
"src/lib/encryption.ts"() {
|
|
472
|
+
exports.encryptPrivateKey = (privateKey, password) => {
|
|
473
|
+
if (!privateKey || !password) {
|
|
474
|
+
throw new Error("Private key and password are required");
|
|
475
|
+
}
|
|
476
|
+
return CryptoES__default.default.AES.encrypt(privateKey, password).toString();
|
|
477
|
+
};
|
|
478
|
+
exports.decryptPrivateKey = (encryptedPrivateKey, password) => {
|
|
479
|
+
if (!encryptedPrivateKey || !password) {
|
|
480
|
+
throw new Error("Encrypted private key and password are required");
|
|
481
|
+
}
|
|
482
|
+
try {
|
|
483
|
+
const bytes = CryptoES__default.default.AES.decrypt(encryptedPrivateKey, password);
|
|
484
|
+
const decrypted = bytes.toString(CryptoES__default.default.enc.Utf8);
|
|
485
|
+
if (!decrypted) {
|
|
486
|
+
throw new Error("Decryption resulted in empty string");
|
|
487
|
+
}
|
|
488
|
+
return decrypted;
|
|
489
|
+
} catch (error) {
|
|
490
|
+
if (error instanceof Error) {
|
|
491
|
+
throw error;
|
|
492
|
+
}
|
|
493
|
+
throw new Error(`Decryption failed: ${error}`);
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
// src/encryption.ts
|
|
500
|
+
var init_encryption2 = __esm({
|
|
501
|
+
"src/encryption.ts"() {
|
|
502
|
+
init_encryption();
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
function buildStarkEnvelope(params) {
|
|
506
|
+
const pkHex = "0x" + (typeof params.privateKey === "bigint" ? params.privateKey : BigInt(params.privateKey)).toString(16).padStart(64, "0");
|
|
507
|
+
const sig = starknet.ec.starkCurve.sign("0x" + params.messageHash.toString(16), pkHex);
|
|
508
|
+
const r = BigInt("0x" + sig.r.toString(16));
|
|
509
|
+
const s = BigInt("0x" + sig.s.toString(16));
|
|
510
|
+
const ownerIndex = params.ownerIndex ?? 0n;
|
|
511
|
+
if (ownerIndex < 0n || ownerIndex >= 0x100000000n) {
|
|
512
|
+
throw new Error(
|
|
513
|
+
`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.`
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
return wrapV2Snip12Envelope({
|
|
517
|
+
ownerId: ownerIndex,
|
|
518
|
+
kindTag: STARK_KIND_TAG,
|
|
519
|
+
payload: [r, s]
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
function starkOwnerId(starkPubKey) {
|
|
523
|
+
return ownerCommitment(STARK_KIND_TAG_FELT, [starkPubKey]);
|
|
524
|
+
}
|
|
525
|
+
function starkPubkeyFelts(starkPubKey) {
|
|
526
|
+
return [typeof starkPubKey === "bigint" ? starkPubKey : BigInt(starkPubKey)];
|
|
527
|
+
}
|
|
528
|
+
var STARK_KIND_TAG, STARK_KIND_TAG_FELT;
|
|
529
|
+
var init_stark = __esm({
|
|
530
|
+
"src/shhh/signers/stark.ts"() {
|
|
531
|
+
init_compute_wallet_address();
|
|
532
|
+
init_oe();
|
|
533
|
+
STARK_KIND_TAG = "STARK";
|
|
534
|
+
STARK_KIND_TAG_FELT = BigInt(starknet.shortString.encodeShortString(STARK_KIND_TAG));
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
// src/shhh/execute-paymaster-raw.ts
|
|
539
|
+
var execute_paymaster_raw_exports = {};
|
|
540
|
+
__export(execute_paymaster_raw_exports, {
|
|
541
|
+
executeShhhSponsoredRaw: () => executeShhhSponsoredRaw,
|
|
542
|
+
resolveShhhChainId: () => resolveShhhChainId,
|
|
543
|
+
serializeExecuteFromOutsideV2Calldata: () => serializeExecuteFromOutsideV2Calldata
|
|
544
|
+
});
|
|
545
|
+
function resolveShhhChainId(params) {
|
|
546
|
+
if (params.override !== void 0) {
|
|
547
|
+
return typeof params.override === "bigint" ? "0x" + params.override.toString(16) : params.override;
|
|
548
|
+
}
|
|
549
|
+
const url = (params.baseUrl ?? "").toLowerCase();
|
|
550
|
+
if (url.includes("sepolia")) return SN_SEPOLIA_CHAIN_ID;
|
|
551
|
+
return SN_MAIN_CHAIN_ID;
|
|
552
|
+
}
|
|
553
|
+
function serializeExecuteFromOutsideV2Calldata(params) {
|
|
554
|
+
const out = [];
|
|
555
|
+
const oe = params.outsideExecution;
|
|
556
|
+
const toBig = (v) => typeof v === "bigint" ? v : BigInt(v);
|
|
557
|
+
out.push(toBig(oe.caller));
|
|
558
|
+
out.push(toBig(oe.nonce));
|
|
559
|
+
out.push(oe.execute_after);
|
|
560
|
+
out.push(oe.execute_before);
|
|
561
|
+
out.push(BigInt(oe.calls.length));
|
|
562
|
+
for (const call of oe.calls) {
|
|
563
|
+
out.push(BigInt(call.contractAddress));
|
|
564
|
+
out.push(BigInt(starknet.hash.getSelectorFromName(call.entrypoint)));
|
|
565
|
+
const callData = Array.isArray(call.calldata) ? call.calldata : [];
|
|
566
|
+
out.push(BigInt(callData.length));
|
|
567
|
+
for (const datum of callData) {
|
|
568
|
+
out.push(typeof datum === "bigint" ? datum : BigInt(datum));
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
out.push(BigInt(params.signature.length));
|
|
572
|
+
for (const sigFelt of params.signature) {
|
|
573
|
+
out.push(sigFelt);
|
|
574
|
+
}
|
|
575
|
+
return out.map((v) => "0x" + v.toString(16));
|
|
576
|
+
}
|
|
577
|
+
function newOeNonce() {
|
|
578
|
+
const tsMs = BigInt(Date.now());
|
|
579
|
+
const rand = BigInt(Math.floor(Math.random() * 16777215));
|
|
580
|
+
return tsMs << 24n | rand;
|
|
581
|
+
}
|
|
582
|
+
async function executeShhhSponsoredRaw(params) {
|
|
583
|
+
const signerKind = params.signerKind ?? "STARK";
|
|
584
|
+
const SDK_SHIPPED_OE_KINDS = ["STARK", "ED25519"];
|
|
585
|
+
if (!SDK_SHIPPED_OE_KINDS.includes(signerKind)) {
|
|
586
|
+
throw new Error(
|
|
587
|
+
`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.`
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
const privateKeyHex = exports.decryptPrivateKey(params.wallet.encryptedPrivateKey, params.encryptKey);
|
|
591
|
+
if (!privateKeyHex) {
|
|
592
|
+
throw new Error(
|
|
593
|
+
"Failed to decrypt private key for SHHH OE. Verify the encryptKey matches the one used at wallet creation."
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
const oe = buildOutsideExecution({
|
|
597
|
+
nonce: params.nonce ?? newOeNonce(),
|
|
598
|
+
calls: params.calls,
|
|
599
|
+
caller: exports.ANY_CALLER_FELT
|
|
600
|
+
});
|
|
601
|
+
const chainId = resolveShhhChainId({
|
|
602
|
+
override: params.chainId,
|
|
603
|
+
baseUrl: params.client.baseUrl
|
|
604
|
+
});
|
|
605
|
+
const messageHash = computeOeHash(oe, params.wallet.publicKey, chainId);
|
|
606
|
+
let signature;
|
|
607
|
+
if (signerKind === "STARK") {
|
|
608
|
+
signature = buildStarkEnvelope({
|
|
609
|
+
privateKey: privateKeyHex,
|
|
610
|
+
messageHash
|
|
611
|
+
});
|
|
612
|
+
} else {
|
|
613
|
+
const edKeyHex = privateKeyHex.startsWith("0x") ? privateKeyHex.slice(2) : privateKeyHex;
|
|
614
|
+
if (edKeyHex.length !== 64) {
|
|
615
|
+
throw new Error(
|
|
616
|
+
`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.`
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
const edPrivKey = Uint8Array.from(Buffer.from(edKeyHex, "hex"));
|
|
620
|
+
signature = await buildEd25519Envelope({
|
|
621
|
+
privateKey: edPrivKey,
|
|
622
|
+
messageHash
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
const calldata = serializeExecuteFromOutsideV2Calldata({
|
|
626
|
+
outsideExecution: oe,
|
|
627
|
+
signature
|
|
628
|
+
});
|
|
629
|
+
const response = await params.client.post({
|
|
630
|
+
endpoint: "/transactions/execute-sponsored-raw",
|
|
631
|
+
bearerToken: params.bearerToken,
|
|
632
|
+
body: {
|
|
633
|
+
accountAddress: params.wallet.publicKey,
|
|
634
|
+
calldata,
|
|
635
|
+
signerKind,
|
|
636
|
+
...params.externalUserId ? { externalUserId: params.externalUserId } : {}
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
if (!response || !response.transactionHash) {
|
|
640
|
+
throw new Error(
|
|
641
|
+
"execute-sponsored-raw response missing transactionHash. Check the chipi-back logs for upstream paymaster errors."
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
return response.transactionHash;
|
|
645
|
+
}
|
|
646
|
+
var SN_MAIN_CHAIN_ID, SN_SEPOLIA_CHAIN_ID;
|
|
647
|
+
var init_execute_paymaster_raw = __esm({
|
|
648
|
+
"src/shhh/execute-paymaster-raw.ts"() {
|
|
649
|
+
init_encryption2();
|
|
650
|
+
init_oe();
|
|
651
|
+
init_stark();
|
|
652
|
+
init_ed25519();
|
|
653
|
+
SN_MAIN_CHAIN_ID = "0x534e5f4d41494e";
|
|
654
|
+
SN_SEPOLIA_CHAIN_ID = "0x534e5f5345504f4c4941";
|
|
655
|
+
}
|
|
656
|
+
});
|
|
14
657
|
var ChipiClient = class {
|
|
15
658
|
constructor(config) {
|
|
16
659
|
if (!shared.isValidApiKey(config.apiPublicKey)) {
|
|
@@ -193,294 +836,60 @@ var ChipiClient = class {
|
|
|
193
836
|
}
|
|
194
837
|
}
|
|
195
838
|
};
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
839
|
+
|
|
840
|
+
// src/wallets.ts
|
|
841
|
+
init_compute_wallet_address();
|
|
842
|
+
init_ed25519();
|
|
843
|
+
init_encryption2();
|
|
844
|
+
|
|
845
|
+
// src/migrate-to-shhh.ts
|
|
846
|
+
init_encryption2();
|
|
847
|
+
|
|
848
|
+
// src/execute-paymaster-transaction.ts
|
|
849
|
+
init_encryption2();
|
|
850
|
+
function unwrapPrepareResponse(response) {
|
|
851
|
+
if (!response || typeof response !== "object") {
|
|
852
|
+
throw new Error(
|
|
853
|
+
"Invalid response from /transactions/prepare-typed-data: response is not an object"
|
|
854
|
+
);
|
|
199
855
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
856
|
+
if ("typedData" in response) {
|
|
857
|
+
const resp = response;
|
|
858
|
+
if (!resp.typedData || typeof resp.typedData !== "object" || !("types" in resp.typedData) || !("message" in resp.typedData)) {
|
|
859
|
+
throw new Error(
|
|
860
|
+
"Invalid response from /transactions/prepare-typed-data: typedData structure is invalid"
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
return { typedData: resp.typedData, walletType: resp.walletType };
|
|
205
864
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
865
|
+
if ("types" in response && "message" in response) {
|
|
866
|
+
return { typedData: response };
|
|
867
|
+
}
|
|
868
|
+
const apiMessage = typeof response.message === "string" ? response.message : void 0;
|
|
869
|
+
throw new Error(
|
|
870
|
+
apiMessage ?? "Invalid response from /transactions/prepare-typed-data: typedData is missing"
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
function normalizeSignature(signature) {
|
|
874
|
+
if (Array.isArray(signature)) {
|
|
875
|
+
if (signature.length < 2) {
|
|
876
|
+
throw new Error(`Array signature must have at least 2 elements, got ${signature.length}`);
|
|
211
877
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (
|
|
215
|
-
throw
|
|
878
|
+
const r = signature[0];
|
|
879
|
+
const s = signature[1];
|
|
880
|
+
if (r == null || s == null) {
|
|
881
|
+
throw new Error(`Signature elements must not be null/undefined. Got r=${r}, s=${s}`);
|
|
216
882
|
}
|
|
217
|
-
|
|
883
|
+
return { r: String(r), s: String(s) };
|
|
218
884
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const privateKey = privateKeyBytes.toString(CryptoES__default.default.enc.Hex);
|
|
228
|
-
const fullPrivateKey = `0x${privateKey}`;
|
|
229
|
-
const maxStarknetValue = BigInt(
|
|
230
|
-
"0x800000000000000000000000000000000000000000000000000000000000000"
|
|
231
|
-
);
|
|
232
|
-
const privateKeyBigInt = BigInt(fullPrivateKey) % maxStarknetValue;
|
|
233
|
-
return `0x${privateKeyBigInt.toString(16)}`;
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
async createWallet(params) {
|
|
237
|
-
try {
|
|
238
|
-
const {
|
|
239
|
-
encryptKey,
|
|
240
|
-
externalUserId,
|
|
241
|
-
bearerToken,
|
|
242
|
-
userId,
|
|
243
|
-
walletType = "CHIPI",
|
|
244
|
-
// Default to CHIPI wallet
|
|
245
|
-
classHash: customClassHash,
|
|
246
|
-
usePasskey,
|
|
247
|
-
chain
|
|
248
|
-
} = params;
|
|
249
|
-
if (!encryptKey) {
|
|
250
|
-
if (usePasskey) {
|
|
251
|
-
throw new Error(
|
|
252
|
-
"encryptKey is required when using passkey. The passkey authentication should have provided the encryptKey."
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
throw new Error("encryptKey is required for wallet creation");
|
|
256
|
-
}
|
|
257
|
-
const rpcUrl = walletType === "READY" ? shared.WALLET_RPC_ENDPOINTS.READY : shared.WALLET_RPC_ENDPOINTS.CHIPI;
|
|
258
|
-
const provider = new starknet.RpcProvider({ nodeUrl: rpcUrl });
|
|
259
|
-
const privateKeyAX = this.getPrivateKeyAX();
|
|
260
|
-
const starkKeyPubAX = starknet.ec.starkCurve.getStarkKey(privateKeyAX);
|
|
261
|
-
const constructorCallData = this.buildConstructorCallData(walletType, starkKeyPubAX);
|
|
262
|
-
const accountClassHash = customClassHash ?? (walletType === "READY" ? shared.WALLET_CLASS_HASHES.READY : shared.WALLET_CLASS_HASHES.CHIPI);
|
|
263
|
-
const publicKey = starknet.hash.calculateContractAddressFromHash(
|
|
264
|
-
starkKeyPubAX,
|
|
265
|
-
accountClassHash,
|
|
266
|
-
constructorCallData,
|
|
267
|
-
0
|
|
268
|
-
);
|
|
269
|
-
const account = new starknet.Account({ provider, address: publicKey, signer: privateKeyAX });
|
|
270
|
-
const typedDataResponse = await this.client.post({
|
|
271
|
-
endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}/prepare-creation`,
|
|
272
|
-
bearerToken,
|
|
273
|
-
body: {
|
|
274
|
-
chain,
|
|
275
|
-
publicKey,
|
|
276
|
-
walletType,
|
|
277
|
-
starkKeyPubAX,
|
|
278
|
-
...customClassHash && { classHash: customClassHash }
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
const { typedData: typedData2, accountClassHash: accountClassHashResponse } = typedDataResponse;
|
|
282
|
-
const userSignature = await account.signMessage(typedData2);
|
|
283
|
-
const deploymentData = {
|
|
284
|
-
class_hash: accountClassHashResponse,
|
|
285
|
-
salt: starkKeyPubAX,
|
|
286
|
-
unique: `${starknet.num.toHex(0)}`,
|
|
287
|
-
calldata: constructorCallData.map((value) => starknet.num.toHex(value))
|
|
288
|
-
};
|
|
289
|
-
const encryptedPrivateKey = encryptPrivateKey(privateKeyAX, encryptKey);
|
|
290
|
-
const encryptedPrivateKeyBackup = params.encryptKeyBackup ? encryptPrivateKey(privateKeyAX, params.encryptKeyBackup) : void 0;
|
|
291
|
-
const executeTransactionResponse = await this.client.post({
|
|
292
|
-
endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}`,
|
|
293
|
-
bearerToken,
|
|
294
|
-
body: {
|
|
295
|
-
externalUserId,
|
|
296
|
-
userId,
|
|
297
|
-
publicKey,
|
|
298
|
-
walletType,
|
|
299
|
-
chain,
|
|
300
|
-
userSignature: {
|
|
301
|
-
r: userSignature.r.toString(),
|
|
302
|
-
s: userSignature.s.toString(),
|
|
303
|
-
recovery: userSignature.recovery
|
|
304
|
-
},
|
|
305
|
-
typedData: typedData2,
|
|
306
|
-
encryptedPrivateKey,
|
|
307
|
-
...encryptedPrivateKeyBackup && { encryptedPrivateKeyBackup },
|
|
308
|
-
...params.authMethod && { authMethod: params.authMethod },
|
|
309
|
-
...params.credentialId && { credentialId: params.credentialId },
|
|
310
|
-
...params.prfSupported != null && { prfSupported: params.prfSupported },
|
|
311
|
-
...params.deviceType && { deviceType: params.deviceType },
|
|
312
|
-
deploymentData: {
|
|
313
|
-
...deploymentData,
|
|
314
|
-
salt: `${deploymentData.salt}`,
|
|
315
|
-
calldata: deploymentData.calldata.map((data) => `${data}`)
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
return executeTransactionResponse;
|
|
320
|
-
} catch (error) {
|
|
321
|
-
console.error("Detailed error:", error);
|
|
322
|
-
if (error instanceof Error && error.message.includes("SSL")) {
|
|
323
|
-
throw new Error(
|
|
324
|
-
"SSL connection error. Try using NODE_TLS_REJECT_UNAUTHORIZED=0 or verify the RPC URL"
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
throw new shared.ChipiTransactionError(
|
|
328
|
-
`Failed to create wallet: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
329
|
-
"WALLET_CREATION_FAILED"
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
/**
|
|
334
|
-
* Build constructor calldata based on wallet type
|
|
335
|
-
* - CHIPI: Simple OpenZeppelin account with just public_key
|
|
336
|
-
* - ARGENT: Argent X Account with owner/guardian structure
|
|
337
|
-
*/
|
|
338
|
-
buildConstructorCallData(walletType, starkKeyPubAX) {
|
|
339
|
-
if (walletType === "READY") {
|
|
340
|
-
const axSigner = new starknet.CairoCustomEnum({
|
|
341
|
-
Starknet: { pubkey: starkKeyPubAX }
|
|
342
|
-
});
|
|
343
|
-
const axGuardian = new starknet.CairoOption(starknet.CairoOptionVariant.None);
|
|
344
|
-
return starknet.CallData.compile({
|
|
345
|
-
owner: axSigner,
|
|
346
|
-
guardian: axGuardian
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
return starknet.CallData.compile({
|
|
350
|
-
public_key: starkKeyPubAX
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
/**
|
|
354
|
-
* Create a custodial merchant wallet
|
|
355
|
-
*/
|
|
356
|
-
async createCustodialWallet({
|
|
357
|
-
params,
|
|
358
|
-
bearerToken
|
|
359
|
-
}) {
|
|
360
|
-
const response = await this.client.post({
|
|
361
|
-
endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}/custodial`,
|
|
362
|
-
bearerToken,
|
|
363
|
-
body: params
|
|
364
|
-
});
|
|
365
|
-
return response;
|
|
366
|
-
}
|
|
367
|
-
async getWallet(params, bearerToken) {
|
|
368
|
-
try {
|
|
369
|
-
const { externalUserId } = params;
|
|
370
|
-
const getWalletResponse = await this.client.get({
|
|
371
|
-
endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}/by-user`,
|
|
372
|
-
params: { externalUserId },
|
|
373
|
-
bearerToken
|
|
374
|
-
});
|
|
375
|
-
return getWalletResponse;
|
|
376
|
-
} catch (err) {
|
|
377
|
-
if (err instanceof shared.ChipiApiError && err.status === 404) {
|
|
378
|
-
return null;
|
|
379
|
-
}
|
|
380
|
-
throw err;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Recover passkey credential metadata for a wallet.
|
|
385
|
-
* Used when localStorage is cleared — SDK recovers credentialId from backend.
|
|
386
|
-
*/
|
|
387
|
-
async getCredentialRecovery(params, bearerToken) {
|
|
388
|
-
return this.client.get({
|
|
389
|
-
endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}/credential-recovery`,
|
|
390
|
-
params: { externalUserId: params.externalUserId },
|
|
391
|
-
bearerToken
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
async updateWalletEncryption(params, bearerToken) {
|
|
395
|
-
const body = {
|
|
396
|
-
externalUserId: params.externalUserId,
|
|
397
|
-
newEncryptedPrivateKey: params.newEncryptedPrivateKey
|
|
398
|
-
};
|
|
399
|
-
if (params.publicKey != null) {
|
|
400
|
-
body.publicKey = params.publicKey;
|
|
401
|
-
}
|
|
402
|
-
return this.client.patch({
|
|
403
|
-
endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}/update-encryption-details`,
|
|
404
|
-
bearerToken,
|
|
405
|
-
body
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
async getTokenBalance({
|
|
409
|
-
params,
|
|
410
|
-
bearerToken
|
|
411
|
-
}) {
|
|
412
|
-
const getBalanceResponse = await this.client.get({
|
|
413
|
-
endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}/token-balance`,
|
|
414
|
-
params,
|
|
415
|
-
bearerToken
|
|
416
|
-
});
|
|
417
|
-
return getBalanceResponse;
|
|
418
|
-
}
|
|
419
|
-
/**
|
|
420
|
-
* Prepare a wallet upgrade (e.g., READY → CHIPI or CHIPI v29 → v33).
|
|
421
|
-
* Returns typed data that must be signed by the wallet owner.
|
|
422
|
-
*/
|
|
423
|
-
async prepareWalletUpgrade(params, bearerToken) {
|
|
424
|
-
return this.client.post({
|
|
425
|
-
endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}/prepare-upgrade`,
|
|
426
|
-
bearerToken,
|
|
427
|
-
body: params
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
/**
|
|
431
|
-
* Execute a wallet upgrade after the owner has signed the typed data.
|
|
432
|
-
*/
|
|
433
|
-
async executeWalletUpgrade(params, bearerToken) {
|
|
434
|
-
return this.client.post({
|
|
435
|
-
endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}/upgrade`,
|
|
436
|
-
bearerToken,
|
|
437
|
-
body: params
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
};
|
|
441
|
-
function unwrapPrepareResponse(response) {
|
|
442
|
-
if (!response || typeof response !== "object") {
|
|
443
|
-
throw new Error(
|
|
444
|
-
"Invalid response from /transactions/prepare-typed-data: response is not an object"
|
|
445
|
-
);
|
|
446
|
-
}
|
|
447
|
-
if ("typedData" in response) {
|
|
448
|
-
const resp = response;
|
|
449
|
-
if (!resp.typedData || typeof resp.typedData !== "object" || !("types" in resp.typedData) || !("message" in resp.typedData)) {
|
|
450
|
-
throw new Error(
|
|
451
|
-
"Invalid response from /transactions/prepare-typed-data: typedData structure is invalid"
|
|
452
|
-
);
|
|
453
|
-
}
|
|
454
|
-
return { typedData: resp.typedData, walletType: resp.walletType };
|
|
455
|
-
}
|
|
456
|
-
if ("types" in response && "message" in response) {
|
|
457
|
-
return { typedData: response };
|
|
458
|
-
}
|
|
459
|
-
const apiMessage = typeof response.message === "string" ? response.message : void 0;
|
|
460
|
-
throw new Error(
|
|
461
|
-
apiMessage ?? "Invalid response from /transactions/prepare-typed-data: typedData is missing"
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
function normalizeSignature(signature) {
|
|
465
|
-
if (Array.isArray(signature)) {
|
|
466
|
-
if (signature.length < 2) {
|
|
467
|
-
throw new Error(`Array signature must have at least 2 elements, got ${signature.length}`);
|
|
468
|
-
}
|
|
469
|
-
const r = signature[0];
|
|
470
|
-
const s = signature[1];
|
|
471
|
-
if (r == null || s == null) {
|
|
472
|
-
throw new Error(`Signature elements must not be null/undefined. Got r=${r}, s=${s}`);
|
|
473
|
-
}
|
|
474
|
-
return { r: String(r), s: String(s) };
|
|
475
|
-
}
|
|
476
|
-
if (signature && typeof signature === "object" && "r" in signature && "s" in signature) {
|
|
477
|
-
const sig = signature;
|
|
478
|
-
if (sig.r == null || sig.s == null) {
|
|
479
|
-
throw new Error(`Object signature has null/undefined fields. Got r=${sig.r}, s=${sig.s}`);
|
|
480
|
-
}
|
|
481
|
-
return {
|
|
482
|
-
r: String(sig.r),
|
|
483
|
-
s: String(sig.s)
|
|
885
|
+
if (signature && typeof signature === "object" && "r" in signature && "s" in signature) {
|
|
886
|
+
const sig = signature;
|
|
887
|
+
if (sig.r == null || sig.s == null) {
|
|
888
|
+
throw new Error(`Object signature has null/undefined fields. Got r=${sig.r}, s=${sig.s}`);
|
|
889
|
+
}
|
|
890
|
+
return {
|
|
891
|
+
r: String(sig.r),
|
|
892
|
+
s: String(sig.s)
|
|
484
893
|
};
|
|
485
894
|
}
|
|
486
895
|
throw new Error("Unrecognized signature format from account.signMessage()");
|
|
@@ -544,12 +953,28 @@ var executePaymasterTransaction = async ({
|
|
|
544
953
|
);
|
|
545
954
|
}
|
|
546
955
|
const walletType = wallet.walletType ?? "READY";
|
|
956
|
+
if (walletType === "SHHH") {
|
|
957
|
+
const { executeShhhSponsoredRaw: executeShhhSponsoredRaw2 } = await Promise.resolve().then(() => (init_execute_paymaster_raw(), execute_paymaster_raw_exports));
|
|
958
|
+
const shhhSignerKind = (wallet.signerKind ?? "STARK") || "STARK";
|
|
959
|
+
return await executeShhhSponsoredRaw2({
|
|
960
|
+
client,
|
|
961
|
+
bearerToken,
|
|
962
|
+
wallet: {
|
|
963
|
+
publicKey: wallet.publicKey,
|
|
964
|
+
encryptedPrivateKey: wallet.encryptedPrivateKey,
|
|
965
|
+
walletType: wallet.walletType
|
|
966
|
+
},
|
|
967
|
+
encryptKey,
|
|
968
|
+
calls,
|
|
969
|
+
signerKind: shhhSignerKind
|
|
970
|
+
});
|
|
971
|
+
}
|
|
547
972
|
const isKnownType = Object.prototype.hasOwnProperty.call(shared.WALLET_RPC_ENDPOINTS, walletType) && Object.prototype.hasOwnProperty.call(shared.WALLET_CLASS_HASHES, walletType);
|
|
548
973
|
const rpcUrl = isKnownType ? shared.WALLET_RPC_ENDPOINTS[walletType] : shared.WALLET_RPC_ENDPOINTS["CHIPI"];
|
|
549
974
|
const accountClassHash = isKnownType ? shared.WALLET_CLASS_HASHES[walletType] : shared.WALLET_CLASS_HASHES["CHIPI"];
|
|
550
975
|
let privateKeyDecrypted;
|
|
551
976
|
try {
|
|
552
|
-
privateKeyDecrypted = decryptPrivateKey(wallet.encryptedPrivateKey, encryptKey);
|
|
977
|
+
privateKeyDecrypted = exports.decryptPrivateKey(wallet.encryptedPrivateKey, encryptKey);
|
|
553
978
|
} catch {
|
|
554
979
|
throw new Error("Failed to decrypt private key. Verify the encryptKey is correct.");
|
|
555
980
|
}
|
|
@@ -628,16 +1053,15 @@ var executePaymasterTransactionWithSession = async ({
|
|
|
628
1053
|
client
|
|
629
1054
|
}) => {
|
|
630
1055
|
const { encryptKey, wallet, session, calls, saveToDatabase } = params;
|
|
631
|
-
if (wallet.walletType !== "CHIPI") {
|
|
1056
|
+
if (wallet.walletType !== "CHIPI" && wallet.walletType !== "SHHH") {
|
|
632
1057
|
console.error("[ChipiSDK:Session:Execute] Invalid wallet type for session execution", {
|
|
633
1058
|
provided: wallet.walletType,
|
|
634
|
-
required: "CHIPI",
|
|
635
|
-
expectedClassHash: shared.WALLET_CLASS_HASHES.CHIPI,
|
|
1059
|
+
required: "CHIPI or SHHH",
|
|
636
1060
|
walletAddress: wallet.publicKey?.slice(0, 15) + "...",
|
|
637
|
-
hint: "Session keys
|
|
1061
|
+
hint: "Session keys require a SNIP-163 sessions component (CHIPI v29 or SHHH V8.4)."
|
|
638
1062
|
});
|
|
639
1063
|
throw new shared.ChipiSessionError(
|
|
640
|
-
`Session execution requires CHIPI wallet type. Got: "${wallet.walletType || "undefined"}"`,
|
|
1064
|
+
`Session execution requires CHIPI or SHHH wallet type. Got: "${wallet.walletType || "undefined"}"`,
|
|
641
1065
|
shared.SESSION_ERRORS.INVALID_WALLET_TYPE_FOR_SESSION
|
|
642
1066
|
);
|
|
643
1067
|
}
|
|
@@ -656,7 +1080,7 @@ var executePaymasterTransactionWithSession = async ({
|
|
|
656
1080
|
try {
|
|
657
1081
|
let sessionPrivateKey;
|
|
658
1082
|
try {
|
|
659
|
-
sessionPrivateKey = decryptPrivateKey(session.encryptedPrivateKey, encryptKey);
|
|
1083
|
+
sessionPrivateKey = exports.decryptPrivateKey(session.encryptedPrivateKey, encryptKey);
|
|
660
1084
|
} catch {
|
|
661
1085
|
console.error("[ChipiSDK:Session:Execute] Failed to decrypt session private key", {
|
|
662
1086
|
sessionPubKey: session.publicKey?.slice(0, 15) + "...",
|
|
@@ -679,6 +1103,52 @@ var executePaymasterTransactionWithSession = async ({
|
|
|
679
1103
|
shared.SESSION_ERRORS.SESSION_DECRYPTION_FAILED
|
|
680
1104
|
);
|
|
681
1105
|
}
|
|
1106
|
+
if (wallet.walletType === "SHHH") {
|
|
1107
|
+
const { buildOutsideExecution: buildOutsideExecution2, computeOeHash: computeOeHash2, ANY_CALLER_FELT: ANY_CALLER_FELT2 } = await Promise.resolve().then(() => (init_oe(), oe_exports));
|
|
1108
|
+
const { resolveShhhChainId: resolveShhhChainId2, serializeExecuteFromOutsideV2Calldata: serializeExecuteFromOutsideV2Calldata2 } = await Promise.resolve().then(() => (init_execute_paymaster_raw(), execute_paymaster_raw_exports));
|
|
1109
|
+
const oe = buildOutsideExecution2({
|
|
1110
|
+
nonce: BigInt(Date.now()) << 24n | BigInt(Math.floor(Math.random() * 16777215)),
|
|
1111
|
+
calls,
|
|
1112
|
+
caller: ANY_CALLER_FELT2
|
|
1113
|
+
});
|
|
1114
|
+
const chainId = resolveShhhChainId2({ baseUrl: client.baseUrl });
|
|
1115
|
+
const messageHash = computeOeHash2(oe, wallet.publicKey, chainId);
|
|
1116
|
+
const messageHashHex = "0x" + messageHash.toString(16);
|
|
1117
|
+
const { r: r2, s: s2 } = starknet.ec.starkCurve.sign(messageHashHex, sessionPrivateKey);
|
|
1118
|
+
const sessionSignature2 = [
|
|
1119
|
+
BigInt(session.publicKey),
|
|
1120
|
+
BigInt(r2),
|
|
1121
|
+
BigInt(s2),
|
|
1122
|
+
BigInt(session.validUntil)
|
|
1123
|
+
];
|
|
1124
|
+
const calldata = serializeExecuteFromOutsideV2Calldata2({
|
|
1125
|
+
outsideExecution: oe,
|
|
1126
|
+
signature: sessionSignature2
|
|
1127
|
+
});
|
|
1128
|
+
const response = await client.post({
|
|
1129
|
+
endpoint: "/transactions/execute-sponsored-raw",
|
|
1130
|
+
bearerToken,
|
|
1131
|
+
body: {
|
|
1132
|
+
accountAddress: wallet.publicKey,
|
|
1133
|
+
calldata,
|
|
1134
|
+
// Gas budget for the session-sig path is the same as STARK
|
|
1135
|
+
// primary (~2M l2_gas). The paymaster's kind-detection
|
|
1136
|
+
// branch reads signature[0] == 'V2_SNIP12' to look up the
|
|
1137
|
+
// per-kind gas overhead; signature[0] here is the
|
|
1138
|
+
// session_pubkey (not the V2_SNIP12 prefix), so the
|
|
1139
|
+
// detection falls through to the STARK default — which is
|
|
1140
|
+
// correct, since the session verifier is STARK ECDSA.
|
|
1141
|
+
signerKind: "STARK"
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
if (!response?.transactionHash) {
|
|
1145
|
+
throw new shared.ChipiSessionError(
|
|
1146
|
+
"execute-sponsored-raw response missing transactionHash. Check chipi-back logs for upstream paymaster errors.",
|
|
1147
|
+
shared.SESSION_ERRORS.SESSION_INVALID_RESPONSE
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
return response.transactionHash;
|
|
1151
|
+
}
|
|
682
1152
|
const accountClassHash = shared.WALLET_CLASS_HASHES.CHIPI;
|
|
683
1153
|
const prepareResponse = await client.post({
|
|
684
1154
|
endpoint: "/transactions/prepare-typed-data",
|
|
@@ -763,11 +1233,572 @@ var executePaymasterTransactionWithSession = async ({
|
|
|
763
1233
|
walletAddress: wallet.publicKey?.slice(0, 15) + "...",
|
|
764
1234
|
sessionPubKey: session.publicKey?.slice(0, 15) + "..."
|
|
765
1235
|
});
|
|
766
|
-
throw error;
|
|
1236
|
+
throw error;
|
|
1237
|
+
}
|
|
1238
|
+
};
|
|
1239
|
+
var BOOTSTRAP_DOMAIN_SEPARATOR = BigInt(
|
|
1240
|
+
starknet.shortString.encodeShortString("SHHH_BOOTSTRAP_V8_4")
|
|
1241
|
+
);
|
|
1242
|
+
var UPGRADE_ENTRYPOINT = "upgrade";
|
|
1243
|
+
var BOOTSTRAP_ENTRYPOINT = "bootstrap_from_sessions";
|
|
1244
|
+
var BOOTSTRAP_SIGNED_ENTRYPOINT = "bootstrap_from_sessions_signed";
|
|
1245
|
+
function _asHex(x) {
|
|
1246
|
+
return typeof x === "bigint" ? "0x" + x.toString(16) : x;
|
|
1247
|
+
}
|
|
1248
|
+
function _felt(x) {
|
|
1249
|
+
if (typeof x === "bigint") return x;
|
|
1250
|
+
return BigInt(x);
|
|
1251
|
+
}
|
|
1252
|
+
function labelToFelt(label) {
|
|
1253
|
+
if (label.length === 0) return 0n;
|
|
1254
|
+
for (let i = 0; i < label.length; i++) {
|
|
1255
|
+
if (label.charCodeAt(i) > 127) {
|
|
1256
|
+
throw new RangeError(
|
|
1257
|
+
`migrate label must be ASCII-only (starknet.js short-string encoder rejects non-ASCII)`
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
if (label.length > 31) {
|
|
1262
|
+
throw new RangeError(
|
|
1263
|
+
`migrate label must fit in felt252 short-string (\u226431 ASCII characters); got ${label.length}`
|
|
1264
|
+
);
|
|
1265
|
+
}
|
|
1266
|
+
return BigInt(starknet.shortString.encodeShortString(label));
|
|
1267
|
+
}
|
|
1268
|
+
function buildMigrationCalls(params) {
|
|
1269
|
+
const upgradeCall = {
|
|
1270
|
+
contractAddress: _asHex(params.legacyAccountAddress),
|
|
1271
|
+
entrypoint: UPGRADE_ENTRYPOINT,
|
|
1272
|
+
calldata: [_asHex(params.shhhAccountClassHash)]
|
|
1273
|
+
};
|
|
1274
|
+
const labelFelt = labelToFelt(params.label ?? "migrated");
|
|
1275
|
+
const bootstrapCall = {
|
|
1276
|
+
contractAddress: _asHex(params.legacyAccountAddress),
|
|
1277
|
+
entrypoint: BOOTSTRAP_ENTRYPOINT,
|
|
1278
|
+
calldata: [
|
|
1279
|
+
_asHex(params.legacyPublicKey),
|
|
1280
|
+
_asHex(params.starkVerifierClassHash),
|
|
1281
|
+
"0x" + labelFelt.toString(16)
|
|
1282
|
+
]
|
|
1283
|
+
};
|
|
1284
|
+
return [upgradeCall, bootstrapCall];
|
|
1285
|
+
}
|
|
1286
|
+
function computeBootstrapMessage(params) {
|
|
1287
|
+
const accountFelt = _felt(params.accountAddress);
|
|
1288
|
+
const pubkeyFelt = _felt(params.publicKey);
|
|
1289
|
+
const verifierFelt = _felt(params.starkVerifierClassHash);
|
|
1290
|
+
const labelFelt = labelToFelt(params.label ?? "migrated");
|
|
1291
|
+
return BigInt(
|
|
1292
|
+
starknet.hash.computePoseidonHashOnElements([
|
|
1293
|
+
"0x" + BOOTSTRAP_DOMAIN_SEPARATOR.toString(16),
|
|
1294
|
+
"0x" + accountFelt.toString(16),
|
|
1295
|
+
"0x" + pubkeyFelt.toString(16),
|
|
1296
|
+
"0x" + verifierFelt.toString(16),
|
|
1297
|
+
"0x" + labelFelt.toString(16)
|
|
1298
|
+
])
|
|
1299
|
+
);
|
|
1300
|
+
}
|
|
1301
|
+
function signBootstrapMessage(params) {
|
|
1302
|
+
const messageHash = computeBootstrapMessage(params.message);
|
|
1303
|
+
const pkHex = _asHex(_felt(params.privateKey));
|
|
1304
|
+
const sig = starknet.ec.starkCurve.sign("0x" + messageHash.toString(16), pkHex);
|
|
1305
|
+
return {
|
|
1306
|
+
r: BigInt("0x" + sig.r.toString(16)),
|
|
1307
|
+
s: BigInt("0x" + sig.s.toString(16))
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
function buildBootstrapSignedCall(params) {
|
|
1311
|
+
const labelFelt = labelToFelt(params.label ?? "migrated");
|
|
1312
|
+
return {
|
|
1313
|
+
contractAddress: _asHex(params.accountAddress),
|
|
1314
|
+
entrypoint: BOOTSTRAP_SIGNED_ENTRYPOINT,
|
|
1315
|
+
calldata: [
|
|
1316
|
+
_asHex(params.publicKey),
|
|
1317
|
+
_asHex(params.starkVerifierClassHash),
|
|
1318
|
+
"0x" + labelFelt.toString(16),
|
|
1319
|
+
_asHex(params.signatureR),
|
|
1320
|
+
_asHex(params.signatureS)
|
|
1321
|
+
]
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
// src/migrate-to-shhh.ts
|
|
1326
|
+
async function migrateChipiWalletToShhh(params) {
|
|
1327
|
+
const prepare = await params.client.post({
|
|
1328
|
+
endpoint: "/chipi-wallets/prepare-migrate-to-shhh",
|
|
1329
|
+
bearerToken: params.bearerToken,
|
|
1330
|
+
body: {
|
|
1331
|
+
walletId: params.walletId,
|
|
1332
|
+
externalUserId: params.externalUserId
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1335
|
+
if (prepare.migrationPath === "ALREADY_MIGRATED") {
|
|
1336
|
+
return {
|
|
1337
|
+
kind: "ALREADY_MIGRATED",
|
|
1338
|
+
walletAddress: prepare.walletAddress,
|
|
1339
|
+
onChainClassHash: prepare.onChainClassHash,
|
|
1340
|
+
primaryKind: prepare.primaryKind
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
if (prepare.migrationPath === "STRANDED") {
|
|
1344
|
+
throw new Error(
|
|
1345
|
+
"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."
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
if (prepare.migrationPath !== "HAPPY") {
|
|
1349
|
+
throw new Error(
|
|
1350
|
+
"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."
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
const privateKeyHex = exports.decryptPrivateKey(params.wallet.encryptedPrivateKey, params.encryptKey);
|
|
1354
|
+
if (!privateKeyHex) {
|
|
1355
|
+
throw new Error(
|
|
1356
|
+
"Failed to decrypt STARK private key for migration. Verify the encryptKey matches the one used at wallet creation."
|
|
1357
|
+
);
|
|
1358
|
+
}
|
|
1359
|
+
const starkKeyPub = BigInt(starknet.ec.starkCurve.getStarkKey(privateKeyHex));
|
|
1360
|
+
const calls = buildMigrationCalls({
|
|
1361
|
+
legacyAccountAddress: params.wallet.publicKey,
|
|
1362
|
+
legacyPublicKey: starkKeyPub,
|
|
1363
|
+
shhhAccountClassHash: BigInt(prepare.shhhClassHash),
|
|
1364
|
+
starkVerifierClassHash: BigInt(prepare.shhhVerifierClassHash),
|
|
1365
|
+
label: prepare.label
|
|
1366
|
+
});
|
|
1367
|
+
const transactionHash = await executePaymasterTransaction({
|
|
1368
|
+
params: {
|
|
1369
|
+
encryptKey: params.encryptKey,
|
|
1370
|
+
wallet: {
|
|
1371
|
+
publicKey: params.wallet.publicKey,
|
|
1372
|
+
encryptedPrivateKey: params.wallet.encryptedPrivateKey,
|
|
1373
|
+
// Explicit walletType: the wallet IS still CHIPI v29 at sign
|
|
1374
|
+
// time. The upgrade(SHHH) inside the multicall changes the
|
|
1375
|
+
// class hash mid-tx; that's after signature verification.
|
|
1376
|
+
walletType: "CHIPI"
|
|
1377
|
+
},
|
|
1378
|
+
calls,
|
|
1379
|
+
// Persist the migration tx so it shows up in the user's tx
|
|
1380
|
+
// history. The on-chain receipt is the load-bearing artifact;
|
|
1381
|
+
// the DB row is convenience surfacing.
|
|
1382
|
+
saveToDatabase: true
|
|
1383
|
+
},
|
|
1384
|
+
bearerToken: params.bearerToken,
|
|
1385
|
+
client: params.client
|
|
1386
|
+
});
|
|
1387
|
+
const execute = await params.client.post({
|
|
1388
|
+
endpoint: "/chipi-wallets/execute-migrate-to-shhh",
|
|
1389
|
+
bearerToken: params.bearerToken,
|
|
1390
|
+
body: {
|
|
1391
|
+
walletId: params.walletId,
|
|
1392
|
+
externalUserId: params.externalUserId,
|
|
1393
|
+
transactionHash
|
|
1394
|
+
}
|
|
1395
|
+
});
|
|
1396
|
+
return { kind: "MIGRATED", ...execute };
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// src/wallets.ts
|
|
1400
|
+
var ChipiWallets = class {
|
|
1401
|
+
constructor(client) {
|
|
1402
|
+
this.client = client;
|
|
1403
|
+
this.getPrivateKeyAX = () => {
|
|
1404
|
+
const privateKeyBytes = CryptoES__default.default.lib.WordArray.random(32);
|
|
1405
|
+
const privateKey = privateKeyBytes.toString(CryptoES__default.default.enc.Hex);
|
|
1406
|
+
const fullPrivateKey = `0x${privateKey}`;
|
|
1407
|
+
const maxStarknetValue = BigInt(
|
|
1408
|
+
"0x800000000000000000000000000000000000000000000000000000000000000"
|
|
1409
|
+
);
|
|
1410
|
+
const privateKeyBigInt = BigInt(fullPrivateKey) % maxStarknetValue;
|
|
1411
|
+
return `0x${privateKeyBigInt.toString(16)}`;
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Create a new wallet on Starknet (gasless, paymaster-sponsored).
|
|
1416
|
+
*
|
|
1417
|
+
* **PIN is weak — not recommended for production.** `encryptKey` here
|
|
1418
|
+
* doubles as the only thing decrypting the wallet's private key. A
|
|
1419
|
+
* user-typed PIN is short, low-entropy, and trivially compromised
|
|
1420
|
+
* by shoulder-surfing or phishing. Production embedded-wallet apps
|
|
1421
|
+
* should derive `encryptKey` from a platform passkey
|
|
1422
|
+
* (`@chipi-stack/chipi-passkey` `getWalletEncryptKey` for the legacy
|
|
1423
|
+
* PRF flow, or — preferred — `createShhhPasskey` for new SHHH
|
|
1424
|
+
* V8.4 wallets where the P-256 key never leaves the platform
|
|
1425
|
+
* authenticator at all). PIN remains only as a last-resort
|
|
1426
|
+
* recovery surface for users without WebAuthn / biometrics.
|
|
1427
|
+
*/
|
|
1428
|
+
async createWallet(params) {
|
|
1429
|
+
try {
|
|
1430
|
+
const {
|
|
1431
|
+
encryptKey,
|
|
1432
|
+
externalUserId,
|
|
1433
|
+
bearerToken,
|
|
1434
|
+
userId,
|
|
1435
|
+
// P2.2 default flip (re-applied 2026-05-29 after #270 revert and
|
|
1436
|
+
// the post-revert SHHH bug-fix run #271–#296). New wallets
|
|
1437
|
+
// default to SHHH V8.4 instead of legacy CHIPI v29 — pluggable-
|
|
1438
|
+
// signer Starknet account, guardian recovery, threshold support
|
|
1439
|
+
// out of the box. Existing wallets unaffected; this only
|
|
1440
|
+
// changes the default for callers that omit `walletType`.
|
|
1441
|
+
// Callers who explicitly pass `walletType: "CHIPI"` or
|
|
1442
|
+
// `"READY"` continue producing those.
|
|
1443
|
+
//
|
|
1444
|
+
// Server-side override: chipi-back's CHIPI_SHHH_DEFAULT env
|
|
1445
|
+
// var (chipi-back#267) lets ops force the legacy default back
|
|
1446
|
+
// for callers that bypass the SDK and POST to /chipi-wallets
|
|
1447
|
+
// directly without walletType.
|
|
1448
|
+
//
|
|
1449
|
+
// signerKind under the SHHH branch defaults to "STARK" further
|
|
1450
|
+
// down — same kind the original #267 P2.2 used and the same
|
|
1451
|
+
// kind every mainnet smoke since 2026-05-26 has been signing
|
|
1452
|
+
// under.
|
|
1453
|
+
walletType = "SHHH",
|
|
1454
|
+
classHash: customClassHash,
|
|
1455
|
+
usePasskey,
|
|
1456
|
+
chain
|
|
1457
|
+
} = params;
|
|
1458
|
+
if (!encryptKey) {
|
|
1459
|
+
if (usePasskey) {
|
|
1460
|
+
throw new Error(
|
|
1461
|
+
"encryptKey is required when using passkey. The passkey authentication should have provided the encryptKey."
|
|
1462
|
+
);
|
|
1463
|
+
}
|
|
1464
|
+
throw new Error("encryptKey is required for wallet creation");
|
|
1465
|
+
}
|
|
1466
|
+
const isShhh = walletType === "SHHH";
|
|
1467
|
+
const effectiveSignerKind = isShhh ? params.signerKind ?? "STARK" : void 0;
|
|
1468
|
+
const SDK_SHIPPED_SHHH_KINDS = [
|
|
1469
|
+
"STARK",
|
|
1470
|
+
"ED25519",
|
|
1471
|
+
"WEBAUTHN_P256",
|
|
1472
|
+
"EIP191_SECP256K1"
|
|
1473
|
+
];
|
|
1474
|
+
if (isShhh && !SDK_SHIPPED_SHHH_KINDS.includes(
|
|
1475
|
+
effectiveSignerKind
|
|
1476
|
+
)) {
|
|
1477
|
+
throw new Error(
|
|
1478
|
+
`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).`
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
const rpcUrl = walletType === "READY" ? shared.WALLET_RPC_ENDPOINTS.READY : shared.WALLET_RPC_ENDPOINTS.CHIPI;
|
|
1482
|
+
const provider = new starknet.RpcProvider({ nodeUrl: rpcUrl });
|
|
1483
|
+
const privateKeyAX = this.getPrivateKeyAX();
|
|
1484
|
+
const starkKeyPubAX = starknet.ec.starkCurve.getStarkKey(privateKeyAX);
|
|
1485
|
+
let constructorCallData;
|
|
1486
|
+
let accountClassHash;
|
|
1487
|
+
let publicKey;
|
|
1488
|
+
let deploymentSalt;
|
|
1489
|
+
let shhhOwnerPubkeyFelts;
|
|
1490
|
+
let shhhKeyForEncryption = privateKeyAX;
|
|
1491
|
+
if (isShhh) {
|
|
1492
|
+
let pubkeyFelts;
|
|
1493
|
+
let shhhPrivateKeyHex;
|
|
1494
|
+
if (effectiveSignerKind === "STARK") {
|
|
1495
|
+
pubkeyFelts = [BigInt(starkKeyPubAX)];
|
|
1496
|
+
shhhPrivateKeyHex = privateKeyAX;
|
|
1497
|
+
} else if (effectiveSignerKind === "ED25519") {
|
|
1498
|
+
if (params.ownerPubkeyFelts && params.ownerPubkeyFelts.length > 0) {
|
|
1499
|
+
if (params.ownerPubkeyFelts.length !== 2) {
|
|
1500
|
+
throw new Error(
|
|
1501
|
+
`SHHH ED25519 ownerPubkeyFelts must be [low_u128, high_u128] (2 felts); got ${params.ownerPubkeyFelts.length}.`
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
pubkeyFelts = params.ownerPubkeyFelts.map((s) => BigInt(s));
|
|
1505
|
+
shhhPrivateKeyHex = "";
|
|
1506
|
+
} else {
|
|
1507
|
+
const edPrivKey = crypto.randomBytes(32);
|
|
1508
|
+
const ed = await import('@noble/ed25519');
|
|
1509
|
+
const edPubKey = await ed.getPublicKeyAsync(edPrivKey);
|
|
1510
|
+
pubkeyFelts = ed25519PubkeyFelts(edPubKey);
|
|
1511
|
+
shhhPrivateKeyHex = Buffer.from(edPrivKey).toString("hex");
|
|
1512
|
+
}
|
|
1513
|
+
} else if (effectiveSignerKind === "WEBAUTHN_P256" || effectiveSignerKind === "EIP191_SECP256K1") {
|
|
1514
|
+
if (!params.ownerPubkeyFelts || params.ownerPubkeyFelts.length !== 4) {
|
|
1515
|
+
throw new Error(
|
|
1516
|
+
`SHHH ${effectiveSignerKind} ownerPubkeyFelts must be [x_low, x_high, y_low, y_high] (4 felts); got ${params.ownerPubkeyFelts?.length ?? 0}.`
|
|
1517
|
+
);
|
|
1518
|
+
}
|
|
1519
|
+
pubkeyFelts = params.ownerPubkeyFelts.map((s) => BigInt(s));
|
|
1520
|
+
shhhPrivateKeyHex = "";
|
|
1521
|
+
} else {
|
|
1522
|
+
throw new Error(
|
|
1523
|
+
`SHHH signerKind=${effectiveSignerKind} reached the address-derivation block without an implementation.`
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
shhhOwnerPubkeyFelts = pubkeyFelts.map((f) => "0x" + f.toString(16));
|
|
1527
|
+
shhhKeyForEncryption = shhhPrivateKeyHex;
|
|
1528
|
+
const verifierClassHash = BigInt(
|
|
1529
|
+
shared.SHHH_VERIFIER_CLASS_HASHES[effectiveSignerKind]
|
|
1530
|
+
);
|
|
1531
|
+
const shhhClassHash = customClassHash ?? shared.WALLET_CLASS_HASHES.SHHH;
|
|
1532
|
+
const computedAddress = computeShhhAddress({
|
|
1533
|
+
classHash: BigInt(shhhClassHash),
|
|
1534
|
+
primaryKind: effectiveSignerKind,
|
|
1535
|
+
pubkey: pubkeyFelts,
|
|
1536
|
+
verifierClassHash
|
|
1537
|
+
});
|
|
1538
|
+
publicKey = "0x" + computedAddress.toString(16);
|
|
1539
|
+
accountClassHash = shhhClassHash;
|
|
1540
|
+
const labelFelt = BigInt(starknet.shortString.encodeShortString("primary"));
|
|
1541
|
+
const kindTagFelt = BigInt(starknet.shortString.encodeShortString(effectiveSignerKind));
|
|
1542
|
+
constructorCallData = [
|
|
1543
|
+
kindTagFelt.toString(),
|
|
1544
|
+
verifierClassHash.toString(),
|
|
1545
|
+
pubkeyFelts.length.toString(),
|
|
1546
|
+
...pubkeyFelts.map((f) => f.toString()),
|
|
1547
|
+
labelFelt.toString()
|
|
1548
|
+
];
|
|
1549
|
+
const salt = (() => {
|
|
1550
|
+
const ownerCommit = BigInt(
|
|
1551
|
+
starknet.hash.computePoseidonHashOnElements([
|
|
1552
|
+
"0x" + kindTagFelt.toString(16),
|
|
1553
|
+
...pubkeyFelts.map((f) => "0x" + f.toString(16))
|
|
1554
|
+
])
|
|
1555
|
+
);
|
|
1556
|
+
return BigInt(
|
|
1557
|
+
starknet.hash.computePoseidonHashOnElements([
|
|
1558
|
+
"0x" + kindTagFelt.toString(16),
|
|
1559
|
+
"0x" + ownerCommit.toString(16)
|
|
1560
|
+
])
|
|
1561
|
+
);
|
|
1562
|
+
})();
|
|
1563
|
+
deploymentSalt = "0x" + salt.toString(16);
|
|
1564
|
+
} else {
|
|
1565
|
+
constructorCallData = this.buildConstructorCallData(walletType, starkKeyPubAX);
|
|
1566
|
+
accountClassHash = customClassHash ?? (walletType === "READY" ? shared.WALLET_CLASS_HASHES.READY : shared.WALLET_CLASS_HASHES.CHIPI);
|
|
1567
|
+
publicKey = starknet.hash.calculateContractAddressFromHash(
|
|
1568
|
+
starkKeyPubAX,
|
|
1569
|
+
accountClassHash,
|
|
1570
|
+
constructorCallData,
|
|
1571
|
+
0
|
|
1572
|
+
);
|
|
1573
|
+
deploymentSalt = starkKeyPubAX;
|
|
1574
|
+
}
|
|
1575
|
+
const account = new starknet.Account({ provider, address: publicKey, signer: privateKeyAX });
|
|
1576
|
+
const typedDataResponse = await this.client.post({
|
|
1577
|
+
endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}/prepare-creation`,
|
|
1578
|
+
bearerToken,
|
|
1579
|
+
body: {
|
|
1580
|
+
chain,
|
|
1581
|
+
publicKey,
|
|
1582
|
+
walletType,
|
|
1583
|
+
starkKeyPubAX,
|
|
1584
|
+
...customClassHash && { classHash: customClassHash },
|
|
1585
|
+
// SHHH: tell chipi-back which signer kind + pubkey felts
|
|
1586
|
+
// produced this deployment so it can validate against the
|
|
1587
|
+
// expected SHHH constructor shape and populate the new
|
|
1588
|
+
// wallet row columns.
|
|
1589
|
+
...isShhh && {
|
|
1590
|
+
signerKind: effectiveSignerKind,
|
|
1591
|
+
ownerPubkeyFelts: shhhOwnerPubkeyFelts
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
});
|
|
1595
|
+
const { typedData: typedData2, accountClassHash: accountClassHashResponse } = typedDataResponse;
|
|
1596
|
+
let userSignaturePayload;
|
|
1597
|
+
if (isShhh) {
|
|
1598
|
+
userSignaturePayload = {
|
|
1599
|
+
r: "0x0",
|
|
1600
|
+
s: "0x0",
|
|
1601
|
+
recovery: 0
|
|
1602
|
+
};
|
|
1603
|
+
} else {
|
|
1604
|
+
const userSignature = await account.signMessage(typedData2);
|
|
1605
|
+
userSignaturePayload = {
|
|
1606
|
+
r: userSignature.r.toString(),
|
|
1607
|
+
s: userSignature.s.toString(),
|
|
1608
|
+
recovery: userSignature.recovery
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
const deploymentData = {
|
|
1612
|
+
class_hash: accountClassHashResponse,
|
|
1613
|
+
salt: deploymentSalt,
|
|
1614
|
+
unique: `${starknet.num.toHex(0)}`,
|
|
1615
|
+
calldata: constructorCallData.map((value) => starknet.num.toHex(value))
|
|
1616
|
+
};
|
|
1617
|
+
const encryptedPrivateKey = shhhKeyForEncryption === "" ? void 0 : exports.encryptPrivateKey(shhhKeyForEncryption, encryptKey);
|
|
1618
|
+
const encryptedPrivateKeyBackup = params.encryptKeyBackup && shhhKeyForEncryption !== "" ? exports.encryptPrivateKey(shhhKeyForEncryption, params.encryptKeyBackup) : void 0;
|
|
1619
|
+
const executeTransactionResponse = await this.client.post({
|
|
1620
|
+
endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}`,
|
|
1621
|
+
bearerToken,
|
|
1622
|
+
body: {
|
|
1623
|
+
externalUserId,
|
|
1624
|
+
userId,
|
|
1625
|
+
publicKey,
|
|
1626
|
+
walletType,
|
|
1627
|
+
chain,
|
|
1628
|
+
userSignature: userSignaturePayload,
|
|
1629
|
+
typedData: typedData2,
|
|
1630
|
+
...encryptedPrivateKey !== void 0 && { encryptedPrivateKey },
|
|
1631
|
+
// SHHH: write the signer kind to the new wallet row.
|
|
1632
|
+
...isShhh && { signerKind: effectiveSignerKind },
|
|
1633
|
+
// External-rooted SHHH (passkey / MetaMask / Phantom): no
|
|
1634
|
+
// Chipi-held key, so chipi-back needs the pubkey felts (address
|
|
1635
|
+
// derivation) + `ownerPublicKey` as the credential source it
|
|
1636
|
+
// stores (encryptedPrivateKey stays null). Canonical
|
|
1637
|
+
// ownerPublicKey = comma-joined felts. STARK / Chipi-held kinds
|
|
1638
|
+
// are untouched — encryptedPrivateKey is their credential source.
|
|
1639
|
+
...isShhh && encryptedPrivateKey === void 0 && shhhOwnerPubkeyFelts && {
|
|
1640
|
+
ownerPubkeyFelts: shhhOwnerPubkeyFelts,
|
|
1641
|
+
ownerPublicKey: shhhOwnerPubkeyFelts.join(",")
|
|
1642
|
+
},
|
|
1643
|
+
...encryptedPrivateKeyBackup && { encryptedPrivateKeyBackup },
|
|
1644
|
+
...params.authMethod && { authMethod: params.authMethod },
|
|
1645
|
+
...params.credentialId && { credentialId: params.credentialId },
|
|
1646
|
+
...params.prfSupported != null && { prfSupported: params.prfSupported },
|
|
1647
|
+
...params.deviceType && { deviceType: params.deviceType },
|
|
1648
|
+
deploymentData: {
|
|
1649
|
+
...deploymentData,
|
|
1650
|
+
salt: `${deploymentData.salt}`,
|
|
1651
|
+
calldata: deploymentData.calldata.map((data) => `${data}`)
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
});
|
|
1655
|
+
return executeTransactionResponse;
|
|
1656
|
+
} catch (error) {
|
|
1657
|
+
console.error("Detailed error:", error);
|
|
1658
|
+
if (error instanceof Error && error.message.includes("SSL")) {
|
|
1659
|
+
throw new Error(
|
|
1660
|
+
"SSL connection error. Try using NODE_TLS_REJECT_UNAUTHORIZED=0 or verify the RPC URL"
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1663
|
+
throw new shared.ChipiTransactionError(
|
|
1664
|
+
`Failed to create wallet: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1665
|
+
"WALLET_CREATION_FAILED"
|
|
1666
|
+
);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
/**
|
|
1670
|
+
* Build constructor calldata based on wallet type
|
|
1671
|
+
* - CHIPI: Simple OpenZeppelin account with just public_key
|
|
1672
|
+
* - ARGENT: Argent X Account with owner/guardian structure
|
|
1673
|
+
*/
|
|
1674
|
+
buildConstructorCallData(walletType, starkKeyPubAX) {
|
|
1675
|
+
if (walletType === "READY") {
|
|
1676
|
+
const axSigner = new starknet.CairoCustomEnum({
|
|
1677
|
+
Starknet: { pubkey: starkKeyPubAX }
|
|
1678
|
+
});
|
|
1679
|
+
const axGuardian = new starknet.CairoOption(starknet.CairoOptionVariant.None);
|
|
1680
|
+
return starknet.CallData.compile({
|
|
1681
|
+
owner: axSigner,
|
|
1682
|
+
guardian: axGuardian
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1685
|
+
return starknet.CallData.compile({
|
|
1686
|
+
public_key: starkKeyPubAX
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* Create a custodial merchant wallet
|
|
1691
|
+
*/
|
|
1692
|
+
async createCustodialWallet({
|
|
1693
|
+
params,
|
|
1694
|
+
bearerToken
|
|
1695
|
+
}) {
|
|
1696
|
+
const response = await this.client.post({
|
|
1697
|
+
endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}/custodial`,
|
|
1698
|
+
bearerToken,
|
|
1699
|
+
body: params
|
|
1700
|
+
});
|
|
1701
|
+
return response;
|
|
1702
|
+
}
|
|
1703
|
+
async getWallet(params, bearerToken) {
|
|
1704
|
+
try {
|
|
1705
|
+
const { externalUserId } = params;
|
|
1706
|
+
const getWalletResponse = await this.client.get({
|
|
1707
|
+
endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}/by-user`,
|
|
1708
|
+
params: { externalUserId },
|
|
1709
|
+
bearerToken
|
|
1710
|
+
});
|
|
1711
|
+
return getWalletResponse;
|
|
1712
|
+
} catch (err) {
|
|
1713
|
+
if (err instanceof shared.ChipiApiError && err.status === 404) {
|
|
1714
|
+
return null;
|
|
1715
|
+
}
|
|
1716
|
+
throw err;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
/**
|
|
1720
|
+
* Recover passkey credential metadata for a wallet.
|
|
1721
|
+
* Used when localStorage is cleared — SDK recovers credentialId from backend.
|
|
1722
|
+
*/
|
|
1723
|
+
async getCredentialRecovery(params, bearerToken) {
|
|
1724
|
+
return this.client.get({
|
|
1725
|
+
endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}/credential-recovery`,
|
|
1726
|
+
params: { externalUserId: params.externalUserId },
|
|
1727
|
+
bearerToken
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
async updateWalletEncryption(params, bearerToken) {
|
|
1731
|
+
const body = {
|
|
1732
|
+
externalUserId: params.externalUserId,
|
|
1733
|
+
newEncryptedPrivateKey: params.newEncryptedPrivateKey
|
|
1734
|
+
};
|
|
1735
|
+
if (params.publicKey != null) {
|
|
1736
|
+
body.publicKey = params.publicKey;
|
|
1737
|
+
}
|
|
1738
|
+
return this.client.patch({
|
|
1739
|
+
endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}/update-encryption-details`,
|
|
1740
|
+
bearerToken,
|
|
1741
|
+
body
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1744
|
+
async getTokenBalance({
|
|
1745
|
+
params,
|
|
1746
|
+
bearerToken
|
|
1747
|
+
}) {
|
|
1748
|
+
const getBalanceResponse = await this.client.get({
|
|
1749
|
+
endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}/token-balance`,
|
|
1750
|
+
params,
|
|
1751
|
+
bearerToken
|
|
1752
|
+
});
|
|
1753
|
+
return getBalanceResponse;
|
|
1754
|
+
}
|
|
1755
|
+
/**
|
|
1756
|
+
* Prepare a wallet upgrade (e.g., READY → CHIPI or CHIPI v29 → v33).
|
|
1757
|
+
* Returns typed data that must be signed by the wallet owner.
|
|
1758
|
+
*/
|
|
1759
|
+
async prepareWalletUpgrade(params, bearerToken) {
|
|
1760
|
+
return this.client.post({
|
|
1761
|
+
endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}/prepare-upgrade`,
|
|
1762
|
+
bearerToken,
|
|
1763
|
+
body: params
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* Execute a wallet upgrade after the owner has signed the typed data.
|
|
1768
|
+
*/
|
|
1769
|
+
async executeWalletUpgrade(params, bearerToken) {
|
|
1770
|
+
return this.client.post({
|
|
1771
|
+
endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}/upgrade`,
|
|
1772
|
+
bearerToken,
|
|
1773
|
+
body: params
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
/**
|
|
1777
|
+
* Migrate a CHIPI v29 wallet to SHHH V8.4 in-place at the same
|
|
1778
|
+
* on-chain address. Composes the two-phase chipi-back endpoint:
|
|
1779
|
+
* prepare returns the path decision + parameters, then this method
|
|
1780
|
+
* builds the [upgrade, bootstrap_from_sessions] multicall locally,
|
|
1781
|
+
* signs the V8.4 OE with the wallet's STARK key (decrypted via
|
|
1782
|
+
* the user's encryptKey), and sends the signed calldata to the
|
|
1783
|
+
* execute endpoint. Server forwards verbatim to the paymaster.
|
|
1784
|
+
*
|
|
1785
|
+
* Returns `{kind: "ALREADY_MIGRATED"}` if the wallet is already
|
|
1786
|
+
* SHHH on chain (DB row gets patched server-side; no on-chain tx
|
|
1787
|
+
* fires); otherwise `{kind: "MIGRATED", transactionHash, ...}`.
|
|
1788
|
+
*
|
|
1789
|
+
* Throws on STRANDED-state recovery, UNDEPLOYED wallets, and
|
|
1790
|
+
* other deferred-feature paths with actionable error messages.
|
|
1791
|
+
*
|
|
1792
|
+
* Endpoints contract shipped in chipi-back task #28 (PR #272).
|
|
1793
|
+
*/
|
|
1794
|
+
async migrateToShhh(params, bearerToken) {
|
|
1795
|
+
return migrateChipiWalletToShhh({
|
|
1796
|
+
client: this.client,
|
|
1797
|
+
bearerToken,
|
|
1798
|
+
...params
|
|
1799
|
+
});
|
|
767
1800
|
}
|
|
768
1801
|
};
|
|
769
|
-
|
|
770
|
-
// src/transactions.ts
|
|
771
1802
|
var ChipiTransactions = class {
|
|
772
1803
|
constructor(client) {
|
|
773
1804
|
this.client = client;
|
|
@@ -900,9 +1931,9 @@ var ChipiTransactions = class {
|
|
|
900
1931
|
/**
|
|
901
1932
|
* Get on-chain transaction status
|
|
902
1933
|
*/
|
|
903
|
-
async getTransactionStatus(
|
|
1934
|
+
async getTransactionStatus(hash9, bearerToken) {
|
|
904
1935
|
return this.client.get({
|
|
905
|
-
endpoint: `${shared.API_ENDPOINTS.TRANSACTIONS}/${
|
|
1936
|
+
endpoint: `${shared.API_ENDPOINTS.TRANSACTIONS}/${hash9}/status`,
|
|
906
1937
|
bearerToken
|
|
907
1938
|
});
|
|
908
1939
|
}
|
|
@@ -1069,25 +2100,39 @@ var ChipiSkus = class {
|
|
|
1069
2100
|
});
|
|
1070
2101
|
}
|
|
1071
2102
|
};
|
|
2103
|
+
|
|
2104
|
+
// src/sessions.ts
|
|
2105
|
+
init_encryption2();
|
|
1072
2106
|
var ChipiSessions = class {
|
|
1073
2107
|
constructor(client) {
|
|
1074
2108
|
this.client = client;
|
|
1075
2109
|
}
|
|
1076
2110
|
/**
|
|
1077
|
-
* Validate that the wallet
|
|
1078
|
-
*
|
|
2111
|
+
* Validate that the wallet supports SNIP-163 session keys.
|
|
2112
|
+
*
|
|
2113
|
+
* Both CHIPI v29 (sessions-smart-contract) and SHHH V8.4
|
|
2114
|
+
* (ShhhAccount) embed the same SNIP-163 SessionKeyComponent, so
|
|
2115
|
+
* `add_or_update_session_key` / `revoke_session_key` /
|
|
2116
|
+
* `get_session_data` selectors exist on both. The 4-felt session
|
|
2117
|
+
* signature shape (`[session_pubkey, r, s, valid_until]`) is the
|
|
2118
|
+
* same on both — V8.4's `execute_from_outside_v2` checks
|
|
2119
|
+
* `signature.len() == 4` at account.cairo:389 BEFORE the
|
|
2120
|
+
* V2_SNIP12 envelope dispatch, so sessions land through the
|
|
2121
|
+
* legacy fast path on V8.4 wallets too.
|
|
2122
|
+
*
|
|
2123
|
+
* READY (Argent X v0.4.0) does NOT implement SNIP-163; reject with
|
|
2124
|
+
* a clear error.
|
|
1079
2125
|
*/
|
|
1080
|
-
|
|
1081
|
-
if (walletType !== "CHIPI") {
|
|
2126
|
+
validateWalletSupportsSessions(walletType, operation, walletPublicKey) {
|
|
2127
|
+
if (walletType !== "CHIPI" && walletType !== "SHHH") {
|
|
1082
2128
|
console.error(`[ChipiSDK:Session:${operation}] Invalid wallet type`, {
|
|
1083
2129
|
provided: walletType,
|
|
1084
|
-
required: "CHIPI",
|
|
1085
|
-
expectedClassHash: shared.WALLET_CLASS_HASHES["CHIPI"],
|
|
2130
|
+
required: "CHIPI or SHHH",
|
|
1086
2131
|
walletAddress: walletPublicKey ? walletPublicKey.slice(0, 15) + "..." : "(not provided)",
|
|
1087
|
-
hint: "Session keys
|
|
2132
|
+
hint: "Session keys require a SNIP-163 sessions component. CHIPI v29 + SHHH V8.4 both embed it; READY (Argent X) does not."
|
|
1088
2133
|
});
|
|
1089
2134
|
throw new shared.ChipiSessionError(
|
|
1090
|
-
`Session keys require CHIPI wallet type. Got: "${walletType || "undefined"}"
|
|
2135
|
+
`Session keys require CHIPI or SHHH wallet type. Got: "${walletType || "undefined"}".`,
|
|
1091
2136
|
shared.SESSION_ERRORS.INVALID_WALLET_TYPE_FOR_SESSION
|
|
1092
2137
|
);
|
|
1093
2138
|
}
|
|
@@ -1107,6 +2152,15 @@ var ChipiSessions = class {
|
|
|
1107
2152
|
*
|
|
1108
2153
|
* After generating, call `addSessionKeyToContract()` to register the session on-chain.
|
|
1109
2154
|
*
|
|
2155
|
+
* **PIN is weak — not recommended for production.** `encryptKey` here
|
|
2156
|
+
* is the symmetric key the SDK uses to encrypt the session's private
|
|
2157
|
+
* key at rest. If you're passing a user-typed PIN, anyone who
|
|
2158
|
+
* observes that PIN can decrypt the session. Derive `encryptKey`
|
|
2159
|
+
* from a platform passkey (`@chipi-stack/chipi-passkey`) — for SHHH
|
|
2160
|
+
* V8.4 wallets prefer `createShhhPasskey` + `signShhhMessage`, where
|
|
2161
|
+
* the wallet's primary key never needs to be decrypted client-side
|
|
2162
|
+
* at all.
|
|
2163
|
+
*
|
|
1110
2164
|
* @param params - Session creation parameters
|
|
1111
2165
|
* @returns SessionKeyData for external storage
|
|
1112
2166
|
*
|
|
@@ -1133,7 +2187,7 @@ var ChipiSessions = class {
|
|
|
1133
2187
|
const privateKeyHex = this.toHex(rawPrivateKey);
|
|
1134
2188
|
const publicKey = starknet.ec.starkCurve.getStarkKey(privateKeyHex);
|
|
1135
2189
|
const validUntil = Math.floor(Date.now() / 1e3) + durationSeconds;
|
|
1136
|
-
const encryptedPrivateKey = encryptPrivateKey(privateKeyHex, encryptKey);
|
|
2190
|
+
const encryptedPrivateKey = exports.encryptPrivateKey(privateKeyHex, encryptKey);
|
|
1137
2191
|
return {
|
|
1138
2192
|
publicKey,
|
|
1139
2193
|
encryptedPrivateKey,
|
|
@@ -1179,7 +2233,7 @@ var ChipiSessions = class {
|
|
|
1179
2233
|
*/
|
|
1180
2234
|
async addSessionKeyToContract(params, bearerToken) {
|
|
1181
2235
|
const { encryptKey, wallet, sessionConfig } = params;
|
|
1182
|
-
this.
|
|
2236
|
+
this.validateWalletSupportsSessions(wallet.walletType, "AddToContract", wallet.publicKey);
|
|
1183
2237
|
try {
|
|
1184
2238
|
console.log("[ChipiSDK:Session:AddToContract] Registering session on-chain", {
|
|
1185
2239
|
walletAddress: wallet.publicKey.slice(0, 15) + "...",
|
|
@@ -1199,7 +2253,7 @@ var ChipiSessions = class {
|
|
|
1199
2253
|
const txHash = await executePaymasterTransaction({
|
|
1200
2254
|
params: {
|
|
1201
2255
|
encryptKey,
|
|
1202
|
-
wallet
|
|
2256
|
+
wallet,
|
|
1203
2257
|
calls: [
|
|
1204
2258
|
{
|
|
1205
2259
|
contractAddress: wallet.publicKey,
|
|
@@ -1253,7 +2307,7 @@ var ChipiSessions = class {
|
|
|
1253
2307
|
*/
|
|
1254
2308
|
async revokeSessionKey(params, bearerToken) {
|
|
1255
2309
|
const { encryptKey, wallet, sessionPublicKey } = params;
|
|
1256
|
-
this.
|
|
2310
|
+
this.validateWalletSupportsSessions(wallet.walletType, "Revoke", wallet.publicKey);
|
|
1257
2311
|
try {
|
|
1258
2312
|
console.log("[ChipiSDK:Session:Revoke] Revoking session from contract", {
|
|
1259
2313
|
walletAddress: wallet.publicKey.slice(0, 15) + "...",
|
|
@@ -1262,7 +2316,7 @@ var ChipiSessions = class {
|
|
|
1262
2316
|
const txHash = await executePaymasterTransaction({
|
|
1263
2317
|
params: {
|
|
1264
2318
|
encryptKey,
|
|
1265
|
-
wallet
|
|
2319
|
+
wallet,
|
|
1266
2320
|
calls: [
|
|
1267
2321
|
{
|
|
1268
2322
|
contractAddress: wallet.publicKey,
|
|
@@ -1435,16 +2489,16 @@ var ChipiSessions = class {
|
|
|
1435
2489
|
*/
|
|
1436
2490
|
async executeTransactionWithSession(params, bearerToken) {
|
|
1437
2491
|
const { encryptKey, wallet, session, calls } = params;
|
|
1438
|
-
if (wallet.walletType && wallet.walletType !== "CHIPI") {
|
|
2492
|
+
if (wallet.walletType && wallet.walletType !== "CHIPI" && wallet.walletType !== "SHHH") {
|
|
1439
2493
|
throw new shared.ChipiSessionError(
|
|
1440
|
-
`Session execution
|
|
2494
|
+
`Session execution requires CHIPI or SHHH wallet type. Got: ${wallet.walletType}`,
|
|
1441
2495
|
shared.SESSION_ERRORS.INVALID_WALLET_TYPE_FOR_SESSION
|
|
1442
2496
|
);
|
|
1443
2497
|
}
|
|
1444
2498
|
return executePaymasterTransactionWithSession({
|
|
1445
2499
|
params: {
|
|
1446
2500
|
encryptKey,
|
|
1447
|
-
wallet: { ...wallet, walletType: "CHIPI" },
|
|
2501
|
+
wallet: { ...wallet, walletType: wallet.walletType ?? "CHIPI" },
|
|
1448
2502
|
session,
|
|
1449
2503
|
calls,
|
|
1450
2504
|
saveToDatabase: true
|
|
@@ -1869,6 +2923,13 @@ var X402Facilitator = class _X402Facilitator {
|
|
|
1869
2923
|
* Otherwise falls back to a presence-only check (backwards compatible).
|
|
1870
2924
|
*/
|
|
1871
2925
|
async verifySignature(payment) {
|
|
2926
|
+
if (payment.payload?.shhh) {
|
|
2927
|
+
const oeCalldata = payment.payload.shhh.oeCalldata;
|
|
2928
|
+
if (!Array.isArray(oeCalldata) || oeCalldata.length === 0) {
|
|
2929
|
+
return { valid: false, reason: "Missing SHHH oeCalldata" };
|
|
2930
|
+
}
|
|
2931
|
+
return { valid: true };
|
|
2932
|
+
}
|
|
1872
2933
|
if (!this.provider) {
|
|
1873
2934
|
if (!payment.payload?.signature?.r || !payment.payload?.signature?.s) {
|
|
1874
2935
|
return { valid: false, reason: "Missing signature" };
|
|
@@ -1922,6 +2983,28 @@ var X402Facilitator = class _X402Facilitator {
|
|
|
1922
2983
|
return { success: false, errorReason: "Nonce already consumed (concurrent replay)" };
|
|
1923
2984
|
}
|
|
1924
2985
|
try {
|
|
2986
|
+
if (payload.shhh) {
|
|
2987
|
+
const shhhResult = await this.client.post({
|
|
2988
|
+
endpoint: "/transactions/execute-sponsored-raw",
|
|
2989
|
+
bearerToken: this.bearerToken,
|
|
2990
|
+
body: {
|
|
2991
|
+
accountAddress: payload.fromAddress,
|
|
2992
|
+
calldata: payload.shhh.oeCalldata,
|
|
2993
|
+
signerKind: payload.shhh.signerKind
|
|
2994
|
+
}
|
|
2995
|
+
});
|
|
2996
|
+
if (typeof shhhResult?.transactionHash !== "string" || !shhhResult.transactionHash.trim()) {
|
|
2997
|
+
return {
|
|
2998
|
+
success: false,
|
|
2999
|
+
errorReason: shhhResult?.message ?? "execute-sponsored-raw returned no transactionHash"
|
|
3000
|
+
};
|
|
3001
|
+
}
|
|
3002
|
+
return {
|
|
3003
|
+
success: true,
|
|
3004
|
+
transactionHash: shhhResult.transactionHash,
|
|
3005
|
+
networkId: "starknet-mainnet"
|
|
3006
|
+
};
|
|
3007
|
+
}
|
|
1925
3008
|
const amountU256 = starknet.uint256.bnToUint256(BigInt(payload.amount));
|
|
1926
3009
|
const transferCall = {
|
|
1927
3010
|
contractAddress: payload.asset,
|
|
@@ -1967,6 +3050,9 @@ var X402Facilitator = class _X402Facilitator {
|
|
|
1967
3050
|
}
|
|
1968
3051
|
}
|
|
1969
3052
|
};
|
|
3053
|
+
|
|
3054
|
+
// src/sign-typed-data.ts
|
|
3055
|
+
init_encryption2();
|
|
1970
3056
|
var signTypedData = async ({
|
|
1971
3057
|
params
|
|
1972
3058
|
}) => {
|
|
@@ -1988,7 +3074,7 @@ var signTypedData = async ({
|
|
|
1988
3074
|
const rpcUrl = shared.WALLET_RPC_ENDPOINTS[walletType];
|
|
1989
3075
|
let privateKeyDecrypted;
|
|
1990
3076
|
try {
|
|
1991
|
-
privateKeyDecrypted = decryptPrivateKey(wallet.encryptedPrivateKey, encryptKey);
|
|
3077
|
+
privateKeyDecrypted = exports.decryptPrivateKey(wallet.encryptedPrivateKey, encryptKey);
|
|
1992
3078
|
} catch {
|
|
1993
3079
|
throw new Error("Failed to decrypt private key. Verify the encryptKey is correct.");
|
|
1994
3080
|
}
|
|
@@ -2154,9 +3240,7 @@ var ChipiSDK = class {
|
|
|
2154
3240
|
* });
|
|
2155
3241
|
* ```
|
|
2156
3242
|
*/
|
|
2157
|
-
async signTypedData({
|
|
2158
|
-
params
|
|
2159
|
-
}) {
|
|
3243
|
+
async signTypedData({ params }) {
|
|
2160
3244
|
return signTypedData({ params });
|
|
2161
3245
|
}
|
|
2162
3246
|
/**
|
|
@@ -2275,8 +3359,8 @@ var ChipiSDK = class {
|
|
|
2275
3359
|
/**
|
|
2276
3360
|
* Get on-chain transaction status (supports polling for confirmation)
|
|
2277
3361
|
*/
|
|
2278
|
-
async getTransactionStatus(
|
|
2279
|
-
return this.transactions.getTransactionStatus(
|
|
3362
|
+
async getTransactionStatus(hash9, bearerToken) {
|
|
3363
|
+
return this.transactions.getTransactionStatus(hash9, this.resolveBearerToken(bearerToken));
|
|
2280
3364
|
}
|
|
2281
3365
|
/**
|
|
2282
3366
|
* Prepare a wallet upgrade (e.g., READY → CHIPI or CHIPI v29 → v33).
|
|
@@ -2324,6 +3408,207 @@ var ChipiBrowserSDK = class extends ChipiSDK {
|
|
|
2324
3408
|
// All methods are inherited from ChipiSDK
|
|
2325
3409
|
// bearerToken parameter is required for each authenticated request
|
|
2326
3410
|
};
|
|
3411
|
+
|
|
3412
|
+
// src/index.ts
|
|
3413
|
+
init_encryption2();
|
|
3414
|
+
|
|
3415
|
+
// src/x402/shhh-client.ts
|
|
3416
|
+
init_encryption2();
|
|
3417
|
+
init_oe();
|
|
3418
|
+
init_stark();
|
|
3419
|
+
init_execute_paymaster_raw();
|
|
3420
|
+
var PAYMENT_REQUIRED_HEADER = "payment-required";
|
|
3421
|
+
var DEFAULT_PAYMENT_HEADER = "X-PAYMENT";
|
|
3422
|
+
var X402ShhhClient = class {
|
|
3423
|
+
constructor(opts) {
|
|
3424
|
+
this.wallet = opts.wallet;
|
|
3425
|
+
this.encryptKey = opts.encryptKey;
|
|
3426
|
+
this.chipiClient = opts.chipiClient;
|
|
3427
|
+
this.bearerToken = opts.bearerToken;
|
|
3428
|
+
this.config = {
|
|
3429
|
+
headerName: DEFAULT_PAYMENT_HEADER,
|
|
3430
|
+
...opts.config
|
|
3431
|
+
};
|
|
3432
|
+
this.fetchFn = globalThis.fetch;
|
|
3433
|
+
}
|
|
3434
|
+
/**
|
|
3435
|
+
* Drop-in `fetch` replacement. If the seller returns 402, signs a
|
|
3436
|
+
* payment + retries with the X-PAYMENT header. Same surface as
|
|
3437
|
+
* `X402Client.fetch`.
|
|
3438
|
+
*/
|
|
3439
|
+
async fetch(url, init) {
|
|
3440
|
+
const response = await this.fetchFn(url, init);
|
|
3441
|
+
if (response.status !== 402) {
|
|
3442
|
+
return response;
|
|
3443
|
+
}
|
|
3444
|
+
const requirement = this.parsePaymentRequired(response);
|
|
3445
|
+
this.validateRequirement(requirement);
|
|
3446
|
+
const payment = await this.signPayment(requirement);
|
|
3447
|
+
return this.retryWithPayment(url, init, payment);
|
|
3448
|
+
}
|
|
3449
|
+
parsePaymentRequired(response) {
|
|
3450
|
+
const headerValue = response.headers.get(PAYMENT_REQUIRED_HEADER);
|
|
3451
|
+
if (!headerValue) {
|
|
3452
|
+
throw new Error(
|
|
3453
|
+
"Server returned 402 but no PAYMENT-REQUIRED header. The server may not support x402 or is misconfigured."
|
|
3454
|
+
);
|
|
3455
|
+
}
|
|
3456
|
+
let requirement;
|
|
3457
|
+
try {
|
|
3458
|
+
requirement = JSON.parse(headerValue);
|
|
3459
|
+
} catch {
|
|
3460
|
+
throw new Error(
|
|
3461
|
+
`Malformed PAYMENT-REQUIRED header: invalid JSON. Got: "${headerValue.slice(0, 100)}"`
|
|
3462
|
+
);
|
|
3463
|
+
}
|
|
3464
|
+
if (!requirement.scheme || !requirement.network || !requirement.maxAmountRequired) {
|
|
3465
|
+
throw new Error(
|
|
3466
|
+
"Invalid payment requirement: missing required fields (scheme, network, maxAmountRequired)"
|
|
3467
|
+
);
|
|
3468
|
+
}
|
|
3469
|
+
if (requirement.scheme !== "exact") {
|
|
3470
|
+
throw new Error(
|
|
3471
|
+
`Unsupported payment scheme: "${requirement.scheme}". This client only supports "exact".`
|
|
3472
|
+
);
|
|
3473
|
+
}
|
|
3474
|
+
if (requirement.network !== "starknet-mainnet") {
|
|
3475
|
+
throw new Error(
|
|
3476
|
+
`Unsupported payment network: "${requirement.network}". This client only supports "starknet-mainnet" per CLAUDE.md.`
|
|
3477
|
+
);
|
|
3478
|
+
}
|
|
3479
|
+
return requirement;
|
|
3480
|
+
}
|
|
3481
|
+
validateRequirement(req) {
|
|
3482
|
+
if (this.config.allowedRecipients && this.config.allowedRecipients.length > 0 && !this.config.allowedRecipients.includes(req.payTo)) {
|
|
3483
|
+
throw new Error(
|
|
3484
|
+
`Payment recipient ${req.payTo} not in allowed list. Update X402ShhhClientConfig.allowedRecipients to permit this seller.`
|
|
3485
|
+
);
|
|
3486
|
+
}
|
|
3487
|
+
if (this.config.maxPaymentAmount) {
|
|
3488
|
+
const maxBaseUnits = humanUsdcToBaseUnits(this.config.maxPaymentAmount, 6);
|
|
3489
|
+
if (BigInt(req.maxAmountRequired) > maxBaseUnits) {
|
|
3490
|
+
throw new Error(
|
|
3491
|
+
`Payment amount ${req.maxAmountRequired} exceeds client max ${this.config.maxPaymentAmount} USDC.`
|
|
3492
|
+
);
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
/**
|
|
3497
|
+
* Sign an x402 payment as a SHHH V8.4 buyer. Builds the V8.4 OE
|
|
3498
|
+
* containing the single USDC.transfer call, computes the SNIP-12
|
|
3499
|
+
* hash against the wallet address + chain id, signs locally with
|
|
3500
|
+
* the decrypted STARK private key, wraps the result in a V2_SNIP12
|
|
3501
|
+
* envelope, and serializes the full execute_from_outside_v2
|
|
3502
|
+
* calldata. Returns a PaymentPayload with the `shhh` extension
|
|
3503
|
+
* populated; the facilitator's settle branch forwards
|
|
3504
|
+
* `payload.shhh.oeCalldata` verbatim to
|
|
3505
|
+
* `/transactions/execute-sponsored-raw`.
|
|
3506
|
+
*
|
|
3507
|
+
* The `signature: {r, s}` field is also populated with the inner
|
|
3508
|
+
* `(r, s)` pair so the wire format stays valid for receivers that
|
|
3509
|
+
* only inspect the legacy field. The facilitator uses the SHHH
|
|
3510
|
+
* branch when `payload.shhh` is present, so the redundant `{r, s}`
|
|
3511
|
+
* is informational, not load-bearing.
|
|
3512
|
+
*/
|
|
3513
|
+
async signPayment(requirement) {
|
|
3514
|
+
const signerKind = this.wallet.signerKind ?? "STARK";
|
|
3515
|
+
if (signerKind !== "STARK") {
|
|
3516
|
+
throw new Error(
|
|
3517
|
+
`X402ShhhClient currently supports signerKind="STARK" only; got "${signerKind}". ED25519 + external-rooted (MetaMask, Phantom, passkey, Apple) x402 buyers ship in a follow-on PR.`
|
|
3518
|
+
);
|
|
3519
|
+
}
|
|
3520
|
+
const nonce = generateNonce();
|
|
3521
|
+
const validUntil = Math.floor(Date.now() / 1e3) + requirement.maxTimeoutSeconds;
|
|
3522
|
+
const amountU256 = starknet.uint256.bnToUint256(BigInt(requirement.maxAmountRequired));
|
|
3523
|
+
const transferCall = {
|
|
3524
|
+
contractAddress: requirement.asset,
|
|
3525
|
+
entrypoint: "transfer",
|
|
3526
|
+
calldata: [requirement.payTo, amountU256.low.toString(), amountU256.high.toString()]
|
|
3527
|
+
};
|
|
3528
|
+
const oe = buildOutsideExecution({
|
|
3529
|
+
nonce: BigInt("0x" + nonceToHexFelt(nonce)),
|
|
3530
|
+
calls: [transferCall],
|
|
3531
|
+
caller: exports.ANY_CALLER_FELT
|
|
3532
|
+
});
|
|
3533
|
+
const chainId = resolveShhhChainId({
|
|
3534
|
+
override: this.config.chainId,
|
|
3535
|
+
baseUrl: this.chipiClient.baseUrl
|
|
3536
|
+
});
|
|
3537
|
+
const messageHash = computeOeHash(oe, this.wallet.publicKey, chainId);
|
|
3538
|
+
const privateKeyHex = exports.decryptPrivateKey(this.wallet.encryptedPrivateKey, this.encryptKey);
|
|
3539
|
+
if (!privateKeyHex) {
|
|
3540
|
+
throw new Error(
|
|
3541
|
+
"Failed to decrypt SHHH STARK private key for x402 payment. Verify the encryptKey matches the wallet creation key."
|
|
3542
|
+
);
|
|
3543
|
+
}
|
|
3544
|
+
const envelope = buildStarkEnvelope({
|
|
3545
|
+
privateKey: privateKeyHex,
|
|
3546
|
+
messageHash
|
|
3547
|
+
});
|
|
3548
|
+
const oeCalldata = serializeExecuteFromOutsideV2Calldata({
|
|
3549
|
+
outsideExecution: oe,
|
|
3550
|
+
signature: envelope
|
|
3551
|
+
});
|
|
3552
|
+
const r = envelope[envelope.length - 2] ?? 0n;
|
|
3553
|
+
const s = envelope[envelope.length - 1] ?? 0n;
|
|
3554
|
+
return {
|
|
3555
|
+
x402Version: 1,
|
|
3556
|
+
scheme: "exact",
|
|
3557
|
+
network: "starknet-mainnet",
|
|
3558
|
+
payload: {
|
|
3559
|
+
signature: {
|
|
3560
|
+
r: "0x" + r.toString(16),
|
|
3561
|
+
s: "0x" + s.toString(16)
|
|
3562
|
+
},
|
|
3563
|
+
fromAddress: this.wallet.publicKey,
|
|
3564
|
+
toAddress: requirement.payTo,
|
|
3565
|
+
amount: requirement.maxAmountRequired,
|
|
3566
|
+
asset: requirement.asset,
|
|
3567
|
+
validUntil,
|
|
3568
|
+
nonce,
|
|
3569
|
+
shhh: {
|
|
3570
|
+
walletType: "SHHH",
|
|
3571
|
+
signerKind,
|
|
3572
|
+
oeCalldata
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
};
|
|
3576
|
+
}
|
|
3577
|
+
async retryWithPayment(url, init, payment) {
|
|
3578
|
+
const HeadersCtor = globalThis.Headers;
|
|
3579
|
+
const headerName = this.config.headerName;
|
|
3580
|
+
const paymentJson = JSON.stringify(payment);
|
|
3581
|
+
let headers;
|
|
3582
|
+
if (typeof HeadersCtor === "function") {
|
|
3583
|
+
const merged = new HeadersCtor(init?.headers);
|
|
3584
|
+
merged.set(headerName, paymentJson);
|
|
3585
|
+
headers = merged;
|
|
3586
|
+
} else {
|
|
3587
|
+
const existing = init?.headers ?? {};
|
|
3588
|
+
headers = { ...existing, [headerName]: paymentJson };
|
|
3589
|
+
}
|
|
3590
|
+
return this.fetchFn(url, { ...init, headers });
|
|
3591
|
+
}
|
|
3592
|
+
};
|
|
3593
|
+
function humanUsdcToBaseUnits(amount, decimals) {
|
|
3594
|
+
const [whole, frac = ""] = amount.trim().split(".");
|
|
3595
|
+
const fracPadded = (frac + "0".repeat(decimals)).slice(0, decimals);
|
|
3596
|
+
return BigInt((whole || "0") + fracPadded);
|
|
3597
|
+
}
|
|
3598
|
+
function generateNonce() {
|
|
3599
|
+
if (typeof globalThis.crypto?.randomUUID === "function") {
|
|
3600
|
+
return globalThis.crypto.randomUUID();
|
|
3601
|
+
}
|
|
3602
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
3603
|
+
}
|
|
3604
|
+
function nonceToHexFelt(nonce) {
|
|
3605
|
+
const bytes = new TextEncoder().encode(nonce);
|
|
3606
|
+
let hex = "";
|
|
3607
|
+
for (const b of bytes) {
|
|
3608
|
+
hex += b.toString(16).padStart(2, "0");
|
|
3609
|
+
}
|
|
3610
|
+
return hex.slice(0, 60).padStart(60, "0");
|
|
3611
|
+
}
|
|
2327
3612
|
function amountToBaseUnits(amount, decimals) {
|
|
2328
3613
|
const [whole, frac = ""] = amount.split(".");
|
|
2329
3614
|
const fracPadded = (frac + "0".repeat(decimals)).slice(0, decimals);
|
|
@@ -2448,6 +3733,815 @@ function x402Middleware(config) {
|
|
|
2448
3733
|
};
|
|
2449
3734
|
}
|
|
2450
3735
|
|
|
3736
|
+
// src/shhh/index.ts
|
|
3737
|
+
init_snip12_hash();
|
|
3738
|
+
init_compute_wallet_address();
|
|
3739
|
+
init_oe();
|
|
3740
|
+
init_stark();
|
|
3741
|
+
init_ed25519();
|
|
3742
|
+
|
|
3743
|
+
// src/shhh/signers/eip191.ts
|
|
3744
|
+
init_compute_wallet_address();
|
|
3745
|
+
init_oe();
|
|
3746
|
+
var EIP191_KIND_TAG = "EIP191_SECP256K1";
|
|
3747
|
+
var EIP191_KIND_TAG_FELT = BigInt(starknet.shortString.encodeShortString(EIP191_KIND_TAG));
|
|
3748
|
+
var SECP256K1_ORDER = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
|
|
3749
|
+
function assertBytes(name, b, len) {
|
|
3750
|
+
if (b.length !== len) {
|
|
3751
|
+
throw new Error(`${name} must be exactly ${len} bytes; got ${b.length}.`);
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
function bytesBeToU256(b) {
|
|
3755
|
+
assertBytes("secp256k1 32B value", b, 32);
|
|
3756
|
+
let x = 0n;
|
|
3757
|
+
for (let i = 0; i < b.length; i++) x = x << 8n | BigInt(b[i]);
|
|
3758
|
+
return { low: x & (1n << 128n) - 1n, high: x >> 128n };
|
|
3759
|
+
}
|
|
3760
|
+
function eip191PubkeyFelts(pubkey) {
|
|
3761
|
+
const x = bytesBeToU256(pubkey.x);
|
|
3762
|
+
const y = bytesBeToU256(pubkey.y);
|
|
3763
|
+
return [x.low, x.high, y.low, y.high];
|
|
3764
|
+
}
|
|
3765
|
+
function eip191OwnerId(pubkey) {
|
|
3766
|
+
return ownerCommitment(EIP191_KIND_TAG_FELT, eip191PubkeyFelts(pubkey));
|
|
3767
|
+
}
|
|
3768
|
+
function parseEip191Signature(signatureHex) {
|
|
3769
|
+
const hex = signatureHex.startsWith("0x") ? signatureHex.slice(2) : signatureHex;
|
|
3770
|
+
if (hex.length !== 130) {
|
|
3771
|
+
throw new Error(`EIP-191 signature must be 65 bytes (130 hex chars); got ${hex.length}.`);
|
|
3772
|
+
}
|
|
3773
|
+
if (!/^[0-9a-fA-F]+$/.test(hex)) {
|
|
3774
|
+
throw new Error("EIP-191 signature contains non-hex characters.");
|
|
3775
|
+
}
|
|
3776
|
+
const bytes = new Uint8Array(65);
|
|
3777
|
+
for (let i = 0; i < 65; i++) {
|
|
3778
|
+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
3779
|
+
}
|
|
3780
|
+
const r = bytesBeToU256(bytes.slice(0, 32));
|
|
3781
|
+
const s = bytesBeToU256(bytes.slice(32, 64));
|
|
3782
|
+
const v = bytes[64];
|
|
3783
|
+
let yParity;
|
|
3784
|
+
if (v === 27) yParity = 0n;
|
|
3785
|
+
else if (v === 28) yParity = 1n;
|
|
3786
|
+
else if (v === 0) yParity = 0n;
|
|
3787
|
+
else if (v === 1) yParity = 1n;
|
|
3788
|
+
else throw new Error(`Unexpected v byte ${v} in EIP-191 signature; expected 27, 28, 0, or 1.`);
|
|
3789
|
+
if (s.low === 0n && s.high === 0n) {
|
|
3790
|
+
throw new Error("EIP-191 signature s component is zero");
|
|
3791
|
+
}
|
|
3792
|
+
return [r.low, r.high, s.low, s.high, yParity];
|
|
3793
|
+
}
|
|
3794
|
+
function buildEip191EnvelopeFromSignature(params) {
|
|
3795
|
+
const ownerIndex = params.ownerIndex ?? 0n;
|
|
3796
|
+
if (ownerIndex < 0n || ownerIndex >= 0x100000000n) {
|
|
3797
|
+
throw new Error(
|
|
3798
|
+
`buildEip191EnvelopeFromSignature: ownerIndex ${ownerIndex} is out of u32 range [0, 2^32).`
|
|
3799
|
+
);
|
|
3800
|
+
}
|
|
3801
|
+
const payload = parseEip191Signature(params.signatureHex);
|
|
3802
|
+
return wrapV2Snip12Envelope({
|
|
3803
|
+
ownerId: ownerIndex,
|
|
3804
|
+
kindTag: EIP191_KIND_TAG,
|
|
3805
|
+
payload
|
|
3806
|
+
});
|
|
3807
|
+
}
|
|
3808
|
+
async function buildEip191EnvelopeFromPrivateKey(params) {
|
|
3809
|
+
const ownerIndex = params.ownerIndex ?? 0n;
|
|
3810
|
+
if (ownerIndex < 0n || ownerIndex >= 0x100000000n) {
|
|
3811
|
+
throw new Error(
|
|
3812
|
+
`buildEip191EnvelopeFromPrivateKey: ownerIndex ${ownerIndex} is out of u32 range [0, 2^32).`
|
|
3813
|
+
);
|
|
3814
|
+
}
|
|
3815
|
+
const privBytes = normalizePrivateKey(params.privateKey);
|
|
3816
|
+
const [secp, keccak] = await Promise.all([
|
|
3817
|
+
import('@noble/secp256k1'),
|
|
3818
|
+
// @noble/hashes@2.x ESM exports use the .js suffix per their package.json
|
|
3819
|
+
// exports map.
|
|
3820
|
+
import('@noble/hashes/sha3.js')
|
|
3821
|
+
]);
|
|
3822
|
+
const msgBe32 = bigintToBe32(params.messageHash);
|
|
3823
|
+
const prefix = new TextEncoder().encode("Ethereum Signed Message:\n32");
|
|
3824
|
+
const prefixedMsg = new Uint8Array(prefix.length + 32);
|
|
3825
|
+
prefixedMsg.set(prefix, 0);
|
|
3826
|
+
prefixedMsg.set(msgBe32, prefix.length);
|
|
3827
|
+
const ethSignedHash = keccak.keccak_256(prefixedMsg);
|
|
3828
|
+
const sig = await secp.signAsync(ethSignedHash, privBytes, {
|
|
3829
|
+
lowS: true
|
|
3830
|
+
});
|
|
3831
|
+
let r = typeof sig.r === "bigint" ? sig.r : BigInt(sig.r);
|
|
3832
|
+
let s = typeof sig.s === "bigint" ? sig.s : BigInt(sig.s);
|
|
3833
|
+
let recovery = typeof sig.recovery === "number" ? sig.recovery : Number(sig.recovery ?? 0);
|
|
3834
|
+
const halfOrder = SECP256K1_ORDER >> 1n;
|
|
3835
|
+
if (s > halfOrder) {
|
|
3836
|
+
s = SECP256K1_ORDER - s;
|
|
3837
|
+
recovery ^= 1;
|
|
3838
|
+
}
|
|
3839
|
+
const rBytes = bigintToBe32(r);
|
|
3840
|
+
const sBytes = bigintToBe32(s);
|
|
3841
|
+
const rU256 = bytesBeToU256(rBytes);
|
|
3842
|
+
const sU256 = bytesBeToU256(sBytes);
|
|
3843
|
+
return wrapV2Snip12Envelope({
|
|
3844
|
+
ownerId: ownerIndex,
|
|
3845
|
+
kindTag: EIP191_KIND_TAG,
|
|
3846
|
+
payload: [rU256.low, rU256.high, sU256.low, sU256.high, BigInt(recovery)]
|
|
3847
|
+
});
|
|
3848
|
+
}
|
|
3849
|
+
function normalizePrivateKey(input) {
|
|
3850
|
+
if (input instanceof Uint8Array) {
|
|
3851
|
+
assertBytes("EIP-191 private key", input, 32);
|
|
3852
|
+
return input;
|
|
3853
|
+
}
|
|
3854
|
+
const hex = input.startsWith("0x") ? input.slice(2) : input;
|
|
3855
|
+
if (hex.length !== 64) {
|
|
3856
|
+
throw new Error(`EIP-191 private key hex must be 64 chars (32 bytes); got ${hex.length}.`);
|
|
3857
|
+
}
|
|
3858
|
+
if (!/^[0-9a-fA-F]+$/.test(hex)) {
|
|
3859
|
+
throw new Error("EIP-191 private key contains non-hex characters.");
|
|
3860
|
+
}
|
|
3861
|
+
const out = new Uint8Array(32);
|
|
3862
|
+
for (let i = 0; i < 32; i++) {
|
|
3863
|
+
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
3864
|
+
}
|
|
3865
|
+
return out;
|
|
3866
|
+
}
|
|
3867
|
+
function bigintToBe32(x) {
|
|
3868
|
+
if (x < 0n) throw new Error("bigintToBe32: negative input");
|
|
3869
|
+
const out = new Uint8Array(32);
|
|
3870
|
+
for (let i = 31; i >= 0; i--) {
|
|
3871
|
+
out[i] = Number(x & 0xffn);
|
|
3872
|
+
x >>= 8n;
|
|
3873
|
+
}
|
|
3874
|
+
if (x !== 0n) throw new Error("bigintToBe32: input exceeds 256 bits");
|
|
3875
|
+
return out;
|
|
3876
|
+
}
|
|
3877
|
+
|
|
3878
|
+
// src/shhh/signers/webauthn.ts
|
|
3879
|
+
init_compute_wallet_address();
|
|
3880
|
+
init_oe();
|
|
3881
|
+
var WEBAUTHN_KIND_TAG = "WEBAUTHN_P256";
|
|
3882
|
+
var WEBAUTHN_KIND_TAG_FELT = BigInt(starknet.shortString.encodeShortString(WEBAUTHN_KIND_TAG));
|
|
3883
|
+
var P256_ORDER = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551n;
|
|
3884
|
+
function assertBytes2(name, b, len) {
|
|
3885
|
+
if (b.length !== len) {
|
|
3886
|
+
throw new Error(`${name} must be exactly ${len} bytes; got ${b.length}.`);
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
function bytesBeToU2562(b) {
|
|
3890
|
+
assertBytes2("32B value", b, 32);
|
|
3891
|
+
let x = 0n;
|
|
3892
|
+
for (let i = 0; i < b.length; i++) x = x << 8n | BigInt(b[i]);
|
|
3893
|
+
return { low: x & (1n << 128n) - 1n, high: x >> 128n };
|
|
3894
|
+
}
|
|
3895
|
+
function bigintToBe322(x) {
|
|
3896
|
+
if (x < 0n) throw new Error("bigintToBe32: negative input");
|
|
3897
|
+
const out = new Uint8Array(32);
|
|
3898
|
+
for (let i = 31; i >= 0; i--) {
|
|
3899
|
+
out[i] = Number(x & 0xffn);
|
|
3900
|
+
x >>= 8n;
|
|
3901
|
+
}
|
|
3902
|
+
if (x !== 0n) throw new Error("bigintToBe32: input exceeds 256 bits");
|
|
3903
|
+
return out;
|
|
3904
|
+
}
|
|
3905
|
+
function base64UrlEncode(bytes) {
|
|
3906
|
+
let bin = "";
|
|
3907
|
+
for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
|
|
3908
|
+
const b64 = typeof Buffer !== "undefined" ? Buffer.from(bytes).toString("base64") : btoa(bin);
|
|
3909
|
+
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
3910
|
+
}
|
|
3911
|
+
function byteArrayToFelts(bytes) {
|
|
3912
|
+
const totalFull = Math.floor(bytes.length / 31);
|
|
3913
|
+
const out = [BigInt(totalFull)];
|
|
3914
|
+
for (let i = 0; i < totalFull; i++) {
|
|
3915
|
+
let v = 0n;
|
|
3916
|
+
for (let j = 0; j < 31; j++) v = v << 8n | BigInt(bytes[i * 31 + j]);
|
|
3917
|
+
out.push(v);
|
|
3918
|
+
}
|
|
3919
|
+
const remStart = totalFull * 31;
|
|
3920
|
+
const remLen = bytes.length - remStart;
|
|
3921
|
+
let pending = 0n;
|
|
3922
|
+
for (let j = 0; j < remLen; j++) {
|
|
3923
|
+
pending = pending << 8n | BigInt(bytes[remStart + j]);
|
|
3924
|
+
}
|
|
3925
|
+
out.push(pending);
|
|
3926
|
+
out.push(BigInt(remLen));
|
|
3927
|
+
return out;
|
|
3928
|
+
}
|
|
3929
|
+
function parseDerEcdsaSignature(der) {
|
|
3930
|
+
if (der.length < 8) {
|
|
3931
|
+
throw new Error(`DER: input too short (${der.length} bytes)`);
|
|
3932
|
+
}
|
|
3933
|
+
if (der[0] !== 48) throw new Error("DER: expected SEQUENCE (0x30)");
|
|
3934
|
+
const seqLen = der[1];
|
|
3935
|
+
if (seqLen & 128) {
|
|
3936
|
+
throw new Error("DER: long-form sequence length not supported");
|
|
3937
|
+
}
|
|
3938
|
+
if (2 + seqLen !== der.length) {
|
|
3939
|
+
throw new Error(
|
|
3940
|
+
`DER: declared sequence length ${seqLen} does not match buffer (${der.length - 2})`
|
|
3941
|
+
);
|
|
3942
|
+
}
|
|
3943
|
+
let off = 2;
|
|
3944
|
+
if (der[off] !== 2) throw new Error("DER: expected INTEGER (0x02) for r");
|
|
3945
|
+
const rLen = der[off + 1];
|
|
3946
|
+
if (rLen === 0) throw new Error("DER: r component has zero length");
|
|
3947
|
+
if (off + 2 + rLen > der.length) {
|
|
3948
|
+
throw new Error(`DER: r length ${rLen} overruns buffer at offset ${off + 2}`);
|
|
3949
|
+
}
|
|
3950
|
+
off += 2;
|
|
3951
|
+
let rBytes = new Uint8Array(rLen);
|
|
3952
|
+
rBytes.set(der.subarray(off, off + rLen));
|
|
3953
|
+
off += rLen;
|
|
3954
|
+
if (off + 2 > der.length) {
|
|
3955
|
+
throw new Error("DER: no room for s INTEGER header");
|
|
3956
|
+
}
|
|
3957
|
+
if (der[off] !== 2) throw new Error("DER: expected INTEGER (0x02) for s");
|
|
3958
|
+
const sLen = der[off + 1];
|
|
3959
|
+
if (sLen === 0) throw new Error("DER: s component has zero length");
|
|
3960
|
+
if (off + 2 + sLen > der.length) {
|
|
3961
|
+
throw new Error(`DER: s length ${sLen} overruns buffer at offset ${off + 2}`);
|
|
3962
|
+
}
|
|
3963
|
+
off += 2;
|
|
3964
|
+
let sBytes = new Uint8Array(sLen);
|
|
3965
|
+
sBytes.set(der.subarray(off, off + sLen));
|
|
3966
|
+
rBytes = leftPadTo32(stripLeadingZero(rBytes));
|
|
3967
|
+
sBytes = leftPadTo32(stripLeadingZero(sBytes));
|
|
3968
|
+
return { r: rBytes, s: sBytes };
|
|
3969
|
+
}
|
|
3970
|
+
function stripLeadingZero(b) {
|
|
3971
|
+
const start = b.length > 0 && b[0] === 0 ? 1 : 0;
|
|
3972
|
+
const out = new Uint8Array(b.length - start);
|
|
3973
|
+
out.set(b.subarray(start));
|
|
3974
|
+
return out;
|
|
3975
|
+
}
|
|
3976
|
+
function leftPadTo32(b) {
|
|
3977
|
+
if (b.length > 32) throw new Error(`P-256 component longer than 32 bytes: ${b.length}`);
|
|
3978
|
+
const out = new Uint8Array(32);
|
|
3979
|
+
out.set(b, 32 - b.length);
|
|
3980
|
+
return out;
|
|
3981
|
+
}
|
|
3982
|
+
function webauthnPubkeyFelts(pub) {
|
|
3983
|
+
const x = bytesBeToU2562(pub.x);
|
|
3984
|
+
const y = bytesBeToU2562(pub.y);
|
|
3985
|
+
return [x.low, x.high, y.low, y.high];
|
|
3986
|
+
}
|
|
3987
|
+
function webauthnOwnerId(pub) {
|
|
3988
|
+
return ownerCommitment(WEBAUTHN_KIND_TAG_FELT, webauthnPubkeyFelts(pub));
|
|
3989
|
+
}
|
|
3990
|
+
function buildWebAuthnEnvelopeFromAssertion(params) {
|
|
3991
|
+
const ownerIndex = params.ownerIndex ?? 0n;
|
|
3992
|
+
if (ownerIndex < 0n || ownerIndex >= 0x100000000n) {
|
|
3993
|
+
throw new Error(
|
|
3994
|
+
`buildWebAuthnEnvelopeFromAssertion: ownerIndex ${ownerIndex} is out of u32 range [0, 2^32).`
|
|
3995
|
+
);
|
|
3996
|
+
}
|
|
3997
|
+
const { r, s } = parseDerEcdsaSignature(params.signatureDer);
|
|
3998
|
+
const expectedChallenge = base64UrlEncode(bigintToBe322(params.messageHash));
|
|
3999
|
+
const needle = `"challenge":"${expectedChallenge}"`;
|
|
4000
|
+
const clientDataStr = new TextDecoder().decode(params.clientDataJSON);
|
|
4001
|
+
const needleStart = clientDataStr.indexOf(needle);
|
|
4002
|
+
if (needleStart < 0) {
|
|
4003
|
+
throw new Error(
|
|
4004
|
+
'WebAuthn clientDataJSON does not contain a `"challenge":"<expected>"` substring for the given messageHash.'
|
|
4005
|
+
);
|
|
4006
|
+
}
|
|
4007
|
+
const challengeOffset = needleStart + '"challenge":"'.length;
|
|
4008
|
+
const rU = bytesBeToU2562(r);
|
|
4009
|
+
const sU = bytesBeToU2562(s);
|
|
4010
|
+
const payload = [
|
|
4011
|
+
...byteArrayToFelts(params.authenticatorData),
|
|
4012
|
+
...byteArrayToFelts(params.clientDataJSON),
|
|
4013
|
+
BigInt(challengeOffset),
|
|
4014
|
+
rU.low,
|
|
4015
|
+
rU.high,
|
|
4016
|
+
sU.low,
|
|
4017
|
+
sU.high,
|
|
4018
|
+
params.yParity ?? 0n
|
|
4019
|
+
];
|
|
4020
|
+
return wrapV2Snip12Envelope({
|
|
4021
|
+
ownerId: ownerIndex,
|
|
4022
|
+
kindTag: WEBAUTHN_KIND_TAG,
|
|
4023
|
+
payload
|
|
4024
|
+
});
|
|
4025
|
+
}
|
|
4026
|
+
async function buildWebAuthnEnvelopeFromPrivateKey(params) {
|
|
4027
|
+
const ownerIndex = params.ownerIndex ?? 0n;
|
|
4028
|
+
if (ownerIndex < 0n || ownerIndex >= 0x100000000n) {
|
|
4029
|
+
throw new Error(
|
|
4030
|
+
`buildWebAuthnEnvelopeFromPrivateKey: ownerIndex ${ownerIndex} is out of u32 range [0, 2^32).`
|
|
4031
|
+
);
|
|
4032
|
+
}
|
|
4033
|
+
const privBytes = normalizePrivateKey2(params.privateKey);
|
|
4034
|
+
const rpId = params.rpId ?? "cifra.mx";
|
|
4035
|
+
const origin = params.origin ?? "https://cifra.mx";
|
|
4036
|
+
const flags = params.flags ?? 5;
|
|
4037
|
+
const signCount = params.signCount ?? 42;
|
|
4038
|
+
if (!Number.isInteger(flags) || flags < 0 || flags > 255) {
|
|
4039
|
+
throw new Error(`WebAuthn flags must be an integer in [0, 255]; got ${flags}.`);
|
|
4040
|
+
}
|
|
4041
|
+
if (!Number.isInteger(signCount) || signCount < 0 || signCount > 4294967295) {
|
|
4042
|
+
throw new Error(`WebAuthn signCount must be an integer in [0, 0xFFFFFFFF]; got ${signCount}.`);
|
|
4043
|
+
}
|
|
4044
|
+
const [{ p256 }, sha2] = await Promise.all([
|
|
4045
|
+
import('@noble/curves/nist.js'),
|
|
4046
|
+
import('@noble/hashes/sha2.js')
|
|
4047
|
+
]);
|
|
4048
|
+
const hashBytes = bigintToBe322(params.messageHash);
|
|
4049
|
+
const challengeStr = base64UrlEncode(hashBytes);
|
|
4050
|
+
const clientDataStr = `{"type":"webauthn.get","challenge":"${challengeStr}","origin":"${origin}","crossOrigin":false}`;
|
|
4051
|
+
const clientData = new TextEncoder().encode(clientDataStr);
|
|
4052
|
+
const challengeOffset = clientDataStr.indexOf(`"challenge":"${challengeStr}"`) + '"challenge":"'.length;
|
|
4053
|
+
const rpIdHash = sha2.sha256(new TextEncoder().encode(rpId));
|
|
4054
|
+
const authData = new Uint8Array(32 + 1 + 4);
|
|
4055
|
+
authData.set(rpIdHash, 0);
|
|
4056
|
+
authData[32] = flags;
|
|
4057
|
+
authData[33] = signCount >>> 24 & 255;
|
|
4058
|
+
authData[34] = signCount >>> 16 & 255;
|
|
4059
|
+
authData[35] = signCount >>> 8 & 255;
|
|
4060
|
+
authData[36] = signCount & 255;
|
|
4061
|
+
const hClient = sha2.sha256(clientData);
|
|
4062
|
+
const signingBase = new Uint8Array(authData.length + hClient.length);
|
|
4063
|
+
signingBase.set(authData, 0);
|
|
4064
|
+
signingBase.set(hClient, authData.length);
|
|
4065
|
+
const outerHash = sha2.sha256(signingBase);
|
|
4066
|
+
const sigBytes = p256.sign(outerHash, privBytes, {
|
|
4067
|
+
prehash: false
|
|
4068
|
+
});
|
|
4069
|
+
if (sigBytes.length !== 64) {
|
|
4070
|
+
throw new Error(`p256.sign returned ${sigBytes.length} bytes; expected 64`);
|
|
4071
|
+
}
|
|
4072
|
+
let r = bytesBeToBigint(sigBytes.slice(0, 32));
|
|
4073
|
+
let s = bytesBeToBigint(sigBytes.slice(32, 64));
|
|
4074
|
+
const halfOrder = P256_ORDER >> 1n;
|
|
4075
|
+
if (s > halfOrder) s = P256_ORDER - s;
|
|
4076
|
+
const rBytes = bigintToBe322(r);
|
|
4077
|
+
const sBytes = bigintToBe322(s);
|
|
4078
|
+
const rU = bytesBeToU2562(rBytes);
|
|
4079
|
+
const sU = bytesBeToU2562(sBytes);
|
|
4080
|
+
const payload = [
|
|
4081
|
+
...byteArrayToFelts(authData),
|
|
4082
|
+
...byteArrayToFelts(clientData),
|
|
4083
|
+
BigInt(challengeOffset),
|
|
4084
|
+
rU.low,
|
|
4085
|
+
rU.high,
|
|
4086
|
+
sU.low,
|
|
4087
|
+
sU.high,
|
|
4088
|
+
0n
|
|
4089
|
+
// y_parity — Cairo verifier handles parity recovery internally
|
|
4090
|
+
];
|
|
4091
|
+
return wrapV2Snip12Envelope({
|
|
4092
|
+
ownerId: ownerIndex,
|
|
4093
|
+
kindTag: WEBAUTHN_KIND_TAG,
|
|
4094
|
+
payload
|
|
4095
|
+
});
|
|
4096
|
+
}
|
|
4097
|
+
function normalizePrivateKey2(input) {
|
|
4098
|
+
if (input instanceof Uint8Array) {
|
|
4099
|
+
assertBytes2("P-256 private key", input, 32);
|
|
4100
|
+
return input;
|
|
4101
|
+
}
|
|
4102
|
+
const hex = input.startsWith("0x") ? input.slice(2) : input;
|
|
4103
|
+
if (hex.length !== 64) {
|
|
4104
|
+
throw new Error(`P-256 private key hex must be 64 chars (32 bytes); got ${hex.length}.`);
|
|
4105
|
+
}
|
|
4106
|
+
if (!/^[0-9a-fA-F]+$/.test(hex)) {
|
|
4107
|
+
throw new Error("P-256 private key contains non-hex characters.");
|
|
4108
|
+
}
|
|
4109
|
+
const out = new Uint8Array(32);
|
|
4110
|
+
for (let i = 0; i < 32; i++) {
|
|
4111
|
+
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
4112
|
+
}
|
|
4113
|
+
return out;
|
|
4114
|
+
}
|
|
4115
|
+
function bytesBeToBigint(b) {
|
|
4116
|
+
let x = 0n;
|
|
4117
|
+
for (let i = 0; i < b.length; i++) x = x << 8n | BigInt(b[i]);
|
|
4118
|
+
return x;
|
|
4119
|
+
}
|
|
4120
|
+
|
|
4121
|
+
// src/shhh/signers/jwt_apple_sub.ts
|
|
4122
|
+
init_compute_wallet_address();
|
|
4123
|
+
init_oe();
|
|
4124
|
+
var JWT_APPLE_SUB_KIND_TAG = "JWT_ES256_APPLE_SUB";
|
|
4125
|
+
var JWT_APPLE_SUB_KIND_TAG_FELT = BigInt(starknet.shortString.encodeShortString(JWT_APPLE_SUB_KIND_TAG));
|
|
4126
|
+
var APPLE_ISSUER = "https://appleid.apple.com";
|
|
4127
|
+
var NONCE_B64URL_LEN = 43;
|
|
4128
|
+
var U128_MASK = (1n << 128n) - 1n;
|
|
4129
|
+
function assertBytes3(name, b, len) {
|
|
4130
|
+
if (b.length !== len) {
|
|
4131
|
+
throw new Error(`${name} must be exactly ${len} bytes; got ${b.length}.`);
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
function bytesBeToU2563(b) {
|
|
4135
|
+
assertBytes3("secp256r1 32B value", b, 32);
|
|
4136
|
+
let x = 0n;
|
|
4137
|
+
for (let i = 0; i < b.length; i++) x = x << 8n | BigInt(b[i]);
|
|
4138
|
+
return { low: x & U128_MASK, high: x >> 128n };
|
|
4139
|
+
}
|
|
4140
|
+
function jwtAppleSubHash(sub) {
|
|
4141
|
+
const bytes = new TextEncoder().encode(sub);
|
|
4142
|
+
const felts = Array.from(bytes).map((b) => BigInt(b));
|
|
4143
|
+
return BigInt(starknet.hash.computePoseidonHashOnElements(felts.map((f) => "0x" + f.toString(16))));
|
|
4144
|
+
}
|
|
4145
|
+
function jwtAppleSubPubkeyFelts(pubkey) {
|
|
4146
|
+
const x = bytesBeToU2563(pubkey.x);
|
|
4147
|
+
const y = bytesBeToU2563(pubkey.y);
|
|
4148
|
+
const subHash = jwtAppleSubHash(pubkey.sub);
|
|
4149
|
+
return [x.low, x.high, y.low, y.high, subHash];
|
|
4150
|
+
}
|
|
4151
|
+
function jwtAppleSubOwnerId(pubkey) {
|
|
4152
|
+
return ownerCommitment(JWT_APPLE_SUB_KIND_TAG_FELT, jwtAppleSubPubkeyFelts(pubkey));
|
|
4153
|
+
}
|
|
4154
|
+
function base64urlEncode(bytes) {
|
|
4155
|
+
let bin = "";
|
|
4156
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
4157
|
+
const b64 = btoaPortable(bin);
|
|
4158
|
+
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
4159
|
+
}
|
|
4160
|
+
function btoaPortable(bin) {
|
|
4161
|
+
if (typeof btoa === "function") return btoa(bin);
|
|
4162
|
+
const buf = globalThis.Buffer;
|
|
4163
|
+
if (buf) return buf.from(bin, "binary").toString("base64");
|
|
4164
|
+
throw new Error("no base64 encoder available (need btoa or Buffer in scope)");
|
|
4165
|
+
}
|
|
4166
|
+
function base64urlDecode(s) {
|
|
4167
|
+
const pad = s.length % 4 === 0 ? 0 : 4 - s.length % 4;
|
|
4168
|
+
const std = s.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat(pad);
|
|
4169
|
+
const bin = atobPortable(std);
|
|
4170
|
+
const out = new Uint8Array(bin.length);
|
|
4171
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
4172
|
+
return out;
|
|
4173
|
+
}
|
|
4174
|
+
function atobPortable(s) {
|
|
4175
|
+
if (typeof atob === "function") return atob(s);
|
|
4176
|
+
const buf = globalThis.Buffer;
|
|
4177
|
+
if (buf) return buf.from(s, "base64").toString("binary");
|
|
4178
|
+
throw new Error("no base64 decoder available (need atob or Buffer in scope)");
|
|
4179
|
+
}
|
|
4180
|
+
function messageHashToNonceB64url(messageHash) {
|
|
4181
|
+
const beBytes = new Uint8Array(32);
|
|
4182
|
+
let x = messageHash;
|
|
4183
|
+
for (let i = 31; i >= 0; i--) {
|
|
4184
|
+
beBytes[i] = Number(x & 0xffn);
|
|
4185
|
+
x >>= 8n;
|
|
4186
|
+
}
|
|
4187
|
+
if (x !== 0n) {
|
|
4188
|
+
throw new RangeError("messageHash exceeds 256 bits");
|
|
4189
|
+
}
|
|
4190
|
+
const out = base64urlEncode(beBytes);
|
|
4191
|
+
if (out.length !== NONCE_B64URL_LEN) {
|
|
4192
|
+
throw new Error(`expected ${NONCE_B64URL_LEN}-char nonce b64url, got ${out.length}`);
|
|
4193
|
+
}
|
|
4194
|
+
return out;
|
|
4195
|
+
}
|
|
4196
|
+
function findByteOffset(haystack, needle, label) {
|
|
4197
|
+
outer: for (let i = 0; i + needle.length <= haystack.length; i++) {
|
|
4198
|
+
for (let j = 0; j < needle.length; j++) {
|
|
4199
|
+
if (haystack[i + j] !== needle[j]) continue outer;
|
|
4200
|
+
}
|
|
4201
|
+
return i;
|
|
4202
|
+
}
|
|
4203
|
+
throw new Error(`JWT_APPLE_SUB: ${label} not found in payload (verifier would reject)`);
|
|
4204
|
+
}
|
|
4205
|
+
function locateExactClaimValueOffset(payload, claim, expectedValue) {
|
|
4206
|
+
const literal = `"${claim}":"${expectedValue}"`;
|
|
4207
|
+
const literalBytes = new TextEncoder().encode(literal);
|
|
4208
|
+
const idx = findByteOffsetOrMinusOne(payload, literalBytes);
|
|
4209
|
+
if (idx < 0) {
|
|
4210
|
+
throw new Error(
|
|
4211
|
+
`JWT_APPLE_SUB: ${claim} claim with expected value not found in payload (verifier would reject)`
|
|
4212
|
+
);
|
|
4213
|
+
}
|
|
4214
|
+
const prefix = `"${claim}":"`;
|
|
4215
|
+
return idx + new TextEncoder().encode(prefix).length;
|
|
4216
|
+
}
|
|
4217
|
+
function findByteOffsetOrMinusOne(haystack, needle) {
|
|
4218
|
+
outer: for (let i = 0; i + needle.length <= haystack.length; i++) {
|
|
4219
|
+
for (let j = 0; j < needle.length; j++) {
|
|
4220
|
+
if (haystack[i + j] !== needle[j]) continue outer;
|
|
4221
|
+
}
|
|
4222
|
+
return i;
|
|
4223
|
+
}
|
|
4224
|
+
return -1;
|
|
4225
|
+
}
|
|
4226
|
+
function parseAppleIdToken(idToken) {
|
|
4227
|
+
const parts = idToken.split(".");
|
|
4228
|
+
if (parts.length !== 3) {
|
|
4229
|
+
throw new Error(
|
|
4230
|
+
`JWT_APPLE_SUB: id_token must have 3 parts separated by '.'; got ${parts.length}`
|
|
4231
|
+
);
|
|
4232
|
+
}
|
|
4233
|
+
const [headerB64, payloadB64, sigB64] = parts;
|
|
4234
|
+
const payloadDecoded = base64urlDecode(payloadB64);
|
|
4235
|
+
const rawSignature = base64urlDecode(sigB64);
|
|
4236
|
+
if (rawSignature.length !== 64) {
|
|
4237
|
+
throw new Error(
|
|
4238
|
+
`JWT_APPLE_SUB: ECDSA signature must be 64 bytes (raw r||s); got ${rawSignature.length}. Apple returns raw 64-byte signatures; if you have a DER-encoded one, decode it first.`
|
|
4239
|
+
);
|
|
4240
|
+
}
|
|
4241
|
+
return { headerB64, payloadDecoded, rawSignature };
|
|
4242
|
+
}
|
|
4243
|
+
function locateSubClaim(payload) {
|
|
4244
|
+
const SUB_KEY = new TextEncoder().encode('"sub":"');
|
|
4245
|
+
const start = findByteOffset(payload, SUB_KEY, '"sub":"');
|
|
4246
|
+
const valueStart = start + SUB_KEY.length;
|
|
4247
|
+
let end = valueStart;
|
|
4248
|
+
while (end < payload.length && payload[end] !== 34) {
|
|
4249
|
+
if (payload[end] === 92) {
|
|
4250
|
+
throw new Error("JWT_APPLE_SUB: sub claim contains escape sequence; not supported");
|
|
4251
|
+
}
|
|
4252
|
+
end += 1;
|
|
4253
|
+
}
|
|
4254
|
+
if (end >= payload.length) {
|
|
4255
|
+
throw new Error("JWT_APPLE_SUB: sub claim is unterminated");
|
|
4256
|
+
}
|
|
4257
|
+
return { offset: valueStart, length: end - valueStart };
|
|
4258
|
+
}
|
|
4259
|
+
function serdeByteArray(bytes) {
|
|
4260
|
+
return byteArrayToFelts(bytes);
|
|
4261
|
+
}
|
|
4262
|
+
function buildEnvelopePayload(fields) {
|
|
4263
|
+
const headerBytes = new TextEncoder().encode(fields.headerB64);
|
|
4264
|
+
const headerSerde = serdeByteArray(headerBytes);
|
|
4265
|
+
const payloadSerde = serdeByteArray(fields.payloadDecoded);
|
|
4266
|
+
return [
|
|
4267
|
+
...headerSerde,
|
|
4268
|
+
...payloadSerde,
|
|
4269
|
+
BigInt(fields.nonceOffset),
|
|
4270
|
+
BigInt(fields.issOffset),
|
|
4271
|
+
BigInt(fields.subOffset),
|
|
4272
|
+
BigInt(fields.subLen),
|
|
4273
|
+
fields.r & U128_MASK,
|
|
4274
|
+
fields.r >> 128n,
|
|
4275
|
+
fields.s & U128_MASK,
|
|
4276
|
+
fields.s >> 128n,
|
|
4277
|
+
BigInt(fields.yParity)
|
|
4278
|
+
];
|
|
4279
|
+
}
|
|
4280
|
+
function buildJwtAppleSubEnvelopeFromTokenResponse(params) {
|
|
4281
|
+
const ownerIndex = params.ownerIndex ?? 0n;
|
|
4282
|
+
if (ownerIndex < 0n || ownerIndex >= 0x100000000n) {
|
|
4283
|
+
throw new Error(
|
|
4284
|
+
`buildJwtAppleSubEnvelopeFromTokenResponse: ownerIndex ${ownerIndex} is out of u32 range [0, 2^32).`
|
|
4285
|
+
);
|
|
4286
|
+
}
|
|
4287
|
+
const { headerB64, payloadDecoded, rawSignature } = parseAppleIdToken(params.idToken);
|
|
4288
|
+
const expectedNonce = messageHashToNonceB64url(params.messageHash);
|
|
4289
|
+
const nonceOffset = locateExactClaimValueOffset(payloadDecoded, "nonce", expectedNonce);
|
|
4290
|
+
const issOffset = locateExactClaimValueOffset(payloadDecoded, "iss", APPLE_ISSUER);
|
|
4291
|
+
const { offset: subOffset, length: subLen } = locateSubClaim(payloadDecoded);
|
|
4292
|
+
const actualSub = new TextDecoder().decode(
|
|
4293
|
+
payloadDecoded.subarray(subOffset, subOffset + subLen)
|
|
4294
|
+
);
|
|
4295
|
+
if (actualSub !== params.pubkey.sub) {
|
|
4296
|
+
throw new Error(
|
|
4297
|
+
`JWT_APPLE_SUB: id_token sub "${actualSub}" doesn't match pubkey.sub "${params.pubkey.sub}" \u2014 wrong user signed in`
|
|
4298
|
+
);
|
|
4299
|
+
}
|
|
4300
|
+
const { r, s } = parseRawEcdsaSignature(rawSignature);
|
|
4301
|
+
const yParity = 0;
|
|
4302
|
+
return wrapV2Snip12Envelope({
|
|
4303
|
+
ownerId: ownerIndex,
|
|
4304
|
+
kindTag: JWT_APPLE_SUB_KIND_TAG,
|
|
4305
|
+
payload: buildEnvelopePayload({
|
|
4306
|
+
headerB64,
|
|
4307
|
+
payloadDecoded,
|
|
4308
|
+
nonceOffset,
|
|
4309
|
+
issOffset,
|
|
4310
|
+
subOffset,
|
|
4311
|
+
subLen,
|
|
4312
|
+
r,
|
|
4313
|
+
s,
|
|
4314
|
+
yParity
|
|
4315
|
+
})
|
|
4316
|
+
});
|
|
4317
|
+
}
|
|
4318
|
+
function parseRawEcdsaSignature(raw) {
|
|
4319
|
+
if (raw.length !== 64) {
|
|
4320
|
+
throw new Error(`JWT_APPLE_SUB: ECDSA signature must be 64 bytes; got ${raw.length}`);
|
|
4321
|
+
}
|
|
4322
|
+
let r = 0n;
|
|
4323
|
+
for (let i = 0; i < 32; i++) r = r << 8n | BigInt(raw[i]);
|
|
4324
|
+
let s = 0n;
|
|
4325
|
+
for (let i = 32; i < 64; i++) s = s << 8n | BigInt(raw[i]);
|
|
4326
|
+
return { r, s };
|
|
4327
|
+
}
|
|
4328
|
+
async function buildJwtAppleSubEnvelopeFromTestKey(params) {
|
|
4329
|
+
const ownerIndex = params.ownerIndex ?? 0n;
|
|
4330
|
+
if (ownerIndex < 0n || ownerIndex >= 0x100000000n) {
|
|
4331
|
+
throw new Error(
|
|
4332
|
+
`buildJwtAppleSubEnvelopeFromTestKey: ownerIndex ${ownerIndex} is out of u32 range [0, 2^32).`
|
|
4333
|
+
);
|
|
4334
|
+
}
|
|
4335
|
+
const { p256 } = await import('@noble/curves/nist.js');
|
|
4336
|
+
const { sha256 } = await import('@noble/hashes/sha2.js');
|
|
4337
|
+
if (params.privateKey.length !== 32) {
|
|
4338
|
+
throw new Error(
|
|
4339
|
+
`JWT_APPLE_SUB: P-256 private key must be 32 bytes; got ${params.privateKey.length}`
|
|
4340
|
+
);
|
|
4341
|
+
}
|
|
4342
|
+
const pubUncompressed = p256.getPublicKey(params.privateKey, false);
|
|
4343
|
+
if (pubUncompressed.length !== 65 || pubUncompressed[0] !== 4) {
|
|
4344
|
+
throw new Error("expected 65-byte uncompressed P-256 pubkey");
|
|
4345
|
+
}
|
|
4346
|
+
const pubkey = {
|
|
4347
|
+
x: pubUncompressed.slice(1, 33),
|
|
4348
|
+
y: pubUncompressed.slice(33, 65),
|
|
4349
|
+
sub: params.sub
|
|
4350
|
+
};
|
|
4351
|
+
const headerJson = JSON.stringify({ alg: "ES256", kid: "synthesized" });
|
|
4352
|
+
const headerB64 = base64urlEncode(new TextEncoder().encode(headerJson));
|
|
4353
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
4354
|
+
const nonce = messageHashToNonceB64url(params.messageHash);
|
|
4355
|
+
const payloadObject = {
|
|
4356
|
+
iss: APPLE_ISSUER,
|
|
4357
|
+
aud: params.aud,
|
|
4358
|
+
sub: params.sub,
|
|
4359
|
+
iat: now,
|
|
4360
|
+
exp: now + 600,
|
|
4361
|
+
nonce
|
|
4362
|
+
};
|
|
4363
|
+
const payloadJson = JSON.stringify(payloadObject);
|
|
4364
|
+
const payloadDecoded = new TextEncoder().encode(payloadJson);
|
|
4365
|
+
const payloadB64 = base64urlEncode(payloadDecoded);
|
|
4366
|
+
const signingInput = new TextEncoder().encode(`${headerB64}.${payloadB64}`);
|
|
4367
|
+
const digest = sha256(signingInput);
|
|
4368
|
+
const rawSig = p256.sign(digest, params.privateKey, {
|
|
4369
|
+
lowS: true
|
|
4370
|
+
});
|
|
4371
|
+
if (rawSig.length !== 64) {
|
|
4372
|
+
throw new Error(`expected 64-byte raw ECDSA signature from p256.sign; got ${rawSig.length}`);
|
|
4373
|
+
}
|
|
4374
|
+
const { r, s } = parseRawEcdsaSignature(rawSig);
|
|
4375
|
+
const idToken = `${headerB64}.${payloadB64}.${base64urlEncode(rawSig)}`;
|
|
4376
|
+
const nonceOffset = locateExactClaimValueOffset(payloadDecoded, "nonce", nonce);
|
|
4377
|
+
const issOffset = locateExactClaimValueOffset(payloadDecoded, "iss", APPLE_ISSUER);
|
|
4378
|
+
const { offset: subOffset, length: subLen } = locateSubClaim(payloadDecoded);
|
|
4379
|
+
const envelope = wrapV2Snip12Envelope({
|
|
4380
|
+
ownerId: ownerIndex,
|
|
4381
|
+
kindTag: JWT_APPLE_SUB_KIND_TAG,
|
|
4382
|
+
payload: buildEnvelopePayload({
|
|
4383
|
+
headerB64,
|
|
4384
|
+
payloadDecoded,
|
|
4385
|
+
nonceOffset,
|
|
4386
|
+
issOffset,
|
|
4387
|
+
subOffset,
|
|
4388
|
+
subLen,
|
|
4389
|
+
r,
|
|
4390
|
+
s,
|
|
4391
|
+
yParity: 0
|
|
4392
|
+
})
|
|
4393
|
+
});
|
|
4394
|
+
return { envelope, pubkey, idToken };
|
|
4395
|
+
}
|
|
4396
|
+
var ROLE_OWNER = BigInt(starknet.shortString.encodeShortString("OWNER"));
|
|
4397
|
+
var ROLE_GUARDIAN = BigInt(starknet.shortString.encodeShortString("GUARDIAN"));
|
|
4398
|
+
var OP_ADD_OWNER = BigInt(starknet.shortString.encodeShortString("ADD_OWNER"));
|
|
4399
|
+
var OP_REMOVE_OWNER = BigInt(starknet.shortString.encodeShortString("REMOVE_OWNER"));
|
|
4400
|
+
var OP_ROTATE_OWNER = BigInt(starknet.shortString.encodeShortString("ROTATE_OWNER"));
|
|
4401
|
+
var OP_SET_THRESHOLD = BigInt(starknet.shortString.encodeShortString("SET_THRESHOLD"));
|
|
4402
|
+
var TIMELOCK_SECONDS = {
|
|
4403
|
+
ADD_OWNER: 172800,
|
|
4404
|
+
// 48h
|
|
4405
|
+
REMOVE_OWNER: 86400,
|
|
4406
|
+
// 24h
|
|
4407
|
+
ROTATE_OWNER: 86400,
|
|
4408
|
+
// 24h
|
|
4409
|
+
SET_THRESHOLD: 172800,
|
|
4410
|
+
// 48h
|
|
4411
|
+
ADD_VERIFIER: 172800,
|
|
4412
|
+
// 48h
|
|
4413
|
+
REMOVE_VERIFIER: 86400,
|
|
4414
|
+
// 24h
|
|
4415
|
+
ADD_GUARDIAN: 86400,
|
|
4416
|
+
// 24h
|
|
4417
|
+
REMOVE_GUARDIAN: 86400,
|
|
4418
|
+
// 24h
|
|
4419
|
+
RECOVERY: 604800
|
|
4420
|
+
// 7d — `TIMELOCK_RECOVERY`, the headline UX gate
|
|
4421
|
+
};
|
|
4422
|
+
var DEFAULT_OP_EXPIRY_SECONDS = 1209600;
|
|
4423
|
+
function _felt2(x, field = "felt") {
|
|
4424
|
+
const v = typeof x === "bigint" ? x : BigInt(x);
|
|
4425
|
+
if (v < 0n) {
|
|
4426
|
+
throw new RangeError(`${field} must be a non-negative felt; got ${v.toString()}`);
|
|
4427
|
+
}
|
|
4428
|
+
return v;
|
|
4429
|
+
}
|
|
4430
|
+
function _encodeOwnerInput(input) {
|
|
4431
|
+
if (input.weight < 0) {
|
|
4432
|
+
throw new RangeError(`NewOwnerInput.weight must be non-negative; got ${input.weight}`);
|
|
4433
|
+
}
|
|
4434
|
+
const kindFelt = _felt2(BigInt(starknet.shortString.encodeShortString(input.kind)), "kind");
|
|
4435
|
+
const roleFelt = input.role === "OWNER" ? ROLE_OWNER : ROLE_GUARDIAN;
|
|
4436
|
+
const pubkey = input.pubkeyBytes.map((p, i) => _felt2(p, `pubkeyBytes[${i}]`));
|
|
4437
|
+
return [
|
|
4438
|
+
kindFelt,
|
|
4439
|
+
BigInt(pubkey.length),
|
|
4440
|
+
...pubkey,
|
|
4441
|
+
roleFelt,
|
|
4442
|
+
BigInt(input.weight),
|
|
4443
|
+
_felt2(BigInt(starknet.shortString.encodeShortString(input.label)), "label")
|
|
4444
|
+
];
|
|
4445
|
+
}
|
|
4446
|
+
function _call(walletAddress, entrypoint, calldata) {
|
|
4447
|
+
for (let i = 0; i < calldata.length; i++) {
|
|
4448
|
+
if (calldata[i] < 0n) {
|
|
4449
|
+
throw new RangeError(
|
|
4450
|
+
`calldata[${i}] must be a non-negative felt; got ${calldata[i].toString()}`
|
|
4451
|
+
);
|
|
4452
|
+
}
|
|
4453
|
+
}
|
|
4454
|
+
return {
|
|
4455
|
+
contractAddress: typeof walletAddress === "bigint" ? "0x" + walletAddress.toString(16) : walletAddress,
|
|
4456
|
+
entrypoint,
|
|
4457
|
+
calldata: calldata.map((v) => "0x" + v.toString(16))
|
|
4458
|
+
};
|
|
4459
|
+
}
|
|
4460
|
+
function buildProposeAddOwnerCall(args) {
|
|
4461
|
+
const calldata = [BigInt(args.proposer), ..._encodeOwnerInput(args.newOwner)];
|
|
4462
|
+
return _call(args.walletAddress, "propose_add_owner", calldata);
|
|
4463
|
+
}
|
|
4464
|
+
function buildExecuteAddOwnerCall(args) {
|
|
4465
|
+
const calldata = [_felt2(args.opId), ..._encodeOwnerInput(args.newOwner)];
|
|
4466
|
+
return _call(args.walletAddress, "execute_add_owner", calldata);
|
|
4467
|
+
}
|
|
4468
|
+
function buildProposeRemoveOwnerCall(args) {
|
|
4469
|
+
return _call(args.walletAddress, "propose_remove_owner", [
|
|
4470
|
+
BigInt(args.proposer),
|
|
4471
|
+
BigInt(args.ownerId)
|
|
4472
|
+
]);
|
|
4473
|
+
}
|
|
4474
|
+
function buildExecuteRemoveOwnerCall(args) {
|
|
4475
|
+
return _call(args.walletAddress, "execute_remove_owner", [
|
|
4476
|
+
_felt2(args.opId),
|
|
4477
|
+
BigInt(args.ownerId)
|
|
4478
|
+
]);
|
|
4479
|
+
}
|
|
4480
|
+
function buildProposeRotateOwnerCall(args) {
|
|
4481
|
+
return _call(args.walletAddress, "propose_rotate_owner", [
|
|
4482
|
+
BigInt(args.proposer),
|
|
4483
|
+
BigInt(args.ownerId),
|
|
4484
|
+
BigInt(args.newPubkeyBytes.length),
|
|
4485
|
+
...args.newPubkeyBytes
|
|
4486
|
+
]);
|
|
4487
|
+
}
|
|
4488
|
+
function buildExecuteRotateOwnerCall(args) {
|
|
4489
|
+
return _call(args.walletAddress, "execute_rotate_owner", [
|
|
4490
|
+
_felt2(args.opId),
|
|
4491
|
+
BigInt(args.ownerId),
|
|
4492
|
+
BigInt(args.newPubkeyBytes.length),
|
|
4493
|
+
...args.newPubkeyBytes
|
|
4494
|
+
]);
|
|
4495
|
+
}
|
|
4496
|
+
function buildProposeSetThresholdCall(args) {
|
|
4497
|
+
return _call(args.walletAddress, "propose_set_threshold", [
|
|
4498
|
+
BigInt(args.proposer),
|
|
4499
|
+
BigInt(args.newThreshold)
|
|
4500
|
+
]);
|
|
4501
|
+
}
|
|
4502
|
+
function buildExecuteSetThresholdCall(args) {
|
|
4503
|
+
return _call(args.walletAddress, "execute_set_threshold", [
|
|
4504
|
+
_felt2(args.opId),
|
|
4505
|
+
BigInt(args.newThreshold)
|
|
4506
|
+
]);
|
|
4507
|
+
}
|
|
4508
|
+
function buildCancelPendingOpCall(args) {
|
|
4509
|
+
return _call(args.walletAddress, "cancel_pending_op", [_felt2(args.opId)]);
|
|
4510
|
+
}
|
|
4511
|
+
function buildInitiateRecoveryCall(args) {
|
|
4512
|
+
const calldata = [BigInt(args.proposer), ..._encodeOwnerInput(args.newOwner)];
|
|
4513
|
+
return _call(args.walletAddress, "initiate_recovery", calldata);
|
|
4514
|
+
}
|
|
4515
|
+
function buildCancelRecoveryCall(args) {
|
|
4516
|
+
return _call(args.walletAddress, "cancel_recovery", [BigInt(args.ownerId)]);
|
|
4517
|
+
}
|
|
4518
|
+
function buildFinalizeRecoveryCall(args) {
|
|
4519
|
+
return _call(args.walletAddress, "finalize_recovery", _encodeOwnerInput(args.newOwner));
|
|
4520
|
+
}
|
|
4521
|
+
var VIEW_SELECTORS = {
|
|
4522
|
+
get_pending_op: starknet.hash.getSelectorFromName("get_pending_op"),
|
|
4523
|
+
get_pending_recovery: starknet.hash.getSelectorFromName("get_pending_recovery"),
|
|
4524
|
+
get_owner: starknet.hash.getSelectorFromName("get_owner")
|
|
4525
|
+
};
|
|
4526
|
+
function _toSecondsBigInt(value, field) {
|
|
4527
|
+
if (typeof value === "bigint") return value;
|
|
4528
|
+
if (!Number.isFinite(value)) {
|
|
4529
|
+
throw new TypeError(`${field} must be a finite timestamp; got ${value}`);
|
|
4530
|
+
}
|
|
4531
|
+
return BigInt(Math.floor(value));
|
|
4532
|
+
}
|
|
4533
|
+
function isReadyToExecute(validAfterSecs, nowSecs) {
|
|
4534
|
+
return _toSecondsBigInt(nowSecs, "nowSecs") >= _toSecondsBigInt(validAfterSecs, "validAfterSecs");
|
|
4535
|
+
}
|
|
4536
|
+
function secondsUntilExecutable(validAfterSecs, nowSecs) {
|
|
4537
|
+
const remaining = _toSecondsBigInt(validAfterSecs, "validAfterSecs") - _toSecondsBigInt(nowSecs, "nowSecs");
|
|
4538
|
+
return remaining <= 0n ? 0 : Number(remaining);
|
|
4539
|
+
}
|
|
4540
|
+
|
|
4541
|
+
// src/shhh/index.ts
|
|
4542
|
+
init_threshold();
|
|
4543
|
+
init_execute_paymaster_raw();
|
|
4544
|
+
|
|
2451
4545
|
Object.defineProperty(exports, "Chain", {
|
|
2452
4546
|
enumerable: true,
|
|
2453
4547
|
get: function () { return types.Chain; }
|
|
@@ -2460,6 +4554,8 @@ Object.defineProperty(exports, "Currency", {
|
|
|
2460
4554
|
enumerable: true,
|
|
2461
4555
|
get: function () { return types.Currency; }
|
|
2462
4556
|
});
|
|
4557
|
+
exports.APPLE_ISSUER = APPLE_ISSUER;
|
|
4558
|
+
exports.BOOTSTRAP_DOMAIN_SEPARATOR = BOOTSTRAP_DOMAIN_SEPARATOR;
|
|
2463
4559
|
exports.ChipiBrowserSDK = ChipiBrowserSDK;
|
|
2464
4560
|
exports.ChipiClient = ChipiClient;
|
|
2465
4561
|
exports.ChipiPaymasterAdapter = ChipiPaymasterAdapter;
|
|
@@ -2470,10 +4566,86 @@ exports.ChipiSkuPurchases = ChipiSkuPurchases;
|
|
|
2470
4566
|
exports.ChipiSkus = ChipiSkus;
|
|
2471
4567
|
exports.ChipiTransactions = ChipiTransactions;
|
|
2472
4568
|
exports.ChipiWallets = ChipiWallets;
|
|
4569
|
+
exports.DEFAULT_OP_EXPIRY_SECONDS = DEFAULT_OP_EXPIRY_SECONDS;
|
|
4570
|
+
exports.OP_ADD_OWNER = OP_ADD_OWNER;
|
|
4571
|
+
exports.OP_REMOVE_OWNER = OP_REMOVE_OWNER;
|
|
4572
|
+
exports.OP_ROTATE_OWNER = OP_ROTATE_OWNER;
|
|
4573
|
+
exports.OP_SET_THRESHOLD = OP_SET_THRESHOLD;
|
|
4574
|
+
exports.ROLE_GUARDIAN = ROLE_GUARDIAN;
|
|
4575
|
+
exports.ROLE_OWNER = ROLE_OWNER;
|
|
4576
|
+
exports.TIMELOCK_SECONDS = TIMELOCK_SECONDS;
|
|
4577
|
+
exports.VIEW_SELECTORS = VIEW_SELECTORS;
|
|
2473
4578
|
exports.X402Facilitator = X402Facilitator;
|
|
2474
|
-
exports.
|
|
2475
|
-
exports.
|
|
4579
|
+
exports.X402ShhhClient = X402ShhhClient;
|
|
4580
|
+
exports.assertValidV2Snip12Envelope = assertValidV2Snip12Envelope;
|
|
4581
|
+
exports.buildBootstrapSignedCall = buildBootstrapSignedCall;
|
|
4582
|
+
exports.buildCancelPendingOpCall = buildCancelPendingOpCall;
|
|
4583
|
+
exports.buildCancelRecoveryCall = buildCancelRecoveryCall;
|
|
4584
|
+
exports.buildEd25519Envelope = buildEd25519Envelope;
|
|
4585
|
+
exports.buildEd25519EnvelopeFromSignature = buildEd25519EnvelopeFromSignature;
|
|
4586
|
+
exports.buildEip191EnvelopeFromPrivateKey = buildEip191EnvelopeFromPrivateKey;
|
|
4587
|
+
exports.buildEip191EnvelopeFromSignature = buildEip191EnvelopeFromSignature;
|
|
4588
|
+
exports.buildExecuteAddOwnerCall = buildExecuteAddOwnerCall;
|
|
4589
|
+
exports.buildExecuteRemoveOwnerCall = buildExecuteRemoveOwnerCall;
|
|
4590
|
+
exports.buildExecuteRotateOwnerCall = buildExecuteRotateOwnerCall;
|
|
4591
|
+
exports.buildExecuteSetThresholdCall = buildExecuteSetThresholdCall;
|
|
4592
|
+
exports.buildFinalizeRecoveryCall = buildFinalizeRecoveryCall;
|
|
4593
|
+
exports.buildInitiateRecoveryCall = buildInitiateRecoveryCall;
|
|
4594
|
+
exports.buildJwtAppleSubEnvelopeFromTestKey = buildJwtAppleSubEnvelopeFromTestKey;
|
|
4595
|
+
exports.buildJwtAppleSubEnvelopeFromTokenResponse = buildJwtAppleSubEnvelopeFromTokenResponse;
|
|
4596
|
+
exports.buildMigrationCalls = buildMigrationCalls;
|
|
4597
|
+
exports.buildOutsideExecution = buildOutsideExecution;
|
|
4598
|
+
exports.buildProposeAddOwnerCall = buildProposeAddOwnerCall;
|
|
4599
|
+
exports.buildProposeRemoveOwnerCall = buildProposeRemoveOwnerCall;
|
|
4600
|
+
exports.buildProposeRotateOwnerCall = buildProposeRotateOwnerCall;
|
|
4601
|
+
exports.buildProposeSetThresholdCall = buildProposeSetThresholdCall;
|
|
4602
|
+
exports.buildStarkEnvelope = buildStarkEnvelope;
|
|
4603
|
+
exports.buildThresholdEnvelope = buildThresholdEnvelope;
|
|
4604
|
+
exports.buildWebAuthnEnvelopeFromAssertion = buildWebAuthnEnvelopeFromAssertion;
|
|
4605
|
+
exports.buildWebAuthnEnvelopeFromPrivateKey = buildWebAuthnEnvelopeFromPrivateKey;
|
|
4606
|
+
exports.byteArrayToFelts = byteArrayToFelts;
|
|
4607
|
+
exports.computeAddressSalt = computeAddressSalt;
|
|
4608
|
+
exports.computeBootstrapMessage = computeBootstrapMessage;
|
|
4609
|
+
exports.computeISignerId = computeISignerId;
|
|
4610
|
+
exports.computeOeHash = computeOeHash;
|
|
4611
|
+
exports.computeShhhAddress = computeShhhAddress;
|
|
4612
|
+
exports.computeSnip12Hash = computeSnip12Hash;
|
|
4613
|
+
exports.ed25519OwnerId = ed25519OwnerId;
|
|
4614
|
+
exports.ed25519PubkeyFelts = ed25519PubkeyFelts;
|
|
4615
|
+
exports.ed25519SignedBytes = ed25519SignedBytes;
|
|
4616
|
+
exports.eip191OwnerId = eip191OwnerId;
|
|
4617
|
+
exports.eip191PubkeyFelts = eip191PubkeyFelts;
|
|
4618
|
+
exports.estimateThresholdGasOverhead = estimateThresholdGasOverhead;
|
|
4619
|
+
exports.executeShhhSponsoredRaw = executeShhhSponsoredRaw;
|
|
4620
|
+
exports.hashCall = hashCall;
|
|
4621
|
+
exports.hashCalls = hashCalls;
|
|
4622
|
+
exports.hashOutsideExecutionStruct = hashOutsideExecutionStruct;
|
|
4623
|
+
exports.hashStarknetDomain = hashStarknetDomain;
|
|
4624
|
+
exports.isReadyToExecute = isReadyToExecute;
|
|
4625
|
+
exports.jwtAppleSubHash = jwtAppleSubHash;
|
|
4626
|
+
exports.jwtAppleSubOwnerId = jwtAppleSubOwnerId;
|
|
4627
|
+
exports.jwtAppleSubPubkeyFelts = jwtAppleSubPubkeyFelts;
|
|
4628
|
+
exports.labelToFelt = labelToFelt;
|
|
4629
|
+
exports.locateSubClaim = locateSubClaim;
|
|
4630
|
+
exports.messageHashToNonceB64url = messageHashToNonceB64url;
|
|
4631
|
+
exports.migrateChipiWalletToShhh = migrateChipiWalletToShhh;
|
|
4632
|
+
exports.ownerCommitment = ownerCommitment;
|
|
4633
|
+
exports.parseAppleIdToken = parseAppleIdToken;
|
|
4634
|
+
exports.parseDerEcdsaSignature = parseDerEcdsaSignature;
|
|
4635
|
+
exports.parseEip191Signature = parseEip191Signature;
|
|
4636
|
+
exports.parseThresholdEnvelope = parseThresholdEnvelope;
|
|
4637
|
+
exports.resolveShhhChainId = resolveShhhChainId;
|
|
4638
|
+
exports.secondsUntilExecutable = secondsUntilExecutable;
|
|
4639
|
+
exports.serializeExecuteFromOutsideV2Calldata = serializeExecuteFromOutsideV2Calldata;
|
|
4640
|
+
exports.signBootstrapMessage = signBootstrapMessage;
|
|
2476
4641
|
exports.signTypedData = signTypedData;
|
|
4642
|
+
exports.snip12HashToHexAsciiBytes = snip12HashToHexAsciiBytes;
|
|
4643
|
+
exports.starkOwnerId = starkOwnerId;
|
|
4644
|
+
exports.starkPubkeyFelts = starkPubkeyFelts;
|
|
4645
|
+
exports.webauthnOwnerId = webauthnOwnerId;
|
|
4646
|
+
exports.webauthnPubkeyFelts = webauthnPubkeyFelts;
|
|
4647
|
+
exports.wrapV2Snip12Envelope = wrapV2Snip12Envelope;
|
|
4648
|
+
exports.wrapV2ThresholdEnvelope = wrapV2ThresholdEnvelope;
|
|
2477
4649
|
exports.x402Middleware = x402Middleware;
|
|
2478
4650
|
//# sourceMappingURL=index.js.map
|
|
2479
4651
|
//# sourceMappingURL=index.js.map
|