@obelyzk/sdk 0.5.0 → 1.0.1
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/index.mjs +1 -1
- package/dist/obelysk/index.d.mts +868 -1
- package/dist/obelysk/index.d.ts +868 -1
- package/dist/obelysk/index.js +1613 -2
- package/dist/obelysk/index.mjs +1597 -2
- package/dist/privacy/index.mjs +1 -1
- package/dist/react/index.mjs +1 -1
- package/package.json +1 -1
package/dist/obelysk/index.mjs
CHANGED
|
@@ -4,11 +4,12 @@ import {
|
|
|
4
4
|
GENERATOR_G,
|
|
5
5
|
GENERATOR_H,
|
|
6
6
|
ObelyskPrivacy,
|
|
7
|
+
__require,
|
|
7
8
|
ecAdd,
|
|
8
9
|
ecMul,
|
|
9
10
|
invMod,
|
|
10
11
|
randomScalar
|
|
11
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-CGPFHXUF.mjs";
|
|
12
13
|
|
|
13
14
|
// src/obelysk/client.ts
|
|
14
15
|
import { RpcProvider } from "starknet";
|
|
@@ -686,6 +687,124 @@ var DarkPoolClient = class {
|
|
|
686
687
|
await this.obelysk.provider.waitForTransaction(result.transaction_hash);
|
|
687
688
|
return { txHash: result.transaction_hash };
|
|
688
689
|
}
|
|
690
|
+
/** Cancel an order before reveal. */
|
|
691
|
+
async cancelOrder(orderId) {
|
|
692
|
+
const account = this.obelysk.requireAccount();
|
|
693
|
+
const low = (orderId & (1n << 128n) - 1n).toString();
|
|
694
|
+
const high = (orderId >> 128n).toString();
|
|
695
|
+
const result = await account.execute([{
|
|
696
|
+
contractAddress: this.darkPoolAddress,
|
|
697
|
+
entrypoint: "cancel_order",
|
|
698
|
+
calldata: [low, high]
|
|
699
|
+
}]);
|
|
700
|
+
return result.transaction_hash;
|
|
701
|
+
}
|
|
702
|
+
/** Claim fills after epoch settlement. */
|
|
703
|
+
async claimFill(params) {
|
|
704
|
+
const account = this.obelysk.requireAccount();
|
|
705
|
+
const oLow = (params.orderId & (1n << 128n) - 1n).toString();
|
|
706
|
+
const oHigh = (params.orderId >> 128n).toString();
|
|
707
|
+
const result = await account.execute([{
|
|
708
|
+
contractAddress: this.darkPoolAddress,
|
|
709
|
+
entrypoint: "claim_fill",
|
|
710
|
+
calldata: [
|
|
711
|
+
oLow,
|
|
712
|
+
oHigh,
|
|
713
|
+
params.receiveCipher.c1.x.toString(),
|
|
714
|
+
params.receiveCipher.c1.y.toString(),
|
|
715
|
+
params.receiveCipher.c2.x.toString(),
|
|
716
|
+
params.receiveCipher.c2.y.toString(),
|
|
717
|
+
params.receiveHint.toString(),
|
|
718
|
+
params.receiveProof.commitment.toString(),
|
|
719
|
+
params.receiveProof.challenge.toString(),
|
|
720
|
+
params.receiveProof.response.toString(),
|
|
721
|
+
params.spendCipher.c1.x.toString(),
|
|
722
|
+
params.spendCipher.c1.y.toString(),
|
|
723
|
+
params.spendCipher.c2.x.toString(),
|
|
724
|
+
params.spendCipher.c2.y.toString(),
|
|
725
|
+
params.spendHint.toString(),
|
|
726
|
+
params.spendProof.commitment.toString(),
|
|
727
|
+
params.spendProof.challenge.toString(),
|
|
728
|
+
params.spendProof.response.toString()
|
|
729
|
+
]
|
|
730
|
+
}]);
|
|
731
|
+
return result.transaction_hash;
|
|
732
|
+
}
|
|
733
|
+
/** Register your ElGamal public key with the DarkPool. */
|
|
734
|
+
async registerPubkey(pubkey) {
|
|
735
|
+
const account = this.obelysk.requireAccount();
|
|
736
|
+
const result = await account.execute([{
|
|
737
|
+
contractAddress: this.darkPoolAddress,
|
|
738
|
+
entrypoint: "register_pubkey",
|
|
739
|
+
calldata: [pubkey.x.toString(), pubkey.y.toString()]
|
|
740
|
+
}]);
|
|
741
|
+
return result.transaction_hash;
|
|
742
|
+
}
|
|
743
|
+
/** Get the result of a settled epoch. */
|
|
744
|
+
async getEpochResult(epochId) {
|
|
745
|
+
try {
|
|
746
|
+
const result = await this.obelysk.provider.callContract({
|
|
747
|
+
contractAddress: this.darkPoolAddress,
|
|
748
|
+
entrypoint: "get_epoch_result",
|
|
749
|
+
calldata: [epochId.toString()]
|
|
750
|
+
});
|
|
751
|
+
if (result.length < 2) return null;
|
|
752
|
+
const settled = BigInt(result[0]) !== 0n;
|
|
753
|
+
const priceCount = Number(BigInt(result[1]));
|
|
754
|
+
const clearingPrices = result.slice(2, 2 + priceCount);
|
|
755
|
+
return { settled, clearingPrices };
|
|
756
|
+
} catch {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
/** Check if an order has been claimed. */
|
|
761
|
+
async isOrderClaimed(orderId) {
|
|
762
|
+
try {
|
|
763
|
+
const low = (orderId & (1n << 128n) - 1n).toString();
|
|
764
|
+
const high = (orderId >> 128n).toString();
|
|
765
|
+
const result = await this.obelysk.provider.callContract({
|
|
766
|
+
contractAddress: this.darkPoolAddress,
|
|
767
|
+
entrypoint: "is_order_claimed",
|
|
768
|
+
calldata: [low, high]
|
|
769
|
+
});
|
|
770
|
+
return BigInt(result[0]) !== 0n;
|
|
771
|
+
} catch {
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
/** Get a trader's registered public key. */
|
|
776
|
+
async getTraderPubkey(address) {
|
|
777
|
+
try {
|
|
778
|
+
const result = await this.obelysk.provider.callContract({
|
|
779
|
+
contractAddress: this.darkPoolAddress,
|
|
780
|
+
entrypoint: "get_trader_pubkey",
|
|
781
|
+
calldata: [address]
|
|
782
|
+
});
|
|
783
|
+
const x = BigInt(result[0]);
|
|
784
|
+
const y = BigInt(result[1]);
|
|
785
|
+
if (x === 0n && y === 0n) return null;
|
|
786
|
+
return { x, y };
|
|
787
|
+
} catch {
|
|
788
|
+
return null;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
/** Get encrypted balance for a trader and asset. */
|
|
792
|
+
async getEncryptedBalance(address, assetId) {
|
|
793
|
+
try {
|
|
794
|
+
const result = await this.obelysk.provider.callContract({
|
|
795
|
+
contractAddress: this.darkPoolAddress,
|
|
796
|
+
entrypoint: "get_encrypted_balance",
|
|
797
|
+
calldata: [address, assetId]
|
|
798
|
+
});
|
|
799
|
+
if (result.length < 4) return null;
|
|
800
|
+
return {
|
|
801
|
+
c1: { x: BigInt(result[0]), y: BigInt(result[1]) },
|
|
802
|
+
c2: { x: BigInt(result[2]), y: BigInt(result[3]) }
|
|
803
|
+
};
|
|
804
|
+
} catch {
|
|
805
|
+
return null;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
689
808
|
};
|
|
690
809
|
|
|
691
810
|
// src/obelysk/stealth.ts
|
|
@@ -845,6 +964,102 @@ var StealthClient = class {
|
|
|
845
964
|
await this.obelysk.provider.waitForTransaction(result.transaction_hash);
|
|
846
965
|
return { txHash: result.transaction_hash };
|
|
847
966
|
}
|
|
967
|
+
/** Claim a stealth payment. */
|
|
968
|
+
async claim(params) {
|
|
969
|
+
const account = this.obelysk.requireAccount();
|
|
970
|
+
const p = params.spendingProof;
|
|
971
|
+
const result = await account.execute([{
|
|
972
|
+
contractAddress: this.registryAddress,
|
|
973
|
+
entrypoint: "claim_stealth_payment",
|
|
974
|
+
calldata: [
|
|
975
|
+
params.announcementIndex.toString(),
|
|
976
|
+
p.commitment.toString(),
|
|
977
|
+
p.challenge.toString(),
|
|
978
|
+
p.response.toString(),
|
|
979
|
+
p.stealthPubkey.x.toString(),
|
|
980
|
+
p.stealthPubkey.y.toString(),
|
|
981
|
+
params.recipient
|
|
982
|
+
]
|
|
983
|
+
}]);
|
|
984
|
+
return result.transaction_hash;
|
|
985
|
+
}
|
|
986
|
+
/** Batch claim multiple stealth payments. */
|
|
987
|
+
async batchClaim(params) {
|
|
988
|
+
const account = this.obelysk.requireAccount();
|
|
989
|
+
const calldata = [
|
|
990
|
+
params.announcementIndices.length.toString(),
|
|
991
|
+
...params.announcementIndices.map((i) => i.toString()),
|
|
992
|
+
params.spendingProofs.length.toString(),
|
|
993
|
+
...params.spendingProofs.flatMap((p) => [
|
|
994
|
+
p.commitment.toString(),
|
|
995
|
+
p.challenge.toString(),
|
|
996
|
+
p.response.toString(),
|
|
997
|
+
p.stealthPubkey.x.toString(),
|
|
998
|
+
p.stealthPubkey.y.toString()
|
|
999
|
+
]),
|
|
1000
|
+
params.recipient
|
|
1001
|
+
];
|
|
1002
|
+
const result = await account.execute([{
|
|
1003
|
+
contractAddress: this.registryAddress,
|
|
1004
|
+
entrypoint: "batch_claim_stealth_payments",
|
|
1005
|
+
calldata
|
|
1006
|
+
}]);
|
|
1007
|
+
return result.transaction_hash;
|
|
1008
|
+
}
|
|
1009
|
+
/** Update your stealth meta-address. */
|
|
1010
|
+
async updateMetaAddress(spendPubKey, viewPubKey) {
|
|
1011
|
+
const account = this.obelysk.requireAccount();
|
|
1012
|
+
const result = await account.execute([{
|
|
1013
|
+
contractAddress: this.registryAddress,
|
|
1014
|
+
entrypoint: "update_meta_address",
|
|
1015
|
+
calldata: [spendPubKey.x.toString(), spendPubKey.y.toString(), viewPubKey.x.toString(), viewPubKey.y.toString()]
|
|
1016
|
+
}]);
|
|
1017
|
+
return result.transaction_hash;
|
|
1018
|
+
}
|
|
1019
|
+
/** Get a specific announcement by index. */
|
|
1020
|
+
async getAnnouncement(index) {
|
|
1021
|
+
try {
|
|
1022
|
+
const result = await this.obelysk.provider.callContract({
|
|
1023
|
+
contractAddress: this.registryAddress,
|
|
1024
|
+
entrypoint: "get_announcement",
|
|
1025
|
+
calldata: [index.toString()]
|
|
1026
|
+
});
|
|
1027
|
+
return {
|
|
1028
|
+
ephemeralPubkey: { x: BigInt(result[0]), y: BigInt(result[1]) },
|
|
1029
|
+
stealthAddress: result[2],
|
|
1030
|
+
viewTag: result[3],
|
|
1031
|
+
token: result[4],
|
|
1032
|
+
amount: BigInt(result[5])
|
|
1033
|
+
};
|
|
1034
|
+
} catch {
|
|
1035
|
+
return null;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
/** Get announcements in a range. */
|
|
1039
|
+
async getAnnouncementsRange(start, end) {
|
|
1040
|
+
try {
|
|
1041
|
+
const result = await this.obelysk.provider.callContract({
|
|
1042
|
+
contractAddress: this.registryAddress,
|
|
1043
|
+
entrypoint: "get_announcements_range",
|
|
1044
|
+
calldata: [start.toString(), end.toString()]
|
|
1045
|
+
});
|
|
1046
|
+
const count = Number(BigInt(result[0]));
|
|
1047
|
+
const items = [];
|
|
1048
|
+
for (let i = 0; i < count; i++) {
|
|
1049
|
+
const base = 1 + i * 6;
|
|
1050
|
+
items.push({
|
|
1051
|
+
ephemeralPubkey: { x: BigInt(result[base]), y: BigInt(result[base + 1]) },
|
|
1052
|
+
stealthAddress: result[base + 2],
|
|
1053
|
+
viewTag: result[base + 3],
|
|
1054
|
+
token: result[base + 4],
|
|
1055
|
+
amount: BigInt(result[base + 5])
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
return items;
|
|
1059
|
+
} catch {
|
|
1060
|
+
return [];
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
848
1063
|
};
|
|
849
1064
|
|
|
850
1065
|
// src/obelysk/confidentialTransfer.ts
|
|
@@ -937,6 +1152,78 @@ var ConfidentialTransferClient = class {
|
|
|
937
1152
|
return null;
|
|
938
1153
|
}
|
|
939
1154
|
}
|
|
1155
|
+
/** Register an ElGamal public key for confidential transfers. */
|
|
1156
|
+
async register(publicKey) {
|
|
1157
|
+
const account = this.obelysk.requireAccount();
|
|
1158
|
+
const result = await account.execute([{
|
|
1159
|
+
contractAddress: this.contractAddress,
|
|
1160
|
+
entrypoint: "register",
|
|
1161
|
+
calldata: [publicKey.x.toString(), publicKey.y.toString()]
|
|
1162
|
+
}]);
|
|
1163
|
+
return result.transaction_hash;
|
|
1164
|
+
}
|
|
1165
|
+
/** Fund: deposit public tokens into encrypted balance. */
|
|
1166
|
+
async fund(params) {
|
|
1167
|
+
const account = this.obelysk.requireAccount();
|
|
1168
|
+
const low = (params.amount & (1n << 128n) - 1n).toString();
|
|
1169
|
+
const high = (params.amount >> 128n).toString();
|
|
1170
|
+
const result = await account.execute([{
|
|
1171
|
+
contractAddress: this.contractAddress,
|
|
1172
|
+
entrypoint: "fund",
|
|
1173
|
+
calldata: [params.assetId.toString(), low, high, params.encryptionRandomness.toString(), params.aeHint.toString()]
|
|
1174
|
+
}]);
|
|
1175
|
+
return result.transaction_hash;
|
|
1176
|
+
}
|
|
1177
|
+
/** Fund another account's encrypted balance. */
|
|
1178
|
+
async fundFor(params) {
|
|
1179
|
+
const signer = this.obelysk.requireAccount();
|
|
1180
|
+
const low = (params.amount & (1n << 128n) - 1n).toString();
|
|
1181
|
+
const high = (params.amount >> 128n).toString();
|
|
1182
|
+
const result = await signer.execute([{
|
|
1183
|
+
contractAddress: this.contractAddress,
|
|
1184
|
+
entrypoint: "fund_for",
|
|
1185
|
+
calldata: [params.account, params.assetId.toString(), low, high, params.encryptionRandomness.toString(), params.aeHint.toString()]
|
|
1186
|
+
}]);
|
|
1187
|
+
return result.transaction_hash;
|
|
1188
|
+
}
|
|
1189
|
+
/** Rollover: claim pending incoming transfers into your balance. */
|
|
1190
|
+
async rollover(assetId) {
|
|
1191
|
+
const account = this.obelysk.requireAccount();
|
|
1192
|
+
const result = await account.execute([{
|
|
1193
|
+
contractAddress: this.contractAddress,
|
|
1194
|
+
entrypoint: "rollover",
|
|
1195
|
+
calldata: [assetId.toString()]
|
|
1196
|
+
}]);
|
|
1197
|
+
return result.transaction_hash;
|
|
1198
|
+
}
|
|
1199
|
+
/** Withdraw: convert encrypted balance back to public tokens. */
|
|
1200
|
+
async withdraw(params) {
|
|
1201
|
+
const account = this.obelysk.requireAccount();
|
|
1202
|
+
const low = (params.amount & (1n << 128n) - 1n).toString();
|
|
1203
|
+
const high = (params.amount >> 128n).toString();
|
|
1204
|
+
const result = await account.execute([{
|
|
1205
|
+
contractAddress: this.contractAddress,
|
|
1206
|
+
entrypoint: "withdraw",
|
|
1207
|
+
calldata: [params.to, params.assetId.toString(), low, high, params.proof.commitment.toString(), params.proof.challenge.toString(), params.proof.response.toString()]
|
|
1208
|
+
}]);
|
|
1209
|
+
return result.transaction_hash;
|
|
1210
|
+
}
|
|
1211
|
+
/** Get a registered public key. */
|
|
1212
|
+
async getPublicKey(address) {
|
|
1213
|
+
try {
|
|
1214
|
+
const result = await this.obelysk.provider.callContract({
|
|
1215
|
+
contractAddress: this.contractAddress,
|
|
1216
|
+
entrypoint: "get_public_key",
|
|
1217
|
+
calldata: [address]
|
|
1218
|
+
});
|
|
1219
|
+
const x = BigInt(result[0]);
|
|
1220
|
+
const y = BigInt(result[1]);
|
|
1221
|
+
if (x === 0n && y === 0n) return null;
|
|
1222
|
+
return { x, y };
|
|
1223
|
+
} catch {
|
|
1224
|
+
return null;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
940
1227
|
};
|
|
941
1228
|
|
|
942
1229
|
// src/obelysk/proverStaking.ts
|
|
@@ -1281,6 +1568,133 @@ var PrivacyRouterClient = class {
|
|
|
1281
1568
|
});
|
|
1282
1569
|
return Number(BigInt(result[0]));
|
|
1283
1570
|
}
|
|
1571
|
+
/** Register an account with the privacy router. */
|
|
1572
|
+
async registerAccount(publicKey) {
|
|
1573
|
+
const account = this.obelysk.requireAccount();
|
|
1574
|
+
const result = await account.execute([{
|
|
1575
|
+
contractAddress: this.contractAddress,
|
|
1576
|
+
entrypoint: "register_account",
|
|
1577
|
+
calldata: [publicKey.x.toString(), publicKey.y.toString()]
|
|
1578
|
+
}]);
|
|
1579
|
+
return result.transaction_hash;
|
|
1580
|
+
}
|
|
1581
|
+
/** Deposit tokens into encrypted balance. */
|
|
1582
|
+
async deposit(params) {
|
|
1583
|
+
const account = this.obelysk.requireAccount();
|
|
1584
|
+
const low = (params.amount & (1n << 128n) - 1n).toString();
|
|
1585
|
+
const high = (params.amount >> 128n).toString();
|
|
1586
|
+
const result = await account.execute([{
|
|
1587
|
+
contractAddress: this.contractAddress,
|
|
1588
|
+
entrypoint: "deposit",
|
|
1589
|
+
calldata: [
|
|
1590
|
+
low,
|
|
1591
|
+
high,
|
|
1592
|
+
params.encryptedAmount.c1.x.toString(),
|
|
1593
|
+
params.encryptedAmount.c1.y.toString(),
|
|
1594
|
+
params.encryptedAmount.c2.x.toString(),
|
|
1595
|
+
params.encryptedAmount.c2.y.toString(),
|
|
1596
|
+
params.proof.commitment.toString(),
|
|
1597
|
+
params.proof.challenge.toString(),
|
|
1598
|
+
params.proof.response.toString()
|
|
1599
|
+
]
|
|
1600
|
+
}]);
|
|
1601
|
+
return result.transaction_hash;
|
|
1602
|
+
}
|
|
1603
|
+
/** Withdraw from encrypted balance to public. */
|
|
1604
|
+
async withdraw(params) {
|
|
1605
|
+
const account = this.obelysk.requireAccount();
|
|
1606
|
+
const low = (params.amount & (1n << 128n) - 1n).toString();
|
|
1607
|
+
const high = (params.amount >> 128n).toString();
|
|
1608
|
+
const calldata = [
|
|
1609
|
+
low,
|
|
1610
|
+
high,
|
|
1611
|
+
params.encryptedDelta.c1.x.toString(),
|
|
1612
|
+
params.encryptedDelta.c1.y.toString(),
|
|
1613
|
+
params.encryptedDelta.c2.x.toString(),
|
|
1614
|
+
params.encryptedDelta.c2.y.toString(),
|
|
1615
|
+
params.proof.commitment.toString(),
|
|
1616
|
+
params.proof.challenge.toString(),
|
|
1617
|
+
params.proof.response.toString()
|
|
1618
|
+
];
|
|
1619
|
+
if (params.rangeProofData) calldata.push(...params.rangeProofData);
|
|
1620
|
+
const result = await account.execute([{
|
|
1621
|
+
contractAddress: this.contractAddress,
|
|
1622
|
+
entrypoint: "withdraw",
|
|
1623
|
+
calldata
|
|
1624
|
+
}]);
|
|
1625
|
+
return result.transaction_hash;
|
|
1626
|
+
}
|
|
1627
|
+
/** Private transfer between accounts. */
|
|
1628
|
+
async privateTransfer(params) {
|
|
1629
|
+
const account = this.obelysk.requireAccount();
|
|
1630
|
+
const result = await account.execute([{
|
|
1631
|
+
contractAddress: this.contractAddress,
|
|
1632
|
+
entrypoint: "private_transfer",
|
|
1633
|
+
calldata: [
|
|
1634
|
+
params.to,
|
|
1635
|
+
params.senderCipher.c1.x.toString(),
|
|
1636
|
+
params.senderCipher.c1.y.toString(),
|
|
1637
|
+
params.senderCipher.c2.x.toString(),
|
|
1638
|
+
params.senderCipher.c2.y.toString(),
|
|
1639
|
+
params.receiverCipher.c1.x.toString(),
|
|
1640
|
+
params.receiverCipher.c1.y.toString(),
|
|
1641
|
+
params.receiverCipher.c2.x.toString(),
|
|
1642
|
+
params.receiverCipher.c2.y.toString(),
|
|
1643
|
+
params.proof.commitment.toString(),
|
|
1644
|
+
params.proof.challenge.toString(),
|
|
1645
|
+
params.proof.response.toString(),
|
|
1646
|
+
params.senderAeHint.toString(),
|
|
1647
|
+
params.receiverAeHint.toString()
|
|
1648
|
+
]
|
|
1649
|
+
}]);
|
|
1650
|
+
return result.transaction_hash;
|
|
1651
|
+
}
|
|
1652
|
+
/** Request disclosure of a nullifier (audit). */
|
|
1653
|
+
async requestDisclosure(nullifier, reason) {
|
|
1654
|
+
const account = this.obelysk.requireAccount();
|
|
1655
|
+
const result = await account.execute([{
|
|
1656
|
+
contractAddress: this.contractAddress,
|
|
1657
|
+
entrypoint: "request_disclosure",
|
|
1658
|
+
calldata: [nullifier, reason]
|
|
1659
|
+
}]);
|
|
1660
|
+
return result.transaction_hash;
|
|
1661
|
+
}
|
|
1662
|
+
/** Ragequit: emergency withdrawal with proof. */
|
|
1663
|
+
async ragequit(proof) {
|
|
1664
|
+
const account = this.obelysk.requireAccount();
|
|
1665
|
+
const result = await account.execute([{
|
|
1666
|
+
contractAddress: this.contractAddress,
|
|
1667
|
+
entrypoint: "ragequit",
|
|
1668
|
+
calldata: proof
|
|
1669
|
+
}]);
|
|
1670
|
+
return result.transaction_hash;
|
|
1671
|
+
}
|
|
1672
|
+
/** Get the nullifier tree state. */
|
|
1673
|
+
async getNullifierTreeState() {
|
|
1674
|
+
const result = await this.obelysk.provider.callContract({
|
|
1675
|
+
contractAddress: this.contractAddress,
|
|
1676
|
+
entrypoint: "get_nullifier_tree_state",
|
|
1677
|
+
calldata: []
|
|
1678
|
+
});
|
|
1679
|
+
return { root: result[0], size: Number(BigInt(result[1])) };
|
|
1680
|
+
}
|
|
1681
|
+
/** Get account balance hints (for O(1) decryption). */
|
|
1682
|
+
async getAccountHints(address) {
|
|
1683
|
+
try {
|
|
1684
|
+
const result = await this.obelysk.provider.callContract({
|
|
1685
|
+
contractAddress: this.contractAddress,
|
|
1686
|
+
entrypoint: "get_account_hints",
|
|
1687
|
+
calldata: [address]
|
|
1688
|
+
});
|
|
1689
|
+
return {
|
|
1690
|
+
balanceHint: BigInt(result[0]),
|
|
1691
|
+
pendingInHint: BigInt(result[1]),
|
|
1692
|
+
pendingOutHint: BigInt(result[2])
|
|
1693
|
+
};
|
|
1694
|
+
} catch {
|
|
1695
|
+
return null;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1284
1698
|
};
|
|
1285
1699
|
|
|
1286
1700
|
// src/obelysk/shieldedSwap.ts
|
|
@@ -1349,6 +1763,1157 @@ var ShieldedSwapClient = class {
|
|
|
1349
1763
|
});
|
|
1350
1764
|
return result[0];
|
|
1351
1765
|
}
|
|
1766
|
+
/**
|
|
1767
|
+
* Execute a shielded swap (privacy pool -> AMM -> re-deposit).
|
|
1768
|
+
* This is the full privacy-preserving swap flow.
|
|
1769
|
+
*/
|
|
1770
|
+
async shieldedSwap(params) {
|
|
1771
|
+
const account = this.obelysk.requireAccount();
|
|
1772
|
+
const result = await account.execute([{
|
|
1773
|
+
contractAddress: this.contractAddress,
|
|
1774
|
+
entrypoint: "shielded_swap",
|
|
1775
|
+
calldata: [
|
|
1776
|
+
params.tokenIn,
|
|
1777
|
+
params.tokenOut,
|
|
1778
|
+
params.amountIn.toString(),
|
|
1779
|
+
params.minAmountOut.toString(),
|
|
1780
|
+
params.nullifier,
|
|
1781
|
+
params.merkleRoot,
|
|
1782
|
+
...params.merkleProof,
|
|
1783
|
+
params.newCommitment,
|
|
1784
|
+
params.encryptedAmount.toString(),
|
|
1785
|
+
params.proof
|
|
1786
|
+
]
|
|
1787
|
+
}]);
|
|
1788
|
+
return result.transaction_hash;
|
|
1789
|
+
}
|
|
1790
|
+
};
|
|
1791
|
+
|
|
1792
|
+
// src/obelysk/ecies.ts
|
|
1793
|
+
var HKDF_INFO = "obelysk-ecies-v1";
|
|
1794
|
+
var ECIES_VERSION = 1;
|
|
1795
|
+
async function eciesEncrypt(payload, relayerPubkeyHex) {
|
|
1796
|
+
const subtle = getCrypto();
|
|
1797
|
+
const relayerPubkeyBytes = hexToBytes(relayerPubkeyHex);
|
|
1798
|
+
if (relayerPubkeyBytes.length !== 32) {
|
|
1799
|
+
throw new Error(`Invalid relayer public key length: ${relayerPubkeyBytes.length} (expected 32)`);
|
|
1800
|
+
}
|
|
1801
|
+
const relayerPubkey = await subtle.importKey(
|
|
1802
|
+
"raw",
|
|
1803
|
+
relayerPubkeyBytes.buffer,
|
|
1804
|
+
{ name: "X25519" },
|
|
1805
|
+
false,
|
|
1806
|
+
[]
|
|
1807
|
+
);
|
|
1808
|
+
const ephemeralKeyPair = await subtle.generateKey(
|
|
1809
|
+
{ name: "X25519" },
|
|
1810
|
+
true,
|
|
1811
|
+
["deriveBits"]
|
|
1812
|
+
);
|
|
1813
|
+
const sharedBits = await subtle.deriveBits(
|
|
1814
|
+
{ name: "X25519", public: relayerPubkey },
|
|
1815
|
+
ephemeralKeyPair.privateKey,
|
|
1816
|
+
256
|
|
1817
|
+
);
|
|
1818
|
+
const sharedKey = await subtle.importKey(
|
|
1819
|
+
"raw",
|
|
1820
|
+
sharedBits,
|
|
1821
|
+
{ name: "HKDF" },
|
|
1822
|
+
false,
|
|
1823
|
+
["deriveKey"]
|
|
1824
|
+
);
|
|
1825
|
+
const aesKey = await subtle.deriveKey(
|
|
1826
|
+
{
|
|
1827
|
+
name: "HKDF",
|
|
1828
|
+
hash: "SHA-256",
|
|
1829
|
+
salt: new ArrayBuffer(0),
|
|
1830
|
+
info: new TextEncoder().encode(HKDF_INFO)
|
|
1831
|
+
},
|
|
1832
|
+
sharedKey,
|
|
1833
|
+
{ name: "AES-GCM", length: 256 },
|
|
1834
|
+
false,
|
|
1835
|
+
["encrypt"]
|
|
1836
|
+
);
|
|
1837
|
+
const nonce = getRandomBytes(12);
|
|
1838
|
+
const plaintext = new TextEncoder().encode(JSON.stringify(payload));
|
|
1839
|
+
const ciphertext = await subtle.encrypt(
|
|
1840
|
+
{ name: "AES-GCM", iv: nonce },
|
|
1841
|
+
aesKey,
|
|
1842
|
+
plaintext
|
|
1843
|
+
);
|
|
1844
|
+
const ephPubRaw = await subtle.exportKey("raw", ephemeralKeyPair.publicKey);
|
|
1845
|
+
return {
|
|
1846
|
+
ephemeral_pubkey: bytesToHex(new Uint8Array(ephPubRaw)),
|
|
1847
|
+
ciphertext: bytesToBase64(new Uint8Array(ciphertext)),
|
|
1848
|
+
nonce: bytesToHex(nonce),
|
|
1849
|
+
version: ECIES_VERSION
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1852
|
+
function getCrypto() {
|
|
1853
|
+
if (typeof globalThis.crypto?.subtle !== "undefined") {
|
|
1854
|
+
return globalThis.crypto.subtle;
|
|
1855
|
+
}
|
|
1856
|
+
try {
|
|
1857
|
+
const { webcrypto } = __require("crypto");
|
|
1858
|
+
return webcrypto.subtle;
|
|
1859
|
+
} catch {
|
|
1860
|
+
throw new Error("ECIES requires Web Crypto API (Node 20+ or a modern browser)");
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
function getRandomBytes(n) {
|
|
1864
|
+
if (typeof globalThis.crypto?.getRandomValues !== "undefined") {
|
|
1865
|
+
return globalThis.crypto.getRandomValues(new Uint8Array(n));
|
|
1866
|
+
}
|
|
1867
|
+
const { randomBytes } = __require("crypto");
|
|
1868
|
+
return new Uint8Array(randomBytes(n));
|
|
1869
|
+
}
|
|
1870
|
+
function hexToBytes(hex) {
|
|
1871
|
+
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
1872
|
+
const bytes = new Uint8Array(clean.length / 2);
|
|
1873
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1874
|
+
bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
1875
|
+
}
|
|
1876
|
+
return bytes;
|
|
1877
|
+
}
|
|
1878
|
+
function bytesToHex(bytes) {
|
|
1879
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1880
|
+
}
|
|
1881
|
+
function bytesToBase64(bytes) {
|
|
1882
|
+
if (typeof btoa === "function") {
|
|
1883
|
+
return btoa(String.fromCharCode(...bytes));
|
|
1884
|
+
}
|
|
1885
|
+
return Buffer.from(bytes).toString("base64");
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
// src/obelysk/denominations.ts
|
|
1889
|
+
var WBTC_DENOMINATIONS = [
|
|
1890
|
+
50000n,
|
|
1891
|
+
// 0.0005 BTC
|
|
1892
|
+
100000n,
|
|
1893
|
+
// 0.001 BTC
|
|
1894
|
+
500000n,
|
|
1895
|
+
// 0.005 BTC
|
|
1896
|
+
1000000n,
|
|
1897
|
+
// 0.01 BTC
|
|
1898
|
+
5000000n,
|
|
1899
|
+
// 0.05 BTC
|
|
1900
|
+
10000000n
|
|
1901
|
+
// 0.1 BTC
|
|
1902
|
+
];
|
|
1903
|
+
var SAGE_DENOMINATIONS = [
|
|
1904
|
+
10000000000000000n,
|
|
1905
|
+
// 0.01 SAGE
|
|
1906
|
+
50000000000000000n,
|
|
1907
|
+
// 0.05 SAGE
|
|
1908
|
+
100000000000000000n,
|
|
1909
|
+
// 0.1 SAGE
|
|
1910
|
+
500000000000000000n,
|
|
1911
|
+
// 0.5 SAGE
|
|
1912
|
+
1000000000000000000n,
|
|
1913
|
+
// 1 SAGE
|
|
1914
|
+
5000000000000000000n
|
|
1915
|
+
// 5 SAGE
|
|
1916
|
+
];
|
|
1917
|
+
var ETH_DENOMINATIONS = [
|
|
1918
|
+
1000000000000000n,
|
|
1919
|
+
// 0.001 ETH
|
|
1920
|
+
5000000000000000n,
|
|
1921
|
+
// 0.005 ETH
|
|
1922
|
+
10000000000000000n,
|
|
1923
|
+
// 0.01 ETH
|
|
1924
|
+
50000000000000000n,
|
|
1925
|
+
// 0.05 ETH
|
|
1926
|
+
100000000000000000n,
|
|
1927
|
+
// 0.1 ETH
|
|
1928
|
+
500000000000000000n
|
|
1929
|
+
// 0.5 ETH
|
|
1930
|
+
];
|
|
1931
|
+
var STRK_DENOMINATIONS = [
|
|
1932
|
+
50000000000000000n,
|
|
1933
|
+
// 0.05 STRK
|
|
1934
|
+
100000000000000000n,
|
|
1935
|
+
// 0.1 STRK
|
|
1936
|
+
500000000000000000n,
|
|
1937
|
+
// 0.5 STRK
|
|
1938
|
+
1000000000000000000n,
|
|
1939
|
+
// 1 STRK
|
|
1940
|
+
5000000000000000000n
|
|
1941
|
+
// 5 STRK
|
|
1942
|
+
];
|
|
1943
|
+
var USDC_DENOMINATIONS = [
|
|
1944
|
+
1000000n,
|
|
1945
|
+
// 1 USDC
|
|
1946
|
+
5000000n,
|
|
1947
|
+
// 5 USDC
|
|
1948
|
+
10000000n,
|
|
1949
|
+
// 10 USDC
|
|
1950
|
+
50000000n,
|
|
1951
|
+
// 50 USDC
|
|
1952
|
+
100000000n,
|
|
1953
|
+
// 100 USDC
|
|
1954
|
+
500000000n
|
|
1955
|
+
// 500 USDC
|
|
1956
|
+
];
|
|
1957
|
+
var VM31_DENOMINATIONS = {
|
|
1958
|
+
0: WBTC_DENOMINATIONS,
|
|
1959
|
+
1: SAGE_DENOMINATIONS,
|
|
1960
|
+
2: ETH_DENOMINATIONS,
|
|
1961
|
+
3: STRK_DENOMINATIONS,
|
|
1962
|
+
4: USDC_DENOMINATIONS
|
|
1963
|
+
};
|
|
1964
|
+
var DENOMINATION_BY_SYMBOL = {
|
|
1965
|
+
wbtc: WBTC_DENOMINATIONS,
|
|
1966
|
+
sage: SAGE_DENOMINATIONS,
|
|
1967
|
+
eth: ETH_DENOMINATIONS,
|
|
1968
|
+
strk: STRK_DENOMINATIONS,
|
|
1969
|
+
usdc: USDC_DENOMINATIONS
|
|
1970
|
+
};
|
|
1971
|
+
function validateDenomination(amount, assetIdOrSymbol) {
|
|
1972
|
+
let denoms;
|
|
1973
|
+
if (typeof assetIdOrSymbol === "number") {
|
|
1974
|
+
denoms = VM31_DENOMINATIONS[assetIdOrSymbol];
|
|
1975
|
+
} else {
|
|
1976
|
+
denoms = DENOMINATION_BY_SYMBOL[assetIdOrSymbol.toLowerCase()];
|
|
1977
|
+
}
|
|
1978
|
+
if (!denoms) return;
|
|
1979
|
+
if (!denoms.includes(amount)) {
|
|
1980
|
+
throw new Error(
|
|
1981
|
+
`Deposit must use a standard denomination for asset ${assetIdOrSymbol}. Got ${amount}. Valid: [${denoms.join(", ")}]`
|
|
1982
|
+
);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
function splitIntoDenominations(amount, assetIdOrSymbol) {
|
|
1986
|
+
let denoms;
|
|
1987
|
+
if (typeof assetIdOrSymbol === "number") {
|
|
1988
|
+
denoms = VM31_DENOMINATIONS[assetIdOrSymbol];
|
|
1989
|
+
} else {
|
|
1990
|
+
denoms = DENOMINATION_BY_SYMBOL[assetIdOrSymbol.toLowerCase()];
|
|
1991
|
+
}
|
|
1992
|
+
if (!denoms) return { denominations: [amount], remainder: 0n };
|
|
1993
|
+
const sorted = [...denoms].sort((a, b) => b > a ? 1 : b < a ? -1 : 0);
|
|
1994
|
+
const result = [];
|
|
1995
|
+
let remaining = amount;
|
|
1996
|
+
for (const denom of sorted) {
|
|
1997
|
+
while (remaining >= denom) {
|
|
1998
|
+
result.push(denom);
|
|
1999
|
+
remaining -= denom;
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
return { denominations: result, remainder: remaining };
|
|
2003
|
+
}
|
|
2004
|
+
function getDenominations(assetIdOrSymbol) {
|
|
2005
|
+
if (typeof assetIdOrSymbol === "number") {
|
|
2006
|
+
return VM31_DENOMINATIONS[assetIdOrSymbol];
|
|
2007
|
+
}
|
|
2008
|
+
return DENOMINATION_BY_SYMBOL[assetIdOrSymbol.toLowerCase()];
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
// src/obelysk/vm31Vault.ts
|
|
2012
|
+
var VM31VaultClient = class {
|
|
2013
|
+
constructor(obelysk) {
|
|
2014
|
+
this.obelysk = obelysk;
|
|
2015
|
+
}
|
|
2016
|
+
get contractAddress() {
|
|
2017
|
+
return this.obelysk.contracts.vm31_pool;
|
|
2018
|
+
}
|
|
2019
|
+
get relayerUrl() {
|
|
2020
|
+
return this.obelysk.relayerUrl ?? "https://relay.bitsage.network:3080";
|
|
2021
|
+
}
|
|
2022
|
+
get relayerApiKey() {
|
|
2023
|
+
return this.obelysk.relayerApiKey;
|
|
2024
|
+
}
|
|
2025
|
+
/** Cached relayer X25519 public key (fetched on first encrypted submit) */
|
|
2026
|
+
_relayerPubkey = null;
|
|
2027
|
+
async relayerFetch(path, init) {
|
|
2028
|
+
const url = `${this.relayerUrl}${path}`;
|
|
2029
|
+
const headers = { "Content-Type": "application/json" };
|
|
2030
|
+
if (this.relayerApiKey) headers["x-api-key"] = this.relayerApiKey;
|
|
2031
|
+
const res = await fetch(url, { ...init, headers: { ...headers, ...init?.headers } });
|
|
2032
|
+
if (!res.ok) {
|
|
2033
|
+
const body = await res.text().catch(() => "");
|
|
2034
|
+
throw new Error(`VM31 relayer ${res.status}: ${body}`);
|
|
2035
|
+
}
|
|
2036
|
+
return res.json();
|
|
2037
|
+
}
|
|
2038
|
+
/**
|
|
2039
|
+
* Submit a payload to the relayer, encrypted with ECIES.
|
|
2040
|
+
* Falls back to plaintext if `encrypt: false` is passed.
|
|
2041
|
+
*/
|
|
2042
|
+
async relayerSubmit(payload, encrypt = true) {
|
|
2043
|
+
if (!encrypt) {
|
|
2044
|
+
return this.relayerFetch("/submit", {
|
|
2045
|
+
method: "POST",
|
|
2046
|
+
body: JSON.stringify(payload)
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
2049
|
+
if (!this._relayerPubkey) {
|
|
2050
|
+
const keyInfo = await this.getRelayerPublicKey();
|
|
2051
|
+
this._relayerPubkey = keyInfo.publicKey;
|
|
2052
|
+
}
|
|
2053
|
+
const envelope = await eciesEncrypt(payload, this._relayerPubkey);
|
|
2054
|
+
return this.relayerFetch("/submit", {
|
|
2055
|
+
method: "POST",
|
|
2056
|
+
body: JSON.stringify(envelope)
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
// --------------------------------------------------------------------------
|
|
2060
|
+
// On-chain reads (callContract to vm31_pool)
|
|
2061
|
+
// --------------------------------------------------------------------------
|
|
2062
|
+
/** Get the current Merkle root of the VM31 commitment tree */
|
|
2063
|
+
async getMerkleRoot() {
|
|
2064
|
+
const result = await this.obelysk.provider.callContract({
|
|
2065
|
+
contractAddress: this.contractAddress,
|
|
2066
|
+
entrypoint: "get_merkle_root",
|
|
2067
|
+
calldata: []
|
|
2068
|
+
});
|
|
2069
|
+
return { lo: result[0], hi: result[1] };
|
|
2070
|
+
}
|
|
2071
|
+
/** Get the number of leaves in the commitment tree */
|
|
2072
|
+
async getTreeSize() {
|
|
2073
|
+
const result = await this.obelysk.provider.callContract({
|
|
2074
|
+
contractAddress: this.contractAddress,
|
|
2075
|
+
entrypoint: "get_tree_size",
|
|
2076
|
+
calldata: []
|
|
2077
|
+
});
|
|
2078
|
+
return Number(BigInt(result[0]));
|
|
2079
|
+
}
|
|
2080
|
+
/** Check if a nullifier has been spent */
|
|
2081
|
+
async isNullifierSpent(nullifier) {
|
|
2082
|
+
const result = await this.obelysk.provider.callContract({
|
|
2083
|
+
contractAddress: this.contractAddress,
|
|
2084
|
+
entrypoint: "is_nullifier_spent",
|
|
2085
|
+
calldata: [nullifier.lo, nullifier.hi]
|
|
2086
|
+
});
|
|
2087
|
+
return BigInt(result[0]) !== 0n;
|
|
2088
|
+
}
|
|
2089
|
+
/** Check if a Merkle root is known (valid historical root) */
|
|
2090
|
+
async isKnownRoot(root) {
|
|
2091
|
+
const result = await this.obelysk.provider.callContract({
|
|
2092
|
+
contractAddress: this.contractAddress,
|
|
2093
|
+
entrypoint: "is_known_root",
|
|
2094
|
+
calldata: [root.lo, root.hi]
|
|
2095
|
+
});
|
|
2096
|
+
return BigInt(result[0]) !== 0n;
|
|
2097
|
+
}
|
|
2098
|
+
/** Get the total balance of an asset held in the pool */
|
|
2099
|
+
async getAssetBalance(assetId) {
|
|
2100
|
+
const result = await this.obelysk.provider.callContract({
|
|
2101
|
+
contractAddress: this.contractAddress,
|
|
2102
|
+
entrypoint: "get_asset_balance",
|
|
2103
|
+
calldata: [assetId.toString()]
|
|
2104
|
+
});
|
|
2105
|
+
return BigInt(result[0]);
|
|
2106
|
+
}
|
|
2107
|
+
/** Get on-chain batch status (0=NONE, 1=SUBMITTED, 2=FINALIZED, 3=CANCELLED) */
|
|
2108
|
+
async getBatchStatus(batchId) {
|
|
2109
|
+
const result = await this.obelysk.provider.callContract({
|
|
2110
|
+
contractAddress: this.contractAddress,
|
|
2111
|
+
entrypoint: "get_batch_status",
|
|
2112
|
+
calldata: [batchId]
|
|
2113
|
+
});
|
|
2114
|
+
return Number(BigInt(result[0]));
|
|
2115
|
+
}
|
|
2116
|
+
/** Get the currently active batch ID */
|
|
2117
|
+
async getActiveBatchId() {
|
|
2118
|
+
const result = await this.obelysk.provider.callContract({
|
|
2119
|
+
contractAddress: this.contractAddress,
|
|
2120
|
+
entrypoint: "get_active_batch_id",
|
|
2121
|
+
calldata: []
|
|
2122
|
+
});
|
|
2123
|
+
return result[0];
|
|
2124
|
+
}
|
|
2125
|
+
/** Get total transaction count in a batch */
|
|
2126
|
+
async getBatchTotalTxs(batchId) {
|
|
2127
|
+
const result = await this.obelysk.provider.callContract({
|
|
2128
|
+
contractAddress: this.contractAddress,
|
|
2129
|
+
entrypoint: "get_batch_total_txs",
|
|
2130
|
+
calldata: [batchId]
|
|
2131
|
+
});
|
|
2132
|
+
return Number(BigInt(result[0]));
|
|
2133
|
+
}
|
|
2134
|
+
/** Get number of processed transactions in a batch */
|
|
2135
|
+
async getBatchProcessedCount(batchId) {
|
|
2136
|
+
const result = await this.obelysk.provider.callContract({
|
|
2137
|
+
contractAddress: this.contractAddress,
|
|
2138
|
+
entrypoint: "get_batch_processed_count",
|
|
2139
|
+
calldata: [batchId]
|
|
2140
|
+
});
|
|
2141
|
+
return Number(BigInt(result[0]));
|
|
2142
|
+
}
|
|
2143
|
+
/** Get the ERC-20 token address for a VM31 asset ID */
|
|
2144
|
+
async getAssetToken(assetId) {
|
|
2145
|
+
const result = await this.obelysk.provider.callContract({
|
|
2146
|
+
contractAddress: this.contractAddress,
|
|
2147
|
+
entrypoint: "get_asset_token",
|
|
2148
|
+
calldata: [assetId.toString()]
|
|
2149
|
+
});
|
|
2150
|
+
return result[0];
|
|
2151
|
+
}
|
|
2152
|
+
/** Get the VM31 asset ID for a given ERC-20 token address */
|
|
2153
|
+
async getTokenAsset(tokenAddress) {
|
|
2154
|
+
const result = await this.obelysk.provider.callContract({
|
|
2155
|
+
contractAddress: this.contractAddress,
|
|
2156
|
+
entrypoint: "get_token_asset",
|
|
2157
|
+
calldata: [tokenAddress]
|
|
2158
|
+
});
|
|
2159
|
+
return Number(BigInt(result[0]));
|
|
2160
|
+
}
|
|
2161
|
+
/** Check if the VM31Pool contract is paused */
|
|
2162
|
+
async isPaused() {
|
|
2163
|
+
const result = await this.obelysk.provider.callContract({
|
|
2164
|
+
contractAddress: this.contractAddress,
|
|
2165
|
+
entrypoint: "is_paused",
|
|
2166
|
+
calldata: []
|
|
2167
|
+
});
|
|
2168
|
+
return BigInt(result[0]) !== 0n;
|
|
2169
|
+
}
|
|
2170
|
+
/** Get the contract owner address */
|
|
2171
|
+
async getOwner() {
|
|
2172
|
+
const result = await this.obelysk.provider.callContract({
|
|
2173
|
+
contractAddress: this.contractAddress,
|
|
2174
|
+
entrypoint: "get_owner",
|
|
2175
|
+
calldata: []
|
|
2176
|
+
});
|
|
2177
|
+
return result[0];
|
|
2178
|
+
}
|
|
2179
|
+
/** Get the authorized relayer address */
|
|
2180
|
+
async getRelayer() {
|
|
2181
|
+
const result = await this.obelysk.provider.callContract({
|
|
2182
|
+
contractAddress: this.contractAddress,
|
|
2183
|
+
entrypoint: "get_relayer",
|
|
2184
|
+
calldata: []
|
|
2185
|
+
});
|
|
2186
|
+
return result[0];
|
|
2187
|
+
}
|
|
2188
|
+
/** Get batch timeout in blocks */
|
|
2189
|
+
async getBatchTimeoutBlocks() {
|
|
2190
|
+
const result = await this.obelysk.provider.callContract({
|
|
2191
|
+
contractAddress: this.contractAddress,
|
|
2192
|
+
entrypoint: "get_batch_timeout_blocks",
|
|
2193
|
+
calldata: []
|
|
2194
|
+
});
|
|
2195
|
+
return Number(BigInt(result[0]));
|
|
2196
|
+
}
|
|
2197
|
+
// --------------------------------------------------------------------------
|
|
2198
|
+
// Relayer HTTP calls
|
|
2199
|
+
// --------------------------------------------------------------------------
|
|
2200
|
+
/** Check relayer health (no auth required) */
|
|
2201
|
+
async getRelayerHealth() {
|
|
2202
|
+
return this.relayerFetch("/health");
|
|
2203
|
+
}
|
|
2204
|
+
/** Get relayer queue status */
|
|
2205
|
+
async getRelayerStatus() {
|
|
2206
|
+
const data = await this.relayerFetch("/status");
|
|
2207
|
+
return {
|
|
2208
|
+
pendingTransactions: data.pending_transactions,
|
|
2209
|
+
batchMaxSize: data.batch_max_size,
|
|
2210
|
+
batchTimeoutSecs: data.batch_timeout_secs
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
/** Get the relayer's public encryption key */
|
|
2214
|
+
async getRelayerPublicKey() {
|
|
2215
|
+
const data = await this.relayerFetch("/public-key");
|
|
2216
|
+
return {
|
|
2217
|
+
publicKey: data.public_key,
|
|
2218
|
+
version: data.version,
|
|
2219
|
+
algorithm: data.algorithm
|
|
2220
|
+
};
|
|
2221
|
+
}
|
|
2222
|
+
/**
|
|
2223
|
+
* Submit a deposit transaction to the relayer.
|
|
2224
|
+
* Validates denomination (privacy gap #7) and encrypts with ECIES by default.
|
|
2225
|
+
* @param encrypt - Set to false for legacy plaintext mode (default: true)
|
|
2226
|
+
*/
|
|
2227
|
+
async submitDeposit(params, encrypt = true) {
|
|
2228
|
+
validateDenomination(params.amount, params.assetId);
|
|
2229
|
+
return this.relayerSubmit({
|
|
2230
|
+
type: "deposit",
|
|
2231
|
+
amount: Number(params.amount),
|
|
2232
|
+
asset_id: params.assetId,
|
|
2233
|
+
recipient_pubkey: params.recipientPubkey,
|
|
2234
|
+
recipient_viewing_key: params.recipientViewingKey
|
|
2235
|
+
}, encrypt);
|
|
2236
|
+
}
|
|
2237
|
+
/**
|
|
2238
|
+
* Submit a withdrawal transaction to the relayer.
|
|
2239
|
+
* Withdrawals are not denomination-restricted.
|
|
2240
|
+
* @param encrypt - Set to false for legacy plaintext mode (default: true)
|
|
2241
|
+
*/
|
|
2242
|
+
async submitWithdraw(params, encrypt = true) {
|
|
2243
|
+
return this.relayerSubmit({
|
|
2244
|
+
type: "withdraw",
|
|
2245
|
+
amount: Number(params.amount),
|
|
2246
|
+
asset_id: params.assetId,
|
|
2247
|
+
note: {
|
|
2248
|
+
owner_pubkey: params.note.owner_pubkey,
|
|
2249
|
+
asset_id: params.note.asset_id,
|
|
2250
|
+
amount_lo: params.note.amount_lo,
|
|
2251
|
+
amount_hi: params.note.amount_hi,
|
|
2252
|
+
blinding: params.note.blinding
|
|
2253
|
+
},
|
|
2254
|
+
spending_key: params.spendingKey,
|
|
2255
|
+
merkle_path: params.merklePath,
|
|
2256
|
+
merkle_root: params.merkleRoot,
|
|
2257
|
+
withdrawal_binding: params.withdrawalBinding,
|
|
2258
|
+
binding_salt: params.bindingSalt
|
|
2259
|
+
}, encrypt);
|
|
2260
|
+
}
|
|
2261
|
+
/**
|
|
2262
|
+
* Submit a private transfer transaction to the relayer.
|
|
2263
|
+
* Transfers are not denomination-restricted.
|
|
2264
|
+
* @param encrypt - Set to false for legacy plaintext mode (default: true)
|
|
2265
|
+
*/
|
|
2266
|
+
async submitTransfer(params, encrypt = true) {
|
|
2267
|
+
return this.relayerSubmit({
|
|
2268
|
+
type: "transfer",
|
|
2269
|
+
amount: Number(params.amount),
|
|
2270
|
+
asset_id: params.assetId,
|
|
2271
|
+
recipient_pubkey: params.recipientPubkey,
|
|
2272
|
+
recipient_viewing_key: params.recipientViewingKey,
|
|
2273
|
+
sender_viewing_key: params.senderViewingKey,
|
|
2274
|
+
input_notes: params.inputNotes.map((n) => ({
|
|
2275
|
+
note: n.note,
|
|
2276
|
+
spending_key: n.spendingKey,
|
|
2277
|
+
merkle_path: n.merklePath
|
|
2278
|
+
})),
|
|
2279
|
+
merkle_root: params.merkleRoot
|
|
2280
|
+
}, encrypt);
|
|
2281
|
+
}
|
|
2282
|
+
/** Query batch info from the relayer */
|
|
2283
|
+
async queryBatch(batchId) {
|
|
2284
|
+
const data = await this.relayerFetch(`/batch/${batchId}`);
|
|
2285
|
+
return {
|
|
2286
|
+
id: data.id,
|
|
2287
|
+
status: data.status,
|
|
2288
|
+
txCount: data.tx_count,
|
|
2289
|
+
proofHash: data.proof_hash,
|
|
2290
|
+
batchIdOnchain: data.batch_id_onchain,
|
|
2291
|
+
txHash: data.tx_hash,
|
|
2292
|
+
createdAt: data.created_at,
|
|
2293
|
+
error: data.error
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
/** Fetch Merkle path for a commitment from the relayer */
|
|
2297
|
+
async fetchMerklePath(commitment) {
|
|
2298
|
+
const data = await this.relayerFetch(`/merkle-path/${commitment}`);
|
|
2299
|
+
return {
|
|
2300
|
+
commitment: data.commitment,
|
|
2301
|
+
merklePath: data.merkle_path ?? null,
|
|
2302
|
+
merkleRoot: data.merkle_root ?? null,
|
|
2303
|
+
batchId: data.batch_id ?? null,
|
|
2304
|
+
createdAt: data.created_at,
|
|
2305
|
+
status: data.status
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
/** Force the relayer to prove the current batch immediately */
|
|
2309
|
+
async forceProve() {
|
|
2310
|
+
const data = await this.relayerFetch("/prove", { method: "POST" });
|
|
2311
|
+
return {
|
|
2312
|
+
status: data.status,
|
|
2313
|
+
batchId: data.batch_id
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2316
|
+
// --------------------------------------------------------------------------
|
|
2317
|
+
// Helpers
|
|
2318
|
+
// --------------------------------------------------------------------------
|
|
2319
|
+
/** Encode a bigint amount into M31 lo/hi pair (31-bit limbs) */
|
|
2320
|
+
static encodeAmountM31(amount) {
|
|
2321
|
+
const lo = Number(amount & 0x7FFFFFFFn);
|
|
2322
|
+
const hi = Number(amount >> 31n);
|
|
2323
|
+
return { lo, hi };
|
|
2324
|
+
}
|
|
2325
|
+
/** Decode an M31 lo/hi pair back into a bigint amount */
|
|
2326
|
+
static decodeAmountM31(lo, hi) {
|
|
2327
|
+
return BigInt(lo) + BigInt(hi) * (1n << 31n);
|
|
2328
|
+
}
|
|
2329
|
+
};
|
|
2330
|
+
|
|
2331
|
+
// src/obelysk/vm31Bridge.ts
|
|
2332
|
+
var VM31BridgeClient = class {
|
|
2333
|
+
constructor(obelysk) {
|
|
2334
|
+
this.obelysk = obelysk;
|
|
2335
|
+
}
|
|
2336
|
+
get contractAddress() {
|
|
2337
|
+
return this.obelysk.contracts.vm31_bridge;
|
|
2338
|
+
}
|
|
2339
|
+
/** Get the authorized relayer address */
|
|
2340
|
+
async getRelayer() {
|
|
2341
|
+
const result = await this.obelysk.provider.callContract({
|
|
2342
|
+
contractAddress: this.contractAddress,
|
|
2343
|
+
entrypoint: "get_relayer",
|
|
2344
|
+
calldata: []
|
|
2345
|
+
});
|
|
2346
|
+
return result[0];
|
|
2347
|
+
}
|
|
2348
|
+
/** Get the VM31Pool contract address */
|
|
2349
|
+
async getVM31Pool() {
|
|
2350
|
+
const result = await this.obelysk.provider.callContract({
|
|
2351
|
+
contractAddress: this.contractAddress,
|
|
2352
|
+
entrypoint: "get_vm31_pool",
|
|
2353
|
+
calldata: []
|
|
2354
|
+
});
|
|
2355
|
+
return result[0];
|
|
2356
|
+
}
|
|
2357
|
+
/** Get the ConfidentialTransfer contract address */
|
|
2358
|
+
async getConfidentialTransfer() {
|
|
2359
|
+
const result = await this.obelysk.provider.callContract({
|
|
2360
|
+
contractAddress: this.contractAddress,
|
|
2361
|
+
entrypoint: "get_confidential_transfer",
|
|
2362
|
+
calldata: []
|
|
2363
|
+
});
|
|
2364
|
+
return result[0];
|
|
2365
|
+
}
|
|
2366
|
+
/** Get the asset pair mapping for a given token address */
|
|
2367
|
+
async getAssetPair(tokenAddress) {
|
|
2368
|
+
try {
|
|
2369
|
+
const result = await this.obelysk.provider.callContract({
|
|
2370
|
+
contractAddress: this.contractAddress,
|
|
2371
|
+
entrypoint: "get_asset_pair",
|
|
2372
|
+
calldata: [tokenAddress]
|
|
2373
|
+
});
|
|
2374
|
+
return {
|
|
2375
|
+
vm31AssetId: result[0],
|
|
2376
|
+
confidentialAssetId: result[1]
|
|
2377
|
+
};
|
|
2378
|
+
} catch {
|
|
2379
|
+
return null;
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
/** Check if a bridge key has already been processed */
|
|
2383
|
+
async isBridgeKeyProcessed(bridgeKey) {
|
|
2384
|
+
const result = await this.obelysk.provider.callContract({
|
|
2385
|
+
contractAddress: this.contractAddress,
|
|
2386
|
+
entrypoint: "is_bridge_key_processed",
|
|
2387
|
+
calldata: [bridgeKey]
|
|
2388
|
+
});
|
|
2389
|
+
return BigInt(result[0]) !== 0n;
|
|
2390
|
+
}
|
|
2391
|
+
/** Compute a bridge key from withdrawal parameters */
|
|
2392
|
+
async computeBridgeKey(params) {
|
|
2393
|
+
const low = params.amount & (1n << 128n) - 1n;
|
|
2394
|
+
const high = params.amount >> 128n;
|
|
2395
|
+
const result = await this.obelysk.provider.callContract({
|
|
2396
|
+
contractAddress: this.contractAddress,
|
|
2397
|
+
entrypoint: "compute_bridge_key",
|
|
2398
|
+
calldata: [
|
|
2399
|
+
params.batchId,
|
|
2400
|
+
params.withdrawalIdx.toString(),
|
|
2401
|
+
params.payoutRecipient,
|
|
2402
|
+
params.creditRecipient,
|
|
2403
|
+
params.token,
|
|
2404
|
+
low.toString(),
|
|
2405
|
+
high.toString()
|
|
2406
|
+
]
|
|
2407
|
+
});
|
|
2408
|
+
return result[0];
|
|
2409
|
+
}
|
|
2410
|
+
/** Get full bridge configuration (relayer, vm31Pool, confidentialTransfer) */
|
|
2411
|
+
async getConfig() {
|
|
2412
|
+
const [relayer, vm31Pool, confidentialTransfer] = await Promise.all([
|
|
2413
|
+
this.getRelayer(),
|
|
2414
|
+
this.getVM31Pool(),
|
|
2415
|
+
this.getConfidentialTransfer()
|
|
2416
|
+
]);
|
|
2417
|
+
return { relayer, vm31Pool, confidentialTransfer };
|
|
2418
|
+
}
|
|
2419
|
+
/** Get pending upgrade info, if any */
|
|
2420
|
+
async getPendingUpgrade() {
|
|
2421
|
+
const result = await this.obelysk.provider.callContract({
|
|
2422
|
+
contractAddress: this.contractAddress,
|
|
2423
|
+
entrypoint: "get_pending_upgrade",
|
|
2424
|
+
calldata: []
|
|
2425
|
+
});
|
|
2426
|
+
const classHash = result[0];
|
|
2427
|
+
if (classHash === "0x0") return null;
|
|
2428
|
+
return {
|
|
2429
|
+
classHash,
|
|
2430
|
+
scheduledAt: Number(BigInt(result[1]))
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2433
|
+
/** Check if the bridge contract is paused */
|
|
2434
|
+
async isPaused() {
|
|
2435
|
+
try {
|
|
2436
|
+
const result = await this.obelysk.provider.callContract({
|
|
2437
|
+
contractAddress: this.contractAddress,
|
|
2438
|
+
entrypoint: "is_paused",
|
|
2439
|
+
calldata: []
|
|
2440
|
+
});
|
|
2441
|
+
return BigInt(result[0]) !== 0n;
|
|
2442
|
+
} catch {
|
|
2443
|
+
return false;
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
};
|
|
2447
|
+
|
|
2448
|
+
// src/obelysk/otcOrderbook.ts
|
|
2449
|
+
var OrderSide = /* @__PURE__ */ ((OrderSide2) => {
|
|
2450
|
+
OrderSide2[OrderSide2["Buy"] = 0] = "Buy";
|
|
2451
|
+
OrderSide2[OrderSide2["Sell"] = 1] = "Sell";
|
|
2452
|
+
return OrderSide2;
|
|
2453
|
+
})(OrderSide || {});
|
|
2454
|
+
var OrderType = /* @__PURE__ */ ((OrderType2) => {
|
|
2455
|
+
OrderType2[OrderType2["Limit"] = 0] = "Limit";
|
|
2456
|
+
OrderType2[OrderType2["Market"] = 1] = "Market";
|
|
2457
|
+
return OrderType2;
|
|
2458
|
+
})(OrderType || {});
|
|
2459
|
+
var OrderStatus = /* @__PURE__ */ ((OrderStatus2) => {
|
|
2460
|
+
OrderStatus2[OrderStatus2["Open"] = 0] = "Open";
|
|
2461
|
+
OrderStatus2[OrderStatus2["PartialFill"] = 1] = "PartialFill";
|
|
2462
|
+
OrderStatus2[OrderStatus2["Filled"] = 2] = "Filled";
|
|
2463
|
+
OrderStatus2[OrderStatus2["Cancelled"] = 3] = "Cancelled";
|
|
2464
|
+
OrderStatus2[OrderStatus2["Expired"] = 4] = "Expired";
|
|
2465
|
+
return OrderStatus2;
|
|
2466
|
+
})(OrderStatus || {});
|
|
2467
|
+
var U128_MASK = (1n << 128n) - 1n;
|
|
2468
|
+
function splitU256(value) {
|
|
2469
|
+
return {
|
|
2470
|
+
low: (value & U128_MASK).toString(),
|
|
2471
|
+
high: (value >> 128n).toString()
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
function parseU256(result, offset) {
|
|
2475
|
+
return BigInt(result[offset]) + (BigInt(result[offset + 1]) << 128n);
|
|
2476
|
+
}
|
|
2477
|
+
var ORDER_SIDE_VALUES = [0 /* Buy */, 1 /* Sell */];
|
|
2478
|
+
var ORDER_TYPE_VALUES = [0 /* Limit */, 1 /* Market */];
|
|
2479
|
+
var ORDER_STATUS_VALUES = [
|
|
2480
|
+
0 /* Open */,
|
|
2481
|
+
1 /* PartialFill */,
|
|
2482
|
+
2 /* Filled */,
|
|
2483
|
+
3 /* Cancelled */,
|
|
2484
|
+
4 /* Expired */
|
|
2485
|
+
];
|
|
2486
|
+
var ORDER_FELT_COUNT = 15;
|
|
2487
|
+
function parseOrder(result, offset) {
|
|
2488
|
+
return {
|
|
2489
|
+
orderId: parseU256(result, offset),
|
|
2490
|
+
maker: result[offset + 2],
|
|
2491
|
+
pairId: Number(BigInt(result[offset + 3])),
|
|
2492
|
+
side: ORDER_SIDE_VALUES[Number(BigInt(result[offset + 4]))] ?? 0 /* Buy */,
|
|
2493
|
+
orderType: ORDER_TYPE_VALUES[Number(BigInt(result[offset + 5]))] ?? 0 /* Limit */,
|
|
2494
|
+
price: parseU256(result, offset + 6),
|
|
2495
|
+
amount: parseU256(result, offset + 8),
|
|
2496
|
+
remaining: parseU256(result, offset + 10),
|
|
2497
|
+
status: ORDER_STATUS_VALUES[Number(BigInt(result[offset + 12]))] ?? 0 /* Open */,
|
|
2498
|
+
createdAt: Number(BigInt(result[offset + 13])),
|
|
2499
|
+
expiresAt: Number(BigInt(result[offset + 14]))
|
|
2500
|
+
};
|
|
2501
|
+
}
|
|
2502
|
+
var TRADE_FELT_COUNT = 15;
|
|
2503
|
+
function parseTrade(result, offset) {
|
|
2504
|
+
return {
|
|
2505
|
+
tradeId: parseU256(result, offset),
|
|
2506
|
+
makerOrderId: parseU256(result, offset + 2),
|
|
2507
|
+
takerOrderId: parseU256(result, offset + 4),
|
|
2508
|
+
maker: result[offset + 6],
|
|
2509
|
+
taker: result[offset + 7],
|
|
2510
|
+
price: parseU256(result, offset + 8),
|
|
2511
|
+
amount: parseU256(result, offset + 10),
|
|
2512
|
+
quoteAmount: parseU256(result, offset + 12),
|
|
2513
|
+
executedAt: Number(BigInt(result[offset + 14]))
|
|
2514
|
+
};
|
|
2515
|
+
}
|
|
2516
|
+
var OTCOrderbookClient = class {
|
|
2517
|
+
constructor(obelysk) {
|
|
2518
|
+
this.obelysk = obelysk;
|
|
2519
|
+
}
|
|
2520
|
+
get contractAddress() {
|
|
2521
|
+
const addr = this.obelysk.contracts.otc_orderbook;
|
|
2522
|
+
if (!addr) throw new Error("OTC Orderbook contract not configured for this network");
|
|
2523
|
+
return addr;
|
|
2524
|
+
}
|
|
2525
|
+
// --------------------------------------------------------------------------
|
|
2526
|
+
// Write operations
|
|
2527
|
+
// --------------------------------------------------------------------------
|
|
2528
|
+
/**
|
|
2529
|
+
* Place a limit order on the orderbook.
|
|
2530
|
+
*
|
|
2531
|
+
* The contract handles internal matching against resting orders.
|
|
2532
|
+
* Returns the transaction hash.
|
|
2533
|
+
*/
|
|
2534
|
+
async placeLimitOrder(params) {
|
|
2535
|
+
const account = this.obelysk.requireAccount();
|
|
2536
|
+
const { low: pLow, high: pHigh } = splitU256(params.price);
|
|
2537
|
+
const { low: aLow, high: aHigh } = splitU256(params.amount);
|
|
2538
|
+
const result = await account.execute([
|
|
2539
|
+
{
|
|
2540
|
+
contractAddress: this.contractAddress,
|
|
2541
|
+
entrypoint: "place_limit_order",
|
|
2542
|
+
calldata: [
|
|
2543
|
+
params.pairId.toString(),
|
|
2544
|
+
params.side.toString(),
|
|
2545
|
+
pLow,
|
|
2546
|
+
pHigh,
|
|
2547
|
+
aLow,
|
|
2548
|
+
aHigh,
|
|
2549
|
+
(params.expiresIn ?? 0).toString()
|
|
2550
|
+
]
|
|
2551
|
+
}
|
|
2552
|
+
]);
|
|
2553
|
+
return result.transaction_hash;
|
|
2554
|
+
}
|
|
2555
|
+
/**
|
|
2556
|
+
* Place a market order that fills immediately against resting liquidity.
|
|
2557
|
+
* Returns the transaction hash.
|
|
2558
|
+
*/
|
|
2559
|
+
async placeMarketOrder(params) {
|
|
2560
|
+
const account = this.obelysk.requireAccount();
|
|
2561
|
+
const { low: aLow, high: aHigh } = splitU256(params.amount);
|
|
2562
|
+
const result = await account.execute([
|
|
2563
|
+
{
|
|
2564
|
+
contractAddress: this.contractAddress,
|
|
2565
|
+
entrypoint: "place_market_order",
|
|
2566
|
+
calldata: [
|
|
2567
|
+
params.pairId.toString(),
|
|
2568
|
+
params.side.toString(),
|
|
2569
|
+
aLow,
|
|
2570
|
+
aHigh
|
|
2571
|
+
]
|
|
2572
|
+
}
|
|
2573
|
+
]);
|
|
2574
|
+
return result.transaction_hash;
|
|
2575
|
+
}
|
|
2576
|
+
/**
|
|
2577
|
+
* Cancel a single order by ID.
|
|
2578
|
+
* Only the maker can cancel their own order.
|
|
2579
|
+
*/
|
|
2580
|
+
async cancelOrder(orderId) {
|
|
2581
|
+
const account = this.obelysk.requireAccount();
|
|
2582
|
+
const { low, high } = splitU256(orderId);
|
|
2583
|
+
const result = await account.execute([
|
|
2584
|
+
{
|
|
2585
|
+
contractAddress: this.contractAddress,
|
|
2586
|
+
entrypoint: "cancel_order",
|
|
2587
|
+
calldata: [low, high]
|
|
2588
|
+
}
|
|
2589
|
+
]);
|
|
2590
|
+
return result.transaction_hash;
|
|
2591
|
+
}
|
|
2592
|
+
/**
|
|
2593
|
+
* Cancel all open orders for the caller.
|
|
2594
|
+
*/
|
|
2595
|
+
async cancelAllOrders() {
|
|
2596
|
+
const account = this.obelysk.requireAccount();
|
|
2597
|
+
const result = await account.execute([
|
|
2598
|
+
{
|
|
2599
|
+
contractAddress: this.contractAddress,
|
|
2600
|
+
entrypoint: "cancel_all_orders",
|
|
2601
|
+
calldata: []
|
|
2602
|
+
}
|
|
2603
|
+
]);
|
|
2604
|
+
return result.transaction_hash;
|
|
2605
|
+
}
|
|
2606
|
+
/**
|
|
2607
|
+
* Place multiple limit orders in a single transaction.
|
|
2608
|
+
*
|
|
2609
|
+
* Cairo expects an array of (u8, OrderSide, u256, u256, u64) tuples.
|
|
2610
|
+
* Calldata: [length, ...flattened orders].
|
|
2611
|
+
*/
|
|
2612
|
+
async batchPlaceOrders(orders) {
|
|
2613
|
+
const account = this.obelysk.requireAccount();
|
|
2614
|
+
const calldata = [orders.length.toString()];
|
|
2615
|
+
for (const order of orders) {
|
|
2616
|
+
const { low: pLow, high: pHigh } = splitU256(order.price);
|
|
2617
|
+
const { low: aLow, high: aHigh } = splitU256(order.amount);
|
|
2618
|
+
calldata.push(
|
|
2619
|
+
order.pairId.toString(),
|
|
2620
|
+
order.side.toString(),
|
|
2621
|
+
pLow,
|
|
2622
|
+
pHigh,
|
|
2623
|
+
aLow,
|
|
2624
|
+
aHigh,
|
|
2625
|
+
(order.expiresIn ?? 0).toString()
|
|
2626
|
+
);
|
|
2627
|
+
}
|
|
2628
|
+
const result = await account.execute([
|
|
2629
|
+
{
|
|
2630
|
+
contractAddress: this.contractAddress,
|
|
2631
|
+
entrypoint: "batch_place_orders",
|
|
2632
|
+
calldata
|
|
2633
|
+
}
|
|
2634
|
+
]);
|
|
2635
|
+
return result.transaction_hash;
|
|
2636
|
+
}
|
|
2637
|
+
/**
|
|
2638
|
+
* Cancel multiple orders in a single transaction.
|
|
2639
|
+
*
|
|
2640
|
+
* Calldata: [length, ...flattened u256 order IDs].
|
|
2641
|
+
*/
|
|
2642
|
+
async batchCancelOrders(orderIds) {
|
|
2643
|
+
const account = this.obelysk.requireAccount();
|
|
2644
|
+
const calldata = [orderIds.length.toString()];
|
|
2645
|
+
for (const id of orderIds) {
|
|
2646
|
+
const { low, high } = splitU256(id);
|
|
2647
|
+
calldata.push(low, high);
|
|
2648
|
+
}
|
|
2649
|
+
const result = await account.execute([
|
|
2650
|
+
{
|
|
2651
|
+
contractAddress: this.contractAddress,
|
|
2652
|
+
entrypoint: "batch_cancel_orders",
|
|
2653
|
+
calldata
|
|
2654
|
+
}
|
|
2655
|
+
]);
|
|
2656
|
+
return result.transaction_hash;
|
|
2657
|
+
}
|
|
2658
|
+
// --------------------------------------------------------------------------
|
|
2659
|
+
// Read operations
|
|
2660
|
+
// --------------------------------------------------------------------------
|
|
2661
|
+
/**
|
|
2662
|
+
* Get a single order by ID.
|
|
2663
|
+
*/
|
|
2664
|
+
async getOrder(orderId) {
|
|
2665
|
+
const { low, high } = splitU256(orderId);
|
|
2666
|
+
const result = await this.obelysk.provider.callContract({
|
|
2667
|
+
contractAddress: this.contractAddress,
|
|
2668
|
+
entrypoint: "get_order",
|
|
2669
|
+
calldata: [low, high]
|
|
2670
|
+
});
|
|
2671
|
+
return parseOrder(result, 0);
|
|
2672
|
+
}
|
|
2673
|
+
/**
|
|
2674
|
+
* Get all order IDs belonging to a user.
|
|
2675
|
+
*/
|
|
2676
|
+
async getUserOrders(user) {
|
|
2677
|
+
const result = await this.obelysk.provider.callContract({
|
|
2678
|
+
contractAddress: this.contractAddress,
|
|
2679
|
+
entrypoint: "get_user_orders",
|
|
2680
|
+
calldata: [user]
|
|
2681
|
+
});
|
|
2682
|
+
const len = Number(BigInt(result[0]));
|
|
2683
|
+
const ids = [];
|
|
2684
|
+
for (let i = 0; i < len; i++) {
|
|
2685
|
+
ids.push(parseU256(result, 1 + i * 2));
|
|
2686
|
+
}
|
|
2687
|
+
return ids;
|
|
2688
|
+
}
|
|
2689
|
+
/**
|
|
2690
|
+
* Get the best (highest) bid price for a trading pair.
|
|
2691
|
+
*/
|
|
2692
|
+
async getBestBid(pairId) {
|
|
2693
|
+
const result = await this.obelysk.provider.callContract({
|
|
2694
|
+
contractAddress: this.contractAddress,
|
|
2695
|
+
entrypoint: "get_best_bid",
|
|
2696
|
+
calldata: [pairId.toString()]
|
|
2697
|
+
});
|
|
2698
|
+
return parseU256(result, 0);
|
|
2699
|
+
}
|
|
2700
|
+
/**
|
|
2701
|
+
* Get the best (lowest) ask price for a trading pair.
|
|
2702
|
+
*/
|
|
2703
|
+
async getBestAsk(pairId) {
|
|
2704
|
+
const result = await this.obelysk.provider.callContract({
|
|
2705
|
+
contractAddress: this.contractAddress,
|
|
2706
|
+
entrypoint: "get_best_ask",
|
|
2707
|
+
calldata: [pairId.toString()]
|
|
2708
|
+
});
|
|
2709
|
+
return parseU256(result, 0);
|
|
2710
|
+
}
|
|
2711
|
+
/**
|
|
2712
|
+
* Get the bid-ask spread for a trading pair.
|
|
2713
|
+
*/
|
|
2714
|
+
async getSpread(pairId) {
|
|
2715
|
+
const result = await this.obelysk.provider.callContract({
|
|
2716
|
+
contractAddress: this.contractAddress,
|
|
2717
|
+
entrypoint: "get_spread",
|
|
2718
|
+
calldata: [pairId.toString()]
|
|
2719
|
+
});
|
|
2720
|
+
return parseU256(result, 0);
|
|
2721
|
+
}
|
|
2722
|
+
/**
|
|
2723
|
+
* Get trading pair configuration.
|
|
2724
|
+
*/
|
|
2725
|
+
async getPair(pairId) {
|
|
2726
|
+
const result = await this.obelysk.provider.callContract({
|
|
2727
|
+
contractAddress: this.contractAddress,
|
|
2728
|
+
entrypoint: "get_pair",
|
|
2729
|
+
calldata: [pairId.toString()]
|
|
2730
|
+
});
|
|
2731
|
+
return {
|
|
2732
|
+
baseToken: result[0],
|
|
2733
|
+
quoteToken: result[1],
|
|
2734
|
+
minOrderSize: parseU256(result, 2),
|
|
2735
|
+
tickSize: parseU256(result, 4),
|
|
2736
|
+
isActive: BigInt(result[6]) !== 0n
|
|
2737
|
+
};
|
|
2738
|
+
}
|
|
2739
|
+
/**
|
|
2740
|
+
* Get the orderbook configuration (fees, limits, pause state).
|
|
2741
|
+
*/
|
|
2742
|
+
async getConfig() {
|
|
2743
|
+
const result = await this.obelysk.provider.callContract({
|
|
2744
|
+
contractAddress: this.contractAddress,
|
|
2745
|
+
entrypoint: "get_config",
|
|
2746
|
+
calldata: []
|
|
2747
|
+
});
|
|
2748
|
+
return {
|
|
2749
|
+
makerFeeBps: Number(BigInt(result[0])),
|
|
2750
|
+
takerFeeBps: Number(BigInt(result[1])),
|
|
2751
|
+
defaultExpirySecs: Number(BigInt(result[2])),
|
|
2752
|
+
maxOrdersPerUser: Number(BigInt(result[3])),
|
|
2753
|
+
paused: BigInt(result[4]) !== 0n
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
/**
|
|
2757
|
+
* Get aggregate orderbook statistics.
|
|
2758
|
+
*/
|
|
2759
|
+
async getStats() {
|
|
2760
|
+
const result = await this.obelysk.provider.callContract({
|
|
2761
|
+
contractAddress: this.contractAddress,
|
|
2762
|
+
entrypoint: "get_stats",
|
|
2763
|
+
calldata: []
|
|
2764
|
+
});
|
|
2765
|
+
return {
|
|
2766
|
+
orderCount: parseU256(result, 0),
|
|
2767
|
+
tradeCount: parseU256(result, 2),
|
|
2768
|
+
totalVolume: parseU256(result, 4),
|
|
2769
|
+
totalFees: parseU256(result, 6)
|
|
2770
|
+
};
|
|
2771
|
+
}
|
|
2772
|
+
/**
|
|
2773
|
+
* Get total number of orders ever placed.
|
|
2774
|
+
*/
|
|
2775
|
+
async getOrderCount() {
|
|
2776
|
+
const result = await this.obelysk.provider.callContract({
|
|
2777
|
+
contractAddress: this.contractAddress,
|
|
2778
|
+
entrypoint: "get_order_count",
|
|
2779
|
+
calldata: []
|
|
2780
|
+
});
|
|
2781
|
+
return parseU256(result, 0);
|
|
2782
|
+
}
|
|
2783
|
+
/**
|
|
2784
|
+
* Get total number of trades executed.
|
|
2785
|
+
*/
|
|
2786
|
+
async getTradeCount() {
|
|
2787
|
+
const result = await this.obelysk.provider.callContract({
|
|
2788
|
+
contractAddress: this.contractAddress,
|
|
2789
|
+
entrypoint: "get_trade_count",
|
|
2790
|
+
calldata: []
|
|
2791
|
+
});
|
|
2792
|
+
return parseU256(result, 0);
|
|
2793
|
+
}
|
|
2794
|
+
/**
|
|
2795
|
+
* Get the time-weighted average price for a trading pair.
|
|
2796
|
+
*/
|
|
2797
|
+
async getTwap(pairId) {
|
|
2798
|
+
const result = await this.obelysk.provider.callContract({
|
|
2799
|
+
contractAddress: this.contractAddress,
|
|
2800
|
+
entrypoint: "get_twap",
|
|
2801
|
+
calldata: [pairId.toString()]
|
|
2802
|
+
});
|
|
2803
|
+
return parseU256(result, 0);
|
|
2804
|
+
}
|
|
2805
|
+
/**
|
|
2806
|
+
* Get 24-hour rolling statistics for a trading pair.
|
|
2807
|
+
*/
|
|
2808
|
+
async get24hStats(pairId) {
|
|
2809
|
+
const result = await this.obelysk.provider.callContract({
|
|
2810
|
+
contractAddress: this.contractAddress,
|
|
2811
|
+
entrypoint: "get_24h_stats",
|
|
2812
|
+
calldata: [pairId.toString()]
|
|
2813
|
+
});
|
|
2814
|
+
return {
|
|
2815
|
+
volume: parseU256(result, 0),
|
|
2816
|
+
high: parseU256(result, 2),
|
|
2817
|
+
low: parseU256(result, 4),
|
|
2818
|
+
tradeCount: parseU256(result, 6)
|
|
2819
|
+
};
|
|
2820
|
+
}
|
|
2821
|
+
/**
|
|
2822
|
+
* Get the most recent trade for a trading pair.
|
|
2823
|
+
*/
|
|
2824
|
+
async getLastTrade(pairId) {
|
|
2825
|
+
const result = await this.obelysk.provider.callContract({
|
|
2826
|
+
contractAddress: this.contractAddress,
|
|
2827
|
+
entrypoint: "get_last_trade",
|
|
2828
|
+
calldata: [pairId.toString()]
|
|
2829
|
+
});
|
|
2830
|
+
return {
|
|
2831
|
+
price: parseU256(result, 0),
|
|
2832
|
+
amount: parseU256(result, 2),
|
|
2833
|
+
timestamp: Number(BigInt(result[4]))
|
|
2834
|
+
};
|
|
2835
|
+
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Get the orderbook depth (price levels) for a trading pair.
|
|
2838
|
+
*
|
|
2839
|
+
* Returns up to `maxLevels` aggregated price levels for both
|
|
2840
|
+
* bids and asks. Each level contains price, total amount, and
|
|
2841
|
+
* number of orders at that price.
|
|
2842
|
+
*/
|
|
2843
|
+
async getOrderbookDepth(pairId, maxLevels) {
|
|
2844
|
+
const result = await this.obelysk.provider.callContract({
|
|
2845
|
+
contractAddress: this.contractAddress,
|
|
2846
|
+
entrypoint: "get_orderbook_depth",
|
|
2847
|
+
calldata: [pairId.toString(), maxLevels.toString()]
|
|
2848
|
+
});
|
|
2849
|
+
let offset = 0;
|
|
2850
|
+
const bidCount = Number(BigInt(result[offset]));
|
|
2851
|
+
offset += 1;
|
|
2852
|
+
const bids = [];
|
|
2853
|
+
for (let i = 0; i < bidCount; i++) {
|
|
2854
|
+
bids.push({
|
|
2855
|
+
price: parseU256(result, offset),
|
|
2856
|
+
totalAmount: parseU256(result, offset + 2),
|
|
2857
|
+
orderCount: Number(BigInt(result[offset + 4]))
|
|
2858
|
+
});
|
|
2859
|
+
offset += 5;
|
|
2860
|
+
}
|
|
2861
|
+
const askCount = Number(BigInt(result[offset]));
|
|
2862
|
+
offset += 1;
|
|
2863
|
+
const asks = [];
|
|
2864
|
+
for (let i = 0; i < askCount; i++) {
|
|
2865
|
+
asks.push({
|
|
2866
|
+
price: parseU256(result, offset),
|
|
2867
|
+
totalAmount: parseU256(result, offset + 2),
|
|
2868
|
+
orderCount: Number(BigInt(result[offset + 4]))
|
|
2869
|
+
});
|
|
2870
|
+
offset += 5;
|
|
2871
|
+
}
|
|
2872
|
+
return { bids, asks };
|
|
2873
|
+
}
|
|
2874
|
+
/**
|
|
2875
|
+
* Get active orders for a trading pair with pagination.
|
|
2876
|
+
*/
|
|
2877
|
+
async getActiveOrders(pairId, offset, limit) {
|
|
2878
|
+
const result = await this.obelysk.provider.callContract({
|
|
2879
|
+
contractAddress: this.contractAddress,
|
|
2880
|
+
entrypoint: "get_active_orders",
|
|
2881
|
+
calldata: [pairId.toString(), offset.toString(), limit.toString()]
|
|
2882
|
+
});
|
|
2883
|
+
const len = Number(BigInt(result[0]));
|
|
2884
|
+
const orders = [];
|
|
2885
|
+
for (let i = 0; i < len; i++) {
|
|
2886
|
+
orders.push(parseOrder(result, 1 + i * ORDER_FELT_COUNT));
|
|
2887
|
+
}
|
|
2888
|
+
return orders;
|
|
2889
|
+
}
|
|
2890
|
+
/**
|
|
2891
|
+
* Get trade history for a trading pair with pagination.
|
|
2892
|
+
*/
|
|
2893
|
+
async getTradeHistory(pairId, offset, limit) {
|
|
2894
|
+
const result = await this.obelysk.provider.callContract({
|
|
2895
|
+
contractAddress: this.contractAddress,
|
|
2896
|
+
entrypoint: "get_trade_history",
|
|
2897
|
+
calldata: [pairId.toString(), offset.toString(), limit.toString()]
|
|
2898
|
+
});
|
|
2899
|
+
const len = Number(BigInt(result[0]));
|
|
2900
|
+
const trades = [];
|
|
2901
|
+
for (let i = 0; i < len; i++) {
|
|
2902
|
+
trades.push(parseTrade(result, 1 + i * TRADE_FELT_COUNT));
|
|
2903
|
+
}
|
|
2904
|
+
return trades;
|
|
2905
|
+
}
|
|
2906
|
+
/**
|
|
2907
|
+
* Get the contract owner address.
|
|
2908
|
+
*/
|
|
2909
|
+
async getOwner() {
|
|
2910
|
+
const result = await this.obelysk.provider.callContract({
|
|
2911
|
+
contractAddress: this.contractAddress,
|
|
2912
|
+
entrypoint: "owner",
|
|
2913
|
+
calldata: []
|
|
2914
|
+
});
|
|
2915
|
+
return result[0];
|
|
2916
|
+
}
|
|
1352
2917
|
};
|
|
1353
2918
|
|
|
1354
2919
|
// src/obelysk/client.ts
|
|
@@ -1360,6 +2925,8 @@ var ObelyskClient = class {
|
|
|
1360
2925
|
contracts;
|
|
1361
2926
|
tokens;
|
|
1362
2927
|
privacyPools;
|
|
2928
|
+
relayerUrl;
|
|
2929
|
+
relayerApiKey;
|
|
1363
2930
|
/** Privacy Pool operations (deposit/withdraw shielded tokens) */
|
|
1364
2931
|
privacyPool;
|
|
1365
2932
|
/** DarkPool batch auction trading */
|
|
@@ -1374,6 +2941,12 @@ var ObelyskClient = class {
|
|
|
1374
2941
|
router;
|
|
1375
2942
|
/** Shielded swap router (privacy-preserving AMM swaps) */
|
|
1376
2943
|
swap;
|
|
2944
|
+
/** VM31 UTXO privacy vault (deposits/withdrawals/transfers via relayer) */
|
|
2945
|
+
vm31;
|
|
2946
|
+
/** VM31 confidential bridge (bridge VM31 withdrawals to encrypted balances) */
|
|
2947
|
+
bridge;
|
|
2948
|
+
/** OTC orderbook (limit/market orders, trade history) */
|
|
2949
|
+
otc;
|
|
1377
2950
|
constructor(config) {
|
|
1378
2951
|
this.network = config.network;
|
|
1379
2952
|
this.provider = new RpcProvider({ nodeUrl: config.rpcUrl ?? getRpcUrl(config.network) });
|
|
@@ -1381,6 +2954,8 @@ var ObelyskClient = class {
|
|
|
1381
2954
|
this.contracts = getContracts(config.network);
|
|
1382
2955
|
this.tokens = this.contracts.tokens ?? MAINNET_TOKENS;
|
|
1383
2956
|
this.privacyPools = config.network === "mainnet" ? MAINNET_PRIVACY_POOLS : {};
|
|
2957
|
+
this.relayerUrl = config.relayerUrl;
|
|
2958
|
+
this.relayerApiKey = config.relayerApiKey;
|
|
1384
2959
|
this.privacy = new ObelyskPrivacy();
|
|
1385
2960
|
if (config.privacyPrivateKey) {
|
|
1386
2961
|
const pk = BigInt(config.privacyPrivateKey);
|
|
@@ -1393,6 +2968,9 @@ var ObelyskClient = class {
|
|
|
1393
2968
|
this.staking = new ProverStakingClient(this);
|
|
1394
2969
|
this.router = new PrivacyRouterClient(this);
|
|
1395
2970
|
this.swap = new ShieldedSwapClient(this);
|
|
2971
|
+
this.vm31 = new VM31VaultClient(this);
|
|
2972
|
+
this.bridge = new VM31BridgeClient(this);
|
|
2973
|
+
this.otc = new OTCOrderbookClient(this);
|
|
1396
2974
|
}
|
|
1397
2975
|
/** Get token address by symbol */
|
|
1398
2976
|
getTokenAddress(symbol) {
|
|
@@ -1416,34 +2994,51 @@ export {
|
|
|
1416
2994
|
CURVE_ORDER,
|
|
1417
2995
|
ConfidentialTransferClient,
|
|
1418
2996
|
DARKPOOL_ASSET_IDS,
|
|
2997
|
+
DENOMINATION_BY_SYMBOL,
|
|
1419
2998
|
DarkPoolClient,
|
|
2999
|
+
ETH_DENOMINATIONS,
|
|
1420
3000
|
FIELD_PRIME,
|
|
1421
3001
|
GENERATOR_G,
|
|
1422
3002
|
GENERATOR_H,
|
|
1423
3003
|
MAINNET_PRIVACY_POOLS,
|
|
1424
3004
|
MAINNET_TOKENS,
|
|
3005
|
+
OTCOrderbookClient,
|
|
1425
3006
|
ObelyskClient,
|
|
1426
3007
|
ObelyskPrivacy,
|
|
3008
|
+
OrderSide,
|
|
3009
|
+
OrderStatus,
|
|
3010
|
+
OrderType,
|
|
1427
3011
|
PrivacyPoolClient,
|
|
1428
3012
|
PrivacyRouterClient,
|
|
1429
3013
|
ProverStakingClient,
|
|
3014
|
+
SAGE_DENOMINATIONS,
|
|
3015
|
+
STRK_DENOMINATIONS,
|
|
1430
3016
|
ShieldedSwapClient,
|
|
1431
3017
|
StealthClient,
|
|
1432
3018
|
TOKEN_DECIMALS,
|
|
3019
|
+
USDC_DENOMINATIONS,
|
|
3020
|
+
VM31BridgeClient,
|
|
3021
|
+
VM31VaultClient,
|
|
1433
3022
|
VM31_ASSET_IDS,
|
|
3023
|
+
VM31_DENOMINATIONS,
|
|
3024
|
+
WBTC_DENOMINATIONS,
|
|
1434
3025
|
commitmentToHash,
|
|
1435
3026
|
createAEHint,
|
|
1436
3027
|
createEncryptionProof,
|
|
1437
3028
|
deriveNullifier,
|
|
1438
3029
|
ecAdd2 as ecAdd,
|
|
1439
3030
|
ecMul2 as ecMul,
|
|
3031
|
+
eciesEncrypt,
|
|
1440
3032
|
elgamalEncrypt,
|
|
1441
3033
|
formatAmount,
|
|
1442
3034
|
getContracts,
|
|
3035
|
+
getDenominations,
|
|
1443
3036
|
getRpcUrl,
|
|
1444
3037
|
mod,
|
|
1445
3038
|
modInverse,
|
|
1446
3039
|
parseAmount,
|
|
1447
3040
|
pedersenCommit,
|
|
1448
|
-
randomScalar2 as randomScalar
|
|
3041
|
+
randomScalar2 as randomScalar,
|
|
3042
|
+
splitIntoDenominations,
|
|
3043
|
+
validateDenomination
|
|
1449
3044
|
};
|