opacity-issuer-core 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -524,12 +524,12 @@ function createPurchasesNamespace(options = {}) {
524
524
  "Payment token returned false from approve."
525
525
  );
526
526
  const wallet = await walletFor(params);
527
- const hash = await wallet.writeContract({
527
+ const hash2 = await wallet.writeContract({
528
528
  ...step.request,
529
529
  account: wallet.account ?? params.buyer,
530
530
  chain: sendChain(wallet, params.deployment.chainId)
531
531
  });
532
- return { ...params, kind: "approval", step: step.kind, approvedAmount: step.amount, hash };
532
+ return { ...params, kind: "approval", step: step.kind, approvedAmount: step.amount, hash: hash2 };
533
533
  });
534
534
  }
535
535
  async function sendPurchase(input) {
@@ -538,17 +538,17 @@ function createPurchasesNamespace(options = {}) {
538
538
  await walletFor(params);
539
539
  const prepared = await simulatePurchase(params);
540
540
  const wallet = await walletFor(params);
541
- const hash = await wallet.writeContract({
541
+ const hash2 = await wallet.writeContract({
542
542
  ...prepared.request,
543
543
  account: wallet.account ?? params.buyer,
544
544
  chain: sendChain(wallet, params.deployment.chainId)
545
545
  });
546
- return { ...params, kind: "purchase", hash };
546
+ return { ...params, kind: "purchase", hash: hash2 };
547
547
  });
548
548
  }
549
549
  async function receiptFor(submitted, options2) {
550
550
  const params = purchaseParams(submitted);
551
- const hash = bytes32(submitted.hash, "hash", true);
551
+ const hash2 = bytes32(submitted.hash, "hash", true);
552
552
  for (const [name, value] of Object.entries(options2)) {
553
553
  if (value !== void 0 && (!Number.isSafeInteger(value) || value <= 0)) {
554
554
  throw new OpacityPurchaseError("invalid-input", `${name} must be a positive safe integer.`);
@@ -557,7 +557,7 @@ function createPurchasesNamespace(options = {}) {
557
557
  const client = await clientFor(params.deployment);
558
558
  let replacementReason;
559
559
  const receipt = await client.waitForTransactionReceipt({
560
- hash,
560
+ hash: hash2,
561
561
  ...options2.confirmations === void 0 ? {} : { confirmations: options2.confirmations },
562
562
  ...options2.timeout === void 0 ? {} : { timeout: options2.timeout },
563
563
  ...options2.pollingInterval === void 0 ? {} : { pollingInterval: options2.pollingInterval },
@@ -565,10 +565,10 @@ function createPurchasesNamespace(options = {}) {
565
565
  replacementReason = replacement.reason;
566
566
  }
567
567
  });
568
- const replaced = receipt.transactionHash.toLowerCase() !== hash.toLowerCase();
568
+ const replaced = receipt.transactionHash.toLowerCase() !== hash2.toLowerCase();
569
569
  return {
570
570
  status: replaced || replacementReason !== void 0 ? "replaced" : receipt.status === "reverted" ? "reverted" : "unverified",
571
- submittedHash: hash,
571
+ submittedHash: hash2,
572
572
  hash: receipt.transactionHash,
573
573
  receipt,
574
574
  ...replacementReason === void 0 ? {} : { replacementReason }
@@ -6246,14 +6246,14 @@ function createTransfersNamespace(options = {}) {
6246
6246
  rpcUrls: { default: { http: [] } }
6247
6247
  });
6248
6248
  const { gas, gasPrice, maxFeePerGas, maxPriorityFeePerGas } = prepared.gas;
6249
- const hash = await wallet.writeContract({
6249
+ const hash2 = await wallet.writeContract({
6250
6250
  ...prepared.request,
6251
6251
  account: wallet.account ?? params.sender,
6252
6252
  chain: chain2,
6253
6253
  gas,
6254
6254
  ...gasPrice === void 0 ? { maxFeePerGas, maxPriorityFeePerGas } : { gasPrice }
6255
6255
  });
6256
- return Object.freeze({ ...params, kind: "transfer", hash });
6256
+ return Object.freeze({ ...params, kind: "transfer", hash: hash2 });
6257
6257
  } finally {
6258
6258
  sendsInProgress.delete(key);
6259
6259
  }
@@ -6262,8 +6262,8 @@ function createTransfersNamespace(options = {}) {
6262
6262
  if (submitted?.kind !== "transfer")
6263
6263
  throw new OpacityTransferError("invalid-input", "Expected a submitted transfer record.");
6264
6264
  const params = transferParams(submitted);
6265
- const hash = submitted.hash;
6266
- if (typeof hash !== "string" || !/^0x[\da-fA-F]{64}$/.test(hash) || /^0x0+$/.test(hash)) {
6265
+ const hash2 = submitted.hash;
6266
+ if (typeof hash2 !== "string" || !/^0x[\da-fA-F]{64}$/.test(hash2) || /^0x0+$/.test(hash2)) {
6267
6267
  throw new OpacityTransferError("invalid-input", "hash must be nonzero bytes32.");
6268
6268
  }
6269
6269
  const waitOptions3 = {
@@ -6282,7 +6282,7 @@ function createTransfersNamespace(options = {}) {
6282
6282
  const client = await clientFor(params.deployment);
6283
6283
  let replacementReason;
6284
6284
  const received = await client.waitForTransactionReceipt({
6285
- hash,
6285
+ hash: hash2,
6286
6286
  ...waitOptions3,
6287
6287
  onReplaced: (replacement) => {
6288
6288
  replacementReason = replacement.reason;
@@ -6294,8 +6294,8 @@ function createTransfersNamespace(options = {}) {
6294
6294
  };
6295
6295
  await clientFor(params.deployment);
6296
6296
  const result = {
6297
- status: receipt.transactionHash.toLowerCase() !== hash.toLowerCase() || replacementReason !== void 0 ? "replaced" : receipt.status === "reverted" ? "reverted" : "unverified",
6298
- submittedHash: hash,
6297
+ status: receipt.transactionHash.toLowerCase() !== hash2.toLowerCase() || replacementReason !== void 0 ? "replaced" : receipt.status === "reverted" ? "reverted" : "unverified",
6298
+ submittedHash: hash2,
6299
6299
  hash: receipt.transactionHash,
6300
6300
  receipt,
6301
6301
  ...replacementReason === void 0 ? {} : { replacementReason }
@@ -6305,15 +6305,15 @@ function createTransfersNamespace(options = {}) {
6305
6305
  return result;
6306
6306
  let transaction;
6307
6307
  try {
6308
- transaction = { ...await client.getTransaction({ hash }) };
6308
+ transaction = { ...await client.getTransaction({ hash: hash2 }) };
6309
6309
  } catch {
6310
6310
  return result;
6311
6311
  }
6312
6312
  await clientFor(params.deployment);
6313
- if (transaction.chainId !== params.deployment.chainId || typeof transaction.hash !== "string" || transaction.hash.toLowerCase() !== hash.toLowerCase() || !sameAddress2(transaction.from, params.sender) || !sameAddress2(transaction.to, params.deployment.token) || transaction.value !== 0n || typeof transaction.input !== "string" || transaction.input.toLowerCase() !== buildTransferTransaction(params).data.toLowerCase())
6313
+ if (transaction.chainId !== params.deployment.chainId || typeof transaction.hash !== "string" || transaction.hash.toLowerCase() !== hash2.toLowerCase() || !sameAddress2(transaction.from, params.sender) || !sameAddress2(transaction.to, params.deployment.token) || transaction.value !== 0n || typeof transaction.input !== "string" || transaction.input.toLowerCase() !== buildTransferTransaction(params).data.toLowerCase())
6314
6314
  return result;
6315
6315
  for (const log of receipt.logs) {
6316
- if (!sameAddress2(log.address, params.deployment.token) || log.removed || log.transactionHash?.toLowerCase() !== hash.toLowerCase())
6316
+ if (!sameAddress2(log.address, params.deployment.token) || log.removed || log.transactionHash?.toLowerCase() !== hash2.toLowerCase())
6317
6317
  continue;
6318
6318
  try {
6319
6319
  const event = viem.decodeEventLog({
@@ -7199,6 +7199,357 @@ function createPolicyReplacementBatch(currentCount, policies) {
7199
7199
  return createPolicyBatch(changes);
7200
7200
  }
7201
7201
 
7202
+ // src/issuers/discovery.ts
7203
+ var discoveryAbi = viem.parseAbi([
7204
+ "function allowlistRegistry() view returns (address)",
7205
+ "function beaconRegistry() view returns (address)",
7206
+ "function platformOperator() view returns (address)",
7207
+ "function policyEngine() view returns (address)",
7208
+ "function policyRegistry() view returns (address)",
7209
+ "function getDefaultVariant() view returns (bytes32)",
7210
+ "function getBeacon(bytes32) view returns (address)",
7211
+ "function isVariantActive(bytes32) view returns (bool)",
7212
+ "function isAuthorizedFactory(address) view returns (bool)",
7213
+ "function getIssuerTokens(address) view returns (address[])",
7214
+ "function implementationsOf(bytes32) view returns (address[])",
7215
+ "function isApproved(address) view returns (bool)",
7216
+ "function policyTypeOf(address) view returns (bytes32)",
7217
+ "function policyType() view returns (bytes32)"
7218
+ ]);
7219
+ var policyKinds = Object.keys(issuerPolicyTypes);
7220
+ function fail(message) {
7221
+ throw new OpacityIssuerOperationError("deployment-mismatch", message);
7222
+ }
7223
+ function address4(value, optional = false) {
7224
+ if (optional && value === null) return viem.zeroAddress;
7225
+ if (typeof value !== "string" || !viem.isAddress(value, { strict: false }) || !optional && value.toLowerCase() === viem.zeroAddress)
7226
+ fail("Issuer discovery returned an invalid contract address.");
7227
+ return value.toLowerCase();
7228
+ }
7229
+ function hash(value) {
7230
+ if (typeof value !== "string" || !/^0x[0-9a-fA-F]{64}$/.test(value))
7231
+ fail("Issuer discovery requires valid block and transaction hashes.");
7232
+ return value.toLowerCase();
7233
+ }
7234
+ function positiveBigint(value) {
7235
+ return typeof value === "bigint" && value > 0n;
7236
+ }
7237
+ function validateHead(meta) {
7238
+ if (!meta || meta.hasIndexingErrors !== false || typeof meta.deployment !== "string" || !meta.deployment || !positiveBigint(meta.block?.number) || !positiveBigint(meta.block.timestamp))
7239
+ fail("Issuer discovery requires a healthy subgraph with complete block evidence.");
7240
+ const age = BigInt(Math.floor(Date.now() / 1e3)) - meta.block.timestamp;
7241
+ if (age > 600n || age < -60n)
7242
+ fail("Issuer discovery requires a subgraph block from the last 10 minutes.");
7243
+ return {
7244
+ deployment: meta.deployment,
7245
+ number: meta.block.number,
7246
+ hash: hash(meta.block.hash),
7247
+ timestamp: meta.block.timestamp
7248
+ };
7249
+ }
7250
+ async function resolveIssuerDeployment(input, options) {
7251
+ if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key) => !["chainId", "token", "maxTokens"].includes(key)) || !options || typeof options !== "object" || Array.isArray(options))
7252
+ throw new OpacityIssuerOperationError("invalid-input", "Invalid issuer discovery arguments.");
7253
+ const chainId4 = issuerChainId(input.chainId);
7254
+ const selectedToken = input.token === void 0 ? void 0 : issuerAddress(input.token, "token").toLowerCase();
7255
+ const maxTokens = input.maxTokens === void 0 ? 100 : input.maxTokens;
7256
+ if (!Number.isSafeInteger(maxTokens) || maxTokens < 1 || maxTokens > 1e3)
7257
+ throw new OpacityIssuerOperationError(
7258
+ "invalid-input",
7259
+ "maxTokens must be an integer between 1 and 1000."
7260
+ );
7261
+ const { indexer, publicClient: client } = options;
7262
+ if (!client || !indexer || ["getMetadata", "getRegistry", "listTokens", "getToken"].some(
7263
+ (method) => typeof indexer[method] !== "function"
7264
+ ) || typeof client.getChainId !== "function")
7265
+ throw new OpacityIssuerOperationError(
7266
+ "missing-client",
7267
+ "Issuer discovery requires publicClient and a configured indexer."
7268
+ );
7269
+ if (client.chain && client.chain.id !== chainId4 || await client.getChainId() !== chainId4)
7270
+ throw new OpacityIssuerOperationError(
7271
+ "chain-mismatch",
7272
+ "RPC and requested issuer discovery chains must agree."
7273
+ );
7274
+ const snapshot2 = validateHead(await indexer.getMetadata());
7275
+ function validateSnapshot(meta) {
7276
+ if (!meta || meta.hasIndexingErrors !== false || meta.deployment !== snapshot2.deployment || meta.block?.number !== snapshot2.number || meta.block.hash !== null && hash(meta.block.hash) !== snapshot2.hash || meta.block.timestamp !== null && meta.block.timestamp !== snapshot2.timestamp)
7277
+ fail("The subgraph snapshot changed during issuer discovery. Retry discovery.");
7278
+ }
7279
+ const atSnapshot = { blockHash: snapshot2.hash };
7280
+ const registryResult = await indexer.getRegistry(atSnapshot);
7281
+ validateSnapshot(registryResult.meta);
7282
+ if (!registryResult.item) fail("The configured subgraph has no indexed protocol registry.");
7283
+ const registry = address4(registryResult.item.contractAddress);
7284
+ const tokens = [];
7285
+ if (selectedToken !== void 0) {
7286
+ const result = await indexer.getToken(selectedToken, atSnapshot);
7287
+ validateSnapshot(result.meta);
7288
+ if (!result.item || address4(result.item.id) !== selectedToken)
7289
+ fail("The selected source token is not indexed in this environment.");
7290
+ tokens.push(result.item);
7291
+ } else {
7292
+ let cursor = null;
7293
+ const seenCursors = /* @__PURE__ */ new Set();
7294
+ do {
7295
+ const page = await indexer.listTokens({
7296
+ registry,
7297
+ ...atSnapshot,
7298
+ pageSize: Math.min(100, maxTokens - tokens.length),
7299
+ ...cursor ? { cursor } : {}
7300
+ });
7301
+ validateSnapshot(page.meta);
7302
+ if (!Array.isArray(page.items) || page.items.length > maxTokens - tokens.length)
7303
+ fail("Issuer discovery returned an invalid token page.");
7304
+ tokens.push(...page.items);
7305
+ cursor = page.nextCursor;
7306
+ if (cursor) {
7307
+ if (tokens.length >= maxTokens)
7308
+ fail("Issuer discovery exceeded maxTokens. Select a source token or raise maxTokens.");
7309
+ if (!page.items.length || seenCursors.has(cursor.after))
7310
+ fail("Issuer discovery pagination did not advance.");
7311
+ seenCursors.add(cursor.after);
7312
+ }
7313
+ } while (cursor);
7314
+ }
7315
+ if (!tokens.length)
7316
+ fail(
7317
+ "The configured subgraph has no indexed token deployments. Index the protocol deployment before discovering its factory; a new issuer does not need an existing token of their own."
7318
+ );
7319
+ const tokenAddresses = tokens.map((token) => address4(token.id));
7320
+ if (new Set(tokenAddresses).size !== tokens.length)
7321
+ fail("Issuer discovery returned duplicate source tokens.");
7322
+ const [indexedBlock, latest] = await Promise.all([
7323
+ client.getBlock({ blockNumber: snapshot2.number }),
7324
+ client.getBlock({ blockTag: "latest" })
7325
+ ]);
7326
+ if (indexedBlock.number !== snapshot2.number || hash(indexedBlock.hash) !== snapshot2.hash || indexedBlock.timestamp !== snapshot2.timestamp || !positiveBigint(latest.number) || latest.number < snapshot2.number || !positiveBigint(latest.timestamp) || latest.timestamp < snapshot2.timestamp)
7327
+ fail("Subgraph block evidence does not match the requested RPC chain.");
7328
+ const latestHash = hash(latest.hash);
7329
+ validateHead({
7330
+ deployment: snapshot2.deployment,
7331
+ hasIndexingErrors: false,
7332
+ block: { number: latest.number, hash: latestHash, timestamp: latest.timestamp }
7333
+ });
7334
+ const blocks = [.../* @__PURE__ */ new Set([snapshot2.number, latest.number])];
7335
+ const readCache = /* @__PURE__ */ new Map();
7336
+ function read(contract, functionName, args, blockNumber) {
7337
+ const key = `${contract}:${functionName}:${args.join(",")}:${blockNumber}`;
7338
+ let result = readCache.get(key);
7339
+ if (!result) {
7340
+ result = client.readContract({
7341
+ address: contract,
7342
+ abi: discoveryAbi,
7343
+ functionName,
7344
+ args,
7345
+ blockNumber
7346
+ });
7347
+ readCache.set(key, result);
7348
+ }
7349
+ return result;
7350
+ }
7351
+ const codeCache = /* @__PURE__ */ new Set();
7352
+ async function requireCode(contract, blockNumber) {
7353
+ const key = `${contract}:${blockNumber}`;
7354
+ if (codeCache.has(key)) return;
7355
+ const code = await client.getBytecode({ address: contract, blockNumber });
7356
+ if (!code || code === "0x") fail(`No contract code at discovered address ${contract}.`);
7357
+ codeCache.add(key);
7358
+ }
7359
+ async function sameAddressAtBoth(contract, functionName, expected, optional = false) {
7360
+ for (const block of blocks) {
7361
+ if (address4(await read(contract, functionName, [], block), optional) !== expected)
7362
+ fail(`Discovered ${functionName} differs from the indexed or current chain state.`);
7363
+ }
7364
+ }
7365
+ const factories = /* @__PURE__ */ new Set();
7366
+ const receiptCache = /* @__PURE__ */ new Map();
7367
+ for (const token of tokens) {
7368
+ const tokenAddress = address4(token.id);
7369
+ const tokenIssuer = address4(token.issuer?.id);
7370
+ if (address4(token.allowlistRegistry) !== registry)
7371
+ fail("An indexed source token belongs to a different registry.");
7372
+ if (!positiveBigint(token.deployedAtBlock) || token.deployedAtBlock > snapshot2.number)
7373
+ fail("The indexed token deployment block is invalid.");
7374
+ for (const block of blocks) await requireCode(tokenAddress, block);
7375
+ await sameAddressAtBoth(tokenAddress, "allowlistRegistry", registry);
7376
+ const engine = address4(token.policyEngine, true);
7377
+ await sameAddressAtBoth(tokenAddress, "policyEngine", engine, true);
7378
+ const txHash = hash(token.deployTxHash);
7379
+ let receipt = receiptCache.get(txHash);
7380
+ if (!receipt) {
7381
+ receipt = await client.getTransactionReceipt({ hash: txHash });
7382
+ receiptCache.set(txHash, receipt);
7383
+ }
7384
+ if (receipt.status !== "success" || hash(receipt.transactionHash) !== txHash || receipt.blockNumber !== token.deployedAtBlock)
7385
+ fail("The indexed token deployment receipt does not match the chain.");
7386
+ const deployedBlock = await client.getBlock({ blockNumber: receipt.blockNumber });
7387
+ if (deployedBlock.number !== token.deployedAtBlock || hash(deployedBlock.hash) !== hash(receipt.blockHash) || deployedBlock.timestamp !== token.deployedAtTimestamp)
7388
+ fail("The indexed token deployment receipt is not on the canonical chain.");
7389
+ const emitters = [];
7390
+ for (const log of receipt.logs) {
7391
+ if (log.removed) continue;
7392
+ try {
7393
+ const event = viem.decodeEventLog({
7394
+ abi: issuerFactoryAbi,
7395
+ eventName: "TokenDeployed",
7396
+ topics: log.topics,
7397
+ data: log.data,
7398
+ strict: true
7399
+ });
7400
+ if (address4(event.args.token) === tokenAddress && address4(event.args.issuer) === tokenIssuer && address4(event.args.allowlistRegistry) === registry)
7401
+ emitters.push(address4(log.address));
7402
+ } catch {
7403
+ }
7404
+ }
7405
+ if (emitters.length !== 1)
7406
+ fail("Expected exactly one matching TokenDeployed event in the indexed receipt.");
7407
+ const emitter = emitters[0];
7408
+ for (const block of blocks) {
7409
+ const issued = await read(emitter, "getIssuerTokens", [tokenIssuer], block);
7410
+ if (!Array.isArray(issued) || !issued.some((item) => address4(item) === tokenAddress))
7411
+ fail("The indexed token is not recorded by its deployment factory for that issuer.");
7412
+ }
7413
+ factories.add(emitter);
7414
+ }
7415
+ if (factories.size !== 1)
7416
+ fail(
7417
+ "Multiple protocol deployments are indexed. Select a source token explicitly; discovery never guesses a factory."
7418
+ );
7419
+ const factory = [...factories][0];
7420
+ for (const block of blocks) {
7421
+ await requireCode(factory, block);
7422
+ await requireCode(registry, block);
7423
+ }
7424
+ await sameAddressAtBoth(factory, "allowlistRegistry", registry);
7425
+ const beaconRegistry = address4(await read(factory, "beaconRegistry", [], snapshot2.number));
7426
+ await sameAddressAtBoth(factory, "beaconRegistry", beaconRegistry);
7427
+ const defaultBeaconVariant = hash(
7428
+ await read(beaconRegistry, "getDefaultVariant", [], snapshot2.number)
7429
+ );
7430
+ const beacon = address4(
7431
+ await read(beaconRegistry, "getBeacon", [defaultBeaconVariant], snapshot2.number)
7432
+ );
7433
+ for (const block of blocks) {
7434
+ await requireCode(beaconRegistry, block);
7435
+ await requireCode(beacon, block);
7436
+ const [variant, actualBeacon, active3, authorized] = await Promise.all([
7437
+ read(beaconRegistry, "getDefaultVariant", [], block),
7438
+ read(beaconRegistry, "getBeacon", [defaultBeaconVariant], block),
7439
+ read(beaconRegistry, "isVariantActive", [defaultBeaconVariant], block),
7440
+ read(beaconRegistry, "isAuthorizedFactory", [factory], block)
7441
+ ]);
7442
+ if (hash(variant) !== defaultBeaconVariant || address4(actualBeacon) !== beacon || active3 !== true || authorized !== true)
7443
+ fail("Discovered factory or default beacon is inactive, unauthorized, or changed.");
7444
+ }
7445
+ const deployment2 = {
7446
+ chainId: chainId4,
7447
+ factory,
7448
+ allowlistRegistry: registry,
7449
+ beaconRegistry
7450
+ };
7451
+ const policies = Object.fromEntries(policyKinds.map((kind) => [kind, []]));
7452
+ const policyEngine = await (async () => {
7453
+ try {
7454
+ const operator = address4(await read(registry, "platformOperator", [], snapshot2.number));
7455
+ await sameAddressAtBoth(registry, "platformOperator", operator);
7456
+ for (const block of blocks) await requireCode(operator, block);
7457
+ const governedEngine = address4(await read(operator, "policyEngine", [], snapshot2.number));
7458
+ await sameAddressAtBoth(operator, "policyEngine", governedEngine);
7459
+ return governedEngine;
7460
+ } catch (cause) {
7461
+ const unsupported = cause instanceof viem.BaseError ? cause.walk(
7462
+ (error) => error instanceof viem.ContractFunctionZeroDataError || error instanceof viem.ContractFunctionRevertedError
7463
+ ) : void 0;
7464
+ if (!(cause instanceof OpacityIssuerOperationError && cause.code === "deployment-mismatch") && !(unsupported instanceof viem.ContractFunctionZeroDataError) && !(unsupported instanceof viem.ContractFunctionRevertedError))
7465
+ throw cause;
7466
+ throw new OpacityIssuerOperationError(
7467
+ "deployment-mismatch",
7468
+ "Cannot verify registry-governed policy infrastructure. Automatic discovery requires a contract platformOperator exposing policyEngine at the indexed and current blocks; an EOA operator or incompatible deployment must be configured by protocol governance before discovery can proceed.",
7469
+ { cause }
7470
+ );
7471
+ }
7472
+ })();
7473
+ if (policyEngine) {
7474
+ const policyRegistry = address4(await read(policyEngine, "policyRegistry", [], snapshot2.number));
7475
+ for (const block of blocks) {
7476
+ await requireCode(policyEngine, block);
7477
+ await requireCode(policyRegistry, block);
7478
+ }
7479
+ await sameAddressAtBoth(policyEngine, "policyRegistry", policyRegistry);
7480
+ await sameAddressAtBoth(policyRegistry, "allowlistRegistry", registry);
7481
+ deployment2.policyEngine = policyEngine;
7482
+ deployment2.policyRegistry = policyRegistry;
7483
+ await Promise.all(
7484
+ policyKinds.map(async (kind) => {
7485
+ const policyType = issuerPolicyTypes[kind];
7486
+ const raw = await read(policyRegistry, "implementationsOf", [policyType], snapshot2.number);
7487
+ if (!Array.isArray(raw) || raw.length > 1e3)
7488
+ fail("The policy registry returned an invalid or excessive implementation catalog.");
7489
+ const implementations = raw.map((item) => address4(item));
7490
+ if (new Set(implementations).size !== implementations.length)
7491
+ fail("The policy registry returned duplicate implementations.");
7492
+ const current = await read(
7493
+ policyRegistry,
7494
+ "implementationsOf",
7495
+ [policyType],
7496
+ latest.number
7497
+ );
7498
+ if (!Array.isArray(current) || current.length !== implementations.length || current.some((item, i) => address4(item) !== implementations[i]))
7499
+ fail("The policy catalog changed since the indexed block. Wait for indexing and retry.");
7500
+ for (const implementation of implementations) {
7501
+ const approved = await read(
7502
+ policyRegistry,
7503
+ "isApproved",
7504
+ [implementation],
7505
+ snapshot2.number
7506
+ );
7507
+ if (typeof approved !== "boolean") fail("The policy registry returned invalid approval.");
7508
+ if (await read(policyRegistry, "isApproved", [implementation], latest.number) !== approved)
7509
+ fail("Policy approval changed since the indexed block. Wait for indexing and retry.");
7510
+ if (!approved) continue;
7511
+ for (const block of blocks) {
7512
+ await requireCode(implementation, block);
7513
+ const [registeredType, actualType] = await Promise.all([
7514
+ read(policyRegistry, "policyTypeOf", [implementation], block),
7515
+ read(implementation, "policyType", [], block)
7516
+ ]);
7517
+ if (hash(registeredType) !== policyType || hash(actualType) !== policyType)
7518
+ fail("An approved policy implementation does not match its registered policy type.");
7519
+ }
7520
+ policies[kind].push(implementation);
7521
+ }
7522
+ })
7523
+ );
7524
+ }
7525
+ const [finalIndexed, finalLatest] = await Promise.all([
7526
+ client.getBlock({ blockNumber: snapshot2.number }),
7527
+ client.getBlock({ blockNumber: latest.number })
7528
+ ]);
7529
+ if (hash(finalIndexed.hash) !== snapshot2.hash || hash(finalLatest.hash) !== latestHash)
7530
+ fail("The chain reorganized during issuer discovery. Retry discovery.");
7531
+ validateHead({
7532
+ deployment: snapshot2.deployment,
7533
+ hasIndexingErrors: false,
7534
+ block: { number: snapshot2.number, hash: snapshot2.hash, timestamp: snapshot2.timestamp }
7535
+ });
7536
+ return {
7537
+ deployment: deployment2,
7538
+ policies,
7539
+ defaultBeaconVariant,
7540
+ beacon,
7541
+ evidence: {
7542
+ subgraphDeployment: snapshot2.deployment,
7543
+ blockNumber: snapshot2.number,
7544
+ blockHash: snapshot2.hash,
7545
+ timestamp: snapshot2.timestamp,
7546
+ verifiedAtBlock: latest.number,
7547
+ verifiedAtBlockHash: latestHash,
7548
+ sourceTokens: tokenAddresses
7549
+ }
7550
+ };
7551
+ }
7552
+
7202
7553
  // src/issuers/sandbox-deployment.ts
7203
7554
  var { chainId: chainId3, addresses } = chunkGMPQFX25_cjs.SEPOLIA_SANDBOX_MANIFEST;
7204
7555
  var SANDBOX_ISSUER_DEPLOYMENT = Object.freeze({
@@ -7217,6 +7568,37 @@ var SANDBOX_ISSUER_DEPLOYMENT = Object.freeze({
7217
7568
  function createIssuersNamespace(options = {}) {
7218
7569
  const configuredChain = options.chainId === void 0 ? void 0 : issuerChainId(options.chainId);
7219
7570
  const busy = /* @__PURE__ */ new Set();
7571
+ async function discoverDeployment(params = {}) {
7572
+ if (!params || typeof params !== "object" || Array.isArray(params) || ![Object.prototype, null].includes(Object.getPrototypeOf(params)))
7573
+ throw new OpacityIssuerOperationError(
7574
+ "invalid-input",
7575
+ "Discovery parameters must be a plain object."
7576
+ );
7577
+ const chainId4 = params.chainId === void 0 ? configuredChain : params.chainId;
7578
+ if (chainId4 === void 0)
7579
+ throw new OpacityIssuerOperationError(
7580
+ "invalid-input",
7581
+ "Configure chainId before discovering an issuer deployment."
7582
+ );
7583
+ issuerChainId(chainId4);
7584
+ if (configuredChain !== void 0 && chainId4 !== configuredChain)
7585
+ throw new OpacityIssuerOperationError(
7586
+ "chain-mismatch",
7587
+ "Discovery chain must match the configured issuer chain."
7588
+ );
7589
+ if (!options.indexer || !options.publicClient)
7590
+ throw new OpacityIssuerOperationError(
7591
+ "missing-client",
7592
+ "Deployment discovery requires an indexer and publicClient."
7593
+ );
7594
+ return resolveIssuerDeployment(
7595
+ { ...params, chainId: chainId4 },
7596
+ {
7597
+ indexer: options.indexer,
7598
+ publicClient: options.publicClient
7599
+ }
7600
+ );
7601
+ }
7220
7602
  async function clientFor(chainId4) {
7221
7603
  issuerChainId(chainId4);
7222
7604
  const client = options.publicClient;
@@ -7246,15 +7628,15 @@ function createIssuersNamespace(options = {}) {
7246
7628
  );
7247
7629
  return wallet;
7248
7630
  }
7249
- async function requireCode(client, address4, blockNumber) {
7631
+ async function requireCode(client, address5, blockNumber) {
7250
7632
  const code = await client.getBytecode({
7251
- address: address4,
7633
+ address: address5,
7252
7634
  ...blockNumber === void 0 ? {} : { blockNumber }
7253
7635
  });
7254
7636
  if (!code || code === "0x")
7255
7637
  throw new OpacityIssuerOperationError(
7256
7638
  "deployment-mismatch",
7257
- `No contract code at ${address4}.`
7639
+ `No contract code at ${address5}.`
7258
7640
  );
7259
7641
  }
7260
7642
  async function sandboxVersionAt(client, registry, blockNumber) {
@@ -7545,10 +7927,10 @@ function createIssuersNamespace(options = {}) {
7545
7927
  "deployment-blocked",
7546
7928
  `Issuer deployment blocked: ${prepared.blockers.join(", ")}.`
7547
7929
  );
7548
- const hash = await write(prepared.transaction);
7930
+ const hash2 = await write(prepared.transaction);
7549
7931
  return {
7550
7932
  kind: "deployment",
7551
- hash,
7933
+ hash: hash2,
7552
7934
  transaction: prepared.transaction,
7553
7935
  params,
7554
7936
  predictedToken: prepared.predictedToken,
@@ -7721,28 +8103,28 @@ function createIssuersNamespace(options = {}) {
7721
8103
  }
7722
8104
  async function waitForTransaction(record2, wait = {}) {
7723
8105
  const tx = issuerTransaction(record2.transaction);
7724
- const hash = issuerBytes(record2.hash, "hash", 32);
8106
+ const hash2 = issuerBytes(record2.hash, "hash", 32);
7725
8107
  const client = await clientFor(tx.chainId);
7726
8108
  let replacementReason;
7727
8109
  const receipt = await client.waitForTransactionReceipt({
7728
- hash,
8110
+ hash: hash2,
7729
8111
  ...wait,
7730
8112
  onReplaced: ({ reason }) => {
7731
8113
  replacementReason = reason;
7732
8114
  }
7733
8115
  });
7734
- const base = { submittedHash: hash, hash: receipt.transactionHash, receipt };
7735
- if (replacementReason || receipt.transactionHash.toLowerCase() !== hash.toLowerCase())
8116
+ const base = { submittedHash: hash2, hash: receipt.transactionHash, receipt };
8117
+ if (replacementReason || receipt.transactionHash.toLowerCase() !== hash2.toLowerCase())
7736
8118
  return {
7737
8119
  ...base,
7738
8120
  status: "replaced",
7739
8121
  ...replacementReason === void 0 ? {} : { replacementReason }
7740
8122
  };
7741
8123
  if (receipt.status === "reverted") return { ...base, status: "reverted" };
7742
- const actual = await client.getTransaction({ hash });
8124
+ const actual = await client.getTransaction({ hash: hash2 });
7743
8125
  const actualTo = actual.to ?? void 0;
7744
8126
  const sameTarget = tx.to === void 0 ? actualTo === void 0 : actualTo !== void 0 && sameIssuerAddress(tx.to, actualTo);
7745
- const verified = actual.hash.toLowerCase() === hash.toLowerCase() && sameIssuerAddress(actual.from, tx.from) && sameTarget && actual.input.toLowerCase() === tx.data.toLowerCase() && actual.value === tx.value && (actual.chainId === void 0 || actual.chainId === tx.chainId) && (tx.to !== void 0 || !!receipt.contractAddress);
8127
+ const verified = actual.hash.toLowerCase() === hash2.toLowerCase() && sameIssuerAddress(actual.from, tx.from) && sameTarget && actual.input.toLowerCase() === tx.data.toLowerCase() && actual.value === tx.value && (actual.chainId === void 0 || actual.chainId === tx.chainId) && (tx.to !== void 0 || !!receipt.contractAddress);
7746
8128
  return { ...base, status: verified ? "confirmed" : "unverified" };
7747
8129
  }
7748
8130
  async function waitForDeployment(record2, wait) {
@@ -7887,10 +8269,10 @@ function createIssuersNamespace(options = {}) {
7887
8269
  }
7888
8270
  async function getApprovedPolicies(input) {
7889
8271
  const client = await clientFor(input.chainId);
7890
- const address4 = issuerAddress(input.policyRegistry, "policyRegistry");
8272
+ const address5 = issuerAddress(input.policyRegistry, "policyRegistry");
7891
8273
  const policyType = issuerBytes(input.policyType, "policyType", 32);
7892
8274
  const { number: blockNumber } = await client.getBlock({ blockTag: "latest" });
7893
- const req = { address: address4, abi: issuerPolicyRegistryAbi, blockNumber };
8275
+ const req = { address: address5, abi: issuerPolicyRegistryAbi, blockNumber };
7894
8276
  const implementations = await client.readContract({
7895
8277
  ...req,
7896
8278
  functionName: "implementationsOf",
@@ -7990,6 +8372,7 @@ function createIssuersNamespace(options = {}) {
7990
8372
  };
7991
8373
  }
7992
8374
  return {
8375
+ discoverDeployment,
7993
8376
  prepareDeployment,
7994
8377
  simulateDeployment,
7995
8378
  sendDeployment,
@@ -8182,21 +8565,21 @@ async function waitForSandboxUserFunding(publicClient, submission, wait) {
8182
8565
  };
8183
8566
  if (tx.value === 0n || tx.data !== "0x")
8184
8567
  invalid("Funding must be a positive native transfer with empty data.");
8185
- const hash = issuerBytes(submission.hash, "hash", 32);
8568
+ const hash2 = issuerBytes(submission.hash, "hash", 32);
8186
8569
  const options = waitOptions(wait);
8187
8570
  await checkChain(publicClient, tx.chainId);
8188
8571
  let replacementReason;
8189
8572
  const receipt = structuredClone(
8190
8573
  await publicClient.waitForTransactionReceipt({
8191
- hash,
8574
+ hash: hash2,
8192
8575
  ...options,
8193
8576
  onReplaced: ({ reason }) => {
8194
8577
  replacementReason = reason;
8195
8578
  }
8196
8579
  })
8197
8580
  );
8198
- const base = { submittedHash: hash, hash: receipt.transactionHash, receipt };
8199
- if (replacementReason || receipt.transactionHash.toLowerCase() !== hash.toLowerCase())
8581
+ const base = { submittedHash: hash2, hash: receipt.transactionHash, receipt };
8582
+ if (replacementReason || receipt.transactionHash.toLowerCase() !== hash2.toLowerCase())
8200
8583
  return {
8201
8584
  ...base,
8202
8585
  status: "replaced",
@@ -8205,11 +8588,11 @@ async function waitForSandboxUserFunding(publicClient, submission, wait) {
8205
8588
  if (receipt.status === "reverted") return { ...base, status: "reverted" };
8206
8589
  let actual;
8207
8590
  try {
8208
- actual = await publicClient.getTransaction({ hash });
8591
+ actual = await publicClient.getTransaction({ hash: hash2 });
8209
8592
  } catch {
8210
8593
  return { ...base, status: "unverified" };
8211
8594
  }
8212
- const verified = actual.hash.toLowerCase() === hash.toLowerCase() && sameIssuerAddress(actual.from, tx.from) && actual.to !== null && sameIssuerAddress(actual.to, tx.to) && actual.input === "0x" && actual.value === tx.value && (actual.chainId === void 0 || actual.chainId === tx.chainId);
8595
+ const verified = actual.hash.toLowerCase() === hash2.toLowerCase() && sameIssuerAddress(actual.from, tx.from) && actual.to !== null && sameIssuerAddress(actual.to, tx.to) && actual.input === "0x" && actual.value === tx.value && (actual.chainId === void 0 || actual.chainId === tx.chainId);
8213
8596
  return { ...base, status: verified ? "confirmed" : "unverified" };
8214
8597
  }
8215
8598
  function sameTransaction(a, b) {
@@ -8327,15 +8710,15 @@ async function createSandboxUser(publicClient, params) {
8327
8710
  await checkChain(publicClient, chainId4);
8328
8711
  if (!sameIssuerAddress(await walletAddress(sponsor, chainId4), from))
8329
8712
  invalid("Selected funding account changed before sending.");
8330
- const hash = await sponsor.sendTransaction({
8713
+ const hash2 = await sponsor.sendTransaction({
8331
8714
  account: sponsor.account ?? from,
8332
8715
  chain: sponsor.chain ?? chain2,
8333
8716
  to: account.address,
8334
8717
  value: transaction.value,
8335
8718
  data: "0x"
8336
8719
  });
8337
- state.funding = { kind: "funding", hash, transaction };
8338
- state.pending.hash = hash;
8720
+ state.funding = { kind: "funding", hash: hash2, transaction };
8721
+ state.pending.hash = hash2;
8339
8722
  state.stage = "waiting-funding";
8340
8723
  await save();
8341
8724
  const result = await waitForSandboxUserFunding(publicClient, state.funding, wait);
@@ -8590,15 +8973,15 @@ async function transferSandboxUser(publicClient, params) {
8590
8973
  await checkChain2(publicClient, chainId4);
8591
8974
  if (!sameIssuerAddress(await fundingAccount(sponsor, chainId4), from))
8592
8975
  invalid("Selected funding account changed before sending.");
8593
- const hash = await sponsor.sendTransaction({
8976
+ const hash2 = await sponsor.sendTransaction({
8594
8977
  account: sendAccount,
8595
8978
  chain: chain2,
8596
8979
  to: wallet,
8597
8980
  value: transaction.value,
8598
8981
  data: "0x"
8599
8982
  });
8600
- state.funding = { kind: "funding", hash, transaction: structuredClone(transaction) };
8601
- state.pending.hash = hash;
8983
+ state.funding = { kind: "funding", hash: hash2, transaction: structuredClone(transaction) };
8984
+ state.pending.hash = hash2;
8602
8985
  state.stage = "waiting-funding";
8603
8986
  await save();
8604
8987
  const result2 = structuredClone(
@@ -8641,6 +9024,777 @@ async function transferSandboxUser(publicClient, params) {
8641
9024
  active2.delete(key);
8642
9025
  }
8643
9026
  }
9027
+ var sandboxWalletLinkAbi = [
9028
+ {
9029
+ type: "function",
9030
+ name: "sandboxWalletLinkingVersion",
9031
+ stateMutability: "pure",
9032
+ inputs: [],
9033
+ outputs: [{ type: "uint256" }]
9034
+ },
9035
+ {
9036
+ type: "function",
9037
+ name: "sandboxWalletLinkUsed",
9038
+ stateMutability: "view",
9039
+ inputs: [{ name: "digest", type: "bytes32" }],
9040
+ outputs: [{ type: "bool" }]
9041
+ },
9042
+ {
9043
+ type: "function",
9044
+ name: "sandboxLinkWallet",
9045
+ stateMutability: "nonpayable",
9046
+ inputs: [
9047
+ { name: "newWallet", type: "address" },
9048
+ { name: "nonce", type: "bytes32" },
9049
+ { name: "deadline", type: "uint64" },
9050
+ { name: "newWalletSignature", type: "bytes" }
9051
+ ],
9052
+ outputs: []
9053
+ },
9054
+ {
9055
+ type: "event",
9056
+ name: "SandboxWalletLinked",
9057
+ anonymous: false,
9058
+ inputs: [
9059
+ { name: "existingWallet", type: "address", indexed: true },
9060
+ { name: "newWallet", type: "address", indexed: true },
9061
+ { name: "userId", type: "bytes32", indexed: true },
9062
+ { name: "nonce", type: "bytes32", indexed: false },
9063
+ { name: "deadline", type: "uint64", indexed: false }
9064
+ ]
9065
+ }
9066
+ ];
9067
+ var sandboxWalletLinkTypes = {
9068
+ SandboxWalletLink: [
9069
+ { name: "existingWallet", type: "address" },
9070
+ { name: "newWallet", type: "address" },
9071
+ { name: "userId", type: "bytes32" },
9072
+ { name: "nonce", type: "bytes32" },
9073
+ { name: "deadline", type: "uint64" }
9074
+ ]
9075
+ };
9076
+ var SandboxWalletLinkError = class extends Error {
9077
+ constructor(code, message, progress) {
9078
+ super(message);
9079
+ this.code = code;
9080
+ this.progress = progress;
9081
+ this.name = "SandboxWalletLinkError";
9082
+ }
9083
+ code;
9084
+ progress;
9085
+ };
9086
+ function walletLinkData(value, label, keys) {
9087
+ if (!value || typeof value !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
9088
+ invalid(`${label} must be a plain object.`);
9089
+ for (const key of Reflect.ownKeys(value)) {
9090
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
9091
+ if (typeof key !== "string" || !keys.includes(key) || !descriptor.enumerable || !Object.hasOwn(descriptor, "value"))
9092
+ invalid(`${label} must contain only recognized enumerable data fields.`);
9093
+ }
9094
+ }
9095
+ var fields = [
9096
+ "chainId",
9097
+ "registryAddress",
9098
+ "existingWallet",
9099
+ "newWallet",
9100
+ "nonce",
9101
+ "deadline",
9102
+ "checkedAssets"
9103
+ ];
9104
+ function validateSandboxWalletLinkParams(input) {
9105
+ walletLinkData(input, "Wallet link", fields);
9106
+ if (!Array.isArray(input.checkedAssets) || input.checkedAssets.length === 0)
9107
+ invalid("checkedAssets must include every relevant fund token.");
9108
+ for (const key of Reflect.ownKeys(input.checkedAssets)) {
9109
+ if (key === "length") continue;
9110
+ const descriptor = Object.getOwnPropertyDescriptor(input.checkedAssets, key);
9111
+ if (typeof key !== "string" || !/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= input.checkedAssets.length || !descriptor.enumerable || !Object.hasOwn(descriptor, "value"))
9112
+ invalid("checkedAssets must contain data entries only.");
9113
+ }
9114
+ if (Object.keys(input.checkedAssets).length !== input.checkedAssets.length)
9115
+ invalid("checkedAssets must not contain missing entries.");
9116
+ const seen = /* @__PURE__ */ new Set();
9117
+ const checkedAssets = input.checkedAssets.map((asset) => {
9118
+ walletLinkData(asset, "Checked asset", ["token", "tokenId"]);
9119
+ const token = issuerAddress(asset.token, "token");
9120
+ const tokenId = issuerUint(asset.tokenId, "tokenId");
9121
+ const key = `${token.toLowerCase()}:${tokenId}`;
9122
+ if (seen.has(key)) invalid("checkedAssets must not contain duplicates.");
9123
+ seen.add(key);
9124
+ return Object.freeze({ token, tokenId });
9125
+ });
9126
+ const result = {
9127
+ checkedAssets: Object.freeze(checkedAssets),
9128
+ chainId: issuerChainId(input.chainId),
9129
+ registryAddress: issuerAddress(input.registryAddress, "registryAddress"),
9130
+ existingWallet: issuerAddress(input.existingWallet, "existingWallet"),
9131
+ newWallet: issuerAddress(input.newWallet, "newWallet"),
9132
+ nonce: issuerBytes(input.nonce, "nonce", 32),
9133
+ deadline: issuerUint(input.deadline, "deadline")
9134
+ };
9135
+ if (result.deadline === 0n || result.deadline >= 1n << 64n)
9136
+ invalid("deadline must be a positive uint64 timestamp.");
9137
+ if (sameIssuerAddress(result.existingWallet, result.newWallet))
9138
+ invalid("Choose a different wallet to link.");
9139
+ return Object.freeze(result);
9140
+ }
9141
+ function validateSandboxWalletLinkIntent(input) {
9142
+ walletLinkData(input, "Wallet link intent", [...fields, "userId"]);
9143
+ const { userId, ...params } = input;
9144
+ const id = issuerBytes(userId, "userId", 32);
9145
+ if (id.toLowerCase() === viem.zeroHash) invalid("The existing wallet must have a user identity.");
9146
+ return Object.freeze({ ...validateSandboxWalletLinkParams(params), userId: id });
9147
+ }
9148
+ function buildSandboxWalletLinkTypedData(input) {
9149
+ const { chainId: chainId4, registryAddress, existingWallet, newWallet, userId, nonce, deadline } = validateSandboxWalletLinkIntent(input);
9150
+ const message = { existingWallet, newWallet, userId, nonce, deadline };
9151
+ return {
9152
+ domain: {
9153
+ name: "AllowlistRegistry",
9154
+ version: "1",
9155
+ chainId: chainId4,
9156
+ verifyingContract: registryAddress
9157
+ },
9158
+ types: sandboxWalletLinkTypes,
9159
+ primaryType: "SandboxWalletLink",
9160
+ message
9161
+ };
9162
+ }
9163
+ function buildSandboxWalletLinkTransaction(input, signature) {
9164
+ const intent = validateSandboxWalletLinkIntent(input);
9165
+ return {
9166
+ from: intent.existingWallet,
9167
+ to: intent.registryAddress,
9168
+ chainId: intent.chainId,
9169
+ value: 0n,
9170
+ data: viem.encodeFunctionData({
9171
+ abi: sandboxWalletLinkAbi,
9172
+ functionName: "sandboxLinkWallet",
9173
+ args: [intent.newWallet, intent.nonce, intent.deadline, issuerBytes(signature, "signature")]
9174
+ })
9175
+ };
9176
+ }
9177
+ function walletLinkWaitOptions(value = {}) {
9178
+ walletLinkData(value, "wait", ["confirmations", "timeout", "pollingInterval"]);
9179
+ for (const n of Object.values(value))
9180
+ if (n !== void 0 && (!Number.isSafeInteger(n) || n <= 0))
9181
+ invalid("Wait options must be positive safe integers.");
9182
+ return { ...value };
9183
+ }
9184
+ async function checkChain3(client, chainId4) {
9185
+ if (client.chain && client.chain.id !== chainId4 || await client.getChainId() !== chainId4)
9186
+ invalid("The wallet, RPC and requested chains must match.");
9187
+ }
9188
+ async function checkWallet(wallet, chainId4, expected) {
9189
+ if (!wallet) invalid("Wallet linking requires a wallet client.");
9190
+ await checkChain3(wallet, chainId4);
9191
+ const selected = wallet.account?.type === "local" ? wallet.account.address : (await wallet.getAddresses())[0];
9192
+ if (!selected || !sameIssuerAddress(selected, expected) || wallet.account && !sameIssuerAddress(wallet.account.address, selected))
9193
+ invalid("The selected wallet changed. Review the link with the expected wallet.");
9194
+ return wallet;
9195
+ }
9196
+ function absentCapability(error) {
9197
+ if (!(error instanceof viem.BaseError)) return false;
9198
+ return !!error.walk(
9199
+ (cause) => cause instanceof viem.ContractFunctionRevertedError || cause instanceof viem.ContractFunctionZeroDataError
9200
+ );
9201
+ }
9202
+ function rejected(error) {
9203
+ const seen = /* @__PURE__ */ new Set();
9204
+ for (let e = error; e && typeof e === "object" && !seen.has(e); e = e.cause) {
9205
+ seen.add(e);
9206
+ if (e.code === 4001) return true;
9207
+ }
9208
+ return false;
9209
+ }
9210
+ var pending = /* @__PURE__ */ new Map();
9211
+ var keyFor = (i) => `${i.chainId}:${i.existingWallet.toLowerCase()}`;
9212
+ var sameIntent = (a, b) => viem.hashTypedData(buildSandboxWalletLinkTypedData(a)) === viem.hashTypedData(buildSandboxWalletLinkTypedData(b));
9213
+ function createSandboxWalletLinksNamespace(options) {
9214
+ async function clientFor(chainId4) {
9215
+ if (options.chainId !== void 0 && options.chainId !== chainId4)
9216
+ invalid("Configured and requested wallet linking chains must match.");
9217
+ await checkChain3(options.publicClient, chainId4);
9218
+ return options.publicClient;
9219
+ }
9220
+ async function prepareWalletLink(input) {
9221
+ const params = validateSandboxWalletLinkParams(input);
9222
+ const client = await clientFor(params.chainId);
9223
+ const block = await client.getBlock();
9224
+ const blockNumber = block.number;
9225
+ const context = { address: params.registryAddress, blockNumber };
9226
+ let supported = false;
9227
+ try {
9228
+ supported = await client.readContract({
9229
+ ...context,
9230
+ abi: sandboxWalletLinkAbi,
9231
+ functionName: "sandboxWalletLinkingVersion"
9232
+ }) === 1n;
9233
+ } catch (error) {
9234
+ if (!absentCapability(error)) throw error;
9235
+ }
9236
+ const [paused, allowed, userId, targetUserId, targetStatus, targetIssuer, sourceExpiry] = await Promise.all([
9237
+ client.readContract({ ...context, abi: issuerRegistryAbi, functionName: "paused" }),
9238
+ client.readContract({
9239
+ ...context,
9240
+ abi: issuerRegistryAbi,
9241
+ functionName: "isAllowed",
9242
+ args: [params.existingWallet]
9243
+ }),
9244
+ client.readContract({
9245
+ ...context,
9246
+ abi: issuerRegistryAbi,
9247
+ functionName: "getUserId",
9248
+ args: [params.existingWallet]
9249
+ }),
9250
+ client.readContract({
9251
+ ...context,
9252
+ abi: issuerRegistryAbi,
9253
+ functionName: "getUserId",
9254
+ args: [params.newWallet]
9255
+ }),
9256
+ client.readContract({
9257
+ ...context,
9258
+ abi: issuerRegistryAbi,
9259
+ functionName: "getEntryStatus",
9260
+ args: [params.newWallet]
9261
+ }),
9262
+ client.readContract({
9263
+ ...context,
9264
+ abi: issuerRegistryAbi,
9265
+ functionName: "isAuthorizedIssuer",
9266
+ args: [params.newWallet]
9267
+ }),
9268
+ client.readContract({
9269
+ ...context,
9270
+ abi: issuerRegistryAbi,
9271
+ functionName: "getEntryExpiry",
9272
+ args: [params.existingWallet]
9273
+ })
9274
+ ]);
9275
+ const intent = { ...params, userId };
9276
+ const blockers = [];
9277
+ if (!supported) blockers.push("unsupported-wallet-linking");
9278
+ if (paused) blockers.push("registry-paused");
9279
+ if (!allowed) blockers.push("source-not-approved");
9280
+ if (userId === viem.zeroHash) blockers.push("source-identity-missing");
9281
+ if (targetUserId !== viem.zeroHash) blockers.push("target-already-linked");
9282
+ if (targetStatus !== 0) blockers.push("target-not-fresh");
9283
+ if (targetIssuer) blockers.push("target-is-issuer");
9284
+ for (const asset of params.checkedAssets) {
9285
+ const registry = await client.readContract({
9286
+ address: asset.token,
9287
+ blockNumber,
9288
+ abi: issuerTokenAbi,
9289
+ functionName: "allowlistRegistry"
9290
+ });
9291
+ if (!sameIssuerAddress(registry, params.registryAddress))
9292
+ invalid("A checked fund token uses a different registry.");
9293
+ const balance = await client.readContract({
9294
+ address: asset.token,
9295
+ blockNumber,
9296
+ abi: issuerTokenAbi,
9297
+ functionName: "balanceOf",
9298
+ args: [params.newWallet, asset.tokenId]
9299
+ });
9300
+ if (balance !== 0n && !blockers.includes("target-holds-assets"))
9301
+ blockers.push("target-holds-assets");
9302
+ }
9303
+ if (params.deadline < block.timestamp) blockers.push("consent-expired");
9304
+ if (supported && userId !== viem.zeroHash && await client.readContract({
9305
+ ...context,
9306
+ abi: sandboxWalletLinkAbi,
9307
+ functionName: "sandboxWalletLinkUsed",
9308
+ args: [viem.hashTypedData(buildSandboxWalletLinkTypedData(intent))]
9309
+ }))
9310
+ blockers.push("consent-used");
9311
+ await clientFor(params.chainId);
9312
+ return {
9313
+ intent: Object.freeze(intent),
9314
+ blockNumber,
9315
+ sourceExpiry,
9316
+ blockers,
9317
+ ready: blockers.length === 0
9318
+ };
9319
+ }
9320
+ async function fresh(input) {
9321
+ const intent = validateSandboxWalletLinkIntent(input);
9322
+ const { userId: _id, ...params } = intent;
9323
+ const prepared = await prepareWalletLink(params);
9324
+ if (prepared.blockers.length)
9325
+ throw new SandboxWalletLinkError(
9326
+ "blocked",
9327
+ `Wallet linking is blocked: ${prepared.blockers.join(", ")}.`
9328
+ );
9329
+ if (!sameIntent(intent, prepared.intent))
9330
+ throw new SandboxWalletLinkError(
9331
+ "stale-intent",
9332
+ "The existing wallet identity changed. Prepare and review a new link."
9333
+ );
9334
+ return prepared;
9335
+ }
9336
+ async function signWalletLink(input, newWalletClient) {
9337
+ const intent = validateSandboxWalletLinkIntent(input);
9338
+ await fresh(intent);
9339
+ const wallet = await checkWallet(newWalletClient, intent.chainId, intent.newWallet);
9340
+ let signature;
9341
+ try {
9342
+ signature = await wallet.signTypedData({
9343
+ ...buildSandboxWalletLinkTypedData(intent),
9344
+ account: wallet.account ?? intent.newWallet
9345
+ });
9346
+ } catch (error) {
9347
+ throw new SandboxWalletLinkError(
9348
+ rejected(error) ? "rejected" : "blocked",
9349
+ "Wallet consent was not signed. No transaction was sent."
9350
+ );
9351
+ }
9352
+ await checkWallet(wallet, intent.chainId, intent.newWallet);
9353
+ return issuerBytes(signature, "signature");
9354
+ }
9355
+ async function preflightWalletLink(input, signature) {
9356
+ const intent = validateSandboxWalletLinkIntent(input);
9357
+ const transaction = buildSandboxWalletLinkTransaction(intent, signature);
9358
+ const prepared = await fresh(intent);
9359
+ const client = await clientFor(intent.chainId);
9360
+ try {
9361
+ await client.call({
9362
+ account: transaction.from,
9363
+ to: transaction.to,
9364
+ data: transaction.data,
9365
+ value: 0n
9366
+ });
9367
+ } catch (error) {
9368
+ const reverted = error instanceof viem.BaseError && !!error.walk(
9369
+ (cause) => cause instanceof viem.ContractFunctionRevertedError || cause instanceof viem.ExecutionRevertedError
9370
+ );
9371
+ throw new SandboxWalletLinkError(
9372
+ reverted ? "blocked" : "check-unavailable",
9373
+ reverted ? "This wallet link was rejected. Check the consent and current wallet eligibility." : "The connection could not check this wallet link. Try the check again before sending."
9374
+ );
9375
+ }
9376
+ let gas;
9377
+ try {
9378
+ const [estimatedGas, gasPrice, nativeBalance] = await Promise.all([
9379
+ client.estimateGas({
9380
+ account: transaction.from,
9381
+ to: transaction.to,
9382
+ data: transaction.data,
9383
+ value: 0n
9384
+ }),
9385
+ client.getGasPrice(),
9386
+ client.getBalance({ address: intent.existingWallet })
9387
+ ]);
9388
+ const estimatedCost = estimatedGas * gasPrice;
9389
+ gas = { estimatedGas, gasPrice, estimatedCost, nativeBalance };
9390
+ if (nativeBalance < estimatedCost || nativeBalance === 0n)
9391
+ throw new SandboxWalletLinkError(
9392
+ "insufficient-gas",
9393
+ "The existing wallet needs more test ETH to link this wallet."
9394
+ );
9395
+ } catch (error) {
9396
+ if (error instanceof SandboxWalletLinkError) throw error;
9397
+ throw new SandboxWalletLinkError(
9398
+ "check-unavailable",
9399
+ "The connection could not estimate the network fee. Check again before sending."
9400
+ );
9401
+ }
9402
+ await clientFor(intent.chainId);
9403
+ return { ...prepared, gas };
9404
+ }
9405
+ async function sendWalletLink(input) {
9406
+ walletLinkData(input, "Send wallet link", ["intent", "signature", "persist"]);
9407
+ const intent = validateSandboxWalletLinkIntent(input.intent);
9408
+ const signature = issuerBytes(input.signature, "signature");
9409
+ const persist = input.persist;
9410
+ if (typeof persist !== "function")
9411
+ invalid("persist must save wallet linking progress before sending.");
9412
+ const key = keyFor(intent);
9413
+ if (pending.has(key))
9414
+ throw new SandboxWalletLinkError(
9415
+ "send-in-progress",
9416
+ "Check the existing wallet link before starting another.",
9417
+ structuredClone(pending.get(key))
9418
+ );
9419
+ const progress = { kind: "wallet-link", intent, stage: "prepared" };
9420
+ pending.set(key, structuredClone(progress));
9421
+ let attempted = false;
9422
+ let persistence = false;
9423
+ const save = async () => {
9424
+ pending.set(key, structuredClone(progress));
9425
+ persistence = true;
9426
+ await persist(structuredClone(progress));
9427
+ persistence = false;
9428
+ };
9429
+ try {
9430
+ await save();
9431
+ await preflightWalletLink(intent, signature);
9432
+ await checkWallet(options.walletClient, intent.chainId, intent.existingWallet);
9433
+ progress.senderNonce = await (await clientFor(intent.chainId)).getTransactionCount({ address: intent.existingWallet, blockTag: "pending" });
9434
+ if (!Number.isSafeInteger(progress.senderNonce) || progress.senderNonce < 0)
9435
+ invalid("The RPC returned an invalid transaction nonce.");
9436
+ progress.stage = "sending";
9437
+ await save();
9438
+ await preflightWalletLink(intent, signature);
9439
+ const wallet = await checkWallet(options.walletClient, intent.chainId, intent.existingWallet);
9440
+ const client = await clientFor(intent.chainId);
9441
+ if (await client.getTransactionCount({
9442
+ address: intent.existingWallet,
9443
+ blockTag: "pending"
9444
+ }) !== progress.senderNonce)
9445
+ throw new SandboxWalletLinkError(
9446
+ "stale-intent",
9447
+ "The existing wallet has another pending transaction. Check it before reviewing a new link."
9448
+ );
9449
+ const tx = buildSandboxWalletLinkTransaction(intent, signature);
9450
+ attempted = true;
9451
+ const hash2 = await wallet.sendTransaction({
9452
+ account: wallet.account ?? intent.existingWallet,
9453
+ chain: wallet.chain ?? viem.defineChain({
9454
+ id: intent.chainId,
9455
+ name: `Sandbox ${intent.chainId}`,
9456
+ nativeCurrency: { name: "Native", symbol: "NATIVE", decimals: 18 },
9457
+ rpcUrls: { default: { http: [] } }
9458
+ }),
9459
+ to: tx.to,
9460
+ data: tx.data,
9461
+ value: 0n,
9462
+ nonce: progress.senderNonce
9463
+ });
9464
+ progress.hash = issuerBytes(hash2, "transaction hash", 32);
9465
+ progress.stage = "submitted";
9466
+ await save();
9467
+ return structuredClone(progress);
9468
+ } catch (error) {
9469
+ if (!attempted || !progress.hash && !persistence && rejected(error)) {
9470
+ pending.delete(key);
9471
+ progress.stage = "rejected";
9472
+ try {
9473
+ await persist(structuredClone(progress));
9474
+ } catch {
9475
+ }
9476
+ if (error instanceof SandboxWalletLinkError)
9477
+ throw new SandboxWalletLinkError(error.code, error.message, structuredClone(progress));
9478
+ throw new SandboxWalletLinkError(
9479
+ persistence ? "persistence-failed" : "rejected",
9480
+ persistence ? "Wallet link progress could not be saved. No transaction was sent." : "Wallet linking stopped before a transaction was submitted.",
9481
+ structuredClone(progress)
9482
+ );
9483
+ }
9484
+ pending.set(key, structuredClone(progress));
9485
+ throw new SandboxWalletLinkError(
9486
+ "uncertain",
9487
+ "Check the existing transaction before trying another wallet link.",
9488
+ structuredClone(progress)
9489
+ );
9490
+ }
9491
+ }
9492
+ async function waitForWalletLink(input, wait = {}) {
9493
+ walletLinkData(input, "Wallet link submission", [
9494
+ "kind",
9495
+ "intent",
9496
+ "stage",
9497
+ "hash",
9498
+ "senderNonce",
9499
+ "result"
9500
+ ]);
9501
+ if (input.kind !== "wallet-link" || !["submitted", "complete"].includes(input.stage))
9502
+ invalid("Expected a submitted wallet link.");
9503
+ const intent = validateSandboxWalletLinkIntent(input.intent);
9504
+ const hash2 = issuerBytes(input.hash, "hash", 32);
9505
+ const senderNonce = input.senderNonce;
9506
+ if (!Number.isSafeInteger(senderNonce) || senderNonce < 0)
9507
+ invalid("senderNonce must be a nonnegative safe integer.");
9508
+ const client = await clientFor(intent.chainId);
9509
+ const waitOptions3 = walletLinkWaitOptions(wait);
9510
+ let replacementReason;
9511
+ const receipt = structuredClone(
9512
+ await client.waitForTransactionReceipt({
9513
+ hash: hash2,
9514
+ ...waitOptions3,
9515
+ onReplaced: ({ reason }) => {
9516
+ replacementReason = reason;
9517
+ }
9518
+ })
9519
+ );
9520
+ await clientFor(intent.chainId);
9521
+ const base = { submittedHash: hash2, hash: receipt.transactionHash, receipt };
9522
+ let exactTransaction = false;
9523
+ let result = { ...base, status: "unverified" };
9524
+ if (replacementReason || receipt.transactionHash.toLowerCase() !== hash2.toLowerCase())
9525
+ result = { ...base, status: "replaced", ...replacementReason ? { replacementReason } : {} };
9526
+ else if (receipt.status === "reverted") result = { ...base, status: "reverted" };
9527
+ else if (receipt.status === "success") {
9528
+ try {
9529
+ const tx = await client.getTransaction({ hash: hash2 });
9530
+ await clientFor(intent.chainId);
9531
+ const decoded = viem.decodeFunctionData({ abi: sandboxWalletLinkAbi, data: tx.input });
9532
+ const matches = sameIssuerAddress(receipt.from, intent.existingWallet) && receipt.to !== null && sameIssuerAddress(receipt.to, intent.registryAddress) && tx.blockHash === receipt.blockHash && tx.blockNumber === receipt.blockNumber && tx.hash.toLowerCase() === hash2.toLowerCase() && tx.chainId === intent.chainId && sameIssuerAddress(tx.from, intent.existingWallet) && tx.to !== null && sameIssuerAddress(tx.to, intent.registryAddress) && tx.value === 0n && (senderNonce === void 0 || tx.nonce === senderNonce) && decoded.functionName === "sandboxLinkWallet" && sameIssuerAddress(decoded.args[0], intent.newWallet) && decoded.args[1].toLowerCase() === intent.nonce.toLowerCase() && decoded.args[2] === intent.deadline;
9533
+ exactTransaction = matches;
9534
+ if (matches)
9535
+ for (const log of receipt.logs) {
9536
+ if (!sameIssuerAddress(log.address, intent.registryAddress) || log.removed || log.blockHash !== receipt.blockHash || log.blockNumber !== receipt.blockNumber || log.transactionHash?.toLowerCase() !== hash2.toLowerCase())
9537
+ continue;
9538
+ try {
9539
+ const { args } = viem.decodeEventLog({
9540
+ abi: sandboxWalletLinkAbi,
9541
+ eventName: "SandboxWalletLinked",
9542
+ data: log.data,
9543
+ topics: log.topics,
9544
+ strict: true
9545
+ });
9546
+ if (sameIssuerAddress(args.existingWallet, intent.existingWallet) && sameIssuerAddress(args.newWallet, intent.newWallet) && args.userId.toLowerCase() === intent.userId.toLowerCase() && args.nonce.toLowerCase() === intent.nonce.toLowerCase() && args.deadline === intent.deadline)
9547
+ result = { ...base, status: "confirmed" };
9548
+ } catch {
9549
+ }
9550
+ }
9551
+ } catch {
9552
+ }
9553
+ }
9554
+ const current = pending.get(keyFor(intent));
9555
+ if (current?.hash === void 0 && current?.stage === "sending" && result.status === "reverted") {
9556
+ try {
9557
+ const tx = await client.getTransaction({ hash: hash2 });
9558
+ await clientFor(intent.chainId);
9559
+ const decoded = viem.decodeFunctionData({ abi: sandboxWalletLinkAbi, data: tx.input });
9560
+ exactTransaction = tx.hash.toLowerCase() === hash2.toLowerCase() && tx.chainId === intent.chainId && sameIssuerAddress(tx.from, intent.existingWallet) && tx.to !== null && sameIssuerAddress(tx.to, intent.registryAddress) && tx.value === 0n && tx.nonce === senderNonce && tx.blockHash === receipt.blockHash && tx.blockNumber === receipt.blockNumber && decoded.functionName === "sandboxLinkWallet" && sameIssuerAddress(decoded.args[0], intent.newWallet) && decoded.args[1].toLowerCase() === intent.nonce.toLowerCase() && decoded.args[2] === intent.deadline;
9561
+ } catch {
9562
+ }
9563
+ }
9564
+ if (current && pending.get(keyFor(intent)) === current && sameIntent(current.intent, intent) && (current.hash?.toLowerCase() === hash2.toLowerCase() || current.hash === void 0 && current.stage === "sending" && current.senderNonce === senderNonce && exactTransaction) && (result.status === "confirmed" || result.status === "reverted" || result.status === "replaced"))
9565
+ pending.delete(keyFor(intent));
9566
+ return result;
9567
+ }
9568
+ return {
9569
+ prepareWalletLink,
9570
+ signWalletLink,
9571
+ preflightWalletLink,
9572
+ sendWalletLink,
9573
+ waitForWalletLink
9574
+ };
9575
+ }
9576
+ var SandboxLinkedWalletCreationError = class extends Error {
9577
+ constructor(progress) {
9578
+ super(
9579
+ "Wallet creation stopped. Recover the saved wallet and check its pending link before another attempt."
9580
+ );
9581
+ this.progress = progress;
9582
+ this.name = "SandboxLinkedWalletCreationError";
9583
+ Object.defineProperty(this, "progress", { enumerable: false });
9584
+ }
9585
+ progress;
9586
+ };
9587
+ async function createSandboxLinkedWallet(publicClient, input) {
9588
+ walletLinkData(input, "Create linked wallet", [
9589
+ "chainId",
9590
+ "registryAddress",
9591
+ "existingWallet",
9592
+ "checkedAssets",
9593
+ "sourceWalletClient",
9594
+ "persist",
9595
+ "privateKey",
9596
+ "nonce",
9597
+ "deadline",
9598
+ "wait"
9599
+ ]);
9600
+ const chainId4 = issuerChainId(input.chainId);
9601
+ const registryAddress = issuerAddress(input.registryAddress, "registryAddress");
9602
+ const existingWallet = issuerAddress(input.existingWallet, "existingWallet");
9603
+ const persist = input.persist;
9604
+ if (typeof persist !== "function") invalid("persist must save the new wallet before sending.");
9605
+ if (!input.sourceWalletClient || typeof input.sourceWalletClient.sendTransaction !== "function")
9606
+ invalid("sourceWalletClient is required.");
9607
+ const sourceWalletClient = input.sourceWalletClient;
9608
+ const privateKey = input.privateKey === void 0 ? accounts.generatePrivateKey() : issuerBytes(input.privateKey, "privateKey", 32);
9609
+ let account;
9610
+ try {
9611
+ account = accounts.privateKeyToAccount(privateKey);
9612
+ } catch {
9613
+ invalid("privateKey must be a valid test-wallet private key.");
9614
+ }
9615
+ const nonce = input.nonce === void 0 ? accounts.generatePrivateKey() : issuerBytes(input.nonce, "nonce", 32);
9616
+ const wait = walletLinkWaitOptions(input.wait);
9617
+ const provisional = validateSandboxWalletLinkParams({
9618
+ chainId: chainId4,
9619
+ registryAddress,
9620
+ existingWallet,
9621
+ newWallet: account.address,
9622
+ checkedAssets: input.checkedAssets,
9623
+ nonce,
9624
+ deadline: input.deadline ?? 1n
9625
+ });
9626
+ const explicitDeadline = input.deadline;
9627
+ const links = createSandboxWalletLinksNamespace({
9628
+ publicClient,
9629
+ walletClient: sourceWalletClient,
9630
+ chainId: chainId4
9631
+ });
9632
+ const deadline = explicitDeadline ?? (await publicClient.getBlock()).timestamp + 900n;
9633
+ const prepared = await links.prepareWalletLink({ ...provisional, deadline });
9634
+ if (!prepared.ready)
9635
+ throw new SandboxWalletLinkError(
9636
+ "blocked",
9637
+ `Wallet linking is blocked: ${prepared.blockers.join(", ")}.`
9638
+ );
9639
+ const state = {
9640
+ kind: "linked-wallet",
9641
+ wallet: account.address,
9642
+ existingWallet,
9643
+ privateKey,
9644
+ chainId: chainId4,
9645
+ registryAddress,
9646
+ userId: prepared.intent.userId,
9647
+ stage: "created",
9648
+ link: { kind: "wallet-link", intent: prepared.intent, stage: "prepared" }
9649
+ };
9650
+ const targetWallet = viem.createWalletClient({
9651
+ account,
9652
+ transport: viem.custom({
9653
+ request: async ({ method }) => {
9654
+ if (method === "eth_chainId") return `0x${chainId4.toString(16)}`;
9655
+ throw new Error("Generated wallet transport supports only local signing.");
9656
+ }
9657
+ })
9658
+ });
9659
+ const save = () => persist(structuredClone(state));
9660
+ try {
9661
+ await save();
9662
+ const signature = await links.signWalletLink(prepared.intent, targetWallet);
9663
+ state.stage = "linking";
9664
+ const submission = await links.sendWalletLink({
9665
+ intent: prepared.intent,
9666
+ signature,
9667
+ persist: async (link) => {
9668
+ state.link = structuredClone(link);
9669
+ await save();
9670
+ }
9671
+ });
9672
+ const result = await links.waitForWalletLink(submission, wait);
9673
+ state.link = {
9674
+ ...submission,
9675
+ result,
9676
+ ...result.status === "confirmed" ? { stage: "complete" } : {}
9677
+ };
9678
+ if (result.status !== "confirmed") {
9679
+ await save();
9680
+ throw new Error("Link is not confirmed.");
9681
+ }
9682
+ state.stage = "complete";
9683
+ await save();
9684
+ return structuredClone(state);
9685
+ } catch (error) {
9686
+ if (error instanceof SandboxWalletLinkError && error.progress)
9687
+ state.link = structuredClone(error.progress);
9688
+ throw new SandboxLinkedWalletCreationError(structuredClone(state));
9689
+ }
9690
+ }
9691
+ function savedData(value, ancestors = /* @__PURE__ */ new Set()) {
9692
+ if (value === null || value === void 0 || ["string", "boolean", "bigint"].includes(typeof value))
9693
+ return;
9694
+ if (typeof value === "number" && Number.isFinite(value)) return;
9695
+ if (typeof value !== "object" || value === null || ancestors.has(value))
9696
+ invalid("Saved wallet must contain acyclic data.");
9697
+ if (!Array.isArray(value) && ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
9698
+ invalid("Saved wallet must contain plain data.");
9699
+ ancestors.add(value);
9700
+ for (const key of Reflect.ownKeys(value)) {
9701
+ if (Array.isArray(value) && key === "length") continue;
9702
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
9703
+ if (typeof key !== "string" || !descriptor.enumerable || !Object.hasOwn(descriptor, "value"))
9704
+ invalid("Saved wallet must contain enumerable data fields.");
9705
+ savedData(descriptor.value, ancestors);
9706
+ }
9707
+ ancestors.delete(value);
9708
+ }
9709
+ function validateSandboxLinkedWalletProgress(input) {
9710
+ savedData(input);
9711
+ walletLinkData(input, "Saved linked wallet", [
9712
+ "kind",
9713
+ "wallet",
9714
+ "existingWallet",
9715
+ "privateKey",
9716
+ "chainId",
9717
+ "registryAddress",
9718
+ "userId",
9719
+ "stage",
9720
+ "link"
9721
+ ]);
9722
+ if (input.kind !== "linked-wallet" || !["created", "linking", "complete"].includes(input.stage))
9723
+ invalid("Invalid saved linked-wallet state.");
9724
+ walletLinkData(input.link, "Saved wallet link", [
9725
+ "kind",
9726
+ "intent",
9727
+ "stage",
9728
+ "hash",
9729
+ "senderNonce",
9730
+ "result"
9731
+ ]);
9732
+ const intent = validateSandboxWalletLinkIntent(input.link.intent);
9733
+ if (input.link.kind !== "wallet-link" || !["prepared", "sending", "submitted", "rejected", "complete"].includes(input.link.stage))
9734
+ invalid("Invalid saved link journal.");
9735
+ if (input.link.hash !== void 0) issuerBytes(input.link.hash, "hash", 32);
9736
+ if (["submitted", "complete"].includes(input.link.stage) && input.link.hash === void 0)
9737
+ invalid("A submitted link requires its transaction hash.");
9738
+ if (input.link.senderNonce !== void 0 && (!Number.isSafeInteger(input.link.senderNonce) || input.link.senderNonce < 0))
9739
+ invalid("Invalid saved sender nonce.");
9740
+ if (["sending", "submitted", "complete"].includes(input.link.stage) && input.link.senderNonce === void 0)
9741
+ invalid("A pending link requires its sender nonce.");
9742
+ if (input.link.result !== void 0) {
9743
+ const result = input.link.result;
9744
+ walletLinkData(result, "Saved link result", [
9745
+ "status",
9746
+ "submittedHash",
9747
+ "hash",
9748
+ "receipt",
9749
+ "replacementReason"
9750
+ ]);
9751
+ if (!["confirmed", "reverted", "unverified", "replaced"].includes(result.status) || result.submittedHash.toLowerCase() !== input.link.hash?.toLowerCase() || result.receipt.transactionHash.toLowerCase() !== result.hash.toLowerCase())
9752
+ invalid("Saved link result does not match its submission.");
9753
+ issuerBytes(result.hash, "result hash", 32);
9754
+ if (result.status === "confirmed" && (result.hash.toLowerCase() !== result.submittedHash.toLowerCase() || result.receipt.status !== "success"))
9755
+ invalid("Saved confirmed result is inconsistent.");
9756
+ }
9757
+ if (input.stage === "complete" && (input.link.stage !== "complete" || input.link.result?.status !== "confirmed"))
9758
+ invalid("Saved wallet completion requires a confirmed link.");
9759
+ if (input.link.stage === "complete" && input.link.result?.status !== "confirmed")
9760
+ invalid("Saved link completion requires confirmation.");
9761
+ if (issuerChainId(input.chainId) !== intent.chainId || !sameIssuerAddress(
9762
+ issuerAddress(input.registryAddress, "registryAddress"),
9763
+ intent.registryAddress
9764
+ ) || !sameIssuerAddress(issuerAddress(input.wallet, "wallet"), intent.newWallet) || !sameIssuerAddress(
9765
+ issuerAddress(input.existingWallet, "existingWallet"),
9766
+ intent.existingWallet
9767
+ ) || issuerBytes(input.userId, "userId", 32).toLowerCase() !== intent.userId.toLowerCase())
9768
+ invalid("Saved wallet and link identities do not match.");
9769
+ try {
9770
+ if (!sameIssuerAddress(
9771
+ accounts.privateKeyToAccount(issuerBytes(input.privateKey, "privateKey", 32)).address,
9772
+ intent.newWallet
9773
+ ))
9774
+ invalid("Saved key and wallet do not match.");
9775
+ } catch {
9776
+ invalid("Saved key and wallet do not match.");
9777
+ }
9778
+ return structuredClone(input);
9779
+ }
9780
+ function restoreSandboxLinkedWallet(input, options) {
9781
+ const progress = validateSandboxLinkedWalletProgress(input);
9782
+ if (progress.stage !== "complete")
9783
+ invalid("Check the pending link before restoring this wallet for transactions.");
9784
+ return restoreSandboxUserWallet(
9785
+ {
9786
+ wallet: progress.wallet,
9787
+ privateKey: progress.privateKey,
9788
+ userId: progress.userId,
9789
+ chainId: progress.chainId,
9790
+ registryAddress: progress.registryAddress,
9791
+ stage: "complete",
9792
+ traits: [],
9793
+ submissions: []
9794
+ },
9795
+ options
9796
+ );
9797
+ }
8644
9798
 
8645
9799
  Object.defineProperty(exports, "SANDBOX_CHAIN_ID", {
8646
9800
  enumerable: true,
@@ -8830,8 +9984,10 @@ exports.OpacityPurchaseError = OpacityPurchaseError;
8830
9984
  exports.OpacityTransferError = OpacityTransferError;
8831
9985
  exports.SANDBOX_ISSUER_DEPLOYMENT = SANDBOX_ISSUER_DEPLOYMENT;
8832
9986
  exports.SANDBOX_PURCHASE_DEPLOYMENT = SANDBOX_PURCHASE_DEPLOYMENT;
9987
+ exports.SandboxLinkedWalletCreationError = SandboxLinkedWalletCreationError;
8833
9988
  exports.SandboxTransferError = SandboxTransferError;
8834
9989
  exports.SandboxUserCreationError = SandboxUserCreationError;
9990
+ exports.SandboxWalletLinkError = SandboxWalletLinkError;
8835
9991
  exports.buildAdapterOperation = buildAdapterOperation;
8836
9992
  exports.buildConfigureVaultTransaction = buildConfigureVaultTransaction;
8837
9993
  exports.buildForcedTransferTransaction = buildForcedTransferTransaction;
@@ -8856,6 +10012,8 @@ exports.buildPurchaseWriteRequest = buildPurchaseWriteRequest;
8856
10012
  exports.buildRuleSetOperation = buildRuleSetOperation;
8857
10013
  exports.buildSandboxRegistryOperation = buildSandboxRegistryOperation;
8858
10014
  exports.buildSandboxVaultOperation = buildSandboxVaultOperation;
10015
+ exports.buildSandboxWalletLinkTransaction = buildSandboxWalletLinkTransaction;
10016
+ exports.buildSandboxWalletLinkTypedData = buildSandboxWalletLinkTypedData;
8859
10017
  exports.buildSetIssuerAdminTransaction = buildSetIssuerAdminTransaction;
8860
10018
  exports.buildSetUserTraitsTransactions = buildSetUserTraitsTransactions;
8861
10019
  exports.buildSetWalletsTransaction = buildSetWalletsTransaction;
@@ -8869,7 +10027,9 @@ exports.createPolicyAttachment = createPolicyAttachment;
8869
10027
  exports.createPolicyBatch = createPolicyBatch;
8870
10028
  exports.createPolicyReplacementBatch = createPolicyReplacementBatch;
8871
10029
  exports.createPurchasesNamespace = createPurchasesNamespace;
10030
+ exports.createSandboxLinkedWallet = createSandboxLinkedWallet;
8872
10031
  exports.createSandboxUser = createSandboxUser;
10032
+ exports.createSandboxWalletLinksNamespace = createSandboxWalletLinksNamespace;
8873
10033
  exports.createTransfersNamespace = createTransfersNamespace;
8874
10034
  exports.encodeAllowedTransfereeConfig = encodeAllowedTransfereeConfig;
8875
10035
  exports.encodeCeilingBalanceConfig = encodeCeilingBalanceConfig;
@@ -8906,12 +10066,21 @@ exports.prepareSandboxUser = prepareSandboxUser;
8906
10066
  exports.purchasePaymentTokenAbi = purchasePaymentTokenAbi;
8907
10067
  exports.purchaseRegistryAbi = purchaseRegistryAbi;
8908
10068
  exports.purchaseSecurityTokenAbi = purchaseSecurityTokenAbi;
10069
+ exports.resolveIssuerDeployment = resolveIssuerDeployment;
8909
10070
  exports.resolveSandboxUserTraits = resolveSandboxUserTraits;
10071
+ exports.restoreSandboxLinkedWallet = restoreSandboxLinkedWallet;
8910
10072
  exports.restoreSandboxUserWallet = restoreSandboxUserWallet;
8911
10073
  exports.sandboxJurisdictionTraitId = sandboxJurisdictionTraitId;
8912
10074
  exports.sandboxTraitId = sandboxTraitId;
10075
+ exports.sandboxWalletLinkAbi = sandboxWalletLinkAbi;
10076
+ exports.sandboxWalletLinkTypes = sandboxWalletLinkTypes;
8913
10077
  exports.securityTokenVaultAbi = securityTokenVaultAbi;
8914
10078
  exports.transferSandboxUser = transferSandboxUser;
10079
+ exports.validateSandboxLinkedWalletProgress = validateSandboxLinkedWalletProgress;
10080
+ exports.validateSandboxWalletLinkIntent = validateSandboxWalletLinkIntent;
10081
+ exports.validateSandboxWalletLinkParams = validateSandboxWalletLinkParams;
8915
10082
  exports.waitForSandboxUserFunding = waitForSandboxUserFunding;
10083
+ exports.walletLinkData = walletLinkData;
10084
+ exports.walletLinkWaitOptions = walletLinkWaitOptions;
8916
10085
  //# sourceMappingURL=index.cjs.map
8917
10086
  //# sourceMappingURL=index.cjs.map