@veridex/sdk 1.0.0-beta.16 → 1.0.0-beta.17
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 +1 -1
- package/dist/chains/aptos/index.d.mts +1 -1
- package/dist/chains/aptos/index.d.ts +1 -1
- package/dist/chains/evm/index.d.mts +3 -3
- package/dist/chains/evm/index.d.ts +3 -3
- package/dist/chains/solana/index.d.mts +1 -1
- package/dist/chains/solana/index.d.ts +1 -1
- package/dist/chains/stacks/index.d.mts +559 -0
- package/dist/chains/stacks/index.d.ts +559 -0
- package/dist/chains/stacks/index.js +1207 -0
- package/dist/chains/stacks/index.js.map +1 -0
- package/dist/chains/stacks/index.mjs +1149 -0
- package/dist/chains/stacks/index.mjs.map +1 -0
- package/dist/chains/starknet/index.d.mts +2 -2
- package/dist/chains/starknet/index.d.ts +2 -2
- package/dist/chains/sui/index.d.mts +2 -2
- package/dist/chains/sui/index.d.ts +2 -2
- package/dist/{index-ruSjoF2m.d.ts → index-BXcR_ypI.d.ts} +28 -10
- package/dist/{index-eadz7SCP.d.mts → index-CYOyIE3b.d.mts} +28 -10
- package/dist/index.d.mts +63 -11
- package/dist/index.d.ts +63 -11
- package/dist/index.js +1409 -158
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1376 -158
- package/dist/index.mjs.map +1 -1
- package/dist/{types-ChIsqCiw.d.mts → types-DE2ICQik.d.mts} +5 -1
- package/dist/{types-ChIsqCiw.d.ts → types-DE2ICQik.d.ts} +5 -1
- package/dist/{types-FJL7j6gQ.d.mts → types-DvFRnIBd.d.mts} +1 -1
- package/dist/{types-FJL7j6gQ.d.ts → types-DvFRnIBd.d.ts} +1 -1
- package/dist/types.d.mts +4 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.js.map +1 -1
- package/package.json +7 -1
package/dist/index.mjs
CHANGED
|
@@ -546,17 +546,36 @@ function sleep(ms) {
|
|
|
546
546
|
}
|
|
547
547
|
|
|
548
548
|
// src/core/PasskeyManager.ts
|
|
549
|
-
|
|
549
|
+
var VERIDEX_RP_ID = "veridex.network";
|
|
550
|
+
function detectRpId(forceLocal) {
|
|
550
551
|
if (typeof window === "undefined") return "localhost";
|
|
551
552
|
const hostname = window.location.hostname;
|
|
552
553
|
if (hostname === "localhost" || hostname === "127.0.0.1" || /^\d+\.\d+\.\d+\.\d+$/.test(hostname)) {
|
|
553
554
|
return hostname;
|
|
554
555
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
556
|
+
if (forceLocal) {
|
|
557
|
+
const parts = hostname.split(".");
|
|
558
|
+
if (parts.length <= 2) {
|
|
559
|
+
return hostname;
|
|
560
|
+
}
|
|
561
|
+
return parts.slice(-2).join(".");
|
|
562
|
+
}
|
|
563
|
+
return VERIDEX_RP_ID;
|
|
564
|
+
}
|
|
565
|
+
async function supportsRelatedOrigins() {
|
|
566
|
+
if (typeof window === "undefined" || !window.PublicKeyCredential) {
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
if ("getClientCapabilities" in PublicKeyCredential) {
|
|
570
|
+
try {
|
|
571
|
+
const getCapabilities = PublicKeyCredential.getClientCapabilities;
|
|
572
|
+
const capabilities = await getCapabilities();
|
|
573
|
+
return capabilities?.relatedOrigins === true;
|
|
574
|
+
} catch {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
558
577
|
}
|
|
559
|
-
return
|
|
578
|
+
return false;
|
|
560
579
|
}
|
|
561
580
|
var PasskeyManager = class _PasskeyManager {
|
|
562
581
|
config;
|
|
@@ -1702,9 +1721,10 @@ function getChainName(wormholeChainId) {
|
|
|
1702
1721
|
|
|
1703
1722
|
// src/core/BalanceManager.ts
|
|
1704
1723
|
var DEFAULT_RPC_URLS = {
|
|
1724
|
+
10002: "https://ethereum-sepolia-rpc.publicnode.com",
|
|
1725
|
+
10003: "https://sepolia-rollup.arbitrum.io/rpc",
|
|
1705
1726
|
10004: "https://sepolia.base.org",
|
|
1706
|
-
10005: "https://sepolia.optimism.io"
|
|
1707
|
-
10003: "https://sepolia-rollup.arbitrum.io/rpc"
|
|
1727
|
+
10005: "https://sepolia.optimism.io"
|
|
1708
1728
|
};
|
|
1709
1729
|
var TESTNET_TOKEN_PRICES = {
|
|
1710
1730
|
ETH: 2500,
|
|
@@ -1939,9 +1959,10 @@ var DEFAULT_POLLING_INTERVAL = 2e3;
|
|
|
1939
1959
|
var DEFAULT_REQUIRED_CONFIRMATIONS = 1;
|
|
1940
1960
|
var DEFAULT_TIMEOUT = 3e5;
|
|
1941
1961
|
var DEFAULT_RPC_URLS2 = {
|
|
1962
|
+
10002: "https://ethereum-sepolia-rpc.publicnode.com",
|
|
1963
|
+
10003: "https://sepolia-rollup.arbitrum.io/rpc",
|
|
1942
1964
|
10004: "https://sepolia.base.org",
|
|
1943
|
-
10005: "https://sepolia.optimism.io"
|
|
1944
|
-
10003: "https://sepolia-rollup.arbitrum.io/rpc"
|
|
1965
|
+
10005: "https://sepolia.optimism.io"
|
|
1945
1966
|
};
|
|
1946
1967
|
var TransactionTracker = class {
|
|
1947
1968
|
config;
|
|
@@ -11054,161 +11075,1130 @@ var EVMClient = class {
|
|
|
11054
11075
|
}
|
|
11055
11076
|
};
|
|
11056
11077
|
|
|
11057
|
-
// src/
|
|
11058
|
-
var
|
|
11059
|
-
|
|
11060
|
-
|
|
11061
|
-
|
|
11062
|
-
|
|
11063
|
-
|
|
11064
|
-
|
|
11065
|
-
|
|
11066
|
-
|
|
11067
|
-
|
|
11068
|
-
|
|
11069
|
-
|
|
11070
|
-
|
|
11071
|
-
|
|
11072
|
-
|
|
11073
|
-
|
|
11074
|
-
|
|
11075
|
-
|
|
11076
|
-
|
|
11077
|
-
|
|
11078
|
-
|
|
11079
|
-
|
|
11080
|
-
|
|
11078
|
+
// src/chains/stacks/StacksSigner.ts
|
|
11079
|
+
var P256_ORDER = BigInt(
|
|
11080
|
+
"0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551"
|
|
11081
|
+
);
|
|
11082
|
+
var P256_HALF_ORDER = P256_ORDER / 2n;
|
|
11083
|
+
function compressPublicKey(x, y) {
|
|
11084
|
+
const prefix = y % 2n === 0n ? 2 : 3;
|
|
11085
|
+
const xBytes = bigintToBytes(x, 32);
|
|
11086
|
+
const compressed = new Uint8Array(33);
|
|
11087
|
+
compressed[0] = prefix;
|
|
11088
|
+
compressed.set(xBytes, 1);
|
|
11089
|
+
return compressed;
|
|
11090
|
+
}
|
|
11091
|
+
function rsToCompactSignature(r, s) {
|
|
11092
|
+
const normalizedS = s > P256_HALF_ORDER ? P256_ORDER - s : s;
|
|
11093
|
+
const compact = new Uint8Array(64);
|
|
11094
|
+
compact.set(bigintToBytes(r, 32), 0);
|
|
11095
|
+
compact.set(bigintToBytes(normalizedS, 32), 32);
|
|
11096
|
+
return compact;
|
|
11097
|
+
}
|
|
11098
|
+
function parseDERSignature2(der) {
|
|
11099
|
+
if (der[0] !== 48) {
|
|
11100
|
+
throw new Error("Invalid DER signature: expected SEQUENCE tag 0x30");
|
|
11101
|
+
}
|
|
11102
|
+
let offset = 2;
|
|
11103
|
+
if (der[offset] !== 2) {
|
|
11104
|
+
throw new Error("Invalid DER signature: expected INTEGER tag 0x02 for r");
|
|
11105
|
+
}
|
|
11106
|
+
offset++;
|
|
11107
|
+
const rLen = der[offset];
|
|
11108
|
+
offset++;
|
|
11109
|
+
const rBytes = der.slice(offset, offset + rLen);
|
|
11110
|
+
offset += rLen;
|
|
11111
|
+
if (der[offset] !== 2) {
|
|
11112
|
+
throw new Error("Invalid DER signature: expected INTEGER tag 0x02 for s");
|
|
11113
|
+
}
|
|
11114
|
+
offset++;
|
|
11115
|
+
const sLen = der[offset];
|
|
11116
|
+
offset++;
|
|
11117
|
+
const sBytes = der.slice(offset, offset + sLen);
|
|
11118
|
+
return {
|
|
11119
|
+
r: bytesToBigint(rBytes),
|
|
11120
|
+
s: bytesToBigint(sBytes)
|
|
11121
|
+
};
|
|
11122
|
+
}
|
|
11123
|
+
function derToCompactSignature(der) {
|
|
11124
|
+
const { r, s } = parseDERSignature2(der);
|
|
11125
|
+
return rsToCompactSignature(r, s);
|
|
11126
|
+
}
|
|
11127
|
+
async function computeKeyHash2(compressedPubkey) {
|
|
11128
|
+
const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", compressedPubkey.buffer);
|
|
11129
|
+
const hashArray = new Uint8Array(hashBuffer);
|
|
11130
|
+
return "0x" + bytesToHex(hashArray);
|
|
11131
|
+
}
|
|
11132
|
+
async function computeKeyHashFromCoords(x, y) {
|
|
11133
|
+
const compressed = compressPublicKey(x, y);
|
|
11134
|
+
return computeKeyHash2(compressed);
|
|
11135
|
+
}
|
|
11136
|
+
async function buildRegistrationHash(nonce) {
|
|
11137
|
+
const message = `veridex:register:${nonce}`;
|
|
11138
|
+
const encoded = new TextEncoder().encode(message);
|
|
11139
|
+
const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded.buffer);
|
|
11140
|
+
return new Uint8Array(hashBuffer);
|
|
11141
|
+
}
|
|
11142
|
+
async function buildSessionRegistrationHash(sessionKeyHash, duration, maxValue, nonce) {
|
|
11143
|
+
const cleanHash = sessionKeyHash.replace("0x", "");
|
|
11144
|
+
const message = `veridex:session:${cleanHash}:${duration}:${maxValue}:${nonce}`;
|
|
11145
|
+
const encoded = new TextEncoder().encode(message);
|
|
11146
|
+
const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded.buffer);
|
|
11147
|
+
return new Uint8Array(hashBuffer);
|
|
11148
|
+
}
|
|
11149
|
+
async function buildRevocationHash(sessionHash, nonce) {
|
|
11150
|
+
const cleanHash = sessionHash.replace("0x", "");
|
|
11151
|
+
const message = `veridex:revoke:${cleanHash}:${nonce}`;
|
|
11152
|
+
const encoded = new TextEncoder().encode(message);
|
|
11153
|
+
const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded.buffer);
|
|
11154
|
+
return new Uint8Array(hashBuffer);
|
|
11155
|
+
}
|
|
11156
|
+
async function buildExecuteHash(actionType, amount, recipient, nonce) {
|
|
11157
|
+
const message = `veridex:execute:${actionType}:${amount}:${recipient}:${nonce}`;
|
|
11158
|
+
const encoded = new TextEncoder().encode(message);
|
|
11159
|
+
const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded.buffer);
|
|
11160
|
+
return new Uint8Array(hashBuffer);
|
|
11161
|
+
}
|
|
11162
|
+
async function buildWithdrawalHash(amount, recipient, nonce) {
|
|
11163
|
+
const message = `veridex:withdraw:${amount}:${recipient}:${nonce}`;
|
|
11164
|
+
const encoded = new TextEncoder().encode(message);
|
|
11165
|
+
const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded.buffer);
|
|
11166
|
+
return new Uint8Array(hashBuffer);
|
|
11167
|
+
}
|
|
11168
|
+
function bigintToBytes(value, length) {
|
|
11169
|
+
const hex = value.toString(16).padStart(length * 2, "0");
|
|
11170
|
+
const bytes = new Uint8Array(length);
|
|
11171
|
+
for (let i = 0; i < length; i++) {
|
|
11172
|
+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
11173
|
+
}
|
|
11174
|
+
return bytes;
|
|
11175
|
+
}
|
|
11176
|
+
function bytesToBigint(bytes) {
|
|
11177
|
+
let start = 0;
|
|
11178
|
+
while (start < bytes.length - 1 && bytes[start] === 0) {
|
|
11179
|
+
start++;
|
|
11180
|
+
}
|
|
11181
|
+
let result = 0n;
|
|
11182
|
+
for (let i = start; i < bytes.length; i++) {
|
|
11183
|
+
result = result << 8n | BigInt(bytes[i]);
|
|
11184
|
+
}
|
|
11185
|
+
return result;
|
|
11186
|
+
}
|
|
11187
|
+
function bytesToHex(bytes) {
|
|
11188
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
11189
|
+
}
|
|
11190
|
+
function hexToBytes(hex) {
|
|
11191
|
+
const clean = hex.replace("0x", "");
|
|
11192
|
+
const bytes = new Uint8Array(clean.length / 2);
|
|
11193
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
11194
|
+
bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
11195
|
+
}
|
|
11196
|
+
return bytes;
|
|
11197
|
+
}
|
|
11198
|
+
|
|
11199
|
+
// src/chains/stacks/StacksAddressUtils.ts
|
|
11200
|
+
var STACKS_MAINNET_PREFIX = "SP";
|
|
11201
|
+
var STACKS_TESTNET_PREFIX = "ST";
|
|
11202
|
+
var C32_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
11203
|
+
function isValidStacksPrincipal(address) {
|
|
11204
|
+
if (!address || typeof address !== "string") {
|
|
11205
|
+
return false;
|
|
11206
|
+
}
|
|
11207
|
+
const parts = address.split(".");
|
|
11208
|
+
if (parts.length > 2) {
|
|
11209
|
+
return false;
|
|
11210
|
+
}
|
|
11211
|
+
const standardPart = parts[0];
|
|
11212
|
+
const contractName = parts[1];
|
|
11213
|
+
if (!isValidStandardPrincipal(standardPart)) {
|
|
11214
|
+
return false;
|
|
11215
|
+
}
|
|
11216
|
+
if (contractName !== void 0) {
|
|
11217
|
+
if (!isValidContractName(contractName)) {
|
|
11218
|
+
return false;
|
|
11219
|
+
}
|
|
11220
|
+
}
|
|
11221
|
+
return true;
|
|
11222
|
+
}
|
|
11223
|
+
function isValidStandardPrincipal(address) {
|
|
11224
|
+
if (!address || address.length < 5) {
|
|
11225
|
+
return false;
|
|
11226
|
+
}
|
|
11227
|
+
const prefix = address.slice(0, 2);
|
|
11228
|
+
if (prefix !== STACKS_MAINNET_PREFIX && prefix !== STACKS_TESTNET_PREFIX) {
|
|
11229
|
+
return false;
|
|
11230
|
+
}
|
|
11231
|
+
if (address.length < 38 || address.length > 42) {
|
|
11232
|
+
return false;
|
|
11233
|
+
}
|
|
11234
|
+
const body = address.slice(1).toUpperCase();
|
|
11235
|
+
for (const char of body) {
|
|
11236
|
+
if (!C32_ALPHABET.includes(char)) {
|
|
11237
|
+
return false;
|
|
11238
|
+
}
|
|
11239
|
+
}
|
|
11240
|
+
return true;
|
|
11241
|
+
}
|
|
11242
|
+
function isValidContractName(name) {
|
|
11243
|
+
if (!name || name.length === 0 || name.length > 128) {
|
|
11244
|
+
return false;
|
|
11245
|
+
}
|
|
11246
|
+
if (!/^[a-zA-Z]/.test(name)) {
|
|
11247
|
+
return false;
|
|
11248
|
+
}
|
|
11249
|
+
if (!/^[a-zA-Z][a-zA-Z0-9-]*$/.test(name)) {
|
|
11250
|
+
return false;
|
|
11251
|
+
}
|
|
11252
|
+
return true;
|
|
11253
|
+
}
|
|
11254
|
+
function getNetworkFromAddress(address) {
|
|
11255
|
+
const prefix = address.slice(0, 2);
|
|
11256
|
+
if (prefix === STACKS_MAINNET_PREFIX) {
|
|
11257
|
+
return "mainnet";
|
|
11258
|
+
}
|
|
11259
|
+
return "testnet";
|
|
11260
|
+
}
|
|
11261
|
+
function getContractPrincipal(deployerAddress, contractName) {
|
|
11262
|
+
if (!isValidStandardPrincipal(deployerAddress)) {
|
|
11263
|
+
throw new Error(`Invalid deployer address: ${deployerAddress}`);
|
|
11264
|
+
}
|
|
11265
|
+
if (!isValidContractName(contractName)) {
|
|
11266
|
+
throw new Error(`Invalid contract name: ${contractName}`);
|
|
11267
|
+
}
|
|
11268
|
+
return `${deployerAddress}.${contractName}`;
|
|
11269
|
+
}
|
|
11270
|
+
function parseContractPrincipal(contractPrincipal) {
|
|
11271
|
+
const dotIndex = contractPrincipal.indexOf(".");
|
|
11272
|
+
if (dotIndex === -1) {
|
|
11273
|
+
throw new Error(
|
|
11274
|
+
`Not a contract principal: ${contractPrincipal}. Expected format: address.contract-name`
|
|
11275
|
+
);
|
|
11276
|
+
}
|
|
11277
|
+
return {
|
|
11278
|
+
address: contractPrincipal.slice(0, dotIndex),
|
|
11279
|
+
contractName: contractPrincipal.slice(dotIndex + 1)
|
|
11280
|
+
};
|
|
11281
|
+
}
|
|
11282
|
+
function isContractPrincipal(address) {
|
|
11283
|
+
return address.includes(".");
|
|
11284
|
+
}
|
|
11285
|
+
function getStacksExplorerTxUrl(txId, network = "testnet") {
|
|
11286
|
+
const cleanTxId = txId.startsWith("0x") ? txId : `0x${txId}`;
|
|
11287
|
+
const chainParam = network === "testnet" ? "&chain=testnet" : "";
|
|
11288
|
+
return `https://explorer.hiro.so/txid/${cleanTxId}?${chainParam}`;
|
|
11289
|
+
}
|
|
11290
|
+
function getStacksExplorerAddressUrl(address, network = "testnet") {
|
|
11291
|
+
const chainParam = network === "testnet" ? "?chain=testnet" : "";
|
|
11292
|
+
return `https://explorer.hiro.so/address/${address}${chainParam}`;
|
|
11293
|
+
}
|
|
11294
|
+
|
|
11295
|
+
// src/chains/stacks/StacksClient.ts
|
|
11296
|
+
var STACKS_ACTION_TYPES = {
|
|
11297
|
+
TRANSFER_STX: 1,
|
|
11298
|
+
TRANSFER_SBTC: 2,
|
|
11299
|
+
CONTRACT_CALL: 3
|
|
11081
11300
|
};
|
|
11082
|
-
var
|
|
11083
|
-
|
|
11084
|
-
|
|
11085
|
-
|
|
11086
|
-
|
|
11087
|
-
|
|
11088
|
-
|
|
11089
|
-
|
|
11090
|
-
|
|
11091
|
-
|
|
11092
|
-
|
|
11093
|
-
|
|
11094
|
-
|
|
11095
|
-
|
|
11096
|
-
|
|
11097
|
-
|
|
11098
|
-
|
|
11099
|
-
|
|
11100
|
-
|
|
11101
|
-
|
|
11102
|
-
tokenBridge: "0x86F55A04690fd7815A3D802bD587e83eA888B239"
|
|
11103
|
-
}
|
|
11104
|
-
},
|
|
11105
|
-
mainnet: {
|
|
11106
|
-
name: "Base",
|
|
11107
|
-
chainId: 8453,
|
|
11108
|
-
wormholeChainId: 30,
|
|
11109
|
-
rpcUrl: "https://mainnet.base.org",
|
|
11110
|
-
explorerUrl: "https://basescan.org",
|
|
11111
|
-
isEvm: true,
|
|
11112
|
-
contracts: {
|
|
11113
|
-
// TODO: Deploy mainnet contracts
|
|
11114
|
-
wormholeCoreBridge: "0xbebdb6C8ddC678FfA9f8748f85C815C556Dd8ac6",
|
|
11115
|
-
tokenBridge: "0x8d2de8d2f73F1F4cAB472AC9A881C9b123C79627"
|
|
11116
|
-
}
|
|
11301
|
+
var HIRO_API = {
|
|
11302
|
+
testnet: "https://api.testnet.hiro.so",
|
|
11303
|
+
mainnet: "https://api.hiro.so"
|
|
11304
|
+
};
|
|
11305
|
+
var StacksClient = class {
|
|
11306
|
+
config;
|
|
11307
|
+
rpcUrl;
|
|
11308
|
+
spokeContract;
|
|
11309
|
+
vaultContract;
|
|
11310
|
+
wormholeVerifierContract;
|
|
11311
|
+
vaultVaaContract;
|
|
11312
|
+
networkType;
|
|
11313
|
+
constructor(clientConfig) {
|
|
11314
|
+
this.networkType = clientConfig.network || "testnet";
|
|
11315
|
+
this.rpcUrl = clientConfig.rpcUrl || HIRO_API[this.networkType];
|
|
11316
|
+
if (clientConfig.spokeContractAddress && isContractPrincipal(clientConfig.spokeContractAddress)) {
|
|
11317
|
+
const parsed = parseContractPrincipal(clientConfig.spokeContractAddress);
|
|
11318
|
+
this.spokeContract = { address: parsed.address, name: parsed.contractName };
|
|
11319
|
+
} else {
|
|
11320
|
+
this.spokeContract = null;
|
|
11117
11321
|
}
|
|
11118
|
-
|
|
11119
|
-
|
|
11120
|
-
|
|
11121
|
-
|
|
11122
|
-
|
|
11123
|
-
displayName: "Optimism",
|
|
11124
|
-
type: "evm",
|
|
11125
|
-
canBeHub: true,
|
|
11126
|
-
testnet: {
|
|
11127
|
-
name: "Optimism Sepolia",
|
|
11128
|
-
chainId: 11155420,
|
|
11129
|
-
wormholeChainId: 10005,
|
|
11130
|
-
rpcUrl: "https://sepolia.optimism.io",
|
|
11131
|
-
explorerUrl: "https://sepolia-optimism.etherscan.io",
|
|
11132
|
-
isEvm: true,
|
|
11133
|
-
contracts: {
|
|
11134
|
-
vaultFactory: "0xA5653d54079ABeCe780F8d9597B2bc4B09fe464A",
|
|
11135
|
-
vaultImplementation: "0x8099b1406485d2255ff89Ce5Ea18520802AFC150",
|
|
11136
|
-
wormholeCoreBridge: "0x31377888146f3253211EFEf5c676D41ECe7D58Fe",
|
|
11137
|
-
tokenBridge: "0x99737Ec4B815d816c49A385943baf0380e75c0Ac"
|
|
11138
|
-
}
|
|
11139
|
-
},
|
|
11140
|
-
mainnet: {
|
|
11141
|
-
name: "Optimism",
|
|
11142
|
-
chainId: 10,
|
|
11143
|
-
wormholeChainId: 24,
|
|
11144
|
-
rpcUrl: "https://mainnet.optimism.io",
|
|
11145
|
-
explorerUrl: "https://optimistic.etherscan.io",
|
|
11146
|
-
isEvm: true,
|
|
11147
|
-
contracts: {
|
|
11148
|
-
wormholeCoreBridge: "0xEe91C335eab126dF5fDB3797EA9d6aD93aeC9722",
|
|
11149
|
-
tokenBridge: "0x1D68124e65faFC907325e3EDbF8c4d84499DAa8b"
|
|
11150
|
-
}
|
|
11322
|
+
if (clientConfig.vaultContractAddress && isContractPrincipal(clientConfig.vaultContractAddress)) {
|
|
11323
|
+
const parsed = parseContractPrincipal(clientConfig.vaultContractAddress);
|
|
11324
|
+
this.vaultContract = { address: parsed.address, name: parsed.contractName };
|
|
11325
|
+
} else {
|
|
11326
|
+
this.vaultContract = null;
|
|
11151
11327
|
}
|
|
11152
|
-
|
|
11153
|
-
|
|
11154
|
-
|
|
11155
|
-
|
|
11156
|
-
|
|
11157
|
-
|
|
11158
|
-
|
|
11159
|
-
|
|
11160
|
-
|
|
11161
|
-
|
|
11162
|
-
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
|
|
11166
|
-
|
|
11167
|
-
|
|
11168
|
-
|
|
11169
|
-
|
|
11170
|
-
|
|
11171
|
-
|
|
11172
|
-
|
|
11173
|
-
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
|
|
11328
|
+
if (clientConfig.wormholeVerifierAddress && isContractPrincipal(clientConfig.wormholeVerifierAddress)) {
|
|
11329
|
+
const parsed = parseContractPrincipal(clientConfig.wormholeVerifierAddress);
|
|
11330
|
+
this.wormholeVerifierContract = { address: parsed.address, name: parsed.contractName };
|
|
11331
|
+
} else {
|
|
11332
|
+
this.wormholeVerifierContract = null;
|
|
11333
|
+
}
|
|
11334
|
+
if (clientConfig.vaultVaaContractAddress && isContractPrincipal(clientConfig.vaultVaaContractAddress)) {
|
|
11335
|
+
const parsed = parseContractPrincipal(clientConfig.vaultVaaContractAddress);
|
|
11336
|
+
this.vaultVaaContract = { address: parsed.address, name: parsed.contractName };
|
|
11337
|
+
} else {
|
|
11338
|
+
this.vaultVaaContract = null;
|
|
11339
|
+
}
|
|
11340
|
+
if (this.spokeContract && !this.vaultContract) {
|
|
11341
|
+
this.vaultContract = {
|
|
11342
|
+
address: this.spokeContract.address,
|
|
11343
|
+
name: "veridex-vault"
|
|
11344
|
+
};
|
|
11345
|
+
}
|
|
11346
|
+
if (this.spokeContract && !this.wormholeVerifierContract) {
|
|
11347
|
+
this.wormholeVerifierContract = {
|
|
11348
|
+
address: this.spokeContract.address,
|
|
11349
|
+
name: "veridex-wormhole-verifier"
|
|
11350
|
+
};
|
|
11351
|
+
}
|
|
11352
|
+
if (this.spokeContract && !this.vaultVaaContract) {
|
|
11353
|
+
this.vaultVaaContract = {
|
|
11354
|
+
address: this.spokeContract.address,
|
|
11355
|
+
name: "veridex-vault-vaa"
|
|
11356
|
+
};
|
|
11357
|
+
}
|
|
11358
|
+
this.config = {
|
|
11359
|
+
name: `Stacks ${this.networkType}`,
|
|
11360
|
+
chainId: this.networkType === "mainnet" ? 1 : 2147483648,
|
|
11361
|
+
wormholeChainId: clientConfig.wormholeChainId,
|
|
11362
|
+
rpcUrl: this.rpcUrl,
|
|
11363
|
+
explorerUrl: this.networkType === "testnet" ? "https://explorer.hiro.so/?chain=testnet" : "https://explorer.hiro.so",
|
|
11364
|
+
isEvm: false,
|
|
11181
11365
|
contracts: {
|
|
11182
|
-
|
|
11183
|
-
|
|
11366
|
+
hub: clientConfig.spokeContractAddress,
|
|
11367
|
+
wormholeCoreBridge: "",
|
|
11368
|
+
wormholeVerifier: this.wormholeVerifierContract ? `${this.wormholeVerifierContract.address}.${this.wormholeVerifierContract.name}` : void 0,
|
|
11369
|
+
vaultVaa: this.vaultVaaContract ? `${this.vaultVaaContract.address}.${this.vaultVaaContract.name}` : void 0
|
|
11184
11370
|
}
|
|
11371
|
+
};
|
|
11372
|
+
}
|
|
11373
|
+
// ========================================================================
|
|
11374
|
+
// ChainClient Interface - Configuration
|
|
11375
|
+
// ========================================================================
|
|
11376
|
+
getConfig() {
|
|
11377
|
+
return this.config;
|
|
11378
|
+
}
|
|
11379
|
+
// ========================================================================
|
|
11380
|
+
// ChainClient Interface - Nonce & Fees
|
|
11381
|
+
// ========================================================================
|
|
11382
|
+
/**
|
|
11383
|
+
* Get the current nonce for a user identity from the spoke contract.
|
|
11384
|
+
* Calls the read-only function `get-nonce` on veridex-spoke.
|
|
11385
|
+
*/
|
|
11386
|
+
async getNonce(userKeyHash) {
|
|
11387
|
+
if (!this.spokeContract) {
|
|
11388
|
+
return 0n;
|
|
11185
11389
|
}
|
|
11186
|
-
|
|
11187
|
-
|
|
11188
|
-
|
|
11189
|
-
|
|
11190
|
-
|
|
11191
|
-
|
|
11192
|
-
|
|
11193
|
-
|
|
11194
|
-
|
|
11195
|
-
name: "Sepolia",
|
|
11196
|
-
chainId: 11155111,
|
|
11197
|
-
wormholeChainId: 10002,
|
|
11198
|
-
rpcUrl: "https://ethereum-sepolia-rpc.publicnode.com",
|
|
11199
|
-
explorerUrl: "https://sepolia.etherscan.io",
|
|
11200
|
-
isEvm: true,
|
|
11201
|
-
contracts: {
|
|
11202
|
-
vaultFactory: "0x07F608AFf6d63b68029488b726d895c4Bb593038",
|
|
11203
|
-
vaultImplementation: "0xD66153fccFB6731fB6c4944FbD607ba86A76a1f6",
|
|
11204
|
-
wormholeCoreBridge: "0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78",
|
|
11205
|
-
tokenBridge: "0xDB5492265f6038831E89f495670FF909aDe94bd9"
|
|
11390
|
+
try {
|
|
11391
|
+
const result = await this.callReadOnly(
|
|
11392
|
+
this.spokeContract.address,
|
|
11393
|
+
this.spokeContract.name,
|
|
11394
|
+
"get-nonce",
|
|
11395
|
+
[`0x${userKeyHash.replace("0x", "")}`]
|
|
11396
|
+
);
|
|
11397
|
+
if (result && result.value !== void 0) {
|
|
11398
|
+
return BigInt(result.value);
|
|
11206
11399
|
}
|
|
11207
|
-
|
|
11208
|
-
|
|
11209
|
-
|
|
11210
|
-
|
|
11211
|
-
|
|
11400
|
+
return 0n;
|
|
11401
|
+
} catch {
|
|
11402
|
+
return 0n;
|
|
11403
|
+
}
|
|
11404
|
+
}
|
|
11405
|
+
/**
|
|
11406
|
+
* Get the Wormhole message fee.
|
|
11407
|
+
* Phase 1: No Wormhole integration, returns 0.
|
|
11408
|
+
*/
|
|
11409
|
+
async getMessageFee() {
|
|
11410
|
+
return 0n;
|
|
11411
|
+
}
|
|
11412
|
+
// ========================================================================
|
|
11413
|
+
// ChainClient Interface - Payload Building
|
|
11414
|
+
// ========================================================================
|
|
11415
|
+
async buildTransferPayload(params) {
|
|
11416
|
+
return encodeTransferAction(params.token, params.recipient, params.amount);
|
|
11417
|
+
}
|
|
11418
|
+
async buildExecutePayload(params) {
|
|
11419
|
+
return encodeExecuteAction(params.target, params.value, params.data);
|
|
11420
|
+
}
|
|
11421
|
+
async buildBridgePayload(params) {
|
|
11422
|
+
return encodeBridgeAction(params.token, params.amount, params.destinationChain, params.recipient);
|
|
11423
|
+
}
|
|
11424
|
+
// ========================================================================
|
|
11425
|
+
// ChainClient Interface - Dispatch
|
|
11426
|
+
// ========================================================================
|
|
11427
|
+
/**
|
|
11428
|
+
* Direct dispatch is not supported on Stacks in Phase 1.
|
|
11429
|
+
* Stacks actions are executed via sponsored transactions through the relayer.
|
|
11430
|
+
*/
|
|
11431
|
+
async dispatch(_signature, _publicKeyX, _publicKeyY, _targetChain, _actionPayload, _nonce, _signer) {
|
|
11432
|
+
throw new Error(
|
|
11433
|
+
"Direct dispatch not supported on Stacks in Phase 1. Use dispatchGasless() to route through the relayer, which sponsors Stacks transactions. Phase 2 will add Wormhole cross-chain dispatch support."
|
|
11434
|
+
);
|
|
11435
|
+
}
|
|
11436
|
+
/**
|
|
11437
|
+
* Dispatch an action via the relayer (gasless/sponsored).
|
|
11438
|
+
*
|
|
11439
|
+
* Flow:
|
|
11440
|
+
* 1. User signs action with Passkey (on client)
|
|
11441
|
+
* 2. SDK submits to relayer with targetChain=60 (Stacks)
|
|
11442
|
+
* 3. Relayer builds Clarity contract-call transaction
|
|
11443
|
+
* 4. Relayer sponsors the transaction (pays STX gas)
|
|
11444
|
+
* 5. Relayer broadcasts to Stacks network
|
|
11445
|
+
* 6. Transaction confirmed on Stacks
|
|
11446
|
+
*/
|
|
11447
|
+
async dispatchGasless(signature, publicKeyX, publicKeyY, targetChain, actionPayload, nonce, relayerUrl) {
|
|
11448
|
+
const keyHash = await computeKeyHashFromCoords(publicKeyX, publicKeyY);
|
|
11449
|
+
const compressedPubkey = compressPublicKey(publicKeyX, publicKeyY);
|
|
11450
|
+
const compactSig = rsToCompactSignature(signature.r, signature.s);
|
|
11451
|
+
const request = {
|
|
11452
|
+
signature: {
|
|
11453
|
+
r: "0x" + signature.r.toString(16).padStart(64, "0"),
|
|
11454
|
+
s: "0x" + signature.s.toString(16).padStart(64, "0"),
|
|
11455
|
+
authenticatorData: signature.authenticatorData,
|
|
11456
|
+
clientDataJSON: signature.clientDataJSON,
|
|
11457
|
+
challengeIndex: signature.challengeIndex,
|
|
11458
|
+
typeIndex: signature.typeIndex
|
|
11459
|
+
},
|
|
11460
|
+
publicKeyX: "0x" + publicKeyX.toString(16).padStart(64, "0"),
|
|
11461
|
+
publicKeyY: "0x" + publicKeyY.toString(16).padStart(64, "0"),
|
|
11462
|
+
compressedPubkey: "0x" + bytesToHex(compressedPubkey),
|
|
11463
|
+
compactSignature: "0x" + bytesToHex(compactSig),
|
|
11464
|
+
targetChain,
|
|
11465
|
+
actionPayload,
|
|
11466
|
+
userNonce: Number(nonce)
|
|
11467
|
+
};
|
|
11468
|
+
const response = await fetch(`${relayerUrl}/api/v1/submit`, {
|
|
11469
|
+
method: "POST",
|
|
11470
|
+
headers: { "Content-Type": "application/json" },
|
|
11471
|
+
body: JSON.stringify(request)
|
|
11472
|
+
});
|
|
11473
|
+
if (!response.ok) {
|
|
11474
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
11475
|
+
throw new Error(
|
|
11476
|
+
`Relayer submission failed: ${response.status} ${response.statusText}. Error: ${errorText}`
|
|
11477
|
+
);
|
|
11478
|
+
}
|
|
11479
|
+
const result = await response.json();
|
|
11480
|
+
return {
|
|
11481
|
+
transactionHash: result.transactionHash ?? result.txHash ?? result.hubTxHash ?? "",
|
|
11482
|
+
sequence: BigInt(result.sequence || 0),
|
|
11483
|
+
userKeyHash: keyHash,
|
|
11484
|
+
targetChain
|
|
11485
|
+
};
|
|
11486
|
+
}
|
|
11487
|
+
// ========================================================================
|
|
11488
|
+
// ChainClient Interface - Vault Management
|
|
11489
|
+
// ========================================================================
|
|
11490
|
+
/**
|
|
11491
|
+
* Get vault address for a user.
|
|
11492
|
+
* On Stacks, vaults are map-based within the vault contract.
|
|
11493
|
+
* The "vault address" is the vault contract principal itself.
|
|
11494
|
+
*/
|
|
11495
|
+
async getVaultAddress(userKeyHash) {
|
|
11496
|
+
if (!this.vaultContract) {
|
|
11497
|
+
return null;
|
|
11498
|
+
}
|
|
11499
|
+
const exists = await this.vaultExists(userKeyHash);
|
|
11500
|
+
if (!exists) {
|
|
11501
|
+
return null;
|
|
11502
|
+
}
|
|
11503
|
+
return `${this.vaultContract.address}.${this.vaultContract.name}`;
|
|
11504
|
+
}
|
|
11505
|
+
/**
|
|
11506
|
+
* Compute vault address deterministically.
|
|
11507
|
+
* On Stacks, all vaults live in the same contract (map-based).
|
|
11508
|
+
*/
|
|
11509
|
+
computeVaultAddress(_userKeyHash) {
|
|
11510
|
+
if (!this.vaultContract) {
|
|
11511
|
+
throw new Error("Vault contract not configured");
|
|
11512
|
+
}
|
|
11513
|
+
return `${this.vaultContract.address}.${this.vaultContract.name}`;
|
|
11514
|
+
}
|
|
11515
|
+
/**
|
|
11516
|
+
* Check if a vault (identity) exists for a user.
|
|
11517
|
+
* Queries the spoke contract's `identity-exists` read-only function.
|
|
11518
|
+
*/
|
|
11519
|
+
async vaultExists(userKeyHash) {
|
|
11520
|
+
if (!this.spokeContract) {
|
|
11521
|
+
return false;
|
|
11522
|
+
}
|
|
11523
|
+
try {
|
|
11524
|
+
const result = await this.callReadOnly(
|
|
11525
|
+
this.spokeContract.address,
|
|
11526
|
+
this.spokeContract.name,
|
|
11527
|
+
"identity-exists",
|
|
11528
|
+
[`0x${userKeyHash.replace("0x", "")}`]
|
|
11529
|
+
);
|
|
11530
|
+
return result === true || result?.value === true;
|
|
11531
|
+
} catch {
|
|
11532
|
+
return false;
|
|
11533
|
+
}
|
|
11534
|
+
}
|
|
11535
|
+
/**
|
|
11536
|
+
* Create a vault (register identity) on Stacks.
|
|
11537
|
+
* Must be done via Hub dispatch or relayer in Phase 1.
|
|
11538
|
+
*/
|
|
11539
|
+
async createVault(userKeyHash, _signer) {
|
|
11540
|
+
throw new Error(
|
|
11541
|
+
`Vault creation on Stacks requires Passkey signature verification. Use createVaultViaRelayer() for sponsored identity registration, or call register-identity directly with a signed Stacks transaction. KeyHash=${userKeyHash}`
|
|
11542
|
+
);
|
|
11543
|
+
}
|
|
11544
|
+
/**
|
|
11545
|
+
* Create a vault with a sponsor wallet.
|
|
11546
|
+
* On Stacks, this registers an identity via sponsored transaction.
|
|
11547
|
+
*/
|
|
11548
|
+
async createVaultSponsored(userKeyHash, _sponsorPrivateKey, _rpcUrl) {
|
|
11549
|
+
throw new Error(
|
|
11550
|
+
`Sponsored vault creation on Stacks requires the user to sign with their Passkey. Use createVaultViaRelayer() which handles the sponsored transaction flow. KeyHash=${userKeyHash}`
|
|
11551
|
+
);
|
|
11552
|
+
}
|
|
11553
|
+
/**
|
|
11554
|
+
* Create a vault via the relayer (sponsored/gasless).
|
|
11555
|
+
* The relayer will sponsor the register-identity transaction.
|
|
11556
|
+
*/
|
|
11557
|
+
async createVaultViaRelayer(userKeyHash, relayerUrl) {
|
|
11558
|
+
const response = await fetch(`${relayerUrl}/api/v1/stacks/vault`, {
|
|
11559
|
+
method: "POST",
|
|
11560
|
+
headers: { "Content-Type": "application/json" },
|
|
11561
|
+
body: JSON.stringify({
|
|
11562
|
+
userKeyHash,
|
|
11563
|
+
chainId: this.config.wormholeChainId
|
|
11564
|
+
})
|
|
11565
|
+
});
|
|
11566
|
+
const result = await response.json();
|
|
11567
|
+
if (!response.ok || !result.success) {
|
|
11568
|
+
throw new Error(result.error || "Failed to create vault via relayer");
|
|
11569
|
+
}
|
|
11570
|
+
return {
|
|
11571
|
+
address: result.vaultAddress || this.computeVaultAddress(userKeyHash),
|
|
11572
|
+
transactionHash: result.transactionHash || "",
|
|
11573
|
+
blockNumber: 0,
|
|
11574
|
+
gasUsed: 0n,
|
|
11575
|
+
alreadyExisted: result.alreadyExists || false,
|
|
11576
|
+
sponsoredBy: "relayer"
|
|
11577
|
+
};
|
|
11578
|
+
}
|
|
11579
|
+
async estimateVaultCreationGas(_userKeyHash) {
|
|
11580
|
+
return 10000n;
|
|
11581
|
+
}
|
|
11582
|
+
getFactoryAddress() {
|
|
11583
|
+
return void 0;
|
|
11584
|
+
}
|
|
11585
|
+
getImplementationAddress() {
|
|
11586
|
+
return void 0;
|
|
11587
|
+
}
|
|
11588
|
+
// ========================================================================
|
|
11589
|
+
// Stacks-Specific: Balance Queries
|
|
11590
|
+
// ========================================================================
|
|
11591
|
+
/**
|
|
11592
|
+
* Get native STX balance for an address.
|
|
11593
|
+
*/
|
|
11594
|
+
async getNativeBalance(address) {
|
|
11595
|
+
try {
|
|
11596
|
+
const response = await fetch(
|
|
11597
|
+
`${this.rpcUrl}/v2/accounts/${address}?proof=0`
|
|
11598
|
+
);
|
|
11599
|
+
if (!response.ok) {
|
|
11600
|
+
return 0n;
|
|
11601
|
+
}
|
|
11602
|
+
const data = await response.json();
|
|
11603
|
+
return BigInt(data.balance || "0");
|
|
11604
|
+
} catch {
|
|
11605
|
+
return 0n;
|
|
11606
|
+
}
|
|
11607
|
+
}
|
|
11608
|
+
/**
|
|
11609
|
+
* Get vault STX balance for an identity.
|
|
11610
|
+
* Queries the vault contract's `get-stx-balance` read-only function.
|
|
11611
|
+
*/
|
|
11612
|
+
async getVaultStxBalance(keyHash) {
|
|
11613
|
+
if (!this.vaultContract) {
|
|
11614
|
+
return 0n;
|
|
11615
|
+
}
|
|
11616
|
+
try {
|
|
11617
|
+
const result = await this.callReadOnly(
|
|
11618
|
+
this.vaultContract.address,
|
|
11619
|
+
this.vaultContract.name,
|
|
11620
|
+
"get-stx-balance",
|
|
11621
|
+
[`0x${keyHash.replace("0x", "")}`]
|
|
11622
|
+
);
|
|
11623
|
+
if (result && result.value !== void 0) {
|
|
11624
|
+
return BigInt(result.value);
|
|
11625
|
+
}
|
|
11626
|
+
return 0n;
|
|
11627
|
+
} catch {
|
|
11628
|
+
return 0n;
|
|
11629
|
+
}
|
|
11630
|
+
}
|
|
11631
|
+
/**
|
|
11632
|
+
* Get vault sBTC balance for an identity.
|
|
11633
|
+
*/
|
|
11634
|
+
async getVaultSbtcBalance(keyHash) {
|
|
11635
|
+
if (!this.vaultContract) {
|
|
11636
|
+
return 0n;
|
|
11637
|
+
}
|
|
11638
|
+
try {
|
|
11639
|
+
const result = await this.callReadOnly(
|
|
11640
|
+
this.vaultContract.address,
|
|
11641
|
+
this.vaultContract.name,
|
|
11642
|
+
"get-sbtc-balance",
|
|
11643
|
+
[`0x${keyHash.replace("0x", "")}`]
|
|
11644
|
+
);
|
|
11645
|
+
if (result && result.value !== void 0) {
|
|
11646
|
+
return BigInt(result.value);
|
|
11647
|
+
}
|
|
11648
|
+
return 0n;
|
|
11649
|
+
} catch {
|
|
11650
|
+
return 0n;
|
|
11651
|
+
}
|
|
11652
|
+
}
|
|
11653
|
+
// ========================================================================
|
|
11654
|
+
// Stacks-Specific: Identity & Session Queries
|
|
11655
|
+
// ========================================================================
|
|
11656
|
+
/**
|
|
11657
|
+
* Get identity info from the spoke contract.
|
|
11658
|
+
*/
|
|
11659
|
+
async getIdentity(keyHash) {
|
|
11660
|
+
if (!this.spokeContract) {
|
|
11661
|
+
return null;
|
|
11662
|
+
}
|
|
11663
|
+
try {
|
|
11664
|
+
const result = await this.callReadOnly(
|
|
11665
|
+
this.spokeContract.address,
|
|
11666
|
+
this.spokeContract.name,
|
|
11667
|
+
"get-identity",
|
|
11668
|
+
[`0x${keyHash.replace("0x", "")}`]
|
|
11669
|
+
);
|
|
11670
|
+
if (!result || result.value === void 0) {
|
|
11671
|
+
return null;
|
|
11672
|
+
}
|
|
11673
|
+
const val = result.value;
|
|
11674
|
+
return {
|
|
11675
|
+
compressedPubkey: val["compressed-pubkey"]?.value || "",
|
|
11676
|
+
owner: val.owner?.value || "",
|
|
11677
|
+
nonce: BigInt(val.nonce?.value || 0),
|
|
11678
|
+
createdAt: BigInt(val["created-at"]?.value || 0)
|
|
11679
|
+
};
|
|
11680
|
+
} catch {
|
|
11681
|
+
return null;
|
|
11682
|
+
}
|
|
11683
|
+
}
|
|
11684
|
+
/**
|
|
11685
|
+
* Get session info from the spoke contract.
|
|
11686
|
+
*/
|
|
11687
|
+
async getSession(keyHash, sessionHash) {
|
|
11688
|
+
if (!this.spokeContract) {
|
|
11689
|
+
return null;
|
|
11690
|
+
}
|
|
11691
|
+
try {
|
|
11692
|
+
const cleanKeyHash = `0x${keyHash.replace("0x", "")}`;
|
|
11693
|
+
const cleanSessionHash = `0x${sessionHash.replace("0x", "")}`;
|
|
11694
|
+
const result = await this.callReadOnly(
|
|
11695
|
+
this.spokeContract.address,
|
|
11696
|
+
this.spokeContract.name,
|
|
11697
|
+
"get-session",
|
|
11698
|
+
[cleanKeyHash, cleanSessionHash]
|
|
11699
|
+
);
|
|
11700
|
+
if (!result || result.value === void 0) {
|
|
11701
|
+
return null;
|
|
11702
|
+
}
|
|
11703
|
+
const val = result.value;
|
|
11704
|
+
return {
|
|
11705
|
+
sessionPubkey: val["session-pubkey"]?.value || "",
|
|
11706
|
+
expiry: BigInt(val.expiry?.value || 0),
|
|
11707
|
+
maxValue: BigInt(val["max-value"]?.value || 0),
|
|
11708
|
+
spent: BigInt(val.spent?.value || 0),
|
|
11709
|
+
revoked: val.revoked?.value === true,
|
|
11710
|
+
createdAt: BigInt(val["created-at"]?.value || 0)
|
|
11711
|
+
};
|
|
11712
|
+
} catch {
|
|
11713
|
+
return null;
|
|
11714
|
+
}
|
|
11715
|
+
}
|
|
11716
|
+
/**
|
|
11717
|
+
* Check if a session is currently active.
|
|
11718
|
+
*/
|
|
11719
|
+
async checkSessionActive(keyHash, sessionHash) {
|
|
11720
|
+
if (!this.spokeContract) {
|
|
11721
|
+
return false;
|
|
11722
|
+
}
|
|
11723
|
+
try {
|
|
11724
|
+
const cleanKeyHash = `0x${keyHash.replace("0x", "")}`;
|
|
11725
|
+
const cleanSessionHash = `0x${sessionHash.replace("0x", "")}`;
|
|
11726
|
+
const result = await this.callReadOnly(
|
|
11727
|
+
this.spokeContract.address,
|
|
11728
|
+
this.spokeContract.name,
|
|
11729
|
+
"is-session-active",
|
|
11730
|
+
[cleanKeyHash, cleanSessionHash]
|
|
11731
|
+
);
|
|
11732
|
+
return result?.value === true;
|
|
11733
|
+
} catch {
|
|
11734
|
+
return false;
|
|
11735
|
+
}
|
|
11736
|
+
}
|
|
11737
|
+
/**
|
|
11738
|
+
* Get remaining spending budget for a session.
|
|
11739
|
+
*/
|
|
11740
|
+
async getRemainingBudget(keyHash, sessionHash) {
|
|
11741
|
+
if (!this.spokeContract) {
|
|
11742
|
+
return 0n;
|
|
11743
|
+
}
|
|
11744
|
+
try {
|
|
11745
|
+
const cleanKeyHash = `0x${keyHash.replace("0x", "")}`;
|
|
11746
|
+
const cleanSessionHash = `0x${sessionHash.replace("0x", "")}`;
|
|
11747
|
+
const result = await this.callReadOnly(
|
|
11748
|
+
this.spokeContract.address,
|
|
11749
|
+
this.spokeContract.name,
|
|
11750
|
+
"get-remaining-budget",
|
|
11751
|
+
[cleanKeyHash, cleanSessionHash]
|
|
11752
|
+
);
|
|
11753
|
+
if (result && result.value !== void 0) {
|
|
11754
|
+
return BigInt(result.value);
|
|
11755
|
+
}
|
|
11756
|
+
return 0n;
|
|
11757
|
+
} catch {
|
|
11758
|
+
return 0n;
|
|
11759
|
+
}
|
|
11760
|
+
}
|
|
11761
|
+
// ========================================================================
|
|
11762
|
+
// Stacks-Specific: Protocol Status
|
|
11763
|
+
// ========================================================================
|
|
11764
|
+
/**
|
|
11765
|
+
* Check if the spoke contract is paused.
|
|
11766
|
+
*/
|
|
11767
|
+
async isProtocolPaused() {
|
|
11768
|
+
if (!this.spokeContract) {
|
|
11769
|
+
return false;
|
|
11770
|
+
}
|
|
11771
|
+
try {
|
|
11772
|
+
const result = await this.callReadOnly(
|
|
11773
|
+
this.spokeContract.address,
|
|
11774
|
+
this.spokeContract.name,
|
|
11775
|
+
"is-paused",
|
|
11776
|
+
[]
|
|
11777
|
+
);
|
|
11778
|
+
return result === true || result?.value === true;
|
|
11779
|
+
} catch {
|
|
11780
|
+
return false;
|
|
11781
|
+
}
|
|
11782
|
+
}
|
|
11783
|
+
/**
|
|
11784
|
+
* Get global identity count.
|
|
11785
|
+
*/
|
|
11786
|
+
async getIdentityCount() {
|
|
11787
|
+
if (!this.spokeContract) {
|
|
11788
|
+
return 0n;
|
|
11789
|
+
}
|
|
11790
|
+
try {
|
|
11791
|
+
const result = await this.callReadOnly(
|
|
11792
|
+
this.spokeContract.address,
|
|
11793
|
+
this.spokeContract.name,
|
|
11794
|
+
"get-identity-count",
|
|
11795
|
+
[]
|
|
11796
|
+
);
|
|
11797
|
+
return BigInt(result?.value || result || 0);
|
|
11798
|
+
} catch {
|
|
11799
|
+
return 0n;
|
|
11800
|
+
}
|
|
11801
|
+
}
|
|
11802
|
+
/**
|
|
11803
|
+
* Get total STX deposited across all vaults.
|
|
11804
|
+
*/
|
|
11805
|
+
async getTotalStxDeposited() {
|
|
11806
|
+
if (!this.vaultContract) {
|
|
11807
|
+
return 0n;
|
|
11808
|
+
}
|
|
11809
|
+
try {
|
|
11810
|
+
const result = await this.callReadOnly(
|
|
11811
|
+
this.vaultContract.address,
|
|
11812
|
+
this.vaultContract.name,
|
|
11813
|
+
"get-total-stx-deposited",
|
|
11814
|
+
[]
|
|
11815
|
+
);
|
|
11816
|
+
return BigInt(result?.value || result || 0);
|
|
11817
|
+
} catch {
|
|
11818
|
+
return 0n;
|
|
11819
|
+
}
|
|
11820
|
+
}
|
|
11821
|
+
// ========================================================================
|
|
11822
|
+
// Session Management (Issue #13)
|
|
11823
|
+
// ========================================================================
|
|
11824
|
+
/**
|
|
11825
|
+
* Register a session key on the Stacks spoke.
|
|
11826
|
+
* On Stacks, sessions are managed directly on the spoke contract
|
|
11827
|
+
* (unlike EVM spokes where sessions are on the Hub).
|
|
11828
|
+
*/
|
|
11829
|
+
async registerSession(_params) {
|
|
11830
|
+
throw new Error(
|
|
11831
|
+
"Session registration on Stacks requires a Passkey signature. Build a register-session transaction with the Passkey signature, then submit via the relayer for sponsored execution."
|
|
11832
|
+
);
|
|
11833
|
+
}
|
|
11834
|
+
/**
|
|
11835
|
+
* Revoke a session key on the Stacks spoke.
|
|
11836
|
+
*/
|
|
11837
|
+
async revokeSession(_params) {
|
|
11838
|
+
throw new Error(
|
|
11839
|
+
"Session revocation on Stacks requires a Passkey signature. Build a revoke-session transaction with the Passkey signature, then submit via the relayer for sponsored execution."
|
|
11840
|
+
);
|
|
11841
|
+
}
|
|
11842
|
+
/**
|
|
11843
|
+
* Check if a session is active.
|
|
11844
|
+
*/
|
|
11845
|
+
async isSessionActive(userKeyHash, sessionKeyHash) {
|
|
11846
|
+
const active = await this.checkSessionActive(userKeyHash, sessionKeyHash);
|
|
11847
|
+
const session = await this.getSession(userKeyHash, sessionKeyHash);
|
|
11848
|
+
return {
|
|
11849
|
+
isActive: active,
|
|
11850
|
+
expiry: session ? Number(session.expiry) : 0,
|
|
11851
|
+
maxValue: session?.maxValue ?? 0n,
|
|
11852
|
+
chainScopes: [this.config.wormholeChainId]
|
|
11853
|
+
};
|
|
11854
|
+
}
|
|
11855
|
+
/**
|
|
11856
|
+
* Get all sessions for a user.
|
|
11857
|
+
* Note: Clarity maps don't support enumeration, so this requires
|
|
11858
|
+
* off-chain indexing or event log parsing.
|
|
11859
|
+
*/
|
|
11860
|
+
async getUserSessions(_userKeyHash) {
|
|
11861
|
+
throw new Error(
|
|
11862
|
+
'Enumerating all sessions is not supported on Stacks (Clarity maps are not iterable). Use checkSessionActive() with a known session hash, or query the Stacks event log for "session-registered" print events via the Hiro API.'
|
|
11863
|
+
);
|
|
11864
|
+
}
|
|
11865
|
+
// ========================================================================
|
|
11866
|
+
// Stacks-Specific: Transaction Status
|
|
11867
|
+
// ========================================================================
|
|
11868
|
+
/**
|
|
11869
|
+
* Get the status of a Stacks transaction.
|
|
11870
|
+
*/
|
|
11871
|
+
async getTransactionStatus(txId) {
|
|
11872
|
+
try {
|
|
11873
|
+
const cleanTxId = txId.startsWith("0x") ? txId : `0x${txId}`;
|
|
11874
|
+
const response = await fetch(
|
|
11875
|
+
`${this.rpcUrl}/extended/v1/tx/${cleanTxId}`
|
|
11876
|
+
);
|
|
11877
|
+
if (!response.ok) {
|
|
11878
|
+
return { status: "not_found" };
|
|
11879
|
+
}
|
|
11880
|
+
const data = await response.json();
|
|
11881
|
+
const txStatus = data.tx_status;
|
|
11882
|
+
if (txStatus === "success") {
|
|
11883
|
+
return {
|
|
11884
|
+
status: "success",
|
|
11885
|
+
blockHeight: data.block_height
|
|
11886
|
+
};
|
|
11887
|
+
}
|
|
11888
|
+
if (txStatus === "pending") {
|
|
11889
|
+
return { status: "pending" };
|
|
11890
|
+
}
|
|
11891
|
+
if (txStatus === "abort_by_response" || txStatus === "abort_by_post_condition") {
|
|
11892
|
+
return {
|
|
11893
|
+
status: "failed",
|
|
11894
|
+
error: `Transaction aborted: ${txStatus}`
|
|
11895
|
+
};
|
|
11896
|
+
}
|
|
11897
|
+
return { status: "pending" };
|
|
11898
|
+
} catch {
|
|
11899
|
+
return { status: "not_found" };
|
|
11900
|
+
}
|
|
11901
|
+
}
|
|
11902
|
+
/**
|
|
11903
|
+
* Wait for a transaction to be confirmed.
|
|
11904
|
+
*
|
|
11905
|
+
* @param txId - Transaction ID
|
|
11906
|
+
* @param maxAttempts - Maximum polling attempts (default: 60)
|
|
11907
|
+
* @param pollIntervalMs - Polling interval in milliseconds (default: 5000)
|
|
11908
|
+
*/
|
|
11909
|
+
async waitForConfirmation(txId, maxAttempts = 60, pollIntervalMs = 5e3) {
|
|
11910
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
11911
|
+
const status = await this.getTransactionStatus(txId);
|
|
11912
|
+
if (status.status === "success") {
|
|
11913
|
+
return { confirmed: true, blockHeight: status.blockHeight };
|
|
11914
|
+
}
|
|
11915
|
+
if (status.status === "failed") {
|
|
11916
|
+
throw new Error(`Transaction failed: ${status.error}`);
|
|
11917
|
+
}
|
|
11918
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
11919
|
+
}
|
|
11920
|
+
return { confirmed: false };
|
|
11921
|
+
}
|
|
11922
|
+
// ========================================================================
|
|
11923
|
+
// Stacks-Specific: Network Info
|
|
11924
|
+
// ========================================================================
|
|
11925
|
+
/**
|
|
11926
|
+
* Get Stacks network info (block height, network version, etc.).
|
|
11927
|
+
*/
|
|
11928
|
+
async getNetworkInfo() {
|
|
11929
|
+
const response = await fetch(`${this.rpcUrl}/v2/info`);
|
|
11930
|
+
if (!response.ok) {
|
|
11931
|
+
throw new Error(`Failed to get Stacks network info: ${response.statusText}`);
|
|
11932
|
+
}
|
|
11933
|
+
const data = await response.json();
|
|
11934
|
+
return {
|
|
11935
|
+
networkId: data.network_id,
|
|
11936
|
+
stacksBlockHeight: data.stacks_tip_height,
|
|
11937
|
+
burnBlockHeight: data.burn_block_height,
|
|
11938
|
+
serverVersion: data.server_version
|
|
11939
|
+
};
|
|
11940
|
+
}
|
|
11941
|
+
/**
|
|
11942
|
+
* Get the current Stacks block height.
|
|
11943
|
+
* Used for session expiry calculations.
|
|
11944
|
+
*/
|
|
11945
|
+
async getCurrentBlockHeight() {
|
|
11946
|
+
const info = await this.getNetworkInfo();
|
|
11947
|
+
return info.stacksBlockHeight;
|
|
11948
|
+
}
|
|
11949
|
+
// ========================================================================
|
|
11950
|
+
// Internal: Read-Only Contract Calls via Hiro API
|
|
11951
|
+
// ========================================================================
|
|
11952
|
+
/**
|
|
11953
|
+
* Call a read-only Clarity function via the Hiro API.
|
|
11954
|
+
* Uses the /v2/contracts/call-read endpoint.
|
|
11955
|
+
*/
|
|
11956
|
+
async callReadOnly(contractAddress, contractName, functionName, args) {
|
|
11957
|
+
const url = `${this.rpcUrl}/v2/contracts/call-read/${contractAddress}/${contractName}/${functionName}`;
|
|
11958
|
+
const response = await fetch(url, {
|
|
11959
|
+
method: "POST",
|
|
11960
|
+
headers: { "Content-Type": "application/json" },
|
|
11961
|
+
body: JSON.stringify({
|
|
11962
|
+
sender: contractAddress,
|
|
11963
|
+
arguments: args
|
|
11964
|
+
})
|
|
11965
|
+
});
|
|
11966
|
+
if (!response.ok) {
|
|
11967
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
11968
|
+
throw new Error(
|
|
11969
|
+
`Read-only call failed: ${contractAddress}.${contractName}::${functionName} - ${response.status}: ${errorText}`
|
|
11970
|
+
);
|
|
11971
|
+
}
|
|
11972
|
+
const data = await response.json();
|
|
11973
|
+
if (!data.okay) {
|
|
11974
|
+
throw new Error(
|
|
11975
|
+
`Read-only call returned error: ${contractAddress}.${contractName}::${functionName} - ${data.cause || "Unknown cause"}`
|
|
11976
|
+
);
|
|
11977
|
+
}
|
|
11978
|
+
return this.parseClarityValue(data.result);
|
|
11979
|
+
}
|
|
11980
|
+
/**
|
|
11981
|
+
* Parse a hex-encoded Clarity value from the API response.
|
|
11982
|
+
* This is a simplified parser for common Clarity types.
|
|
11983
|
+
*/
|
|
11984
|
+
parseClarityValue(hex) {
|
|
11985
|
+
if (!hex || hex === "0x") {
|
|
11986
|
+
return null;
|
|
11987
|
+
}
|
|
11988
|
+
const bytes = hexToBytes(hex);
|
|
11989
|
+
if (bytes.length === 0) {
|
|
11990
|
+
return null;
|
|
11991
|
+
}
|
|
11992
|
+
const typeId = bytes[0];
|
|
11993
|
+
switch (typeId) {
|
|
11994
|
+
// int (0x00)
|
|
11995
|
+
case 0: {
|
|
11996
|
+
let value = 0n;
|
|
11997
|
+
for (let i = 1; i < 17 && i < bytes.length; i++) {
|
|
11998
|
+
value = value << 8n | BigInt(bytes[i]);
|
|
11999
|
+
}
|
|
12000
|
+
return { value };
|
|
12001
|
+
}
|
|
12002
|
+
// uint (0x01)
|
|
12003
|
+
case 1: {
|
|
12004
|
+
let value = 0n;
|
|
12005
|
+
for (let i = 1; i < 17 && i < bytes.length; i++) {
|
|
12006
|
+
value = value << 8n | BigInt(bytes[i]);
|
|
12007
|
+
}
|
|
12008
|
+
return { value };
|
|
12009
|
+
}
|
|
12010
|
+
// buffer (0x02)
|
|
12011
|
+
case 2: {
|
|
12012
|
+
const len = bytes[1] << 24 | bytes[2] << 16 | bytes[3] << 8 | bytes[4];
|
|
12013
|
+
const bufValue = bytes.slice(5, 5 + len);
|
|
12014
|
+
return { value: "0x" + bytesToHex(bufValue) };
|
|
12015
|
+
}
|
|
12016
|
+
// bool true (0x03)
|
|
12017
|
+
case 3:
|
|
12018
|
+
return true;
|
|
12019
|
+
// bool false (0x04)
|
|
12020
|
+
case 4:
|
|
12021
|
+
return false;
|
|
12022
|
+
// optional none (0x09)
|
|
12023
|
+
case 9:
|
|
12024
|
+
return null;
|
|
12025
|
+
// optional some (0x0a)
|
|
12026
|
+
case 10:
|
|
12027
|
+
return this.parseClarityValue("0x" + bytesToHex(bytes.slice(1)));
|
|
12028
|
+
// response ok (0x07)
|
|
12029
|
+
case 7:
|
|
12030
|
+
return this.parseClarityValue("0x" + bytesToHex(bytes.slice(1)));
|
|
12031
|
+
// response err (0x08)
|
|
12032
|
+
case 8: {
|
|
12033
|
+
const errVal = this.parseClarityValue("0x" + bytesToHex(bytes.slice(1)));
|
|
12034
|
+
throw new Error(`Clarity error: ${JSON.stringify(errVal)}`);
|
|
12035
|
+
}
|
|
12036
|
+
// tuple (0x0c)
|
|
12037
|
+
case 12: {
|
|
12038
|
+
return { value: hex };
|
|
12039
|
+
}
|
|
12040
|
+
default:
|
|
12041
|
+
return { value: hex };
|
|
12042
|
+
}
|
|
12043
|
+
}
|
|
12044
|
+
};
|
|
12045
|
+
|
|
12046
|
+
// src/presets.ts
|
|
12047
|
+
var CHAIN_NAMES = {
|
|
12048
|
+
// EVM L2s (Hub-capable)
|
|
12049
|
+
BASE: "base",
|
|
12050
|
+
OPTIMISM: "optimism",
|
|
12051
|
+
ARBITRUM: "arbitrum",
|
|
12052
|
+
SCROLL: "scroll",
|
|
12053
|
+
BLAST: "blast",
|
|
12054
|
+
MANTLE: "mantle",
|
|
12055
|
+
// EVM L1s
|
|
12056
|
+
ETHEREUM: "ethereum",
|
|
12057
|
+
POLYGON: "polygon",
|
|
12058
|
+
BSC: "bsc",
|
|
12059
|
+
AVALANCHE: "avalanche",
|
|
12060
|
+
FANTOM: "fantom",
|
|
12061
|
+
CELO: "celo",
|
|
12062
|
+
MOONBEAM: "moonbeam",
|
|
12063
|
+
// Non-EVM
|
|
12064
|
+
SOLANA: "solana",
|
|
12065
|
+
APTOS: "aptos",
|
|
12066
|
+
SUI: "sui",
|
|
12067
|
+
STARKNET: "starknet",
|
|
12068
|
+
STACKS: "stacks",
|
|
12069
|
+
NEAR: "near",
|
|
12070
|
+
SEI: "sei"
|
|
12071
|
+
};
|
|
12072
|
+
var CHAIN_PRESETS = {
|
|
12073
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
12074
|
+
// BASE - Primary Hub Chain
|
|
12075
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
12076
|
+
base: {
|
|
12077
|
+
displayName: "Base",
|
|
12078
|
+
type: "evm",
|
|
12079
|
+
canBeHub: true,
|
|
12080
|
+
testnet: {
|
|
12081
|
+
name: "Base Sepolia",
|
|
12082
|
+
chainId: 84532,
|
|
12083
|
+
wormholeChainId: 10004,
|
|
12084
|
+
rpcUrl: "https://sepolia.base.org",
|
|
12085
|
+
explorerUrl: "https://sepolia.basescan.org",
|
|
12086
|
+
isEvm: true,
|
|
12087
|
+
contracts: {
|
|
12088
|
+
hub: "0x66D87dE68327f48A099c5B9bE97020Feab9a7c82",
|
|
12089
|
+
vaultFactory: "0xCFaEb5652aa2Ee60b2229dC8895B4159749C7e53",
|
|
12090
|
+
vaultImplementation: "0x0d13367C16c6f0B24eD275CC67C7D9f42878285c",
|
|
12091
|
+
wormholeCoreBridge: "0x79A1027a6A159502049F10906D333EC57E95F083",
|
|
12092
|
+
tokenBridge: "0x86F55A04690fd7815A3D802bD587e83eA888B239"
|
|
12093
|
+
}
|
|
12094
|
+
},
|
|
12095
|
+
mainnet: {
|
|
12096
|
+
name: "Base",
|
|
12097
|
+
chainId: 8453,
|
|
12098
|
+
wormholeChainId: 30,
|
|
12099
|
+
rpcUrl: "https://mainnet.base.org",
|
|
12100
|
+
explorerUrl: "https://basescan.org",
|
|
12101
|
+
isEvm: true,
|
|
12102
|
+
contracts: {
|
|
12103
|
+
// TODO: Deploy mainnet contracts
|
|
12104
|
+
wormholeCoreBridge: "0xbebdb6C8ddC678FfA9f8748f85C815C556Dd8ac6",
|
|
12105
|
+
tokenBridge: "0x8d2de8d2f73F1F4cAB472AC9A881C9b123C79627"
|
|
12106
|
+
}
|
|
12107
|
+
}
|
|
12108
|
+
},
|
|
12109
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
12110
|
+
// OPTIMISM - Secondary Hub / Spoke
|
|
12111
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
12112
|
+
optimism: {
|
|
12113
|
+
displayName: "Optimism",
|
|
12114
|
+
type: "evm",
|
|
12115
|
+
canBeHub: true,
|
|
12116
|
+
testnet: {
|
|
12117
|
+
name: "Optimism Sepolia",
|
|
12118
|
+
chainId: 11155420,
|
|
12119
|
+
wormholeChainId: 10005,
|
|
12120
|
+
rpcUrl: "https://sepolia.optimism.io",
|
|
12121
|
+
explorerUrl: "https://sepolia-optimism.etherscan.io",
|
|
12122
|
+
isEvm: true,
|
|
12123
|
+
contracts: {
|
|
12124
|
+
vaultFactory: "0xA5653d54079ABeCe780F8d9597B2bc4B09fe464A",
|
|
12125
|
+
vaultImplementation: "0x8099b1406485d2255ff89Ce5Ea18520802AFC150",
|
|
12126
|
+
wormholeCoreBridge: "0x31377888146f3253211EFEf5c676D41ECe7D58Fe",
|
|
12127
|
+
tokenBridge: "0x99737Ec4B815d816c49A385943baf0380e75c0Ac"
|
|
12128
|
+
}
|
|
12129
|
+
},
|
|
12130
|
+
mainnet: {
|
|
12131
|
+
name: "Optimism",
|
|
12132
|
+
chainId: 10,
|
|
12133
|
+
wormholeChainId: 24,
|
|
12134
|
+
rpcUrl: "https://mainnet.optimism.io",
|
|
12135
|
+
explorerUrl: "https://optimistic.etherscan.io",
|
|
12136
|
+
isEvm: true,
|
|
12137
|
+
contracts: {
|
|
12138
|
+
wormholeCoreBridge: "0xEe91C335eab126dF5fDB3797EA9d6aD93aeC9722",
|
|
12139
|
+
tokenBridge: "0x1D68124e65faFC907325e3EDbF8c4d84499DAa8b"
|
|
12140
|
+
}
|
|
12141
|
+
}
|
|
12142
|
+
},
|
|
12143
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
12144
|
+
// ARBITRUM
|
|
12145
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
12146
|
+
arbitrum: {
|
|
12147
|
+
displayName: "Arbitrum",
|
|
12148
|
+
type: "evm",
|
|
12149
|
+
canBeHub: true,
|
|
12150
|
+
testnet: {
|
|
12151
|
+
name: "Arbitrum Sepolia",
|
|
12152
|
+
chainId: 421614,
|
|
12153
|
+
wormholeChainId: 10003,
|
|
12154
|
+
rpcUrl: "https://sepolia-rollup.arbitrum.io/rpc",
|
|
12155
|
+
explorerUrl: "https://sepolia.arbiscan.io",
|
|
12156
|
+
isEvm: true,
|
|
12157
|
+
contracts: {
|
|
12158
|
+
vaultFactory: "0xd36D3D5DB59d78f1E33813490F72DABC15C9B07c",
|
|
12159
|
+
vaultImplementation: "0xB10ACf39eBF17fc33F722cBD955b7aeCB0611bc4",
|
|
12160
|
+
wormholeCoreBridge: "0x6b9C8671cdDC8dEab9c719bB87cBd3e782bA6a35",
|
|
12161
|
+
tokenBridge: "0xC7A204bDBFe983FCD8d8E61D02b475D4073fF97e"
|
|
12162
|
+
}
|
|
12163
|
+
},
|
|
12164
|
+
mainnet: {
|
|
12165
|
+
name: "Arbitrum",
|
|
12166
|
+
chainId: 42161,
|
|
12167
|
+
wormholeChainId: 23,
|
|
12168
|
+
rpcUrl: "https://arb1.arbitrum.io/rpc",
|
|
12169
|
+
explorerUrl: "https://arbiscan.io",
|
|
12170
|
+
isEvm: true,
|
|
12171
|
+
contracts: {
|
|
12172
|
+
wormholeCoreBridge: "0xa5f208e072434bC67592E4C49C1B991BA79BCA46",
|
|
12173
|
+
tokenBridge: "0x0b2402144Bb366A632D14B83F244D2e0e21bD39c"
|
|
12174
|
+
}
|
|
12175
|
+
}
|
|
12176
|
+
},
|
|
12177
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
12178
|
+
// ETHEREUM
|
|
12179
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
12180
|
+
ethereum: {
|
|
12181
|
+
displayName: "Ethereum",
|
|
12182
|
+
type: "evm",
|
|
12183
|
+
canBeHub: false,
|
|
12184
|
+
testnet: {
|
|
12185
|
+
name: "Sepolia",
|
|
12186
|
+
chainId: 11155111,
|
|
12187
|
+
wormholeChainId: 10002,
|
|
12188
|
+
rpcUrl: "https://ethereum-sepolia-rpc.publicnode.com",
|
|
12189
|
+
explorerUrl: "https://sepolia.etherscan.io",
|
|
12190
|
+
isEvm: true,
|
|
12191
|
+
contracts: {
|
|
12192
|
+
vaultFactory: "0x07F608AFf6d63b68029488b726d895c4Bb593038",
|
|
12193
|
+
vaultImplementation: "0xD66153fccFB6731fB6c4944FbD607ba86A76a1f6",
|
|
12194
|
+
wormholeCoreBridge: "0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78",
|
|
12195
|
+
tokenBridge: "0xDB5492265f6038831E89f495670FF909aDe94bd9"
|
|
12196
|
+
}
|
|
12197
|
+
},
|
|
12198
|
+
mainnet: {
|
|
12199
|
+
name: "Ethereum",
|
|
12200
|
+
chainId: 1,
|
|
12201
|
+
wormholeChainId: 2,
|
|
11212
12202
|
rpcUrl: "https://eth.llamarpc.com",
|
|
11213
12203
|
explorerUrl: "https://etherscan.io",
|
|
11214
12204
|
isEvm: true,
|
|
@@ -11671,6 +12661,49 @@ var CHAIN_PRESETS = {
|
|
|
11671
12661
|
}
|
|
11672
12662
|
},
|
|
11673
12663
|
// ────────────────────────────────────────────────────────────────────────
|
|
12664
|
+
// STACKS
|
|
12665
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
12666
|
+
stacks: {
|
|
12667
|
+
displayName: "Stacks",
|
|
12668
|
+
type: "stacks",
|
|
12669
|
+
canBeHub: false,
|
|
12670
|
+
testnet: {
|
|
12671
|
+
name: "Stacks Testnet",
|
|
12672
|
+
chainId: 2147483648,
|
|
12673
|
+
// CAIP-2: stacks:2147483648
|
|
12674
|
+
wormholeChainId: 60,
|
|
12675
|
+
// Official Wormhole chain ID for Stacks
|
|
12676
|
+
rpcUrl: "https://api.testnet.hiro.so",
|
|
12677
|
+
explorerUrl: "https://explorer.hiro.so/?chain=testnet",
|
|
12678
|
+
isEvm: false,
|
|
12679
|
+
contracts: {
|
|
12680
|
+
// Spoke contract: identity + session management
|
|
12681
|
+
hub: "ST1CKNN84MDPCJRQDHDCY34GMRKQ3TASNNDJQDRTN.veridex-spoke",
|
|
12682
|
+
// Vault contract: STX/sBTC custody
|
|
12683
|
+
vaultFactory: "ST1CKNN84MDPCJRQDHDCY34GMRKQ3TASNNDJQDRTN.veridex-vault",
|
|
12684
|
+
wormholeCoreBridge: "",
|
|
12685
|
+
// Phase 2: Wormhole integration contracts
|
|
12686
|
+
wormholeVerifier: "ST1CKNN84MDPCJRQDHDCY34GMRKQ3TASNNDJQDRTN.veridex-wormhole-verifier",
|
|
12687
|
+
vaultVaa: "ST1CKNN84MDPCJRQDHDCY34GMRKQ3TASNNDJQDRTN.veridex-vault-vaa"
|
|
12688
|
+
},
|
|
12689
|
+
hubChainId: 10004
|
|
12690
|
+
// Base Sepolia
|
|
12691
|
+
},
|
|
12692
|
+
mainnet: {
|
|
12693
|
+
name: "Stacks",
|
|
12694
|
+
chainId: 1,
|
|
12695
|
+
// CAIP-2: stacks:1
|
|
12696
|
+
wormholeChainId: 60,
|
|
12697
|
+
rpcUrl: "https://api.hiro.so",
|
|
12698
|
+
explorerUrl: "https://explorer.hiro.so",
|
|
12699
|
+
isEvm: false,
|
|
12700
|
+
contracts: {
|
|
12701
|
+
// TODO: Deploy mainnet contracts
|
|
12702
|
+
wormholeCoreBridge: ""
|
|
12703
|
+
}
|
|
12704
|
+
}
|
|
12705
|
+
},
|
|
12706
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
11674
12707
|
// NEAR
|
|
11675
12708
|
// ────────────────────────────────────────────────────────────────────────
|
|
11676
12709
|
near: {
|
|
@@ -11793,6 +12826,13 @@ function createChainClient(chain, network, customRpcUrl) {
|
|
|
11793
12826
|
wormholeChainId: config.wormholeChainId,
|
|
11794
12827
|
network: network === "testnet" ? "sepolia" : "mainnet"
|
|
11795
12828
|
});
|
|
12829
|
+
case "stacks":
|
|
12830
|
+
return new StacksClient({
|
|
12831
|
+
rpcUrl,
|
|
12832
|
+
spokeContractAddress: config.contracts.hub || void 0,
|
|
12833
|
+
wormholeChainId: config.wormholeChainId,
|
|
12834
|
+
network
|
|
12835
|
+
});
|
|
11796
12836
|
case "near":
|
|
11797
12837
|
case "cosmos":
|
|
11798
12838
|
throw new Error(`Chain type "${preset.type}" is not yet supported. Coming soon!`);
|
|
@@ -11848,8 +12888,8 @@ function createSessionSDK(chain = "base", config = {}) {
|
|
|
11848
12888
|
}
|
|
11849
12889
|
|
|
11850
12890
|
// src/core/CrossOriginAuth.ts
|
|
11851
|
-
var VERIDEX_RP_ID = "veridex.network";
|
|
11852
12891
|
var DEFAULT_AUTH_PORTAL_URL = "https://auth.veridex.network";
|
|
12892
|
+
var DEFAULT_RELAYER_URL = "https://amused-kameko-veridex-demo-37453117.koyeb.app/api/v1";
|
|
11853
12893
|
var AUTH_MESSAGE_TYPES = {
|
|
11854
12894
|
AUTH_REQUEST: "VERIDEX_AUTH_REQUEST",
|
|
11855
12895
|
AUTH_RESPONSE: "VERIDEX_AUTH_RESPONSE",
|
|
@@ -11862,6 +12902,7 @@ var CrossOriginAuth = class {
|
|
|
11862
12902
|
this.config = {
|
|
11863
12903
|
rpId: config.rpId ?? VERIDEX_RP_ID,
|
|
11864
12904
|
authPortalUrl: config.authPortalUrl ?? DEFAULT_AUTH_PORTAL_URL,
|
|
12905
|
+
relayerUrl: config.relayerUrl ?? DEFAULT_RELAYER_URL,
|
|
11865
12906
|
mode: config.mode ?? "popup",
|
|
11866
12907
|
popupFeatures: config.popupFeatures ?? "width=500,height=600,left=100,top=100",
|
|
11867
12908
|
timeout: config.timeout ?? 12e4,
|
|
@@ -12049,6 +13090,84 @@ var CrossOriginAuth = class {
|
|
|
12049
13090
|
getAuthPortalUrl() {
|
|
12050
13091
|
return this.config.authPortalUrl;
|
|
12051
13092
|
}
|
|
13093
|
+
// ========================================================================
|
|
13094
|
+
// Server-Side Session Tokens (ADR-0018)
|
|
13095
|
+
// ========================================================================
|
|
13096
|
+
/**
|
|
13097
|
+
* Create a server-validated session token via the relayer.
|
|
13098
|
+
* Call this after authenticating (via ROR or auth portal) to get a
|
|
13099
|
+
* server-side session that the relayer can verify on subsequent requests.
|
|
13100
|
+
*/
|
|
13101
|
+
async createServerSession(session, options) {
|
|
13102
|
+
const keyHash = session.credential?.keyHash;
|
|
13103
|
+
if (!keyHash) {
|
|
13104
|
+
throw new Error("Session must include credential with keyHash");
|
|
13105
|
+
}
|
|
13106
|
+
const response = await fetch(`${this.config.relayerUrl}/session/create`, {
|
|
13107
|
+
method: "POST",
|
|
13108
|
+
headers: { "Content-Type": "application/json" },
|
|
13109
|
+
body: JSON.stringify({
|
|
13110
|
+
keyHash,
|
|
13111
|
+
appOrigin: typeof window !== "undefined" ? window.location.origin : "",
|
|
13112
|
+
sessionPublicKey: session.sessionPublicKey || "",
|
|
13113
|
+
permissions: options?.permissions ?? ["read", "transfer"],
|
|
13114
|
+
expiresInMs: options?.expiresInMs ?? 36e5,
|
|
13115
|
+
signature: session.signature
|
|
13116
|
+
})
|
|
13117
|
+
});
|
|
13118
|
+
if (!response.ok) {
|
|
13119
|
+
const data2 = await response.json().catch(() => ({ error: "Unknown error" }));
|
|
13120
|
+
throw new Error(data2.error || `Failed to create server session: ${response.status}`);
|
|
13121
|
+
}
|
|
13122
|
+
const data = await response.json();
|
|
13123
|
+
return data.session;
|
|
13124
|
+
}
|
|
13125
|
+
/**
|
|
13126
|
+
* Validate an existing server session token.
|
|
13127
|
+
* Returns the session details if valid, null if expired/revoked.
|
|
13128
|
+
*/
|
|
13129
|
+
async validateServerSession(sessionId) {
|
|
13130
|
+
const response = await fetch(`${this.config.relayerUrl}/session/${encodeURIComponent(sessionId)}`);
|
|
13131
|
+
if (!response.ok) {
|
|
13132
|
+
return null;
|
|
13133
|
+
}
|
|
13134
|
+
const data = await response.json();
|
|
13135
|
+
if (!data.valid) {
|
|
13136
|
+
return null;
|
|
13137
|
+
}
|
|
13138
|
+
return data.session;
|
|
13139
|
+
}
|
|
13140
|
+
/**
|
|
13141
|
+
* Revoke a server session token.
|
|
13142
|
+
*/
|
|
13143
|
+
async revokeServerSession(sessionId) {
|
|
13144
|
+
const response = await fetch(`${this.config.relayerUrl}/session/${encodeURIComponent(sessionId)}`, {
|
|
13145
|
+
method: "DELETE"
|
|
13146
|
+
});
|
|
13147
|
+
return response.ok;
|
|
13148
|
+
}
|
|
13149
|
+
/**
|
|
13150
|
+
* Full authentication flow: authenticate + create server session.
|
|
13151
|
+
* Automatically detects ROR support and falls back to auth portal.
|
|
13152
|
+
*/
|
|
13153
|
+
async authenticateAndCreateSession(options) {
|
|
13154
|
+
let session;
|
|
13155
|
+
if (await this.supportsRelatedOrigins()) {
|
|
13156
|
+
const result = await this.authenticate();
|
|
13157
|
+
session = {
|
|
13158
|
+
address: "",
|
|
13159
|
+
sessionPublicKey: "",
|
|
13160
|
+
expiresAt: Date.now() + (options?.expiresInMs ?? 36e5),
|
|
13161
|
+
signature: result.signature,
|
|
13162
|
+
credential: result.credential
|
|
13163
|
+
};
|
|
13164
|
+
} else {
|
|
13165
|
+
session = await this.connectWithVeridex();
|
|
13166
|
+
}
|
|
13167
|
+
const serverSession = await this.createServerSession(session, options);
|
|
13168
|
+
session.serverSessionId = serverSession.id;
|
|
13169
|
+
return { session, serverSession };
|
|
13170
|
+
}
|
|
12052
13171
|
};
|
|
12053
13172
|
function createCrossOriginAuth(config) {
|
|
12054
13173
|
return new CrossOriginAuth(config);
|
|
@@ -13102,6 +14221,72 @@ var EVMHubClientAdapter = class {
|
|
|
13102
14221
|
}
|
|
13103
14222
|
};
|
|
13104
14223
|
|
|
14224
|
+
// src/chains/stacks/StacksPostConditions.ts
|
|
14225
|
+
function buildStxWithdrawalPostConditions(contractPrincipal, amount) {
|
|
14226
|
+
return [
|
|
14227
|
+
{
|
|
14228
|
+
type: "stx",
|
|
14229
|
+
principal: contractPrincipal,
|
|
14230
|
+
comparison: "eq",
|
|
14231
|
+
amount
|
|
14232
|
+
}
|
|
14233
|
+
];
|
|
14234
|
+
}
|
|
14235
|
+
function buildStxDepositPostConditions(senderPrincipal, amount) {
|
|
14236
|
+
return [
|
|
14237
|
+
{
|
|
14238
|
+
type: "stx",
|
|
14239
|
+
principal: senderPrincipal,
|
|
14240
|
+
comparison: "eq",
|
|
14241
|
+
amount
|
|
14242
|
+
}
|
|
14243
|
+
];
|
|
14244
|
+
}
|
|
14245
|
+
function buildSbtcWithdrawalPostConditions(contractPrincipal, amount, sbtcContractAddress = "SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4", sbtcContractName = "sbtc-token") {
|
|
14246
|
+
return [
|
|
14247
|
+
{
|
|
14248
|
+
type: "ft",
|
|
14249
|
+
principal: contractPrincipal,
|
|
14250
|
+
comparison: "eq",
|
|
14251
|
+
amount,
|
|
14252
|
+
contractAddress: sbtcContractAddress,
|
|
14253
|
+
contractName: sbtcContractName,
|
|
14254
|
+
tokenName: "sbtc-token"
|
|
14255
|
+
}
|
|
14256
|
+
];
|
|
14257
|
+
}
|
|
14258
|
+
function buildExecutePostConditions(actionType, contractPrincipal, amount) {
|
|
14259
|
+
switch (actionType) {
|
|
14260
|
+
case 1:
|
|
14261
|
+
return buildStxWithdrawalPostConditions(contractPrincipal, amount);
|
|
14262
|
+
case 2:
|
|
14263
|
+
return buildSbtcWithdrawalPostConditions(contractPrincipal, amount);
|
|
14264
|
+
default:
|
|
14265
|
+
return [];
|
|
14266
|
+
}
|
|
14267
|
+
}
|
|
14268
|
+
function validatePostConditions(postConditions, expectedAmount) {
|
|
14269
|
+
if (postConditions.length === 0) {
|
|
14270
|
+
return {
|
|
14271
|
+
valid: false,
|
|
14272
|
+
error: "No post-conditions attached. Asset transfers require post-conditions for safety."
|
|
14273
|
+
};
|
|
14274
|
+
}
|
|
14275
|
+
const hasMatchingAmount = postConditions.some((pc) => {
|
|
14276
|
+
if (pc.type === "stx" || pc.type === "ft") {
|
|
14277
|
+
return pc.amount === expectedAmount && pc.comparison === "eq";
|
|
14278
|
+
}
|
|
14279
|
+
return false;
|
|
14280
|
+
});
|
|
14281
|
+
if (!hasMatchingAmount) {
|
|
14282
|
+
return {
|
|
14283
|
+
valid: false,
|
|
14284
|
+
error: `No post-condition matches expected amount ${expectedAmount}. Ensure exact-match post-conditions are attached.`
|
|
14285
|
+
};
|
|
14286
|
+
}
|
|
14287
|
+
return { valid: true };
|
|
14288
|
+
}
|
|
14289
|
+
|
|
13105
14290
|
// src/constants/errors.ts
|
|
13106
14291
|
var ERROR_RANGES = {
|
|
13107
14292
|
/** Core protocol errors (paused, unauthorized, limits, etc.) */
|
|
@@ -13352,6 +14537,7 @@ export {
|
|
|
13352
14537
|
ACTION_TYPES,
|
|
13353
14538
|
ARBITRUM_SEPOLIA_TOKENS,
|
|
13354
14539
|
AUTH_MESSAGE_TYPES,
|
|
14540
|
+
AptosClient,
|
|
13355
14541
|
BASE_SEPOLIA_TOKENS,
|
|
13356
14542
|
BalanceManager,
|
|
13357
14543
|
CHAIN_DISPLAY_INFO,
|
|
@@ -13364,10 +14550,12 @@ export {
|
|
|
13364
14550
|
CrossOriginAuth,
|
|
13365
14551
|
DEFAULT_AUTH_PORTAL_URL,
|
|
13366
14552
|
DEFAULT_REFRESH_BUFFER,
|
|
14553
|
+
DEFAULT_RELAYER_URL,
|
|
13367
14554
|
DEFAULT_SESSION_DURATION,
|
|
13368
14555
|
ERROR_MESSAGES,
|
|
13369
14556
|
ERROR_RANGES,
|
|
13370
14557
|
ETHEREUM_SEPOLIA_TOKENS,
|
|
14558
|
+
EVMClient,
|
|
13371
14559
|
EVMHubClientAdapter,
|
|
13372
14560
|
EVM_ZERO_ADDRESS,
|
|
13373
14561
|
GUARDIAN_CONFIG,
|
|
@@ -13386,9 +14574,14 @@ export {
|
|
|
13386
14574
|
QueryHubStateError,
|
|
13387
14575
|
QueryPortfolioError,
|
|
13388
14576
|
RelayerClient,
|
|
14577
|
+
STACKS_ACTION_TYPES,
|
|
13389
14578
|
SessionError,
|
|
13390
14579
|
SessionManager,
|
|
14580
|
+
SolanaClient,
|
|
13391
14581
|
SpendingLimitsManager,
|
|
14582
|
+
StacksClient,
|
|
14583
|
+
StarknetClient,
|
|
14584
|
+
SuiClient,
|
|
13392
14585
|
TESTNET_CHAINS,
|
|
13393
14586
|
TOKEN_REGISTRY,
|
|
13394
14587
|
TransactionParser,
|
|
@@ -13410,6 +14603,10 @@ export {
|
|
|
13410
14603
|
base64URLEncode,
|
|
13411
14604
|
buildChallenge,
|
|
13412
14605
|
buildGaslessChallenge,
|
|
14606
|
+
buildSbtcWithdrawalPostConditions,
|
|
14607
|
+
buildExecutePostConditions as buildStacksExecutePostConditions,
|
|
14608
|
+
buildStxDepositPostConditions,
|
|
14609
|
+
buildStxWithdrawalPostConditions,
|
|
13413
14610
|
calculatePercentage,
|
|
13414
14611
|
computeKeyHash,
|
|
13415
14612
|
computeSessionKeyHash,
|
|
@@ -13475,6 +14672,10 @@ export {
|
|
|
13475
14672
|
getExplorerUrl,
|
|
13476
14673
|
getHubChains,
|
|
13477
14674
|
getSequenceFromTxReceipt,
|
|
14675
|
+
getContractPrincipal as getStacksContractPrincipal,
|
|
14676
|
+
getStacksExplorerAddressUrl,
|
|
14677
|
+
getStacksExplorerTxUrl,
|
|
14678
|
+
getNetworkFromAddress as getStacksNetworkFromAddress,
|
|
13478
14679
|
getSuggestedAction,
|
|
13479
14680
|
getSupportedChainIds,
|
|
13480
14681
|
getSupportedChains,
|
|
@@ -13496,14 +14697,19 @@ export {
|
|
|
13496
14697
|
isQueryExecutionError,
|
|
13497
14698
|
isQueryParsingError,
|
|
13498
14699
|
isRetryableError,
|
|
14700
|
+
isContractPrincipal as isStacksContractPrincipal,
|
|
13499
14701
|
isValidBytes32,
|
|
13500
14702
|
isValidEvmAddress,
|
|
14703
|
+
isValidContractName as isValidStacksContractName,
|
|
14704
|
+
isValidStacksPrincipal,
|
|
14705
|
+
isValidStandardPrincipal as isValidStacksStandardPrincipal,
|
|
13501
14706
|
isValidWormholeChainId,
|
|
13502
14707
|
logTransactionSummary,
|
|
13503
14708
|
normalizeEmitterAddress,
|
|
13504
14709
|
padTo32Bytes,
|
|
13505
14710
|
parseAmount,
|
|
13506
14711
|
parseDERSignature,
|
|
14712
|
+
parseContractPrincipal as parseStacksContractPrincipal,
|
|
13507
14713
|
parseVAA,
|
|
13508
14714
|
parseVAABytes,
|
|
13509
14715
|
parseVeridexError,
|
|
@@ -13515,10 +14721,22 @@ export {
|
|
|
13515
14721
|
sendAuthResponse,
|
|
13516
14722
|
signWithSessionKey,
|
|
13517
14723
|
solanaAddressToBytes32,
|
|
14724
|
+
buildExecuteHash as stacksBuildExecuteHash,
|
|
14725
|
+
buildRegistrationHash as stacksBuildRegistrationHash,
|
|
14726
|
+
buildRevocationHash as stacksBuildRevocationHash,
|
|
14727
|
+
buildSessionRegistrationHash as stacksBuildSessionRegistrationHash,
|
|
14728
|
+
buildWithdrawalHash as stacksBuildWithdrawalHash,
|
|
14729
|
+
compressPublicKey as stacksCompressPublicKey,
|
|
14730
|
+
computeKeyHash2 as stacksComputeKeyHash,
|
|
14731
|
+
computeKeyHashFromCoords as stacksComputeKeyHashFromCoords,
|
|
14732
|
+
derToCompactSignature as stacksDerToCompactSignature,
|
|
14733
|
+
rsToCompactSignature as stacksRsToCompactSignature,
|
|
14734
|
+
supportsRelatedOrigins,
|
|
13518
14735
|
supportsRelayer,
|
|
13519
14736
|
trimTo20Bytes,
|
|
13520
14737
|
validateEmitter,
|
|
13521
14738
|
validateSessionConfig,
|
|
14739
|
+
validatePostConditions as validateStacksPostConditions,
|
|
13522
14740
|
verifySessionSignature,
|
|
13523
14741
|
waitForGuardianSignatures
|
|
13524
14742
|
};
|