@obelyzk/sdk 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +481 -0
- package/bin/bitsage-demo.ts +12 -0
- package/dist/chunk-3D3DPNVV.mjs +700 -0
- package/dist/chunk-CGPFHXUF.mjs +701 -0
- package/dist/chunk-F3E66KH7.mjs +683 -0
- package/dist/chunk-KRCKY3II.mjs +5857 -0
- package/dist/chunk-LXJT3QK6.mjs +5950 -0
- package/dist/chunk-O2PF7VJA.mjs +700 -0
- package/dist/index.d.mts +868 -0
- package/dist/index.d.ts +868 -0
- package/dist/index.js +7445 -0
- package/dist/index.mjs +853 -0
- package/dist/obelysk/index.d.mts +567 -0
- package/dist/obelysk/index.d.ts +567 -0
- package/dist/obelysk/index.js +1760 -0
- package/dist/obelysk/index.mjs +1449 -0
- package/dist/privacy/index.d.mts +408 -0
- package/dist/privacy/index.d.ts +408 -0
- package/dist/privacy/index.js +735 -0
- package/dist/privacy/index.mjs +44 -0
- package/dist/react/index.d.mts +1367 -0
- package/dist/react/index.d.ts +1367 -0
- package/dist/react/index.js +8442 -0
- package/dist/react/index.mjs +2600 -0
- package/dist/tee-BKXj7gQs.d.mts +4749 -0
- package/dist/tee-BKXj7gQs.d.ts +4749 -0
- package/dist/tee-BslKx4iU.d.mts +4749 -0
- package/dist/tee-BslKx4iU.d.ts +4749 -0
- package/dist/tee-CiR0hpfm.d.mts +4794 -0
- package/dist/tee-CiR0hpfm.d.ts +4794 -0
- package/package.json +101 -0
|
@@ -0,0 +1,1449 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CURVE_ORDER,
|
|
3
|
+
FIELD_PRIME,
|
|
4
|
+
GENERATOR_G,
|
|
5
|
+
GENERATOR_H,
|
|
6
|
+
ObelyskPrivacy,
|
|
7
|
+
ecAdd,
|
|
8
|
+
ecMul,
|
|
9
|
+
invMod,
|
|
10
|
+
randomScalar
|
|
11
|
+
} from "../chunk-O2PF7VJA.mjs";
|
|
12
|
+
|
|
13
|
+
// src/obelysk/client.ts
|
|
14
|
+
import { RpcProvider } from "starknet";
|
|
15
|
+
|
|
16
|
+
// src/obelysk/privacyPool.ts
|
|
17
|
+
import { CallData, hash as hash2 } from "starknet";
|
|
18
|
+
|
|
19
|
+
// src/obelysk/contracts.ts
|
|
20
|
+
var MAINNET_RPC = "https://starknet-mainnet.public.blastapi.io/rpc/v0_7";
|
|
21
|
+
var SEPOLIA_RPC = "https://starknet-sepolia.public.blastapi.io/rpc/v0_7";
|
|
22
|
+
function getRpcUrl(network) {
|
|
23
|
+
return network === "mainnet" ? MAINNET_RPC : SEPOLIA_RPC;
|
|
24
|
+
}
|
|
25
|
+
var MAINNET_CONTRACTS = {
|
|
26
|
+
sage_token: "0x0098d563900789f934e610b67482ae58793a2efc373ba3a45af94cdbf931c799",
|
|
27
|
+
confidential_transfer: "0x0673685bdb01fbf57c390ec2c0d893e7c77316cdea315b0fbfbc85b9a9a979d2",
|
|
28
|
+
privacy_router: "0x00f3fd871ba1b5b176270a7eb9e222c964c50fa8a31234394ea00ce70bfbdfbd",
|
|
29
|
+
prover_staking: "0x07d2ecff4a4d7ca6c75df367d8dbc7cc12ea583f88813f7020832a7cf7f293e3",
|
|
30
|
+
dark_pool: "0x0230b5822556f0d9afca7b02f01e37cb9cf2a7e8d590a9020e9bbca183ea7727",
|
|
31
|
+
shielded_swap: "0x05a7f8a6ab74ee6ab41169118ca2ea21070dc6594bae5a39f5bb9ac50629725b",
|
|
32
|
+
stealth_registry: "0x077ee4c38201b4e45b643f4af56ff6daf780260e9c8a281f3536fb711afcaea8",
|
|
33
|
+
otc_orderbook: "0x04165f8fe590e94d7f12e77af72577123b24cbd01101a62b53c3e119fb8bb119",
|
|
34
|
+
sumcheck_verifier: "0x05071a9428cba9a7e4cbcbf3cee2d16caaaf2b6b9d270a8fb6089a4a97d330e8",
|
|
35
|
+
vm31_pool: "0x0230eb355e54a98b4511d86585d45d6a5b9075d0ec254877485047b6d651400d",
|
|
36
|
+
vm31_bridge: "0x048f481c4ada306f5b62d7d223ddd0cf8055a423ffa2b278b3ff767ca9c0356c",
|
|
37
|
+
privacy_pool_wbtc: "0x030fcfd4ae4f022e720e52f54359258a02517e11701c153ae46ab2cf10d5e5e2",
|
|
38
|
+
privacy_pool_sage: "0x0224977344d123eb5c20fd088f15b62d0541f8282f4a23dd87bdf9839aac724f",
|
|
39
|
+
privacy_pool_eth: "0x06d0b41c96809796faa02a5eac2f74e090effd09ccab7274054b90aa671e82b5",
|
|
40
|
+
privacy_pool_strk: "0x02c348e89b355691ba5e4ece681fd6b497f8ab2ba670fa5842208b251a3c9cf1",
|
|
41
|
+
privacy_pool_usdc: "0x05d36d7fd19d094ee0fd454e461061d68eb9f4fd0b241e2d1c94320b46d4d59b",
|
|
42
|
+
tokens: {
|
|
43
|
+
eth: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
|
|
44
|
+
strk: "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d",
|
|
45
|
+
usdc: "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8",
|
|
46
|
+
wbtc: "0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac",
|
|
47
|
+
sage: "0x0098d563900789f934e610b67482ae58793a2efc373ba3a45af94cdbf931c799"
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var SEPOLIA_CONTRACTS = {
|
|
51
|
+
sage_token: "0x072349097c8a802e7f66dc96b95aca84e4d78ddad22014904076c76293a99850",
|
|
52
|
+
prover_staking: "0x3287a0af5ab2d74fbf968204ce2291adde008d645d42bc363cb741ebfa941b",
|
|
53
|
+
privacy_router: "0x7d1a6c242a4f0573696e117790f431fd60518a000b85fe5ee507456049ffc53",
|
|
54
|
+
tokens: {
|
|
55
|
+
eth: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
|
|
56
|
+
strk: "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d"
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
function getContracts(network) {
|
|
60
|
+
return network === "mainnet" ? MAINNET_CONTRACTS : SEPOLIA_CONTRACTS;
|
|
61
|
+
}
|
|
62
|
+
var MAINNET_TOKENS = {
|
|
63
|
+
eth: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
|
|
64
|
+
strk: "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d",
|
|
65
|
+
usdc: "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8",
|
|
66
|
+
wbtc: "0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac",
|
|
67
|
+
sage: "0x0098d563900789f934e610b67482ae58793a2efc373ba3a45af94cdbf931c799"
|
|
68
|
+
};
|
|
69
|
+
var MAINNET_PRIVACY_POOLS = {
|
|
70
|
+
eth: "0x06d0b41c96809796faa02a5eac2f74e090effd09ccab7274054b90aa671e82b5",
|
|
71
|
+
strk: "0x02c348e89b355691ba5e4ece681fd6b497f8ab2ba670fa5842208b251a3c9cf1",
|
|
72
|
+
usdc: "0x05d36d7fd19d094ee0fd454e461061d68eb9f4fd0b241e2d1c94320b46d4d59b",
|
|
73
|
+
wbtc: "0x030fcfd4ae4f022e720e52f54359258a02517e11701c153ae46ab2cf10d5e5e2",
|
|
74
|
+
sage: "0x0224977344d123eb5c20fd088f15b62d0541f8282f4a23dd87bdf9839aac724f"
|
|
75
|
+
};
|
|
76
|
+
var TOKEN_DECIMALS = {
|
|
77
|
+
eth: 18,
|
|
78
|
+
strk: 18,
|
|
79
|
+
usdc: 6,
|
|
80
|
+
wbtc: 8,
|
|
81
|
+
sage: 18
|
|
82
|
+
};
|
|
83
|
+
var DARKPOOL_ASSET_IDS = {
|
|
84
|
+
eth: "0x2",
|
|
85
|
+
strk: "0x3",
|
|
86
|
+
usdc: "0x4",
|
|
87
|
+
wbtc: "0x0",
|
|
88
|
+
sage: "0x1"
|
|
89
|
+
};
|
|
90
|
+
var VM31_ASSET_IDS = {
|
|
91
|
+
wbtc: 0,
|
|
92
|
+
sage: 1,
|
|
93
|
+
eth: 2,
|
|
94
|
+
strk: 3,
|
|
95
|
+
usdc: 4
|
|
96
|
+
};
|
|
97
|
+
function parseAmount(amount, token) {
|
|
98
|
+
const decimals = TOKEN_DECIMALS[token.toLowerCase()] ?? 18;
|
|
99
|
+
const parts = String(amount).split(".");
|
|
100
|
+
const whole = BigInt(parts[0] || "0");
|
|
101
|
+
const frac = (parts[1] || "").padEnd(decimals, "0").slice(0, decimals);
|
|
102
|
+
return whole * 10n ** BigInt(decimals) + BigInt(frac);
|
|
103
|
+
}
|
|
104
|
+
function formatAmount(amount, token) {
|
|
105
|
+
const decimals = TOKEN_DECIMALS[token.toLowerCase()] ?? 18;
|
|
106
|
+
const divisor = 10n ** BigInt(decimals);
|
|
107
|
+
const whole = amount / divisor;
|
|
108
|
+
const frac = (amount % divisor).toString().padStart(decimals, "0");
|
|
109
|
+
const trimmed = frac.replace(/0+$/, "") || "0";
|
|
110
|
+
return trimmed === "0" ? whole.toString() : `${whole}.${trimmed}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/obelysk/crypto.ts
|
|
114
|
+
import { hash } from "starknet";
|
|
115
|
+
function mod(a, m) {
|
|
116
|
+
const r = a % m;
|
|
117
|
+
return r < 0n ? r + m : r;
|
|
118
|
+
}
|
|
119
|
+
var modInverse = invMod;
|
|
120
|
+
var ecAdd2 = ecAdd;
|
|
121
|
+
var ecMul2 = ecMul;
|
|
122
|
+
var randomScalar2 = randomScalar;
|
|
123
|
+
function pedersenCommit(value, blinding) {
|
|
124
|
+
return ecAdd2(
|
|
125
|
+
ecMul2(mod(value, CURVE_ORDER), GENERATOR_G),
|
|
126
|
+
ecMul2(mod(blinding, CURVE_ORDER), GENERATOR_H)
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
function elgamalEncrypt(amount, publicKey) {
|
|
130
|
+
const r = randomScalar2();
|
|
131
|
+
const c1 = ecMul2(r, GENERATOR_G);
|
|
132
|
+
const mH = ecMul2(amount, GENERATOR_H);
|
|
133
|
+
const rPK = ecMul2(r, publicKey);
|
|
134
|
+
const c2 = ecAdd2(mH, rPK);
|
|
135
|
+
return { c1, c2, randomness: r };
|
|
136
|
+
}
|
|
137
|
+
function createEncryptionProof(r, c1) {
|
|
138
|
+
const k = randomScalar2();
|
|
139
|
+
const commitment = ecMul2(k, GENERATOR_G);
|
|
140
|
+
const challenge = BigInt(hash.computePoseidonHashOnElements([
|
|
141
|
+
commitment.x.toString(),
|
|
142
|
+
commitment.y.toString(),
|
|
143
|
+
c1.x.toString(),
|
|
144
|
+
c1.y.toString()
|
|
145
|
+
]));
|
|
146
|
+
const response = mod(k - challenge * r, CURVE_ORDER);
|
|
147
|
+
return { commitment, challenge, response };
|
|
148
|
+
}
|
|
149
|
+
function createAEHint(amount, sharedSecret) {
|
|
150
|
+
const encryptedAmount = mod(amount + sharedSecret, FIELD_PRIME);
|
|
151
|
+
const mac = BigInt(hash.computePoseidonHash(sharedSecret.toString(), encryptedAmount.toString()));
|
|
152
|
+
return { encryptedAmount, mac };
|
|
153
|
+
}
|
|
154
|
+
function commitmentToHash(point) {
|
|
155
|
+
return hash.computePoseidonHash(point.x.toString(), point.y.toString());
|
|
156
|
+
}
|
|
157
|
+
function deriveNullifier(nullifierSecret, commitmentHash) {
|
|
158
|
+
return hash.computePoseidonHash(nullifierSecret.toString(), commitmentHash);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/obelysk/privacyPool.ts
|
|
162
|
+
function generateRangeProof32(value, blinding) {
|
|
163
|
+
if (value < 0n || value >= 1n << 32n) {
|
|
164
|
+
throw new Error("Value out of 32-bit range");
|
|
165
|
+
}
|
|
166
|
+
const value_bits = [];
|
|
167
|
+
const blinding_bits = [];
|
|
168
|
+
const bit_commitments = [];
|
|
169
|
+
let blindingSum = 0n;
|
|
170
|
+
for (let i = 0; i < 32; i++) {
|
|
171
|
+
const bit = value >> BigInt(i) & 1n;
|
|
172
|
+
value_bits.push(bit);
|
|
173
|
+
let r_i;
|
|
174
|
+
if (i === 31) {
|
|
175
|
+
r_i = mod(blinding - blindingSum, CURVE_ORDER);
|
|
176
|
+
} else {
|
|
177
|
+
r_i = randomScalar2();
|
|
178
|
+
blindingSum = mod(blindingSum + r_i * (1n << BigInt(i)), CURVE_ORDER);
|
|
179
|
+
}
|
|
180
|
+
blinding_bits.push(r_i);
|
|
181
|
+
bit_commitments.push(pedersenCommit(bit, r_i));
|
|
182
|
+
}
|
|
183
|
+
return { value_bits, blinding_bits, bit_commitments };
|
|
184
|
+
}
|
|
185
|
+
function serializeRangeProof32(proof) {
|
|
186
|
+
const calldata = [];
|
|
187
|
+
for (const c of proof.bit_commitments) {
|
|
188
|
+
calldata.push("0x" + c.x.toString(16));
|
|
189
|
+
calldata.push("0x" + c.y.toString(16));
|
|
190
|
+
}
|
|
191
|
+
for (const r of proof.blinding_bits) {
|
|
192
|
+
calldata.push("0x" + r.toString(16));
|
|
193
|
+
}
|
|
194
|
+
return calldata;
|
|
195
|
+
}
|
|
196
|
+
function deriveNullifier2(nullifierSecret, leafIndex) {
|
|
197
|
+
return hash2.computePoseidonHash(nullifierSecret.toString(), leafIndex.toString());
|
|
198
|
+
}
|
|
199
|
+
var PrivacyPoolClient = class {
|
|
200
|
+
constructor(obelysk) {
|
|
201
|
+
this.obelysk = obelysk;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Deposit tokens into a privacy pool.
|
|
205
|
+
*
|
|
206
|
+
* Creates a Pedersen commitment and range proof, then calls the
|
|
207
|
+
* privacy pool contract's deposit function.
|
|
208
|
+
*
|
|
209
|
+
* @returns DepositResult with the note (SAVE THIS for withdrawal)
|
|
210
|
+
*/
|
|
211
|
+
async deposit(params) {
|
|
212
|
+
const account = this.obelysk.requireAccount();
|
|
213
|
+
const poolAddress = this.obelysk.getPrivacyPoolAddress(params.token);
|
|
214
|
+
const tokenAddress = this.obelysk.getTokenAddress(params.token);
|
|
215
|
+
const amountWei = parseAmount(params.amount, params.token);
|
|
216
|
+
const blinding = randomScalar2();
|
|
217
|
+
const nullifierSecret = randomScalar2();
|
|
218
|
+
const commitment = pedersenCommit(amountWei, blinding);
|
|
219
|
+
const commitHash = commitmentToHash(commitment);
|
|
220
|
+
const rangeProof = generateRangeProof32(amountWei, blinding);
|
|
221
|
+
const amountLow = amountWei & (1n << 128n) - 1n;
|
|
222
|
+
const amountHigh = amountWei >> 128n;
|
|
223
|
+
const calls = [
|
|
224
|
+
{
|
|
225
|
+
contractAddress: tokenAddress,
|
|
226
|
+
entrypoint: "approve",
|
|
227
|
+
calldata: CallData.compile({
|
|
228
|
+
spender: poolAddress,
|
|
229
|
+
amount: { low: amountLow.toString(), high: amountHigh.toString() }
|
|
230
|
+
})
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
contractAddress: poolAddress,
|
|
234
|
+
entrypoint: "deposit",
|
|
235
|
+
calldata: CallData.compile({
|
|
236
|
+
amount: { low: amountLow.toString(), high: amountHigh.toString() },
|
|
237
|
+
commitment_x: commitment.x.toString(),
|
|
238
|
+
commitment_y: commitment.y.toString(),
|
|
239
|
+
range_proof: serializeRangeProof32(rangeProof)
|
|
240
|
+
})
|
|
241
|
+
}
|
|
242
|
+
];
|
|
243
|
+
const result = await account.execute(calls);
|
|
244
|
+
await this.obelysk.provider.waitForTransaction(result.transaction_hash);
|
|
245
|
+
const note = {
|
|
246
|
+
token: params.token,
|
|
247
|
+
amount: amountWei.toString(),
|
|
248
|
+
blinding: blinding.toString(),
|
|
249
|
+
nullifierSecret: nullifierSecret.toString(),
|
|
250
|
+
commitmentX: commitment.x.toString(),
|
|
251
|
+
commitmentY: commitment.y.toString(),
|
|
252
|
+
commitmentHash: commitHash,
|
|
253
|
+
depositTxHash: result.transaction_hash,
|
|
254
|
+
createdAt: Date.now()
|
|
255
|
+
};
|
|
256
|
+
return { txHash: result.transaction_hash, note, commitmentHash: commitHash };
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Withdraw tokens from a privacy pool.
|
|
260
|
+
*
|
|
261
|
+
* Requires the original note, a Merkle proof of inclusion,
|
|
262
|
+
* and the leaf index. The recipient can be any address.
|
|
263
|
+
*/
|
|
264
|
+
async withdraw(params) {
|
|
265
|
+
const account = this.obelysk.requireAccount();
|
|
266
|
+
const poolAddress = this.obelysk.getPrivacyPoolAddress(params.note.token);
|
|
267
|
+
const leafIndex = params.leafIndex ?? params.note.leafIndex;
|
|
268
|
+
if (leafIndex === void 0) throw new Error("leafIndex required for withdrawal");
|
|
269
|
+
const nullifier = deriveNullifier2(BigInt(params.note.nullifierSecret), leafIndex);
|
|
270
|
+
const amountWei = BigInt(params.note.amount);
|
|
271
|
+
const amountLow = amountWei & (1n << 128n) - 1n;
|
|
272
|
+
const amountHigh = amountWei >> 128n;
|
|
273
|
+
const calls = [
|
|
274
|
+
{
|
|
275
|
+
contractAddress: poolAddress,
|
|
276
|
+
entrypoint: "withdraw",
|
|
277
|
+
calldata: CallData.compile({
|
|
278
|
+
amount: { low: amountLow.toString(), high: amountHigh.toString() },
|
|
279
|
+
nullifier,
|
|
280
|
+
recipient: params.recipient,
|
|
281
|
+
commitment_x: params.note.commitmentX,
|
|
282
|
+
commitment_y: params.note.commitmentY,
|
|
283
|
+
blinding: params.note.blinding,
|
|
284
|
+
merkle_proof: params.merkleProof,
|
|
285
|
+
leaf_index: leafIndex
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
];
|
|
289
|
+
const result = await account.execute(calls);
|
|
290
|
+
await this.obelysk.provider.waitForTransaction(result.transaction_hash);
|
|
291
|
+
return {
|
|
292
|
+
txHash: result.transaction_hash,
|
|
293
|
+
amount: formatAmount(amountWei, params.note.token),
|
|
294
|
+
token: params.note.token
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Read the current global deposit Merkle root of a privacy pool.
|
|
299
|
+
*/
|
|
300
|
+
async getMerkleRoot(token) {
|
|
301
|
+
const poolAddress = this.obelysk.getPrivacyPoolAddress(token);
|
|
302
|
+
const result = await this.obelysk.provider.callContract({
|
|
303
|
+
contractAddress: poolAddress,
|
|
304
|
+
entrypoint: "get_global_deposit_root",
|
|
305
|
+
calldata: []
|
|
306
|
+
});
|
|
307
|
+
return result[0];
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Get pool stats: (total_deposits, total_withdrawals, total_deposited_amount, total_withdrawn_amount)
|
|
311
|
+
*/
|
|
312
|
+
async getPoolStats(token) {
|
|
313
|
+
const poolAddress = this.obelysk.getPrivacyPoolAddress(token);
|
|
314
|
+
const result = await this.obelysk.provider.callContract({
|
|
315
|
+
contractAddress: poolAddress,
|
|
316
|
+
entrypoint: "get_pp_stats",
|
|
317
|
+
calldata: []
|
|
318
|
+
});
|
|
319
|
+
return {
|
|
320
|
+
totalDeposits: Number(BigInt(result[0])),
|
|
321
|
+
totalWithdrawals: Number(BigInt(result[1])),
|
|
322
|
+
totalDeposited: BigInt(result[2]),
|
|
323
|
+
totalWithdrawn: BigInt(result[3])
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Get the current leaf count (number of deposits) in a privacy pool.
|
|
328
|
+
*/
|
|
329
|
+
async getLeafCount(token) {
|
|
330
|
+
const stats = await this.getPoolStats(token);
|
|
331
|
+
return stats.totalDeposits;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Check if a nullifier has been used (prevents double-withdrawal).
|
|
335
|
+
*/
|
|
336
|
+
async isNullifierUsed(token, nullifier) {
|
|
337
|
+
const poolAddress = this.obelysk.getPrivacyPoolAddress(token);
|
|
338
|
+
try {
|
|
339
|
+
const result = await this.obelysk.provider.callContract({
|
|
340
|
+
contractAddress: poolAddress,
|
|
341
|
+
entrypoint: "is_pp_nullifier_used",
|
|
342
|
+
calldata: [nullifier]
|
|
343
|
+
});
|
|
344
|
+
return BigInt(result[0]) !== 0n;
|
|
345
|
+
} catch {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Check if a deposit commitment is valid.
|
|
351
|
+
*/
|
|
352
|
+
async isDepositValid(token, commitment) {
|
|
353
|
+
const poolAddress = this.obelysk.getPrivacyPoolAddress(token);
|
|
354
|
+
try {
|
|
355
|
+
const result = await this.obelysk.provider.callContract({
|
|
356
|
+
contractAddress: poolAddress,
|
|
357
|
+
entrypoint: "is_pp_deposit_valid",
|
|
358
|
+
calldata: [commitment]
|
|
359
|
+
});
|
|
360
|
+
return BigInt(result[0]) !== 0n;
|
|
361
|
+
} catch {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Get fee info for a privacy pool (bps, recipient, accumulated).
|
|
367
|
+
*/
|
|
368
|
+
async getFeeInfo(token) {
|
|
369
|
+
const poolAddress = this.obelysk.getPrivacyPoolAddress(token);
|
|
370
|
+
const result = await this.obelysk.provider.callContract({
|
|
371
|
+
contractAddress: poolAddress,
|
|
372
|
+
entrypoint: "get_fee_info",
|
|
373
|
+
calldata: []
|
|
374
|
+
});
|
|
375
|
+
return {
|
|
376
|
+
feeBps: Number(BigInt(result[0])),
|
|
377
|
+
feeRecipient: result[1],
|
|
378
|
+
accumulated: BigInt(result[2])
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Fetch a Merkle proof for a given leaf index from on-chain data.
|
|
383
|
+
* Reconstructs the tree from deposit events.
|
|
384
|
+
*/
|
|
385
|
+
async fetchMerkleProof(token, leafIndex) {
|
|
386
|
+
const poolAddress = this.obelysk.getPrivacyPoolAddress(token);
|
|
387
|
+
const events = await this.obelysk.provider.getEvents({
|
|
388
|
+
address: poolAddress,
|
|
389
|
+
keys: [],
|
|
390
|
+
from_block: { block_number: 0 },
|
|
391
|
+
to_block: "latest",
|
|
392
|
+
chunk_size: 1e3
|
|
393
|
+
});
|
|
394
|
+
const leaves = [];
|
|
395
|
+
for (const event of events.events) {
|
|
396
|
+
if (event.data.length >= 1) {
|
|
397
|
+
leaves.push(event.data[0]);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (leafIndex >= leaves.length) {
|
|
401
|
+
throw new Error(`Leaf index ${leafIndex} out of range (${leaves.length} leaves)`);
|
|
402
|
+
}
|
|
403
|
+
const { proof, root } = buildMerkleProof(leaves, leafIndex);
|
|
404
|
+
return { proof, root };
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
function poseidonHash2(a, b) {
|
|
408
|
+
return hash2.computePoseidonHash(a, b);
|
|
409
|
+
}
|
|
410
|
+
function buildMerkleProof(leaves, leafIndex) {
|
|
411
|
+
if (leaves.length === 0) throw new Error("Empty tree");
|
|
412
|
+
let level = [...leaves];
|
|
413
|
+
const depth = Math.ceil(Math.log2(Math.max(level.length, 2)));
|
|
414
|
+
while (level.length < 1 << depth) {
|
|
415
|
+
level.push("0x0");
|
|
416
|
+
}
|
|
417
|
+
const proof = [];
|
|
418
|
+
let idx = leafIndex;
|
|
419
|
+
for (let d = 0; d < depth; d++) {
|
|
420
|
+
const siblingIdx = idx ^ 1;
|
|
421
|
+
proof.push(level[siblingIdx] ?? "0x0");
|
|
422
|
+
const nextLevel = [];
|
|
423
|
+
for (let i = 0; i < level.length; i += 2) {
|
|
424
|
+
nextLevel.push(poseidonHash2(level[i], level[i + 1]));
|
|
425
|
+
}
|
|
426
|
+
level = nextLevel;
|
|
427
|
+
idx >>= 1;
|
|
428
|
+
}
|
|
429
|
+
return { proof, root: level[0] };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/obelysk/darkPool.ts
|
|
433
|
+
import { CallData as CallData2, hash as hash3 } from "starknet";
|
|
434
|
+
function hashOrder(price, amount, side, giveAsset, wantAsset, salt) {
|
|
435
|
+
return hash3.computePoseidonHashOnElements([
|
|
436
|
+
"0x" + price.toString(16),
|
|
437
|
+
"0x" + amount.toString(16),
|
|
438
|
+
side.toString(),
|
|
439
|
+
giveAsset,
|
|
440
|
+
wantAsset,
|
|
441
|
+
"0x" + salt.toString(16)
|
|
442
|
+
]);
|
|
443
|
+
}
|
|
444
|
+
var DarkPoolClient = class {
|
|
445
|
+
constructor(obelysk) {
|
|
446
|
+
this.obelysk = obelysk;
|
|
447
|
+
}
|
|
448
|
+
get darkPoolAddress() {
|
|
449
|
+
const addr = this.obelysk.contracts.dark_pool;
|
|
450
|
+
if (!addr) throw new Error("DarkPool contract not configured for this network");
|
|
451
|
+
return addr;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Deposit tokens into the DarkPool for trading.
|
|
455
|
+
*
|
|
456
|
+
* Encrypts the amount using ElGamal and generates an AE hint
|
|
457
|
+
* for O(1) balance decryption.
|
|
458
|
+
*/
|
|
459
|
+
async deposit(params) {
|
|
460
|
+
const account = this.obelysk.requireAccount();
|
|
461
|
+
const tokenAddress = this.obelysk.getTokenAddress(params.token);
|
|
462
|
+
const assetId = DARKPOOL_ASSET_IDS[params.token.toLowerCase()];
|
|
463
|
+
if (!assetId) throw new Error(`Unknown DarkPool asset: ${params.token}`);
|
|
464
|
+
const amountWei = parseAmount(params.amount, params.token);
|
|
465
|
+
const amountLow = amountWei & (1n << 128n) - 1n;
|
|
466
|
+
const amountHigh = amountWei >> 128n;
|
|
467
|
+
const keyPair = this.obelysk.privacy.generateKeyPair();
|
|
468
|
+
const encrypted = this.obelysk.privacy.encrypt(amountWei, keyPair.publicKey);
|
|
469
|
+
const nonce = randomScalar2();
|
|
470
|
+
const sharedSecret = BigInt(hash3.computePoseidonHash(
|
|
471
|
+
keyPair.privateKey.toString(),
|
|
472
|
+
nonce.toString()
|
|
473
|
+
));
|
|
474
|
+
const { encryptedAmount, mac } = createAEHint(amountWei, sharedSecret);
|
|
475
|
+
const calls = [
|
|
476
|
+
{
|
|
477
|
+
contractAddress: tokenAddress,
|
|
478
|
+
entrypoint: "approve",
|
|
479
|
+
calldata: CallData2.compile({
|
|
480
|
+
spender: this.darkPoolAddress,
|
|
481
|
+
amount: { low: amountLow.toString(), high: amountHigh.toString() }
|
|
482
|
+
})
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
contractAddress: this.darkPoolAddress,
|
|
486
|
+
entrypoint: "deposit",
|
|
487
|
+
calldata: CallData2.compile({
|
|
488
|
+
asset: assetId,
|
|
489
|
+
amount: { low: amountLow.toString(), high: amountHigh.toString() },
|
|
490
|
+
encrypted_amount: {
|
|
491
|
+
c1_x: encrypted.c1.x.toString(),
|
|
492
|
+
c1_y: encrypted.c1.y.toString(),
|
|
493
|
+
c2_x: encrypted.c2.x.toString(),
|
|
494
|
+
c2_y: encrypted.c2.y.toString()
|
|
495
|
+
},
|
|
496
|
+
ae_hint: {
|
|
497
|
+
encrypted_amount: encryptedAmount.toString(),
|
|
498
|
+
nonce: nonce.toString(),
|
|
499
|
+
mac: mac.toString()
|
|
500
|
+
}
|
|
501
|
+
})
|
|
502
|
+
}
|
|
503
|
+
];
|
|
504
|
+
const result = await account.execute(calls);
|
|
505
|
+
await this.obelysk.provider.waitForTransaction(result.transaction_hash);
|
|
506
|
+
return { txHash: result.transaction_hash };
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Commit an order in the current commit phase.
|
|
510
|
+
*
|
|
511
|
+
* Returns order data that must be saved for the reveal phase.
|
|
512
|
+
*/
|
|
513
|
+
async commitOrder(params) {
|
|
514
|
+
const account = this.obelysk.requireAccount();
|
|
515
|
+
const [giveSymbol, wantSymbol] = params.pair.split("/");
|
|
516
|
+
const giveAsset = DARKPOOL_ASSET_IDS[giveSymbol.toLowerCase()];
|
|
517
|
+
const wantAsset = DARKPOOL_ASSET_IDS[wantSymbol.toLowerCase()];
|
|
518
|
+
if (!giveAsset || !wantAsset) throw new Error(`Unknown pair: ${params.pair}`);
|
|
519
|
+
const price = parseAmount(params.price, "eth");
|
|
520
|
+
const amount = parseAmount(params.amount, giveSymbol.toLowerCase());
|
|
521
|
+
const salt = randomScalar2();
|
|
522
|
+
const side = params.side === "sell" ? 1 : 0;
|
|
523
|
+
const commitHash = hashOrder(price, amount, side, giveAsset, wantAsset, salt);
|
|
524
|
+
const amountBlinding = randomScalar2();
|
|
525
|
+
const amountCommitment = pedersenCommit(amount, amountBlinding);
|
|
526
|
+
const epochInfo = await this.getEpochInfo();
|
|
527
|
+
const calls = [
|
|
528
|
+
{
|
|
529
|
+
contractAddress: this.darkPoolAddress,
|
|
530
|
+
entrypoint: "commit_order",
|
|
531
|
+
calldata: CallData2.compile({
|
|
532
|
+
order_hash: commitHash,
|
|
533
|
+
amount_commitment_x: amountCommitment.x.toString(),
|
|
534
|
+
amount_commitment_y: amountCommitment.y.toString()
|
|
535
|
+
})
|
|
536
|
+
}
|
|
537
|
+
];
|
|
538
|
+
const result = await account.execute(calls);
|
|
539
|
+
await this.obelysk.provider.waitForTransaction(result.transaction_hash);
|
|
540
|
+
let orderId = "0";
|
|
541
|
+
return {
|
|
542
|
+
orderId,
|
|
543
|
+
pair: params.pair,
|
|
544
|
+
side: params.side,
|
|
545
|
+
price: price.toString(),
|
|
546
|
+
amount: amount.toString(),
|
|
547
|
+
salt: salt.toString(),
|
|
548
|
+
amountBlinding: amountBlinding.toString(),
|
|
549
|
+
commitHash,
|
|
550
|
+
epoch: epochInfo.epoch
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Reveal a previously committed order in the reveal phase.
|
|
555
|
+
*/
|
|
556
|
+
async revealOrder(params) {
|
|
557
|
+
const account = this.obelysk.requireAccount();
|
|
558
|
+
const { orderData } = params;
|
|
559
|
+
const [giveSymbol, wantSymbol] = orderData.pair.split("/");
|
|
560
|
+
const calls = [
|
|
561
|
+
{
|
|
562
|
+
contractAddress: this.darkPoolAddress,
|
|
563
|
+
entrypoint: "reveal_order",
|
|
564
|
+
calldata: CallData2.compile({
|
|
565
|
+
order_id: orderData.orderId,
|
|
566
|
+
price: orderData.price,
|
|
567
|
+
amount: orderData.amount,
|
|
568
|
+
side: orderData.side === "sell" ? 1 : 0,
|
|
569
|
+
give_asset: DARKPOOL_ASSET_IDS[giveSymbol.toLowerCase()],
|
|
570
|
+
want_asset: DARKPOOL_ASSET_IDS[wantSymbol.toLowerCase()],
|
|
571
|
+
salt: orderData.salt,
|
|
572
|
+
amount_blinding: orderData.amountBlinding
|
|
573
|
+
})
|
|
574
|
+
}
|
|
575
|
+
];
|
|
576
|
+
const result = await account.execute(calls);
|
|
577
|
+
await this.obelysk.provider.waitForTransaction(result.transaction_hash);
|
|
578
|
+
return { txHash: result.transaction_hash };
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Get current epoch info from the contract.
|
|
582
|
+
*/
|
|
583
|
+
async getEpochInfo() {
|
|
584
|
+
const [epochResult, phaseResult] = await Promise.all([
|
|
585
|
+
this.obelysk.provider.callContract({
|
|
586
|
+
contractAddress: this.darkPoolAddress,
|
|
587
|
+
entrypoint: "get_current_epoch",
|
|
588
|
+
calldata: []
|
|
589
|
+
}),
|
|
590
|
+
this.obelysk.provider.callContract({
|
|
591
|
+
contractAddress: this.darkPoolAddress,
|
|
592
|
+
entrypoint: "get_epoch_phase",
|
|
593
|
+
calldata: []
|
|
594
|
+
})
|
|
595
|
+
]);
|
|
596
|
+
const epoch = Number(BigInt(epochResult[0]));
|
|
597
|
+
const phaseNum = Number(BigInt(phaseResult[0]));
|
|
598
|
+
const phases = ["Commit", "Reveal", "Settle", "Closed"];
|
|
599
|
+
return {
|
|
600
|
+
epoch,
|
|
601
|
+
phase: phases[phaseNum] ?? "Closed",
|
|
602
|
+
blocksRemaining: 0
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Get the total number of orders ever placed.
|
|
607
|
+
*/
|
|
608
|
+
async getOrderCount() {
|
|
609
|
+
const result = await this.obelysk.provider.callContract({
|
|
610
|
+
contractAddress: this.darkPoolAddress,
|
|
611
|
+
entrypoint: "get_order_count",
|
|
612
|
+
calldata: []
|
|
613
|
+
});
|
|
614
|
+
return Number(BigInt(result[0]));
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Get order IDs for a given epoch.
|
|
618
|
+
*/
|
|
619
|
+
async getEpochOrders(epochId) {
|
|
620
|
+
const result = await this.obelysk.provider.callContract({
|
|
621
|
+
contractAddress: this.darkPoolAddress,
|
|
622
|
+
entrypoint: "get_epoch_orders",
|
|
623
|
+
calldata: [epochId.toString()]
|
|
624
|
+
});
|
|
625
|
+
const len = Number(BigInt(result[0]));
|
|
626
|
+
return result.slice(1, 1 + len);
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Get supported trading pairs.
|
|
630
|
+
*/
|
|
631
|
+
async getSupportedPairs() {
|
|
632
|
+
const result = await this.obelysk.provider.callContract({
|
|
633
|
+
contractAddress: this.darkPoolAddress,
|
|
634
|
+
entrypoint: "get_supported_pairs",
|
|
635
|
+
calldata: []
|
|
636
|
+
});
|
|
637
|
+
const len = Number(BigInt(result[0]));
|
|
638
|
+
const pairs = [];
|
|
639
|
+
for (let i = 0; i < len; i++) {
|
|
640
|
+
pairs.push({
|
|
641
|
+
giveAsset: result[1 + i * 2],
|
|
642
|
+
wantAsset: result[2 + i * 2]
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
return pairs;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Get fee info (bps, recipient). Returns null if fee module not yet active.
|
|
649
|
+
*/
|
|
650
|
+
async getFeeInfo() {
|
|
651
|
+
try {
|
|
652
|
+
const result = await this.obelysk.provider.callContract({
|
|
653
|
+
contractAddress: this.darkPoolAddress,
|
|
654
|
+
entrypoint: "get_fee_info",
|
|
655
|
+
calldata: []
|
|
656
|
+
});
|
|
657
|
+
return {
|
|
658
|
+
feeBps: Number(BigInt(result[0])),
|
|
659
|
+
feeRecipient: result[1]
|
|
660
|
+
};
|
|
661
|
+
} catch {
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Withdraw tokens from the DarkPool.
|
|
667
|
+
*/
|
|
668
|
+
async withdraw(params) {
|
|
669
|
+
const account = this.obelysk.requireAccount();
|
|
670
|
+
const assetId = DARKPOOL_ASSET_IDS[params.token.toLowerCase()];
|
|
671
|
+
if (!assetId) throw new Error(`Unknown DarkPool asset: ${params.token}`);
|
|
672
|
+
const amountWei = parseAmount(params.amount, params.token);
|
|
673
|
+
const amountLow = amountWei & (1n << 128n) - 1n;
|
|
674
|
+
const amountHigh = amountWei >> 128n;
|
|
675
|
+
const calls = [
|
|
676
|
+
{
|
|
677
|
+
contractAddress: this.darkPoolAddress,
|
|
678
|
+
entrypoint: "withdraw",
|
|
679
|
+
calldata: CallData2.compile({
|
|
680
|
+
asset: assetId,
|
|
681
|
+
amount: { low: amountLow.toString(), high: amountHigh.toString() }
|
|
682
|
+
})
|
|
683
|
+
}
|
|
684
|
+
];
|
|
685
|
+
const result = await account.execute(calls);
|
|
686
|
+
await this.obelysk.provider.waitForTransaction(result.transaction_hash);
|
|
687
|
+
return { txHash: result.transaction_hash };
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
// src/obelysk/stealth.ts
|
|
692
|
+
import { CallData as CallData3, hash as hash4 } from "starknet";
|
|
693
|
+
var StealthClient = class {
|
|
694
|
+
constructor(obelysk) {
|
|
695
|
+
this.obelysk = obelysk;
|
|
696
|
+
}
|
|
697
|
+
get registryAddress() {
|
|
698
|
+
const addr = this.obelysk.contracts.stealth_registry;
|
|
699
|
+
if (!addr) throw new Error("StealthRegistry not configured for this network");
|
|
700
|
+
return addr;
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Look up a recipient's stealth meta-address from the registry.
|
|
704
|
+
*/
|
|
705
|
+
async lookupRecipient(address) {
|
|
706
|
+
try {
|
|
707
|
+
const result = await this.obelysk.provider.callContract({
|
|
708
|
+
contractAddress: this.registryAddress,
|
|
709
|
+
entrypoint: "get_meta_address",
|
|
710
|
+
calldata: [address]
|
|
711
|
+
});
|
|
712
|
+
if (result.length < 4 || result[0] === "0x0") return null;
|
|
713
|
+
return {
|
|
714
|
+
spendPublicKey: { x: BigInt(result[0]), y: BigInt(result[1]) },
|
|
715
|
+
viewPublicKey: { x: BigInt(result[2]), y: BigInt(result[3]) }
|
|
716
|
+
};
|
|
717
|
+
} catch {
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Send tokens to a stealth address.
|
|
723
|
+
*
|
|
724
|
+
* Generates ephemeral key, derives stealth address via ECDH,
|
|
725
|
+
* sends tokens + publishes ephemeral pubkey on-chain.
|
|
726
|
+
*/
|
|
727
|
+
async send(params) {
|
|
728
|
+
const account = this.obelysk.requireAccount();
|
|
729
|
+
const tokenAddress = this.obelysk.getTokenAddress(params.token);
|
|
730
|
+
const meta = await this.lookupRecipient(params.to);
|
|
731
|
+
if (!meta) throw new Error(`Recipient ${params.to} not registered in StealthRegistry`);
|
|
732
|
+
const ephemeralSecret = randomScalar2();
|
|
733
|
+
const ephemeralPubKey = ecMul2(ephemeralSecret, GENERATOR_G);
|
|
734
|
+
const sharedPoint = ecMul2(ephemeralSecret, meta.viewPublicKey);
|
|
735
|
+
const sharedSecretHash = BigInt(hash4.computePoseidonHash(
|
|
736
|
+
sharedPoint.x.toString(),
|
|
737
|
+
sharedPoint.y.toString()
|
|
738
|
+
));
|
|
739
|
+
const stealthOffset = ecMul2(sharedSecretHash, GENERATOR_G);
|
|
740
|
+
const stealthPubKey = ecAdd2(meta.spendPublicKey, stealthOffset);
|
|
741
|
+
const stealthAddress = "0x" + mod(
|
|
742
|
+
BigInt(hash4.computePoseidonHash(stealthPubKey.x.toString(), stealthPubKey.y.toString())),
|
|
743
|
+
FIELD_PRIME
|
|
744
|
+
).toString(16);
|
|
745
|
+
const amountWei = parseAmount(params.amount, params.token);
|
|
746
|
+
const amountLow = amountWei & (1n << 128n) - 1n;
|
|
747
|
+
const amountHigh = amountWei >> 128n;
|
|
748
|
+
const calls = [
|
|
749
|
+
{
|
|
750
|
+
contractAddress: tokenAddress,
|
|
751
|
+
entrypoint: "transfer",
|
|
752
|
+
calldata: CallData3.compile({
|
|
753
|
+
recipient: stealthAddress,
|
|
754
|
+
amount: { low: amountLow.toString(), high: amountHigh.toString() }
|
|
755
|
+
})
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
contractAddress: this.registryAddress,
|
|
759
|
+
entrypoint: "announce",
|
|
760
|
+
calldata: CallData3.compile({
|
|
761
|
+
ephemeral_pub_key_x: ephemeralPubKey.x.toString(),
|
|
762
|
+
ephemeral_pub_key_y: ephemeralPubKey.y.toString(),
|
|
763
|
+
stealth_address: stealthAddress,
|
|
764
|
+
token: tokenAddress
|
|
765
|
+
})
|
|
766
|
+
}
|
|
767
|
+
];
|
|
768
|
+
const result = await account.execute(calls);
|
|
769
|
+
await this.obelysk.provider.waitForTransaction(result.transaction_hash);
|
|
770
|
+
return {
|
|
771
|
+
txHash: result.transaction_hash,
|
|
772
|
+
stealthAddress,
|
|
773
|
+
ephemeralPubKey: {
|
|
774
|
+
x: "0x" + ephemeralPubKey.x.toString(16),
|
|
775
|
+
y: "0x" + ephemeralPubKey.y.toString(16)
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Check if an address has a registered stealth meta-address.
|
|
781
|
+
*/
|
|
782
|
+
async hasMetaAddress(address) {
|
|
783
|
+
try {
|
|
784
|
+
const result = await this.obelysk.provider.callContract({
|
|
785
|
+
contractAddress: this.registryAddress,
|
|
786
|
+
entrypoint: "has_meta_address",
|
|
787
|
+
calldata: [address]
|
|
788
|
+
});
|
|
789
|
+
return BigInt(result[0]) !== 0n;
|
|
790
|
+
} catch {
|
|
791
|
+
return false;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Get the total number of stealth payment announcements.
|
|
796
|
+
*/
|
|
797
|
+
async getAnnouncementCount() {
|
|
798
|
+
const result = await this.obelysk.provider.callContract({
|
|
799
|
+
contractAddress: this.registryAddress,
|
|
800
|
+
entrypoint: "get_announcement_count",
|
|
801
|
+
calldata: []
|
|
802
|
+
});
|
|
803
|
+
return Number(BigInt(result[0]));
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Get the total number of registered workers/recipients.
|
|
807
|
+
*/
|
|
808
|
+
async getRegisteredCount() {
|
|
809
|
+
const result = await this.obelysk.provider.callContract({
|
|
810
|
+
contractAddress: this.registryAddress,
|
|
811
|
+
entrypoint: "get_registered_worker_count",
|
|
812
|
+
calldata: []
|
|
813
|
+
});
|
|
814
|
+
return Number(BigInt(result[0]));
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Check if a stealth payment announcement has been claimed.
|
|
818
|
+
*/
|
|
819
|
+
async isClaimed(announcementIndex) {
|
|
820
|
+
const result = await this.obelysk.provider.callContract({
|
|
821
|
+
contractAddress: this.registryAddress,
|
|
822
|
+
entrypoint: "is_claimed",
|
|
823
|
+
calldata: [announcementIndex.toString()]
|
|
824
|
+
});
|
|
825
|
+
return BigInt(result[0]) !== 0n;
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Register your stealth meta-address in the registry.
|
|
829
|
+
*/
|
|
830
|
+
async register(spendPubKey, viewPubKey) {
|
|
831
|
+
const account = this.obelysk.requireAccount();
|
|
832
|
+
const calls = [
|
|
833
|
+
{
|
|
834
|
+
contractAddress: this.registryAddress,
|
|
835
|
+
entrypoint: "register_meta_address",
|
|
836
|
+
calldata: CallData3.compile({
|
|
837
|
+
spend_pub_key_x: spendPubKey.x.toString(),
|
|
838
|
+
spend_pub_key_y: spendPubKey.y.toString(),
|
|
839
|
+
view_pub_key_x: viewPubKey.x.toString(),
|
|
840
|
+
view_pub_key_y: viewPubKey.y.toString()
|
|
841
|
+
})
|
|
842
|
+
}
|
|
843
|
+
];
|
|
844
|
+
const result = await account.execute(calls);
|
|
845
|
+
await this.obelysk.provider.waitForTransaction(result.transaction_hash);
|
|
846
|
+
return { txHash: result.transaction_hash };
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
// src/obelysk/confidentialTransfer.ts
|
|
851
|
+
import { CallData as CallData4, hash as hash5 } from "starknet";
|
|
852
|
+
var ConfidentialTransferClient = class {
|
|
853
|
+
constructor(obelysk) {
|
|
854
|
+
this.obelysk = obelysk;
|
|
855
|
+
}
|
|
856
|
+
get contractAddress() {
|
|
857
|
+
const addr = this.obelysk.contracts.confidential_transfer;
|
|
858
|
+
if (!addr) throw new Error("ConfidentialTransfer not configured for this network");
|
|
859
|
+
return addr;
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Send a confidential transfer with ElGamal encrypted amount.
|
|
863
|
+
*
|
|
864
|
+
* Generates encryption proof (Schnorr) to prove the ciphertext is well-formed.
|
|
865
|
+
*/
|
|
866
|
+
async transfer(params) {
|
|
867
|
+
const account = this.obelysk.requireAccount();
|
|
868
|
+
const tokenAddress = this.obelysk.getTokenAddress(params.token);
|
|
869
|
+
const amountWei = parseAmount(params.amount, params.token);
|
|
870
|
+
const { c1, c2, randomness } = elgamalEncrypt(amountWei, params.recipientPublicKey);
|
|
871
|
+
const proof = createEncryptionProof(randomness, c1);
|
|
872
|
+
const nonce = randomScalar2();
|
|
873
|
+
const sharedPoint = ecMul2(randomness, params.recipientPublicKey);
|
|
874
|
+
const sharedSecret = BigInt(hash5.computePoseidonHash(
|
|
875
|
+
sharedPoint.x.toString(),
|
|
876
|
+
sharedPoint.y.toString()
|
|
877
|
+
));
|
|
878
|
+
const { encryptedAmount: encryptedHint, mac } = createAEHint(amountWei, sharedSecret);
|
|
879
|
+
const amountLow = amountWei & (1n << 128n) - 1n;
|
|
880
|
+
const amountHigh = amountWei >> 128n;
|
|
881
|
+
const calls = [
|
|
882
|
+
{
|
|
883
|
+
contractAddress: tokenAddress,
|
|
884
|
+
entrypoint: "approve",
|
|
885
|
+
calldata: CallData4.compile({
|
|
886
|
+
spender: this.contractAddress,
|
|
887
|
+
amount: { low: amountLow.toString(), high: amountHigh.toString() }
|
|
888
|
+
})
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
contractAddress: this.contractAddress,
|
|
892
|
+
entrypoint: "confidential_transfer",
|
|
893
|
+
calldata: CallData4.compile({
|
|
894
|
+
recipient: params.to,
|
|
895
|
+
amount: { low: amountLow.toString(), high: amountHigh.toString() },
|
|
896
|
+
encrypted_amount: {
|
|
897
|
+
c1_x: c1.x.toString(),
|
|
898
|
+
c1_y: c1.y.toString(),
|
|
899
|
+
c2_x: c2.x.toString(),
|
|
900
|
+
c2_y: c2.y.toString()
|
|
901
|
+
},
|
|
902
|
+
encryption_proof: {
|
|
903
|
+
commitment_x: proof.commitment.x.toString(),
|
|
904
|
+
commitment_y: proof.commitment.y.toString(),
|
|
905
|
+
challenge: proof.challenge.toString(),
|
|
906
|
+
response: proof.response.toString()
|
|
907
|
+
},
|
|
908
|
+
ae_hint: {
|
|
909
|
+
encrypted_amount: encryptedHint.toString(),
|
|
910
|
+
nonce: nonce.toString(),
|
|
911
|
+
mac: mac.toString()
|
|
912
|
+
}
|
|
913
|
+
})
|
|
914
|
+
}
|
|
915
|
+
];
|
|
916
|
+
const result = await account.execute(calls);
|
|
917
|
+
await this.obelysk.provider.waitForTransaction(result.transaction_hash);
|
|
918
|
+
return { txHash: result.transaction_hash };
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Read your encrypted balance from the contract.
|
|
922
|
+
*/
|
|
923
|
+
async getEncryptedBalance(address, token) {
|
|
924
|
+
try {
|
|
925
|
+
const tokenAddress = this.obelysk.getTokenAddress(token);
|
|
926
|
+
const result = await this.obelysk.provider.callContract({
|
|
927
|
+
contractAddress: this.contractAddress,
|
|
928
|
+
entrypoint: "get_encrypted_balance",
|
|
929
|
+
calldata: [address, tokenAddress]
|
|
930
|
+
});
|
|
931
|
+
if (result.length < 4) return null;
|
|
932
|
+
return {
|
|
933
|
+
c1: { x: BigInt(result[0]), y: BigInt(result[1]) },
|
|
934
|
+
c2: { x: BigInt(result[2]), y: BigInt(result[3]) }
|
|
935
|
+
};
|
|
936
|
+
} catch {
|
|
937
|
+
return null;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
|
|
942
|
+
// src/obelysk/proverStaking.ts
|
|
943
|
+
import { CallData as CallData5 } from "starknet";
|
|
944
|
+
var GPU_TIER_MAP = {
|
|
945
|
+
Consumer: 0,
|
|
946
|
+
Professional: 1,
|
|
947
|
+
DataCenter: 2,
|
|
948
|
+
H100: 3
|
|
949
|
+
};
|
|
950
|
+
var ProverStakingClient = class {
|
|
951
|
+
constructor(obelysk) {
|
|
952
|
+
this.obelysk = obelysk;
|
|
953
|
+
}
|
|
954
|
+
get contractAddress() {
|
|
955
|
+
const addr = this.obelysk.contracts.prover_staking;
|
|
956
|
+
if (!addr) throw new Error("ProverStaking not configured for this network");
|
|
957
|
+
return addr;
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Get a worker's current stake info.
|
|
961
|
+
*/
|
|
962
|
+
async getStake(workerAddress) {
|
|
963
|
+
const result = await this.obelysk.provider.callContract({
|
|
964
|
+
contractAddress: this.contractAddress,
|
|
965
|
+
entrypoint: "get_stake",
|
|
966
|
+
calldata: [workerAddress]
|
|
967
|
+
});
|
|
968
|
+
const tiers = ["Consumer", "Professional", "DataCenter", "H100"];
|
|
969
|
+
return {
|
|
970
|
+
amount: BigInt(result[0]),
|
|
971
|
+
gpuTier: tiers[Number(BigInt(result[1]))] ?? "Consumer",
|
|
972
|
+
stakedAt: Number(BigInt(result[2])),
|
|
973
|
+
isActive: BigInt(result[3]) !== 0n,
|
|
974
|
+
successCount: Number(BigInt(result[4])),
|
|
975
|
+
failCount: Number(BigInt(result[5]))
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Check if a worker is eligible (staked enough, not in cooldown).
|
|
980
|
+
*/
|
|
981
|
+
async isEligible(workerAddress) {
|
|
982
|
+
const result = await this.obelysk.provider.callContract({
|
|
983
|
+
contractAddress: this.contractAddress,
|
|
984
|
+
entrypoint: "is_eligible",
|
|
985
|
+
calldata: [workerAddress]
|
|
986
|
+
});
|
|
987
|
+
return BigInt(result[0]) !== 0n;
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Get minimum stake required for a GPU tier.
|
|
991
|
+
*/
|
|
992
|
+
async getMinStake(tier) {
|
|
993
|
+
const result = await this.obelysk.provider.callContract({
|
|
994
|
+
contractAddress: this.contractAddress,
|
|
995
|
+
entrypoint: "get_min_stake",
|
|
996
|
+
calldata: [GPU_TIER_MAP[tier].toString()]
|
|
997
|
+
});
|
|
998
|
+
return BigInt(result[0]);
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Get total SAGE staked across all workers.
|
|
1002
|
+
*/
|
|
1003
|
+
async getTotalStaked() {
|
|
1004
|
+
const result = await this.obelysk.provider.callContract({
|
|
1005
|
+
contractAddress: this.contractAddress,
|
|
1006
|
+
entrypoint: "total_staked",
|
|
1007
|
+
calldata: []
|
|
1008
|
+
});
|
|
1009
|
+
return BigInt(result[0]);
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Get total SAGE slashed.
|
|
1013
|
+
*/
|
|
1014
|
+
async getTotalSlashed() {
|
|
1015
|
+
const result = await this.obelysk.provider.callContract({
|
|
1016
|
+
contractAddress: this.contractAddress,
|
|
1017
|
+
entrypoint: "total_slashed",
|
|
1018
|
+
calldata: []
|
|
1019
|
+
});
|
|
1020
|
+
return BigInt(result[0]);
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Get staking configuration.
|
|
1024
|
+
*/
|
|
1025
|
+
async getConfig() {
|
|
1026
|
+
const result = await this.obelysk.provider.callContract({
|
|
1027
|
+
contractAddress: this.contractAddress,
|
|
1028
|
+
entrypoint: "get_config",
|
|
1029
|
+
calldata: []
|
|
1030
|
+
});
|
|
1031
|
+
return {
|
|
1032
|
+
minStakeConsumer: BigInt(result[0]),
|
|
1033
|
+
minStakeProfessional: BigInt(result[1]),
|
|
1034
|
+
minStakeDataCenter: BigInt(result[2]),
|
|
1035
|
+
minStakeH100: BigInt(result[3]),
|
|
1036
|
+
cooldownPeriod: Number(BigInt(result[4])),
|
|
1037
|
+
slashPercent: Number(BigInt(result[5])),
|
|
1038
|
+
rewardRateBps: Number(BigInt(result[6]))
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Stake SAGE tokens to become a prover.
|
|
1043
|
+
*/
|
|
1044
|
+
async stake(amount, tier) {
|
|
1045
|
+
const account = this.obelysk.requireAccount();
|
|
1046
|
+
const sageToken = this.obelysk.getTokenAddress("sage");
|
|
1047
|
+
const amountWei = parseAmount(amount, "sage");
|
|
1048
|
+
const amountLow = amountWei & (1n << 128n) - 1n;
|
|
1049
|
+
const amountHigh = amountWei >> 128n;
|
|
1050
|
+
const calls = [
|
|
1051
|
+
{
|
|
1052
|
+
contractAddress: sageToken,
|
|
1053
|
+
entrypoint: "approve",
|
|
1054
|
+
calldata: CallData5.compile({
|
|
1055
|
+
spender: this.contractAddress,
|
|
1056
|
+
amount: { low: amountLow.toString(), high: amountHigh.toString() }
|
|
1057
|
+
})
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
contractAddress: this.contractAddress,
|
|
1061
|
+
entrypoint: "stake",
|
|
1062
|
+
calldata: CallData5.compile({
|
|
1063
|
+
amount: { low: amountLow.toString(), high: amountHigh.toString() },
|
|
1064
|
+
gpu_tier: GPU_TIER_MAP[tier].toString()
|
|
1065
|
+
})
|
|
1066
|
+
}
|
|
1067
|
+
];
|
|
1068
|
+
const result = await account.execute(calls);
|
|
1069
|
+
await this.obelysk.provider.waitForTransaction(result.transaction_hash);
|
|
1070
|
+
return { txHash: result.transaction_hash };
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Request unstaking (starts cooldown period).
|
|
1074
|
+
*/
|
|
1075
|
+
async requestUnstake(amount) {
|
|
1076
|
+
const account = this.obelysk.requireAccount();
|
|
1077
|
+
const amountWei = parseAmount(amount, "sage");
|
|
1078
|
+
const amountLow = amountWei & (1n << 128n) - 1n;
|
|
1079
|
+
const amountHigh = amountWei >> 128n;
|
|
1080
|
+
const calls = [
|
|
1081
|
+
{
|
|
1082
|
+
contractAddress: this.contractAddress,
|
|
1083
|
+
entrypoint: "request_unstake",
|
|
1084
|
+
calldata: CallData5.compile({
|
|
1085
|
+
amount: { low: amountLow.toString(), high: amountHigh.toString() }
|
|
1086
|
+
})
|
|
1087
|
+
}
|
|
1088
|
+
];
|
|
1089
|
+
const result = await account.execute(calls);
|
|
1090
|
+
await this.obelysk.provider.waitForTransaction(result.transaction_hash);
|
|
1091
|
+
return { txHash: result.transaction_hash };
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Complete unstaking after cooldown period.
|
|
1095
|
+
*/
|
|
1096
|
+
async completeUnstake() {
|
|
1097
|
+
const account = this.obelysk.requireAccount();
|
|
1098
|
+
const calls = [
|
|
1099
|
+
{
|
|
1100
|
+
contractAddress: this.contractAddress,
|
|
1101
|
+
entrypoint: "complete_unstake",
|
|
1102
|
+
calldata: []
|
|
1103
|
+
}
|
|
1104
|
+
];
|
|
1105
|
+
const result = await account.execute(calls);
|
|
1106
|
+
await this.obelysk.provider.waitForTransaction(result.transaction_hash);
|
|
1107
|
+
return { txHash: result.transaction_hash };
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Claim accumulated staking rewards.
|
|
1111
|
+
*/
|
|
1112
|
+
async claimRewards() {
|
|
1113
|
+
const account = this.obelysk.requireAccount();
|
|
1114
|
+
const calls = [
|
|
1115
|
+
{
|
|
1116
|
+
contractAddress: this.contractAddress,
|
|
1117
|
+
entrypoint: "claim_rewards",
|
|
1118
|
+
calldata: []
|
|
1119
|
+
}
|
|
1120
|
+
];
|
|
1121
|
+
const result = await account.execute(calls);
|
|
1122
|
+
await this.obelysk.provider.waitForTransaction(result.transaction_hash);
|
|
1123
|
+
return { txHash: result.transaction_hash };
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
|
|
1127
|
+
// src/obelysk/privacyRouter.ts
|
|
1128
|
+
var PrivacyRouterClient = class {
|
|
1129
|
+
constructor(obelysk) {
|
|
1130
|
+
this.obelysk = obelysk;
|
|
1131
|
+
}
|
|
1132
|
+
get contractAddress() {
|
|
1133
|
+
const addr = this.obelysk.contracts.privacy_router;
|
|
1134
|
+
if (!addr) throw new Error("PrivacyRouter not configured for this network");
|
|
1135
|
+
return addr;
|
|
1136
|
+
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Get a registered private account's info.
|
|
1139
|
+
*/
|
|
1140
|
+
async getAccount(address) {
|
|
1141
|
+
try {
|
|
1142
|
+
const result = await this.obelysk.provider.callContract({
|
|
1143
|
+
contractAddress: this.contractAddress,
|
|
1144
|
+
entrypoint: "get_account",
|
|
1145
|
+
calldata: [address]
|
|
1146
|
+
});
|
|
1147
|
+
if (result.length < 8) return null;
|
|
1148
|
+
return {
|
|
1149
|
+
publicKey: { x: BigInt(result[0]), y: BigInt(result[1]) },
|
|
1150
|
+
encryptedBalance: {
|
|
1151
|
+
c1: { x: BigInt(result[2]), y: BigInt(result[3]) },
|
|
1152
|
+
c2: { x: BigInt(result[4]), y: BigInt(result[5]) }
|
|
1153
|
+
},
|
|
1154
|
+
registered: BigInt(result[6]) !== 0n
|
|
1155
|
+
};
|
|
1156
|
+
} catch {
|
|
1157
|
+
return null;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
/** Check if a nullifier has been spent. */
|
|
1161
|
+
async isNullifierUsed(nullifier) {
|
|
1162
|
+
try {
|
|
1163
|
+
const result = await this.obelysk.provider.callContract({
|
|
1164
|
+
contractAddress: this.contractAddress,
|
|
1165
|
+
entrypoint: "is_nullifier_used",
|
|
1166
|
+
calldata: [nullifier]
|
|
1167
|
+
});
|
|
1168
|
+
return BigInt(result[0]) !== 0n;
|
|
1169
|
+
} catch {
|
|
1170
|
+
return false;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
/** Get the current nullifier tree root. */
|
|
1174
|
+
async getNullifierTreeRoot() {
|
|
1175
|
+
const result = await this.obelysk.provider.callContract({
|
|
1176
|
+
contractAddress: this.contractAddress,
|
|
1177
|
+
entrypoint: "get_nullifier_tree_root",
|
|
1178
|
+
calldata: []
|
|
1179
|
+
});
|
|
1180
|
+
return result[0];
|
|
1181
|
+
}
|
|
1182
|
+
/** Get the current epoch of the privacy router. */
|
|
1183
|
+
async getCurrentEpoch() {
|
|
1184
|
+
const result = await this.obelysk.provider.callContract({
|
|
1185
|
+
contractAddress: this.contractAddress,
|
|
1186
|
+
entrypoint: "get_current_epoch",
|
|
1187
|
+
calldata: []
|
|
1188
|
+
});
|
|
1189
|
+
return Number(BigInt(result[0]));
|
|
1190
|
+
}
|
|
1191
|
+
/** Get total nullifier count. */
|
|
1192
|
+
async getNullifierCount() {
|
|
1193
|
+
const result = await this.obelysk.provider.callContract({
|
|
1194
|
+
contractAddress: this.contractAddress,
|
|
1195
|
+
entrypoint: "get_nullifier_count",
|
|
1196
|
+
calldata: []
|
|
1197
|
+
});
|
|
1198
|
+
return Number(BigInt(result[0]));
|
|
1199
|
+
}
|
|
1200
|
+
/** Check if an asset ID is supported. */
|
|
1201
|
+
async isAssetSupported(assetId) {
|
|
1202
|
+
try {
|
|
1203
|
+
const result = await this.obelysk.provider.callContract({
|
|
1204
|
+
contractAddress: this.contractAddress,
|
|
1205
|
+
entrypoint: "is_asset_supported",
|
|
1206
|
+
calldata: [assetId.toString()]
|
|
1207
|
+
});
|
|
1208
|
+
return BigInt(result[0]) !== 0n;
|
|
1209
|
+
} catch {
|
|
1210
|
+
return false;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
/** Get the token address for an asset ID. */
|
|
1214
|
+
async getAssetToken(assetId) {
|
|
1215
|
+
const result = await this.obelysk.provider.callContract({
|
|
1216
|
+
contractAddress: this.contractAddress,
|
|
1217
|
+
entrypoint: "get_asset_token",
|
|
1218
|
+
calldata: [assetId.toString()]
|
|
1219
|
+
});
|
|
1220
|
+
return result[0];
|
|
1221
|
+
}
|
|
1222
|
+
/** Get the number of auditors. */
|
|
1223
|
+
async getAuditorCount() {
|
|
1224
|
+
const result = await this.obelysk.provider.callContract({
|
|
1225
|
+
contractAddress: this.contractAddress,
|
|
1226
|
+
entrypoint: "get_auditor_count",
|
|
1227
|
+
calldata: []
|
|
1228
|
+
});
|
|
1229
|
+
return Number(BigInt(result[0]));
|
|
1230
|
+
}
|
|
1231
|
+
/** Check if an address is a registered auditor. */
|
|
1232
|
+
async isAuditor(address) {
|
|
1233
|
+
try {
|
|
1234
|
+
const result = await this.obelysk.provider.callContract({
|
|
1235
|
+
contractAddress: this.contractAddress,
|
|
1236
|
+
entrypoint: "is_auditor",
|
|
1237
|
+
calldata: [address]
|
|
1238
|
+
});
|
|
1239
|
+
return BigInt(result[0]) !== 0n;
|
|
1240
|
+
} catch {
|
|
1241
|
+
return false;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
/** Get the audit approval threshold. */
|
|
1245
|
+
async getAuditThreshold() {
|
|
1246
|
+
const result = await this.obelysk.provider.callContract({
|
|
1247
|
+
contractAddress: this.contractAddress,
|
|
1248
|
+
entrypoint: "get_audit_threshold",
|
|
1249
|
+
calldata: []
|
|
1250
|
+
});
|
|
1251
|
+
return Number(BigInt(result[0]));
|
|
1252
|
+
}
|
|
1253
|
+
/** Get the large transfer threshold. */
|
|
1254
|
+
async getLargeTransferThreshold() {
|
|
1255
|
+
const result = await this.obelysk.provider.callContract({
|
|
1256
|
+
contractAddress: this.contractAddress,
|
|
1257
|
+
entrypoint: "get_large_transfer_threshold",
|
|
1258
|
+
calldata: []
|
|
1259
|
+
});
|
|
1260
|
+
return BigInt(result[0]);
|
|
1261
|
+
}
|
|
1262
|
+
/** Check if the Lean IMT is active. */
|
|
1263
|
+
async isLeanIMTActive() {
|
|
1264
|
+
try {
|
|
1265
|
+
const result = await this.obelysk.provider.callContract({
|
|
1266
|
+
contractAddress: this.contractAddress,
|
|
1267
|
+
entrypoint: "is_lean_imt_active",
|
|
1268
|
+
calldata: []
|
|
1269
|
+
});
|
|
1270
|
+
return BigInt(result[0]) !== 0n;
|
|
1271
|
+
} catch {
|
|
1272
|
+
return false;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
/** Get pending audit/disclosure request count. */
|
|
1276
|
+
async getPendingRequestCount() {
|
|
1277
|
+
const result = await this.obelysk.provider.callContract({
|
|
1278
|
+
contractAddress: this.contractAddress,
|
|
1279
|
+
entrypoint: "get_pending_request_count",
|
|
1280
|
+
calldata: []
|
|
1281
|
+
});
|
|
1282
|
+
return Number(BigInt(result[0]));
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
|
|
1286
|
+
// src/obelysk/shieldedSwap.ts
|
|
1287
|
+
var ShieldedSwapClient = class {
|
|
1288
|
+
constructor(obelysk) {
|
|
1289
|
+
this.obelysk = obelysk;
|
|
1290
|
+
}
|
|
1291
|
+
get contractAddress() {
|
|
1292
|
+
const addr = this.obelysk.contracts.shielded_swap;
|
|
1293
|
+
if (!addr) throw new Error("ShieldedSwap not configured for this network");
|
|
1294
|
+
return addr;
|
|
1295
|
+
}
|
|
1296
|
+
/** Get total number of shielded swaps executed. */
|
|
1297
|
+
async getSwapCount() {
|
|
1298
|
+
const result = await this.obelysk.provider.callContract({
|
|
1299
|
+
contractAddress: this.contractAddress,
|
|
1300
|
+
entrypoint: "get_swap_count",
|
|
1301
|
+
calldata: []
|
|
1302
|
+
});
|
|
1303
|
+
return Number(BigInt(result[0]));
|
|
1304
|
+
}
|
|
1305
|
+
/** Get the privacy pool address registered for a token. */
|
|
1306
|
+
async getPool(tokenAddress) {
|
|
1307
|
+
try {
|
|
1308
|
+
const result = await this.obelysk.provider.callContract({
|
|
1309
|
+
contractAddress: this.contractAddress,
|
|
1310
|
+
entrypoint: "get_pool",
|
|
1311
|
+
calldata: [tokenAddress]
|
|
1312
|
+
});
|
|
1313
|
+
return result[0] === "0x0" ? null : result[0];
|
|
1314
|
+
} catch {
|
|
1315
|
+
return null;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
/** Get the Ekubo core address (AMM integration). */
|
|
1319
|
+
async getEkuboCore() {
|
|
1320
|
+
const result = await this.obelysk.provider.callContract({
|
|
1321
|
+
contractAddress: this.contractAddress,
|
|
1322
|
+
entrypoint: "get_ekubo_core",
|
|
1323
|
+
calldata: []
|
|
1324
|
+
});
|
|
1325
|
+
return result[0];
|
|
1326
|
+
}
|
|
1327
|
+
/** Get swap fee info (bps, recipient). Returns null if not active. */
|
|
1328
|
+
async getSwapFeeInfo() {
|
|
1329
|
+
try {
|
|
1330
|
+
const result = await this.obelysk.provider.callContract({
|
|
1331
|
+
contractAddress: this.contractAddress,
|
|
1332
|
+
entrypoint: "get_swap_fee_info",
|
|
1333
|
+
calldata: []
|
|
1334
|
+
});
|
|
1335
|
+
return {
|
|
1336
|
+
feeBps: Number(BigInt(result[0])),
|
|
1337
|
+
feeRecipient: result[1]
|
|
1338
|
+
};
|
|
1339
|
+
} catch {
|
|
1340
|
+
return null;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
/** Get contract owner. */
|
|
1344
|
+
async getOwner() {
|
|
1345
|
+
const result = await this.obelysk.provider.callContract({
|
|
1346
|
+
contractAddress: this.contractAddress,
|
|
1347
|
+
entrypoint: "owner",
|
|
1348
|
+
calldata: []
|
|
1349
|
+
});
|
|
1350
|
+
return result[0];
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
|
|
1354
|
+
// src/obelysk/client.ts
|
|
1355
|
+
var ObelyskClient = class {
|
|
1356
|
+
network;
|
|
1357
|
+
provider;
|
|
1358
|
+
account;
|
|
1359
|
+
privacy;
|
|
1360
|
+
contracts;
|
|
1361
|
+
tokens;
|
|
1362
|
+
privacyPools;
|
|
1363
|
+
/** Privacy Pool operations (deposit/withdraw shielded tokens) */
|
|
1364
|
+
privacyPool;
|
|
1365
|
+
/** DarkPool batch auction trading */
|
|
1366
|
+
darkPool;
|
|
1367
|
+
/** Stealth address payments */
|
|
1368
|
+
stealth;
|
|
1369
|
+
/** Confidential peer-to-peer transfers */
|
|
1370
|
+
confidentialTransfer;
|
|
1371
|
+
/** SAGE prover staking (stake/unstake/rewards) */
|
|
1372
|
+
staking;
|
|
1373
|
+
/** Privacy Router (private balances, nullifiers, auditing) */
|
|
1374
|
+
router;
|
|
1375
|
+
/** Shielded swap router (privacy-preserving AMM swaps) */
|
|
1376
|
+
swap;
|
|
1377
|
+
constructor(config) {
|
|
1378
|
+
this.network = config.network;
|
|
1379
|
+
this.provider = new RpcProvider({ nodeUrl: config.rpcUrl ?? getRpcUrl(config.network) });
|
|
1380
|
+
this.account = config.account;
|
|
1381
|
+
this.contracts = getContracts(config.network);
|
|
1382
|
+
this.tokens = this.contracts.tokens ?? MAINNET_TOKENS;
|
|
1383
|
+
this.privacyPools = config.network === "mainnet" ? MAINNET_PRIVACY_POOLS : {};
|
|
1384
|
+
this.privacy = new ObelyskPrivacy();
|
|
1385
|
+
if (config.privacyPrivateKey) {
|
|
1386
|
+
const pk = BigInt(config.privacyPrivateKey);
|
|
1387
|
+
this.privacy.setKeyPair(pk);
|
|
1388
|
+
}
|
|
1389
|
+
this.privacyPool = new PrivacyPoolClient(this);
|
|
1390
|
+
this.darkPool = new DarkPoolClient(this);
|
|
1391
|
+
this.stealth = new StealthClient(this);
|
|
1392
|
+
this.confidentialTransfer = new ConfidentialTransferClient(this);
|
|
1393
|
+
this.staking = new ProverStakingClient(this);
|
|
1394
|
+
this.router = new PrivacyRouterClient(this);
|
|
1395
|
+
this.swap = new ShieldedSwapClient(this);
|
|
1396
|
+
}
|
|
1397
|
+
/** Get token address by symbol */
|
|
1398
|
+
getTokenAddress(symbol) {
|
|
1399
|
+
const addr = this.tokens[symbol.toLowerCase()];
|
|
1400
|
+
if (!addr) throw new Error(`Unknown token: ${symbol}`);
|
|
1401
|
+
return addr;
|
|
1402
|
+
}
|
|
1403
|
+
/** Get privacy pool address for a token */
|
|
1404
|
+
getPrivacyPoolAddress(symbol) {
|
|
1405
|
+
const addr = this.privacyPools[symbol.toLowerCase()];
|
|
1406
|
+
if (!addr) throw new Error(`No privacy pool for: ${symbol}`);
|
|
1407
|
+
return addr;
|
|
1408
|
+
}
|
|
1409
|
+
/** Require an account is set */
|
|
1410
|
+
requireAccount() {
|
|
1411
|
+
if (!this.account) throw new Error("ObelyskClient: account required for transactions");
|
|
1412
|
+
return this.account;
|
|
1413
|
+
}
|
|
1414
|
+
};
|
|
1415
|
+
export {
|
|
1416
|
+
CURVE_ORDER,
|
|
1417
|
+
ConfidentialTransferClient,
|
|
1418
|
+
DARKPOOL_ASSET_IDS,
|
|
1419
|
+
DarkPoolClient,
|
|
1420
|
+
FIELD_PRIME,
|
|
1421
|
+
GENERATOR_G,
|
|
1422
|
+
GENERATOR_H,
|
|
1423
|
+
MAINNET_PRIVACY_POOLS,
|
|
1424
|
+
MAINNET_TOKENS,
|
|
1425
|
+
ObelyskClient,
|
|
1426
|
+
ObelyskPrivacy,
|
|
1427
|
+
PrivacyPoolClient,
|
|
1428
|
+
PrivacyRouterClient,
|
|
1429
|
+
ProverStakingClient,
|
|
1430
|
+
ShieldedSwapClient,
|
|
1431
|
+
StealthClient,
|
|
1432
|
+
TOKEN_DECIMALS,
|
|
1433
|
+
VM31_ASSET_IDS,
|
|
1434
|
+
commitmentToHash,
|
|
1435
|
+
createAEHint,
|
|
1436
|
+
createEncryptionProof,
|
|
1437
|
+
deriveNullifier,
|
|
1438
|
+
ecAdd2 as ecAdd,
|
|
1439
|
+
ecMul2 as ecMul,
|
|
1440
|
+
elgamalEncrypt,
|
|
1441
|
+
formatAmount,
|
|
1442
|
+
getContracts,
|
|
1443
|
+
getRpcUrl,
|
|
1444
|
+
mod,
|
|
1445
|
+
modInverse,
|
|
1446
|
+
parseAmount,
|
|
1447
|
+
pedersenCommit,
|
|
1448
|
+
randomScalar2 as randomScalar
|
|
1449
|
+
};
|