@opaquecash/opaque 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Recipient identity resolution beyond the on-chain registries (CSAP §2.9).
3
+ *
4
+ * `OpaqueClient.resolveRecipient` accepts, in one entry point:
5
+ * - a raw 66-byte meta-address (`0x…` 132 hex, optionally `st:opq:`-prefixed)
6
+ * - a 20-byte EVM address → ERC-6538 `StealthMetaAddressRegistry`
7
+ * - a Solana base58 pubkey → `stealth-registry` PDA
8
+ * - `ipfs://…` / bare CID → off-chain DID document fetch (gateway or injected loader)
9
+ * - `*.eth` → ENS `com.opaque.meta` text record (read path; ONS is Phase 6)
10
+ *
11
+ * Every path funnels through {@link parseMetaAddressValue}, which validates that both
12
+ * 33-byte halves are valid compressed secp256k1 points before the value is used.
13
+ */
14
+ import { secp256k1 } from "@noble/curves/secp256k1";
15
+ /** ENSIP-5 reverse-DNS record key for a published meta-address (CSAP §2.9). */
16
+ export const OPAQUE_META_RECORD_KEY = "com.opaque.meta";
17
+ /** Optional self-describing prefix for published meta-address values (CSAP §2.9). */
18
+ export const META_ADDRESS_VALUE_PREFIX = "st:opq:";
19
+ /** Default public IPFS gateways tried in order for `ipfs://` recipients. */
20
+ export const DEFAULT_IPFS_GATEWAYS = [
21
+ "https://ipfs.io",
22
+ "https://cloudflare-ipfs.com",
23
+ ];
24
+ /**
25
+ * Parse and validate a published meta-address value: the §2.1 serialisation
26
+ * (`0x` + 132 hex, `V‖S`), optionally `st:opq:`-prefixed. Returns the canonical
27
+ * `0x`-form, or `null` when malformed or when either 33-byte half is not a valid
28
+ * compressed secp256k1 point.
29
+ */
30
+ export function parseMetaAddressValue(value) {
31
+ let v = value.trim();
32
+ if (v.startsWith(META_ADDRESS_VALUE_PREFIX)) {
33
+ v = v.slice(META_ADDRESS_VALUE_PREFIX.length).trim();
34
+ }
35
+ if (!v.startsWith("0x") && /^[0-9a-fA-F]{132}$/.test(v))
36
+ v = `0x${v}`;
37
+ if (!/^0x[0-9a-fA-F]{132}$/.test(v))
38
+ return null;
39
+ const viewHalf = v.slice(2, 68);
40
+ const spendHalf = v.slice(68, 134);
41
+ for (const half of [viewHalf, spendHalf]) {
42
+ if (!isCompressedSecp256k1Point(half))
43
+ return null;
44
+ }
45
+ return v.toLowerCase();
46
+ }
47
+ function isCompressedSecp256k1Point(hex66) {
48
+ const prefix = hex66.slice(0, 2);
49
+ if (prefix !== "02" && prefix !== "03")
50
+ return false;
51
+ try {
52
+ secp256k1.ProjectivePoint.fromHex(hex66).assertValidity();
53
+ return true;
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ /** True for a 20-byte `0x` EVM address. */
60
+ export function isEvmAddressInput(input) {
61
+ return /^0x[0-9a-fA-F]{40}$/.test(input);
62
+ }
63
+ /** True for a plausible Solana base58 pubkey (32 bytes ≈ 32–44 base58 chars). */
64
+ export function isSolanaPubkeyInput(input) {
65
+ return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(input);
66
+ }
67
+ /** True for an ENS name (`*.eth`). */
68
+ export function isEnsNameInput(input) {
69
+ return /^[^\s.]+(\.[^\s.]+)*\.eth$/i.test(input);
70
+ }
71
+ /** True for an SNS name (`*.sol`). */
72
+ export function isSnsNameInput(input) {
73
+ return /^[^\s.]+(\.[^\s.]+)*\.sol$/i.test(input);
74
+ }
75
+ /**
76
+ * True when `input` is a depth-1 subname of the ONS parent in force
77
+ * (`alice.opq.eth` for parent `opq.eth`; `alice.opqtest.eth` on testnet).
78
+ */
79
+ export function isOnsNameInput(input, parentName) {
80
+ const lower = input.toLowerCase();
81
+ const parent = parentName.toLowerCase();
82
+ if (!lower.endsWith(`.${parent}`))
83
+ return false;
84
+ const label = lower.slice(0, lower.length - parent.length - 1);
85
+ return /^[a-z0-9-]{1,63}$/.test(label) && !label.startsWith("-") && !label.endsWith("-");
86
+ }
87
+ /**
88
+ * Read the `com.opaque.meta` TXT record of a `.sol` domain through the injected
89
+ * SNS reader and validate it. Throws when no reader is configured, the record is
90
+ * unset, or the value is not a valid meta-address.
91
+ */
92
+ export async function resolveSnsMetaAddress(name, snsGetRecord) {
93
+ if (!snsGetRecord) {
94
+ throw new Error("Opaque: .sol resolution needs SNS access (pass `solana` to OpaqueClient.create for the " +
95
+ "bundled Records V2 TXT reader, or inject sns.getRecord).");
96
+ }
97
+ const value = await snsGetRecord(name, OPAQUE_META_RECORD_KEY);
98
+ if (!value) {
99
+ throw new Error(`Opaque: ${name} has no ${OPAQUE_META_RECORD_KEY} (TXT) record.`);
100
+ }
101
+ const meta = parseMetaAddressValue(value);
102
+ if (!meta) {
103
+ throw new Error(`Opaque: ${name}'s TXT record is not a valid 66-byte meta-address.`);
104
+ }
105
+ return meta;
106
+ }
107
+ /**
108
+ * Extract the `<cid>[/path]` from an `ipfs://` URI, an `/ipfs/` gateway path, or a
109
+ * bare CID (v0 `Qm…` base58 or v1 base32). Returns `null` when the input is not IPFS-shaped.
110
+ */
111
+ export function ipfsPathFromInput(input) {
112
+ let rest = null;
113
+ if (input.startsWith("ipfs://")) {
114
+ rest = input.slice("ipfs://".length).replace(/^ipfs\//, "");
115
+ }
116
+ else if (input.startsWith("/ipfs/")) {
117
+ rest = input.slice("/ipfs/".length);
118
+ }
119
+ else if (/^Qm[1-9A-HJ-NP-Za-km-z]{44}(\/.*)?$/.test(input) ||
120
+ /^b[a-z2-7]{30,}(\/.*)?$/.test(input)) {
121
+ rest = input;
122
+ }
123
+ if (!rest)
124
+ return null;
125
+ const cid = rest.split("/")[0];
126
+ const cidOk = /^Qm[1-9A-HJ-NP-Za-km-z]{44}$/.test(cid) || /^b[a-z2-7]{30,}$/.test(cid);
127
+ return cidOk ? rest : null;
128
+ }
129
+ /**
130
+ * Pull a meta-address out of a DID document (or any JSON object) fetched from IPFS.
131
+ * Accepted shapes, in order:
132
+ * 1. a `service` entry of type `OpaqueStealthMetaAddress` whose `serviceEndpoint`
133
+ * carries the value (W3C DID style)
134
+ * 2. a top-level `com.opaque.meta` or `opaqueMetaAddress` field
135
+ * Values may be `st:opq:`-prefixed; both halves are point-validated.
136
+ */
137
+ export function extractMetaAddressFromDidDocument(doc) {
138
+ if (doc == null || typeof doc !== "object")
139
+ return null;
140
+ const d = doc;
141
+ const services = Array.isArray(d.service) ? d.service : [];
142
+ for (const s of services) {
143
+ if (s == null || typeof s !== "object")
144
+ continue;
145
+ const svc = s;
146
+ if (svc.type !== "OpaqueStealthMetaAddress")
147
+ continue;
148
+ const endpoint = svc.serviceEndpoint;
149
+ if (typeof endpoint === "string") {
150
+ const meta = parseMetaAddressValue(endpoint);
151
+ if (meta)
152
+ return meta;
153
+ }
154
+ }
155
+ for (const key of [OPAQUE_META_RECORD_KEY, "opaqueMetaAddress"]) {
156
+ const v = d[key];
157
+ if (typeof v === "string") {
158
+ const meta = parseMetaAddressValue(v);
159
+ if (meta)
160
+ return meta;
161
+ }
162
+ }
163
+ return null;
164
+ }
165
+ /**
166
+ * Fetch a DID document from IPFS via the configured gateways (tried in order) and
167
+ * extract its meta-address. Throws when every gateway fails or no valid meta-address
168
+ * is present in the document.
169
+ */
170
+ export async function resolveIpfsDidMetaAddress(cidPath, transports = {}) {
171
+ const gateways = transports.ipfsGateways ?? DEFAULT_IPFS_GATEWAYS;
172
+ const fetchFn = transports.fetchFn ?? globalThis.fetch;
173
+ if (!fetchFn) {
174
+ throw new Error("Opaque: no fetch implementation available for IPFS resolution; pass transports.fetchFn.");
175
+ }
176
+ let lastError = null;
177
+ for (const gateway of gateways) {
178
+ const url = `${gateway.replace(/\/$/, "")}/ipfs/${cidPath}`;
179
+ try {
180
+ const res = await fetchFn(url);
181
+ if (!res.ok) {
182
+ lastError = new Error(`${url} -> HTTP ${res.status}`);
183
+ continue;
184
+ }
185
+ const doc = await res.json();
186
+ const meta = extractMetaAddressFromDidDocument(doc);
187
+ if (!meta) {
188
+ throw new Error(`Opaque: DID document at ipfs://${cidPath} carries no valid com.opaque.meta meta-address.`);
189
+ }
190
+ return meta;
191
+ }
192
+ catch (e) {
193
+ if (e instanceof Error && e.message.includes("com.opaque.meta"))
194
+ throw e;
195
+ lastError = e;
196
+ }
197
+ }
198
+ throw new Error(`Opaque: failed to fetch DID document ipfs://${cidPath} from ${gateways.length} gateway(s): ${String(lastError)}`);
199
+ }
200
+ /**
201
+ * Read the `com.opaque.meta` text record for an ENS name through the injected reader
202
+ * and validate it. Throws when the record is unset or invalid. The on-chain registry
203
+ * stays authoritative on conflict (CSAP §2.9) — callers who also hold a registry entry
204
+ * for the name's address SHOULD prefer that entry.
205
+ */
206
+ export async function resolveEnsMetaAddress(name, transports) {
207
+ if (!transports.ensGetText) {
208
+ throw new Error("Opaque: ENS resolution needs an ens.getText reader (pass `ens` to OpaqueClient.create " +
209
+ "or transports.ensGetText). An ENS-capable RPC (mainnet/Sepolia) is required.");
210
+ }
211
+ const value = await transports.ensGetText(name, OPAQUE_META_RECORD_KEY);
212
+ if (!value) {
213
+ throw new Error(`Opaque: ${name} has no ${OPAQUE_META_RECORD_KEY} text record.`);
214
+ }
215
+ const meta = parseMetaAddressValue(value);
216
+ if (!meta) {
217
+ throw new Error(`Opaque: ${name}'s ${OPAQUE_META_RECORD_KEY} record is not a valid 66-byte meta-address.`);
218
+ }
219
+ return meta;
220
+ }
221
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Unified signer abstraction (Phase 2.5): one adapter shape over EIP-1193
3
+ * (`personal_sign`) and Solana wallet-adapter (`signMessage`), so integrators hand
4
+ * {@link OpaqueClient.fromWallet} whatever wallet they have and get a fully wired
5
+ * client back — setup-signature prompt, key derivation, and per-chain write signers.
6
+ */
7
+ import type { Address, EIP1193Provider, Hex, WalletClient } from "viem";
8
+ import type { PublicKey, Transaction } from "@solana/web3.js";
9
+ /** An Ethereum wallet in unified shape: EIP-1193 provider or a pre-built viem WalletClient. */
10
+ export interface EvmUnifiedSigner {
11
+ chain: "ethereum";
12
+ /** Account that signs the setup message and transactions. */
13
+ address: Address;
14
+ /** EIP-1193 provider (`window.ethereum`, a connector bridge, …). */
15
+ provider?: EIP1193Provider;
16
+ /** Alternative to `provider`: a viem WalletClient (e.g. wagmi's, or `privateKeyToAccount`). */
17
+ walletClient?: WalletClient;
18
+ }
19
+ /** A Solana wallet in unified shape (wallet-adapter compatible). */
20
+ export interface SolanaUnifiedSigner {
21
+ chain: "solana";
22
+ publicKey: PublicKey | string;
23
+ /** wallet-adapter `signMessage`; required only when this wallet derives the keys. */
24
+ signMessage?: (message: Uint8Array) => Promise<Uint8Array>;
25
+ /** wallet-adapter `signTransaction`; required for Solana writes. */
26
+ signTransaction?: (transaction: Transaction) => Promise<Transaction>;
27
+ }
28
+ /** One wallet, either chain — the single shape integrators pass around. */
29
+ export type UnifiedSigner = EvmUnifiedSigner | SolanaUnifiedSigner;
30
+ /**
31
+ * Prompt `signer` for the canonical {@link SETUP_MESSAGE} signature used as HKDF
32
+ * entropy for the viewing/spending keys. Ethereum signs via `personal_sign`
33
+ * (WalletClient when given, raw EIP-1193 otherwise); Solana signs the UTF-8 bytes
34
+ * via wallet-adapter `signMessage`. The signature never goes on-chain.
35
+ */
36
+ export declare function requestSetupSignature(signer: UnifiedSigner): Promise<Hex>;
37
+ /** Pick the first signer for a chain out of a one-or-many `wallets` argument. */
38
+ export declare function selectSigner<C extends UnifiedSigner["chain"]>(wallets: UnifiedSigner | UnifiedSigner[], chain: C): Extract<UnifiedSigner, {
39
+ chain: C;
40
+ }> | undefined;
41
+ //# sourceMappingURL=signer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../src/signer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAExE,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAG9D,+FAA+F;AAC/F,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,UAAU,CAAC;IAClB,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAC;IACjB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,+FAA+F;IAC/F,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,oEAAoE;AACpE,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,QAAQ,CAAC;IAChB,SAAS,EAAE,SAAS,GAAG,MAAM,CAAC;IAC9B,qFAAqF;IACrF,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3D,oEAAoE;IACpE,eAAe,CAAC,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;CACtE;AAED,2EAA2E;AAC3E,MAAM,MAAM,aAAa,GAAG,gBAAgB,GAAG,mBAAmB,CAAC;AAEnE;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,CAgC/E;AAED,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,CAAC,SAAS,aAAa,CAAC,OAAO,CAAC,EAC3D,OAAO,EAAE,aAAa,GAAG,aAAa,EAAE,EACxC,KAAK,EAAE,CAAC,GACP,OAAO,CAAC,aAAa,EAAE;IAAE,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC,GAAG,SAAS,CAKlD"}
package/dist/signer.js ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Unified signer abstraction (Phase 2.5): one adapter shape over EIP-1193
3
+ * (`personal_sign`) and Solana wallet-adapter (`signMessage`), so integrators hand
4
+ * {@link OpaqueClient.fromWallet} whatever wallet they have and get a fully wired
5
+ * client back — setup-signature prompt, key derivation, and per-chain write signers.
6
+ */
7
+ import { stringToHex } from "viem";
8
+ import { SETUP_MESSAGE } from "./crypto/dksap.js";
9
+ /**
10
+ * Prompt `signer` for the canonical {@link SETUP_MESSAGE} signature used as HKDF
11
+ * entropy for the viewing/spending keys. Ethereum signs via `personal_sign`
12
+ * (WalletClient when given, raw EIP-1193 otherwise); Solana signs the UTF-8 bytes
13
+ * via wallet-adapter `signMessage`. The signature never goes on-chain.
14
+ */
15
+ export async function requestSetupSignature(signer) {
16
+ if (signer.chain === "ethereum") {
17
+ if (signer.walletClient) {
18
+ return (await signer.walletClient.signMessage({
19
+ account: signer.walletClient.account ?? signer.address,
20
+ message: SETUP_MESSAGE,
21
+ }));
22
+ }
23
+ if (signer.provider) {
24
+ return (await signer.provider.request({
25
+ method: "personal_sign",
26
+ params: [stringToHex(SETUP_MESSAGE), signer.address],
27
+ }));
28
+ }
29
+ throw new Error("Opaque: Ethereum unified signer needs a `provider` (EIP-1193) or `walletClient`.");
30
+ }
31
+ if (signer.chain === "solana") {
32
+ if (!signer.signMessage) {
33
+ throw new Error("Opaque: Solana unified signer needs `signMessage` to derive keys (or pass a cached walletSignature).");
34
+ }
35
+ const sig = await signer.signMessage(new TextEncoder().encode(SETUP_MESSAGE));
36
+ return (`0x${Array.from(sig)
37
+ .map((b) => b.toString(16).padStart(2, "0"))
38
+ .join("")}`);
39
+ }
40
+ throw new Error(`Opaque: unsupported unified signer chain "${signer.chain}".`);
41
+ }
42
+ /** Pick the first signer for a chain out of a one-or-many `wallets` argument. */
43
+ export function selectSigner(wallets, chain) {
44
+ const list = Array.isArray(wallets) ? wallets : [wallets];
45
+ return list.find((w) => w.chain === chain);
46
+ }
47
+ //# sourceMappingURL=signer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signer.js","sourceRoot":"","sources":["../src/signer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,MAAM,CAAC;AAEnC,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AA0BlD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,MAAqB;IAC/D,IAAI,MAAM,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QAChC,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACxB,OAAO,CAAC,MAAM,MAAM,CAAC,YAAY,CAAC,WAAW,CAAC;gBAC5C,OAAO,EAAE,MAAM,CAAC,YAAY,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO;gBACtD,OAAO,EAAE,aAAa;aACvB,CAAC,CAAQ,CAAC;QACb,CAAC;QACD,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACpB,OAAO,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;gBACpC,MAAM,EAAE,eAAe;gBACvB,MAAM,EAAE,CAAC,WAAW,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;aACrD,CAAC,CAAQ,CAAC;QACb,CAAC;QACD,MAAM,IAAI,KAAK,CACb,kFAAkF,CACnF,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACb,sGAAsG,CACvG,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC;QAC9E,OAAO,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;aACzB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;aAC3C,IAAI,CAAC,EAAE,CAAC,EAAE,CAAQ,CAAC;IACxB,CAAC;IACD,MAAM,IAAI,KAAK,CACb,6CAA8C,MAA4B,CAAC,KAAK,IAAI,CACrF,CAAC;AACJ,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,YAAY,CAC1B,OAAwC,EACxC,KAAQ;IAER,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC1D,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAE5B,CAAC;AAChB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opaquecash/opaque",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Unified Opaque SDK client: config, indexer announcements, announce payloads, balances, traits",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -11,7 +11,11 @@
11
11
  "import": "./dist/index.js"
12
12
  }
13
13
  },
14
- "files": ["dist", "API.md", "README.md"],
14
+ "files": [
15
+ "dist",
16
+ "API.md",
17
+ "README.md"
18
+ ],
15
19
  "scripts": {
16
20
  "build": "tsc -p tsconfig.json",
17
21
  "clean": "rm -rf dist"
@@ -19,14 +23,30 @@
19
23
  "dependencies": {
20
24
  "@noble/curves": "^1.6.0",
21
25
  "@noble/hashes": "^1.5.0",
22
- "@opaquecash/psr-chain": "0.1.0",
23
- "@opaquecash/psr-core": "0.1.0",
24
- "@opaquecash/psr-prover": "0.1.0",
25
- "@opaquecash/stealth-balance": "0.1.0",
26
- "@opaquecash/stealth-chain": "0.1.0",
27
- "@opaquecash/stealth-core": "0.1.0",
28
- "@opaquecash/stealth-wasm": "0.1.0",
26
+ "@opaquecash/adapter": "0.2.0",
27
+ "@opaquecash/deployments": "0.2.0",
28
+ "@opaquecash/psr-chain": "0.2.0",
29
+ "@opaquecash/psr-chain-solana": "0.2.0",
30
+ "@opaquecash/psr-core": "0.2.0",
31
+ "@opaquecash/psr-prover": "0.2.0",
32
+ "@opaquecash/stealth-balance": "0.2.0",
33
+ "@opaquecash/stealth-chain": "0.2.0",
34
+ "@opaquecash/stealth-chain-solana": "0.2.0",
35
+ "@opaquecash/stealth-core": "0.2.0",
36
+ "@opaquecash/stealth-wasm": "0.2.0",
37
+ "@opaquecash/uab": "0.2.0",
38
+ "@solana/web3.js": "^1.98.4",
29
39
  "viem": "^2.21.0"
30
40
  },
31
- "sideEffects": false
41
+ "sideEffects": false,
42
+ "license": "Apache-2.0",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/opaquecash/sdk.git",
46
+ "directory": "packages/opaque"
47
+ },
48
+ "homepage": "https://docs.opaque.cash",
49
+ "publishConfig": {
50
+ "access": "public"
51
+ }
32
52
  }