@manifoldxyz/dropkit 0.1.0 → 0.2.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
@@ -25,16 +25,18 @@ __export(index_exports, {
25
25
  MINT_STATES: () => MINT_STATES,
26
26
  MintButton: () => MintButton,
27
27
  MintError: () => MintError,
28
+ PayWithPicker: () => PayWithPicker,
28
29
  REASON_CODES: () => REASON_CODES,
29
30
  VERSION: () => VERSION,
30
31
  isMintError: () => isMintError,
31
- useMint: () => useMint
32
+ useMint: () => useMint,
33
+ usePayOptions: () => usePayOptions
32
34
  });
33
35
  module.exports = __toCommonJS(index_exports);
34
36
 
35
37
  // src/react/useMint.ts
36
38
  var import_react = require("react");
37
- var import_viem8 = require("viem");
39
+ var import_viem9 = require("viem");
38
40
 
39
41
  // src/core/rpc/index.ts
40
42
  var import_viem = require("viem");
@@ -56,7 +58,12 @@ var ERROR_REASONS = [
56
58
  "RATE_LIMITED",
57
59
  "RPC_ERROR",
58
60
  "CHAIN_SWITCH_FAILED",
59
- "ALLOWLIST_UNAVAILABLE"
61
+ "ALLOWLIST_UNAVAILABLE",
62
+ // Relay cross-chain mint (spec relay-cross-chain-mint #execute-click).
63
+ "BRIDGE_UNAVAILABLE",
64
+ "BRIDGE_REFUNDED",
65
+ "BRIDGE_FAILED",
66
+ "BRIDGE_TIMEOUT"
60
67
  ];
61
68
  var FUNDS_REASONS = ["INSUFFICIENT_FUNDS"];
62
69
  var REASON_CODES = [
@@ -69,11 +76,14 @@ var MintError = class extends Error {
69
76
  reason;
70
77
  /** INVALID_CONFIG only: dotted path of the bad field, e.g. "mint.args[1]" or "price". */
71
78
  field;
79
+ /** 'relay': the message is Relay's decoded destination revert, shown as-is. */
80
+ domain;
72
81
  constructor(reason, message, opts) {
73
82
  super(message, opts?.cause === void 0 ? void 0 : { cause: opts.cause });
74
83
  this.name = "MintError";
75
84
  this.reason = reason;
76
85
  if (opts?.field !== void 0) this.field = opts.field;
86
+ if (opts?.domain !== void 0) this.domain = opts.domain;
77
87
  }
78
88
  };
79
89
  function isMintError(e) {
@@ -95,8 +105,22 @@ var CHAINS = {
95
105
  [import_chains.base.id]: import_chains.base,
96
106
  [import_chains.optimism.id]: import_chains.optimism,
97
107
  [import_chains.shape.id]: import_chains.shape,
98
- [import_chains.apeChain.id]: import_chains.apeChain
108
+ [import_chains.apeChain.id]: import_chains.apeChain,
109
+ // Relay pay chain for Sepolia drops only: never a drop chain, no Manifold bridge.
110
+ [import_chains.baseSepolia.id]: import_chains.baseSepolia
111
+ };
112
+ var CHAIN_NAMES = {
113
+ 1: "Ethereum",
114
+ 8453: "Base",
115
+ 10: "Optimism",
116
+ 360: "Shape",
117
+ 33139: "ApeChain",
118
+ 11155111: "Sepolia",
119
+ 84532: "Base Sepolia"
99
120
  };
121
+ function chainName(chainId) {
122
+ return CHAIN_NAMES[chainId] ?? CHAINS[chainId]?.name ?? `chain ${String(chainId)}`;
123
+ }
100
124
  var TRANSPORT_TIMEOUT_MS = 8e3;
101
125
  function getChain(chainId) {
102
126
  const chain = CHAINS[chainId];
@@ -334,6 +358,37 @@ function getWalletClient(chainId) {
334
358
  const request = ((args) => provider.request(args));
335
359
  return (0, import_viem2.createWalletClient)({ chain: getChain(chainId), transport: (0, import_viem2.custom)({ request }) });
336
360
  }
361
+ async function waitForDeposit(chainId, hash, timeoutMs = 12e4) {
362
+ try {
363
+ const receipt = await getWalletClient(chainId).extend(import_viem2.publicActions).waitForTransactionReceipt({ hash, confirmations: 1, timeout: timeoutMs });
364
+ return receipt.status === "reverted" ? "reverted" : "mined";
365
+ } catch {
366
+ return "unknown";
367
+ }
368
+ }
369
+ var WALLET_READ_TIMEOUT = { ms: 1e4 };
370
+ async function getConnectedBalance(address, chainId) {
371
+ const provider = eip1193();
372
+ let timer;
373
+ const timeout = new Promise((_, reject) => {
374
+ timer = setTimeout(() => reject(new MintError("RPC_ERROR", "The wallet did not report a balance in time.")), WALLET_READ_TIMEOUT.ms);
375
+ });
376
+ try {
377
+ return await Promise.race([
378
+ (async () => {
379
+ const current = Number(await provider.request({ method: "eth_chainId" }));
380
+ if (current !== chainId) {
381
+ throw new MintError("CHAIN_SWITCH_FAILED", `The wallet is on chain ${String(current)}, not ${String(chainId)}.`);
382
+ }
383
+ const hex = await provider.request({ method: "eth_getBalance", params: [address, "latest"] });
384
+ return BigInt(hex);
385
+ })(),
386
+ timeout
387
+ ]);
388
+ } finally {
389
+ clearTimeout(timer);
390
+ }
391
+ }
337
392
  var code = (e) => e?.code;
338
393
  async function switchToChain(chainId) {
339
394
  const chain = getChain(chainId);
@@ -365,31 +420,258 @@ async function switchToChain(chainId) {
365
420
  }
366
421
  }
367
422
 
423
+ // src/core/relay/client.ts
424
+ var import_viem3 = require("viem");
425
+ var RELAY_API = { mainnet: "https://api.relay.link", testnet: "https://api.testnets.relay.link" };
426
+ var MAINNET_PAY_CHAINS = [1, 8453, 10, 360, 33139];
427
+ var MAINNET_DROP_CHAINS = [1, 8453, 10, 360];
428
+ var SEPOLIA = 11155111;
429
+ var BASE_SEPOLIA = 84532;
430
+ var isRelayTestnet = (dropChainId) => dropChainId === SEPOLIA;
431
+ function payChainsFor(dropChainId) {
432
+ if (MAINNET_DROP_CHAINS.includes(dropChainId)) return [...MAINNET_PAY_CHAINS];
433
+ if (dropChainId === SEPOLIA) return [SEPOLIA, BASE_SEPOLIA];
434
+ return [];
435
+ }
436
+ var CACHE_MS = 3e4;
437
+ var cache = /* @__PURE__ */ new Map();
438
+ var cacheKey = (a) => [a.testnet, a.originChainId, a.destinationChainId, a.tx.to.toLowerCase(), a.tx.value, (0, import_viem3.keccak256)(a.tx.data), a.payer.toLowerCase(), a.recipient.toLowerCase()].join(":");
439
+ var big = (v) => {
440
+ if (typeof v === "string" && /^\d+$/.test(v)) return BigInt(v);
441
+ if (typeof v === "number" && Number.isSafeInteger(v) && v >= 0) return BigInt(v);
442
+ return 0n;
443
+ };
444
+ var obj = (v) => v !== null && typeof v === "object" ? v : {};
445
+ function moneyOf(amount, currency) {
446
+ const c = obj(currency);
447
+ const decimals = typeof c.decimals === "number" ? c.decimals : 18;
448
+ const symbol = typeof c.symbol === "string" ? c.symbol : "ETH";
449
+ const value = big(amount);
450
+ return { value, decimals, symbol, formatted: (0, import_viem3.formatUnits)(value, decimals) };
451
+ }
452
+ function revertReason(errorData) {
453
+ if (typeof errorData !== "string") return "";
454
+ const text2 = errorData.replace(/^execution reverted:?\s*/i, "").trim();
455
+ return text2 === "execution reverted" ? "" : text2;
456
+ }
457
+ function parseQuote(body, originChainId) {
458
+ const b = obj(body);
459
+ const steps = Array.isArray(b.steps) ? b.steps.map(obj) : [];
460
+ const unknownStep = () => new MintError("BRIDGE_UNAVAILABLE", "Relay returned a route this button cannot execute.");
461
+ if (steps.length !== 1 || steps[0].kind !== "transaction") throw unknownStep();
462
+ const step2 = steps[0];
463
+ const rawItems = Array.isArray(step2.items) ? step2.items.map(obj) : [];
464
+ if (rawItems.length !== 1) throw unknownStep();
465
+ const items = rawItems.map((item) => {
466
+ const d = obj(item.data);
467
+ const chainId = Number(d.chainId);
468
+ if (chainId !== originChainId || typeof d.to !== "string" || !(0, import_viem3.isHex)(d.data)) throw unknownStep();
469
+ return { chainId, to: (0, import_viem3.getAddress)(d.to), data: d.data, value: big(d.value), gas: d.gas === void 0 ? null : big(d.gas) };
470
+ });
471
+ const requestId = (0, import_viem3.isHex)(step2.requestId) ? step2.requestId : (0, import_viem3.isHex)(b.requestId) ? b.requestId : null;
472
+ if (!requestId) throw unknownStep();
473
+ const details = obj(b.details);
474
+ const currencyIn = obj(details.currencyIn);
475
+ const fees = obj(b.fees);
476
+ const amountIn = moneyOf(currencyIn.amount, currencyIn.currency);
477
+ if (obj(currencyIn.currency).address !== void 0 && obj(currencyIn.currency).address !== import_viem3.zeroAddress) throw unknownStep();
478
+ if (items[0].value !== amountIn.value) throw unknownStep();
479
+ return {
480
+ requestId,
481
+ originChainId,
482
+ items,
483
+ amountIn,
484
+ relayFee: moneyOf(obj(fees.relayer).amount, obj(fees.relayer).currency ?? currencyIn.currency),
485
+ originGas: moneyOf(obj(fees.gas).amount, obj(fees.gas).currency ?? currencyIn.currency),
486
+ quotedAt: Math.floor(Date.now() / 1e3)
487
+ };
488
+ }
489
+ var RELAY_TIMEOUTS = { quoteMs: 1e4, statusMs: 1e4 };
490
+ function raceSignal(p, signal) {
491
+ if (!signal) return p;
492
+ if (signal.aborted) return Promise.reject(new DOMException("Relay quote cancelled", "AbortError"));
493
+ return new Promise((resolve, reject) => {
494
+ const onAbort = () => reject(new DOMException("Relay quote cancelled", "AbortError"));
495
+ signal.addEventListener("abort", onAbort, { once: true });
496
+ p.then(
497
+ (v) => {
498
+ signal.removeEventListener("abort", onAbort);
499
+ resolve(v);
500
+ },
501
+ (e) => {
502
+ signal.removeEventListener("abort", onAbort);
503
+ reject(e instanceof Error ? e : new Error(String(e)));
504
+ }
505
+ );
506
+ });
507
+ }
508
+ async function timedFetch(f, url, init, ms) {
509
+ const controller = new AbortController();
510
+ let t;
511
+ const timeout = new Promise((_, reject) => {
512
+ t = setTimeout(() => {
513
+ controller.abort();
514
+ reject(new DOMException("Relay request timed out", "TimeoutError"));
515
+ }, ms);
516
+ });
517
+ try {
518
+ return await Promise.race([
519
+ (async () => {
520
+ const res = await f(url, { ...init, signal: controller.signal });
521
+ const json = await res.json().catch(() => null);
522
+ return { ok: res.ok, status: res.status, json };
523
+ })(),
524
+ timeout
525
+ ]);
526
+ } finally {
527
+ clearTimeout(t);
528
+ }
529
+ }
530
+ async function requestQuote(a) {
531
+ const f = a.fetch ?? globalThis.fetch;
532
+ const body = {
533
+ user: a.payer,
534
+ recipient: a.recipient,
535
+ refundTo: a.payer,
536
+ // R11: without refundTo, Relay disables automatic refunds.
537
+ originChainId: a.originChainId,
538
+ destinationChainId: a.destinationChainId,
539
+ originCurrency: import_viem3.zeroAddress,
540
+ destinationCurrency: import_viem3.zeroAddress,
541
+ tradeType: "EXACT_OUTPUT",
542
+ amount: a.tx.value.toString(),
543
+ txs: [{ to: a.tx.to, value: a.tx.value.toString(), data: a.tx.data }]
544
+ };
545
+ let res;
546
+ try {
547
+ res = await timedFetch(f, `${a.testnet ? RELAY_API.testnet : RELAY_API.mainnet}/quote`, {
548
+ method: "POST",
549
+ headers: { "Content-Type": "application/json" },
550
+ body: JSON.stringify(body)
551
+ }, RELAY_TIMEOUTS.quoteMs);
552
+ } catch (e) {
553
+ throw new MintError("BRIDGE_UNAVAILABLE", "Could not reach Relay for a cross-chain quote.", { cause: e });
554
+ }
555
+ const json = res.json;
556
+ if (res.ok) return parseQuote(json, a.originChainId);
557
+ const j = obj(json);
558
+ if (j.errorCode === "DESTINATION_TX_FAILED") {
559
+ const reason = revertReason(j.errorData);
560
+ throw new MintError("SIMULATION_REVERTED", reason || "The mint would fail on the drop network.", { cause: json, domain: "relay" });
561
+ }
562
+ if (res.status === 401 || j.errorCode === "UNAUTHORIZED_QUOTE") {
563
+ console.warn("dropkit: Relay rejected the quote (401 UNAUTHORIZED_QUOTE). Relay gates quotes that carry a referrer or need an API key.");
564
+ throw new MintError("BRIDGE_UNAVAILABLE", "Relay refused the cross-chain quote.", { cause: json });
565
+ }
566
+ if (res.status === 429) throw new MintError("RATE_LIMITED", "Relay is rate limiting quotes. Try again in a moment.", { cause: json });
567
+ throw new MintError("BRIDGE_UNAVAILABLE", `Relay could not quote this mint (${String(res.status)}).`, { cause: json });
568
+ }
569
+ function getQuote(a) {
570
+ const k = cacheKey(a);
571
+ const now = Date.now();
572
+ const hit = cache.get(k);
573
+ if (!a.fresh && hit && now - hit.at < CACHE_MS) return raceSignal(hit.quote, a.signal);
574
+ const quote = requestQuote(a);
575
+ cache.set(k, { at: now, quote });
576
+ quote.catch(() => {
577
+ if (cache.get(k)?.quote === quote) cache.delete(k);
578
+ });
579
+ return raceSignal(quote, a.signal);
580
+ }
581
+ var realReason = (v) => typeof v === "string" && v !== "" && v !== "N/A" ? v : null;
582
+ async function getStatus(a) {
583
+ const f = a.fetch ?? globalThis.fetch;
584
+ const res = await raceSignal(timedFetch(f, `${a.testnet ? RELAY_API.testnet : RELAY_API.mainnet}/intents/status/v3?requestId=${a.requestId}`, {}, RELAY_TIMEOUTS.statusMs), a.signal);
585
+ if (!res.ok) throw new Error(`Relay status ${String(res.status)}`);
586
+ const j = obj(res.json);
587
+ return {
588
+ status: typeof j.status === "string" ? j.status : "unknown",
589
+ txHashes: Array.isArray(j.txHashes) ? j.txHashes.filter((h) => (0, import_viem3.isHex)(h)) : [],
590
+ failReason: realReason(j.failReason),
591
+ refundFailReason: realReason(j.refundFailReason)
592
+ };
593
+ }
594
+ var KEEP_POLLING = ["waiting", "depositing", "pending", "submitted", "delayed"];
595
+ var TERMINAL = ["success", "refund", "failure"];
596
+
597
+ // src/core/relay/status.ts
598
+ var POLL_FAST_MS = 2e3;
599
+ var POLL_SLOW_MS = 5e3;
600
+ var POLL_FAST_FOR_MS = 6e4;
601
+ var POLL_CAP_MS = 10 * 6e4;
602
+ var defaultSleep = (ms, signal) => new Promise((resolve, reject) => {
603
+ const t = setTimeout(resolve, ms);
604
+ signal?.addEventListener("abort", () => {
605
+ clearTimeout(t);
606
+ reject(new DOMException("Relay polling cancelled", "AbortError"));
607
+ }, { once: true });
608
+ });
609
+ async function pollUntilTerminal(a) {
610
+ const sleep = a.sleep ?? defaultSleep;
611
+ const now = a.now ?? Date.now;
612
+ const start = now();
613
+ let warned = false;
614
+ for (; ; ) {
615
+ a.assertActive?.();
616
+ try {
617
+ const status = await getStatus({ testnet: a.testnet, requestId: a.requestId, ...a.signal ? { signal: a.signal } : {}, ...a.fetch ? { fetch: a.fetch } : {} });
618
+ if (TERMINAL.includes(status.status)) return { kind: "terminal", status };
619
+ if (!KEEP_POLLING.includes(status.status) && !warned) {
620
+ warned = true;
621
+ console.warn(`dropkit: Relay status "${status.status}" for ${a.requestId}; still waiting.`);
622
+ }
623
+ } catch (e) {
624
+ if (e?.name === "AbortError") throw e;
625
+ }
626
+ a.assertActive?.();
627
+ const elapsed = now() - start;
628
+ if (elapsed >= POLL_CAP_MS) return { kind: "timeout" };
629
+ await sleep(elapsed < POLL_FAST_FOR_MS ? POLL_FAST_MS : POLL_SLOW_MS, a.signal);
630
+ }
631
+ }
632
+
633
+ // src/core/relay/outstanding.ts
634
+ var memory = /* @__PURE__ */ new Map();
635
+ var claimed = /* @__PURE__ */ new Set();
636
+ function outstandingKey(config, payer) {
637
+ const target = config.type === "manifold-claim" ? `${config.creatorContract.toLowerCase()}:${config.instanceId}` : config.address.toLowerCase();
638
+ return `${String(config.chainId)}:${target}:${payer.toLowerCase()}`;
639
+ }
640
+ var getOutstanding = (key) => memory.get(key) ?? null;
641
+ var setOutstanding = (key, out) => void memory.set(key, out);
642
+ var clearOutstanding = (key) => void memory.delete(key);
643
+ function claimDeposit(key) {
644
+ if (claimed.has(key) || memory.has(key)) return false;
645
+ claimed.add(key);
646
+ return true;
647
+ }
648
+ var releaseDeposit = (key) => void claimed.delete(key);
649
+
368
650
  // src/core/adapters/manifold-claim/index.ts
369
- var import_viem4 = require("viem");
651
+ var import_viem5 = require("viem");
370
652
 
371
653
  // src/core/adapters/manifold-claim/abis.ts
372
- var import_viem3 = require("viem");
373
- var CLAIM_721_V5_ABI = (0, import_viem3.parseAbi)([
654
+ var import_viem4 = require("viem");
655
+ var CLAIM_721_V5_ABI = (0, import_viem4.parseAbi)([
374
656
  "function getClaim(address creatorContractAddress, uint256 instanceId) view returns ((uint32 total, uint32 totalMax, uint32 walletMax, uint48 startDate, uint48 endDate, uint8 storageProtocol, uint8 contractVersion, bool identical, bytes32 merkleRoot, string location, uint256 cost, address paymentReceiver, address erc20) claim)"
375
657
  ]);
376
- var CLAIM_1155_V5_ABI = (0, import_viem3.parseAbi)([
658
+ var CLAIM_1155_V5_ABI = (0, import_viem4.parseAbi)([
377
659
  "function getClaim(address creatorContractAddress, uint256 instanceId) view returns ((uint32 total, uint32 totalMax, uint32 walletMax, uint48 startDate, uint48 endDate, uint8 storageProtocol, bytes32 merkleRoot, string location, uint256 tokenId, uint256 cost, address paymentReceiver, address erc20) claim)"
378
660
  ]);
379
- var CLAIM_721_V7_ABI = (0, import_viem3.parseAbi)([
661
+ var CLAIM_721_V7_ABI = (0, import_viem4.parseAbi)([
380
662
  "function getClaim(address creatorContractAddress, uint256 instanceId) view returns ((uint32 total, uint32 totalMax, uint32 walletMax, uint48 startDate, uint48 endDate, uint8 storageProtocol, uint8 contractVersion, bool identical, bytes32 merkleRoot, string location, uint256 cost, address paymentReceiver, address erc20, address signingAddress) claim)"
381
663
  ]);
382
- var CLAIM_1155_V7_ABI = (0, import_viem3.parseAbi)([
664
+ var CLAIM_1155_V7_ABI = (0, import_viem4.parseAbi)([
383
665
  "function getClaim(address creatorContractAddress, uint256 instanceId) view returns ((uint32 total, uint32 totalMax, uint32 walletMax, uint48 startDate, uint48 endDate, uint8 storageProtocol, bytes32 merkleRoot, string location, uint256 tokenId, uint256 cost, address paymentReceiver, address erc20, address signingAddress) claim)"
384
666
  ]);
385
- var EXTENSION_COMMON_ABI = (0, import_viem3.parseAbi)([
667
+ var EXTENSION_COMMON_ABI = (0, import_viem4.parseAbi)([
386
668
  "function MINT_FEE() view returns (uint256)",
387
669
  "function MINT_FEE_MERKLE() view returns (uint256)",
388
670
  "function getTotalMints(address minter, address creatorContractAddress, uint256 instanceId) view returns (uint32)",
389
671
  "function checkMintIndices(address creatorContractAddress, uint256 instanceId, uint32[] mintIndices) view returns (bool[] minted)",
390
672
  "function mintProxy(address creatorContractAddress, uint256 instanceId, uint16 mintCount, uint32[] mintIndices, bytes32[][] merkleProofs, address mintFor) payable"
391
673
  ]);
392
- var EXTENSION_ERRORS_ABI = (0, import_viem3.parseAbi)([
674
+ var EXTENSION_ERRORS_ABI = (0, import_viem4.parseAbi)([
393
675
  "error Error(string message)",
394
676
  "error Panic(uint256 code)",
395
677
  "error ClaimInactive()",
@@ -406,8 +688,8 @@ var EXTENSION_ERRORS_ABI = (0, import_viem3.parseAbi)([
406
688
  "error TokenDNE()",
407
689
  "error TooManyRequested()"
408
690
  ]);
409
- var CREATOR_ABI = (0, import_viem3.parseAbi)(["function getExtensions() view returns (address[])"]);
410
- var ERC20_ABI = (0, import_viem3.parseAbi)([
691
+ var CREATOR_ABI = (0, import_viem4.parseAbi)(["function getExtensions() view returns (address[])"]);
692
+ var ERC20_ABI = (0, import_viem4.parseAbi)([
411
693
  "function symbol() view returns (string)",
412
694
  "function decimals() view returns (uint8)",
413
695
  "function balanceOf(address owner) view returns (uint256)",
@@ -435,13 +717,13 @@ async function getJson(fetchFn, url, signal, what) {
435
717
  }
436
718
  var isRecord = (v) => typeof v === "object" && v !== null;
437
719
  var scopeCaches = /* @__PURE__ */ new WeakMap();
438
- function memo(cache, key, signal, load) {
439
- const hit = cache.get(key);
720
+ function memo(cache2, key, signal, load) {
721
+ const hit = cache2.get(key);
440
722
  if (hit) return hit;
441
723
  const p = withSignal(load(), signal);
442
- cache.set(key, p);
724
+ cache2.set(key, p);
443
725
  p.catch(() => {
444
- if (cache.get(key) === p) cache.delete(key);
726
+ if (cache2.get(key) === p) cache2.delete(key);
445
727
  });
446
728
  return p;
447
729
  }
@@ -491,8 +773,8 @@ async function fetchMerkleEntries(args) {
491
773
  scoped = { signal: args.signal, instances: /* @__PURE__ */ new Map(), entries: /* @__PURE__ */ new Map() };
492
774
  scopeCaches.set(args.scope, scoped);
493
775
  }
494
- const cache = scoped;
495
- const entries = await memo(cache.entries, key, cache.signal, () => loadEntries(fetchFn, args.api, args.instanceId, args.recipient, cache));
776
+ const cache2 = scoped;
777
+ const entries = await memo(cache2.entries, key, cache2.signal, () => loadEntries(fetchFn, args.api, args.instanceId, args.recipient, cache2));
496
778
  return entries.slice();
497
779
  }
498
780
 
@@ -537,14 +819,14 @@ var GAS_UNITS_APPROVE = 60000n;
537
819
  var dropResolved = /* @__PURE__ */ new WeakMap();
538
820
  var WALLET_DATA_BRAND = /* @__PURE__ */ Symbol("manifold-claim-wallet-data");
539
821
  var isWalletData = (d) => typeof d === "object" && d !== null && d.brand === WALLET_DATA_BRAND;
540
- var isAllowlist = (claim) => claim.merkleRoot !== import_viem4.zeroHash;
822
+ var isAllowlist = (claim) => claim.merkleRoot !== import_viem5.zeroHash;
541
823
  function isContractFailure(e) {
542
- if (!(e instanceof import_viem4.BaseError)) return false;
543
- return e.walk((x) => x instanceof import_viem4.ContractFunctionRevertedError || x.name === "ContractFunctionZeroDataError") !== null;
824
+ if (!(e instanceof import_viem5.BaseError)) return false;
825
+ return e.walk((x) => x instanceof import_viem5.ContractFunctionRevertedError || x.name === "ContractFunctionZeroDataError") !== null;
544
826
  }
545
827
  function toRpcError(e) {
546
828
  if (e instanceof MintError) return e;
547
- const msg = e instanceof import_viem4.BaseError ? e.shortMessage : e instanceof Error ? e.message : String(e);
829
+ const msg = e instanceof import_viem5.BaseError ? e.shortMessage : e instanceof Error ? e.message : String(e);
548
830
  return new MintError("RPC_ERROR", `RPC request failed: ${msg}`, { cause: e });
549
831
  }
550
832
  async function rpc(fn) {
@@ -557,19 +839,19 @@ async function rpc(fn) {
557
839
  function revertData(e) {
558
840
  const hit = e.walk((x) => {
559
841
  const d2 = x.data;
560
- return (0, import_viem4.isHex)(d2) && d2.length >= 10 || typeof d2 === "object" && d2 !== null && (0, import_viem4.isHex)(d2.data);
842
+ return (0, import_viem5.isHex)(d2) && d2.length >= 10 || typeof d2 === "object" && d2 !== null && (0, import_viem5.isHex)(d2.data);
561
843
  });
562
844
  const d = hit?.data;
563
- if ((0, import_viem4.isHex)(d)) return d;
845
+ if ((0, import_viem5.isHex)(d)) return d;
564
846
  const inner = d?.data;
565
- return (0, import_viem4.isHex)(inner) ? inner : void 0;
847
+ return (0, import_viem5.isHex)(inner) ? inner : void 0;
566
848
  }
567
849
  function decodeRevert(e) {
568
- if (!(e instanceof import_viem4.BaseError)) return null;
850
+ if (!(e instanceof import_viem5.BaseError)) return null;
569
851
  const data = revertData(e);
570
852
  if (data) {
571
853
  try {
572
- const d = (0, import_viem4.decodeErrorResult)({ abi: EXTENSION_ERRORS_ABI, data });
854
+ const d = (0, import_viem5.decodeErrorResult)({ abi: EXTENSION_ERRORS_ABI, data });
573
855
  if (d.errorName === "Error" && typeof d.args?.[0] === "string") return d.args[0];
574
856
  if (d.errorName === "Panic") return `Panic(${String(d.args?.[0])})`;
575
857
  return d.errorName;
@@ -578,33 +860,33 @@ function decodeRevert(e) {
578
860
  }
579
861
  }
580
862
  const named = e.walk((x) => ["ExecutionRevertedError", "InsufficientFundsError"].includes(x.name));
581
- return named instanceof import_viem4.BaseError ? named.shortMessage : null;
863
+ return named instanceof import_viem5.BaseError ? named.shortMessage : null;
582
864
  }
583
- var money = (value, decimals, symbol) => ({ value, decimals, symbol, formatted: (0, import_viem4.formatUnits)(value, decimals) });
865
+ var money = (value, decimals, symbol) => ({ value, decimals, symbol, formatted: (0, import_viem5.formatUnits)(value, decimals) });
584
866
  function timestamp(v, field) {
585
867
  if (v === 0n) return null;
586
868
  if (v > BigInt(Number.MAX_SAFE_INTEGER)) throw new MintError("RPC_ERROR", `Claim ${field} does not fit a safe integer`);
587
869
  return Number(v);
588
870
  }
589
871
  function parseConfig(config) {
590
- if (!(0, import_viem4.isAddress)(config.creatorContract, { strict: false })) {
872
+ if (!(0, import_viem5.isAddress)(config.creatorContract, { strict: false })) {
591
873
  throw new MintError("INVALID_CONFIG", "creatorContract is not a valid address.", { field: "creatorContract" });
592
874
  }
593
- if (config.extension !== void 0 && !(0, import_viem4.isAddress)(config.extension, { strict: false })) {
875
+ if (config.extension !== void 0 && !(0, import_viem5.isAddress)(config.extension, { strict: false })) {
594
876
  throw new MintError("INVALID_CONFIG", "extension is not a valid address.", { field: "extension" });
595
877
  }
596
878
  if (typeof config.instanceId !== "string" || !/^\d+$/.test(config.instanceId)) {
597
879
  throw new MintError("INVALID_CONFIG", "instanceId must be a non-negative integer string.", { field: "instanceId" });
598
880
  }
599
881
  return {
600
- creator: (0, import_viem4.getAddress)(config.creatorContract),
882
+ creator: (0, import_viem5.getAddress)(config.creatorContract),
601
883
  instanceId: BigInt(config.instanceId),
602
- extension: config.extension === void 0 ? void 0 : (0, import_viem4.getAddress)(config.extension)
884
+ extension: config.extension === void 0 ? void 0 : (0, import_viem5.getAddress)(config.extension)
603
885
  };
604
886
  }
605
887
  var notFound = (config) => new MintError("CLAIM_NOT_FOUND", `No Manifold claim ${config.instanceId} found on ${config.creatorContract}.`);
606
888
  async function readClaim(client, entry, creator, instanceId, config) {
607
- const address = (0, import_viem4.getAddress)(entry.address);
889
+ const address = (0, import_viem5.getAddress)(entry.address);
608
890
  const args = [creator, instanceId];
609
891
  try {
610
892
  switch (entry.abi) {
@@ -643,7 +925,7 @@ function base2(c) {
643
925
  };
644
926
  }
645
927
  async function hasCode(client, address) {
646
- const code2 = await rpc(() => client.getCode({ address: (0, import_viem4.getAddress)(address) }));
928
+ const code2 = await rpc(() => client.getCode({ address: (0, import_viem5.getAddress)(address) }));
647
929
  return code2 !== void 0 && code2 !== "0x";
648
930
  }
649
931
  var unknownExtension = (address, why) => new MintError("UNKNOWN_EXTENSION", `Extension ${address} ${why}.`);
@@ -687,21 +969,21 @@ async function resolveExtension(ctx, config) {
687
969
  }
688
970
  async function loadDrop(ctx, config) {
689
971
  const { entry, claim } = await resolveExtension(ctx, config);
690
- if (claim.signingAddress !== null && claim.signingAddress !== import_viem4.zeroAddress) {
972
+ if (claim.signingAddress !== null && claim.signingAddress !== import_viem5.zeroAddress) {
691
973
  throw new MintError("SIGNATURE_CLAIM", "This claim mints with a signature (redemption code), which this button does not support.");
692
974
  }
693
975
  const { client } = ctx;
694
- const extension = (0, import_viem4.getAddress)(entry.address);
976
+ const extension = (0, import_viem5.getAddress)(entry.address);
695
977
  const fee = await rpc(
696
978
  () => client.readContract({ address: extension, abi: EXTENSION_COMMON_ABI, functionName: isAllowlist(claim) ? "MINT_FEE_MERKLE" : "MINT_FEE" })
697
979
  );
698
980
  const native = client.chain?.nativeCurrency ?? { symbol: "ETH", decimals: 18 };
699
981
  let unitPrice;
700
982
  let paymentToken = null;
701
- if (claim.erc20 === import_viem4.zeroAddress) {
983
+ if (claim.erc20 === import_viem5.zeroAddress) {
702
984
  unitPrice = money(claim.cost, native.decimals, native.symbol);
703
985
  } else {
704
- paymentToken = (0, import_viem4.getAddress)(claim.erc20);
986
+ paymentToken = (0, import_viem5.getAddress)(claim.erc20);
705
987
  const token = paymentToken;
706
988
  const [symbol, decimals] = await rpc(
707
989
  () => Promise.all([
@@ -732,9 +1014,9 @@ async function loadWallet(ctx, config, args) {
732
1014
  const { client } = ctx;
733
1015
  const resolved = dropResolved.get(args.drop) ?? (await loadDrop(ctx, config)).resolved;
734
1016
  const { entry, claim } = resolved;
735
- const extension = (0, import_viem4.getAddress)(entry.address);
736
- const payer = (0, import_viem4.getAddress)(args.payer);
737
- const recipient = (0, import_viem4.getAddress)(args.recipient);
1017
+ const extension = (0, import_viem5.getAddress)(entry.address);
1018
+ const payer = (0, import_viem5.getAddress)(args.payer);
1019
+ const recipient = (0, import_viem5.getAddress)(args.recipient);
738
1020
  let walletMinted = null;
739
1021
  let walletMax = null;
740
1022
  let allowlistRemaining = null;
@@ -770,7 +1052,7 @@ async function loadWallet(ctx, config, args) {
770
1052
  maxQuantity = Number(left);
771
1053
  if (left === 0n) ineligible = { reason: "WALLET_LIMIT", message: `This wallet has already minted its limit of ${walletMax}.` };
772
1054
  }
773
- const token = claim.erc20 === import_viem4.zeroAddress ? null : (0, import_viem4.getAddress)(claim.erc20);
1055
+ const token = claim.erc20 === import_viem5.zeroAddress ? null : (0, import_viem5.getAddress)(claim.erc20);
774
1056
  const [native, erc20, gasPrice] = await rpc(
775
1057
  () => Promise.all([
776
1058
  client.getBalance({ address: payer }),
@@ -798,8 +1080,8 @@ async function buildPlan(ctx, attempt, wallet) {
798
1080
  throw new MintError("INVALID_CONFIG", `quantity must be an integer from 1 to ${MAX_MINT_COUNT}.`, { field: "quantity" });
799
1081
  }
800
1082
  const { creator, instanceId } = parseConfig(config);
801
- const recipient = (0, import_viem4.getAddress)(attempt.recipient);
802
- const payer = (0, import_viem4.getAddress)(attempt.payer);
1083
+ const recipient = (0, import_viem5.getAddress)(attempt.recipient);
1084
+ const payer = (0, import_viem5.getAddress)(attempt.payer);
803
1085
  let data = wallet.data;
804
1086
  if (!isWalletData(data) || !sameClaim(data.config, config) || data.recipient !== recipient) {
805
1087
  const drop = (await loadDrop(ctx, config)).drop;
@@ -807,7 +1089,7 @@ async function buildPlan(ctx, attempt, wallet) {
807
1089
  }
808
1090
  if (!isWalletData(data)) throw new MintError("RPC_ERROR", "Wallet read produced no plan data.");
809
1091
  const { entry, claim, fee } = data.resolved;
810
- const extension = (0, import_viem4.getAddress)(entry.address);
1092
+ const extension = (0, import_viem5.getAddress)(entry.address);
811
1093
  let mintIndices = [];
812
1094
  let merkleProofs = [];
813
1095
  if (isAllowlist(claim)) {
@@ -819,7 +1101,7 @@ async function buildPlan(ctx, attempt, wallet) {
819
1101
  merkleProofs = picked.map((p) => p.proof);
820
1102
  }
821
1103
  const qty = BigInt(quantity);
822
- const erc20 = claim.erc20 === import_viem4.zeroAddress ? null : (0, import_viem4.getAddress)(claim.erc20);
1104
+ const erc20 = claim.erc20 === import_viem5.zeroAddress ? null : (0, import_viem5.getAddress)(claim.erc20);
823
1105
  const cost = claim.cost * qty;
824
1106
  const value = fee * qty + (erc20 ? 0n : cost);
825
1107
  let approve = null;
@@ -830,7 +1112,7 @@ async function buildPlan(ctx, attempt, wallet) {
830
1112
  token: erc20,
831
1113
  spender: extension,
832
1114
  amount: cost,
833
- tx: { to: erc20, data: (0, import_viem4.encodeFunctionData)({ abi: ERC20_ABI, functionName: "approve", args: [extension, cost] }), value: 0n }
1115
+ tx: { to: erc20, data: (0, import_viem5.encodeFunctionData)({ abi: ERC20_ABI, functionName: "approve", args: [extension, cost] }), value: 0n }
834
1116
  };
835
1117
  }
836
1118
  }
@@ -838,7 +1120,7 @@ async function buildPlan(ctx, attempt, wallet) {
838
1120
  approve,
839
1121
  mint: {
840
1122
  to: extension,
841
- data: (0, import_viem4.encodeFunctionData)({
1123
+ data: (0, import_viem5.encodeFunctionData)({
842
1124
  abi: EXTENSION_COMMON_ABI,
843
1125
  functionName: "mintProxy",
844
1126
  args: [creator, instanceId, quantity, mintIndices, merkleProofs.map((p) => [...p]), recipient]
@@ -848,13 +1130,18 @@ async function buildPlan(ctx, attempt, wallet) {
848
1130
  tokenContract: creator
849
1131
  };
850
1132
  }
1133
+ var RELAY_VERIFIED = ["V6", "V7"];
851
1134
  var manifoldClaimAdapter = {
1135
+ supportsRelay: (drop) => {
1136
+ const resolved = dropResolved.get(drop);
1137
+ return resolved !== void 0 && RELAY_VERIFIED.includes(resolved.entry.generation) && drop.paymentToken === null;
1138
+ },
852
1139
  readDrop: async (ctx, config) => (await loadDrop(ctx, config)).drop,
853
1140
  readWallet: loadWallet,
854
1141
  buildPlan,
855
1142
  simulate: async (ctx, attempt, plan) => {
856
1143
  try {
857
- await ctx.client.call({ account: (0, import_viem4.getAddress)(attempt.payer), to: plan.mint.to, data: plan.mint.data, value: plan.mint.value });
1144
+ await ctx.client.call({ account: (0, import_viem5.getAddress)(attempt.payer), to: plan.mint.to, data: plan.mint.data, value: plan.mint.value });
858
1145
  } catch (e) {
859
1146
  const text2 = decodeRevert(e);
860
1147
  if (text2 === null) throw toRpcError(e);
@@ -864,10 +1151,10 @@ var manifoldClaimAdapter = {
864
1151
  };
865
1152
 
866
1153
  // src/core/adapters/contract/index.ts
867
- var import_viem6 = require("viem");
1154
+ var import_viem7 = require("viem");
868
1155
 
869
1156
  // src/core/machine.ts
870
- var import_viem5 = require("viem");
1157
+ var import_viem6 = require("viem");
871
1158
  var MINT_STATES = [
872
1159
  "loading",
873
1160
  "unsupported",
@@ -904,9 +1191,15 @@ function initialState(opts) {
904
1191
  attempt: null,
905
1192
  result: null,
906
1193
  pendingRefresh: false,
907
- now: 0
1194
+ now: 0,
1195
+ crossChain: false,
1196
+ routePending: false,
1197
+ route: null,
1198
+ short: null
908
1199
  };
909
1200
  }
1201
+ var NO_ROUTE = { crossChain: false, routePending: false, route: null, short: null };
1202
+ var PICK_ANOTHER = "Pick another network to pay from.";
910
1203
  function toQuantity(q, max) {
911
1204
  if (Number.isNaN(q)) return 1;
912
1205
  return Math.max(1, Math.min(Math.floor(q), max));
@@ -931,11 +1224,17 @@ function derive(s, unsupported) {
931
1224
  if (walletRead.ineligible !== null) {
932
1225
  return { name: "ineligible", reason: walletRead.ineligible.reason, message: walletRead.ineligible.message };
933
1226
  }
1227
+ if (s.routePending) return named("loading");
1228
+ if (s.route?.kind === "relay") return named("live");
1229
+ if (s.short !== null) {
1230
+ return { name: "insufficient-funds", reason: "INSUFFICIENT_FUNDS", message: `Not enough ${s.short.symbol} on ${s.short.name}. ${PICK_ANOTHER}` };
1231
+ }
934
1232
  const total = selectTotal(s);
935
1233
  if (total !== null) {
936
1234
  const { native, erc20 } = total;
937
1235
  if (native.value + walletRead.gasReserve > walletRead.balances.native) {
938
- return { name: "insufficient-funds", reason: "INSUFFICIENT_FUNDS", message: `Not enough ${native.symbol} to cover the mint.` };
1236
+ const more = s.crossChain ? ` ${PICK_ANOTHER}` : "";
1237
+ return { name: "insufficient-funds", reason: "INSUFFICIENT_FUNDS", message: `Not enough ${native.symbol} to cover the mint.${more}` };
939
1238
  }
940
1239
  if (erc20 !== null && walletRead.balances.erc20 !== null && erc20.value > walletRead.balances.erc20) {
941
1240
  return { name: "insufficient-funds", reason: "INSUFFICIENT_FUNDS", message: `Not enough ${erc20.symbol} to cover the mint.` };
@@ -964,7 +1263,7 @@ function reducer(state, event) {
964
1263
  const next2 = { ...state, readGen: event.gen };
965
1264
  return inFlight ? { ...next2, pendingRefresh: true } : settle(next2);
966
1265
  }
967
- const next = { ...state, readGen: event.gen, drop: null, walletRead: null, blocked: null, walletGen: state.walletGen + 1 };
1266
+ const next = { ...state, ...NO_ROUTE, readGen: event.gen, drop: null, walletRead: null, blocked: null, walletGen: state.walletGen + 1 };
968
1267
  return inFlight ? { ...next, pendingRefresh: true } : settle(next, false);
969
1268
  }
970
1269
  case "DROP_LOADED": {
@@ -982,16 +1281,22 @@ function reducer(state, event) {
982
1281
  case "READ_FAILED":
983
1282
  return event.gen !== state.readGen ? state : failed(event.reason, event.message);
984
1283
  case "ACCOUNT_CHANGED": {
985
- const next = { ...state, account: event.account, walletRead: null, blocked: null, walletGen: state.walletGen + 1 };
1284
+ const next = { ...state, ...NO_ROUTE, account: event.account, walletRead: null, blocked: null, walletGen: state.walletGen + 1 };
986
1285
  if (inFlight) return { ...next, pendingRefresh: true };
987
1286
  if (state.name === "connecting") return event.account === null ? next : settle({ ...next, name: "loading" }, false);
988
1287
  return settle(next);
989
1288
  }
990
1289
  case "WALLET_LOADED": {
991
1290
  if (event.gen !== state.walletGen) return state;
992
- const next = { ...state, walletRead: event.read, blocked: null };
1291
+ const route = event.routePending ? { routePending: state.route === null && state.short === null, route: state.route, short: state.short } : { routePending: false, route: null, short: null };
1292
+ const next = { ...state, ...route, crossChain: event.crossChain ?? false, walletRead: event.read, blocked: null };
993
1293
  return inFlight ? { ...next, pendingRefresh: true } : settle(clampQuantity(next));
994
1294
  }
1295
+ case "ROUTE_LOADED": {
1296
+ if (event.gen !== state.walletGen) return state;
1297
+ const next = { ...state, routePending: false, route: event.route, short: event.short };
1298
+ return inFlight ? { ...next, pendingRefresh: true } : settle(next);
1299
+ }
995
1300
  case "WALLET_FAILED":
996
1301
  return event.gen !== state.walletGen ? state : failed(event.reason, event.message);
997
1302
  case "CONNECT_STARTED":
@@ -1005,7 +1310,9 @@ function reducer(state, event) {
1005
1310
  return inFlight ? { ...next, pendingRefresh: true } : settle(next);
1006
1311
  }
1007
1312
  case "ATTEMPT_STARTED":
1008
- return state.name === "live" && state.attempt === null ? { ...state, attempt: event.attempt } : state;
1313
+ if (state.attempt !== null) return state;
1314
+ if (event.resume === true) return IN_FLIGHT_STATES.includes(state.name) ? state : { ...state, attempt: event.attempt };
1315
+ return state.name === "live" ? { ...state, attempt: event.attempt } : state;
1009
1316
  case "SWITCHING_CHAIN":
1010
1317
  return step(state, event.attemptId, "switching-chain");
1011
1318
  case "APPROVING":
@@ -1047,6 +1354,7 @@ function reducer(state, event) {
1047
1354
  const next = {
1048
1355
  ...state,
1049
1356
  readGen: event.gen,
1357
+ ...NO_ROUTE,
1050
1358
  walletRead: null,
1051
1359
  blocked: null,
1052
1360
  walletGen: state.walletGen + 1,
@@ -1092,7 +1400,7 @@ function selectTotal(state) {
1092
1400
  return { native: money2(priceTotal + fee.value * q, price), erc20: null };
1093
1401
  }
1094
1402
  function money2(value, like) {
1095
- return { value, decimals: like.decimals, symbol: like.symbol, formatted: (0, import_viem5.formatUnits)(value, like.decimals) };
1403
+ return { value, decimals: like.decimals, symbol: like.symbol, formatted: (0, import_viem6.formatUnits)(value, like.decimals) };
1096
1404
  }
1097
1405
  function canAct(state) {
1098
1406
  return state.attempt === null && CLICKABLE_STATES.includes(state.name);
@@ -1100,7 +1408,7 @@ function canAct(state) {
1100
1408
 
1101
1409
  // src/core/adapters/contract/index.ts
1102
1410
  var PLACEHOLDERS = ["{recipient}", "{payer}", "{quantity}", "{unitPrice}", "{totalPrice}"];
1103
- var ERC20_ABI2 = (0, import_viem6.parseAbi)([
1411
+ var ERC20_ABI2 = (0, import_viem7.parseAbi)([
1104
1412
  "function symbol() view returns (string)",
1105
1413
  "function decimals() view returns (uint8)",
1106
1414
  "function balanceOf(address) view returns (uint256)",
@@ -1115,7 +1423,7 @@ var NUMERIC_TYPE = /^u?int\d*$/;
1115
1423
  function bad(field, message) {
1116
1424
  throw new MintError("INVALID_CONFIG", `${field} ${message}`, { field });
1117
1425
  }
1118
- var isHexAddress = (a) => typeof a === "string" && (0, import_viem6.isAddress)(a, { strict: false });
1426
+ var isHexAddress = (a) => typeof a === "string" && (0, import_viem7.isAddress)(a, { strict: false });
1119
1427
  function functionsNamed(abi, name) {
1120
1428
  return abi.filter((i) => i.type === "function" && i.name === name);
1121
1429
  }
@@ -1172,7 +1480,7 @@ function typeIssue(t, p, path) {
1172
1480
  if (Array.isArray(t)) return no(`must not be an array for ${p.type}`);
1173
1481
  if (p.type === "address") {
1174
1482
  if (isExactPlaceholder(t)) return t === "{recipient}" || t === "{payer}" ? null : no(`${t} is a number, but the abi input is an address`);
1175
- return typeof t === "string" && !hasPlaceholder(t) && (0, import_viem6.isAddress)(t) ? null : no(`${JSON.stringify(String(t))} is not an address`);
1483
+ return typeof t === "string" && !hasPlaceholder(t) && (0, import_viem7.isAddress)(t) ? null : no(`${JSON.stringify(String(t))} is not an address`);
1176
1484
  }
1177
1485
  if (p.type === "bool") return typeof t === "boolean" ? null : no(`${JSON.stringify(String(t))} is not a boolean`);
1178
1486
  if (p.type === "string") return typeof t === "string" ? null : no(`${JSON.stringify(String(t))} is not a string`);
@@ -1329,7 +1637,7 @@ function varFor(token, vars) {
1329
1637
  const key = token.slice(1, -1);
1330
1638
  const v = vars[key];
1331
1639
  if (v === void 0) throw new MintError("INVALID_CONFIG", `placeholder ${token} has no value here`);
1332
- return typeof v === "string" ? (0, import_viem6.getAddress)(v) : v;
1640
+ return typeof v === "string" ? (0, import_viem7.getAddress)(v) : v;
1333
1641
  }
1334
1642
  function fill(t, p, vars) {
1335
1643
  const arr = ARRAY_TYPE.exec(p.type);
@@ -1345,7 +1653,7 @@ function fill(t, p, vars) {
1345
1653
  var fillArgs = (args, inputs, vars) => (args ?? []).map((a, i) => fill(a, inputs[i], vars));
1346
1654
  var totalFor = (per, price, quantity) => per === "total" ? price : price * quantity;
1347
1655
  function shortMessage(e) {
1348
- return e instanceof import_viem6.BaseError ? e.shortMessage : e instanceof Error ? e.message : String(e);
1656
+ return e instanceof import_viem7.BaseError ? e.shortMessage : e instanceof Error ? e.message : String(e);
1349
1657
  }
1350
1658
  var abortError = () => new DOMException("The operation was aborted", "AbortError");
1351
1659
  var isAbortError = (e) => e?.name === "AbortError";
@@ -1368,7 +1676,7 @@ function rpcError(e) {
1368
1676
  }
1369
1677
  function classifyReadError(e, field) {
1370
1678
  if (isMintError(e) || isAbortError(e)) throw e;
1371
- if (e instanceof import_viem6.BaseError && e.walk((x) => x instanceof import_viem6.ContractFunctionRevertedError || x instanceof import_viem6.ContractFunctionZeroDataError)) {
1679
+ if (e instanceof import_viem7.BaseError && e.walk((x) => x instanceof import_viem7.ContractFunctionRevertedError || x instanceof import_viem7.ContractFunctionZeroDataError)) {
1372
1680
  throw new MintError("INVALID_CONFIG", `${field} read reverted or returned no data: ${e.shortMessage}`, { field, cause: e });
1373
1681
  }
1374
1682
  return rpcError(e);
@@ -1418,7 +1726,7 @@ function nativeMeta(ctx) {
1418
1726
  const n = ctx.client.chain?.nativeCurrency;
1419
1727
  return { decimals: n?.decimals ?? 18, symbol: n?.symbol ?? "ETH" };
1420
1728
  }
1421
- var erc20Of = (config) => config.price.currency === "native" ? null : (0, import_viem6.getAddress)(config.price.currency.address);
1729
+ var erc20Of = (config) => config.price.currency === "native" ? null : (0, import_viem7.getAddress)(config.price.currency.address);
1422
1730
  async function currencyMeta(ctx, config) {
1423
1731
  const token = erc20Of(config);
1424
1732
  if (!token) return { token, ...nativeMeta(ctx) };
@@ -1437,7 +1745,7 @@ function parseDisplay(value, decimals) {
1437
1745
  if (frac.length > decimals) {
1438
1746
  throw new MintError("INVALID_CONFIG", `price.value "${value}" has more than ${decimals} decimal places`, { field: "price.value" });
1439
1747
  }
1440
- return (0, import_viem6.parseUnits)(value, decimals);
1748
+ return (0, import_viem7.parseUnits)(value, decimals);
1441
1749
  }
1442
1750
  async function resolvePrice(ctx, config, meta) {
1443
1751
  const price = config.price;
@@ -1450,7 +1758,7 @@ var money3 = (value, decimals, symbol) => ({
1450
1758
  value,
1451
1759
  decimals,
1452
1760
  symbol,
1453
- formatted: (0, import_viem6.formatUnits)(value, decimals)
1761
+ formatted: (0, import_viem7.formatUnits)(value, decimals)
1454
1762
  });
1455
1763
  function revertData2(e) {
1456
1764
  for (let cur = e, depth = 0; cur && depth < 12; depth++) {
@@ -1466,7 +1774,7 @@ function revertData2(e) {
1466
1774
  }
1467
1775
  function describeRevert(abi, data) {
1468
1776
  try {
1469
- const d = (0, import_viem6.decodeErrorResult)({ abi, data });
1777
+ const d = (0, import_viem7.decodeErrorResult)({ abi, data });
1470
1778
  const args = d.args ?? [];
1471
1779
  if (d.errorName === "Error") return String(args[0] ?? "execution reverted");
1472
1780
  const inputs = d.abiItem?.inputs ?? [];
@@ -1479,12 +1787,12 @@ function describeRevert(abi, data) {
1479
1787
  function toSimulationError(e, abi) {
1480
1788
  if (isMintError(e)) return e;
1481
1789
  if (isAbortError(e)) throw e;
1482
- if (e instanceof import_viem6.BaseError && e.walk((x) => x instanceof import_viem6.InsufficientFundsError)) {
1790
+ if (e instanceof import_viem7.BaseError && e.walk((x) => x instanceof import_viem7.InsufficientFundsError)) {
1483
1791
  return new MintError("INSUFFICIENT_FUNDS", `Payer cannot cover the mint: ${e.shortMessage}`, { cause: e });
1484
1792
  }
1485
1793
  const data = revertData2(e);
1486
1794
  if (data) return new MintError("SIMULATION_REVERTED", describeRevert(abi, data), { cause: e });
1487
- const isRevert = e instanceof import_viem6.BaseError && e.walk((x) => x.code === 3 || /execution reverted/i.test(x.message));
1795
+ const isRevert = e instanceof import_viem7.BaseError && e.walk((x) => x.code === 3 || /execution reverted/i.test(x.message));
1488
1796
  if (isRevert) return new MintError("SIMULATION_REVERTED", shortMessage(e), { cause: e });
1489
1797
  return new MintError("RPC_ERROR", `RPC request failed: ${shortMessage(e)}`, { cause: e });
1490
1798
  }
@@ -1498,7 +1806,7 @@ async function simulateMint(ctx, config, call) {
1498
1806
  function encodeMint(config, vars) {
1499
1807
  const fn = validateCall(config.abi, config.mint, "mint", "mint", PLACEHOLDERS);
1500
1808
  try {
1501
- return (0, import_viem6.encodeFunctionData)({ abi: [fn], functionName: fn.name, args: fillArgs(config.mint.args, fn.inputs, vars) });
1809
+ return (0, import_viem7.encodeFunctionData)({ abi: [fn], functionName: fn.name, args: fillArgs(config.mint.args, fn.inputs, vars) });
1502
1810
  } catch (e) {
1503
1811
  if (isMintError(e)) throw e;
1504
1812
  throw new MintError("INVALID_CONFIG", `mint.args cannot be encoded for ${signature(fn)}: ${shortMessage(e)}`, { field: "mint.args", cause: e });
@@ -1563,7 +1871,7 @@ async function readWallet(ctx, config, { payer, recipient, quantity, drop }) {
1563
1871
  if (remaining === 0n) {
1564
1872
  ineligible = { reason: "WALLET_LIMIT", message: `This wallet has already minted its limit of ${walletMax}.` };
1565
1873
  } else if (drop.paymentToken === null && native >= totalPrice) {
1566
- const call = { payer, to: (0, import_viem6.getAddress)(config.address), data: encodeMint(config, vars), value: mintValue(config, null, totalPrice) };
1874
+ const call = { payer, to: (0, import_viem7.getAddress)(config.address), data: encodeMint(config, vars), value: mintValue(config, null, totalPrice) };
1567
1875
  try {
1568
1876
  await simulateMint(ctx, config, call);
1569
1877
  gas = await io(ctx, () => ctx.client.estimateGas({ account: payer, to: call.to, data: call.data, value: call.value })).catch((e) => {
@@ -1594,7 +1902,7 @@ async function buildPlan2(ctx, attempt, wallet) {
1594
1902
  const qty = BigInt(attempt.quantity);
1595
1903
  const unitPrice = await resolvePrice(ctx, config);
1596
1904
  const totalPrice = totalFor(config.price.per ?? "unit", unitPrice, qty);
1597
- const to = (0, import_viem6.getAddress)(config.address);
1905
+ const to = (0, import_viem7.getAddress)(config.address);
1598
1906
  const token = erc20Of(config);
1599
1907
  const mint = {
1600
1908
  to,
@@ -1612,11 +1920,11 @@ async function buildPlan2(ctx, attempt, wallet) {
1612
1920
  token,
1613
1921
  spender: to,
1614
1922
  amount: totalPrice,
1615
- tx: { to: token, data: (0, import_viem6.encodeFunctionData)({ abi: ERC20_ABI2, functionName: "approve", args: [to, totalPrice] }), value: 0n }
1923
+ tx: { to: token, data: (0, import_viem7.encodeFunctionData)({ abi: ERC20_ABI2, functionName: "approve", args: [to, totalPrice] }), value: 0n }
1616
1924
  };
1617
1925
  }
1618
1926
  }
1619
- return { approve, mint, tokenContract: (0, import_viem6.getAddress)(attempt.tokenContract) };
1927
+ return { approve, mint, tokenContract: (0, import_viem7.getAddress)(attempt.tokenContract) };
1620
1928
  }
1621
1929
  async function simulate(ctx, attempt, plan) {
1622
1930
  throwIfAborted(ctx);
@@ -1625,29 +1933,29 @@ async function simulate(ctx, attempt, plan) {
1625
1933
  var contractAdapter = { readDrop, readWallet, buildPlan: buildPlan2, simulate };
1626
1934
 
1627
1935
  // src/core/adapters/receipt.ts
1628
- var import_viem7 = require("viem");
1936
+ var import_viem8 = require("viem");
1629
1937
  var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
1630
1938
  var TRANSFER_SINGLE_TOPIC = "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62";
1631
1939
  var TRANSFER_BATCH_TOPIC = "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb";
1632
- var topicAddress = (t) => t && t.length === 66 ? (0, import_viem7.getAddress)(`0x${t.slice(26)}`) : null;
1940
+ var topicAddress = (t) => t && t.length === 66 ? (0, import_viem8.getAddress)(`0x${t.slice(26)}`) : null;
1633
1941
  function parseMintReceipt(logs, { tokenContract, recipient }) {
1634
- const contract = (0, import_viem7.getAddress)(tokenContract);
1635
- const to = (0, import_viem7.getAddress)(recipient);
1942
+ const contract = (0, import_viem8.getAddress)(tokenContract);
1943
+ const to = (0, import_viem8.getAddress)(recipient);
1636
1944
  const out = /* @__PURE__ */ new Map();
1637
1945
  const add = (id, amount) => out.set(id, (out.get(id) ?? 0n) + amount);
1638
1946
  for (const log of logs) {
1639
- if ((0, import_viem7.getAddress)(log.address) !== contract) continue;
1947
+ if ((0, import_viem8.getAddress)(log.address) !== contract) continue;
1640
1948
  const [t0, t1, t2, t3] = log.topics;
1641
1949
  if (t0 === TRANSFER_TOPIC && log.topics.length === 4) {
1642
- if (topicAddress(t1) !== import_viem7.zeroAddress || topicAddress(t2) !== to || !t3) continue;
1950
+ if (topicAddress(t1) !== import_viem8.zeroAddress || topicAddress(t2) !== to || !t3) continue;
1643
1951
  add(BigInt(t3), 1n);
1644
1952
  } else if (t0 === TRANSFER_SINGLE_TOPIC && log.topics.length === 4) {
1645
- if (topicAddress(t2) !== import_viem7.zeroAddress || topicAddress(t3) !== to) continue;
1646
- const [id, value] = (0, import_viem7.decodeAbiParameters)([{ type: "uint256" }, { type: "uint256" }], log.data);
1953
+ if (topicAddress(t2) !== import_viem8.zeroAddress || topicAddress(t3) !== to) continue;
1954
+ const [id, value] = (0, import_viem8.decodeAbiParameters)([{ type: "uint256" }, { type: "uint256" }], log.data);
1647
1955
  add(id, value);
1648
1956
  } else if (t0 === TRANSFER_BATCH_TOPIC && log.topics.length === 4) {
1649
- if (topicAddress(t2) !== import_viem7.zeroAddress || topicAddress(t3) !== to) continue;
1650
- const [ids, values] = (0, import_viem7.decodeAbiParameters)([{ type: "uint256[]" }, { type: "uint256[]" }], log.data);
1957
+ if (topicAddress(t2) !== import_viem8.zeroAddress || topicAddress(t3) !== to) continue;
1958
+ const [ids, values] = (0, import_viem8.decodeAbiParameters)([{ type: "uint256[]" }, { type: "uint256[]" }], log.data);
1651
1959
  if (ids.length !== values.length) continue;
1652
1960
  ids.forEach((id, i) => add(id, values[i]));
1653
1961
  }
@@ -1694,6 +2002,7 @@ function friendlyErrorMessage(error) {
1694
2002
  if (isMintError(error)) {
1695
2003
  if (error.reason === "INSUFFICIENT_FUNDS") return fundsMessage(collect(error.cause)) ?? error.message;
1696
2004
  if (error.reason !== "RPC_ERROR" && error.reason !== "SIMULATION_REVERTED") return error.message;
2005
+ if (error.reason === "SIMULATION_REVERTED" && error.domain === "relay") return error.message;
1697
2006
  if (error.reason === "SIMULATION_REVERTED" && (error.message === "The token refused the approval." || error.message === "The drop is no longer available for this quantity.")) return error.message;
1698
2007
  }
1699
2008
  const errors = collect(error);
@@ -1755,6 +2064,25 @@ function adapterFor(config) {
1755
2064
  return config.type === "manifold-claim" ? manifoldClaimAdapter : contractAdapter;
1756
2065
  }
1757
2066
  var bridgeEnabled = (config) => config.type === "manifold-claim";
2067
+ function payChainsOf(config, option) {
2068
+ if (config.type !== "manifold-claim" || option === false) return [];
2069
+ const all = payChainsFor(config.chainId);
2070
+ const narrowed = typeof option === "object" && option.chains ? all.filter((id) => id === config.chainId || option.chains.includes(id)) : all;
2071
+ return narrowed.length > 1 ? narrowed : [];
2072
+ }
2073
+ function routeChainOf(payChains, accountChainId, payChainId) {
2074
+ if (payChainId !== void 0 && payChains.includes(payChainId)) return payChainId;
2075
+ return accountChainId;
2076
+ }
2077
+ var ROUTE_DEBOUNCE_MS = 500;
2078
+ var symbolOf = (chainId) => {
2079
+ try {
2080
+ return getChain(chainId).nativeCurrency.symbol;
2081
+ } catch {
2082
+ return "ETH";
2083
+ }
2084
+ };
2085
+ var shortOf = (chainId) => ({ chainId, symbol: symbolOf(chainId), name: chainName(chainId) });
1758
2086
  function tokenContractOf(config) {
1759
2087
  return config.type === "manifold-claim" ? config.creatorContract : config.tokenContract ?? config.address;
1760
2088
  }
@@ -1812,6 +2140,8 @@ function useMint(options) {
1812
2140
  const widgetFailRef = (0, import_react.useRef)(null);
1813
2141
  const widgetLoadRef = (0, import_react.useRef)(null);
1814
2142
  const pushAccountRef = (0, import_react.useRef)(null);
2143
+ const autoResumedRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
2144
+ const [crossChainInfo, setCrossChainInfo] = (0, import_react.useState)(null);
1815
2145
  (0, import_react.useEffect)(() => {
1816
2146
  stateRef.current = machine;
1817
2147
  propsRef.current = options;
@@ -1827,6 +2157,7 @@ function useMint(options) {
1827
2157
  if (!handler || !mounted.current) return;
1828
2158
  const cfg = attemptRef.current?.config ?? propsRef.current.config;
1829
2159
  const s = stateRef.current;
2160
+ const originChainId = attemptRef.current?.originChainId ?? (s.route?.kind === "relay" ? s.route.originChainId : s.short?.chainId ?? cfg.chainId);
1830
2161
  const payload = {
1831
2162
  type,
1832
2163
  state: s.name,
@@ -1834,6 +2165,8 @@ function useMint(options) {
1834
2165
  chainId: cfg.chainId,
1835
2166
  quantity: s.quantity,
1836
2167
  config: { type: cfg.type, chainId: cfg.chainId, address: eventAddressOf(cfg) },
2168
+ route: originChainId === cfg.chainId ? "direct" : "relay",
2169
+ originChainId,
1837
2170
  ...over
1838
2171
  };
1839
2172
  try {
@@ -1953,7 +2286,7 @@ function useMint(options) {
1953
2286
  if (cancelled) throw new MintError("USER_REJECTED", "The transaction was cancelled or replaced with a different transaction.");
1954
2287
  if (await walletFor().getChainId() !== attempt.chainId) throw e;
1955
2288
  assertActive();
1956
- receipt2 = await walletFor().extend(import_viem8.publicActions).waitForTransactionReceipt(params);
2289
+ receipt2 = await walletFor().extend(import_viem9.publicActions).waitForTransactionReceipt(params);
1957
2290
  }
1958
2291
  assertActive();
1959
2292
  if (cancelled) throw new MintError("USER_REJECTED", "The transaction was cancelled or replaced with a different transaction.");
@@ -1976,8 +2309,8 @@ function useMint(options) {
1976
2309
  }
1977
2310
  } catch (e) {
1978
2311
  if (isMintError(e)) throw e;
1979
- const reverted = e instanceof import_viem8.BaseError ? e.walk((cause) => cause instanceof import_viem8.ExecutionRevertedError) : null;
1980
- if (reverted instanceof import_viem8.ExecutionRevertedError) {
2312
+ const reverted = e instanceof import_viem9.BaseError ? e.walk((cause) => cause instanceof import_viem9.ExecutionRevertedError) : null;
2313
+ if (reverted instanceof import_viem9.ExecutionRevertedError) {
1981
2314
  throw new MintError("SIMULATION_REVERTED", reverted.shortMessage);
1982
2315
  }
1983
2316
  throw e;
@@ -2033,6 +2366,156 @@ function useMint(options) {
2033
2366
  },
2034
2367
  [clientFor, ctxFor, dispatch, emit]
2035
2368
  );
2369
+ const finishRelay = (0, import_react.useCallback)(
2370
+ async (attempt, out, life) => {
2371
+ const cfg = attempt.config;
2372
+ const client = clientFor(life, cfg);
2373
+ const assertActive = () => {
2374
+ if (!mounted.current || life.controller.signal.aborted || lifeRef.current !== life || attemptRef.current !== attempt) {
2375
+ throw new DOMException("Mint attempt cancelled", "AbortError");
2376
+ }
2377
+ };
2378
+ const outcome = await pollUntilTerminal({ testnet: out.testnet, requestId: out.requestId, signal: life.controller.signal, assertActive });
2379
+ assertActive();
2380
+ const where = chainName(out.originChainId);
2381
+ if (outcome.kind === "timeout") {
2382
+ throw new MintError("BRIDGE_TIMEOUT", "The bridge is still processing. Your mint may still complete.");
2383
+ }
2384
+ const key = outstandingKey(attempt.config, attempt.payer);
2385
+ const { status } = outcome;
2386
+ if (status.status !== "success") clearOutstanding(key);
2387
+ if (status.status === "refund") {
2388
+ throw new MintError("BRIDGE_REFUNDED", `Relay could not complete the mint and is refunding your payment. Relay request ${out.requestId}.`);
2389
+ }
2390
+ if (status.status === "failure") {
2391
+ const why = status.failReason ? ` (${status.failReason})` : "";
2392
+ const noRefund = status.refundFailReason ? ` No automatic refund was sent. Contact support with Relay request ${out.requestId}.` : "";
2393
+ throw new MintError("BRIDGE_FAILED", `Relay could not complete the mint paid on ${where}${why}.${noRefund}`);
2394
+ }
2395
+ const fillHash = status.txHashes[0] ?? null;
2396
+ let tokens = [];
2397
+ if (fillHash) {
2398
+ try {
2399
+ const receipt = await client.waitForTransactionReceipt({ hash: fillHash, confirmations: 1, timeout: 6e4 });
2400
+ if (receiptOk(receipt.status)) {
2401
+ tokens = parseMintReceipt(receipt.logs ?? [], { tokenContract: attempt.tokenContract, recipient: attempt.recipient });
2402
+ }
2403
+ } catch {
2404
+ }
2405
+ assertActive();
2406
+ }
2407
+ const txHash = fillHash ?? out.originTxHash;
2408
+ const result = {
2409
+ txHash,
2410
+ explorerUrl: explorerUrlFor(fillHash ? cfg.chainId : out.originChainId, txHash),
2411
+ tokens,
2412
+ relay: { requestId: out.requestId, originTxHash: out.originTxHash, originChainId: out.originChainId }
2413
+ };
2414
+ clearOutstanding(key);
2415
+ dispatch({ type: "ATTEMPT_SUCCEEDED", attemptId: attempt.attemptId, result });
2416
+ emit("success", { state: "success", reason: null, txHash, quantity: attempt.quantity, chainId: cfg.chainId });
2417
+ },
2418
+ [clientFor, dispatch, emit]
2419
+ );
2420
+ const executeRelayAttempt = (0, import_react.useCallback)(
2421
+ async (attempt, walletRead, life, account2) => {
2422
+ const cfg = attempt.config;
2423
+ const adapter = adapterFor(cfg);
2424
+ const ctx = ctxFor(life, cfg);
2425
+ const testnet = isRelayTestnet(cfg.chainId);
2426
+ const origin = attempt.originChainId;
2427
+ const assertPayer = () => {
2428
+ if (!mounted.current || life.controller.signal.aborted || lifeRef.current !== life || attemptRef.current !== attempt) {
2429
+ throw new DOMException("Mint attempt cancelled", "AbortError");
2430
+ }
2431
+ const live = getAccount();
2432
+ if (!live || live.address.toLowerCase() !== attempt.payer.toLowerCase()) {
2433
+ throw new MintError("USER_REJECTED", "The connected wallet changed before the transaction was sent.");
2434
+ }
2435
+ };
2436
+ assertPayer();
2437
+ if (account2.chainId !== origin) {
2438
+ dispatch({ type: "SWITCHING_CHAIN", attemptId: attempt.attemptId });
2439
+ await switchToChain(origin);
2440
+ }
2441
+ assertPayer();
2442
+ const plan = await adapter.buildPlan(ctx, attempt, walletRead);
2443
+ if (plan.approve) throw new MintError("BRIDGE_UNAVAILABLE", "Cross-chain mint supports native-priced drops only.");
2444
+ assertPayer();
2445
+ const quote = await getQuote({
2446
+ testnet,
2447
+ payer: attempt.payer,
2448
+ recipient: attempt.recipient,
2449
+ originChainId: origin,
2450
+ destinationChainId: cfg.chainId,
2451
+ tx: plan.mint,
2452
+ fresh: true,
2453
+ signal: life.controller.signal
2454
+ });
2455
+ assertPayer();
2456
+ const balance = await getConnectedBalance(attempt.payer, origin);
2457
+ if (balance < quote.amountIn.value + quote.originGas.value) {
2458
+ const s = shortOf(origin);
2459
+ throw new MintError("INSUFFICIENT_FUNDS", `Not enough ${s.symbol} on ${s.name}. Pick another network to pay from.`);
2460
+ }
2461
+ assertPayer();
2462
+ const item = quote.items[0];
2463
+ const key = outstandingKey(cfg, attempt.payer);
2464
+ const wallet2 = getWalletClient(origin);
2465
+ assertPayer();
2466
+ if (!claimDeposit(key)) {
2467
+ throw new MintError("BRIDGE_UNAVAILABLE", "A cross-chain payment for this drop is already in progress. Try again once it finishes.");
2468
+ }
2469
+ let out;
2470
+ try {
2471
+ dispatch({ type: "MINT_PROMPTED", attemptId: attempt.attemptId });
2472
+ const send = wallet2;
2473
+ const originTxHash2 = await send.sendTransaction({
2474
+ account: attempt.payer,
2475
+ to: item.to,
2476
+ data: item.data,
2477
+ value: item.value,
2478
+ ...item.gas !== null && item.gas > 0n ? { gas: item.gas } : {},
2479
+ chain: getChain(origin)
2480
+ });
2481
+ out = { requestId: quote.requestId, testnet, originChainId: origin, originTxHash: originTxHash2, attempt };
2482
+ setOutstanding(key, out);
2483
+ } finally {
2484
+ releaseDeposit(key);
2485
+ }
2486
+ const { originTxHash } = out;
2487
+ autoResumedRef.current.add(key);
2488
+ dispatch({ type: "TX_SUBMITTED", attemptId: attempt.attemptId });
2489
+ emit("tx-submitted", { state: "confirming", reason: null, txHash: originTxHash, quantity: attempt.quantity, chainId: cfg.chainId });
2490
+ const deposit = await waitForDeposit(origin, originTxHash);
2491
+ if (deposit === "reverted") {
2492
+ clearOutstanding(key);
2493
+ throw new MintError("TX_REVERTED", `The payment transaction reverted (${originTxHash}).`);
2494
+ }
2495
+ await finishRelay(attempt, out, life);
2496
+ },
2497
+ [ctxFor, dispatch, emit, finishRelay]
2498
+ );
2499
+ const resumeOutstanding = (0, import_react.useCallback)(
2500
+ async (out, life) => {
2501
+ const attempt = Object.freeze({ ...out.attempt, attemptId: ++attemptIdRef.current });
2502
+ attemptRef.current = attempt;
2503
+ dispatch({ type: "ATTEMPT_STARTED", attempt, resume: true });
2504
+ dispatch({ type: "TX_SUBMITTED", attemptId: attempt.attemptId });
2505
+ emit("attempt", { state: "confirming", reason: null, quantity: attempt.quantity, chainId: attempt.chainId });
2506
+ try {
2507
+ await finishRelay(attempt, out, life);
2508
+ } catch (e) {
2509
+ if (!mounted.current || life.controller.signal.aborted || lifeRef.current !== life) return;
2510
+ const { reason, message } = toReason(e);
2511
+ dispatch({ type: "ATTEMPT_FAILED", attemptId: attempt.attemptId, reason, message });
2512
+ emit("error", { quantity: attempt.quantity, chainId: attempt.chainId });
2513
+ } finally {
2514
+ attemptRef.current = null;
2515
+ }
2516
+ },
2517
+ [dispatch, emit, finishRelay]
2518
+ );
2036
2519
  const runMint = (0, import_react.useCallback)(async () => {
2037
2520
  if (!mounted.current) return;
2038
2521
  if (attemptRef.current !== null) return;
@@ -2069,6 +2552,12 @@ function useMint(options) {
2069
2552
  }
2070
2553
  return;
2071
2554
  }
2555
+ const payer = s.account?.address;
2556
+ const out = payer ? getOutstanding(outstandingKey(propsRef.current.config, payer)) : null;
2557
+ if (out && lifeRef.current) {
2558
+ await resumeOutstanding(out, lifeRef.current);
2559
+ return;
2560
+ }
2072
2561
  if (s.name === "success" || s.name === "error") {
2073
2562
  const life2 = lifeRef.current;
2074
2563
  const gen = ++genRef.current;
@@ -2084,6 +2573,7 @@ function useMint(options) {
2084
2573
  const life = lifeRef.current;
2085
2574
  if (!account2 || !walletRead || !life) return;
2086
2575
  const cfg = propsRef.current.config;
2576
+ const relayRoute = s.route?.kind === "relay" ? s.route : null;
2087
2577
  const attempt = Object.freeze({
2088
2578
  attemptId: ++attemptIdRef.current,
2089
2579
  config: structuredClone(cfg),
@@ -2091,13 +2581,15 @@ function useMint(options) {
2091
2581
  recipient: propsRef.current.recipient ?? account2.address,
2092
2582
  quantity: s.quantity,
2093
2583
  chainId: cfg.chainId,
2094
- tokenContract: tokenContractOf(cfg)
2584
+ tokenContract: tokenContractOf(cfg),
2585
+ originChainId: relayRoute ? relayRoute.originChainId : cfg.chainId
2095
2586
  });
2096
2587
  attemptRef.current = attempt;
2097
2588
  dispatch({ type: "ATTEMPT_STARTED", attempt });
2098
2589
  emit("attempt", { state: "live", reason: null, quantity: attempt.quantity, chainId: attempt.chainId });
2099
2590
  try {
2100
- await executeAttempt(attempt, walletRead, life, account2);
2591
+ if (relayRoute) await executeRelayAttempt(attempt, walletRead, life, account2);
2592
+ else await executeAttempt(attempt, walletRead, life, account2);
2101
2593
  } catch (e) {
2102
2594
  if (!mounted.current || life.controller.signal.aborted || lifeRef.current !== life) return;
2103
2595
  const { reason, message } = toReason(e);
@@ -2106,7 +2598,7 @@ function useMint(options) {
2106
2598
  } finally {
2107
2599
  attemptRef.current = null;
2108
2600
  }
2109
- }, [dispatch, emit, readDrop2, executeAttempt, refreshNow]);
2601
+ }, [dispatch, emit, readDrop2, executeAttempt, executeRelayAttempt, resumeOutstanding, refreshNow]);
2110
2602
  const mint = (0, import_react.useCallback)(() => {
2111
2603
  void runMint();
2112
2604
  }, [runMint]);
@@ -2141,9 +2633,10 @@ function useMint(options) {
2141
2633
  pushAccountRef.current?.(getAccount());
2142
2634
  }, [recipient, machine.attempt, isBusy]);
2143
2635
  const projectId = connect?.walletConnectProjectId;
2636
+ const widgetChains = [config.chainId, ...payChainsOf(config, options.crossChain).filter((id) => id !== config.chainId)].join(",");
2144
2637
  (0, import_react.useEffect)(() => {
2145
2638
  let alive = true;
2146
- const loading = loadConnectWidget({ chainIds: [config.chainId], ...projectId ? { walletConnectProjectId: projectId } : {} });
2639
+ const loading = loadConnectWidget({ chainIds: widgetChains.split(",").map(Number), ...projectId ? { walletConnectProjectId: projectId } : {} });
2147
2640
  widgetLoadRef.current = loading;
2148
2641
  loading.then(
2149
2642
  () => {
@@ -2158,7 +2651,7 @@ function useMint(options) {
2158
2651
  return () => {
2159
2652
  alive = false;
2160
2653
  };
2161
- }, [config.chainId, projectId, widgetRetry]);
2654
+ }, [widgetChains, projectId, widgetRetry]);
2162
2655
  (0, import_react.useEffect)(() => {
2163
2656
  if (!mounted.current) return;
2164
2657
  if (isBusy()) return;
@@ -2175,6 +2668,22 @@ function useMint(options) {
2175
2668
  void refreshNow();
2176
2669
  }
2177
2670
  });
2671
+ const resumeKey = machine.account ? outstandingKey(config, machine.account.address) : null;
2672
+ (0, import_react.useEffect)(() => {
2673
+ if (!resumeKey || autoResumedRef.current.has(resumeKey) || attemptRef.current !== null || isBusy()) return;
2674
+ const out = getOutstanding(resumeKey);
2675
+ const life = lifeRef.current;
2676
+ if (!out || !life) return;
2677
+ autoResumedRef.current.add(resumeKey);
2678
+ void resumeOutstanding(out, life);
2679
+ }, [resumeKey, machine.attempt, isBusy, resumeOutstanding]);
2680
+ const routeOptionsKey = `${String(options.payChainId ?? "")}|${JSON.stringify(options.crossChain ?? null)}`;
2681
+ const previousRouteOptions = (0, import_react.useRef)(routeOptionsKey);
2682
+ (0, import_react.useEffect)(() => {
2683
+ if (previousRouteOptions.current === routeOptionsKey || isBusy()) return;
2684
+ previousRouteOptions.current = routeOptionsKey;
2685
+ if (stateRef.current.account) dispatch({ type: "ACCOUNT_CHANGED", account: stateRef.current.account });
2686
+ }, [routeOptionsKey, machine.attempt, dispatch, isBusy]);
2178
2687
  const previousQuantity = (0, import_react.useRef)(machine.quantity);
2179
2688
  (0, import_react.useEffect)(() => {
2180
2689
  if (previousQuantity.current === machine.quantity || isBusy()) return;
@@ -2207,7 +2716,61 @@ function useMint(options) {
2207
2716
  });
2208
2717
  if (!alive || life.controller.signal.aborted || lifeRef.current !== life || life.gen !== readGen) return;
2209
2718
  const ineligible = read.ineligible?.reason === "SIMULATION_REVERTED" ? { ...read.ineligible, message: friendlyErrorMessage(new MintError("SIMULATION_REVERTED", read.ineligible.message)) } : read.ineligible;
2210
- dispatch({ type: "WALLET_LOADED", gen: walletGen, read: { ...read, ineligible } });
2719
+ const fullRead = { ...read, ineligible };
2720
+ const payChains = payChainsOf(cfg, propsRef.current.crossChain);
2721
+ const crossChain = payChains.length > 0 && ineligible === null && (adapter.supportsRelay?.(drop) ?? false);
2722
+ const routeChain = routeChainOf(payChains, account.chainId, propsRef.current.payChainId);
2723
+ const routePending = crossChain && routeChain !== cfg.chainId && payChains.includes(routeChain);
2724
+ dispatch({ type: "WALLET_LOADED", gen: walletGen, read: fullRead, crossChain, routePending });
2725
+ if (!crossChain) {
2726
+ setCrossChainInfo(null);
2727
+ return;
2728
+ }
2729
+ const recipientAddr = propsRef.current.recipient ?? account.address;
2730
+ const draft = {
2731
+ attemptId: 0,
2732
+ config: cfg,
2733
+ payer: account.address,
2734
+ recipient: recipientAddr,
2735
+ quantity: stateRef.current.quantity,
2736
+ chainId: cfg.chainId,
2737
+ tokenContract: tokenContractOf(cfg),
2738
+ originChainId: routeChain
2739
+ };
2740
+ const testnet = isRelayTestnet(cfg.chainId);
2741
+ let tx;
2742
+ try {
2743
+ tx = (await adapter.buildPlan(ctxFor(life, cfg), draft, fullRead)).mint;
2744
+ } catch {
2745
+ if (!alive) return;
2746
+ setCrossChainInfo(null);
2747
+ if (routePending) dispatch({ type: "ROUTE_LOADED", gen: walletGen, route: { kind: "direct", chainId: cfg.chainId }, short: null });
2748
+ return;
2749
+ }
2750
+ if (!alive || life.controller.signal.aborted || lifeRef.current !== life) return;
2751
+ setCrossChainInfo((prev) => {
2752
+ const same = prev && prev.info.dropChainId === cfg.chainId && prev.info.testnet === testnet && prev.info.tx.to === tx.to && prev.info.tx.data === tx.data && prev.info.tx.value === tx.value && prev.info.payer === account.address && prev.info.recipient === recipientAddr && prev.info.payChains.join() === payChains.join();
2753
+ return { gen: walletGen, info: same ? prev.info : { dropChainId: cfg.chainId, payChains, tx, payer: account.address, recipient: recipientAddr, testnet } };
2754
+ });
2755
+ if (!routePending) return;
2756
+ await new Promise((r) => setTimeout(r, ROUTE_DEBOUNCE_MS));
2757
+ if (!alive || life.controller.signal.aborted || lifeRef.current !== life) return;
2758
+ let route = { kind: "direct", chainId: cfg.chainId };
2759
+ let short = null;
2760
+ try {
2761
+ const quote = await getQuote({ testnet, payer: account.address, recipient: recipientAddr, originChainId: routeChain, destinationChainId: cfg.chainId, tx, signal: life.controller.signal });
2762
+ const relay = { kind: "relay", originChainId: routeChain, amountIn: quote.amountIn, relayFee: quote.relayFee, originGas: quote.originGas, quotedAt: quote.quotedAt };
2763
+ if (routeChain !== account.chainId) {
2764
+ route = relay;
2765
+ } else {
2766
+ const balance = await getConnectedBalance(account.address, routeChain);
2767
+ if (balance >= quote.amountIn.value + quote.originGas.value) route = relay;
2768
+ else short = shortOf(routeChain);
2769
+ }
2770
+ } catch {
2771
+ }
2772
+ if (!alive || life.controller.signal.aborted || lifeRef.current !== life) return;
2773
+ dispatch({ type: "ROUTE_LOADED", gen: walletGen, route: short ? null : route, short });
2211
2774
  } catch (e) {
2212
2775
  if (!alive || life.controller.signal.aborted || lifeRef.current !== life || life.gen !== readGen) return;
2213
2776
  dispatch({ type: "WALLET_FAILED", gen: walletGen, ...errorReasonOf(e) });
@@ -2307,12 +2870,137 @@ function useMint(options) {
2307
2870
  result: machine.result,
2308
2871
  refresh,
2309
2872
  // An attempt claimed by a click is not clickable, even while the reducer still reads 'live'.
2310
- canAct: canAct({ ...machine, name: effective.name }) && machine.attempt === null
2873
+ canAct: canAct({ ...machine, name: effective.name }) && machine.attempt === null,
2874
+ // Short on the wallet chain is neither route: nothing can be paid until the collector picks another chain.
2875
+ route: machine.route ?? (machine.walletRead && machine.drop && !machine.routePending && machine.short === null ? { kind: "direct", chainId: machine.drop.chainId } : null),
2876
+ crossChain: machine.crossChain && machine.account && crossChainInfo?.gen === machine.walletGen ? crossChainInfo.info : null
2311
2877
  };
2312
2878
  }
2313
2879
 
2314
- // src/react/MintButton.ts
2880
+ // src/react/PayWithPicker.ts
2315
2881
  var import_react2 = require("react");
2882
+ var import_viem10 = require("viem");
2883
+ var PICKER_REFRESH_MS = 2e4;
2884
+ var PICKER_DEBOUNCE_MS = 500;
2885
+ var RELAY_REASONS = /* @__PURE__ */ new Set(["BRIDGE_UNAVAILABLE", "BRIDGE_TIMEOUT", "BRIDGE_REFUNDED", "BRIDGE_FAILED"]);
2886
+ var money4 = (value, chainId) => {
2887
+ const c = getChain(chainId).nativeCurrency;
2888
+ return { value, decimals: c.decimals, symbol: c.symbol, formatted: (0, import_viem10.formatUnits)(value, c.decimals) };
2889
+ };
2890
+ function usePayOptions(mint, onSelect) {
2891
+ const info = mint.crossChain;
2892
+ const current = mint.wallet?.chainId ?? null;
2893
+ const [quotes, setQuotes] = (0, import_react2.useState)({});
2894
+ const [balance, setBalance] = (0, import_react2.useState)(null);
2895
+ const [tick, setTick] = (0, import_react2.useState)(0);
2896
+ (0, import_react2.useEffect)(() => {
2897
+ const t = setInterval(() => setTick((n) => n + 1), PICKER_REFRESH_MS);
2898
+ return () => clearInterval(t);
2899
+ }, []);
2900
+ const key = info ? `${info.tx.to}:${info.tx.value}:${info.tx.data}:${info.payer}:${info.recipient}:${info.payChains.join()}` : "";
2901
+ const [quotedKey, setQuotedKey] = (0, import_react2.useState)(key);
2902
+ if (quotedKey !== key) {
2903
+ setQuotedKey(key);
2904
+ setQuotes({});
2905
+ }
2906
+ (0, import_react2.useEffect)(() => {
2907
+ if (!info) return;
2908
+ const controller = new AbortController();
2909
+ const timer = setTimeout(() => {
2910
+ for (const chainId of info.payChains) {
2911
+ if (chainId === info.dropChainId) continue;
2912
+ getQuote({ testnet: info.testnet, payer: info.payer, recipient: info.recipient, originChainId: chainId, destinationChainId: info.dropChainId, tx: info.tx, signal: controller.signal }).then(
2913
+ (q) => {
2914
+ if (!controller.signal.aborted) setQuotes((prev) => ({ ...prev, [chainId]: { relayFee: q.relayFee, amountIn: q.amountIn } }));
2915
+ },
2916
+ (e) => {
2917
+ if (controller.signal.aborted || e?.reason === "RATE_LIMITED") return;
2918
+ setQuotes((prev) => ({ ...prev, [chainId]: "failed" }));
2919
+ }
2920
+ );
2921
+ }
2922
+ }, PICKER_DEBOUNCE_MS);
2923
+ return () => {
2924
+ clearTimeout(timer);
2925
+ controller.abort();
2926
+ };
2927
+ }, [key, tick]);
2928
+ (0, import_react2.useEffect)(() => {
2929
+ if (!info || current === null || !mint.wallet) return;
2930
+ let alive = true;
2931
+ getConnectedBalance(mint.wallet.address, current).then(
2932
+ (value) => {
2933
+ if (alive) setBalance({ chainId: current, value });
2934
+ },
2935
+ () => {
2936
+ if (alive) setBalance(null);
2937
+ }
2938
+ );
2939
+ return () => {
2940
+ alive = false;
2941
+ };
2942
+ }, [current, mint.wallet?.address, key, tick]);
2943
+ const select = (0, import_react2.useCallback)(
2944
+ (chainId) => {
2945
+ switchToChain(chainId).then(
2946
+ () => onSelect?.(chainId, null),
2947
+ (e) => onSelect?.(chainId, e)
2948
+ );
2949
+ },
2950
+ [onSelect]
2951
+ );
2952
+ if (!info) return [];
2953
+ return info.payChains.filter((chainId) => chainId === info.dropChainId || quotes[chainId] !== "failed").map((chainId) => {
2954
+ const q = quotes[chainId];
2955
+ const isDropChain = chainId === info.dropChainId;
2956
+ const quoted = q && q !== "failed" ? q : null;
2957
+ return {
2958
+ chainId,
2959
+ name: chainName(chainId),
2960
+ selected: chainId === current,
2961
+ isDropChain,
2962
+ relayFee: isDropChain ? null : quoted?.relayFee ?? null,
2963
+ amountIn: isDropChain ? null : quoted?.amountIn ?? null,
2964
+ balance: balance && balance.chainId === chainId && chainId === current ? money4(balance.value, chainId) : null,
2965
+ loading: !isDropChain && q === void 0,
2966
+ select: () => select(chainId)
2967
+ };
2968
+ });
2969
+ }
2970
+ function defaultRow(row) {
2971
+ const fee = row.isDropChain ? "None" : row.loading ? "\u2026" : row.relayFee ? `${row.relayFee.formatted} ${row.relayFee.symbol}` : "--";
2972
+ const balance = row.balance ? ` \xB7 ${row.balance.formatted} ${row.balance.symbol}` : "";
2973
+ return `${row.name}${balance} \xB7 fee ${fee}`;
2974
+ }
2975
+ function PayWithPicker(props) {
2976
+ const rows = usePayOptions(props.mint, props.onSelect);
2977
+ if (rows.length === 0) return null;
2978
+ const open = props.mint.state === "insufficient-funds" || props.mint.reason !== null && RELAY_REASONS.has(props.mint.reason);
2979
+ return (0, import_react2.createElement)(
2980
+ "div",
2981
+ { className: props.className, role: "group", "aria-label": "Pay with", "data-open": open ? "true" : "false" },
2982
+ rows.map(
2983
+ (row) => (0, import_react2.createElement)(
2984
+ "button",
2985
+ {
2986
+ key: row.chainId,
2987
+ type: "button",
2988
+ "data-chain": String(row.chainId),
2989
+ "data-selected": row.selected ? "true" : "false",
2990
+ "data-loading": row.loading ? "true" : "false",
2991
+ "aria-pressed": row.selected,
2992
+ // Only an idle button may change chains: a click in progress already chose its pay chain.
2993
+ disabled: row.selected || !(props.mint.canAct || props.mint.state === "insufficient-funds" || props.mint.state === "loading"),
2994
+ onClick: row.select
2995
+ },
2996
+ props.renderRow ? props.renderRow(row) : defaultRow(row)
2997
+ )
2998
+ )
2999
+ );
3000
+ }
3001
+
3002
+ // src/react/MintButton.ts
3003
+ var import_react3 = require("react");
2316
3004
  function defaultLabel(m) {
2317
3005
  const symbol = m.total?.erc20?.symbol ?? m.total?.native.symbol ?? m.drop?.unitPrice.symbol ?? "";
2318
3006
  switch (m.state) {
@@ -2333,6 +3021,10 @@ function defaultLabel(m) {
2333
3021
  case "connecting":
2334
3022
  return "Connecting\u2026";
2335
3023
  case "live": {
3024
+ const route = m.route;
3025
+ if (route?.kind === "relay") {
3026
+ return `Mint \xB7 ${route.amountIn.formatted} ${route.amountIn.symbol} on ${chainName(route.originChainId)}`;
3027
+ }
2336
3028
  const total = m.total;
2337
3029
  if (!total) return "Mint";
2338
3030
  const legs = total.erc20 ? [total.erc20, ...total.native.value > 0n ? [total.native] : []] : [total.native];
@@ -2364,7 +3056,7 @@ function MintButton(props) {
2364
3056
  const m = useMint(options);
2365
3057
  if (typeof children === "function") return children(m);
2366
3058
  const label = labels?.[m.state] ?? defaultLabel(m);
2367
- return (0, import_react2.createElement)(
3059
+ return (0, import_react3.createElement)(
2368
3060
  "button",
2369
3061
  {
2370
3062
  type: "button",
@@ -2380,7 +3072,7 @@ function MintButton(props) {
2380
3072
  }
2381
3073
 
2382
3074
  // src/index.ts
2383
- var VERSION = "0.1.0";
3075
+ var VERSION = "0.2.0";
2384
3076
  // Annotate the CommonJS export names for ESM import in node:
2385
3077
  0 && (module.exports = {
2386
3078
  CLICKABLE_STATES,
@@ -2388,9 +3080,11 @@ var VERSION = "0.1.0";
2388
3080
  MINT_STATES,
2389
3081
  MintButton,
2390
3082
  MintError,
3083
+ PayWithPicker,
2391
3084
  REASON_CODES,
2392
3085
  VERSION,
2393
3086
  isMintError,
2394
- useMint
3087
+ useMint,
3088
+ usePayOptions
2395
3089
  });
2396
3090
  //# sourceMappingURL=index.cjs.map