@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.js CHANGED
@@ -36,6 +36,8 @@ __export(src_exports, {
36
36
  ACTION_TRANSFER: () => ACTION_TRANSFER,
37
37
  ACTION_TYPES: () => ACTION_TYPES,
38
38
  ARBITRUM_SEPOLIA_TOKENS: () => ARBITRUM_SEPOLIA_TOKENS,
39
+ AUTH_MESSAGE_TYPES: () => AUTH_MESSAGE_TYPES,
40
+ AptosClient: () => AptosClient,
39
41
  BASE_SEPOLIA_TOKENS: () => BASE_SEPOLIA_TOKENS,
40
42
  BalanceManager: () => BalanceManager,
41
43
  CHAIN_DISPLAY_INFO: () => CHAIN_DISPLAY_INFO,
@@ -45,11 +47,15 @@ __export(src_exports, {
45
47
  CONSISTENCY_LEVELS: () => CONSISTENCY_LEVELS,
46
48
  ChainDetector: () => ChainDetector,
47
49
  CrossChainManager: () => CrossChainManager,
50
+ CrossOriginAuth: () => CrossOriginAuth,
51
+ DEFAULT_AUTH_PORTAL_URL: () => DEFAULT_AUTH_PORTAL_URL,
48
52
  DEFAULT_REFRESH_BUFFER: () => DEFAULT_REFRESH_BUFFER,
53
+ DEFAULT_RELAYER_URL: () => DEFAULT_RELAYER_URL,
49
54
  DEFAULT_SESSION_DURATION: () => DEFAULT_SESSION_DURATION,
50
55
  ERROR_MESSAGES: () => ERROR_MESSAGES,
51
56
  ERROR_RANGES: () => ERROR_RANGES,
52
57
  ETHEREUM_SEPOLIA_TOKENS: () => ETHEREUM_SEPOLIA_TOKENS,
58
+ EVMClient: () => EVMClient,
53
59
  EVMHubClientAdapter: () => EVMHubClientAdapter,
54
60
  EVM_ZERO_ADDRESS: () => EVM_ZERO_ADDRESS,
55
61
  GUARDIAN_CONFIG: () => GUARDIAN_CONFIG,
@@ -68,9 +74,14 @@ __export(src_exports, {
68
74
  QueryHubStateError: () => QueryHubStateError,
69
75
  QueryPortfolioError: () => QueryPortfolioError,
70
76
  RelayerClient: () => RelayerClient,
77
+ STACKS_ACTION_TYPES: () => STACKS_ACTION_TYPES,
71
78
  SessionError: () => SessionError,
72
79
  SessionManager: () => SessionManager,
80
+ SolanaClient: () => SolanaClient,
73
81
  SpendingLimitsManager: () => SpendingLimitsManager,
82
+ StacksClient: () => StacksClient,
83
+ StarknetClient: () => StarknetClient,
84
+ SuiClient: () => SuiClient,
74
85
  TESTNET_CHAINS: () => TESTNET_CHAINS,
75
86
  TOKEN_REGISTRY: () => TOKEN_REGISTRY,
76
87
  TransactionParser: () => TransactionParser,
@@ -78,6 +89,7 @@ __export(src_exports, {
78
89
  VAULT_ABI: () => VAULT_ABI,
79
90
  VAULT_FACTORY_ABI: () => VAULT_FACTORY_ABI,
80
91
  VERIDEX_ERRORS: () => VERIDEX_ERRORS,
92
+ VERIDEX_RP_ID: () => VERIDEX_RP_ID,
81
93
  VeridexSDK: () => VeridexSDK,
82
94
  WORMHOLE_API: () => WORMHOLE_API,
83
95
  WORMHOLE_CHAIN_IDS: () => WORMHOLE_CHAIN_IDS,
@@ -91,11 +103,16 @@ __export(src_exports, {
91
103
  base64URLEncode: () => base64URLEncode,
92
104
  buildChallenge: () => buildChallenge,
93
105
  buildGaslessChallenge: () => buildGaslessChallenge,
106
+ buildSbtcWithdrawalPostConditions: () => buildSbtcWithdrawalPostConditions,
107
+ buildStacksExecutePostConditions: () => buildExecutePostConditions,
108
+ buildStxDepositPostConditions: () => buildStxDepositPostConditions,
109
+ buildStxWithdrawalPostConditions: () => buildStxWithdrawalPostConditions,
94
110
  calculatePercentage: () => calculatePercentage,
95
111
  computeKeyHash: () => computeKeyHash,
96
112
  computeSessionKeyHash: () => computeSessionKeyHash,
97
113
  createAuditEntry: () => createAuditEntry,
98
114
  createChainDetector: () => createChainDetector,
115
+ createCrossOriginAuth: () => createCrossOriginAuth,
99
116
  createGasSponsor: () => createGasSponsor,
100
117
  createGaslessMessageHash: () => createGaslessMessageHash,
101
118
  createHubSDK: () => createHubSDK,
@@ -116,6 +133,7 @@ __export(src_exports, {
116
133
  decrypt: () => decrypt,
117
134
  default: () => VeridexSDK,
118
135
  deriveEncryptionKey: () => deriveEncryptionKey,
136
+ detectRpId: () => detectRpId,
119
137
  emitterToEvmAddress: () => emitterToEvmAddress,
120
138
  encodeAptosTransferAction: () => encodeAptosTransferAction,
121
139
  encodeBridgeAction: () => encodeBridgeAction,
@@ -154,6 +172,10 @@ __export(src_exports, {
154
172
  getExplorerUrl: () => getExplorerUrl,
155
173
  getHubChains: () => getHubChains,
156
174
  getSequenceFromTxReceipt: () => getSequenceFromTxReceipt,
175
+ getStacksContractPrincipal: () => getContractPrincipal,
176
+ getStacksExplorerAddressUrl: () => getStacksExplorerAddressUrl,
177
+ getStacksExplorerTxUrl: () => getStacksExplorerTxUrl,
178
+ getStacksNetworkFromAddress: () => getNetworkFromAddress,
157
179
  getSuggestedAction: () => getSuggestedAction,
158
180
  getSupportedChainIds: () => getSupportedChainIds,
159
181
  getSupportedChains: () => getSupportedChains,
@@ -175,14 +197,19 @@ __export(src_exports, {
175
197
  isQueryExecutionError: () => isQueryExecutionError,
176
198
  isQueryParsingError: () => isQueryParsingError,
177
199
  isRetryableError: () => isRetryableError,
200
+ isStacksContractPrincipal: () => isContractPrincipal,
178
201
  isValidBytes32: () => isValidBytes32,
179
202
  isValidEvmAddress: () => isValidEvmAddress,
203
+ isValidStacksContractName: () => isValidContractName,
204
+ isValidStacksPrincipal: () => isValidStacksPrincipal,
205
+ isValidStacksStandardPrincipal: () => isValidStandardPrincipal,
180
206
  isValidWormholeChainId: () => isValidWormholeChainId,
181
207
  logTransactionSummary: () => logTransactionSummary,
182
208
  normalizeEmitterAddress: () => normalizeEmitterAddress,
183
209
  padTo32Bytes: () => padTo32Bytes,
184
210
  parseAmount: () => parseAmount,
185
211
  parseDERSignature: () => parseDERSignature,
212
+ parseStacksContractPrincipal: () => parseContractPrincipal,
186
213
  parseVAA: () => parseVAA,
187
214
  parseVAABytes: () => parseVAABytes,
188
215
  parseVeridexError: () => parseVeridexError,
@@ -190,12 +217,26 @@ __export(src_exports, {
190
217
  queryHubState: () => queryHubState,
191
218
  queryPortfolio: () => queryPortfolio,
192
219
  retryWithBackoff: () => retryWithBackoff,
220
+ sendAuthError: () => sendAuthError,
221
+ sendAuthResponse: () => sendAuthResponse,
193
222
  signWithSessionKey: () => signWithSessionKey,
194
223
  solanaAddressToBytes32: () => solanaAddressToBytes32,
224
+ stacksBuildExecuteHash: () => buildExecuteHash,
225
+ stacksBuildRegistrationHash: () => buildRegistrationHash,
226
+ stacksBuildRevocationHash: () => buildRevocationHash,
227
+ stacksBuildSessionRegistrationHash: () => buildSessionRegistrationHash,
228
+ stacksBuildWithdrawalHash: () => buildWithdrawalHash,
229
+ stacksCompressPublicKey: () => compressPublicKey,
230
+ stacksComputeKeyHash: () => computeKeyHash2,
231
+ stacksComputeKeyHashFromCoords: () => computeKeyHashFromCoords,
232
+ stacksDerToCompactSignature: () => derToCompactSignature,
233
+ stacksRsToCompactSignature: () => rsToCompactSignature,
234
+ supportsRelatedOrigins: () => supportsRelatedOrigins,
195
235
  supportsRelayer: () => supportsRelayer,
196
236
  trimTo20Bytes: () => trimTo20Bytes,
197
237
  validateEmitter: () => validateEmitter,
198
238
  validateSessionConfig: () => validateSessionConfig,
239
+ validateStacksPostConditions: () => validatePostConditions,
199
240
  verifySessionSignature: () => verifySessionSignature,
200
241
  waitForGuardianSignatures: () => waitForGuardianSignatures
201
242
  });
@@ -745,13 +786,44 @@ function sleep(ms) {
745
786
  }
746
787
 
747
788
  // src/core/PasskeyManager.ts
789
+ var VERIDEX_RP_ID = "veridex.network";
790
+ function detectRpId(forceLocal) {
791
+ if (typeof window === "undefined") return "localhost";
792
+ const hostname = window.location.hostname;
793
+ if (hostname === "localhost" || hostname === "127.0.0.1" || /^\d+\.\d+\.\d+\.\d+$/.test(hostname)) {
794
+ return hostname;
795
+ }
796
+ if (forceLocal) {
797
+ const parts = hostname.split(".");
798
+ if (parts.length <= 2) {
799
+ return hostname;
800
+ }
801
+ return parts.slice(-2).join(".");
802
+ }
803
+ return VERIDEX_RP_ID;
804
+ }
805
+ async function supportsRelatedOrigins() {
806
+ if (typeof window === "undefined" || !window.PublicKeyCredential) {
807
+ return false;
808
+ }
809
+ if ("getClientCapabilities" in PublicKeyCredential) {
810
+ try {
811
+ const getCapabilities = PublicKeyCredential.getClientCapabilities;
812
+ const capabilities = await getCapabilities();
813
+ return capabilities?.relatedOrigins === true;
814
+ } catch {
815
+ return false;
816
+ }
817
+ }
818
+ return false;
819
+ }
748
820
  var PasskeyManager = class _PasskeyManager {
749
821
  config;
750
822
  credential = null;
751
823
  constructor(config = {}) {
752
824
  this.config = {
753
825
  rpName: config.rpName ?? "Veridex Protocol",
754
- rpId: config.rpId ?? (typeof window !== "undefined" ? window.location.hostname : "localhost"),
826
+ rpId: config.rpId ?? detectRpId(),
755
827
  timeout: config.timeout ?? 6e4,
756
828
  userVerification: config.userVerification ?? "required",
757
829
  authenticatorAttachment: config.authenticatorAttachment ?? "platform",
@@ -1889,9 +1961,10 @@ function getChainName(wormholeChainId) {
1889
1961
 
1890
1962
  // src/core/BalanceManager.ts
1891
1963
  var DEFAULT_RPC_URLS = {
1964
+ 10002: "https://ethereum-sepolia-rpc.publicnode.com",
1965
+ 10003: "https://sepolia-rollup.arbitrum.io/rpc",
1892
1966
  10004: "https://sepolia.base.org",
1893
- 10005: "https://sepolia.optimism.io",
1894
- 10003: "https://sepolia-rollup.arbitrum.io/rpc"
1967
+ 10005: "https://sepolia.optimism.io"
1895
1968
  };
1896
1969
  var TESTNET_TOKEN_PRICES = {
1897
1970
  ETH: 2500,
@@ -2126,9 +2199,10 @@ var DEFAULT_POLLING_INTERVAL = 2e3;
2126
2199
  var DEFAULT_REQUIRED_CONFIRMATIONS = 1;
2127
2200
  var DEFAULT_TIMEOUT = 3e5;
2128
2201
  var DEFAULT_RPC_URLS2 = {
2202
+ 10002: "https://ethereum-sepolia-rpc.publicnode.com",
2203
+ 10003: "https://sepolia-rollup.arbitrum.io/rpc",
2129
2204
  10004: "https://sepolia.base.org",
2130
- 10005: "https://sepolia.optimism.io",
2131
- 10003: "https://sepolia-rollup.arbitrum.io/rpc"
2205
+ 10005: "https://sepolia.optimism.io"
2132
2206
  };
2133
2207
  var TransactionTracker = class {
2134
2208
  config;
@@ -11218,160 +11292,1129 @@ var EVMClient = class {
11218
11292
  }
11219
11293
  };
11220
11294
 
11221
- // src/presets.ts
11222
- var CHAIN_NAMES = {
11223
- // EVM L2s (Hub-capable)
11224
- BASE: "base",
11225
- OPTIMISM: "optimism",
11226
- ARBITRUM: "arbitrum",
11227
- SCROLL: "scroll",
11228
- BLAST: "blast",
11229
- MANTLE: "mantle",
11230
- // EVM L1s
11231
- ETHEREUM: "ethereum",
11232
- POLYGON: "polygon",
11233
- BSC: "bsc",
11234
- AVALANCHE: "avalanche",
11235
- FANTOM: "fantom",
11236
- CELO: "celo",
11237
- MOONBEAM: "moonbeam",
11238
- // Non-EVM
11239
- SOLANA: "solana",
11240
- APTOS: "aptos",
11241
- SUI: "sui",
11242
- STARKNET: "starknet",
11243
- NEAR: "near",
11244
- SEI: "sei"
11295
+ // src/chains/stacks/StacksSigner.ts
11296
+ var P256_ORDER = BigInt(
11297
+ "0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551"
11298
+ );
11299
+ var P256_HALF_ORDER = P256_ORDER / 2n;
11300
+ function compressPublicKey(x, y) {
11301
+ const prefix = y % 2n === 0n ? 2 : 3;
11302
+ const xBytes = bigintToBytes(x, 32);
11303
+ const compressed = new Uint8Array(33);
11304
+ compressed[0] = prefix;
11305
+ compressed.set(xBytes, 1);
11306
+ return compressed;
11307
+ }
11308
+ function rsToCompactSignature(r, s) {
11309
+ const normalizedS = s > P256_HALF_ORDER ? P256_ORDER - s : s;
11310
+ const compact = new Uint8Array(64);
11311
+ compact.set(bigintToBytes(r, 32), 0);
11312
+ compact.set(bigintToBytes(normalizedS, 32), 32);
11313
+ return compact;
11314
+ }
11315
+ function parseDERSignature2(der) {
11316
+ if (der[0] !== 48) {
11317
+ throw new Error("Invalid DER signature: expected SEQUENCE tag 0x30");
11318
+ }
11319
+ let offset = 2;
11320
+ if (der[offset] !== 2) {
11321
+ throw new Error("Invalid DER signature: expected INTEGER tag 0x02 for r");
11322
+ }
11323
+ offset++;
11324
+ const rLen = der[offset];
11325
+ offset++;
11326
+ const rBytes = der.slice(offset, offset + rLen);
11327
+ offset += rLen;
11328
+ if (der[offset] !== 2) {
11329
+ throw new Error("Invalid DER signature: expected INTEGER tag 0x02 for s");
11330
+ }
11331
+ offset++;
11332
+ const sLen = der[offset];
11333
+ offset++;
11334
+ const sBytes = der.slice(offset, offset + sLen);
11335
+ return {
11336
+ r: bytesToBigint(rBytes),
11337
+ s: bytesToBigint(sBytes)
11338
+ };
11339
+ }
11340
+ function derToCompactSignature(der) {
11341
+ const { r, s } = parseDERSignature2(der);
11342
+ return rsToCompactSignature(r, s);
11343
+ }
11344
+ async function computeKeyHash2(compressedPubkey) {
11345
+ const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", compressedPubkey.buffer);
11346
+ const hashArray = new Uint8Array(hashBuffer);
11347
+ return "0x" + bytesToHex(hashArray);
11348
+ }
11349
+ async function computeKeyHashFromCoords(x, y) {
11350
+ const compressed = compressPublicKey(x, y);
11351
+ return computeKeyHash2(compressed);
11352
+ }
11353
+ async function buildRegistrationHash(nonce) {
11354
+ const message = `veridex:register:${nonce}`;
11355
+ const encoded = new TextEncoder().encode(message);
11356
+ const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded.buffer);
11357
+ return new Uint8Array(hashBuffer);
11358
+ }
11359
+ async function buildSessionRegistrationHash(sessionKeyHash, duration, maxValue, nonce) {
11360
+ const cleanHash = sessionKeyHash.replace("0x", "");
11361
+ const message = `veridex:session:${cleanHash}:${duration}:${maxValue}:${nonce}`;
11362
+ const encoded = new TextEncoder().encode(message);
11363
+ const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded.buffer);
11364
+ return new Uint8Array(hashBuffer);
11365
+ }
11366
+ async function buildRevocationHash(sessionHash, nonce) {
11367
+ const cleanHash = sessionHash.replace("0x", "");
11368
+ const message = `veridex:revoke:${cleanHash}:${nonce}`;
11369
+ const encoded = new TextEncoder().encode(message);
11370
+ const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded.buffer);
11371
+ return new Uint8Array(hashBuffer);
11372
+ }
11373
+ async function buildExecuteHash(actionType, amount, recipient, nonce) {
11374
+ const message = `veridex:execute:${actionType}:${amount}:${recipient}:${nonce}`;
11375
+ const encoded = new TextEncoder().encode(message);
11376
+ const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded.buffer);
11377
+ return new Uint8Array(hashBuffer);
11378
+ }
11379
+ async function buildWithdrawalHash(amount, recipient, nonce) {
11380
+ const message = `veridex:withdraw:${amount}:${recipient}:${nonce}`;
11381
+ const encoded = new TextEncoder().encode(message);
11382
+ const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded.buffer);
11383
+ return new Uint8Array(hashBuffer);
11384
+ }
11385
+ function bigintToBytes(value, length) {
11386
+ const hex = value.toString(16).padStart(length * 2, "0");
11387
+ const bytes = new Uint8Array(length);
11388
+ for (let i = 0; i < length; i++) {
11389
+ bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
11390
+ }
11391
+ return bytes;
11392
+ }
11393
+ function bytesToBigint(bytes) {
11394
+ let start = 0;
11395
+ while (start < bytes.length - 1 && bytes[start] === 0) {
11396
+ start++;
11397
+ }
11398
+ let result = 0n;
11399
+ for (let i = start; i < bytes.length; i++) {
11400
+ result = result << 8n | BigInt(bytes[i]);
11401
+ }
11402
+ return result;
11403
+ }
11404
+ function bytesToHex(bytes) {
11405
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
11406
+ }
11407
+ function hexToBytes(hex) {
11408
+ const clean = hex.replace("0x", "");
11409
+ const bytes = new Uint8Array(clean.length / 2);
11410
+ for (let i = 0; i < bytes.length; i++) {
11411
+ bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
11412
+ }
11413
+ return bytes;
11414
+ }
11415
+
11416
+ // src/chains/stacks/StacksAddressUtils.ts
11417
+ var STACKS_MAINNET_PREFIX = "SP";
11418
+ var STACKS_TESTNET_PREFIX = "ST";
11419
+ var C32_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
11420
+ function isValidStacksPrincipal(address) {
11421
+ if (!address || typeof address !== "string") {
11422
+ return false;
11423
+ }
11424
+ const parts = address.split(".");
11425
+ if (parts.length > 2) {
11426
+ return false;
11427
+ }
11428
+ const standardPart = parts[0];
11429
+ const contractName = parts[1];
11430
+ if (!isValidStandardPrincipal(standardPart)) {
11431
+ return false;
11432
+ }
11433
+ if (contractName !== void 0) {
11434
+ if (!isValidContractName(contractName)) {
11435
+ return false;
11436
+ }
11437
+ }
11438
+ return true;
11439
+ }
11440
+ function isValidStandardPrincipal(address) {
11441
+ if (!address || address.length < 5) {
11442
+ return false;
11443
+ }
11444
+ const prefix = address.slice(0, 2);
11445
+ if (prefix !== STACKS_MAINNET_PREFIX && prefix !== STACKS_TESTNET_PREFIX) {
11446
+ return false;
11447
+ }
11448
+ if (address.length < 38 || address.length > 42) {
11449
+ return false;
11450
+ }
11451
+ const body = address.slice(1).toUpperCase();
11452
+ for (const char of body) {
11453
+ if (!C32_ALPHABET.includes(char)) {
11454
+ return false;
11455
+ }
11456
+ }
11457
+ return true;
11458
+ }
11459
+ function isValidContractName(name) {
11460
+ if (!name || name.length === 0 || name.length > 128) {
11461
+ return false;
11462
+ }
11463
+ if (!/^[a-zA-Z]/.test(name)) {
11464
+ return false;
11465
+ }
11466
+ if (!/^[a-zA-Z][a-zA-Z0-9-]*$/.test(name)) {
11467
+ return false;
11468
+ }
11469
+ return true;
11470
+ }
11471
+ function getNetworkFromAddress(address) {
11472
+ const prefix = address.slice(0, 2);
11473
+ if (prefix === STACKS_MAINNET_PREFIX) {
11474
+ return "mainnet";
11475
+ }
11476
+ return "testnet";
11477
+ }
11478
+ function getContractPrincipal(deployerAddress, contractName) {
11479
+ if (!isValidStandardPrincipal(deployerAddress)) {
11480
+ throw new Error(`Invalid deployer address: ${deployerAddress}`);
11481
+ }
11482
+ if (!isValidContractName(contractName)) {
11483
+ throw new Error(`Invalid contract name: ${contractName}`);
11484
+ }
11485
+ return `${deployerAddress}.${contractName}`;
11486
+ }
11487
+ function parseContractPrincipal(contractPrincipal) {
11488
+ const dotIndex = contractPrincipal.indexOf(".");
11489
+ if (dotIndex === -1) {
11490
+ throw new Error(
11491
+ `Not a contract principal: ${contractPrincipal}. Expected format: address.contract-name`
11492
+ );
11493
+ }
11494
+ return {
11495
+ address: contractPrincipal.slice(0, dotIndex),
11496
+ contractName: contractPrincipal.slice(dotIndex + 1)
11497
+ };
11498
+ }
11499
+ function isContractPrincipal(address) {
11500
+ return address.includes(".");
11501
+ }
11502
+ function getStacksExplorerTxUrl(txId, network = "testnet") {
11503
+ const cleanTxId = txId.startsWith("0x") ? txId : `0x${txId}`;
11504
+ const chainParam = network === "testnet" ? "&chain=testnet" : "";
11505
+ return `https://explorer.hiro.so/txid/${cleanTxId}?${chainParam}`;
11506
+ }
11507
+ function getStacksExplorerAddressUrl(address, network = "testnet") {
11508
+ const chainParam = network === "testnet" ? "?chain=testnet" : "";
11509
+ return `https://explorer.hiro.so/address/${address}${chainParam}`;
11510
+ }
11511
+
11512
+ // src/chains/stacks/StacksClient.ts
11513
+ var STACKS_ACTION_TYPES = {
11514
+ TRANSFER_STX: 1,
11515
+ TRANSFER_SBTC: 2,
11516
+ CONTRACT_CALL: 3
11245
11517
  };
11246
- var CHAIN_PRESETS = {
11247
- // ────────────────────────────────────────────────────────────────────────
11248
- // BASE - Primary Hub Chain
11249
- // ────────────────────────────────────────────────────────────────────────
11250
- base: {
11251
- displayName: "Base",
11252
- type: "evm",
11253
- canBeHub: true,
11254
- testnet: {
11255
- name: "Base Sepolia",
11256
- chainId: 84532,
11257
- wormholeChainId: 10004,
11258
- rpcUrl: "https://sepolia.base.org",
11259
- explorerUrl: "https://sepolia.basescan.org",
11260
- isEvm: true,
11261
- contracts: {
11262
- hub: "0x66D87dE68327f48A099c5B9bE97020Feab9a7c82",
11263
- vaultFactory: "0xCFaEb5652aa2Ee60b2229dC8895B4159749C7e53",
11264
- vaultImplementation: "0x0d13367C16c6f0B24eD275CC67C7D9f42878285c",
11265
- wormholeCoreBridge: "0x79A1027a6A159502049F10906D333EC57E95F083",
11266
- tokenBridge: "0x86F55A04690fd7815A3D802bD587e83eA888B239"
11267
- }
11268
- },
11269
- mainnet: {
11270
- name: "Base",
11271
- chainId: 8453,
11272
- wormholeChainId: 30,
11273
- rpcUrl: "https://mainnet.base.org",
11274
- explorerUrl: "https://basescan.org",
11275
- isEvm: true,
11276
- contracts: {
11277
- // TODO: Deploy mainnet contracts
11278
- wormholeCoreBridge: "0xbebdb6C8ddC678FfA9f8748f85C815C556Dd8ac6",
11279
- tokenBridge: "0x8d2de8d2f73F1F4cAB472AC9A881C9b123C79627"
11280
- }
11518
+ var HIRO_API = {
11519
+ testnet: "https://api.testnet.hiro.so",
11520
+ mainnet: "https://api.hiro.so"
11521
+ };
11522
+ var StacksClient = class {
11523
+ config;
11524
+ rpcUrl;
11525
+ spokeContract;
11526
+ vaultContract;
11527
+ wormholeVerifierContract;
11528
+ vaultVaaContract;
11529
+ networkType;
11530
+ constructor(clientConfig) {
11531
+ this.networkType = clientConfig.network || "testnet";
11532
+ this.rpcUrl = clientConfig.rpcUrl || HIRO_API[this.networkType];
11533
+ if (clientConfig.spokeContractAddress && isContractPrincipal(clientConfig.spokeContractAddress)) {
11534
+ const parsed = parseContractPrincipal(clientConfig.spokeContractAddress);
11535
+ this.spokeContract = { address: parsed.address, name: parsed.contractName };
11536
+ } else {
11537
+ this.spokeContract = null;
11281
11538
  }
11282
- },
11283
- // ────────────────────────────────────────────────────────────────────────
11284
- // OPTIMISM - Secondary Hub / Spoke
11285
- // ────────────────────────────────────────────────────────────────────────
11286
- optimism: {
11287
- displayName: "Optimism",
11288
- type: "evm",
11289
- canBeHub: true,
11290
- testnet: {
11291
- name: "Optimism Sepolia",
11292
- chainId: 11155420,
11293
- wormholeChainId: 10005,
11294
- rpcUrl: "https://sepolia.optimism.io",
11295
- explorerUrl: "https://sepolia-optimism.etherscan.io",
11296
- isEvm: true,
11297
- contracts: {
11298
- vaultFactory: "0xA5653d54079ABeCe780F8d9597B2bc4B09fe464A",
11299
- vaultImplementation: "0x8099b1406485d2255ff89Ce5Ea18520802AFC150",
11300
- wormholeCoreBridge: "0x31377888146f3253211EFEf5c676D41ECe7D58Fe",
11301
- tokenBridge: "0x99737Ec4B815d816c49A385943baf0380e75c0Ac"
11302
- }
11303
- },
11304
- mainnet: {
11305
- name: "Optimism",
11306
- chainId: 10,
11307
- wormholeChainId: 24,
11308
- rpcUrl: "https://mainnet.optimism.io",
11309
- explorerUrl: "https://optimistic.etherscan.io",
11310
- isEvm: true,
11311
- contracts: {
11312
- wormholeCoreBridge: "0xEe91C335eab126dF5fDB3797EA9d6aD93aeC9722",
11313
- tokenBridge: "0x1D68124e65faFC907325e3EDbF8c4d84499DAa8b"
11314
- }
11539
+ if (clientConfig.vaultContractAddress && isContractPrincipal(clientConfig.vaultContractAddress)) {
11540
+ const parsed = parseContractPrincipal(clientConfig.vaultContractAddress);
11541
+ this.vaultContract = { address: parsed.address, name: parsed.contractName };
11542
+ } else {
11543
+ this.vaultContract = null;
11315
11544
  }
11316
- },
11317
- // ────────────────────────────────────────────────────────────────────────
11318
- // ARBITRUM
11319
- // ────────────────────────────────────────────────────────────────────────
11320
- arbitrum: {
11321
- displayName: "Arbitrum",
11322
- type: "evm",
11323
- canBeHub: true,
11324
- testnet: {
11325
- name: "Arbitrum Sepolia",
11326
- chainId: 421614,
11327
- wormholeChainId: 10003,
11328
- rpcUrl: "https://sepolia-rollup.arbitrum.io/rpc",
11329
- explorerUrl: "https://sepolia.arbiscan.io",
11330
- isEvm: true,
11331
- contracts: {
11332
- vaultFactory: "0xd36D3D5DB59d78f1E33813490F72DABC15C9B07c",
11333
- vaultImplementation: "0xB10ACf39eBF17fc33F722cBD955b7aeCB0611bc4",
11334
- wormholeCoreBridge: "0x6b9C8671cdDC8dEab9c719bB87cBd3e782bA6a35",
11335
- tokenBridge: "0xC7A204bDBFe983FCD8d8E61D02b475D4073fF97e"
11336
- }
11337
- },
11338
- mainnet: {
11339
- name: "Arbitrum",
11340
- chainId: 42161,
11341
- wormholeChainId: 23,
11342
- rpcUrl: "https://arb1.arbitrum.io/rpc",
11343
- explorerUrl: "https://arbiscan.io",
11344
- isEvm: true,
11545
+ if (clientConfig.wormholeVerifierAddress && isContractPrincipal(clientConfig.wormholeVerifierAddress)) {
11546
+ const parsed = parseContractPrincipal(clientConfig.wormholeVerifierAddress);
11547
+ this.wormholeVerifierContract = { address: parsed.address, name: parsed.contractName };
11548
+ } else {
11549
+ this.wormholeVerifierContract = null;
11550
+ }
11551
+ if (clientConfig.vaultVaaContractAddress && isContractPrincipal(clientConfig.vaultVaaContractAddress)) {
11552
+ const parsed = parseContractPrincipal(clientConfig.vaultVaaContractAddress);
11553
+ this.vaultVaaContract = { address: parsed.address, name: parsed.contractName };
11554
+ } else {
11555
+ this.vaultVaaContract = null;
11556
+ }
11557
+ if (this.spokeContract && !this.vaultContract) {
11558
+ this.vaultContract = {
11559
+ address: this.spokeContract.address,
11560
+ name: "veridex-vault"
11561
+ };
11562
+ }
11563
+ if (this.spokeContract && !this.wormholeVerifierContract) {
11564
+ this.wormholeVerifierContract = {
11565
+ address: this.spokeContract.address,
11566
+ name: "veridex-wormhole-verifier"
11567
+ };
11568
+ }
11569
+ if (this.spokeContract && !this.vaultVaaContract) {
11570
+ this.vaultVaaContract = {
11571
+ address: this.spokeContract.address,
11572
+ name: "veridex-vault-vaa"
11573
+ };
11574
+ }
11575
+ this.config = {
11576
+ name: `Stacks ${this.networkType}`,
11577
+ chainId: this.networkType === "mainnet" ? 1 : 2147483648,
11578
+ wormholeChainId: clientConfig.wormholeChainId,
11579
+ rpcUrl: this.rpcUrl,
11580
+ explorerUrl: this.networkType === "testnet" ? "https://explorer.hiro.so/?chain=testnet" : "https://explorer.hiro.so",
11581
+ isEvm: false,
11345
11582
  contracts: {
11346
- wormholeCoreBridge: "0xa5f208e072434bC67592E4C49C1B991BA79BCA46",
11347
- tokenBridge: "0x0b2402144Bb366A632D14B83F244D2e0e21bD39c"
11583
+ hub: clientConfig.spokeContractAddress,
11584
+ wormholeCoreBridge: "",
11585
+ wormholeVerifier: this.wormholeVerifierContract ? `${this.wormholeVerifierContract.address}.${this.wormholeVerifierContract.name}` : void 0,
11586
+ vaultVaa: this.vaultVaaContract ? `${this.vaultVaaContract.address}.${this.vaultVaaContract.name}` : void 0
11348
11587
  }
11588
+ };
11589
+ }
11590
+ // ========================================================================
11591
+ // ChainClient Interface - Configuration
11592
+ // ========================================================================
11593
+ getConfig() {
11594
+ return this.config;
11595
+ }
11596
+ // ========================================================================
11597
+ // ChainClient Interface - Nonce & Fees
11598
+ // ========================================================================
11599
+ /**
11600
+ * Get the current nonce for a user identity from the spoke contract.
11601
+ * Calls the read-only function `get-nonce` on veridex-spoke.
11602
+ */
11603
+ async getNonce(userKeyHash) {
11604
+ if (!this.spokeContract) {
11605
+ return 0n;
11349
11606
  }
11350
- },
11351
- // ────────────────────────────────────────────────────────────────────────
11352
- // ETHEREUM
11353
- // ────────────────────────────────────────────────────────────────────────
11354
- ethereum: {
11355
- displayName: "Ethereum",
11356
- type: "evm",
11357
- canBeHub: false,
11358
- testnet: {
11359
- name: "Sepolia",
11360
- chainId: 11155111,
11361
- wormholeChainId: 10002,
11362
- rpcUrl: "https://ethereum-sepolia-rpc.publicnode.com",
11363
- explorerUrl: "https://sepolia.etherscan.io",
11364
- isEvm: true,
11365
- contracts: {
11366
- vaultFactory: "0x07F608AFf6d63b68029488b726d895c4Bb593038",
11367
- vaultImplementation: "0xD66153fccFB6731fB6c4944FbD607ba86A76a1f6",
11368
- wormholeCoreBridge: "0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78",
11369
- tokenBridge: "0xDB5492265f6038831E89f495670FF909aDe94bd9"
11607
+ try {
11608
+ const result = await this.callReadOnly(
11609
+ this.spokeContract.address,
11610
+ this.spokeContract.name,
11611
+ "get-nonce",
11612
+ [`0x${userKeyHash.replace("0x", "")}`]
11613
+ );
11614
+ if (result && result.value !== void 0) {
11615
+ return BigInt(result.value);
11370
11616
  }
11371
- },
11372
- mainnet: {
11373
- name: "Ethereum",
11374
- chainId: 1,
11617
+ return 0n;
11618
+ } catch {
11619
+ return 0n;
11620
+ }
11621
+ }
11622
+ /**
11623
+ * Get the Wormhole message fee.
11624
+ * Phase 1: No Wormhole integration, returns 0.
11625
+ */
11626
+ async getMessageFee() {
11627
+ return 0n;
11628
+ }
11629
+ // ========================================================================
11630
+ // ChainClient Interface - Payload Building
11631
+ // ========================================================================
11632
+ async buildTransferPayload(params) {
11633
+ return encodeTransferAction(params.token, params.recipient, params.amount);
11634
+ }
11635
+ async buildExecutePayload(params) {
11636
+ return encodeExecuteAction(params.target, params.value, params.data);
11637
+ }
11638
+ async buildBridgePayload(params) {
11639
+ return encodeBridgeAction(params.token, params.amount, params.destinationChain, params.recipient);
11640
+ }
11641
+ // ========================================================================
11642
+ // ChainClient Interface - Dispatch
11643
+ // ========================================================================
11644
+ /**
11645
+ * Direct dispatch is not supported on Stacks in Phase 1.
11646
+ * Stacks actions are executed via sponsored transactions through the relayer.
11647
+ */
11648
+ async dispatch(_signature, _publicKeyX, _publicKeyY, _targetChain, _actionPayload, _nonce, _signer) {
11649
+ throw new Error(
11650
+ "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."
11651
+ );
11652
+ }
11653
+ /**
11654
+ * Dispatch an action via the relayer (gasless/sponsored).
11655
+ *
11656
+ * Flow:
11657
+ * 1. User signs action with Passkey (on client)
11658
+ * 2. SDK submits to relayer with targetChain=60 (Stacks)
11659
+ * 3. Relayer builds Clarity contract-call transaction
11660
+ * 4. Relayer sponsors the transaction (pays STX gas)
11661
+ * 5. Relayer broadcasts to Stacks network
11662
+ * 6. Transaction confirmed on Stacks
11663
+ */
11664
+ async dispatchGasless(signature, publicKeyX, publicKeyY, targetChain, actionPayload, nonce, relayerUrl) {
11665
+ const keyHash = await computeKeyHashFromCoords(publicKeyX, publicKeyY);
11666
+ const compressedPubkey = compressPublicKey(publicKeyX, publicKeyY);
11667
+ const compactSig = rsToCompactSignature(signature.r, signature.s);
11668
+ const request = {
11669
+ signature: {
11670
+ r: "0x" + signature.r.toString(16).padStart(64, "0"),
11671
+ s: "0x" + signature.s.toString(16).padStart(64, "0"),
11672
+ authenticatorData: signature.authenticatorData,
11673
+ clientDataJSON: signature.clientDataJSON,
11674
+ challengeIndex: signature.challengeIndex,
11675
+ typeIndex: signature.typeIndex
11676
+ },
11677
+ publicKeyX: "0x" + publicKeyX.toString(16).padStart(64, "0"),
11678
+ publicKeyY: "0x" + publicKeyY.toString(16).padStart(64, "0"),
11679
+ compressedPubkey: "0x" + bytesToHex(compressedPubkey),
11680
+ compactSignature: "0x" + bytesToHex(compactSig),
11681
+ targetChain,
11682
+ actionPayload,
11683
+ userNonce: Number(nonce)
11684
+ };
11685
+ const response = await fetch(`${relayerUrl}/api/v1/submit`, {
11686
+ method: "POST",
11687
+ headers: { "Content-Type": "application/json" },
11688
+ body: JSON.stringify(request)
11689
+ });
11690
+ if (!response.ok) {
11691
+ const errorText = await response.text().catch(() => "Unknown error");
11692
+ throw new Error(
11693
+ `Relayer submission failed: ${response.status} ${response.statusText}. Error: ${errorText}`
11694
+ );
11695
+ }
11696
+ const result = await response.json();
11697
+ return {
11698
+ transactionHash: result.transactionHash ?? result.txHash ?? result.hubTxHash ?? "",
11699
+ sequence: BigInt(result.sequence || 0),
11700
+ userKeyHash: keyHash,
11701
+ targetChain
11702
+ };
11703
+ }
11704
+ // ========================================================================
11705
+ // ChainClient Interface - Vault Management
11706
+ // ========================================================================
11707
+ /**
11708
+ * Get vault address for a user.
11709
+ * On Stacks, vaults are map-based within the vault contract.
11710
+ * The "vault address" is the vault contract principal itself.
11711
+ */
11712
+ async getVaultAddress(userKeyHash) {
11713
+ if (!this.vaultContract) {
11714
+ return null;
11715
+ }
11716
+ const exists = await this.vaultExists(userKeyHash);
11717
+ if (!exists) {
11718
+ return null;
11719
+ }
11720
+ return `${this.vaultContract.address}.${this.vaultContract.name}`;
11721
+ }
11722
+ /**
11723
+ * Compute vault address deterministically.
11724
+ * On Stacks, all vaults live in the same contract (map-based).
11725
+ */
11726
+ computeVaultAddress(_userKeyHash) {
11727
+ if (!this.vaultContract) {
11728
+ throw new Error("Vault contract not configured");
11729
+ }
11730
+ return `${this.vaultContract.address}.${this.vaultContract.name}`;
11731
+ }
11732
+ /**
11733
+ * Check if a vault (identity) exists for a user.
11734
+ * Queries the spoke contract's `identity-exists` read-only function.
11735
+ */
11736
+ async vaultExists(userKeyHash) {
11737
+ if (!this.spokeContract) {
11738
+ return false;
11739
+ }
11740
+ try {
11741
+ const result = await this.callReadOnly(
11742
+ this.spokeContract.address,
11743
+ this.spokeContract.name,
11744
+ "identity-exists",
11745
+ [`0x${userKeyHash.replace("0x", "")}`]
11746
+ );
11747
+ return result === true || result?.value === true;
11748
+ } catch {
11749
+ return false;
11750
+ }
11751
+ }
11752
+ /**
11753
+ * Create a vault (register identity) on Stacks.
11754
+ * Must be done via Hub dispatch or relayer in Phase 1.
11755
+ */
11756
+ async createVault(userKeyHash, _signer) {
11757
+ throw new Error(
11758
+ `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}`
11759
+ );
11760
+ }
11761
+ /**
11762
+ * Create a vault with a sponsor wallet.
11763
+ * On Stacks, this registers an identity via sponsored transaction.
11764
+ */
11765
+ async createVaultSponsored(userKeyHash, _sponsorPrivateKey, _rpcUrl) {
11766
+ throw new Error(
11767
+ `Sponsored vault creation on Stacks requires the user to sign with their Passkey. Use createVaultViaRelayer() which handles the sponsored transaction flow. KeyHash=${userKeyHash}`
11768
+ );
11769
+ }
11770
+ /**
11771
+ * Create a vault via the relayer (sponsored/gasless).
11772
+ * The relayer will sponsor the register-identity transaction.
11773
+ */
11774
+ async createVaultViaRelayer(userKeyHash, relayerUrl) {
11775
+ const response = await fetch(`${relayerUrl}/api/v1/stacks/vault`, {
11776
+ method: "POST",
11777
+ headers: { "Content-Type": "application/json" },
11778
+ body: JSON.stringify({
11779
+ userKeyHash,
11780
+ chainId: this.config.wormholeChainId
11781
+ })
11782
+ });
11783
+ const result = await response.json();
11784
+ if (!response.ok || !result.success) {
11785
+ throw new Error(result.error || "Failed to create vault via relayer");
11786
+ }
11787
+ return {
11788
+ address: result.vaultAddress || this.computeVaultAddress(userKeyHash),
11789
+ transactionHash: result.transactionHash || "",
11790
+ blockNumber: 0,
11791
+ gasUsed: 0n,
11792
+ alreadyExisted: result.alreadyExists || false,
11793
+ sponsoredBy: "relayer"
11794
+ };
11795
+ }
11796
+ async estimateVaultCreationGas(_userKeyHash) {
11797
+ return 10000n;
11798
+ }
11799
+ getFactoryAddress() {
11800
+ return void 0;
11801
+ }
11802
+ getImplementationAddress() {
11803
+ return void 0;
11804
+ }
11805
+ // ========================================================================
11806
+ // Stacks-Specific: Balance Queries
11807
+ // ========================================================================
11808
+ /**
11809
+ * Get native STX balance for an address.
11810
+ */
11811
+ async getNativeBalance(address) {
11812
+ try {
11813
+ const response = await fetch(
11814
+ `${this.rpcUrl}/v2/accounts/${address}?proof=0`
11815
+ );
11816
+ if (!response.ok) {
11817
+ return 0n;
11818
+ }
11819
+ const data = await response.json();
11820
+ return BigInt(data.balance || "0");
11821
+ } catch {
11822
+ return 0n;
11823
+ }
11824
+ }
11825
+ /**
11826
+ * Get vault STX balance for an identity.
11827
+ * Queries the vault contract's `get-stx-balance` read-only function.
11828
+ */
11829
+ async getVaultStxBalance(keyHash) {
11830
+ if (!this.vaultContract) {
11831
+ return 0n;
11832
+ }
11833
+ try {
11834
+ const result = await this.callReadOnly(
11835
+ this.vaultContract.address,
11836
+ this.vaultContract.name,
11837
+ "get-stx-balance",
11838
+ [`0x${keyHash.replace("0x", "")}`]
11839
+ );
11840
+ if (result && result.value !== void 0) {
11841
+ return BigInt(result.value);
11842
+ }
11843
+ return 0n;
11844
+ } catch {
11845
+ return 0n;
11846
+ }
11847
+ }
11848
+ /**
11849
+ * Get vault sBTC balance for an identity.
11850
+ */
11851
+ async getVaultSbtcBalance(keyHash) {
11852
+ if (!this.vaultContract) {
11853
+ return 0n;
11854
+ }
11855
+ try {
11856
+ const result = await this.callReadOnly(
11857
+ this.vaultContract.address,
11858
+ this.vaultContract.name,
11859
+ "get-sbtc-balance",
11860
+ [`0x${keyHash.replace("0x", "")}`]
11861
+ );
11862
+ if (result && result.value !== void 0) {
11863
+ return BigInt(result.value);
11864
+ }
11865
+ return 0n;
11866
+ } catch {
11867
+ return 0n;
11868
+ }
11869
+ }
11870
+ // ========================================================================
11871
+ // Stacks-Specific: Identity & Session Queries
11872
+ // ========================================================================
11873
+ /**
11874
+ * Get identity info from the spoke contract.
11875
+ */
11876
+ async getIdentity(keyHash) {
11877
+ if (!this.spokeContract) {
11878
+ return null;
11879
+ }
11880
+ try {
11881
+ const result = await this.callReadOnly(
11882
+ this.spokeContract.address,
11883
+ this.spokeContract.name,
11884
+ "get-identity",
11885
+ [`0x${keyHash.replace("0x", "")}`]
11886
+ );
11887
+ if (!result || result.value === void 0) {
11888
+ return null;
11889
+ }
11890
+ const val = result.value;
11891
+ return {
11892
+ compressedPubkey: val["compressed-pubkey"]?.value || "",
11893
+ owner: val.owner?.value || "",
11894
+ nonce: BigInt(val.nonce?.value || 0),
11895
+ createdAt: BigInt(val["created-at"]?.value || 0)
11896
+ };
11897
+ } catch {
11898
+ return null;
11899
+ }
11900
+ }
11901
+ /**
11902
+ * Get session info from the spoke contract.
11903
+ */
11904
+ async getSession(keyHash, sessionHash) {
11905
+ if (!this.spokeContract) {
11906
+ return null;
11907
+ }
11908
+ try {
11909
+ const cleanKeyHash = `0x${keyHash.replace("0x", "")}`;
11910
+ const cleanSessionHash = `0x${sessionHash.replace("0x", "")}`;
11911
+ const result = await this.callReadOnly(
11912
+ this.spokeContract.address,
11913
+ this.spokeContract.name,
11914
+ "get-session",
11915
+ [cleanKeyHash, cleanSessionHash]
11916
+ );
11917
+ if (!result || result.value === void 0) {
11918
+ return null;
11919
+ }
11920
+ const val = result.value;
11921
+ return {
11922
+ sessionPubkey: val["session-pubkey"]?.value || "",
11923
+ expiry: BigInt(val.expiry?.value || 0),
11924
+ maxValue: BigInt(val["max-value"]?.value || 0),
11925
+ spent: BigInt(val.spent?.value || 0),
11926
+ revoked: val.revoked?.value === true,
11927
+ createdAt: BigInt(val["created-at"]?.value || 0)
11928
+ };
11929
+ } catch {
11930
+ return null;
11931
+ }
11932
+ }
11933
+ /**
11934
+ * Check if a session is currently active.
11935
+ */
11936
+ async checkSessionActive(keyHash, sessionHash) {
11937
+ if (!this.spokeContract) {
11938
+ return false;
11939
+ }
11940
+ try {
11941
+ const cleanKeyHash = `0x${keyHash.replace("0x", "")}`;
11942
+ const cleanSessionHash = `0x${sessionHash.replace("0x", "")}`;
11943
+ const result = await this.callReadOnly(
11944
+ this.spokeContract.address,
11945
+ this.spokeContract.name,
11946
+ "is-session-active",
11947
+ [cleanKeyHash, cleanSessionHash]
11948
+ );
11949
+ return result?.value === true;
11950
+ } catch {
11951
+ return false;
11952
+ }
11953
+ }
11954
+ /**
11955
+ * Get remaining spending budget for a session.
11956
+ */
11957
+ async getRemainingBudget(keyHash, sessionHash) {
11958
+ if (!this.spokeContract) {
11959
+ return 0n;
11960
+ }
11961
+ try {
11962
+ const cleanKeyHash = `0x${keyHash.replace("0x", "")}`;
11963
+ const cleanSessionHash = `0x${sessionHash.replace("0x", "")}`;
11964
+ const result = await this.callReadOnly(
11965
+ this.spokeContract.address,
11966
+ this.spokeContract.name,
11967
+ "get-remaining-budget",
11968
+ [cleanKeyHash, cleanSessionHash]
11969
+ );
11970
+ if (result && result.value !== void 0) {
11971
+ return BigInt(result.value);
11972
+ }
11973
+ return 0n;
11974
+ } catch {
11975
+ return 0n;
11976
+ }
11977
+ }
11978
+ // ========================================================================
11979
+ // Stacks-Specific: Protocol Status
11980
+ // ========================================================================
11981
+ /**
11982
+ * Check if the spoke contract is paused.
11983
+ */
11984
+ async isProtocolPaused() {
11985
+ if (!this.spokeContract) {
11986
+ return false;
11987
+ }
11988
+ try {
11989
+ const result = await this.callReadOnly(
11990
+ this.spokeContract.address,
11991
+ this.spokeContract.name,
11992
+ "is-paused",
11993
+ []
11994
+ );
11995
+ return result === true || result?.value === true;
11996
+ } catch {
11997
+ return false;
11998
+ }
11999
+ }
12000
+ /**
12001
+ * Get global identity count.
12002
+ */
12003
+ async getIdentityCount() {
12004
+ if (!this.spokeContract) {
12005
+ return 0n;
12006
+ }
12007
+ try {
12008
+ const result = await this.callReadOnly(
12009
+ this.spokeContract.address,
12010
+ this.spokeContract.name,
12011
+ "get-identity-count",
12012
+ []
12013
+ );
12014
+ return BigInt(result?.value || result || 0);
12015
+ } catch {
12016
+ return 0n;
12017
+ }
12018
+ }
12019
+ /**
12020
+ * Get total STX deposited across all vaults.
12021
+ */
12022
+ async getTotalStxDeposited() {
12023
+ if (!this.vaultContract) {
12024
+ return 0n;
12025
+ }
12026
+ try {
12027
+ const result = await this.callReadOnly(
12028
+ this.vaultContract.address,
12029
+ this.vaultContract.name,
12030
+ "get-total-stx-deposited",
12031
+ []
12032
+ );
12033
+ return BigInt(result?.value || result || 0);
12034
+ } catch {
12035
+ return 0n;
12036
+ }
12037
+ }
12038
+ // ========================================================================
12039
+ // Session Management (Issue #13)
12040
+ // ========================================================================
12041
+ /**
12042
+ * Register a session key on the Stacks spoke.
12043
+ * On Stacks, sessions are managed directly on the spoke contract
12044
+ * (unlike EVM spokes where sessions are on the Hub).
12045
+ */
12046
+ async registerSession(_params) {
12047
+ throw new Error(
12048
+ "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."
12049
+ );
12050
+ }
12051
+ /**
12052
+ * Revoke a session key on the Stacks spoke.
12053
+ */
12054
+ async revokeSession(_params) {
12055
+ throw new Error(
12056
+ "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."
12057
+ );
12058
+ }
12059
+ /**
12060
+ * Check if a session is active.
12061
+ */
12062
+ async isSessionActive(userKeyHash, sessionKeyHash) {
12063
+ const active = await this.checkSessionActive(userKeyHash, sessionKeyHash);
12064
+ const session = await this.getSession(userKeyHash, sessionKeyHash);
12065
+ return {
12066
+ isActive: active,
12067
+ expiry: session ? Number(session.expiry) : 0,
12068
+ maxValue: session?.maxValue ?? 0n,
12069
+ chainScopes: [this.config.wormholeChainId]
12070
+ };
12071
+ }
12072
+ /**
12073
+ * Get all sessions for a user.
12074
+ * Note: Clarity maps don't support enumeration, so this requires
12075
+ * off-chain indexing or event log parsing.
12076
+ */
12077
+ async getUserSessions(_userKeyHash) {
12078
+ throw new Error(
12079
+ '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.'
12080
+ );
12081
+ }
12082
+ // ========================================================================
12083
+ // Stacks-Specific: Transaction Status
12084
+ // ========================================================================
12085
+ /**
12086
+ * Get the status of a Stacks transaction.
12087
+ */
12088
+ async getTransactionStatus(txId) {
12089
+ try {
12090
+ const cleanTxId = txId.startsWith("0x") ? txId : `0x${txId}`;
12091
+ const response = await fetch(
12092
+ `${this.rpcUrl}/extended/v1/tx/${cleanTxId}`
12093
+ );
12094
+ if (!response.ok) {
12095
+ return { status: "not_found" };
12096
+ }
12097
+ const data = await response.json();
12098
+ const txStatus = data.tx_status;
12099
+ if (txStatus === "success") {
12100
+ return {
12101
+ status: "success",
12102
+ blockHeight: data.block_height
12103
+ };
12104
+ }
12105
+ if (txStatus === "pending") {
12106
+ return { status: "pending" };
12107
+ }
12108
+ if (txStatus === "abort_by_response" || txStatus === "abort_by_post_condition") {
12109
+ return {
12110
+ status: "failed",
12111
+ error: `Transaction aborted: ${txStatus}`
12112
+ };
12113
+ }
12114
+ return { status: "pending" };
12115
+ } catch {
12116
+ return { status: "not_found" };
12117
+ }
12118
+ }
12119
+ /**
12120
+ * Wait for a transaction to be confirmed.
12121
+ *
12122
+ * @param txId - Transaction ID
12123
+ * @param maxAttempts - Maximum polling attempts (default: 60)
12124
+ * @param pollIntervalMs - Polling interval in milliseconds (default: 5000)
12125
+ */
12126
+ async waitForConfirmation(txId, maxAttempts = 60, pollIntervalMs = 5e3) {
12127
+ for (let i = 0; i < maxAttempts; i++) {
12128
+ const status = await this.getTransactionStatus(txId);
12129
+ if (status.status === "success") {
12130
+ return { confirmed: true, blockHeight: status.blockHeight };
12131
+ }
12132
+ if (status.status === "failed") {
12133
+ throw new Error(`Transaction failed: ${status.error}`);
12134
+ }
12135
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
12136
+ }
12137
+ return { confirmed: false };
12138
+ }
12139
+ // ========================================================================
12140
+ // Stacks-Specific: Network Info
12141
+ // ========================================================================
12142
+ /**
12143
+ * Get Stacks network info (block height, network version, etc.).
12144
+ */
12145
+ async getNetworkInfo() {
12146
+ const response = await fetch(`${this.rpcUrl}/v2/info`);
12147
+ if (!response.ok) {
12148
+ throw new Error(`Failed to get Stacks network info: ${response.statusText}`);
12149
+ }
12150
+ const data = await response.json();
12151
+ return {
12152
+ networkId: data.network_id,
12153
+ stacksBlockHeight: data.stacks_tip_height,
12154
+ burnBlockHeight: data.burn_block_height,
12155
+ serverVersion: data.server_version
12156
+ };
12157
+ }
12158
+ /**
12159
+ * Get the current Stacks block height.
12160
+ * Used for session expiry calculations.
12161
+ */
12162
+ async getCurrentBlockHeight() {
12163
+ const info = await this.getNetworkInfo();
12164
+ return info.stacksBlockHeight;
12165
+ }
12166
+ // ========================================================================
12167
+ // Internal: Read-Only Contract Calls via Hiro API
12168
+ // ========================================================================
12169
+ /**
12170
+ * Call a read-only Clarity function via the Hiro API.
12171
+ * Uses the /v2/contracts/call-read endpoint.
12172
+ */
12173
+ async callReadOnly(contractAddress, contractName, functionName, args) {
12174
+ const url = `${this.rpcUrl}/v2/contracts/call-read/${contractAddress}/${contractName}/${functionName}`;
12175
+ const response = await fetch(url, {
12176
+ method: "POST",
12177
+ headers: { "Content-Type": "application/json" },
12178
+ body: JSON.stringify({
12179
+ sender: contractAddress,
12180
+ arguments: args
12181
+ })
12182
+ });
12183
+ if (!response.ok) {
12184
+ const errorText = await response.text().catch(() => "Unknown error");
12185
+ throw new Error(
12186
+ `Read-only call failed: ${contractAddress}.${contractName}::${functionName} - ${response.status}: ${errorText}`
12187
+ );
12188
+ }
12189
+ const data = await response.json();
12190
+ if (!data.okay) {
12191
+ throw new Error(
12192
+ `Read-only call returned error: ${contractAddress}.${contractName}::${functionName} - ${data.cause || "Unknown cause"}`
12193
+ );
12194
+ }
12195
+ return this.parseClarityValue(data.result);
12196
+ }
12197
+ /**
12198
+ * Parse a hex-encoded Clarity value from the API response.
12199
+ * This is a simplified parser for common Clarity types.
12200
+ */
12201
+ parseClarityValue(hex) {
12202
+ if (!hex || hex === "0x") {
12203
+ return null;
12204
+ }
12205
+ const bytes = hexToBytes(hex);
12206
+ if (bytes.length === 0) {
12207
+ return null;
12208
+ }
12209
+ const typeId = bytes[0];
12210
+ switch (typeId) {
12211
+ // int (0x00)
12212
+ case 0: {
12213
+ let value = 0n;
12214
+ for (let i = 1; i < 17 && i < bytes.length; i++) {
12215
+ value = value << 8n | BigInt(bytes[i]);
12216
+ }
12217
+ return { value };
12218
+ }
12219
+ // uint (0x01)
12220
+ case 1: {
12221
+ let value = 0n;
12222
+ for (let i = 1; i < 17 && i < bytes.length; i++) {
12223
+ value = value << 8n | BigInt(bytes[i]);
12224
+ }
12225
+ return { value };
12226
+ }
12227
+ // buffer (0x02)
12228
+ case 2: {
12229
+ const len = bytes[1] << 24 | bytes[2] << 16 | bytes[3] << 8 | bytes[4];
12230
+ const bufValue = bytes.slice(5, 5 + len);
12231
+ return { value: "0x" + bytesToHex(bufValue) };
12232
+ }
12233
+ // bool true (0x03)
12234
+ case 3:
12235
+ return true;
12236
+ // bool false (0x04)
12237
+ case 4:
12238
+ return false;
12239
+ // optional none (0x09)
12240
+ case 9:
12241
+ return null;
12242
+ // optional some (0x0a)
12243
+ case 10:
12244
+ return this.parseClarityValue("0x" + bytesToHex(bytes.slice(1)));
12245
+ // response ok (0x07)
12246
+ case 7:
12247
+ return this.parseClarityValue("0x" + bytesToHex(bytes.slice(1)));
12248
+ // response err (0x08)
12249
+ case 8: {
12250
+ const errVal = this.parseClarityValue("0x" + bytesToHex(bytes.slice(1)));
12251
+ throw new Error(`Clarity error: ${JSON.stringify(errVal)}`);
12252
+ }
12253
+ // tuple (0x0c)
12254
+ case 12: {
12255
+ return { value: hex };
12256
+ }
12257
+ default:
12258
+ return { value: hex };
12259
+ }
12260
+ }
12261
+ };
12262
+
12263
+ // src/presets.ts
12264
+ var CHAIN_NAMES = {
12265
+ // EVM L2s (Hub-capable)
12266
+ BASE: "base",
12267
+ OPTIMISM: "optimism",
12268
+ ARBITRUM: "arbitrum",
12269
+ SCROLL: "scroll",
12270
+ BLAST: "blast",
12271
+ MANTLE: "mantle",
12272
+ // EVM L1s
12273
+ ETHEREUM: "ethereum",
12274
+ POLYGON: "polygon",
12275
+ BSC: "bsc",
12276
+ AVALANCHE: "avalanche",
12277
+ FANTOM: "fantom",
12278
+ CELO: "celo",
12279
+ MOONBEAM: "moonbeam",
12280
+ // Non-EVM
12281
+ SOLANA: "solana",
12282
+ APTOS: "aptos",
12283
+ SUI: "sui",
12284
+ STARKNET: "starknet",
12285
+ STACKS: "stacks",
12286
+ NEAR: "near",
12287
+ SEI: "sei"
12288
+ };
12289
+ var CHAIN_PRESETS = {
12290
+ // ────────────────────────────────────────────────────────────────────────
12291
+ // BASE - Primary Hub Chain
12292
+ // ────────────────────────────────────────────────────────────────────────
12293
+ base: {
12294
+ displayName: "Base",
12295
+ type: "evm",
12296
+ canBeHub: true,
12297
+ testnet: {
12298
+ name: "Base Sepolia",
12299
+ chainId: 84532,
12300
+ wormholeChainId: 10004,
12301
+ rpcUrl: "https://sepolia.base.org",
12302
+ explorerUrl: "https://sepolia.basescan.org",
12303
+ isEvm: true,
12304
+ contracts: {
12305
+ hub: "0x66D87dE68327f48A099c5B9bE97020Feab9a7c82",
12306
+ vaultFactory: "0xCFaEb5652aa2Ee60b2229dC8895B4159749C7e53",
12307
+ vaultImplementation: "0x0d13367C16c6f0B24eD275CC67C7D9f42878285c",
12308
+ wormholeCoreBridge: "0x79A1027a6A159502049F10906D333EC57E95F083",
12309
+ tokenBridge: "0x86F55A04690fd7815A3D802bD587e83eA888B239"
12310
+ }
12311
+ },
12312
+ mainnet: {
12313
+ name: "Base",
12314
+ chainId: 8453,
12315
+ wormholeChainId: 30,
12316
+ rpcUrl: "https://mainnet.base.org",
12317
+ explorerUrl: "https://basescan.org",
12318
+ isEvm: true,
12319
+ contracts: {
12320
+ // TODO: Deploy mainnet contracts
12321
+ wormholeCoreBridge: "0xbebdb6C8ddC678FfA9f8748f85C815C556Dd8ac6",
12322
+ tokenBridge: "0x8d2de8d2f73F1F4cAB472AC9A881C9b123C79627"
12323
+ }
12324
+ }
12325
+ },
12326
+ // ────────────────────────────────────────────────────────────────────────
12327
+ // OPTIMISM - Secondary Hub / Spoke
12328
+ // ────────────────────────────────────────────────────────────────────────
12329
+ optimism: {
12330
+ displayName: "Optimism",
12331
+ type: "evm",
12332
+ canBeHub: true,
12333
+ testnet: {
12334
+ name: "Optimism Sepolia",
12335
+ chainId: 11155420,
12336
+ wormholeChainId: 10005,
12337
+ rpcUrl: "https://sepolia.optimism.io",
12338
+ explorerUrl: "https://sepolia-optimism.etherscan.io",
12339
+ isEvm: true,
12340
+ contracts: {
12341
+ vaultFactory: "0xA5653d54079ABeCe780F8d9597B2bc4B09fe464A",
12342
+ vaultImplementation: "0x8099b1406485d2255ff89Ce5Ea18520802AFC150",
12343
+ wormholeCoreBridge: "0x31377888146f3253211EFEf5c676D41ECe7D58Fe",
12344
+ tokenBridge: "0x99737Ec4B815d816c49A385943baf0380e75c0Ac"
12345
+ }
12346
+ },
12347
+ mainnet: {
12348
+ name: "Optimism",
12349
+ chainId: 10,
12350
+ wormholeChainId: 24,
12351
+ rpcUrl: "https://mainnet.optimism.io",
12352
+ explorerUrl: "https://optimistic.etherscan.io",
12353
+ isEvm: true,
12354
+ contracts: {
12355
+ wormholeCoreBridge: "0xEe91C335eab126dF5fDB3797EA9d6aD93aeC9722",
12356
+ tokenBridge: "0x1D68124e65faFC907325e3EDbF8c4d84499DAa8b"
12357
+ }
12358
+ }
12359
+ },
12360
+ // ────────────────────────────────────────────────────────────────────────
12361
+ // ARBITRUM
12362
+ // ────────────────────────────────────────────────────────────────────────
12363
+ arbitrum: {
12364
+ displayName: "Arbitrum",
12365
+ type: "evm",
12366
+ canBeHub: true,
12367
+ testnet: {
12368
+ name: "Arbitrum Sepolia",
12369
+ chainId: 421614,
12370
+ wormholeChainId: 10003,
12371
+ rpcUrl: "https://sepolia-rollup.arbitrum.io/rpc",
12372
+ explorerUrl: "https://sepolia.arbiscan.io",
12373
+ isEvm: true,
12374
+ contracts: {
12375
+ vaultFactory: "0xd36D3D5DB59d78f1E33813490F72DABC15C9B07c",
12376
+ vaultImplementation: "0xB10ACf39eBF17fc33F722cBD955b7aeCB0611bc4",
12377
+ wormholeCoreBridge: "0x6b9C8671cdDC8dEab9c719bB87cBd3e782bA6a35",
12378
+ tokenBridge: "0xC7A204bDBFe983FCD8d8E61D02b475D4073fF97e"
12379
+ }
12380
+ },
12381
+ mainnet: {
12382
+ name: "Arbitrum",
12383
+ chainId: 42161,
12384
+ wormholeChainId: 23,
12385
+ rpcUrl: "https://arb1.arbitrum.io/rpc",
12386
+ explorerUrl: "https://arbiscan.io",
12387
+ isEvm: true,
12388
+ contracts: {
12389
+ wormholeCoreBridge: "0xa5f208e072434bC67592E4C49C1B991BA79BCA46",
12390
+ tokenBridge: "0x0b2402144Bb366A632D14B83F244D2e0e21bD39c"
12391
+ }
12392
+ }
12393
+ },
12394
+ // ────────────────────────────────────────────────────────────────────────
12395
+ // ETHEREUM
12396
+ // ────────────────────────────────────────────────────────────────────────
12397
+ ethereum: {
12398
+ displayName: "Ethereum",
12399
+ type: "evm",
12400
+ canBeHub: false,
12401
+ testnet: {
12402
+ name: "Sepolia",
12403
+ chainId: 11155111,
12404
+ wormholeChainId: 10002,
12405
+ rpcUrl: "https://ethereum-sepolia-rpc.publicnode.com",
12406
+ explorerUrl: "https://sepolia.etherscan.io",
12407
+ isEvm: true,
12408
+ contracts: {
12409
+ vaultFactory: "0x07F608AFf6d63b68029488b726d895c4Bb593038",
12410
+ vaultImplementation: "0xD66153fccFB6731fB6c4944FbD607ba86A76a1f6",
12411
+ wormholeCoreBridge: "0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78",
12412
+ tokenBridge: "0xDB5492265f6038831E89f495670FF909aDe94bd9"
12413
+ }
12414
+ },
12415
+ mainnet: {
12416
+ name: "Ethereum",
12417
+ chainId: 1,
11375
12418
  wormholeChainId: 2,
11376
12419
  rpcUrl: "https://eth.llamarpc.com",
11377
12420
  explorerUrl: "https://etherscan.io",
@@ -11835,6 +12878,49 @@ var CHAIN_PRESETS = {
11835
12878
  }
11836
12879
  },
11837
12880
  // ────────────────────────────────────────────────────────────────────────
12881
+ // STACKS
12882
+ // ────────────────────────────────────────────────────────────────────────
12883
+ stacks: {
12884
+ displayName: "Stacks",
12885
+ type: "stacks",
12886
+ canBeHub: false,
12887
+ testnet: {
12888
+ name: "Stacks Testnet",
12889
+ chainId: 2147483648,
12890
+ // CAIP-2: stacks:2147483648
12891
+ wormholeChainId: 60,
12892
+ // Official Wormhole chain ID for Stacks
12893
+ rpcUrl: "https://api.testnet.hiro.so",
12894
+ explorerUrl: "https://explorer.hiro.so/?chain=testnet",
12895
+ isEvm: false,
12896
+ contracts: {
12897
+ // Spoke contract: identity + session management
12898
+ hub: "ST1CKNN84MDPCJRQDHDCY34GMRKQ3TASNNDJQDRTN.veridex-spoke",
12899
+ // Vault contract: STX/sBTC custody
12900
+ vaultFactory: "ST1CKNN84MDPCJRQDHDCY34GMRKQ3TASNNDJQDRTN.veridex-vault",
12901
+ wormholeCoreBridge: "",
12902
+ // Phase 2: Wormhole integration contracts
12903
+ wormholeVerifier: "ST1CKNN84MDPCJRQDHDCY34GMRKQ3TASNNDJQDRTN.veridex-wormhole-verifier",
12904
+ vaultVaa: "ST1CKNN84MDPCJRQDHDCY34GMRKQ3TASNNDJQDRTN.veridex-vault-vaa"
12905
+ },
12906
+ hubChainId: 10004
12907
+ // Base Sepolia
12908
+ },
12909
+ mainnet: {
12910
+ name: "Stacks",
12911
+ chainId: 1,
12912
+ // CAIP-2: stacks:1
12913
+ wormholeChainId: 60,
12914
+ rpcUrl: "https://api.hiro.so",
12915
+ explorerUrl: "https://explorer.hiro.so",
12916
+ isEvm: false,
12917
+ contracts: {
12918
+ // TODO: Deploy mainnet contracts
12919
+ wormholeCoreBridge: ""
12920
+ }
12921
+ }
12922
+ },
12923
+ // ────────────────────────────────────────────────────────────────────────
11838
12924
  // NEAR
11839
12925
  // ────────────────────────────────────────────────────────────────────────
11840
12926
  near: {
@@ -11957,6 +13043,13 @@ function createChainClient(chain, network, customRpcUrl) {
11957
13043
  wormholeChainId: config.wormholeChainId,
11958
13044
  network: network === "testnet" ? "sepolia" : "mainnet"
11959
13045
  });
13046
+ case "stacks":
13047
+ return new StacksClient({
13048
+ rpcUrl,
13049
+ spokeContractAddress: config.contracts.hub || void 0,
13050
+ wormholeChainId: config.wormholeChainId,
13051
+ network
13052
+ });
11960
13053
  case "near":
11961
13054
  case "cosmos":
11962
13055
  throw new Error(`Chain type "${preset.type}" is not yet supported. Coming soon!`);
@@ -12011,6 +13104,329 @@ function createSessionSDK(chain = "base", config = {}) {
12011
13104
  return createSDK(chain, config);
12012
13105
  }
12013
13106
 
13107
+ // src/core/CrossOriginAuth.ts
13108
+ var DEFAULT_AUTH_PORTAL_URL = "https://auth.veridex.network";
13109
+ var DEFAULT_RELAYER_URL = "https://amused-kameko-veridex-demo-37453117.koyeb.app/api/v1";
13110
+ var AUTH_MESSAGE_TYPES = {
13111
+ AUTH_REQUEST: "VERIDEX_AUTH_REQUEST",
13112
+ AUTH_RESPONSE: "VERIDEX_AUTH_RESPONSE",
13113
+ AUTH_ERROR: "VERIDEX_AUTH_ERROR"
13114
+ };
13115
+ var CrossOriginAuth = class {
13116
+ config;
13117
+ passkeyManager = null;
13118
+ constructor(config = {}) {
13119
+ this.config = {
13120
+ rpId: config.rpId ?? VERIDEX_RP_ID,
13121
+ authPortalUrl: config.authPortalUrl ?? DEFAULT_AUTH_PORTAL_URL,
13122
+ relayerUrl: config.relayerUrl ?? DEFAULT_RELAYER_URL,
13123
+ mode: config.mode ?? "popup",
13124
+ popupFeatures: config.popupFeatures ?? "width=500,height=600,left=100,top=100",
13125
+ timeout: config.timeout ?? 12e4,
13126
+ // 2 minutes
13127
+ redirectUri: config.redirectUri ?? (typeof window !== "undefined" ? window.location.href : "")
13128
+ };
13129
+ }
13130
+ // ========================================================================
13131
+ // Browser Capability Detection
13132
+ // ========================================================================
13133
+ /**
13134
+ * Check if the browser supports Related Origin Requests.
13135
+ * This is a WebAuthn Level 3 feature that allows using passkeys across different domains.
13136
+ */
13137
+ async supportsRelatedOrigins() {
13138
+ if (typeof window === "undefined" || !window.PublicKeyCredential) {
13139
+ return false;
13140
+ }
13141
+ if ("getClientCapabilities" in PublicKeyCredential) {
13142
+ try {
13143
+ const getCapabilities = PublicKeyCredential.getClientCapabilities;
13144
+ const capabilities = await getCapabilities();
13145
+ return capabilities?.relatedOrigins === true;
13146
+ } catch {
13147
+ return false;
13148
+ }
13149
+ }
13150
+ return false;
13151
+ }
13152
+ /**
13153
+ * Check if WebAuthn is supported at all.
13154
+ */
13155
+ isSupported() {
13156
+ return PasskeyManager.isSupported();
13157
+ }
13158
+ // ========================================================================
13159
+ // Related Origin Requests (Direct Method)
13160
+ // ========================================================================
13161
+ /**
13162
+ * Authenticate using Related Origin Requests.
13163
+ * This allows using a passkey registered at veridex.network from any origin
13164
+ * listed in the /.well-known/webauthn file.
13165
+ *
13166
+ * @throws If browser doesn't support Related Origin Requests
13167
+ * @throws If the current origin isn't listed in veridex.network's well-known file
13168
+ */
13169
+ async authenticate(challenge) {
13170
+ const manager = new PasskeyManager({
13171
+ rpId: this.config.rpId,
13172
+ rpName: "Veridex Protocol"
13173
+ });
13174
+ return manager.authenticate(challenge);
13175
+ }
13176
+ /**
13177
+ * Register a new passkey with veridex.network as the RP.
13178
+ * This should only be called from veridex.network origins.
13179
+ */
13180
+ async register(username, displayName) {
13181
+ const manager = new PasskeyManager({
13182
+ rpId: this.config.rpId,
13183
+ rpName: "Veridex Protocol"
13184
+ });
13185
+ return manager.register(username, displayName);
13186
+ }
13187
+ // ========================================================================
13188
+ // Auth Portal Flow (Popup/Redirect)
13189
+ // ========================================================================
13190
+ /**
13191
+ * Authenticate via the Veridex Auth Portal.
13192
+ * Opens a popup or redirects to auth.veridex.network where the user
13193
+ * signs with their passkey, then returns a session to the calling app.
13194
+ */
13195
+ async connectWithVeridex() {
13196
+ if (this.config.mode === "popup") {
13197
+ return this.authenticateViaPopup();
13198
+ } else {
13199
+ return this.initiateRedirectAuth();
13200
+ }
13201
+ }
13202
+ /**
13203
+ * Popup-based authentication flow.
13204
+ */
13205
+ async authenticateViaPopup() {
13206
+ return new Promise((resolve, reject) => {
13207
+ const state = this.generateState();
13208
+ const authUrl = new URL("/auth", this.config.authPortalUrl);
13209
+ authUrl.searchParams.set("state", state);
13210
+ authUrl.searchParams.set("origin", window.location.origin);
13211
+ authUrl.searchParams.set("callback", "postMessage");
13212
+ const popup = window.open(
13213
+ authUrl.toString(),
13214
+ "veridex-auth",
13215
+ this.config.popupFeatures
13216
+ );
13217
+ if (!popup) {
13218
+ reject(new Error("Failed to open auth popup. Please allow popups for this site."));
13219
+ return;
13220
+ }
13221
+ const timeoutId = setTimeout(() => {
13222
+ popup.close();
13223
+ window.removeEventListener("message", messageHandler);
13224
+ reject(new Error("Authentication timed out"));
13225
+ }, this.config.timeout);
13226
+ const messageHandler = (event) => {
13227
+ if (!event.origin.includes("veridex.network")) {
13228
+ return;
13229
+ }
13230
+ const { type, payload } = event.data;
13231
+ if (type === AUTH_MESSAGE_TYPES.AUTH_RESPONSE) {
13232
+ clearTimeout(timeoutId);
13233
+ window.removeEventListener("message", messageHandler);
13234
+ popup.close();
13235
+ resolve(payload);
13236
+ } else if (type === AUTH_MESSAGE_TYPES.AUTH_ERROR) {
13237
+ clearTimeout(timeoutId);
13238
+ window.removeEventListener("message", messageHandler);
13239
+ popup.close();
13240
+ const error = payload;
13241
+ reject(new Error(error.error));
13242
+ }
13243
+ };
13244
+ window.addEventListener("message", messageHandler);
13245
+ });
13246
+ }
13247
+ /**
13248
+ * Redirect-based authentication flow.
13249
+ * Stores state in sessionStorage and redirects to auth portal.
13250
+ */
13251
+ async initiateRedirectAuth() {
13252
+ const state = this.generateState();
13253
+ sessionStorage.setItem("veridex_auth_state", state);
13254
+ sessionStorage.setItem("veridex_auth_redirect", this.config.redirectUri);
13255
+ const authUrl = new URL("/auth", this.config.authPortalUrl);
13256
+ authUrl.searchParams.set("state", state);
13257
+ authUrl.searchParams.set("redirect_uri", this.config.redirectUri);
13258
+ authUrl.searchParams.set("origin", window.location.origin);
13259
+ window.location.href = authUrl.toString();
13260
+ return new Promise(() => {
13261
+ });
13262
+ }
13263
+ /**
13264
+ * Complete redirect-based authentication.
13265
+ * Call this on your callback page to extract the session from URL params.
13266
+ */
13267
+ completeRedirectAuth() {
13268
+ const params = new URLSearchParams(window.location.search);
13269
+ const session = params.get("session");
13270
+ const state = params.get("state");
13271
+ const error = params.get("error");
13272
+ const storedState = sessionStorage.getItem("veridex_auth_state");
13273
+ if (state !== storedState) {
13274
+ throw new Error("Invalid auth state - possible CSRF attack");
13275
+ }
13276
+ sessionStorage.removeItem("veridex_auth_state");
13277
+ sessionStorage.removeItem("veridex_auth_redirect");
13278
+ window.history.replaceState({}, "", window.location.pathname);
13279
+ if (error) {
13280
+ throw new Error(error);
13281
+ }
13282
+ if (!session) {
13283
+ return null;
13284
+ }
13285
+ return JSON.parse(atob(session));
13286
+ }
13287
+ // ========================================================================
13288
+ // Utility Methods
13289
+ // ========================================================================
13290
+ /**
13291
+ * Generate a random state string for CSRF protection.
13292
+ */
13293
+ generateState() {
13294
+ const array = new Uint8Array(32);
13295
+ crypto.getRandomValues(array);
13296
+ return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
13297
+ }
13298
+ /**
13299
+ * Get the RP ID being used.
13300
+ */
13301
+ getRpId() {
13302
+ return this.config.rpId;
13303
+ }
13304
+ /**
13305
+ * Get the auth portal URL.
13306
+ */
13307
+ getAuthPortalUrl() {
13308
+ return this.config.authPortalUrl;
13309
+ }
13310
+ // ========================================================================
13311
+ // Server-Side Session Tokens (ADR-0018)
13312
+ // ========================================================================
13313
+ /**
13314
+ * Create a server-validated session token via the relayer.
13315
+ * Call this after authenticating (via ROR or auth portal) to get a
13316
+ * server-side session that the relayer can verify on subsequent requests.
13317
+ */
13318
+ async createServerSession(session, options) {
13319
+ const keyHash = session.credential?.keyHash;
13320
+ if (!keyHash) {
13321
+ throw new Error("Session must include credential with keyHash");
13322
+ }
13323
+ const response = await fetch(`${this.config.relayerUrl}/session/create`, {
13324
+ method: "POST",
13325
+ headers: { "Content-Type": "application/json" },
13326
+ body: JSON.stringify({
13327
+ keyHash,
13328
+ appOrigin: typeof window !== "undefined" ? window.location.origin : "",
13329
+ sessionPublicKey: session.sessionPublicKey || "",
13330
+ permissions: options?.permissions ?? ["read", "transfer"],
13331
+ expiresInMs: options?.expiresInMs ?? 36e5,
13332
+ signature: session.signature
13333
+ })
13334
+ });
13335
+ if (!response.ok) {
13336
+ const data2 = await response.json().catch(() => ({ error: "Unknown error" }));
13337
+ throw new Error(data2.error || `Failed to create server session: ${response.status}`);
13338
+ }
13339
+ const data = await response.json();
13340
+ return data.session;
13341
+ }
13342
+ /**
13343
+ * Validate an existing server session token.
13344
+ * Returns the session details if valid, null if expired/revoked.
13345
+ */
13346
+ async validateServerSession(sessionId) {
13347
+ const response = await fetch(`${this.config.relayerUrl}/session/${encodeURIComponent(sessionId)}`);
13348
+ if (!response.ok) {
13349
+ return null;
13350
+ }
13351
+ const data = await response.json();
13352
+ if (!data.valid) {
13353
+ return null;
13354
+ }
13355
+ return data.session;
13356
+ }
13357
+ /**
13358
+ * Revoke a server session token.
13359
+ */
13360
+ async revokeServerSession(sessionId) {
13361
+ const response = await fetch(`${this.config.relayerUrl}/session/${encodeURIComponent(sessionId)}`, {
13362
+ method: "DELETE"
13363
+ });
13364
+ return response.ok;
13365
+ }
13366
+ /**
13367
+ * Full authentication flow: authenticate + create server session.
13368
+ * Automatically detects ROR support and falls back to auth portal.
13369
+ */
13370
+ async authenticateAndCreateSession(options) {
13371
+ let session;
13372
+ if (await this.supportsRelatedOrigins()) {
13373
+ const result = await this.authenticate();
13374
+ session = {
13375
+ address: "",
13376
+ sessionPublicKey: "",
13377
+ expiresAt: Date.now() + (options?.expiresInMs ?? 36e5),
13378
+ signature: result.signature,
13379
+ credential: result.credential
13380
+ };
13381
+ } else {
13382
+ session = await this.connectWithVeridex();
13383
+ }
13384
+ const serverSession = await this.createServerSession(session, options);
13385
+ session.serverSessionId = serverSession.id;
13386
+ return { session, serverSession };
13387
+ }
13388
+ };
13389
+ function createCrossOriginAuth(config) {
13390
+ return new CrossOriginAuth(config);
13391
+ }
13392
+ function sendAuthResponse(session, targetOrigin) {
13393
+ if (window.opener) {
13394
+ window.opener.postMessage({
13395
+ type: AUTH_MESSAGE_TYPES.AUTH_RESPONSE,
13396
+ payload: session,
13397
+ origin: window.location.origin
13398
+ }, targetOrigin);
13399
+ } else {
13400
+ const redirectUri = new URLSearchParams(window.location.search).get("redirect_uri");
13401
+ const state = new URLSearchParams(window.location.search).get("state");
13402
+ if (redirectUri) {
13403
+ const url = new URL(redirectUri);
13404
+ url.searchParams.set("session", btoa(JSON.stringify(session)));
13405
+ url.searchParams.set("state", state || "");
13406
+ window.location.href = url.toString();
13407
+ }
13408
+ }
13409
+ }
13410
+ function sendAuthError(error, code, targetOrigin) {
13411
+ if (window.opener) {
13412
+ window.opener.postMessage({
13413
+ type: AUTH_MESSAGE_TYPES.AUTH_ERROR,
13414
+ payload: { error, code },
13415
+ origin: window.location.origin
13416
+ }, targetOrigin);
13417
+ } else {
13418
+ const redirectUri = new URLSearchParams(window.location.search).get("redirect_uri");
13419
+ const state = new URLSearchParams(window.location.search).get("state");
13420
+ if (redirectUri) {
13421
+ const url = new URL(redirectUri);
13422
+ url.searchParams.set("error", error);
13423
+ url.searchParams.set("error_code", code);
13424
+ url.searchParams.set("state", state || "");
13425
+ window.location.href = url.toString();
13426
+ }
13427
+ }
13428
+ }
13429
+
12014
13430
  // src/sessions/index.ts
12015
13431
  var import_ethers19 = require("ethers");
12016
13432
 
@@ -13022,6 +14438,72 @@ var EVMHubClientAdapter = class {
13022
14438
  }
13023
14439
  };
13024
14440
 
14441
+ // src/chains/stacks/StacksPostConditions.ts
14442
+ function buildStxWithdrawalPostConditions(contractPrincipal, amount) {
14443
+ return [
14444
+ {
14445
+ type: "stx",
14446
+ principal: contractPrincipal,
14447
+ comparison: "eq",
14448
+ amount
14449
+ }
14450
+ ];
14451
+ }
14452
+ function buildStxDepositPostConditions(senderPrincipal, amount) {
14453
+ return [
14454
+ {
14455
+ type: "stx",
14456
+ principal: senderPrincipal,
14457
+ comparison: "eq",
14458
+ amount
14459
+ }
14460
+ ];
14461
+ }
14462
+ function buildSbtcWithdrawalPostConditions(contractPrincipal, amount, sbtcContractAddress = "SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4", sbtcContractName = "sbtc-token") {
14463
+ return [
14464
+ {
14465
+ type: "ft",
14466
+ principal: contractPrincipal,
14467
+ comparison: "eq",
14468
+ amount,
14469
+ contractAddress: sbtcContractAddress,
14470
+ contractName: sbtcContractName,
14471
+ tokenName: "sbtc-token"
14472
+ }
14473
+ ];
14474
+ }
14475
+ function buildExecutePostConditions(actionType, contractPrincipal, amount) {
14476
+ switch (actionType) {
14477
+ case 1:
14478
+ return buildStxWithdrawalPostConditions(contractPrincipal, amount);
14479
+ case 2:
14480
+ return buildSbtcWithdrawalPostConditions(contractPrincipal, amount);
14481
+ default:
14482
+ return [];
14483
+ }
14484
+ }
14485
+ function validatePostConditions(postConditions, expectedAmount) {
14486
+ if (postConditions.length === 0) {
14487
+ return {
14488
+ valid: false,
14489
+ error: "No post-conditions attached. Asset transfers require post-conditions for safety."
14490
+ };
14491
+ }
14492
+ const hasMatchingAmount = postConditions.some((pc) => {
14493
+ if (pc.type === "stx" || pc.type === "ft") {
14494
+ return pc.amount === expectedAmount && pc.comparison === "eq";
14495
+ }
14496
+ return false;
14497
+ });
14498
+ if (!hasMatchingAmount) {
14499
+ return {
14500
+ valid: false,
14501
+ error: `No post-condition matches expected amount ${expectedAmount}. Ensure exact-match post-conditions are attached.`
14502
+ };
14503
+ }
14504
+ return { valid: true };
14505
+ }
14506
+
13025
14507
  // src/constants/errors.ts
13026
14508
  var ERROR_RANGES = {
13027
14509
  /** Core protocol errors (paused, unauthorized, limits, etc.) */
@@ -13272,6 +14754,8 @@ function getSuggestedAction(code) {
13272
14754
  ACTION_TRANSFER,
13273
14755
  ACTION_TYPES,
13274
14756
  ARBITRUM_SEPOLIA_TOKENS,
14757
+ AUTH_MESSAGE_TYPES,
14758
+ AptosClient,
13275
14759
  BASE_SEPOLIA_TOKENS,
13276
14760
  BalanceManager,
13277
14761
  CHAIN_DISPLAY_INFO,
@@ -13281,11 +14765,15 @@ function getSuggestedAction(code) {
13281
14765
  CONSISTENCY_LEVELS,
13282
14766
  ChainDetector,
13283
14767
  CrossChainManager,
14768
+ CrossOriginAuth,
14769
+ DEFAULT_AUTH_PORTAL_URL,
13284
14770
  DEFAULT_REFRESH_BUFFER,
14771
+ DEFAULT_RELAYER_URL,
13285
14772
  DEFAULT_SESSION_DURATION,
13286
14773
  ERROR_MESSAGES,
13287
14774
  ERROR_RANGES,
13288
14775
  ETHEREUM_SEPOLIA_TOKENS,
14776
+ EVMClient,
13289
14777
  EVMHubClientAdapter,
13290
14778
  EVM_ZERO_ADDRESS,
13291
14779
  GUARDIAN_CONFIG,
@@ -13304,9 +14792,14 @@ function getSuggestedAction(code) {
13304
14792
  QueryHubStateError,
13305
14793
  QueryPortfolioError,
13306
14794
  RelayerClient,
14795
+ STACKS_ACTION_TYPES,
13307
14796
  SessionError,
13308
14797
  SessionManager,
14798
+ SolanaClient,
13309
14799
  SpendingLimitsManager,
14800
+ StacksClient,
14801
+ StarknetClient,
14802
+ SuiClient,
13310
14803
  TESTNET_CHAINS,
13311
14804
  TOKEN_REGISTRY,
13312
14805
  TransactionParser,
@@ -13314,6 +14807,7 @@ function getSuggestedAction(code) {
13314
14807
  VAULT_ABI,
13315
14808
  VAULT_FACTORY_ABI,
13316
14809
  VERIDEX_ERRORS,
14810
+ VERIDEX_RP_ID,
13317
14811
  VeridexSDK,
13318
14812
  WORMHOLE_API,
13319
14813
  WORMHOLE_CHAIN_IDS,
@@ -13327,11 +14821,16 @@ function getSuggestedAction(code) {
13327
14821
  base64URLEncode,
13328
14822
  buildChallenge,
13329
14823
  buildGaslessChallenge,
14824
+ buildSbtcWithdrawalPostConditions,
14825
+ buildStacksExecutePostConditions,
14826
+ buildStxDepositPostConditions,
14827
+ buildStxWithdrawalPostConditions,
13330
14828
  calculatePercentage,
13331
14829
  computeKeyHash,
13332
14830
  computeSessionKeyHash,
13333
14831
  createAuditEntry,
13334
14832
  createChainDetector,
14833
+ createCrossOriginAuth,
13335
14834
  createGasSponsor,
13336
14835
  createGaslessMessageHash,
13337
14836
  createHubSDK,
@@ -13351,6 +14850,7 @@ function getSuggestedAction(code) {
13351
14850
  decodeTransferAction,
13352
14851
  decrypt,
13353
14852
  deriveEncryptionKey,
14853
+ detectRpId,
13354
14854
  emitterToEvmAddress,
13355
14855
  encodeAptosTransferAction,
13356
14856
  encodeBridgeAction,
@@ -13389,6 +14889,10 @@ function getSuggestedAction(code) {
13389
14889
  getExplorerUrl,
13390
14890
  getHubChains,
13391
14891
  getSequenceFromTxReceipt,
14892
+ getStacksContractPrincipal,
14893
+ getStacksExplorerAddressUrl,
14894
+ getStacksExplorerTxUrl,
14895
+ getStacksNetworkFromAddress,
13392
14896
  getSuggestedAction,
13393
14897
  getSupportedChainIds,
13394
14898
  getSupportedChains,
@@ -13410,14 +14914,19 @@ function getSuggestedAction(code) {
13410
14914
  isQueryExecutionError,
13411
14915
  isQueryParsingError,
13412
14916
  isRetryableError,
14917
+ isStacksContractPrincipal,
13413
14918
  isValidBytes32,
13414
14919
  isValidEvmAddress,
14920
+ isValidStacksContractName,
14921
+ isValidStacksPrincipal,
14922
+ isValidStacksStandardPrincipal,
13415
14923
  isValidWormholeChainId,
13416
14924
  logTransactionSummary,
13417
14925
  normalizeEmitterAddress,
13418
14926
  padTo32Bytes,
13419
14927
  parseAmount,
13420
14928
  parseDERSignature,
14929
+ parseStacksContractPrincipal,
13421
14930
  parseVAA,
13422
14931
  parseVAABytes,
13423
14932
  parseVeridexError,
@@ -13425,12 +14934,26 @@ function getSuggestedAction(code) {
13425
14934
  queryHubState,
13426
14935
  queryPortfolio,
13427
14936
  retryWithBackoff,
14937
+ sendAuthError,
14938
+ sendAuthResponse,
13428
14939
  signWithSessionKey,
13429
14940
  solanaAddressToBytes32,
14941
+ stacksBuildExecuteHash,
14942
+ stacksBuildRegistrationHash,
14943
+ stacksBuildRevocationHash,
14944
+ stacksBuildSessionRegistrationHash,
14945
+ stacksBuildWithdrawalHash,
14946
+ stacksCompressPublicKey,
14947
+ stacksComputeKeyHash,
14948
+ stacksComputeKeyHashFromCoords,
14949
+ stacksDerToCompactSignature,
14950
+ stacksRsToCompactSignature,
14951
+ supportsRelatedOrigins,
13430
14952
  supportsRelayer,
13431
14953
  trimTo20Bytes,
13432
14954
  validateEmitter,
13433
14955
  validateSessionConfig,
14956
+ validateStacksPostConditions,
13434
14957
  verifySessionSignature,
13435
14958
  waitForGuardianSignatures
13436
14959
  });