@invisi-labs/sdk 1.1.5 → 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/broadcaster-preparation-DMxxZ_1Q.d.cts +587 -0
- package/dist/broadcaster-preparation-DMxxZ_1Q.d.ts +587 -0
- package/dist/broadcaster-preparation-RMWQFYNL.js +11 -0
- package/dist/broadcaster-preparation-UGOTO4SX.cjs +11 -0
- package/dist/broadcaster-preparation-UGOTO4SX.cjs.map +1 -0
- package/dist/chunk-7F7ZZW3L.cjs +374 -0
- package/dist/chunk-7F7ZZW3L.cjs.map +1 -0
- package/dist/chunk-EHP5TBC6.js +3565 -0
- package/dist/chunk-EHP5TBC6.js.map +1 -0
- package/dist/chunk-GTPUYPQL.js +128 -0
- package/dist/chunk-GTPUYPQL.js.map +1 -0
- package/dist/chunk-ITTGBIG2.js +1877 -0
- package/dist/chunk-ITTGBIG2.js.map +1 -0
- package/dist/chunk-J2JH6HIG.cjs +1877 -0
- package/dist/chunk-J2JH6HIG.cjs.map +1 -0
- package/dist/chunk-LOU7AP2B.cjs +128 -0
- package/dist/chunk-LOU7AP2B.cjs.map +1 -0
- package/dist/chunk-RHNIHI4P.cjs +3565 -0
- package/dist/chunk-RHNIHI4P.cjs.map +1 -0
- package/dist/{chunk-4HMM7ZKY.js → chunk-XHKQ2BO2.js} +19 -19
- package/dist/chunk-XHKQ2BO2.js.map +1 -0
- package/dist/ethers.cjs +11 -385
- package/dist/ethers.cjs.map +1 -1
- package/dist/ethers.d.cts +4 -5
- package/dist/ethers.d.ts +4 -5
- package/dist/ethers.js +1 -1
- package/dist/index.cjs +441 -6217
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +109 -491
- package/dist/index.d.ts +109 -491
- package/dist/index.js +518 -599
- package/dist/index.js.map +1 -1
- package/dist/receipt-logs-CvC951p7.d.cts +318 -0
- package/dist/receipt-logs-m0-bhaxW.d.ts +318 -0
- package/dist/session-support.cjs +168 -0
- package/dist/session-support.cjs.map +1 -0
- package/dist/session-support.d.cts +252 -0
- package/dist/session-support.d.ts +252 -0
- package/dist/session-support.js +168 -0
- package/dist/session-support.js.map +1 -0
- package/package.json +10 -20
- package/dist/chunk-4HMM7ZKY.js.map +0 -1
- package/dist/chunk-BE52B32Z.js +0 -5490
- package/dist/chunk-BE52B32Z.js.map +0 -1
- package/dist/indexeddb.cjs +0 -5636
- package/dist/indexeddb.cjs.map +0 -1
- package/dist/indexeddb.d.cts +0 -20
- package/dist/indexeddb.d.ts +0 -20
- package/dist/indexeddb.js +0 -70
- package/dist/indexeddb.js.map +0 -1
- package/dist/memory.cjs +0 -5644
- package/dist/memory.cjs.map +0 -1
- package/dist/memory.d.cts +0 -12
- package/dist/memory.d.ts +0 -12
- package/dist/memory.js +0 -10
- package/dist/operation-types-D7I405FX.d.ts +0 -275
- package/dist/operation-types-DIazgS2d.d.cts +0 -275
- package/dist/sqlite.cjs +0 -5620
- package/dist/sqlite.cjs.map +0 -1
- package/dist/sqlite.d.cts +0 -28
- package/dist/sqlite.d.ts +0 -28
- package/dist/sqlite.js +0 -54
- package/dist/sqlite.js.map +0 -1
- package/dist/state-AUnz7TG7.d.cts +0 -78
- package/dist/state-AUnz7TG7.d.ts +0 -78
- package/dist/store-BMN8kd0c.d.ts +0 -42
- package/dist/store-Cz75STRl.d.cts +0 -42
- /package/dist/{memory.js.map → broadcaster-preparation-RMWQFYNL.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,694 +1,613 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
AddressRegistry,
|
|
3
|
+
BroadcasterClient,
|
|
4
|
+
BroadcasterRpcError,
|
|
5
|
+
Invisi,
|
|
6
|
+
PreparedTransaction,
|
|
7
|
+
TransactionOutput,
|
|
8
|
+
broadcasterQuoteDomain,
|
|
9
|
+
broadcasterQuoteTypes,
|
|
10
|
+
broadcasterRequestHash,
|
|
11
|
+
broadcasterTransactInterface,
|
|
12
|
+
decodeBroadcasterIntent,
|
|
10
13
|
decryptCandidates,
|
|
11
14
|
defineGasBenchmarks,
|
|
12
|
-
|
|
13
|
-
encodeOperation,
|
|
14
|
-
estimateGasReference,
|
|
15
|
-
fundingSigningRequest,
|
|
16
|
-
operationPreview,
|
|
17
|
-
positiveAmount,
|
|
18
|
-
prepareWithErrors,
|
|
19
|
-
proveOperation,
|
|
20
|
-
publicProof,
|
|
15
|
+
extractDeliveryCandidates,
|
|
21
16
|
readProtocolLogs,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
serializeState,
|
|
25
|
-
statement,
|
|
26
|
-
walletBalance,
|
|
27
|
-
walletSigningRequest
|
|
28
|
-
} from "./chunk-BE52B32Z.js";
|
|
17
|
+
verifyBroadcasterQuote
|
|
18
|
+
} from "./chunk-EHP5TBC6.js";
|
|
29
19
|
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
InvisiError,
|
|
21
|
+
NoteOutput,
|
|
22
|
+
decodePaymentNote,
|
|
23
|
+
decodePaymentRequest,
|
|
24
|
+
encodePaymentNote,
|
|
25
|
+
encodePaymentRequest
|
|
26
|
+
} from "./chunk-ITTGBIG2.js";
|
|
27
|
+
import {
|
|
28
|
+
EthersChainReader
|
|
29
|
+
} from "./chunk-XHKQ2BO2.js";
|
|
34
30
|
|
|
35
|
-
// src/
|
|
36
|
-
import {
|
|
31
|
+
// src/index.ts
|
|
32
|
+
import {
|
|
33
|
+
Detector,
|
|
34
|
+
Disclosure,
|
|
35
|
+
MAX_MEMO_BYTES,
|
|
36
|
+
MissingCapabilityError,
|
|
37
|
+
NullifierCapability,
|
|
38
|
+
PublicWallet,
|
|
39
|
+
ViewingCapability,
|
|
40
|
+
Wallet as Wallet2,
|
|
41
|
+
WalletAuthorization,
|
|
42
|
+
computeCreditNullifier,
|
|
43
|
+
creditPeriods,
|
|
44
|
+
creditV1,
|
|
45
|
+
decodeDeliveryHeader,
|
|
46
|
+
decodeMemoText,
|
|
47
|
+
encodeMemo,
|
|
48
|
+
fundsV1
|
|
49
|
+
} from "@invisi-labs/core";
|
|
50
|
+
import { RegistrationOperation } from "@invisi-labs/protocol";
|
|
37
51
|
|
|
38
|
-
// src/
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
function check(ok, message) {
|
|
42
|
-
if (!ok) throw new Error(message);
|
|
52
|
+
// src/disclosure/DisclosureVerifier.ts
|
|
53
|
+
function check(value, message) {
|
|
54
|
+
if (!value) throw new Error(message);
|
|
43
55
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
get kind() {
|
|
70
|
-
return this.#saved.plan.kind;
|
|
71
|
-
}
|
|
72
|
-
estimateGas() {
|
|
73
|
-
return estimateGasReference(
|
|
74
|
-
this.#saved.plan,
|
|
75
|
-
this.#runtime.options.protocol,
|
|
76
|
-
this.#runtime.options.gasBenchmarks
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
preview() {
|
|
80
|
-
return operationPreview(this.#saved.plan, this.status);
|
|
81
|
-
}
|
|
82
|
-
current() {
|
|
56
|
+
function reference(value) {
|
|
57
|
+
check(/^0x[0-9a-fA-F]{64}$/.test(value.transactionHash), "invalid disclosure transaction hash");
|
|
58
|
+
check(
|
|
59
|
+
typeof value.treeId === "bigint" && value.treeId > 0n && value.treeId < 1n << 64n,
|
|
60
|
+
"invalid disclosure tree ID"
|
|
61
|
+
);
|
|
62
|
+
check(
|
|
63
|
+
Number.isSafeInteger(value.leafIndex) && value.leafIndex >= 0 && value.leafIndex < 65536,
|
|
64
|
+
"invalid disclosure leaf index"
|
|
65
|
+
);
|
|
66
|
+
check(["funds", "owner", "spender"].includes(value.role), "invalid disclosure reference role");
|
|
67
|
+
return {
|
|
68
|
+
transactionHash: value.transactionHash.toLowerCase(),
|
|
69
|
+
treeId: value.treeId,
|
|
70
|
+
leafIndex: value.leafIndex,
|
|
71
|
+
role: value.role
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
var DisclosureVerifier = class {
|
|
75
|
+
#protocol;
|
|
76
|
+
#reader;
|
|
77
|
+
#confirmations;
|
|
78
|
+
constructor(options) {
|
|
79
|
+
const confirmations = options.confirmations ?? 1;
|
|
83
80
|
check(
|
|
84
|
-
|
|
85
|
-
"
|
|
81
|
+
Number.isSafeInteger(confirmations) && confirmations >= 1,
|
|
82
|
+
"invalid disclosure confirmations"
|
|
86
83
|
);
|
|
84
|
+
this.#protocol = options.protocol;
|
|
85
|
+
this.#reader = "provider" in options ? new EthersChainReader(options.provider) : options.reader;
|
|
86
|
+
this.#confirmations = BigInt(confirmations);
|
|
87
|
+
Object.freeze(this);
|
|
87
88
|
}
|
|
88
|
-
async
|
|
89
|
-
|
|
90
|
-
this.
|
|
91
|
-
check(allowed.includes(this.status), `invalid transaction stage for ${stage}`);
|
|
92
|
-
this.#lifecycle.busy = true;
|
|
93
|
-
const saved = structuredClone(this.#saved);
|
|
94
|
-
try {
|
|
95
|
-
const next = await work(saved);
|
|
96
|
-
signal?.throwIfAborted();
|
|
97
|
-
saved.generation++;
|
|
98
|
-
this.#lifecycle.generation = saved.generation;
|
|
99
|
-
return new _PreparedTransaction(
|
|
100
|
-
this.#runtime,
|
|
101
|
-
saved,
|
|
102
|
-
next.status,
|
|
103
|
-
this.#lifecycle,
|
|
104
|
-
next.authorization ?? this.#authorization
|
|
105
|
-
);
|
|
106
|
-
} finally {
|
|
107
|
-
this.#lifecycle.busy = false;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
/** Public EIP-712 request; signing transport and key custody belong to the caller. */
|
|
111
|
-
async exportWalletAuthorization(options = {}) {
|
|
112
|
-
options.signal?.throwIfAborted();
|
|
113
|
-
this.current();
|
|
89
|
+
async barrier(block) {
|
|
90
|
+
await this.#protocol.validateDeployment(this.#reader);
|
|
91
|
+
const canonical = await this.#reader.getBlock(block.number);
|
|
114
92
|
check(
|
|
115
|
-
|
|
116
|
-
"
|
|
93
|
+
canonical.number === block.number && canonical.hash.toLowerCase() === block.hash.toLowerCase(),
|
|
94
|
+
"disclosure canonical block changed; reorg verification required"
|
|
117
95
|
);
|
|
96
|
+
const head = await this.#reader.getLatestBlockNumber();
|
|
118
97
|
check(
|
|
119
|
-
|
|
120
|
-
"
|
|
98
|
+
head >= block.number && head - block.number + 1n >= this.#confirmations,
|
|
99
|
+
"insufficient disclosure confirmations"
|
|
121
100
|
);
|
|
122
|
-
return walletSigningRequest(this.#saved.plan);
|
|
123
|
-
}
|
|
124
|
-
/** Attach an external EOA signature, retaining its witness only at the private proving boundary. */
|
|
125
|
-
async withWalletSignature(signature, options = {}) {
|
|
126
|
-
options.signal?.throwIfAborted();
|
|
127
|
-
this.current();
|
|
128
101
|
check(
|
|
129
|
-
|
|
130
|
-
"
|
|
102
|
+
await this.#reader.getChainId() === this.#protocol.context.chainId,
|
|
103
|
+
"disclosure chain changed during verification"
|
|
131
104
|
);
|
|
105
|
+
return head - block.number + 1n;
|
|
106
|
+
}
|
|
107
|
+
/** Resolve the exact inserted output and its delivery from a successful canonical receipt. */
|
|
108
|
+
async readOutput(value) {
|
|
109
|
+
const selected = reference(value);
|
|
110
|
+
await this.#protocol.validateDeployment(this.#reader);
|
|
111
|
+
const received = await this.#reader.getReceipt(selected.transactionHash);
|
|
112
|
+
check(received, "disclosure transaction receipt is unavailable");
|
|
113
|
+
const receipt = structuredClone(received);
|
|
114
|
+
check(receipt.status === "success", "disclosure requires a successful transaction receipt");
|
|
132
115
|
check(
|
|
133
|
-
|
|
134
|
-
"
|
|
116
|
+
receipt.transactionHash.toLowerCase() === selected.transactionHash,
|
|
117
|
+
"disclosure receipt transaction mismatch"
|
|
135
118
|
);
|
|
136
|
-
const authorization = await acceptWalletSignature(this.#saved.plan, signature);
|
|
137
|
-
return this.stage(
|
|
138
|
-
"wallet authorization",
|
|
139
|
-
["prepared", "authorized"],
|
|
140
|
-
async () => ({ status: "authorized", authorization }),
|
|
141
|
-
options.signal
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
/** Export only after proving when the funding authorization binds an actual proof hash. */
|
|
145
|
-
async exportFundingAuthorization(options = {}) {
|
|
146
|
-
options.signal?.throwIfAborted();
|
|
147
|
-
this.current();
|
|
148
|
-
check(
|
|
149
|
-
this.status === (this.kind === "shield" ? "prepared" : "proved"),
|
|
150
|
-
"operation is not awaiting funding authorization or proof"
|
|
151
|
-
);
|
|
152
|
-
const request = await fundingSigningRequest(this.#saved, this.#runtime);
|
|
153
|
-
options.signal?.throwIfAborted();
|
|
154
|
-
this.current();
|
|
155
|
-
return request;
|
|
156
|
-
}
|
|
157
|
-
/** Verify an external signature against this handle's own frozen operation. */
|
|
158
|
-
async withFundingSignature(signature, options = {}) {
|
|
159
|
-
options.signal?.throwIfAborted();
|
|
160
|
-
this.current();
|
|
161
119
|
check(
|
|
162
|
-
|
|
163
|
-
"
|
|
120
|
+
typeof receipt.block.number === "bigint" && receipt.block.number >= 0n && /^0x[0-9a-fA-F]{64}$/.test(receipt.block.hash),
|
|
121
|
+
"invalid disclosure receipt block"
|
|
164
122
|
);
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
async (saved) => {
|
|
170
|
-
saved.plan.funding.signature = verified;
|
|
171
|
-
if (saved.plan.statement?.target !== "noir-recursive") {
|
|
172
|
-
saved.call = await this.encode(saved);
|
|
173
|
-
}
|
|
174
|
-
return { status: "ready" };
|
|
175
|
-
},
|
|
176
|
-
options.signal
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
async prove(backendOrOptions = {}, suppliedOptions = {}) {
|
|
180
|
-
const options = { ..."prove" in backendOrOptions ? suppliedOptions : backendOrOptions };
|
|
181
|
-
const plan = this.#saved.plan;
|
|
182
|
-
check(plan.kind !== "shield", "shield requires no proof");
|
|
183
|
-
if (requiresAuthorization(plan)) {
|
|
184
|
-
check(
|
|
185
|
-
this.#authorization,
|
|
186
|
-
"local wallet authorization required; authorize again after recovery"
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
const prover = "prove" in backendOrOptions ? backendOrOptions : this.#runtime.options.prover;
|
|
190
|
-
if (!prover) {
|
|
191
|
-
throw new MissingCapabilityError("prover");
|
|
192
|
-
}
|
|
193
|
-
return this.stage(
|
|
194
|
-
"proof",
|
|
195
|
-
requiresAuthorization(plan) ? ["authorized"] : ["prepared"],
|
|
196
|
-
async (saved) => {
|
|
197
|
-
const p = saved.plan;
|
|
198
|
-
saved.proof = await proveOperation(
|
|
199
|
-
this.#runtime.options.protocol,
|
|
200
|
-
p,
|
|
201
|
-
this.#authorization,
|
|
202
|
-
prover,
|
|
203
|
-
options
|
|
204
|
-
);
|
|
205
|
-
if (p.fundingMode === "signature") {
|
|
206
|
-
return { status: "proved" };
|
|
207
|
-
}
|
|
208
|
-
if (p.statement?.target !== "noir-recursive") {
|
|
209
|
-
saved.call = await this.encode(saved);
|
|
210
|
-
}
|
|
211
|
-
return { status: "ready" };
|
|
212
|
-
},
|
|
213
|
-
options.signal
|
|
123
|
+
const { context } = this.#protocol;
|
|
124
|
+
const logs = readProtocolLogs(receipt, context.protocolAddress);
|
|
125
|
+
const candidates = extractDeliveryCandidates(logs, context, receipt.block.number).filter(
|
|
126
|
+
(candidate2) => candidate2.treeId === selected.treeId && candidate2.leafIndex === selected.leafIndex && candidate2.role === selected.role && candidate2.tokenAddress.toLowerCase() === context.tokenAddress.toLowerCase()
|
|
214
127
|
);
|
|
215
|
-
}
|
|
216
|
-
encode(saved) {
|
|
217
|
-
return encodeOperation(saved);
|
|
218
|
-
}
|
|
219
|
-
/** Shield caller funding needs no wallet signing or proof; this step only encodes its frozen request. */
|
|
220
|
-
async ready() {
|
|
221
128
|
check(
|
|
222
|
-
|
|
223
|
-
"
|
|
129
|
+
candidates.length === 1,
|
|
130
|
+
"disclosure output has no unique canonical insertion and delivery"
|
|
224
131
|
);
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
132
|
+
const candidate = candidates[0];
|
|
133
|
+
const confirmations = await this.barrier(receipt.block);
|
|
134
|
+
return Object.freeze({
|
|
135
|
+
reference: Object.freeze(selected),
|
|
136
|
+
block: Object.freeze({ ...receipt.block }),
|
|
137
|
+
confirmations,
|
|
138
|
+
output: Object.freeze({
|
|
139
|
+
chainId: context.chainId,
|
|
140
|
+
protocolAddress: BigInt(context.protocolAddress),
|
|
141
|
+
tokenAddress: BigInt(context.tokenAddress),
|
|
142
|
+
outputCommitment: candidate.commitment,
|
|
143
|
+
role: selected.role,
|
|
144
|
+
ciphertext: candidate.delivery.ciphertext
|
|
145
|
+
})
|
|
228
146
|
});
|
|
229
147
|
}
|
|
230
|
-
async
|
|
231
|
-
this.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
exportAggregateMember() {
|
|
245
|
-
this.current();
|
|
246
|
-
const saved = this.#saved, plan = saved.plan;
|
|
247
|
-
check(
|
|
248
|
-
this.status === "ready" && plan.params && saved.proof && plan.statement?.target === "noir-recursive" && plan.statement.circuitName === "transact_user_slots14",
|
|
249
|
-
"ready recursive slots14 transaction required"
|
|
250
|
-
);
|
|
251
|
-
sameStatement(saved.proof, statement(plan));
|
|
252
|
-
check(
|
|
253
|
-
saved.proof.proofHex.length === 2 + 458 * 64,
|
|
254
|
-
"recursive member proof must have 458 fields"
|
|
255
|
-
);
|
|
256
|
-
return {
|
|
257
|
-
bundle: publicProof(saved.proof),
|
|
258
|
-
statement: structuredClone(plan.statement),
|
|
259
|
-
context: { ...plan.context },
|
|
260
|
-
request: {
|
|
261
|
-
params: structuredClone(plan.params),
|
|
262
|
-
deliveries: plan.outputs.map((output) => structuredClone(output.delivery)),
|
|
263
|
-
funding: normalizeFunding(plan.funding)
|
|
264
|
-
}
|
|
265
|
-
};
|
|
148
|
+
async verify(disclosure, value) {
|
|
149
|
+
const evidence = await this.readOutput(value);
|
|
150
|
+
const result = await disclosure.verify(evidence.output);
|
|
151
|
+
const confirmations = await this.barrier(evidence.block);
|
|
152
|
+
return Object.freeze({
|
|
153
|
+
result: Object.freeze(result),
|
|
154
|
+
block: evidence.block,
|
|
155
|
+
confirmations,
|
|
156
|
+
output: Object.freeze({
|
|
157
|
+
...evidence.reference,
|
|
158
|
+
commitment: evidence.output.outputCommitment
|
|
159
|
+
})
|
|
160
|
+
});
|
|
266
161
|
}
|
|
267
162
|
toJSON() {
|
|
268
|
-
return {
|
|
163
|
+
return { disclosureVerifier: true, confirmations: this.#confirmations.toString() };
|
|
269
164
|
}
|
|
270
165
|
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
|
271
166
|
return this.toJSON();
|
|
272
167
|
}
|
|
273
168
|
};
|
|
274
169
|
|
|
275
|
-
// src/
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
function position(input) {
|
|
283
|
-
if (typeof input.treeId !== "bigint" || input.treeId < 0n || input.treeId >= 1n << 256n || !Number.isInteger(input.leafIndex) || input.leafIndex < 0 || input.leafIndex >= 65536)
|
|
284
|
-
throw new Error("invalid explicit input position");
|
|
285
|
-
}
|
|
286
|
-
for (const input of inputs) {
|
|
287
|
-
position(input);
|
|
288
|
-
positiveAmount(input.note.amount);
|
|
289
|
-
if (inventory.notes[input.commitment]) throw new Error("duplicate explicit note input");
|
|
290
|
-
if (input.note.chainId !== context.chainId || input.note.protocolAddress !== BigInt(context.protocolAddress) || input.note.walletIdentity !== options.wallet.walletIdentity)
|
|
291
|
-
throw new Error("explicit note identity or deployment mismatch");
|
|
292
|
-
inventory.notes[input.commitment] = { ...input, spent: "unspent" };
|
|
293
|
-
}
|
|
294
|
-
for (const input of credits) {
|
|
295
|
-
position(input);
|
|
296
|
-
if (input.role !== "owner" && input.role !== "spender")
|
|
297
|
-
throw new Error("invalid explicit Credit role");
|
|
298
|
-
const key = input.commitment + ":" + input.role;
|
|
299
|
-
if (inventory.credits[key]) throw new Error("duplicate explicit Credit input");
|
|
300
|
-
inventory.credits[key] = { ...input, spent: "unspent" };
|
|
301
|
-
}
|
|
302
|
-
const scope = {
|
|
303
|
-
...context,
|
|
304
|
-
namespace: "explicit",
|
|
305
|
-
walletIdentity: options.wallet.walletIdentity
|
|
306
|
-
};
|
|
307
|
-
const {
|
|
308
|
-
store: _store,
|
|
309
|
-
coinSelector: _selector,
|
|
310
|
-
authorization: _authorization,
|
|
311
|
-
viewing: _viewing,
|
|
312
|
-
signer: _signer,
|
|
313
|
-
fundingSigner: _fundingSigner,
|
|
314
|
-
...capabilities
|
|
315
|
-
} = options;
|
|
316
|
-
if (inputs.some((input) => input.note.tokenAddress !== BigInt(getAddress(tokenAddress))))
|
|
317
|
-
throw new Error("explicit note token mismatch");
|
|
318
|
-
const runtime = {
|
|
319
|
-
options: { ...capabilities, protocol: options.protocol.forToken(tokenAddress) },
|
|
320
|
-
reader: options.chainReader,
|
|
321
|
-
scope
|
|
322
|
-
};
|
|
323
|
-
return new TransactionPreparation({
|
|
324
|
-
...runtime,
|
|
325
|
-
explicitInputs: true,
|
|
326
|
-
...data.changeAddress ? { changeAddress: data.changeAddress } : {},
|
|
327
|
-
readInventory: async () => structuredClone(inventory),
|
|
328
|
-
complete: async (plan) => PreparedTransaction.prepare(runtime, plan)
|
|
329
|
-
});
|
|
170
|
+
// src/protocol/connections.ts
|
|
171
|
+
var connections = /* @__PURE__ */ new WeakMap();
|
|
172
|
+
function rememberProtocolConnection(protocol, provider, protocolRpc) {
|
|
173
|
+
connections.set(protocol, { provider, protocolRpc });
|
|
174
|
+
}
|
|
175
|
+
function protocolConnection(protocol) {
|
|
176
|
+
return connections.get(protocol);
|
|
330
177
|
}
|
|
331
178
|
|
|
332
|
-
// src/
|
|
333
|
-
import {
|
|
334
|
-
import {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
179
|
+
// src/protocol/Protocol.ts
|
|
180
|
+
import { Protocol as BaseProtocol } from "@invisi-labs/protocol";
|
|
181
|
+
import { getAddress } from "ethers";
|
|
182
|
+
var sources = /* @__PURE__ */ new WeakMap();
|
|
183
|
+
var Protocol = class _Protocol extends BaseProtocol {
|
|
184
|
+
static async fromRpc(rpc, provider, options = {}) {
|
|
185
|
+
const verified = await BaseProtocol.fromRpc(rpc, provider, options);
|
|
186
|
+
const protocol = new _Protocol({ deployment: verified.deployment });
|
|
187
|
+
if (rpc.getBroadcasters)
|
|
188
|
+
sources.set(protocol, rpc);
|
|
189
|
+
if (rpc.withReader)
|
|
190
|
+
rememberProtocolConnection(
|
|
191
|
+
protocol,
|
|
192
|
+
provider,
|
|
193
|
+
rpc
|
|
194
|
+
);
|
|
195
|
+
return protocol;
|
|
196
|
+
}
|
|
197
|
+
/** Returns enabled services that answer a token fee probe; final quotes are checked by prepared. */
|
|
198
|
+
async getBroadcasters(options) {
|
|
199
|
+
const token = getAddress(options.tokenAddress);
|
|
200
|
+
if (!this.deployment.broadcasterRegistry)
|
|
201
|
+
throw new Error("broadcaster registry is not configured");
|
|
202
|
+
const rpc = sources.get(this);
|
|
203
|
+
if (!rpc) throw new Error("broadcaster discovery requires Protocol.fromRpc");
|
|
204
|
+
const clients = [];
|
|
205
|
+
let after;
|
|
206
|
+
let block;
|
|
207
|
+
const seen = /* @__PURE__ */ new Set();
|
|
208
|
+
do {
|
|
209
|
+
const page = await rpc.getBroadcasters({
|
|
210
|
+
enabled: true,
|
|
211
|
+
limit: 100,
|
|
212
|
+
...after ? { after } : {},
|
|
213
|
+
...block ? { block } : {}
|
|
214
|
+
});
|
|
215
|
+
block ??= page.block;
|
|
216
|
+
const compatible = await Promise.all(
|
|
217
|
+
page.broadcasters.map(async (entry) => {
|
|
218
|
+
if (!entry.enabled) return void 0;
|
|
219
|
+
try {
|
|
220
|
+
const client = new BroadcasterClient(entry);
|
|
221
|
+
await client.fee(token);
|
|
222
|
+
return client;
|
|
223
|
+
} catch {
|
|
224
|
+
return void 0;
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
);
|
|
228
|
+
clients.push(
|
|
229
|
+
...compatible.filter((client) => client !== void 0)
|
|
230
|
+
);
|
|
231
|
+
after = page.next;
|
|
232
|
+
if (after && seen.has(after)) throw new Error("repeated broadcaster pagination cursor");
|
|
233
|
+
if (after) seen.add(after);
|
|
234
|
+
} while (after);
|
|
235
|
+
return clients;
|
|
350
236
|
}
|
|
351
237
|
};
|
|
352
238
|
|
|
353
|
-
// src/
|
|
354
|
-
|
|
239
|
+
// src/index.ts
|
|
240
|
+
import { MAX_UPDATE_BLOCKS, ProtocolRpcError } from "@invisi-labs/rpc";
|
|
241
|
+
|
|
242
|
+
// src/protocol/BroadcasterRegistry.ts
|
|
243
|
+
import { broadcasterRegistryAbi } from "@invisi-labs/protocol/contracts";
|
|
244
|
+
import { getAddress as getAddress2, Interface, keccak256 } from "ethers";
|
|
245
|
+
var abi = new Interface(broadcasterRegistryAbi);
|
|
246
|
+
function address(value) {
|
|
247
|
+
const normalized = getAddress2(value);
|
|
248
|
+
if (BigInt(normalized) === 0n) throw new Error("zero broadcaster address");
|
|
249
|
+
return normalized;
|
|
250
|
+
}
|
|
251
|
+
var BroadcasterRegistry = class {
|
|
252
|
+
address;
|
|
355
253
|
#options;
|
|
356
|
-
constructor(options
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
);
|
|
254
|
+
constructor(options) {
|
|
255
|
+
this.address = address(options.address);
|
|
256
|
+
const blockRange = options.blockRange ?? 2e3;
|
|
257
|
+
if (options.chainId <= 0n || !Number.isSafeInteger(options.fromBlock) || options.fromBlock < 0 || !Number.isSafeInteger(blockRange) || blockRange < 1 || blockRange > 1e5 || !/^0x[0-9a-fA-F]{64}$/.test(options.runtimeCodeHash))
|
|
258
|
+
throw new Error("invalid broadcaster registry configuration");
|
|
259
|
+
this.#options = Object.freeze({ ...options, blockRange });
|
|
260
|
+
}
|
|
261
|
+
prepareRegistration(registration) {
|
|
262
|
+
const { endpoint, metadataURI = "" } = registration;
|
|
263
|
+
if (!endpoint.startsWith("https://") || endpoint.length <= 8 || endpoint.length > 2048 || /[^\x21-\x7e]/.test(endpoint))
|
|
264
|
+
throw new Error("broadcaster endpoint must be a printable HTTPS URI");
|
|
265
|
+
const url = new URL(endpoint);
|
|
266
|
+
if (!url.hostname || url.username || url.password) {
|
|
267
|
+
throw new Error("broadcaster HTTPS endpoint must not include credentials");
|
|
361
268
|
}
|
|
362
|
-
if (
|
|
363
|
-
throw new Error(
|
|
364
|
-
"Invisi does not accept signer defaults; export a signing request and attach the external signature"
|
|
365
|
-
);
|
|
269
|
+
if (metadataURI.length > 2048 || /[^\x21-\x7e]/.test(metadataURI)) {
|
|
270
|
+
throw new Error("invalid broadcaster metadata URI");
|
|
366
271
|
}
|
|
367
|
-
this
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
"
|
|
384
|
-
"fundingSigner"
|
|
385
|
-
];
|
|
386
|
-
for (const key of Object.keys(options)) {
|
|
387
|
-
if (!allowed.includes(key))
|
|
388
|
-
throw new Error(
|
|
389
|
-
`Unknown connection option: ${key}; configure infrastructure on Invisi`
|
|
390
|
-
);
|
|
272
|
+
return this.call("setBroadcaster", [
|
|
273
|
+
endpoint,
|
|
274
|
+
metadataURI,
|
|
275
|
+
address(registration.quoteSigner),
|
|
276
|
+
address(registration.feeRecipient)
|
|
277
|
+
]);
|
|
278
|
+
}
|
|
279
|
+
prepareAvailability(enabled) {
|
|
280
|
+
return this.call("setEnabled", [enabled]);
|
|
281
|
+
}
|
|
282
|
+
call(name, args) {
|
|
283
|
+
return { to: this.address, data: abi.encodeFunctionData(name, args), value: 0n };
|
|
284
|
+
}
|
|
285
|
+
async validate() {
|
|
286
|
+
const { provider, chainId, runtimeCodeHash } = this.#options;
|
|
287
|
+
if ((await provider.getNetwork()).chainId !== chainId) {
|
|
288
|
+
throw new Error("broadcaster registry chain mismatch");
|
|
391
289
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
const { provider, addressLookup = "protocol-rpc", ...runtime } = combined;
|
|
395
|
-
if (addressLookup !== void 0 && addressLookup !== "chain" && addressLookup !== "protocol-rpc") {
|
|
396
|
-
throw new Error("Invalid recipient address lookup source");
|
|
397
|
-
}
|
|
398
|
-
const reader = combined.chainReader ?? (provider ? new EthersChainReader(provider) : void 0);
|
|
399
|
-
if (!reader) throw new MissingCapabilityError2("chain reader");
|
|
400
|
-
if (provider && (await provider.getNetwork()).chainId !== selected.protocol.context.chainId)
|
|
401
|
-
throw new Error("provider deployment chain mismatch");
|
|
402
|
-
await selected.protocol.validateDeployment(reader);
|
|
403
|
-
if (runtime.protocolRpc instanceof ProtocolRpcClient) {
|
|
404
|
-
runtime.protocolRpc = runtime.protocolRpc.withReader(reader);
|
|
405
|
-
}
|
|
406
|
-
const registry = selected.protocol.deployment.registry;
|
|
407
|
-
const fundingVerifier = combined.fundingVerifier ?? (provider ? new EthersFundingVerifier(provider, selected.protocol.context.chainId) : void 0);
|
|
408
|
-
let addressResolver = combined.addressResolver;
|
|
409
|
-
if (!addressResolver && registry) {
|
|
410
|
-
if (addressLookup === "protocol-rpc") {
|
|
411
|
-
const rpc = runtime.protocolRpc;
|
|
412
|
-
addressResolver = async (identity) => {
|
|
413
|
-
if (!(rpc instanceof ProtocolRpcClient)) {
|
|
414
|
-
throw new MissingCapabilityError2("Protocol RPC recipient discovery");
|
|
415
|
-
}
|
|
416
|
-
const discovered = await rpc.getDeployment(selected.protocol.context.chainId);
|
|
417
|
-
const expected = selected.protocol.deployment;
|
|
418
|
-
if (discovered.protocol.address !== expected.protocol.address || discovered.protocol.runtimeCodeHash !== expected.protocol.runtimeCodeHash || discovered.protocol.fromBlock !== expected.protocol.fromBlock || discovered.registry?.address !== registry.address || discovered.registry.runtimeCodeHash !== registry.runtimeCodeHash || discovered.registry.fromBlock !== registry.fromBlock)
|
|
419
|
-
throw new Error("Recipient registry deployment pins mismatch");
|
|
420
|
-
const registration = await rpc.getReceivingAddress(identity);
|
|
421
|
-
return registration ? ReceivingAddress.parse(registration.receivingAddress).getReceivingKeys() : null;
|
|
422
|
-
};
|
|
423
|
-
} else if (provider) {
|
|
424
|
-
addressResolver = new EthersAddressResolver(
|
|
425
|
-
provider,
|
|
426
|
-
{
|
|
427
|
-
chainId: selected.protocol.context.chainId,
|
|
428
|
-
registryAddress: registry.address
|
|
429
|
-
},
|
|
430
|
-
registry.runtimeCodeHash
|
|
431
|
-
).lookup;
|
|
432
|
-
}
|
|
290
|
+
if (keccak256(await provider.getCode(this.address)).toLowerCase() !== runtimeCodeHash.toLowerCase()) {
|
|
291
|
+
throw new Error("broadcaster registry runtime code mismatch");
|
|
433
292
|
}
|
|
293
|
+
}
|
|
294
|
+
async get(operator) {
|
|
295
|
+
await this.validate();
|
|
296
|
+
return this.read(address(operator));
|
|
297
|
+
}
|
|
298
|
+
async read(operator) {
|
|
299
|
+
const result = await this.#options.provider.call(this.call("broadcasterOf", [operator]));
|
|
300
|
+
const [entry] = abi.decodeFunctionResult("broadcasterOf", result);
|
|
434
301
|
return {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
302
|
+
operator,
|
|
303
|
+
endpoint: entry.endpoint,
|
|
304
|
+
metadataURI: entry.metadataURI,
|
|
305
|
+
quoteSigner: address(entry.quoteSigner),
|
|
306
|
+
feeRecipient: address(entry.feeRecipient),
|
|
307
|
+
enabled: entry.enabled
|
|
440
308
|
};
|
|
441
309
|
}
|
|
442
|
-
/** Discover
|
|
443
|
-
async
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
310
|
+
/** Discover event operators, then read their latest entry; disabled services remain explicit. */
|
|
311
|
+
async discover(options = {}) {
|
|
312
|
+
const { provider, fromBlock, blockRange } = this.#options;
|
|
313
|
+
options.signal?.throwIfAborted();
|
|
314
|
+
await this.validate();
|
|
315
|
+
const toBlock = options.toBlock ?? await provider.getBlockNumber();
|
|
316
|
+
if (!Number.isSafeInteger(toBlock) || toBlock < fromBlock) {
|
|
317
|
+
throw new Error("invalid broadcaster discovery block range");
|
|
318
|
+
}
|
|
319
|
+
const operators = /* @__PURE__ */ new Set();
|
|
320
|
+
for (let start = fromBlock; start <= toBlock; ) {
|
|
321
|
+
options.signal?.throwIfAborted();
|
|
322
|
+
const end = Math.min(start + blockRange - 1, toBlock);
|
|
323
|
+
const logs = await provider.getLogs({
|
|
324
|
+
address: this.address,
|
|
325
|
+
topics: [abi.getEvent("BroadcasterSet").topicHash],
|
|
326
|
+
fromBlock: start,
|
|
327
|
+
toBlock: end
|
|
328
|
+
});
|
|
329
|
+
for (const log of logs) {
|
|
330
|
+
if (getAddress2(log.address) !== this.address)
|
|
331
|
+
throw new Error("registry event address mismatch");
|
|
332
|
+
const event = abi.parseLog(log);
|
|
333
|
+
if (!event || event.name !== "BroadcasterSet")
|
|
334
|
+
throw new Error("invalid registry event");
|
|
335
|
+
operators.add(address(event.args.operator));
|
|
461
336
|
}
|
|
462
|
-
|
|
463
|
-
{ chainId, registryAddress: deployment.registry.address },
|
|
464
|
-
{ expectedRuntimeCodeHash: deployment.registry.runtimeCodeHash }
|
|
465
|
-
);
|
|
466
|
-
return registry.prepareRegistration({ ...selected, provider });
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
assertPublicIdentity(options) {
|
|
470
|
-
const { wallet } = options;
|
|
471
|
-
if (!(wallet instanceof PublicWallet) || ["getViewer", "getDetector", "exportAuthorization"].some((key) => key in wallet)) {
|
|
472
|
-
throw new Error(
|
|
473
|
-
"Independent preparation requires a PublicWallet without private capabilities"
|
|
474
|
-
);
|
|
337
|
+
start = end + 1;
|
|
475
338
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
339
|
+
const entries = [];
|
|
340
|
+
for (const operator of operators) {
|
|
341
|
+
options.signal?.throwIfAborted();
|
|
342
|
+
entries.push(await this.read(operator));
|
|
479
343
|
}
|
|
480
|
-
|
|
481
|
-
resolveExplicit(options) {
|
|
482
|
-
this.assertPublicIdentity(options);
|
|
483
|
-
return this.resolve({ wallet: options.wallet, protocol: options.protocol });
|
|
484
|
-
}
|
|
485
|
-
async prepareTransfer(options) {
|
|
486
|
-
return prepareWithErrors("prepareTransfer", async () => {
|
|
487
|
-
const { wallet, protocol, ...input } = options;
|
|
488
|
-
const request = structuredClone(input);
|
|
489
|
-
const runtime = await this.resolveExplicit(options);
|
|
490
|
-
const preparation = explicitPreparation(runtime, request, request.tokenAddress);
|
|
491
|
-
return preparation.prepareTransfer(request);
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
async prepareShield(options) {
|
|
495
|
-
return prepareWithErrors("prepareShield", async () => {
|
|
496
|
-
const { wallet, protocol, ...input } = options;
|
|
497
|
-
const request = structuredClone(input);
|
|
498
|
-
const runtime = await this.resolveExplicit(options);
|
|
499
|
-
const preparation = explicitPreparation(runtime, {}, request.tokenAddress);
|
|
500
|
-
return preparation.prepareShield(request);
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
async prepareCreateCredit(options) {
|
|
504
|
-
return prepareWithErrors("prepareCreateCredit", async () => {
|
|
505
|
-
const { wallet, protocol, ...input } = options;
|
|
506
|
-
const request = structuredClone(input);
|
|
507
|
-
const runtime = await this.resolveExplicit(options);
|
|
508
|
-
const preparation = explicitPreparation(runtime, request, request.tokenAddress);
|
|
509
|
-
return preparation.prepareCreateCredit(request);
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
async prepareDrawCredit(options) {
|
|
513
|
-
return prepareWithErrors("prepareDrawCredit", async () => {
|
|
514
|
-
const { wallet, protocol, ...input } = options;
|
|
515
|
-
const request = structuredClone(input);
|
|
516
|
-
const runtime = await this.resolveExplicit(options);
|
|
517
|
-
const preparation = explicitPreparation(runtime, request, request.tokenAddress);
|
|
518
|
-
return preparation.prepareDrawCredit(request);
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
async prepareTopUpCredit(options) {
|
|
522
|
-
return prepareWithErrors("prepareTopUpCredit", async () => {
|
|
523
|
-
const { wallet, protocol, ...input } = options;
|
|
524
|
-
const request = structuredClone(input);
|
|
525
|
-
const runtime = await this.resolveExplicit(options);
|
|
526
|
-
const preparation = explicitPreparation(runtime, request, request.tokenAddress);
|
|
527
|
-
return preparation.prepareTopUpCredit(request);
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
async prepareCancelCredit(options) {
|
|
531
|
-
return prepareWithErrors("prepareCancelCredit", async () => {
|
|
532
|
-
const { wallet, protocol, ...input } = options;
|
|
533
|
-
const request = structuredClone(input);
|
|
534
|
-
const runtime = await this.resolveExplicit(options);
|
|
535
|
-
const preparation = explicitPreparation(runtime, request, request.tokenAddress);
|
|
536
|
-
return preparation.prepareCancelCredit(request);
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
toJSON() {
|
|
540
|
-
return { invisi: true };
|
|
541
|
-
}
|
|
542
|
-
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
|
543
|
-
return this.toJSON();
|
|
344
|
+
return entries;
|
|
544
345
|
}
|
|
545
346
|
};
|
|
546
347
|
|
|
547
|
-
// src/
|
|
548
|
-
import {
|
|
549
|
-
fundsV1,
|
|
550
|
-
creditV1,
|
|
551
|
-
decodeDeliveryHeader,
|
|
552
|
-
Wallet,
|
|
553
|
-
PublicWallet as PublicWallet2,
|
|
554
|
-
ViewingCapability,
|
|
555
|
-
Detector,
|
|
556
|
-
NullifierCapability,
|
|
557
|
-
WalletAuthorization,
|
|
558
|
-
MissingCapabilityError as MissingCapabilityError3,
|
|
559
|
-
Disclosure,
|
|
560
|
-
encodeMemo,
|
|
561
|
-
decodeMemoText,
|
|
562
|
-
MAX_MEMO_BYTES,
|
|
563
|
-
computeCreditNullifier,
|
|
564
|
-
creditPeriods
|
|
565
|
-
} from "@invisi-labs/core";
|
|
566
|
-
import { Protocol, RegistrationOperation } from "@invisi-labs/protocol";
|
|
567
|
-
import { ProtocolRpcError } from "@invisi-labs/rpc";
|
|
568
|
-
|
|
569
|
-
// src/ProtocolRpcClient.ts
|
|
348
|
+
// src/protocol/ProtocolRpcClient.ts
|
|
570
349
|
import { ProtocolRpcClient as RpcClient } from "@invisi-labs/rpc";
|
|
571
|
-
var
|
|
350
|
+
var ProtocolRpcClient = class extends RpcClient {
|
|
572
351
|
withProvider(provider) {
|
|
573
352
|
return this.withReader(new EthersChainReader(provider));
|
|
574
353
|
}
|
|
575
354
|
};
|
|
576
355
|
|
|
577
|
-
// src/
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
356
|
+
// src/payment-note/PaymentNote.ts
|
|
357
|
+
import { computeNoteIdentity, computeNoteCommitment, Wallet } from "@invisi-labs/core";
|
|
358
|
+
|
|
359
|
+
// src/payment-note/verification.ts
|
|
360
|
+
import {
|
|
361
|
+
poseidonHash,
|
|
362
|
+
TREE_DEPTH,
|
|
363
|
+
computeNoteNullifier
|
|
364
|
+
} from "@invisi-labs/core";
|
|
365
|
+
import { getAddress as getAddress3 } from "ethers";
|
|
366
|
+
function verificationOptions(protocol, options = {}) {
|
|
367
|
+
const connected = protocolConnection(protocol);
|
|
368
|
+
const resolved = { ...connected, ...options };
|
|
369
|
+
if (!("protocolRpc" in resolved) || !resolved.protocolRpc || !("chainReader" in resolved) && !("provider" in resolved))
|
|
370
|
+
throw new Error(
|
|
371
|
+
"Payment note verification requires Protocol.fromRpc or explicit chain and Protocol RPC connections"
|
|
372
|
+
);
|
|
373
|
+
return resolved;
|
|
374
|
+
}
|
|
375
|
+
function sameBlock(a, b) {
|
|
376
|
+
return a.number === b.number && a.hash === b.hash && a.parentHash === b.parentHash && a.timestamp === b.timestamp;
|
|
377
|
+
}
|
|
378
|
+
function asNote(data) {
|
|
379
|
+
return {
|
|
380
|
+
chainId: data.chainId,
|
|
381
|
+
protocolAddress: BigInt(data.protocolAddress),
|
|
382
|
+
tokenAddress: BigInt(data.tokenAddress),
|
|
383
|
+
amount: data.amount,
|
|
384
|
+
ownerAddress: data.ownerAddress,
|
|
385
|
+
nonce: data.nonce
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
async function verifyPaymentNote(data, protocol, options, wallet) {
|
|
389
|
+
if (data.chainId !== protocol.context.chainId || getAddress3(data.protocolAddress) !== getAddress3(protocol.context.protocolAddress))
|
|
390
|
+
throw new Error("Payment note protocol context mismatch");
|
|
391
|
+
const confirmations = options.confirmations ?? 1;
|
|
392
|
+
if (!Number.isSafeInteger(confirmations) || confirmations < 1)
|
|
393
|
+
throw new RangeError("confirmations must be positive");
|
|
394
|
+
const reader = options.chainReader ?? new EthersChainReader(options.provider);
|
|
395
|
+
await protocol.validateDeployment(reader);
|
|
396
|
+
if (wallet && getAddress3(wallet.ownerAddress) !== getAddress3(data.ownerAddress))
|
|
397
|
+
throw new Error("Payment note owner mismatch");
|
|
398
|
+
if (wallet) {
|
|
399
|
+
if (typeof wallet.exportAuthorization !== "function")
|
|
400
|
+
throw new Error("Wallet spending authorization capability required");
|
|
401
|
+
const authorization = wallet.exportAuthorization();
|
|
402
|
+
if (getAddress3(authorization.ownerAddress) !== getAddress3(data.ownerAddress))
|
|
403
|
+
throw new Error("Wallet authorization owner mismatch");
|
|
404
|
+
}
|
|
405
|
+
const latest = await reader.getLatestBlockNumber();
|
|
406
|
+
const number = latest - BigInt(confirmations - 1);
|
|
407
|
+
if (number < protocol.deployment.protocol.fromBlock)
|
|
408
|
+
throw new Error("Insufficient confirmed blocks");
|
|
409
|
+
const rpc = options.protocolRpc.withReader(reader);
|
|
410
|
+
const status = await rpc.getStatus(protocol.context);
|
|
411
|
+
if (!status.indexedHead) throw new Error("Protocol RPC has no indexed blocks");
|
|
412
|
+
const indexedNumber = status.indexedHead.number < number ? status.indexedHead.number : number;
|
|
413
|
+
if (indexedNumber < protocol.deployment.protocol.fromBlock)
|
|
414
|
+
throw new Error("Protocol RPC has not reached the deployment");
|
|
415
|
+
const checkedAt = await reader.getBlock(indexedNumber);
|
|
416
|
+
if (checkedAt.number !== indexedNumber)
|
|
417
|
+
throw new Error("Payment note confirmation block mismatch");
|
|
418
|
+
const result = await rpc.getNote(protocol.context, data.commitment, checkedAt);
|
|
419
|
+
if (!result) throw new Error("Payment note is not indexed at the requested confirmed block");
|
|
420
|
+
const { output, membership } = result;
|
|
421
|
+
if (output.commitment !== data.commitment || getAddress3(output.tokenAddress) !== getAddress3(data.tokenAddress) || membership.leaf !== data.commitment || membership.treeId !== output.treeId || membership.leafIndex !== output.leafIndex || membership.block.number > checkedAt.number || output.block.number > checkedAt.number || membership.path.length !== TREE_DEPTH || !Number.isSafeInteger(output.leafIndex) || output.leafIndex < 0 || output.leafIndex >= 2 ** TREE_DEPTH)
|
|
422
|
+
throw new Error("Payment note membership metadata mismatch");
|
|
423
|
+
let root = BigInt(data.commitment);
|
|
424
|
+
for (let level = 0; level < membership.path.length; level++) {
|
|
425
|
+
const sibling = membership.path[level];
|
|
426
|
+
root = BigInt(
|
|
427
|
+
await poseidonHash(output.leafIndex >> level & 1 ? [sibling, root] : [root, sibling])
|
|
428
|
+
);
|
|
582
429
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
430
|
+
if (root !== BigInt(membership.root) || !await reader.isKnownRoot(protocol.context, membership.treeId, membership.root, checkedAt))
|
|
431
|
+
throw new Error("Payment note membership root is not accepted on chain");
|
|
432
|
+
if (!sameBlock(output.block, await reader.getBlock(output.block.number)) || !sameBlock(membership.block, await reader.getBlock(membership.block.number)))
|
|
433
|
+
throw new Error("Payment note block is no longer canonical");
|
|
434
|
+
let spendabilityCheckedAt;
|
|
435
|
+
if (wallet) {
|
|
436
|
+
const current = await reader.getBlock(await reader.getLatestBlockNumber());
|
|
437
|
+
const nullifier = await computeNoteNullifier(data.chainId, data.nonce, data.commitment);
|
|
438
|
+
const spent = await reader.isSpent(protocol.context, [nullifier], current);
|
|
439
|
+
if (spent.length !== 1 || spent[0] !== false)
|
|
440
|
+
throw new Error("Payment note is already spent");
|
|
441
|
+
if (!await reader.isKnownRoot(
|
|
442
|
+
protocol.context,
|
|
443
|
+
membership.treeId,
|
|
444
|
+
membership.root,
|
|
445
|
+
current
|
|
446
|
+
))
|
|
447
|
+
throw new Error("Payment note membership root is no longer spendable");
|
|
448
|
+
if (!sameBlock(current, await reader.getBlock(current.number)))
|
|
449
|
+
throw new Error("Spendability block reorganized");
|
|
450
|
+
spendabilityCheckedAt = current;
|
|
451
|
+
}
|
|
452
|
+
if (!sameBlock(checkedAt, await reader.getBlock(checkedAt.number)))
|
|
453
|
+
throw new Error("Payment note verification block reorganized");
|
|
454
|
+
return {
|
|
455
|
+
input: {
|
|
456
|
+
note: asNote(data),
|
|
457
|
+
commitment: data.commitment,
|
|
458
|
+
treeId: output.treeId,
|
|
459
|
+
leafIndex: output.leafIndex
|
|
460
|
+
},
|
|
461
|
+
membership,
|
|
462
|
+
checkedAt,
|
|
463
|
+
...spendabilityCheckedAt ? { spendabilityCheckedAt } : {}
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/payment-note/PaymentNote.ts
|
|
468
|
+
var verification = /* @__PURE__ */ Symbol("PaymentNote verification");
|
|
469
|
+
var VerifiedPaymentNote = class {
|
|
470
|
+
#membership;
|
|
471
|
+
#checkedAt;
|
|
472
|
+
constructor(token, membership, checkedAt) {
|
|
473
|
+
if (token !== verification) throw new Error("Payment note verification required");
|
|
474
|
+
this.#membership = structuredClone(membership);
|
|
475
|
+
this.#checkedAt = structuredClone(checkedAt);
|
|
476
|
+
}
|
|
477
|
+
get membership() {
|
|
478
|
+
return structuredClone(this.#membership);
|
|
479
|
+
}
|
|
480
|
+
get checkedAt() {
|
|
481
|
+
return structuredClone(this.#checkedAt);
|
|
587
482
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
);
|
|
598
|
-
} finally {
|
|
599
|
-
copy.fill(0);
|
|
600
|
-
}
|
|
483
|
+
};
|
|
484
|
+
var SpendablePaymentNote = class extends VerifiedPaymentNote {
|
|
485
|
+
#input;
|
|
486
|
+
#spendabilityCheckedAt;
|
|
487
|
+
constructor(token, input, membership, checkedAt, spendabilityCheckedAt) {
|
|
488
|
+
super(token, membership, checkedAt);
|
|
489
|
+
this.#input = structuredClone(input);
|
|
490
|
+
this.#spendabilityCheckedAt = structuredClone(spendabilityCheckedAt);
|
|
491
|
+
Object.freeze(this);
|
|
601
492
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
throw new Error("invalid encrypted record revision");
|
|
605
|
-
return new TextEncoder().encode(
|
|
606
|
-
`Invisi.WalletRecord.v1\0${context.revision}\0${context.key}`
|
|
607
|
-
);
|
|
493
|
+
get spendabilityCheckedAt() {
|
|
494
|
+
return structuredClone(this.#spendabilityCheckedAt);
|
|
608
495
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
try {
|
|
612
|
-
const encrypted = new Uint8Array(
|
|
613
|
-
await crypto.subtle.encrypt(
|
|
614
|
-
{ name: "AES-GCM", iv, additionalData: this.aad(context) },
|
|
615
|
-
this.#key,
|
|
616
|
-
plain
|
|
617
|
-
)
|
|
618
|
-
);
|
|
619
|
-
const out = new Uint8Array(13 + encrypted.length);
|
|
620
|
-
out[0] = 1;
|
|
621
|
-
out.set(iv, 1);
|
|
622
|
-
out.set(encrypted, 13);
|
|
623
|
-
return out;
|
|
624
|
-
} finally {
|
|
625
|
-
plain.fill(0);
|
|
626
|
-
}
|
|
496
|
+
toInput() {
|
|
497
|
+
return structuredClone(this.#input);
|
|
627
498
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
return deserializeState(plain);
|
|
649
|
-
} finally {
|
|
650
|
-
plain.fill(0);
|
|
651
|
-
}
|
|
499
|
+
};
|
|
500
|
+
var PaymentNote = class _PaymentNote {
|
|
501
|
+
chainId;
|
|
502
|
+
protocolAddress;
|
|
503
|
+
tokenAddress;
|
|
504
|
+
amount;
|
|
505
|
+
ownerAddress;
|
|
506
|
+
nonce;
|
|
507
|
+
commitment;
|
|
508
|
+
ipn;
|
|
509
|
+
constructor(data, ipn) {
|
|
510
|
+
this.chainId = data.chainId;
|
|
511
|
+
this.protocolAddress = data.protocolAddress;
|
|
512
|
+
this.tokenAddress = data.tokenAddress;
|
|
513
|
+
this.amount = data.amount;
|
|
514
|
+
this.ownerAddress = data.ownerAddress;
|
|
515
|
+
this.nonce = data.nonce;
|
|
516
|
+
this.commitment = data.commitment;
|
|
517
|
+
this.ipn = ipn;
|
|
518
|
+
Object.freeze(this);
|
|
652
519
|
}
|
|
653
|
-
|
|
654
|
-
|
|
520
|
+
static async from(ipn) {
|
|
521
|
+
const data = decodePaymentNote(ipn);
|
|
522
|
+
const identity = await computeNoteIdentity(BigInt(data.ownerAddress), data.nonce);
|
|
523
|
+
const commitment = await computeNoteCommitment(
|
|
524
|
+
data.chainId,
|
|
525
|
+
identity,
|
|
526
|
+
BigInt(data.tokenAddress),
|
|
527
|
+
data.amount
|
|
528
|
+
);
|
|
529
|
+
if (commitment !== data.commitment) throw new Error("Payment note commitment mismatch");
|
|
530
|
+
return new _PaymentNote(data, encodePaymentNote(data));
|
|
531
|
+
}
|
|
532
|
+
async verify(protocol, options = {}) {
|
|
533
|
+
const result = await verifyPaymentNote(
|
|
534
|
+
this.data(),
|
|
535
|
+
protocol,
|
|
536
|
+
verificationOptions(protocol, options)
|
|
537
|
+
);
|
|
538
|
+
const verified = new VerifiedPaymentNote(verification, result.membership, result.checkedAt);
|
|
539
|
+
Object.freeze(verified);
|
|
540
|
+
return verified;
|
|
541
|
+
}
|
|
542
|
+
async verifySpendable(protocol, wallet, options = {}) {
|
|
543
|
+
if (!(wallet instanceof Wallet))
|
|
544
|
+
throw new Error("Wallet spending authorization capability required");
|
|
545
|
+
const result = await verifyPaymentNote(
|
|
546
|
+
this.data(),
|
|
547
|
+
protocol,
|
|
548
|
+
verificationOptions(protocol, options),
|
|
549
|
+
wallet
|
|
550
|
+
);
|
|
551
|
+
return new SpendablePaymentNote(
|
|
552
|
+
verification,
|
|
553
|
+
result.input,
|
|
554
|
+
result.membership,
|
|
555
|
+
result.checkedAt,
|
|
556
|
+
result.spendabilityCheckedAt
|
|
557
|
+
);
|
|
655
558
|
}
|
|
656
|
-
|
|
657
|
-
|
|
559
|
+
data() {
|
|
560
|
+
const { chainId, protocolAddress, tokenAddress, amount, ownerAddress, nonce, commitment } = this;
|
|
561
|
+
return { chainId, protocolAddress, tokenAddress, amount, ownerAddress, nonce, commitment };
|
|
658
562
|
}
|
|
659
563
|
};
|
|
660
564
|
export {
|
|
661
565
|
AddressRegistry,
|
|
566
|
+
BroadcasterClient,
|
|
567
|
+
BroadcasterRegistry,
|
|
568
|
+
BroadcasterRpcError,
|
|
662
569
|
Detector,
|
|
663
570
|
Disclosure,
|
|
664
571
|
DisclosureVerifier,
|
|
665
|
-
EncryptedWalletCodec,
|
|
666
572
|
Invisi,
|
|
667
573
|
InvisiError,
|
|
668
574
|
MAX_MEMO_BYTES,
|
|
669
|
-
|
|
575
|
+
MAX_UPDATE_BLOCKS,
|
|
576
|
+
MissingCapabilityError,
|
|
577
|
+
NoteOutput,
|
|
670
578
|
NullifierCapability,
|
|
671
|
-
|
|
579
|
+
PaymentNote,
|
|
672
580
|
PreparedTransaction,
|
|
673
581
|
Protocol,
|
|
674
|
-
|
|
582
|
+
ProtocolRpcClient,
|
|
675
583
|
ProtocolRpcError,
|
|
676
|
-
|
|
584
|
+
PublicWallet,
|
|
677
585
|
RegistrationOperation,
|
|
586
|
+
SpendablePaymentNote,
|
|
587
|
+
TransactionOutput,
|
|
588
|
+
VerifiedPaymentNote,
|
|
678
589
|
ViewingCapability,
|
|
679
|
-
Wallet,
|
|
590
|
+
Wallet2 as Wallet,
|
|
680
591
|
WalletAuthorization,
|
|
681
|
-
|
|
592
|
+
broadcasterQuoteDomain,
|
|
593
|
+
broadcasterQuoteTypes,
|
|
594
|
+
broadcasterRequestHash,
|
|
595
|
+
broadcasterTransactInterface,
|
|
682
596
|
computeCreditNullifier,
|
|
683
597
|
creditPeriods,
|
|
684
598
|
creditV1,
|
|
599
|
+
decodeBroadcasterIntent,
|
|
685
600
|
decodeDeliveryHeader,
|
|
686
601
|
decodeMemoText,
|
|
602
|
+
decodePaymentNote,
|
|
603
|
+
decodePaymentRequest,
|
|
687
604
|
decryptCandidates,
|
|
688
605
|
defineGasBenchmarks,
|
|
689
606
|
encodeMemo,
|
|
607
|
+
encodePaymentNote,
|
|
608
|
+
encodePaymentRequest,
|
|
690
609
|
fundsV1,
|
|
691
610
|
readProtocolLogs,
|
|
692
|
-
|
|
611
|
+
verifyBroadcasterQuote
|
|
693
612
|
};
|
|
694
613
|
//# sourceMappingURL=index.js.map
|