@manifoldxyz/dropkit 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -5
- package/dist/index.cjs +796 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +102 -3
- package/dist/index.d.ts +102 -3
- package/dist/index.js +764 -72
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// src/react/useMint.ts
|
|
2
2
|
import { useCallback, useEffect, useReducer, useRef, useState } from "react";
|
|
3
|
-
import { BaseError as BaseError3, ExecutionRevertedError, publicActions } from "viem";
|
|
3
|
+
import { BaseError as BaseError3, ExecutionRevertedError, publicActions as publicActions2 } from "viem";
|
|
4
4
|
|
|
5
5
|
// src/core/rpc/index.ts
|
|
6
6
|
import { createPublicClient, http } from "viem";
|
|
7
|
-
import { apeChain, base, mainnet, optimism, sepolia, shape } from "viem/chains";
|
|
7
|
+
import { apeChain, base, baseSepolia, mainnet, optimism, sepolia, shape } from "viem/chains";
|
|
8
8
|
|
|
9
9
|
// src/core/errors.ts
|
|
10
10
|
var UNSUPPORTED_REASONS = [
|
|
@@ -22,7 +22,12 @@ var ERROR_REASONS = [
|
|
|
22
22
|
"RATE_LIMITED",
|
|
23
23
|
"RPC_ERROR",
|
|
24
24
|
"CHAIN_SWITCH_FAILED",
|
|
25
|
-
"ALLOWLIST_UNAVAILABLE"
|
|
25
|
+
"ALLOWLIST_UNAVAILABLE",
|
|
26
|
+
// Relay cross-chain mint (spec relay-cross-chain-mint #execute-click).
|
|
27
|
+
"BRIDGE_UNAVAILABLE",
|
|
28
|
+
"BRIDGE_REFUNDED",
|
|
29
|
+
"BRIDGE_FAILED",
|
|
30
|
+
"BRIDGE_TIMEOUT"
|
|
26
31
|
];
|
|
27
32
|
var FUNDS_REASONS = ["INSUFFICIENT_FUNDS"];
|
|
28
33
|
var REASON_CODES = [
|
|
@@ -35,11 +40,14 @@ var MintError = class extends Error {
|
|
|
35
40
|
reason;
|
|
36
41
|
/** INVALID_CONFIG only: dotted path of the bad field, e.g. "mint.args[1]" or "price". */
|
|
37
42
|
field;
|
|
43
|
+
/** 'relay': the message is Relay's decoded destination revert, shown as-is. */
|
|
44
|
+
domain;
|
|
38
45
|
constructor(reason, message, opts) {
|
|
39
46
|
super(message, opts?.cause === void 0 ? void 0 : { cause: opts.cause });
|
|
40
47
|
this.name = "MintError";
|
|
41
48
|
this.reason = reason;
|
|
42
49
|
if (opts?.field !== void 0) this.field = opts.field;
|
|
50
|
+
if (opts?.domain !== void 0) this.domain = opts.domain;
|
|
43
51
|
}
|
|
44
52
|
};
|
|
45
53
|
function isMintError(e) {
|
|
@@ -61,8 +69,22 @@ var CHAINS = {
|
|
|
61
69
|
[base.id]: base,
|
|
62
70
|
[optimism.id]: optimism,
|
|
63
71
|
[shape.id]: shape,
|
|
64
|
-
[apeChain.id]: apeChain
|
|
72
|
+
[apeChain.id]: apeChain,
|
|
73
|
+
// Relay pay chain for Sepolia drops only: never a drop chain, no Manifold bridge.
|
|
74
|
+
[baseSepolia.id]: baseSepolia
|
|
65
75
|
};
|
|
76
|
+
var CHAIN_NAMES = {
|
|
77
|
+
1: "Ethereum",
|
|
78
|
+
8453: "Base",
|
|
79
|
+
10: "Optimism",
|
|
80
|
+
360: "Shape",
|
|
81
|
+
33139: "ApeChain",
|
|
82
|
+
11155111: "Sepolia",
|
|
83
|
+
84532: "Base Sepolia"
|
|
84
|
+
};
|
|
85
|
+
function chainName(chainId) {
|
|
86
|
+
return CHAIN_NAMES[chainId] ?? CHAINS[chainId]?.name ?? `chain ${String(chainId)}`;
|
|
87
|
+
}
|
|
66
88
|
var TRANSPORT_TIMEOUT_MS = 8e3;
|
|
67
89
|
function getChain(chainId) {
|
|
68
90
|
const chain = CHAINS[chainId];
|
|
@@ -133,7 +155,7 @@ function createReadClient(opts) {
|
|
|
133
155
|
}
|
|
134
156
|
|
|
135
157
|
// src/core/wallet/index.ts
|
|
136
|
-
import { createWalletClient, custom, getAddress } from "viem";
|
|
158
|
+
import { createWalletClient, custom, getAddress, publicActions } from "viem";
|
|
137
159
|
var CONNECT_WIDGET_VERSION = "6.4.0";
|
|
138
160
|
var CONNECT_BASE_URL = `https://connect.manifoldxyz.dev/${CONNECT_WIDGET_VERSION}`;
|
|
139
161
|
var CONNECT_ID = "mint-button";
|
|
@@ -300,6 +322,37 @@ function getWalletClient(chainId) {
|
|
|
300
322
|
const request = ((args) => provider.request(args));
|
|
301
323
|
return createWalletClient({ chain: getChain(chainId), transport: custom({ request }) });
|
|
302
324
|
}
|
|
325
|
+
async function waitForDeposit(chainId, hash, timeoutMs = 12e4) {
|
|
326
|
+
try {
|
|
327
|
+
const receipt = await getWalletClient(chainId).extend(publicActions).waitForTransactionReceipt({ hash, confirmations: 1, timeout: timeoutMs });
|
|
328
|
+
return receipt.status === "reverted" ? "reverted" : "mined";
|
|
329
|
+
} catch {
|
|
330
|
+
return "unknown";
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
var WALLET_READ_TIMEOUT = { ms: 1e4 };
|
|
334
|
+
async function getConnectedBalance(address, chainId) {
|
|
335
|
+
const provider = eip1193();
|
|
336
|
+
let timer;
|
|
337
|
+
const timeout = new Promise((_, reject) => {
|
|
338
|
+
timer = setTimeout(() => reject(new MintError("RPC_ERROR", "The wallet did not report a balance in time.")), WALLET_READ_TIMEOUT.ms);
|
|
339
|
+
});
|
|
340
|
+
try {
|
|
341
|
+
return await Promise.race([
|
|
342
|
+
(async () => {
|
|
343
|
+
const current = Number(await provider.request({ method: "eth_chainId" }));
|
|
344
|
+
if (current !== chainId) {
|
|
345
|
+
throw new MintError("CHAIN_SWITCH_FAILED", `The wallet is on chain ${String(current)}, not ${String(chainId)}.`);
|
|
346
|
+
}
|
|
347
|
+
const hex = await provider.request({ method: "eth_getBalance", params: [address, "latest"] });
|
|
348
|
+
return BigInt(hex);
|
|
349
|
+
})(),
|
|
350
|
+
timeout
|
|
351
|
+
]);
|
|
352
|
+
} finally {
|
|
353
|
+
clearTimeout(timer);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
303
356
|
var code = (e) => e?.code;
|
|
304
357
|
async function switchToChain(chainId) {
|
|
305
358
|
const chain = getChain(chainId);
|
|
@@ -331,17 +384,244 @@ async function switchToChain(chainId) {
|
|
|
331
384
|
}
|
|
332
385
|
}
|
|
333
386
|
|
|
387
|
+
// src/core/relay/client.ts
|
|
388
|
+
import { formatUnits, getAddress as getAddress2, isHex, keccak256, zeroAddress } from "viem";
|
|
389
|
+
var RELAY_API = { mainnet: "https://api.relay.link", testnet: "https://api.testnets.relay.link" };
|
|
390
|
+
var MAINNET_PAY_CHAINS = [1, 8453, 10, 360, 33139];
|
|
391
|
+
var MAINNET_DROP_CHAINS = [1, 8453, 10, 360];
|
|
392
|
+
var SEPOLIA = 11155111;
|
|
393
|
+
var BASE_SEPOLIA = 84532;
|
|
394
|
+
var isRelayTestnet = (dropChainId) => dropChainId === SEPOLIA;
|
|
395
|
+
function payChainsFor(dropChainId) {
|
|
396
|
+
if (MAINNET_DROP_CHAINS.includes(dropChainId)) return [...MAINNET_PAY_CHAINS];
|
|
397
|
+
if (dropChainId === SEPOLIA) return [SEPOLIA, BASE_SEPOLIA];
|
|
398
|
+
return [];
|
|
399
|
+
}
|
|
400
|
+
var CACHE_MS = 3e4;
|
|
401
|
+
var cache = /* @__PURE__ */ new Map();
|
|
402
|
+
var cacheKey = (a) => [a.testnet, a.originChainId, a.destinationChainId, a.tx.to.toLowerCase(), a.tx.value, keccak256(a.tx.data), a.payer.toLowerCase(), a.recipient.toLowerCase()].join(":");
|
|
403
|
+
var big = (v) => {
|
|
404
|
+
if (typeof v === "string" && /^\d+$/.test(v)) return BigInt(v);
|
|
405
|
+
if (typeof v === "number" && Number.isSafeInteger(v) && v >= 0) return BigInt(v);
|
|
406
|
+
return 0n;
|
|
407
|
+
};
|
|
408
|
+
var obj = (v) => v !== null && typeof v === "object" ? v : {};
|
|
409
|
+
function moneyOf(amount, currency) {
|
|
410
|
+
const c = obj(currency);
|
|
411
|
+
const decimals = typeof c.decimals === "number" ? c.decimals : 18;
|
|
412
|
+
const symbol = typeof c.symbol === "string" ? c.symbol : "ETH";
|
|
413
|
+
const value = big(amount);
|
|
414
|
+
return { value, decimals, symbol, formatted: formatUnits(value, decimals) };
|
|
415
|
+
}
|
|
416
|
+
function revertReason(errorData) {
|
|
417
|
+
if (typeof errorData !== "string") return "";
|
|
418
|
+
const text2 = errorData.replace(/^execution reverted:?\s*/i, "").trim();
|
|
419
|
+
return text2 === "execution reverted" ? "" : text2;
|
|
420
|
+
}
|
|
421
|
+
function parseQuote(body, originChainId) {
|
|
422
|
+
const b = obj(body);
|
|
423
|
+
const steps = Array.isArray(b.steps) ? b.steps.map(obj) : [];
|
|
424
|
+
const unknownStep = () => new MintError("BRIDGE_UNAVAILABLE", "Relay returned a route this button cannot execute.");
|
|
425
|
+
if (steps.length !== 1 || steps[0].kind !== "transaction") throw unknownStep();
|
|
426
|
+
const step2 = steps[0];
|
|
427
|
+
const rawItems = Array.isArray(step2.items) ? step2.items.map(obj) : [];
|
|
428
|
+
if (rawItems.length !== 1) throw unknownStep();
|
|
429
|
+
const items = rawItems.map((item) => {
|
|
430
|
+
const d = obj(item.data);
|
|
431
|
+
const chainId = Number(d.chainId);
|
|
432
|
+
if (chainId !== originChainId || typeof d.to !== "string" || !isHex(d.data)) throw unknownStep();
|
|
433
|
+
return { chainId, to: getAddress2(d.to), data: d.data, value: big(d.value), gas: d.gas === void 0 ? null : big(d.gas) };
|
|
434
|
+
});
|
|
435
|
+
const requestId = isHex(step2.requestId) ? step2.requestId : isHex(b.requestId) ? b.requestId : null;
|
|
436
|
+
if (!requestId) throw unknownStep();
|
|
437
|
+
const details = obj(b.details);
|
|
438
|
+
const currencyIn = obj(details.currencyIn);
|
|
439
|
+
const fees = obj(b.fees);
|
|
440
|
+
const amountIn = moneyOf(currencyIn.amount, currencyIn.currency);
|
|
441
|
+
if (obj(currencyIn.currency).address !== void 0 && obj(currencyIn.currency).address !== zeroAddress) throw unknownStep();
|
|
442
|
+
if (items[0].value !== amountIn.value) throw unknownStep();
|
|
443
|
+
return {
|
|
444
|
+
requestId,
|
|
445
|
+
originChainId,
|
|
446
|
+
items,
|
|
447
|
+
amountIn,
|
|
448
|
+
relayFee: moneyOf(obj(fees.relayer).amount, obj(fees.relayer).currency ?? currencyIn.currency),
|
|
449
|
+
originGas: moneyOf(obj(fees.gas).amount, obj(fees.gas).currency ?? currencyIn.currency),
|
|
450
|
+
quotedAt: Math.floor(Date.now() / 1e3)
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
var RELAY_TIMEOUTS = { quoteMs: 1e4, statusMs: 1e4 };
|
|
454
|
+
function raceSignal(p, signal) {
|
|
455
|
+
if (!signal) return p;
|
|
456
|
+
if (signal.aborted) return Promise.reject(new DOMException("Relay quote cancelled", "AbortError"));
|
|
457
|
+
return new Promise((resolve, reject) => {
|
|
458
|
+
const onAbort = () => reject(new DOMException("Relay quote cancelled", "AbortError"));
|
|
459
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
460
|
+
p.then(
|
|
461
|
+
(v) => {
|
|
462
|
+
signal.removeEventListener("abort", onAbort);
|
|
463
|
+
resolve(v);
|
|
464
|
+
},
|
|
465
|
+
(e) => {
|
|
466
|
+
signal.removeEventListener("abort", onAbort);
|
|
467
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
468
|
+
}
|
|
469
|
+
);
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
async function timedFetch(f, url, init, ms) {
|
|
473
|
+
const controller = new AbortController();
|
|
474
|
+
let t;
|
|
475
|
+
const timeout = new Promise((_, reject) => {
|
|
476
|
+
t = setTimeout(() => {
|
|
477
|
+
controller.abort();
|
|
478
|
+
reject(new DOMException("Relay request timed out", "TimeoutError"));
|
|
479
|
+
}, ms);
|
|
480
|
+
});
|
|
481
|
+
try {
|
|
482
|
+
return await Promise.race([
|
|
483
|
+
(async () => {
|
|
484
|
+
const res = await f(url, { ...init, signal: controller.signal });
|
|
485
|
+
const json = await res.json().catch(() => null);
|
|
486
|
+
return { ok: res.ok, status: res.status, json };
|
|
487
|
+
})(),
|
|
488
|
+
timeout
|
|
489
|
+
]);
|
|
490
|
+
} finally {
|
|
491
|
+
clearTimeout(t);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
async function requestQuote(a) {
|
|
495
|
+
const f = a.fetch ?? globalThis.fetch;
|
|
496
|
+
const body = {
|
|
497
|
+
user: a.payer,
|
|
498
|
+
recipient: a.recipient,
|
|
499
|
+
refundTo: a.payer,
|
|
500
|
+
// R11: without refundTo, Relay disables automatic refunds.
|
|
501
|
+
originChainId: a.originChainId,
|
|
502
|
+
destinationChainId: a.destinationChainId,
|
|
503
|
+
originCurrency: zeroAddress,
|
|
504
|
+
destinationCurrency: zeroAddress,
|
|
505
|
+
tradeType: "EXACT_OUTPUT",
|
|
506
|
+
amount: a.tx.value.toString(),
|
|
507
|
+
txs: [{ to: a.tx.to, value: a.tx.value.toString(), data: a.tx.data }]
|
|
508
|
+
};
|
|
509
|
+
let res;
|
|
510
|
+
try {
|
|
511
|
+
res = await timedFetch(f, `${a.testnet ? RELAY_API.testnet : RELAY_API.mainnet}/quote`, {
|
|
512
|
+
method: "POST",
|
|
513
|
+
headers: { "Content-Type": "application/json" },
|
|
514
|
+
body: JSON.stringify(body)
|
|
515
|
+
}, RELAY_TIMEOUTS.quoteMs);
|
|
516
|
+
} catch (e) {
|
|
517
|
+
throw new MintError("BRIDGE_UNAVAILABLE", "Could not reach Relay for a cross-chain quote.", { cause: e });
|
|
518
|
+
}
|
|
519
|
+
const json = res.json;
|
|
520
|
+
if (res.ok) return parseQuote(json, a.originChainId);
|
|
521
|
+
const j = obj(json);
|
|
522
|
+
if (j.errorCode === "DESTINATION_TX_FAILED") {
|
|
523
|
+
const reason = revertReason(j.errorData);
|
|
524
|
+
throw new MintError("SIMULATION_REVERTED", reason || "The mint would fail on the drop network.", { cause: json, domain: "relay" });
|
|
525
|
+
}
|
|
526
|
+
if (res.status === 401 || j.errorCode === "UNAUTHORIZED_QUOTE") {
|
|
527
|
+
console.warn("dropkit: Relay rejected the quote (401 UNAUTHORIZED_QUOTE). Relay gates quotes that carry a referrer or need an API key.");
|
|
528
|
+
throw new MintError("BRIDGE_UNAVAILABLE", "Relay refused the cross-chain quote.", { cause: json });
|
|
529
|
+
}
|
|
530
|
+
if (res.status === 429) throw new MintError("RATE_LIMITED", "Relay is rate limiting quotes. Try again in a moment.", { cause: json });
|
|
531
|
+
throw new MintError("BRIDGE_UNAVAILABLE", `Relay could not quote this mint (${String(res.status)}).`, { cause: json });
|
|
532
|
+
}
|
|
533
|
+
function getQuote(a) {
|
|
534
|
+
const k = cacheKey(a);
|
|
535
|
+
const now = Date.now();
|
|
536
|
+
const hit = cache.get(k);
|
|
537
|
+
if (!a.fresh && hit && now - hit.at < CACHE_MS) return raceSignal(hit.quote, a.signal);
|
|
538
|
+
const quote = requestQuote(a);
|
|
539
|
+
cache.set(k, { at: now, quote });
|
|
540
|
+
quote.catch(() => {
|
|
541
|
+
if (cache.get(k)?.quote === quote) cache.delete(k);
|
|
542
|
+
});
|
|
543
|
+
return raceSignal(quote, a.signal);
|
|
544
|
+
}
|
|
545
|
+
var realReason = (v) => typeof v === "string" && v !== "" && v !== "N/A" ? v : null;
|
|
546
|
+
async function getStatus(a) {
|
|
547
|
+
const f = a.fetch ?? globalThis.fetch;
|
|
548
|
+
const res = await raceSignal(timedFetch(f, `${a.testnet ? RELAY_API.testnet : RELAY_API.mainnet}/intents/status/v3?requestId=${a.requestId}`, {}, RELAY_TIMEOUTS.statusMs), a.signal);
|
|
549
|
+
if (!res.ok) throw new Error(`Relay status ${String(res.status)}`);
|
|
550
|
+
const j = obj(res.json);
|
|
551
|
+
return {
|
|
552
|
+
status: typeof j.status === "string" ? j.status : "unknown",
|
|
553
|
+
txHashes: Array.isArray(j.txHashes) ? j.txHashes.filter((h) => isHex(h)) : [],
|
|
554
|
+
failReason: realReason(j.failReason),
|
|
555
|
+
refundFailReason: realReason(j.refundFailReason)
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
var KEEP_POLLING = ["waiting", "depositing", "pending", "submitted", "delayed"];
|
|
559
|
+
var TERMINAL = ["success", "refund", "failure"];
|
|
560
|
+
|
|
561
|
+
// src/core/relay/status.ts
|
|
562
|
+
var POLL_FAST_MS = 2e3;
|
|
563
|
+
var POLL_SLOW_MS = 5e3;
|
|
564
|
+
var POLL_FAST_FOR_MS = 6e4;
|
|
565
|
+
var POLL_CAP_MS = 10 * 6e4;
|
|
566
|
+
var defaultSleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
567
|
+
const t = setTimeout(resolve, ms);
|
|
568
|
+
signal?.addEventListener("abort", () => {
|
|
569
|
+
clearTimeout(t);
|
|
570
|
+
reject(new DOMException("Relay polling cancelled", "AbortError"));
|
|
571
|
+
}, { once: true });
|
|
572
|
+
});
|
|
573
|
+
async function pollUntilTerminal(a) {
|
|
574
|
+
const sleep = a.sleep ?? defaultSleep;
|
|
575
|
+
const now = a.now ?? Date.now;
|
|
576
|
+
const start = now();
|
|
577
|
+
let warned = false;
|
|
578
|
+
for (; ; ) {
|
|
579
|
+
a.assertActive?.();
|
|
580
|
+
try {
|
|
581
|
+
const status = await getStatus({ testnet: a.testnet, requestId: a.requestId, ...a.signal ? { signal: a.signal } : {}, ...a.fetch ? { fetch: a.fetch } : {} });
|
|
582
|
+
if (TERMINAL.includes(status.status)) return { kind: "terminal", status };
|
|
583
|
+
if (!KEEP_POLLING.includes(status.status) && !warned) {
|
|
584
|
+
warned = true;
|
|
585
|
+
console.warn(`dropkit: Relay status "${status.status}" for ${a.requestId}; still waiting.`);
|
|
586
|
+
}
|
|
587
|
+
} catch (e) {
|
|
588
|
+
if (e?.name === "AbortError") throw e;
|
|
589
|
+
}
|
|
590
|
+
a.assertActive?.();
|
|
591
|
+
const elapsed = now() - start;
|
|
592
|
+
if (elapsed >= POLL_CAP_MS) return { kind: "timeout" };
|
|
593
|
+
await sleep(elapsed < POLL_FAST_FOR_MS ? POLL_FAST_MS : POLL_SLOW_MS, a.signal);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// src/core/relay/outstanding.ts
|
|
598
|
+
var memory = /* @__PURE__ */ new Map();
|
|
599
|
+
var claimed = /* @__PURE__ */ new Set();
|
|
600
|
+
function outstandingKey(config, payer) {
|
|
601
|
+
const target = config.type === "manifold-claim" ? `${config.creatorContract.toLowerCase()}:${config.instanceId}` : config.address.toLowerCase();
|
|
602
|
+
return `${String(config.chainId)}:${target}:${payer.toLowerCase()}`;
|
|
603
|
+
}
|
|
604
|
+
var getOutstanding = (key) => memory.get(key) ?? null;
|
|
605
|
+
var setOutstanding = (key, out) => void memory.set(key, out);
|
|
606
|
+
var clearOutstanding = (key) => void memory.delete(key);
|
|
607
|
+
function claimDeposit(key) {
|
|
608
|
+
if (claimed.has(key) || memory.has(key)) return false;
|
|
609
|
+
claimed.add(key);
|
|
610
|
+
return true;
|
|
611
|
+
}
|
|
612
|
+
var releaseDeposit = (key) => void claimed.delete(key);
|
|
613
|
+
|
|
334
614
|
// src/core/adapters/manifold-claim/index.ts
|
|
335
615
|
import {
|
|
336
616
|
BaseError,
|
|
337
617
|
ContractFunctionRevertedError,
|
|
338
618
|
decodeErrorResult,
|
|
339
619
|
encodeFunctionData,
|
|
340
|
-
formatUnits,
|
|
341
|
-
getAddress as
|
|
620
|
+
formatUnits as formatUnits2,
|
|
621
|
+
getAddress as getAddress3,
|
|
342
622
|
isAddress,
|
|
343
|
-
isHex,
|
|
344
|
-
zeroAddress,
|
|
623
|
+
isHex as isHex2,
|
|
624
|
+
zeroAddress as zeroAddress2,
|
|
345
625
|
zeroHash
|
|
346
626
|
} from "viem";
|
|
347
627
|
|
|
@@ -412,13 +692,13 @@ async function getJson(fetchFn, url, signal, what) {
|
|
|
412
692
|
}
|
|
413
693
|
var isRecord = (v) => typeof v === "object" && v !== null;
|
|
414
694
|
var scopeCaches = /* @__PURE__ */ new WeakMap();
|
|
415
|
-
function memo(
|
|
416
|
-
const hit =
|
|
695
|
+
function memo(cache2, key, signal, load) {
|
|
696
|
+
const hit = cache2.get(key);
|
|
417
697
|
if (hit) return hit;
|
|
418
698
|
const p = withSignal(load(), signal);
|
|
419
|
-
|
|
699
|
+
cache2.set(key, p);
|
|
420
700
|
p.catch(() => {
|
|
421
|
-
if (
|
|
701
|
+
if (cache2.get(key) === p) cache2.delete(key);
|
|
422
702
|
});
|
|
423
703
|
return p;
|
|
424
704
|
}
|
|
@@ -468,8 +748,8 @@ async function fetchMerkleEntries(args) {
|
|
|
468
748
|
scoped = { signal: args.signal, instances: /* @__PURE__ */ new Map(), entries: /* @__PURE__ */ new Map() };
|
|
469
749
|
scopeCaches.set(args.scope, scoped);
|
|
470
750
|
}
|
|
471
|
-
const
|
|
472
|
-
const entries = await memo(
|
|
751
|
+
const cache2 = scoped;
|
|
752
|
+
const entries = await memo(cache2.entries, key, cache2.signal, () => loadEntries(fetchFn, args.api, args.instanceId, args.recipient, cache2));
|
|
473
753
|
return entries.slice();
|
|
474
754
|
}
|
|
475
755
|
|
|
@@ -534,12 +814,12 @@ async function rpc(fn) {
|
|
|
534
814
|
function revertData(e) {
|
|
535
815
|
const hit = e.walk((x) => {
|
|
536
816
|
const d2 = x.data;
|
|
537
|
-
return
|
|
817
|
+
return isHex2(d2) && d2.length >= 10 || typeof d2 === "object" && d2 !== null && isHex2(d2.data);
|
|
538
818
|
});
|
|
539
819
|
const d = hit?.data;
|
|
540
|
-
if (
|
|
820
|
+
if (isHex2(d)) return d;
|
|
541
821
|
const inner = d?.data;
|
|
542
|
-
return
|
|
822
|
+
return isHex2(inner) ? inner : void 0;
|
|
543
823
|
}
|
|
544
824
|
function decodeRevert(e) {
|
|
545
825
|
if (!(e instanceof BaseError)) return null;
|
|
@@ -557,7 +837,7 @@ function decodeRevert(e) {
|
|
|
557
837
|
const named = e.walk((x) => ["ExecutionRevertedError", "InsufficientFundsError"].includes(x.name));
|
|
558
838
|
return named instanceof BaseError ? named.shortMessage : null;
|
|
559
839
|
}
|
|
560
|
-
var money = (value, decimals, symbol) => ({ value, decimals, symbol, formatted:
|
|
840
|
+
var money = (value, decimals, symbol) => ({ value, decimals, symbol, formatted: formatUnits2(value, decimals) });
|
|
561
841
|
function timestamp(v, field) {
|
|
562
842
|
if (v === 0n) return null;
|
|
563
843
|
if (v > BigInt(Number.MAX_SAFE_INTEGER)) throw new MintError("RPC_ERROR", `Claim ${field} does not fit a safe integer`);
|
|
@@ -574,14 +854,14 @@ function parseConfig(config) {
|
|
|
574
854
|
throw new MintError("INVALID_CONFIG", "instanceId must be a non-negative integer string.", { field: "instanceId" });
|
|
575
855
|
}
|
|
576
856
|
return {
|
|
577
|
-
creator:
|
|
857
|
+
creator: getAddress3(config.creatorContract),
|
|
578
858
|
instanceId: BigInt(config.instanceId),
|
|
579
|
-
extension: config.extension === void 0 ? void 0 :
|
|
859
|
+
extension: config.extension === void 0 ? void 0 : getAddress3(config.extension)
|
|
580
860
|
};
|
|
581
861
|
}
|
|
582
862
|
var notFound = (config) => new MintError("CLAIM_NOT_FOUND", `No Manifold claim ${config.instanceId} found on ${config.creatorContract}.`);
|
|
583
863
|
async function readClaim(client, entry, creator, instanceId, config) {
|
|
584
|
-
const address =
|
|
864
|
+
const address = getAddress3(entry.address);
|
|
585
865
|
const args = [creator, instanceId];
|
|
586
866
|
try {
|
|
587
867
|
switch (entry.abi) {
|
|
@@ -620,7 +900,7 @@ function base2(c) {
|
|
|
620
900
|
};
|
|
621
901
|
}
|
|
622
902
|
async function hasCode(client, address) {
|
|
623
|
-
const code2 = await rpc(() => client.getCode({ address:
|
|
903
|
+
const code2 = await rpc(() => client.getCode({ address: getAddress3(address) }));
|
|
624
904
|
return code2 !== void 0 && code2 !== "0x";
|
|
625
905
|
}
|
|
626
906
|
var unknownExtension = (address, why) => new MintError("UNKNOWN_EXTENSION", `Extension ${address} ${why}.`);
|
|
@@ -664,21 +944,21 @@ async function resolveExtension(ctx, config) {
|
|
|
664
944
|
}
|
|
665
945
|
async function loadDrop(ctx, config) {
|
|
666
946
|
const { entry, claim } = await resolveExtension(ctx, config);
|
|
667
|
-
if (claim.signingAddress !== null && claim.signingAddress !==
|
|
947
|
+
if (claim.signingAddress !== null && claim.signingAddress !== zeroAddress2) {
|
|
668
948
|
throw new MintError("SIGNATURE_CLAIM", "This claim mints with a signature (redemption code), which this button does not support.");
|
|
669
949
|
}
|
|
670
950
|
const { client } = ctx;
|
|
671
|
-
const extension =
|
|
951
|
+
const extension = getAddress3(entry.address);
|
|
672
952
|
const fee = await rpc(
|
|
673
953
|
() => client.readContract({ address: extension, abi: EXTENSION_COMMON_ABI, functionName: isAllowlist(claim) ? "MINT_FEE_MERKLE" : "MINT_FEE" })
|
|
674
954
|
);
|
|
675
955
|
const native = client.chain?.nativeCurrency ?? { symbol: "ETH", decimals: 18 };
|
|
676
956
|
let unitPrice;
|
|
677
957
|
let paymentToken = null;
|
|
678
|
-
if (claim.erc20 ===
|
|
958
|
+
if (claim.erc20 === zeroAddress2) {
|
|
679
959
|
unitPrice = money(claim.cost, native.decimals, native.symbol);
|
|
680
960
|
} else {
|
|
681
|
-
paymentToken =
|
|
961
|
+
paymentToken = getAddress3(claim.erc20);
|
|
682
962
|
const token = paymentToken;
|
|
683
963
|
const [symbol, decimals] = await rpc(
|
|
684
964
|
() => Promise.all([
|
|
@@ -709,9 +989,9 @@ async function loadWallet(ctx, config, args) {
|
|
|
709
989
|
const { client } = ctx;
|
|
710
990
|
const resolved = dropResolved.get(args.drop) ?? (await loadDrop(ctx, config)).resolved;
|
|
711
991
|
const { entry, claim } = resolved;
|
|
712
|
-
const extension =
|
|
713
|
-
const payer =
|
|
714
|
-
const recipient =
|
|
992
|
+
const extension = getAddress3(entry.address);
|
|
993
|
+
const payer = getAddress3(args.payer);
|
|
994
|
+
const recipient = getAddress3(args.recipient);
|
|
715
995
|
let walletMinted = null;
|
|
716
996
|
let walletMax = null;
|
|
717
997
|
let allowlistRemaining = null;
|
|
@@ -747,7 +1027,7 @@ async function loadWallet(ctx, config, args) {
|
|
|
747
1027
|
maxQuantity = Number(left);
|
|
748
1028
|
if (left === 0n) ineligible = { reason: "WALLET_LIMIT", message: `This wallet has already minted its limit of ${walletMax}.` };
|
|
749
1029
|
}
|
|
750
|
-
const token = claim.erc20 ===
|
|
1030
|
+
const token = claim.erc20 === zeroAddress2 ? null : getAddress3(claim.erc20);
|
|
751
1031
|
const [native, erc20, gasPrice] = await rpc(
|
|
752
1032
|
() => Promise.all([
|
|
753
1033
|
client.getBalance({ address: payer }),
|
|
@@ -775,8 +1055,8 @@ async function buildPlan(ctx, attempt, wallet) {
|
|
|
775
1055
|
throw new MintError("INVALID_CONFIG", `quantity must be an integer from 1 to ${MAX_MINT_COUNT}.`, { field: "quantity" });
|
|
776
1056
|
}
|
|
777
1057
|
const { creator, instanceId } = parseConfig(config);
|
|
778
|
-
const recipient =
|
|
779
|
-
const payer =
|
|
1058
|
+
const recipient = getAddress3(attempt.recipient);
|
|
1059
|
+
const payer = getAddress3(attempt.payer);
|
|
780
1060
|
let data = wallet.data;
|
|
781
1061
|
if (!isWalletData(data) || !sameClaim(data.config, config) || data.recipient !== recipient) {
|
|
782
1062
|
const drop = (await loadDrop(ctx, config)).drop;
|
|
@@ -784,7 +1064,7 @@ async function buildPlan(ctx, attempt, wallet) {
|
|
|
784
1064
|
}
|
|
785
1065
|
if (!isWalletData(data)) throw new MintError("RPC_ERROR", "Wallet read produced no plan data.");
|
|
786
1066
|
const { entry, claim, fee } = data.resolved;
|
|
787
|
-
const extension =
|
|
1067
|
+
const extension = getAddress3(entry.address);
|
|
788
1068
|
let mintIndices = [];
|
|
789
1069
|
let merkleProofs = [];
|
|
790
1070
|
if (isAllowlist(claim)) {
|
|
@@ -796,7 +1076,7 @@ async function buildPlan(ctx, attempt, wallet) {
|
|
|
796
1076
|
merkleProofs = picked.map((p) => p.proof);
|
|
797
1077
|
}
|
|
798
1078
|
const qty = BigInt(quantity);
|
|
799
|
-
const erc20 = claim.erc20 ===
|
|
1079
|
+
const erc20 = claim.erc20 === zeroAddress2 ? null : getAddress3(claim.erc20);
|
|
800
1080
|
const cost = claim.cost * qty;
|
|
801
1081
|
const value = fee * qty + (erc20 ? 0n : cost);
|
|
802
1082
|
let approve = null;
|
|
@@ -825,13 +1105,18 @@ async function buildPlan(ctx, attempt, wallet) {
|
|
|
825
1105
|
tokenContract: creator
|
|
826
1106
|
};
|
|
827
1107
|
}
|
|
1108
|
+
var RELAY_VERIFIED = ["V6", "V7"];
|
|
828
1109
|
var manifoldClaimAdapter = {
|
|
1110
|
+
supportsRelay: (drop) => {
|
|
1111
|
+
const resolved = dropResolved.get(drop);
|
|
1112
|
+
return resolved !== void 0 && RELAY_VERIFIED.includes(resolved.entry.generation) && drop.paymentToken === null;
|
|
1113
|
+
},
|
|
829
1114
|
readDrop: async (ctx, config) => (await loadDrop(ctx, config)).drop,
|
|
830
1115
|
readWallet: loadWallet,
|
|
831
1116
|
buildPlan,
|
|
832
1117
|
simulate: async (ctx, attempt, plan) => {
|
|
833
1118
|
try {
|
|
834
|
-
await ctx.client.call({ account:
|
|
1119
|
+
await ctx.client.call({ account: getAddress3(attempt.payer), to: plan.mint.to, data: plan.mint.data, value: plan.mint.value });
|
|
835
1120
|
} catch (e) {
|
|
836
1121
|
const text2 = decodeRevert(e);
|
|
837
1122
|
if (text2 === null) throw toRpcError(e);
|
|
@@ -848,15 +1133,15 @@ import {
|
|
|
848
1133
|
InsufficientFundsError,
|
|
849
1134
|
decodeErrorResult as decodeErrorResult2,
|
|
850
1135
|
encodeFunctionData as encodeFunctionData2,
|
|
851
|
-
formatUnits as
|
|
852
|
-
getAddress as
|
|
1136
|
+
formatUnits as formatUnits4,
|
|
1137
|
+
getAddress as getAddress4,
|
|
853
1138
|
isAddress as isAddress2,
|
|
854
1139
|
parseAbi as parseAbi2,
|
|
855
1140
|
parseUnits
|
|
856
1141
|
} from "viem";
|
|
857
1142
|
|
|
858
1143
|
// src/core/machine.ts
|
|
859
|
-
import { formatUnits as
|
|
1144
|
+
import { formatUnits as formatUnits3 } from "viem";
|
|
860
1145
|
var MINT_STATES = [
|
|
861
1146
|
"loading",
|
|
862
1147
|
"unsupported",
|
|
@@ -893,9 +1178,15 @@ function initialState(opts) {
|
|
|
893
1178
|
attempt: null,
|
|
894
1179
|
result: null,
|
|
895
1180
|
pendingRefresh: false,
|
|
896
|
-
now: 0
|
|
1181
|
+
now: 0,
|
|
1182
|
+
crossChain: false,
|
|
1183
|
+
routePending: false,
|
|
1184
|
+
route: null,
|
|
1185
|
+
short: null
|
|
897
1186
|
};
|
|
898
1187
|
}
|
|
1188
|
+
var NO_ROUTE = { crossChain: false, routePending: false, route: null, short: null };
|
|
1189
|
+
var PICK_ANOTHER = "Pick another network to pay from.";
|
|
899
1190
|
function toQuantity(q, max) {
|
|
900
1191
|
if (Number.isNaN(q)) return 1;
|
|
901
1192
|
return Math.max(1, Math.min(Math.floor(q), max));
|
|
@@ -920,11 +1211,17 @@ function derive(s, unsupported) {
|
|
|
920
1211
|
if (walletRead.ineligible !== null) {
|
|
921
1212
|
return { name: "ineligible", reason: walletRead.ineligible.reason, message: walletRead.ineligible.message };
|
|
922
1213
|
}
|
|
1214
|
+
if (s.routePending) return named("loading");
|
|
1215
|
+
if (s.route?.kind === "relay") return named("live");
|
|
1216
|
+
if (s.short !== null) {
|
|
1217
|
+
return { name: "insufficient-funds", reason: "INSUFFICIENT_FUNDS", message: `Not enough ${s.short.symbol} on ${s.short.name}. ${PICK_ANOTHER}` };
|
|
1218
|
+
}
|
|
923
1219
|
const total = selectTotal(s);
|
|
924
1220
|
if (total !== null) {
|
|
925
1221
|
const { native, erc20 } = total;
|
|
926
1222
|
if (native.value + walletRead.gasReserve > walletRead.balances.native) {
|
|
927
|
-
|
|
1223
|
+
const more = s.crossChain ? ` ${PICK_ANOTHER}` : "";
|
|
1224
|
+
return { name: "insufficient-funds", reason: "INSUFFICIENT_FUNDS", message: `Not enough ${native.symbol} to cover the mint.${more}` };
|
|
928
1225
|
}
|
|
929
1226
|
if (erc20 !== null && walletRead.balances.erc20 !== null && erc20.value > walletRead.balances.erc20) {
|
|
930
1227
|
return { name: "insufficient-funds", reason: "INSUFFICIENT_FUNDS", message: `Not enough ${erc20.symbol} to cover the mint.` };
|
|
@@ -953,7 +1250,7 @@ function reducer(state, event) {
|
|
|
953
1250
|
const next2 = { ...state, readGen: event.gen };
|
|
954
1251
|
return inFlight ? { ...next2, pendingRefresh: true } : settle(next2);
|
|
955
1252
|
}
|
|
956
|
-
const next = { ...state, readGen: event.gen, drop: null, walletRead: null, blocked: null, walletGen: state.walletGen + 1 };
|
|
1253
|
+
const next = { ...state, ...NO_ROUTE, readGen: event.gen, drop: null, walletRead: null, blocked: null, walletGen: state.walletGen + 1 };
|
|
957
1254
|
return inFlight ? { ...next, pendingRefresh: true } : settle(next, false);
|
|
958
1255
|
}
|
|
959
1256
|
case "DROP_LOADED": {
|
|
@@ -971,16 +1268,22 @@ function reducer(state, event) {
|
|
|
971
1268
|
case "READ_FAILED":
|
|
972
1269
|
return event.gen !== state.readGen ? state : failed(event.reason, event.message);
|
|
973
1270
|
case "ACCOUNT_CHANGED": {
|
|
974
|
-
const next = { ...state, account: event.account, walletRead: null, blocked: null, walletGen: state.walletGen + 1 };
|
|
1271
|
+
const next = { ...state, ...NO_ROUTE, account: event.account, walletRead: null, blocked: null, walletGen: state.walletGen + 1 };
|
|
975
1272
|
if (inFlight) return { ...next, pendingRefresh: true };
|
|
976
1273
|
if (state.name === "connecting") return event.account === null ? next : settle({ ...next, name: "loading" }, false);
|
|
977
1274
|
return settle(next);
|
|
978
1275
|
}
|
|
979
1276
|
case "WALLET_LOADED": {
|
|
980
1277
|
if (event.gen !== state.walletGen) return state;
|
|
981
|
-
const
|
|
1278
|
+
const route = event.routePending ? { routePending: state.route === null && state.short === null, route: state.route, short: state.short } : { routePending: false, route: null, short: null };
|
|
1279
|
+
const next = { ...state, ...route, crossChain: event.crossChain ?? false, walletRead: event.read, blocked: null };
|
|
982
1280
|
return inFlight ? { ...next, pendingRefresh: true } : settle(clampQuantity(next));
|
|
983
1281
|
}
|
|
1282
|
+
case "ROUTE_LOADED": {
|
|
1283
|
+
if (event.gen !== state.walletGen) return state;
|
|
1284
|
+
const next = { ...state, routePending: false, route: event.route, short: event.short };
|
|
1285
|
+
return inFlight ? { ...next, pendingRefresh: true } : settle(next);
|
|
1286
|
+
}
|
|
984
1287
|
case "WALLET_FAILED":
|
|
985
1288
|
return event.gen !== state.walletGen ? state : failed(event.reason, event.message);
|
|
986
1289
|
case "CONNECT_STARTED":
|
|
@@ -994,7 +1297,9 @@ function reducer(state, event) {
|
|
|
994
1297
|
return inFlight ? { ...next, pendingRefresh: true } : settle(next);
|
|
995
1298
|
}
|
|
996
1299
|
case "ATTEMPT_STARTED":
|
|
997
|
-
|
|
1300
|
+
if (state.attempt !== null) return state;
|
|
1301
|
+
if (event.resume === true) return IN_FLIGHT_STATES.includes(state.name) ? state : { ...state, attempt: event.attempt };
|
|
1302
|
+
return state.name === "live" ? { ...state, attempt: event.attempt } : state;
|
|
998
1303
|
case "SWITCHING_CHAIN":
|
|
999
1304
|
return step(state, event.attemptId, "switching-chain");
|
|
1000
1305
|
case "APPROVING":
|
|
@@ -1036,6 +1341,7 @@ function reducer(state, event) {
|
|
|
1036
1341
|
const next = {
|
|
1037
1342
|
...state,
|
|
1038
1343
|
readGen: event.gen,
|
|
1344
|
+
...NO_ROUTE,
|
|
1039
1345
|
walletRead: null,
|
|
1040
1346
|
blocked: null,
|
|
1041
1347
|
walletGen: state.walletGen + 1,
|
|
@@ -1081,7 +1387,7 @@ function selectTotal(state) {
|
|
|
1081
1387
|
return { native: money2(priceTotal + fee.value * q, price), erc20: null };
|
|
1082
1388
|
}
|
|
1083
1389
|
function money2(value, like) {
|
|
1084
|
-
return { value, decimals: like.decimals, symbol: like.symbol, formatted:
|
|
1390
|
+
return { value, decimals: like.decimals, symbol: like.symbol, formatted: formatUnits3(value, like.decimals) };
|
|
1085
1391
|
}
|
|
1086
1392
|
function canAct(state) {
|
|
1087
1393
|
return state.attempt === null && CLICKABLE_STATES.includes(state.name);
|
|
@@ -1318,7 +1624,7 @@ function varFor(token, vars) {
|
|
|
1318
1624
|
const key = token.slice(1, -1);
|
|
1319
1625
|
const v = vars[key];
|
|
1320
1626
|
if (v === void 0) throw new MintError("INVALID_CONFIG", `placeholder ${token} has no value here`);
|
|
1321
|
-
return typeof v === "string" ?
|
|
1627
|
+
return typeof v === "string" ? getAddress4(v) : v;
|
|
1322
1628
|
}
|
|
1323
1629
|
function fill(t, p, vars) {
|
|
1324
1630
|
const arr = ARRAY_TYPE.exec(p.type);
|
|
@@ -1407,7 +1713,7 @@ function nativeMeta(ctx) {
|
|
|
1407
1713
|
const n = ctx.client.chain?.nativeCurrency;
|
|
1408
1714
|
return { decimals: n?.decimals ?? 18, symbol: n?.symbol ?? "ETH" };
|
|
1409
1715
|
}
|
|
1410
|
-
var erc20Of = (config) => config.price.currency === "native" ? null :
|
|
1716
|
+
var erc20Of = (config) => config.price.currency === "native" ? null : getAddress4(config.price.currency.address);
|
|
1411
1717
|
async function currencyMeta(ctx, config) {
|
|
1412
1718
|
const token = erc20Of(config);
|
|
1413
1719
|
if (!token) return { token, ...nativeMeta(ctx) };
|
|
@@ -1439,7 +1745,7 @@ var money3 = (value, decimals, symbol) => ({
|
|
|
1439
1745
|
value,
|
|
1440
1746
|
decimals,
|
|
1441
1747
|
symbol,
|
|
1442
|
-
formatted:
|
|
1748
|
+
formatted: formatUnits4(value, decimals)
|
|
1443
1749
|
});
|
|
1444
1750
|
function revertData2(e) {
|
|
1445
1751
|
for (let cur = e, depth = 0; cur && depth < 12; depth++) {
|
|
@@ -1552,7 +1858,7 @@ async function readWallet(ctx, config, { payer, recipient, quantity, drop }) {
|
|
|
1552
1858
|
if (remaining === 0n) {
|
|
1553
1859
|
ineligible = { reason: "WALLET_LIMIT", message: `This wallet has already minted its limit of ${walletMax}.` };
|
|
1554
1860
|
} else if (drop.paymentToken === null && native >= totalPrice) {
|
|
1555
|
-
const call = { payer, to:
|
|
1861
|
+
const call = { payer, to: getAddress4(config.address), data: encodeMint(config, vars), value: mintValue(config, null, totalPrice) };
|
|
1556
1862
|
try {
|
|
1557
1863
|
await simulateMint(ctx, config, call);
|
|
1558
1864
|
gas = await io(ctx, () => ctx.client.estimateGas({ account: payer, to: call.to, data: call.data, value: call.value })).catch((e) => {
|
|
@@ -1583,7 +1889,7 @@ async function buildPlan2(ctx, attempt, wallet) {
|
|
|
1583
1889
|
const qty = BigInt(attempt.quantity);
|
|
1584
1890
|
const unitPrice = await resolvePrice(ctx, config);
|
|
1585
1891
|
const totalPrice = totalFor(config.price.per ?? "unit", unitPrice, qty);
|
|
1586
|
-
const to =
|
|
1892
|
+
const to = getAddress4(config.address);
|
|
1587
1893
|
const token = erc20Of(config);
|
|
1588
1894
|
const mint = {
|
|
1589
1895
|
to,
|
|
@@ -1605,7 +1911,7 @@ async function buildPlan2(ctx, attempt, wallet) {
|
|
|
1605
1911
|
};
|
|
1606
1912
|
}
|
|
1607
1913
|
}
|
|
1608
|
-
return { approve, mint, tokenContract:
|
|
1914
|
+
return { approve, mint, tokenContract: getAddress4(attempt.tokenContract) };
|
|
1609
1915
|
}
|
|
1610
1916
|
async function simulate(ctx, attempt, plan) {
|
|
1611
1917
|
throwIfAborted(ctx);
|
|
@@ -1614,28 +1920,28 @@ async function simulate(ctx, attempt, plan) {
|
|
|
1614
1920
|
var contractAdapter = { readDrop, readWallet, buildPlan: buildPlan2, simulate };
|
|
1615
1921
|
|
|
1616
1922
|
// src/core/adapters/receipt.ts
|
|
1617
|
-
import { decodeAbiParameters, getAddress as
|
|
1923
|
+
import { decodeAbiParameters, getAddress as getAddress5, zeroAddress as zeroAddress3 } from "viem";
|
|
1618
1924
|
var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
1619
1925
|
var TRANSFER_SINGLE_TOPIC = "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62";
|
|
1620
1926
|
var TRANSFER_BATCH_TOPIC = "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb";
|
|
1621
|
-
var topicAddress = (t) => t && t.length === 66 ?
|
|
1927
|
+
var topicAddress = (t) => t && t.length === 66 ? getAddress5(`0x${t.slice(26)}`) : null;
|
|
1622
1928
|
function parseMintReceipt(logs, { tokenContract, recipient }) {
|
|
1623
|
-
const contract =
|
|
1624
|
-
const to =
|
|
1929
|
+
const contract = getAddress5(tokenContract);
|
|
1930
|
+
const to = getAddress5(recipient);
|
|
1625
1931
|
const out = /* @__PURE__ */ new Map();
|
|
1626
1932
|
const add = (id, amount) => out.set(id, (out.get(id) ?? 0n) + amount);
|
|
1627
1933
|
for (const log of logs) {
|
|
1628
|
-
if (
|
|
1934
|
+
if (getAddress5(log.address) !== contract) continue;
|
|
1629
1935
|
const [t0, t1, t2, t3] = log.topics;
|
|
1630
1936
|
if (t0 === TRANSFER_TOPIC && log.topics.length === 4) {
|
|
1631
|
-
if (topicAddress(t1) !==
|
|
1937
|
+
if (topicAddress(t1) !== zeroAddress3 || topicAddress(t2) !== to || !t3) continue;
|
|
1632
1938
|
add(BigInt(t3), 1n);
|
|
1633
1939
|
} else if (t0 === TRANSFER_SINGLE_TOPIC && log.topics.length === 4) {
|
|
1634
|
-
if (topicAddress(t2) !==
|
|
1940
|
+
if (topicAddress(t2) !== zeroAddress3 || topicAddress(t3) !== to) continue;
|
|
1635
1941
|
const [id, value] = decodeAbiParameters([{ type: "uint256" }, { type: "uint256" }], log.data);
|
|
1636
1942
|
add(id, value);
|
|
1637
1943
|
} else if (t0 === TRANSFER_BATCH_TOPIC && log.topics.length === 4) {
|
|
1638
|
-
if (topicAddress(t2) !==
|
|
1944
|
+
if (topicAddress(t2) !== zeroAddress3 || topicAddress(t3) !== to) continue;
|
|
1639
1945
|
const [ids, values] = decodeAbiParameters([{ type: "uint256[]" }, { type: "uint256[]" }], log.data);
|
|
1640
1946
|
if (ids.length !== values.length) continue;
|
|
1641
1947
|
ids.forEach((id, i) => add(id, values[i]));
|
|
@@ -1683,6 +1989,7 @@ function friendlyErrorMessage(error) {
|
|
|
1683
1989
|
if (isMintError(error)) {
|
|
1684
1990
|
if (error.reason === "INSUFFICIENT_FUNDS") return fundsMessage(collect(error.cause)) ?? error.message;
|
|
1685
1991
|
if (error.reason !== "RPC_ERROR" && error.reason !== "SIMULATION_REVERTED") return error.message;
|
|
1992
|
+
if (error.reason === "SIMULATION_REVERTED" && error.domain === "relay") return error.message;
|
|
1686
1993
|
if (error.reason === "SIMULATION_REVERTED" && (error.message === "The token refused the approval." || error.message === "The drop is no longer available for this quantity.")) return error.message;
|
|
1687
1994
|
}
|
|
1688
1995
|
const errors = collect(error);
|
|
@@ -1744,6 +2051,25 @@ function adapterFor(config) {
|
|
|
1744
2051
|
return config.type === "manifold-claim" ? manifoldClaimAdapter : contractAdapter;
|
|
1745
2052
|
}
|
|
1746
2053
|
var bridgeEnabled = (config) => config.type === "manifold-claim";
|
|
2054
|
+
function payChainsOf(config, option) {
|
|
2055
|
+
if (config.type !== "manifold-claim" || option === false) return [];
|
|
2056
|
+
const all = payChainsFor(config.chainId);
|
|
2057
|
+
const narrowed = typeof option === "object" && option.chains ? all.filter((id) => id === config.chainId || option.chains.includes(id)) : all;
|
|
2058
|
+
return narrowed.length > 1 ? narrowed : [];
|
|
2059
|
+
}
|
|
2060
|
+
function routeChainOf(payChains, accountChainId, payChainId) {
|
|
2061
|
+
if (payChainId !== void 0 && payChains.includes(payChainId)) return payChainId;
|
|
2062
|
+
return accountChainId;
|
|
2063
|
+
}
|
|
2064
|
+
var ROUTE_DEBOUNCE_MS = 500;
|
|
2065
|
+
var symbolOf = (chainId) => {
|
|
2066
|
+
try {
|
|
2067
|
+
return getChain(chainId).nativeCurrency.symbol;
|
|
2068
|
+
} catch {
|
|
2069
|
+
return "ETH";
|
|
2070
|
+
}
|
|
2071
|
+
};
|
|
2072
|
+
var shortOf = (chainId) => ({ chainId, symbol: symbolOf(chainId), name: chainName(chainId) });
|
|
1747
2073
|
function tokenContractOf(config) {
|
|
1748
2074
|
return config.type === "manifold-claim" ? config.creatorContract : config.tokenContract ?? config.address;
|
|
1749
2075
|
}
|
|
@@ -1801,6 +2127,8 @@ function useMint(options) {
|
|
|
1801
2127
|
const widgetFailRef = useRef(null);
|
|
1802
2128
|
const widgetLoadRef = useRef(null);
|
|
1803
2129
|
const pushAccountRef = useRef(null);
|
|
2130
|
+
const autoResumedRef = useRef(/* @__PURE__ */ new Set());
|
|
2131
|
+
const [crossChainInfo, setCrossChainInfo] = useState(null);
|
|
1804
2132
|
useEffect(() => {
|
|
1805
2133
|
stateRef.current = machine;
|
|
1806
2134
|
propsRef.current = options;
|
|
@@ -1816,6 +2144,7 @@ function useMint(options) {
|
|
|
1816
2144
|
if (!handler || !mounted.current) return;
|
|
1817
2145
|
const cfg = attemptRef.current?.config ?? propsRef.current.config;
|
|
1818
2146
|
const s = stateRef.current;
|
|
2147
|
+
const originChainId = attemptRef.current?.originChainId ?? (s.route?.kind === "relay" ? s.route.originChainId : s.short?.chainId ?? cfg.chainId);
|
|
1819
2148
|
const payload = {
|
|
1820
2149
|
type,
|
|
1821
2150
|
state: s.name,
|
|
@@ -1823,6 +2152,8 @@ function useMint(options) {
|
|
|
1823
2152
|
chainId: cfg.chainId,
|
|
1824
2153
|
quantity: s.quantity,
|
|
1825
2154
|
config: { type: cfg.type, chainId: cfg.chainId, address: eventAddressOf(cfg) },
|
|
2155
|
+
route: originChainId === cfg.chainId ? "direct" : "relay",
|
|
2156
|
+
originChainId,
|
|
1826
2157
|
...over
|
|
1827
2158
|
};
|
|
1828
2159
|
try {
|
|
@@ -1942,7 +2273,7 @@ function useMint(options) {
|
|
|
1942
2273
|
if (cancelled) throw new MintError("USER_REJECTED", "The transaction was cancelled or replaced with a different transaction.");
|
|
1943
2274
|
if (await walletFor().getChainId() !== attempt.chainId) throw e;
|
|
1944
2275
|
assertActive();
|
|
1945
|
-
receipt2 = await walletFor().extend(
|
|
2276
|
+
receipt2 = await walletFor().extend(publicActions2).waitForTransactionReceipt(params);
|
|
1946
2277
|
}
|
|
1947
2278
|
assertActive();
|
|
1948
2279
|
if (cancelled) throw new MintError("USER_REJECTED", "The transaction was cancelled or replaced with a different transaction.");
|
|
@@ -2022,6 +2353,156 @@ function useMint(options) {
|
|
|
2022
2353
|
},
|
|
2023
2354
|
[clientFor, ctxFor, dispatch, emit]
|
|
2024
2355
|
);
|
|
2356
|
+
const finishRelay = useCallback(
|
|
2357
|
+
async (attempt, out, life) => {
|
|
2358
|
+
const cfg = attempt.config;
|
|
2359
|
+
const client = clientFor(life, cfg);
|
|
2360
|
+
const assertActive = () => {
|
|
2361
|
+
if (!mounted.current || life.controller.signal.aborted || lifeRef.current !== life || attemptRef.current !== attempt) {
|
|
2362
|
+
throw new DOMException("Mint attempt cancelled", "AbortError");
|
|
2363
|
+
}
|
|
2364
|
+
};
|
|
2365
|
+
const outcome = await pollUntilTerminal({ testnet: out.testnet, requestId: out.requestId, signal: life.controller.signal, assertActive });
|
|
2366
|
+
assertActive();
|
|
2367
|
+
const where = chainName(out.originChainId);
|
|
2368
|
+
if (outcome.kind === "timeout") {
|
|
2369
|
+
throw new MintError("BRIDGE_TIMEOUT", "The bridge is still processing. Your mint may still complete.");
|
|
2370
|
+
}
|
|
2371
|
+
const key = outstandingKey(attempt.config, attempt.payer);
|
|
2372
|
+
const { status } = outcome;
|
|
2373
|
+
if (status.status !== "success") clearOutstanding(key);
|
|
2374
|
+
if (status.status === "refund") {
|
|
2375
|
+
throw new MintError("BRIDGE_REFUNDED", `Relay could not complete the mint and is refunding your payment. Relay request ${out.requestId}.`);
|
|
2376
|
+
}
|
|
2377
|
+
if (status.status === "failure") {
|
|
2378
|
+
const why = status.failReason ? ` (${status.failReason})` : "";
|
|
2379
|
+
const noRefund = status.refundFailReason ? ` No automatic refund was sent. Contact support with Relay request ${out.requestId}.` : "";
|
|
2380
|
+
throw new MintError("BRIDGE_FAILED", `Relay could not complete the mint paid on ${where}${why}.${noRefund}`);
|
|
2381
|
+
}
|
|
2382
|
+
const fillHash = status.txHashes[0] ?? null;
|
|
2383
|
+
let tokens = [];
|
|
2384
|
+
if (fillHash) {
|
|
2385
|
+
try {
|
|
2386
|
+
const receipt = await client.waitForTransactionReceipt({ hash: fillHash, confirmations: 1, timeout: 6e4 });
|
|
2387
|
+
if (receiptOk(receipt.status)) {
|
|
2388
|
+
tokens = parseMintReceipt(receipt.logs ?? [], { tokenContract: attempt.tokenContract, recipient: attempt.recipient });
|
|
2389
|
+
}
|
|
2390
|
+
} catch {
|
|
2391
|
+
}
|
|
2392
|
+
assertActive();
|
|
2393
|
+
}
|
|
2394
|
+
const txHash = fillHash ?? out.originTxHash;
|
|
2395
|
+
const result = {
|
|
2396
|
+
txHash,
|
|
2397
|
+
explorerUrl: explorerUrlFor(fillHash ? cfg.chainId : out.originChainId, txHash),
|
|
2398
|
+
tokens,
|
|
2399
|
+
relay: { requestId: out.requestId, originTxHash: out.originTxHash, originChainId: out.originChainId }
|
|
2400
|
+
};
|
|
2401
|
+
clearOutstanding(key);
|
|
2402
|
+
dispatch({ type: "ATTEMPT_SUCCEEDED", attemptId: attempt.attemptId, result });
|
|
2403
|
+
emit("success", { state: "success", reason: null, txHash, quantity: attempt.quantity, chainId: cfg.chainId });
|
|
2404
|
+
},
|
|
2405
|
+
[clientFor, dispatch, emit]
|
|
2406
|
+
);
|
|
2407
|
+
const executeRelayAttempt = useCallback(
|
|
2408
|
+
async (attempt, walletRead, life, account2) => {
|
|
2409
|
+
const cfg = attempt.config;
|
|
2410
|
+
const adapter = adapterFor(cfg);
|
|
2411
|
+
const ctx = ctxFor(life, cfg);
|
|
2412
|
+
const testnet = isRelayTestnet(cfg.chainId);
|
|
2413
|
+
const origin = attempt.originChainId;
|
|
2414
|
+
const assertPayer = () => {
|
|
2415
|
+
if (!mounted.current || life.controller.signal.aborted || lifeRef.current !== life || attemptRef.current !== attempt) {
|
|
2416
|
+
throw new DOMException("Mint attempt cancelled", "AbortError");
|
|
2417
|
+
}
|
|
2418
|
+
const live = getAccount();
|
|
2419
|
+
if (!live || live.address.toLowerCase() !== attempt.payer.toLowerCase()) {
|
|
2420
|
+
throw new MintError("USER_REJECTED", "The connected wallet changed before the transaction was sent.");
|
|
2421
|
+
}
|
|
2422
|
+
};
|
|
2423
|
+
assertPayer();
|
|
2424
|
+
if (account2.chainId !== origin) {
|
|
2425
|
+
dispatch({ type: "SWITCHING_CHAIN", attemptId: attempt.attemptId });
|
|
2426
|
+
await switchToChain(origin);
|
|
2427
|
+
}
|
|
2428
|
+
assertPayer();
|
|
2429
|
+
const plan = await adapter.buildPlan(ctx, attempt, walletRead);
|
|
2430
|
+
if (plan.approve) throw new MintError("BRIDGE_UNAVAILABLE", "Cross-chain mint supports native-priced drops only.");
|
|
2431
|
+
assertPayer();
|
|
2432
|
+
const quote = await getQuote({
|
|
2433
|
+
testnet,
|
|
2434
|
+
payer: attempt.payer,
|
|
2435
|
+
recipient: attempt.recipient,
|
|
2436
|
+
originChainId: origin,
|
|
2437
|
+
destinationChainId: cfg.chainId,
|
|
2438
|
+
tx: plan.mint,
|
|
2439
|
+
fresh: true,
|
|
2440
|
+
signal: life.controller.signal
|
|
2441
|
+
});
|
|
2442
|
+
assertPayer();
|
|
2443
|
+
const balance = await getConnectedBalance(attempt.payer, origin);
|
|
2444
|
+
if (balance < quote.amountIn.value + quote.originGas.value) {
|
|
2445
|
+
const s = shortOf(origin);
|
|
2446
|
+
throw new MintError("INSUFFICIENT_FUNDS", `Not enough ${s.symbol} on ${s.name}. Pick another network to pay from.`);
|
|
2447
|
+
}
|
|
2448
|
+
assertPayer();
|
|
2449
|
+
const item = quote.items[0];
|
|
2450
|
+
const key = outstandingKey(cfg, attempt.payer);
|
|
2451
|
+
const wallet2 = getWalletClient(origin);
|
|
2452
|
+
assertPayer();
|
|
2453
|
+
if (!claimDeposit(key)) {
|
|
2454
|
+
throw new MintError("BRIDGE_UNAVAILABLE", "A cross-chain payment for this drop is already in progress. Try again once it finishes.");
|
|
2455
|
+
}
|
|
2456
|
+
let out;
|
|
2457
|
+
try {
|
|
2458
|
+
dispatch({ type: "MINT_PROMPTED", attemptId: attempt.attemptId });
|
|
2459
|
+
const send = wallet2;
|
|
2460
|
+
const originTxHash2 = await send.sendTransaction({
|
|
2461
|
+
account: attempt.payer,
|
|
2462
|
+
to: item.to,
|
|
2463
|
+
data: item.data,
|
|
2464
|
+
value: item.value,
|
|
2465
|
+
...item.gas !== null && item.gas > 0n ? { gas: item.gas } : {},
|
|
2466
|
+
chain: getChain(origin)
|
|
2467
|
+
});
|
|
2468
|
+
out = { requestId: quote.requestId, testnet, originChainId: origin, originTxHash: originTxHash2, attempt };
|
|
2469
|
+
setOutstanding(key, out);
|
|
2470
|
+
} finally {
|
|
2471
|
+
releaseDeposit(key);
|
|
2472
|
+
}
|
|
2473
|
+
const { originTxHash } = out;
|
|
2474
|
+
autoResumedRef.current.add(key);
|
|
2475
|
+
dispatch({ type: "TX_SUBMITTED", attemptId: attempt.attemptId });
|
|
2476
|
+
emit("tx-submitted", { state: "confirming", reason: null, txHash: originTxHash, quantity: attempt.quantity, chainId: cfg.chainId });
|
|
2477
|
+
const deposit = await waitForDeposit(origin, originTxHash);
|
|
2478
|
+
if (deposit === "reverted") {
|
|
2479
|
+
clearOutstanding(key);
|
|
2480
|
+
throw new MintError("TX_REVERTED", `The payment transaction reverted (${originTxHash}).`);
|
|
2481
|
+
}
|
|
2482
|
+
await finishRelay(attempt, out, life);
|
|
2483
|
+
},
|
|
2484
|
+
[ctxFor, dispatch, emit, finishRelay]
|
|
2485
|
+
);
|
|
2486
|
+
const resumeOutstanding = useCallback(
|
|
2487
|
+
async (out, life) => {
|
|
2488
|
+
const attempt = Object.freeze({ ...out.attempt, attemptId: ++attemptIdRef.current });
|
|
2489
|
+
attemptRef.current = attempt;
|
|
2490
|
+
dispatch({ type: "ATTEMPT_STARTED", attempt, resume: true });
|
|
2491
|
+
dispatch({ type: "TX_SUBMITTED", attemptId: attempt.attemptId });
|
|
2492
|
+
emit("attempt", { state: "confirming", reason: null, quantity: attempt.quantity, chainId: attempt.chainId });
|
|
2493
|
+
try {
|
|
2494
|
+
await finishRelay(attempt, out, life);
|
|
2495
|
+
} catch (e) {
|
|
2496
|
+
if (!mounted.current || life.controller.signal.aborted || lifeRef.current !== life) return;
|
|
2497
|
+
const { reason, message } = toReason(e);
|
|
2498
|
+
dispatch({ type: "ATTEMPT_FAILED", attemptId: attempt.attemptId, reason, message });
|
|
2499
|
+
emit("error", { quantity: attempt.quantity, chainId: attempt.chainId });
|
|
2500
|
+
} finally {
|
|
2501
|
+
attemptRef.current = null;
|
|
2502
|
+
}
|
|
2503
|
+
},
|
|
2504
|
+
[dispatch, emit, finishRelay]
|
|
2505
|
+
);
|
|
2025
2506
|
const runMint = useCallback(async () => {
|
|
2026
2507
|
if (!mounted.current) return;
|
|
2027
2508
|
if (attemptRef.current !== null) return;
|
|
@@ -2058,6 +2539,12 @@ function useMint(options) {
|
|
|
2058
2539
|
}
|
|
2059
2540
|
return;
|
|
2060
2541
|
}
|
|
2542
|
+
const payer = s.account?.address;
|
|
2543
|
+
const out = payer ? getOutstanding(outstandingKey(propsRef.current.config, payer)) : null;
|
|
2544
|
+
if (out && lifeRef.current) {
|
|
2545
|
+
await resumeOutstanding(out, lifeRef.current);
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2061
2548
|
if (s.name === "success" || s.name === "error") {
|
|
2062
2549
|
const life2 = lifeRef.current;
|
|
2063
2550
|
const gen = ++genRef.current;
|
|
@@ -2073,6 +2560,7 @@ function useMint(options) {
|
|
|
2073
2560
|
const life = lifeRef.current;
|
|
2074
2561
|
if (!account2 || !walletRead || !life) return;
|
|
2075
2562
|
const cfg = propsRef.current.config;
|
|
2563
|
+
const relayRoute = s.route?.kind === "relay" ? s.route : null;
|
|
2076
2564
|
const attempt = Object.freeze({
|
|
2077
2565
|
attemptId: ++attemptIdRef.current,
|
|
2078
2566
|
config: structuredClone(cfg),
|
|
@@ -2080,13 +2568,15 @@ function useMint(options) {
|
|
|
2080
2568
|
recipient: propsRef.current.recipient ?? account2.address,
|
|
2081
2569
|
quantity: s.quantity,
|
|
2082
2570
|
chainId: cfg.chainId,
|
|
2083
|
-
tokenContract: tokenContractOf(cfg)
|
|
2571
|
+
tokenContract: tokenContractOf(cfg),
|
|
2572
|
+
originChainId: relayRoute ? relayRoute.originChainId : cfg.chainId
|
|
2084
2573
|
});
|
|
2085
2574
|
attemptRef.current = attempt;
|
|
2086
2575
|
dispatch({ type: "ATTEMPT_STARTED", attempt });
|
|
2087
2576
|
emit("attempt", { state: "live", reason: null, quantity: attempt.quantity, chainId: attempt.chainId });
|
|
2088
2577
|
try {
|
|
2089
|
-
await
|
|
2578
|
+
if (relayRoute) await executeRelayAttempt(attempt, walletRead, life, account2);
|
|
2579
|
+
else await executeAttempt(attempt, walletRead, life, account2);
|
|
2090
2580
|
} catch (e) {
|
|
2091
2581
|
if (!mounted.current || life.controller.signal.aborted || lifeRef.current !== life) return;
|
|
2092
2582
|
const { reason, message } = toReason(e);
|
|
@@ -2095,7 +2585,7 @@ function useMint(options) {
|
|
|
2095
2585
|
} finally {
|
|
2096
2586
|
attemptRef.current = null;
|
|
2097
2587
|
}
|
|
2098
|
-
}, [dispatch, emit, readDrop2, executeAttempt, refreshNow]);
|
|
2588
|
+
}, [dispatch, emit, readDrop2, executeAttempt, executeRelayAttempt, resumeOutstanding, refreshNow]);
|
|
2099
2589
|
const mint = useCallback(() => {
|
|
2100
2590
|
void runMint();
|
|
2101
2591
|
}, [runMint]);
|
|
@@ -2130,9 +2620,10 @@ function useMint(options) {
|
|
|
2130
2620
|
pushAccountRef.current?.(getAccount());
|
|
2131
2621
|
}, [recipient, machine.attempt, isBusy]);
|
|
2132
2622
|
const projectId = connect?.walletConnectProjectId;
|
|
2623
|
+
const widgetChains = [config.chainId, ...payChainsOf(config, options.crossChain).filter((id) => id !== config.chainId)].join(",");
|
|
2133
2624
|
useEffect(() => {
|
|
2134
2625
|
let alive = true;
|
|
2135
|
-
const loading = loadConnectWidget({ chainIds:
|
|
2626
|
+
const loading = loadConnectWidget({ chainIds: widgetChains.split(",").map(Number), ...projectId ? { walletConnectProjectId: projectId } : {} });
|
|
2136
2627
|
widgetLoadRef.current = loading;
|
|
2137
2628
|
loading.then(
|
|
2138
2629
|
() => {
|
|
@@ -2147,7 +2638,7 @@ function useMint(options) {
|
|
|
2147
2638
|
return () => {
|
|
2148
2639
|
alive = false;
|
|
2149
2640
|
};
|
|
2150
|
-
}, [
|
|
2641
|
+
}, [widgetChains, projectId, widgetRetry]);
|
|
2151
2642
|
useEffect(() => {
|
|
2152
2643
|
if (!mounted.current) return;
|
|
2153
2644
|
if (isBusy()) return;
|
|
@@ -2164,6 +2655,22 @@ function useMint(options) {
|
|
|
2164
2655
|
void refreshNow();
|
|
2165
2656
|
}
|
|
2166
2657
|
});
|
|
2658
|
+
const resumeKey = machine.account ? outstandingKey(config, machine.account.address) : null;
|
|
2659
|
+
useEffect(() => {
|
|
2660
|
+
if (!resumeKey || autoResumedRef.current.has(resumeKey) || attemptRef.current !== null || isBusy()) return;
|
|
2661
|
+
const out = getOutstanding(resumeKey);
|
|
2662
|
+
const life = lifeRef.current;
|
|
2663
|
+
if (!out || !life) return;
|
|
2664
|
+
autoResumedRef.current.add(resumeKey);
|
|
2665
|
+
void resumeOutstanding(out, life);
|
|
2666
|
+
}, [resumeKey, machine.attempt, isBusy, resumeOutstanding]);
|
|
2667
|
+
const routeOptionsKey = `${String(options.payChainId ?? "")}|${JSON.stringify(options.crossChain ?? null)}`;
|
|
2668
|
+
const previousRouteOptions = useRef(routeOptionsKey);
|
|
2669
|
+
useEffect(() => {
|
|
2670
|
+
if (previousRouteOptions.current === routeOptionsKey || isBusy()) return;
|
|
2671
|
+
previousRouteOptions.current = routeOptionsKey;
|
|
2672
|
+
if (stateRef.current.account) dispatch({ type: "ACCOUNT_CHANGED", account: stateRef.current.account });
|
|
2673
|
+
}, [routeOptionsKey, machine.attempt, dispatch, isBusy]);
|
|
2167
2674
|
const previousQuantity = useRef(machine.quantity);
|
|
2168
2675
|
useEffect(() => {
|
|
2169
2676
|
if (previousQuantity.current === machine.quantity || isBusy()) return;
|
|
@@ -2196,7 +2703,61 @@ function useMint(options) {
|
|
|
2196
2703
|
});
|
|
2197
2704
|
if (!alive || life.controller.signal.aborted || lifeRef.current !== life || life.gen !== readGen) return;
|
|
2198
2705
|
const ineligible = read.ineligible?.reason === "SIMULATION_REVERTED" ? { ...read.ineligible, message: friendlyErrorMessage(new MintError("SIMULATION_REVERTED", read.ineligible.message)) } : read.ineligible;
|
|
2199
|
-
|
|
2706
|
+
const fullRead = { ...read, ineligible };
|
|
2707
|
+
const payChains = payChainsOf(cfg, propsRef.current.crossChain);
|
|
2708
|
+
const crossChain = payChains.length > 0 && ineligible === null && (adapter.supportsRelay?.(drop) ?? false);
|
|
2709
|
+
const routeChain = routeChainOf(payChains, account.chainId, propsRef.current.payChainId);
|
|
2710
|
+
const routePending = crossChain && routeChain !== cfg.chainId && payChains.includes(routeChain);
|
|
2711
|
+
dispatch({ type: "WALLET_LOADED", gen: walletGen, read: fullRead, crossChain, routePending });
|
|
2712
|
+
if (!crossChain) {
|
|
2713
|
+
setCrossChainInfo(null);
|
|
2714
|
+
return;
|
|
2715
|
+
}
|
|
2716
|
+
const recipientAddr = propsRef.current.recipient ?? account.address;
|
|
2717
|
+
const draft = {
|
|
2718
|
+
attemptId: 0,
|
|
2719
|
+
config: cfg,
|
|
2720
|
+
payer: account.address,
|
|
2721
|
+
recipient: recipientAddr,
|
|
2722
|
+
quantity: stateRef.current.quantity,
|
|
2723
|
+
chainId: cfg.chainId,
|
|
2724
|
+
tokenContract: tokenContractOf(cfg),
|
|
2725
|
+
originChainId: routeChain
|
|
2726
|
+
};
|
|
2727
|
+
const testnet = isRelayTestnet(cfg.chainId);
|
|
2728
|
+
let tx;
|
|
2729
|
+
try {
|
|
2730
|
+
tx = (await adapter.buildPlan(ctxFor(life, cfg), draft, fullRead)).mint;
|
|
2731
|
+
} catch {
|
|
2732
|
+
if (!alive) return;
|
|
2733
|
+
setCrossChainInfo(null);
|
|
2734
|
+
if (routePending) dispatch({ type: "ROUTE_LOADED", gen: walletGen, route: { kind: "direct", chainId: cfg.chainId }, short: null });
|
|
2735
|
+
return;
|
|
2736
|
+
}
|
|
2737
|
+
if (!alive || life.controller.signal.aborted || lifeRef.current !== life) return;
|
|
2738
|
+
setCrossChainInfo((prev) => {
|
|
2739
|
+
const same = prev && prev.info.dropChainId === cfg.chainId && prev.info.testnet === testnet && prev.info.tx.to === tx.to && prev.info.tx.data === tx.data && prev.info.tx.value === tx.value && prev.info.payer === account.address && prev.info.recipient === recipientAddr && prev.info.payChains.join() === payChains.join();
|
|
2740
|
+
return { gen: walletGen, info: same ? prev.info : { dropChainId: cfg.chainId, payChains, tx, payer: account.address, recipient: recipientAddr, testnet } };
|
|
2741
|
+
});
|
|
2742
|
+
if (!routePending) return;
|
|
2743
|
+
await new Promise((r) => setTimeout(r, ROUTE_DEBOUNCE_MS));
|
|
2744
|
+
if (!alive || life.controller.signal.aborted || lifeRef.current !== life) return;
|
|
2745
|
+
let route = { kind: "direct", chainId: cfg.chainId };
|
|
2746
|
+
let short = null;
|
|
2747
|
+
try {
|
|
2748
|
+
const quote = await getQuote({ testnet, payer: account.address, recipient: recipientAddr, originChainId: routeChain, destinationChainId: cfg.chainId, tx, signal: life.controller.signal });
|
|
2749
|
+
const relay = { kind: "relay", originChainId: routeChain, amountIn: quote.amountIn, relayFee: quote.relayFee, originGas: quote.originGas, quotedAt: quote.quotedAt };
|
|
2750
|
+
if (routeChain !== account.chainId) {
|
|
2751
|
+
route = relay;
|
|
2752
|
+
} else {
|
|
2753
|
+
const balance = await getConnectedBalance(account.address, routeChain);
|
|
2754
|
+
if (balance >= quote.amountIn.value + quote.originGas.value) route = relay;
|
|
2755
|
+
else short = shortOf(routeChain);
|
|
2756
|
+
}
|
|
2757
|
+
} catch {
|
|
2758
|
+
}
|
|
2759
|
+
if (!alive || life.controller.signal.aborted || lifeRef.current !== life) return;
|
|
2760
|
+
dispatch({ type: "ROUTE_LOADED", gen: walletGen, route: short ? null : route, short });
|
|
2200
2761
|
} catch (e) {
|
|
2201
2762
|
if (!alive || life.controller.signal.aborted || lifeRef.current !== life || life.gen !== readGen) return;
|
|
2202
2763
|
dispatch({ type: "WALLET_FAILED", gen: walletGen, ...errorReasonOf(e) });
|
|
@@ -2296,12 +2857,137 @@ function useMint(options) {
|
|
|
2296
2857
|
result: machine.result,
|
|
2297
2858
|
refresh,
|
|
2298
2859
|
// An attempt claimed by a click is not clickable, even while the reducer still reads 'live'.
|
|
2299
|
-
canAct: canAct({ ...machine, name: effective.name }) && machine.attempt === null
|
|
2860
|
+
canAct: canAct({ ...machine, name: effective.name }) && machine.attempt === null,
|
|
2861
|
+
// Short on the wallet chain is neither route: nothing can be paid until the collector picks another chain.
|
|
2862
|
+
route: machine.route ?? (machine.walletRead && machine.drop && !machine.routePending && machine.short === null ? { kind: "direct", chainId: machine.drop.chainId } : null),
|
|
2863
|
+
crossChain: machine.crossChain && machine.account && crossChainInfo?.gen === machine.walletGen ? crossChainInfo.info : null
|
|
2300
2864
|
};
|
|
2301
2865
|
}
|
|
2302
2866
|
|
|
2867
|
+
// src/react/PayWithPicker.ts
|
|
2868
|
+
import { createElement, useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
|
|
2869
|
+
import { formatUnits as formatUnits5 } from "viem";
|
|
2870
|
+
var PICKER_REFRESH_MS = 2e4;
|
|
2871
|
+
var PICKER_DEBOUNCE_MS = 500;
|
|
2872
|
+
var RELAY_REASONS = /* @__PURE__ */ new Set(["BRIDGE_UNAVAILABLE", "BRIDGE_TIMEOUT", "BRIDGE_REFUNDED", "BRIDGE_FAILED"]);
|
|
2873
|
+
var money4 = (value, chainId) => {
|
|
2874
|
+
const c = getChain(chainId).nativeCurrency;
|
|
2875
|
+
return { value, decimals: c.decimals, symbol: c.symbol, formatted: formatUnits5(value, c.decimals) };
|
|
2876
|
+
};
|
|
2877
|
+
function usePayOptions(mint, onSelect) {
|
|
2878
|
+
const info = mint.crossChain;
|
|
2879
|
+
const current = mint.wallet?.chainId ?? null;
|
|
2880
|
+
const [quotes, setQuotes] = useState2({});
|
|
2881
|
+
const [balance, setBalance] = useState2(null);
|
|
2882
|
+
const [tick, setTick] = useState2(0);
|
|
2883
|
+
useEffect2(() => {
|
|
2884
|
+
const t = setInterval(() => setTick((n) => n + 1), PICKER_REFRESH_MS);
|
|
2885
|
+
return () => clearInterval(t);
|
|
2886
|
+
}, []);
|
|
2887
|
+
const key = info ? `${info.tx.to}:${info.tx.value}:${info.tx.data}:${info.payer}:${info.recipient}:${info.payChains.join()}` : "";
|
|
2888
|
+
const [quotedKey, setQuotedKey] = useState2(key);
|
|
2889
|
+
if (quotedKey !== key) {
|
|
2890
|
+
setQuotedKey(key);
|
|
2891
|
+
setQuotes({});
|
|
2892
|
+
}
|
|
2893
|
+
useEffect2(() => {
|
|
2894
|
+
if (!info) return;
|
|
2895
|
+
const controller = new AbortController();
|
|
2896
|
+
const timer = setTimeout(() => {
|
|
2897
|
+
for (const chainId of info.payChains) {
|
|
2898
|
+
if (chainId === info.dropChainId) continue;
|
|
2899
|
+
getQuote({ testnet: info.testnet, payer: info.payer, recipient: info.recipient, originChainId: chainId, destinationChainId: info.dropChainId, tx: info.tx, signal: controller.signal }).then(
|
|
2900
|
+
(q) => {
|
|
2901
|
+
if (!controller.signal.aborted) setQuotes((prev) => ({ ...prev, [chainId]: { relayFee: q.relayFee, amountIn: q.amountIn } }));
|
|
2902
|
+
},
|
|
2903
|
+
(e) => {
|
|
2904
|
+
if (controller.signal.aborted || e?.reason === "RATE_LIMITED") return;
|
|
2905
|
+
setQuotes((prev) => ({ ...prev, [chainId]: "failed" }));
|
|
2906
|
+
}
|
|
2907
|
+
);
|
|
2908
|
+
}
|
|
2909
|
+
}, PICKER_DEBOUNCE_MS);
|
|
2910
|
+
return () => {
|
|
2911
|
+
clearTimeout(timer);
|
|
2912
|
+
controller.abort();
|
|
2913
|
+
};
|
|
2914
|
+
}, [key, tick]);
|
|
2915
|
+
useEffect2(() => {
|
|
2916
|
+
if (!info || current === null || !mint.wallet) return;
|
|
2917
|
+
let alive = true;
|
|
2918
|
+
getConnectedBalance(mint.wallet.address, current).then(
|
|
2919
|
+
(value) => {
|
|
2920
|
+
if (alive) setBalance({ chainId: current, value });
|
|
2921
|
+
},
|
|
2922
|
+
() => {
|
|
2923
|
+
if (alive) setBalance(null);
|
|
2924
|
+
}
|
|
2925
|
+
);
|
|
2926
|
+
return () => {
|
|
2927
|
+
alive = false;
|
|
2928
|
+
};
|
|
2929
|
+
}, [current, mint.wallet?.address, key, tick]);
|
|
2930
|
+
const select = useCallback2(
|
|
2931
|
+
(chainId) => {
|
|
2932
|
+
switchToChain(chainId).then(
|
|
2933
|
+
() => onSelect?.(chainId, null),
|
|
2934
|
+
(e) => onSelect?.(chainId, e)
|
|
2935
|
+
);
|
|
2936
|
+
},
|
|
2937
|
+
[onSelect]
|
|
2938
|
+
);
|
|
2939
|
+
if (!info) return [];
|
|
2940
|
+
return info.payChains.filter((chainId) => chainId === info.dropChainId || quotes[chainId] !== "failed").map((chainId) => {
|
|
2941
|
+
const q = quotes[chainId];
|
|
2942
|
+
const isDropChain = chainId === info.dropChainId;
|
|
2943
|
+
const quoted = q && q !== "failed" ? q : null;
|
|
2944
|
+
return {
|
|
2945
|
+
chainId,
|
|
2946
|
+
name: chainName(chainId),
|
|
2947
|
+
selected: chainId === current,
|
|
2948
|
+
isDropChain,
|
|
2949
|
+
relayFee: isDropChain ? null : quoted?.relayFee ?? null,
|
|
2950
|
+
amountIn: isDropChain ? null : quoted?.amountIn ?? null,
|
|
2951
|
+
balance: balance && balance.chainId === chainId && chainId === current ? money4(balance.value, chainId) : null,
|
|
2952
|
+
loading: !isDropChain && q === void 0,
|
|
2953
|
+
select: () => select(chainId)
|
|
2954
|
+
};
|
|
2955
|
+
});
|
|
2956
|
+
}
|
|
2957
|
+
function defaultRow(row) {
|
|
2958
|
+
const fee = row.isDropChain ? "None" : row.loading ? "\u2026" : row.relayFee ? `${row.relayFee.formatted} ${row.relayFee.symbol}` : "--";
|
|
2959
|
+
const balance = row.balance ? ` \xB7 ${row.balance.formatted} ${row.balance.symbol}` : "";
|
|
2960
|
+
return `${row.name}${balance} \xB7 fee ${fee}`;
|
|
2961
|
+
}
|
|
2962
|
+
function PayWithPicker(props) {
|
|
2963
|
+
const rows = usePayOptions(props.mint, props.onSelect);
|
|
2964
|
+
if (rows.length === 0) return null;
|
|
2965
|
+
const open = props.mint.state === "insufficient-funds" || props.mint.reason !== null && RELAY_REASONS.has(props.mint.reason);
|
|
2966
|
+
return createElement(
|
|
2967
|
+
"div",
|
|
2968
|
+
{ className: props.className, role: "group", "aria-label": "Pay with", "data-open": open ? "true" : "false" },
|
|
2969
|
+
rows.map(
|
|
2970
|
+
(row) => createElement(
|
|
2971
|
+
"button",
|
|
2972
|
+
{
|
|
2973
|
+
key: row.chainId,
|
|
2974
|
+
type: "button",
|
|
2975
|
+
"data-chain": String(row.chainId),
|
|
2976
|
+
"data-selected": row.selected ? "true" : "false",
|
|
2977
|
+
"data-loading": row.loading ? "true" : "false",
|
|
2978
|
+
"aria-pressed": row.selected,
|
|
2979
|
+
// Only an idle button may change chains: a click in progress already chose its pay chain.
|
|
2980
|
+
disabled: row.selected || !(props.mint.canAct || props.mint.state === "insufficient-funds" || props.mint.state === "loading"),
|
|
2981
|
+
onClick: row.select
|
|
2982
|
+
},
|
|
2983
|
+
props.renderRow ? props.renderRow(row) : defaultRow(row)
|
|
2984
|
+
)
|
|
2985
|
+
)
|
|
2986
|
+
);
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2303
2989
|
// src/react/MintButton.ts
|
|
2304
|
-
import { createElement } from "react";
|
|
2990
|
+
import { createElement as createElement2 } from "react";
|
|
2305
2991
|
function defaultLabel(m) {
|
|
2306
2992
|
const symbol = m.total?.erc20?.symbol ?? m.total?.native.symbol ?? m.drop?.unitPrice.symbol ?? "";
|
|
2307
2993
|
switch (m.state) {
|
|
@@ -2322,6 +3008,10 @@ function defaultLabel(m) {
|
|
|
2322
3008
|
case "connecting":
|
|
2323
3009
|
return "Connecting\u2026";
|
|
2324
3010
|
case "live": {
|
|
3011
|
+
const route = m.route;
|
|
3012
|
+
if (route?.kind === "relay") {
|
|
3013
|
+
return `Mint \xB7 ${route.amountIn.formatted} ${route.amountIn.symbol} on ${chainName(route.originChainId)}`;
|
|
3014
|
+
}
|
|
2325
3015
|
const total = m.total;
|
|
2326
3016
|
if (!total) return "Mint";
|
|
2327
3017
|
const legs = total.erc20 ? [total.erc20, ...total.native.value > 0n ? [total.native] : []] : [total.native];
|
|
@@ -2353,7 +3043,7 @@ function MintButton(props) {
|
|
|
2353
3043
|
const m = useMint(options);
|
|
2354
3044
|
if (typeof children === "function") return children(m);
|
|
2355
3045
|
const label = labels?.[m.state] ?? defaultLabel(m);
|
|
2356
|
-
return
|
|
3046
|
+
return createElement2(
|
|
2357
3047
|
"button",
|
|
2358
3048
|
{
|
|
2359
3049
|
type: "button",
|
|
@@ -2369,16 +3059,18 @@ function MintButton(props) {
|
|
|
2369
3059
|
}
|
|
2370
3060
|
|
|
2371
3061
|
// src/index.ts
|
|
2372
|
-
var VERSION = "0.
|
|
3062
|
+
var VERSION = "0.2.0";
|
|
2373
3063
|
export {
|
|
2374
3064
|
CLICKABLE_STATES,
|
|
2375
3065
|
MAX_QUANTITY,
|
|
2376
3066
|
MINT_STATES,
|
|
2377
3067
|
MintButton,
|
|
2378
3068
|
MintError,
|
|
3069
|
+
PayWithPicker,
|
|
2379
3070
|
REASON_CODES,
|
|
2380
3071
|
VERSION,
|
|
2381
3072
|
isMintError,
|
|
2382
|
-
useMint
|
|
3073
|
+
useMint,
|
|
3074
|
+
usePayOptions
|
|
2383
3075
|
};
|
|
2384
3076
|
//# sourceMappingURL=index.js.map
|