@veridex/sdk 1.0.0-beta.15 → 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/dist/index.mjs CHANGED
@@ -546,13 +546,44 @@ function sleep(ms) {
546
546
  }
547
547
 
548
548
  // src/core/PasskeyManager.ts
549
+ var VERIDEX_RP_ID = "veridex.network";
550
+ function detectRpId(forceLocal) {
551
+ if (typeof window === "undefined") return "localhost";
552
+ const hostname = window.location.hostname;
553
+ if (hostname === "localhost" || hostname === "127.0.0.1" || /^\d+\.\d+\.\d+\.\d+$/.test(hostname)) {
554
+ return hostname;
555
+ }
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
+ }
577
+ }
578
+ return false;
579
+ }
549
580
  var PasskeyManager = class _PasskeyManager {
550
581
  config;
551
582
  credential = null;
552
583
  constructor(config = {}) {
553
584
  this.config = {
554
585
  rpName: config.rpName ?? "Veridex Protocol",
555
- rpId: config.rpId ?? (typeof window !== "undefined" ? window.location.hostname : "localhost"),
586
+ rpId: config.rpId ?? detectRpId(),
556
587
  timeout: config.timeout ?? 6e4,
557
588
  userVerification: config.userVerification ?? "required",
558
589
  authenticatorAttachment: config.authenticatorAttachment ?? "platform",
@@ -1690,9 +1721,10 @@ function getChainName(wormholeChainId) {
1690
1721
 
1691
1722
  // src/core/BalanceManager.ts
1692
1723
  var DEFAULT_RPC_URLS = {
1724
+ 10002: "https://ethereum-sepolia-rpc.publicnode.com",
1725
+ 10003: "https://sepolia-rollup.arbitrum.io/rpc",
1693
1726
  10004: "https://sepolia.base.org",
1694
- 10005: "https://sepolia.optimism.io",
1695
- 10003: "https://sepolia-rollup.arbitrum.io/rpc"
1727
+ 10005: "https://sepolia.optimism.io"
1696
1728
  };
1697
1729
  var TESTNET_TOKEN_PRICES = {
1698
1730
  ETH: 2500,
@@ -1927,9 +1959,10 @@ var DEFAULT_POLLING_INTERVAL = 2e3;
1927
1959
  var DEFAULT_REQUIRED_CONFIRMATIONS = 1;
1928
1960
  var DEFAULT_TIMEOUT = 3e5;
1929
1961
  var DEFAULT_RPC_URLS2 = {
1962
+ 10002: "https://ethereum-sepolia-rpc.publicnode.com",
1963
+ 10003: "https://sepolia-rollup.arbitrum.io/rpc",
1930
1964
  10004: "https://sepolia.base.org",
1931
- 10005: "https://sepolia.optimism.io",
1932
- 10003: "https://sepolia-rollup.arbitrum.io/rpc"
1965
+ 10005: "https://sepolia.optimism.io"
1933
1966
  };
1934
1967
  var TransactionTracker = class {
1935
1968
  config;
@@ -11042,160 +11075,1129 @@ var EVMClient = class {
11042
11075
  }
11043
11076
  };
11044
11077
 
11045
- // src/presets.ts
11046
- var CHAIN_NAMES = {
11047
- // EVM L2s (Hub-capable)
11048
- BASE: "base",
11049
- OPTIMISM: "optimism",
11050
- ARBITRUM: "arbitrum",
11051
- SCROLL: "scroll",
11052
- BLAST: "blast",
11053
- MANTLE: "mantle",
11054
- // EVM L1s
11055
- ETHEREUM: "ethereum",
11056
- POLYGON: "polygon",
11057
- BSC: "bsc",
11058
- AVALANCHE: "avalanche",
11059
- FANTOM: "fantom",
11060
- CELO: "celo",
11061
- MOONBEAM: "moonbeam",
11062
- // Non-EVM
11063
- SOLANA: "solana",
11064
- APTOS: "aptos",
11065
- SUI: "sui",
11066
- STARKNET: "starknet",
11067
- NEAR: "near",
11068
- SEI: "sei"
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
11069
11300
  };
11070
- var CHAIN_PRESETS = {
11071
- // ────────────────────────────────────────────────────────────────────────
11072
- // BASE - Primary Hub Chain
11073
- // ────────────────────────────────────────────────────────────────────────
11074
- base: {
11075
- displayName: "Base",
11076
- type: "evm",
11077
- canBeHub: true,
11078
- testnet: {
11079
- name: "Base Sepolia",
11080
- chainId: 84532,
11081
- wormholeChainId: 10004,
11082
- rpcUrl: "https://sepolia.base.org",
11083
- explorerUrl: "https://sepolia.basescan.org",
11084
- isEvm: true,
11085
- contracts: {
11086
- hub: "0x66D87dE68327f48A099c5B9bE97020Feab9a7c82",
11087
- vaultFactory: "0xCFaEb5652aa2Ee60b2229dC8895B4159749C7e53",
11088
- vaultImplementation: "0x0d13367C16c6f0B24eD275CC67C7D9f42878285c",
11089
- wormholeCoreBridge: "0x79A1027a6A159502049F10906D333EC57E95F083",
11090
- tokenBridge: "0x86F55A04690fd7815A3D802bD587e83eA888B239"
11091
- }
11092
- },
11093
- mainnet: {
11094
- name: "Base",
11095
- chainId: 8453,
11096
- wormholeChainId: 30,
11097
- rpcUrl: "https://mainnet.base.org",
11098
- explorerUrl: "https://basescan.org",
11099
- isEvm: true,
11100
- contracts: {
11101
- // TODO: Deploy mainnet contracts
11102
- wormholeCoreBridge: "0xbebdb6C8ddC678FfA9f8748f85C815C556Dd8ac6",
11103
- tokenBridge: "0x8d2de8d2f73F1F4cAB472AC9A881C9b123C79627"
11104
- }
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;
11105
11321
  }
11106
- },
11107
- // ────────────────────────────────────────────────────────────────────────
11108
- // OPTIMISM - Secondary Hub / Spoke
11109
- // ────────────────────────────────────────────────────────────────────────
11110
- optimism: {
11111
- displayName: "Optimism",
11112
- type: "evm",
11113
- canBeHub: true,
11114
- testnet: {
11115
- name: "Optimism Sepolia",
11116
- chainId: 11155420,
11117
- wormholeChainId: 10005,
11118
- rpcUrl: "https://sepolia.optimism.io",
11119
- explorerUrl: "https://sepolia-optimism.etherscan.io",
11120
- isEvm: true,
11121
- contracts: {
11122
- vaultFactory: "0xA5653d54079ABeCe780F8d9597B2bc4B09fe464A",
11123
- vaultImplementation: "0x8099b1406485d2255ff89Ce5Ea18520802AFC150",
11124
- wormholeCoreBridge: "0x31377888146f3253211EFEf5c676D41ECe7D58Fe",
11125
- tokenBridge: "0x99737Ec4B815d816c49A385943baf0380e75c0Ac"
11126
- }
11127
- },
11128
- mainnet: {
11129
- name: "Optimism",
11130
- chainId: 10,
11131
- wormholeChainId: 24,
11132
- rpcUrl: "https://mainnet.optimism.io",
11133
- explorerUrl: "https://optimistic.etherscan.io",
11134
- isEvm: true,
11135
- contracts: {
11136
- wormholeCoreBridge: "0xEe91C335eab126dF5fDB3797EA9d6aD93aeC9722",
11137
- tokenBridge: "0x1D68124e65faFC907325e3EDbF8c4d84499DAa8b"
11138
- }
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;
11139
11327
  }
11140
- },
11141
- // ────────────────────────────────────────────────────────────────────────
11142
- // ARBITRUM
11143
- // ────────────────────────────────────────────────────────────────────────
11144
- arbitrum: {
11145
- displayName: "Arbitrum",
11146
- type: "evm",
11147
- canBeHub: true,
11148
- testnet: {
11149
- name: "Arbitrum Sepolia",
11150
- chainId: 421614,
11151
- wormholeChainId: 10003,
11152
- rpcUrl: "https://sepolia-rollup.arbitrum.io/rpc",
11153
- explorerUrl: "https://sepolia.arbiscan.io",
11154
- isEvm: true,
11155
- contracts: {
11156
- vaultFactory: "0xd36D3D5DB59d78f1E33813490F72DABC15C9B07c",
11157
- vaultImplementation: "0xB10ACf39eBF17fc33F722cBD955b7aeCB0611bc4",
11158
- wormholeCoreBridge: "0x6b9C8671cdDC8dEab9c719bB87cBd3e782bA6a35",
11159
- tokenBridge: "0xC7A204bDBFe983FCD8d8E61D02b475D4073fF97e"
11160
- }
11161
- },
11162
- mainnet: {
11163
- name: "Arbitrum",
11164
- chainId: 42161,
11165
- wormholeChainId: 23,
11166
- rpcUrl: "https://arb1.arbitrum.io/rpc",
11167
- explorerUrl: "https://arbiscan.io",
11168
- isEvm: true,
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,
11169
11365
  contracts: {
11170
- wormholeCoreBridge: "0xa5f208e072434bC67592E4C49C1B991BA79BCA46",
11171
- tokenBridge: "0x0b2402144Bb366A632D14B83F244D2e0e21bD39c"
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
11172
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;
11173
11389
  }
11174
- },
11175
- // ────────────────────────────────────────────────────────────────────────
11176
- // ETHEREUM
11177
- // ────────────────────────────────────────────────────────────────────────
11178
- ethereum: {
11179
- displayName: "Ethereum",
11180
- type: "evm",
11181
- canBeHub: false,
11182
- testnet: {
11183
- name: "Sepolia",
11184
- chainId: 11155111,
11185
- wormholeChainId: 10002,
11186
- rpcUrl: "https://ethereum-sepolia-rpc.publicnode.com",
11187
- explorerUrl: "https://sepolia.etherscan.io",
11188
- isEvm: true,
11189
- contracts: {
11190
- vaultFactory: "0x07F608AFf6d63b68029488b726d895c4Bb593038",
11191
- vaultImplementation: "0xD66153fccFB6731fB6c4944FbD607ba86A76a1f6",
11192
- wormholeCoreBridge: "0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78",
11193
- 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);
11194
11399
  }
11195
- },
11196
- mainnet: {
11197
- name: "Ethereum",
11198
- chainId: 1,
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,
11199
12201
  wormholeChainId: 2,
11200
12202
  rpcUrl: "https://eth.llamarpc.com",
11201
12203
  explorerUrl: "https://etherscan.io",
@@ -11659,6 +12661,49 @@ var CHAIN_PRESETS = {
11659
12661
  }
11660
12662
  },
11661
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
+ // ────────────────────────────────────────────────────────────────────────
11662
12707
  // NEAR
11663
12708
  // ────────────────────────────────────────────────────────────────────────
11664
12709
  near: {
@@ -11781,6 +12826,13 @@ function createChainClient(chain, network, customRpcUrl) {
11781
12826
  wormholeChainId: config.wormholeChainId,
11782
12827
  network: network === "testnet" ? "sepolia" : "mainnet"
11783
12828
  });
12829
+ case "stacks":
12830
+ return new StacksClient({
12831
+ rpcUrl,
12832
+ spokeContractAddress: config.contracts.hub || void 0,
12833
+ wormholeChainId: config.wormholeChainId,
12834
+ network
12835
+ });
11784
12836
  case "near":
11785
12837
  case "cosmos":
11786
12838
  throw new Error(`Chain type "${preset.type}" is not yet supported. Coming soon!`);
@@ -11835,6 +12887,329 @@ function createSessionSDK(chain = "base", config = {}) {
11835
12887
  return createSDK(chain, config);
11836
12888
  }
11837
12889
 
12890
+ // src/core/CrossOriginAuth.ts
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";
12893
+ var AUTH_MESSAGE_TYPES = {
12894
+ AUTH_REQUEST: "VERIDEX_AUTH_REQUEST",
12895
+ AUTH_RESPONSE: "VERIDEX_AUTH_RESPONSE",
12896
+ AUTH_ERROR: "VERIDEX_AUTH_ERROR"
12897
+ };
12898
+ var CrossOriginAuth = class {
12899
+ config;
12900
+ passkeyManager = null;
12901
+ constructor(config = {}) {
12902
+ this.config = {
12903
+ rpId: config.rpId ?? VERIDEX_RP_ID,
12904
+ authPortalUrl: config.authPortalUrl ?? DEFAULT_AUTH_PORTAL_URL,
12905
+ relayerUrl: config.relayerUrl ?? DEFAULT_RELAYER_URL,
12906
+ mode: config.mode ?? "popup",
12907
+ popupFeatures: config.popupFeatures ?? "width=500,height=600,left=100,top=100",
12908
+ timeout: config.timeout ?? 12e4,
12909
+ // 2 minutes
12910
+ redirectUri: config.redirectUri ?? (typeof window !== "undefined" ? window.location.href : "")
12911
+ };
12912
+ }
12913
+ // ========================================================================
12914
+ // Browser Capability Detection
12915
+ // ========================================================================
12916
+ /**
12917
+ * Check if the browser supports Related Origin Requests.
12918
+ * This is a WebAuthn Level 3 feature that allows using passkeys across different domains.
12919
+ */
12920
+ async supportsRelatedOrigins() {
12921
+ if (typeof window === "undefined" || !window.PublicKeyCredential) {
12922
+ return false;
12923
+ }
12924
+ if ("getClientCapabilities" in PublicKeyCredential) {
12925
+ try {
12926
+ const getCapabilities = PublicKeyCredential.getClientCapabilities;
12927
+ const capabilities = await getCapabilities();
12928
+ return capabilities?.relatedOrigins === true;
12929
+ } catch {
12930
+ return false;
12931
+ }
12932
+ }
12933
+ return false;
12934
+ }
12935
+ /**
12936
+ * Check if WebAuthn is supported at all.
12937
+ */
12938
+ isSupported() {
12939
+ return PasskeyManager.isSupported();
12940
+ }
12941
+ // ========================================================================
12942
+ // Related Origin Requests (Direct Method)
12943
+ // ========================================================================
12944
+ /**
12945
+ * Authenticate using Related Origin Requests.
12946
+ * This allows using a passkey registered at veridex.network from any origin
12947
+ * listed in the /.well-known/webauthn file.
12948
+ *
12949
+ * @throws If browser doesn't support Related Origin Requests
12950
+ * @throws If the current origin isn't listed in veridex.network's well-known file
12951
+ */
12952
+ async authenticate(challenge) {
12953
+ const manager = new PasskeyManager({
12954
+ rpId: this.config.rpId,
12955
+ rpName: "Veridex Protocol"
12956
+ });
12957
+ return manager.authenticate(challenge);
12958
+ }
12959
+ /**
12960
+ * Register a new passkey with veridex.network as the RP.
12961
+ * This should only be called from veridex.network origins.
12962
+ */
12963
+ async register(username, displayName) {
12964
+ const manager = new PasskeyManager({
12965
+ rpId: this.config.rpId,
12966
+ rpName: "Veridex Protocol"
12967
+ });
12968
+ return manager.register(username, displayName);
12969
+ }
12970
+ // ========================================================================
12971
+ // Auth Portal Flow (Popup/Redirect)
12972
+ // ========================================================================
12973
+ /**
12974
+ * Authenticate via the Veridex Auth Portal.
12975
+ * Opens a popup or redirects to auth.veridex.network where the user
12976
+ * signs with their passkey, then returns a session to the calling app.
12977
+ */
12978
+ async connectWithVeridex() {
12979
+ if (this.config.mode === "popup") {
12980
+ return this.authenticateViaPopup();
12981
+ } else {
12982
+ return this.initiateRedirectAuth();
12983
+ }
12984
+ }
12985
+ /**
12986
+ * Popup-based authentication flow.
12987
+ */
12988
+ async authenticateViaPopup() {
12989
+ return new Promise((resolve, reject) => {
12990
+ const state = this.generateState();
12991
+ const authUrl = new URL("/auth", this.config.authPortalUrl);
12992
+ authUrl.searchParams.set("state", state);
12993
+ authUrl.searchParams.set("origin", window.location.origin);
12994
+ authUrl.searchParams.set("callback", "postMessage");
12995
+ const popup = window.open(
12996
+ authUrl.toString(),
12997
+ "veridex-auth",
12998
+ this.config.popupFeatures
12999
+ );
13000
+ if (!popup) {
13001
+ reject(new Error("Failed to open auth popup. Please allow popups for this site."));
13002
+ return;
13003
+ }
13004
+ const timeoutId = setTimeout(() => {
13005
+ popup.close();
13006
+ window.removeEventListener("message", messageHandler);
13007
+ reject(new Error("Authentication timed out"));
13008
+ }, this.config.timeout);
13009
+ const messageHandler = (event) => {
13010
+ if (!event.origin.includes("veridex.network")) {
13011
+ return;
13012
+ }
13013
+ const { type, payload } = event.data;
13014
+ if (type === AUTH_MESSAGE_TYPES.AUTH_RESPONSE) {
13015
+ clearTimeout(timeoutId);
13016
+ window.removeEventListener("message", messageHandler);
13017
+ popup.close();
13018
+ resolve(payload);
13019
+ } else if (type === AUTH_MESSAGE_TYPES.AUTH_ERROR) {
13020
+ clearTimeout(timeoutId);
13021
+ window.removeEventListener("message", messageHandler);
13022
+ popup.close();
13023
+ const error = payload;
13024
+ reject(new Error(error.error));
13025
+ }
13026
+ };
13027
+ window.addEventListener("message", messageHandler);
13028
+ });
13029
+ }
13030
+ /**
13031
+ * Redirect-based authentication flow.
13032
+ * Stores state in sessionStorage and redirects to auth portal.
13033
+ */
13034
+ async initiateRedirectAuth() {
13035
+ const state = this.generateState();
13036
+ sessionStorage.setItem("veridex_auth_state", state);
13037
+ sessionStorage.setItem("veridex_auth_redirect", this.config.redirectUri);
13038
+ const authUrl = new URL("/auth", this.config.authPortalUrl);
13039
+ authUrl.searchParams.set("state", state);
13040
+ authUrl.searchParams.set("redirect_uri", this.config.redirectUri);
13041
+ authUrl.searchParams.set("origin", window.location.origin);
13042
+ window.location.href = authUrl.toString();
13043
+ return new Promise(() => {
13044
+ });
13045
+ }
13046
+ /**
13047
+ * Complete redirect-based authentication.
13048
+ * Call this on your callback page to extract the session from URL params.
13049
+ */
13050
+ completeRedirectAuth() {
13051
+ const params = new URLSearchParams(window.location.search);
13052
+ const session = params.get("session");
13053
+ const state = params.get("state");
13054
+ const error = params.get("error");
13055
+ const storedState = sessionStorage.getItem("veridex_auth_state");
13056
+ if (state !== storedState) {
13057
+ throw new Error("Invalid auth state - possible CSRF attack");
13058
+ }
13059
+ sessionStorage.removeItem("veridex_auth_state");
13060
+ sessionStorage.removeItem("veridex_auth_redirect");
13061
+ window.history.replaceState({}, "", window.location.pathname);
13062
+ if (error) {
13063
+ throw new Error(error);
13064
+ }
13065
+ if (!session) {
13066
+ return null;
13067
+ }
13068
+ return JSON.parse(atob(session));
13069
+ }
13070
+ // ========================================================================
13071
+ // Utility Methods
13072
+ // ========================================================================
13073
+ /**
13074
+ * Generate a random state string for CSRF protection.
13075
+ */
13076
+ generateState() {
13077
+ const array = new Uint8Array(32);
13078
+ crypto.getRandomValues(array);
13079
+ return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
13080
+ }
13081
+ /**
13082
+ * Get the RP ID being used.
13083
+ */
13084
+ getRpId() {
13085
+ return this.config.rpId;
13086
+ }
13087
+ /**
13088
+ * Get the auth portal URL.
13089
+ */
13090
+ getAuthPortalUrl() {
13091
+ return this.config.authPortalUrl;
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
+ }
13171
+ };
13172
+ function createCrossOriginAuth(config) {
13173
+ return new CrossOriginAuth(config);
13174
+ }
13175
+ function sendAuthResponse(session, targetOrigin) {
13176
+ if (window.opener) {
13177
+ window.opener.postMessage({
13178
+ type: AUTH_MESSAGE_TYPES.AUTH_RESPONSE,
13179
+ payload: session,
13180
+ origin: window.location.origin
13181
+ }, targetOrigin);
13182
+ } else {
13183
+ const redirectUri = new URLSearchParams(window.location.search).get("redirect_uri");
13184
+ const state = new URLSearchParams(window.location.search).get("state");
13185
+ if (redirectUri) {
13186
+ const url = new URL(redirectUri);
13187
+ url.searchParams.set("session", btoa(JSON.stringify(session)));
13188
+ url.searchParams.set("state", state || "");
13189
+ window.location.href = url.toString();
13190
+ }
13191
+ }
13192
+ }
13193
+ function sendAuthError(error, code, targetOrigin) {
13194
+ if (window.opener) {
13195
+ window.opener.postMessage({
13196
+ type: AUTH_MESSAGE_TYPES.AUTH_ERROR,
13197
+ payload: { error, code },
13198
+ origin: window.location.origin
13199
+ }, targetOrigin);
13200
+ } else {
13201
+ const redirectUri = new URLSearchParams(window.location.search).get("redirect_uri");
13202
+ const state = new URLSearchParams(window.location.search).get("state");
13203
+ if (redirectUri) {
13204
+ const url = new URL(redirectUri);
13205
+ url.searchParams.set("error", error);
13206
+ url.searchParams.set("error_code", code);
13207
+ url.searchParams.set("state", state || "");
13208
+ window.location.href = url.toString();
13209
+ }
13210
+ }
13211
+ }
13212
+
11838
13213
  // src/sessions/index.ts
11839
13214
  import { ethers as ethers19 } from "ethers";
11840
13215
 
@@ -12846,6 +14221,72 @@ var EVMHubClientAdapter = class {
12846
14221
  }
12847
14222
  };
12848
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
+
12849
14290
  // src/constants/errors.ts
12850
14291
  var ERROR_RANGES = {
12851
14292
  /** Core protocol errors (paused, unauthorized, limits, etc.) */
@@ -13095,6 +14536,8 @@ export {
13095
14536
  ACTION_TRANSFER,
13096
14537
  ACTION_TYPES,
13097
14538
  ARBITRUM_SEPOLIA_TOKENS,
14539
+ AUTH_MESSAGE_TYPES,
14540
+ AptosClient,
13098
14541
  BASE_SEPOLIA_TOKENS,
13099
14542
  BalanceManager,
13100
14543
  CHAIN_DISPLAY_INFO,
@@ -13104,11 +14547,15 @@ export {
13104
14547
  CONSISTENCY_LEVELS,
13105
14548
  ChainDetector,
13106
14549
  CrossChainManager,
14550
+ CrossOriginAuth,
14551
+ DEFAULT_AUTH_PORTAL_URL,
13107
14552
  DEFAULT_REFRESH_BUFFER,
14553
+ DEFAULT_RELAYER_URL,
13108
14554
  DEFAULT_SESSION_DURATION,
13109
14555
  ERROR_MESSAGES,
13110
14556
  ERROR_RANGES,
13111
14557
  ETHEREUM_SEPOLIA_TOKENS,
14558
+ EVMClient,
13112
14559
  EVMHubClientAdapter,
13113
14560
  EVM_ZERO_ADDRESS,
13114
14561
  GUARDIAN_CONFIG,
@@ -13127,9 +14574,14 @@ export {
13127
14574
  QueryHubStateError,
13128
14575
  QueryPortfolioError,
13129
14576
  RelayerClient,
14577
+ STACKS_ACTION_TYPES,
13130
14578
  SessionError,
13131
14579
  SessionManager,
14580
+ SolanaClient,
13132
14581
  SpendingLimitsManager,
14582
+ StacksClient,
14583
+ StarknetClient,
14584
+ SuiClient,
13133
14585
  TESTNET_CHAINS,
13134
14586
  TOKEN_REGISTRY,
13135
14587
  TransactionParser,
@@ -13137,6 +14589,7 @@ export {
13137
14589
  VAULT_ABI,
13138
14590
  VAULT_FACTORY_ABI,
13139
14591
  VERIDEX_ERRORS,
14592
+ VERIDEX_RP_ID,
13140
14593
  VeridexSDK,
13141
14594
  WORMHOLE_API,
13142
14595
  WORMHOLE_CHAIN_IDS,
@@ -13150,11 +14603,16 @@ export {
13150
14603
  base64URLEncode,
13151
14604
  buildChallenge,
13152
14605
  buildGaslessChallenge,
14606
+ buildSbtcWithdrawalPostConditions,
14607
+ buildExecutePostConditions as buildStacksExecutePostConditions,
14608
+ buildStxDepositPostConditions,
14609
+ buildStxWithdrawalPostConditions,
13153
14610
  calculatePercentage,
13154
14611
  computeKeyHash,
13155
14612
  computeSessionKeyHash,
13156
14613
  createAuditEntry,
13157
14614
  createChainDetector,
14615
+ createCrossOriginAuth,
13158
14616
  createGasSponsor,
13159
14617
  createGaslessMessageHash,
13160
14618
  createHubSDK,
@@ -13175,6 +14633,7 @@ export {
13175
14633
  decrypt,
13176
14634
  VeridexSDK as default,
13177
14635
  deriveEncryptionKey,
14636
+ detectRpId,
13178
14637
  emitterToEvmAddress,
13179
14638
  encodeAptosTransferAction,
13180
14639
  encodeBridgeAction,
@@ -13213,6 +14672,10 @@ export {
13213
14672
  getExplorerUrl,
13214
14673
  getHubChains,
13215
14674
  getSequenceFromTxReceipt,
14675
+ getContractPrincipal as getStacksContractPrincipal,
14676
+ getStacksExplorerAddressUrl,
14677
+ getStacksExplorerTxUrl,
14678
+ getNetworkFromAddress as getStacksNetworkFromAddress,
13216
14679
  getSuggestedAction,
13217
14680
  getSupportedChainIds,
13218
14681
  getSupportedChains,
@@ -13234,14 +14697,19 @@ export {
13234
14697
  isQueryExecutionError,
13235
14698
  isQueryParsingError,
13236
14699
  isRetryableError,
14700
+ isContractPrincipal as isStacksContractPrincipal,
13237
14701
  isValidBytes32,
13238
14702
  isValidEvmAddress,
14703
+ isValidContractName as isValidStacksContractName,
14704
+ isValidStacksPrincipal,
14705
+ isValidStandardPrincipal as isValidStacksStandardPrincipal,
13239
14706
  isValidWormholeChainId,
13240
14707
  logTransactionSummary,
13241
14708
  normalizeEmitterAddress,
13242
14709
  padTo32Bytes,
13243
14710
  parseAmount,
13244
14711
  parseDERSignature,
14712
+ parseContractPrincipal as parseStacksContractPrincipal,
13245
14713
  parseVAA,
13246
14714
  parseVAABytes,
13247
14715
  parseVeridexError,
@@ -13249,12 +14717,26 @@ export {
13249
14717
  queryHubState,
13250
14718
  queryPortfolio,
13251
14719
  retryWithBackoff,
14720
+ sendAuthError,
14721
+ sendAuthResponse,
13252
14722
  signWithSessionKey,
13253
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,
13254
14735
  supportsRelayer,
13255
14736
  trimTo20Bytes,
13256
14737
  validateEmitter,
13257
14738
  validateSessionConfig,
14739
+ validatePostConditions as validateStacksPostConditions,
13258
14740
  verifySessionSignature,
13259
14741
  waitForGuardianSignatures
13260
14742
  };