@invisi-labs/sdk 1.1.6 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/broadcaster-3KTB4JO2.cjs +21 -0
- package/dist/broadcaster-3KTB4JO2.cjs.map +1 -0
- package/dist/broadcaster-7ZKKSTZQ.js +21 -0
- package/dist/broadcaster-preparation-PJ263WBL.cjs +15 -0
- package/dist/broadcaster-preparation-PJ263WBL.cjs.map +1 -0
- package/dist/broadcaster-preparation-ZQAPZAUG.js +15 -0
- package/dist/broadcaster-preparation-ZQAPZAUG.js.map +1 -0
- package/dist/chunk-2URICDPW.cjs +167 -0
- package/dist/chunk-2URICDPW.cjs.map +1 -0
- package/dist/chunk-3D24ABYR.js +86 -0
- package/dist/chunk-3D24ABYR.js.map +1 -0
- package/dist/chunk-3KTQQZOW.cjs +71 -0
- package/dist/chunk-3KTQQZOW.cjs.map +1 -0
- package/dist/chunk-4H4FARQK.js +1582 -0
- package/dist/chunk-4H4FARQK.js.map +1 -0
- package/dist/chunk-4SDP53YQ.cjs +765 -0
- package/dist/chunk-4SDP53YQ.cjs.map +1 -0
- package/dist/{chunk-QC6TQR3R.js → chunk-4VI6LYGH.js} +35 -12
- package/dist/chunk-4VI6LYGH.js.map +1 -0
- package/dist/chunk-BBSRP4MH.cjs +287 -0
- package/dist/chunk-BBSRP4MH.cjs.map +1 -0
- package/dist/chunk-DGMVGFKM.js +41 -0
- package/dist/chunk-DGMVGFKM.js.map +1 -0
- package/dist/chunk-GUD3LAUO.js +765 -0
- package/dist/chunk-GUD3LAUO.js.map +1 -0
- package/dist/chunk-HCB7RQ3S.cjs +79 -0
- package/dist/chunk-HCB7RQ3S.cjs.map +1 -0
- package/dist/{broadcaster-preparation-QVL62KM6.js → chunk-HKGJXQVN.js} +73 -34
- package/dist/chunk-HKGJXQVN.js.map +1 -0
- package/dist/chunk-HLH5JZD4.js +287 -0
- package/dist/chunk-HLH5JZD4.js.map +1 -0
- package/dist/chunk-J6TZZVAM.cjs +230 -0
- package/dist/chunk-J6TZZVAM.cjs.map +1 -0
- package/dist/chunk-LOR6PYH5.cjs +397 -0
- package/dist/chunk-LOR6PYH5.cjs.map +1 -0
- package/dist/chunk-MA2QZEF5.cjs +399 -0
- package/dist/chunk-MA2QZEF5.cjs.map +1 -0
- package/dist/chunk-MI4IP4ZD.cjs +86 -0
- package/dist/chunk-MI4IP4ZD.cjs.map +1 -0
- package/dist/chunk-NKYHYIRP.cjs +1582 -0
- package/dist/chunk-NKYHYIRP.cjs.map +1 -0
- package/dist/chunk-OCZLJKIA.cjs +41 -0
- package/dist/chunk-OCZLJKIA.cjs.map +1 -0
- package/dist/chunk-R5YAOJKB.js +71 -0
- package/dist/chunk-R5YAOJKB.js.map +1 -0
- package/dist/chunk-RY4M2CIQ.js +399 -0
- package/dist/chunk-RY4M2CIQ.js.map +1 -0
- package/dist/chunk-THMD5QO2.js +79 -0
- package/dist/chunk-THMD5QO2.js.map +1 -0
- package/dist/chunk-ZD2652WH.js +230 -0
- package/dist/chunk-ZD2652WH.js.map +1 -0
- package/dist/credit.cjs +1 -0
- package/dist/credit.cjs.map +1 -0
- package/dist/credit.d.cts +12 -0
- package/dist/credit.d.ts +12 -0
- package/dist/credit.js +1 -0
- package/dist/credit.js.map +1 -0
- package/dist/ethers.cjs +11 -385
- package/dist/ethers.cjs.map +1 -1
- package/dist/ethers.d.cts +16 -6
- package/dist/ethers.d.ts +16 -6
- package/dist/ethers.js +1 -1
- package/dist/index-CLhGwLxZ.d.cts +42 -0
- package/dist/index-Crp-ZFiM.d.ts +42 -0
- package/dist/index.cjs +479 -7847
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -710
- package/dist/index.d.ts +87 -710
- package/dist/index.js +407 -869
- package/dist/index.js.map +1 -1
- package/dist/note.cjs +11 -0
- package/dist/note.cjs.map +1 -0
- package/dist/note.d.cts +29 -0
- package/dist/note.d.ts +29 -0
- package/dist/note.js +11 -0
- package/dist/note.js.map +1 -0
- package/dist/operation-bridge-CoECSxfi.d.cts +472 -0
- package/dist/operation-bridge-CoECSxfi.d.ts +472 -0
- package/dist/preparation-HUBTVCZE.js +404 -0
- package/dist/preparation-HUBTVCZE.js.map +1 -0
- package/dist/preparation-QD3ZSYV5.cjs +404 -0
- package/dist/preparation-QD3ZSYV5.cjs.map +1 -0
- package/dist/provenance-preparation-43DN2WK7.cjs +16 -0
- package/dist/provenance-preparation-43DN2WK7.cjs.map +1 -0
- package/dist/provenance-preparation-HEZE4L5H.js +16 -0
- package/dist/provenance-preparation-HEZE4L5H.js.map +1 -0
- package/dist/provenance.cjs +59 -0
- package/dist/provenance.cjs.map +1 -0
- package/dist/provenance.d.cts +34 -0
- package/dist/provenance.d.ts +34 -0
- package/dist/provenance.js +59 -0
- package/dist/provenance.js.map +1 -0
- package/dist/receipt-logs-8Msm_HZS.d.ts +322 -0
- package/dist/receipt-logs-DWG4In8N.d.cts +322 -0
- package/dist/session-support.cjs +189 -0
- package/dist/session-support.cjs.map +1 -0
- package/dist/session-support.d.cts +284 -0
- package/dist/session-support.d.ts +284 -0
- package/dist/session-support.js +189 -0
- package/dist/session-support.js.map +1 -0
- package/package.json +25 -19
- package/dist/NoteOutput-DRkVnwlx.d.cts +0 -398
- package/dist/NoteOutput-DpDC6T2O.d.ts +0 -398
- package/dist/broadcaster-preparation-QVL62KM6.js.map +0 -1
- package/dist/chunk-4SD3MW6N.js +0 -4711
- package/dist/chunk-4SD3MW6N.js.map +0 -1
- package/dist/chunk-FL4TIGWW.js +0 -1781
- package/dist/chunk-FL4TIGWW.js.map +0 -1
- package/dist/chunk-QC6TQR3R.js.map +0 -1
- package/dist/indexeddb.cjs +0 -6878
- 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 -71
- package/dist/indexeddb.js.map +0 -1
- package/dist/memory.cjs +0 -6886
- 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 -11
- package/dist/sqlite.cjs +0 -6862
- 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 -55
- package/dist/sqlite.js.map +0 -1
- package/dist/state-C4HZFotm.d.cts +0 -78
- package/dist/state-C4HZFotm.d.ts +0 -78
- package/dist/store-8aBhnbGr.d.ts +0 -42
- package/dist/store-YrbXb6CY.d.cts +0 -42
- /package/dist/{memory.js.map → broadcaster-7ZKKSTZQ.js.map} +0 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import {
|
|
2
|
+
InvisiError,
|
|
3
|
+
NoteOutput,
|
|
4
|
+
materializeNoteOutput
|
|
5
|
+
} from "./chunk-GUD3LAUO.js";
|
|
6
|
+
import {
|
|
7
|
+
EthersAddressResolver
|
|
8
|
+
} from "./chunk-4VI6LYGH.js";
|
|
9
|
+
|
|
10
|
+
// src/credit/state.ts
|
|
11
|
+
import {
|
|
12
|
+
computeCreditCommitment,
|
|
13
|
+
deriveCreditUpdate
|
|
14
|
+
} from "@invisi-labs/primitives";
|
|
15
|
+
async function creditLeaf(state) {
|
|
16
|
+
return computeCreditCommitment(state.note);
|
|
17
|
+
}
|
|
18
|
+
async function assertCreditUpdatePlan(plan) {
|
|
19
|
+
const credit = plan.credit;
|
|
20
|
+
if (!credit?.update) return;
|
|
21
|
+
const input = credit.inputs[0], output = credit.outputs[0], changes = credit.updateChanges;
|
|
22
|
+
if (!input || !output || !changes)
|
|
23
|
+
throw Error("Credit update input, output and adjustment required");
|
|
24
|
+
const derived = await deriveCreditUpdate(input.state.note, changes, plan.deadline);
|
|
25
|
+
if (derived.nullifier !== credit.update.nullifier || derived.stateCommitment !== credit.update.creditCommitment || output.commitment !== derived.stateCommitment || await computeCreditCommitment(output.state.note) !== derived.stateCommitment)
|
|
26
|
+
throw Error("Credit update adjustment does not match frozen successor");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/notes/recipient-infrastructure.ts
|
|
30
|
+
import {
|
|
31
|
+
MissingCapabilityError,
|
|
32
|
+
ReceivingAddress
|
|
33
|
+
} from "@invisi-labs/primitives";
|
|
34
|
+
import { ProtocolRpcClient } from "@invisi-labs/rpc";
|
|
35
|
+
var recipientInfrastructure = /* @__PURE__ */ Symbol("SDK recipient infrastructure");
|
|
36
|
+
function configureRecipientInfrastructure(options) {
|
|
37
|
+
const { provider, protocolRpc, chainReader } = options;
|
|
38
|
+
const registry = options.protocol.deployment.registry;
|
|
39
|
+
const deployment = options.protocol.deployment;
|
|
40
|
+
const lookup = (source) => async (ownerAddress) => {
|
|
41
|
+
if (!registry) throw new MissingCapabilityError("receiving-key registry deployment");
|
|
42
|
+
if (source === "chain") {
|
|
43
|
+
if (!provider)
|
|
44
|
+
throw new MissingCapabilityError("ethers provider for registry lookup");
|
|
45
|
+
return new EthersAddressResolver(
|
|
46
|
+
provider,
|
|
47
|
+
{
|
|
48
|
+
chainId: deployment.chainId,
|
|
49
|
+
registryAddress: registry.address
|
|
50
|
+
},
|
|
51
|
+
registry.runtimeCodeHash
|
|
52
|
+
).lookup(ownerAddress);
|
|
53
|
+
}
|
|
54
|
+
if (!(protocolRpc instanceof ProtocolRpcClient))
|
|
55
|
+
throw new MissingCapabilityError("Protocol RPC recipient discovery");
|
|
56
|
+
const rpc = chainReader ? protocolRpc.withReader(chainReader) : protocolRpc;
|
|
57
|
+
const discovered = await rpc.getDeployment(deployment.chainId);
|
|
58
|
+
if (discovered.protocol.address !== deployment.protocol.address || discovered.protocol.runtimeCodeHash !== deployment.protocol.runtimeCodeHash || discovered.protocol.fromBlock !== deployment.protocol.fromBlock || discovered.registry?.address !== registry.address || discovered.registry.runtimeCodeHash !== registry.runtimeCodeHash || discovered.registry.fromBlock !== registry.fromBlock)
|
|
59
|
+
throw new Error("Recipient registry deployment pins mismatch");
|
|
60
|
+
const registration = await rpc.getReceivingAddress(ownerAddress);
|
|
61
|
+
return registration ? ReceivingAddress.parse(registration.receivingAddress).getReceivingKeys() : null;
|
|
62
|
+
};
|
|
63
|
+
return { ...options, [recipientInfrastructure]: lookup };
|
|
64
|
+
}
|
|
65
|
+
function recipientLookup(options, source = "protocol-rpc") {
|
|
66
|
+
return async (ownerAddress) => {
|
|
67
|
+
const lookup = options[recipientInfrastructure];
|
|
68
|
+
if (!lookup) throw new MissingCapabilityError("recipient infrastructure");
|
|
69
|
+
return lookup(source)(ownerAddress);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/discovery/membership.ts
|
|
74
|
+
import { FIELD_MODULUS, poseidonHash } from "@invisi-labs/primitives";
|
|
75
|
+
async function verifyMembership(reader, context, block, input, proof) {
|
|
76
|
+
if (proof.block.hash !== block.hash || proof.block.number !== block.number || proof.leaf !== input.commitment || proof.treeId !== input.treeId || proof.leafIndex !== input.leafIndex || proof.path.length !== 16 || !Number.isInteger(input.leafIndex) || input.leafIndex < 0 || input.leafIndex >= 65536 || !await reader.isKnownRoot(context, input.treeId, proof.root, block))
|
|
77
|
+
throw new Error("membership context/root mismatch");
|
|
78
|
+
let root = BigInt(input.commitment);
|
|
79
|
+
for (let i = 0; i < 16; i++) {
|
|
80
|
+
const sibling = proof.path[i];
|
|
81
|
+
if (typeof sibling !== "bigint" || sibling < 0n || sibling >= FIELD_MODULUS)
|
|
82
|
+
throw new Error("noncanonical membership path");
|
|
83
|
+
root = BigInt(
|
|
84
|
+
await poseidonHash(input.leafIndex >> i & 1 ? [sibling, root] : [root, sibling])
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
if (root !== BigInt(proof.root))
|
|
88
|
+
throw new Error("membership path does not produce canonical root");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/transactions/preparation/prepare-common.ts
|
|
92
|
+
import { getAddress } from "ethers";
|
|
93
|
+
import {
|
|
94
|
+
MissingCapabilityError as MissingCapabilityError2,
|
|
95
|
+
computeNoteCommitment,
|
|
96
|
+
computeNoteIdentity
|
|
97
|
+
} from "@invisi-labs/primitives";
|
|
98
|
+
import {
|
|
99
|
+
normalizeFunding
|
|
100
|
+
} from "@invisi-labs/protocol";
|
|
101
|
+
|
|
102
|
+
// src/transactions/preparation/selection.ts
|
|
103
|
+
function selectNotes(notes, target, reserved) {
|
|
104
|
+
if (typeof target !== "bigint" || target < 0n)
|
|
105
|
+
throw new Error("selection target must be a nonnegative amount");
|
|
106
|
+
if (target === 0n) return [];
|
|
107
|
+
const eligible = notes.filter(
|
|
108
|
+
(note) => note.spent === "unspent" && !reserved.has(note.commitment)
|
|
109
|
+
);
|
|
110
|
+
for (const record of eligible)
|
|
111
|
+
if (typeof record.note.amount !== "bigint" || record.note.amount <= 0n || record.note.amount >= 1n << 128n)
|
|
112
|
+
throw new Error("invalid verified note amount");
|
|
113
|
+
eligible.sort(
|
|
114
|
+
(a, b) => a.note.amount > b.note.amount ? -1 : a.note.amount < b.note.amount ? 1 : a.treeId < b.treeId ? -1 : a.treeId > b.treeId ? 1 : a.leafIndex - b.leafIndex || a.commitment.localeCompare(b.commitment)
|
|
115
|
+
);
|
|
116
|
+
const selected = [];
|
|
117
|
+
let total = 0n;
|
|
118
|
+
for (const record of eligible) {
|
|
119
|
+
selected.push(structuredClone(record));
|
|
120
|
+
total += record.note.amount;
|
|
121
|
+
if (total >= target) return selected;
|
|
122
|
+
}
|
|
123
|
+
throw new Error("insufficient verified available funds");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/transactions/preparation/prepare-common.ts
|
|
127
|
+
function positiveAmount(value, bits = 128) {
|
|
128
|
+
if (typeof value !== "bigint" || value <= 0n || value >= 1n << BigInt(bits)) {
|
|
129
|
+
throw new Error(`amount must be a positive uint${bits}`);
|
|
130
|
+
}
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
function copyApplicationPayload(value) {
|
|
134
|
+
if (value === void 0) return new Uint8Array();
|
|
135
|
+
if (!(value instanceof Uint8Array)) throw new TypeError("payload must be Uint8Array");
|
|
136
|
+
return Uint8Array.from(value);
|
|
137
|
+
}
|
|
138
|
+
function positiveOutputAmount(payment, outputIndex) {
|
|
139
|
+
try {
|
|
140
|
+
return positiveAmount(payment.amount);
|
|
141
|
+
} catch (cause) {
|
|
142
|
+
throw new InvisiError(
|
|
143
|
+
"PREPARATION_FAILED",
|
|
144
|
+
cause instanceof Error ? cause.message : "Invalid output amount",
|
|
145
|
+
{
|
|
146
|
+
address: payment.to,
|
|
147
|
+
field: "amount",
|
|
148
|
+
...outputIndex === void 0 ? {} : { outputIndex },
|
|
149
|
+
cause
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
var Preparation = class {
|
|
155
|
+
constructor(runtime) {
|
|
156
|
+
this.runtime = runtime;
|
|
157
|
+
}
|
|
158
|
+
#roots = /* @__PURE__ */ new Map();
|
|
159
|
+
async frame(requested) {
|
|
160
|
+
const { reader, options } = this.runtime;
|
|
161
|
+
await options.protocol.validateDeployment(reader);
|
|
162
|
+
const block = await reader.getBlock(await reader.getLatestBlockNumber());
|
|
163
|
+
const deadline = requested ?? block.timestamp + (options.transactionTTL ?? 300n);
|
|
164
|
+
if (typeof deadline !== "bigint" || deadline < block.timestamp || deadline >= 1n << 64n) {
|
|
165
|
+
throw new Error("invalid transaction deadline");
|
|
166
|
+
}
|
|
167
|
+
const state = await this.runtime.readInventory();
|
|
168
|
+
if (state.invalidated) {
|
|
169
|
+
throw new Error("wallet requires canonical resync");
|
|
170
|
+
}
|
|
171
|
+
for (const point of [state.observedThrough]) {
|
|
172
|
+
if (point && (await reader.getBlock(point.number)).hash !== point.hash) {
|
|
173
|
+
throw new Error("canonical wallet checkpoint changed; resync required");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
context: {
|
|
178
|
+
...options.protocol.context,
|
|
179
|
+
now: block.timestamp,
|
|
180
|
+
timestamp: block.timestamp
|
|
181
|
+
},
|
|
182
|
+
block,
|
|
183
|
+
deadline
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
async assertBlock(frame) {
|
|
187
|
+
if ((await this.runtime.reader.getBlock(frame.block.number)).hash !== frame.block.hash) {
|
|
188
|
+
throw new Error("canonical block changed while preparing");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async funding(source, frame) {
|
|
192
|
+
if (!source) {
|
|
193
|
+
return { funding: {}, fundingMode: "none" };
|
|
194
|
+
}
|
|
195
|
+
positiveAmount(source.amount);
|
|
196
|
+
const payer = getAddress(source.payer);
|
|
197
|
+
const mode = source.authorization ?? "signature";
|
|
198
|
+
if (BigInt(payer) === 0n) {
|
|
199
|
+
throw new Error("funding payer must be nonzero");
|
|
200
|
+
}
|
|
201
|
+
if (mode !== "signature" && mode !== "caller") {
|
|
202
|
+
throw new Error("invalid funding authorization mode");
|
|
203
|
+
}
|
|
204
|
+
if (mode === "caller" && (source.sigNonce || source.deadline)) {
|
|
205
|
+
throw new Error("caller funding uses zero nonce/deadline");
|
|
206
|
+
}
|
|
207
|
+
const funding = normalizeFunding({
|
|
208
|
+
payer,
|
|
209
|
+
amount: source.amount,
|
|
210
|
+
sigNonce: mode === "caller" ? 0n : source.sigNonce ?? await this.runtime.reader.getFundingNonce(
|
|
211
|
+
frame.context,
|
|
212
|
+
payer,
|
|
213
|
+
frame.block
|
|
214
|
+
),
|
|
215
|
+
deadline: mode === "caller" ? 0n : source.deadline ?? frame.deadline,
|
|
216
|
+
...source.permit ? { permit: structuredClone(source.permit) } : {}
|
|
217
|
+
});
|
|
218
|
+
if (mode === "signature" && funding.deadline < frame.block.timestamp) {
|
|
219
|
+
throw new Error("funding authorization deadline has expired");
|
|
220
|
+
}
|
|
221
|
+
return { funding, fundingMode: mode };
|
|
222
|
+
}
|
|
223
|
+
async change(amount, context) {
|
|
224
|
+
const address = this.runtime.changeAddress ?? this.runtime.options.wallet.getReceivingAddress().toString();
|
|
225
|
+
try {
|
|
226
|
+
const output = await NoteOutput.create({
|
|
227
|
+
chainId: context.chainId,
|
|
228
|
+
protocolAddress: context.protocolAddress,
|
|
229
|
+
tokenAddress: context.tokenAddress,
|
|
230
|
+
amount,
|
|
231
|
+
recipient: address.startsWith("0i") ? { type: "invisi", address } : { type: "evm", address }
|
|
232
|
+
});
|
|
233
|
+
return await this.output(output, context, true);
|
|
234
|
+
} catch (cause) {
|
|
235
|
+
if (cause instanceof InvisiError)
|
|
236
|
+
throw new InvisiError(cause.code, cause.message, {
|
|
237
|
+
address,
|
|
238
|
+
field: "changeAddress",
|
|
239
|
+
...cause.capability ? { capability: cause.capability } : {},
|
|
240
|
+
cause: cause.cause ?? cause
|
|
241
|
+
});
|
|
242
|
+
throw new InvisiError(
|
|
243
|
+
"PREPARATION_FAILED",
|
|
244
|
+
cause instanceof Error ? cause.message : "Change output preparation failed",
|
|
245
|
+
{ address, field: "changeAddress", cause }
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
async output(payment, context, change = false, outputIndex) {
|
|
250
|
+
try {
|
|
251
|
+
positiveOutputAmount(payment, outputIndex);
|
|
252
|
+
const output = await materializeNoteOutput(
|
|
253
|
+
payment,
|
|
254
|
+
context,
|
|
255
|
+
(source) => recipientLookup(this.runtime.options, source)
|
|
256
|
+
);
|
|
257
|
+
return { ...output, change };
|
|
258
|
+
} catch (cause) {
|
|
259
|
+
if (cause instanceof InvisiError) {
|
|
260
|
+
throw new InvisiError(cause.code, cause.message, {
|
|
261
|
+
address: cause.address ?? payment.to,
|
|
262
|
+
field: change ? "changeAddress" : cause.field ?? "to",
|
|
263
|
+
...outputIndex === void 0 ? {} : { outputIndex },
|
|
264
|
+
...cause.capability ? { capability: cause.capability } : {},
|
|
265
|
+
cause: cause.cause ?? cause
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
throw new InvisiError(
|
|
269
|
+
"PREPARATION_FAILED",
|
|
270
|
+
cause instanceof Error ? cause.message : "Output preparation failed",
|
|
271
|
+
{
|
|
272
|
+
address: payment.to,
|
|
273
|
+
field: change ? "changeAddress" : "to",
|
|
274
|
+
...outputIndex === void 0 ? {} : { outputIndex },
|
|
275
|
+
cause
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
async membership(input, frame) {
|
|
281
|
+
const { options, reader } = this.runtime;
|
|
282
|
+
if (!options.protocolRpc)
|
|
283
|
+
throw new MissingCapabilityError2("Protocol RPC membership provider");
|
|
284
|
+
const root = this.#roots.get(input.treeId);
|
|
285
|
+
const proof = await options.protocolRpc.getMembership(
|
|
286
|
+
frame.context,
|
|
287
|
+
{
|
|
288
|
+
...input,
|
|
289
|
+
...root ? { root } : {}
|
|
290
|
+
},
|
|
291
|
+
frame.block
|
|
292
|
+
);
|
|
293
|
+
if (root && proof.root !== root)
|
|
294
|
+
throw new Error("membership provider changed the pinned source root");
|
|
295
|
+
await verifyMembership(reader, frame.context, frame.block, input, proof);
|
|
296
|
+
this.#roots.set(input.treeId, proof.root);
|
|
297
|
+
return { ...proof, path: [...proof.path] };
|
|
298
|
+
}
|
|
299
|
+
async selectFunds(target, frame, maxInputs = 190) {
|
|
300
|
+
const { scope, options, reader } = this.runtime;
|
|
301
|
+
const state = await this.runtime.readInventory();
|
|
302
|
+
if (state.invalidated) {
|
|
303
|
+
throw new Error("wallet requires canonical resync");
|
|
304
|
+
}
|
|
305
|
+
const need = target > 0n ? target : 0n;
|
|
306
|
+
const nullifier = options.wallet.exportNullifier();
|
|
307
|
+
const reserved = new Set(Object.keys(state.reservations));
|
|
308
|
+
const eligible = Object.values(state.notes).filter(
|
|
309
|
+
(note) => note.note.tokenAddress === BigInt(frame.context.tokenAddress) && note.spent === "unspent" && !reserved.has(note.commitment)
|
|
310
|
+
);
|
|
311
|
+
const byId = new Map(eligible.map((note) => [note.commitment, note]));
|
|
312
|
+
const choices = this.runtime.inputSelection ? [...this.runtime.inputSelection] : this.runtime.explicitInputs ? eligible.map((note) => note.commitment) : need > 0n && options.coinSelector ? await options.coinSelector.select(storedCandidates(eligible), need) : selectNotes(eligible, need, reserved).map((note) => note.commitment);
|
|
313
|
+
if (!Array.isArray(choices) || new Set(choices).size !== choices.length || choices.some((id) => !byId.has(id))) {
|
|
314
|
+
throw new Error("invalid or duplicate coin selection");
|
|
315
|
+
}
|
|
316
|
+
const selected = choices.map((id) => structuredClone(byId.get(id)));
|
|
317
|
+
const amount = selected.reduce((sum, input) => sum + input.note.amount, 0n);
|
|
318
|
+
if (amount < need) {
|
|
319
|
+
throw new Error("coin selection has insufficient verified funds");
|
|
320
|
+
}
|
|
321
|
+
if (selected.length > maxInputs || amount >= 1n << 135n) {
|
|
322
|
+
throw new Error("coin selection exceeds transaction capacity");
|
|
323
|
+
}
|
|
324
|
+
const inputs = [];
|
|
325
|
+
const sources = /* @__PURE__ */ new Map();
|
|
326
|
+
const nullifiers = [];
|
|
327
|
+
for (const input of selected) {
|
|
328
|
+
const note = input.note;
|
|
329
|
+
const c = frame.context;
|
|
330
|
+
if (note.ownerAddress !== scope.ownerAddress || note.chainId !== c.chainId || note.protocolAddress !== BigInt(c.protocolAddress) || note.tokenAddress !== BigInt(c.tokenAddress) || await computeNoteCommitment(
|
|
331
|
+
c.chainId,
|
|
332
|
+
BigInt(c.protocolAddress),
|
|
333
|
+
await computeNoteIdentity(BigInt(note.ownerAddress), note.nonce),
|
|
334
|
+
note.tokenAddress,
|
|
335
|
+
note.amount
|
|
336
|
+
) !== input.commitment) {
|
|
337
|
+
throw new Error("selected input preimage/context mismatch");
|
|
338
|
+
}
|
|
339
|
+
const proof = await this.membership(input, frame);
|
|
340
|
+
const path = [...proof.path];
|
|
341
|
+
const root = proof.root;
|
|
342
|
+
if (sources.has(input.treeId) && sources.get(input.treeId) !== root) {
|
|
343
|
+
throw new Error("source roots do not share a canonical snapshot");
|
|
344
|
+
}
|
|
345
|
+
sources.set(input.treeId, root);
|
|
346
|
+
inputs.push({
|
|
347
|
+
note,
|
|
348
|
+
commitment: input.commitment,
|
|
349
|
+
treeId: input.treeId,
|
|
350
|
+
leafIndex: input.leafIndex,
|
|
351
|
+
path
|
|
352
|
+
});
|
|
353
|
+
nullifiers.push(
|
|
354
|
+
await nullifier.compute(
|
|
355
|
+
c.chainId,
|
|
356
|
+
BigInt(c.protocolAddress),
|
|
357
|
+
input.commitment,
|
|
358
|
+
input.note.nonce
|
|
359
|
+
)
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
await this.assertUnspent(nullifiers, frame);
|
|
363
|
+
return {
|
|
364
|
+
inputs,
|
|
365
|
+
sources: [...sources].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([treeId, merkleRoot]) => ({ treeId, merkleRoot })),
|
|
366
|
+
nullifiers,
|
|
367
|
+
amount
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
async assertUnspent(nullifiers, frame) {
|
|
371
|
+
if (nullifiers.length) {
|
|
372
|
+
const spent = await this.runtime.reader.isSpent(frame.context, nullifiers, frame.block);
|
|
373
|
+
if (spent.length !== nullifiers.length || spent.some((value) => value !== false)) {
|
|
374
|
+
throw new Error("selected input spent or invalid spent-state response");
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
function storedCandidates(inputs) {
|
|
380
|
+
const records = inputs.filter(
|
|
381
|
+
(input) => "blockNumber" in input && typeof input.blockNumber === "bigint" && "blockHash" in input && typeof input.blockHash === "string"
|
|
382
|
+
);
|
|
383
|
+
if (records.length !== inputs.length)
|
|
384
|
+
throw new Error("custom coin selection requires stored receipt metadata");
|
|
385
|
+
return structuredClone(records);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export {
|
|
389
|
+
creditLeaf,
|
|
390
|
+
assertCreditUpdatePlan,
|
|
391
|
+
configureRecipientInfrastructure,
|
|
392
|
+
recipientLookup,
|
|
393
|
+
verifyMembership,
|
|
394
|
+
positiveAmount,
|
|
395
|
+
copyApplicationPayload,
|
|
396
|
+
positiveOutputAmount,
|
|
397
|
+
Preparation
|
|
398
|
+
};
|
|
399
|
+
//# sourceMappingURL=chunk-RY4M2CIQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/credit/state.ts","../src/notes/recipient-infrastructure.ts","../src/discovery/membership.ts","../src/transactions/preparation/prepare-common.ts","../src/transactions/preparation/selection.ts"],"sourcesContent":["import type { Hex } from \"@invisi-labs/protocol/contracts\";\n\nimport {\n computeCreditCommitment,\n deriveCreditUpdate,\n type CreditDeliveryState,\n} from \"@invisi-labs/primitives\";\nexport async function creditLeaf(state: CreditDeliveryState): Promise<Hex> {\n return computeCreditCommitment(state.note);\n}\n\n/** Rebuild from the original input and delta; cached output balances are never update intent. */\nexport async function assertCreditUpdatePlan(\n plan: import(\"../transactions/operation-types\").OperationPlan\n): Promise<void> {\n const credit = plan.credit;\n if (!credit?.update) return;\n const input = credit.inputs[0],\n output = credit.outputs[0],\n changes = credit.updateChanges;\n if (!input || !output || !changes)\n throw Error(\"Credit update input, output and adjustment required\");\n const derived = await deriveCreditUpdate(input.state.note, changes, plan.deadline);\n if (\n derived.nullifier !== credit.update.nullifier ||\n derived.stateCommitment !== credit.update.creditCommitment ||\n output.commitment !== derived.stateCommitment ||\n (await computeCreditCommitment(output.state.note)) !== derived.stateCommitment\n )\n throw Error(\"Credit update adjustment does not match frozen successor\");\n}\n","import {\n MissingCapabilityError,\n ReceivingAddress,\n type ReceivingKeyLookup,\n} from \"@invisi-labs/primitives\";\nimport { ProtocolRpcClient } from \"@invisi-labs/rpc\";\nimport { EthersAddressResolver } from \"../integrations/ethers\";\nimport type { TransactionInfrastructure } from \"../transactions/operation-types\";\n\nexport type AddressLookup = \"chain\" | \"protocol-rpc\";\nconst recipientInfrastructure = Symbol(\"SDK recipient infrastructure\");\ntype InternalInfrastructure = {\n [recipientInfrastructure]?: (source: AddressLookup) => ReceivingKeyLookup;\n};\n\n/** Capture deployment pins before a Protocol is bound to an operation's token. */\nexport function configureRecipientInfrastructure<T extends TransactionInfrastructure>(\n options: T\n): T {\n const { provider, protocolRpc, chainReader } = options;\n const registry = options.protocol.deployment.registry;\n const deployment = options.protocol.deployment;\n const lookup =\n (source: AddressLookup): ReceivingKeyLookup =>\n async (ownerAddress) => {\n if (!registry) throw new MissingCapabilityError(\"receiving-key registry deployment\");\n if (source === \"chain\") {\n if (!provider)\n throw new MissingCapabilityError(\"ethers provider for registry lookup\");\n return new EthersAddressResolver(\n provider,\n {\n chainId: deployment.chainId,\n registryAddress: registry.address,\n },\n registry.runtimeCodeHash\n ).lookup(ownerAddress);\n }\n if (!(protocolRpc instanceof ProtocolRpcClient))\n throw new MissingCapabilityError(\"Protocol RPC recipient discovery\");\n const rpc = chainReader ? protocolRpc.withReader(chainReader) : protocolRpc;\n const discovered = await rpc.getDeployment(deployment.chainId);\n if (\n discovered.protocol.address !== deployment.protocol.address ||\n discovered.protocol.runtimeCodeHash !== deployment.protocol.runtimeCodeHash ||\n discovered.protocol.fromBlock !== deployment.protocol.fromBlock ||\n discovered.registry?.address !== registry.address ||\n discovered.registry.runtimeCodeHash !== registry.runtimeCodeHash ||\n discovered.registry.fromBlock !== registry.fromBlock\n )\n throw new Error(\"Recipient registry deployment pins mismatch\");\n const registration = await rpc.getReceivingAddress(ownerAddress);\n return registration\n ? ReceivingAddress.parse(registration.receivingAddress).getReceivingKeys()\n : null;\n };\n return { ...options, [recipientInfrastructure]: lookup };\n}\n\n/** Internal lookup plumbing; public APIs never accept resolver functions. */\nexport function recipientLookup(\n options: object,\n source: AddressLookup = \"protocol-rpc\"\n): ReceivingKeyLookup {\n return async (ownerAddress) => {\n const lookup = (options as InternalInfrastructure)[recipientInfrastructure];\n if (!lookup) throw new MissingCapabilityError(\"recipient infrastructure\");\n return lookup(source)(ownerAddress);\n };\n}\n","import type { Hex } from \"@invisi-labs/protocol/contracts\";\n\nimport { FIELD_MODULUS, poseidonHash } from \"@invisi-labs/primitives\";\nimport type {\n CanonicalBlock,\n ChainReader,\n Membership,\n TransactionContext,\n} from \"@invisi-labs/protocol\";\nexport async function verifyMembership(\n reader: ChainReader,\n context: TransactionContext,\n block: CanonicalBlock,\n input: { treeId: bigint; leafIndex: number; commitment: Hex },\n proof: Membership\n): Promise<void> {\n if (\n proof.block.hash !== block.hash ||\n proof.block.number !== block.number ||\n proof.leaf !== input.commitment ||\n proof.treeId !== input.treeId ||\n proof.leafIndex !== input.leafIndex ||\n proof.path.length !== 16 ||\n !Number.isInteger(input.leafIndex) ||\n input.leafIndex < 0 ||\n input.leafIndex >= 65536 ||\n !(await reader.isKnownRoot(context, input.treeId, proof.root, block))\n )\n throw new Error(\"membership context/root mismatch\");\n let root = BigInt(input.commitment);\n for (let i = 0; i < 16; i++) {\n const sibling = proof.path[i]!;\n if (typeof sibling !== \"bigint\" || sibling < 0n || sibling >= FIELD_MODULUS)\n throw new Error(\"noncanonical membership path\");\n root = BigInt(\n await poseidonHash((input.leafIndex >> i) & 1 ? [sibling, root] : [root, sibling])\n );\n }\n if (root !== BigInt(proof.root))\n throw new Error(\"membership path does not produce canonical root\");\n}\n","import type { Hex } from \"@invisi-labs/protocol/contracts\";\nimport { getAddress } from \"ethers\";\nimport { InvisiError } from \"../../errors\";\nimport { NoteOutput, materializeNoteOutput } from \"../../notes/NoteOutput\";\nimport { recipientLookup } from \"../../notes/recipient-infrastructure\";\n\nimport {\n MissingCapabilityError,\n computeNoteCommitment,\n computeNoteIdentity,\n} from \"@invisi-labs/primitives\";\nimport {\n normalizeFunding,\n type CanonicalBlock,\n type FundingInput,\n type Membership,\n type TransactionContext,\n} from \"@invisi-labs/protocol\";\nimport { type NoteMembership } from \"@invisi-labs/prover\";\nimport { verifyMembership } from \"../../discovery/membership\";\nimport type {\n FundingSource,\n OperationPlan,\n PreparationRuntime,\n PreparedOutput,\n} from \"../operation-types\";\nimport type { NoteRecord } from \"../records\";\nimport { selectNotes, type SelectableNote } from \"./selection\";\n\nexport interface PreparationFrame {\n context: TransactionContext;\n block: CanonicalBlock;\n deadline: bigint;\n}\n\nexport function positiveAmount(value: bigint, bits = 128): bigint {\n if (typeof value !== \"bigint\" || value <= 0n || value >= 1n << BigInt(bits)) {\n throw new Error(`amount must be a positive uint${bits}`);\n }\n return value;\n}\n\n/** Reject non-byte JavaScript inputs before copying; empty bytes explicitly clear metadata. */\nexport function copyApplicationPayload(value?: Uint8Array): Uint8Array {\n if (value === undefined) return new Uint8Array();\n if (!(value instanceof Uint8Array)) throw new TypeError(\"payload must be Uint8Array\");\n return Uint8Array.from(value);\n}\n\n/** Preserve the offending payment position even when summing transfer amounts first. */\nexport function positiveOutputAmount(payment: NoteOutput, outputIndex?: number): bigint {\n try {\n return positiveAmount(payment.amount);\n } catch (cause) {\n throw new InvisiError(\n \"PREPARATION_FAILED\",\n cause instanceof Error ? cause.message : \"Invalid output amount\",\n {\n address: payment.to,\n field: \"amount\",\n ...(outputIndex === undefined ? {} : { outputIndex }),\n cause,\n }\n );\n }\n}\n\n/** Shared local preparation checks; private witnesses never enter this object's public records. */\nexport class Preparation<Result> {\n readonly #roots = new Map<bigint, Hex>();\n constructor(readonly runtime: PreparationRuntime<Result>) {}\n\n async frame(requested?: bigint): Promise<PreparationFrame> {\n const { reader, options } = this.runtime;\n await options.protocol.validateDeployment(reader);\n const block = await reader.getBlock(await reader.getLatestBlockNumber());\n const deadline = requested ?? block.timestamp + (options.transactionTTL ?? 300n);\n if (typeof deadline !== \"bigint\" || deadline < block.timestamp || deadline >= 1n << 64n) {\n throw new Error(\"invalid transaction deadline\");\n }\n const state = await this.runtime.readInventory();\n if (state.invalidated) {\n throw new Error(\"wallet requires canonical resync\");\n }\n for (const point of [state.observedThrough]) {\n if (point && (await reader.getBlock(point.number)).hash !== point.hash) {\n throw new Error(\"canonical wallet checkpoint changed; resync required\");\n }\n }\n return {\n context: {\n ...options.protocol.context,\n now: block.timestamp,\n timestamp: block.timestamp,\n },\n block,\n deadline,\n };\n }\n\n async assertBlock(frame: PreparationFrame): Promise<void> {\n if ((await this.runtime.reader.getBlock(frame.block.number)).hash !== frame.block.hash) {\n throw new Error(\"canonical block changed while preparing\");\n }\n }\n\n async funding(\n source: FundingSource | undefined,\n frame: PreparationFrame\n ): Promise<{ funding: FundingInput; fundingMode: OperationPlan[\"fundingMode\"] }> {\n if (!source) {\n return { funding: {}, fundingMode: \"none\" };\n }\n positiveAmount(source.amount);\n const payer = getAddress(source.payer) as Hex;\n const mode = source.authorization ?? \"signature\";\n if (BigInt(payer) === 0n) {\n throw new Error(\"funding payer must be nonzero\");\n }\n if (mode !== \"signature\" && mode !== \"caller\") {\n throw new Error(\"invalid funding authorization mode\");\n }\n if (mode === \"caller\" && (source.sigNonce || source.deadline)) {\n throw new Error(\"caller funding uses zero nonce/deadline\");\n }\n const funding = normalizeFunding({\n payer,\n amount: source.amount,\n sigNonce:\n mode === \"caller\"\n ? 0n\n : (source.sigNonce ??\n (await this.runtime.reader.getFundingNonce(\n frame.context,\n payer,\n frame.block\n ))),\n deadline: mode === \"caller\" ? 0n : (source.deadline ?? frame.deadline),\n ...(source.permit ? { permit: structuredClone(source.permit) } : {}),\n });\n if (mode === \"signature\" && funding.deadline < frame.block.timestamp) {\n throw new Error(\"funding authorization deadline has expired\");\n }\n return { funding, fundingMode: mode };\n }\n\n async change(amount: bigint, context: TransactionContext): Promise<PreparedOutput> {\n const address =\n this.runtime.changeAddress ??\n this.runtime.options.wallet.getReceivingAddress().toString();\n try {\n const output = await NoteOutput.create({\n chainId: context.chainId,\n protocolAddress: context.protocolAddress,\n tokenAddress: context.tokenAddress,\n amount,\n recipient: address.startsWith(\"0i\")\n ? { type: \"invisi\", address }\n : { type: \"evm\", address },\n });\n return await this.output(output, context, true);\n } catch (cause) {\n if (cause instanceof InvisiError)\n throw new InvisiError(cause.code, cause.message, {\n address,\n field: \"changeAddress\",\n ...(cause.capability ? { capability: cause.capability } : {}),\n cause: cause.cause ?? cause,\n });\n throw new InvisiError(\n \"PREPARATION_FAILED\",\n cause instanceof Error ? cause.message : \"Change output preparation failed\",\n { address, field: \"changeAddress\", cause }\n );\n }\n }\n\n async output(\n payment: NoteOutput,\n context: TransactionContext,\n change = false,\n outputIndex?: number\n ): Promise<PreparedOutput> {\n try {\n positiveOutputAmount(payment, outputIndex);\n const output = await materializeNoteOutput(payment, context, (source) =>\n recipientLookup(this.runtime.options, source)\n );\n return { ...output, change };\n } catch (cause) {\n if (cause instanceof InvisiError) {\n throw new InvisiError(cause.code, cause.message, {\n address: cause.address ?? payment.to,\n field: change ? \"changeAddress\" : (cause.field ?? \"to\"),\n ...(outputIndex === undefined ? {} : { outputIndex }),\n ...(cause.capability ? { capability: cause.capability } : {}),\n cause: cause.cause ?? cause,\n });\n }\n throw new InvisiError(\n \"PREPARATION_FAILED\",\n cause instanceof Error ? cause.message : \"Output preparation failed\",\n {\n address: payment.to,\n field: change ? \"changeAddress\" : \"to\",\n ...(outputIndex === undefined ? {} : { outputIndex }),\n cause,\n }\n );\n }\n }\n\n async membership(\n input: { treeId: bigint; leafIndex: number; commitment: Hex },\n frame: PreparationFrame\n ): Promise<Membership> {\n const { options, reader } = this.runtime;\n if (!options.protocolRpc)\n throw new MissingCapabilityError(\"Protocol RPC membership provider\");\n const root = this.#roots.get(input.treeId);\n const proof = await options.protocolRpc.getMembership(\n frame.context,\n {\n ...input,\n ...(root ? { root } : {}),\n },\n frame.block\n );\n if (root && proof.root !== root)\n throw new Error(\"membership provider changed the pinned source root\");\n await verifyMembership(reader, frame.context, frame.block, input, proof);\n this.#roots.set(input.treeId, proof.root);\n return { ...proof, path: [...proof.path] };\n }\n\n async selectFunds(target: bigint, frame: PreparationFrame, maxInputs = 190) {\n const { scope, options, reader } = this.runtime;\n const state = await this.runtime.readInventory();\n if (state.invalidated) {\n throw new Error(\"wallet requires canonical resync\");\n }\n const need = target > 0n ? target : 0n;\n const nullifier = options.wallet.exportNullifier();\n const reserved = new Set(Object.keys(state.reservations));\n const eligible = Object.values(state.notes).filter(\n (note) =>\n note.note.tokenAddress === BigInt(frame.context.tokenAddress) &&\n note.spent === \"unspent\" &&\n !reserved.has(note.commitment)\n );\n const byId = new Map(eligible.map((note) => [note.commitment, note]));\n const choices = this.runtime.inputSelection\n ? [...this.runtime.inputSelection]\n : this.runtime.explicitInputs\n ? eligible.map((note) => note.commitment)\n : need > 0n && options.coinSelector\n ? await options.coinSelector.select(storedCandidates(eligible), need)\n : selectNotes(eligible, need, reserved).map((note) => note.commitment);\n if (\n !Array.isArray(choices) ||\n new Set(choices).size !== choices.length ||\n choices.some((id) => !byId.has(id))\n ) {\n throw new Error(\"invalid or duplicate coin selection\");\n }\n const selected = choices.map((id) => structuredClone(byId.get(id)!));\n const amount = selected.reduce((sum, input) => sum + input.note.amount, 0n);\n if (amount < need) {\n throw new Error(\"coin selection has insufficient verified funds\");\n }\n if (selected.length > maxInputs || amount >= 1n << 135n) {\n throw new Error(\"coin selection exceeds transaction capacity\");\n }\n const inputs: Array<NoteMembership & { commitment: Hex }> = [];\n const sources = new Map<bigint, Hex>();\n const nullifiers: Hex[] = [];\n for (const input of selected) {\n const note = input.note;\n const c = frame.context;\n if (\n note.ownerAddress !== scope.ownerAddress ||\n note.chainId !== c.chainId ||\n note.protocolAddress !== BigInt(c.protocolAddress) ||\n note.tokenAddress !== BigInt(c.tokenAddress) ||\n (await computeNoteCommitment(\n c.chainId,\n BigInt(c.protocolAddress),\n await computeNoteIdentity(BigInt(note.ownerAddress), note.nonce),\n note.tokenAddress,\n note.amount\n )) !== input.commitment\n ) {\n throw new Error(\"selected input preimage/context mismatch\");\n }\n const proof = await this.membership(input, frame);\n const path = [...proof.path];\n const root = proof.root;\n if (sources.has(input.treeId) && sources.get(input.treeId) !== root) {\n throw new Error(\"source roots do not share a canonical snapshot\");\n }\n sources.set(input.treeId, root);\n inputs.push({\n note,\n commitment: input.commitment,\n treeId: input.treeId,\n leafIndex: input.leafIndex,\n path,\n });\n nullifiers.push(\n await nullifier!.compute(\n c.chainId,\n BigInt(c.protocolAddress),\n input.commitment,\n input.note.nonce\n )\n );\n }\n await this.assertUnspent(nullifiers, frame);\n return {\n inputs,\n sources: [...sources]\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([treeId, merkleRoot]) => ({ treeId, merkleRoot })),\n nullifiers,\n amount,\n };\n }\n\n async assertUnspent(nullifiers: readonly Hex[], frame: PreparationFrame): Promise<void> {\n if (nullifiers.length) {\n const spent = await this.runtime.reader.isSpent(frame.context, nullifiers, frame.block);\n if (spent.length !== nullifiers.length || spent.some((value) => value !== false)) {\n throw new Error(\"selected input spent or invalid spent-state response\");\n }\n }\n }\n}\n\n// Managed custom selectors retain access to receipt metadata. Explicit inputs\n// use their own exact selection and never pass fabricated metadata to a selector.\nfunction storedCandidates(inputs: readonly SelectableNote[]): NoteRecord[] {\n const records = inputs.filter(\n (input): input is NoteRecord =>\n \"blockNumber\" in input &&\n typeof input.blockNumber === \"bigint\" &&\n \"blockHash\" in input &&\n typeof input.blockHash === \"string\"\n );\n if (records.length !== inputs.length)\n throw new Error(\"custom coin selection requires stored receipt metadata\");\n return structuredClone(records);\n}\n","import type { NoteRecord } from \"../records\";\n/** Minimal input count by descending amount, with deterministic tree/position/commitment ties. */\nexport type SelectableNote = Pick<\n NoteRecord,\n \"note\" | \"commitment\" | \"treeId\" | \"leafIndex\" | \"spent\"\n>;\nexport function selectNotes<T extends SelectableNote>(\n notes: readonly T[],\n target: bigint,\n reserved: ReadonlySet<string>\n): T[] {\n if (typeof target !== \"bigint\" || target < 0n)\n throw new Error(\"selection target must be a nonnegative amount\");\n if (target === 0n) return [];\n const eligible = notes.filter(\n (note) => note.spent === \"unspent\" && !reserved.has(note.commitment)\n );\n for (const record of eligible)\n if (\n typeof record.note.amount !== \"bigint\" ||\n record.note.amount <= 0n ||\n record.note.amount >= 1n << 128n\n )\n throw new Error(\"invalid verified note amount\");\n eligible.sort((a, b) =>\n a.note.amount > b.note.amount\n ? -1\n : a.note.amount < b.note.amount\n ? 1\n : a.treeId < b.treeId\n ? -1\n : a.treeId > b.treeId\n ? 1\n : a.leafIndex - b.leafIndex || a.commitment.localeCompare(b.commitment)\n );\n const selected: T[] = [];\n let total = 0n;\n for (const record of eligible) {\n selected.push(structuredClone(record));\n total += record.note.amount;\n if (total >= target) return selected;\n }\n throw new Error(\"insufficient verified available funds\");\n}\n"],"mappings":"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","names":["MissingCapabilityError","MissingCapabilityError"]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// src/transactions/signing/statement.ts
|
|
2
|
+
import { creditPeriods } from "@invisi-labs/primitives";
|
|
3
|
+
import { WitnessBuilder } from "@invisi-labs/prover";
|
|
4
|
+
import {
|
|
5
|
+
createCreditPublicInputs,
|
|
6
|
+
creditDrawPublicInputs,
|
|
7
|
+
updateCreditPublicInputs,
|
|
8
|
+
transactUserPublicInputs
|
|
9
|
+
} from "@invisi-labs/protocol";
|
|
10
|
+
function requiresAuthorization(plan) {
|
|
11
|
+
if (plan.credit?.revocation) return false;
|
|
12
|
+
const ownerOnlyFullWithdrawal = !plan.credit && plan.inputs.length > 0 && plan.outputs.length === 0 && plan.params !== void 0 && plan.params.noteCommitments.length === 0 && (plan.params.feeAmount ?? 0n) === 0n && BigInt(plan.params.feeRecipient ?? 0n) === 0n && (plan.funding.amount ?? 0n) === 0n && plan.fundingMode === "none" && BigInt(plan.params.withdrawalAddress) === BigInt(plan.scope.ownerAddress) && plan.params.withdrawalAmount === plan.inputs.reduce((sum, input) => sum + input.note.amount, 0n) && plan.inputs.every((input) => input.note.ownerAddress === plan.scope.ownerAddress);
|
|
13
|
+
if (ownerOnlyFullWithdrawal) return false;
|
|
14
|
+
return !!plan.credit || plan.inputs.length > 0;
|
|
15
|
+
}
|
|
16
|
+
function statement(plan) {
|
|
17
|
+
if (!plan.statement) throw new Error("operation has no proof statement");
|
|
18
|
+
const credit = plan.credit;
|
|
19
|
+
let words;
|
|
20
|
+
if (credit?.update)
|
|
21
|
+
words = updateCreditPublicInputs(plan.context, credit.update, credit.deliveries);
|
|
22
|
+
else if (credit?.creation)
|
|
23
|
+
words = createCreditPublicInputs(plan.context, credit.creation, credit.deliveries);
|
|
24
|
+
else if (credit?.draw)
|
|
25
|
+
words = creditDrawPublicInputs(plan.context, credit.draw, credit.deliveries);
|
|
26
|
+
else {
|
|
27
|
+
words = transactUserPublicInputs(
|
|
28
|
+
plan.context,
|
|
29
|
+
plan.params,
|
|
30
|
+
plan.outputs.map((output) => output.delivery),
|
|
31
|
+
plan.funding
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const result = WitnessBuilder.statement(
|
|
35
|
+
plan.statement.circuitName,
|
|
36
|
+
words,
|
|
37
|
+
plan.statement.target
|
|
38
|
+
);
|
|
39
|
+
if (JSON.stringify(result) !== JSON.stringify(plan.statement)) {
|
|
40
|
+
throw new Error("frozen operation statement changed");
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
function authorizationRequest(plan) {
|
|
45
|
+
let operation = "transact";
|
|
46
|
+
if (plan.kind === "draw-credit") {
|
|
47
|
+
operation = "credit-draw";
|
|
48
|
+
} else if (plan.kind === "create-credit" || plan.kind === "update-credit") {
|
|
49
|
+
operation = plan.kind;
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
context: {
|
|
53
|
+
chainId: plan.context.chainId,
|
|
54
|
+
protocolAddress: BigInt(plan.context.protocolAddress)
|
|
55
|
+
},
|
|
56
|
+
operation,
|
|
57
|
+
capacity: plan.capacity,
|
|
58
|
+
words: statement(plan).expectedPublicInputs.slice(0, -2).map(BigInt)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function operationExpired(plan, now) {
|
|
62
|
+
if (now > plan.deadline) return true;
|
|
63
|
+
const credit = plan.credit;
|
|
64
|
+
if (credit?.inputs.some((i) => now > i.state.note.expiry)) return true;
|
|
65
|
+
if (credit?.draw) {
|
|
66
|
+
const prepared = creditPeriods(plan.preparedAt.timestamp), current = creditPeriods(now);
|
|
67
|
+
if (prepared.currentDay !== current.currentDay || prepared.currentMonth !== current.currentMonth)
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export {
|
|
74
|
+
requiresAuthorization,
|
|
75
|
+
statement,
|
|
76
|
+
authorizationRequest,
|
|
77
|
+
operationExpired
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=chunk-THMD5QO2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/transactions/signing/statement.ts"],"sourcesContent":["import { creditPeriods, type WalletOperation } from \"@invisi-labs/primitives\";\nimport { WitnessBuilder } from \"@invisi-labs/prover\";\nimport {\n createCreditPublicInputs,\n creditDrawPublicInputs,\n updateCreditPublicInputs,\n transactUserPublicInputs,\n} from \"@invisi-labs/protocol\";\nimport type { Hex } from \"@invisi-labs/protocol/contracts\";\nimport type { ProofStatement } from \"@invisi-labs/prover\";\nimport type { OperationPlan } from \"../operation-types\";\n\nexport function requiresAuthorization(plan: OperationPlan): boolean {\n if (plan.credit?.revocation) return false;\n // Derive the exemption from the actual frozen transaction, never an API/mode flag.\n const ownerOnlyFullWithdrawal =\n !plan.credit &&\n plan.inputs.length > 0 &&\n plan.outputs.length === 0 &&\n plan.params !== undefined &&\n plan.params.noteCommitments.length === 0 &&\n (plan.params.feeAmount ?? 0n) === 0n &&\n BigInt(plan.params.feeRecipient ?? 0n) === 0n &&\n (plan.funding.amount ?? 0n) === 0n &&\n plan.fundingMode === \"none\" &&\n BigInt(plan.params.withdrawalAddress) === BigInt(plan.scope.ownerAddress) &&\n plan.params.withdrawalAmount ===\n plan.inputs.reduce((sum, input) => sum + input.note.amount, 0n) &&\n plan.inputs.every((input) => input.note.ownerAddress === plan.scope.ownerAddress);\n if (ownerOnlyFullWithdrawal) return false;\n return !!plan.credit || plan.inputs.length > 0;\n}\n\nexport function statement(plan: OperationPlan): ProofStatement {\n if (!plan.statement) throw new Error(\"operation has no proof statement\");\n\n const credit = plan.credit;\n let words: Hex[];\n if (credit?.update)\n words = updateCreditPublicInputs(plan.context, credit.update, credit.deliveries);\n else if (credit?.creation)\n words = createCreditPublicInputs(plan.context, credit.creation, credit.deliveries);\n else if (credit?.draw)\n words = creditDrawPublicInputs(plan.context, credit.draw, credit.deliveries);\n else {\n words = transactUserPublicInputs(\n plan.context,\n plan.params!,\n plan.outputs.map((output) => output.delivery),\n plan.funding\n );\n }\n\n const result = WitnessBuilder.statement(\n plan.statement.circuitName,\n words,\n plan.statement.target\n );\n if (JSON.stringify(result) !== JSON.stringify(plan.statement)) {\n throw new Error(\"frozen operation statement changed\");\n }\n return result;\n}\n\nexport function authorizationRequest(plan: OperationPlan) {\n let operation: WalletOperation = \"transact\";\n if (plan.kind === \"draw-credit\") {\n operation = \"credit-draw\";\n } else if (plan.kind === \"create-credit\" || plan.kind === \"update-credit\") {\n operation = plan.kind;\n }\n\n return {\n context: {\n chainId: plan.context.chainId,\n protocolAddress: BigInt(plan.context.protocolAddress),\n },\n operation,\n capacity: plan.capacity!,\n words: statement(plan).expectedPublicInputs.slice(0, -2).map(BigInt),\n };\n}\n\nexport function operationExpired(plan: OperationPlan, now: bigint): boolean {\n if (now > plan.deadline) return true;\n\n const credit = plan.credit;\n if (credit?.inputs.some((i) => now > i.state.note.expiry)) return true;\n if (credit?.draw) {\n const prepared = creditPeriods(plan.preparedAt.timestamp),\n current = creditPeriods(now);\n if (\n prepared.currentDay !== current.currentDay ||\n prepared.currentMonth !== current.currentMonth\n )\n return true;\n }\n return false;\n}\n"],"mappings":";AAAA,SAAS,qBAA2C;AACpD,SAAS,sBAAsB;AAC/B;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AAKA,SAAS,sBAAsB,MAA8B;AAChE,MAAI,KAAK,QAAQ,WAAY,QAAO;AAEpC,QAAM,0BACF,CAAC,KAAK,UACN,KAAK,OAAO,SAAS,KACrB,KAAK,QAAQ,WAAW,KACxB,KAAK,WAAW,UAChB,KAAK,OAAO,gBAAgB,WAAW,MACtC,KAAK,OAAO,aAAa,QAAQ,MAClC,OAAO,KAAK,OAAO,gBAAgB,EAAE,MAAM,OAC1C,KAAK,QAAQ,UAAU,QAAQ,MAChC,KAAK,gBAAgB,UACrB,OAAO,KAAK,OAAO,iBAAiB,MAAM,OAAO,KAAK,MAAM,YAAY,KACxE,KAAK,OAAO,qBACR,KAAK,OAAO,OAAO,CAAC,KAAK,UAAU,MAAM,MAAM,KAAK,QAAQ,EAAE,KAClE,KAAK,OAAO,MAAM,CAAC,UAAU,MAAM,KAAK,iBAAiB,KAAK,MAAM,YAAY;AACpF,MAAI,wBAAyB,QAAO;AACpC,SAAO,CAAC,CAAC,KAAK,UAAU,KAAK,OAAO,SAAS;AACjD;AAEO,SAAS,UAAU,MAAqC;AAC3D,MAAI,CAAC,KAAK,UAAW,OAAM,IAAI,MAAM,kCAAkC;AAEvE,QAAM,SAAS,KAAK;AACpB,MAAI;AACJ,MAAI,QAAQ;AACR,YAAQ,yBAAyB,KAAK,SAAS,OAAO,QAAQ,OAAO,UAAU;AAAA,WAC1E,QAAQ;AACb,YAAQ,yBAAyB,KAAK,SAAS,OAAO,UAAU,OAAO,UAAU;AAAA,WAC5E,QAAQ;AACb,YAAQ,uBAAuB,KAAK,SAAS,OAAO,MAAM,OAAO,UAAU;AAAA,OAC1E;AACD,YAAQ;AAAA,MACJ,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK,QAAQ,IAAI,CAAC,WAAW,OAAO,QAAQ;AAAA,MAC5C,KAAK;AAAA,IACT;AAAA,EACJ;AAEA,QAAM,SAAS,eAAe;AAAA,IAC1B,KAAK,UAAU;AAAA,IACf;AAAA,IACA,KAAK,UAAU;AAAA,EACnB;AACA,MAAI,KAAK,UAAU,MAAM,MAAM,KAAK,UAAU,KAAK,SAAS,GAAG;AAC3D,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACxD;AACA,SAAO;AACX;AAEO,SAAS,qBAAqB,MAAqB;AACtD,MAAI,YAA6B;AACjC,MAAI,KAAK,SAAS,eAAe;AAC7B,gBAAY;AAAA,EAChB,WAAW,KAAK,SAAS,mBAAmB,KAAK,SAAS,iBAAiB;AACvE,gBAAY,KAAK;AAAA,EACrB;AAEA,SAAO;AAAA,IACH,SAAS;AAAA,MACL,SAAS,KAAK,QAAQ;AAAA,MACtB,iBAAiB,OAAO,KAAK,QAAQ,eAAe;AAAA,IACxD;AAAA,IACA;AAAA,IACA,UAAU,KAAK;AAAA,IACf,OAAO,UAAU,IAAI,EAAE,qBAAqB,MAAM,GAAG,EAAE,EAAE,IAAI,MAAM;AAAA,EACvE;AACJ;AAEO,SAAS,iBAAiB,MAAqB,KAAsB;AACxE,MAAI,MAAM,KAAK,SAAU,QAAO;AAEhC,QAAM,SAAS,KAAK;AACpB,MAAI,QAAQ,OAAO,KAAK,CAAC,MAAM,MAAM,EAAE,MAAM,KAAK,MAAM,EAAG,QAAO;AAClE,MAAI,QAAQ,MAAM;AACd,UAAM,WAAW,cAAc,KAAK,WAAW,SAAS,GACpD,UAAU,cAAc,GAAG;AAC/B,QACI,SAAS,eAAe,QAAQ,cAChC,SAAS,iBAAiB,QAAQ;AAElC,aAAO;AAAA,EACf;AACA,SAAO;AACX;","names":[]}
|