@manifoldxyz/dropkit 0.4.1 → 0.5.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/dropkit/dropkit/packages/react/dropkit/dist/chunk-YGBIJOKQ.cjs","../src/core/machine.ts","../src/core/wallet/index.ts","../src/core/adapters/receipt.ts","../src/core/mint-record.ts"],"names":[],"mappings":"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","file":"/home/runner/work/dropkit/dropkit/packages/react/dropkit/dist/chunk-YGBIJOKQ.cjs","sourcesContent":[null,"// Interface contract (orchestrator-owned). Implementation: story s3.\n// Pure reducer, no I/O, no Date.now(): time arrives in events.\nimport { formatUnits } from 'viem';\nimport type { Address, Attempt, DropInfo, MintResult, Money, Route } from './types';\nimport { INELIGIBLE_REASONS } from './errors';\nimport type { ErrorReason, ReasonCode, UnsupportedReason } from './errors';\nimport type { WalletRead } from './adapters/types';\n\nexport const MINT_STATES = [\n 'loading',\n 'unsupported',\n 'upcoming',\n 'ended',\n 'sold-out',\n 'disconnected',\n 'connecting',\n 'live',\n 'ineligible',\n 'insufficient-funds',\n 'switching-chain',\n 'approving',\n 'minting',\n 'confirming',\n 'success',\n 'error',\n] as const;\nexport type MintStateName = (typeof MINT_STATES)[number];\n\n/** States where mint() does something (spec: Clickable column). */\nexport const CLICKABLE_STATES: readonly MintStateName[] = ['disconnected', 'live', 'success', 'error'];\n/** An attempt is in flight; reads/prop changes are deferred (pendingRefresh) until a terminal state. */\nexport const IN_FLIGHT_STATES: readonly MintStateName[] = ['switching-chain', 'approving', 'minting', 'confirming'];\n\n/** uint16 mintCount cap on Manifold extensions; also the global quantity cap. */\nexport const MAX_QUANTITY = 65535;\n\nexport type Account = { address: Address; chainId: number };\n\n/** The wallet's chain cannot cover the Relay quote (spec R3). */\nexport type PayShort = { chainId: number; symbol: string; name: string };\n\nexport type MachineState = {\n name: MintStateName;\n reason: ReasonCode | null;\n /** Plain-English text for reason (decoded revert for SIMULATION_REVERTED / TX_REVERTED). */\n message: string | null;\n /** Generation of the current drop read; DROP_* events with another gen are ignored. */\n readGen: number;\n /** Generation of the current wallet read; bumped by ACCOUNT_CHANGED. WALLET_* with another gen are ignored. */\n walletGen: number;\n drop: DropInfo | null;\n account: Account | null;\n walletRead: WalletRead | null;\n /**\n * Set by ATTEMPT_FAILED for ineligible / insufficient-funds reasons. Sticky: survives drop refreshes and is\n * cleared by the next WALLET_LOADED, ACCOUNT_CHANGED, config LOAD or RESET.\n */\n blocked: Derived | null;\n quantity: number;\n attempt: Attempt | null;\n result: MintResult | null;\n /** Set when a read/account/quantity change arrived during an in-flight attempt. */\n pendingRefresh: boolean;\n /** Monotonic Unix seconds: the maximum accepted DROP_LOADED / TICK time. */\n now: number;\n /** Relay may mint this drop for this wallet (set by WALLET_LOADED). */\n crossChain: boolean;\n /** A Relay route read for the current walletGen is in flight: show loading, not insufficient-funds. */\n routePending: boolean;\n /** How the current wallet pays. null = no route read (direct by default). */\n route: Route | null;\n /** Set when the wallet's chain cannot cover the Relay quote. */\n short: PayShort | null;\n};\n\nexport type MachineEvent =\n /**\n * Sets readGen = gen. kind 'refresh' (30s timer, visibilitychange): keeps the known drop on screen.\n * kind 'config' (mount, config/recipient change): clears drop + walletRead, bumps walletGen, shows 'loading'.\n */\n | { type: 'LOAD'; gen: number; kind: 'config' | 'refresh' }\n | { type: 'DROP_LOADED'; gen: number; drop: DropInfo; now: number }\n | { type: 'DROP_UNSUPPORTED'; gen: number; reason: UnsupportedReason; message: string }\n | { type: 'READ_FAILED'; gen: number; reason: ErrorReason; message: string }\n /** Wallet connected / switched account / switched chain / disconnected (null). Bumps walletGen. */\n | { type: 'ACCOUNT_CHANGED'; account: Account | null }\n /**\n * crossChain: Relay may mint this drop. routePending: a ROUTE_LOADED for this walletGen will follow\n * (the wallet is on another pay chain), so the drop-chain funds check must wait for it.\n */\n | { type: 'WALLET_LOADED'; gen: number; read: WalletRead; crossChain?: boolean; routePending?: boolean }\n /** Result of the Relay route read for walletGen `gen`. Stale gens are ignored. */\n | { type: 'ROUTE_LOADED'; gen: number; route: Route | null; short: PayShort | null }\n | { type: 'WALLET_FAILED'; gen: number; reason: ErrorReason; message: string }\n | { type: 'CONNECT_STARTED' }\n | { type: 'CONNECT_DISMISSED' }\n | { type: 'TICK'; now: number }\n | { type: 'SET_QUANTITY'; quantity: number }\n /** resume: re-attach to a paid Relay request (spec R12); accepted from any idle state. */\n | { type: 'ATTEMPT_STARTED'; attempt: Attempt; resume?: boolean }\n | { type: 'SWITCHING_CHAIN'; attemptId: number }\n | { type: 'APPROVING'; attemptId: number }\n | { type: 'MINT_PROMPTED'; attemptId: number }\n | { type: 'TX_SUBMITTED'; attemptId: number }\n | { type: 'ATTEMPT_SUCCEEDED'; attemptId: number; result: MintResult }\n | { type: 'ATTEMPT_FAILED'; attemptId: number; reason: ReasonCode; message: string }\n /**\n * Click in success/error: clears result/error/walletRead, sets readGen = gen, bumps walletGen and\n * re-derives; with an account it shows 'loading' until the fresh WALLET_LOADED (AC-15).\n */\n | { type: 'RESET'; gen: number };\n\nexport function initialState(opts?: { quantity?: number }): MachineState {\n return {\n name: 'loading',\n reason: null,\n message: null,\n readGen: 0,\n walletGen: 0,\n drop: null,\n account: null,\n walletRead: null,\n blocked: null,\n quantity: toQuantity(opts?.quantity ?? 1, MAX_QUANTITY),\n attempt: null,\n result: null,\n pendingRefresh: false,\n now: 0,\n crossChain: false,\n routePending: false,\n route: null,\n short: null,\n };\n}\n\nconst NO_ROUTE = { crossChain: false, routePending: false, route: null, short: null } as const;\nconst PICK_ANOTHER = 'Pick another network to pay from.';\n\n/** Floor, then clamp to [1, max]. NaN becomes 1. */\nfunction toQuantity(q: number, max: number): number {\n if (Number.isNaN(q)) return 1;\n return Math.max(1, Math.min(Math.floor(q), max));\n}\n\n/** An attempt is in flight from ATTEMPT_STARTED until success/error, including the 'live' gap before the first step. */\nfunction isInFlight(s: MachineState): boolean {\n return s.attempt !== null || IN_FLIGHT_STATES.includes(s.name);\n}\n\n/** Names that are re-derived from data on every change (rule 2). */\nfunction isIdle(s: MachineState): boolean {\n return !isInFlight(s) && s.name !== 'connecting' && s.name !== 'success' && s.name !== 'error';\n}\n\ntype Derived = Pick<MachineState, 'name' | 'reason' | 'message'>;\n\nfunction derive(s: MachineState, unsupported: boolean): Derived {\n const named = (name: MintStateName): Derived => ({ name, reason: null, message: null });\n const { drop, walletRead } = s;\n if (unsupported) return { name: 'unsupported', reason: s.reason, message: s.message };\n if (drop === null) return named('loading');\n if (drop.endTime !== null && drop.endTime <= s.now) return named('ended');\n if (drop.maxSupply !== null && drop.maxSupply > 0n && drop.totalMinted >= drop.maxSupply) return named('sold-out');\n if (drop.startTime !== null && drop.startTime > s.now) return named('upcoming');\n if (s.account === null) return named('disconnected');\n if (walletRead === null) return named('loading');\n if (s.blocked !== null) return s.blocked;\n if (walletRead.ineligible !== null) {\n return { name: 'ineligible', reason: walletRead.ineligible.reason, message: walletRead.ineligible.message };\n }\n // Relay route (spec #machine-changes): pending -> loading, relay -> live, short wallet chain -> insufficient-funds.\n if (s.routePending) return named('loading');\n if (s.route?.kind === 'relay') return named('live');\n if (s.short !== null) {\n return { name: 'insufficient-funds', reason: 'INSUFFICIENT_FUNDS', message: `Not enough ${s.short.symbol} on ${s.short.name}. ${PICK_ANOTHER}` };\n }\n const total = selectTotal(s);\n if (total !== null) {\n const { native, erc20 } = total;\n if (native.value + walletRead.gasReserve > walletRead.balances.native) {\n const more = s.crossChain ? ` ${PICK_ANOTHER}` : '';\n return { name: 'insufficient-funds', reason: 'INSUFFICIENT_FUNDS', message: `Not enough ${native.symbol} to cover the mint.${more}` };\n }\n // A null ERC20 balance means the adapter could not read it; the simulation catches a real shortfall.\n if (erc20 !== null && walletRead.balances.erc20 !== null && erc20.value > walletRead.balances.erc20) {\n return { name: 'insufficient-funds', reason: 'INSUFFICIENT_FUNDS', message: `Not enough ${erc20.symbol} to cover the mint.` };\n }\n }\n return named('live');\n}\n\n/** Re-derive the name when idle; otherwise keep it. `unsupported` stays sticky until a new read result replaces it. */\nfunction settle(s: MachineState, unsupported = s.name === 'unsupported'): MachineState {\n if (!isIdle(s)) return s;\n return { ...s, ...derive(s, unsupported) };\n}\n\n/** Shrink quantity when new data lowers the cap. Never raises it. */\nfunction clampQuantity(s: MachineState): MachineState {\n const quantity = toQuantity(s.quantity, selectMaxQuantity(s) ?? MAX_QUANTITY);\n return quantity === s.quantity ? s : { ...s, quantity };\n}\n\n/**\n * Rules (all are tested):\n * 1. Stale results: DROP_LOADED / DROP_UNSUPPORTED / READ_FAILED with gen !== readGen, WALLET_LOADED /\n * WALLET_FAILED with gen !== walletGen, and attempt events whose attemptId !== attempt?.attemptId\n * return the SAME state object (reference-equal).\n * 2. Derivation when idle (no attempt, not connecting/success/error), in priority order:\n * unsupported (DROP_UNSUPPORTED) > loading (no drop) > ended (endTime !== null && endTime <= now)\n * > sold-out (maxSupply !== null && maxSupply > 0n && totalMinted >= maxSupply)\n * > upcoming (startTime !== null && startTime > now) > disconnected (account null)\n * > loading (account set, no walletRead for the current walletGen)\n * > ineligible (walletRead.ineligible) > insufficient-funds (selectTotal vs walletRead.balances,\n * native leg includes walletRead.gasReserve) > live.\n * Wrong chain is NOT a derived state: live stays live; switching happens on click.\n * 3. TICK only changes time and time never goes backward (now = max(now, event.now)); it can flip\n * upcoming -> live/disconnected and live -> ended, never ended -> anything.\n * 4. CONNECT_STARTED from disconnected -> connecting. CONNECT_DISMISSED from connecting -> derived\n * (disconnected). ACCOUNT_CHANGED with an account while connecting -> derived (loading until WALLET_LOADED).\n * 5. ATTEMPT_STARTED is accepted only from live. The attempt is frozen; name stays 'live' until the\n * first step event (SWITCHING_CHAIN -> switching-chain, APPROVING -> approving,\n * MINT_PROMPTED -> minting, TX_SUBMITTED -> confirming). Steps only move forward in that order\n * (skips allowed); a duplicate or backward step returns the SAME state object.\n * 6. During IN_FLIGHT_STATES: LOAD, ACCOUNT_CHANGED, SET_QUANTITY, TICK, DROP_LOADED and WALLET_LOADED\n * do not change name/attempt; they store data where applicable and set pendingRefresh = true\n * (TICK only updates now). ACCOUNT_CHANGED still bumps walletGen.\n * 7. ATTEMPT_SUCCEEDED -> success with result. ATTEMPT_FAILED maps by reason category:\n * INELIGIBLE_REASONS -> ineligible, INSUFFICIENT_FUNDS -> insufficient-funds (both stored in `blocked`,\n * sticky until the next WALLET_LOADED / ACCOUNT_CHANGED / config LOAD / RESET, and not clickable),\n * ERROR_REASONS -> error. All clear attempt. In success/error, reads still update data but name\n * stays until RESET. Every success and every submitted error clears the drop and invalidates its\n * read generation, so RESET cannot become live until a post-terminal DROP_LOADED and a fresh\n * WALLET_LOADED arrive.\n * 8. SET_QUANTITY clamps to [1, selectMaxQuantity(state) ?? MAX_QUANTITY]; non-integers floor.\n * 9. READ_FAILED (current gen) when idle -> error with reason/message.\n */\nexport function reducer(state: MachineState, event: MachineEvent): MachineState {\n const inFlight = isInFlight(state);\n // A failed read/wallet result: error when idle or connecting, deferred in flight, ignored in success/error.\n const failed = (reason: ErrorReason, message: string): MachineState => {\n if (inFlight) return { ...state, pendingRefresh: true };\n if (state.name === 'success' || state.name === 'error') return state;\n return { ...state, name: 'error', reason, message };\n };\n\n switch (event.type) {\n case 'LOAD': {\n if (event.kind === 'refresh') {\n const next = { ...state, readGen: event.gen };\n return inFlight ? { ...next, pendingRefresh: true } : settle(next);\n }\n // A config change is a read boundary: nothing from the previous drop or wallet may stay actionable.\n const next = { ...state, ...NO_ROUTE, readGen: event.gen, drop: null, walletRead: null, blocked: null, walletGen: state.walletGen + 1 };\n return inFlight ? { ...next, pendingRefresh: true } : settle(next, false);\n }\n case 'DROP_LOADED': {\n if (event.gen !== state.readGen) return state;\n let next = { ...state, drop: event.drop, now: Math.max(state.now, event.now) };\n // A drop that sets walletKey (tl-stacks) says which wallet reads still describe it. A changed key drops the\n // read and bumps the generation, so a read started for the old stage is ignored. Other drops never set it.\n const oldKey = state.drop?.walletKey;\n const newKey = event.drop.walletKey;\n if (oldKey !== undefined && newKey !== undefined && oldKey !== newKey) {\n next = { ...next, walletRead: null, walletGen: state.walletGen + 1 };\n }\n if (inFlight) return { ...next, pendingRefresh: true };\n return isIdle(next) ? settle(clampQuantity(next), false) : clampQuantity(next);\n }\n case 'DROP_UNSUPPORTED': {\n if (event.gen !== state.readGen) return state;\n if (inFlight) return { ...state, pendingRefresh: true };\n if (state.name === 'success' || state.name === 'error') return state;\n return { ...state, name: 'unsupported', reason: event.reason, message: event.message };\n }\n case 'READ_FAILED':\n return event.gen !== state.readGen ? state : failed(event.reason, event.message);\n case 'ACCOUNT_CHANGED': {\n const next = { ...state, ...NO_ROUTE, account: event.account, walletRead: null, blocked: null, walletGen: state.walletGen + 1 };\n if (inFlight) return { ...next, pendingRefresh: true };\n // Connecting only ends when an account arrives; a null account keeps the connect UI open.\n if (state.name === 'connecting') return event.account === null ? next : settle({ ...next, name: 'loading' }, false);\n return settle(next);\n }\n case 'WALLET_LOADED': {\n if (event.gen !== state.walletGen) return state;\n // A refresh keeps the last route on screen until the new ROUTE_LOADED replaces it.\n const route = event.routePending\n ? { routePending: state.route === null && state.short === null, route: state.route, short: state.short }\n : { routePending: false, route: null, short: null };\n const next = { ...state, ...route, crossChain: event.crossChain ?? false, walletRead: event.read, blocked: null };\n return inFlight ? { ...next, pendingRefresh: true } : settle(clampQuantity(next));\n }\n case 'ROUTE_LOADED': {\n if (event.gen !== state.walletGen) return state;\n const next = { ...state, routePending: false, route: event.route, short: event.short };\n return inFlight ? { ...next, pendingRefresh: true } : settle(next);\n }\n case 'WALLET_FAILED':\n return event.gen !== state.walletGen ? state : failed(event.reason, event.message);\n case 'CONNECT_STARTED':\n return state.name === 'disconnected' && state.attempt === null\n ? { ...state, name: 'connecting', reason: null, message: null }\n : state;\n case 'CONNECT_DISMISSED':\n return state.name === 'connecting' ? settle({ ...state, name: 'disconnected' }, false) : state;\n case 'TICK':\n return settle({ ...state, now: Math.max(state.now, event.now) });\n case 'SET_QUANTITY': {\n const next = { ...state, quantity: toQuantity(event.quantity, selectMaxQuantity(state) ?? MAX_QUANTITY) };\n return inFlight ? { ...next, pendingRefresh: true } : settle(next);\n }\n case 'ATTEMPT_STARTED':\n if (state.attempt !== null) return state;\n // A resume re-attaches to a deposit that already left the wallet, so it may start from any idle state.\n if (event.resume === true) return IN_FLIGHT_STATES.includes(state.name) ? state : { ...state, attempt: event.attempt };\n return state.name === 'live' ? { ...state, attempt: event.attempt } : state;\n case 'SWITCHING_CHAIN':\n return step(state, event.attemptId, 'switching-chain');\n case 'APPROVING':\n return step(state, event.attemptId, 'approving');\n case 'MINT_PROMPTED':\n return step(state, event.attemptId, 'minting');\n case 'TX_SUBMITTED':\n return step(state, event.attemptId, 'confirming');\n case 'ATTEMPT_SUCCEEDED':\n return state.attempt?.attemptId === event.attemptId\n ? { ...state, name: 'success', reason: null, message: null, attempt: null, result: event.result,\n drop: null, readGen: state.readGen + 1 }\n : state;\n case 'ATTEMPT_FAILED': {\n if (state.attempt?.attemptId !== event.attemptId) return state;\n const { reason, message } = event;\n const done = { ...state, reason, message, attempt: null, result: null };\n if ((INELIGIBLE_REASONS as readonly string[]).includes(reason)) {\n return { ...done, name: 'ineligible', blocked: { name: 'ineligible', reason, message } };\n }\n if (reason === 'INSUFFICIENT_FUNDS') {\n return { ...done, name: 'insufficient-funds', blocked: { name: 'insufficient-funds', reason, message } };\n }\n // Reads before the receipt cannot establish post-transaction freshness.\n return { ...done, name: 'error', drop: state.name === 'confirming' ? null : state.drop,\n readGen: state.name === 'confirming' ? state.readGen + 1 : state.readGen };\n }\n case 'RESET': {\n if (state.name !== 'success' && state.name !== 'error') return state;\n // The wallet data is stale after an attempt: re-read it before the button can be live again (AC-15).\n const next = {\n ...state,\n readGen: event.gen,\n ...NO_ROUTE,\n walletRead: null,\n blocked: null,\n walletGen: state.walletGen + 1,\n result: null,\n pendingRefresh: false,\n };\n // Leave the terminal name so settle() treats the state as idle.\n return settle(clampQuantity({ ...next, name: 'loading' }), false);\n }\n }\n}\n\n/** Attempt steps in lifecycle order; the pre-step 'live' gap ranks below all of them. */\nconst STEP_ORDER: readonly MintStateName[] = ['switching-chain', 'approving', 'minting', 'confirming'];\n\nfunction step(state: MachineState, attemptId: number, name: MintStateName): MachineState {\n if (state.attempt?.attemptId !== attemptId) return state;\n if (STEP_ORDER.indexOf(name) <= STEP_ORDER.indexOf(state.name)) return state;\n return { ...state, name, reason: null, message: null };\n}\n\n/**\n * min(remaining supply, branch limit, MAX_QUANTITY). Remaining supply = maxSupply - totalMinted when\n * maxSupply is a positive bigint. Branch limit = walletRead.maxQuantity when not null.\n * null only when no drop is loaded.\n */\nexport function selectMaxQuantity(state: MachineState): number | null {\n const { drop, walletRead } = state;\n if (drop === null) return null;\n let max = MAX_QUANTITY;\n if (drop.maxSupply !== null && drop.maxSupply > 0n) {\n const remaining = drop.maxSupply - drop.totalMinted;\n if (remaining < BigInt(max)) max = remaining > 0n ? Number(remaining) : 0;\n }\n if (walletRead !== null && walletRead.maxQuantity !== null) {\n max = Math.min(max, Math.max(0, Math.floor(walletRead.maxQuantity)));\n }\n return max;\n}\n\n/**\n * Totals for state.quantity, fee included.\n * native price: native = (unitPrice + unitFee) * q (pricePer 'total': unitPrice + unitFee * q).\n * ERC20 price: native = unitFee * q, erc20 = unitPrice * q (or unitPrice when pricePer 'total').\n * null when no drop. Money.formatted uses viem formatUnits.\n */\nexport function selectTotal(state: MachineState): { native: Money; erc20: Money | null } | null {\n const { drop } = state;\n if (drop === null) return null;\n const q = BigInt(state.quantity);\n const price = drop.unitPrice;\n const fee = drop.unitFee;\n const priceTotal = drop.pricePer === 'total' ? price.value : price.value * q;\n if (drop.paymentToken !== null) {\n return {\n native: money(fee.value * q, fee),\n erc20: money(priceTotal, price),\n };\n }\n // pricePer 'total': the fee is still per unit.\n return { native: money(priceTotal + fee.value * q, price), erc20: null };\n}\n\nfunction money(value: bigint, like: Money): Money {\n return { value, decimals: like.decimals, symbol: like.symbol, formatted: formatUnits(value, like.decimals) };\n}\n\nexport function canAct(state: MachineState): boolean {\n return state.attempt === null && CLICKABLE_STATES.includes(state.name);\n}\n","// Interface contract (orchestrator-owned). Implementation: story s4. Spec #state-management\n// \"Connect widget lifecycle\" + Q20 (no getProvider; use ManifoldEthereumProvider).\nimport { createWalletClient, custom, getAddress, publicActions, type Address, type EIP1193Provider, type Hex, type WalletClient } from 'viem';\nimport type { Account } from '../machine';\nimport { MintError } from '../errors';\nimport { getChain } from '../rpc';\n\nexport const CONNECT_WIDGET_VERSION = '6.5.0';\nexport const CONNECT_BASE_URL = `https://connect.manifoldxyz.dev/${CONNECT_WIDGET_VERSION}`;\nexport const CONNECT_ID = 'mint-button';\n/** Event names copied from @manifoldxyz/frontend-provider-types (not imported). */\nexport const ADDRESS_CHANGED = 'ethereum-address-changed';\nexport const CHAIN_CHANGED = 'frontend-provider-chain-changed';\nexport const LOADER_KEY = '__manifoldMintConnectLoader';\n/** Manifold's WalletConnect project id, used when no caller passes one so the WalletConnect option always shows. */\nexport const DEFAULT_WALLETCONNECT_PROJECT_ID = 'a03ac1695f309dc8d756e7b4006e5e7d';\n\nexport type ConnectOptions = { chainIds: number[]; walletConnectProjectId?: string };\n\ntype ConnectGlobal = { version?: string; capabilities?: string[]; ready?: Promise<unknown> };\ntype EthProviderGlobal = {\n selectedAddress?: () => string | undefined;\n chainId?: () => number | undefined;\n provider?: (withSigner?: boolean) => { provider?: EIP1193Provider } | undefined;\n browserChainId?: () => number | undefined;\n browserProvider?: () => { provider?: EIP1193Provider } | undefined;\n};\nconst STATE_KEY = '__manifoldMintConnectStateV1';\ntype LoadState = { promise: Promise<void>; networks: number[]; projectId?: string; div?: HTMLElement; nodes: Node[] };\ntype PageState = { load?: LoadState; pendingModal?: Promise<'connected' | 'dismissed'> };\ntype Win = Window & { [LOADER_KEY]?: Promise<void>; [STATE_KEY]?: PageState; ManifoldConnect?: ConnectGlobal; ManifoldEthereumProvider?: EthProviderGlobal };\nconst win = () => window as unknown as Win;\n// Lazily accessed so importing this module never reads window during SSR.\nconst pageState = (): PageState => (win()[STATE_KEY] ??= {});\n\nconst TOO_OLD = 'A Manifold connect widget older than 6.4.0 is already on this page; update it or remove it so dropkit can load ' + CONNECT_WIDGET_VERSION + '.';\n\nconst MANIFOLD_HOST = /(^|\\.)(manifoldxyz\\.dev|manifold\\.xyz)$/;\n\n/** Only Manifold connect-widget bundles count as an old widget: connect.manifoldxyz.dev, or connect.umd*.js on a Manifold host. */\nfunction isManifoldConnectScript(src: string): boolean {\n try {\n const url = new URL(src, document.baseURI);\n return url.hostname === 'connect.manifoldxyz.dev' || (MANIFOLD_HOST.test(url.hostname) && /\\/connect\\.umd[^/]*\\.js$/.test(url.pathname));\n } catch {\n return false;\n }\n}\n\nfunction applyState(state: LoadState): void {\n if (!state.div) return;\n const network = state.networks.join(',');\n // Connect 6.4 remounts on every attribute mutation, even an identical value.\n if (state.div.dataset.network !== network) state.div.dataset.network = network;\n // A caller's id wins; otherwise keep a host page's own id, else use Manifold's default.\n const projectId = state.projectId ?? state.div.dataset.walletConnectProjectId ?? DEFAULT_WALLETCONNECT_PROJECT_ID;\n if (state.div.dataset.walletConnectProjectId !== projectId) state.div.dataset.walletConnectProjectId = projectId;\n}\n\n/** Adds a caller's chains (first-call order) and promotes the first nonempty project id; a conflicting later id only warns. */\nfunction register(state: LoadState, opts: ConnectOptions): void {\n for (const id of opts.chainIds) if (!state.networks.includes(id)) state.networks.push(id);\n const id = opts.walletConnectProjectId;\n if (id) {\n if (!state.projectId) state.projectId = id;\n else if (state.projectId !== id) {\n console.warn(`dropkit: walletConnectProjectId \"${id}\" ignored; this page already uses \"${state.projectId}\" for the shared connect widget.`);\n }\n }\n applyState(state);\n}\n\nfunction startLoad(state: LoadState): Promise<void> {\n const connect = win().ManifoldConnect;\n if (connect?.capabilities?.includes('open')) return Promise.resolve(connect.ready).then(() => undefined);\n\n const hasScript = Array.from(document.querySelectorAll('script[src]')).some((s) => isManifoldConnectScript((s as HTMLScriptElement).src));\n if (hasScript || document.querySelector('[data-widget=\"m-connect\"]')) {\n return Promise.reject(new MintError('CONNECT_WIDGET_TOO_OLD', TOO_OLD));\n }\n\n return new Promise<void>((resolve, reject) => {\n const css = document.createElement('link');\n css.rel = 'stylesheet';\n css.href = `${CONNECT_BASE_URL}/connect.css`;\n document.head.appendChild(css);\n state.nodes.push(css);\n\n const div = document.createElement('div');\n div.hidden = true;\n div.style.display = 'none';\n div.dataset.widget = 'm-connect';\n div.dataset.connectId = CONNECT_ID;\n div.dataset.autoReconnect = 'true';\n div.dataset.enableNetworkSwitching = 'true';\n state.div = div;\n applyState(state);\n document.body.appendChild(div);\n state.nodes.push(div);\n\n const script = document.createElement('script');\n script.src = `${CONNECT_BASE_URL}/connect.umd.min.js`;\n script.async = true;\n script.addEventListener('load', () => {\n const ready = win().ManifoldConnect?.ready;\n if (!ready) return reject(new MintError('CONNECT_WIDGET_TOO_OLD', TOO_OLD));\n Promise.resolve(ready).then(() => resolve(), reject);\n });\n script.addEventListener('error', () => reject(new MintError('RPC_ERROR', `Could not load the Manifold connect widget from ${CONNECT_BASE_URL}`)));\n document.head.appendChild(script);\n state.nodes.push(script);\n });\n}\n\n/**\n * Idempotent across buttons, React Strict Mode and repeated calls: the promise is stored on\n * window[LOADER_KEY] BEFORE anything is appended. Order: window.ManifoldConnect lists 'open' -> reuse;\n * else an existing Manifold connect script tag or [data-widget=\"m-connect\"] -> reject MintError('CONNECT_WIDGET_TOO_OLD')\n * and inject nothing; else inject CONNECT_BASE_URL/connect.umd.min.js + connect.css and one hidden\n * <div data-widget=\"m-connect\" data-connect-id=\"mint-button\" data-auto-reconnect=\"true\"\n * data-enable-network-switching=\"true\" data-network=\"<ids joined by ,>\" [data-wallet-connect-project-id]>,\n * then await window.ManifoldConnect.ready. Every caller (also after load) adds its chains to the one widget's\n * data-network and may promote the first nonempty project id. A failed load clears window[LOADER_KEY] and\n * removes the nodes it created, so the next call retries.\n */\nexport function loadConnectWidget(opts: ConnectOptions): Promise<void> {\n const w = win();\n const page = pageState();\n const existing = w[LOADER_KEY];\n if (existing) {\n // Adopt a legacy promise without replacing it or taking ownership of its DOM.\n if (page.load?.promise !== existing) {\n const div = document.querySelector<HTMLElement>(`[data-widget=\"m-connect\"][data-connect-id=\"${CONNECT_ID}\"]`) ?? undefined;\n page.load = {\n promise: existing,\n networks: div?.dataset.network?.split(',').filter(Boolean).map(Number) ?? [],\n // The default id is a placeholder, not a choice: a caller's real id may still replace it.\n projectId: div?.dataset.walletConnectProjectId === DEFAULT_WALLETCONNECT_PROJECT_ID ? undefined : div?.dataset.walletConnectProjectId,\n div,\n nodes: [],\n };\n }\n register(page.load, opts);\n return existing;\n }\n let resolve!: () => void;\n let reject!: (reason: unknown) => void;\n const p = new Promise<void>((res, rej) => { resolve = res; reject = rej; });\n const state: LoadState = { promise: p, networks: [], nodes: [] };\n // Publish before DOM insertion: synchronous observers and other copies must see the same load.\n page.load = state;\n w[LOADER_KEY] = p;\n p.catch(() => {\n if (w[LOADER_KEY] === p) delete w[LOADER_KEY];\n if (page.load === state) page.load = undefined;\n for (const node of state.nodes) node.parentNode?.removeChild(node);\n state.nodes.length = 0;\n state.div = undefined;\n });\n register(state, opts);\n try {\n startLoad(state).then(resolve, reject);\n } catch (error) {\n reject(error);\n }\n return p;\n}\n\n/**\n * Dispatches m-connect:open {connectId: CONNECT_ID}; resolves with the next m-connect:closed reason.\n * Calls made before that close share one pending promise and one listener.\n */\nexport function openConnectModal(): Promise<'connected' | 'dismissed'> {\n const page = pageState();\n if (page.pendingModal) return page.pendingModal;\n const p = new Promise<'connected' | 'dismissed'>((resolve) => {\n const onClosed = (e: Event) => {\n const detail = (e as CustomEvent<{ connectId?: string; reason?: string }>).detail ?? {};\n if (detail.connectId !== undefined && detail.connectId !== CONNECT_ID) return;\n window.removeEventListener('m-connect:closed', onClosed);\n page.pendingModal = undefined;\n resolve(detail.reason === 'connected' ? 'connected' : 'dismissed');\n };\n window.addEventListener('m-connect:closed', onClosed);\n });\n page.pendingModal = p;\n window.dispatchEvent(new CustomEvent('m-connect:open', { detail: { connectId: CONNECT_ID } }));\n return p;\n}\n\n/**\n * From window.ManifoldEthereumProvider.selectedAddress()/chainId(); null when not connected.\n * When the wallet is on a chain outside the widget's data-network, chainId() is undefined, so the\n * chain falls back to browserChainId() (the wallet's real chain, updated before CHAIN_CHANGED fires).\n * chainId() wins when present; a throwing browserChainId() counts as unknown (null).\n */\nexport function getAccount(): Account | null {\n const p = win().ManifoldEthereumProvider;\n const address = p?.selectedAddress?.();\n let chainId = p?.chainId?.();\n if (chainId === undefined || chainId === null) {\n try {\n chainId = p?.browserChainId?.();\n } catch {\n chainId = undefined;\n }\n }\n if (!address || chainId === undefined || chainId === null) return null;\n return { address: getAddress(address), chainId: Number(chainId) };\n}\n\n/** Calls cb(getAccount()) on ADDRESS_CHANGED and CHAIN_CHANGED window events. Returns unsubscribe. */\nexport function subscribeAccount(cb: (account: Account | null) => void): () => void {\n const handler = () => cb(getAccount());\n window.addEventListener(ADDRESS_CHANGED, handler);\n window.addEventListener(CHAIN_CHANGED, handler);\n return () => {\n window.removeEventListener(ADDRESS_CHANGED, handler);\n window.removeEventListener(CHAIN_CHANGED, handler);\n };\n}\n\nfunction eip1193(): EIP1193Provider {\n const p = win().ManifoldEthereumProvider;\n const provider = (p?.provider?.(true) ?? p?.browserProvider?.())?.provider;\n if (!provider) throw new MintError('CHAIN_SWITCH_FAILED', 'No wallet is connected.');\n return provider;\n}\n\n/** viem WalletClient over (ManifoldEthereumProvider.provider(true) ?? browserProvider()).provider. */\nexport function getWalletClient(chainId: number): WalletClient {\n const provider = eip1193();\n // viem's custom() forwards a second options argument; wallet providers take exactly one.\n const request = ((args: Parameters<EIP1193Provider['request']>[0]) => provider.request(args)) as EIP1193Provider['request'];\n return createWalletClient({ chain: getChain(chainId), transport: custom({ request }) });\n}\n\nexport type DepositOutcome = 'mined' | 'reverted' | 'unknown';\n\n/**\n * Waits for a Relay deposit through the wallet's own provider (D14). Only a reverted receipt is final;\n * anything else ('unknown': no receipt in time) keeps polling Relay, same as claim-widgets.\n */\nexport async function waitForDeposit(chainId: number, hash: Hex, timeoutMs = 120_000): Promise<DepositOutcome> {\n try {\n const receipt = await getWalletClient(chainId).extend(publicActions).waitForTransactionReceipt({ hash, confirmations: 1, timeout: timeoutMs });\n return receipt.status === 'reverted' ? 'reverted' : 'mined';\n } catch {\n return 'unknown';\n }\n}\n\n/** A wallet that never answers must not wedge the button (N3). Mutable for tests. */\nexport const WALLET_READ_TIMEOUT = { ms: 10_000 };\n\n/**\n * Native balance of `address` on `chainId`, read through the connected wallet's own provider (spec D14):\n * the wallet is the only provider Dropkit can count on for a chain other than the drop chain.\n * Throws MintError('CHAIN_SWITCH_FAILED') when the wallet is not on `chainId`.\n */\nexport async function getConnectedBalance(address: Address, chainId: number): Promise<bigint> {\n const provider = eip1193();\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new MintError('RPC_ERROR', 'The wallet did not report a balance in time.')), WALLET_READ_TIMEOUT.ms);\n });\n try {\n return await Promise.race([\n (async () => {\n const current = Number(await provider.request({ method: 'eth_chainId' }));\n if (current !== chainId) {\n throw new MintError('CHAIN_SWITCH_FAILED', `The wallet is on chain ${String(current)}, not ${String(chainId)}.`);\n }\n const hex = await provider.request({ method: 'eth_getBalance', params: [address, 'latest'] });\n return BigInt(hex as string);\n })(),\n timeout,\n ]);\n } finally {\n clearTimeout(timer);\n }\n}\n\nconst code = (e: unknown): unknown => (e as { code?: unknown } | null)?.code;\n\ntype Eip1193Request = EIP1193Provider['request'];\nconst hexChainId = (chainId: number): Hex => `0x${chainId.toString(16)}`;\n\n/** The one wallet_switchEthereumChain request body, shared by switchToChain and requestSwitch. */\nconst sendSwitchRequest = (provider: { request: Eip1193Request }, chainId: number) =>\n provider.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: hexChainId(chainId) }] });\n\n/** The one wallet_addEthereumChain request body (from getChain), shared by switchToChain and requestAddChain. */\nconst sendAddRequest = (provider: { request: Eip1193Request }, chainId: number) => {\n const chain = getChain(chainId);\n return provider.request({\n method: 'wallet_addEthereumChain',\n params: [\n {\n chainId: hexChainId(chainId),\n chainName: chain.name,\n nativeCurrency: chain.nativeCurrency,\n rpcUrls: [...chain.rpcUrls.default.http],\n ...(chain.blockExplorers ? { blockExplorerUrls: [chain.blockExplorers.default.url] } : {}),\n },\n ],\n });\n};\n\n/** 4001 -> USER_REJECTED; anything else -> CHAIN_SWITCH_FAILED. */\nfunction chainRequestError(e: unknown, chainId: number): MintError {\n if (code(e) === 4001) return new MintError('USER_REJECTED', 'You rejected the network switch.', { cause: e });\n return new MintError('CHAIN_SWITCH_FAILED', `Could not switch the wallet to chain ${String(chainId)}.`, { cause: e });\n}\n\n/**\n * wallet_switchEthereumChain; on error code 4902 wallet_addEthereumChain (from getChain) then switch again.\n * 4001 -> MintError('USER_REJECTED'); anything else -> MintError('CHAIN_SWITCH_FAILED').\n */\nexport async function switchToChain(chainId: number): Promise<void> {\n getChain(chainId);\n const provider = eip1193();\n try {\n try {\n await sendSwitchRequest(provider, chainId);\n } catch (e) {\n if (code(e) !== 4902) throw e;\n await sendAddRequest(provider, chainId);\n await sendSwitchRequest(provider, chainId);\n }\n } catch (e) {\n throw chainRequestError(e, chainId);\n }\n}\n\n/**\n * Sends wallet_switchEthereumChain and nothing else (one wallet request, for the cart).\n * Error 4902 (unknown chain) returns { needsAdd: true } and sends nothing more; the caller adds the chain on its own click.\n * 4001 -> MintError('USER_REJECTED'); anything else -> MintError('CHAIN_SWITCH_FAILED').\n */\nexport async function requestSwitch(chainId: number): Promise<{ needsAdd: boolean }> {\n getChain(chainId);\n const provider = eip1193();\n try {\n await sendSwitchRequest(provider, chainId);\n return { needsAdd: false };\n } catch (e) {\n if (code(e) === 4902) return { needsAdd: true };\n throw chainRequestError(e, chainId);\n }\n}\n\n/** Sends wallet_addEthereumChain and nothing else. Same error mapping as requestSwitch. */\nexport async function requestAddChain(chainId: number): Promise<void> {\n getChain(chainId);\n const provider = eip1193();\n try {\n await sendAddRequest(provider, chainId);\n } catch (e) {\n throw chainRequestError(e, chainId);\n }\n}\n\n/** The connected wallet's EIP-1193 provider (the one getWalletClient wraps). Throws MintError('CHAIN_SWITCH_FAILED') when none. */\nexport function getWalletProvider(): EIP1193Provider {\n return eip1193();\n}\n","// Shared receipt parsing (orchestrator-owned, spec #edge-cases \"Receipt has other Transfer logs\").\nimport { decodeAbiParameters, getAddress, zeroAddress } from 'viem';\nimport type { Address, MintedToken } from '../types';\nimport type { ParseReceiptArgs, ReceiptLog } from './types';\n\n// keccak256 of the event signatures\nexport const TRANSFER_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';\nexport const TRANSFER_SINGLE_TOPIC = '0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62';\nexport const TRANSFER_BATCH_TOPIC = '0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb';\n\nconst topicAddress = (t: string | undefined): Address | null =>\n t && t.length === 66 ? getAddress(`0x${t.slice(26)}`) : null;\n\n/**\n * Mints to recipient emitted by tokenContract: ERC721 Transfer (4 topics), ERC1155 TransferSingle and\n * TransferBatch, with from = 0x0 and to = recipient. Batch ids/amounts are zipped (lengths must match,\n * else the log is skipped); duplicate token ids are aggregated; order of first appearance is kept.\n */\nexport function parseMintReceipt(logs: readonly ReceiptLog[], { tokenContract, recipient }: ParseReceiptArgs): MintedToken[] {\n const contract = getAddress(tokenContract);\n const to = getAddress(recipient);\n const out = new Map<bigint, bigint>();\n const add = (id: bigint, amount: bigint) => out.set(id, (out.get(id) ?? 0n) + amount);\n for (const log of logs) {\n if (getAddress(log.address) !== contract) continue;\n const [t0, t1, t2, t3] = log.topics;\n if (t0 === TRANSFER_TOPIC && log.topics.length === 4) {\n if (topicAddress(t1) !== zeroAddress || topicAddress(t2) !== to || !t3) continue;\n add(BigInt(t3), 1n);\n } else if (t0 === TRANSFER_SINGLE_TOPIC && log.topics.length === 4) {\n if (topicAddress(t2) !== zeroAddress || topicAddress(t3) !== to) continue;\n const [id, value] = decodeAbiParameters([{ type: 'uint256' }, { type: 'uint256' }], log.data);\n add(id, value);\n } else if (t0 === TRANSFER_BATCH_TOPIC && log.topics.length === 4) {\n if (topicAddress(t2) !== zeroAddress || topicAddress(t3) !== to) continue;\n const [ids, values] = decodeAbiParameters([{ type: 'uint256[]' }, { type: 'uint256[]' }], log.data);\n if (ids.length !== values.length) continue;\n ids.forEach((id, i) => add(id, values[i]!));\n }\n }\n return [...out].map(([tokenId, amount]) => ({ contract, tokenId, amount }));\n}\n","// Manifold's mint record for a claim mint, the same request the Manifold claim widget sends\n// (claim-widgets src/api/apps.ts postTransaction, called from src/lib/transactionsFlow/steps/mint.tsx).\n//\n// After a mint transaction is sent, the widget calls POST {apps API}/public/instance/tx with appId, instanceId and\n// hash in the query string. dropkit sends the same request so its claim mints are recorded like the widget's.\n//\n// Fire and forget: it never throws, never delays the mint, and is not cancelled by unmount (keepalive, no lifetime\n// signal), so a collector who closes the tab right after sending still gets recorded.\nimport type { Hex } from './types';\n\nexport const APPS_API = 'https://apps.api.manifoldxyz.dev';\n\ntype Fetch = typeof fetch;\n\nconst isRecord = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null;\n\n/** instanceId -> the claim's Studio appId, for the page lifetime. Failed lookups are evicted so the next mint retries. */\nconst appIds = new Map<string, Promise<string>>();\n\nasync function loadAppId(fetchFn: Fetch, instanceId: string): Promise<string> {\n const res = await fetchFn(`${APPS_API}/public/instance/data?id=${encodeURIComponent(instanceId)}`, { keepalive: true });\n if (!res.ok) throw new Error(`instance data HTTP ${res.status}`);\n const body: unknown = await res.json();\n const appId = isRecord(body) ? body.appId : undefined;\n if (typeof appId !== 'number' && typeof appId !== 'string') throw new Error('instance data has no appId');\n return String(appId);\n}\n\nfunction appIdFor(fetchFn: Fetch, instanceId: string): Promise<string> {\n const hit = appIds.get(instanceId);\n if (hit) return hit;\n const p = loadAppId(fetchFn, instanceId);\n appIds.set(instanceId, p);\n p.catch(() => {\n if (appIds.get(instanceId) === p) appIds.delete(instanceId);\n });\n return p;\n}\n\n/**\n * Sends Manifold's mint record for a claim mint. `instanceId` is the claim's instance id (the widget sends\n * publicData.claimIndex, falling back to the Studio id; both equal the on-chain claim index dropkit is configured\n * with). The appId is looked up from the public instance data. If that lookup fails the record still goes out with\n * an empty appId: the hash identifies the mint, and the request keeps the widget's shape (appId key present).\n */\nexport function recordClaimMint(args: { instanceId: string; hash: Hex; fetch?: Fetch }): Promise<void> {\n const fetchFn: Fetch = args.fetch ?? ((input, init) => globalThis.fetch(input, init));\n const send = async () => {\n let appId = '';\n try {\n appId = await appIdFor(fetchFn, args.instanceId);\n } catch {\n /* send with an empty appId */\n }\n const query = new URLSearchParams({ appId, instanceId: args.instanceId, hash: args.hash });\n await fetchFn(`${APPS_API}/public/instance/tx?${query.toString()}`, { method: 'POST', keepalive: true });\n };\n return send().catch(() => undefined);\n}\n\n/** Tests only. */\nexport const resetMintRecordCache = (): void => appIds.clear();\n"]}