@manifoldxyz/dropkit 0.3.0 → 0.4.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.js CHANGED
@@ -27,11 +27,11 @@ import {
27
27
  switchToChain,
28
28
  toReason,
29
29
  waitForDeposit
30
- } from "./chunk-3PA74M2L.js";
30
+ } from "./chunk-SUJQB3SL.js";
31
31
 
32
32
  // src/react/useMint.ts
33
33
  import { useCallback, useEffect, useReducer, useRef, useState } from "react";
34
- import { BaseError as BaseError2, ExecutionRevertedError, publicActions } from "viem";
34
+ import { BaseError as BaseError3, ExecutionRevertedError, publicActions } from "viem";
35
35
 
36
36
  // src/core/relay/client.ts
37
37
  import { formatUnits, getAddress, isHex, keccak256, zeroAddress } from "viem";
@@ -83,8 +83,8 @@ function parseQuote(body, originChainId) {
83
83
  });
84
84
  const requestId = isHex(step.requestId) ? step.requestId : isHex(b.requestId) ? b.requestId : null;
85
85
  if (!requestId) throw unknownStep();
86
- const details = obj(b.details);
87
- const currencyIn = obj(details.currencyIn);
86
+ const details2 = obj(b.details);
87
+ const currencyIn = obj(details2.currencyIn);
88
88
  const fees = obj(b.fees);
89
89
  const amountIn = moneyOf(currencyIn.amount, currencyIn.currency);
90
90
  if (obj(currencyIn.currency).address !== void 0 && obj(currencyIn.currency).address !== zeroAddress) throw unknownStep();
@@ -243,12 +243,605 @@ async function pollUntilTerminal(a) {
243
243
  }
244
244
  }
245
245
 
246
+ // src/core/adapters/tl-stacks/index.ts
247
+ import {
248
+ BaseError,
249
+ ContractFunctionRevertedError,
250
+ ContractFunctionZeroDataError,
251
+ InsufficientFundsError,
252
+ decodeErrorResult,
253
+ encodeFunctionData,
254
+ formatUnits as formatUnits2,
255
+ getAddress as getAddress3,
256
+ isAddress,
257
+ zeroAddress as zeroAddress2
258
+ } from "viem";
259
+
260
+ // src/core/adapters/tl-stacks/abis.ts
261
+ var DROP_PHASE = { NOT_CONFIGURED: 0, NOT_STARTED: 1, PRESALE: 2, PUBLIC_SALE: 3, ENDED: 4 };
262
+ var SUPPORTED_VERSIONS = ["2.3.0", "2.3.1"];
263
+ var stacksAbi = [
264
+ { type: "function", name: "VERSION", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
265
+ { type: "function", name: "protocolFee", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "uint256" }] },
266
+ {
267
+ type: "function",
268
+ name: "getDrop",
269
+ stateMutability: "view",
270
+ inputs: [{ name: "nftAddress", type: "address" }],
271
+ outputs: [
272
+ {
273
+ name: "",
274
+ type: "tuple",
275
+ components: [
276
+ { name: "dropType", type: "uint8" },
277
+ { name: "payoutReceiver", type: "address" },
278
+ { name: "initialSupply", type: "uint256" },
279
+ { name: "supply", type: "uint256" },
280
+ { name: "allowance", type: "uint256" },
281
+ { name: "currencyAddress", type: "address" },
282
+ { name: "startTime", type: "uint256" },
283
+ { name: "presaleDuration", type: "uint256" },
284
+ { name: "presaleCost", type: "uint256" },
285
+ { name: "presaleMerkleRoot", type: "bytes32" },
286
+ { name: "publicDuration", type: "uint256" },
287
+ { name: "publicCost", type: "uint256" },
288
+ { name: "decayRate", type: "int256" },
289
+ { name: "baseUri", type: "string" }
290
+ ]
291
+ }
292
+ ]
293
+ },
294
+ {
295
+ type: "function",
296
+ name: "getDropPhase",
297
+ stateMutability: "view",
298
+ inputs: [{ name: "nftAddress", type: "address" }],
299
+ outputs: [{ name: "", type: "uint8" }]
300
+ },
301
+ {
302
+ type: "function",
303
+ name: "getNumberMinted",
304
+ stateMutability: "view",
305
+ inputs: [
306
+ { name: "nftAddress", type: "address" },
307
+ { name: "recipient", type: "address" }
308
+ ],
309
+ outputs: [{ name: "", type: "uint256" }]
310
+ },
311
+ {
312
+ type: "function",
313
+ name: "purchase",
314
+ stateMutability: "payable",
315
+ inputs: [
316
+ { name: "nftAddress", type: "address" },
317
+ { name: "recipient", type: "address" },
318
+ { name: "numberToMint", type: "uint256" },
319
+ { name: "presaleNumberCanMint", type: "uint256" },
320
+ { name: "proof", type: "bytes32[]" }
321
+ ],
322
+ outputs: [{ name: "refundAmount", type: "uint256" }]
323
+ },
324
+ { type: "error", name: "MintZeroTokens", inputs: [] },
325
+ { type: "error", name: "NotOnAllowlist", inputs: [] },
326
+ { type: "error", name: "YouShallNotMint", inputs: [] },
327
+ { type: "error", name: "AlreadyReachedMintAllowance", inputs: [] },
328
+ { type: "error", name: "InsufficientFunds", inputs: [] },
329
+ { type: "error", name: "SanctionedAddress", inputs: [] },
330
+ // OpenZeppelin v5 Pausable: purchase is whenNotPaused.
331
+ { type: "error", name: "EnforcedPause", inputs: [] }
332
+ ];
333
+
334
+ // src/core/adapters/tl-stacks/allowlist.ts
335
+ import { concatHex, encodeAbiParameters, getAddress as getAddress2, keccak256 as keccak2562 } from "viem";
336
+ var HASH = /^0x[0-9a-fA-F]{64}$/;
337
+ var UINT = /^\d+$/;
338
+ var MAX_UINT256 = 2n ** 256n - 1n;
339
+ var unavailable = (message, cause) => new MintError("ALLOWLIST_UNAVAILABLE", message, cause === void 0 ? void 0 : { cause });
340
+ var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
341
+ var hashWallet = (wallet) => keccak2562(encodeAbiParameters([{ type: "address" }], [getAddress2(wallet)]));
342
+ var leafOf = (hashedWallet, allowance) => keccak2562(concatHex([keccak2562(encodeAbiParameters([{ type: "bytes32" }, { type: "uint256" }], [hashedWallet, allowance]))]));
343
+ var hashPair = (a, b) => BigInt(a) < BigInt(b) ? keccak2562(concatHex([a, b])) : keccak2562(concatHex([b, a]));
344
+ var processProof = (leaf, proof) => proof.reduce((node, sibling) => hashPair(node, sibling), leaf);
345
+ function parseAllowlist(body) {
346
+ if (!isRecord(body)) throw unavailable("The allowlist file is not a merkle tree dump.");
347
+ if (body.format !== "standard-v1") throw unavailable("The allowlist file is in an unsupported format.");
348
+ const enc = body.leafEncoding;
349
+ if (!Array.isArray(enc) || enc.length !== 2 || enc[0] !== "bytes32" || enc[1] !== "uint256") {
350
+ throw unavailable("The allowlist file uses an unsupported leaf encoding.");
351
+ }
352
+ const { tree, values } = body;
353
+ if (!Array.isArray(tree) || tree.length === 0 || !tree.every((n) => typeof n === "string" && HASH.test(n))) {
354
+ throw unavailable("The allowlist file has no readable merkle tree.");
355
+ }
356
+ if (!Array.isArray(values)) throw unavailable("The allowlist file has no wallet values.");
357
+ const entries = /* @__PURE__ */ new Map();
358
+ for (const row of values) {
359
+ const value = isRecord(row) ? row.value : void 0;
360
+ const treeIndex = isRecord(row) ? row.treeIndex : void 0;
361
+ if (!Array.isArray(value) || value.length !== 2 || typeof value[0] !== "string" || !HASH.test(value[0])) {
362
+ throw unavailable("The allowlist file has an unreadable wallet entry.");
363
+ }
364
+ const raw = value[1];
365
+ const allowanceText = typeof raw === "string" ? raw : typeof raw === "number" && Number.isSafeInteger(raw) ? String(raw) : "";
366
+ if (!UINT.test(allowanceText) || BigInt(allowanceText) > MAX_UINT256) throw unavailable("The allowlist file has an unreadable allowance.");
367
+ if (typeof treeIndex !== "number") throw unavailable("The allowlist file has an unreadable tree index.");
368
+ const key = value[0].toLowerCase();
369
+ const allowance = BigInt(allowanceText);
370
+ const prev = entries.get(key);
371
+ if (!prev || allowance > prev.allowance) entries.set(key, { allowance, treeIndex });
372
+ }
373
+ return { root: tree[0].toLowerCase(), tree, entries };
374
+ }
375
+ var findEntry = (list, recipient) => list.entries.get(hashWallet(recipient).toLowerCase()) ?? null;
376
+ function assertRoot(list, presaleMerkleRoot) {
377
+ if (list.root !== presaleMerkleRoot.toLowerCase()) {
378
+ throw unavailable("The allowlist file does not match this drop. The creator may have updated the allowlist; reload the page.");
379
+ }
380
+ }
381
+ var buildProof = (list, recipient, entry) => buildProofForHash(list, hashWallet(recipient), entry);
382
+ function buildProofForHash(list, hashedWallet, entry) {
383
+ const { tree } = list;
384
+ const { treeIndex } = entry;
385
+ if (!Number.isInteger(treeIndex) || treeIndex < 1 || treeIndex >= tree.length) {
386
+ throw unavailable("The allowlist file has a tree index outside the tree.");
387
+ }
388
+ const leaf = leafOf(hashedWallet, entry.allowance);
389
+ if (tree[treeIndex].toLowerCase() !== leaf.toLowerCase()) throw unavailable("The allowlist file entry does not match its merkle leaf.");
390
+ const proof = [];
391
+ let i = treeIndex;
392
+ while (i > 0) {
393
+ const sibling = i % 2 === 0 ? i - 1 : i + 1;
394
+ const node = tree[sibling];
395
+ if (node === void 0) throw unavailable("The allowlist file has a broken merkle tree.");
396
+ proof.push(node);
397
+ i = Math.floor((i - 1) / 2);
398
+ }
399
+ if (processProof(leaf, proof).toLowerCase() !== list.root) throw unavailable("The allowlist file does not produce a valid proof for this wallet.");
400
+ return proof;
401
+ }
402
+ var scopeCaches = /* @__PURE__ */ new WeakMap();
403
+ function withSignal(p, signal) {
404
+ if (!signal) return p;
405
+ return new Promise((resolve, reject) => {
406
+ const onAbort = () => reject(unavailable("The allowlist request was cancelled.", signal.reason));
407
+ p.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
408
+ if (signal.aborted) return onAbort();
409
+ signal.addEventListener("abort", onAbort, { once: true });
410
+ });
411
+ }
412
+ var ALLOWLIST_TIMEOUT_MS = 3e4;
413
+ var ALLOWLIST_MAX_BYTES = 10 * 1024 * 1024;
414
+ var tooLarge = () => unavailable(`The allowlist file is larger than ${ALLOWLIST_MAX_BYTES / (1024 * 1024)} MB.`);
415
+ async function readCapped(res) {
416
+ const declared = Number(res.headers.get("content-length"));
417
+ if (Number.isFinite(declared) && declared > ALLOWLIST_MAX_BYTES) throw tooLarge();
418
+ if (!res.body) return "";
419
+ const reader = res.body.getReader();
420
+ const chunks = [];
421
+ let total = 0;
422
+ for (; ; ) {
423
+ const { done, value } = await reader.read();
424
+ if (done) break;
425
+ total += value.byteLength;
426
+ if (total > ALLOWLIST_MAX_BYTES) {
427
+ void reader.cancel().catch(() => void 0);
428
+ throw tooLarge();
429
+ }
430
+ chunks.push(value);
431
+ }
432
+ const bytes = new Uint8Array(total);
433
+ let offset = 0;
434
+ for (const c of chunks) {
435
+ bytes.set(c, offset);
436
+ offset += c.byteLength;
437
+ }
438
+ return new TextDecoder().decode(bytes);
439
+ }
440
+ async function fetchAndParse(fetchFn, url, signal) {
441
+ let res;
442
+ try {
443
+ signal.throwIfAborted();
444
+ res = await fetchFn(url, { signal });
445
+ } catch (cause) {
446
+ throw unavailable("Could not reach the allowlist file.", cause);
447
+ }
448
+ if (res.status === 429) throw new MintError("RATE_LIMITED", "The allowlist host is rate limiting requests. Try again shortly.");
449
+ if (!res.ok) throw unavailable(`The allowlist file is not available (HTTP ${res.status}).`);
450
+ let body;
451
+ try {
452
+ const text = await readCapped(res);
453
+ body = JSON.parse(text);
454
+ } catch (cause) {
455
+ if (cause instanceof MintError) throw cause;
456
+ throw unavailable("The allowlist file could not be read.", cause);
457
+ }
458
+ return parseAllowlist(body);
459
+ }
460
+ async function download(fetchFn, url, signal) {
461
+ const controller = new AbortController();
462
+ let onAbort;
463
+ const stopped = new Promise((_, reject) => {
464
+ onAbort = () => {
465
+ const reason = controller.signal.reason;
466
+ reject(reason instanceof MintError ? reason : unavailable("The allowlist request was cancelled.", reason));
467
+ };
468
+ controller.signal.addEventListener("abort", onAbort, { once: true });
469
+ });
470
+ const onLifetimeAbort = () => controller.abort(signal?.reason);
471
+ if (signal?.aborted) onLifetimeAbort();
472
+ else signal?.addEventListener("abort", onLifetimeAbort, { once: true });
473
+ const timer = setTimeout(
474
+ () => controller.abort(unavailable(`The allowlist file did not load within ${ALLOWLIST_TIMEOUT_MS / 1e3} seconds. Try again.`)),
475
+ ALLOWLIST_TIMEOUT_MS
476
+ );
477
+ try {
478
+ return await Promise.race([fetchAndParse(fetchFn, url, controller.signal), stopped]);
479
+ } finally {
480
+ clearTimeout(timer);
481
+ if (onAbort) controller.signal.removeEventListener("abort", onAbort);
482
+ signal?.removeEventListener("abort", onLifetimeAbort);
483
+ }
484
+ }
485
+ function loadAllowlist(args) {
486
+ if (!args.url) {
487
+ return Promise.reject(unavailable("This presale has no allowlist file configured (allowlistUrl). Ask the creator to add it."));
488
+ }
489
+ if (args.signal?.aborted) return Promise.reject(unavailable("The allowlist request was cancelled.", args.signal.reason));
490
+ const fetchFn = args.fetch ?? ((input, init) => globalThis.fetch(input, init));
491
+ let scoped = scopeCaches.get(args.scope);
492
+ if (!scoped || scoped.signal?.aborted) {
493
+ scoped = { signal: args.signal, loads: /* @__PURE__ */ new Map() };
494
+ scopeCaches.set(args.scope, scoped);
495
+ }
496
+ const cache2 = scoped;
497
+ const hit = cache2.loads.get(args.url);
498
+ if (hit) return hit;
499
+ const url = args.url;
500
+ const p = withSignal(download(fetchFn, url, cache2.signal), cache2.signal);
501
+ cache2.loads.set(url, p);
502
+ p.catch(() => {
503
+ if (cache2.loads.get(url) === p) cache2.loads.delete(url);
504
+ });
505
+ return p;
506
+ }
507
+
508
+ // src/core/adapters/tl-stacks/index.ts
509
+ var FALLBACK_GAS_BASE = 150000n;
510
+ var FALLBACK_GAS_PER_TOKEN = 150000n;
511
+ var details = /* @__PURE__ */ new WeakMap();
512
+ function bad(field, message) {
513
+ throw new MintError("INVALID_CONFIG", `${field} ${message}`, { field });
514
+ }
515
+ function validateTlStacksConfig(config) {
516
+ if (!Number.isInteger(config.chainId) || config.chainId <= 0) bad("chainId", "must be a positive integer");
517
+ if (typeof config.stacks !== "string" || !isAddress(config.stacks, { strict: false })) bad("stacks", "is not a valid address");
518
+ if (typeof config.nftContract !== "string" || !isAddress(config.nftContract, { strict: false })) bad("nftContract", "is not a valid address");
519
+ if (config.allowlistUrl !== void 0) {
520
+ const https = (() => {
521
+ try {
522
+ return typeof config.allowlistUrl === "string" && new URL(config.allowlistUrl).protocol === "https:";
523
+ } catch {
524
+ return false;
525
+ }
526
+ })();
527
+ if (!https) bad("allowlistUrl", "must be an https: URL");
528
+ }
529
+ }
530
+ var abortError = () => new DOMException("The operation was aborted", "AbortError");
531
+ var isAbortError = (e) => e?.name === "AbortError";
532
+ function throwIfAborted(ctx) {
533
+ if (ctx.signal?.aborted) throw abortError();
534
+ }
535
+ function io(ctx, start) {
536
+ const signal = ctx.signal;
537
+ if (!signal) return start();
538
+ if (signal.aborted) return Promise.reject(abortError());
539
+ return new Promise((resolve, reject) => {
540
+ const onAbort = () => reject(abortError());
541
+ signal.addEventListener("abort", onAbort, { once: true });
542
+ start().then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
543
+ });
544
+ }
545
+ var shortMessage = (e) => e instanceof BaseError ? e.shortMessage : e instanceof Error ? e.message : String(e);
546
+ function rpcError(e) {
547
+ if (isMintError(e) || isAbortError(e)) throw e;
548
+ throw new MintError("RPC_ERROR", `RPC request failed: ${shortMessage(e)}`, { cause: e });
549
+ }
550
+ async function readStacks(ctx, config, functionName, args = []) {
551
+ try {
552
+ return await io(
553
+ ctx,
554
+ () => ctx.client.readContract({ address: getAddress3(config.stacks), abi: stacksAbi, functionName, args })
555
+ );
556
+ } catch (e) {
557
+ if (isMintError(e) || isAbortError(e)) throw e;
558
+ if (e instanceof BaseError && e.walk((x) => x instanceof ContractFunctionRevertedError || x instanceof ContractFunctionZeroDataError)) {
559
+ throw new MintError("INVALID_CONFIG", `stacks ${functionName}() reverted or returned no data. Is ${config.stacks} a TL Stacks 721 contract on chain ${config.chainId}?`, {
560
+ field: "stacks",
561
+ cause: e
562
+ });
563
+ }
564
+ return rpcError(e);
565
+ }
566
+ }
567
+ var versions = /* @__PURE__ */ new WeakMap();
568
+ async function checkVersion(ctx, config) {
569
+ const load = () => readStacks(ctx, config, "VERSION");
570
+ let version;
571
+ if (ctx.cacheScope) {
572
+ let byStacks = versions.get(ctx.cacheScope);
573
+ if (!byStacks) versions.set(ctx.cacheScope, byStacks = /* @__PURE__ */ new Map());
574
+ const key = `${config.chainId}:${config.stacks.toLowerCase()}`;
575
+ const cache2 = byStacks;
576
+ let hit = cache2.get(key);
577
+ if (!hit) {
578
+ const p = load();
579
+ cache2.set(key, hit = p);
580
+ p.catch(() => {
581
+ if (cache2.get(key) === p) cache2.delete(key);
582
+ });
583
+ }
584
+ version = hit;
585
+ } else {
586
+ version = load();
587
+ }
588
+ const v = await version;
589
+ if (!SUPPORTED_VERSIONS.includes(v)) {
590
+ bad("stacks", `is TL Stacks ${v}. Dropkit supports ${SUPPORTED_VERSIONS.join(" and ")} only.`);
591
+ }
592
+ }
593
+ var money = (value, decimals, symbol) => ({ value, decimals, symbol, formatted: formatUnits2(value, decimals) });
594
+ function nativeMeta(ctx) {
595
+ const n = ctx.client.chain?.nativeCurrency;
596
+ return { decimals: n?.decimals ?? 18, symbol: n?.symbol ?? "ETH" };
597
+ }
598
+ function seconds(v, field) {
599
+ if (v > BigInt(Number.MAX_SAFE_INTEGER)) throw new MintError("INVALID_CONFIG", `${field} is too large for a unix timestamp`, { field });
600
+ return Number(v);
601
+ }
602
+ async function readDrop(ctx, config) {
603
+ validateTlStacksConfig(config);
604
+ throwIfAborted(ctx);
605
+ await checkVersion(ctx, config);
606
+ const nft = getAddress3(config.nftContract);
607
+ const [drop, phaseRaw, fee] = await Promise.all([
608
+ readStacks(ctx, config, "getDrop", [nft]),
609
+ readStacks(ctx, config, "getDropPhase", [nft]),
610
+ readStacks(ctx, config, "protocolFee")
611
+ ]);
612
+ const phase = Number(phaseRaw);
613
+ if (phase === DROP_PHASE.NOT_CONFIGURED) {
614
+ throw new MintError("DROP_NOT_FOUND", "No Transient Labs drop is configured for this NFT contract on this chain.");
615
+ }
616
+ if (drop.currencyAddress !== zeroAddress2) {
617
+ bad("nftContract", `is sold for an ERC-20 (${drop.currencyAddress}). Dropkit supports drops priced in the native token only.`);
618
+ }
619
+ const hasPresale = drop.presaleDuration > 0n;
620
+ const presalePrice = phase === DROP_PHASE.PRESALE || phase === DROP_PHASE.NOT_STARTED && hasPresale;
621
+ const unitPrice = presalePrice ? drop.presaleCost : drop.publicCost;
622
+ const startTime = seconds(drop.startTime, "startTime");
623
+ const presaleEnd = seconds(drop.startTime + drop.presaleDuration, "presaleDuration");
624
+ const soldOut = drop.supply === 0n;
625
+ const neverMintable = drop.initialSupply === 0n;
626
+ const native = nativeMeta(ctx);
627
+ const info = {
628
+ chainId: config.chainId,
629
+ unitPrice: money(unitPrice, native.decimals, native.symbol),
630
+ unitFee: money(fee, native.decimals, native.symbol),
631
+ totalMinted: drop.initialSupply - drop.supply,
632
+ maxSupply: drop.initialSupply,
633
+ // A never-mintable drop has no start either, so the hook clock has no boundary ahead to tick towards (review F13).
634
+ startTime: neverMintable || startTime === 0 ? null : startTime,
635
+ // At supply 0 the drop is sold out for good: no end time, so it never turns into `ended` (D14).
636
+ endTime: neverMintable ? 0 : soldOut ? null : seconds(drop.startTime + drop.presaleDuration + drop.publicDuration, "publicDuration"),
637
+ isAllowlist: phase === DROP_PHASE.PRESALE,
638
+ paymentToken: null,
639
+ pricePer: "unit",
640
+ phaseEndsAt: presalePrice ? presaleEnd : null,
641
+ walletKey: `${phase}:${drop.presaleMerkleRoot}:${unitPrice}:${fee}`
642
+ };
643
+ details.set(info, {
644
+ phase,
645
+ presaleMerkleRoot: drop.presaleMerkleRoot,
646
+ allowance: drop.allowance,
647
+ presaleDuration: drop.presaleDuration,
648
+ publicDuration: drop.publicDuration,
649
+ startTime: drop.startTime
650
+ });
651
+ return info;
652
+ }
653
+ async function detailOf(ctx, config, drop) {
654
+ const hit = details.get(drop);
655
+ if (hit) return hit;
656
+ const nft = getAddress3(config.nftContract);
657
+ const [raw, phase] = await Promise.all([
658
+ readStacks(ctx, config, "getDrop", [nft]),
659
+ readStacks(ctx, config, "getDropPhase", [nft])
660
+ ]);
661
+ return {
662
+ phase: Number(phase),
663
+ presaleMerkleRoot: raw.presaleMerkleRoot,
664
+ allowance: raw.allowance,
665
+ presaleDuration: raw.presaleDuration,
666
+ publicDuration: raw.publicDuration,
667
+ startTime: raw.startTime
668
+ };
669
+ }
670
+ var dayTime = (unix) => new Date(unix * 1e3).toLocaleString(void 0, { dateStyle: "medium", timeStyle: "short" });
671
+ function notOnAllowlist(d) {
672
+ const opens = Number(d.startTime + d.presaleDuration);
673
+ const message = d.publicDuration > 0n ? `This wallet is not on the allowlist. The public sale opens ${dayTime(opens)}.` : "This wallet is not on the allowlist. This drop is allowlist only.";
674
+ return { reason: "NOT_ON_ALLOWLIST", message };
675
+ }
676
+ async function readWallet(ctx, config, { payer, recipient, quantity, drop }) {
677
+ validateTlStacksConfig(config);
678
+ throwIfAborted(ctx);
679
+ const detail = await detailOf(ctx, config, drop);
680
+ const nft = getAddress3(config.nftContract);
681
+ const presale = detail.phase === DROP_PHASE.PRESALE;
682
+ const [minted, native, gasPrice, list] = await Promise.all([
683
+ readStacks(ctx, config, "getNumberMinted", [nft, getAddress3(recipient)]),
684
+ io(ctx, () => ctx.client.getBalance({ address: payer })).catch(rpcError),
685
+ io(ctx, () => ctx.client.getGasPrice()).catch(rpcError),
686
+ presale ? loadAllowlist({ scope: ctx.cacheScope ?? drop, url: config.allowlistUrl, fetch: ctx.fetch, signal: ctx.signal }) : Promise.resolve(null)
687
+ ]);
688
+ let walletMax = detail.allowance;
689
+ let proof = [];
690
+ let presaleNumberCanMint = 0n;
691
+ let ineligible = null;
692
+ let allowlistRemaining = null;
693
+ if (presale && list) {
694
+ assertRoot(list, detail.presaleMerkleRoot);
695
+ const entry = findEntry(list, recipient);
696
+ if (!entry) {
697
+ walletMax = 0n;
698
+ ineligible = notOnAllowlist(detail);
699
+ } else {
700
+ walletMax = entry.allowance;
701
+ presaleNumberCanMint = entry.allowance;
702
+ proof = buildProof(list, recipient, entry);
703
+ }
704
+ }
705
+ const remaining = walletMax > minted ? walletMax - minted : 0n;
706
+ if (presale && ineligible === null) {
707
+ allowlistRemaining = Number(remaining < BigInt(MAX_QUANTITY) ? remaining : BigInt(MAX_QUANTITY));
708
+ if (remaining === 0n) ineligible = { reason: "ALLOWLIST_USED", message: `This wallet has already minted its allowlist allocation of ${walletMax}.` };
709
+ } else if (!presale && remaining === 0n) {
710
+ ineligible = { reason: "WALLET_LIMIT", message: `This wallet has already minted its limit of ${walletMax}.` };
711
+ }
712
+ const maxQuantity = Number(remaining < BigInt(MAX_QUANTITY) ? remaining : BigInt(MAX_QUANTITY));
713
+ const data = { presaleNumberCanMint, proof, unitPrice: drop.unitPrice.value, unitFee: drop.unitFee.value };
714
+ const qty = BigInt(quantity);
715
+ const value = qty * (data.unitPrice + data.unitFee);
716
+ let gas = FALLBACK_GAS_BASE + FALLBACK_GAS_PER_TOKEN * qty;
717
+ const open = detail.phase === DROP_PHASE.PRESALE || detail.phase === DROP_PHASE.PUBLIC_SALE;
718
+ if (open && ineligible === null && native >= value) {
719
+ try {
720
+ const estimated = await io(
721
+ ctx,
722
+ () => ctx.client.estimateGas({ account: payer, to: getAddress3(config.stacks), data: encodePurchase(config, recipient, qty, data), value })
723
+ );
724
+ gas = estimated;
725
+ } catch (e) {
726
+ if (isAbortError(e)) throw e;
727
+ }
728
+ }
729
+ return {
730
+ walletMinted: minted,
731
+ walletMax,
732
+ allowlistRemaining,
733
+ maxQuantity,
734
+ ineligible,
735
+ balances: { native, erc20: null },
736
+ gasReserve: gas * gasPrice,
737
+ data
738
+ };
739
+ }
740
+ function encodePurchase(config, recipient, quantity, data) {
741
+ return encodeFunctionData({
742
+ abi: stacksAbi,
743
+ functionName: "purchase",
744
+ args: [getAddress3(config.nftContract), getAddress3(recipient), quantity, data.presaleNumberCanMint, [...data.proof]]
745
+ });
746
+ }
747
+ function walletData(wallet) {
748
+ const d = wallet.data;
749
+ if (!d || typeof d.presaleNumberCanMint !== "bigint" || typeof d.unitPrice !== "bigint" || typeof d.unitFee !== "bigint" || !Array.isArray(d.proof)) {
750
+ throw new MintError("RPC_ERROR", "The wallet read is missing the data this mint needs. Reload and try again.");
751
+ }
752
+ return d;
753
+ }
754
+ async function buildPlan(ctx, attempt, wallet) {
755
+ const { config, recipient } = attempt;
756
+ validateTlStacksConfig(config);
757
+ throwIfAborted(ctx);
758
+ if (!Number.isInteger(attempt.quantity) || attempt.quantity < 1) {
759
+ throw new MintError("SIMULATION_REVERTED", "Choose at least one token to mint.");
760
+ }
761
+ const data = walletData(wallet);
762
+ const qty = BigInt(attempt.quantity);
763
+ return {
764
+ approve: null,
765
+ mint: {
766
+ to: getAddress3(config.stacks),
767
+ data: encodePurchase(config, recipient, qty, data),
768
+ value: qty * (data.unitPrice + data.unitFee)
769
+ },
770
+ tokenContract: getAddress3(config.nftContract)
771
+ };
772
+ }
773
+ var TL_MESSAGES = {
774
+ NotOnAllowlist: "This wallet is not on the allowlist for the current sale.",
775
+ YouShallNotMint: "This drop is not open for minting right now.",
776
+ AlreadyReachedMintAllowance: "This wallet has already minted its limit.",
777
+ InsufficientFunds: "The price or fee changed since the page loaded, so the payment is too low. Reload the page and try again.",
778
+ MintZeroTokens: "Choose at least one token to mint.",
779
+ SanctionedAddress: "This wallet or the recipient is blocked by a sanctions check, so it cannot mint.",
780
+ EnforcedPause: "Minting is paused by Transient Labs right now. Try again later."
781
+ };
782
+ function revertData(e) {
783
+ for (let cur = e, depth = 0; cur && depth < 12; depth++) {
784
+ const d = cur.data;
785
+ if (typeof d === "string" && d.startsWith("0x") && d.length >= 10) return d;
786
+ if (d && typeof d === "object" && typeof d.data === "string") {
787
+ const inner = d.data;
788
+ if (inner.startsWith("0x") && inner.length >= 10) return inner;
789
+ }
790
+ cur = cur.cause;
791
+ }
792
+ return null;
793
+ }
794
+ function decodeTlRevert(e) {
795
+ if (isMintError(e)) return e;
796
+ if (isAbortError(e)) throw e;
797
+ if (e instanceof BaseError && e.walk((x) => x instanceof InsufficientFundsError)) {
798
+ return new MintError("INSUFFICIENT_FUNDS", `The wallet cannot cover the mint: ${e.shortMessage}`, { cause: e });
799
+ }
800
+ const data = revertData(e);
801
+ if (data) {
802
+ try {
803
+ const { errorName, args } = decodeErrorResult({ abi: stacksAbi, data });
804
+ const known = TL_MESSAGES[errorName];
805
+ if (known) return new MintError("SIMULATION_REVERTED", known, { cause: e, domain: "tl-stacks" });
806
+ const shown = (args ?? []).map(String).join(", ");
807
+ return new MintError("SIMULATION_REVERTED", `Transient Labs rejected the mint (${errorName}(${shown})).`, { cause: e, domain: "tl-stacks" });
808
+ } catch {
809
+ return new MintError("SIMULATION_REVERTED", `Transient Labs rejected the mint (unrecognized error ${data.slice(0, 10)}).`, { cause: e, domain: "tl-stacks" });
810
+ }
811
+ }
812
+ const reverted = e instanceof BaseError && e.walk((x) => x.code === 3 || /execution reverted/i.test(x.message));
813
+ if (reverted) return new MintError("SIMULATION_REVERTED", shortMessage(e), { cause: e });
814
+ return new MintError("RPC_ERROR", `RPC request failed: ${shortMessage(e)}`, { cause: e });
815
+ }
816
+ async function simulate(ctx, attempt, plan) {
817
+ throwIfAborted(ctx);
818
+ try {
819
+ await io(ctx, () => ctx.client.call({ account: attempt.payer, to: plan.mint.to, data: plan.mint.data, value: plan.mint.value }));
820
+ } catch (e) {
821
+ throw decodeTlRevert(e);
822
+ }
823
+ }
824
+ var tlStacksAdapter = { readDrop, readWallet, buildPlan, simulate };
825
+
246
826
  // src/core/relay/outstanding.ts
247
827
  var memory = /* @__PURE__ */ new Map();
248
828
  var claimed = /* @__PURE__ */ new Set();
829
+ function targetOf(config) {
830
+ switch (config.type) {
831
+ case "manifold-claim":
832
+ return `${config.creatorContract.toLowerCase()}:${config.instanceId}`;
833
+ case "contract":
834
+ return config.address.toLowerCase();
835
+ case "tl-stacks":
836
+ return `stacks:${config.nftContract.toLowerCase()}`;
837
+ default: {
838
+ const unreachable = config;
839
+ return unreachable;
840
+ }
841
+ }
842
+ }
249
843
  function outstandingKey(config, payer) {
250
- const target = config.type === "manifold-claim" ? `${config.creatorContract.toLowerCase()}:${config.instanceId}` : config.address.toLowerCase();
251
- return `${String(config.chainId)}:${target}:${payer.toLowerCase()}`;
844
+ return `${String(config.chainId)}:${targetOf(config)}:${payer.toLowerCase()}`;
252
845
  }
253
846
  var getOutstanding = (key) => memory.get(key) ?? null;
254
847
  var setOutstanding = (key, out) => void memory.set(key, out);
@@ -262,15 +855,15 @@ var releaseDeposit = (key) => void claimed.delete(key);
262
855
 
263
856
  // src/core/adapters/contract/index.ts
264
857
  import {
265
- BaseError,
266
- ContractFunctionRevertedError,
267
- ContractFunctionZeroDataError,
268
- InsufficientFundsError,
269
- decodeErrorResult,
270
- encodeFunctionData,
271
- formatUnits as formatUnits2,
272
- getAddress as getAddress2,
273
- isAddress,
858
+ BaseError as BaseError2,
859
+ ContractFunctionRevertedError as ContractFunctionRevertedError2,
860
+ ContractFunctionZeroDataError as ContractFunctionZeroDataError2,
861
+ InsufficientFundsError as InsufficientFundsError2,
862
+ decodeErrorResult as decodeErrorResult2,
863
+ encodeFunctionData as encodeFunctionData2,
864
+ formatUnits as formatUnits3,
865
+ getAddress as getAddress4,
866
+ isAddress as isAddress2,
274
867
  parseAbi,
275
868
  parseUnits
276
869
  } from "viem";
@@ -287,25 +880,25 @@ var APPROVE_GAS = 65000n;
287
880
  var PLACEHOLDER_TOKEN = /\{[A-Za-z_]\w*\}/g;
288
881
  var DISPLAY_AMOUNT = /^\d+(\.\d+)?$/;
289
882
  var NUMERIC_TYPE = /^u?int\d*$/;
290
- function bad(field, message) {
883
+ function bad2(field, message) {
291
884
  throw new MintError("INVALID_CONFIG", `${field} ${message}`, { field });
292
885
  }
293
- var isHexAddress = (a) => typeof a === "string" && isAddress(a, { strict: false });
886
+ var isHexAddress = (a) => typeof a === "string" && isAddress2(a, { strict: false });
294
887
  function functionsNamed(abi, name) {
295
888
  return abi.filter((i) => i.type === "function" && i.name === name);
296
889
  }
297
890
  function validateTemplate(t, path, allowed) {
298
891
  if (typeof t === "string") {
299
892
  for (const token of t.match(PLACEHOLDER_TOKEN) ?? []) {
300
- if (!PLACEHOLDERS.includes(token)) bad(path, `"${t}" is not a known placeholder (${token})`);
301
- if (!allowed.includes(token)) bad(path, `"${t}" uses ${token}, which is not available in this field`);
893
+ if (!PLACEHOLDERS.includes(token)) bad2(path, `"${t}" is not a known placeholder (${token})`);
894
+ if (!allowed.includes(token)) bad2(path, `"${t}" uses ${token}, which is not available in this field`);
302
895
  }
303
896
  } else if (Array.isArray(t)) {
304
897
  t.forEach((x, i) => validateTemplate(x, `${path}[${i}]`, allowed));
305
898
  } else if (typeof t === "number") {
306
- if (!Number.isSafeInteger(t)) bad(path, `${t} is not a safe integer; pass larger or fractional values as a bigint or decimal string`);
899
+ if (!Number.isSafeInteger(t)) bad2(path, `${t} is not a safe integer; pass larger or fractional values as a bigint or decimal string`);
307
900
  } else if (typeof t !== "bigint" && typeof t !== "boolean") {
308
- bad(path, "must be a string, number, bigint, boolean or array");
901
+ bad2(path, "must be a string, number, bigint, boolean or array");
309
902
  }
310
903
  }
311
904
  var ARRAY_TYPE = /^(.*)\[(\d*)\]$/;
@@ -347,7 +940,7 @@ function typeIssue(t, p, path) {
347
940
  if (Array.isArray(t)) return no(`must not be an array for ${p.type}`);
348
941
  if (p.type === "address") {
349
942
  if (isExactPlaceholder(t)) return t === "{recipient}" || t === "{payer}" ? null : no(`${t} is a number, but the abi input is an address`);
350
- return typeof t === "string" && !hasPlaceholder(t) && isAddress(t) ? null : no(`${JSON.stringify(String(t))} is not an address`);
943
+ return typeof t === "string" && !hasPlaceholder(t) && isAddress2(t) ? null : no(`${JSON.stringify(String(t))} is not an address`);
351
944
  }
352
945
  if (p.type === "bool") return typeof t === "boolean" ? null : no(`${JSON.stringify(String(t))} is not a boolean`);
353
946
  if (p.type === "string") return typeof t === "string" ? null : no(`${JSON.stringify(String(t))} is not a string`);
@@ -373,15 +966,15 @@ var NO_PLACEHOLDERS = [];
373
966
  function validateCall(abi, call, prefix, kind, allowed) {
374
967
  const c = call ?? {};
375
968
  const named = typeof c.functionName === "string" ? functionsNamed(abi, c.functionName) : [];
376
- if (named.length === 0) bad(`${prefix}.functionName`, `"${String(c.functionName)}" is not a function in the abi`);
377
- if (c.args !== void 0 && !Array.isArray(c.args)) bad(`${prefix}.args`, "must be an array");
378
- if (kind === "mint" && c.args === void 0) bad(`${prefix}.args`, "is required");
969
+ if (named.length === 0) bad2(`${prefix}.functionName`, `"${String(c.functionName)}" is not a function in the abi`);
970
+ if (c.args !== void 0 && !Array.isArray(c.args)) bad2(`${prefix}.args`, "must be an array");
971
+ if (kind === "mint" && c.args === void 0) bad2(`${prefix}.args`, "is required");
379
972
  const args = c.args ?? [];
380
973
  args.forEach((a, i) => validateTemplate(a, `${prefix}.args[${i}]`, allowed));
381
974
  const sameArity = named.filter((f) => f.inputs.length === args.length);
382
975
  if (sameArity.length === 0) {
383
976
  const want = [...new Set(named.map((f) => f.inputs.length))].join(" or ");
384
- bad(`${prefix}.args`, `has ${args.length} argument(s) but "${String(c.functionName)}" takes ${want}`);
977
+ bad2(`${prefix}.args`, `has ${args.length} argument(s) but "${String(c.functionName)}" takes ${want}`);
385
978
  }
386
979
  const checked = sameArity.map((f) => {
387
980
  let issue = null;
@@ -391,75 +984,75 @@ function validateCall(abi, call, prefix, kind, allowed) {
391
984
  const fits = checked.filter((x) => !x.issue);
392
985
  if (fits.length === 0) {
393
986
  const first = checked[0].issue;
394
- if (sameArity.length === 1) bad(first.path, first.message);
395
- bad(`${prefix}.args`, `fit none of the overloads ${sameArity.map(signature).join(", ")}: ${first.path} ${first.message}`);
987
+ if (sameArity.length === 1) bad2(first.path, first.message);
988
+ bad2(`${prefix}.args`, `fit none of the overloads ${sameArity.map(signature).join(", ")}: ${first.path} ${first.message}`);
396
989
  }
397
990
  const roleFits = fits.filter(
398
991
  ({ f }) => kind === "read" ? f.stateMutability === "view" || f.stateMutability === "pure" : f.stateMutability === "payable" || f.stateMutability === "nonpayable"
399
992
  );
400
993
  if (roleFits.length === 0) {
401
994
  const name = fits[0].f.name;
402
- if (kind === "read") bad(`${prefix}.functionName`, `"${name}" is not a view or pure function`);
995
+ if (kind === "read") bad2(`${prefix}.functionName`, `"${name}" is not a view or pure function`);
403
996
  if (fits.some(({ f }) => f.stateMutability !== "view" && f.stateMutability !== "pure")) {
404
- bad(`${prefix}.functionName`, `"${name}" has missing or invalid stateMutability; mint requires payable or nonpayable`);
997
+ bad2(`${prefix}.functionName`, `"${name}" has missing or invalid stateMutability; mint requires payable or nonpayable`);
405
998
  }
406
- bad(`${prefix}.functionName`, `"${name}" is a view or pure function and cannot mint`);
999
+ bad2(`${prefix}.functionName`, `"${name}" is a view or pure function and cannot mint`);
407
1000
  }
408
1001
  if (roleFits.length > 1) {
409
- bad(`${prefix}.functionName`, `"${String(c.functionName)}" is ambiguous: ${roleFits.map((x) => signature(x.f)).join(" and ")} both accept these arguments`);
1002
+ bad2(`${prefix}.functionName`, `"${String(c.functionName)}" is ambiguous: ${roleFits.map((x) => signature(x.f)).join(" and ")} both accept these arguments`);
410
1003
  }
411
1004
  return roleFits[0].f;
412
1005
  }
413
1006
  function resolveOutput(fn, output, path) {
414
1007
  const outs = fn.outputs;
415
- if (outs.length === 0) bad(`${path}.functionName`, `"${fn.name}" returns nothing`);
1008
+ if (outs.length === 0) bad2(`${path}.functionName`, `"${fn.name}" returns nothing`);
416
1009
  const first = outs[0];
417
1010
  const tuple = outs.length === 1 && first.type === "tuple";
418
1011
  const members = tuple ? first.components ?? [] : outs;
419
1012
  const memberNames = members.map((m, i) => m.name || String(i)).join(", ");
420
1013
  let index;
421
1014
  if (output === void 0) {
422
- if (members.length !== 1) bad(`${path}.output`, `is required: "${fn.name}" returns several values (${memberNames})`);
1015
+ if (members.length !== 1) bad2(`${path}.output`, `is required: "${fn.name}" returns several values (${memberNames})`);
423
1016
  index = 0;
424
1017
  } else if (typeof output === "number") {
425
1018
  if (!Number.isInteger(output) || output < 0 || output >= members.length) {
426
- bad(`${path}.output`, `${output} is not in the abi outputs of "${fn.name}" (${memberNames})`);
1019
+ bad2(`${path}.output`, `${output} is not in the abi outputs of "${fn.name}" (${memberNames})`);
427
1020
  }
428
1021
  index = output;
429
1022
  } else if (typeof output === "string") {
430
1023
  index = members.findIndex((m) => m.name === output);
431
- if (index < 0) bad(`${path}.output`, `"${output}" is not in the abi outputs of "${fn.name}" (${memberNames})`);
1024
+ if (index < 0) bad2(`${path}.output`, `"${output}" is not in the abi outputs of "${fn.name}" (${memberNames})`);
432
1025
  } else {
433
- return bad(`${path}.output`, "must be an index or a name");
1026
+ return bad2(`${path}.output`, "must be an index or a name");
434
1027
  }
435
1028
  const type = members[index].type;
436
1029
  if (!NUMERIC_TYPE.test(type)) {
437
1030
  const reason = `returns ${type}, not a number`;
438
- if (output === void 0) bad(`${path}.functionName`, `"${fn.name}" ${reason}`);
439
- bad(`${path}.output`, `selects a value that is ${type}, not a number`);
1031
+ if (output === void 0) bad2(`${path}.functionName`, `"${fn.name}" ${reason}`);
1032
+ bad2(`${path}.output`, `selects a value that is ${type}, not a number`);
440
1033
  }
441
1034
  return { tuple, index, type };
442
1035
  }
443
1036
  function validateField(abi, name, src, allowed, isPrice) {
444
- if (typeof src !== "object" || src === null) bad(name, "is required and must be { value } or { read }");
1037
+ if (typeof src !== "object" || src === null) bad2(name, "is required and must be { value } or { read }");
445
1038
  const s = src;
446
1039
  const hasRead = Object.hasOwn(s, "read");
447
1040
  const hasValue = Object.hasOwn(s, "value");
448
- if (hasRead && hasValue) bad(name, "must be exactly one of { value } or { read }, not both");
1041
+ if (hasRead && hasValue) bad2(name, "must be exactly one of { value } or { read }, not both");
449
1042
  if (hasRead) {
450
- if (typeof s.read !== "object" || s.read === null) bad(`${name}.read`, "must be { functionName, args?, output? }");
451
- if (s.unit !== void 0) bad(name, "unit is only valid with a string value");
1043
+ if (typeof s.read !== "object" || s.read === null) bad2(`${name}.read`, "must be { functionName, args?, output? }");
1044
+ if (s.unit !== void 0) bad2(name, "unit is only valid with a string value");
452
1045
  const fn = validateCall(abi, s.read, `${name}.read`, "read", allowed);
453
1046
  resolveOutput(fn, s.read.output, `${name}.read`);
454
1047
  } else if (typeof s.value === "bigint") {
455
- if (s.unit !== void 0) bad(name, "unit is only valid with a string value");
456
- if (s.value < 0n) bad(`${name}.value`, "must not be negative");
1048
+ if (s.unit !== void 0) bad2(name, "unit is only valid with a string value");
1049
+ if (s.value < 0n) bad2(`${name}.value`, "must not be negative");
457
1050
  } else if (typeof s.value === "string") {
458
- if (s.unit !== "display") bad(name, 'string values need unit: "display"');
459
- if (!isPrice) bad(name, "display units are only valid for price; use a bigint in base units");
460
- if (!DISPLAY_AMOUNT.test(s.value)) bad(`${name}.value`, `"${s.value}" is not a decimal number like "0.01"`);
1051
+ if (s.unit !== "display") bad2(name, 'string values need unit: "display"');
1052
+ if (!isPrice) bad2(name, "display units are only valid for price; use a bigint in base units");
1053
+ if (!DISPLAY_AMOUNT.test(s.value)) bad2(`${name}.value`, `"${s.value}" is not a decimal number like "0.01"`);
461
1054
  } else {
462
- bad(name, 'must be { value: bigint }, { value: string, unit: "display" } (price only) or { read: {...} }');
1055
+ bad2(name, 'must be { value: bigint }, { value: string, unit: "display" } (price only) or { read: {...} }');
463
1056
  }
464
1057
  }
465
1058
  function hasNonzeroStaticPrice(price) {
@@ -469,27 +1062,27 @@ function hasNonzeroStaticPrice(price) {
469
1062
  }
470
1063
  function validateContractConfig(config) {
471
1064
  const c = config;
472
- if (!c || typeof c !== "object") bad("config", "must be an object");
473
- if (c.type !== "contract") bad("type", 'must be "contract"');
474
- if (!Number.isSafeInteger(c.chainId) || c.chainId <= 0) bad("chainId", "must be a positive safe integer");
475
- if (!isHexAddress(c.address)) bad("address", `"${String(c.address)}" is not a valid address`);
1065
+ if (!c || typeof c !== "object") bad2("config", "must be an object");
1066
+ if (c.type !== "contract") bad2("type", 'must be "contract"');
1067
+ if (!Number.isSafeInteger(c.chainId) || c.chainId <= 0) bad2("chainId", "must be a positive safe integer");
1068
+ if (!isHexAddress(c.address)) bad2("address", `"${String(c.address)}" is not a valid address`);
476
1069
  if (c.tokenContract !== void 0 && !isHexAddress(c.tokenContract)) {
477
- bad("tokenContract", `"${String(c.tokenContract)}" is not a valid address`);
1070
+ bad2("tokenContract", `"${String(c.tokenContract)}" is not a valid address`);
478
1071
  }
479
- if (!Array.isArray(c.abi)) bad("abi", "must be an abi array");
1072
+ if (!Array.isArray(c.abi)) bad2("abi", "must be an abi array");
480
1073
  const abi = c.abi;
481
1074
  const mintFn = validateCall(abi, c.mint, "mint", "mint", PLACEHOLDERS);
482
- if (c.price === void 0 || c.price === null) bad("price", "is required");
1075
+ if (c.price === void 0 || c.price === null) bad2("price", "is required");
483
1076
  validateField(abi, "price", c.price, NO_PLACEHOLDERS, true);
484
1077
  const cur = c.price.currency;
485
1078
  if (cur !== "native" && !(typeof cur === "object" && cur !== null && isHexAddress(cur.address))) {
486
- bad("price.currency", 'must be "native" or { address } with a valid ERC20 address');
1079
+ bad2("price.currency", 'must be "native" or { address } with a valid ERC20 address');
487
1080
  }
488
1081
  if (cur === "native" && mintFn.stateMutability === "nonpayable" && hasNonzeroStaticPrice(c.price)) {
489
- bad("price", `is a native price, but "${mintFn.name}" is nonpayable and cannot take value`);
1082
+ bad2("price", `is a native price, but "${mintFn.name}" is nonpayable and cannot take value`);
490
1083
  }
491
1084
  const per = c.price.per;
492
- if (per !== void 0 && per !== "unit" && per !== "total") bad("price.per", 'must be "unit" or "total"');
1085
+ if (per !== void 0 && per !== "unit" && per !== "total") bad2("price.per", 'must be "unit" or "total"');
493
1086
  validateOptional(abi, "totalMinted", c.totalMinted, NO_PLACEHOLDERS);
494
1087
  validateOptional(abi, "maxSupply", c.maxSupply, NO_PLACEHOLDERS);
495
1088
  validateOptional(abi, "startTime", c.startTime, NO_PLACEHOLDERS);
@@ -504,7 +1097,7 @@ function varFor(token, vars) {
504
1097
  const key = token.slice(1, -1);
505
1098
  const v = vars[key];
506
1099
  if (v === void 0) throw new MintError("INVALID_CONFIG", `placeholder ${token} has no value here`);
507
- return typeof v === "string" ? getAddress2(v) : v;
1100
+ return typeof v === "string" ? getAddress4(v) : v;
508
1101
  }
509
1102
  function fill(t, p, vars) {
510
1103
  const arr = ARRAY_TYPE.exec(p.type);
@@ -519,38 +1112,38 @@ function fill(t, p, vars) {
519
1112
  }
520
1113
  var fillArgs = (args, inputs, vars) => (args ?? []).map((a, i) => fill(a, inputs[i], vars));
521
1114
  var totalFor = (per, price, quantity) => per === "total" ? price : price * quantity;
522
- function shortMessage(e) {
523
- return e instanceof BaseError ? e.shortMessage : e instanceof Error ? e.message : String(e);
1115
+ function shortMessage2(e) {
1116
+ return e instanceof BaseError2 ? e.shortMessage : e instanceof Error ? e.message : String(e);
524
1117
  }
525
- var abortError = () => new DOMException("The operation was aborted", "AbortError");
526
- var isAbortError = (e) => e?.name === "AbortError";
527
- function throwIfAborted(ctx) {
528
- if (ctx.signal?.aborted) throw abortError();
1118
+ var abortError2 = () => new DOMException("The operation was aborted", "AbortError");
1119
+ var isAbortError2 = (e) => e?.name === "AbortError";
1120
+ function throwIfAborted2(ctx) {
1121
+ if (ctx.signal?.aborted) throw abortError2();
529
1122
  }
530
- function io(ctx, start) {
1123
+ function io2(ctx, start) {
531
1124
  const signal = ctx.signal;
532
1125
  if (!signal) return start();
533
- if (signal.aborted) return Promise.reject(abortError());
1126
+ if (signal.aborted) return Promise.reject(abortError2());
534
1127
  return new Promise((resolve, reject) => {
535
- const onAbort = () => reject(abortError());
1128
+ const onAbort = () => reject(abortError2());
536
1129
  signal.addEventListener("abort", onAbort, { once: true });
537
1130
  start().then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
538
1131
  });
539
1132
  }
540
- function rpcError(e) {
541
- if (isMintError(e) || isAbortError(e)) throw e;
542
- throw new MintError("RPC_ERROR", `RPC request failed: ${shortMessage(e)}`, { cause: e });
1133
+ function rpcError2(e) {
1134
+ if (isMintError(e) || isAbortError2(e)) throw e;
1135
+ throw new MintError("RPC_ERROR", `RPC request failed: ${shortMessage2(e)}`, { cause: e });
543
1136
  }
544
1137
  function classifyReadError(e, field) {
545
- if (isMintError(e) || isAbortError(e)) throw e;
546
- if (e instanceof BaseError && e.walk((x) => x instanceof ContractFunctionRevertedError || x instanceof ContractFunctionZeroDataError)) {
1138
+ if (isMintError(e) || isAbortError2(e)) throw e;
1139
+ if (e instanceof BaseError2 && e.walk((x) => x instanceof ContractFunctionRevertedError2 || x instanceof ContractFunctionZeroDataError2)) {
547
1140
  throw new MintError("INVALID_CONFIG", `${field} read reverted or returned no data: ${e.shortMessage}`, { field, cause: e });
548
1141
  }
549
- return rpcError(e);
1142
+ return rpcError2(e);
550
1143
  }
551
1144
  async function readCall(ctx, params, field) {
552
1145
  try {
553
- return await io(ctx, () => ctx.client.readContract(params));
1146
+ return await io2(ctx, () => ctx.client.readContract(params));
554
1147
  } catch (e) {
555
1148
  return classifyReadError(e, field);
556
1149
  }
@@ -589,14 +1182,14 @@ function toSeconds(v, field) {
589
1182
  if (v > BigInt(Number.MAX_SAFE_INTEGER)) throw new MintError("INVALID_CONFIG", `${field} is too large for a unix timestamp`, { field });
590
1183
  return Number(v);
591
1184
  }
592
- function nativeMeta(ctx) {
1185
+ function nativeMeta2(ctx) {
593
1186
  const n = ctx.client.chain?.nativeCurrency;
594
1187
  return { decimals: n?.decimals ?? 18, symbol: n?.symbol ?? "ETH" };
595
1188
  }
596
- var erc20Of = (config) => config.price.currency === "native" ? null : getAddress2(config.price.currency.address);
1189
+ var erc20Of = (config) => config.price.currency === "native" ? null : getAddress4(config.price.currency.address);
597
1190
  async function currencyMeta(ctx, config) {
598
1191
  const token = erc20Of(config);
599
- if (!token) return { token, ...nativeMeta(ctx) };
1192
+ if (!token) return { token, ...nativeMeta2(ctx) };
600
1193
  const field = "price.currency.address";
601
1194
  const [symbol, decimals] = await Promise.all([
602
1195
  readCall(ctx, { address: token, abi: ERC20_ABI, functionName: "symbol" }, field),
@@ -621,13 +1214,13 @@ async function resolvePrice(ctx, config, meta) {
621
1214
  if (typeof value === "bigint") return value;
622
1215
  return parseDisplay(value, (meta ?? await currencyMeta(ctx, config)).decimals);
623
1216
  }
624
- var money = (value, decimals, symbol) => ({
1217
+ var money2 = (value, decimals, symbol) => ({
625
1218
  value,
626
1219
  decimals,
627
1220
  symbol,
628
- formatted: formatUnits2(value, decimals)
1221
+ formatted: formatUnits3(value, decimals)
629
1222
  });
630
- function revertData(e) {
1223
+ function revertData2(e) {
631
1224
  for (let cur = e, depth = 0; cur && depth < 12; depth++) {
632
1225
  const d = cur.data;
633
1226
  if (typeof d === "string" && d.startsWith("0x") && d.length >= 10) return d;
@@ -641,7 +1234,7 @@ function revertData(e) {
641
1234
  }
642
1235
  function describeRevert(abi, data) {
643
1236
  try {
644
- const d = decodeErrorResult({ abi, data });
1237
+ const d = decodeErrorResult2({ abi, data });
645
1238
  const args = d.args ?? [];
646
1239
  if (d.errorName === "Error") return String(args[0] ?? "execution reverted");
647
1240
  const inputs = d.abiItem?.inputs ?? [];
@@ -653,19 +1246,19 @@ function describeRevert(abi, data) {
653
1246
  }
654
1247
  function toSimulationError(e, abi) {
655
1248
  if (isMintError(e)) return e;
656
- if (isAbortError(e)) throw e;
657
- if (e instanceof BaseError && e.walk((x) => x instanceof InsufficientFundsError)) {
1249
+ if (isAbortError2(e)) throw e;
1250
+ if (e instanceof BaseError2 && e.walk((x) => x instanceof InsufficientFundsError2)) {
658
1251
  return new MintError("INSUFFICIENT_FUNDS", `Payer cannot cover the mint: ${e.shortMessage}`, { cause: e });
659
1252
  }
660
- const data = revertData(e);
1253
+ const data = revertData2(e);
661
1254
  if (data) return new MintError("SIMULATION_REVERTED", describeRevert(abi, data), { cause: e });
662
- const isRevert = e instanceof BaseError && e.walk((x) => x.code === 3 || /execution reverted/i.test(x.message));
663
- if (isRevert) return new MintError("SIMULATION_REVERTED", shortMessage(e), { cause: e });
664
- return new MintError("RPC_ERROR", `RPC request failed: ${shortMessage(e)}`, { cause: e });
1255
+ const isRevert = e instanceof BaseError2 && e.walk((x) => x.code === 3 || /execution reverted/i.test(x.message));
1256
+ if (isRevert) return new MintError("SIMULATION_REVERTED", shortMessage2(e), { cause: e });
1257
+ return new MintError("RPC_ERROR", `RPC request failed: ${shortMessage2(e)}`, { cause: e });
665
1258
  }
666
1259
  async function simulateMint(ctx, config, call) {
667
1260
  try {
668
- await io(ctx, () => ctx.client.call({ account: call.payer, to: call.to, data: call.data, value: call.value }));
1261
+ await io2(ctx, () => ctx.client.call({ account: call.payer, to: call.to, data: call.data, value: call.value }));
669
1262
  } catch (e) {
670
1263
  throw toSimulationError(e, config.abi);
671
1264
  }
@@ -673,24 +1266,24 @@ async function simulateMint(ctx, config, call) {
673
1266
  function encodeMint(config, vars) {
674
1267
  const fn = validateCall(config.abi, config.mint, "mint", "mint", PLACEHOLDERS);
675
1268
  try {
676
- return encodeFunctionData({ abi: [fn], functionName: fn.name, args: fillArgs(config.mint.args, fn.inputs, vars) });
1269
+ return encodeFunctionData2({ abi: [fn], functionName: fn.name, args: fillArgs(config.mint.args, fn.inputs, vars) });
677
1270
  } catch (e) {
678
1271
  if (isMintError(e)) throw e;
679
- throw new MintError("INVALID_CONFIG", `mint.args cannot be encoded for ${signature(fn)}: ${shortMessage(e)}`, { field: "mint.args", cause: e });
1272
+ throw new MintError("INVALID_CONFIG", `mint.args cannot be encoded for ${signature(fn)}: ${shortMessage2(e)}`, { field: "mint.args", cause: e });
680
1273
  }
681
1274
  }
682
1275
  function mintValue(config, token, totalPrice) {
683
1276
  if (token) return 0n;
684
1277
  const fn = validateCall(config.abi, config.mint, "mint", "mint", PLACEHOLDERS);
685
1278
  if (fn.stateMutability === "nonpayable") {
686
- if (totalPrice > 0n) bad("price", `is ${totalPrice} wei in native currency, but "${fn.name}" is nonpayable and cannot take value`);
1279
+ if (totalPrice > 0n) bad2("price", `is ${totalPrice} wei in native currency, but "${fn.name}" is nonpayable and cannot take value`);
687
1280
  return 0n;
688
1281
  }
689
1282
  return totalPrice;
690
1283
  }
691
- async function readDrop(ctx, config) {
1284
+ async function readDrop2(ctx, config) {
692
1285
  validateContractConfig(config);
693
- throwIfAborted(ctx);
1286
+ throwIfAborted2(ctx);
694
1287
  const meta = await currencyMeta(ctx, config);
695
1288
  const none = {};
696
1289
  const [price, totalMinted, maxSupply, start, end] = await Promise.all([
@@ -701,11 +1294,11 @@ async function readDrop(ctx, config) {
701
1294
  optField(ctx, config, "endTime", config.endTime, none)
702
1295
  ]);
703
1296
  mintValue(config, meta.token, price);
704
- const native = nativeMeta(ctx);
1297
+ const native = nativeMeta2(ctx);
705
1298
  return {
706
1299
  chainId: config.chainId,
707
- unitPrice: money(price, meta.decimals, meta.symbol),
708
- unitFee: money(0n, native.decimals, native.symbol),
1300
+ unitPrice: money2(price, meta.decimals, meta.symbol),
1301
+ unitFee: money2(0n, native.decimals, native.symbol),
709
1302
  totalMinted: totalMinted ?? 0n,
710
1303
  maxSupply: maxSupply === null || maxSupply === 0n ? null : maxSupply,
711
1304
  startTime: toSeconds(start, "startTime"),
@@ -715,9 +1308,9 @@ async function readDrop(ctx, config) {
715
1308
  pricePer: config.price.per ?? "unit"
716
1309
  };
717
1310
  }
718
- async function readWallet(ctx, config, { payer, recipient, quantity, drop }) {
1311
+ async function readWallet2(ctx, config, { payer, recipient, quantity, drop }) {
719
1312
  validateContractConfig(config);
720
- throwIfAborted(ctx);
1313
+ throwIfAborted2(ctx);
721
1314
  const qty = BigInt(quantity);
722
1315
  const unitPrice = drop.unitPrice.value;
723
1316
  const totalPrice = totalFor(drop.pricePer, unitPrice, qty);
@@ -725,7 +1318,7 @@ async function readWallet(ctx, config, { payer, recipient, quantity, drop }) {
725
1318
  const [walletMinted, rawMax, native, erc20] = await Promise.all([
726
1319
  optField(ctx, config, "walletMinted", config.walletMinted, vars),
727
1320
  optField(ctx, config, "walletMax", config.walletMax, vars),
728
- io(ctx, () => ctx.client.getBalance({ address: payer })).catch(rpcError),
1321
+ io2(ctx, () => ctx.client.getBalance({ address: payer })).catch(rpcError2),
729
1322
  drop.paymentToken ? readCall(ctx, { address: drop.paymentToken, abi: ERC20_ABI, functionName: "balanceOf", args: [payer] }, "price.currency.address").then(
730
1323
  (v) => toBigInt(v, "price.currency.address")
731
1324
  ) : Promise.resolve(null)
@@ -738,10 +1331,10 @@ async function readWallet(ctx, config, { payer, recipient, quantity, drop }) {
738
1331
  if (remaining === 0n) {
739
1332
  ineligible = { reason: "WALLET_LIMIT", message: `This wallet has already minted its limit of ${walletMax}.` };
740
1333
  } else if (drop.paymentToken === null && native >= totalPrice) {
741
- const call = { payer, to: getAddress2(config.address), data: encodeMint(config, vars), value: mintValue(config, null, totalPrice) };
1334
+ const call = { payer, to: getAddress4(config.address), data: encodeMint(config, vars), value: mintValue(config, null, totalPrice) };
742
1335
  try {
743
1336
  await simulateMint(ctx, config, call);
744
- gas = await io(ctx, () => ctx.client.estimateGas({ account: payer, to: call.to, data: call.data, value: call.value })).catch((e) => {
1337
+ gas = await io2(ctx, () => ctx.client.estimateGas({ account: payer, to: call.to, data: call.data, value: call.value })).catch((e) => {
745
1338
  throw toSimulationError(e, config.abi);
746
1339
  });
747
1340
  } catch (e) {
@@ -750,7 +1343,7 @@ async function readWallet(ctx, config, { payer, recipient, quantity, drop }) {
750
1343
  }
751
1344
  }
752
1345
  if (drop.paymentToken) gas += APPROVE_GAS;
753
- const gasPrice = await io(ctx, () => ctx.client.getGasPrice()).catch(rpcError);
1346
+ const gasPrice = await io2(ctx, () => ctx.client.getGasPrice()).catch(rpcError2);
754
1347
  return {
755
1348
  walletMinted,
756
1349
  walletMax,
@@ -761,15 +1354,15 @@ async function readWallet(ctx, config, { payer, recipient, quantity, drop }) {
761
1354
  gasReserve: gas * gasPrice
762
1355
  };
763
1356
  }
764
- async function buildPlan(ctx, attempt, wallet) {
1357
+ async function buildPlan2(ctx, attempt, wallet) {
765
1358
  void wallet;
766
1359
  const { config, payer, recipient } = attempt;
767
1360
  validateContractConfig(config);
768
- throwIfAborted(ctx);
1361
+ throwIfAborted2(ctx);
769
1362
  const qty = BigInt(attempt.quantity);
770
1363
  const unitPrice = await resolvePrice(ctx, config);
771
1364
  const totalPrice = totalFor(config.price.per ?? "unit", unitPrice, qty);
772
- const to = getAddress2(config.address);
1365
+ const to = getAddress4(config.address);
773
1366
  const token = erc20Of(config);
774
1367
  const mint = {
775
1368
  to,
@@ -787,17 +1380,17 @@ async function buildPlan(ctx, attempt, wallet) {
787
1380
  token,
788
1381
  spender: to,
789
1382
  amount: totalPrice,
790
- tx: { to: token, data: encodeFunctionData({ abi: ERC20_ABI, functionName: "approve", args: [to, totalPrice] }), value: 0n }
1383
+ tx: { to: token, data: encodeFunctionData2({ abi: ERC20_ABI, functionName: "approve", args: [to, totalPrice] }), value: 0n }
791
1384
  };
792
1385
  }
793
1386
  }
794
- return { approve, mint, tokenContract: getAddress2(attempt.tokenContract) };
1387
+ return { approve, mint, tokenContract: getAddress4(attempt.tokenContract) };
795
1388
  }
796
- async function simulate(ctx, attempt, plan) {
797
- throwIfAborted(ctx);
1389
+ async function simulate2(ctx, attempt, plan) {
1390
+ throwIfAborted2(ctx);
798
1391
  await simulateMint(ctx, attempt.config, { payer: attempt.payer, to: plan.mint.to, data: plan.mint.data, value: plan.mint.value });
799
1392
  }
800
- var contractAdapter = { readDrop, readWallet, buildPlan, simulate };
1393
+ var contractAdapter = { readDrop: readDrop2, readWallet: readWallet2, buildPlan: buildPlan2, simulate: simulate2 };
801
1394
 
802
1395
  // src/react/useMint.ts
803
1396
  var REFRESH_MS = 3e4;
@@ -811,7 +1404,18 @@ function configKeyOf(config) {
811
1404
  return JSON.stringify(config, (_k, v) => typeof v === "bigint" ? { __dropkitBigInt: v.toString() } : v);
812
1405
  }
813
1406
  function adapterFor(config) {
814
- return config.type === "manifold-claim" ? manifoldClaimAdapter : contractAdapter;
1407
+ switch (config.type) {
1408
+ case "manifold-claim":
1409
+ return manifoldClaimAdapter;
1410
+ case "contract":
1411
+ return contractAdapter;
1412
+ case "tl-stacks":
1413
+ return tlStacksAdapter;
1414
+ default: {
1415
+ const unreachable = config;
1416
+ return unreachable;
1417
+ }
1418
+ }
815
1419
  }
816
1420
  var bridgeEnabled = (config) => config.type === "manifold-claim";
817
1421
  function payChainsOf(config, option) {
@@ -834,10 +1438,32 @@ var symbolOf = (chainId) => {
834
1438
  };
835
1439
  var shortOf = (chainId) => ({ chainId, symbol: symbolOf(chainId), name: chainName(chainId) });
836
1440
  function tokenContractOf(config) {
837
- return config.type === "manifold-claim" ? config.creatorContract : config.tokenContract ?? config.address;
1441
+ switch (config.type) {
1442
+ case "manifold-claim":
1443
+ return config.creatorContract;
1444
+ case "contract":
1445
+ return config.tokenContract ?? config.address;
1446
+ case "tl-stacks":
1447
+ return config.nftContract;
1448
+ default: {
1449
+ const unreachable = config;
1450
+ return unreachable;
1451
+ }
1452
+ }
838
1453
  }
839
1454
  function eventAddressOf(config) {
840
- return config.type === "manifold-claim" ? config.creatorContract : config.address;
1455
+ switch (config.type) {
1456
+ case "manifold-claim":
1457
+ return config.creatorContract;
1458
+ case "contract":
1459
+ return config.address;
1460
+ case "tl-stacks":
1461
+ return config.nftContract;
1462
+ default: {
1463
+ const unreachable = config;
1464
+ return unreachable;
1465
+ }
1466
+ }
841
1467
  }
842
1468
  var isUnsupported = (reason) => UNSUPPORTED_REASONS.includes(reason);
843
1469
  function errorReasonOf(e) {
@@ -925,7 +1551,7 @@ function useMint(options) {
925
1551
  }),
926
1552
  [clientFor]
927
1553
  );
928
- const readDrop2 = useCallback(
1554
+ const readDrop3 = useCallback(
929
1555
  async (life, gen, cfg) => {
930
1556
  const adapter = adapterFor(cfg);
931
1557
  try {
@@ -950,9 +1576,9 @@ function useMint(options) {
950
1576
  const life = { key, controller: new AbortController(), cacheScope: {}, gen };
951
1577
  lifeRef.current = life;
952
1578
  dispatch({ type: "LOAD", gen, kind: "config" });
953
- void readDrop2(life, gen, cfg);
1579
+ void readDrop3(life, gen, cfg);
954
1580
  },
955
- [dispatch, readDrop2]
1581
+ [dispatch, readDrop3]
956
1582
  );
957
1583
  const refreshNow = useCallback(
958
1584
  async (kind = "refresh") => {
@@ -965,9 +1591,9 @@ function useMint(options) {
965
1591
  const gen = ++genRef.current;
966
1592
  life.gen = gen;
967
1593
  dispatch({ type: "LOAD", gen, kind });
968
- await readDrop2(life, gen, propsRef.current.config);
1594
+ await readDrop3(life, gen, propsRef.current.config);
969
1595
  },
970
- [dispatch, readDrop2]
1596
+ [dispatch, readDrop3]
971
1597
  );
972
1598
  const refresh = useCallback(() => refreshNow("refresh"), [refreshNow]);
973
1599
  const isBusy = useCallback(
@@ -1043,7 +1669,7 @@ function useMint(options) {
1043
1669
  }
1044
1670
  } catch (e) {
1045
1671
  if (isMintError(e)) throw e;
1046
- const reverted = e instanceof BaseError2 ? e.walk((cause) => cause instanceof ExecutionRevertedError) : null;
1672
+ const reverted = e instanceof BaseError3 ? e.walk((cause) => cause instanceof ExecutionRevertedError) : null;
1047
1673
  if (reverted instanceof ExecutionRevertedError) {
1048
1674
  throw new MintError("SIMULATION_REVERTED", reverted.shortMessage);
1049
1675
  }
@@ -1094,7 +1720,7 @@ function useMint(options) {
1094
1720
  tokenContract: plan.tokenContract ?? attempt.tokenContract,
1095
1721
  recipient: attempt.recipient
1096
1722
  });
1097
- const result = { txHash: confirmedHash, explorerUrl: explorerUrlFor(attempt.chainId, confirmedHash), tokens };
1723
+ const result = { txHash: confirmedHash, explorerUrl: explorerUrlFor(attempt.chainId, confirmedHash), tokens, requested: attempt.quantity };
1098
1724
  dispatch({ type: "ATTEMPT_SUCCEEDED", attemptId: attempt.attemptId, result });
1099
1725
  emit("success", { state: "success", reason: null, txHash: confirmedHash, quantity: attempt.quantity, chainId: attempt.chainId });
1100
1726
  },
@@ -1143,6 +1769,7 @@ function useMint(options) {
1143
1769
  txHash,
1144
1770
  explorerUrl: explorerUrlFor(fillHash ? cfg.chainId : out.originChainId, txHash),
1145
1771
  tokens,
1772
+ requested: attempt.quantity,
1146
1773
  relay: { requestId: out.requestId, originTxHash: out.originTxHash, originChainId: out.originChainId }
1147
1774
  };
1148
1775
  clearOutstanding(key);
@@ -1298,7 +1925,7 @@ function useMint(options) {
1298
1925
  if (life2) life2.gen = gen;
1299
1926
  dispatch({ type: "RESET", gen });
1300
1927
  dispatch({ type: "LOAD", gen, kind: "config" });
1301
- if (life2) void readDrop2(life2, gen, propsRef.current.config);
1928
+ if (life2) void readDrop3(life2, gen, propsRef.current.config);
1302
1929
  return;
1303
1930
  }
1304
1931
  if (s.name !== "live") return;
@@ -1332,7 +1959,7 @@ function useMint(options) {
1332
1959
  } finally {
1333
1960
  attemptRef.current = null;
1334
1961
  }
1335
- }, [dispatch, emit, readDrop2, executeAttempt, executeRelayAttempt, resumeOutstanding, refreshNow]);
1962
+ }, [dispatch, emit, readDrop3, executeAttempt, executeRelayAttempt, resumeOutstanding, refreshNow]);
1336
1963
  const mint = useCallback(() => {
1337
1964
  void runMint();
1338
1965
  }, [runMint]);
@@ -1541,13 +2168,13 @@ function useMint(options) {
1541
2168
  document.removeEventListener("visibilitychange", onVisible);
1542
2169
  };
1543
2170
  }, [refreshNow]);
1544
- const boundary = machine.drop ? [machine.drop.startTime, machine.drop.endTime].some((t) => t !== null && t > machine.now) : false;
2171
+ const boundary = machine.drop ? [machine.drop.startTime, machine.drop.endTime, machine.drop.phaseEndsAt ?? null].some((t) => t !== null && t > machine.now) : false;
1545
2172
  useEffect(() => {
1546
2173
  if (!boundary) return;
1547
2174
  const timer = setInterval(() => {
1548
2175
  const s = stateRef.current;
1549
2176
  const now = nowSec();
1550
- const crossed = s.drop && [s.drop.startTime, s.drop.endTime].some((t) => t !== null && t > s.now && t <= now);
2177
+ const crossed = s.drop && [s.drop.startTime, s.drop.endTime, s.drop.phaseEndsAt ?? null].some((t) => t !== null && t > s.now && t <= now);
1551
2178
  dispatch({ type: "TICK", now });
1552
2179
  if (crossed) void refreshNow();
1553
2180
  }, TICK_MS);
@@ -1613,13 +2240,13 @@ function useMint(options) {
1613
2240
 
1614
2241
  // src/react/PayWithPicker.ts
1615
2242
  import { createElement, useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
1616
- import { formatUnits as formatUnits3 } from "viem";
2243
+ import { formatUnits as formatUnits4 } from "viem";
1617
2244
  var PICKER_REFRESH_MS = 2e4;
1618
2245
  var PICKER_DEBOUNCE_MS = 500;
1619
2246
  var RELAY_REASONS = /* @__PURE__ */ new Set(["BRIDGE_UNAVAILABLE", "BRIDGE_TIMEOUT", "BRIDGE_REFUNDED", "BRIDGE_FAILED"]);
1620
- var money2 = (value, chainId) => {
2247
+ var money3 = (value, chainId) => {
1621
2248
  const c = getChain(chainId).nativeCurrency;
1622
- return { value, decimals: c.decimals, symbol: c.symbol, formatted: formatUnits3(value, c.decimals) };
2249
+ return { value, decimals: c.decimals, symbol: c.symbol, formatted: formatUnits4(value, c.decimals) };
1623
2250
  };
1624
2251
  function usePayOptions(mint, onSelect) {
1625
2252
  const info = mint.crossChain;
@@ -1695,7 +2322,7 @@ function usePayOptions(mint, onSelect) {
1695
2322
  isDropChain,
1696
2323
  relayFee: isDropChain ? null : quoted?.relayFee ?? null,
1697
2324
  amountIn: isDropChain ? null : quoted?.amountIn ?? null,
1698
- balance: balance && balance.chainId === chainId && chainId === current ? money2(balance.value, chainId) : null,
2325
+ balance: balance && balance.chainId === chainId && chainId === current ? money3(balance.value, chainId) : null,
1699
2326
  loading: !isDropChain && q === void 0,
1700
2327
  select: () => select(chainId)
1701
2328
  };
@@ -1806,7 +2433,7 @@ function MintButton(props) {
1806
2433
  }
1807
2434
 
1808
2435
  // src/index.ts
1809
- var VERSION = "0.3.0";
2436
+ var VERSION = "0.4.0";
1810
2437
  export {
1811
2438
  CLICKABLE_STATES,
1812
2439
  MAX_QUANTITY,