@invisi-labs/protocol 1.1.6 → 1.1.7

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.js CHANGED
@@ -4,623 +4,145 @@ import {
4
4
  invisiProtocolAbi
5
5
  } from "./chunk-BYJ4LXCJ.js";
6
6
 
7
- // src/AddressRegistry.ts
8
- import { getAddress as getAddress2, keccak256 } from "ethers";
7
+ // src/authorization/WitnessContext.ts
9
8
  import {
10
- MissingCapabilityError,
11
- ReceivingAddress as ReceivingAddress3
9
+ computeCreditCommitment,
10
+ computeCreditSharedKeyHash,
11
+ computeNoteCommitment,
12
+ computeNoteIdentity,
13
+ computeNoteNullifier,
14
+ computeWalletAuthorizationDigest,
15
+ poseidonHash,
16
+ verifyWalletAuthorization
12
17
  } from "@invisi-labs/core";
13
18
 
14
- // src/RegistrationOperation.ts
15
- import { hexlify as hexlify2, Interface as Interface2, getBytes as getBytes2, verifyTypedData } from "ethers";
16
- import { ReceivingAddress as ReceivingAddress2, createSigningRequest } from "@invisi-labs/core";
19
+ // src/authorization/common.ts
20
+ import {
21
+ normalizeCreditPolicy
22
+ } from "@invisi-labs/core";
23
+ import {
24
+ LocalWitness
25
+ } from "@invisi-labs/prover";
17
26
 
18
- // src/registry.ts
27
+ // src/validation.ts
28
+ import { MAX_CIPHERTEXT_BYTES } from "@invisi-labs/core";
19
29
  import {
20
- Contract,
21
- Interface,
30
+ AbiCoder,
31
+ dataLength,
22
32
  getAddress,
23
- getBytes,
24
- hexlify,
25
33
  isHexString,
26
- TypedDataEncoder,
34
+ keccak256,
35
+ ParamType,
27
36
  toBeHex
28
37
  } from "ethers";
29
- import { ReceivingAddress, decodeAddress } from "@invisi-labs/core";
30
- var MAX_NONCE = (1n << 64n) - 1n;
31
- function uint64(value, name) {
32
- if (typeof value !== "bigint" || value < 0n || value > MAX_NONCE)
33
- throw new RangeError(`${name} must be uint64`);
34
- return value;
38
+ var FIELD_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
39
+ var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
40
+ var ZERO_WORD = toBeHex(0n, 32);
41
+ var EMPTY_PERMIT = {
42
+ deadline: 0n,
43
+ v: 0,
44
+ r: ZERO_WORD,
45
+ s: ZERO_WORD
46
+ };
47
+ var EMPTY_FUNDING = {
48
+ payer: ZERO_ADDRESS,
49
+ amount: 0n,
50
+ sigNonce: 0n,
51
+ deadline: 0n,
52
+ signature: "0x",
53
+ permit: EMPTY_PERMIT
54
+ };
55
+ function uint(v, bits, name = "value") {
56
+ if (typeof v !== "bigint" && typeof v !== "number" && typeof v !== "string" || typeof v === "number" && !Number.isSafeInteger(v))
57
+ throw new Error(`${name} must be an integer`);
58
+ const n = BigInt(v);
59
+ if (n < 0n || n >= 1n << BigInt(bits)) throw new Error(`${name} exceeds u${bits}`);
60
+ return n;
35
61
  }
36
- function hexBytes(value, length) {
37
- if (typeof value !== "string" || !isHexString(value) || value.length !== 2 + length * 2)
38
- throw new Error(`Expected ${length} hexadecimal bytes`);
39
- return getBytes(value);
62
+ function field(v, name = "field") {
63
+ const n = uint(v, 256, name);
64
+ if (n >= FIELD_MODULUS) throw new Error(`${name} is noncanonical field`);
65
+ return n;
40
66
  }
41
- function normalizeKeys(ownerAddress, keys) {
42
- const decoded = decodeAddress(
43
- ReceivingAddress.from({
44
- ownerAddress,
45
- viewingPublicKey: hexBytes(keys.viewingPublicKey, 33),
46
- clueKey: hexBytes(keys.clueKey, 32)
47
- }).toString()
48
- );
49
- if (decoded.kind !== "long") throw new Error("Expected full receiving address");
50
- return {
51
- viewingPublicKey: hexlify(decoded.viewingPublicKey),
52
- clueKey: hexlify(decoded.clueKey)
53
- };
67
+ var word = (v) => toBeHex(typeof v === "bigint" ? v : BigInt(v), 32);
68
+ function dense(v, name) {
69
+ if (!Array.isArray(v)) throw new Error(`${name} must be array`);
70
+ for (let i = 0; i < v.length; i++)
71
+ if (!Object.hasOwn(v, i) || v[i] === void 0 || v[i] === null)
72
+ throw new Error(`${name} has missing or sparse entries`);
73
+ return v;
54
74
  }
55
- function normalize(context, registration, options) {
56
- uint64(context.chainId, "chainId");
57
- const registryAddress = getAddress(context.registryAddress);
58
- if (BigInt(registryAddress) === 0n) throw new Error("Registry address must be nonzero");
59
- if (!/^0x(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64})$/.test(registration.ownerAddress))
60
- throw new Error("Wallet identity must be an EVM owner or padded bytes32");
61
- const ownerAddress = decodeAddress(
62
- ReceivingAddress.fromIdentity(
63
- toBeHex(BigInt(registration.ownerAddress), 20)
64
- ).toString()
65
- ).ownerAddress;
66
- const keys = normalizeKeys(ownerAddress, registration.keys);
67
- const nonce = uint64(registration.nonce, "nonce"), deadline = uint64(registration.deadline, "deadline");
68
- if (nonce === MAX_NONCE) throw new Error("Registry nonce exhausted");
69
- if (options.update !== void 0 && typeof options.update !== "boolean")
70
- throw new TypeError("update must be boolean");
71
- if (!options.update && nonce !== 0n)
72
- throw new Error("Initial registration requires nonce zero");
73
- if (options.expectedNonce !== void 0 && uint64(options.expectedNonce, "expectedNonce") !== nonce)
74
- throw new Error("Registry nonce mismatch");
75
- if (context.now !== void 0 && deadline < uint64(context.now, "now"))
76
- throw new Error("Registration expired");
77
- return { registryAddress, registration: { ownerAddress, keys, nonce, deadline } };
75
+ function known(v, keys, name = "params") {
76
+ if (!v || typeof v !== "object" || Array.isArray(v) || Object.keys(v).some((k) => !keys.includes(k)))
77
+ throw new Error(`${name} has unsupported fields`);
78
78
  }
79
- function registrationTypedData(context, registration, options = {}) {
80
- const { registryAddress, registration: r } = normalize(context, registration, options);
81
- const primaryType = options.update ? "UpdateReceivingKeys" : "RegisterReceivingKeys";
82
- return {
83
- domain: {
84
- name: "InvisiAddressRegistry",
85
- version: "1",
86
- chainId: context.chainId,
87
- verifyingContract: registryAddress
88
- },
89
- primaryType,
90
- types: {
91
- [primaryType]: [
92
- { name: "walletIdentity", type: "bytes32" },
93
- { name: "viewingPublicKey", type: "bytes" },
94
- { name: "clueKey", type: "bytes32" },
95
- { name: "nonce", type: "uint64" },
96
- { name: "deadline", type: "uint64" }
97
- ]
98
- },
99
- message: {
100
- walletIdentity: toBeHex(BigInt(r.ownerAddress), 32),
101
- ...r.keys,
102
- nonce: r.nonce,
103
- deadline: r.deadline
104
- }
105
- };
79
+ function notes(v, name) {
80
+ const r = dense(v, name).map((v2) => word(field(v2, name)));
81
+ if (r.some((v2) => BigInt(v2) === 0n) || new Set(r).size !== r.length)
82
+ throw new Error(`duplicate or zero ${name}`);
83
+ return r;
106
84
  }
107
- function registrationDigest(context, registration, options = {}) {
108
- const typed = registrationTypedData(context, registration, options);
109
- return TypedDataEncoder.hash(typed.domain, typed.types, typed.message);
85
+ function envelope(v, length, version, maximum = length) {
86
+ if (!isHexString(v) || !/^0x(?:[a-fA-F0-9]{2})*$/.test(v) || dataLength(v) < length || dataLength(v) > maximum)
87
+ throw new Error(`ciphertext must be ${length} bytes`);
88
+ if (version !== void 0 && Number.parseInt(v.slice(2, 4), 16) !== version)
89
+ throw new Error("unsupported ciphertext envelope version");
90
+ return v;
110
91
  }
111
- function buildRegistrationCalldata(context, registration, signature, options = {}) {
112
- const normalized = normalize(context, registration, options);
113
- if (typeof signature !== "string" || !isHexString(signature) || signature.length % 2 !== 0)
114
- throw new Error("Registration signature must be hexadecimal bytes");
92
+ function normalizeContext(c) {
115
93
  return {
116
- to: normalized.registryAddress,
117
- value: 0n,
118
- callData: new Interface(invisiAddressRegistryAbi).encodeFunctionData(
119
- options.update ? "updateKeys" : "registerKeys",
120
- [
121
- {
122
- // The registry wire schema retains its deployed EIP-712/ABI field name.
123
- walletIdentity: toBeHex(BigInt(normalized.registration.ownerAddress), 32),
124
- keys: normalized.registration.keys,
125
- nonce: normalized.registration.nonce,
126
- deadline: normalized.registration.deadline
127
- },
128
- signature
129
- ]
130
- )
94
+ ...c,
95
+ chainId: uint(c.chainId, 64, "chainId"),
96
+ protocolAddress: getAddress(c.protocolAddress),
97
+ tokenAddress: getAddress(c.tokenAddress)
131
98
  };
132
99
  }
133
- function createRegistryReceivingKeyLookup(client, registryAddress) {
134
- const address = getAddress(registryAddress);
135
- if (BigInt(address) === 0n) throw new Error("Registry address must be nonzero");
136
- return async (ownerAddress) => {
137
- const identity = decodeAddress(
138
- ReceivingAddress.fromIdentity(ownerAddress).toString()
139
- ).ownerAddress;
140
- const [keys, nonce] = await new Contract(
141
- address,
142
- invisiAddressRegistryAbi,
143
- client
144
- ).getFunction("receivingKeysOf")(toBeHex(BigInt(identity), 32));
145
- uint64(nonce, "registry nonce");
146
- const normalized = normalizeKeys(identity, keys);
147
- return {
148
- viewingPublicKey: getBytes(normalized.viewingPublicKey),
149
- clueKey: getBytes(normalized.clueKey)
150
- };
151
- };
100
+ function checkDeadline(c, value) {
101
+ const n = uint(value, 64, "deadline");
102
+ if (c.now !== void 0 && n < uint(c.now, 64, "now")) throw new Error("deadline has expired");
103
+ return n;
152
104
  }
153
-
154
- // src/RegistrationOperation.ts
155
- function check(ok, message) {
156
- if (!ok) {
157
- throw new Error(message);
158
- }
105
+ function normalizeSources(sources, inputCount, capacity) {
106
+ dense(sources, "sources");
107
+ if (inputCount < 0 || inputCount > capacity || sources.length > inputCount || (inputCount === 0 ? sources.length !== 0 : sources.length === 0))
108
+ throw new Error("source count must be 1..inputCount, or empty for zero inputs");
109
+ let previous = 0n;
110
+ return sources.map((s) => {
111
+ known(s, ["treeId", "merkleRoot"], "source");
112
+ const treeId = uint(s.treeId, 64, "treeId"), merkleRoot = word(field(s.merkleRoot, "merkleRoot"));
113
+ if (treeId === 0n || treeId <= previous || BigInt(merkleRoot) === 0n)
114
+ throw new Error("sources must have positive roots and strictly ascending tree IDs");
115
+ previous = treeId;
116
+ return { treeId, merkleRoot };
117
+ });
159
118
  }
160
- var RegistrationOperation = class _RegistrationOperation {
161
- #registry;
162
- #runtime;
163
- #plan;
164
- #signature;
165
- #hash;
166
- #anchor;
167
- status;
168
- constructor(registry, runtime, plan, status, signature, hash, anchor) {
169
- this.#registry = registry;
170
- this.#runtime = { ...runtime };
171
- this.#plan = structuredClone(plan);
172
- this.status = status;
173
- this.#signature = signature;
174
- this.#hash = hash;
175
- this.#anchor = anchor ? { ...anchor } : void 0;
176
- Object.freeze(this);
119
+ var pad = (v, n) => [
120
+ ...v,
121
+ ...Array(n - v.length).fill(0n)
122
+ ];
123
+ function normalizeOpaqueDelivery(value, options = {}) {
124
+ known(value, ["ciphertext", "clue"], "delivery");
125
+ if (options.empty) {
126
+ if (value.ciphertext !== "0x" || value.clue !== "0x")
127
+ throw new Error("absent output requires an empty delivery");
128
+ return { ciphertext: "0x", clue: "0x" };
177
129
  }
178
- static async prepare(registry, options) {
179
- const runtime = { ...options };
180
- const update = options.update ?? false;
181
- check(typeof update === "boolean", "update must be boolean");
182
- await registry.validateDeployment(runtime.reader);
183
- const block = await runtime.reader.getBlock(await runtime.reader.getLatestBlockNumber());
184
- const context = { ...registry.context, now: block.timestamp };
185
- const nonce = await runtime.reader.getRegistryNonce(
186
- context,
187
- runtime.wallet.ownerAddress,
188
- block
189
- );
190
- check(
191
- typeof nonce === "bigint" && nonce >= 0n && nonce < (1n << 64n) - 1n,
192
- "invalid or exhausted registry nonce"
193
- );
194
- check(
195
- update ? nonce > 0n : nonce === 0n,
196
- update ? "wallet is not registered" : "wallet is already registered; prepare an update"
197
- );
198
- const address = ReceivingAddress2.parse(await registry.resolve(runtime.wallet.getAddress()));
199
- const keys = address.getReceivingKeys();
200
- const registration = {
201
- ownerAddress: runtime.wallet.ownerAddress,
202
- keys: {
203
- viewingPublicKey: hexlify2(keys.viewingPublicKey),
204
- clueKey: hexlify2(keys.clueKey)
205
- },
206
- nonce,
207
- deadline: options.deadline ?? block.timestamp + 300n
208
- };
209
- registrationTypedData(context, registration, { update, expectedNonce: nonce });
210
- check(
211
- (await runtime.reader.getBlock(block.number)).hash === block.hash,
212
- "canonical registry block changed while preparing"
213
- );
214
- return new _RegistrationOperation(
215
- registry,
216
- runtime,
217
- { id: crypto.randomUUID(), context, registration, update },
218
- "prepared"
219
- );
220
- }
221
- get id() {
222
- return this.#plan.id;
223
- }
224
- preview() {
225
- return {
226
- id: this.id,
227
- status: this.status,
228
- chainId: this.#plan.context.chainId,
229
- registryAddress: this.#plan.context.registryAddress,
230
- ...structuredClone(this.#plan.registration),
231
- update: this.#plan.update
232
- };
233
- }
234
- /** Export portable EIP-712 data for external signing. */
235
- async exportRegistrationAuthorization(options = {}) {
236
- check(this.status === "prepared", "registry operation is not awaiting authorization");
237
- options.signal?.throwIfAborted();
238
- await this.validateFreshness();
239
- options.signal?.throwIfAborted();
240
- return createSigningRequest(this.typedData());
241
- }
242
- /** Validate and attach an independently obtained EOA or ERC-1271 signature. */
243
- async withSignature(signature, options = {}) {
244
- check(this.status === "prepared", "registry operation is not awaiting authorization");
245
- options.signal?.throwIfAborted();
246
- await this.validateFreshness();
247
- options.signal?.throwIfAborted();
248
- check(
249
- /^0x(?:[a-fA-F0-9]{2})+$/.test(signature),
250
- "registry signature must be nonempty hexadecimal bytes"
251
- );
252
- const p = this.#plan;
253
- const owner = p.registration.ownerAddress;
254
- let valid = false;
255
- try {
256
- const bytes = getBytes2(signature);
257
- const typed = this.typedData();
258
- valid = bytes.length === 65 && (bytes[64] === 27 || bytes[64] === 28) && BigInt(verifyTypedData(typed.domain, typed.types, typed.message, signature)) === BigInt(owner);
259
- } catch {
260
- valid = false;
261
- }
262
- if (!valid) {
263
- const code = await this.#runtime.reader.getCode(owner);
264
- check(/^0x(?:[a-fA-F0-9]{2})*$/.test(code), "invalid owner contract code");
265
- check(code !== "0x", "invalid EOA registry signature");
266
- const verify = this.#runtime.reader.isValidRegistrationSignature;
267
- check(verify, "ERC-1271 registry signature verification capability is missing");
268
- check(
269
- await verify.call(
270
- this.#runtime.reader,
271
- owner,
272
- registrationDigest(p.context, p.registration, { update: p.update }),
273
- signature
274
- ),
275
- "invalid ERC-1271 registry signature"
276
- );
277
- }
278
- options.signal?.throwIfAborted();
279
- return new _RegistrationOperation(
280
- this.#registry,
281
- this.#runtime,
282
- this.#plan,
283
- "ready",
284
- signature
285
- );
286
- }
287
- /** Empty signatures require the transaction sender to be the wallet owner. */
288
- async authorizeDirect() {
289
- check(this.status === "prepared", "registry operation is not awaiting authorization");
290
- await this.validateFreshness();
291
- return new _RegistrationOperation(this.#registry, this.#runtime, this.#plan, "ready", "0x");
292
- }
293
- typedData() {
294
- const p = this.#plan;
295
- return registrationTypedData(p.context, p.registration, {
296
- update: p.update,
297
- expectedNonce: p.registration.nonce
298
- });
299
- }
300
- call() {
301
- check(this.#signature !== void 0, "registry signature is missing");
302
- const p = this.#plan;
303
- const result = buildRegistrationCalldata(p.context, p.registration, this.#signature, {
304
- update: p.update,
305
- expectedNonce: p.registration.nonce
306
- });
307
- return { to: result.to, data: result.callData, value: result.value };
308
- }
309
- async exportTransaction() {
310
- check(
311
- this.#signature !== void 0 && this.status !== "prepared",
312
- "registry operation is not ready"
313
- );
314
- await this.validateFreshness();
315
- return this.call();
316
- }
317
- async validateFreshness() {
318
- await this.#registry.validateDeployment(this.#runtime.reader);
319
- const block = await this.#runtime.reader.getBlock(
320
- await this.#runtime.reader.getLatestBlockNumber()
321
- );
322
- const p = this.#plan;
323
- check(block.timestamp <= p.registration.deadline, "registry operation has expired");
324
- check(
325
- await this.#runtime.reader.getRegistryNonce(
326
- p.context,
327
- p.registration.ownerAddress,
328
- block
329
- ) === p.registration.nonce,
330
- "registry nonce changed; prepare again"
331
- );
332
- check(
333
- (await this.#runtime.reader.getBlock(block.number)).hash === block.hash,
334
- "canonical registry block changed during authorization"
335
- );
336
- }
337
- /** Associate a signed/broadcast transaction; ethers owns sending and receipt waiting. */
338
- trackTransaction(transactionHash) {
339
- check(/^0x[a-fA-F0-9]{64}$/.test(transactionHash), "invalid registry transaction hash");
340
- check(
341
- this.#signature !== void 0 && ["ready", "uncertain"].includes(this.status),
342
- "registry operation is not ready"
343
- );
344
- return new _RegistrationOperation(
345
- this.#registry,
346
- this.#runtime,
347
- this.#plan,
348
- "uncertain",
349
- this.#signature,
350
- transactionHash
351
- );
352
- }
353
- async reconcile(options = {}) {
354
- options.signal?.throwIfAborted();
355
- const { reader } = this.#runtime;
356
- const p = this.#plan;
357
- await this.#registry.validateDeployment(reader);
358
- if (this.#anchor && (await reader.getBlock(this.#anchor.number)).hash !== this.#anchor.hash) {
359
- return new _RegistrationOperation(
360
- this.#registry,
361
- this.#runtime,
362
- p,
363
- "reorged",
364
- this.#signature,
365
- this.#hash
366
- );
367
- }
368
- const hash = options.transactionHash ?? this.#hash;
369
- const receipt = hash ? await reader.getReceipt(hash) : null;
370
- if (receipt) {
371
- check(
372
- receipt.transactionHash === hash && (await reader.getBlock(receipt.block.number)).hash === receipt.block.hash,
373
- "registry receipt is not canonical"
374
- );
375
- const transaction = await reader.getTransaction(hash);
376
- const call = this.call();
377
- check(
378
- transaction && transaction.transactionHash === hash && transaction.to.toLowerCase() === call.to.toLowerCase() && transaction.data.toLowerCase() === call.data.toLowerCase() && transaction.value === 0n && (this.#signature !== "0x" || BigInt(transaction.from) === BigInt(p.registration.ownerAddress)),
379
- "registry transaction differs from frozen request"
380
- );
381
- check(
382
- receipt.status === "success" || receipt.status === "reverted",
383
- "registry execution status is missing"
384
- );
385
- if (receipt.status === "success") {
386
- const found = receipt.logs.some((log) => {
387
- if (log.address.toLowerCase() !== p.context.registryAddress.toLowerCase() || log.transactionHash !== hash) {
388
- return false;
389
- }
390
- try {
391
- const event = ((decoded) => ({
392
- eventName: decoded.name,
393
- args: decoded.args.toObject()
394
- }))(
395
- new Interface2(invisiAddressRegistryAbi).parseLog({
396
- data: log.data,
397
- topics: log.topics
398
- })
399
- );
400
- return event.eventName === "ReceivingKeysSet" && BigInt(event.args.walletIdentity) === BigInt(p.registration.ownerAddress) && event.args.viewingPublicKey.toLowerCase() === p.registration.keys.viewingPublicKey.toLowerCase() && event.args.clueKey.toLowerCase() === p.registration.keys.clueKey.toLowerCase() && event.args.nextNonce === p.registration.nonce + 1n;
401
- } catch {
402
- return false;
403
- }
404
- });
405
- check(found, "registry receipt has no matching key update event");
406
- check(
407
- (await reader.getBlock(receipt.block.number)).hash === receipt.block.hash,
408
- "registry block changed during confirmation"
409
- );
410
- options.signal?.throwIfAborted();
411
- return new _RegistrationOperation(
412
- this.#registry,
413
- this.#runtime,
414
- p,
415
- "confirmed",
416
- this.#signature,
417
- hash,
418
- receipt.block
419
- );
420
- }
421
- }
422
- const head = await reader.getBlock(await reader.getLatestBlockNumber());
423
- const nonce = await reader.getRegistryNonce(p.context, p.registration.ownerAddress, head);
424
- check(
425
- (await reader.getBlock(head.number)).hash === head.hash,
426
- "canonical registry block changed during reconciliation"
427
- );
428
- return new _RegistrationOperation(
429
- this.#registry,
430
- this.#runtime,
431
- p,
432
- head.timestamp > p.registration.deadline ? "expired" : nonce !== p.registration.nonce ? "failed" : "uncertain",
433
- this.#signature,
434
- hash
435
- );
436
- }
437
- toJSON() {
438
- return {
439
- id: this.id,
440
- kind: this.#plan.update ? "update-receiving-keys" : "register-receiving-keys",
441
- status: this.status
442
- };
443
- }
444
- [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
445
- return this.toJSON();
446
- }
447
- };
448
-
449
- // src/AddressRegistry.ts
450
- var AddressRegistry = class {
451
- context;
452
- #lookup;
453
- expectedRuntimeCodeHash;
454
- constructor(context, options = {}) {
455
- if (typeof context.chainId !== "bigint" || context.chainId < 0n || context.chainId >= 1n << 64n || BigInt(getAddress2(context.registryAddress)) === 0n) {
456
- throw new Error("invalid registry context");
457
- }
458
- this.context = Object.freeze({
459
- ...context,
460
- registryAddress: getAddress2(context.registryAddress)
461
- });
462
- this.#lookup = options.lookup;
463
- this.expectedRuntimeCodeHash = options.expectedRuntimeCodeHash?.toLowerCase();
464
- if (this.expectedRuntimeCodeHash && !/^0x[0-9a-f]{64}$/.test(this.expectedRuntimeCodeHash)) {
465
- throw new Error("invalid registry runtime profile");
466
- }
467
- Object.freeze(this);
468
- }
469
- async validateDeployment(reader) {
470
- if (!this.expectedRuntimeCodeHash) {
471
- throw new MissingCapabilityError("trusted registry deployment profile");
472
- }
473
- if (await reader.getChainId() !== this.context.chainId) {
474
- throw new Error("registry chain mismatch");
475
- }
476
- const code = await reader.getCode(this.context.registryAddress);
477
- if (!/^0x(?:[0-9a-fA-F]{2})+$/.test(code) || keccak256(code) !== this.expectedRuntimeCodeHash) {
478
- throw new Error("registry runtime profile mismatch");
479
- }
480
- }
481
- prepareRegistration(options) {
482
- return RegistrationOperation.prepare(this, options);
483
- }
484
- async resolve(address) {
485
- const receiving = ReceivingAddress3.parse(address);
486
- if (receiving.kind === "short" && !this.#lookup) {
487
- throw new MissingCapabilityError("registry reader");
488
- }
489
- return (await receiving.resolve(this.#lookup)).toString();
490
- }
491
- typedData(registration, options) {
492
- return registrationTypedData(this.context, registration, options);
493
- }
494
- digest(registration, options) {
495
- return registrationDigest(this.context, registration, options);
496
- }
497
- buildCall(registration, signature, options) {
498
- return buildRegistrationCalldata(this.context, registration, signature, options);
499
- }
500
- };
501
-
502
- // src/fee.ts
503
- import { getAddress as getAddress4 } from "ethers";
504
-
505
- // src/validation.ts
506
- import { MAX_CIPHERTEXT_BYTES } from "@invisi-labs/core";
507
- import {
508
- AbiCoder,
509
- dataLength,
510
- getAddress as getAddress3,
511
- isHexString as isHexString2,
512
- keccak256 as keccak2562,
513
- ParamType,
514
- toBeHex as toBeHex2
515
- } from "ethers";
516
- var FIELD_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
517
- var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
518
- var ZERO_WORD = toBeHex2(0n, 32);
519
- var EMPTY_PERMIT = {
520
- deadline: 0n,
521
- v: 0,
522
- r: ZERO_WORD,
523
- s: ZERO_WORD
524
- };
525
- var EMPTY_FUNDING = {
526
- payer: ZERO_ADDRESS,
527
- amount: 0n,
528
- sigNonce: 0n,
529
- deadline: 0n,
530
- signature: "0x",
531
- permit: EMPTY_PERMIT
532
- };
533
- function uint(v, bits, name = "value") {
534
- if (typeof v !== "bigint" && typeof v !== "number" && typeof v !== "string" || typeof v === "number" && !Number.isSafeInteger(v))
535
- throw new Error(`${name} must be an integer`);
536
- const n = BigInt(v);
537
- if (n < 0n || n >= 1n << BigInt(bits)) throw new Error(`${name} exceeds u${bits}`);
538
- return n;
539
- }
540
- function field(v, name = "field") {
541
- const n = uint(v, 256, name);
542
- if (n >= FIELD_MODULUS) throw new Error(`${name} is noncanonical field`);
543
- return n;
544
- }
545
- var word = (v) => toBeHex2(typeof v === "bigint" ? v : BigInt(v), 32);
546
- function dense(v, name) {
547
- if (!Array.isArray(v)) throw new Error(`${name} must be array`);
548
- for (let i = 0; i < v.length; i++)
549
- if (!Object.hasOwn(v, i) || v[i] === void 0 || v[i] === null)
550
- throw new Error(`${name} has missing or sparse entries`);
551
- return v;
552
- }
553
- function known(v, keys, name = "params") {
554
- if (!v || typeof v !== "object" || Array.isArray(v) || Object.keys(v).some((k) => !keys.includes(k)))
555
- throw new Error(`${name} has unsupported fields`);
556
- }
557
- function notes(v, name) {
558
- const r = dense(v, name).map((v2) => word(field(v2, name)));
559
- if (r.some((v2) => BigInt(v2) === 0n) || new Set(r).size !== r.length)
560
- throw new Error(`duplicate or zero ${name}`);
561
- return r;
562
- }
563
- function envelope(v, length, version, maximum = length) {
564
- if (!isHexString2(v) || !/^0x(?:[a-fA-F0-9]{2})*$/.test(v) || dataLength(v) < length || dataLength(v) > maximum)
565
- throw new Error(`ciphertext must be ${length} bytes`);
566
- if (version !== void 0 && Number.parseInt(v.slice(2, 4), 16) !== version)
567
- throw new Error("unsupported ciphertext envelope version");
568
- return v;
569
- }
570
- function normalizeContext(c) {
571
- return {
572
- ...c,
573
- chainId: uint(c.chainId, 64, "chainId"),
574
- protocolAddress: getAddress3(c.protocolAddress),
575
- tokenAddress: getAddress3(c.tokenAddress)
576
- };
577
- }
578
- function checkDeadline(c, value) {
579
- const n = uint(value, 64, "deadline");
580
- if (c.now !== void 0 && n < uint(c.now, 64, "now")) throw new Error("deadline has expired");
581
- return n;
582
- }
583
- function normalizeSources(sources, inputCount, capacity) {
584
- dense(sources, "sources");
585
- if (inputCount < 0 || inputCount > capacity || sources.length > inputCount || (inputCount === 0 ? sources.length !== 0 : sources.length === 0))
586
- throw new Error("source count must be 1..inputCount, or empty for zero inputs");
587
- let previous = 0n;
588
- return sources.map((s) => {
589
- known(s, ["treeId", "merkleRoot"], "source");
590
- const treeId = uint(s.treeId, 64, "treeId"), merkleRoot = word(field(s.merkleRoot, "merkleRoot"));
591
- if (treeId === 0n || treeId <= previous || BigInt(merkleRoot) === 0n)
592
- throw new Error("sources must have positive roots and strictly ascending tree IDs");
593
- previous = treeId;
594
- return { treeId, merkleRoot };
595
- });
596
- }
597
- var pad = (v, n) => [
598
- ...v,
599
- ...Array(n - v.length).fill(0n)
600
- ];
601
- function normalizeOpaqueDelivery(value, options = {}) {
602
- known(value, ["ciphertext", "clue"], "delivery");
603
- if (options.empty) {
604
- if (value.ciphertext !== "0x" || value.clue !== "0x")
605
- throw new Error("absent output requires an empty delivery");
606
- return { ciphertext: "0x", clue: "0x" };
607
- }
608
- if (options.allowEmpty && value.ciphertext === "0x" && value.clue === "0x")
609
- return { ciphertext: "0x", clue: "0x" };
610
- const ciphertext = envelope(value.ciphertext, 1, void 0, MAX_CIPHERTEXT_BYTES);
611
- const clue = envelope(value.clue, 68);
612
- if (clue.slice(130, 132).toLowerCase() !== "18") throw new Error("clue precision must be 24");
613
- return { ciphertext, clue };
614
- }
615
- var deliveriesHash = (v) => keccak2562(
616
- AbiCoder.defaultAbiCoder().encode(
617
- [
618
- {
619
- type: "tuple[]",
620
- components: [
621
- { name: "ciphertext", type: "bytes" },
622
- { name: "clue", type: "bytes" }
623
- ]
130
+ if (options.allowEmpty && value.ciphertext === "0x" && value.clue === "0x")
131
+ return { ciphertext: "0x", clue: "0x" };
132
+ const ciphertext = envelope(value.ciphertext, 1, void 0, MAX_CIPHERTEXT_BYTES);
133
+ const clue = envelope(value.clue, 68);
134
+ if (clue.slice(130, 132).toLowerCase() !== "18") throw new Error("clue precision must be 24");
135
+ return { ciphertext, clue };
136
+ }
137
+ var deliveriesHash = (v) => keccak256(
138
+ AbiCoder.defaultAbiCoder().encode(
139
+ [
140
+ {
141
+ type: "tuple[]",
142
+ components: [
143
+ { name: "ciphertext", type: "bytes" },
144
+ { name: "clue", type: "bytes" }
145
+ ]
624
146
  }
625
147
  ].map((parameter) => ParamType.from(parameter)),
626
148
  [dense(v, "deliveries")]
@@ -628,34 +150,34 @@ var deliveriesHash = (v) => keccak2562(
628
150
  );
629
151
  function normalizeFunding(input = {}) {
630
152
  const f = { ...EMPTY_FUNDING, ...input };
631
- getAddress3(f.payer);
153
+ getAddress(f.payer);
632
154
  uint(f.amount, 128, "depositAmount");
633
155
  uint(f.sigNonce, 256, "sigNonce");
634
156
  uint(f.deadline, 256, "funding deadline");
635
- if (!isHexString2(f.signature) || !/^0x(?:[a-fA-F0-9]{2})*$/.test(f.signature))
157
+ if (!isHexString(f.signature) || !/^0x(?:[a-fA-F0-9]{2})*$/.test(f.signature))
636
158
  throw new Error("invalid signature bytes");
637
159
  const p = f.permit;
638
160
  uint(p.deadline, 256);
639
161
  uint(p.v, 8);
640
162
  for (const v of [p.r, p.s])
641
- if (!isHexString2(v) || dataLength(v) !== 32) throw new Error("permit r/s must be bytes32");
163
+ if (!isHexString(v) || dataLength(v) !== 32) throw new Error("permit r/s must be bytes32");
642
164
  if (f.amount === 0n !== (BigInt(f.payer) === 0n))
643
165
  throw new Error("deposit amount and payer must both be zero or positive");
644
166
  if (f.amount === 0n && (f.sigNonce !== 0n || f.deadline !== 0n || f.signature !== "0x" || p.deadline !== 0n || p.v !== 0 || BigInt(p.r) !== 0n || BigInt(p.s) !== 0n))
645
167
  throw new Error("zero funding must be entirely empty");
646
- return { ...f, payer: getAddress3(f.payer) };
168
+ return { ...f, payer: getAddress(f.payer) };
647
169
  }
648
170
  function validateFundingSubmission(f, caller, now) {
649
171
  if (f.signature !== "0x" && now !== void 0 && uint(now, 64, "now") > f.deadline)
650
172
  throw new Error("funding authorization has expired");
651
- if (f.signature === "0x" && (f.sigNonce !== 0n || f.deadline !== 0n || caller !== void 0 && f.amount > 0n && getAddress3(caller) !== f.payer))
173
+ if (f.signature === "0x" && (f.sigNonce !== 0n || f.deadline !== 0n || caller !== void 0 && f.amount > 0n && getAddress(caller) !== f.payer))
652
174
  throw new Error("direct funding requires payer caller and zero nonce/deadline");
653
175
  }
654
176
  var getEIP712Domain = (c) => ({
655
177
  name: "InvisibleProtocol",
656
178
  version: "1",
657
179
  chainId: uint(c.chainId, 64),
658
- verifyingContract: getAddress3(c.protocolAddress)
180
+ verifyingContract: getAddress(c.protocolAddress)
659
181
  });
660
182
  var fundingFields = [
661
183
  { name: "requestHash", type: "bytes32" },
@@ -665,1075 +187,6 @@ var fundingFields = [
665
187
  { name: "deadline", type: "uint256" }
666
188
  ];
667
189
 
668
- // src/fee.ts
669
- function normalizeFee(p) {
670
- const feeRecipient = getAddress4(p.feeRecipient ?? ZERO_ADDRESS);
671
- const feeAmount = uint(p.feeAmount ?? 0n, 128, "feeAmount");
672
- if (BigInt(feeRecipient) === 0n !== (feeAmount === 0n))
673
- throw new Error("invalid fee context");
674
- return { feeRecipient, feeAmount };
675
- }
676
-
677
- // src/credit-create.ts
678
- import {
679
- AbiCoder as AbiCoder2,
680
- dataLength as dataLength2,
681
- getAddress as getAddress5,
682
- Interface as Interface3,
683
- isHexString as isHexString3,
684
- keccak256 as keccak2563,
685
- ParamType as ParamType2
686
- } from "ethers";
687
- import { computeWalletAuthorizationDigest, walletAuthorizationTypedData } from "@invisi-labs/core";
688
- var creationTuple = [
689
- "(address token,(uint256 treeId,bytes32 merkleRoot)[] sources,bytes32[] nullifiers,bytes32 creditCommitment,bytes32 changeCommitment,address owner,uint256 expiry,uint256 deadline,address feeRecipient,uint256 feeAmount)"
690
- ][0];
691
- var CREATE_CREDIT_FIELDS = [
692
- "token",
693
- "sources",
694
- "nullifiers",
695
- "creditCommitment",
696
- "changeCommitment",
697
- "owner",
698
- "expiry",
699
- "deadline",
700
- "feeRecipient",
701
- "feeAmount"
702
- ];
703
- function validateCreateCreditRequest(c, p, cs, funding = {}) {
704
- known(p, CREATE_CREDIT_FIELDS);
705
- if (getAddress5(p.token) !== getAddress5(c.tokenAddress))
706
- throw new Error("request token mismatch");
707
- const ctx = normalizeContext(c), ns = notes(p.nullifiers, "nullifiers"), sources = normalizeSources(p.sources, ns.length, 10), creditCommitment = word(field(p.creditCommitment)), changeCommitment = word(field(p.changeCommitment)), owner = getAddress5(p.owner), expiry = uint(p.expiry, 64, "expiry"), deadline = checkDeadline(c, p.deadline), f = normalizeFunding(funding);
708
- if (BigInt(creditCommitment) === 0n || creditCommitment === changeCommitment)
709
- throw new Error("zero or duplicate commitment");
710
- if (BigInt(owner) === 0n || expiry === 0n || c.now !== void 0 && expiry <= c.now)
711
- throw new Error("owner must be nonzero and expiry future");
712
- if (dense(cs, "deliveries").length !== 3) throw new Error("deliveries count mismatch");
713
- const deliveries = cs.map(
714
- (v, i) => normalizeOpaqueDelivery(v, {
715
- empty: i === 2 && BigInt(changeCommitment) === 0n,
716
- allowEmpty: i === 2
717
- })
718
- );
719
- if (ns.length === 0 && f.amount === 0n)
720
- throw new Error("empty inputs require positive funding");
721
- return {
722
- ...ctx,
723
- ...normalizeFee(p),
724
- sources,
725
- nullifiers: ns,
726
- actualCount: ns.length,
727
- sourceCount: sources.length,
728
- creditCommitment,
729
- changeCommitment,
730
- owner,
731
- expiry,
732
- deadline,
733
- deliveries,
734
- funding: f
735
- };
736
- }
737
- function createCreditBaseInputs(c, p, cs, f = {}) {
738
- const r = validateCreateCreditRequest(c, p, cs, f), h = BigInt(deliveriesHash(cs));
739
- return [
740
- r.chainId,
741
- BigInt(r.protocolAddress),
742
- BigInt(r.tokenAddress),
743
- BigInt(r.owner),
744
- BigInt(r.actualCount),
745
- BigInt(r.sourceCount),
746
- ...pad(
747
- r.sources.map((s) => s.treeId),
748
- 10
749
- ),
750
- ...pad(
751
- r.sources.map((s) => BigInt(s.merkleRoot)),
752
- 10
753
- ),
754
- ...pad(r.nullifiers.map(BigInt), 10),
755
- BigInt(r.creditCommitment),
756
- BigInt(r.changeCommitment),
757
- h >> 128n,
758
- h & (1n << 128n) - 1n,
759
- r.expiry,
760
- r.deadline,
761
- r.funding.amount,
762
- BigInt(r.funding.payer),
763
- BigInt(r.feeRecipient),
764
- r.feeAmount
765
- ].map(word);
766
- }
767
- function createCreditPublicInputs(c, p, deliveries, funding = {}) {
768
- const base = createCreditBaseInputs(c, p, deliveries, funding);
769
- const digest = BigInt(
770
- computeWalletAuthorizationDigest({
771
- context: { chainId: c.chainId, protocolAddress: BigInt(c.protocolAddress) },
772
- operation: "create-credit",
773
- capacity: 10,
774
- words: base.map(BigInt)
775
- })
776
- );
777
- return [...base, word(digest >> 128n), word(digest & (1n << 128n) - 1n)];
778
- }
779
- var hashContext = {
780
- chainId: 0n,
781
- protocolAddress: ZERO_ADDRESS,
782
- tokenAddress: ZERO_ADDRESS
783
- };
784
- function createCreditRequestHash(p, cs, f = {}) {
785
- const r = validateCreateCreditRequest({ ...hashContext, tokenAddress: p.token }, p, cs, f);
786
- return keccak2563(
787
- AbiCoder2.defaultAbiCoder().encode(
788
- [
789
- { type: "bytes32" },
790
- creationTuple,
791
- { type: "bytes32" },
792
- { type: "address" },
793
- { type: "uint256" }
794
- ].map((parameter) => ParamType2.from(parameter)),
795
- [
796
- keccak2563(new TextEncoder().encode("InvisiCreateCredit")),
797
- { ...p, ...normalizeFee(p) },
798
- deliveriesHash(cs),
799
- r.funding.payer,
800
- r.funding.amount
801
- ]
802
- )
803
- );
804
- }
805
- function encodeCreateCreditTypedData(c, p, deliveries, funding = {}) {
806
- return walletAuthorizationTypedData({
807
- context: { chainId: c.chainId, protocolAddress: BigInt(c.protocolAddress) },
808
- operation: "create-credit",
809
- capacity: 10,
810
- words: createCreditBaseInputs(c, p, deliveries, funding).map(BigInt)
811
- });
812
- }
813
- function encodeCreateCreditFundingTypedData(c, p, cs, f) {
814
- const r = validateCreateCreditRequest(c, p, cs, f);
815
- if (r.funding.amount === 0n) throw new Error("funding authorization requires positive deposit");
816
- return {
817
- domain: getEIP712Domain(c),
818
- primaryType: "FundTransact",
819
- types: { FundTransact: fundingFields },
820
- message: {
821
- requestHash: createCreditRequestHash(p, cs, f),
822
- payer: r.funding.payer,
823
- amount: r.funding.amount,
824
- sigNonce: r.funding.sigNonce,
825
- deadline: r.funding.deadline
826
- }
827
- };
828
- }
829
- function buildCreateCreditCalldata(p) {
830
- const r = validateCreateCreditRequest(p.context, p.params, p.deliveries, p.funding);
831
- validateFundingSubmission(r.funding, p.caller, p.context.now);
832
- return {
833
- data: new Interface3(invisiProtocolAbi).encodeFunctionData("createCredit", [
834
- encodeCreditProof(p.proof),
835
- { ...p.params, ...normalizeFee(p.params) },
836
- p.deliveries,
837
- r.funding
838
- ]),
839
- approveAmount: r.funding.amount
840
- };
841
- }
842
- function encodeCreditProof(proof) {
843
- if (!isHexString3(proof) || !/^0x(?:[a-fA-F0-9]{2})*$/.test(proof) || dataLength2(proof) === 0)
844
- throw new Error("empty or malformed Credit proof");
845
- return AbiCoder2.defaultAbiCoder().encode(
846
- ["uint8", "bytes"].map((parameter) => ParamType2.from(parameter)),
847
- [0, proof]
848
- );
849
- }
850
-
851
- // src/credit-lifecycle.ts
852
- import { AbiCoder as AbiCoder3, getAddress as getAddress6, Interface as Interface4, keccak256 as keccak2564, ParamType as ParamType3 } from "ethers";
853
- import {
854
- creditPeriods,
855
- computeWalletAuthorizationDigest as computeWalletAuthorizationDigest2,
856
- walletAuthorizationTypedData as walletAuthorizationTypedData2
857
- } from "@invisi-labs/core";
858
- var CREDIT_DRAW_CAPACITIES = [1, 12, 50, 120];
859
- function selectCreditDrawCircuit(count) {
860
- if (!Number.isInteger(count) || count < 1 || count > 120)
861
- throw new Error("draw requires 1..120 credits");
862
- const maxSize = CREDIT_DRAW_CAPACITIES.find((n) => count <= n);
863
- return {
864
- name: `credit_draw_slots${maxSize}`,
865
- maxSize,
866
- publicInputCount: 6 * maxSize + 14,
867
- totalBits: 126 + Math.ceil(Math.log2(maxSize))
868
- };
869
- }
870
- function validateCreditDrawRequest(c, p, cs) {
871
- known(p, [
872
- "token",
873
- "sources",
874
- "nullifiers",
875
- "remainderCommitments",
876
- "expiries",
877
- "recoveryAmounts",
878
- "spender",
879
- "totalAmount",
880
- "deadline"
881
- ]);
882
- if (getAddress6(p.token) !== getAddress6(c.tokenAddress))
883
- throw new Error("request token mismatch");
884
- const ctx = normalizeContext(c), ns = notes(p.nullifiers, "nullifiers"), profile = selectCreditDrawCircuit(ns.length), sources = normalizeSources(p.sources, ns.length, profile.maxSize);
885
- for (const [key, v] of Object.entries({
886
- remainderCommitments: p.remainderCommitments,
887
- expiries: p.expiries,
888
- recoveryAmounts: p.recoveryAmounts
889
- }))
890
- if (dense(v, key).length !== ns.length) throw new Error(`${key} count mismatch`);
891
- const remainderCommitments = p.remainderCommitments.map((v) => word(field(v))), recoveryAmounts = p.recoveryAmounts.map((v) => word(field(v))), expiries = p.expiries.map((v) => uint(v, 64, "expiry")), positives = remainderCommitments.filter((v) => BigInt(v) !== 0n);
892
- if (new Set(positives).size !== positives.length)
893
- throw new Error("duplicate remainder commitment");
894
- for (let i = 0; i < ns.length; i++) {
895
- if (expiries[i] === 0n || c.now !== void 0 && expiries[i] <= c.now)
896
- throw new Error("expiry must be future");
897
- if (BigInt(remainderCommitments[i]) === 0n && BigInt(recoveryAmounts[i]) !== 0n)
898
- throw new Error("full draw requires zero recovery amount");
899
- }
900
- const spender = getAddress6(p.spender), totalAmount = uint(p.totalAmount, profile.totalBits, "totalAmount");
901
- if (BigInt(spender) === 0n || totalAmount === 0n)
902
- throw new Error("spender and total amount must be positive");
903
- if (dense(cs, "deliveries").length !== 2 * ns.length)
904
- throw new Error("deliveries count mismatch");
905
- const deliveries = cs.map(
906
- (v, i) => BigInt(remainderCommitments[Math.floor(i / 2)]) === 0n ? normalizeOpaqueDelivery(v, { empty: true }) : normalizeOpaqueDelivery(v, {})
907
- );
908
- return {
909
- ...ctx,
910
- sources,
911
- nullifiers: ns,
912
- remainderCommitments,
913
- recoveryAmounts,
914
- expiries,
915
- spender,
916
- totalAmount,
917
- deadline: checkDeadline(c, p.deadline),
918
- deliveries,
919
- profile,
920
- periods: creditPeriods(c.timestamp)
921
- };
922
- }
923
- function creditDrawBaseInputs(c, p, cs) {
924
- const r = validateCreditDrawRequest(c, p, cs), n = r.profile.maxSize, h = BigInt(deliveriesHash(cs));
925
- return [
926
- r.chainId,
927
- BigInt(r.protocolAddress),
928
- BigInt(r.tokenAddress),
929
- BigInt(r.spender),
930
- BigInt(r.nullifiers.length),
931
- BigInt(r.sources.length),
932
- ...pad(
933
- r.sources.map((s) => s.treeId),
934
- n
935
- ),
936
- ...pad(
937
- r.sources.map((s) => BigInt(s.merkleRoot)),
938
- n
939
- ),
940
- ...pad(r.nullifiers.map(BigInt), n),
941
- ...pad(r.remainderCommitments.map(BigInt), n),
942
- ...pad(r.expiries, n),
943
- r.totalAmount,
944
- r.deadline,
945
- h >> 128n,
946
- h & (1n << 128n) - 1n,
947
- ...pad(r.recoveryAmounts.map(BigInt), n),
948
- r.periods.currentDay,
949
- r.periods.currentMonth
950
- ].map(word);
951
- }
952
- function validateCancelCreditRequest(c, p) {
953
- known(p, [
954
- "token",
955
- "source",
956
- "nullifier",
957
- "owner",
958
- "amount",
959
- "deadline",
960
- "feeRecipient",
961
- "feeAmount"
962
- ]);
963
- if (getAddress6(p.token) !== getAddress6(c.tokenAddress))
964
- throw new Error("request token mismatch");
965
- const ctx = normalizeContext(c), source = normalizeSources([p.source], 1, 1)[0], nullifier = notes([p.nullifier], "nullifier")[0], owner = getAddress6(p.owner), amount = uint(p.amount, 126, "amount");
966
- if (BigInt(owner) === 0n || amount === 0n) throw new Error("owner and amount must be positive");
967
- const fee = normalizeFee(p);
968
- if (fee.feeAmount >= amount) throw new Error("fee must leave a positive owner refund");
969
- return {
970
- ...ctx,
971
- ...fee,
972
- source,
973
- nullifier,
974
- owner,
975
- amount,
976
- deadline: checkDeadline(c, p.deadline)
977
- };
978
- }
979
- function cancelCreditBaseInputs(c, p) {
980
- const r = validateCancelCreditRequest(c, p);
981
- return [
982
- r.chainId,
983
- BigInt(r.protocolAddress),
984
- BigInt(r.tokenAddress),
985
- BigInt(r.owner),
986
- r.source.treeId,
987
- BigInt(r.source.merkleRoot),
988
- BigInt(r.nullifier),
989
- r.amount,
990
- r.deadline,
991
- BigInt(r.feeRecipient),
992
- r.feeAmount
993
- ].map(word);
994
- }
995
- function validateTopUpCreditRequest(c, p, cs, funding = {}) {
996
- known(p, ["token", "creation", "creditSource", "creditNullifier", "previousExpiry"]);
997
- const creation = validateCreateCreditRequest(c, p.creation, cs, funding), creditSource = normalizeSources([p.creditSource], 1, 1)[0], creditNullifier = notes([p.creditNullifier], "creditNullifier")[0], previousExpiry = uint(p.previousExpiry, 64, "previousExpiry");
998
- if (previousExpiry === 0n || c.now !== void 0 && previousExpiry <= c.now)
999
- throw new Error("previous Credit has expired");
1000
- if (creation.expiry < previousExpiry) throw new Error("expiry must not decrease");
1001
- if (creation.nullifiers.includes(creditNullifier))
1002
- throw new Error("duplicate credit and ordinary nullifier");
1003
- return { creation, creditSource, creditNullifier, previousExpiry };
1004
- }
1005
- function topUpCreditBaseInputs(c, p, cs, f = {}) {
1006
- const r = validateTopUpCreditRequest(c, p, cs, f);
1007
- return [
1008
- ...createCreditBaseInputs(c, p.creation, cs, f),
1009
- word(r.creditSource.treeId),
1010
- r.creditSource.merkleRoot,
1011
- r.creditNullifier,
1012
- word(r.previousExpiry)
1013
- ];
1014
- }
1015
- var cancelTuple = [
1016
- "(address token,(uint256 treeId,bytes32 merkleRoot) source,bytes32 nullifier,address owner,uint256 amount,uint256 deadline,address feeRecipient,uint256 feeAmount)"
1017
- ][0];
1018
- var topTuple = [
1019
- "((address token,(uint256 treeId,bytes32 merkleRoot)[] sources,bytes32[] nullifiers,bytes32 creditCommitment,bytes32 changeCommitment,address owner,uint256 expiry,uint256 deadline,address feeRecipient,uint256 feeAmount) creation,(uint256 treeId,bytes32 merkleRoot) creditSource,bytes32 creditNullifier,uint256 previousExpiry)"
1020
- ][0];
1021
- var hashContext2 = {
1022
- chainId: 0n,
1023
- protocolAddress: ZERO_ADDRESS,
1024
- tokenAddress: ZERO_ADDRESS
1025
- };
1026
- function cancelCreditRequestHash(p) {
1027
- validateCancelCreditRequest({ ...hashContext2, tokenAddress: p.token }, p);
1028
- return keccak2564(
1029
- AbiCoder3.defaultAbiCoder().encode(
1030
- [{ type: "bytes32" }, cancelTuple].map((parameter) => ParamType3.from(parameter)),
1031
- [
1032
- keccak2564(new TextEncoder().encode("InvisiCancelCredit")),
1033
- { ...p, ...normalizeFee(p) }
1034
- ]
1035
- )
1036
- );
1037
- }
1038
- function topUpCreditRequestHash(p, cs, f = {}) {
1039
- const r = validateTopUpCreditRequest(
1040
- { ...hashContext2, tokenAddress: p.creation.token },
1041
- p,
1042
- cs,
1043
- f
1044
- );
1045
- return keccak2564(
1046
- AbiCoder3.defaultAbiCoder().encode(
1047
- [
1048
- { type: "bytes32" },
1049
- topTuple,
1050
- { type: "bytes32" },
1051
- { type: "address" },
1052
- { type: "uint256" }
1053
- ].map((parameter) => ParamType3.from(parameter)),
1054
- [
1055
- keccak2564(new TextEncoder().encode("InvisiTopUpCredit")),
1056
- { ...p, creation: { ...p.creation, ...normalizeFee(p.creation) } },
1057
- deliveriesHash(cs),
1058
- r.creation.funding.payer,
1059
- r.creation.funding.amount
1060
- ]
1061
- )
1062
- );
1063
- }
1064
- function authorize(c, operation, capacity, words) {
1065
- return walletAuthorizationTypedData2({
1066
- context: { chainId: c.chainId, protocolAddress: BigInt(c.protocolAddress) },
1067
- operation,
1068
- capacity,
1069
- words: words.map(BigInt)
1070
- });
1071
- }
1072
- function appendDigest(c, operation, capacity, words) {
1073
- const digest = BigInt(
1074
- computeWalletAuthorizationDigest2({
1075
- context: { chainId: c.chainId, protocolAddress: BigInt(c.protocolAddress) },
1076
- operation,
1077
- capacity,
1078
- words: words.map(BigInt)
1079
- })
1080
- );
1081
- return [...words, word(digest >> 128n), word(digest & (1n << 128n) - 1n)];
1082
- }
1083
- function encodeCancelCreditTypedData(c, p) {
1084
- return authorize(c, "cancel-credit", 1, cancelCreditBaseInputs(c, p));
1085
- }
1086
- function encodeTopUpCreditTypedData(c, p, deliveries, funding = {}) {
1087
- return authorize(c, "top-up-credit", 10, topUpCreditBaseInputs(c, p, deliveries, funding));
1088
- }
1089
- function encodeCreditDrawTypedData(c, p, deliveries) {
1090
- return authorize(
1091
- c,
1092
- "credit-draw",
1093
- selectCreditDrawCircuit(p.nullifiers.length).maxSize,
1094
- creditDrawBaseInputs(c, p, deliveries)
1095
- );
1096
- }
1097
- function creditDrawPublicInputs(c, p, deliveries) {
1098
- return appendDigest(
1099
- c,
1100
- "credit-draw",
1101
- selectCreditDrawCircuit(p.nullifiers.length).maxSize,
1102
- creditDrawBaseInputs(c, p, deliveries)
1103
- );
1104
- }
1105
- function cancelCreditPublicInputs(c, p) {
1106
- return appendDigest(c, "cancel-credit", 1, cancelCreditBaseInputs(c, p));
1107
- }
1108
- function topUpCreditPublicInputs(c, p, deliveries, funding = {}) {
1109
- return appendDigest(c, "top-up-credit", 10, topUpCreditBaseInputs(c, p, deliveries, funding));
1110
- }
1111
- function encodeTopUpCreditFundingTypedData(c, p, cs, f) {
1112
- const r = validateTopUpCreditRequest(c, p, cs, f), funding = r.creation.funding;
1113
- if (funding.amount === 0n) throw new Error("funding authorization requires positive deposit");
1114
- return {
1115
- domain: getEIP712Domain(c),
1116
- primaryType: "FundTransact",
1117
- types: { FundTransact: fundingFields },
1118
- message: {
1119
- requestHash: topUpCreditRequestHash(p, cs, f),
1120
- payer: funding.payer,
1121
- amount: funding.amount,
1122
- sigNonce: funding.sigNonce,
1123
- deadline: funding.deadline
1124
- }
1125
- };
1126
- }
1127
- function buildCreditDrawCalldata(p) {
1128
- validateCreditDrawRequest(p.context, p.params, p.deliveries);
1129
- return {
1130
- data: new Interface4(invisiProtocolAbi).encodeFunctionData("creditDraw", [
1131
- encodeCreditProof(p.proof),
1132
- p.params,
1133
- p.deliveries
1134
- ]),
1135
- nullifiers: p.params.nullifiers,
1136
- totalAmount: p.params.totalAmount
1137
- };
1138
- }
1139
- function buildCancelCreditCalldata(p) {
1140
- validateCancelCreditRequest(p.context, p.params);
1141
- return {
1142
- data: new Interface4(invisiProtocolAbi).encodeFunctionData("cancelCredit", [
1143
- encodeCreditProof(p.proof),
1144
- { ...p.params, ...normalizeFee(p.params) }
1145
- ])
1146
- };
1147
- }
1148
- function buildTopUpCreditCalldata(p) {
1149
- const r = validateTopUpCreditRequest(p.context, p.params, p.deliveries, p.funding);
1150
- validateFundingSubmission(r.creation.funding, p.caller, p.context.now);
1151
- return {
1152
- data: new Interface4(invisiProtocolAbi).encodeFunctionData("topUpCredit", [
1153
- encodeCreditProof(p.proof),
1154
- { ...p.params, creation: { ...p.params.creation, ...normalizeFee(p.params.creation) } },
1155
- p.deliveries,
1156
- r.creation.funding
1157
- ]),
1158
- approveAmount: r.creation.funding.amount
1159
- };
1160
- }
1161
-
1162
- // src/funds.ts
1163
- import { AbiCoder as AbiCoder4, getAddress as getAddress7, Interface as Interface5, keccak256 as keccak2565, ParamType as ParamType4, TypedDataEncoder as TypedDataEncoder2 } from "ethers";
1164
- import { computeNoteCommitment, walletAuthorizationTypedData as walletAuthorizationTypedData3 } from "@invisi-labs/core";
1165
- var TRANSACT_CAPACITIES = [14, 56, 112, 168];
1166
- function selectTransactCapacity(inputs, outputs) {
1167
- if (!Number.isInteger(inputs) || !Number.isInteger(outputs) || inputs < 0 || outputs < 0 || inputs + outputs < 1 || inputs === 0 && outputs < 2 || inputs + outputs > 168)
1168
- throw new Error("unsupported transact shape");
1169
- return TRANSACT_CAPACITIES.find((n) => inputs + outputs <= n);
1170
- }
1171
- function normalizeTransactRequest(c, p, cs, funding = {}) {
1172
- const ctx = normalizeContext(c);
1173
- if (getAddress7(p.token) !== ctx.tokenAddress)
1174
- throw new Error("request token mismatch");
1175
- known(p, [
1176
- "token",
1177
- "sources",
1178
- "nullifiers",
1179
- "outputCommitments",
1180
- "withdrawalAddress",
1181
- "withdrawalAmount",
1182
- "deadline",
1183
- "feeRecipient",
1184
- "feeAmount"
1185
- ]);
1186
- const ns = notes(p.nullifiers, "nullifiers"), os = notes(p.outputCommitments, "outputCommitments"), capacity = selectTransactCapacity(ns.length, os.length);
1187
- const sources = normalizeSources(p.sources, ns.length, capacity), deliveries = dense(cs, "deliveries").map(
1188
- (v) => normalizeOpaqueDelivery(v, { allowEmpty: true })
1189
- );
1190
- if (deliveries.length !== os.length) throw new Error("deliveries count mismatch");
1191
- const withdrawalAmount = uint(p.withdrawalAmount, 128, "withdrawalAmount"), withdrawalAddress = getAddress7(p.withdrawalAddress), f = normalizeFunding(funding);
1192
- const feeAmount = uint(p.feeAmount ?? 0n, 128, "feeAmount");
1193
- const feeRecipient = getAddress7(
1194
- p.feeRecipient ?? "0x0000000000000000000000000000000000000000"
1195
- );
1196
- if (feeAmount === 0n !== (BigInt(feeRecipient) === 0n)) throw new Error("invalid fee");
1197
- if (withdrawalAmount === 0n !== (BigInt(withdrawalAddress) === 0n) || os.length === 0 && withdrawalAmount === 0n)
1198
- throw new Error("invalid withdrawal");
1199
- if (ns.length === 0 && (f.amount === 0n || withdrawalAmount !== 0n))
1200
- throw new Error("zero inputs require positive funding and zero withdrawal");
1201
- return {
1202
- ...ctx,
1203
- capacity,
1204
- sources,
1205
- nullifiers: ns,
1206
- outputCommitments: os,
1207
- inputCount: ns.length,
1208
- outputCount: os.length,
1209
- sourceCount: sources.length,
1210
- inputTreeIds: pad(
1211
- sources.map((s) => s.treeId),
1212
- capacity
1213
- ),
1214
- merkleRoots: pad(
1215
- sources.map((s) => BigInt(s.merkleRoot)),
1216
- capacity
1217
- ),
1218
- slotValues: pad([...ns, ...os].map(BigInt), capacity),
1219
- deliveries,
1220
- deliveriesHash: deliveriesHash(deliveries),
1221
- withdrawalAddress,
1222
- withdrawalAmount,
1223
- feeRecipient,
1224
- feeAmount,
1225
- deadline: checkDeadline(c, p.deadline),
1226
- depositAmount: f.amount,
1227
- depositPayer: f.payer,
1228
- funding: f
1229
- };
1230
- }
1231
- function transactPublicInputs(c, p, cs, f = {}) {
1232
- const r = normalizeTransactRequest(c, p, cs, f), h = BigInt(r.deliveriesHash);
1233
- return [
1234
- r.chainId,
1235
- BigInt(r.protocolAddress),
1236
- BigInt(r.tokenAddress),
1237
- BigInt(r.inputCount),
1238
- BigInt(r.outputCount),
1239
- BigInt(r.sourceCount),
1240
- ...r.inputTreeIds,
1241
- ...r.merkleRoots,
1242
- ...r.slotValues,
1243
- h >> 128n,
1244
- h & (1n << 128n) - 1n,
1245
- BigInt(r.withdrawalAddress),
1246
- r.withdrawalAmount,
1247
- r.deadline,
1248
- r.depositAmount,
1249
- BigInt(r.depositPayer),
1250
- BigInt(r.feeRecipient),
1251
- r.feeAmount
1252
- ].map(word);
1253
- }
1254
- function encodeTransactTypedData(c, p, deliveries, f = {}) {
1255
- const r = normalizeTransactRequest(c, p, deliveries, f);
1256
- return walletAuthorizationTypedData3({
1257
- context: { chainId: r.chainId, protocolAddress: BigInt(r.protocolAddress) },
1258
- operation: "transact",
1259
- capacity: r.capacity,
1260
- words: transactPublicInputs(c, p, deliveries, f).map(BigInt)
1261
- });
1262
- }
1263
- var singleTuple = [
1264
- "(address token,bytes32 merkleRoot,bytes32[] nullifiers,bytes32[] outputCommitments,uint256 inputTreeId,address withdrawalAddress,uint256 withdrawalAmount,uint256 deadline,address feeRecipient,uint256 feeAmount)"
1265
- ][0];
1266
- var crossTuple = [
1267
- "(address token,(uint256 treeId,bytes32 merkleRoot)[] sources,bytes32[] nullifiers,bytes32[] outputCommitments,address withdrawalAddress,uint256 withdrawalAmount,uint256 deadline,address feeRecipient,uint256 feeAmount)"
1268
- ][0];
1269
- function transactFundingRequestHash(proof, p, cs) {
1270
- const single = {
1271
- token: p.token,
1272
- merkleRoot: p.sources[0]?.merkleRoot ?? word(0n),
1273
- nullifiers: p.nullifiers,
1274
- outputCommitments: p.outputCommitments,
1275
- inputTreeId: p.sources[0]?.treeId ?? 0n,
1276
- withdrawalAddress: p.withdrawalAddress,
1277
- withdrawalAmount: p.withdrawalAmount,
1278
- deadline: p.deadline,
1279
- feeRecipient: p.feeRecipient ?? "0x0000000000000000000000000000000000000000",
1280
- feeAmount: p.feeAmount ?? 0n
1281
- };
1282
- return p.sources.length > 1 ? keccak2565(
1283
- AbiCoder4.defaultAbiCoder().encode(
1284
- [{ type: "bytes32" }, { type: "bytes32" }, crossTuple, { type: "bytes32" }].map(
1285
- (parameter) => ParamType4.from(parameter)
1286
- ),
1287
- [
1288
- keccak2565(new TextEncoder().encode("InvisiCrossTreeTransact")),
1289
- keccak2565(proof),
1290
- { ...p, feeRecipient: single.feeRecipient, feeAmount: single.feeAmount },
1291
- deliveriesHash(cs)
1292
- ]
1293
- )
1294
- ) : keccak2565(
1295
- AbiCoder4.defaultAbiCoder().encode(
1296
- [{ type: "bytes32" }, singleTuple, { type: "bytes32" }].map(
1297
- (parameter) => ParamType4.from(parameter)
1298
- ),
1299
- [keccak2565(proof), single, deliveriesHash(cs)]
1300
- )
1301
- );
1302
- }
1303
- function encodeTransactFundingTypedData(c, proof, p, cs, funding) {
1304
- normalizeTransactRequest(c, p, cs, funding);
1305
- const f = normalizeFunding(funding);
1306
- if (f.amount === 0n) throw new Error("funding authorization requires positive deposit");
1307
- return {
1308
- domain: getEIP712Domain(c),
1309
- primaryType: "FundTransact",
1310
- types: { FundTransact: fundingFields },
1311
- message: {
1312
- requestHash: transactFundingRequestHash(proof, p, cs),
1313
- payer: f.payer,
1314
- amount: f.amount,
1315
- sigNonce: f.sigNonce,
1316
- deadline: f.deadline
1317
- }
1318
- };
1319
- }
1320
- function buildTransactCalldata(p) {
1321
- const r = normalizeTransactRequest(p.context, p.params, p.deliveries, p.funding);
1322
- validateFundingSubmission(r.funding, p.caller, p.context.now);
1323
- const request = {
1324
- params: {
1325
- token: r.tokenAddress,
1326
- sources: r.sources,
1327
- nullifiers: r.nullifiers,
1328
- outputCommitments: r.outputCommitments,
1329
- withdrawalAddress: r.withdrawalAddress,
1330
- withdrawalAmount: r.withdrawalAmount,
1331
- deadline: r.deadline,
1332
- feeRecipient: r.feeRecipient,
1333
- feeAmount: r.feeAmount
1334
- },
1335
- deliveries: p.deliveries,
1336
- funding: r.funding
1337
- };
1338
- const abi = invisiProtocolAbi.filter(
1339
- (item) => item.type === "function" && item.name === "transact" && item.inputs.length === 2 && item.inputs[1]?.type === "tuple"
1340
- );
1341
- const data = new Interface5(abi).encodeFunctionData("transact", [p.proof, request]);
1342
- return { data, capacity: r.capacity, approveAmount: r.depositAmount };
1343
- }
1344
- function buildTransactAggregateCalldata(p) {
1345
- if (![4, 8, 12].includes(dense(p.requests, "requests").length))
1346
- throw new Error("aggregate requires 4, 8 or 12 requests");
1347
- const requests = p.requests.map((request) => {
1348
- const r = normalizeTransactRequest(
1349
- { ...p.context, tokenAddress: request.params.token },
1350
- request.params,
1351
- request.deliveries,
1352
- request.funding
1353
- );
1354
- if (r.capacity !== 14) throw new Error("aggregate supports only capacity 14");
1355
- validateFundingSubmission(r.funding, p.caller, p.context.now);
1356
- return {
1357
- ...request,
1358
- params: { ...request.params, feeRecipient: r.feeRecipient, feeAmount: r.feeAmount }
1359
- };
1360
- });
1361
- return {
1362
- data: new Interface5(
1363
- invisiProtocolAbi.filter(
1364
- (item) => item.type === "function" && item.name === "transact" && item.inputs[1]?.type === "tuple[]"
1365
- )
1366
- ).encodeFunctionData("transact", [p.proof, requests]),
1367
- capacity: 14
1368
- };
1369
- }
1370
- async function validateShieldRequest(p) {
1371
- normalizeContext(p.context);
1372
- if (getAddress7(p.params.token) !== getAddress7(p.context.tokenAddress))
1373
- throw new Error("request token mismatch");
1374
- known(p.params, ["token", "outputCommitments", "deadline", "feeRecipient", "feeAmount"]);
1375
- checkDeadline(p.context, p.params.deadline);
1376
- const outputs = dense(p.outputs, "outputs"), commitments = notes(p.params.outputCommitments, "outputCommitments"), cs = dense(p.deliveries, "deliveries"), f = normalizeFunding(p.funding);
1377
- if (outputs.length < 1 || outputs.length > 10 || commitments.length !== outputs.length || cs.length !== outputs.length)
1378
- throw new Error("shield requires 1..10 outputs and matching deliveries");
1379
- let total = 0n;
1380
- for (const [i, o] of outputs.entries()) {
1381
- known(o, ["identity", "amount"], "output");
1382
- uint(o.amount, 128, "amount");
1383
- if (o.amount === 0n) throw new Error("amount must be positive");
1384
- field(o.identity);
1385
- normalizeOpaqueDelivery(cs[i], { allowEmpty: true });
1386
- if (await computeNoteCommitment(
1387
- p.context.chainId,
1388
- o.identity,
1389
- BigInt(p.context.tokenAddress),
1390
- o.amount
1391
- ) !== commitments[i])
1392
- throw new Error("output commitment mismatch");
1393
- total += o.amount;
1394
- }
1395
- if (total + normalizeFee(p.params).feeAmount !== f.amount)
1396
- throw new Error("funding must equal output total plus fee");
1397
- return f;
1398
- }
1399
- function shieldFundingRequestHash(params, outputs, cs) {
1400
- const outputHash = keccak2565(
1401
- AbiCoder4.defaultAbiCoder().encode(
1402
- ["(bytes32 identity,uint256 amount)[]"].map((parameter) => ParamType4.from(parameter)),
1403
- [outputs]
1404
- )
1405
- );
1406
- return keccak2565(
1407
- AbiCoder4.defaultAbiCoder().encode(
1408
- [
1409
- "bytes32",
1410
- "(address token,bytes32[] outputCommitments,uint256 deadline,address feeRecipient,uint256 feeAmount)",
1411
- "bytes32",
1412
- "bytes32"
1413
- ].map((parameter) => ParamType4.from(parameter)),
1414
- [
1415
- keccak2565(new TextEncoder().encode("InvisiShield")),
1416
- { ...params, ...normalizeFee(params) },
1417
- outputHash,
1418
- deliveriesHash(cs)
1419
- ]
1420
- )
1421
- );
1422
- }
1423
- async function encodeShieldFundingTypedData(p) {
1424
- const f = await validateShieldRequest(p);
1425
- return {
1426
- domain: getEIP712Domain(p.context),
1427
- primaryType: "FundShield",
1428
- types: { FundShield: fundingFields },
1429
- message: {
1430
- requestHash: shieldFundingRequestHash(p.params, p.outputs, p.deliveries),
1431
- payer: f.payer,
1432
- amount: f.amount,
1433
- sigNonce: f.sigNonce,
1434
- deadline: f.deadline
1435
- }
1436
- };
1437
- }
1438
- async function buildShieldCalldata(p) {
1439
- const f = await validateShieldRequest(p);
1440
- validateFundingSubmission(f, p.caller, p.context.now);
1441
- return {
1442
- data: new Interface5(invisiProtocolAbi).encodeFunctionData("shield", [
1443
- { ...p.params, ...normalizeFee(p.params) },
1444
- p.outputs,
1445
- p.deliveries,
1446
- f
1447
- ]),
1448
- approveAmount: f.amount
1449
- };
1450
- }
1451
- function transactUserPublicInputs(c, p, cs, f = {}) {
1452
- const words = transactPublicInputs(c, p, cs, f), digest = BigInt(
1453
- ((typed) => TypedDataEncoder2.hash(
1454
- typed.domain,
1455
- Object.fromEntries(
1456
- Object.entries(typed.types).map(([name, fields]) => [
1457
- name,
1458
- [...fields]
1459
- ])
1460
- ),
1461
- typed.message
1462
- ))(encodeTransactTypedData(c, p, cs, f))
1463
- );
1464
- return [...words, word(digest >> 128n), word(digest & (1n << 128n) - 1n)];
1465
- }
1466
- function aggregateMemberRequestHash(c, r) {
1467
- const capacity = selectTransactCapacity(
1468
- r.params.nullifiers.length,
1469
- r.params.outputCommitments.length
1470
- );
1471
- if (capacity !== 14) throw new Error("aggregate supports only capacity 14");
1472
- return keccak2565(
1473
- AbiCoder4.defaultAbiCoder().encode(
1474
- ["bytes32", "uint256", "bytes32[]"].map((parameter) => ParamType4.from(parameter)),
1475
- [
1476
- keccak2565(new TextEncoder().encode("InvisiAtomicBatchMemberV1")),
1477
- BigInt(capacity),
1478
- transactPublicInputs(c, r.params, r.deliveries, r.funding)
1479
- ]
1480
- )
1481
- );
1482
- }
1483
- function aggregatePublicInputs(c, requests) {
1484
- if (![4, 8, 12].includes(dense(requests, "requests").length))
1485
- throw new Error("aggregate requires 4, 8 or 12 requests");
1486
- return requests.flatMap((r) => {
1487
- const context = { ...c, tokenAddress: r.params.token };
1488
- aggregateMemberRequestHash(context, r);
1489
- return [word(14n), ...transactPublicInputs(context, r.params, r.deliveries, r.funding)];
1490
- });
1491
- }
1492
- function encodeAggregateFundingTypedData(c, r) {
1493
- const f = normalizeFunding(r.funding);
1494
- if (f.amount === 0n) throw new Error("funding authorization requires positive deposit");
1495
- return {
1496
- domain: getEIP712Domain(c),
1497
- primaryType: "FundTransact",
1498
- types: { FundTransact: fundingFields },
1499
- message: {
1500
- requestHash: aggregateMemberRequestHash(c, r),
1501
- payer: f.payer,
1502
- amount: f.amount,
1503
- sigNonce: f.sigNonce,
1504
- deadline: f.deadline
1505
- }
1506
- };
1507
- }
1508
-
1509
- // src/Protocol.ts
1510
- import { Contract as Contract2, getAddress as getAddress8, keccak256 as keccak2567 } from "ethers";
1511
- import {
1512
- defineDeployment
1513
- } from "@invisi-labs/rpc";
1514
-
1515
- // src/TokenProtocol.ts
1516
- import { keccak256 as keccak2566 } from "ethers";
1517
- import { MissingCapabilityError as MissingCapabilityError2 } from "@invisi-labs/core";
1518
- var TokenProtocol = class {
1519
- context;
1520
- expectedRuntimeCodeHash;
1521
- constructor(context, options = {}) {
1522
- const normalized = normalizeContext(context);
1523
- if (BigInt(normalized.protocolAddress) === 0n || BigInt(normalized.tokenAddress) === 0n) {
1524
- throw new Error("deployment addresses must be nonzero");
1525
- }
1526
- if (options.expectedRuntimeCodeHash && !/^0x[0-9a-fA-F]{64}$/.test(options.expectedRuntimeCodeHash)) {
1527
- throw new Error("invalid runtime code hash");
1528
- }
1529
- this.expectedRuntimeCodeHash = options.expectedRuntimeCodeHash?.toLowerCase();
1530
- this.context = Object.freeze(normalized);
1531
- Object.freeze(this);
1532
- }
1533
- async validateDeployment(reader, options = {}) {
1534
- if ((options.requireRuntimeProfile ?? true) && !this.expectedRuntimeCodeHash) {
1535
- throw new MissingCapabilityError2("trusted deployment profile");
1536
- }
1537
- if (await reader.getChainId() !== this.context.chainId) {
1538
- throw new Error("deployment chain mismatch");
1539
- }
1540
- const code = await reader.getCode(this.context.protocolAddress);
1541
- if (!/^0x(?:[0-9a-fA-F]{2})+$/.test(code) || this.expectedRuntimeCodeHash && keccak2566(code) !== this.expectedRuntimeCodeHash) {
1542
- throw new Error("deployment runtime code profile mismatch");
1543
- }
1544
- const tokenCode = await reader.getCode(this.context.tokenAddress);
1545
- if (!/^0x(?:[0-9a-fA-F]{2})+$/.test(tokenCode)) {
1546
- throw new Error("token address must have contract code");
1547
- }
1548
- }
1549
- transact(request) {
1550
- return {
1551
- to: this.context.protocolAddress,
1552
- value: 0n,
1553
- ...buildTransactCalldata({ ...request, context: this.context })
1554
- };
1555
- }
1556
- aggregate(request) {
1557
- return {
1558
- to: this.context.protocolAddress,
1559
- value: 0n,
1560
- ...buildTransactAggregateCalldata({ ...request, context: this.context })
1561
- };
1562
- }
1563
- async shield(request) {
1564
- return {
1565
- to: this.context.protocolAddress,
1566
- value: 0n,
1567
- ...await buildShieldCalldata({ ...request, context: this.context })
1568
- };
1569
- }
1570
- createCredit(request) {
1571
- return {
1572
- to: this.context.protocolAddress,
1573
- value: 0n,
1574
- ...buildCreateCreditCalldata({ ...request, context: this.context })
1575
- };
1576
- }
1577
- drawCredit(request) {
1578
- return {
1579
- to: this.context.protocolAddress,
1580
- value: 0n,
1581
- ...buildCreditDrawCalldata({ ...request, context: this.context })
1582
- };
1583
- }
1584
- cancelCredit(request) {
1585
- return {
1586
- to: this.context.protocolAddress,
1587
- value: 0n,
1588
- ...buildCancelCreditCalldata({ ...request, context: this.context })
1589
- };
1590
- }
1591
- topUpCredit(request) {
1592
- return {
1593
- to: this.context.protocolAddress,
1594
- value: 0n,
1595
- ...buildTopUpCreditCalldata({ ...request, context: this.context })
1596
- };
1597
- }
1598
- };
1599
-
1600
- // src/Protocol.ts
1601
- var Protocol = class _Protocol {
1602
- deployment;
1603
- context;
1604
- constructor(options) {
1605
- known(options, ["deployment"], "protocol options");
1606
- this.deployment = defineDeployment(options.deployment);
1607
- this.context = Object.freeze({
1608
- chainId: this.deployment.chainId,
1609
- protocolAddress: this.deployment.protocol.address
1610
- });
1611
- Object.freeze(this);
1612
- }
1613
- /** Load the selected deployment and verify its contract against Chain RPC before returning. */
1614
- static async fromRpc(rpc, provider, options = {}) {
1615
- const chainId = (await provider.getNetwork()).chainId;
1616
- if (typeof chainId !== "bigint" || chainId <= 0n || chainId >= 1n << 64n)
1617
- throw new Error("invalid protocol chainId");
1618
- const deployment = await rpc.getDeployment(
1619
- chainId,
1620
- options.signal ? { signal: options.signal } : {}
1621
- );
1622
- const protocol = new _Protocol({ deployment });
1623
- if (protocol.context.chainId !== chainId) throw new Error("RPC deployment chain mismatch");
1624
- await protocol.validateDeployment(provider);
1625
- return protocol;
1626
- }
1627
- async validateDeployment(connection) {
1628
- const reader = "getNetwork" in connection ? {
1629
- getChainId: async () => (await connection.getNetwork()).chainId,
1630
- getCode: async (address) => await connection.getCode(address),
1631
- getProtocolAddressRegistry: async (address) => getAddress8(
1632
- await new Contract2(
1633
- address,
1634
- ["function addressRegistry() view returns (address)"],
1635
- connection
1636
- ).getFunction("addressRegistry")()
1637
- ),
1638
- getProtocolVersion: async (address) => BigInt(
1639
- await new Contract2(
1640
- address,
1641
- ["function VERSION() view returns (uint256)"],
1642
- connection
1643
- ).getFunction("VERSION")()
1644
- )
1645
- } : connection;
1646
- if (await reader.getChainId() !== this.context.chainId)
1647
- throw new Error("deployment chain mismatch");
1648
- const code = await reader.getCode(this.context.protocolAddress);
1649
- if (!/^0x(?:[0-9a-fA-F]{2})+$/.test(code) || keccak2567(code) !== this.deployment.protocol.runtimeCodeHash)
1650
- throw new Error("protocol runtime code profile mismatch");
1651
- if (reader.getProtocolVersion && await reader.getProtocolVersion(this.context.protocolAddress) !== 1n)
1652
- throw new Error("unsupported onchain protocol version");
1653
- const registry = this.deployment.registry;
1654
- if (registry) {
1655
- if (!reader.getProtocolAddressRegistry)
1656
- throw new Error("registry binding reader capability is missing");
1657
- if (getAddress8(
1658
- await reader.getProtocolAddressRegistry(this.context.protocolAddress)
1659
- ) !== getAddress8(registry.address))
1660
- throw new Error("protocol registry binding mismatch");
1661
- const registryCode = await reader.getCode(registry.address);
1662
- if (!/^0x(?:[0-9a-fA-F]{2})+$/.test(registryCode) || keccak2567(registryCode) !== registry.runtimeCodeHash)
1663
- throw new Error("registry runtime code profile mismatch");
1664
- }
1665
- }
1666
- aggregate(request) {
1667
- return {
1668
- to: this.context.protocolAddress,
1669
- value: 0n,
1670
- ...buildTransactAggregateCalldata({ ...request, context: this.context })
1671
- };
1672
- }
1673
- /** Build an operation context for one asset within this same deployment. */
1674
- forToken(tokenAddress) {
1675
- return new TokenProtocol(
1676
- { ...this.context, tokenAddress: getAddress8(tokenAddress) },
1677
- { expectedRuntimeCodeHash: this.deployment.protocol.runtimeCodeHash }
1678
- );
1679
- }
1680
- };
1681
-
1682
- // src/verify.ts
1683
- import {
1684
- computeCreditCommitment,
1685
- computeCreditSharedKeyHash,
1686
- computeNoteCommitment as computeNoteCommitment2,
1687
- computeNoteIdentity,
1688
- FIELD_MODULUS as FIELD_MODULUS2
1689
- } from "@invisi-labs/core";
1690
- async function verifyNoteCommitment(params) {
1691
- const expected = await computeNoteCommitment2(
1692
- params.chainId,
1693
- await computeNoteIdentity(BigInt(params.ownerAddress), BigInt(params.nonce)),
1694
- BigInt(params.tokenAddress),
1695
- params.amount
1696
- );
1697
- return BigInt(expected) === BigInt(params.noteCommitment);
1698
- }
1699
- async function verifyCreditCommitment(params) {
1700
- if (params.creditSharedKey <= 0n || params.creditSharedKey >= FIELD_MODULUS2)
1701
- throw new RangeError("creditSharedKey must be a nonzero canonical Field");
1702
- const expected = await computeCreditCommitment(
1703
- params.chainId,
1704
- params.spenderWalletIdentity,
1705
- params.ownerWalletIdentity,
1706
- BigInt(params.spenderPayout),
1707
- BigInt(params.ownerPayout),
1708
- BigInt(params.tokenAddress),
1709
- params.amount,
1710
- params.nonce,
1711
- params.expiry,
1712
- await computeCreditSharedKeyHash(params.creditSharedKey),
1713
- params
1714
- );
1715
- return BigInt(expected) === BigInt(params.creditCommitment);
1716
- }
1717
-
1718
- // src/witness.ts
1719
- import {
1720
- computeCreditCommitment as computeCreditCommitment2,
1721
- normalizeCreditPolicy,
1722
- applyCreditDrawUsage,
1723
- computeCreditNullifier,
1724
- computeCreditSharedKeyHash as computeCreditSharedKeyHash2,
1725
- computeNoteCommitment as computeNoteCommitment3,
1726
- computeNoteIdentity as computeNoteIdentity2,
1727
- computeNoteNullifier,
1728
- computeWalletAuthorizationDigest as computeWalletAuthorizationDigest3,
1729
- normalizeCreditDeliveryState,
1730
- poseidonHash,
1731
- verifyWalletAuthorization
1732
- } from "@invisi-labs/core";
1733
- import {
1734
- LocalWitness
1735
- } from "@invisi-labs/prover";
1736
-
1737
190
  // src/witness-layouts.json
1738
191
  var witness_layouts_default = {
1739
192
  transact_user_slots14: {
@@ -3178,541 +1631,2163 @@ var witness_layouts_default = {
3178
1631
  count: 1,
3179
1632
  array: false
3180
1633
  }
3181
- ],
3182
- private: [
3183
- "input_amounts",
3184
- "input_nonces",
3185
- "leaf_indices",
3186
- "merkle_paths",
3187
- "input_source_indices",
3188
- "public_key_x",
3189
- "public_key_y",
3190
- "signature",
3191
- "spender_payout",
3192
- "spender_wallet_identity",
3193
- "owner_wallet_identity",
3194
- "credit_amount",
3195
- "credit_nonce",
3196
- "credit_shared_key",
3197
- "single_limit",
3198
- "daily_limit",
3199
- "monthly_limit",
3200
- "previous_single_limit",
3201
- "previous_daily_limit",
3202
- "previous_monthly_limit",
3203
- "last_day",
3204
- "daily_spent",
3205
- "last_month",
3206
- "monthly_spent",
3207
- "change_amount",
3208
- "change_nonce",
3209
- "previous_credit_amount",
3210
- "previous_credit_nonce",
3211
- "previous_credit_leaf_index",
3212
- "previous_credit_merkle_path"
3213
- ]
1634
+ ],
1635
+ private: [
1636
+ "input_amounts",
1637
+ "input_nonces",
1638
+ "leaf_indices",
1639
+ "merkle_paths",
1640
+ "input_source_indices",
1641
+ "public_key_x",
1642
+ "public_key_y",
1643
+ "signature",
1644
+ "spender_payout",
1645
+ "spender_wallet_identity",
1646
+ "owner_wallet_identity",
1647
+ "credit_amount",
1648
+ "credit_nonce",
1649
+ "credit_shared_key",
1650
+ "single_limit",
1651
+ "daily_limit",
1652
+ "monthly_limit",
1653
+ "previous_single_limit",
1654
+ "previous_daily_limit",
1655
+ "previous_monthly_limit",
1656
+ "last_day",
1657
+ "daily_spent",
1658
+ "last_month",
1659
+ "monthly_spent",
1660
+ "change_amount",
1661
+ "change_nonce",
1662
+ "previous_credit_amount",
1663
+ "previous_credit_nonce",
1664
+ "previous_credit_leaf_index",
1665
+ "previous_credit_merkle_path"
1666
+ ]
1667
+ }
1668
+ };
1669
+
1670
+ // src/authorization/common.ts
1671
+ var pad2 = (values, count, zero) => {
1672
+ if (values.length > count) throw new Error("witness capacity exceeded");
1673
+ return [...values, ...Array.from({ length: count - values.length }, zero)];
1674
+ };
1675
+ var policyWitness = (note, usage = true) => {
1676
+ const p = normalizeCreditPolicy(note);
1677
+ return {
1678
+ single_limit: p.singleLimit,
1679
+ daily_limit: p.dailyLimit,
1680
+ monthly_limit: p.monthlyLimit,
1681
+ ...usage ? {
1682
+ last_day: p.lastDay,
1683
+ daily_spent: p.dailySpent,
1684
+ last_month: p.lastMonth,
1685
+ monthly_spent: p.monthlySpent
1686
+ } : {}
1687
+ };
1688
+ };
1689
+ var zeros = () => Array(16).fill(0n);
1690
+ var zeroAuthority = () => ({
1691
+ public_key_x: Array(32).fill(0),
1692
+ public_key_y: Array(32).fill(0),
1693
+ signature: Array(64).fill(0)
1694
+ });
1695
+ function check(ok, message) {
1696
+ if (!ok) throw new Error(message);
1697
+ }
1698
+ function provingRequest(circuitName, privateInputs, expectedPublicInputs, target = "evm") {
1699
+ const schema = witness_layouts_default[circuitName], key = schema.keys[target];
1700
+ check(typeof key === "string", "unpublished proof target");
1701
+ check(
1702
+ expectedPublicInputs.length === schema.publicInputCount,
1703
+ "public input order/width mismatch"
1704
+ );
1705
+ check(
1706
+ Object.keys(privateInputs).length === schema.private.length && schema.private.every((name) => Object.hasOwn(privateInputs, name)),
1707
+ "private witness schema mismatch"
1708
+ );
1709
+ let offset = 0;
1710
+ const publicInputs = {};
1711
+ for (const entry of schema.public) {
1712
+ const values = expectedPublicInputs.slice(offset, offset + entry.count).map((value) => {
1713
+ const n = BigInt(value);
1714
+ check(n >= 0n && n < FIELD_MODULUS, "noncanonical public input");
1715
+ return n;
1716
+ });
1717
+ publicInputs[entry.name] = entry.array ? values : values[0];
1718
+ offset += entry.count;
1719
+ }
1720
+ check(offset === expectedPublicInputs.length, "public input layout mismatch");
1721
+ return {
1722
+ circuitName,
1723
+ target,
1724
+ artifactSha256: schema.artifactSha256,
1725
+ vkSha256: key,
1726
+ expectedPublicInputs: [...expectedPublicInputs],
1727
+ witness: new LocalWitness({ ...privateInputs, ...publicInputs })
1728
+ };
1729
+ }
1730
+ function witnessStatement(circuitName, expectedPublicInputs, target = "evm") {
1731
+ const schema = witness_layouts_default[circuitName], key = schema.keys[target];
1732
+ check(
1733
+ typeof key === "string" && expectedPublicInputs.length === schema.publicInputCount,
1734
+ "unpublished target or statement width mismatch"
1735
+ );
1736
+ return {
1737
+ circuitName,
1738
+ target,
1739
+ artifactSha256: schema.artifactSha256,
1740
+ vkSha256: key,
1741
+ expectedPublicInputs: [...expectedPublicInputs]
1742
+ };
1743
+ }
1744
+
1745
+ // src/authorization/WitnessContext.ts
1746
+ var WitnessContext = class {
1747
+ constructor(protocol) {
1748
+ this.protocol = protocol;
1749
+ }
1750
+ context(note) {
1751
+ const c = this.protocol.context;
1752
+ check(
1753
+ note.chainId === c.chainId && note.protocolAddress === BigInt(c.protocolAddress) && note.tokenAddress === BigInt(c.tokenAddress),
1754
+ "note context mismatch"
1755
+ );
1756
+ }
1757
+ async authority(operation, capacity, words, signed, identity) {
1758
+ check(!!signed, "private wallet authorization required");
1759
+ const c = this.protocol.context, request = {
1760
+ context: { chainId: c.chainId, protocolAddress: BigInt(c.protocolAddress) },
1761
+ operation,
1762
+ capacity,
1763
+ words: words.slice(0, -2).map(BigInt)
1764
+ };
1765
+ check(
1766
+ signed.ownerAddress === identity && signed.digest === computeWalletAuthorizationDigest(request),
1767
+ "authorization statement mismatch"
1768
+ );
1769
+ const witness = signed.withWitness((value) => value);
1770
+ check(
1771
+ await verifyWalletAuthorization(
1772
+ identity,
1773
+ Uint8Array.from([4, ...witness.public_key_x, ...witness.public_key_y]),
1774
+ Uint8Array.from(witness.signature),
1775
+ request
1776
+ ),
1777
+ "invalid wallet authorization"
1778
+ );
1779
+ return witness;
1780
+ }
1781
+ async membership(leaf, input, sources) {
1782
+ const source = sources.findIndex((value) => value.treeId === input.treeId);
1783
+ check(
1784
+ source >= 0 && Number.isInteger(input.leafIndex) && input.leafIndex >= 0 && input.leafIndex < 65536 && input.path.length === 16,
1785
+ "invalid membership position"
1786
+ );
1787
+ let root = BigInt(leaf);
1788
+ for (let level = 0; level < 16; level++)
1789
+ root = BigInt(
1790
+ await poseidonHash(
1791
+ input.leafIndex >> level & 1 ? [input.path[level], root] : [root, input.path[level]]
1792
+ )
1793
+ );
1794
+ check(root === BigInt(sources[source].merkleRoot), "membership root mismatch");
1795
+ return source;
1796
+ }
1797
+ async noteLeaf(note) {
1798
+ this.context(note);
1799
+ return computeNoteCommitment(
1800
+ note.chainId,
1801
+ await computeNoteIdentity(BigInt(note.ownerAddress), note.nonce),
1802
+ note.tokenAddress,
1803
+ note.amount
1804
+ );
1805
+ }
1806
+ async creditLeaf(note) {
1807
+ this.context(note);
1808
+ return computeCreditCommitment(
1809
+ note.chainId,
1810
+ note.spenderWalletIdentity,
1811
+ note.ownerWalletIdentity,
1812
+ note.spenderPayout,
1813
+ note.ownerPayout,
1814
+ note.tokenAddress,
1815
+ note.amount,
1816
+ note.nonce,
1817
+ note.expiry,
1818
+ await computeCreditSharedKeyHash(note.creditSharedKey),
1819
+ note
1820
+ );
1821
+ }
1822
+ async noteInputs(inputs, sources, nullifiers, identity, authority) {
1823
+ check(inputs.length === nullifiers.length, "input count mismatch");
1824
+ return Promise.all(
1825
+ inputs.map(async (input, i) => {
1826
+ check(input.note.ownerAddress === identity, "input wallet mismatch");
1827
+ const leaf = await this.noteLeaf(input.note);
1828
+ check(
1829
+ await computeNoteNullifier(input.note.chainId, input.note.nonce, leaf) === nullifiers[i],
1830
+ "input nullifier mismatch"
1831
+ );
1832
+ return BigInt(await this.membership(leaf, input, sources));
1833
+ })
1834
+ );
1835
+ }
1836
+ };
1837
+
1838
+ // src/authorization/credit-cancel.ts
1839
+ import { computeCreditNullifier, normalizeCreditDeliveryState } from "@invisi-labs/core";
1840
+
1841
+ // src/credit/credit-lifecycle.ts
1842
+ import { AbiCoder as AbiCoder3, getAddress as getAddress4, Interface as Interface2, keccak256 as keccak2563, ParamType as ParamType3 } from "ethers";
1843
+
1844
+ // src/fees/fee.ts
1845
+ import { getAddress as getAddress2 } from "ethers";
1846
+ function normalizeFee(p) {
1847
+ const feeRecipient = getAddress2(p.feeRecipient ?? ZERO_ADDRESS);
1848
+ const feeAmount = uint(p.feeAmount ?? 0n, 128, "feeAmount");
1849
+ if (BigInt(feeRecipient) === 0n !== (feeAmount === 0n))
1850
+ throw new Error("invalid fee context");
1851
+ return { feeRecipient, feeAmount };
1852
+ }
1853
+
1854
+ // src/credit/credit-lifecycle.ts
1855
+ import {
1856
+ computeWalletAuthorizationDigest as computeWalletAuthorizationDigest3,
1857
+ creditPeriods,
1858
+ walletAuthorizationTypedData as walletAuthorizationTypedData2
1859
+ } from "@invisi-labs/core";
1860
+
1861
+ // src/credit/credit-create.ts
1862
+ import {
1863
+ AbiCoder as AbiCoder2,
1864
+ dataLength as dataLength2,
1865
+ getAddress as getAddress3,
1866
+ Interface,
1867
+ isHexString as isHexString2,
1868
+ keccak256 as keccak2562,
1869
+ ParamType as ParamType2
1870
+ } from "ethers";
1871
+ import { computeWalletAuthorizationDigest as computeWalletAuthorizationDigest2, walletAuthorizationTypedData } from "@invisi-labs/core";
1872
+ var creationTuple = [
1873
+ "(address token,(uint256 treeId,bytes32 merkleRoot)[] sources,bytes32[] nullifiers,bytes32 creditCommitment,bytes32 changeCommitment,address owner,uint256 expiry,uint256 deadline,address feeRecipient,uint256 feeAmount)"
1874
+ ][0];
1875
+ var CREATE_CREDIT_FIELDS = [
1876
+ "token",
1877
+ "sources",
1878
+ "nullifiers",
1879
+ "creditCommitment",
1880
+ "changeCommitment",
1881
+ "owner",
1882
+ "expiry",
1883
+ "deadline",
1884
+ "feeRecipient",
1885
+ "feeAmount"
1886
+ ];
1887
+ function validateCreateCreditRequest(c, p, cs, funding = {}) {
1888
+ known(p, CREATE_CREDIT_FIELDS);
1889
+ if (getAddress3(p.token) !== getAddress3(c.tokenAddress))
1890
+ throw new Error("request token mismatch");
1891
+ const ctx = normalizeContext(c), ns = notes(p.nullifiers, "nullifiers"), sources = normalizeSources(p.sources, ns.length, 10), creditCommitment = word(field(p.creditCommitment)), changeCommitment = word(field(p.changeCommitment)), owner = getAddress3(p.owner), expiry = uint(p.expiry, 64, "expiry"), deadline = checkDeadline(c, p.deadline), f = normalizeFunding(funding);
1892
+ if (BigInt(creditCommitment) === 0n || creditCommitment === changeCommitment)
1893
+ throw new Error("zero or duplicate commitment");
1894
+ if (BigInt(owner) === 0n || expiry === 0n || c.now !== void 0 && expiry <= c.now)
1895
+ throw new Error("owner must be nonzero and expiry future");
1896
+ if (dense(cs, "deliveries").length !== 3) throw new Error("deliveries count mismatch");
1897
+ const deliveries = cs.map(
1898
+ (v, i) => normalizeOpaqueDelivery(v, {
1899
+ empty: i === 2 && BigInt(changeCommitment) === 0n,
1900
+ allowEmpty: i === 2
1901
+ })
1902
+ );
1903
+ if (ns.length === 0 && f.amount === 0n)
1904
+ throw new Error("empty inputs require positive funding");
1905
+ return {
1906
+ ...ctx,
1907
+ ...normalizeFee(p),
1908
+ sources,
1909
+ nullifiers: ns,
1910
+ actualCount: ns.length,
1911
+ sourceCount: sources.length,
1912
+ creditCommitment,
1913
+ changeCommitment,
1914
+ owner,
1915
+ expiry,
1916
+ deadline,
1917
+ deliveries,
1918
+ funding: f
1919
+ };
1920
+ }
1921
+ function createCreditBaseInputs(c, p, cs, f = {}) {
1922
+ const r = validateCreateCreditRequest(c, p, cs, f), h = BigInt(deliveriesHash(cs));
1923
+ return [
1924
+ r.chainId,
1925
+ BigInt(r.protocolAddress),
1926
+ BigInt(r.tokenAddress),
1927
+ BigInt(r.owner),
1928
+ BigInt(r.actualCount),
1929
+ BigInt(r.sourceCount),
1930
+ ...pad(
1931
+ r.sources.map((s) => s.treeId),
1932
+ 10
1933
+ ),
1934
+ ...pad(
1935
+ r.sources.map((s) => BigInt(s.merkleRoot)),
1936
+ 10
1937
+ ),
1938
+ ...pad(r.nullifiers.map(BigInt), 10),
1939
+ BigInt(r.creditCommitment),
1940
+ BigInt(r.changeCommitment),
1941
+ h >> 128n,
1942
+ h & (1n << 128n) - 1n,
1943
+ r.expiry,
1944
+ r.deadline,
1945
+ r.funding.amount,
1946
+ BigInt(r.funding.payer),
1947
+ BigInt(r.feeRecipient),
1948
+ r.feeAmount
1949
+ ].map(word);
1950
+ }
1951
+ function createCreditPublicInputs(c, p, deliveries, funding = {}) {
1952
+ const base = createCreditBaseInputs(c, p, deliveries, funding);
1953
+ const digest = BigInt(
1954
+ computeWalletAuthorizationDigest2({
1955
+ context: { chainId: c.chainId, protocolAddress: BigInt(c.protocolAddress) },
1956
+ operation: "create-credit",
1957
+ capacity: 10,
1958
+ words: base.map(BigInt)
1959
+ })
1960
+ );
1961
+ return [...base, word(digest >> 128n), word(digest & (1n << 128n) - 1n)];
1962
+ }
1963
+ var hashContext = {
1964
+ chainId: 0n,
1965
+ protocolAddress: ZERO_ADDRESS,
1966
+ tokenAddress: ZERO_ADDRESS
1967
+ };
1968
+ function createCreditRequestHash(p, cs, f = {}) {
1969
+ const r = validateCreateCreditRequest({ ...hashContext, tokenAddress: p.token }, p, cs, f);
1970
+ return keccak2562(
1971
+ AbiCoder2.defaultAbiCoder().encode(
1972
+ [
1973
+ { type: "bytes32" },
1974
+ creationTuple,
1975
+ { type: "bytes32" },
1976
+ { type: "address" },
1977
+ { type: "uint256" }
1978
+ ].map((parameter) => ParamType2.from(parameter)),
1979
+ [
1980
+ keccak2562(new TextEncoder().encode("InvisiCreateCredit")),
1981
+ { ...p, ...normalizeFee(p) },
1982
+ deliveriesHash(cs),
1983
+ r.funding.payer,
1984
+ r.funding.amount
1985
+ ]
1986
+ )
1987
+ );
1988
+ }
1989
+ function encodeCreateCreditTypedData(c, p, deliveries, funding = {}) {
1990
+ return walletAuthorizationTypedData({
1991
+ context: { chainId: c.chainId, protocolAddress: BigInt(c.protocolAddress) },
1992
+ operation: "create-credit",
1993
+ capacity: 10,
1994
+ words: createCreditBaseInputs(c, p, deliveries, funding).map(BigInt)
1995
+ });
1996
+ }
1997
+ function encodeCreateCreditFundingTypedData(c, p, cs, f) {
1998
+ const r = validateCreateCreditRequest(c, p, cs, f);
1999
+ if (r.funding.amount === 0n) throw new Error("funding authorization requires positive deposit");
2000
+ return {
2001
+ domain: getEIP712Domain(c),
2002
+ primaryType: "FundTransact",
2003
+ types: { FundTransact: fundingFields },
2004
+ message: {
2005
+ requestHash: createCreditRequestHash(p, cs, f),
2006
+ payer: r.funding.payer,
2007
+ amount: r.funding.amount,
2008
+ sigNonce: r.funding.sigNonce,
2009
+ deadline: r.funding.deadline
2010
+ }
2011
+ };
2012
+ }
2013
+ function buildCreateCreditCalldata(p) {
2014
+ const r = validateCreateCreditRequest(p.context, p.params, p.deliveries, p.funding);
2015
+ validateFundingSubmission(r.funding, p.caller, p.context.now);
2016
+ return {
2017
+ data: new Interface(invisiProtocolAbi).encodeFunctionData("createCredit", [
2018
+ encodeCreditProof(p.proof),
2019
+ { ...p.params, ...normalizeFee(p.params) },
2020
+ p.deliveries,
2021
+ r.funding
2022
+ ]),
2023
+ approveAmount: r.funding.amount
2024
+ };
2025
+ }
2026
+ function encodeCreditProof(proof) {
2027
+ if (!isHexString2(proof) || !/^0x(?:[a-fA-F0-9]{2})*$/.test(proof) || dataLength2(proof) === 0)
2028
+ throw new Error("empty or malformed Credit proof");
2029
+ return AbiCoder2.defaultAbiCoder().encode(
2030
+ ["uint8", "bytes"].map((parameter) => ParamType2.from(parameter)),
2031
+ [0, proof]
2032
+ );
2033
+ }
2034
+
2035
+ // src/credit/credit-lifecycle.ts
2036
+ var CREDIT_DRAW_CAPACITIES = [1, 12, 50, 120];
2037
+ function selectCreditDrawCircuit(count) {
2038
+ if (!Number.isInteger(count) || count < 1 || count > 120)
2039
+ throw new Error("draw requires 1..120 credits");
2040
+ const maxSize = CREDIT_DRAW_CAPACITIES.find((n) => count <= n);
2041
+ return {
2042
+ name: `credit_draw_slots${maxSize}`,
2043
+ maxSize,
2044
+ publicInputCount: 6 * maxSize + 14,
2045
+ totalBits: 126 + Math.ceil(Math.log2(maxSize))
2046
+ };
2047
+ }
2048
+ function validateCreditDrawRequest(c, p, cs) {
2049
+ known(p, [
2050
+ "token",
2051
+ "sources",
2052
+ "nullifiers",
2053
+ "remainderCommitments",
2054
+ "expiries",
2055
+ "recoveryAmounts",
2056
+ "spender",
2057
+ "totalAmount",
2058
+ "deadline"
2059
+ ]);
2060
+ if (getAddress4(p.token) !== getAddress4(c.tokenAddress))
2061
+ throw new Error("request token mismatch");
2062
+ const ctx = normalizeContext(c), ns = notes(p.nullifiers, "nullifiers"), profile = selectCreditDrawCircuit(ns.length), sources = normalizeSources(p.sources, ns.length, profile.maxSize);
2063
+ for (const [key, v] of Object.entries({
2064
+ remainderCommitments: p.remainderCommitments,
2065
+ expiries: p.expiries,
2066
+ recoveryAmounts: p.recoveryAmounts
2067
+ }))
2068
+ if (dense(v, key).length !== ns.length) throw new Error(`${key} count mismatch`);
2069
+ const remainderCommitments = p.remainderCommitments.map((v) => word(field(v))), recoveryAmounts = p.recoveryAmounts.map((v) => word(field(v))), expiries = p.expiries.map((v) => uint(v, 64, "expiry")), positives = remainderCommitments.filter((v) => BigInt(v) !== 0n);
2070
+ if (new Set(positives).size !== positives.length)
2071
+ throw new Error("duplicate remainder commitment");
2072
+ for (let i = 0; i < ns.length; i++) {
2073
+ if (expiries[i] === 0n || c.now !== void 0 && expiries[i] <= c.now)
2074
+ throw new Error("expiry must be future");
2075
+ if (BigInt(remainderCommitments[i]) === 0n && BigInt(recoveryAmounts[i]) !== 0n)
2076
+ throw new Error("full draw requires zero recovery amount");
2077
+ }
2078
+ const spender = getAddress4(p.spender), totalAmount = uint(p.totalAmount, profile.totalBits, "totalAmount");
2079
+ if (BigInt(spender) === 0n || totalAmount === 0n)
2080
+ throw new Error("spender and total amount must be positive");
2081
+ if (dense(cs, "deliveries").length !== 2 * ns.length)
2082
+ throw new Error("deliveries count mismatch");
2083
+ const deliveries = cs.map(
2084
+ (v, i) => BigInt(remainderCommitments[Math.floor(i / 2)]) === 0n ? normalizeOpaqueDelivery(v, { empty: true }) : normalizeOpaqueDelivery(v, {})
2085
+ );
2086
+ return {
2087
+ ...ctx,
2088
+ sources,
2089
+ nullifiers: ns,
2090
+ remainderCommitments,
2091
+ recoveryAmounts,
2092
+ expiries,
2093
+ spender,
2094
+ totalAmount,
2095
+ deadline: checkDeadline(c, p.deadline),
2096
+ deliveries,
2097
+ profile,
2098
+ periods: creditPeriods(c.timestamp)
2099
+ };
2100
+ }
2101
+ function creditDrawBaseInputs(c, p, cs) {
2102
+ const r = validateCreditDrawRequest(c, p, cs), n = r.profile.maxSize, h = BigInt(deliveriesHash(cs));
2103
+ return [
2104
+ r.chainId,
2105
+ BigInt(r.protocolAddress),
2106
+ BigInt(r.tokenAddress),
2107
+ BigInt(r.spender),
2108
+ BigInt(r.nullifiers.length),
2109
+ BigInt(r.sources.length),
2110
+ ...pad(
2111
+ r.sources.map((s) => s.treeId),
2112
+ n
2113
+ ),
2114
+ ...pad(
2115
+ r.sources.map((s) => BigInt(s.merkleRoot)),
2116
+ n
2117
+ ),
2118
+ ...pad(r.nullifiers.map(BigInt), n),
2119
+ ...pad(r.remainderCommitments.map(BigInt), n),
2120
+ ...pad(r.expiries, n),
2121
+ r.totalAmount,
2122
+ r.deadline,
2123
+ h >> 128n,
2124
+ h & (1n << 128n) - 1n,
2125
+ ...pad(r.recoveryAmounts.map(BigInt), n),
2126
+ r.periods.currentDay,
2127
+ r.periods.currentMonth
2128
+ ].map(word);
2129
+ }
2130
+ function validateCancelCreditRequest(c, p) {
2131
+ known(p, [
2132
+ "token",
2133
+ "source",
2134
+ "nullifier",
2135
+ "owner",
2136
+ "amount",
2137
+ "deadline",
2138
+ "feeRecipient",
2139
+ "feeAmount"
2140
+ ]);
2141
+ if (getAddress4(p.token) !== getAddress4(c.tokenAddress))
2142
+ throw new Error("request token mismatch");
2143
+ const ctx = normalizeContext(c), source = normalizeSources([p.source], 1, 1)[0], nullifier = notes([p.nullifier], "nullifier")[0], owner = getAddress4(p.owner), amount = uint(p.amount, 126, "amount");
2144
+ if (BigInt(owner) === 0n || amount === 0n) throw new Error("owner and amount must be positive");
2145
+ const fee = normalizeFee(p);
2146
+ if (fee.feeAmount >= amount) throw new Error("fee must leave a positive owner refund");
2147
+ return {
2148
+ ...ctx,
2149
+ ...fee,
2150
+ source,
2151
+ nullifier,
2152
+ owner,
2153
+ amount,
2154
+ deadline: checkDeadline(c, p.deadline)
2155
+ };
2156
+ }
2157
+ function cancelCreditBaseInputs(c, p) {
2158
+ const r = validateCancelCreditRequest(c, p);
2159
+ return [
2160
+ r.chainId,
2161
+ BigInt(r.protocolAddress),
2162
+ BigInt(r.tokenAddress),
2163
+ BigInt(r.owner),
2164
+ r.source.treeId,
2165
+ BigInt(r.source.merkleRoot),
2166
+ BigInt(r.nullifier),
2167
+ r.amount,
2168
+ r.deadline,
2169
+ BigInt(r.feeRecipient),
2170
+ r.feeAmount
2171
+ ].map(word);
2172
+ }
2173
+ function validateTopUpCreditRequest(c, p, cs, funding = {}) {
2174
+ known(p, ["token", "creation", "creditSource", "creditNullifier", "previousExpiry"]);
2175
+ const creation2 = validateCreateCreditRequest(c, p.creation, cs, funding), creditSource = normalizeSources([p.creditSource], 1, 1)[0], creditNullifier = notes([p.creditNullifier], "creditNullifier")[0], previousExpiry = uint(p.previousExpiry, 64, "previousExpiry");
2176
+ if (previousExpiry === 0n || c.now !== void 0 && previousExpiry <= c.now)
2177
+ throw new Error("previous Credit has expired");
2178
+ if (creation2.expiry < previousExpiry) throw new Error("expiry must not decrease");
2179
+ if (creation2.nullifiers.includes(creditNullifier))
2180
+ throw new Error("duplicate credit and ordinary nullifier");
2181
+ return { creation: creation2, creditSource, creditNullifier, previousExpiry };
2182
+ }
2183
+ function topUpCreditBaseInputs(c, p, cs, f = {}) {
2184
+ const r = validateTopUpCreditRequest(c, p, cs, f);
2185
+ return [
2186
+ ...createCreditBaseInputs(c, p.creation, cs, f),
2187
+ word(r.creditSource.treeId),
2188
+ r.creditSource.merkleRoot,
2189
+ r.creditNullifier,
2190
+ word(r.previousExpiry)
2191
+ ];
2192
+ }
2193
+ var cancelTuple = [
2194
+ "(address token,(uint256 treeId,bytes32 merkleRoot) source,bytes32 nullifier,address owner,uint256 amount,uint256 deadline,address feeRecipient,uint256 feeAmount)"
2195
+ ][0];
2196
+ var topTuple = [
2197
+ "((address token,(uint256 treeId,bytes32 merkleRoot)[] sources,bytes32[] nullifiers,bytes32 creditCommitment,bytes32 changeCommitment,address owner,uint256 expiry,uint256 deadline,address feeRecipient,uint256 feeAmount) creation,(uint256 treeId,bytes32 merkleRoot) creditSource,bytes32 creditNullifier,uint256 previousExpiry)"
2198
+ ][0];
2199
+ var hashContext2 = {
2200
+ chainId: 0n,
2201
+ protocolAddress: ZERO_ADDRESS,
2202
+ tokenAddress: ZERO_ADDRESS
2203
+ };
2204
+ function cancelCreditRequestHash(p) {
2205
+ validateCancelCreditRequest({ ...hashContext2, tokenAddress: p.token }, p);
2206
+ return keccak2563(
2207
+ AbiCoder3.defaultAbiCoder().encode(
2208
+ [{ type: "bytes32" }, cancelTuple].map((parameter) => ParamType3.from(parameter)),
2209
+ [
2210
+ keccak2563(new TextEncoder().encode("InvisiCancelCredit")),
2211
+ { ...p, ...normalizeFee(p) }
2212
+ ]
2213
+ )
2214
+ );
2215
+ }
2216
+ function topUpCreditRequestHash(p, cs, f = {}) {
2217
+ const r = validateTopUpCreditRequest(
2218
+ { ...hashContext2, tokenAddress: p.creation.token },
2219
+ p,
2220
+ cs,
2221
+ f
2222
+ );
2223
+ return keccak2563(
2224
+ AbiCoder3.defaultAbiCoder().encode(
2225
+ [
2226
+ { type: "bytes32" },
2227
+ topTuple,
2228
+ { type: "bytes32" },
2229
+ { type: "address" },
2230
+ { type: "uint256" }
2231
+ ].map((parameter) => ParamType3.from(parameter)),
2232
+ [
2233
+ keccak2563(new TextEncoder().encode("InvisiTopUpCredit")),
2234
+ { ...p, creation: { ...p.creation, ...normalizeFee(p.creation) } },
2235
+ deliveriesHash(cs),
2236
+ r.creation.funding.payer,
2237
+ r.creation.funding.amount
2238
+ ]
2239
+ )
2240
+ );
2241
+ }
2242
+ function authorize(c, operation, capacity, words) {
2243
+ return walletAuthorizationTypedData2({
2244
+ context: { chainId: c.chainId, protocolAddress: BigInt(c.protocolAddress) },
2245
+ operation,
2246
+ capacity,
2247
+ words: words.map(BigInt)
2248
+ });
2249
+ }
2250
+ function appendDigest(c, operation, capacity, words) {
2251
+ const digest = BigInt(
2252
+ computeWalletAuthorizationDigest3({
2253
+ context: { chainId: c.chainId, protocolAddress: BigInt(c.protocolAddress) },
2254
+ operation,
2255
+ capacity,
2256
+ words: words.map(BigInt)
2257
+ })
2258
+ );
2259
+ return [...words, word(digest >> 128n), word(digest & (1n << 128n) - 1n)];
2260
+ }
2261
+ function encodeCancelCreditTypedData(c, p) {
2262
+ return authorize(c, "cancel-credit", 1, cancelCreditBaseInputs(c, p));
2263
+ }
2264
+ function encodeTopUpCreditTypedData(c, p, deliveries, funding = {}) {
2265
+ return authorize(c, "top-up-credit", 10, topUpCreditBaseInputs(c, p, deliveries, funding));
2266
+ }
2267
+ function encodeCreditDrawTypedData(c, p, deliveries) {
2268
+ return authorize(
2269
+ c,
2270
+ "credit-draw",
2271
+ selectCreditDrawCircuit(p.nullifiers.length).maxSize,
2272
+ creditDrawBaseInputs(c, p, deliveries)
2273
+ );
2274
+ }
2275
+ function creditDrawPublicInputs(c, p, deliveries) {
2276
+ return appendDigest(
2277
+ c,
2278
+ "credit-draw",
2279
+ selectCreditDrawCircuit(p.nullifiers.length).maxSize,
2280
+ creditDrawBaseInputs(c, p, deliveries)
2281
+ );
2282
+ }
2283
+ function cancelCreditPublicInputs(c, p) {
2284
+ return appendDigest(c, "cancel-credit", 1, cancelCreditBaseInputs(c, p));
2285
+ }
2286
+ function topUpCreditPublicInputs(c, p, deliveries, funding = {}) {
2287
+ return appendDigest(c, "top-up-credit", 10, topUpCreditBaseInputs(c, p, deliveries, funding));
2288
+ }
2289
+ function encodeTopUpCreditFundingTypedData(c, p, cs, f) {
2290
+ const r = validateTopUpCreditRequest(c, p, cs, f), funding = r.creation.funding;
2291
+ if (funding.amount === 0n) throw new Error("funding authorization requires positive deposit");
2292
+ return {
2293
+ domain: getEIP712Domain(c),
2294
+ primaryType: "FundTransact",
2295
+ types: { FundTransact: fundingFields },
2296
+ message: {
2297
+ requestHash: topUpCreditRequestHash(p, cs, f),
2298
+ payer: funding.payer,
2299
+ amount: funding.amount,
2300
+ sigNonce: funding.sigNonce,
2301
+ deadline: funding.deadline
2302
+ }
2303
+ };
2304
+ }
2305
+ function buildCreditDrawCalldata(p) {
2306
+ validateCreditDrawRequest(p.context, p.params, p.deliveries);
2307
+ return {
2308
+ data: new Interface2(invisiProtocolAbi).encodeFunctionData("creditDraw", [
2309
+ encodeCreditProof(p.proof),
2310
+ p.params,
2311
+ p.deliveries
2312
+ ]),
2313
+ nullifiers: p.params.nullifiers,
2314
+ totalAmount: p.params.totalAmount
2315
+ };
2316
+ }
2317
+ function buildCancelCreditCalldata(p) {
2318
+ validateCancelCreditRequest(p.context, p.params);
2319
+ return {
2320
+ data: new Interface2(invisiProtocolAbi).encodeFunctionData("cancelCredit", [
2321
+ encodeCreditProof(p.proof),
2322
+ { ...p.params, ...normalizeFee(p.params) }
2323
+ ])
2324
+ };
2325
+ }
2326
+ function buildTopUpCreditCalldata(p) {
2327
+ const r = validateTopUpCreditRequest(p.context, p.params, p.deliveries, p.funding);
2328
+ validateFundingSubmission(r.creation.funding, p.caller, p.context.now);
2329
+ return {
2330
+ data: new Interface2(invisiProtocolAbi).encodeFunctionData("topUpCredit", [
2331
+ encodeCreditProof(p.proof),
2332
+ { ...p.params, creation: { ...p.params.creation, ...normalizeFee(p.params.creation) } },
2333
+ p.deliveries,
2334
+ r.creation.funding
2335
+ ]),
2336
+ approveAmount: r.creation.funding.amount
2337
+ };
2338
+ }
2339
+
2340
+ // src/authorization/credit-cancel.ts
2341
+ async function cancelCredit(witness, p) {
2342
+ const note = normalizeCreditDeliveryState(p.input.state).note, leaf = await witness.creditLeaf(note), c = witness.protocol.context;
2343
+ check(
2344
+ await computeCreditNullifier(c.chainId, note.creditSharedKey, leaf) === p.params.nullifier && note.amount === p.params.amount && note.ownerPayout === BigInt(p.params.owner),
2345
+ "Credit cancellation mismatch"
2346
+ );
2347
+ await witness.membership(leaf, p.input, [p.params.source]);
2348
+ const words = cancelCreditPublicInputs(c, p.params), authority = await witness.authority(
2349
+ "cancel-credit",
2350
+ 1,
2351
+ words,
2352
+ p.authorization,
2353
+ note.ownerWalletIdentity
2354
+ );
2355
+ return provingRequest(
2356
+ "credit_cancel",
2357
+ {
2358
+ ...authority,
2359
+ spender_payout: note.spenderPayout,
2360
+ spender_wallet_identity: BigInt(note.spenderWalletIdentity),
2361
+ owner_wallet_identity: BigInt(note.ownerWalletIdentity),
2362
+ ...policyWitness(note),
2363
+ input_nonce: note.nonce,
2364
+ expiry: note.expiry,
2365
+ credit_shared_key: note.creditSharedKey,
2366
+ leaf_index: BigInt(p.input.leafIndex),
2367
+ merkle_path: [...p.input.path]
2368
+ },
2369
+ words
2370
+ );
2371
+ }
2372
+
2373
+ // src/authorization/credit-creation.ts
2374
+ import { normalizeCreditDeliveryState as normalizeCreditDeliveryState2 } from "@invisi-labs/core";
2375
+ async function creation(witness, p, operation, words) {
2376
+ const state = normalizeCreditDeliveryState2(p.state), note = state.note;
2377
+ check(
2378
+ await witness.creditLeaf(note) === p.params.creditCommitment && note.ownerPayout === BigInt(p.params.owner) && note.expiry === p.params.expiry,
2379
+ "Credit creation mismatch"
2380
+ );
2381
+ if (operation === "create-credit" && [note.lastDay, note.dailySpent, note.lastMonth, note.monthlySpent].some((v) => v !== 0n))
2382
+ throw new Error("new Credit usage must be zero");
2383
+ const authority = await witness.authority(
2384
+ operation,
2385
+ 10,
2386
+ words,
2387
+ p.authorization,
2388
+ note.ownerWalletIdentity
2389
+ );
2390
+ const sources = await witness.noteInputs(
2391
+ p.inputs,
2392
+ p.params.sources,
2393
+ p.params.nullifiers,
2394
+ note.ownerWalletIdentity,
2395
+ authority
2396
+ );
2397
+ if (p.change)
2398
+ check(
2399
+ p.change.ownerAddress === note.ownerWalletIdentity && await witness.noteLeaf(p.change) === p.params.changeCommitment,
2400
+ "change commitment mismatch"
2401
+ );
2402
+ else check(BigInt(p.params.changeCommitment) === 0n, "missing change note");
2403
+ return {
2404
+ ...authority,
2405
+ input_amounts: pad2(
2406
+ p.inputs.map((input) => input.note.amount),
2407
+ 10,
2408
+ () => 0n
2409
+ ),
2410
+ input_nonces: pad2(
2411
+ p.inputs.map((input) => input.note.nonce),
2412
+ 10,
2413
+ () => 0n
2414
+ ),
2415
+ leaf_indices: pad2(
2416
+ p.inputs.map((input) => BigInt(input.leafIndex)),
2417
+ 10,
2418
+ () => 0n
2419
+ ),
2420
+ merkle_paths: pad2(
2421
+ p.inputs.map((input) => [...input.path]),
2422
+ 10,
2423
+ zeros
2424
+ ),
2425
+ input_source_indices: pad2(sources, 10, () => 0n),
2426
+ spender_payout: note.spenderPayout,
2427
+ spender_wallet_identity: BigInt(note.spenderWalletIdentity),
2428
+ owner_wallet_identity: BigInt(note.ownerWalletIdentity),
2429
+ ...policyWitness(note, operation === "top-up-credit"),
2430
+ credit_amount: note.amount,
2431
+ credit_nonce: note.nonce,
2432
+ credit_shared_key: note.creditSharedKey,
2433
+ change_amount: p.change?.amount ?? 0n,
2434
+ change_nonce: p.change?.nonce ?? 0n
2435
+ };
2436
+ }
2437
+
2438
+ // src/authorization/credit-create.ts
2439
+ async function createCredit(witness, p) {
2440
+ const words = createCreditPublicInputs(
2441
+ witness.protocol.context,
2442
+ p.params,
2443
+ p.deliveries,
2444
+ p.funding
2445
+ );
2446
+ return provingRequest(
2447
+ "create_credit",
2448
+ await creation(witness, p, "create-credit", words),
2449
+ words
2450
+ );
2451
+ }
2452
+
2453
+ // src/authorization/credit-draw.ts
2454
+ import {
2455
+ applyCreditDrawUsage,
2456
+ computeCreditNullifier as computeCreditNullifier2,
2457
+ normalizeCreditDeliveryState as normalizeCreditDeliveryState3,
2458
+ normalizeCreditPolicy as normalizeCreditPolicy2
2459
+ } from "@invisi-labs/core";
2460
+ async function drawCredit(witness, p) {
2461
+ const c = { ...witness.protocol.context, timestamp: p.timestamp }, profile = selectCreditDrawCircuit(p.inputs.length), capacity = profile.maxSize;
2462
+ check(
2463
+ p.inputs.length === p.amounts.length && p.inputs.length === p.params.nullifiers.length,
2464
+ "draw input count mismatch"
2465
+ );
2466
+ const notes2 = p.inputs.map((input) => normalizeCreditDeliveryState3(input.state).note), identity = notes2[0].spenderWalletIdentity;
2467
+ const words = creditDrawPublicInputs(c, p.params, p.deliveries), authority = await witness.authority(
2468
+ "credit-draw",
2469
+ capacity,
2470
+ words,
2471
+ p.authorization,
2472
+ identity
2473
+ );
2474
+ const sourceIndices = await Promise.all(
2475
+ notes2.map(async (note, i) => {
2476
+ applyCreditDrawUsage(note, p.amounts[i], p.timestamp);
2477
+ const leaf = await witness.creditLeaf(note);
2478
+ check(
2479
+ note.spenderWalletIdentity === identity && note.spenderPayout === BigInt(p.params.spender) && note.expiry === p.params.expiries[i] && p.amounts[i] > 0n && p.amounts[i] <= note.amount && await computeCreditNullifier2(c.chainId, note.creditSharedKey, leaf) === p.params.nullifiers[i],
2480
+ "Credit draw mismatch"
2481
+ );
2482
+ return BigInt(await witness.membership(leaf, p.inputs[i], p.params.sources));
2483
+ })
2484
+ );
2485
+ return provingRequest(
2486
+ profile.name,
2487
+ {
2488
+ ...authority,
2489
+ ...Object.fromEntries(
2490
+ Object.entries({
2491
+ singleLimit: "single_limits",
2492
+ dailyLimit: "daily_limits",
2493
+ monthlyLimit: "monthly_limits",
2494
+ lastDay: "last_days",
2495
+ dailySpent: "daily_spents",
2496
+ lastMonth: "last_months",
2497
+ monthlySpent: "monthly_spents"
2498
+ }).map(([key, name]) => [
2499
+ name,
2500
+ pad2(
2501
+ notes2.map(
2502
+ (note) => normalizeCreditPolicy2(note)[key]
2503
+ ),
2504
+ capacity,
2505
+ () => 0n
2506
+ )
2507
+ ])
2508
+ ),
2509
+ spender_wallet_identity: BigInt(identity),
2510
+ owner_payouts: pad2(
2511
+ notes2.map((note) => note.ownerPayout),
2512
+ capacity,
2513
+ () => 0n
2514
+ ),
2515
+ owner_wallet_identities: pad2(
2516
+ notes2.map((note) => BigInt(note.ownerWalletIdentity)),
2517
+ capacity,
2518
+ () => 0n
2519
+ ),
2520
+ input_amounts: pad2(
2521
+ notes2.map((note) => note.amount),
2522
+ capacity,
2523
+ () => 0n
2524
+ ),
2525
+ input_nonces: pad2(
2526
+ notes2.map((note) => note.nonce),
2527
+ capacity,
2528
+ () => 0n
2529
+ ),
2530
+ credit_shared_keys: pad2(
2531
+ notes2.map((note) => note.creditSharedKey),
2532
+ capacity,
2533
+ () => 0n
2534
+ ),
2535
+ leaf_indices: pad2(
2536
+ p.inputs.map((input) => BigInt(input.leafIndex)),
2537
+ capacity,
2538
+ () => 0n
2539
+ ),
2540
+ merkle_paths: pad2(
2541
+ p.inputs.map((input) => [...input.path]),
2542
+ capacity,
2543
+ zeros
2544
+ ),
2545
+ input_source_indices: pad2(sourceIndices, capacity, () => 0n),
2546
+ draw_amounts: pad2(p.amounts, capacity, () => 0n)
2547
+ },
2548
+ words
2549
+ );
2550
+ }
2551
+
2552
+ // src/authorization/credit-top-up.ts
2553
+ import { computeCreditNullifier as computeCreditNullifier3, normalizeCreditDeliveryState as normalizeCreditDeliveryState4 } from "@invisi-labs/core";
2554
+ async function topUpCredit(witness, p) {
2555
+ const c = witness.protocol.context, previous = normalizeCreditDeliveryState4(p.previous.state), next = normalizeCreditDeliveryState4(p.state), old = previous.note, note = next.note, leaf = await witness.creditLeaf(old);
2556
+ for (const field2 of [
2557
+ "spenderWalletIdentity",
2558
+ "ownerWalletIdentity",
2559
+ "spenderPayout",
2560
+ "ownerPayout",
2561
+ "creditSharedKey",
2562
+ "lastDay",
2563
+ "dailySpent",
2564
+ "lastMonth",
2565
+ "monthlySpent"
2566
+ ])
2567
+ check(old[field2] === note[field2], "Credit role or payout changed");
2568
+ check(
2569
+ JSON.stringify(previous.ownerReceivingKeys) === JSON.stringify(next.ownerReceivingKeys) && JSON.stringify(previous.spenderReceivingKeys) === JSON.stringify(next.spenderReceivingKeys),
2570
+ "Credit receiving snapshots changed"
2571
+ );
2572
+ check(
2573
+ old.amount < note.amount && old.expiry <= note.expiry && old.expiry === p.params.previousExpiry && (c.now === void 0 || old.expiry > c.now),
2574
+ "invalid Credit top-up"
2575
+ );
2576
+ check(
2577
+ await computeCreditNullifier3(c.chainId, old.creditSharedKey, leaf) === p.params.creditNullifier,
2578
+ "previous Credit nullifier mismatch"
2579
+ );
2580
+ await witness.membership(leaf, p.previous, [p.params.creditSource]);
2581
+ const words = topUpCreditPublicInputs(c, p.params, p.deliveries, p.funding), base = await creation(witness, { ...p, params: p.params.creation }, "top-up-credit", words);
2582
+ return provingRequest(
2583
+ "credit_top_up",
2584
+ {
2585
+ ...base,
2586
+ previous_single_limit: old.singleLimit,
2587
+ previous_daily_limit: old.dailyLimit,
2588
+ previous_monthly_limit: old.monthlyLimit,
2589
+ previous_credit_amount: old.amount,
2590
+ previous_credit_nonce: old.nonce,
2591
+ previous_credit_leaf_index: BigInt(p.previous.leafIndex),
2592
+ previous_credit_merkle_path: [...p.previous.path]
2593
+ },
2594
+ words
2595
+ );
2596
+ }
2597
+
2598
+ // src/authorization/transact.ts
2599
+ import { computeNoteCommitment as computeNoteCommitment2, computeNoteIdentity as computeNoteIdentity2 } from "@invisi-labs/core";
2600
+
2601
+ // src/funds/public-inputs.ts
2602
+ import { TypedDataEncoder } from "ethers";
2603
+
2604
+ // src/funds/funding.ts
2605
+ import { walletAuthorizationTypedData as walletAuthorizationTypedData3 } from "@invisi-labs/core";
2606
+ import { AbiCoder as AbiCoder4, keccak256 as keccak2564, ParamType as ParamType4 } from "ethers";
2607
+
2608
+ // src/funds/abi-tuples.ts
2609
+ var singleTuple = [
2610
+ "(address token,bytes32 merkleRoot,bytes32[] nullifiers,bytes32[] outputCommitments,uint256 inputTreeId,address withdrawalAddress,uint256 withdrawalAmount,uint256 deadline,address feeRecipient,uint256 feeAmount)"
2611
+ ][0];
2612
+ var crossTuple = [
2613
+ "(address token,(uint256 treeId,bytes32 merkleRoot)[] sources,bytes32[] nullifiers,bytes32[] outputCommitments,address withdrawalAddress,uint256 withdrawalAmount,uint256 deadline,address feeRecipient,uint256 feeAmount)"
2614
+ ][0];
2615
+
2616
+ // src/funds/request.ts
2617
+ import { getAddress as getAddress5 } from "ethers";
2618
+ var TRANSACT_CAPACITIES = [14, 56, 112, 168];
2619
+ function selectTransactCapacity(inputs, outputs) {
2620
+ if (!Number.isInteger(inputs) || !Number.isInteger(outputs) || inputs < 0 || outputs < 0 || inputs + outputs < 1 || inputs === 0 && outputs < 2 || inputs + outputs > 168)
2621
+ throw new Error("unsupported transact shape");
2622
+ return TRANSACT_CAPACITIES.find((n) => inputs + outputs <= n);
2623
+ }
2624
+ function normalizeTransactRequest(c, p, cs, funding = {}) {
2625
+ const ctx = normalizeContext(c);
2626
+ if (getAddress5(p.token) !== ctx.tokenAddress)
2627
+ throw new Error("request token mismatch");
2628
+ known(p, [
2629
+ "token",
2630
+ "sources",
2631
+ "nullifiers",
2632
+ "outputCommitments",
2633
+ "withdrawalAddress",
2634
+ "withdrawalAmount",
2635
+ "deadline",
2636
+ "feeRecipient",
2637
+ "feeAmount"
2638
+ ]);
2639
+ const ns = notes(p.nullifiers, "nullifiers"), os = notes(p.outputCommitments, "outputCommitments"), capacity = selectTransactCapacity(ns.length, os.length);
2640
+ const sources = normalizeSources(p.sources, ns.length, capacity), deliveries = dense(cs, "deliveries").map(
2641
+ (v) => normalizeOpaqueDelivery(v, { allowEmpty: true })
2642
+ );
2643
+ if (deliveries.length !== os.length) throw new Error("deliveries count mismatch");
2644
+ const withdrawalAmount = uint(p.withdrawalAmount, 128, "withdrawalAmount"), withdrawalAddress = getAddress5(p.withdrawalAddress), f = normalizeFunding(funding);
2645
+ const feeAmount = uint(p.feeAmount ?? 0n, 128, "feeAmount");
2646
+ const feeRecipient = getAddress5(
2647
+ p.feeRecipient ?? "0x0000000000000000000000000000000000000000"
2648
+ );
2649
+ if (feeAmount === 0n !== (BigInt(feeRecipient) === 0n)) throw new Error("invalid fee");
2650
+ if (withdrawalAmount === 0n !== (BigInt(withdrawalAddress) === 0n) || os.length === 0 && withdrawalAmount === 0n)
2651
+ throw new Error("invalid withdrawal");
2652
+ if (ns.length === 0 && (f.amount === 0n || withdrawalAmount !== 0n))
2653
+ throw new Error("zero inputs require positive funding and zero withdrawal");
2654
+ return {
2655
+ ...ctx,
2656
+ capacity,
2657
+ sources,
2658
+ nullifiers: ns,
2659
+ outputCommitments: os,
2660
+ inputCount: ns.length,
2661
+ outputCount: os.length,
2662
+ sourceCount: sources.length,
2663
+ inputTreeIds: pad(
2664
+ sources.map((s) => s.treeId),
2665
+ capacity
2666
+ ),
2667
+ merkleRoots: pad(
2668
+ sources.map((s) => BigInt(s.merkleRoot)),
2669
+ capacity
2670
+ ),
2671
+ slotValues: pad([...ns, ...os].map(BigInt), capacity),
2672
+ deliveries,
2673
+ deliveriesHash: deliveriesHash(deliveries),
2674
+ withdrawalAddress,
2675
+ withdrawalAmount,
2676
+ feeRecipient,
2677
+ feeAmount,
2678
+ deadline: checkDeadline(c, p.deadline),
2679
+ depositAmount: f.amount,
2680
+ depositPayer: f.payer,
2681
+ funding: f
2682
+ };
2683
+ }
2684
+ function transactPublicInputs(c, p, cs, f = {}) {
2685
+ const r = normalizeTransactRequest(c, p, cs, f), h = BigInt(r.deliveriesHash);
2686
+ return [
2687
+ r.chainId,
2688
+ BigInt(r.protocolAddress),
2689
+ BigInt(r.tokenAddress),
2690
+ BigInt(r.inputCount),
2691
+ BigInt(r.outputCount),
2692
+ BigInt(r.sourceCount),
2693
+ ...r.inputTreeIds,
2694
+ ...r.merkleRoots,
2695
+ ...r.slotValues,
2696
+ h >> 128n,
2697
+ h & (1n << 128n) - 1n,
2698
+ BigInt(r.withdrawalAddress),
2699
+ r.withdrawalAmount,
2700
+ r.deadline,
2701
+ r.depositAmount,
2702
+ BigInt(r.depositPayer),
2703
+ BigInt(r.feeRecipient),
2704
+ r.feeAmount
2705
+ ].map(word);
2706
+ }
2707
+
2708
+ // src/funds/funding.ts
2709
+ function encodeTransactTypedData(c, p, deliveries, f = {}) {
2710
+ const r = normalizeTransactRequest(c, p, deliveries, f);
2711
+ return walletAuthorizationTypedData3({
2712
+ context: { chainId: r.chainId, protocolAddress: BigInt(r.protocolAddress) },
2713
+ operation: "transact",
2714
+ capacity: r.capacity,
2715
+ words: transactPublicInputs(c, p, deliveries, f).map(BigInt)
2716
+ });
2717
+ }
2718
+ function transactFundingRequestHash(proof, p, cs) {
2719
+ const single = {
2720
+ token: p.token,
2721
+ merkleRoot: p.sources[0]?.merkleRoot ?? word(0n),
2722
+ nullifiers: p.nullifiers,
2723
+ outputCommitments: p.outputCommitments,
2724
+ inputTreeId: p.sources[0]?.treeId ?? 0n,
2725
+ withdrawalAddress: p.withdrawalAddress,
2726
+ withdrawalAmount: p.withdrawalAmount,
2727
+ deadline: p.deadline,
2728
+ feeRecipient: p.feeRecipient ?? "0x0000000000000000000000000000000000000000",
2729
+ feeAmount: p.feeAmount ?? 0n
2730
+ };
2731
+ return p.sources.length > 1 ? keccak2564(
2732
+ AbiCoder4.defaultAbiCoder().encode(
2733
+ [{ type: "bytes32" }, { type: "bytes32" }, crossTuple, { type: "bytes32" }].map(
2734
+ (parameter) => ParamType4.from(parameter)
2735
+ ),
2736
+ [
2737
+ keccak2564(new TextEncoder().encode("InvisiCrossTreeTransact")),
2738
+ keccak2564(proof),
2739
+ { ...p, feeRecipient: single.feeRecipient, feeAmount: single.feeAmount },
2740
+ deliveriesHash(cs)
2741
+ ]
2742
+ )
2743
+ ) : keccak2564(
2744
+ AbiCoder4.defaultAbiCoder().encode(
2745
+ [{ type: "bytes32" }, singleTuple, { type: "bytes32" }].map(
2746
+ (parameter) => ParamType4.from(parameter)
2747
+ ),
2748
+ [keccak2564(proof), single, deliveriesHash(cs)]
2749
+ )
2750
+ );
2751
+ }
2752
+ function encodeTransactFundingTypedData(c, proof, p, cs, funding) {
2753
+ normalizeTransactRequest(c, p, cs, funding);
2754
+ const f = normalizeFunding(funding);
2755
+ if (f.amount === 0n) throw new Error("funding authorization requires positive deposit");
2756
+ return {
2757
+ domain: getEIP712Domain(c),
2758
+ primaryType: "FundTransact",
2759
+ types: { FundTransact: fundingFields },
2760
+ message: {
2761
+ requestHash: transactFundingRequestHash(proof, p, cs),
2762
+ payer: f.payer,
2763
+ amount: f.amount,
2764
+ sigNonce: f.sigNonce,
2765
+ deadline: f.deadline
2766
+ }
2767
+ };
2768
+ }
2769
+
2770
+ // src/funds/public-inputs.ts
2771
+ function transactUserPublicInputs(c, p, cs, f = {}) {
2772
+ const words = transactPublicInputs(c, p, cs, f), digest = BigInt(
2773
+ ((typed) => TypedDataEncoder.hash(
2774
+ typed.domain,
2775
+ Object.fromEntries(
2776
+ Object.entries(typed.types).map(([name, fields]) => [
2777
+ name,
2778
+ [...fields]
2779
+ ])
2780
+ ),
2781
+ typed.message
2782
+ ))(encodeTransactTypedData(c, p, cs, f))
2783
+ );
2784
+ return [...words, word(digest >> 128n), word(digest & (1n << 128n) - 1n)];
2785
+ }
2786
+
2787
+ // src/authorization/transact.ts
2788
+ async function transact(witness, p) {
2789
+ const c = witness.protocol.context, n = normalizeTransactRequest(c, p.params, p.deliveries, p.funding), words = transactUserPublicInputs(c, p.params, p.deliveries, p.funding);
2790
+ check(
2791
+ p.inputs.length === n.inputCount && p.outputs.length === n.outputCount,
2792
+ "note count mismatch"
2793
+ );
2794
+ const identity = p.inputs[0]?.note.ownerAddress;
2795
+ const unsignedSweep = !!identity && !p.authorization && n.outputCount === 0 && n.depositAmount === 0n && n.feeAmount === 0n && BigInt(p.params.withdrawalAddress) === BigInt(identity);
2796
+ const authority = identity && !unsignedSweep ? await witness.authority("transact", n.capacity, words, p.authorization, identity) : zeroAuthority();
2797
+ const sources = await witness.noteInputs(
2798
+ p.inputs,
2799
+ p.params.sources,
2800
+ p.params.nullifiers,
2801
+ identity ?? "0x" + "00".repeat(32),
2802
+ authority
2803
+ );
2804
+ const outputs = await Promise.all(
2805
+ p.outputs.map(async (output, i) => {
2806
+ const opening = "identity" in output ? output.note : output;
2807
+ const identity2 = "identity" in output ? field(output.identity) : BigInt(await computeNoteIdentity2(BigInt(output.ownerAddress), output.nonce));
2808
+ const amount = uint(output.amount, 128, "output amount");
2809
+ check(identity2 !== 0n && amount !== 0n, "zero output identity or amount");
2810
+ if (opening) {
2811
+ witness.context(opening);
2812
+ check(
2813
+ opening.amount === amount && BigInt(
2814
+ await computeNoteIdentity2(BigInt(opening.ownerAddress), opening.nonce)
2815
+ ) === identity2,
2816
+ "output opening mismatch"
2817
+ );
2818
+ }
2819
+ const commitment = await computeNoteCommitment2(
2820
+ c.chainId,
2821
+ word(identity2),
2822
+ BigInt(c.tokenAddress),
2823
+ amount
2824
+ );
2825
+ check(commitment === p.params.outputCommitments[i], "output commitment mismatch");
2826
+ return { identity: identity2, amount };
2827
+ })
2828
+ );
2829
+ const all = [...p.inputs.map((input) => input.note), ...outputs];
2830
+ return provingRequest(
2831
+ `transact_user_slots${n.capacity}`,
2832
+ {
2833
+ input_owner_address: identity ? BigInt(identity) : 0n,
2834
+ ...authority,
2835
+ output_identities: pad2(
2836
+ [...p.inputs.map(() => 0n), ...outputs.map((output) => output.identity)],
2837
+ n.capacity,
2838
+ () => 0n
2839
+ ),
2840
+ amounts: pad2(
2841
+ all.map((note) => note.amount),
2842
+ n.capacity,
2843
+ () => 0n
2844
+ ),
2845
+ nonces: pad2(
2846
+ [...p.inputs.map((input) => input.note.nonce), ...outputs.map(() => 0n)],
2847
+ n.capacity,
2848
+ () => 0n
2849
+ ),
2850
+ leaf_indices: pad2(
2851
+ p.inputs.map((input) => BigInt(input.leafIndex)),
2852
+ n.capacity,
2853
+ () => 0n
2854
+ ),
2855
+ merkle_paths: pad2(
2856
+ p.inputs.map((input) => [...input.path]),
2857
+ n.capacity,
2858
+ zeros
2859
+ ),
2860
+ input_source_indices: pad2(sources, n.capacity, () => 0n)
2861
+ },
2862
+ words,
2863
+ p.target
2864
+ );
2865
+ }
2866
+
2867
+ // src/authorization/witness.ts
2868
+ var WitnessBuilder = class {
2869
+ #witness;
2870
+ constructor(protocol) {
2871
+ this.#witness = new WitnessContext(protocol);
2872
+ }
2873
+ static statement(circuitName, expectedPublicInputs, target = "evm") {
2874
+ return witnessStatement(circuitName, expectedPublicInputs, target);
2875
+ }
2876
+ async transact(p) {
2877
+ return transact(this.#witness, p);
2878
+ }
2879
+ async createCredit(p) {
2880
+ return createCredit(this.#witness, p);
2881
+ }
2882
+ async cancelCredit(p) {
2883
+ return cancelCredit(this.#witness, p);
2884
+ }
2885
+ async drawCredit(p) {
2886
+ return drawCredit(this.#witness, p);
2887
+ }
2888
+ async topUpCredit(p) {
2889
+ return topUpCredit(this.#witness, p);
2890
+ }
2891
+ };
2892
+
2893
+ // src/client/Protocol.ts
2894
+ import { Contract, getAddress as getAddress7, keccak256 as keccak2568 } from "ethers";
2895
+ import {
2896
+ defineDeployment
2897
+ } from "@invisi-labs/rpc";
2898
+
2899
+ // src/funds/aggregate.ts
2900
+ import { AbiCoder as AbiCoder5, Interface as Interface3, keccak256 as keccak2565, ParamType as ParamType5 } from "ethers";
2901
+ function buildTransactAggregateCalldata(p) {
2902
+ if (![4, 8, 12].includes(dense(p.requests, "requests").length))
2903
+ throw new Error("aggregate requires 4, 8 or 12 requests");
2904
+ const requests = p.requests.map((request) => {
2905
+ const r = normalizeTransactRequest(
2906
+ { ...p.context, tokenAddress: request.params.token },
2907
+ request.params,
2908
+ request.deliveries,
2909
+ request.funding
2910
+ );
2911
+ if (r.capacity !== 14) throw new Error("aggregate supports only capacity 14");
2912
+ validateFundingSubmission(r.funding, p.caller, p.context.now);
2913
+ return {
2914
+ ...request,
2915
+ params: { ...request.params, feeRecipient: r.feeRecipient, feeAmount: r.feeAmount }
2916
+ };
2917
+ });
2918
+ return {
2919
+ data: new Interface3(
2920
+ invisiProtocolAbi.filter(
2921
+ (item) => item.type === "function" && item.name === "transact" && item.inputs[1]?.type === "tuple[]"
2922
+ )
2923
+ ).encodeFunctionData("transact", [p.proof, requests]),
2924
+ capacity: 14
2925
+ };
2926
+ }
2927
+ function aggregateMemberRequestHash(c, r) {
2928
+ const capacity = selectTransactCapacity(
2929
+ r.params.nullifiers.length,
2930
+ r.params.outputCommitments.length
2931
+ );
2932
+ if (capacity !== 14) throw new Error("aggregate supports only capacity 14");
2933
+ return keccak2565(
2934
+ AbiCoder5.defaultAbiCoder().encode(
2935
+ ["bytes32", "uint256", "bytes32[]"].map((parameter) => ParamType5.from(parameter)),
2936
+ [
2937
+ keccak2565(new TextEncoder().encode("InvisiAtomicBatchMemberV1")),
2938
+ BigInt(capacity),
2939
+ transactPublicInputs(c, r.params, r.deliveries, r.funding)
2940
+ ]
2941
+ )
2942
+ );
2943
+ }
2944
+ function aggregatePublicInputs(c, requests) {
2945
+ if (![4, 8, 12].includes(dense(requests, "requests").length))
2946
+ throw new Error("aggregate requires 4, 8 or 12 requests");
2947
+ return requests.flatMap((r) => {
2948
+ const context = { ...c, tokenAddress: r.params.token };
2949
+ aggregateMemberRequestHash(context, r);
2950
+ return [word(14n), ...transactPublicInputs(context, r.params, r.deliveries, r.funding)];
2951
+ });
2952
+ }
2953
+ function encodeAggregateFundingTypedData(c, r) {
2954
+ const f = normalizeFunding(r.funding);
2955
+ if (f.amount === 0n) throw new Error("funding authorization requires positive deposit");
2956
+ return {
2957
+ domain: getEIP712Domain(c),
2958
+ primaryType: "FundTransact",
2959
+ types: { FundTransact: fundingFields },
2960
+ message: {
2961
+ requestHash: aggregateMemberRequestHash(c, r),
2962
+ payer: f.payer,
2963
+ amount: f.amount,
2964
+ sigNonce: f.sigNonce,
2965
+ deadline: f.deadline
2966
+ }
2967
+ };
2968
+ }
2969
+
2970
+ // src/client/TokenProtocol.ts
2971
+ import { keccak256 as keccak2567 } from "ethers";
2972
+ import { MissingCapabilityError } from "@invisi-labs/core";
2973
+
2974
+ // src/funds/calldata.ts
2975
+ import { Interface as Interface4 } from "ethers";
2976
+ function buildTransactCalldata(p) {
2977
+ const r = normalizeTransactRequest(p.context, p.params, p.deliveries, p.funding);
2978
+ validateFundingSubmission(r.funding, p.caller, p.context.now);
2979
+ const request = {
2980
+ params: {
2981
+ token: r.tokenAddress,
2982
+ sources: r.sources,
2983
+ nullifiers: r.nullifiers,
2984
+ outputCommitments: r.outputCommitments,
2985
+ withdrawalAddress: r.withdrawalAddress,
2986
+ withdrawalAmount: r.withdrawalAmount,
2987
+ deadline: r.deadline,
2988
+ feeRecipient: r.feeRecipient,
2989
+ feeAmount: r.feeAmount
2990
+ },
2991
+ deliveries: p.deliveries,
2992
+ funding: r.funding
2993
+ };
2994
+ const abi = invisiProtocolAbi.filter(
2995
+ (item) => item.type === "function" && item.name === "transact" && item.inputs.length === 2 && item.inputs[1]?.type === "tuple"
2996
+ );
2997
+ const data = new Interface4(abi).encodeFunctionData("transact", [p.proof, request]);
2998
+ return { data, capacity: r.capacity, approveAmount: r.depositAmount };
2999
+ }
3000
+
3001
+ // src/funds/shield.ts
3002
+ import { computeNoteCommitment as computeNoteCommitment3 } from "@invisi-labs/core";
3003
+ import { AbiCoder as AbiCoder6, getAddress as getAddress6, Interface as Interface5, keccak256 as keccak2566, ParamType as ParamType6 } from "ethers";
3004
+ async function validateShieldRequest(p) {
3005
+ normalizeContext(p.context);
3006
+ if (getAddress6(p.params.token) !== getAddress6(p.context.tokenAddress))
3007
+ throw new Error("request token mismatch");
3008
+ known(p.params, ["token", "outputCommitments", "deadline", "feeRecipient", "feeAmount"]);
3009
+ checkDeadline(p.context, p.params.deadline);
3010
+ const outputs = dense(p.outputs, "outputs"), commitments = notes(p.params.outputCommitments, "outputCommitments"), cs = dense(p.deliveries, "deliveries"), f = normalizeFunding(p.funding);
3011
+ if (outputs.length < 1 || outputs.length > 10 || commitments.length !== outputs.length || cs.length !== outputs.length)
3012
+ throw new Error("shield requires 1..10 outputs and matching deliveries");
3013
+ let total = 0n;
3014
+ for (const [i, o] of outputs.entries()) {
3015
+ known(o, ["identity", "amount"], "output");
3016
+ uint(o.amount, 128, "amount");
3017
+ if (o.amount === 0n) throw new Error("amount must be positive");
3018
+ field(o.identity);
3019
+ normalizeOpaqueDelivery(cs[i], { allowEmpty: true });
3020
+ if (await computeNoteCommitment3(
3021
+ p.context.chainId,
3022
+ o.identity,
3023
+ BigInt(p.context.tokenAddress),
3024
+ o.amount
3025
+ ) !== commitments[i])
3026
+ throw new Error("output commitment mismatch");
3027
+ total += o.amount;
3028
+ }
3029
+ if (total + normalizeFee(p.params).feeAmount !== f.amount)
3030
+ throw new Error("funding must equal output total plus fee");
3031
+ return f;
3032
+ }
3033
+ async function encodeShieldFundingTypedData(p) {
3034
+ const f = await validateShieldRequest(p);
3035
+ return {
3036
+ domain: getEIP712Domain(p.context),
3037
+ primaryType: "FundShield",
3038
+ types: { FundShield: fundingFields },
3039
+ message: {
3040
+ requestHash: shieldFundingRequestHash(p.params, p.outputs, p.deliveries),
3041
+ payer: f.payer,
3042
+ amount: f.amount,
3043
+ sigNonce: f.sigNonce,
3044
+ deadline: f.deadline
3045
+ }
3046
+ };
3047
+ }
3048
+ async function buildShieldCalldata(p) {
3049
+ const f = await validateShieldRequest(p);
3050
+ validateFundingSubmission(f, p.caller, p.context.now);
3051
+ return {
3052
+ data: new Interface5(invisiProtocolAbi).encodeFunctionData("shield", [
3053
+ { ...p.params, ...normalizeFee(p.params) },
3054
+ p.outputs,
3055
+ p.deliveries,
3056
+ f
3057
+ ]),
3058
+ approveAmount: f.amount
3059
+ };
3060
+ }
3061
+ function shieldFundingRequestHash(params, outputs, cs) {
3062
+ const outputHash = keccak2566(
3063
+ AbiCoder6.defaultAbiCoder().encode(
3064
+ ["(bytes32 identity,uint256 amount)[]"].map((parameter) => ParamType6.from(parameter)),
3065
+ [outputs]
3066
+ )
3067
+ );
3068
+ return keccak2566(
3069
+ AbiCoder6.defaultAbiCoder().encode(
3070
+ [
3071
+ "bytes32",
3072
+ "(address token,bytes32[] outputCommitments,uint256 deadline,address feeRecipient,uint256 feeAmount)",
3073
+ "bytes32",
3074
+ "bytes32"
3075
+ ].map((parameter) => ParamType6.from(parameter)),
3076
+ [
3077
+ keccak2566(new TextEncoder().encode("InvisiShield")),
3078
+ { ...params, ...normalizeFee(params) },
3079
+ outputHash,
3080
+ deliveriesHash(cs)
3081
+ ]
3082
+ )
3083
+ );
3084
+ }
3085
+
3086
+ // src/client/TokenProtocol.ts
3087
+ var TokenProtocol = class {
3088
+ context;
3089
+ expectedRuntimeCodeHash;
3090
+ constructor(context, options = {}) {
3091
+ const normalized = normalizeContext(context);
3092
+ if (BigInt(normalized.protocolAddress) === 0n || BigInt(normalized.tokenAddress) === 0n) {
3093
+ throw new Error("deployment addresses must be nonzero");
3094
+ }
3095
+ if (options.expectedRuntimeCodeHash && !/^0x[0-9a-fA-F]{64}$/.test(options.expectedRuntimeCodeHash)) {
3096
+ throw new Error("invalid runtime code hash");
3097
+ }
3098
+ this.expectedRuntimeCodeHash = options.expectedRuntimeCodeHash?.toLowerCase();
3099
+ this.context = Object.freeze(normalized);
3100
+ Object.freeze(this);
3101
+ }
3102
+ async validateDeployment(reader, options = {}) {
3103
+ if ((options.requireRuntimeProfile ?? true) && !this.expectedRuntimeCodeHash) {
3104
+ throw new MissingCapabilityError("trusted deployment profile");
3105
+ }
3106
+ if (await reader.getChainId() !== this.context.chainId) {
3107
+ throw new Error("deployment chain mismatch");
3108
+ }
3109
+ const code = await reader.getCode(this.context.protocolAddress);
3110
+ if (!/^0x(?:[0-9a-fA-F]{2})+$/.test(code) || this.expectedRuntimeCodeHash && keccak2567(code) !== this.expectedRuntimeCodeHash) {
3111
+ throw new Error("deployment runtime code profile mismatch");
3112
+ }
3113
+ const tokenCode = await reader.getCode(this.context.tokenAddress);
3114
+ if (!/^0x(?:[0-9a-fA-F]{2})+$/.test(tokenCode)) {
3115
+ throw new Error("token address must have contract code");
3116
+ }
3117
+ }
3118
+ transact(request) {
3119
+ return {
3120
+ to: this.context.protocolAddress,
3121
+ value: 0n,
3122
+ ...buildTransactCalldata({ ...request, context: this.context })
3123
+ };
3124
+ }
3125
+ aggregate(request) {
3126
+ return {
3127
+ to: this.context.protocolAddress,
3128
+ value: 0n,
3129
+ ...buildTransactAggregateCalldata({ ...request, context: this.context })
3130
+ };
3131
+ }
3132
+ async shield(request) {
3133
+ return {
3134
+ to: this.context.protocolAddress,
3135
+ value: 0n,
3136
+ ...await buildShieldCalldata({ ...request, context: this.context })
3137
+ };
3138
+ }
3139
+ createCredit(request) {
3140
+ return {
3141
+ to: this.context.protocolAddress,
3142
+ value: 0n,
3143
+ ...buildCreateCreditCalldata({ ...request, context: this.context })
3144
+ };
3145
+ }
3146
+ drawCredit(request) {
3147
+ return {
3148
+ to: this.context.protocolAddress,
3149
+ value: 0n,
3150
+ ...buildCreditDrawCalldata({ ...request, context: this.context })
3151
+ };
3152
+ }
3153
+ cancelCredit(request) {
3154
+ return {
3155
+ to: this.context.protocolAddress,
3156
+ value: 0n,
3157
+ ...buildCancelCreditCalldata({ ...request, context: this.context })
3158
+ };
3159
+ }
3160
+ topUpCredit(request) {
3161
+ return {
3162
+ to: this.context.protocolAddress,
3163
+ value: 0n,
3164
+ ...buildTopUpCreditCalldata({ ...request, context: this.context })
3165
+ };
3214
3166
  }
3215
3167
  };
3216
3168
 
3217
- // src/witness.ts
3218
- var pad2 = (values, count, zero) => {
3219
- if (values.length > count) throw new Error("witness capacity exceeded");
3220
- return [...values, ...Array.from({ length: count - values.length }, zero)];
3169
+ // src/client/Protocol.ts
3170
+ var Protocol = class _Protocol {
3171
+ deployment;
3172
+ context;
3173
+ constructor(options) {
3174
+ known(options, ["deployment"], "protocol options");
3175
+ this.deployment = defineDeployment(options.deployment);
3176
+ this.context = Object.freeze({
3177
+ chainId: this.deployment.chainId,
3178
+ protocolAddress: this.deployment.protocol.address
3179
+ });
3180
+ Object.freeze(this);
3181
+ }
3182
+ /** Load the selected deployment and verify its contract against Chain RPC before returning. */
3183
+ static async fromRpc(rpc, provider, options = {}) {
3184
+ const chainId = (await provider.getNetwork()).chainId;
3185
+ if (typeof chainId !== "bigint" || chainId <= 0n || chainId >= 1n << 64n)
3186
+ throw new Error("invalid protocol chainId");
3187
+ const deployment = await rpc.getDeployment(
3188
+ chainId,
3189
+ options.signal ? { signal: options.signal } : {}
3190
+ );
3191
+ const protocol = new _Protocol({ deployment });
3192
+ if (protocol.context.chainId !== chainId) throw new Error("RPC deployment chain mismatch");
3193
+ await protocol.validateDeployment(provider);
3194
+ return protocol;
3195
+ }
3196
+ async validateDeployment(connection) {
3197
+ const reader = "getNetwork" in connection ? {
3198
+ getChainId: async () => (await connection.getNetwork()).chainId,
3199
+ getCode: async (address) => await connection.getCode(address),
3200
+ getProtocolAddressRegistry: async (address) => getAddress7(
3201
+ await new Contract(
3202
+ address,
3203
+ ["function addressRegistry() view returns (address)"],
3204
+ connection
3205
+ ).getFunction("addressRegistry")()
3206
+ ),
3207
+ getProtocolVersion: async (address) => BigInt(
3208
+ await new Contract(
3209
+ address,
3210
+ ["function VERSION() view returns (uint256)"],
3211
+ connection
3212
+ ).getFunction("VERSION")()
3213
+ )
3214
+ } : connection;
3215
+ if (await reader.getChainId() !== this.context.chainId)
3216
+ throw new Error("deployment chain mismatch");
3217
+ const code = await reader.getCode(this.context.protocolAddress);
3218
+ if (!/^0x(?:[0-9a-fA-F]{2})+$/.test(code) || keccak2568(code) !== this.deployment.protocol.runtimeCodeHash)
3219
+ throw new Error("protocol runtime code profile mismatch");
3220
+ if (reader.getProtocolVersion && await reader.getProtocolVersion(this.context.protocolAddress) !== 1n)
3221
+ throw new Error("unsupported onchain protocol version");
3222
+ const registry = this.deployment.registry;
3223
+ if (registry) {
3224
+ if (!reader.getProtocolAddressRegistry)
3225
+ throw new Error("registry binding reader capability is missing");
3226
+ if (getAddress7(
3227
+ await reader.getProtocolAddressRegistry(this.context.protocolAddress)
3228
+ ) !== getAddress7(registry.address))
3229
+ throw new Error("protocol registry binding mismatch");
3230
+ const registryCode = await reader.getCode(registry.address);
3231
+ if (!/^0x(?:[0-9a-fA-F]{2})+$/.test(registryCode) || keccak2568(registryCode) !== registry.runtimeCodeHash)
3232
+ throw new Error("registry runtime code profile mismatch");
3233
+ }
3234
+ }
3235
+ aggregate(request) {
3236
+ return {
3237
+ to: this.context.protocolAddress,
3238
+ value: 0n,
3239
+ ...buildTransactAggregateCalldata({ ...request, context: this.context })
3240
+ };
3241
+ }
3242
+ /** Build an operation context for one asset within this same deployment. */
3243
+ forToken(tokenAddress) {
3244
+ return new TokenProtocol(
3245
+ { ...this.context, tokenAddress: getAddress7(tokenAddress) },
3246
+ { expectedRuntimeCodeHash: this.deployment.protocol.runtimeCodeHash }
3247
+ );
3248
+ }
3221
3249
  };
3222
- var policyWitness = (note, usage = true) => {
3223
- const p = normalizeCreditPolicy(note);
3250
+
3251
+ // src/registry/AddressRegistry.ts
3252
+ import { getAddress as getAddress9, keccak256 as keccak2569 } from "ethers";
3253
+ import {
3254
+ MissingCapabilityError as MissingCapabilityError2,
3255
+ ReceivingAddress as ReceivingAddress3
3256
+ } from "@invisi-labs/core";
3257
+
3258
+ // src/registry/RegistrationOperation.ts
3259
+ import { Interface as Interface7, getBytes as getBytes2, hexlify as hexlify2, verifyTypedData } from "ethers";
3260
+ import { ReceivingAddress as ReceivingAddress2, createSigningRequest } from "@invisi-labs/core";
3261
+
3262
+ // src/registry/registry.ts
3263
+ import {
3264
+ Contract as Contract2,
3265
+ Interface as Interface6,
3266
+ TypedDataEncoder as TypedDataEncoder2,
3267
+ getAddress as getAddress8,
3268
+ getBytes,
3269
+ hexlify,
3270
+ isHexString as isHexString3,
3271
+ toBeHex as toBeHex2
3272
+ } from "ethers";
3273
+ import { ReceivingAddress, decodeAddress } from "@invisi-labs/core";
3274
+ var MAX_NONCE = (1n << 64n) - 1n;
3275
+ function uint64(value, name) {
3276
+ if (typeof value !== "bigint" || value < 0n || value > MAX_NONCE)
3277
+ throw new RangeError(`${name} must be uint64`);
3278
+ return value;
3279
+ }
3280
+ function hexBytes(value, length) {
3281
+ if (typeof value !== "string" || !isHexString3(value) || value.length !== 2 + length * 2)
3282
+ throw new Error(`Expected ${length} hexadecimal bytes`);
3283
+ return getBytes(value);
3284
+ }
3285
+ function normalizeKeys(ownerAddress, keys) {
3286
+ const decoded = decodeAddress(
3287
+ ReceivingAddress.from({
3288
+ ownerAddress,
3289
+ viewingPublicKey: hexBytes(keys.viewingPublicKey, 33),
3290
+ clueKey: hexBytes(keys.clueKey, 32)
3291
+ }).toString()
3292
+ );
3293
+ if (decoded.kind !== "long") throw new Error("Expected full receiving address");
3224
3294
  return {
3225
- single_limit: p.singleLimit,
3226
- daily_limit: p.dailyLimit,
3227
- monthly_limit: p.monthlyLimit,
3228
- ...usage ? {
3229
- last_day: p.lastDay,
3230
- daily_spent: p.dailySpent,
3231
- last_month: p.lastMonth,
3232
- monthly_spent: p.monthlySpent
3233
- } : {}
3295
+ viewingPublicKey: hexlify(decoded.viewingPublicKey),
3296
+ clueKey: hexlify(decoded.clueKey)
3234
3297
  };
3235
- };
3236
- var zeros = () => Array(16).fill(0n);
3237
- var zeroAuthority = () => ({
3238
- public_key_x: Array(32).fill(0),
3239
- public_key_y: Array(32).fill(0),
3240
- signature: Array(64).fill(0)
3241
- });
3242
- function check2(ok, message) {
3243
- if (!ok) throw new Error(message);
3244
3298
  }
3245
- function provingRequest(circuitName, privateInputs, expectedPublicInputs, target = "evm") {
3246
- const schema = witness_layouts_default[circuitName], key = schema.keys[target];
3247
- check2(typeof key === "string", "unpublished proof target");
3248
- check2(
3249
- expectedPublicInputs.length === schema.publicInputCount,
3250
- "public input order/width mismatch"
3251
- );
3252
- check2(
3253
- Object.keys(privateInputs).length === schema.private.length && schema.private.every((name) => Object.hasOwn(privateInputs, name)),
3254
- "private witness schema mismatch"
3255
- );
3256
- let offset = 0;
3257
- const publicInputs = {};
3258
- for (const entry of schema.public) {
3259
- const values = expectedPublicInputs.slice(offset, offset + entry.count).map((value) => {
3260
- const n = BigInt(value);
3261
- check2(n >= 0n && n < FIELD_MODULUS, "noncanonical public input");
3262
- return n;
3263
- });
3264
- publicInputs[entry.name] = entry.array ? values : values[0];
3265
- offset += entry.count;
3266
- }
3267
- check2(offset === expectedPublicInputs.length, "public input layout mismatch");
3299
+ function normalize(context, registration, options) {
3300
+ uint64(context.chainId, "chainId");
3301
+ const registryAddress = getAddress8(context.registryAddress);
3302
+ if (BigInt(registryAddress) === 0n) throw new Error("Registry address must be nonzero");
3303
+ if (!/^0x(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64})$/.test(registration.ownerAddress))
3304
+ throw new Error("Wallet identity must be an EVM owner or padded bytes32");
3305
+ const ownerAddress = decodeAddress(
3306
+ ReceivingAddress.fromIdentity(
3307
+ toBeHex2(BigInt(registration.ownerAddress), 20)
3308
+ ).toString()
3309
+ ).ownerAddress;
3310
+ const keys = normalizeKeys(ownerAddress, registration.keys);
3311
+ const nonce = uint64(registration.nonce, "nonce"), deadline = uint64(registration.deadline, "deadline");
3312
+ if (nonce === MAX_NONCE) throw new Error("Registry nonce exhausted");
3313
+ if (options.update !== void 0 && typeof options.update !== "boolean")
3314
+ throw new TypeError("update must be boolean");
3315
+ if (!options.update && nonce !== 0n)
3316
+ throw new Error("Initial registration requires nonce zero");
3317
+ if (options.expectedNonce !== void 0 && uint64(options.expectedNonce, "expectedNonce") !== nonce)
3318
+ throw new Error("Registry nonce mismatch");
3319
+ if (context.now !== void 0 && deadline < uint64(context.now, "now"))
3320
+ throw new Error("Registration expired");
3321
+ return { registryAddress, registration: { ownerAddress, keys, nonce, deadline } };
3322
+ }
3323
+ function registrationTypedData(context, registration, options = {}) {
3324
+ const { registryAddress, registration: r } = normalize(context, registration, options);
3325
+ const primaryType = options.update ? "UpdateReceivingKeys" : "RegisterReceivingKeys";
3326
+ return {
3327
+ domain: {
3328
+ name: "InvisiAddressRegistry",
3329
+ version: "1",
3330
+ chainId: context.chainId,
3331
+ verifyingContract: registryAddress
3332
+ },
3333
+ primaryType,
3334
+ types: {
3335
+ [primaryType]: [
3336
+ { name: "walletIdentity", type: "bytes32" },
3337
+ { name: "viewingPublicKey", type: "bytes" },
3338
+ { name: "clueKey", type: "bytes32" },
3339
+ { name: "nonce", type: "uint64" },
3340
+ { name: "deadline", type: "uint64" }
3341
+ ]
3342
+ },
3343
+ message: {
3344
+ walletIdentity: toBeHex2(BigInt(r.ownerAddress), 32),
3345
+ ...r.keys,
3346
+ nonce: r.nonce,
3347
+ deadline: r.deadline
3348
+ }
3349
+ };
3350
+ }
3351
+ function registrationDigest(context, registration, options = {}) {
3352
+ const typed = registrationTypedData(context, registration, options);
3353
+ return TypedDataEncoder2.hash(typed.domain, typed.types, typed.message);
3354
+ }
3355
+ function buildRegistrationCalldata(context, registration, signature, options = {}) {
3356
+ const normalized = normalize(context, registration, options);
3357
+ if (typeof signature !== "string" || !isHexString3(signature) || signature.length % 2 !== 0)
3358
+ throw new Error("Registration signature must be hexadecimal bytes");
3268
3359
  return {
3269
- circuitName,
3270
- target,
3271
- artifactSha256: schema.artifactSha256,
3272
- vkSha256: key,
3273
- expectedPublicInputs: [...expectedPublicInputs],
3274
- witness: new LocalWitness({ ...privateInputs, ...publicInputs })
3360
+ to: normalized.registryAddress,
3361
+ value: 0n,
3362
+ callData: new Interface6(invisiAddressRegistryAbi).encodeFunctionData(
3363
+ options.update ? "updateKeys" : "registerKeys",
3364
+ [
3365
+ {
3366
+ // The registry wire schema retains its deployed EIP-712/ABI field name.
3367
+ walletIdentity: toBeHex2(BigInt(normalized.registration.ownerAddress), 32),
3368
+ keys: normalized.registration.keys,
3369
+ nonce: normalized.registration.nonce,
3370
+ deadline: normalized.registration.deadline
3371
+ },
3372
+ signature
3373
+ ]
3374
+ )
3275
3375
  };
3276
3376
  }
3277
- var WitnessBuilder = class {
3278
- #protocol;
3279
- constructor(protocol) {
3280
- this.#protocol = protocol;
3281
- }
3282
- static statement(circuitName, expectedPublicInputs, target = "evm") {
3283
- const schema = witness_layouts_default[circuitName], key = schema.keys[target];
3284
- check2(
3285
- typeof key === "string" && expectedPublicInputs.length === schema.publicInputCount,
3286
- "unpublished target or statement width mismatch"
3287
- );
3377
+ function createRegistryReceivingKeyLookup(client, registryAddress) {
3378
+ const address = getAddress8(registryAddress);
3379
+ if (BigInt(address) === 0n) throw new Error("Registry address must be nonzero");
3380
+ return async (ownerAddress) => {
3381
+ const identity = decodeAddress(
3382
+ ReceivingAddress.fromIdentity(ownerAddress).toString()
3383
+ ).ownerAddress;
3384
+ const [keys, nonce] = await new Contract2(
3385
+ address,
3386
+ invisiAddressRegistryAbi,
3387
+ client
3388
+ ).getFunction("receivingKeysOf")(toBeHex2(BigInt(identity), 32));
3389
+ uint64(nonce, "registry nonce");
3390
+ const normalized = normalizeKeys(identity, keys);
3288
3391
  return {
3289
- circuitName,
3290
- target,
3291
- artifactSha256: schema.artifactSha256,
3292
- vkSha256: key,
3293
- expectedPublicInputs: [...expectedPublicInputs]
3392
+ viewingPublicKey: getBytes(normalized.viewingPublicKey),
3393
+ clueKey: getBytes(normalized.clueKey)
3294
3394
  };
3395
+ };
3396
+ }
3397
+
3398
+ // src/registry/RegistrationOperation.ts
3399
+ function check2(ok, message) {
3400
+ if (!ok) {
3401
+ throw new Error(message);
3295
3402
  }
3296
- context(note) {
3297
- const c = this.#protocol.context;
3298
- check2(
3299
- note.chainId === c.chainId && note.protocolAddress === BigInt(c.protocolAddress) && note.tokenAddress === BigInt(c.tokenAddress),
3300
- "note context mismatch"
3301
- );
3403
+ }
3404
+ var RegistrationOperation = class _RegistrationOperation {
3405
+ #registry;
3406
+ #runtime;
3407
+ #plan;
3408
+ #signature;
3409
+ #hash;
3410
+ #anchor;
3411
+ status;
3412
+ constructor(registry, runtime, plan, status, signature, hash, anchor) {
3413
+ this.#registry = registry;
3414
+ this.#runtime = { ...runtime };
3415
+ this.#plan = structuredClone(plan);
3416
+ this.status = status;
3417
+ this.#signature = signature;
3418
+ this.#hash = hash;
3419
+ this.#anchor = anchor ? { ...anchor } : void 0;
3420
+ Object.freeze(this);
3302
3421
  }
3303
- async authority(operation, capacity, words, signed, identity) {
3304
- check2(!!signed, "private wallet authorization required");
3305
- const c = this.#protocol.context, request = {
3306
- context: { chainId: c.chainId, protocolAddress: BigInt(c.protocolAddress) },
3307
- operation,
3308
- capacity,
3309
- words: words.slice(0, -2).map(BigInt)
3310
- };
3422
+ static async prepare(registry, options) {
3423
+ const runtime = { ...options };
3424
+ const update = options.update ?? false;
3425
+ check2(typeof update === "boolean", "update must be boolean");
3426
+ await registry.validateDeployment(runtime.reader);
3427
+ const block = await runtime.reader.getBlock(await runtime.reader.getLatestBlockNumber());
3428
+ const context = { ...registry.context, now: block.timestamp };
3429
+ const nonce = await runtime.reader.getRegistryNonce(
3430
+ context,
3431
+ runtime.wallet.ownerAddress,
3432
+ block
3433
+ );
3311
3434
  check2(
3312
- signed.ownerAddress === identity && signed.digest === computeWalletAuthorizationDigest3(request),
3313
- "authorization statement mismatch"
3435
+ typeof nonce === "bigint" && nonce >= 0n && nonce < (1n << 64n) - 1n,
3436
+ "invalid or exhausted registry nonce"
3314
3437
  );
3315
- const witness = signed.withWitness((value) => value);
3316
3438
  check2(
3317
- await verifyWalletAuthorization(
3318
- identity,
3319
- Uint8Array.from([4, ...witness.public_key_x, ...witness.public_key_y]),
3320
- Uint8Array.from(witness.signature),
3321
- request
3322
- ),
3323
- "invalid wallet authorization"
3439
+ update ? nonce > 0n : nonce === 0n,
3440
+ update ? "wallet is not registered" : "wallet is already registered; prepare an update"
3324
3441
  );
3325
- return witness;
3326
- }
3327
- async membership(leaf, input, sources) {
3328
- const source = sources.findIndex((value) => value.treeId === input.treeId);
3442
+ const address = ReceivingAddress2.parse(await registry.resolve(runtime.wallet.getAddress()));
3443
+ const keys = address.getReceivingKeys();
3444
+ const registration = {
3445
+ ownerAddress: runtime.wallet.ownerAddress,
3446
+ keys: {
3447
+ viewingPublicKey: hexlify2(keys.viewingPublicKey),
3448
+ clueKey: hexlify2(keys.clueKey)
3449
+ },
3450
+ nonce,
3451
+ deadline: options.deadline ?? block.timestamp + 300n
3452
+ };
3453
+ registrationTypedData(context, registration, { update, expectedNonce: nonce });
3329
3454
  check2(
3330
- source >= 0 && Number.isInteger(input.leafIndex) && input.leafIndex >= 0 && input.leafIndex < 65536 && input.path.length === 16,
3331
- "invalid membership position"
3455
+ (await runtime.reader.getBlock(block.number)).hash === block.hash,
3456
+ "canonical registry block changed while preparing"
3332
3457
  );
3333
- let root = BigInt(leaf);
3334
- for (let level = 0; level < 16; level++)
3335
- root = BigInt(
3336
- await poseidonHash(
3337
- input.leafIndex >> level & 1 ? [input.path[level], root] : [root, input.path[level]]
3338
- )
3339
- );
3340
- check2(root === BigInt(sources[source].merkleRoot), "membership root mismatch");
3341
- return source;
3342
- }
3343
- async noteLeaf(note) {
3344
- this.context(note);
3345
- return computeNoteCommitment3(
3346
- note.chainId,
3347
- await computeNoteIdentity2(BigInt(note.ownerAddress), note.nonce),
3348
- note.tokenAddress,
3349
- note.amount
3458
+ return new _RegistrationOperation(
3459
+ registry,
3460
+ runtime,
3461
+ { id: crypto.randomUUID(), context, registration, update },
3462
+ "prepared"
3350
3463
  );
3351
3464
  }
3352
- async creditLeaf(note) {
3353
- this.context(note);
3354
- return computeCreditCommitment2(
3355
- note.chainId,
3356
- note.spenderWalletIdentity,
3357
- note.ownerWalletIdentity,
3358
- note.spenderPayout,
3359
- note.ownerPayout,
3360
- note.tokenAddress,
3361
- note.amount,
3362
- note.nonce,
3363
- note.expiry,
3364
- await computeCreditSharedKeyHash2(note.creditSharedKey),
3365
- note
3366
- );
3465
+ get id() {
3466
+ return this.#plan.id;
3367
3467
  }
3368
- async noteInputs(inputs, sources, nullifiers, identity, authority) {
3369
- check2(inputs.length === nullifiers.length, "input count mismatch");
3370
- return Promise.all(
3371
- inputs.map(async (input, i) => {
3372
- check2(input.note.ownerAddress === identity, "input wallet mismatch");
3373
- const leaf = await this.noteLeaf(input.note);
3374
- check2(
3375
- await computeNoteNullifier(input.note.chainId, input.note.nonce, leaf) === nullifiers[i],
3376
- "input nullifier mismatch"
3377
- );
3378
- return BigInt(await this.membership(leaf, input, sources));
3379
- })
3380
- );
3468
+ preview() {
3469
+ return {
3470
+ id: this.id,
3471
+ status: this.status,
3472
+ chainId: this.#plan.context.chainId,
3473
+ registryAddress: this.#plan.context.registryAddress,
3474
+ ...structuredClone(this.#plan.registration),
3475
+ update: this.#plan.update
3476
+ };
3381
3477
  }
3382
- async transact(p) {
3383
- const c = this.#protocol.context, n = normalizeTransactRequest(c, p.params, p.deliveries, p.funding), words = transactUserPublicInputs(c, p.params, p.deliveries, p.funding);
3478
+ /** Export portable EIP-712 data for external signing. */
3479
+ async exportRegistrationAuthorization(options = {}) {
3480
+ check2(this.status === "prepared", "registry operation is not awaiting authorization");
3481
+ options.signal?.throwIfAborted();
3482
+ await this.validateFreshness();
3483
+ options.signal?.throwIfAborted();
3484
+ return createSigningRequest(this.typedData());
3485
+ }
3486
+ /** Validate and attach an independently obtained EOA or ERC-1271 signature. */
3487
+ async withSignature(signature, options = {}) {
3488
+ check2(this.status === "prepared", "registry operation is not awaiting authorization");
3489
+ options.signal?.throwIfAborted();
3490
+ await this.validateFreshness();
3491
+ options.signal?.throwIfAborted();
3384
3492
  check2(
3385
- p.inputs.length === n.inputCount && p.outputs.length === n.outputCount,
3386
- "note count mismatch"
3387
- );
3388
- const identity = p.inputs[0]?.note.ownerAddress;
3389
- const unsignedSweep = !!identity && !p.authorization && n.outputCount === 0 && n.depositAmount === 0n && n.feeAmount === 0n && BigInt(p.params.withdrawalAddress) === BigInt(identity);
3390
- const authority = identity && !unsignedSweep ? await this.authority("transact", n.capacity, words, p.authorization, identity) : zeroAuthority();
3391
- const sources = await this.noteInputs(
3392
- p.inputs,
3393
- p.params.sources,
3394
- p.params.nullifiers,
3395
- identity ?? "0x" + "00".repeat(32),
3396
- authority
3397
- );
3398
- const outputs = await Promise.all(
3399
- p.outputs.map(async (output, i) => {
3400
- const opening = "identity" in output ? output.note : output;
3401
- const identity2 = "identity" in output ? field(output.identity) : BigInt(
3402
- await computeNoteIdentity2(BigInt(output.ownerAddress), output.nonce)
3403
- );
3404
- const amount = uint(output.amount, 128, "output amount");
3405
- check2(identity2 !== 0n && amount !== 0n, "zero output identity or amount");
3406
- if (opening) {
3407
- this.context(opening);
3408
- check2(
3409
- opening.amount === amount && BigInt(
3410
- await computeNoteIdentity2(
3411
- BigInt(opening.ownerAddress),
3412
- opening.nonce
3413
- )
3414
- ) === identity2,
3415
- "output opening mismatch"
3416
- );
3417
- }
3418
- const commitment = await computeNoteCommitment3(
3419
- c.chainId,
3420
- word(identity2),
3421
- BigInt(c.tokenAddress),
3422
- amount
3423
- );
3424
- check2(commitment === p.params.outputCommitments[i], "output commitment mismatch");
3425
- return { identity: identity2, amount };
3426
- })
3493
+ /^0x(?:[a-fA-F0-9]{2})+$/.test(signature),
3494
+ "registry signature must be nonempty hexadecimal bytes"
3427
3495
  );
3428
- const all = [...p.inputs.map((input) => input.note), ...outputs];
3429
- return provingRequest(
3430
- `transact_user_slots${n.capacity}`,
3431
- {
3432
- input_owner_address: identity ? BigInt(identity) : 0n,
3433
- ...authority,
3434
- output_identities: pad2(
3435
- [...p.inputs.map(() => 0n), ...outputs.map((output) => output.identity)],
3436
- n.capacity,
3437
- () => 0n
3438
- ),
3439
- amounts: pad2(
3440
- all.map((note) => note.amount),
3441
- n.capacity,
3442
- () => 0n
3443
- ),
3444
- nonces: pad2(
3445
- [...p.inputs.map((input) => input.note.nonce), ...outputs.map(() => 0n)],
3446
- n.capacity,
3447
- () => 0n
3448
- ),
3449
- leaf_indices: pad2(
3450
- p.inputs.map((input) => BigInt(input.leafIndex)),
3451
- n.capacity,
3452
- () => 0n
3453
- ),
3454
- merkle_paths: pad2(
3455
- p.inputs.map((input) => [...input.path]),
3456
- n.capacity,
3457
- zeros
3496
+ const p = this.#plan;
3497
+ const owner = p.registration.ownerAddress;
3498
+ let valid = false;
3499
+ try {
3500
+ const bytes = getBytes2(signature);
3501
+ const typed = this.typedData();
3502
+ valid = bytes.length === 65 && (bytes[64] === 27 || bytes[64] === 28) && BigInt(verifyTypedData(typed.domain, typed.types, typed.message, signature)) === BigInt(owner);
3503
+ } catch {
3504
+ valid = false;
3505
+ }
3506
+ if (!valid) {
3507
+ const code = await this.#runtime.reader.getCode(owner);
3508
+ check2(/^0x(?:[a-fA-F0-9]{2})*$/.test(code), "invalid owner contract code");
3509
+ check2(code !== "0x", "invalid EOA registry signature");
3510
+ const verify = this.#runtime.reader.isValidRegistrationSignature;
3511
+ check2(verify, "ERC-1271 registry signature verification capability is missing");
3512
+ check2(
3513
+ await verify.call(
3514
+ this.#runtime.reader,
3515
+ owner,
3516
+ registrationDigest(p.context, p.registration, { update: p.update }),
3517
+ signature
3458
3518
  ),
3459
- input_source_indices: pad2(sources, n.capacity, () => 0n)
3460
- },
3461
- words,
3462
- p.target
3519
+ "invalid ERC-1271 registry signature"
3520
+ );
3521
+ }
3522
+ options.signal?.throwIfAborted();
3523
+ return new _RegistrationOperation(
3524
+ this.#registry,
3525
+ this.#runtime,
3526
+ this.#plan,
3527
+ "ready",
3528
+ signature
3463
3529
  );
3464
3530
  }
3465
- async creation(p, operation, words) {
3466
- const state = normalizeCreditDeliveryState(p.state), note = state.note;
3531
+ /** Empty signatures require the transaction sender to be the wallet owner. */
3532
+ async authorizeDirect() {
3533
+ check2(this.status === "prepared", "registry operation is not awaiting authorization");
3534
+ await this.validateFreshness();
3535
+ return new _RegistrationOperation(this.#registry, this.#runtime, this.#plan, "ready", "0x");
3536
+ }
3537
+ typedData() {
3538
+ const p = this.#plan;
3539
+ return registrationTypedData(p.context, p.registration, {
3540
+ update: p.update,
3541
+ expectedNonce: p.registration.nonce
3542
+ });
3543
+ }
3544
+ call() {
3545
+ check2(this.#signature !== void 0, "registry signature is missing");
3546
+ const p = this.#plan;
3547
+ const result = buildRegistrationCalldata(p.context, p.registration, this.#signature, {
3548
+ update: p.update,
3549
+ expectedNonce: p.registration.nonce
3550
+ });
3551
+ return { to: result.to, data: result.callData, value: result.value };
3552
+ }
3553
+ async exportTransaction() {
3467
3554
  check2(
3468
- await this.creditLeaf(note) === p.params.creditCommitment && note.ownerPayout === BigInt(p.params.owner) && note.expiry === p.params.expiry,
3469
- "Credit creation mismatch"
3470
- );
3471
- if (operation === "create-credit" && [note.lastDay, note.dailySpent, note.lastMonth, note.monthlySpent].some((v) => v !== 0n))
3472
- throw new Error("new Credit usage must be zero");
3473
- const authority = await this.authority(
3474
- operation,
3475
- 10,
3476
- words,
3477
- p.authorization,
3478
- note.ownerWalletIdentity
3479
- );
3480
- const sources = await this.noteInputs(
3481
- p.inputs,
3482
- p.params.sources,
3483
- p.params.nullifiers,
3484
- note.ownerWalletIdentity,
3485
- authority
3555
+ this.#signature !== void 0 && this.status !== "prepared",
3556
+ "registry operation is not ready"
3486
3557
  );
3487
- if (p.change)
3488
- check2(
3489
- p.change.ownerAddress === note.ownerWalletIdentity && await this.noteLeaf(p.change) === p.params.changeCommitment,
3490
- "change commitment mismatch"
3491
- );
3492
- else check2(BigInt(p.params.changeCommitment) === 0n, "missing change note");
3493
- return {
3494
- ...authority,
3495
- input_amounts: pad2(
3496
- p.inputs.map((input) => input.note.amount),
3497
- 10,
3498
- () => 0n
3499
- ),
3500
- input_nonces: pad2(
3501
- p.inputs.map((input) => input.note.nonce),
3502
- 10,
3503
- () => 0n
3504
- ),
3505
- leaf_indices: pad2(
3506
- p.inputs.map((input) => BigInt(input.leafIndex)),
3507
- 10,
3508
- () => 0n
3509
- ),
3510
- merkle_paths: pad2(
3511
- p.inputs.map((input) => [...input.path]),
3512
- 10,
3513
- zeros
3514
- ),
3515
- input_source_indices: pad2(sources, 10, () => 0n),
3516
- spender_payout: note.spenderPayout,
3517
- spender_wallet_identity: BigInt(note.spenderWalletIdentity),
3518
- owner_wallet_identity: BigInt(note.ownerWalletIdentity),
3519
- ...policyWitness(note, operation === "top-up-credit"),
3520
- credit_amount: note.amount,
3521
- credit_nonce: note.nonce,
3522
- credit_shared_key: note.creditSharedKey,
3523
- change_amount: p.change?.amount ?? 0n,
3524
- change_nonce: p.change?.nonce ?? 0n
3525
- };
3558
+ await this.validateFreshness();
3559
+ return this.call();
3526
3560
  }
3527
- async createCredit(p) {
3528
- const words = createCreditPublicInputs(
3529
- this.#protocol.context,
3530
- p.params,
3531
- p.deliveries,
3532
- p.funding
3533
- );
3534
- return provingRequest(
3535
- "create_credit",
3536
- await this.creation(p, "create-credit", words),
3537
- words
3561
+ async validateFreshness() {
3562
+ await this.#registry.validateDeployment(this.#runtime.reader);
3563
+ const block = await this.#runtime.reader.getBlock(
3564
+ await this.#runtime.reader.getLatestBlockNumber()
3538
3565
  );
3539
- }
3540
- async cancelCredit(p) {
3541
- const note = normalizeCreditDeliveryState(p.input.state).note, leaf = await this.creditLeaf(note), c = this.#protocol.context;
3566
+ const p = this.#plan;
3567
+ check2(block.timestamp <= p.registration.deadline, "registry operation has expired");
3542
3568
  check2(
3543
- await computeCreditNullifier(c.chainId, note.creditSharedKey, leaf) === p.params.nullifier && note.amount === p.params.amount && note.ownerPayout === BigInt(p.params.owner),
3544
- "Credit cancellation mismatch"
3545
- );
3546
- await this.membership(leaf, p.input, [p.params.source]);
3547
- const words = cancelCreditPublicInputs(c, p.params), authority = await this.authority(
3548
- "cancel-credit",
3549
- 1,
3550
- words,
3551
- p.authorization,
3552
- note.ownerWalletIdentity
3569
+ await this.#runtime.reader.getRegistryNonce(
3570
+ p.context,
3571
+ p.registration.ownerAddress,
3572
+ block
3573
+ ) === p.registration.nonce,
3574
+ "registry nonce changed; prepare again"
3553
3575
  );
3554
- return provingRequest(
3555
- "credit_cancel",
3556
- {
3557
- ...authority,
3558
- spender_payout: note.spenderPayout,
3559
- spender_wallet_identity: BigInt(note.spenderWalletIdentity),
3560
- owner_wallet_identity: BigInt(note.ownerWalletIdentity),
3561
- ...policyWitness(note),
3562
- input_nonce: note.nonce,
3563
- expiry: note.expiry,
3564
- credit_shared_key: note.creditSharedKey,
3565
- leaf_index: BigInt(p.input.leafIndex),
3566
- merkle_path: [...p.input.path]
3567
- },
3568
- words
3576
+ check2(
3577
+ (await this.#runtime.reader.getBlock(block.number)).hash === block.hash,
3578
+ "canonical registry block changed during authorization"
3569
3579
  );
3570
3580
  }
3571
- async drawCredit(p) {
3572
- const c = { ...this.#protocol.context, timestamp: p.timestamp }, profile = selectCreditDrawCircuit(p.inputs.length), capacity = profile.maxSize;
3581
+ /** Associate a signed/broadcast transaction; ethers owns sending and receipt waiting. */
3582
+ trackTransaction(transactionHash) {
3583
+ check2(/^0x[a-fA-F0-9]{64}$/.test(transactionHash), "invalid registry transaction hash");
3573
3584
  check2(
3574
- p.inputs.length === p.amounts.length && p.inputs.length === p.params.nullifiers.length,
3575
- "draw input count mismatch"
3585
+ this.#signature !== void 0 && ["ready", "uncertain"].includes(this.status),
3586
+ "registry operation is not ready"
3576
3587
  );
3577
- const notes2 = p.inputs.map((input) => normalizeCreditDeliveryState(input.state).note), identity = notes2[0].spenderWalletIdentity;
3578
- const words = creditDrawPublicInputs(c, p.params, p.deliveries), authority = await this.authority(
3579
- "credit-draw",
3580
- capacity,
3581
- words,
3582
- p.authorization,
3583
- identity
3588
+ return new _RegistrationOperation(
3589
+ this.#registry,
3590
+ this.#runtime,
3591
+ this.#plan,
3592
+ "uncertain",
3593
+ this.#signature,
3594
+ transactionHash
3584
3595
  );
3585
- const sourceIndices = await Promise.all(
3586
- notes2.map(async (note, i) => {
3587
- applyCreditDrawUsage(note, p.amounts[i], p.timestamp);
3588
- const leaf = await this.creditLeaf(note);
3596
+ }
3597
+ async reconcile(options = {}) {
3598
+ options.signal?.throwIfAborted();
3599
+ const { reader } = this.#runtime;
3600
+ const p = this.#plan;
3601
+ await this.#registry.validateDeployment(reader);
3602
+ if (this.#anchor && (await reader.getBlock(this.#anchor.number)).hash !== this.#anchor.hash) {
3603
+ return new _RegistrationOperation(
3604
+ this.#registry,
3605
+ this.#runtime,
3606
+ p,
3607
+ "reorged",
3608
+ this.#signature,
3609
+ this.#hash
3610
+ );
3611
+ }
3612
+ const hash = options.transactionHash ?? this.#hash;
3613
+ const receipt = hash ? await reader.getReceipt(hash) : null;
3614
+ if (receipt) {
3615
+ check2(
3616
+ receipt.transactionHash === hash && (await reader.getBlock(receipt.block.number)).hash === receipt.block.hash,
3617
+ "registry receipt is not canonical"
3618
+ );
3619
+ const transaction = await reader.getTransaction(hash);
3620
+ const call = this.call();
3621
+ check2(
3622
+ transaction && transaction.transactionHash === hash && transaction.to.toLowerCase() === call.to.toLowerCase() && transaction.data.toLowerCase() === call.data.toLowerCase() && transaction.value === 0n && (this.#signature !== "0x" || BigInt(transaction.from) === BigInt(p.registration.ownerAddress)),
3623
+ "registry transaction differs from frozen request"
3624
+ );
3625
+ check2(
3626
+ receipt.status === "success" || receipt.status === "reverted",
3627
+ "registry execution status is missing"
3628
+ );
3629
+ if (receipt.status === "success") {
3630
+ const found = receipt.logs.some((log) => {
3631
+ if (log.address.toLowerCase() !== p.context.registryAddress.toLowerCase() || log.transactionHash !== hash) {
3632
+ return false;
3633
+ }
3634
+ try {
3635
+ const event = ((decoded) => ({
3636
+ eventName: decoded.name,
3637
+ args: decoded.args.toObject()
3638
+ }))(
3639
+ new Interface7(invisiAddressRegistryAbi).parseLog({
3640
+ data: log.data,
3641
+ topics: log.topics
3642
+ })
3643
+ );
3644
+ return event.eventName === "ReceivingKeysSet" && BigInt(event.args.walletIdentity) === BigInt(p.registration.ownerAddress) && event.args.viewingPublicKey.toLowerCase() === p.registration.keys.viewingPublicKey.toLowerCase() && event.args.clueKey.toLowerCase() === p.registration.keys.clueKey.toLowerCase() && event.args.nextNonce === p.registration.nonce + 1n;
3645
+ } catch {
3646
+ return false;
3647
+ }
3648
+ });
3649
+ check2(found, "registry receipt has no matching key update event");
3589
3650
  check2(
3590
- note.spenderWalletIdentity === identity && note.spenderPayout === BigInt(p.params.spender) && note.expiry === p.params.expiries[i] && p.amounts[i] > 0n && p.amounts[i] <= note.amount && await computeCreditNullifier(c.chainId, note.creditSharedKey, leaf) === p.params.nullifiers[i],
3591
- "Credit draw mismatch"
3651
+ (await reader.getBlock(receipt.block.number)).hash === receipt.block.hash,
3652
+ "registry block changed during confirmation"
3592
3653
  );
3593
- return BigInt(await this.membership(leaf, p.inputs[i], p.params.sources));
3594
- })
3595
- );
3596
- return provingRequest(
3597
- profile.name,
3598
- {
3599
- ...authority,
3600
- ...Object.fromEntries(
3601
- Object.entries({
3602
- singleLimit: "single_limits",
3603
- dailyLimit: "daily_limits",
3604
- monthlyLimit: "monthly_limits",
3605
- lastDay: "last_days",
3606
- dailySpent: "daily_spents",
3607
- lastMonth: "last_months",
3608
- monthlySpent: "monthly_spents"
3609
- }).map(([key, name]) => [
3610
- name,
3611
- pad2(
3612
- notes2.map(
3613
- (note) => normalizeCreditPolicy(note)[key]
3614
- ),
3615
- capacity,
3616
- () => 0n
3617
- )
3618
- ])
3619
- ),
3620
- spender_wallet_identity: BigInt(identity),
3621
- owner_payouts: pad2(
3622
- notes2.map((note) => note.ownerPayout),
3623
- capacity,
3624
- () => 0n
3625
- ),
3626
- owner_wallet_identities: pad2(
3627
- notes2.map((note) => BigInt(note.ownerWalletIdentity)),
3628
- capacity,
3629
- () => 0n
3630
- ),
3631
- input_amounts: pad2(
3632
- notes2.map((note) => note.amount),
3633
- capacity,
3634
- () => 0n
3635
- ),
3636
- input_nonces: pad2(
3637
- notes2.map((note) => note.nonce),
3638
- capacity,
3639
- () => 0n
3640
- ),
3641
- credit_shared_keys: pad2(
3642
- notes2.map((note) => note.creditSharedKey),
3643
- capacity,
3644
- () => 0n
3645
- ),
3646
- leaf_indices: pad2(
3647
- p.inputs.map((input) => BigInt(input.leafIndex)),
3648
- capacity,
3649
- () => 0n
3650
- ),
3651
- merkle_paths: pad2(
3652
- p.inputs.map((input) => [...input.path]),
3653
- capacity,
3654
- zeros
3655
- ),
3656
- input_source_indices: pad2(sourceIndices, capacity, () => 0n),
3657
- draw_amounts: pad2(p.amounts, capacity, () => 0n)
3658
- },
3659
- words
3660
- );
3661
- }
3662
- async topUpCredit(p) {
3663
- const c = this.#protocol.context, previous = normalizeCreditDeliveryState(p.previous.state), next = normalizeCreditDeliveryState(p.state), old = previous.note, note = next.note, leaf = await this.creditLeaf(old);
3664
- for (const field2 of [
3665
- "spenderWalletIdentity",
3666
- "ownerWalletIdentity",
3667
- "spenderPayout",
3668
- "ownerPayout",
3669
- "creditSharedKey",
3670
- "lastDay",
3671
- "dailySpent",
3672
- "lastMonth",
3673
- "monthlySpent"
3674
- ])
3675
- check2(old[field2] === note[field2], "Credit role or payout changed");
3676
- check2(
3677
- JSON.stringify(previous.ownerReceivingKeys) === JSON.stringify(next.ownerReceivingKeys) && JSON.stringify(previous.spenderReceivingKeys) === JSON.stringify(next.spenderReceivingKeys),
3678
- "Credit receiving snapshots changed"
3679
- );
3680
- check2(
3681
- old.amount < note.amount && old.expiry <= note.expiry && old.expiry === p.params.previousExpiry && (c.now === void 0 || old.expiry > c.now),
3682
- "invalid Credit top-up"
3683
- );
3654
+ options.signal?.throwIfAborted();
3655
+ return new _RegistrationOperation(
3656
+ this.#registry,
3657
+ this.#runtime,
3658
+ p,
3659
+ "confirmed",
3660
+ this.#signature,
3661
+ hash,
3662
+ receipt.block
3663
+ );
3664
+ }
3665
+ }
3666
+ const head = await reader.getBlock(await reader.getLatestBlockNumber());
3667
+ const nonce = await reader.getRegistryNonce(p.context, p.registration.ownerAddress, head);
3684
3668
  check2(
3685
- await computeCreditNullifier(c.chainId, old.creditSharedKey, leaf) === p.params.creditNullifier,
3686
- "previous Credit nullifier mismatch"
3669
+ (await reader.getBlock(head.number)).hash === head.hash,
3670
+ "canonical registry block changed during reconciliation"
3687
3671
  );
3688
- await this.membership(leaf, p.previous, [p.params.creditSource]);
3689
- const words = topUpCreditPublicInputs(c, p.params, p.deliveries, p.funding), base = await this.creation({ ...p, params: p.params.creation }, "top-up-credit", words);
3690
- return provingRequest(
3691
- "credit_top_up",
3692
- {
3693
- ...base,
3694
- previous_single_limit: old.singleLimit,
3695
- previous_daily_limit: old.dailyLimit,
3696
- previous_monthly_limit: old.monthlyLimit,
3697
- previous_credit_amount: old.amount,
3698
- previous_credit_nonce: old.nonce,
3699
- previous_credit_leaf_index: BigInt(p.previous.leafIndex),
3700
- previous_credit_merkle_path: [...p.previous.path]
3701
- },
3702
- words
3672
+ return new _RegistrationOperation(
3673
+ this.#registry,
3674
+ this.#runtime,
3675
+ p,
3676
+ head.timestamp > p.registration.deadline ? "expired" : nonce !== p.registration.nonce ? "failed" : "uncertain",
3677
+ this.#signature,
3678
+ hash
3703
3679
  );
3704
3680
  }
3681
+ toJSON() {
3682
+ return {
3683
+ id: this.id,
3684
+ kind: this.#plan.update ? "update-receiving-keys" : "register-receiving-keys",
3685
+ status: this.status
3686
+ };
3687
+ }
3688
+ [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
3689
+ return this.toJSON();
3690
+ }
3691
+ };
3692
+
3693
+ // src/registry/AddressRegistry.ts
3694
+ var AddressRegistry = class {
3695
+ context;
3696
+ #lookup;
3697
+ expectedRuntimeCodeHash;
3698
+ constructor(context, options = {}) {
3699
+ if (typeof context.chainId !== "bigint" || context.chainId < 0n || context.chainId >= 1n << 64n || BigInt(getAddress9(context.registryAddress)) === 0n) {
3700
+ throw new Error("invalid registry context");
3701
+ }
3702
+ this.context = Object.freeze({
3703
+ ...context,
3704
+ registryAddress: getAddress9(context.registryAddress)
3705
+ });
3706
+ this.#lookup = options.lookup;
3707
+ this.expectedRuntimeCodeHash = options.expectedRuntimeCodeHash?.toLowerCase();
3708
+ if (this.expectedRuntimeCodeHash && !/^0x[0-9a-f]{64}$/.test(this.expectedRuntimeCodeHash)) {
3709
+ throw new Error("invalid registry runtime profile");
3710
+ }
3711
+ Object.freeze(this);
3712
+ }
3713
+ async validateDeployment(reader) {
3714
+ if (!this.expectedRuntimeCodeHash) {
3715
+ throw new MissingCapabilityError2("trusted registry deployment profile");
3716
+ }
3717
+ if (await reader.getChainId() !== this.context.chainId) {
3718
+ throw new Error("registry chain mismatch");
3719
+ }
3720
+ const code = await reader.getCode(this.context.registryAddress);
3721
+ if (!/^0x(?:[0-9a-fA-F]{2})+$/.test(code) || keccak2569(code) !== this.expectedRuntimeCodeHash) {
3722
+ throw new Error("registry runtime profile mismatch");
3723
+ }
3724
+ }
3725
+ prepareRegistration(options) {
3726
+ return RegistrationOperation.prepare(this, options);
3727
+ }
3728
+ async resolve(address) {
3729
+ const receiving = ReceivingAddress3.parse(address);
3730
+ if (receiving.kind === "short" && !this.#lookup) {
3731
+ throw new MissingCapabilityError2("registry reader");
3732
+ }
3733
+ return (await receiving.resolve(this.#lookup)).toString();
3734
+ }
3735
+ typedData(registration, options) {
3736
+ return registrationTypedData(this.context, registration, options);
3737
+ }
3738
+ digest(registration, options) {
3739
+ return registrationDigest(this.context, registration, options);
3740
+ }
3741
+ buildCall(registration, signature, options) {
3742
+ return buildRegistrationCalldata(this.context, registration, signature, options);
3743
+ }
3705
3744
  };
3706
3745
 
3707
- // src/factory.ts
3708
- import { Contract as Contract3, getAddress as getAddress9 } from "ethers";
3746
+ // src/verification/verify.ts
3747
+ import {
3748
+ computeCreditCommitment as computeCreditCommitment2,
3749
+ computeCreditSharedKeyHash as computeCreditSharedKeyHash2,
3750
+ computeNoteCommitment as computeNoteCommitment4,
3751
+ computeNoteIdentity as computeNoteIdentity3,
3752
+ FIELD_MODULUS as FIELD_MODULUS2
3753
+ } from "@invisi-labs/core";
3754
+ async function verifyNoteCommitment(params) {
3755
+ const expected = await computeNoteCommitment4(
3756
+ params.chainId,
3757
+ await computeNoteIdentity3(BigInt(params.ownerAddress), BigInt(params.nonce)),
3758
+ BigInt(params.tokenAddress),
3759
+ params.amount
3760
+ );
3761
+ return BigInt(expected) === BigInt(params.noteCommitment);
3762
+ }
3763
+ async function verifyCreditCommitment(params) {
3764
+ if (params.creditSharedKey <= 0n || params.creditSharedKey >= FIELD_MODULUS2)
3765
+ throw new RangeError("creditSharedKey must be a nonzero canonical Field");
3766
+ const expected = await computeCreditCommitment2(
3767
+ params.chainId,
3768
+ params.spenderWalletIdentity,
3769
+ params.ownerWalletIdentity,
3770
+ BigInt(params.spenderPayout),
3771
+ BigInt(params.ownerPayout),
3772
+ BigInt(params.tokenAddress),
3773
+ params.amount,
3774
+ params.nonce,
3775
+ params.expiry,
3776
+ await computeCreditSharedKeyHash2(params.creditSharedKey),
3777
+ params
3778
+ );
3779
+ return BigInt(expected) === BigInt(params.creditCommitment);
3780
+ }
3781
+
3782
+ // src/factory/factory.ts
3783
+ import { Contract as Contract3, getAddress as getAddress10 } from "ethers";
3709
3784
  async function resolveInstanceAddress(client, factoryAddress, salt) {
3710
3785
  const instance = await new Contract3(
3711
- getAddress9(factoryAddress),
3786
+ getAddress10(factoryAddress),
3712
3787
  invisiFactoryAbi,
3713
3788
  client
3714
3789
  ).getFunction("getInstanceAddress")(salt);
3715
- return getAddress9(instance);
3790
+ return getAddress10(instance);
3716
3791
  }
3717
3792
  export {
3718
3793
  AddressRegistry,