@invisi-labs/sdk 1.1.6 → 1.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{NoteOutput-DRkVnwlx.d.cts → broadcaster-preparation-DMxxZ_1Q.d.cts} +309 -120
- package/dist/{NoteOutput-DpDC6T2O.d.ts → broadcaster-preparation-DMxxZ_1Q.d.ts} +309 -120
- 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-4SD3MW6N.js → chunk-EHP5TBC6.js} +1347 -2493
- package/dist/chunk-EHP5TBC6.js.map +1 -0
- package/dist/{broadcaster-preparation-QVL62KM6.js → chunk-GTPUYPQL.js} +6 -6
- package/dist/chunk-GTPUYPQL.js.map +1 -0
- package/dist/{chunk-FL4TIGWW.js → chunk-ITTGBIG2.js} +617 -521
- 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-QC6TQR3R.js → chunk-XHKQ2BO2.js} +12 -12
- 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 +3 -4
- package/dist/ethers.d.ts +3 -4
- package/dist/ethers.js +1 -1
- package/dist/index.cjs +451 -7845
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -702
- package/dist/index.d.ts +72 -702
- package/dist/index.js +374 -862
- 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/broadcaster-preparation-QVL62KM6.js.map +0 -1
- package/dist/chunk-4SD3MW6N.js.map +0 -1
- 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-preparation-RMWQFYNL.js.map} +0 -0
|
@@ -2,28 +2,67 @@ import {
|
|
|
2
2
|
TransactionPreparation,
|
|
3
3
|
authorizationRequest,
|
|
4
4
|
configureRecipientInfrastructure,
|
|
5
|
-
creditLeaf,
|
|
6
|
-
creditReservationKey,
|
|
7
|
-
encodeOperation,
|
|
8
5
|
exportBroadcasterIntent,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
operationExpired,
|
|
12
|
-
operationSources,
|
|
6
|
+
exportPaymentNote,
|
|
7
|
+
positiveAmount,
|
|
13
8
|
prepareWithErrors,
|
|
14
|
-
publicProof,
|
|
15
9
|
requiresAuthorization,
|
|
16
|
-
statement
|
|
17
|
-
|
|
18
|
-
walletBalance,
|
|
19
|
-
walletBalances,
|
|
20
|
-
witnessFor
|
|
21
|
-
} from "./chunk-FL4TIGWW.js";
|
|
10
|
+
statement
|
|
11
|
+
} from "./chunk-ITTGBIG2.js";
|
|
22
12
|
import {
|
|
23
|
-
EthersChainReader
|
|
24
|
-
|
|
13
|
+
EthersChainReader,
|
|
14
|
+
EthersFundingVerifier
|
|
15
|
+
} from "./chunk-XHKQ2BO2.js";
|
|
16
|
+
|
|
17
|
+
// src/transactions/records.ts
|
|
18
|
+
function creditReservationKey(commitment) {
|
|
19
|
+
return "credit:" + commitment;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/transactions/preparation/inputs.ts
|
|
23
|
+
function inputFacts(plan) {
|
|
24
|
+
const credit = plan.credit;
|
|
25
|
+
const noteNullifiers = plan.params?.nullifiers ?? credit?.creation?.nullifiers ?? [];
|
|
26
|
+
let creditNullifiers = [];
|
|
27
|
+
if (credit?.draw) {
|
|
28
|
+
creditNullifiers = credit.draw.nullifiers;
|
|
29
|
+
} else if (credit?.topUp) {
|
|
30
|
+
creditNullifiers = [credit.topUp.creditNullifier];
|
|
31
|
+
} else if (credit?.cancel) {
|
|
32
|
+
creditNullifiers = [credit.cancel.nullifier];
|
|
33
|
+
}
|
|
34
|
+
if (noteNullifiers.length !== plan.inputs.length || creditNullifiers.length !== (credit?.inputs.length ?? 0)) {
|
|
35
|
+
throw new Error("operation input statement count mismatch");
|
|
36
|
+
}
|
|
37
|
+
return [
|
|
38
|
+
...plan.inputs.map((input, index2) => ({
|
|
39
|
+
key: input.commitment,
|
|
40
|
+
reservationKey: input.commitment,
|
|
41
|
+
nullifier: noteNullifiers[index2],
|
|
42
|
+
kind: "notes"
|
|
43
|
+
})),
|
|
44
|
+
...(credit?.inputs ?? []).map((input, index2) => ({
|
|
45
|
+
key: input.commitment + ":" + input.role,
|
|
46
|
+
reservationKey: creditReservationKey(input.commitment),
|
|
47
|
+
nullifier: creditNullifiers[index2],
|
|
48
|
+
kind: "credits"
|
|
49
|
+
}))
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
function operationSources(plan) {
|
|
53
|
+
const noteSources = plan.params?.sources;
|
|
54
|
+
if (noteSources != null) return noteSources;
|
|
55
|
+
const credit = plan.credit;
|
|
56
|
+
if (credit?.topUp) {
|
|
57
|
+
return [...credit.topUp.creation.sources, credit.topUp.creditSource];
|
|
58
|
+
}
|
|
59
|
+
if (credit?.creation) return credit.creation.sources;
|
|
60
|
+
if (credit?.draw) return credit.draw.sources;
|
|
61
|
+
if (credit?.cancel) return [credit.cancel.source];
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
25
64
|
|
|
26
|
-
// src/data/gas-benchmarks.json
|
|
65
|
+
// src/gas/data/gas-benchmarks.json
|
|
27
66
|
var gas_benchmarks_default = [
|
|
28
67
|
{
|
|
29
68
|
formatVersion: 1,
|
|
@@ -1724,7 +1763,7 @@ var gas_benchmarks_default = [
|
|
|
1724
1763
|
}
|
|
1725
1764
|
];
|
|
1726
1765
|
|
|
1727
|
-
// src/gas
|
|
1766
|
+
// src/gas/profiles.ts
|
|
1728
1767
|
var word = /^0x[0-9a-fA-F]{64}$/;
|
|
1729
1768
|
var address = /^0x[0-9a-fA-F]{40}$/;
|
|
1730
1769
|
var decimal = /^(0|[1-9][0-9]*)$/;
|
|
@@ -1807,6 +1846,86 @@ function sameDeployment(a, b) {
|
|
|
1807
1846
|
function sameCircuit(a, b) {
|
|
1808
1847
|
return a === null ? b === null : b !== null && a.circuitName === b.circuitName && a.target === b.target && a.artifactSha256.toLowerCase() === b.artifactSha256.toLowerCase() && a.vkSha256.toLowerCase() === b.vkSha256.toLowerCase();
|
|
1809
1848
|
}
|
|
1849
|
+
|
|
1850
|
+
// src/gas/shape.ts
|
|
1851
|
+
function estimateShapeGas(request, profiles = defaultProfiles) {
|
|
1852
|
+
if (request.shape.target === "noir-recursive") {
|
|
1853
|
+
throw new Error(
|
|
1854
|
+
"Estimate gas for the complete aggregate, not an individual recursive member"
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1857
|
+
const measurements = profiles.flatMap((profile) => profile.measurements);
|
|
1858
|
+
const matching = measurements.filter(
|
|
1859
|
+
(measurement) => measurement.shape.kind === request.shape.kind && measurement.shape.target === request.shape.target && sameCircuit(measurement.circuit, request.circuit)
|
|
1860
|
+
);
|
|
1861
|
+
const exact = matching.filter(
|
|
1862
|
+
(measurement) => shapeKeys.every((key) => measurement.shape[key] === request.shape[key])
|
|
1863
|
+
);
|
|
1864
|
+
if (exact.length) {
|
|
1865
|
+
const means = exact.map((m) => {
|
|
1866
|
+
const count = BigInt(m.samples.length);
|
|
1867
|
+
return (m.samples.reduce((sum, s) => sum + BigInt(s.gasUsed), 0n) + count - 1n) / count;
|
|
1868
|
+
});
|
|
1869
|
+
return { gasUnits: means.reduce((max, v) => v > max ? v : max) };
|
|
1870
|
+
}
|
|
1871
|
+
if (["create-credit", "draw-credit", "top-up-credit", "cancel-credit"].includes(
|
|
1872
|
+
request.shape.kind
|
|
1873
|
+
) && !matching.length)
|
|
1874
|
+
throw new Error("No current Credit gas reference for this artifact and verification key");
|
|
1875
|
+
const candidates = matching.length ? matching : measurements.filter(
|
|
1876
|
+
(measurement) => measurement.shape.kind === request.shape.kind && measurement.shape.target === request.shape.target
|
|
1877
|
+
);
|
|
1878
|
+
if (!candidates.length) throw new Error("No gas reference for this operation kind and target");
|
|
1879
|
+
const dimensions = [
|
|
1880
|
+
"noteInputs",
|
|
1881
|
+
"noteOutputs",
|
|
1882
|
+
"sourceTrees",
|
|
1883
|
+
"creditInputs",
|
|
1884
|
+
"creditOutputs",
|
|
1885
|
+
"creditRemainders",
|
|
1886
|
+
"deliveryBytes",
|
|
1887
|
+
...!matching.length ? ["capacity"] : []
|
|
1888
|
+
];
|
|
1889
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1890
|
+
for (const candidate of candidates) {
|
|
1891
|
+
const key = JSON.stringify([
|
|
1892
|
+
shapeKeys.map((k) => candidate.shape[k]),
|
|
1893
|
+
candidate.circuit?.circuitName,
|
|
1894
|
+
candidate.circuit?.target,
|
|
1895
|
+
candidate.circuit?.artifactSha256.toLowerCase(),
|
|
1896
|
+
candidate.circuit?.vkSha256.toLowerCase()
|
|
1897
|
+
]);
|
|
1898
|
+
const previous = groups.get(key);
|
|
1899
|
+
groups.set(
|
|
1900
|
+
key,
|
|
1901
|
+
previous ? { ...candidate, samples: [...previous.samples, ...candidate.samples] } : candidate
|
|
1902
|
+
);
|
|
1903
|
+
}
|
|
1904
|
+
const estimates = [...groups.values()].map((measurement) => {
|
|
1905
|
+
let numerator = 1n;
|
|
1906
|
+
let denominator = 1n;
|
|
1907
|
+
for (const key of dimensions) {
|
|
1908
|
+
const current = BigInt(request.shape[key]) + 1n;
|
|
1909
|
+
const baseline = BigInt(measurement.shape[key]) + 1n;
|
|
1910
|
+
if (current * denominator > numerator * baseline) {
|
|
1911
|
+
numerator = current;
|
|
1912
|
+
denominator = baseline;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
let gas = measurement.samples.reduce(
|
|
1916
|
+
(max, sample) => BigInt(sample.gasUsed) > max ? BigInt(sample.gasUsed) : max,
|
|
1917
|
+
0n
|
|
1918
|
+
);
|
|
1919
|
+
gas = (gas * numerator + denominator - 1n) / denominator;
|
|
1920
|
+
if (request.shape.funding === "signature" && measurement.shape.funding !== "signature")
|
|
1921
|
+
gas *= 2n;
|
|
1922
|
+
if (request.shape.permit && !measurement.shape.permit) gas *= 2n;
|
|
1923
|
+
return gas;
|
|
1924
|
+
});
|
|
1925
|
+
return { gasUnits: estimates.reduce((min, gas) => gas < min ? gas : min) };
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
// src/gas/gas-estimation.ts
|
|
1810
1929
|
function estimateBenchmarkGas(plan, protocol, profiles) {
|
|
1811
1930
|
const credit = plan.credit;
|
|
1812
1931
|
const deliveries = credit?.deliveries ?? plan.outputs.map((out) => out.delivery);
|
|
@@ -1882,680 +2001,278 @@ function estimateBenchmarkGas(plan, protocol, profiles) {
|
|
|
1882
2001
|
}
|
|
1883
2002
|
return { ...base, available: false, reason: "shape-not-covered" };
|
|
1884
2003
|
}
|
|
1885
|
-
function
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
2004
|
+
function estimateGasReference(plan, protocol, profiles) {
|
|
2005
|
+
const reference = estimateBenchmarkGas(plan, protocol, profiles);
|
|
2006
|
+
if (reference.available) return { gasUnits: reference.gasUnits };
|
|
2007
|
+
return estimateShapeGas(reference.request);
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
// src/protocol/AddressRegistry.ts
|
|
2011
|
+
import { AddressRegistry as Registry } from "@invisi-labs/protocol";
|
|
2012
|
+
var AddressRegistry = class extends Registry {
|
|
2013
|
+
prepareRegistration(options) {
|
|
2014
|
+
return prepareWithErrors("prepareRegistration", async () => {
|
|
2015
|
+
if ("provider" in options) {
|
|
2016
|
+
const { provider, ...preparation } = options;
|
|
2017
|
+
return super.prepareRegistration({
|
|
2018
|
+
...preparation,
|
|
2019
|
+
reader: new EthersChainReader(provider)
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
return super.prepareRegistration(options);
|
|
2023
|
+
});
|
|
1890
2024
|
}
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
2025
|
+
};
|
|
2026
|
+
|
|
2027
|
+
// src/notes/TransactionOutput.ts
|
|
2028
|
+
var TransactionOutput = class {
|
|
2029
|
+
#output;
|
|
2030
|
+
constructor(output) {
|
|
2031
|
+
this.#output = structuredClone(output);
|
|
2032
|
+
Object.freeze(this);
|
|
2033
|
+
}
|
|
2034
|
+
get context() {
|
|
2035
|
+
return structuredClone(this.#output.context);
|
|
2036
|
+
}
|
|
2037
|
+
get recipient() {
|
|
2038
|
+
return structuredClone(this.#output.recipient);
|
|
2039
|
+
}
|
|
2040
|
+
get amount() {
|
|
2041
|
+
return this.#output.amount;
|
|
2042
|
+
}
|
|
2043
|
+
get commitment() {
|
|
2044
|
+
return this.#output.commitment;
|
|
2045
|
+
}
|
|
2046
|
+
get identity() {
|
|
2047
|
+
return this.#output.identity;
|
|
2048
|
+
}
|
|
2049
|
+
get nonce() {
|
|
2050
|
+
return this.#output.note?.nonce;
|
|
2051
|
+
}
|
|
2052
|
+
get change() {
|
|
2053
|
+
return this.#output.change;
|
|
2054
|
+
}
|
|
2055
|
+
exportPaymentNote() {
|
|
2056
|
+
return exportPaymentNote(this.#output);
|
|
2057
|
+
}
|
|
2058
|
+
};
|
|
2059
|
+
|
|
2060
|
+
// src/transactions/proving/proof.ts
|
|
2061
|
+
function publicProof(bundle) {
|
|
2062
|
+
return {
|
|
2063
|
+
formatVersion: bundle.formatVersion,
|
|
2064
|
+
circuitName: bundle.circuitName,
|
|
2065
|
+
target: bundle.target,
|
|
2066
|
+
bbVersion: bundle.bbVersion,
|
|
2067
|
+
artifactSha256: bundle.artifactSha256,
|
|
2068
|
+
vkSha256: bundle.vkSha256,
|
|
2069
|
+
proofHex: bundle.proofHex,
|
|
2070
|
+
proofSha256: bundle.proofSha256,
|
|
2071
|
+
publicInputs: [...bundle.publicInputs]
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
// src/transactions/proving/proof-integrity.ts
|
|
2076
|
+
import { sha256 } from "@noble/hashes/sha256";
|
|
2077
|
+
import { bytesToHex } from "@noble/hashes/utils";
|
|
2078
|
+
import { getBytes } from "ethers";
|
|
2079
|
+
function check(ok, message) {
|
|
2080
|
+
if (!ok) throw new Error(message);
|
|
2081
|
+
}
|
|
2082
|
+
function sameStatement(proof, expected) {
|
|
2083
|
+
check(
|
|
2084
|
+
proof.formatVersion === 1 && proof.bbVersion === "6.0.0" && proof.circuitName === expected.circuitName && proof.target === expected.target && proof.artifactSha256 === expected.artifactSha256 && proof.vkSha256 === expected.vkSha256,
|
|
2085
|
+
"proof release/target mismatch"
|
|
1894
2086
|
);
|
|
1895
|
-
|
|
1896
|
-
|
|
2087
|
+
check(
|
|
2088
|
+
proof.publicInputs.length === expected.expectedPublicInputs.length && proof.publicInputs.every((word2, i) => word2 === expected.expectedPublicInputs[i]),
|
|
2089
|
+
"proof public statement mismatch"
|
|
1897
2090
|
);
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
return (m.samples.reduce((sum, s) => sum + BigInt(s.gasUsed), 0n) + count - 1n) / count;
|
|
1902
|
-
});
|
|
1903
|
-
return { gasUnits: means.reduce((max, v) => v > max ? v : max) };
|
|
1904
|
-
}
|
|
1905
|
-
if (["create-credit", "draw-credit", "top-up-credit", "cancel-credit"].includes(
|
|
1906
|
-
request.shape.kind
|
|
1907
|
-
) && !matching.length)
|
|
1908
|
-
throw new Error("No current Credit gas reference for this artifact and verification key");
|
|
1909
|
-
const candidates = matching.length ? matching : measurements.filter(
|
|
1910
|
-
(measurement) => measurement.shape.kind === request.shape.kind && measurement.shape.target === request.shape.target
|
|
2091
|
+
check(
|
|
2092
|
+
/^0x(?:[a-fA-F0-9]{2})+$/.test(proof.proofHex) && bytesToHex(sha256(getBytes(proof.proofHex))) === proof.proofSha256,
|
|
2093
|
+
"proof integrity mismatch"
|
|
1911
2094
|
);
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
// src/transactions/execution/calldata.ts
|
|
2098
|
+
import {
|
|
2099
|
+
buildCancelCreditCalldata,
|
|
2100
|
+
buildCreateCreditCalldata,
|
|
2101
|
+
buildCreditDrawCalldata,
|
|
2102
|
+
buildShieldCalldata,
|
|
2103
|
+
buildTopUpCreditCalldata,
|
|
2104
|
+
buildTransactCalldata
|
|
2105
|
+
} from "@invisi-labs/protocol";
|
|
2106
|
+
async function encodeOperation(saved) {
|
|
2107
|
+
const plan = saved.plan;
|
|
2108
|
+
const credit = plan.credit;
|
|
2109
|
+
const caller = plan.fundingMode === "caller" ? plan.funding.payer : void 0;
|
|
2110
|
+
const common = { context: plan.context, proof: saved.proof?.proofHex ?? "0x" };
|
|
2111
|
+
const funded = { ...common, funding: plan.funding, ...caller ? { caller } : {} };
|
|
2112
|
+
let encoded;
|
|
2113
|
+
if (credit?.topUp) {
|
|
2114
|
+
encoded = buildTopUpCreditCalldata({
|
|
2115
|
+
...funded,
|
|
2116
|
+
params: credit.topUp,
|
|
2117
|
+
deliveries: credit.deliveries
|
|
2118
|
+
});
|
|
2119
|
+
} else if (credit?.creation) {
|
|
2120
|
+
encoded = buildCreateCreditCalldata({
|
|
2121
|
+
...funded,
|
|
2122
|
+
params: credit.creation,
|
|
2123
|
+
deliveries: credit.deliveries
|
|
2124
|
+
});
|
|
2125
|
+
} else if (credit?.draw) {
|
|
2126
|
+
encoded = buildCreditDrawCalldata({
|
|
2127
|
+
...common,
|
|
2128
|
+
params: credit.draw,
|
|
2129
|
+
deliveries: credit.deliveries
|
|
2130
|
+
});
|
|
2131
|
+
} else if (credit?.cancel) {
|
|
2132
|
+
encoded = buildCancelCreditCalldata({ ...common, params: credit.cancel });
|
|
2133
|
+
} else if (plan.kind === "shield") {
|
|
2134
|
+
encoded = await buildShieldCalldata({
|
|
2135
|
+
...funded,
|
|
2136
|
+
params: plan.shieldParams,
|
|
2137
|
+
outputs: plan.shieldOutputs,
|
|
2138
|
+
deliveries: plan.outputs.map((output) => output.delivery)
|
|
2139
|
+
});
|
|
2140
|
+
} else {
|
|
2141
|
+
encoded = buildTransactCalldata({
|
|
2142
|
+
...funded,
|
|
2143
|
+
params: plan.params,
|
|
2144
|
+
deliveries: plan.outputs.map((output) => output.delivery)
|
|
2145
|
+
});
|
|
1937
2146
|
}
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
2147
|
+
return { to: plan.context.protocolAddress, data: encoded.data, value: 0n };
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
// src/transactions/proving/witness.ts
|
|
2151
|
+
import { WitnessBuilder } from "@invisi-labs/protocol";
|
|
2152
|
+
async function witnessFor(protocol, plan, authorization) {
|
|
2153
|
+
const builder = new WitnessBuilder(protocol);
|
|
2154
|
+
const credit = plan.credit;
|
|
2155
|
+
if (credit) {
|
|
2156
|
+
if (!authorization) throw new Error("private Credit wallet authorization required");
|
|
2157
|
+
if (credit.topUp) {
|
|
2158
|
+
return builder.topUpCredit({
|
|
2159
|
+
params: credit.topUp,
|
|
2160
|
+
deliveries: credit.deliveries,
|
|
2161
|
+
funding: plan.funding,
|
|
2162
|
+
inputs: plan.inputs,
|
|
2163
|
+
state: credit.outputs[0].state,
|
|
2164
|
+
...plan.outputs[0] ? { change: plan.outputs[0].note } : {},
|
|
2165
|
+
previous: credit.inputs[0],
|
|
2166
|
+
authorization
|
|
2167
|
+
});
|
|
1948
2168
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
2169
|
+
if (credit.creation) {
|
|
2170
|
+
return builder.createCredit({
|
|
2171
|
+
params: credit.creation,
|
|
2172
|
+
deliveries: credit.deliveries,
|
|
2173
|
+
funding: plan.funding,
|
|
2174
|
+
inputs: plan.inputs,
|
|
2175
|
+
state: credit.outputs[0].state,
|
|
2176
|
+
...plan.outputs[0] ? { change: plan.outputs[0].note } : {},
|
|
2177
|
+
authorization
|
|
2178
|
+
});
|
|
2179
|
+
}
|
|
2180
|
+
if (credit.draw) {
|
|
2181
|
+
return builder.drawCredit({
|
|
2182
|
+
timestamp: plan.preparedAt.timestamp,
|
|
2183
|
+
params: credit.draw,
|
|
2184
|
+
deliveries: credit.deliveries,
|
|
2185
|
+
inputs: credit.inputs,
|
|
2186
|
+
amounts: credit.amounts,
|
|
2187
|
+
authorization
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
if (credit.cancel) {
|
|
2191
|
+
return builder.cancelCredit({
|
|
2192
|
+
params: credit.cancel,
|
|
2193
|
+
input: credit.inputs[0],
|
|
2194
|
+
authorization
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
throw new Error("unsupported Credit operation");
|
|
2198
|
+
}
|
|
2199
|
+
return builder.transact({
|
|
2200
|
+
params: plan.params,
|
|
2201
|
+
deliveries: plan.outputs.map((output) => output.delivery),
|
|
2202
|
+
funding: plan.funding,
|
|
2203
|
+
inputs: plan.inputs,
|
|
2204
|
+
outputs: plan.outputs.map((output) => ({
|
|
2205
|
+
identity: output.identity,
|
|
2206
|
+
amount: output.amount,
|
|
2207
|
+
...output.note ? { note: output.note } : {}
|
|
2208
|
+
})),
|
|
2209
|
+
...authorization ? { authorization } : {},
|
|
2210
|
+
target: plan.statement.target
|
|
1958
2211
|
});
|
|
1959
|
-
return { gasUnits: estimates.reduce((min, gas) => gas < min ? gas : min) };
|
|
1960
|
-
}
|
|
1961
|
-
function estimateGasReference(plan, protocol, profiles) {
|
|
1962
|
-
const reference2 = estimateBenchmarkGas(plan, protocol, profiles);
|
|
1963
|
-
if (reference2.available) return { gasUnits: reference2.gasUnits };
|
|
1964
|
-
return estimateShapeGas(reference2.request);
|
|
1965
2212
|
}
|
|
1966
2213
|
|
|
1967
|
-
// src/
|
|
1968
|
-
import {
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
"leafIndices",
|
|
1986
|
-
"remainderLeafIndices"
|
|
1987
|
-
]) {
|
|
1988
|
-
const values = decoded.args.getValue(key);
|
|
1989
|
-
if (values) args[key] = values.toArray(true);
|
|
2214
|
+
// src/transactions/signing/funding.ts
|
|
2215
|
+
import {
|
|
2216
|
+
encodeAggregateFundingTypedData,
|
|
2217
|
+
encodeCreateCreditFundingTypedData,
|
|
2218
|
+
encodeShieldFundingTypedData,
|
|
2219
|
+
encodeTopUpCreditFundingTypedData,
|
|
2220
|
+
encodeTransactFundingTypedData
|
|
2221
|
+
} from "@invisi-labs/protocol";
|
|
2222
|
+
async function fundingTypedData(saved) {
|
|
2223
|
+
const plan = saved.plan;
|
|
2224
|
+
const credit = plan.credit;
|
|
2225
|
+
if (credit?.topUp) {
|
|
2226
|
+
return encodeTopUpCreditFundingTypedData(
|
|
2227
|
+
plan.context,
|
|
2228
|
+
credit.topUp,
|
|
2229
|
+
credit.deliveries,
|
|
2230
|
+
plan.funding
|
|
2231
|
+
);
|
|
1990
2232
|
}
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
2233
|
+
if (credit?.creation) {
|
|
2234
|
+
return encodeCreateCreditFundingTypedData(
|
|
2235
|
+
plan.context,
|
|
2236
|
+
credit.creation,
|
|
2237
|
+
credit.deliveries,
|
|
2238
|
+
plan.funding
|
|
2239
|
+
);
|
|
1994
2240
|
}
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
}
|
|
2003
|
-
const sources = source.getValue("sources");
|
|
2004
|
-
if (sources) target.sources = sources.map((value) => value.toObject(true));
|
|
2005
|
-
}
|
|
2006
|
-
return { eventName: decoded.name, args };
|
|
2007
|
-
}
|
|
2008
|
-
|
|
2009
|
-
// src/session/receipt-logs.ts
|
|
2010
|
-
function readProtocolLogs(receipt, protocolAddress) {
|
|
2011
|
-
if (receipt.status !== "success" || !/^0x[0-9a-fA-F]{64}$/.test(receipt.transactionHash) || typeof receipt.block.number !== "bigint" || receipt.block.number < 0n || !/^0x[0-9a-fA-F]{64}$/.test(receipt.block.hash))
|
|
2012
|
-
throw new Error("Invalid canonical protocol receipt");
|
|
2013
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2014
|
-
return receipt.logs.filter((log) => log.address.toLowerCase() === protocolAddress.toLowerCase()).map((log) => {
|
|
2015
|
-
if (log.transactionHash.toLowerCase() !== receipt.transactionHash.toLowerCase() || !Number.isSafeInteger(log.logIndex) || log.logIndex < 0 || seen.has(log.logIndex))
|
|
2016
|
-
throw new Error("Invalid canonical receipt log identity");
|
|
2017
|
-
seen.add(log.logIndex);
|
|
2018
|
-
const decoded = decodeProtocolEvent(log);
|
|
2019
|
-
return {
|
|
2020
|
-
address: log.address,
|
|
2021
|
-
eventName: decoded.eventName,
|
|
2022
|
-
args: decoded.args,
|
|
2023
|
-
blockNumber: receipt.block.number,
|
|
2024
|
-
blockHash: receipt.block.hash,
|
|
2025
|
-
transactionHash: receipt.transactionHash.toLowerCase(),
|
|
2026
|
-
logIndex: log.logIndex,
|
|
2027
|
-
removed: false
|
|
2028
|
-
};
|
|
2029
|
-
}).sort((a, b) => a.logIndex - b.logIndex);
|
|
2030
|
-
}
|
|
2031
|
-
|
|
2032
|
-
// src/session/discovery.ts
|
|
2033
|
-
import { MAX_CIPHERTEXT_BYTES } from "@invisi-labs/core";
|
|
2034
|
-
import { isAddress } from "ethers";
|
|
2035
|
-
import {
|
|
2036
|
-
decodeAddress,
|
|
2037
|
-
ReceivingAddress
|
|
2038
|
-
} from "@invisi-labs/core";
|
|
2039
|
-
import { normalizeOpaqueDelivery } from "@invisi-labs/protocol";
|
|
2040
|
-
var EVENTS = /* @__PURE__ */ new Set([
|
|
2041
|
-
"CommitmentsInserted",
|
|
2042
|
-
"BatchOutputs",
|
|
2043
|
-
"Shield",
|
|
2044
|
-
"Transact",
|
|
2045
|
-
"CrossTreeTransact",
|
|
2046
|
-
"CreditCreated",
|
|
2047
|
-
"CreditDraw",
|
|
2048
|
-
"CreditToppedUp",
|
|
2049
|
-
"CreditCancelled"
|
|
2050
|
-
]);
|
|
2051
|
-
function check(ok, message) {
|
|
2052
|
-
if (!ok) throw new Error(message);
|
|
2053
|
-
}
|
|
2054
|
-
function object(value) {
|
|
2055
|
-
check(value && typeof value === "object" && !Array.isArray(value), "invalid event object");
|
|
2056
|
-
return value;
|
|
2057
|
-
}
|
|
2058
|
-
function array(value) {
|
|
2059
|
-
check(Array.isArray(value), "invalid event array");
|
|
2060
|
-
check(
|
|
2061
|
-
Array.from({ length: value.length }, (_, i) => i in value).every(Boolean),
|
|
2062
|
-
"sparse event array"
|
|
2063
|
-
);
|
|
2064
|
-
return value;
|
|
2065
|
-
}
|
|
2066
|
-
function integer(value) {
|
|
2067
|
-
check(
|
|
2068
|
-
typeof value === "bigint" || typeof value === "number" && Number.isSafeInteger(value),
|
|
2069
|
-
"invalid event integer"
|
|
2070
|
-
);
|
|
2071
|
-
const n = BigInt(value);
|
|
2072
|
-
check(n >= 0n, "negative event integer");
|
|
2073
|
-
return n;
|
|
2074
|
-
}
|
|
2075
|
-
function index(value) {
|
|
2076
|
-
const n = integer(value);
|
|
2077
|
-
check(n < 65536n, "invalid event leaf index");
|
|
2078
|
-
return Number(n);
|
|
2079
|
-
}
|
|
2080
|
-
function hash(value) {
|
|
2081
|
-
check(typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value), "invalid event hash");
|
|
2082
|
-
return value.toLowerCase();
|
|
2083
|
-
}
|
|
2084
|
-
function contextCheck(context) {
|
|
2085
|
-
check(context.chainId >= 0n && context.chainId < 1n << 64n, "invalid discovery chain");
|
|
2086
|
-
check(
|
|
2087
|
-
isAddress(context.protocolAddress) && BigInt(context.protocolAddress) !== 0n,
|
|
2088
|
-
"invalid discovery instance/token"
|
|
2089
|
-
);
|
|
2090
|
-
}
|
|
2091
|
-
var position = (tree, at) => `${tree}:${at}`;
|
|
2092
|
-
function extractDeliveryCandidates(logs, context, confirmedThrough) {
|
|
2093
|
-
contextCheck(context);
|
|
2094
|
-
check(confirmedThrough >= 0n, "invalid confirmed height");
|
|
2095
|
-
const relevant = logs.filter(
|
|
2096
|
-
(log) => log.address.toLowerCase() === context.protocolAddress.toLowerCase() && EVENTS.has(log.eventName)
|
|
2097
|
-
);
|
|
2098
|
-
const insertions = /* @__PURE__ */ new Map();
|
|
2099
|
-
const metadata = (log) => {
|
|
2100
|
-
check(!log.removed, "discovery resync required after reorg");
|
|
2101
|
-
check(
|
|
2102
|
-
typeof log.blockNumber === "bigint" && log.blockNumber >= 0n && log.blockNumber <= confirmedThrough,
|
|
2103
|
-
"event is not confirmed"
|
|
2104
|
-
);
|
|
2105
|
-
check(Number.isSafeInteger(log.logIndex) && log.logIndex >= 0, "invalid log position");
|
|
2106
|
-
return {
|
|
2107
|
-
blockNumber: log.blockNumber,
|
|
2108
|
-
blockHash: hash(log.blockHash),
|
|
2109
|
-
transactionHash: hash(log.transactionHash)
|
|
2110
|
-
};
|
|
2111
|
-
};
|
|
2112
|
-
const insertionKey = (log, tree, at) => `${hash(log.transactionHash)}:${position(tree, at)}`;
|
|
2113
|
-
for (const log of relevant) {
|
|
2114
|
-
metadata(log);
|
|
2115
|
-
if (log.eventName !== "CommitmentsInserted" && log.eventName !== "BatchOutputs") continue;
|
|
2116
|
-
const a = object(log.args), tree = integer(a.treeId), start = index(a.startLeafIndex), leaves = array(a.commitments);
|
|
2117
|
-
check(tree > 0n && tree < 1n << 64n, "invalid insertion tree");
|
|
2118
|
-
check(start + leaves.length <= 65536, "insertion exceeds tree capacity");
|
|
2119
|
-
for (const [i, leaf] of leaves.entries()) {
|
|
2120
|
-
const key = insertionKey(log, tree, start + i), value = hash(leaf), previous = insertions.get(key);
|
|
2121
|
-
check(!previous || previous === value, "conflicting insertion events");
|
|
2122
|
-
insertions.set(key, value);
|
|
2123
|
-
}
|
|
2124
|
-
}
|
|
2125
|
-
const results = /* @__PURE__ */ new Map();
|
|
2126
|
-
const add = (log, tree, at, commitment, role, wire, token = log.args?.token ?? log.args?.params?.token ?? log.args?.params?.creation?.token) => {
|
|
2127
|
-
check(tree > 0n && tree < 1n << 64n, "invalid output tree");
|
|
2128
|
-
const inserted = insertions.get(insertionKey(log, tree, at));
|
|
2129
|
-
check(inserted, "delivery has no matching canonical insertion");
|
|
2130
|
-
if (commitment !== void 0)
|
|
2131
|
-
check(hash(commitment) === inserted, "delivery commitment conflicts with insertion");
|
|
2132
|
-
const delivery = normalizeOpaqueDelivery(wire, { allowEmpty: role === "funds" });
|
|
2133
|
-
check(
|
|
2134
|
-
typeof token === "string" && isAddress(token) && BigInt(token) !== 0n,
|
|
2135
|
-
"invalid event token"
|
|
2136
|
-
);
|
|
2137
|
-
const value = {
|
|
2138
|
-
tokenAddress: token,
|
|
2139
|
-
treeId: tree,
|
|
2140
|
-
leafIndex: at,
|
|
2141
|
-
commitment: inserted,
|
|
2142
|
-
role,
|
|
2143
|
-
delivery,
|
|
2144
|
-
...metadata(log)
|
|
2145
|
-
};
|
|
2146
|
-
const key = `${insertionKey(log, tree, at)}:${role}`, previous = results.get(key);
|
|
2147
|
-
check(
|
|
2148
|
-
!previous || previous.tokenAddress.toLowerCase() === token.toLowerCase() && previous.delivery.ciphertext === delivery.ciphertext && previous.delivery.clue === delivery.clue && previous.blockHash === value.blockHash,
|
|
2149
|
-
"conflicting delivery events"
|
|
2150
|
-
);
|
|
2151
|
-
results.set(key, value);
|
|
2152
|
-
};
|
|
2153
|
-
const creation = (log, a, fields) => {
|
|
2154
|
-
const tree = integer(a.outputTreeId), leaves = array(a.leafIndices), ds = array(a.deliveries);
|
|
2155
|
-
const change = hash(fields.changeCommitment), hasChange = BigInt(change) !== 0n;
|
|
2156
|
-
check(
|
|
2157
|
-
ds.length === 3 && leaves.length === (hasChange ? 2 : 1),
|
|
2158
|
-
"invalid Credit creation delivery slots"
|
|
2159
|
-
);
|
|
2160
|
-
add(log, tree, index(leaves[0]), fields.creditCommitment, "owner", ds[0]);
|
|
2161
|
-
add(log, tree, index(leaves[0]), fields.creditCommitment, "spender", ds[1]);
|
|
2162
|
-
if (hasChange) add(log, tree, index(leaves[1]), change, "funds", ds[2]);
|
|
2163
|
-
else normalizeOpaqueDelivery(ds[2], { empty: true });
|
|
2164
|
-
};
|
|
2165
|
-
for (const log of relevant) {
|
|
2166
|
-
const a = object(log.args);
|
|
2167
|
-
if (log.eventName === "Shield") {
|
|
2168
|
-
const ds = array(a.deliveries), tree = integer(a.treeId), start = index(a.startLeafIndex);
|
|
2169
|
-
check(start + ds.length <= 65536, "shield exceeds tree capacity");
|
|
2170
|
-
ds.forEach((d, i) => add(log, tree, start + i, void 0, "funds", d));
|
|
2171
|
-
} else if (log.eventName === "Transact" || log.eventName === "CrossTreeTransact") {
|
|
2172
|
-
const cs = array(a.outputCommitments), ds = array(a.deliveries), leaves = array(a.leafIndices), tree = integer(a.outputTreeId);
|
|
2173
|
-
check(
|
|
2174
|
-
cs.length === ds.length && cs.length === leaves.length,
|
|
2175
|
-
"invalid transact delivery slots"
|
|
2176
|
-
);
|
|
2177
|
-
cs.forEach((c, i) => add(log, tree, index(leaves[i]), c, "funds", ds[i]));
|
|
2178
|
-
} else if (log.eventName === "BatchOutputs") {
|
|
2179
|
-
const tokens = array(a.tokens);
|
|
2180
|
-
const cs = array(a.commitments), tree = integer(a.treeId), start = index(a.startLeafIndex), packed = a.packedDeliveries;
|
|
2181
|
-
check(
|
|
2182
|
-
typeof packed === "string" && /^0x(?:[0-9a-fA-F]{2})*$/.test(packed),
|
|
2183
|
-
"invalid packed deliveries"
|
|
2184
|
-
);
|
|
2185
|
-
check(tokens.length === cs.length, "batch token count mismatch");
|
|
2186
|
-
check(start + cs.length <= 65536, "batch exceeds tree capacity");
|
|
2187
|
-
let offset = 2;
|
|
2188
|
-
cs.forEach((c, i) => {
|
|
2189
|
-
check(offset + 4 <= packed.length, "truncated delivery length prefix");
|
|
2190
|
-
const length = Number.parseInt(packed.slice(offset, offset + 4), 16);
|
|
2191
|
-
offset += 4;
|
|
2192
|
-
check(
|
|
2193
|
-
length <= MAX_CIPHERTEXT_BYTES && offset + length * 2 + (length === 0 ? 0 : 136) <= packed.length,
|
|
2194
|
-
"invalid packed delivery length"
|
|
2195
|
-
);
|
|
2196
|
-
const ciphertext = `0x${packed.slice(offset, offset + length * 2)}`;
|
|
2197
|
-
offset += length * 2;
|
|
2198
|
-
const clue = length === 0 ? "0x" : `0x${packed.slice(offset, offset + 136)}`;
|
|
2199
|
-
if (length !== 0) offset += 136;
|
|
2200
|
-
add(log, tree, start + i, c, "funds", { ciphertext, clue }, tokens[i]);
|
|
2201
|
-
});
|
|
2202
|
-
check(offset === packed.length, "trailing packed delivery bytes");
|
|
2203
|
-
} else if (log.eventName === "CreditCreated") creation(log, a, a);
|
|
2204
|
-
else if (log.eventName === "CreditToppedUp")
|
|
2205
|
-
creation(log, a, object(object(a.params).creation));
|
|
2206
|
-
else if (log.eventName === "CreditDraw") {
|
|
2207
|
-
const p = object(a.params), cs = array(p.remainderCommitments), ds = array(a.deliveries), leaves = array(a.remainderLeafIndices), tree = integer(a.outputTreeId);
|
|
2208
|
-
check(
|
|
2209
|
-
ds.length === 2 * cs.length && leaves.length === cs.length,
|
|
2210
|
-
"invalid draw delivery slots"
|
|
2211
|
-
);
|
|
2212
|
-
cs.forEach((c, i) => {
|
|
2213
|
-
if (BigInt(hash(c)) === 0n) {
|
|
2214
|
-
check(integer(leaves[i]) === 0n, "absent remainder has nonzero position");
|
|
2215
|
-
normalizeOpaqueDelivery(ds[2 * i], { empty: true });
|
|
2216
|
-
normalizeOpaqueDelivery(ds[2 * i + 1], { empty: true });
|
|
2217
|
-
} else {
|
|
2218
|
-
add(log, tree, index(leaves[i]), c, "owner", ds[2 * i]);
|
|
2219
|
-
add(log, tree, index(leaves[i]), c, "spender", ds[2 * i + 1]);
|
|
2220
|
-
}
|
|
2221
|
-
});
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
return [...results.values()];
|
|
2225
|
-
}
|
|
2226
|
-
async function decryptCandidates(logs, options) {
|
|
2227
|
-
contextCheck(options.context);
|
|
2228
|
-
decodeAddress(ReceivingAddress.fromIdentity(options.viewing.ownerAddress).toString());
|
|
2229
|
-
check(
|
|
2230
|
-
!logs.some(
|
|
2231
|
-
(log) => log.address.toLowerCase() === options.context.protocolAddress.toLowerCase() && log.removed
|
|
2232
|
-
),
|
|
2233
|
-
"discovery resync required after reorg"
|
|
2234
|
-
);
|
|
2235
|
-
const candidates = extractDeliveryCandidates(logs, options.context, options.confirmedThrough);
|
|
2236
|
-
const positions = /* @__PURE__ */ new Map();
|
|
2237
|
-
const unique = /* @__PURE__ */ new Map();
|
|
2238
|
-
for (const candidate of candidates) {
|
|
2239
|
-
const key = position(candidate.treeId, candidate.leafIndex);
|
|
2240
|
-
const previous = positions.get(key);
|
|
2241
|
-
check(
|
|
2242
|
-
!previous || previous.commitment === candidate.commitment && previous.blockHash === candidate.blockHash && previous.transactionHash === candidate.transactionHash && previous.tokenAddress.toLowerCase() === candidate.tokenAddress.toLowerCase(),
|
|
2243
|
-
"conflicting canonical position"
|
|
2244
|
-
);
|
|
2245
|
-
positions.set(key, candidate);
|
|
2246
|
-
unique.set(`${key}:${candidate.role}`, candidate);
|
|
2247
|
-
}
|
|
2248
|
-
const notes = [];
|
|
2249
|
-
const credits = [];
|
|
2250
|
-
for (const candidate of unique.values()) {
|
|
2251
|
-
const { delivery, role, ...positionData } = candidate;
|
|
2252
|
-
const context = {
|
|
2253
|
-
chainId: options.context.chainId,
|
|
2254
|
-
protocolAddress: BigInt(options.context.protocolAddress),
|
|
2255
|
-
tokenAddress: BigInt(candidate.tokenAddress),
|
|
2256
|
-
outputCommitment: candidate.commitment
|
|
2257
|
-
};
|
|
2258
|
-
try {
|
|
2259
|
-
if (role === "funds") {
|
|
2260
|
-
if (delivery.ciphertext === "0x") continue;
|
|
2261
|
-
const note = await options.viewing.decryptNote(delivery.ciphertext, context);
|
|
2262
|
-
notes.push({ ...positionData, note });
|
|
2263
|
-
} else {
|
|
2264
|
-
const state = await options.viewing.decryptCredit(delivery.ciphertext, {
|
|
2265
|
-
...context,
|
|
2266
|
-
role
|
|
2267
|
-
});
|
|
2268
|
-
credits.push({ ...positionData, role, state });
|
|
2269
|
-
}
|
|
2270
|
-
} catch {
|
|
2271
|
-
}
|
|
2272
|
-
}
|
|
2273
|
-
return { notes, credits };
|
|
2274
|
-
}
|
|
2275
|
-
|
|
2276
|
-
// src/session/DisclosureVerifier.ts
|
|
2277
|
-
function check2(value, message) {
|
|
2278
|
-
if (!value) throw new Error(message);
|
|
2279
|
-
}
|
|
2280
|
-
function reference(value) {
|
|
2281
|
-
check2(/^0x[0-9a-fA-F]{64}$/.test(value.transactionHash), "invalid disclosure transaction hash");
|
|
2282
|
-
check2(
|
|
2283
|
-
typeof value.treeId === "bigint" && value.treeId > 0n && value.treeId < 1n << 64n,
|
|
2284
|
-
"invalid disclosure tree ID"
|
|
2285
|
-
);
|
|
2286
|
-
check2(
|
|
2287
|
-
Number.isSafeInteger(value.leafIndex) && value.leafIndex >= 0 && value.leafIndex < 65536,
|
|
2288
|
-
"invalid disclosure leaf index"
|
|
2289
|
-
);
|
|
2290
|
-
check2(["funds", "owner", "spender"].includes(value.role), "invalid disclosure reference role");
|
|
2291
|
-
return {
|
|
2292
|
-
transactionHash: value.transactionHash.toLowerCase(),
|
|
2293
|
-
treeId: value.treeId,
|
|
2294
|
-
leafIndex: value.leafIndex,
|
|
2295
|
-
role: value.role
|
|
2296
|
-
};
|
|
2297
|
-
}
|
|
2298
|
-
var DisclosureVerifier = class {
|
|
2299
|
-
#protocol;
|
|
2300
|
-
#reader;
|
|
2301
|
-
#confirmations;
|
|
2302
|
-
constructor(options) {
|
|
2303
|
-
const confirmations = options.confirmations ?? 1;
|
|
2304
|
-
check2(
|
|
2305
|
-
Number.isSafeInteger(confirmations) && confirmations >= 1,
|
|
2306
|
-
"invalid disclosure confirmations"
|
|
2307
|
-
);
|
|
2308
|
-
this.#protocol = options.protocol;
|
|
2309
|
-
this.#reader = "provider" in options ? new EthersChainReader(options.provider) : options.reader;
|
|
2310
|
-
this.#confirmations = BigInt(confirmations);
|
|
2311
|
-
Object.freeze(this);
|
|
2312
|
-
}
|
|
2313
|
-
async barrier(block) {
|
|
2314
|
-
await this.#protocol.validateDeployment(this.#reader);
|
|
2315
|
-
const canonical2 = await this.#reader.getBlock(block.number);
|
|
2316
|
-
check2(
|
|
2317
|
-
canonical2.number === block.number && canonical2.hash.toLowerCase() === block.hash.toLowerCase(),
|
|
2318
|
-
"disclosure canonical block changed; reorg verification required"
|
|
2319
|
-
);
|
|
2320
|
-
const head = await this.#reader.getLatestBlockNumber();
|
|
2321
|
-
check2(
|
|
2322
|
-
head >= block.number && head - block.number + 1n >= this.#confirmations,
|
|
2323
|
-
"insufficient disclosure confirmations"
|
|
2324
|
-
);
|
|
2325
|
-
check2(
|
|
2326
|
-
await this.#reader.getChainId() === this.#protocol.context.chainId,
|
|
2327
|
-
"disclosure chain changed during verification"
|
|
2328
|
-
);
|
|
2329
|
-
return head - block.number + 1n;
|
|
2330
|
-
}
|
|
2331
|
-
/** Resolve the exact inserted output and its delivery from a successful canonical receipt. */
|
|
2332
|
-
async readOutput(value) {
|
|
2333
|
-
const selected = reference(value);
|
|
2334
|
-
await this.#protocol.validateDeployment(this.#reader);
|
|
2335
|
-
const received = await this.#reader.getReceipt(selected.transactionHash);
|
|
2336
|
-
check2(received, "disclosure transaction receipt is unavailable");
|
|
2337
|
-
const receipt = structuredClone(received);
|
|
2338
|
-
check2(receipt.status === "success", "disclosure requires a successful transaction receipt");
|
|
2339
|
-
check2(
|
|
2340
|
-
receipt.transactionHash.toLowerCase() === selected.transactionHash,
|
|
2341
|
-
"disclosure receipt transaction mismatch"
|
|
2342
|
-
);
|
|
2343
|
-
check2(
|
|
2344
|
-
typeof receipt.block.number === "bigint" && receipt.block.number >= 0n && /^0x[0-9a-fA-F]{64}$/.test(receipt.block.hash),
|
|
2345
|
-
"invalid disclosure receipt block"
|
|
2346
|
-
);
|
|
2347
|
-
const { context } = this.#protocol;
|
|
2348
|
-
const logs = readProtocolLogs(receipt, context.protocolAddress);
|
|
2349
|
-
const candidates = extractDeliveryCandidates(logs, context, receipt.block.number).filter(
|
|
2350
|
-
(candidate2) => candidate2.treeId === selected.treeId && candidate2.leafIndex === selected.leafIndex && candidate2.role === selected.role && candidate2.tokenAddress.toLowerCase() === context.tokenAddress.toLowerCase()
|
|
2351
|
-
);
|
|
2352
|
-
check2(
|
|
2353
|
-
candidates.length === 1,
|
|
2354
|
-
"disclosure output has no unique canonical insertion and delivery"
|
|
2355
|
-
);
|
|
2356
|
-
const candidate = candidates[0];
|
|
2357
|
-
const confirmations = await this.barrier(receipt.block);
|
|
2358
|
-
return Object.freeze({
|
|
2359
|
-
reference: Object.freeze(selected),
|
|
2360
|
-
block: Object.freeze({ ...receipt.block }),
|
|
2361
|
-
confirmations,
|
|
2362
|
-
output: Object.freeze({
|
|
2363
|
-
chainId: context.chainId,
|
|
2364
|
-
protocolAddress: BigInt(context.protocolAddress),
|
|
2365
|
-
tokenAddress: BigInt(context.tokenAddress),
|
|
2366
|
-
outputCommitment: candidate.commitment,
|
|
2367
|
-
role: selected.role,
|
|
2368
|
-
ciphertext: candidate.delivery.ciphertext
|
|
2369
|
-
})
|
|
2370
|
-
});
|
|
2371
|
-
}
|
|
2372
|
-
async verify(disclosure, value) {
|
|
2373
|
-
const evidence = await this.readOutput(value);
|
|
2374
|
-
const result = await disclosure.verify(evidence.output);
|
|
2375
|
-
const confirmations = await this.barrier(evidence.block);
|
|
2376
|
-
return Object.freeze({
|
|
2377
|
-
result: Object.freeze(result),
|
|
2378
|
-
block: evidence.block,
|
|
2379
|
-
confirmations,
|
|
2380
|
-
output: Object.freeze({
|
|
2381
|
-
...evidence.reference,
|
|
2382
|
-
commitment: evidence.output.outputCommitment
|
|
2383
|
-
})
|
|
2384
|
-
});
|
|
2385
|
-
}
|
|
2386
|
-
toJSON() {
|
|
2387
|
-
return { disclosureVerifier: true, confirmations: this.#confirmations.toString() };
|
|
2388
|
-
}
|
|
2389
|
-
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
|
2390
|
-
return this.toJSON();
|
|
2391
|
-
}
|
|
2392
|
-
};
|
|
2393
|
-
|
|
2394
|
-
// src/session/store.ts
|
|
2395
|
-
import { FIELD_MODULUS, normalizeCreditDeliveryState } from "@invisi-labs/core";
|
|
2396
|
-
import { sha256 } from "@noble/hashes/sha256";
|
|
2397
|
-
import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils";
|
|
2398
|
-
function scopeKey(scope) {
|
|
2399
|
-
if (typeof scope.namespace !== "string" || !scope.namespace.length || scope.namespace.length > 256)
|
|
2400
|
-
throw new Error("invalid wallet namespace");
|
|
2401
|
-
if (typeof scope.chainId !== "bigint" || scope.chainId < 0n || scope.chainId >= 1n << 64n)
|
|
2402
|
-
throw new Error("invalid scope chain");
|
|
2403
|
-
for (const address2 of [scope.protocolAddress])
|
|
2404
|
-
if (!/^0x[0-9a-fA-F]{40}$/.test(address2) || BigInt(address2) === 0n)
|
|
2405
|
-
throw new Error("invalid scope deployment");
|
|
2406
|
-
if (!/^0x[0-9a-fA-F]{40}$/.test(scope.ownerAddress) || BigInt(scope.ownerAddress) <= 0n || BigInt(scope.ownerAddress) >= 1n << 160n)
|
|
2407
|
-
throw new Error("invalid scope wallet");
|
|
2408
|
-
return bytesToHex(
|
|
2409
|
-
sha256(
|
|
2410
|
-
utf8ToBytes(
|
|
2411
|
-
"Invisi.WalletScope.v1:" + JSON.stringify([
|
|
2412
|
-
scope.namespace,
|
|
2413
|
-
scope.chainId.toString(),
|
|
2414
|
-
scope.protocolAddress.toLowerCase(),
|
|
2415
|
-
scope.ownerAddress.toLowerCase()
|
|
2416
|
-
])
|
|
2417
|
-
)
|
|
2418
|
-
)
|
|
2419
|
-
);
|
|
2420
|
-
}
|
|
2421
|
-
function initialState(scope) {
|
|
2422
|
-
scopeKey(scope);
|
|
2423
|
-
return {
|
|
2424
|
-
schemaVersion: 1,
|
|
2425
|
-
scope: { ...scope },
|
|
2426
|
-
revision: 0,
|
|
2427
|
-
notes: {},
|
|
2428
|
-
credits: {},
|
|
2429
|
-
reservations: {},
|
|
2430
|
-
operations: {}
|
|
2431
|
-
};
|
|
2432
|
-
}
|
|
2433
|
-
function validateState(state, scope, revision) {
|
|
2434
|
-
if (!state || state.schemaVersion !== 1 || scopeKey(state.scope) !== scopeKey(scope) || state.revision !== revision)
|
|
2435
|
-
throw new Error("wallet state scope/schema/revision mismatch");
|
|
2436
|
-
if (!Number.isSafeInteger(revision) || revision < 0)
|
|
2437
|
-
throw new Error("invalid wallet state revision");
|
|
2438
|
-
for (const field of [state.notes, state.credits, state.reservations, state.operations])
|
|
2439
|
-
if (!field || typeof field !== "object" || Array.isArray(field))
|
|
2440
|
-
throw new Error("invalid wallet state records");
|
|
2441
|
-
for (const record of Object.values(state.notes)) {
|
|
2442
|
-
if (typeof record.note.tokenAddress !== "bigint" || record.note.tokenAddress <= 0n || record.note.tokenAddress >= 1n << 160n || record.note.chainId !== scope.chainId || record.note.protocolAddress !== BigInt(scope.protocolAddress) || record.note.ownerAddress.toLowerCase() !== scope.ownerAddress.toLowerCase() || typeof record.note.amount !== "bigint" || record.note.amount <= 0n || record.note.amount >= 1n << 128n)
|
|
2443
|
-
throw new Error("invalid stored note context or amount");
|
|
2444
|
-
if (!["unspent", "unknown", "spent"].includes(record.spent))
|
|
2445
|
-
throw new Error("invalid spent state");
|
|
2446
|
-
}
|
|
2447
|
-
for (const record of Object.values(state.credits)) {
|
|
2448
|
-
const note = normalizeCreditDeliveryState(record.state).note;
|
|
2449
|
-
if (note.chainId !== scope.chainId || note.protocolAddress !== BigInt(scope.protocolAddress) || !["owner", "spender"].includes(record.role) || (record.role === "owner" ? note.ownerWalletIdentity : note.spenderWalletIdentity).toLowerCase() !== scope.ownerAddress.toLowerCase() || !["unspent", "unknown", "spent"].includes(record.spent))
|
|
2450
|
-
throw new Error("invalid stored Credit context or role");
|
|
2451
|
-
}
|
|
2452
|
-
for (const record of [...Object.values(state.notes), ...Object.values(state.credits)]) {
|
|
2453
|
-
if (!/^0x[0-9a-fA-F]{64}$/.test(record.commitment) || BigInt(record.commitment) <= 0n || BigInt(record.commitment) >= FIELD_MODULUS || typeof record.treeId !== "bigint" || record.treeId < 1n || record.treeId >= 1n << 64n || !Number.isInteger(record.leafIndex) || record.leafIndex < 0 || record.leafIndex >= 65536 || typeof record.blockNumber !== "bigint" || record.blockNumber < 0n || !/^0x[0-9a-fA-F]{64}$/.test(record.blockHash))
|
|
2454
|
-
throw new Error("invalid stored receipt position");
|
|
2455
|
-
}
|
|
2456
|
-
for (const [id, operation] of Object.entries(state.reservations))
|
|
2457
|
-
if (!state.operations[operation] || !state.notes[id] && !Object.values(state.credits).some(
|
|
2458
|
-
(record) => creditReservationKey(record.commitment) === id
|
|
2459
|
-
) && !["submitted", "uncertain", "reorged"].includes(
|
|
2460
|
-
state.operations[operation].status
|
|
2461
|
-
))
|
|
2462
|
-
throw new Error("orphan input reservation");
|
|
2463
|
-
}
|
|
2464
|
-
var TransactionalWalletStore = class {
|
|
2465
|
-
persistent;
|
|
2466
|
-
#records;
|
|
2467
|
-
#codec;
|
|
2468
|
-
constructor(records, codec, options) {
|
|
2469
|
-
this.#records = records;
|
|
2470
|
-
this.#codec = codec;
|
|
2471
|
-
this.persistent = options.persistent;
|
|
2472
|
-
}
|
|
2473
|
-
async readSnapshot(scope) {
|
|
2474
|
-
const key = scopeKey(scope), record = await this.#records.read(key);
|
|
2475
|
-
if (!record) return initialState(scope);
|
|
2476
|
-
const state = await this.#codec.decode(record.data, { key, revision: record.revision });
|
|
2477
|
-
validateState(state, scope, record.revision);
|
|
2478
|
-
return state;
|
|
2479
|
-
}
|
|
2480
|
-
async transaction(scope, action) {
|
|
2481
|
-
if (action.constructor.name === "AsyncFunction")
|
|
2482
|
-
throw new Error("wallet transaction action must be synchronous");
|
|
2483
|
-
const copied = { ...scope }, key = scopeKey(copied);
|
|
2484
|
-
for (let attempt = 0; attempt < 100; attempt++) {
|
|
2485
|
-
const draft = await this.readSnapshot(copied), previous = draft.revision;
|
|
2486
|
-
const result = action(draft);
|
|
2487
|
-
if (result && typeof result === "object" && "then" in result) {
|
|
2488
|
-
Promise.resolve(result).catch(() => {
|
|
2489
|
-
});
|
|
2490
|
-
throw new Error("wallet transaction action must be synchronous");
|
|
2491
|
-
}
|
|
2492
|
-
validateState(draft, copied, previous);
|
|
2493
|
-
if (!Number.isSafeInteger(previous + 1))
|
|
2494
|
-
throw new Error("wallet state revision exhausted");
|
|
2495
|
-
draft.revision = previous + 1;
|
|
2496
|
-
const data = await this.#codec.encode(draft, { key, revision: draft.revision });
|
|
2497
|
-
if (await this.#records.compareAndSwap(key, previous, {
|
|
2498
|
-
revision: draft.revision,
|
|
2499
|
-
data
|
|
2500
|
-
}))
|
|
2501
|
-
return result;
|
|
2502
|
-
}
|
|
2503
|
-
throw new Error("wallet transaction contention limit exceeded");
|
|
2504
|
-
}
|
|
2505
|
-
toJSON() {
|
|
2506
|
-
return { walletStore: true, persistent: this.persistent };
|
|
2507
|
-
}
|
|
2508
|
-
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
|
2509
|
-
return this.toJSON();
|
|
2510
|
-
}
|
|
2511
|
-
};
|
|
2512
|
-
var MemoryAtomicRecords = class {
|
|
2513
|
-
#values = /* @__PURE__ */ new Map();
|
|
2514
|
-
async read(key) {
|
|
2515
|
-
const value = this.#values.get(key);
|
|
2516
|
-
return value ? { revision: value.revision, data: Uint8Array.from(value.data) } : void 0;
|
|
2241
|
+
if (plan.kind === "shield") {
|
|
2242
|
+
return encodeShieldFundingTypedData({
|
|
2243
|
+
context: plan.context,
|
|
2244
|
+
params: plan.shieldParams,
|
|
2245
|
+
outputs: plan.shieldOutputs,
|
|
2246
|
+
deliveries: plan.outputs.map((output) => output.delivery),
|
|
2247
|
+
funding: plan.funding
|
|
2248
|
+
});
|
|
2517
2249
|
}
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2250
|
+
if (plan.statement?.target === "noir-recursive") {
|
|
2251
|
+
return encodeAggregateFundingTypedData(plan.context, {
|
|
2252
|
+
params: plan.params,
|
|
2253
|
+
deliveries: plan.outputs.map((output) => output.delivery),
|
|
2254
|
+
funding: plan.funding
|
|
2255
|
+
});
|
|
2522
2256
|
}
|
|
2523
|
-
|
|
2257
|
+
return encodeTransactFundingTypedData(
|
|
2258
|
+
plan.context,
|
|
2259
|
+
saved.proof.proofHex,
|
|
2260
|
+
plan.params,
|
|
2261
|
+
plan.outputs.map((output) => output.delivery),
|
|
2262
|
+
plan.funding
|
|
2263
|
+
);
|
|
2264
|
+
}
|
|
2524
2265
|
|
|
2525
|
-
// src/
|
|
2526
|
-
import { Signature, SigningKey, getBytes as getBytes2, concat, verifyTypedData } from "ethers";
|
|
2266
|
+
// src/transactions/execution/operation-execution.ts
|
|
2527
2267
|
import {
|
|
2528
|
-
computeWalletAuthorizationDigest,
|
|
2529
2268
|
AuthorizedStatement,
|
|
2530
2269
|
computeOwnerAddress,
|
|
2270
|
+
computeWalletAuthorizationDigest,
|
|
2531
2271
|
createSigningRequest,
|
|
2532
2272
|
walletAuthorizationTypedData
|
|
2533
2273
|
} from "@invisi-labs/core";
|
|
2534
|
-
import { WitnessBuilder } from "@invisi-labs/protocol";
|
|
2535
|
-
|
|
2536
|
-
// src/session/proof-integrity.ts
|
|
2537
|
-
import { getBytes } from "ethers";
|
|
2538
|
-
import { sha256 as sha2562 } from "@noble/hashes/sha256";
|
|
2539
|
-
import { bytesToHex as bytesToHex2 } from "@noble/hashes/utils";
|
|
2540
|
-
function check3(ok, message) {
|
|
2541
|
-
if (!ok) throw new Error(message);
|
|
2542
|
-
}
|
|
2543
|
-
function sameStatement(proof, expected) {
|
|
2544
|
-
check3(
|
|
2545
|
-
proof.formatVersion === 1 && proof.bbVersion === "6.0.0" && proof.circuitName === expected.circuitName && proof.target === expected.target && proof.artifactSha256 === expected.artifactSha256 && proof.vkSha256 === expected.vkSha256,
|
|
2546
|
-
"proof release/target mismatch"
|
|
2547
|
-
);
|
|
2548
|
-
check3(
|
|
2549
|
-
proof.publicInputs.length === expected.expectedPublicInputs.length && proof.publicInputs.every((word2, i) => word2 === expected.expectedPublicInputs[i]),
|
|
2550
|
-
"proof public statement mismatch"
|
|
2551
|
-
);
|
|
2552
|
-
check3(
|
|
2553
|
-
/^0x(?:[a-fA-F0-9]{2})+$/.test(proof.proofHex) && bytesToHex2(sha2562(getBytes(proof.proofHex))) === proof.proofSha256,
|
|
2554
|
-
"proof integrity mismatch"
|
|
2555
|
-
);
|
|
2556
|
-
}
|
|
2557
|
-
|
|
2558
|
-
// src/session/operation-execution.ts
|
|
2274
|
+
import { WitnessBuilder as WitnessBuilder2 } from "@invisi-labs/protocol";
|
|
2275
|
+
import { Signature, SigningKey, concat, getBytes as getBytes2, verifyTypedData } from "ethers";
|
|
2559
2276
|
async function authorizeOperation(plan, provider) {
|
|
2560
2277
|
if (provider.ownerAddress !== plan.scope.ownerAddress)
|
|
2561
2278
|
throw new Error("wallet authorization capability mismatch");
|
|
@@ -2579,7 +2296,7 @@ async function proveOperation(protocol, plan, authorization, prover, options) {
|
|
|
2579
2296
|
throw new Error("aggregate execution requires a 14-slot transfer");
|
|
2580
2297
|
}
|
|
2581
2298
|
if (plan.kind === "transfer") {
|
|
2582
|
-
plan.statement =
|
|
2299
|
+
plan.statement = WitnessBuilder2.statement(
|
|
2583
2300
|
frozen.circuitName,
|
|
2584
2301
|
frozen.expectedPublicInputs,
|
|
2585
2302
|
execution === "aggregate" ? "noir-recursive" : "evm"
|
|
@@ -2658,753 +2375,164 @@ async function fundingSigningRequest(saved, runtime) {
|
|
|
2658
2375
|
async function acceptFundingSignature(saved, signature, runtime) {
|
|
2659
2376
|
if (!/^0x(?:[a-fA-F0-9]{2})+$/.test(signature)) {
|
|
2660
2377
|
throw new Error("invalid EVM funding signature");
|
|
2661
|
-
}
|
|
2662
|
-
const request = await fundingSigningRequest(saved, runtime);
|
|
2663
|
-
return verifyFundingSignature(
|
|
2664
|
-
request,
|
|
2665
|
-
saved.plan.funding.payer,
|
|
2666
|
-
signature,
|
|
2667
|
-
runtime.options.fundingVerifier
|
|
2668
|
-
);
|
|
2669
|
-
}
|
|
2670
|
-
async function verifyFundingSignature(request, payer, signature, verifier) {
|
|
2671
|
-
if (!/^0x(?:[a-fA-F0-9]{2})+$/.test(signature)) {
|
|
2672
|
-
throw new Error("invalid EVM funding signature");
|
|
2673
|
-
}
|
|
2674
|
-
const types = Object.fromEntries(
|
|
2675
|
-
Object.entries(request.types).map(([name, fields]) => [name, [...fields]])
|
|
2676
|
-
);
|
|
2677
|
-
let validEOA = false;
|
|
2678
|
-
try {
|
|
2679
|
-
validEOA = verifyTypedData(request.domain, types, request.message, signature).toLowerCase() === payer.toLowerCase();
|
|
2680
|
-
if (validEOA) {
|
|
2681
|
-
signature = Signature.from(signature).serialized;
|
|
2682
|
-
}
|
|
2683
|
-
} catch {
|
|
2684
|
-
validEOA = false;
|
|
2685
|
-
}
|
|
2686
|
-
const valid = verifier ? await verifier.verifyTypedData({
|
|
2687
|
-
...structuredClone(request),
|
|
2688
|
-
address: payer,
|
|
2689
|
-
signature
|
|
2690
|
-
}) : validEOA;
|
|
2691
|
-
if (valid !== true) {
|
|
2692
|
-
throw new Error("invalid EVM funding signature");
|
|
2693
|
-
}
|
|
2694
|
-
return signature;
|
|
2695
|
-
}
|
|
2696
|
-
|
|
2697
|
-
// src/session/operation-preview.ts
|
|
2698
|
-
import { normalizeFunding } from "@invisi-labs/protocol";
|
|
2699
|
-
function operationPreview(plan, status) {
|
|
2700
|
-
const p = plan;
|
|
2701
|
-
const fee = p.params ?? p.shieldParams ?? p.credit?.creation ?? p.credit?.cancel;
|
|
2702
|
-
const funding = normalizeFunding(p.funding);
|
|
2703
|
-
return {
|
|
2704
|
-
id: p.id,
|
|
2705
|
-
kind: p.kind,
|
|
2706
|
-
status,
|
|
2707
|
-
context: { ...p.context },
|
|
2708
|
-
inputs: inputFacts(p).length,
|
|
2709
|
-
inputDetails: [
|
|
2710
|
-
...p.inputs.map((input) => ({
|
|
2711
|
-
kind: "notes",
|
|
2712
|
-
commitment: input.commitment,
|
|
2713
|
-
amount: input.note.amount,
|
|
2714
|
-
owner: input.note.ownerAddress,
|
|
2715
|
-
treeId: input.treeId,
|
|
2716
|
-
leafIndex: input.leafIndex
|
|
2717
|
-
})),
|
|
2718
|
-
...(p.credit?.inputs ?? []).map((input) => ({
|
|
2719
|
-
kind: "credits",
|
|
2720
|
-
commitment: input.commitment,
|
|
2721
|
-
amount: input.state.note.amount,
|
|
2722
|
-
owner: input.state.note.ownerWalletIdentity,
|
|
2723
|
-
spender: input.state.note.spenderWalletIdentity,
|
|
2724
|
-
expiry: input.state.note.expiry,
|
|
2725
|
-
role: input.role,
|
|
2726
|
-
treeId: input.treeId,
|
|
2727
|
-
leafIndex: input.leafIndex
|
|
2728
|
-
}))
|
|
2729
|
-
],
|
|
2730
|
-
funding: {
|
|
2731
|
-
payer: funding.payer,
|
|
2732
|
-
amount: funding.amount,
|
|
2733
|
-
authorization: p.fundingMode,
|
|
2734
|
-
sigNonce: funding.sigNonce,
|
|
2735
|
-
deadline: funding.deadline,
|
|
2736
|
-
permit: { deadline: funding.permit.deadline }
|
|
2737
|
-
},
|
|
2738
|
-
creditOutputs: p.credit?.outputs.map((output) => ({
|
|
2739
|
-
commitment: output.commitment,
|
|
2740
|
-
amount: output.state.note.amount,
|
|
2741
|
-
expiry: output.state.note.expiry,
|
|
2742
|
-
owner: output.state.note.ownerWalletIdentity,
|
|
2743
|
-
spender: output.state.note.spenderWalletIdentity,
|
|
2744
|
-
ownerPayout: output.state.note.ownerPayout,
|
|
2745
|
-
spenderPayout: output.state.note.spenderPayout
|
|
2746
|
-
})),
|
|
2747
|
-
outputs: p.outputs.map((output) => ({
|
|
2748
|
-
commitment: output.commitment,
|
|
2749
|
-
to: output.address,
|
|
2750
|
-
amount: output.amount,
|
|
2751
|
-
change: output.change
|
|
2752
|
-
})),
|
|
2753
|
-
capacity: p.capacity,
|
|
2754
|
-
deadline: p.deadline,
|
|
2755
|
-
feeRecipient: fee?.feeRecipient,
|
|
2756
|
-
feeAmount: fee?.feeAmount ?? 0n,
|
|
2757
|
-
fundingAmount: p.funding.amount ?? 0n,
|
|
2758
|
-
fundingPayer: p.funding.payer,
|
|
2759
|
-
withdrawalAmount: p.params?.withdrawalAmount ?? p.credit?.draw?.totalAmount ?? (p.credit?.cancel ? p.credit.cancel.amount - (p.credit.cancel.feeAmount ?? 0n) : void 0) ?? 0n,
|
|
2760
|
-
withdrawalAddress: p.params?.withdrawalAddress ?? p.credit?.draw?.spender ?? p.credit?.cancel?.owner,
|
|
2761
|
-
permissions: {
|
|
2762
|
-
walletAuthorization: requiresAuthorization(p),
|
|
2763
|
-
proof: p.kind !== "shield",
|
|
2764
|
-
fundingAuthorization: p.fundingMode === "signature"
|
|
2765
|
-
}
|
|
2766
|
-
};
|
|
2767
|
-
}
|
|
2768
|
-
|
|
2769
|
-
// src/session/Operation.ts
|
|
2770
|
-
import { MissingCapabilityError } from "@invisi-labs/core";
|
|
2771
|
-
import { normalizeFunding as normalizeFunding2 } from "@invisi-labs/protocol";
|
|
2772
|
-
|
|
2773
|
-
// src/session/operation-confirmation.ts
|
|
2774
|
-
import { computeCreditNullifier } from "@invisi-labs/core";
|
|
2775
|
-
|
|
2776
|
-
// src/session/aggregate-receipt.ts
|
|
2777
|
-
import { Interface as Interface2 } from "ethers";
|
|
2778
|
-
import { invisiProtocolAbi as invisiProtocolAbi2 } from "@invisi-labs/protocol/contracts";
|
|
2779
|
-
import { buildTransactCalldata } from "@invisi-labs/protocol";
|
|
2780
|
-
var aggregateAbi = invisiProtocolAbi2.filter(
|
|
2781
|
-
(item) => item.type === "function" && item.name === "transact" && item.inputs.length === 2 && item.inputs[1]?.type === "tuple[]"
|
|
2782
|
-
);
|
|
2783
|
-
function matchesAggregateTransaction(saved, transaction) {
|
|
2784
|
-
const plan = saved.plan;
|
|
2785
|
-
if (plan.statement?.target !== "noir-recursive" || !plan.params || !saved.externalized || transaction.to.toLowerCase() !== plan.context.protocolAddress.toLowerCase() || transaction.value !== 0n)
|
|
2786
|
-
return false;
|
|
2787
|
-
try {
|
|
2788
|
-
const decoded = new Interface2(aggregateAbi).parseTransaction({ data: transaction.data }), requests = decoded.args[1].map((request) => {
|
|
2789
|
-
const member = request.toObject(true);
|
|
2790
|
-
member.params.sources = request.params.sources.map(
|
|
2791
|
-
(source) => source.toObject(true)
|
|
2792
|
-
);
|
|
2793
|
-
member.params.nullifiers = request.params.nullifiers.toArray(true);
|
|
2794
|
-
member.params.outputCommitments = request.params.outputCommitments.toArray(true);
|
|
2795
|
-
member.deliveries = request.deliveries.map(
|
|
2796
|
-
(delivery) => delivery.toObject(true)
|
|
2797
|
-
);
|
|
2798
|
-
member.funding.permit.v = Number(member.funding.permit.v);
|
|
2799
|
-
return {
|
|
2800
|
-
params: member.params,
|
|
2801
|
-
deliveries: member.deliveries,
|
|
2802
|
-
funding: member.funding
|
|
2803
|
-
};
|
|
2804
|
-
});
|
|
2805
|
-
if (decoded.name !== "transact" || !Array.isArray(requests) || ![4, 8, 12].includes(requests.length))
|
|
2806
|
-
return false;
|
|
2807
|
-
const canonical2 = (request) => buildTransactCalldata({
|
|
2808
|
-
context: { ...plan.context, tokenAddress: request.params.token },
|
|
2809
|
-
proof: "0x00",
|
|
2810
|
-
params: request.params,
|
|
2811
|
-
deliveries: request.deliveries,
|
|
2812
|
-
funding: request.funding,
|
|
2813
|
-
caller: transaction.from
|
|
2814
|
-
}).data.toLowerCase();
|
|
2815
|
-
const expected = buildTransactCalldata({
|
|
2816
|
-
context: plan.context,
|
|
2817
|
-
proof: "0x00",
|
|
2818
|
-
params: plan.params,
|
|
2819
|
-
deliveries: plan.outputs.map((output) => output.delivery),
|
|
2820
|
-
funding: plan.funding,
|
|
2821
|
-
caller: transaction.from
|
|
2822
|
-
}).data.toLowerCase();
|
|
2823
|
-
return requests.filter((request) => canonical2(request) === expected).length === 1;
|
|
2824
|
-
} catch {
|
|
2825
|
-
return false;
|
|
2826
|
-
}
|
|
2827
|
-
}
|
|
2828
|
-
|
|
2829
|
-
// src/session/receipts.ts
|
|
2830
|
-
function canonical(value) {
|
|
2831
|
-
return JSON.stringify(value, (_, item) => {
|
|
2832
|
-
if (typeof item === "bigint") {
|
|
2833
|
-
return ["bigint", item.toString()];
|
|
2834
|
-
}
|
|
2835
|
-
if (item instanceof Uint8Array) {
|
|
2836
|
-
return Array.from(item);
|
|
2837
|
-
}
|
|
2838
|
-
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
2839
|
-
return Object.fromEntries(
|
|
2840
|
-
Object.keys(item).sort().map((key) => [key, item[key]])
|
|
2841
|
-
);
|
|
2842
|
-
}
|
|
2843
|
-
return item;
|
|
2844
|
-
});
|
|
2845
|
-
}
|
|
2846
|
-
function validateInheritance(state, record) {
|
|
2847
|
-
const source = record.receivingKeySource;
|
|
2848
|
-
if (!source) return false;
|
|
2849
|
-
const parent = state.credits[source.commitment + ":" + record.role];
|
|
2850
|
-
if (!parent) {
|
|
2851
|
-
throw new Error("Credit receiving inheritance has no authenticated parent");
|
|
2852
|
-
}
|
|
2853
|
-
const previous = parent.state;
|
|
2854
|
-
const successor = record.state;
|
|
2855
|
-
const immutableFields = [
|
|
2856
|
-
"chainId",
|
|
2857
|
-
"protocolAddress",
|
|
2858
|
-
"tokenAddress",
|
|
2859
|
-
"spenderWalletIdentity",
|
|
2860
|
-
"ownerWalletIdentity",
|
|
2861
|
-
"spenderPayout",
|
|
2862
|
-
"ownerPayout",
|
|
2863
|
-
"creditSharedKey"
|
|
2864
|
-
];
|
|
2865
|
-
if (immutableFields.some((field) => previous.note[field] !== successor.note[field]) || canonical(previous.ownerReceivingKeys) !== canonical(successor.ownerReceivingKeys) || canonical(previous.spenderReceivingKeys) !== canonical(successor.spenderReceivingKeys)) {
|
|
2866
|
-
throw new Error("Credit receiving inheritance changed immutable fields");
|
|
2867
|
-
}
|
|
2868
|
-
if (source.kind === "draw") {
|
|
2869
|
-
if (successor.note.amount >= previous.note.amount || successor.note.expiry !== previous.note.expiry) {
|
|
2870
|
-
throw new Error("invalid Credit receiving inheritance");
|
|
2871
|
-
}
|
|
2872
|
-
} else if (source.kind !== "top-up" || successor.note.amount <= previous.note.amount || successor.note.expiry < previous.note.expiry) {
|
|
2873
|
-
throw new Error("invalid Credit receiving inheritance");
|
|
2874
|
-
}
|
|
2875
|
-
return true;
|
|
2876
|
-
}
|
|
2877
|
-
function mergeSpentObservation(previous, incoming) {
|
|
2878
|
-
const next = incoming.checkedThrough;
|
|
2879
|
-
const old = previous.checkedThrough;
|
|
2880
|
-
if (!next || incoming.spent === "unknown") {
|
|
2881
|
-
return;
|
|
2882
|
-
}
|
|
2883
|
-
if (old && next.number < old.number) {
|
|
2884
|
-
return;
|
|
2885
|
-
}
|
|
2886
|
-
if (old && next.number === old.number && next.hash !== old.hash) {
|
|
2887
|
-
throw new Error("canonical spent checkpoint conflict requires resync");
|
|
2888
|
-
}
|
|
2889
|
-
if (previous.spent === "spent" && incoming.spent !== "spent") {
|
|
2890
|
-
throw new Error("canonical spent observation conflict requires resync");
|
|
2891
|
-
}
|
|
2892
|
-
previous.spent = incoming.spent;
|
|
2893
|
-
previous.checkedThrough = { ...next };
|
|
2894
|
-
}
|
|
2895
|
-
function mergeReceiptRecords(state, batch) {
|
|
2896
|
-
if (state.invalidated) {
|
|
2897
|
-
throw new Error("wallet inventory is invalidated");
|
|
2898
|
-
}
|
|
2899
|
-
const checkpoint = { ...batch.checkpoint };
|
|
2900
|
-
for (const record of [...batch.notes, ...batch.credits]) {
|
|
2901
|
-
if (record.blockNumber > checkpoint.number) {
|
|
2902
|
-
throw new Error("receipt exceeds confirmed checkpoint");
|
|
2903
|
-
}
|
|
2904
|
-
const atPosition = [...Object.values(state.notes), ...Object.values(state.credits)].filter(
|
|
2905
|
-
(existing) => existing.treeId === record.treeId && existing.leafIndex === record.leafIndex
|
|
2906
|
-
);
|
|
2907
|
-
if (atPosition.some(
|
|
2908
|
-
(existing) => existing.commitment !== record.commitment || existing.blockHash !== record.blockHash
|
|
2909
|
-
)) {
|
|
2910
|
-
throw new Error("canonical position conflict requires resync");
|
|
2911
|
-
}
|
|
2912
|
-
if ("note" in record) {
|
|
2913
|
-
const previous = state.notes[record.commitment];
|
|
2914
|
-
if (previous) {
|
|
2915
|
-
if (canonical(previous.note) !== canonical(record.note)) {
|
|
2916
|
-
throw new Error("canonical note preimage conflict");
|
|
2917
|
-
}
|
|
2918
|
-
mergeSpentObservation(previous, record);
|
|
2919
|
-
continue;
|
|
2920
|
-
}
|
|
2921
|
-
state.notes[record.commitment] = record;
|
|
2922
|
-
} else {
|
|
2923
|
-
const key = record.commitment + ":" + record.role;
|
|
2924
|
-
const previous = state.credits[key];
|
|
2925
|
-
const inherited = validateInheritance(state, record);
|
|
2926
|
-
if (previous) {
|
|
2927
|
-
if (canonical(previous.state.note) !== canonical(record.state.note)) {
|
|
2928
|
-
throw new Error("canonical Credit preimage conflict");
|
|
2929
|
-
}
|
|
2930
|
-
if (canonical(previous.state) !== canonical(record.state)) {
|
|
2931
|
-
if (!inherited) {
|
|
2932
|
-
if (previous.receivingKeySource) {
|
|
2933
|
-
mergeSpentObservation(previous, record);
|
|
2934
|
-
continue;
|
|
2935
|
-
}
|
|
2936
|
-
throw new Error("Credit receiving metadata conflict");
|
|
2937
|
-
}
|
|
2938
|
-
previous.state = record.state;
|
|
2939
|
-
previous.receivingKeySource = record.receivingKeySource;
|
|
2940
|
-
} else if (inherited) {
|
|
2941
|
-
previous.receivingKeySource = record.receivingKeySource;
|
|
2942
|
-
}
|
|
2943
|
-
mergeSpentObservation(previous, record);
|
|
2944
|
-
continue;
|
|
2945
|
-
}
|
|
2946
|
-
state.credits[key] = record;
|
|
2947
|
-
}
|
|
2948
|
-
}
|
|
2949
|
-
}
|
|
2950
|
-
|
|
2951
|
-
// src/session/operation-confirmation.ts
|
|
2952
|
-
function check4(ok, message) {
|
|
2953
|
-
if (!ok) {
|
|
2954
|
-
throw new Error(message);
|
|
2955
|
-
}
|
|
2956
|
-
}
|
|
2957
|
-
async function barrier(runtime, state, block) {
|
|
2958
|
-
const checkpoints = [
|
|
2959
|
-
state.observedThrough,
|
|
2960
|
-
...block ? [{ number: block.number, hash: block.hash }] : []
|
|
2961
|
-
];
|
|
2962
|
-
for (const point of checkpoints) {
|
|
2963
|
-
if (point && (await runtime.reader.getBlock(point.number)).hash !== point.hash) {
|
|
2964
|
-
await runtime.store.transaction(runtime.scope, (draft) => {
|
|
2965
|
-
draft.invalidated = "Canonical operation checkpoint changed; full resync required";
|
|
2966
|
-
for (const record of [
|
|
2967
|
-
...Object.values(draft.notes),
|
|
2968
|
-
...Object.values(draft.credits)
|
|
2969
|
-
]) {
|
|
2970
|
-
record.spent = "unknown";
|
|
2971
|
-
}
|
|
2972
|
-
for (const operation of Object.values(draft.operations)) {
|
|
2973
|
-
if (["failed", "expired"].includes(operation.status) && !operation.payload?.externalized) {
|
|
2974
|
-
continue;
|
|
2975
|
-
}
|
|
2976
|
-
operation.status = "reorged";
|
|
2977
|
-
for (const id of operation.inputs) {
|
|
2978
|
-
if (!draft.reservations[id]) {
|
|
2979
|
-
draft.reservations[id] = operation.id;
|
|
2980
|
-
}
|
|
2981
|
-
}
|
|
2982
|
-
}
|
|
2983
|
-
});
|
|
2984
|
-
throw new Error("canonical operation checkpoint changed; full resync required");
|
|
2985
|
-
}
|
|
2986
|
-
}
|
|
2987
|
-
}
|
|
2988
|
-
function current(state, id, generation) {
|
|
2989
|
-
check4(!state.invalidated, "wallet requires canonical resync");
|
|
2990
|
-
const record = state.operations[id];
|
|
2991
|
-
const saved = record?.payload;
|
|
2992
|
-
check4(record && saved.generation === generation && !saved.claim, "stale operation recovery");
|
|
2993
|
-
return { record, saved };
|
|
2994
|
-
}
|
|
2995
|
-
function observe(state, block) {
|
|
2996
|
-
if (state.observedThrough?.number === block.number) {
|
|
2997
|
-
check4(state.observedThrough.hash === block.hash, "operation checkpoint conflict");
|
|
2998
|
-
}
|
|
2999
|
-
if (!state.observedThrough || state.observedThrough.number < block.number) {
|
|
3000
|
-
state.observedThrough = { number: block.number, hash: block.hash };
|
|
3001
|
-
}
|
|
3002
|
-
}
|
|
3003
|
-
async function confirmOperation(runtime, id, generation, hash2, signal) {
|
|
3004
|
-
const { reader, store, scope, options } = runtime;
|
|
3005
|
-
const state = await store.readSnapshot(scope);
|
|
3006
|
-
const { saved } = current(state, id, generation);
|
|
3007
|
-
const plan = saved.plan;
|
|
3008
|
-
await options.protocol.validateDeployment(reader);
|
|
3009
|
-
await barrier(runtime, state);
|
|
3010
|
-
const receipt = await reader.getReceipt(hash2);
|
|
3011
|
-
if (!receipt) {
|
|
3012
|
-
return void 0;
|
|
3013
|
-
}
|
|
3014
|
-
check4(receipt.transactionHash === hash2, "receipt transaction identity mismatch");
|
|
3015
|
-
await barrier(runtime, state, receipt.block);
|
|
3016
|
-
const transaction = await reader.getTransaction(hash2);
|
|
3017
|
-
check4(
|
|
3018
|
-
transaction && transaction.transactionHash === hash2,
|
|
3019
|
-
"confirmed transaction differs from the frozen operation"
|
|
3020
|
-
);
|
|
3021
|
-
let matchesRequest;
|
|
3022
|
-
if (plan.statement?.target === "noir-recursive") {
|
|
3023
|
-
matchesRequest = matchesAggregateTransaction(saved, transaction);
|
|
3024
|
-
} else {
|
|
3025
|
-
matchesRequest = !!saved.call && transaction.to.toLowerCase() === saved.call.to.toLowerCase() && transaction.value === saved.call.value && transaction.data.toLowerCase() === saved.call.data.toLowerCase();
|
|
3026
|
-
}
|
|
3027
|
-
check4(matchesRequest, "confirmed transaction differs from the frozen operation");
|
|
3028
|
-
check4(
|
|
3029
|
-
receipt.status === "success" || receipt.status === "reverted",
|
|
3030
|
-
"receipt execution status is missing"
|
|
3031
|
-
);
|
|
3032
|
-
if (receipt.status === "reverted") {
|
|
3033
|
-
return void 0;
|
|
3034
|
-
}
|
|
3035
|
-
const candidates = extractDeliveryCandidates(
|
|
3036
|
-
readProtocolLogs(receipt, plan.context.protocolAddress),
|
|
3037
|
-
plan.context,
|
|
3038
|
-
receipt.block.number
|
|
3039
|
-
);
|
|
3040
|
-
const latest = await reader.getLatestBlockNumber();
|
|
3041
|
-
const height = [latest, receipt.block.number, state.observedThrough?.number ?? 0n].reduce(
|
|
3042
|
-
(a, b) => a > b ? a : b
|
|
3043
|
-
);
|
|
3044
|
-
const spentBlock = await reader.getBlock(height);
|
|
3045
|
-
check4(spentBlock.number === height, "spent-state canonical block mismatch");
|
|
3046
|
-
const notes = [];
|
|
3047
|
-
for (const output of plan.outputs) {
|
|
3048
|
-
const candidate = candidates.find(
|
|
3049
|
-
(value) => value.role === "funds" && value.commitment === output.commitment
|
|
3050
|
-
);
|
|
3051
|
-
check4(candidate, "confirmed output is missing from canonical receipt");
|
|
3052
|
-
if (!output.note || output.note.ownerAddress !== scope.ownerAddress) {
|
|
3053
|
-
continue;
|
|
3054
|
-
}
|
|
3055
|
-
const input = {
|
|
3056
|
-
treeId: candidate.treeId,
|
|
3057
|
-
leafIndex: candidate.leafIndex,
|
|
3058
|
-
commitment: candidate.commitment
|
|
3059
|
-
};
|
|
3060
|
-
check4(options.protocolRpc, "membership provider required for confirmed owned output");
|
|
3061
|
-
const proof = await options.protocolRpc.getMembership(plan.context, input, receipt.block);
|
|
3062
|
-
await verifyMembership(reader, plan.context, receipt.block, input, proof);
|
|
3063
|
-
notes.push({
|
|
3064
|
-
...input,
|
|
3065
|
-
note: structuredClone(output.note),
|
|
3066
|
-
blockNumber: receipt.block.number,
|
|
3067
|
-
blockHash: receipt.block.hash,
|
|
3068
|
-
transactionHash: hash2,
|
|
3069
|
-
spent: "unknown"
|
|
3070
|
-
});
|
|
3071
|
-
}
|
|
3072
|
-
const credits = [];
|
|
3073
|
-
for (const output of plan.credit?.outputs ?? []) {
|
|
3074
|
-
const n = output.state.note;
|
|
3075
|
-
for (const role of ["owner", "spender"]) {
|
|
3076
|
-
if ((role === "owner" ? n.ownerWalletIdentity : n.spenderWalletIdentity) !== scope.ownerAddress) {
|
|
3077
|
-
continue;
|
|
3078
|
-
}
|
|
3079
|
-
const candidate = candidates.find(
|
|
3080
|
-
(value) => value.role === role && value.commitment === output.commitment
|
|
3081
|
-
);
|
|
3082
|
-
check4(candidate, "confirmed Credit output is missing from canonical receipt");
|
|
3083
|
-
const input = {
|
|
3084
|
-
treeId: candidate.treeId,
|
|
3085
|
-
leafIndex: candidate.leafIndex,
|
|
3086
|
-
commitment: candidate.commitment
|
|
3087
|
-
};
|
|
3088
|
-
check4(options.protocolRpc, "membership provider required for confirmed Credit");
|
|
3089
|
-
const proof = await options.protocolRpc.getMembership(
|
|
3090
|
-
plan.context,
|
|
3091
|
-
input,
|
|
3092
|
-
receipt.block
|
|
3093
|
-
);
|
|
3094
|
-
await verifyMembership(reader, plan.context, receipt.block, input, proof);
|
|
3095
|
-
const index2 = plan.credit?.draw?.remainderCommitments.indexOf(output.commitment);
|
|
3096
|
-
const parent = plan.credit?.topUp ? plan.credit.inputs[0] : index2 !== void 0 && index2 >= 0 ? plan.credit?.inputs[index2] : void 0;
|
|
3097
|
-
const receivingKeySource = parent && state.credits[parent.commitment + ":" + role] ? {
|
|
3098
|
-
kind: plan.credit?.topUp ? "top-up" : "draw",
|
|
3099
|
-
commitment: parent.commitment
|
|
3100
|
-
} : void 0;
|
|
3101
|
-
credits.push({
|
|
3102
|
-
...input,
|
|
3103
|
-
state: structuredClone(output.state),
|
|
3104
|
-
role,
|
|
3105
|
-
blockNumber: receipt.block.number,
|
|
3106
|
-
blockHash: receipt.block.hash,
|
|
3107
|
-
transactionHash: hash2,
|
|
3108
|
-
spent: "unknown",
|
|
3109
|
-
...receivingKeySource ? { receivingKeySource } : {}
|
|
3110
|
-
});
|
|
3111
|
-
}
|
|
3112
|
-
}
|
|
3113
|
-
if (credits.length) {
|
|
3114
|
-
const nullifiers = await Promise.all(
|
|
3115
|
-
credits.map(
|
|
3116
|
-
async (record) => computeCreditNullifier(
|
|
3117
|
-
plan.context.chainId,
|
|
3118
|
-
record.state.note.creditSharedKey,
|
|
3119
|
-
await creditLeaf(record.state)
|
|
3120
|
-
)
|
|
3121
|
-
)
|
|
3122
|
-
);
|
|
3123
|
-
const values = await reader.isSpent(plan.context, nullifiers, spentBlock);
|
|
3124
|
-
check4(
|
|
3125
|
-
values.length === credits.length && values.every((value) => typeof value === "boolean"),
|
|
3126
|
-
"invalid Credit spent-state response"
|
|
3127
|
-
);
|
|
3128
|
-
credits.forEach((record, i) => {
|
|
3129
|
-
record.spent = values[i] ? "spent" : "unspent";
|
|
3130
|
-
record.checkedThrough = { number: spentBlock.number, hash: spentBlock.hash };
|
|
3131
|
-
});
|
|
3132
|
-
}
|
|
3133
|
-
const facts = inputFacts(plan);
|
|
3134
|
-
const nfs = facts.map((input) => input.nullifier);
|
|
3135
|
-
const spent = nfs.length ? await reader.isSpent(plan.context, nfs, spentBlock) : [];
|
|
3136
|
-
check4(
|
|
3137
|
-
spent.length === nfs.length && spent.every((value) => value === true),
|
|
3138
|
-
"successful operation inputs are not consumed"
|
|
3139
|
-
);
|
|
3140
|
-
const nullifier = options.wallet.exportNullifier();
|
|
3141
|
-
if (notes.length) {
|
|
3142
|
-
const values = await reader.isSpent(
|
|
3143
|
-
plan.context,
|
|
3144
|
-
await Promise.all(
|
|
3145
|
-
notes.map(
|
|
3146
|
-
(note) => nullifier.compute(plan.context.chainId, note.commitment, note.note.nonce)
|
|
3147
|
-
)
|
|
3148
|
-
),
|
|
3149
|
-
spentBlock
|
|
3150
|
-
);
|
|
3151
|
-
check4(
|
|
3152
|
-
values.length === notes.length && values.every((value) => typeof value === "boolean"),
|
|
3153
|
-
"invalid output spent-state response"
|
|
3154
|
-
);
|
|
3155
|
-
notes.forEach((note, i) => {
|
|
3156
|
-
note.spent = values[i] ? "spent" : "unspent";
|
|
3157
|
-
note.checkedThrough = { number: spentBlock.number, hash: spentBlock.hash };
|
|
3158
|
-
});
|
|
3159
|
-
}
|
|
3160
|
-
signal?.throwIfAborted();
|
|
3161
|
-
await barrier(runtime, state, receipt.block);
|
|
3162
|
-
await barrier(runtime, state, spentBlock);
|
|
3163
|
-
return store.transaction(scope, (draft) => {
|
|
3164
|
-
signal?.throwIfAborted();
|
|
3165
|
-
const { record, saved: active } = current(draft, id, generation);
|
|
3166
|
-
for (const point of [draft.observedThrough]) {
|
|
3167
|
-
check4(
|
|
3168
|
-
!point || point.number < spentBlock.number || point.number === spentBlock.number && point.hash === spentBlock.hash,
|
|
3169
|
-
"wallet advanced or changed during confirmation; retry"
|
|
3170
|
-
);
|
|
3171
|
-
}
|
|
3172
|
-
mergeReceiptRecords(draft, {
|
|
3173
|
-
notes,
|
|
3174
|
-
credits,
|
|
3175
|
-
checkpoint: { number: receipt.block.number, hash: receipt.block.hash }
|
|
3176
|
-
});
|
|
3177
|
-
for (const fact of facts) {
|
|
3178
|
-
const input = draft[fact.kind][fact.key];
|
|
3179
|
-
if (input) {
|
|
3180
|
-
mergeSpentObservation(input, {
|
|
3181
|
-
spent: "spent",
|
|
3182
|
-
checkedThrough: { number: spentBlock.number, hash: spentBlock.hash }
|
|
3183
|
-
});
|
|
3184
|
-
}
|
|
3185
|
-
if (fact.kind === "credits" && input) {
|
|
3186
|
-
for (const other of Object.values(draft.credits)) {
|
|
3187
|
-
if (other.commitment === input.commitment) {
|
|
3188
|
-
mergeSpentObservation(other, {
|
|
3189
|
-
spent: "spent",
|
|
3190
|
-
checkedThrough: { number: spentBlock.number, hash: spentBlock.hash }
|
|
3191
|
-
});
|
|
3192
|
-
}
|
|
3193
|
-
}
|
|
3194
|
-
}
|
|
3195
|
-
if (draft.reservations[fact.reservationKey] === id) {
|
|
3196
|
-
delete draft.reservations[fact.reservationKey];
|
|
3197
|
-
}
|
|
3198
|
-
}
|
|
3199
|
-
observe(draft, spentBlock);
|
|
3200
|
-
record.status = "confirmed";
|
|
3201
|
-
record.transactionHash = hash2;
|
|
3202
|
-
active.generation++;
|
|
3203
|
-
return { status: record.status, saved: structuredClone(active) };
|
|
3204
|
-
});
|
|
2378
|
+
}
|
|
2379
|
+
const request = await fundingSigningRequest(saved, runtime);
|
|
2380
|
+
return verifyFundingSignature(
|
|
2381
|
+
request,
|
|
2382
|
+
saved.plan.funding.payer,
|
|
2383
|
+
signature,
|
|
2384
|
+
runtime.options.fundingVerifier
|
|
2385
|
+
);
|
|
3205
2386
|
}
|
|
3206
|
-
async function
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
const
|
|
3211
|
-
|
|
3212
|
-
await barrier(runtime, state);
|
|
3213
|
-
const block = await reader.getBlock(await reader.getLatestBlockNumber());
|
|
3214
|
-
const facts = inputFacts(plan);
|
|
3215
|
-
const nfs = facts.map((input) => input.nullifier);
|
|
3216
|
-
const spent = nfs.length ? await reader.isSpent(plan.context, nfs, block) : [];
|
|
3217
|
-
check4(
|
|
3218
|
-
spent.length === nfs.length && spent.every((value) => typeof value === "boolean"),
|
|
3219
|
-
"invalid reconciliation spent-state response"
|
|
2387
|
+
async function verifyFundingSignature(request, payer, signature, verifier) {
|
|
2388
|
+
if (!/^0x(?:[a-fA-F0-9]{2})+$/.test(signature)) {
|
|
2389
|
+
throw new Error("invalid EVM funding signature");
|
|
2390
|
+
}
|
|
2391
|
+
const types = Object.fromEntries(
|
|
2392
|
+
Object.entries(request.types).map(([name, fields]) => [name, [...fields]])
|
|
3220
2393
|
);
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
signal?.throwIfAborted();
|
|
3227
|
-
const { record: active, saved: next } = current(draft, id, generation);
|
|
3228
|
-
for (const point of [
|
|
3229
|
-
draft.observedThrough,
|
|
3230
|
-
...facts.map((fact) => draft[fact.kind][fact.key]?.checkedThrough)
|
|
3231
|
-
]) {
|
|
3232
|
-
check4(
|
|
3233
|
-
!point || point.number < block.number || point.number === block.number && point.hash === block.hash,
|
|
3234
|
-
"wallet advanced or changed during reconciliation; retry"
|
|
3235
|
-
);
|
|
2394
|
+
let validEOA = false;
|
|
2395
|
+
try {
|
|
2396
|
+
validEOA = verifyTypedData(request.domain, types, request.message, signature).toLowerCase() === payer.toLowerCase();
|
|
2397
|
+
if (validEOA) {
|
|
2398
|
+
signature = Signature.from(signature).serialized;
|
|
3236
2399
|
}
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
2400
|
+
} catch {
|
|
2401
|
+
validEOA = false;
|
|
2402
|
+
}
|
|
2403
|
+
const valid = verifier ? await verifier.verifyTypedData({
|
|
2404
|
+
...structuredClone(request),
|
|
2405
|
+
address: payer,
|
|
2406
|
+
signature
|
|
2407
|
+
}) : validEOA;
|
|
2408
|
+
if (valid !== true) {
|
|
2409
|
+
throw new Error("invalid EVM funding signature");
|
|
2410
|
+
}
|
|
2411
|
+
return signature;
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
// src/transactions/execution/operation-preview.ts
|
|
2415
|
+
import { normalizeFunding } from "@invisi-labs/protocol";
|
|
2416
|
+
function operationPreview(plan, status) {
|
|
2417
|
+
const p = plan;
|
|
2418
|
+
const fee = p.params ?? p.shieldParams ?? p.credit?.creation ?? p.credit?.cancel;
|
|
2419
|
+
const funding = normalizeFunding(p.funding);
|
|
2420
|
+
return {
|
|
2421
|
+
id: p.id,
|
|
2422
|
+
kind: p.kind,
|
|
2423
|
+
status,
|
|
2424
|
+
context: { ...p.context },
|
|
2425
|
+
inputs: inputFacts(p).length,
|
|
2426
|
+
inputDetails: [
|
|
2427
|
+
...p.inputs.map((input) => ({
|
|
2428
|
+
kind: "notes",
|
|
2429
|
+
commitment: input.commitment,
|
|
2430
|
+
amount: input.note.amount,
|
|
2431
|
+
owner: input.note.ownerAddress,
|
|
2432
|
+
treeId: input.treeId,
|
|
2433
|
+
leafIndex: input.leafIndex
|
|
2434
|
+
})),
|
|
2435
|
+
...(p.credit?.inputs ?? []).map((input) => ({
|
|
2436
|
+
kind: "credits",
|
|
2437
|
+
commitment: input.commitment,
|
|
2438
|
+
amount: input.state.note.amount,
|
|
2439
|
+
owner: input.state.note.ownerWalletIdentity,
|
|
2440
|
+
spender: input.state.note.spenderWalletIdentity,
|
|
2441
|
+
expiry: input.state.note.expiry,
|
|
2442
|
+
role: input.role,
|
|
2443
|
+
treeId: input.treeId,
|
|
2444
|
+
leafIndex: input.leafIndex
|
|
2445
|
+
}))
|
|
2446
|
+
],
|
|
2447
|
+
funding: {
|
|
2448
|
+
payer: funding.payer,
|
|
2449
|
+
amount: funding.amount,
|
|
2450
|
+
authorization: p.fundingMode,
|
|
2451
|
+
sigNonce: funding.sigNonce,
|
|
2452
|
+
deadline: funding.deadline,
|
|
2453
|
+
permit: { deadline: funding.permit.deadline }
|
|
2454
|
+
},
|
|
2455
|
+
creditOutputs: p.credit?.outputs.map((output) => ({
|
|
2456
|
+
commitment: output.commitment,
|
|
2457
|
+
amount: output.state.note.amount,
|
|
2458
|
+
expiry: output.state.note.expiry,
|
|
2459
|
+
owner: output.state.note.ownerWalletIdentity,
|
|
2460
|
+
spender: output.state.note.spenderWalletIdentity,
|
|
2461
|
+
ownerPayout: output.state.note.ownerPayout,
|
|
2462
|
+
spenderPayout: output.state.note.spenderPayout
|
|
2463
|
+
})),
|
|
2464
|
+
outputs: p.outputs.map((output) => ({
|
|
2465
|
+
commitment: output.commitment,
|
|
2466
|
+
to: output.address,
|
|
2467
|
+
amount: output.amount,
|
|
2468
|
+
change: output.change
|
|
2469
|
+
})),
|
|
2470
|
+
capacity: p.capacity,
|
|
2471
|
+
deadline: p.deadline,
|
|
2472
|
+
feeRecipient: fee?.feeRecipient,
|
|
2473
|
+
feeAmount: fee?.feeAmount ?? 0n,
|
|
2474
|
+
fundingAmount: p.funding.amount ?? 0n,
|
|
2475
|
+
fundingPayer: p.funding.payer,
|
|
2476
|
+
withdrawalAmount: p.params?.withdrawalAmount ?? p.credit?.draw?.totalAmount ?? (p.credit?.cancel ? p.credit.cancel.amount - (p.credit.cancel.feeAmount ?? 0n) : void 0) ?? 0n,
|
|
2477
|
+
withdrawalAddress: p.params?.withdrawalAddress ?? p.credit?.draw?.spender ?? p.credit?.cancel?.owner,
|
|
2478
|
+
permissions: {
|
|
2479
|
+
walletAuthorization: requiresAuthorization(p),
|
|
2480
|
+
proof: p.kind !== "shield",
|
|
2481
|
+
fundingAuthorization: p.fundingMode === "signature"
|
|
3264
2482
|
}
|
|
3265
|
-
|
|
3266
|
-
return { status: active.status, saved: structuredClone(next) };
|
|
3267
|
-
});
|
|
2483
|
+
};
|
|
3268
2484
|
}
|
|
3269
2485
|
|
|
3270
|
-
// src/
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
2486
|
+
// src/transactions/execution/PreparedTransaction.ts
|
|
2487
|
+
import { MissingCapabilityError } from "@invisi-labs/core";
|
|
2488
|
+
import { normalizeFunding as normalizeFunding2 } from "@invisi-labs/protocol";
|
|
2489
|
+
function check2(ok, message) {
|
|
2490
|
+
if (!ok) throw new Error(message);
|
|
3275
2491
|
}
|
|
3276
|
-
var
|
|
2492
|
+
var PreparedTransaction = class _PreparedTransaction {
|
|
3277
2493
|
#runtime;
|
|
3278
2494
|
#saved;
|
|
3279
2495
|
#authorization;
|
|
2496
|
+
#lifecycle;
|
|
3280
2497
|
status;
|
|
3281
|
-
constructor(runtime, saved, status, authorization) {
|
|
2498
|
+
constructor(runtime, saved, status, lifecycle, authorization) {
|
|
3282
2499
|
this.#runtime = runtime;
|
|
3283
2500
|
this.#saved = structuredClone(saved);
|
|
3284
|
-
this
|
|
2501
|
+
this.#lifecycle = lifecycle;
|
|
3285
2502
|
this.#authorization = authorization;
|
|
2503
|
+
this.status = status;
|
|
3286
2504
|
Object.freeze(this);
|
|
3287
2505
|
}
|
|
3288
|
-
static
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
for (const input of facts) {
|
|
3295
|
-
const record = state[input.kind][input.key];
|
|
3296
|
-
check5(
|
|
3297
|
-
record && record.spent === "unspent" && !state.reservations[input.reservationKey],
|
|
3298
|
-
"input unavailable or already reserved"
|
|
3299
|
-
);
|
|
3300
|
-
}
|
|
3301
|
-
state.operations[plan.id] = {
|
|
3302
|
-
id: plan.id,
|
|
3303
|
-
status: "prepared",
|
|
3304
|
-
inputs: facts.map((input) => input.reservationKey),
|
|
3305
|
-
deadline: plan.deadline,
|
|
3306
|
-
payload: saved
|
|
3307
|
-
};
|
|
3308
|
-
for (const input of facts) {
|
|
3309
|
-
state.reservations[input.reservationKey] = plan.id;
|
|
3310
|
-
}
|
|
3311
|
-
});
|
|
3312
|
-
return new _Operation(runtime, saved, "prepared");
|
|
3313
|
-
}
|
|
3314
|
-
static async resume(runtime, id) {
|
|
3315
|
-
const result = await runtime.store.transaction(runtime.scope, (state) => {
|
|
3316
|
-
const record = state.operations[id];
|
|
3317
|
-
check5(record, "unknown operation");
|
|
3318
|
-
const saved = record.payload;
|
|
3319
|
-
check5(
|
|
3320
|
-
saved?.plan?.version === 1 && saved.plan.id === id && Number.isSafeInteger(saved.generation) && saved.generation >= 0 && saved.generation < Number.MAX_SAFE_INTEGER,
|
|
3321
|
-
"invalid operation payload"
|
|
3322
|
-
);
|
|
3323
|
-
const context = runtime.options.protocol.context;
|
|
3324
|
-
const actual = saved.plan.context;
|
|
3325
|
-
check5(
|
|
3326
|
-
scopeKey(saved.plan.scope) === scopeKey(runtime.scope) && actual.chainId === context.chainId && actual.protocolAddress.toLowerCase() === context.protocolAddress.toLowerCase() && actual.tokenAddress.toLowerCase() === context.tokenAddress.toLowerCase(),
|
|
3327
|
-
"recovered operation scope/deployment context mismatch"
|
|
3328
|
-
);
|
|
3329
|
-
if (saved.plan.kind !== "shield") {
|
|
3330
|
-
statement(saved.plan);
|
|
3331
|
-
}
|
|
3332
|
-
if (saved.proof) {
|
|
3333
|
-
sameStatement(saved.proof, saved.plan.statement);
|
|
3334
|
-
}
|
|
3335
|
-
saved.generation++;
|
|
3336
|
-
delete saved.claim;
|
|
3337
|
-
return new _Operation(runtime, saved, record.status);
|
|
3338
|
-
});
|
|
3339
|
-
if (result.#saved.call) {
|
|
3340
|
-
const expected = await result.encode(result.#saved);
|
|
3341
|
-
check5(
|
|
3342
|
-
expected.to === result.#saved.call.to && expected.value === result.#saved.call.value && expected.data === result.#saved.call.data,
|
|
3343
|
-
"recovered transaction differs from frozen operation"
|
|
3344
|
-
);
|
|
3345
|
-
}
|
|
3346
|
-
return result;
|
|
3347
|
-
}
|
|
3348
|
-
/** Public non-sendable intent for a firm quote before wallet authorization. */
|
|
3349
|
-
async exportBroadcasterIntent() {
|
|
3350
|
-
check5(
|
|
3351
|
-
["prepared", "authorized"].includes(this.status),
|
|
3352
|
-
"quote intent requires a prepared operation"
|
|
2506
|
+
static prepare(runtime, plan) {
|
|
2507
|
+
return new _PreparedTransaction(
|
|
2508
|
+
runtime,
|
|
2509
|
+
{ plan: structuredClone(plan), generation: 0 },
|
|
2510
|
+
"prepared",
|
|
2511
|
+
{ generation: 0, busy: false }
|
|
3353
2512
|
);
|
|
3354
|
-
return exportBroadcasterIntent(this.#saved.plan);
|
|
3355
2513
|
}
|
|
3356
2514
|
broadcasterQuote() {
|
|
3357
2515
|
return this.#saved.broadcasterQuote ? structuredClone(this.#saved.broadcasterQuote) : void 0;
|
|
3358
2516
|
}
|
|
3359
2517
|
async withBroadcaster(client, options) {
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
2518
|
+
this.current();
|
|
2519
|
+
check2(
|
|
2520
|
+
this.status === "prepared" && this.#runtime.reprepare,
|
|
2521
|
+
"broadcaster must attach to an unsigned prepared transaction"
|
|
2522
|
+
);
|
|
2523
|
+
const { prepareBroadcasterPlan } = await import("./broadcaster-preparation-RMWQFYNL.js");
|
|
2524
|
+
const selected = await prepareBroadcasterPlan(
|
|
2525
|
+
this.#saved.plan,
|
|
2526
|
+
this.#runtime.reprepare,
|
|
2527
|
+
client,
|
|
2528
|
+
options
|
|
2529
|
+
);
|
|
2530
|
+
return new _PreparedTransaction(
|
|
2531
|
+
this.#runtime,
|
|
2532
|
+
{ plan: selected.plan, broadcasterQuote: selected.quote, generation: 0 },
|
|
2533
|
+
"prepared",
|
|
2534
|
+
{ generation: 0, busy: false }
|
|
3377
2535
|
);
|
|
3378
|
-
const selected = await prepareBroadcasterPlan(this.#saved.plan, reprepare, client, options);
|
|
3379
|
-
const saved = await runtime.store.transaction(runtime.scope, (state) => {
|
|
3380
|
-
const record = state.operations[this.id];
|
|
3381
|
-
const old = record?.payload;
|
|
3382
|
-
check5(
|
|
3383
|
-
record?.status === "prepared" && old?.generation === this.#saved.generation && !old.claim && !old.externalized,
|
|
3384
|
-
"stale operation or active worker"
|
|
3385
|
-
);
|
|
3386
|
-
check5(!state.invalidated, "wallet state requires canonical resync");
|
|
3387
|
-
const nextPlan = { ...selected.plan, id: this.id };
|
|
3388
|
-
const facts = inputFacts(nextPlan);
|
|
3389
|
-
for (const fact of facts)
|
|
3390
|
-
check5(
|
|
3391
|
-
state[fact.kind][fact.key]?.spent === "unspent" && (!state.reservations[fact.reservationKey] || state.reservations[fact.reservationKey] === this.id),
|
|
3392
|
-
"input unavailable or already reserved"
|
|
3393
|
-
);
|
|
3394
|
-
for (const [key, owner] of Object.entries(state.reservations))
|
|
3395
|
-
if (owner === this.id) delete state.reservations[key];
|
|
3396
|
-
for (const fact of facts) state.reservations[fact.reservationKey] = this.id;
|
|
3397
|
-
const next = {
|
|
3398
|
-
plan: nextPlan,
|
|
3399
|
-
broadcasterQuote: selected.quote,
|
|
3400
|
-
generation: old.generation + 1
|
|
3401
|
-
};
|
|
3402
|
-
record.payload = next;
|
|
3403
|
-
record.inputs = facts.map((fact) => fact.reservationKey);
|
|
3404
|
-
record.deadline = nextPlan.deadline;
|
|
3405
|
-
return structuredClone(next);
|
|
3406
|
-
});
|
|
3407
|
-
return new _Operation(runtime, saved, "prepared");
|
|
3408
2536
|
}
|
|
3409
2537
|
get id() {
|
|
3410
2538
|
return this.#saved.plan.id;
|
|
@@ -3419,77 +2547,61 @@ var Operation = class _Operation {
|
|
|
3419
2547
|
this.#runtime.options.gasBenchmarks
|
|
3420
2548
|
);
|
|
3421
2549
|
}
|
|
2550
|
+
/** Final Note outputs, including change; Credit outputs are not exported as IPN. */
|
|
2551
|
+
getOutputs() {
|
|
2552
|
+
return Object.freeze(
|
|
2553
|
+
this.#saved.plan.outputs.map((output) => new TransactionOutput(output))
|
|
2554
|
+
);
|
|
2555
|
+
}
|
|
3422
2556
|
preview() {
|
|
3423
2557
|
return operationPreview(this.#saved.plan, this.status);
|
|
3424
2558
|
}
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
"
|
|
2559
|
+
/** Obtain a firm quote before signing. This intent contains no private inputs or witness. */
|
|
2560
|
+
async exportBroadcasterIntent() {
|
|
2561
|
+
this.current();
|
|
2562
|
+
check2(
|
|
2563
|
+
["prepared", "authorized"].includes(this.status),
|
|
2564
|
+
"quote intent requires a prepared transaction"
|
|
2565
|
+
);
|
|
2566
|
+
return exportBroadcasterIntent(this.#saved.plan);
|
|
2567
|
+
}
|
|
2568
|
+
current() {
|
|
2569
|
+
check2(
|
|
2570
|
+
!this.#lifecycle.busy && this.#lifecycle.generation === this.#saved.generation,
|
|
2571
|
+
"stale transaction handle or active worker"
|
|
3431
2572
|
);
|
|
3432
|
-
return saved;
|
|
3433
2573
|
}
|
|
3434
2574
|
async stage(stage, allowed, work, signal) {
|
|
3435
2575
|
signal?.throwIfAborted();
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
const record = state.operations[this.id];
|
|
3441
|
-
const current2 = this.current(record);
|
|
3442
|
-
check5(allowed.includes(record.status), `invalid operation stage for ${stage}`);
|
|
3443
|
-
current2.generation++;
|
|
3444
|
-
current2.claim = { stage, token };
|
|
3445
|
-
return structuredClone(current2);
|
|
3446
|
-
});
|
|
2576
|
+
this.current();
|
|
2577
|
+
check2(allowed.includes(this.status), `invalid transaction stage for ${stage}`);
|
|
2578
|
+
this.#lifecycle.busy = true;
|
|
2579
|
+
const saved = structuredClone(this.#saved);
|
|
3447
2580
|
try {
|
|
3448
2581
|
const next = await work(saved);
|
|
3449
2582
|
signal?.throwIfAborted();
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
const current2 = record?.payload;
|
|
3454
|
-
check5(
|
|
3455
|
-
record && current2?.generation === saved.generation && current2.claim?.token === token && allowed.includes(record.status),
|
|
3456
|
-
"stale operation worker after recovery"
|
|
3457
|
-
);
|
|
3458
|
-
delete saved.claim;
|
|
3459
|
-
record.status = next.status;
|
|
3460
|
-
record.payload = structuredClone(saved);
|
|
3461
|
-
});
|
|
3462
|
-
return new _Operation(
|
|
2583
|
+
saved.generation++;
|
|
2584
|
+
this.#lifecycle.generation = saved.generation;
|
|
2585
|
+
return new _PreparedTransaction(
|
|
3463
2586
|
this.#runtime,
|
|
3464
2587
|
saved,
|
|
3465
2588
|
next.status,
|
|
2589
|
+
this.#lifecycle,
|
|
3466
2590
|
next.authorization ?? this.#authorization
|
|
3467
2591
|
);
|
|
3468
|
-
}
|
|
3469
|
-
|
|
3470
|
-
const current2 = state.operations[this.id]?.payload;
|
|
3471
|
-
if (current2?.claim?.token === token) {
|
|
3472
|
-
delete current2.claim;
|
|
3473
|
-
}
|
|
3474
|
-
});
|
|
3475
|
-
throw error;
|
|
2592
|
+
} finally {
|
|
2593
|
+
this.#lifecycle.busy = false;
|
|
3476
2594
|
}
|
|
3477
2595
|
}
|
|
3478
|
-
async signingCurrent() {
|
|
3479
|
-
await this.#runtime.store.transaction(this.#runtime.scope, (state) => {
|
|
3480
|
-
check5(!state.invalidated, "wallet state requires canonical resync");
|
|
3481
|
-
this.current(state.operations[this.id]);
|
|
3482
|
-
});
|
|
3483
|
-
}
|
|
3484
2596
|
/** Public EIP-712 request; signing transport and key custody belong to the caller. */
|
|
3485
2597
|
async exportWalletAuthorization(options = {}) {
|
|
3486
2598
|
options.signal?.throwIfAborted();
|
|
3487
|
-
|
|
3488
|
-
|
|
2599
|
+
this.current();
|
|
2600
|
+
check2(
|
|
3489
2601
|
requiresAuthorization(this.#saved.plan),
|
|
3490
2602
|
"operation does not require wallet authorization"
|
|
3491
2603
|
);
|
|
3492
|
-
|
|
2604
|
+
check2(
|
|
3493
2605
|
["prepared", "authorized"].includes(this.status),
|
|
3494
2606
|
"operation is not awaiting wallet authorization"
|
|
3495
2607
|
);
|
|
@@ -3498,12 +2610,12 @@ var Operation = class _Operation {
|
|
|
3498
2610
|
/** Attach an external EOA signature, retaining its witness only at the private proving boundary. */
|
|
3499
2611
|
async withWalletSignature(signature, options = {}) {
|
|
3500
2612
|
options.signal?.throwIfAborted();
|
|
3501
|
-
|
|
3502
|
-
|
|
2613
|
+
this.current();
|
|
2614
|
+
check2(
|
|
3503
2615
|
requiresAuthorization(this.#saved.plan),
|
|
3504
2616
|
"operation does not require wallet authorization"
|
|
3505
2617
|
);
|
|
3506
|
-
|
|
2618
|
+
check2(
|
|
3507
2619
|
["prepared", "authorized"].includes(this.status),
|
|
3508
2620
|
"operation is not awaiting wallet authorization"
|
|
3509
2621
|
);
|
|
@@ -3518,21 +2630,21 @@ var Operation = class _Operation {
|
|
|
3518
2630
|
/** Export only after proving when the funding authorization binds an actual proof hash. */
|
|
3519
2631
|
async exportFundingAuthorization(options = {}) {
|
|
3520
2632
|
options.signal?.throwIfAborted();
|
|
3521
|
-
|
|
3522
|
-
|
|
2633
|
+
this.current();
|
|
2634
|
+
check2(
|
|
3523
2635
|
this.status === (this.kind === "shield" ? "prepared" : "proved"),
|
|
3524
2636
|
"operation is not awaiting funding authorization or proof"
|
|
3525
2637
|
);
|
|
3526
2638
|
const request = await fundingSigningRequest(this.#saved, this.#runtime);
|
|
3527
2639
|
options.signal?.throwIfAborted();
|
|
3528
|
-
|
|
2640
|
+
this.current();
|
|
3529
2641
|
return request;
|
|
3530
2642
|
}
|
|
3531
2643
|
/** Verify an external signature against this handle's own frozen operation. */
|
|
3532
2644
|
async withFundingSignature(signature, options = {}) {
|
|
3533
2645
|
options.signal?.throwIfAborted();
|
|
3534
|
-
|
|
3535
|
-
|
|
2646
|
+
this.current();
|
|
2647
|
+
check2(
|
|
3536
2648
|
this.status === (this.kind === "shield" ? "prepared" : "proved"),
|
|
3537
2649
|
"operation is not awaiting funding authorization or proof"
|
|
3538
2650
|
);
|
|
@@ -3550,41 +2662,21 @@ var Operation = class _Operation {
|
|
|
3550
2662
|
options.signal
|
|
3551
2663
|
);
|
|
3552
2664
|
}
|
|
3553
|
-
async
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
"operation does not require wallet authorization"
|
|
3557
|
-
);
|
|
3558
|
-
const provider = this.#runtime.authorization();
|
|
3559
|
-
check5(
|
|
3560
|
-
provider.ownerAddress === this.#runtime.scope.ownerAddress,
|
|
3561
|
-
"wallet authorization capability mismatch"
|
|
3562
|
-
);
|
|
3563
|
-
return this.stage(
|
|
3564
|
-
"wallet authorization",
|
|
3565
|
-
["prepared", "authorized"],
|
|
3566
|
-
async (saved) => {
|
|
3567
|
-
const authorization = await authorizeOperation(saved.plan, provider);
|
|
3568
|
-
return { status: "authorized", authorization };
|
|
3569
|
-
},
|
|
3570
|
-
options.signal
|
|
3571
|
-
);
|
|
3572
|
-
}
|
|
3573
|
-
async prove(suppliedOptions = {}) {
|
|
3574
|
-
const options = { ...suppliedOptions };
|
|
3575
|
-
check5(
|
|
2665
|
+
async prove(backendOrOptions = {}, suppliedOptions = {}) {
|
|
2666
|
+
const options = { ..."prove" in backendOrOptions ? suppliedOptions : backendOrOptions };
|
|
2667
|
+
check2(
|
|
3576
2668
|
!this.#saved.broadcasterQuote || options.execution !== "aggregate",
|
|
3577
2669
|
"a broadcaster quote requires direct execution"
|
|
3578
2670
|
);
|
|
3579
2671
|
const plan = this.#saved.plan;
|
|
3580
|
-
|
|
2672
|
+
check2(plan.kind !== "shield", "shield requires no proof");
|
|
3581
2673
|
if (requiresAuthorization(plan)) {
|
|
3582
|
-
|
|
2674
|
+
check2(
|
|
3583
2675
|
this.#authorization,
|
|
3584
2676
|
"local wallet authorization required; authorize again after recovery"
|
|
3585
2677
|
);
|
|
3586
2678
|
}
|
|
3587
|
-
const prover = this.#runtime.options.prover;
|
|
2679
|
+
const prover = "prove" in backendOrOptions ? backendOrOptions : this.#runtime.options.prover;
|
|
3588
2680
|
if (!prover) {
|
|
3589
2681
|
throw new MissingCapabilityError("prover");
|
|
3590
2682
|
}
|
|
@@ -3611,1101 +2703,863 @@ var Operation = class _Operation {
|
|
|
3611
2703
|
options.signal
|
|
3612
2704
|
);
|
|
3613
2705
|
}
|
|
3614
|
-
encode(saved) {
|
|
3615
|
-
return encodeOperation(saved);
|
|
2706
|
+
encode(saved) {
|
|
2707
|
+
return encodeOperation(saved);
|
|
2708
|
+
}
|
|
2709
|
+
/** Shield caller funding needs no wallet signing or proof; this step only encodes its frozen request. */
|
|
2710
|
+
async ready() {
|
|
2711
|
+
check2(
|
|
2712
|
+
this.kind === "shield" && this.#saved.plan.fundingMode === "caller",
|
|
2713
|
+
"operation needs authorization or proof first"
|
|
2714
|
+
);
|
|
2715
|
+
return this.stage("encoding", ["prepared"], async (saved) => {
|
|
2716
|
+
saved.call = await this.encode(saved);
|
|
2717
|
+
return { status: "ready" };
|
|
2718
|
+
});
|
|
2719
|
+
}
|
|
2720
|
+
async exportTransaction() {
|
|
2721
|
+
this.current();
|
|
2722
|
+
check2(
|
|
2723
|
+
this.#saved.plan.statement?.target !== "noir-recursive",
|
|
2724
|
+
"recursive operations must be exported as aggregate members"
|
|
2725
|
+
);
|
|
2726
|
+
check2(this.status === "ready" && this.#saved.call, "transaction is not ready");
|
|
2727
|
+
return structuredClone(this.#saved.call);
|
|
2728
|
+
}
|
|
2729
|
+
exportProof() {
|
|
2730
|
+
this.current();
|
|
2731
|
+
check2(this.#saved.proof, "transaction has no proof");
|
|
2732
|
+
return publicProof(this.#saved.proof);
|
|
2733
|
+
}
|
|
2734
|
+
exportAggregateMember() {
|
|
2735
|
+
this.current();
|
|
2736
|
+
const saved = this.#saved, plan = saved.plan;
|
|
2737
|
+
check2(
|
|
2738
|
+
this.status === "ready" && plan.params && saved.proof && plan.statement?.target === "noir-recursive" && plan.statement.circuitName === "transact_user_slots14",
|
|
2739
|
+
"ready recursive slots14 transaction required"
|
|
2740
|
+
);
|
|
2741
|
+
sameStatement(saved.proof, statement(plan));
|
|
2742
|
+
check2(
|
|
2743
|
+
saved.proof.proofHex.length === 2 + 458 * 64,
|
|
2744
|
+
"recursive member proof must have 458 fields"
|
|
2745
|
+
);
|
|
2746
|
+
return {
|
|
2747
|
+
bundle: publicProof(saved.proof),
|
|
2748
|
+
statement: structuredClone(plan.statement),
|
|
2749
|
+
context: { ...plan.context },
|
|
2750
|
+
request: {
|
|
2751
|
+
params: structuredClone(plan.params),
|
|
2752
|
+
deliveries: plan.outputs.map((output) => structuredClone(output.delivery)),
|
|
2753
|
+
funding: normalizeFunding2(plan.funding)
|
|
2754
|
+
}
|
|
2755
|
+
};
|
|
2756
|
+
}
|
|
2757
|
+
toJSON() {
|
|
2758
|
+
return { transaction: true, id: this.id, kind: this.kind, status: this.status };
|
|
2759
|
+
}
|
|
2760
|
+
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
|
2761
|
+
return this.toJSON();
|
|
2762
|
+
}
|
|
2763
|
+
};
|
|
2764
|
+
|
|
2765
|
+
// src/client/infrastructure.ts
|
|
2766
|
+
import { MissingCapabilityError as MissingCapabilityError2 } from "@invisi-labs/core";
|
|
2767
|
+
import { ProtocolRpcClient } from "@invisi-labs/rpc";
|
|
2768
|
+
function configureTransactionInfrastructure(options) {
|
|
2769
|
+
const { provider } = options;
|
|
2770
|
+
const reader = options.chainReader ?? (provider ? new EthersChainReader(provider) : void 0);
|
|
2771
|
+
const protocolRpc = options.protocolRpc instanceof ProtocolRpcClient && reader ? options.protocolRpc.withReader(reader) : options.protocolRpc;
|
|
2772
|
+
const fundingVerifier = options.fundingVerifier ?? (provider ? new EthersFundingVerifier(provider, options.protocol.context.chainId) : void 0);
|
|
2773
|
+
return configureRecipientInfrastructure({
|
|
2774
|
+
...options,
|
|
2775
|
+
...reader ? { chainReader: reader } : {},
|
|
2776
|
+
...protocolRpc ? { protocolRpc } : {},
|
|
2777
|
+
...fundingVerifier ? { fundingVerifier } : {}
|
|
2778
|
+
});
|
|
2779
|
+
}
|
|
2780
|
+
async function validateTransactionInfrastructure(options) {
|
|
2781
|
+
if (!options.chainReader) throw new MissingCapabilityError2("chain reader");
|
|
2782
|
+
if (options.provider && (await options.provider.getNetwork()).chainId !== options.protocol.context.chainId)
|
|
2783
|
+
throw new Error("provider deployment chain mismatch");
|
|
2784
|
+
await options.protocol.validateDeployment(options.chainReader);
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
// src/client/Invisi.ts
|
|
2788
|
+
import { MissingCapabilityError as MissingCapabilityError3, PublicWallet } from "@invisi-labs/core";
|
|
2789
|
+
import { ProtocolRpcClient as ProtocolRpcClient2 } from "@invisi-labs/rpc";
|
|
2790
|
+
|
|
2791
|
+
// src/transactions/preparation/explicit-preparation.ts
|
|
2792
|
+
import { getAddress } from "ethers";
|
|
2793
|
+
function explicitPreparation(options, data, tokenAddress) {
|
|
2794
|
+
if (!options.chainReader) throw new Error("chain reader required");
|
|
2795
|
+
const inputs = structuredClone(data.inputs ?? []);
|
|
2796
|
+
const credits = structuredClone(data.creditInputs ?? []);
|
|
2797
|
+
const inventory = { notes: {}, credits: {}, reservations: {} };
|
|
2798
|
+
const context = options.protocol.context;
|
|
2799
|
+
function position2(input) {
|
|
2800
|
+
if (typeof input.treeId !== "bigint" || input.treeId < 0n || input.treeId >= 1n << 256n || !Number.isInteger(input.leafIndex) || input.leafIndex < 0 || input.leafIndex >= 65536)
|
|
2801
|
+
throw new Error("invalid explicit input position");
|
|
2802
|
+
}
|
|
2803
|
+
for (const input of inputs) {
|
|
2804
|
+
position2(input);
|
|
2805
|
+
positiveAmount(input.note.amount);
|
|
2806
|
+
if (inventory.notes[input.commitment]) throw new Error("duplicate explicit note input");
|
|
2807
|
+
if (input.note.chainId !== context.chainId || input.note.protocolAddress !== BigInt(context.protocolAddress) || input.note.ownerAddress !== options.wallet.ownerAddress)
|
|
2808
|
+
throw new Error("explicit note identity or deployment mismatch");
|
|
2809
|
+
inventory.notes[input.commitment] = { ...input, spent: "unspent" };
|
|
2810
|
+
}
|
|
2811
|
+
for (const input of credits) {
|
|
2812
|
+
position2(input);
|
|
2813
|
+
if (input.role !== "owner" && input.role !== "spender")
|
|
2814
|
+
throw new Error("invalid explicit Credit role");
|
|
2815
|
+
const key = input.commitment + ":" + input.role;
|
|
2816
|
+
if (inventory.credits[key]) throw new Error("duplicate explicit Credit input");
|
|
2817
|
+
inventory.credits[key] = { ...input, spent: "unspent" };
|
|
2818
|
+
}
|
|
2819
|
+
const scope = {
|
|
2820
|
+
...context,
|
|
2821
|
+
namespace: "explicit",
|
|
2822
|
+
ownerAddress: options.wallet.ownerAddress
|
|
2823
|
+
};
|
|
2824
|
+
const capabilities = options;
|
|
2825
|
+
if (inputs.some((input) => input.note.tokenAddress !== BigInt(getAddress(tokenAddress))))
|
|
2826
|
+
throw new Error("explicit note token mismatch");
|
|
2827
|
+
const runtime = {
|
|
2828
|
+
options: { ...capabilities, protocol: options.protocol.forToken(tokenAddress) },
|
|
2829
|
+
reader: options.chainReader,
|
|
2830
|
+
scope
|
|
2831
|
+
};
|
|
2832
|
+
const reprepare = (intent) => import("./broadcaster-preparation-RMWQFYNL.js").then(
|
|
2833
|
+
({ prepareIntent }) => prepareIntent(
|
|
2834
|
+
{
|
|
2835
|
+
...runtime,
|
|
2836
|
+
explicitInputs: true,
|
|
2837
|
+
...data.changeAddress ? { changeAddress: data.changeAddress } : {},
|
|
2838
|
+
readInventory: async () => structuredClone(inventory),
|
|
2839
|
+
complete: async (plan) => plan
|
|
2840
|
+
},
|
|
2841
|
+
intent
|
|
2842
|
+
)
|
|
2843
|
+
);
|
|
2844
|
+
return new TransactionPreparation({
|
|
2845
|
+
...runtime,
|
|
2846
|
+
explicitInputs: true,
|
|
2847
|
+
...data.changeAddress ? { changeAddress: data.changeAddress } : {},
|
|
2848
|
+
readInventory: async () => structuredClone(inventory),
|
|
2849
|
+
complete: async (plan) => PreparedTransaction.prepare({ ...runtime, reprepare }, plan)
|
|
2850
|
+
});
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
// src/client/Invisi.ts
|
|
2854
|
+
var Invisi = class {
|
|
2855
|
+
#options;
|
|
2856
|
+
constructor(options = {}) {
|
|
2857
|
+
if ("addressLookup" in options || "addressResolver" in options)
|
|
2858
|
+
throw new TypeError(
|
|
2859
|
+
"Recipient lookup belongs to each EVM output; caller resolver injection is not supported"
|
|
2860
|
+
);
|
|
2861
|
+
if ("ciphertextCodecs" in options || "envelopeVersion" in options) {
|
|
2862
|
+
throw new Error(
|
|
2863
|
+
"SDK envelope versions are fixed; encode application payload bytes externally"
|
|
2864
|
+
);
|
|
2865
|
+
}
|
|
2866
|
+
if ("signer" in options || "fundingSigner" in options) {
|
|
2867
|
+
throw new Error(
|
|
2868
|
+
"Invisi does not accept signer defaults; export a signing request and attach the external signature"
|
|
2869
|
+
);
|
|
2870
|
+
}
|
|
2871
|
+
for (const key of [
|
|
2872
|
+
"store",
|
|
2873
|
+
"viewing",
|
|
2874
|
+
"authorization",
|
|
2875
|
+
"namespace",
|
|
2876
|
+
"coinSelector",
|
|
2877
|
+
"wallet",
|
|
2878
|
+
"protocol"
|
|
2879
|
+
]) {
|
|
2880
|
+
if (key in options)
|
|
2881
|
+
throw new TypeError(`Wallet state and permissions belong to WalletSession: ${key}`);
|
|
2882
|
+
}
|
|
2883
|
+
this.#options = {
|
|
2884
|
+
...options,
|
|
2885
|
+
...options.gasBenchmarks ? { gasBenchmarks: copyGasBenchmarks(options.gasBenchmarks) } : {}
|
|
2886
|
+
};
|
|
2887
|
+
Object.freeze(this);
|
|
2888
|
+
}
|
|
2889
|
+
/** A detached snapshot of shared infrastructure; wallet permissions and persistence stay external. */
|
|
2890
|
+
getInfrastructure() {
|
|
2891
|
+
return Object.freeze({
|
|
2892
|
+
...this.#options,
|
|
2893
|
+
...this.#options.gasBenchmarks ? { gasBenchmarks: copyGasBenchmarks(this.#options.gasBenchmarks) } : {}
|
|
2894
|
+
});
|
|
3616
2895
|
}
|
|
3617
|
-
async
|
|
3618
|
-
const
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
p.kind === "shield" ? ["prepared"] : ["proved"],
|
|
3632
|
-
async (saved) => {
|
|
3633
|
-
saved.plan.funding.signature = await signOperationFunding(
|
|
3634
|
-
saved,
|
|
3635
|
-
signer,
|
|
3636
|
-
this.#runtime.options.fundingVerifier
|
|
3637
|
-
);
|
|
3638
|
-
if (saved.plan.statement?.target !== "noir-recursive") {
|
|
3639
|
-
saved.call = await this.encode(saved);
|
|
2896
|
+
async resolve(options) {
|
|
2897
|
+
const runtime = configureTransactionInfrastructure({
|
|
2898
|
+
...this.getInfrastructure(),
|
|
2899
|
+
...options
|
|
2900
|
+
});
|
|
2901
|
+
await validateTransactionInfrastructure(runtime);
|
|
2902
|
+
return runtime;
|
|
2903
|
+
}
|
|
2904
|
+
/** Discover the selected chain's registry and prepare an independent signing request. */
|
|
2905
|
+
async prepareRegistration(options) {
|
|
2906
|
+
return prepareWithErrors("prepareRegistration", async () => {
|
|
2907
|
+
for (const key of Object.keys(options)) {
|
|
2908
|
+
if (!["wallet", "update", "deadline"].includes(key)) {
|
|
2909
|
+
throw new Error(`Unknown registration option: ${key}`);
|
|
3640
2910
|
}
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
options
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
2911
|
+
}
|
|
2912
|
+
this.assertPublicIdentity(options);
|
|
2913
|
+
const selected = { ...options };
|
|
2914
|
+
const { provider, protocolRpc } = this.#options;
|
|
2915
|
+
if (!provider) throw new MissingCapabilityError3("ethers provider");
|
|
2916
|
+
if (!(protocolRpc instanceof ProtocolRpcClient2)) {
|
|
2917
|
+
throw new MissingCapabilityError3("Protocol RPC deployment discovery");
|
|
2918
|
+
}
|
|
2919
|
+
const chainId = (await provider.getNetwork()).chainId;
|
|
2920
|
+
const deployment = await protocolRpc.getDeployment(chainId);
|
|
2921
|
+
if (!deployment.registry) {
|
|
2922
|
+
throw new MissingCapabilityError3("receiving-key registry deployment");
|
|
2923
|
+
}
|
|
2924
|
+
const registry = new AddressRegistry(
|
|
2925
|
+
{ chainId, registryAddress: deployment.registry.address },
|
|
2926
|
+
{ expectedRuntimeCodeHash: deployment.registry.runtimeCodeHash }
|
|
2927
|
+
);
|
|
2928
|
+
return registry.prepareRegistration({ ...selected, provider });
|
|
3655
2929
|
});
|
|
3656
2930
|
}
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
const plan = this.#saved.plan;
|
|
3663
|
-
check5(!operationExpired(plan, head.timestamp), "operation has expired");
|
|
3664
|
-
if (plan.fundingMode === "signature") {
|
|
3665
|
-
check5(head.timestamp <= (plan.funding.deadline ?? 0n), "funding authorization expired");
|
|
3666
|
-
check5(
|
|
3667
|
-
await reader.getFundingNonce(plan.context, plan.funding.payer, head) === plan.funding.sigNonce,
|
|
3668
|
-
"funding nonce changed; prepare again"
|
|
3669
|
-
);
|
|
3670
|
-
}
|
|
3671
|
-
for (const source of operationSources(plan)) {
|
|
3672
|
-
check5(
|
|
3673
|
-
await reader.isKnownRoot(plan.context, source.treeId, source.merkleRoot, head),
|
|
3674
|
-
"source root is no longer accepted; prepare again"
|
|
2931
|
+
assertPublicIdentity(options) {
|
|
2932
|
+
const { wallet } = options;
|
|
2933
|
+
if (!(wallet instanceof PublicWallet) || ["getViewer", "getDetector", "exportAuthorization"].some((key) => key in wallet)) {
|
|
2934
|
+
throw new Error(
|
|
2935
|
+
"Independent preparation requires a PublicWallet without private capabilities"
|
|
3675
2936
|
);
|
|
3676
2937
|
}
|
|
3677
|
-
const
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
check5(
|
|
3681
|
-
spent.length === nullifiers.length && spent.every((value) => value === false),
|
|
3682
|
-
"input spent or invalid spent response"
|
|
3683
|
-
);
|
|
2938
|
+
for (const key of ["authorization", "signer", "fundingSigner", "viewing"]) {
|
|
2939
|
+
if (key in options)
|
|
2940
|
+
throw new Error(`Independent preparation does not accept capability: ${key}`);
|
|
3684
2941
|
}
|
|
3685
|
-
signal?.throwIfAborted();
|
|
3686
2942
|
}
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
const
|
|
3696
|
-
const
|
|
3697
|
-
|
|
3698
|
-
saved.externalized = true;
|
|
3699
|
-
return structuredClone(saved.call);
|
|
2943
|
+
resolveExplicit(options) {
|
|
2944
|
+
this.assertPublicIdentity(options);
|
|
2945
|
+
return this.resolve({ wallet: options.wallet, protocol: options.protocol });
|
|
2946
|
+
}
|
|
2947
|
+
async prepareTransfer(options) {
|
|
2948
|
+
return prepareWithErrors("prepareTransfer", async () => {
|
|
2949
|
+
const { wallet, protocol, ...input } = options;
|
|
2950
|
+
const request = structuredClone(input);
|
|
2951
|
+
const runtime = await this.resolveExplicit(options);
|
|
2952
|
+
const preparation = explicitPreparation(runtime, request, request.tokenAddress);
|
|
2953
|
+
return preparation.prepareTransfer(request);
|
|
3700
2954
|
});
|
|
3701
|
-
return result;
|
|
3702
2955
|
}
|
|
3703
|
-
async
|
|
3704
|
-
return
|
|
3705
|
-
const
|
|
3706
|
-
const
|
|
3707
|
-
const
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
"operation is not a ready recursive slots14 aggregate member"
|
|
3711
|
-
);
|
|
3712
|
-
sameStatement(saved.proof, statement(plan));
|
|
3713
|
-
check5(
|
|
3714
|
-
saved.proof.proofHex.length === 2 + 458 * 64,
|
|
3715
|
-
"recursive member proof must have 458 fields"
|
|
3716
|
-
);
|
|
3717
|
-
saved.externalized = true;
|
|
3718
|
-
return {
|
|
3719
|
-
bundle: publicProof(saved.proof),
|
|
3720
|
-
statement: structuredClone(plan.statement),
|
|
3721
|
-
context: { ...plan.context },
|
|
3722
|
-
request: {
|
|
3723
|
-
params: structuredClone(plan.params),
|
|
3724
|
-
deliveries: plan.outputs.map((output) => structuredClone(output.delivery)),
|
|
3725
|
-
funding: normalizeFunding2(plan.funding)
|
|
3726
|
-
}
|
|
3727
|
-
};
|
|
2956
|
+
async prepareShield(options) {
|
|
2957
|
+
return prepareWithErrors("prepareShield", async () => {
|
|
2958
|
+
const { wallet, protocol, ...input } = options;
|
|
2959
|
+
const request = structuredClone(input);
|
|
2960
|
+
const runtime = await this.resolveExplicit(options);
|
|
2961
|
+
const preparation = explicitPreparation(runtime, {}, request.tokenAddress);
|
|
2962
|
+
return preparation.prepareShield(request);
|
|
3728
2963
|
});
|
|
3729
2964
|
}
|
|
3730
|
-
async
|
|
3731
|
-
|
|
3732
|
-
const
|
|
3733
|
-
const
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
);
|
|
3738
|
-
check5(
|
|
3739
|
-
!["confirmed", "reorged"].includes(record.status),
|
|
3740
|
-
"operation requires chain reconciliation"
|
|
3741
|
-
);
|
|
3742
|
-
for (const id of record.inputs) {
|
|
3743
|
-
if (state.reservations[id] === this.id) {
|
|
3744
|
-
delete state.reservations[id];
|
|
3745
|
-
}
|
|
3746
|
-
}
|
|
3747
|
-
record.status = "failed";
|
|
3748
|
-
current2.generation++;
|
|
3749
|
-
return structuredClone(current2);
|
|
2965
|
+
async prepareCreateCredit(options) {
|
|
2966
|
+
return prepareWithErrors("prepareCreateCredit", async () => {
|
|
2967
|
+
const { wallet, protocol, ...input } = options;
|
|
2968
|
+
const request = structuredClone(input);
|
|
2969
|
+
const runtime = await this.resolveExplicit(options);
|
|
2970
|
+
const preparation = explicitPreparation(runtime, request, request.tokenAddress);
|
|
2971
|
+
return preparation.prepareCreateCredit(request);
|
|
3750
2972
|
});
|
|
3751
|
-
return new _Operation(this.#runtime, saved, "failed");
|
|
3752
2973
|
}
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
const
|
|
3759
|
-
|
|
3760
|
-
check5(
|
|
3761
|
-
["ready", "uncertain", "submitted"].includes(record.status),
|
|
3762
|
-
"operation is not ready"
|
|
3763
|
-
);
|
|
3764
|
-
check5(
|
|
3765
|
-
current2.externalized,
|
|
3766
|
-
"export the transaction or aggregate member before tracking"
|
|
3767
|
-
);
|
|
3768
|
-
for (const input of record.inputs) {
|
|
3769
|
-
check5(state.reservations[input] === this.id, "operation input reservation lost");
|
|
3770
|
-
}
|
|
3771
|
-
current2.generation++;
|
|
3772
|
-
current2.externalized = true;
|
|
3773
|
-
record.transactionHash = transactionHash;
|
|
3774
|
-
record.status = "uncertain";
|
|
3775
|
-
return structuredClone(current2);
|
|
2974
|
+
async prepareDrawCredit(options) {
|
|
2975
|
+
return prepareWithErrors("prepareDrawCredit", async () => {
|
|
2976
|
+
const { wallet, protocol, ...input } = options;
|
|
2977
|
+
const request = structuredClone(input);
|
|
2978
|
+
const runtime = await this.resolveExplicit(options);
|
|
2979
|
+
const preparation = explicitPreparation(runtime, request, request.tokenAddress);
|
|
2980
|
+
return preparation.prepareDrawCredit(request);
|
|
3776
2981
|
});
|
|
3777
|
-
return new _Operation(this.#runtime, saved, "uncertain");
|
|
3778
2982
|
}
|
|
3779
|
-
async
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
);
|
|
3795
|
-
}
|
|
3796
|
-
outcome ??= await reconcileOperation(
|
|
3797
|
-
this.#runtime,
|
|
3798
|
-
this.id,
|
|
3799
|
-
this.#saved.generation,
|
|
3800
|
-
options.signal
|
|
3801
|
-
);
|
|
3802
|
-
return new _Operation(this.#runtime, outcome.saved, outcome.status);
|
|
2983
|
+
async prepareTopUpCredit(options) {
|
|
2984
|
+
return prepareWithErrors("prepareTopUpCredit", async () => {
|
|
2985
|
+
const { wallet, protocol, ...input } = options;
|
|
2986
|
+
const request = structuredClone(input);
|
|
2987
|
+
const runtime = await this.resolveExplicit(options);
|
|
2988
|
+
const preparation = explicitPreparation(runtime, request, request.tokenAddress);
|
|
2989
|
+
return preparation.prepareTopUpCredit(request);
|
|
2990
|
+
});
|
|
2991
|
+
}
|
|
2992
|
+
async prepareCancelCredit(options) {
|
|
2993
|
+
return prepareWithErrors("prepareCancelCredit", async () => {
|
|
2994
|
+
const { wallet, protocol, ...input } = options;
|
|
2995
|
+
const request = structuredClone(input);
|
|
2996
|
+
const runtime = await this.resolveExplicit(options);
|
|
2997
|
+
const preparation = explicitPreparation(runtime, request, request.tokenAddress);
|
|
2998
|
+
return preparation.prepareCancelCredit(request);
|
|
2999
|
+
});
|
|
3803
3000
|
}
|
|
3804
3001
|
toJSON() {
|
|
3805
|
-
return {
|
|
3002
|
+
return { invisi: true };
|
|
3806
3003
|
}
|
|
3807
3004
|
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
|
3808
3005
|
return this.toJSON();
|
|
3809
3006
|
}
|
|
3810
3007
|
};
|
|
3811
3008
|
|
|
3812
|
-
// src/
|
|
3813
|
-
import {
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
function
|
|
3817
|
-
const
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
}))
|
|
3836
|
-
});
|
|
3009
|
+
// src/discovery/protocol-events.ts
|
|
3010
|
+
import { invisiProtocolAbi } from "@invisi-labs/protocol/contracts";
|
|
3011
|
+
import { Interface } from "ethers";
|
|
3012
|
+
var protocolInterface = new Interface(invisiProtocolAbi);
|
|
3013
|
+
function decodeProtocolEvent(log) {
|
|
3014
|
+
const decoded = protocolInterface.parseLog(log);
|
|
3015
|
+
if (!decoded) throw new Error("Unknown protocol receipt event");
|
|
3016
|
+
const encoded = protocolInterface.encodeEventLog(decoded.fragment, decoded.args);
|
|
3017
|
+
if (encoded.data.toLowerCase() !== log.data.toLowerCase() || encoded.topics.length !== log.topics.length || encoded.topics.some(
|
|
3018
|
+
(topic, index2) => topic.toLowerCase() !== log.topics[index2].toLowerCase()
|
|
3019
|
+
))
|
|
3020
|
+
throw new Error("Noncanonical protocol receipt event");
|
|
3021
|
+
const args = decoded.args.toObject(true);
|
|
3022
|
+
for (const key of [
|
|
3023
|
+
"tokens",
|
|
3024
|
+
"commitments",
|
|
3025
|
+
"nullifiers",
|
|
3026
|
+
"outputCommitments",
|
|
3027
|
+
"leafIndices",
|
|
3028
|
+
"remainderLeafIndices"
|
|
3029
|
+
]) {
|
|
3030
|
+
const values = decoded.args.getValue(key);
|
|
3031
|
+
if (values) args[key] = values.toArray(true);
|
|
3837
3032
|
}
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
const incoming = [];
|
|
3842
|
-
for (const record of Object.values(state.notes)) {
|
|
3843
|
-
const tokenAddress = `0x${record.note.tokenAddress.toString(16).padStart(40, "0")}`;
|
|
3844
|
-
if (token && tokenAddress !== token) continue;
|
|
3845
|
-
incoming.push({
|
|
3846
|
-
commitment: record.commitment,
|
|
3847
|
-
treeId: record.treeId,
|
|
3848
|
-
leafIndex: record.leafIndex,
|
|
3849
|
-
blockNumber: record.blockNumber,
|
|
3850
|
-
blockHash: record.blockHash,
|
|
3851
|
-
...record.transactionHash ? { transactionHash: record.transactionHash } : {},
|
|
3852
|
-
tokenAddress,
|
|
3853
|
-
amount: record.note.amount,
|
|
3854
|
-
payload: Uint8Array.from(record.note.payload ?? []),
|
|
3855
|
-
spent: record.spent
|
|
3856
|
-
});
|
|
3033
|
+
for (const key of ["sources", "deliveries"]) {
|
|
3034
|
+
const values = decoded.args.getValue(key);
|
|
3035
|
+
if (values) args[key] = values.map((value) => value.toObject(true));
|
|
3857
3036
|
}
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
outgoing,
|
|
3866
|
-
incoming
|
|
3867
|
-
});
|
|
3868
|
-
}
|
|
3869
|
-
|
|
3870
|
-
// src/session/WalletSession.ts
|
|
3871
|
-
import { getAddress } from "ethers";
|
|
3872
|
-
import { MissingCapabilityError as MissingCapabilityError2 } from "@invisi-labs/core";
|
|
3873
|
-
|
|
3874
|
-
// src/session/refresh.ts
|
|
3875
|
-
import { MAX_UPDATE_BLOCKS } from "@invisi-labs/rpc";
|
|
3876
|
-
async function refreshWallet(runtime, options) {
|
|
3877
|
-
const { signal } = options;
|
|
3878
|
-
try {
|
|
3879
|
-
signal?.throwIfAborted();
|
|
3880
|
-
if (typeof options.fromBlock !== "bigint" || options.fromBlock < runtime.fromBlock)
|
|
3881
|
-
throw new Error("refresh range precedes deployment");
|
|
3882
|
-
const pageSize = options.pageSize ?? MAX_UPDATE_BLOCKS;
|
|
3883
|
-
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > MAX_UPDATE_BLOCKS)
|
|
3884
|
-
throw new Error(`refresh page size must be 1..${MAX_UPDATE_BLOCKS}`);
|
|
3885
|
-
const status = await runtime.provider.getStatus(runtime.context, {
|
|
3886
|
-
...signal ? { signal } : {}
|
|
3887
|
-
});
|
|
3888
|
-
if (!status.indexedHead) throw new Error("Protocol RPC has not indexed any blocks");
|
|
3889
|
-
const toBlock = options.toBlock ?? status.indexedHead.number;
|
|
3890
|
-
if (typeof toBlock !== "bigint" || toBlock < options.fromBlock || toBlock > status.indexedHead.number)
|
|
3891
|
-
throw new Error("refresh range is outside indexed history");
|
|
3892
|
-
const target = await runtime.reader.getBlock(toBlock);
|
|
3893
|
-
if (target.number !== toBlock)
|
|
3894
|
-
throw new Error("Chain RPC returned the wrong refresh anchor");
|
|
3895
|
-
let result;
|
|
3896
|
-
let notes = 0;
|
|
3897
|
-
let credits = 0;
|
|
3898
|
-
for (let from = options.fromBlock; from <= toBlock; from += BigInt(pageSize)) {
|
|
3899
|
-
signal?.throwIfAborted();
|
|
3900
|
-
const end = from + BigInt(pageSize) - 1n;
|
|
3901
|
-
const page = await runtime.provider.getUpdates(runtime.context, {
|
|
3902
|
-
fromBlock: from,
|
|
3903
|
-
toBlock: end < toBlock ? end : toBlock,
|
|
3904
|
-
...options.detectionKey ? { detectionKey: options.detectionKey } : {},
|
|
3905
|
-
...signal ? { signal } : {}
|
|
3906
|
-
});
|
|
3907
|
-
if (page.block.number !== (end < toBlock ? end : toBlock))
|
|
3908
|
-
throw new Error("Protocol RPC returned the wrong page anchor");
|
|
3909
|
-
signal?.throwIfAborted();
|
|
3910
|
-
if (page.transactionHashes.length === 0 && (result || from + BigInt(pageSize) <= toBlock))
|
|
3911
|
-
continue;
|
|
3912
|
-
const firstApplication = result === void 0;
|
|
3913
|
-
result = await runtime.apply(
|
|
3914
|
-
{
|
|
3915
|
-
context: runtime.context,
|
|
3916
|
-
transactionHashes: page.transactionHashes,
|
|
3917
|
-
block: target
|
|
3918
|
-
},
|
|
3919
|
-
{
|
|
3920
|
-
...signal ? { signal } : {},
|
|
3921
|
-
...firstApplication && options.reset ? { reset: true } : {}
|
|
3922
|
-
}
|
|
3923
|
-
);
|
|
3924
|
-
notes += result.notes;
|
|
3925
|
-
credits += result.credits;
|
|
3037
|
+
const params = decoded.args.getValue("params");
|
|
3038
|
+
if (params) {
|
|
3039
|
+
const source = params.getValue("creation") ?? params;
|
|
3040
|
+
const target = params.getValue("creation") ? args.params.creation : args.params;
|
|
3041
|
+
for (const key of ["nullifiers", "remainderCommitments", "expiries", "recoveryAmounts"]) {
|
|
3042
|
+
const values = source.getValue(key);
|
|
3043
|
+
if (values) target[key] = values.toArray(true);
|
|
3926
3044
|
}
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
options.detectionKey?.fill(0);
|
|
3045
|
+
const sources = source.getValue("sources");
|
|
3046
|
+
if (sources) target.sources = sources.map((value) => value.toObject(true));
|
|
3930
3047
|
}
|
|
3048
|
+
return { eventName: decoded.name, args };
|
|
3931
3049
|
}
|
|
3932
3050
|
|
|
3933
|
-
// src/
|
|
3934
|
-
|
|
3051
|
+
// src/discovery/receipt-logs.ts
|
|
3052
|
+
function readProtocolLogs(receipt, protocolAddress) {
|
|
3053
|
+
if (receipt.status !== "success" || !/^0x[0-9a-fA-F]{64}$/.test(receipt.transactionHash) || typeof receipt.block.number !== "bigint" || receipt.block.number < 0n || !/^0x[0-9a-fA-F]{64}$/.test(receipt.block.hash))
|
|
3054
|
+
throw new Error("Invalid canonical protocol receipt");
|
|
3055
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3056
|
+
return receipt.logs.filter((log) => log.address.toLowerCase() === protocolAddress.toLowerCase()).map((log) => {
|
|
3057
|
+
if (log.transactionHash.toLowerCase() !== receipt.transactionHash.toLowerCase() || !Number.isSafeInteger(log.logIndex) || log.logIndex < 0 || seen.has(log.logIndex))
|
|
3058
|
+
throw new Error("Invalid canonical receipt log identity");
|
|
3059
|
+
seen.add(log.logIndex);
|
|
3060
|
+
const decoded = decodeProtocolEvent(log);
|
|
3061
|
+
return {
|
|
3062
|
+
address: log.address,
|
|
3063
|
+
eventName: decoded.eventName,
|
|
3064
|
+
args: decoded.args,
|
|
3065
|
+
blockNumber: receipt.block.number,
|
|
3066
|
+
blockHash: receipt.block.hash,
|
|
3067
|
+
transactionHash: receipt.transactionHash.toLowerCase(),
|
|
3068
|
+
logIndex: log.logIndex,
|
|
3069
|
+
removed: false
|
|
3070
|
+
};
|
|
3071
|
+
}).sort((a, b) => a.logIndex - b.logIndex);
|
|
3072
|
+
}
|
|
3935
3073
|
|
|
3936
|
-
// src/
|
|
3074
|
+
// src/discovery/discovery.ts
|
|
3075
|
+
import { MAX_CIPHERTEXT_BYTES } from "@invisi-labs/core";
|
|
3076
|
+
import { isAddress } from "ethers";
|
|
3937
3077
|
import {
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
computeCreditSharedKeyHash,
|
|
3941
|
-
recoverCreditRemainder
|
|
3078
|
+
decodeAddress,
|
|
3079
|
+
ReceivingAddress
|
|
3942
3080
|
} from "@invisi-labs/core";
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3081
|
+
import { normalizeOpaqueDelivery } from "@invisi-labs/protocol";
|
|
3082
|
+
var EVENTS = /* @__PURE__ */ new Set([
|
|
3083
|
+
"CommitmentsInserted",
|
|
3084
|
+
"BatchOutputs",
|
|
3085
|
+
"Shield",
|
|
3086
|
+
"Transact",
|
|
3087
|
+
"CrossTreeTransact",
|
|
3088
|
+
"CreditCreated",
|
|
3089
|
+
"CreditDraw",
|
|
3090
|
+
"CreditToppedUp",
|
|
3091
|
+
"CreditCancelled"
|
|
3092
|
+
]);
|
|
3093
|
+
function check3(ok, message) {
|
|
3094
|
+
if (!ok) throw new Error(message);
|
|
3951
3095
|
}
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
n.ownerPayout,
|
|
4003
|
-
n.tokenAddress,
|
|
4004
|
-
n.amount,
|
|
4005
|
-
n.nonce,
|
|
4006
|
-
n.expiry,
|
|
4007
|
-
await computeCreditSharedKeyHash(n.creditSharedKey),
|
|
4008
|
-
n
|
|
4009
|
-
)
|
|
4010
|
-
);
|
|
4011
|
-
}
|
|
4012
|
-
async recoverCredits(base, found, logs, snapshot) {
|
|
4013
|
-
const known = /* @__PURE__ */ new Map();
|
|
4014
|
-
const byNullifier = /* @__PURE__ */ new Map();
|
|
4015
|
-
const cachedNullifiers = /* @__PURE__ */ new Map();
|
|
4016
|
-
const recovered = [];
|
|
4017
|
-
const foundByKey = new Map(
|
|
4018
|
-
found.map((record) => [record.commitment + ":" + record.role, record])
|
|
3096
|
+
function object(value) {
|
|
3097
|
+
check3(value && typeof value === "object" && !Array.isArray(value), "invalid event object");
|
|
3098
|
+
return value;
|
|
3099
|
+
}
|
|
3100
|
+
function array(value) {
|
|
3101
|
+
check3(Array.isArray(value), "invalid event array");
|
|
3102
|
+
check3(
|
|
3103
|
+
Array.from({ length: value.length }, (_, i) => i in value).every(Boolean),
|
|
3104
|
+
"sparse event array"
|
|
3105
|
+
);
|
|
3106
|
+
return value;
|
|
3107
|
+
}
|
|
3108
|
+
function integer(value) {
|
|
3109
|
+
check3(
|
|
3110
|
+
typeof value === "bigint" || typeof value === "number" && Number.isSafeInteger(value),
|
|
3111
|
+
"invalid event integer"
|
|
3112
|
+
);
|
|
3113
|
+
const n = BigInt(value);
|
|
3114
|
+
check3(n >= 0n, "negative event integer");
|
|
3115
|
+
return n;
|
|
3116
|
+
}
|
|
3117
|
+
function index(value) {
|
|
3118
|
+
const n = integer(value);
|
|
3119
|
+
check3(n < 65536n, "invalid event leaf index");
|
|
3120
|
+
return Number(n);
|
|
3121
|
+
}
|
|
3122
|
+
function hash(value) {
|
|
3123
|
+
check3(typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value), "invalid event hash");
|
|
3124
|
+
return value.toLowerCase();
|
|
3125
|
+
}
|
|
3126
|
+
function contextCheck(context) {
|
|
3127
|
+
check3(context.chainId >= 0n && context.chainId < 1n << 64n, "invalid discovery chain");
|
|
3128
|
+
check3(
|
|
3129
|
+
isAddress(context.protocolAddress) && BigInt(context.protocolAddress) !== 0n,
|
|
3130
|
+
"invalid discovery instance/token"
|
|
3131
|
+
);
|
|
3132
|
+
}
|
|
3133
|
+
var position = (tree, at) => `${tree}:${at}`;
|
|
3134
|
+
function extractDeliveryCandidates(logs, context, confirmedThrough) {
|
|
3135
|
+
contextCheck(context);
|
|
3136
|
+
check3(confirmedThrough >= 0n, "invalid confirmed height");
|
|
3137
|
+
const relevant = logs.filter(
|
|
3138
|
+
(log) => log.address.toLowerCase() === context.protocolAddress.toLowerCase() && EVENTS.has(log.eventName)
|
|
3139
|
+
);
|
|
3140
|
+
const insertions = /* @__PURE__ */ new Map();
|
|
3141
|
+
const metadata = (log) => {
|
|
3142
|
+
check3(!log.removed, "discovery resync required after reorg");
|
|
3143
|
+
check3(
|
|
3144
|
+
typeof log.blockNumber === "bigint" && log.blockNumber >= 0n && log.blockNumber <= confirmedThrough,
|
|
3145
|
+
"event is not confirmed"
|
|
4019
3146
|
);
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
cachedNullifiers.set(record.commitment, nullifier);
|
|
4026
|
-
}
|
|
4027
|
-
known.set(key, record);
|
|
4028
|
-
let members = byNullifier.get(nullifier);
|
|
4029
|
-
if (!members) {
|
|
4030
|
-
members = /* @__PURE__ */ new Set();
|
|
4031
|
-
byNullifier.set(nullifier, members);
|
|
4032
|
-
}
|
|
4033
|
-
members.add(key);
|
|
4034
|
-
};
|
|
4035
|
-
const matching = (nullifier) => [...byNullifier.get(nullifier) ?? []].map((key) => known.get(key));
|
|
4036
|
-
for (const record of Object.values(base.credits)) {
|
|
4037
|
-
await remember(record);
|
|
4038
|
-
}
|
|
4039
|
-
for (const record of found) {
|
|
4040
|
-
try {
|
|
4041
|
-
this.checkSnapshotKeys(record);
|
|
4042
|
-
await remember({ ...record, spent: "unknown" });
|
|
4043
|
-
} catch (error) {
|
|
4044
|
-
if (!(error instanceof CreditMetadataMismatch)) {
|
|
4045
|
-
throw error;
|
|
4046
|
-
}
|
|
4047
|
-
}
|
|
4048
|
-
}
|
|
4049
|
-
const context = {
|
|
4050
|
-
chainId: this.#options.scope.chainId,
|
|
4051
|
-
protocolAddress: BigInt(this.#options.scope.protocolAddress)
|
|
3147
|
+
check3(Number.isSafeInteger(log.logIndex) && log.logIndex >= 0, "invalid log position");
|
|
3148
|
+
return {
|
|
3149
|
+
blockNumber: log.blockNumber,
|
|
3150
|
+
blockHash: hash(log.blockHash),
|
|
3151
|
+
transactionHash: hash(log.transactionHash)
|
|
4052
3152
|
};
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
for (const [index2, nullifier] of params.nullifiers.entries()) {
|
|
4066
|
-
for (const previous of matching(nullifier)) {
|
|
4067
|
-
check6(
|
|
4068
|
-
previous.state.note.expiry === params.expiries[index2],
|
|
4069
|
-
"draw recovery expiry mismatch"
|
|
4070
|
-
);
|
|
4071
|
-
const remainder = await recoverCreditRemainder(
|
|
4072
|
-
context,
|
|
4073
|
-
previous.state.note,
|
|
4074
|
-
{
|
|
4075
|
-
timestamp: block.timestamp,
|
|
4076
|
-
creditNullifier: nullifier,
|
|
4077
|
-
remainderCommitment: params.remainderCommitments[index2],
|
|
4078
|
-
recoveryAmount: BigInt(params.recoveryAmounts[index2])
|
|
4079
|
-
}
|
|
4080
|
-
);
|
|
4081
|
-
if (!remainder) {
|
|
4082
|
-
continue;
|
|
4083
|
-
}
|
|
4084
|
-
const child = foundByKey.get(
|
|
4085
|
-
params.remainderCommitments[index2] + ":" + previous.role
|
|
4086
|
-
);
|
|
4087
|
-
const payload = child?.state.note.payload;
|
|
4088
|
-
const record = {
|
|
4089
|
-
commitment: params.remainderCommitments[index2],
|
|
4090
|
-
treeId: BigInt(args.outputTreeId),
|
|
4091
|
-
leafIndex: Number(indices[index2]),
|
|
4092
|
-
blockNumber: event.blockNumber,
|
|
4093
|
-
blockHash: event.blockHash,
|
|
4094
|
-
transactionHash: event.transactionHash,
|
|
4095
|
-
role: previous.role,
|
|
4096
|
-
state: {
|
|
4097
|
-
...clone(previous.state),
|
|
4098
|
-
note: {
|
|
4099
|
-
...remainder,
|
|
4100
|
-
...payload === void 0 ? {} : { payload: clone(payload) }
|
|
4101
|
-
}
|
|
4102
|
-
},
|
|
4103
|
-
receivingKeySource: { kind: "draw", commitment: previous.commitment },
|
|
4104
|
-
spent: "unknown"
|
|
4105
|
-
};
|
|
4106
|
-
check6(
|
|
4107
|
-
await snapshot.getLeaf(record.treeId, record.leafIndex) === BigInt(record.commitment),
|
|
4108
|
-
"draw recovery canonical leaf mismatch"
|
|
4109
|
-
);
|
|
4110
|
-
this.checkSnapshotKeys(record);
|
|
4111
|
-
await remember(record);
|
|
4112
|
-
recovered.push(record);
|
|
4113
|
-
}
|
|
4114
|
-
}
|
|
4115
|
-
} else if (event.eventName === "CreditToppedUp") {
|
|
4116
|
-
const params = event.args.params;
|
|
4117
|
-
for (const previous of matching(params.creditNullifier)) {
|
|
4118
|
-
const child = foundByKey.get(
|
|
4119
|
-
params.creation.creditCommitment + ":" + previous.role
|
|
4120
|
-
);
|
|
4121
|
-
if (!child) {
|
|
4122
|
-
continue;
|
|
4123
|
-
}
|
|
4124
|
-
const old = previous.state.note;
|
|
4125
|
-
const next = child.state.note;
|
|
4126
|
-
if ([
|
|
4127
|
-
"chainId",
|
|
4128
|
-
"protocolAddress",
|
|
4129
|
-
"tokenAddress",
|
|
4130
|
-
"spenderWalletIdentity",
|
|
4131
|
-
"ownerWalletIdentity",
|
|
4132
|
-
"spenderPayout",
|
|
4133
|
-
"ownerPayout",
|
|
4134
|
-
"creditSharedKey"
|
|
4135
|
-
].some(
|
|
4136
|
-
(field) => old[field] !== next[field]
|
|
4137
|
-
) || next.amount <= old.amount || next.expiry < old.expiry) {
|
|
4138
|
-
continue;
|
|
4139
|
-
}
|
|
4140
|
-
const repaired = {
|
|
4141
|
-
...clone(child),
|
|
4142
|
-
state: {
|
|
4143
|
-
note: clone(next),
|
|
4144
|
-
ownerReceivingKeys: clone(previous.state.ownerReceivingKeys),
|
|
4145
|
-
spenderReceivingKeys: clone(previous.state.spenderReceivingKeys)
|
|
4146
|
-
},
|
|
4147
|
-
receivingKeySource: { kind: "top-up", commitment: previous.commitment }
|
|
4148
|
-
};
|
|
4149
|
-
this.checkSnapshotKeys(repaired);
|
|
4150
|
-
await remember(repaired);
|
|
4151
|
-
recovered.push(repaired);
|
|
4152
|
-
}
|
|
4153
|
-
}
|
|
3153
|
+
};
|
|
3154
|
+
const insertionKey = (log, tree, at) => `${hash(log.transactionHash)}:${position(tree, at)}`;
|
|
3155
|
+
for (const log of relevant) {
|
|
3156
|
+
metadata(log);
|
|
3157
|
+
if (log.eventName !== "CommitmentsInserted" && log.eventName !== "BatchOutputs") continue;
|
|
3158
|
+
const a = object(log.args), tree = integer(a.treeId), start = index(a.startLeafIndex), leaves = array(a.commitments);
|
|
3159
|
+
check3(tree > 0n && tree < 1n << 64n, "invalid insertion tree");
|
|
3160
|
+
check3(start + leaves.length <= 65536, "insertion exceeds tree capacity");
|
|
3161
|
+
for (const [i, leaf] of leaves.entries()) {
|
|
3162
|
+
const key = insertionKey(log, tree, start + i), value = hash(leaf), previous = insertions.get(key);
|
|
3163
|
+
check3(!previous || previous === value, "conflicting insertion events");
|
|
3164
|
+
insertions.set(key, value);
|
|
4154
3165
|
}
|
|
4155
|
-
return recovered;
|
|
4156
3166
|
}
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
this.#options = { ...options };
|
|
4169
|
-
check7(
|
|
4170
|
-
options.viewing.ownerAddress === options.scope.ownerAddress,
|
|
4171
|
-
"viewing capability wallet mismatch"
|
|
3167
|
+
const results = /* @__PURE__ */ new Map();
|
|
3168
|
+
const add = (log, tree, at, commitment, role, wire, token = log.args?.token ?? log.args?.params?.token ?? log.args?.params?.creation?.token) => {
|
|
3169
|
+
check3(tree > 0n && tree < 1n << 64n, "invalid output tree");
|
|
3170
|
+
const inserted = insertions.get(insertionKey(log, tree, at));
|
|
3171
|
+
check3(inserted, "delivery has no matching canonical insertion");
|
|
3172
|
+
if (commitment !== void 0)
|
|
3173
|
+
check3(hash(commitment) === inserted, "delivery commitment conflicts with insertion");
|
|
3174
|
+
const delivery = normalizeOpaqueDelivery(wire, { allowEmpty: role === "funds" });
|
|
3175
|
+
check3(
|
|
3176
|
+
typeof token === "string" && isAddress(token) && BigInt(token) !== 0n,
|
|
3177
|
+
"invalid event token"
|
|
4172
3178
|
);
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
const
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
"
|
|
3179
|
+
const value = {
|
|
3180
|
+
tokenAddress: token,
|
|
3181
|
+
treeId: tree,
|
|
3182
|
+
leafIndex: at,
|
|
3183
|
+
commitment: inserted,
|
|
3184
|
+
role,
|
|
3185
|
+
delivery,
|
|
3186
|
+
...metadata(log)
|
|
3187
|
+
};
|
|
3188
|
+
const key = `${insertionKey(log, tree, at)}:${role}`, previous = results.get(key);
|
|
3189
|
+
check3(
|
|
3190
|
+
!previous || previous.tokenAddress.toLowerCase() === token.toLowerCase() && previous.delivery.ciphertext === delivery.ciphertext && previous.delivery.clue === delivery.clue && previous.blockHash === value.blockHash,
|
|
3191
|
+
"conflicting delivery events"
|
|
4186
3192
|
);
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
3193
|
+
results.set(key, value);
|
|
3194
|
+
};
|
|
3195
|
+
const creation = (log, a, fields) => {
|
|
3196
|
+
const tree = integer(a.outputTreeId), leaves = array(a.leafIndices), ds = array(a.deliveries);
|
|
3197
|
+
const change = hash(fields.changeCommitment), hasChange = BigInt(change) !== 0n;
|
|
3198
|
+
check3(
|
|
3199
|
+
ds.length === 3 && leaves.length === (hasChange ? 2 : 1),
|
|
3200
|
+
"invalid Credit creation delivery slots"
|
|
4190
3201
|
);
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
base.credits = {};
|
|
4208
|
-
}
|
|
4209
|
-
const block = await reader.getBlock(batch.block.number);
|
|
4210
|
-
check7(
|
|
4211
|
-
block.hash === batch.block.hash && block.number === batch.block.number,
|
|
4212
|
-
"update target block is not canonical"
|
|
4213
|
-
);
|
|
4214
|
-
const receipts = [];
|
|
4215
|
-
for (const hash2 of new Set(batch.transactionHashes)) {
|
|
4216
|
-
signal?.throwIfAborted();
|
|
4217
|
-
check7(/^0x[0-9a-fA-F]{64}$/.test(hash2), "invalid candidate transaction hash");
|
|
4218
|
-
const receipt = await reader.getReceipt(hash2);
|
|
4219
|
-
check7(
|
|
4220
|
-
receipt && receipt.status === "success" && receipt.transactionHash.toLowerCase() === hash2.toLowerCase(),
|
|
4221
|
-
"invalid or unsuccessful candidate receipt"
|
|
4222
|
-
);
|
|
4223
|
-
check7(
|
|
4224
|
-
receipt.block.number >= protocol.deployment.protocol.fromBlock && receipt.block.number <= block.number,
|
|
4225
|
-
"candidate receipt outside update range"
|
|
4226
|
-
);
|
|
4227
|
-
if ((await reader.getBlock(receipt.block.number)).hash !== receipt.block.hash)
|
|
4228
|
-
throw new CanonicalConflict("Candidate receipt disagrees with canonical chain");
|
|
4229
|
-
receipts.push(receipt);
|
|
4230
|
-
}
|
|
4231
|
-
const logs = receipts.flatMap(
|
|
4232
|
-
(receipt) => readProtocolLogs(receipt, context.protocolAddress)
|
|
4233
|
-
);
|
|
4234
|
-
logs.sort(
|
|
4235
|
-
(a, b) => a.blockNumber < b.blockNumber ? -1 : a.blockNumber > b.blockNumber ? 1 : a.logIndex - b.logIndex
|
|
4236
|
-
);
|
|
4237
|
-
const leaves = /* @__PURE__ */ new Map();
|
|
4238
|
-
for (const event of logs) {
|
|
4239
|
-
if (event.eventName !== "CommitmentsInserted" && event.eventName !== "BatchOutputs")
|
|
4240
|
-
continue;
|
|
4241
|
-
const { treeId, startLeafIndex, commitments } = event.args;
|
|
4242
|
-
for (const [i, commitment] of commitments.entries()) {
|
|
4243
|
-
const key = position2(treeId, Number(startLeafIndex) + i);
|
|
4244
|
-
check7(
|
|
4245
|
-
!leaves.has(key) || leaves.get(key) === BigInt(commitment),
|
|
4246
|
-
"conflicting canonical insertion"
|
|
4247
|
-
);
|
|
4248
|
-
leaves.set(key, BigInt(commitment));
|
|
4249
|
-
}
|
|
4250
|
-
}
|
|
4251
|
-
const getLeaf = (tree, index2) => {
|
|
4252
|
-
const leaf = leaves.get(position2(tree, index2));
|
|
4253
|
-
check7(leaf !== void 0, "missing canonical insertion");
|
|
4254
|
-
return leaf;
|
|
4255
|
-
};
|
|
4256
|
-
const decrypted = await decryptCandidates(logs, {
|
|
4257
|
-
context,
|
|
4258
|
-
viewing,
|
|
4259
|
-
confirmedThrough: block.number
|
|
4260
|
-
});
|
|
4261
|
-
const notes = decrypted.notes.map((record) => ({
|
|
4262
|
-
...record,
|
|
4263
|
-
spent: "unknown"
|
|
4264
|
-
}));
|
|
4265
|
-
const incoming = decrypted.credits.map((record) => ({
|
|
4266
|
-
...record,
|
|
4267
|
-
spent: "unknown"
|
|
4268
|
-
}));
|
|
4269
|
-
const recovered = await this.recoverCredits(base, incoming, logs, { getLeaf });
|
|
4270
|
-
const creditsByKey = new Map(
|
|
4271
|
-
incoming.map((record) => [record.commitment + ":" + record.role, record])
|
|
3202
|
+
add(log, tree, index(leaves[0]), fields.creditCommitment, "owner", ds[0]);
|
|
3203
|
+
add(log, tree, index(leaves[0]), fields.creditCommitment, "spender", ds[1]);
|
|
3204
|
+
if (hasChange) add(log, tree, index(leaves[1]), change, "funds", ds[2]);
|
|
3205
|
+
else normalizeOpaqueDelivery(ds[2], { empty: true });
|
|
3206
|
+
};
|
|
3207
|
+
for (const log of relevant) {
|
|
3208
|
+
const a = object(log.args);
|
|
3209
|
+
if (log.eventName === "Shield") {
|
|
3210
|
+
const ds = array(a.deliveries), tree = integer(a.treeId), start = index(a.startLeafIndex);
|
|
3211
|
+
check3(start + ds.length <= 65536, "shield exceeds tree capacity");
|
|
3212
|
+
ds.forEach((d, i) => add(log, tree, start + i, void 0, "funds", d));
|
|
3213
|
+
} else if (log.eventName === "Transact" || log.eventName === "CrossTreeTransact") {
|
|
3214
|
+
const cs = array(a.outputCommitments), ds = array(a.deliveries), leaves = array(a.leafIndices), tree = integer(a.outputTreeId);
|
|
3215
|
+
check3(
|
|
3216
|
+
cs.length === ds.length && cs.length === leaves.length,
|
|
3217
|
+
"invalid transact delivery slots"
|
|
4272
3218
|
);
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
const
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
if (error instanceof CreditMetadataMismatch) return false;
|
|
4281
|
-
throw error;
|
|
4282
|
-
}
|
|
4283
|
-
}).sort(
|
|
4284
|
-
(a, b) => a.blockNumber < b.blockNumber ? -1 : a.blockNumber > b.blockNumber ? 1 : a.treeId < b.treeId ? -1 : a.treeId > b.treeId ? 1 : a.leafIndex - b.leafIndex
|
|
3219
|
+
cs.forEach((c, i) => add(log, tree, index(leaves[i]), c, "funds", ds[i]));
|
|
3220
|
+
} else if (log.eventName === "BatchOutputs") {
|
|
3221
|
+
const tokens = array(a.tokens);
|
|
3222
|
+
const cs = array(a.commitments), tree = integer(a.treeId), start = index(a.startLeafIndex), packed = a.packedDeliveries;
|
|
3223
|
+
check3(
|
|
3224
|
+
typeof packed === "string" && /^0x(?:[0-9a-fA-F]{2})*$/.test(packed),
|
|
3225
|
+
"invalid packed deliveries"
|
|
4285
3226
|
);
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
block,
|
|
4297
|
-
record,
|
|
4298
|
-
proof
|
|
3227
|
+
check3(tokens.length === cs.length, "batch token count mismatch");
|
|
3228
|
+
check3(start + cs.length <= 65536, "batch exceeds tree capacity");
|
|
3229
|
+
let offset = 2;
|
|
3230
|
+
cs.forEach((c, i) => {
|
|
3231
|
+
check3(offset + 4 <= packed.length, "truncated delivery length prefix");
|
|
3232
|
+
const length = Number.parseInt(packed.slice(offset, offset + 4), 16);
|
|
3233
|
+
offset += 4;
|
|
3234
|
+
check3(
|
|
3235
|
+
length <= MAX_CIPHERTEXT_BYTES && offset + length * 2 + (length === 0 ? 0 : 136) <= packed.length,
|
|
3236
|
+
"invalid packed delivery length"
|
|
4299
3237
|
);
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
const
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
id,
|
|
4316
|
-
nullifier: await nullifier.compute(
|
|
4317
|
-
scope.chainId,
|
|
4318
|
-
record.commitment,
|
|
4319
|
-
record.note.nonce
|
|
4320
|
-
)
|
|
4321
|
-
});
|
|
4322
|
-
for (const [id, record] of Object.entries(allCredits))
|
|
4323
|
-
observations.push({
|
|
4324
|
-
kind: "credits",
|
|
4325
|
-
id,
|
|
4326
|
-
nullifier: await this.creditNullifier(record.state)
|
|
4327
|
-
});
|
|
4328
|
-
const spent = observations.length ? await reader.isSpent(
|
|
4329
|
-
context,
|
|
4330
|
-
observations.map((item) => item.nullifier),
|
|
4331
|
-
block
|
|
4332
|
-
) : [];
|
|
4333
|
-
check7(
|
|
4334
|
-
spent.length === observations.length && spent.every((value) => typeof value === "boolean"),
|
|
4335
|
-
"invalid spent-state response"
|
|
3238
|
+
const ciphertext = `0x${packed.slice(offset, offset + length * 2)}`;
|
|
3239
|
+
offset += length * 2;
|
|
3240
|
+
const clue = length === 0 ? "0x" : `0x${packed.slice(offset, offset + 136)}`;
|
|
3241
|
+
if (length !== 0) offset += 136;
|
|
3242
|
+
add(log, tree, start + i, c, "funds", { ciphertext, clue }, tokens[i]);
|
|
3243
|
+
});
|
|
3244
|
+
check3(offset === packed.length, "trailing packed delivery bytes");
|
|
3245
|
+
} else if (log.eventName === "CreditCreated") creation(log, a, a);
|
|
3246
|
+
else if (log.eventName === "CreditToppedUp")
|
|
3247
|
+
creation(log, a, object(object(a.params).creation));
|
|
3248
|
+
else if (log.eventName === "CreditDraw") {
|
|
3249
|
+
const p = object(a.params), cs = array(p.remainderCommitments), ds = array(a.deliveries), leaves = array(a.remainderLeafIndices), tree = integer(a.outputTreeId);
|
|
3250
|
+
check3(
|
|
3251
|
+
ds.length === 2 * cs.length && leaves.length === cs.length,
|
|
3252
|
+
"invalid draw delivery slots"
|
|
4336
3253
|
);
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
draft.notes = {};
|
|
4346
|
-
draft.credits = {};
|
|
4347
|
-
delete draft.observedThrough;
|
|
4348
|
-
delete draft.discoveryThrough;
|
|
4349
|
-
delete draft.invalidated;
|
|
3254
|
+
cs.forEach((c, i) => {
|
|
3255
|
+
if (BigInt(hash(c)) === 0n) {
|
|
3256
|
+
check3(integer(leaves[i]) === 0n, "absent remainder has nonzero position");
|
|
3257
|
+
normalizeOpaqueDelivery(ds[2 * i], { empty: true });
|
|
3258
|
+
normalizeOpaqueDelivery(ds[2 * i + 1], { empty: true });
|
|
3259
|
+
} else {
|
|
3260
|
+
add(log, tree, index(leaves[i]), c, "owner", ds[2 * i]);
|
|
3261
|
+
add(log, tree, index(leaves[i]), c, "spender", ds[2 * i + 1]);
|
|
4350
3262
|
}
|
|
4351
|
-
mergeReceiptRecords(draft, { notes, credits, checkpoint: block });
|
|
4352
|
-
observations.forEach((item, index2) => {
|
|
4353
|
-
const record = draft[item.kind][item.id];
|
|
4354
|
-
if (record) {
|
|
4355
|
-
mergeSpentObservation(record, {
|
|
4356
|
-
spent: spent[index2] ? "spent" : "unspent",
|
|
4357
|
-
checkedThrough: { number: block.number, hash: block.hash }
|
|
4358
|
-
});
|
|
4359
|
-
}
|
|
4360
|
-
});
|
|
4361
|
-
draft.observedThrough = { number: block.number, hash: block.hash };
|
|
4362
3263
|
});
|
|
4363
|
-
return {
|
|
4364
|
-
block,
|
|
4365
|
-
notes: notes.length,
|
|
4366
|
-
credits: credits.length,
|
|
4367
|
-
balances: walletBalances(await store.readSnapshot(scope))
|
|
4368
|
-
};
|
|
4369
|
-
} catch (error) {
|
|
4370
|
-
if (error instanceof CanonicalConflict) await this.invalidate();
|
|
4371
|
-
throw error;
|
|
4372
3264
|
}
|
|
4373
3265
|
}
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
this.#options = {
|
|
4398
|
-
...options,
|
|
4399
|
-
...store ? {
|
|
4400
|
-
store: {
|
|
4401
|
-
persistent: store.persistent,
|
|
4402
|
-
readSnapshot: (scope) => store.readSnapshot(scope),
|
|
4403
|
-
transaction: async (scope, action) => {
|
|
4404
|
-
const result = await store.transaction(scope, action);
|
|
4405
|
-
if (scopeKey(scope) === scopeKey(this.scope)) {
|
|
4406
|
-
for (const listener of [...this.#listeners]) {
|
|
4407
|
-
try {
|
|
4408
|
-
listener();
|
|
4409
|
-
} catch {
|
|
4410
|
-
}
|
|
4411
|
-
}
|
|
4412
|
-
}
|
|
4413
|
-
return result;
|
|
4414
|
-
}
|
|
4415
|
-
}
|
|
4416
|
-
} : {},
|
|
4417
|
-
...options.gasBenchmarks ? { gasBenchmarks: copyGasBenchmarks(options.gasBenchmarks) } : {}
|
|
4418
|
-
};
|
|
4419
|
-
this.protocol = options.protocol;
|
|
4420
|
-
this.scope = Object.freeze({
|
|
4421
|
-
namespace: options.namespace ?? "default",
|
|
4422
|
-
...options.protocol.context,
|
|
4423
|
-
ownerAddress: options.wallet.ownerAddress
|
|
4424
|
-
});
|
|
4425
|
-
scopeKey(this.scope);
|
|
4426
|
-
Object.freeze(this);
|
|
3266
|
+
return [...results.values()];
|
|
3267
|
+
}
|
|
3268
|
+
async function decryptCandidates(logs, options) {
|
|
3269
|
+
contextCheck(options.context);
|
|
3270
|
+
decodeAddress(ReceivingAddress.fromIdentity(options.viewing.ownerAddress).toString());
|
|
3271
|
+
check3(
|
|
3272
|
+
!logs.some(
|
|
3273
|
+
(log) => log.address.toLowerCase() === options.context.protocolAddress.toLowerCase() && log.removed
|
|
3274
|
+
),
|
|
3275
|
+
"discovery resync required after reorg"
|
|
3276
|
+
);
|
|
3277
|
+
const candidates = extractDeliveryCandidates(logs, options.context, options.confirmedThrough);
|
|
3278
|
+
const positions = /* @__PURE__ */ new Map();
|
|
3279
|
+
const unique = /* @__PURE__ */ new Map();
|
|
3280
|
+
for (const candidate of candidates) {
|
|
3281
|
+
const key = position(candidate.treeId, candidate.leafIndex);
|
|
3282
|
+
const previous = positions.get(key);
|
|
3283
|
+
check3(
|
|
3284
|
+
!previous || previous.commitment === candidate.commitment && previous.blockHash === candidate.blockHash && previous.transactionHash === candidate.transactionHash && previous.tokenAddress.toLowerCase() === candidate.tokenAddress.toLowerCase(),
|
|
3285
|
+
"conflicting canonical position"
|
|
3286
|
+
);
|
|
3287
|
+
positions.set(key, candidate);
|
|
3288
|
+
unique.set(`${key}:${candidate.role}`, candidate);
|
|
4427
3289
|
}
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
const
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
3290
|
+
const notes = [];
|
|
3291
|
+
const credits = [];
|
|
3292
|
+
for (const candidate of unique.values()) {
|
|
3293
|
+
const { delivery, role, ...positionData } = candidate;
|
|
3294
|
+
const context = {
|
|
3295
|
+
chainId: options.context.chainId,
|
|
3296
|
+
protocolAddress: BigInt(options.context.protocolAddress),
|
|
3297
|
+
tokenAddress: BigInt(candidate.tokenAddress),
|
|
3298
|
+
outputCommitment: candidate.commitment
|
|
3299
|
+
};
|
|
4438
3300
|
try {
|
|
4439
|
-
if (
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
const call = operation ? await operation.exportTransaction() : plain;
|
|
4450
|
-
const transaction = await signer.sendTransaction({
|
|
4451
|
-
...overrides,
|
|
4452
|
-
to: call.to,
|
|
4453
|
-
data: call.data,
|
|
4454
|
-
value: call.value,
|
|
4455
|
-
chainId: this.protocol.context.chainId
|
|
4456
|
-
});
|
|
4457
|
-
if (operation) {
|
|
4458
|
-
try {
|
|
4459
|
-
await operation.trackTransaction(transaction.hash);
|
|
4460
|
-
} catch (cause) {
|
|
4461
|
-
throw new Error(
|
|
4462
|
-
`Transaction ${transaction.hash} was sent but its operation association could not be saved; resume and reconcile this hash`,
|
|
4463
|
-
{ cause }
|
|
4464
|
-
);
|
|
4465
|
-
}
|
|
3301
|
+
if (role === "funds") {
|
|
3302
|
+
if (delivery.ciphertext === "0x") continue;
|
|
3303
|
+
const note = await options.viewing.decryptNote(delivery.ciphertext, context);
|
|
3304
|
+
notes.push({ ...positionData, note });
|
|
3305
|
+
} else {
|
|
3306
|
+
const state = await options.viewing.decryptCredit(delivery.ciphertext, {
|
|
3307
|
+
...context,
|
|
3308
|
+
role
|
|
3309
|
+
});
|
|
3310
|
+
credits.push({ ...positionData, role, state });
|
|
4466
3311
|
}
|
|
4467
|
-
|
|
4468
|
-
} finally {
|
|
4469
|
-
if (operation) this.#sending.delete(operation.id);
|
|
4470
|
-
}
|
|
4471
|
-
}
|
|
4472
|
-
runtime(tokenAddress) {
|
|
4473
|
-
const options = this.#options;
|
|
4474
|
-
if (!options.store) {
|
|
4475
|
-
throw new MissingCapabilityError2("wallet store");
|
|
4476
|
-
}
|
|
4477
|
-
if (!options.chainReader) {
|
|
4478
|
-
throw new MissingCapabilityError2("chain reader");
|
|
3312
|
+
} catch {
|
|
4479
3313
|
}
|
|
4480
|
-
return {
|
|
4481
|
-
options: { ...options, protocol: options.protocol.forToken(tokenAddress) },
|
|
4482
|
-
scope: this.scope,
|
|
4483
|
-
store: options.store,
|
|
4484
|
-
reader: options.chainReader,
|
|
4485
|
-
authorization: () => {
|
|
4486
|
-
if (options.authorization) return options.authorization;
|
|
4487
|
-
if (options.wallet instanceof Wallet) return options.wallet.exportAuthorization();
|
|
4488
|
-
throw new MissingCapabilityError2("wallet authorization");
|
|
4489
|
-
}
|
|
4490
|
-
};
|
|
4491
3314
|
}
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
3315
|
+
return { notes, credits };
|
|
3316
|
+
}
|
|
3317
|
+
|
|
3318
|
+
// src/broadcaster/index.ts
|
|
3319
|
+
import { invisiProtocolAbi as invisiProtocolAbi2 } from "@invisi-labs/protocol/contracts";
|
|
3320
|
+
import { AbiCoder, getAddress as getAddress2, Interface as Interface2, keccak256, verifyTypedData as verifyTypedData2 } from "ethers";
|
|
3321
|
+
var broadcasterQuoteTypes = {
|
|
3322
|
+
Quote: [
|
|
3323
|
+
{ name: "token", type: "address" },
|
|
3324
|
+
{ name: "feeRecipient", type: "address" },
|
|
3325
|
+
{ name: "feeAmount", type: "uint256" },
|
|
3326
|
+
{ name: "deadline", type: "uint256" },
|
|
3327
|
+
{ name: "maxGas", type: "uint256" },
|
|
3328
|
+
{ name: "requestHash", type: "bytes32" }
|
|
3329
|
+
]
|
|
3330
|
+
};
|
|
3331
|
+
function broadcasterQuoteDomain(trust) {
|
|
3332
|
+
return {
|
|
3333
|
+
name: "InvisiBroadcaster",
|
|
3334
|
+
version: "1",
|
|
3335
|
+
chainId: trust.chainId,
|
|
3336
|
+
verifyingContract: getAddress2(trust.protocol)
|
|
3337
|
+
};
|
|
3338
|
+
}
|
|
3339
|
+
var supportedMethods = ["transact", "shield", "createCredit", "topUpCredit", "cancelCredit"];
|
|
3340
|
+
var intentAbi = invisiProtocolAbi2.filter(
|
|
3341
|
+
(item) => item.type === "function" && supportedMethods.includes(item.name) && (item.name !== "transact" || item.inputs[1]?.type === "tuple")
|
|
3342
|
+
);
|
|
3343
|
+
var broadcasterTransactInterface = new Interface2(intentAbi);
|
|
3344
|
+
function decodeBroadcasterIntent(data) {
|
|
3345
|
+
const parsed = broadcasterTransactInterface.parseTransaction({ data });
|
|
3346
|
+
if (!parsed || !supportedMethods.includes(parsed.name))
|
|
3347
|
+
throw new Error("unsupported broadcaster method");
|
|
3348
|
+
const fragment = parsed.fragment;
|
|
3349
|
+
const decoded = parsed.args;
|
|
3350
|
+
if (broadcasterTransactInterface.encodeFunctionData(fragment, decoded).toLowerCase() !== data.toLowerCase())
|
|
3351
|
+
throw new Error("noncanonical broadcaster calldata");
|
|
3352
|
+
const method = parsed.name;
|
|
3353
|
+
const raw = method === "transact" ? decoded[1] : void 0;
|
|
3354
|
+
const params = raw?.params ?? (method === "shield" ? decoded[0] : method === "topUpCredit" ? decoded[1].creation : decoded[1]);
|
|
3355
|
+
const funding = raw?.funding ?? (method === "shield" ? decoded[3] : ["createCredit", "topUpCredit"].includes(method) ? decoded[3] : void 0);
|
|
3356
|
+
const deliveries = raw?.deliveries ?? (method === "shield" ? decoded[2] : ["createCredit", "topUpCredit"].includes(method) ? decoded[2] : []);
|
|
3357
|
+
return {
|
|
3358
|
+
fragment,
|
|
3359
|
+
method,
|
|
3360
|
+
args: decoded,
|
|
3361
|
+
proof: method === "shield" ? "0x" : decoded[0],
|
|
3362
|
+
request: { params, funding, deliveries }
|
|
3363
|
+
};
|
|
3364
|
+
}
|
|
3365
|
+
function broadcasterRequestHash(data) {
|
|
3366
|
+
const { fragment, method, args } = decodeBroadcasterIntent(data);
|
|
3367
|
+
const copy = (value) => Array.isArray(value) ? value.map(copy) : value;
|
|
3368
|
+
const canonical = copy(args);
|
|
3369
|
+
if (method !== "shield") canonical[0] = "0x";
|
|
3370
|
+
if (method === "transact") canonical[1][2][4] = "0x";
|
|
3371
|
+
else if (method !== "cancelCredit") canonical[3][4] = "0x";
|
|
3372
|
+
const calldata = broadcasterTransactInterface.encodeFunctionData(fragment, canonical);
|
|
3373
|
+
return keccak256(
|
|
3374
|
+
AbiCoder.defaultAbiCoder().encode(
|
|
3375
|
+
["string", "string", "bytes"],
|
|
3376
|
+
["InvisiBroadcasterIntentV1", method, calldata]
|
|
3377
|
+
)
|
|
3378
|
+
);
|
|
3379
|
+
}
|
|
3380
|
+
function verifyBroadcasterQuote(signed, trust, data, now = BigInt(Math.floor(Date.now() / 1e3))) {
|
|
3381
|
+
const { quote } = signed;
|
|
3382
|
+
const { request } = decodeBroadcasterIntent(data);
|
|
3383
|
+
const p = request.params;
|
|
3384
|
+
if (quote.requestHash !== broadcasterRequestHash(data) || getAddress2(quote.token) !== getAddress2(p.token) || getAddress2(quote.feeRecipient) !== getAddress2(p.feeRecipient) || BigInt(quote.feeAmount) !== p.feeAmount || BigInt(quote.deadline) > p.deadline || BigInt(quote.deadline) < now || BigInt(quote.maxGas) <= 0n || BigInt(quote.feeAmount) <= 0n || BigInt(quote.feeAmount) >= 1n << 128n)
|
|
3385
|
+
throw new Error("quote does not match final intent or bounds");
|
|
3386
|
+
if (getAddress2(
|
|
3387
|
+
verifyTypedData2(
|
|
3388
|
+
broadcasterQuoteDomain(trust),
|
|
3389
|
+
broadcasterQuoteTypes,
|
|
3390
|
+
quote,
|
|
3391
|
+
signed.signature
|
|
3392
|
+
)
|
|
3393
|
+
) !== getAddress2(trust.signer))
|
|
3394
|
+
throw new Error("untrusted quote signature");
|
|
3395
|
+
return quote;
|
|
3396
|
+
}
|
|
3397
|
+
function checkedStatus(value, id) {
|
|
3398
|
+
const status = value;
|
|
3399
|
+
if (!status || status.id !== id || !["pending", "submitted", "confirmed", "reverted", "uncertain"].includes(status.state) || status.transactionHash !== void 0 && !/^0x[0-9a-fA-F]{64}$/.test(status.transactionHash))
|
|
3400
|
+
throw new Error("invalid broadcaster status");
|
|
3401
|
+
return status;
|
|
3402
|
+
}
|
|
3403
|
+
var BroadcasterClient = class {
|
|
3404
|
+
configuration;
|
|
3405
|
+
url;
|
|
3406
|
+
transport;
|
|
3407
|
+
requestId = 0;
|
|
3408
|
+
constructor(configuration, transport) {
|
|
3409
|
+
this.configuration = Object.freeze({
|
|
3410
|
+
endpoint: configuration.endpoint,
|
|
3411
|
+
quoteSigner: getAddress2(configuration.quoteSigner),
|
|
3412
|
+
feeRecipient: getAddress2(configuration.feeRecipient)
|
|
4509
3413
|
});
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
if (!store) throw new MissingCapabilityError2("wallet store");
|
|
4524
|
-
return walletHistory(
|
|
4525
|
-
await store.readSnapshot(this.scope),
|
|
4526
|
-
tokenAddress ? { tokenAddress } : {}
|
|
4527
|
-
);
|
|
4528
|
-
}
|
|
4529
|
-
async balance(tokenAddress) {
|
|
4530
|
-
const store = this.#options.store;
|
|
4531
|
-
if (!store) {
|
|
4532
|
-
throw new MissingCapabilityError2("wallet store");
|
|
4533
|
-
}
|
|
4534
|
-
return walletBalance(await store.readSnapshot(this.scope), getAddress(tokenAddress));
|
|
4535
|
-
}
|
|
4536
|
-
/** Pull explicitly selected indexed pages; no chain scanning or public tree reconstruction. */
|
|
4537
|
-
async refresh(options) {
|
|
4538
|
-
const provider = this.#options.protocolRpc;
|
|
4539
|
-
const reader = this.#options.chainReader;
|
|
4540
|
-
if (!reader) throw new MissingCapabilityError2("chain reader");
|
|
4541
|
-
if (!provider) throw new MissingCapabilityError2("Protocol RPC");
|
|
4542
|
-
const updater = this.updater();
|
|
4543
|
-
const request = {
|
|
4544
|
-
...options,
|
|
4545
|
-
...options.detectionKey ? { detectionKey: options.detectionKey.slice() } : {}
|
|
4546
|
-
};
|
|
4547
|
-
const task = this.#refreshQueue.then(async () => {
|
|
4548
|
-
const result = await refreshWallet(
|
|
4549
|
-
{
|
|
4550
|
-
provider,
|
|
4551
|
-
reader,
|
|
4552
|
-
context: this.protocol.context,
|
|
4553
|
-
fromBlock: this.protocol.deployment.protocol.fromBlock,
|
|
4554
|
-
apply: (batch, settings) => updater.applyUpdates(batch, settings)
|
|
4555
|
-
},
|
|
4556
|
-
request
|
|
4557
|
-
);
|
|
4558
|
-
await this.#options.store.transaction(this.scope, (state) => {
|
|
4559
|
-
request.signal?.throwIfAborted();
|
|
4560
|
-
if (state.invalidated) throw new Error("wallet requires canonical resync");
|
|
4561
|
-
const previous = state.discoveryThrough;
|
|
4562
|
-
const start = previous ? previous.number + 1n : this.protocol.deployment.protocol.fromBlock;
|
|
4563
|
-
if (request.fromBlock <= start && (!previous || result.block.number >= previous.number)) {
|
|
4564
|
-
state.discoveryThrough = {
|
|
4565
|
-
number: result.block.number,
|
|
4566
|
-
hash: result.block.hash
|
|
4567
|
-
};
|
|
4568
|
-
}
|
|
3414
|
+
if (BigInt(this.configuration.quoteSigner) === 0n || BigInt(this.configuration.feeRecipient) === 0n)
|
|
3415
|
+
throw new Error("invalid broadcaster configuration");
|
|
3416
|
+
this.url = configuration.endpoint;
|
|
3417
|
+
const base = new URL(this.url);
|
|
3418
|
+
if (base.protocol !== "https:" || base.username || base.password)
|
|
3419
|
+
throw new Error("broadcaster requires HTTPS");
|
|
3420
|
+
this.transport = transport ?? (async (request) => {
|
|
3421
|
+
const response = await fetch(base, {
|
|
3422
|
+
redirect: "error",
|
|
3423
|
+
method: "POST",
|
|
3424
|
+
body: JSON.stringify(request),
|
|
3425
|
+
headers: { "content-type": "application/json" },
|
|
3426
|
+
signal: AbortSignal.timeout(3e4)
|
|
4569
3427
|
});
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
return this.preparation(options.tokenAddress).prepareShield(options);
|
|
4594
|
-
});
|
|
4595
|
-
}
|
|
4596
|
-
async prepareCreateCredit(options) {
|
|
4597
|
-
return prepareWithErrors("prepareCreateCredit", async () => {
|
|
4598
|
-
return this.preparation(options.tokenAddress).prepareCreateCredit(options);
|
|
3428
|
+
if (!response.ok) throw new Error(`Broadcaster HTTP ${response.status}`);
|
|
3429
|
+
if (!response.body) throw new Error("empty broadcaster response");
|
|
3430
|
+
const reader = response.body.getReader();
|
|
3431
|
+
const chunks = [];
|
|
3432
|
+
let size = 0;
|
|
3433
|
+
try {
|
|
3434
|
+
while (true) {
|
|
3435
|
+
const result = await reader.read();
|
|
3436
|
+
if (result.done) break;
|
|
3437
|
+
size += result.value.length;
|
|
3438
|
+
if (size > 65536) throw new Error("broadcaster response too large");
|
|
3439
|
+
chunks.push(result.value);
|
|
3440
|
+
}
|
|
3441
|
+
} finally {
|
|
3442
|
+
await reader.cancel();
|
|
3443
|
+
}
|
|
3444
|
+
const bytes = new Uint8Array(size);
|
|
3445
|
+
let offset = 0;
|
|
3446
|
+
for (const chunk of chunks) {
|
|
3447
|
+
bytes.set(chunk, offset);
|
|
3448
|
+
offset += chunk.length;
|
|
3449
|
+
}
|
|
3450
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
4599
3451
|
});
|
|
4600
3452
|
}
|
|
4601
|
-
async
|
|
4602
|
-
|
|
4603
|
-
|
|
3453
|
+
async call(method, params) {
|
|
3454
|
+
const id = ++this.requestId;
|
|
3455
|
+
const response = await this.transport({
|
|
3456
|
+
jsonrpc: "2.0",
|
|
3457
|
+
id,
|
|
3458
|
+
method,
|
|
3459
|
+
params: [params]
|
|
4604
3460
|
});
|
|
3461
|
+
if (!response || response.jsonrpc !== "2.0" || response.id !== id || Object.hasOwn(response, "result") === Object.hasOwn(response, "error"))
|
|
3462
|
+
throw new Error("invalid broadcaster JSON-RPC response");
|
|
3463
|
+
if (response.error) {
|
|
3464
|
+
if (!Number.isInteger(response.error.code) || typeof response.error.message !== "string")
|
|
3465
|
+
throw new Error("invalid broadcaster JSON-RPC error");
|
|
3466
|
+
throw new BroadcasterRpcError(response.error.code, response.error.message);
|
|
3467
|
+
}
|
|
3468
|
+
return response.result;
|
|
4605
3469
|
}
|
|
4606
|
-
async
|
|
4607
|
-
|
|
4608
|
-
|
|
3470
|
+
async fee(token, data) {
|
|
3471
|
+
const fee = await this.call("broadcaster_getFee", {
|
|
3472
|
+
token: getAddress2(token),
|
|
3473
|
+
...data ? { data } : {}
|
|
4609
3474
|
});
|
|
3475
|
+
if (!fee || getAddress2(fee.feeRecipient) !== this.configuration.feeRecipient || !/^\d+$/.test(fee.feeAmount) || BigInt(fee.feeAmount) <= 0n || BigInt(fee.feeAmount) >= 1n << 128n || !/^\d+$/.test(fee.maxGas) || BigInt(fee.maxGas) <= 0n)
|
|
3476
|
+
throw new Error("invalid broadcaster fee");
|
|
3477
|
+
return fee;
|
|
4610
3478
|
}
|
|
4611
|
-
async
|
|
4612
|
-
|
|
4613
|
-
|
|
3479
|
+
async quote(data, context) {
|
|
3480
|
+
const signed = await this.call("broadcaster_getQuote", {
|
|
3481
|
+
data
|
|
4614
3482
|
});
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
return
|
|
4623
|
-
}
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
throw new Error("unsupported wallet record value");
|
|
4643
|
-
}
|
|
4644
|
-
function decode(node) {
|
|
4645
|
-
if (!Array.isArray(node)) throw new Error("invalid wallet record encoding");
|
|
4646
|
-
const [type, value] = node;
|
|
4647
|
-
if (type === "bigint" && typeof value === "string" && /^-?(0|[1-9][0-9]*)$/.test(value))
|
|
4648
|
-
return BigInt(value);
|
|
4649
|
-
if (type === "bytes" && Array.isArray(value) && value.every((n) => Number.isInteger(n) && n >= 0 && n < 256))
|
|
4650
|
-
return Uint8Array.from(value);
|
|
4651
|
-
if (type === "array" && Array.isArray(value)) return value.map(decode);
|
|
4652
|
-
if (type === "object" && Array.isArray(value))
|
|
4653
|
-
return Object.fromEntries(
|
|
4654
|
-
value.map((pair) => {
|
|
4655
|
-
if (!Array.isArray(pair) || pair.length !== 2 || typeof pair[0] !== "string")
|
|
4656
|
-
throw new Error("invalid object record");
|
|
4657
|
-
return [pair[0], decode(pair[1])];
|
|
4658
|
-
})
|
|
3483
|
+
verifyBroadcasterQuote(
|
|
3484
|
+
signed,
|
|
3485
|
+
{ ...context, signer: this.configuration.quoteSigner },
|
|
3486
|
+
data
|
|
3487
|
+
);
|
|
3488
|
+
if (getAddress2(signed.quote.feeRecipient) !== this.configuration.feeRecipient)
|
|
3489
|
+
throw new Error("unregistered fee recipient");
|
|
3490
|
+
return { quote: signed.quote, signature: signed.signature, context: { ...context } };
|
|
3491
|
+
}
|
|
3492
|
+
async submit(data, signed, idempotencyKey) {
|
|
3493
|
+
if (!signed.context) throw new Error("missing verified quote context");
|
|
3494
|
+
verifyBroadcasterQuote(
|
|
3495
|
+
signed,
|
|
3496
|
+
{ ...signed.context, signer: this.configuration.quoteSigner },
|
|
3497
|
+
data,
|
|
3498
|
+
0n
|
|
3499
|
+
);
|
|
3500
|
+
if (getAddress2(signed.quote.feeRecipient) !== this.configuration.feeRecipient)
|
|
3501
|
+
throw new Error("unregistered fee recipient");
|
|
3502
|
+
return checkedStatus(
|
|
3503
|
+
await this.call("broadcaster_submitTransaction", {
|
|
3504
|
+
data,
|
|
3505
|
+
quote: signed.quote,
|
|
3506
|
+
signature: signed.signature,
|
|
3507
|
+
idempotencyKey
|
|
3508
|
+
}),
|
|
3509
|
+
idempotencyKey
|
|
4659
3510
|
);
|
|
4660
|
-
if (type === "undefined" && node.length === 1) return void 0;
|
|
4661
|
-
if (type === "value" && (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)))
|
|
4662
|
-
return value;
|
|
4663
|
-
throw new Error("invalid wallet record node");
|
|
4664
|
-
}
|
|
4665
|
-
function serializeState(state) {
|
|
4666
|
-
return new TextEncoder().encode(JSON.stringify(encode(state)));
|
|
4667
|
-
}
|
|
4668
|
-
function deserializeState(data) {
|
|
4669
|
-
return decode(
|
|
4670
|
-
JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(data))
|
|
4671
|
-
);
|
|
4672
|
-
}
|
|
4673
|
-
var MemoryRecordCodec = class {
|
|
4674
|
-
async encode(state) {
|
|
4675
|
-
return serializeState(state);
|
|
4676
3511
|
}
|
|
4677
|
-
async
|
|
4678
|
-
return
|
|
3512
|
+
async status(id) {
|
|
3513
|
+
return checkedStatus(await this.call("broadcaster_getTransactionStatus", { id }), id);
|
|
4679
3514
|
}
|
|
4680
3515
|
};
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
3516
|
+
var BroadcasterRpcError = class extends Error {
|
|
3517
|
+
constructor(code, message) {
|
|
3518
|
+
super(message);
|
|
3519
|
+
this.code = code;
|
|
3520
|
+
this.name = "BroadcasterRpcError";
|
|
4686
3521
|
}
|
|
4687
3522
|
};
|
|
4688
3523
|
|
|
4689
3524
|
export {
|
|
3525
|
+
creditReservationKey,
|
|
3526
|
+
inputFacts,
|
|
3527
|
+
operationSources,
|
|
4690
3528
|
copyGasBenchmarks,
|
|
4691
3529
|
defineGasBenchmarks,
|
|
3530
|
+
estimateShapeGas,
|
|
3531
|
+
estimateBenchmarkGas,
|
|
4692
3532
|
estimateGasReference,
|
|
3533
|
+
AddressRegistry,
|
|
3534
|
+
TransactionOutput,
|
|
3535
|
+
publicProof,
|
|
4693
3536
|
sameStatement,
|
|
3537
|
+
encodeOperation,
|
|
3538
|
+
witnessFor,
|
|
3539
|
+
fundingTypedData,
|
|
3540
|
+
authorizeOperation,
|
|
4694
3541
|
proveOperation,
|
|
3542
|
+
signOperationFunding,
|
|
4695
3543
|
walletSigningRequest,
|
|
4696
3544
|
acceptWalletSignature,
|
|
4697
3545
|
fundingSigningRequest,
|
|
4698
3546
|
acceptFundingSignature,
|
|
4699
3547
|
operationPreview,
|
|
4700
|
-
|
|
4701
|
-
|
|
3548
|
+
PreparedTransaction,
|
|
3549
|
+
configureTransactionInfrastructure,
|
|
3550
|
+
validateTransactionInfrastructure,
|
|
3551
|
+
Invisi,
|
|
3552
|
+
decodeProtocolEvent,
|
|
4702
3553
|
readProtocolLogs,
|
|
3554
|
+
extractDeliveryCandidates,
|
|
4703
3555
|
decryptCandidates,
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
3556
|
+
broadcasterQuoteTypes,
|
|
3557
|
+
broadcasterQuoteDomain,
|
|
3558
|
+
broadcasterTransactInterface,
|
|
3559
|
+
decodeBroadcasterIntent,
|
|
3560
|
+
broadcasterRequestHash,
|
|
3561
|
+
verifyBroadcasterQuote,
|
|
3562
|
+
BroadcasterClient,
|
|
3563
|
+
BroadcasterRpcError
|
|
4710
3564
|
};
|
|
4711
|
-
//# sourceMappingURL=chunk-
|
|
3565
|
+
//# sourceMappingURL=chunk-EHP5TBC6.js.map
|