@manifoldxyz/dropkit 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -3
- package/dist/cart.cjs +1832 -0
- package/dist/cart.cjs.map +1 -0
- package/dist/cart.d.cts +216 -0
- package/dist/cart.d.ts +216 -0
- package/dist/cart.js +1832 -0
- package/dist/cart.js.map +1 -0
- package/dist/chunk-GO4TR3HB.cjs +1406 -0
- package/dist/chunk-GO4TR3HB.cjs.map +1 -0
- package/dist/chunk-SUJQB3SL.js +1406 -0
- package/dist/chunk-SUJQB3SL.js.map +1 -0
- package/dist/index.cjs +1004 -1647
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -194
- package/dist/index.d.ts +5 -194
- package/dist/index.js +724 -1353
- package/dist/index.js.map +1 -1
- package/dist/types-BjKGb6c7.d.cts +210 -0
- package/dist/types-BjKGb6c7.d.ts +210 -0
- package/package.json +11 -1
package/dist/index.js
CHANGED
|
@@ -1,394 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CLICKABLE_STATES,
|
|
3
|
+
IN_FLIGHT_STATES,
|
|
4
|
+
MAX_QUANTITY,
|
|
5
|
+
MINT_STATES,
|
|
6
|
+
MintError,
|
|
7
|
+
REASON_CODES,
|
|
8
|
+
UNSUPPORTED_REASONS,
|
|
9
|
+
canAct,
|
|
10
|
+
chainName,
|
|
11
|
+
createReadClient,
|
|
12
|
+
friendlyErrorMessage,
|
|
13
|
+
getAccount,
|
|
14
|
+
getChain,
|
|
15
|
+
getConnectedBalance,
|
|
16
|
+
getWalletClient,
|
|
17
|
+
initialState,
|
|
18
|
+
isMintError,
|
|
19
|
+
loadConnectWidget,
|
|
20
|
+
manifoldClaimAdapter,
|
|
21
|
+
openConnectModal,
|
|
22
|
+
parseMintReceipt,
|
|
23
|
+
reducer,
|
|
24
|
+
selectMaxQuantity,
|
|
25
|
+
selectTotal,
|
|
26
|
+
subscribeAccount,
|
|
27
|
+
switchToChain,
|
|
28
|
+
toReason,
|
|
29
|
+
waitForDeposit
|
|
30
|
+
} from "./chunk-SUJQB3SL.js";
|
|
31
|
+
|
|
1
32
|
// src/react/useMint.ts
|
|
2
33
|
import { useCallback, useEffect, useReducer, useRef, useState } from "react";
|
|
3
|
-
import { BaseError as BaseError3, ExecutionRevertedError, publicActions
|
|
4
|
-
|
|
5
|
-
// src/core/rpc/index.ts
|
|
6
|
-
import { createPublicClient, http } from "viem";
|
|
7
|
-
import { apeChain, base, baseSepolia, mainnet, optimism, sepolia, shape } from "viem/chains";
|
|
8
|
-
|
|
9
|
-
// src/core/errors.ts
|
|
10
|
-
var UNSUPPORTED_REASONS = [
|
|
11
|
-
"UNKNOWN_EXTENSION",
|
|
12
|
-
"AMBIGUOUS_EXTENSION",
|
|
13
|
-
"CLAIM_NOT_FOUND",
|
|
14
|
-
"SIGNATURE_CLAIM",
|
|
15
|
-
"INVALID_CONFIG",
|
|
16
|
-
"CONNECT_WIDGET_TOO_OLD"
|
|
17
|
-
];
|
|
18
|
-
var INELIGIBLE_REASONS = ["NOT_ON_ALLOWLIST", "ALLOWLIST_USED", "WALLET_LIMIT", "SIMULATION_REVERTED"];
|
|
19
|
-
var ERROR_REASONS = [
|
|
20
|
-
"USER_REJECTED",
|
|
21
|
-
"TX_REVERTED",
|
|
22
|
-
"RATE_LIMITED",
|
|
23
|
-
"RPC_ERROR",
|
|
24
|
-
"CHAIN_SWITCH_FAILED",
|
|
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"
|
|
31
|
-
];
|
|
32
|
-
var FUNDS_REASONS = ["INSUFFICIENT_FUNDS"];
|
|
33
|
-
var REASON_CODES = [
|
|
34
|
-
...UNSUPPORTED_REASONS,
|
|
35
|
-
...INELIGIBLE_REASONS,
|
|
36
|
-
...ERROR_REASONS,
|
|
37
|
-
...FUNDS_REASONS
|
|
38
|
-
];
|
|
39
|
-
var MintError = class extends Error {
|
|
40
|
-
reason;
|
|
41
|
-
/** INVALID_CONFIG only: dotted path of the bad field, e.g. "mint.args[1]" or "price". */
|
|
42
|
-
field;
|
|
43
|
-
/** 'relay': the message is Relay's decoded destination revert, shown as-is. */
|
|
44
|
-
domain;
|
|
45
|
-
constructor(reason, message, opts) {
|
|
46
|
-
super(message, opts?.cause === void 0 ? void 0 : { cause: opts.cause });
|
|
47
|
-
this.name = "MintError";
|
|
48
|
-
this.reason = reason;
|
|
49
|
-
if (opts?.field !== void 0) this.field = opts.field;
|
|
50
|
-
if (opts?.domain !== void 0) this.domain = opts.domain;
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
function isMintError(e) {
|
|
54
|
-
return e instanceof MintError;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// src/core/rpc/index.ts
|
|
58
|
-
var BRIDGE_URLS = {
|
|
59
|
-
1: "https://bridge.manifoldxyz.dev/rpc",
|
|
60
|
-
11155111: "https://sepolia.bridge.manifoldxyz.dev/rpc",
|
|
61
|
-
8453: "https://base.bridge.manifoldxyz.dev/rpc",
|
|
62
|
-
10: "https://optimism.bridge.manifoldxyz.dev/rpc",
|
|
63
|
-
360: "https://shape.bridge.manifoldxyz.dev/rpc",
|
|
64
|
-
33139: "https://apechain.bridge.manifoldxyz.dev/rpc"
|
|
65
|
-
};
|
|
66
|
-
var CHAINS = {
|
|
67
|
-
[mainnet.id]: mainnet,
|
|
68
|
-
[sepolia.id]: sepolia,
|
|
69
|
-
[base.id]: base,
|
|
70
|
-
[optimism.id]: optimism,
|
|
71
|
-
[shape.id]: shape,
|
|
72
|
-
[apeChain.id]: apeChain,
|
|
73
|
-
// Relay pay chain for Sepolia drops only: never a drop chain, no Manifold bridge.
|
|
74
|
-
[baseSepolia.id]: baseSepolia
|
|
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
|
-
}
|
|
88
|
-
var TRANSPORT_TIMEOUT_MS = 8e3;
|
|
89
|
-
function getChain(chainId) {
|
|
90
|
-
const chain = CHAINS[chainId];
|
|
91
|
-
if (!chain) {
|
|
92
|
-
throw new MintError("INVALID_CONFIG", `chainId ${String(chainId)} is not supported`, { field: "chainId" });
|
|
93
|
-
}
|
|
94
|
-
return chain;
|
|
95
|
-
}
|
|
96
|
-
var TERMINAL_CODES = /* @__PURE__ */ new Set([3, -32602, -32003, 4001, 4100, 4200, 4900, 4901, 4902]);
|
|
97
|
-
var FALLBACK_CODES = /* @__PURE__ */ new Set([-32700, -32600, -32601, -32001, -32002, -32004, -32005, 429]);
|
|
98
|
-
var EXECUTION_WORDING = /execution reverted|execution error|\breverted?\b|out of gas|invalid opcode|insufficient funds|nonce too low|gas required exceeds/i;
|
|
99
|
-
var UPSTREAM_WORDING = /\btimeout\b|\btimed out\b|\btoo many requests\b|\brate limit\b|\brequest limit reached\b|\b(?:exceeded.*capacity|capacity.*exceeded)\b|\bheader not found\b|\binternal (?:json-rpc )?(?:server )?error\b|\bnon-200 status code:\s*['"]?503\b/i;
|
|
100
|
-
var TRANSPORT_ERROR_NAMES = /* @__PURE__ */ new Set(["HttpRequestError", "TimeoutError", "SocketClosedError", "WebSocketRequestError"]);
|
|
101
|
-
function carriesExecutionEvidence(value, seen = /* @__PURE__ */ new Set()) {
|
|
102
|
-
if (typeof value === "string") return /^(?:Reverted\s+)?0x(?:[a-f\d]{2})*$/i.test(value) || EXECUTION_WORDING.test(value);
|
|
103
|
-
if (!value || typeof value !== "object" || seen.has(value)) return false;
|
|
104
|
-
seen.add(value);
|
|
105
|
-
const e = value;
|
|
106
|
-
if (typeof e.code === "number" && TERMINAL_CODES.has(e.code)) return true;
|
|
107
|
-
if (typeof e.message === "string" && EXECUTION_WORDING.test(e.message)) return true;
|
|
108
|
-
return ["data", "error", "originalError", "cause", "result"].some((key) => carriesExecutionEvidence(e[key], seen));
|
|
109
|
-
}
|
|
110
|
-
function shouldFallback(err) {
|
|
111
|
-
let retry = false;
|
|
112
|
-
const seen = /* @__PURE__ */ new Set();
|
|
113
|
-
for (let e = err; e && typeof e === "object" && !seen.has(e); ) {
|
|
114
|
-
seen.add(e);
|
|
115
|
-
const error = e;
|
|
116
|
-
const message = typeof error.details === "string" ? error.details : typeof error.shortMessage !== "string" ? error.message : "";
|
|
117
|
-
if (typeof message === "string" && EXECUTION_WORDING.test(message)) return false;
|
|
118
|
-
if (carriesExecutionEvidence(error.data)) return false;
|
|
119
|
-
if (typeof error.code === "number" && TERMINAL_CODES.has(error.code)) return false;
|
|
120
|
-
if (error.name === "ContractFunctionRevertedError" || error.name === "ExecutionRevertedError") return false;
|
|
121
|
-
if (typeof error.name === "string" && TRANSPORT_ERROR_NAMES.has(error.name)) retry = true;
|
|
122
|
-
if (typeof error.code === "number" && FALLBACK_CODES.has(error.code)) retry = true;
|
|
123
|
-
if (typeof message === "string" && UPSTREAM_WORDING.test(message)) retry = true;
|
|
124
|
-
e = error.cause;
|
|
125
|
-
}
|
|
126
|
-
return retry;
|
|
127
|
-
}
|
|
128
|
-
function createReadClient(opts) {
|
|
129
|
-
const chain = getChain(opts.chainId);
|
|
130
|
-
const urls = [opts.rpcUrls?.[opts.chainId], opts.useBridge ? BRIDGE_URLS[opts.chainId] : void 0, chain.rpcUrls.default.http[0]].filter(
|
|
131
|
-
(u, i, all) => !!u && all.indexOf(u) === i
|
|
132
|
-
);
|
|
133
|
-
const transport = ({ chain: c, pollingInterval }) => {
|
|
134
|
-
const transports = urls.map((url) => http(url, { timeout: TRANSPORT_TIMEOUT_MS, retryCount: 0 })({ chain: c, pollingInterval, retryCount: 0, timeout: TRANSPORT_TIMEOUT_MS }));
|
|
135
|
-
const request = (async (args) => {
|
|
136
|
-
let last;
|
|
137
|
-
for (const t of transports) {
|
|
138
|
-
try {
|
|
139
|
-
return await t.request(args);
|
|
140
|
-
} catch (err) {
|
|
141
|
-
if (!shouldFallback(err)) throw err;
|
|
142
|
-
last = err;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
console.warn(`dropkit: all RPC endpoints failed for chain ${String(opts.chainId)}; pass rpcUrls={{ ${String(opts.chainId)}: '<your RPC url>' }} to <MintButton>.`);
|
|
146
|
-
throw new MintError("RPC_ERROR", `All RPC endpoints failed for chain ${String(opts.chainId)}`, { cause: last });
|
|
147
|
-
});
|
|
148
|
-
return {
|
|
149
|
-
config: { key: "dropkit-fallback", name: "Dropkit fallback", request, retryCount: 0, retryDelay: 0, timeout: TRANSPORT_TIMEOUT_MS, type: "fallback" },
|
|
150
|
-
request,
|
|
151
|
-
value: {}
|
|
152
|
-
};
|
|
153
|
-
};
|
|
154
|
-
return createPublicClient({ chain, transport });
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// src/core/wallet/index.ts
|
|
158
|
-
import { createWalletClient, custom, getAddress, publicActions } from "viem";
|
|
159
|
-
var CONNECT_WIDGET_VERSION = "6.5.0";
|
|
160
|
-
var CONNECT_BASE_URL = `https://connect.manifoldxyz.dev/${CONNECT_WIDGET_VERSION}`;
|
|
161
|
-
var CONNECT_ID = "mint-button";
|
|
162
|
-
var ADDRESS_CHANGED = "ethereum-address-changed";
|
|
163
|
-
var CHAIN_CHANGED = "frontend-provider-chain-changed";
|
|
164
|
-
var LOADER_KEY = "__manifoldMintConnectLoader";
|
|
165
|
-
var DEFAULT_WALLETCONNECT_PROJECT_ID = "a03ac1695f309dc8d756e7b4006e5e7d";
|
|
166
|
-
var STATE_KEY = "__manifoldMintConnectStateV1";
|
|
167
|
-
var win = () => window;
|
|
168
|
-
var pageState = () => win()[STATE_KEY] ??= {};
|
|
169
|
-
var 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 + ".";
|
|
170
|
-
var MANIFOLD_HOST = /(^|\.)(manifoldxyz\.dev|manifold\.xyz)$/;
|
|
171
|
-
function isManifoldConnectScript(src) {
|
|
172
|
-
try {
|
|
173
|
-
const url = new URL(src, document.baseURI);
|
|
174
|
-
return url.hostname === "connect.manifoldxyz.dev" || MANIFOLD_HOST.test(url.hostname) && /\/connect\.umd[^/]*\.js$/.test(url.pathname);
|
|
175
|
-
} catch {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
function applyState(state) {
|
|
180
|
-
if (!state.div) return;
|
|
181
|
-
const network = state.networks.join(",");
|
|
182
|
-
if (state.div.dataset.network !== network) state.div.dataset.network = network;
|
|
183
|
-
const projectId = state.projectId ?? state.div.dataset.walletConnectProjectId ?? DEFAULT_WALLETCONNECT_PROJECT_ID;
|
|
184
|
-
if (state.div.dataset.walletConnectProjectId !== projectId) state.div.dataset.walletConnectProjectId = projectId;
|
|
185
|
-
}
|
|
186
|
-
function register(state, opts) {
|
|
187
|
-
for (const id2 of opts.chainIds) if (!state.networks.includes(id2)) state.networks.push(id2);
|
|
188
|
-
const id = opts.walletConnectProjectId;
|
|
189
|
-
if (id) {
|
|
190
|
-
if (!state.projectId) state.projectId = id;
|
|
191
|
-
else if (state.projectId !== id) {
|
|
192
|
-
console.warn(`dropkit: walletConnectProjectId "${id}" ignored; this page already uses "${state.projectId}" for the shared connect widget.`);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
applyState(state);
|
|
196
|
-
}
|
|
197
|
-
function startLoad(state) {
|
|
198
|
-
const connect = win().ManifoldConnect;
|
|
199
|
-
if (connect?.capabilities?.includes("open")) return Promise.resolve(connect.ready).then(() => void 0);
|
|
200
|
-
const hasScript = Array.from(document.querySelectorAll("script[src]")).some((s) => isManifoldConnectScript(s.src));
|
|
201
|
-
if (hasScript || document.querySelector('[data-widget="m-connect"]')) {
|
|
202
|
-
return Promise.reject(new MintError("CONNECT_WIDGET_TOO_OLD", TOO_OLD));
|
|
203
|
-
}
|
|
204
|
-
return new Promise((resolve, reject) => {
|
|
205
|
-
const css = document.createElement("link");
|
|
206
|
-
css.rel = "stylesheet";
|
|
207
|
-
css.href = `${CONNECT_BASE_URL}/connect.css`;
|
|
208
|
-
document.head.appendChild(css);
|
|
209
|
-
state.nodes.push(css);
|
|
210
|
-
const div = document.createElement("div");
|
|
211
|
-
div.hidden = true;
|
|
212
|
-
div.style.display = "none";
|
|
213
|
-
div.dataset.widget = "m-connect";
|
|
214
|
-
div.dataset.connectId = CONNECT_ID;
|
|
215
|
-
div.dataset.autoReconnect = "true";
|
|
216
|
-
div.dataset.enableNetworkSwitching = "true";
|
|
217
|
-
state.div = div;
|
|
218
|
-
applyState(state);
|
|
219
|
-
document.body.appendChild(div);
|
|
220
|
-
state.nodes.push(div);
|
|
221
|
-
const script = document.createElement("script");
|
|
222
|
-
script.src = `${CONNECT_BASE_URL}/connect.umd.min.js`;
|
|
223
|
-
script.async = true;
|
|
224
|
-
script.addEventListener("load", () => {
|
|
225
|
-
const ready = win().ManifoldConnect?.ready;
|
|
226
|
-
if (!ready) return reject(new MintError("CONNECT_WIDGET_TOO_OLD", TOO_OLD));
|
|
227
|
-
Promise.resolve(ready).then(() => resolve(), reject);
|
|
228
|
-
});
|
|
229
|
-
script.addEventListener("error", () => reject(new MintError("RPC_ERROR", `Could not load the Manifold connect widget from ${CONNECT_BASE_URL}`)));
|
|
230
|
-
document.head.appendChild(script);
|
|
231
|
-
state.nodes.push(script);
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
function loadConnectWidget(opts) {
|
|
235
|
-
const w = win();
|
|
236
|
-
const page = pageState();
|
|
237
|
-
const existing = w[LOADER_KEY];
|
|
238
|
-
if (existing) {
|
|
239
|
-
if (page.load?.promise !== existing) {
|
|
240
|
-
const div = document.querySelector(`[data-widget="m-connect"][data-connect-id="${CONNECT_ID}"]`) ?? void 0;
|
|
241
|
-
page.load = {
|
|
242
|
-
promise: existing,
|
|
243
|
-
networks: div?.dataset.network?.split(",").filter(Boolean).map(Number) ?? [],
|
|
244
|
-
// The default id is a placeholder, not a choice: a caller's real id may still replace it.
|
|
245
|
-
projectId: div?.dataset.walletConnectProjectId === DEFAULT_WALLETCONNECT_PROJECT_ID ? void 0 : div?.dataset.walletConnectProjectId,
|
|
246
|
-
div,
|
|
247
|
-
nodes: []
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
register(page.load, opts);
|
|
251
|
-
return existing;
|
|
252
|
-
}
|
|
253
|
-
let resolve;
|
|
254
|
-
let reject;
|
|
255
|
-
const p = new Promise((res, rej) => {
|
|
256
|
-
resolve = res;
|
|
257
|
-
reject = rej;
|
|
258
|
-
});
|
|
259
|
-
const state = { promise: p, networks: [], nodes: [] };
|
|
260
|
-
page.load = state;
|
|
261
|
-
w[LOADER_KEY] = p;
|
|
262
|
-
p.catch(() => {
|
|
263
|
-
if (w[LOADER_KEY] === p) delete w[LOADER_KEY];
|
|
264
|
-
if (page.load === state) page.load = void 0;
|
|
265
|
-
for (const node of state.nodes) node.parentNode?.removeChild(node);
|
|
266
|
-
state.nodes.length = 0;
|
|
267
|
-
state.div = void 0;
|
|
268
|
-
});
|
|
269
|
-
register(state, opts);
|
|
270
|
-
try {
|
|
271
|
-
startLoad(state).then(resolve, reject);
|
|
272
|
-
} catch (error) {
|
|
273
|
-
reject(error);
|
|
274
|
-
}
|
|
275
|
-
return p;
|
|
276
|
-
}
|
|
277
|
-
function openConnectModal() {
|
|
278
|
-
const page = pageState();
|
|
279
|
-
if (page.pendingModal) return page.pendingModal;
|
|
280
|
-
const p = new Promise((resolve) => {
|
|
281
|
-
const onClosed = (e) => {
|
|
282
|
-
const detail = e.detail ?? {};
|
|
283
|
-
if (detail.connectId !== void 0 && detail.connectId !== CONNECT_ID) return;
|
|
284
|
-
window.removeEventListener("m-connect:closed", onClosed);
|
|
285
|
-
page.pendingModal = void 0;
|
|
286
|
-
resolve(detail.reason === "connected" ? "connected" : "dismissed");
|
|
287
|
-
};
|
|
288
|
-
window.addEventListener("m-connect:closed", onClosed);
|
|
289
|
-
});
|
|
290
|
-
page.pendingModal = p;
|
|
291
|
-
window.dispatchEvent(new CustomEvent("m-connect:open", { detail: { connectId: CONNECT_ID } }));
|
|
292
|
-
return p;
|
|
293
|
-
}
|
|
294
|
-
function getAccount() {
|
|
295
|
-
const p = win().ManifoldEthereumProvider;
|
|
296
|
-
const address = p?.selectedAddress?.();
|
|
297
|
-
let chainId = p?.chainId?.();
|
|
298
|
-
if (chainId === void 0 || chainId === null) {
|
|
299
|
-
try {
|
|
300
|
-
chainId = p?.browserChainId?.();
|
|
301
|
-
} catch {
|
|
302
|
-
chainId = void 0;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
if (!address || chainId === void 0 || chainId === null) return null;
|
|
306
|
-
return { address: getAddress(address), chainId: Number(chainId) };
|
|
307
|
-
}
|
|
308
|
-
function subscribeAccount(cb) {
|
|
309
|
-
const handler = () => cb(getAccount());
|
|
310
|
-
window.addEventListener(ADDRESS_CHANGED, handler);
|
|
311
|
-
window.addEventListener(CHAIN_CHANGED, handler);
|
|
312
|
-
return () => {
|
|
313
|
-
window.removeEventListener(ADDRESS_CHANGED, handler);
|
|
314
|
-
window.removeEventListener(CHAIN_CHANGED, handler);
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
function eip1193() {
|
|
318
|
-
const p = win().ManifoldEthereumProvider;
|
|
319
|
-
const provider = (p?.provider?.(true) ?? p?.browserProvider?.())?.provider;
|
|
320
|
-
if (!provider) throw new MintError("CHAIN_SWITCH_FAILED", "No wallet is connected.");
|
|
321
|
-
return provider;
|
|
322
|
-
}
|
|
323
|
-
function getWalletClient(chainId) {
|
|
324
|
-
const provider = eip1193();
|
|
325
|
-
const request = ((args) => provider.request(args));
|
|
326
|
-
return createWalletClient({ chain: getChain(chainId), transport: custom({ request }) });
|
|
327
|
-
}
|
|
328
|
-
async function waitForDeposit(chainId, hash, timeoutMs = 12e4) {
|
|
329
|
-
try {
|
|
330
|
-
const receipt = await getWalletClient(chainId).extend(publicActions).waitForTransactionReceipt({ hash, confirmations: 1, timeout: timeoutMs });
|
|
331
|
-
return receipt.status === "reverted" ? "reverted" : "mined";
|
|
332
|
-
} catch {
|
|
333
|
-
return "unknown";
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
var WALLET_READ_TIMEOUT = { ms: 1e4 };
|
|
337
|
-
async function getConnectedBalance(address, chainId) {
|
|
338
|
-
const provider = eip1193();
|
|
339
|
-
let timer;
|
|
340
|
-
const timeout = new Promise((_, reject) => {
|
|
341
|
-
timer = setTimeout(() => reject(new MintError("RPC_ERROR", "The wallet did not report a balance in time.")), WALLET_READ_TIMEOUT.ms);
|
|
342
|
-
});
|
|
343
|
-
try {
|
|
344
|
-
return await Promise.race([
|
|
345
|
-
(async () => {
|
|
346
|
-
const current = Number(await provider.request({ method: "eth_chainId" }));
|
|
347
|
-
if (current !== chainId) {
|
|
348
|
-
throw new MintError("CHAIN_SWITCH_FAILED", `The wallet is on chain ${String(current)}, not ${String(chainId)}.`);
|
|
349
|
-
}
|
|
350
|
-
const hex = await provider.request({ method: "eth_getBalance", params: [address, "latest"] });
|
|
351
|
-
return BigInt(hex);
|
|
352
|
-
})(),
|
|
353
|
-
timeout
|
|
354
|
-
]);
|
|
355
|
-
} finally {
|
|
356
|
-
clearTimeout(timer);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
var code = (e) => e?.code;
|
|
360
|
-
async function switchToChain(chainId) {
|
|
361
|
-
const chain = getChain(chainId);
|
|
362
|
-
const provider = eip1193();
|
|
363
|
-
const hexId = `0x${chainId.toString(16)}`;
|
|
364
|
-
const switchChain = () => provider.request({ method: "wallet_switchEthereumChain", params: [{ chainId: hexId }] });
|
|
365
|
-
try {
|
|
366
|
-
try {
|
|
367
|
-
await switchChain();
|
|
368
|
-
} catch (e) {
|
|
369
|
-
if (code(e) !== 4902) throw e;
|
|
370
|
-
await provider.request({
|
|
371
|
-
method: "wallet_addEthereumChain",
|
|
372
|
-
params: [
|
|
373
|
-
{
|
|
374
|
-
chainId: hexId,
|
|
375
|
-
chainName: chain.name,
|
|
376
|
-
nativeCurrency: chain.nativeCurrency,
|
|
377
|
-
rpcUrls: [...chain.rpcUrls.default.http],
|
|
378
|
-
...chain.blockExplorers ? { blockExplorerUrls: [chain.blockExplorers.default.url] } : {}
|
|
379
|
-
}
|
|
380
|
-
]
|
|
381
|
-
});
|
|
382
|
-
await switchChain();
|
|
383
|
-
}
|
|
384
|
-
} catch (e) {
|
|
385
|
-
if (code(e) === 4001) throw new MintError("USER_REJECTED", "You rejected the network switch.", { cause: e });
|
|
386
|
-
throw new MintError("CHAIN_SWITCH_FAILED", `Could not switch the wallet to chain ${String(chainId)}.`, { cause: e });
|
|
387
|
-
}
|
|
388
|
-
}
|
|
34
|
+
import { BaseError as BaseError3, ExecutionRevertedError, publicActions } from "viem";
|
|
389
35
|
|
|
390
36
|
// src/core/relay/client.ts
|
|
391
|
-
import { formatUnits, getAddress
|
|
37
|
+
import { formatUnits, getAddress, isHex, keccak256, zeroAddress } from "viem";
|
|
392
38
|
var RELAY_API = { mainnet: "https://api.relay.link", testnet: "https://api.testnets.relay.link" };
|
|
393
39
|
var MAINNET_PAY_CHAINS = [1, 8453, 10, 360, 33139];
|
|
394
40
|
var MAINNET_DROP_CHAINS = [1, 8453, 10, 360];
|
|
@@ -418,27 +64,27 @@ function moneyOf(amount, currency) {
|
|
|
418
64
|
}
|
|
419
65
|
function revertReason(errorData) {
|
|
420
66
|
if (typeof errorData !== "string") return "";
|
|
421
|
-
const
|
|
422
|
-
return
|
|
67
|
+
const text = errorData.replace(/^execution reverted:?\s*/i, "").trim();
|
|
68
|
+
return text === "execution reverted" ? "" : text;
|
|
423
69
|
}
|
|
424
70
|
function parseQuote(body, originChainId) {
|
|
425
71
|
const b = obj(body);
|
|
426
72
|
const steps = Array.isArray(b.steps) ? b.steps.map(obj) : [];
|
|
427
73
|
const unknownStep = () => new MintError("BRIDGE_UNAVAILABLE", "Relay returned a route this button cannot execute.");
|
|
428
74
|
if (steps.length !== 1 || steps[0].kind !== "transaction") throw unknownStep();
|
|
429
|
-
const
|
|
430
|
-
const rawItems = Array.isArray(
|
|
75
|
+
const step = steps[0];
|
|
76
|
+
const rawItems = Array.isArray(step.items) ? step.items.map(obj) : [];
|
|
431
77
|
if (rawItems.length !== 1) throw unknownStep();
|
|
432
78
|
const items = rawItems.map((item) => {
|
|
433
79
|
const d = obj(item.data);
|
|
434
80
|
const chainId = Number(d.chainId);
|
|
435
81
|
if (chainId !== originChainId || typeof d.to !== "string" || !isHex(d.data)) throw unknownStep();
|
|
436
|
-
return { chainId, to:
|
|
82
|
+
return { chainId, to: getAddress(d.to), data: d.data, value: big(d.value), gas: d.gas === void 0 ? null : big(d.gas) };
|
|
437
83
|
});
|
|
438
|
-
const requestId = isHex(
|
|
84
|
+
const requestId = isHex(step.requestId) ? step.requestId : isHex(b.requestId) ? b.requestId : null;
|
|
439
85
|
if (!requestId) throw unknownStep();
|
|
440
|
-
const
|
|
441
|
-
const currencyIn = obj(
|
|
86
|
+
const details2 = obj(b.details);
|
|
87
|
+
const currencyIn = obj(details2.currencyIn);
|
|
442
88
|
const fees = obj(b.fees);
|
|
443
89
|
const amountIn = moneyOf(currencyIn.amount, currencyIn.currency);
|
|
444
90
|
if (obj(currencyIn.currency).address !== void 0 && obj(currencyIn.currency).address !== zeroAddress) throw unknownStep();
|
|
@@ -597,808 +243,632 @@ async function pollUntilTerminal(a) {
|
|
|
597
243
|
}
|
|
598
244
|
}
|
|
599
245
|
|
|
600
|
-
// src/core/
|
|
601
|
-
var memory = /* @__PURE__ */ new Map();
|
|
602
|
-
var claimed = /* @__PURE__ */ new Set();
|
|
603
|
-
function outstandingKey(config, payer) {
|
|
604
|
-
const target = config.type === "manifold-claim" ? `${config.creatorContract.toLowerCase()}:${config.instanceId}` : config.address.toLowerCase();
|
|
605
|
-
return `${String(config.chainId)}:${target}:${payer.toLowerCase()}`;
|
|
606
|
-
}
|
|
607
|
-
var getOutstanding = (key) => memory.get(key) ?? null;
|
|
608
|
-
var setOutstanding = (key, out) => void memory.set(key, out);
|
|
609
|
-
var clearOutstanding = (key) => void memory.delete(key);
|
|
610
|
-
function claimDeposit(key) {
|
|
611
|
-
if (claimed.has(key) || memory.has(key)) return false;
|
|
612
|
-
claimed.add(key);
|
|
613
|
-
return true;
|
|
614
|
-
}
|
|
615
|
-
var releaseDeposit = (key) => void claimed.delete(key);
|
|
616
|
-
|
|
617
|
-
// src/core/adapters/manifold-claim/index.ts
|
|
246
|
+
// src/core/adapters/tl-stacks/index.ts
|
|
618
247
|
import {
|
|
619
248
|
BaseError,
|
|
620
249
|
ContractFunctionRevertedError,
|
|
250
|
+
ContractFunctionZeroDataError,
|
|
251
|
+
InsufficientFundsError,
|
|
621
252
|
decodeErrorResult,
|
|
622
253
|
encodeFunctionData,
|
|
623
254
|
formatUnits as formatUnits2,
|
|
624
255
|
getAddress as getAddress3,
|
|
625
256
|
isAddress,
|
|
626
|
-
|
|
627
|
-
zeroAddress as zeroAddress2,
|
|
628
|
-
zeroHash
|
|
257
|
+
zeroAddress as zeroAddress2
|
|
629
258
|
} from "viem";
|
|
630
259
|
|
|
631
|
-
// src/core/adapters/
|
|
632
|
-
|
|
633
|
-
var
|
|
634
|
-
|
|
635
|
-
]
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
]
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
260
|
+
// src/core/adapters/tl-stacks/abis.ts
|
|
261
|
+
var DROP_PHASE = { NOT_CONFIGURED: 0, NOT_STARTED: 1, PRESALE: 2, PUBLIC_SALE: 3, ENDED: 4 };
|
|
262
|
+
var SUPPORTED_VERSIONS = ["2.3.0", "2.3.1"];
|
|
263
|
+
var stacksAbi = [
|
|
264
|
+
{ type: "function", name: "VERSION", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
|
|
265
|
+
{ type: "function", name: "protocolFee", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "uint256" }] },
|
|
266
|
+
{
|
|
267
|
+
type: "function",
|
|
268
|
+
name: "getDrop",
|
|
269
|
+
stateMutability: "view",
|
|
270
|
+
inputs: [{ name: "nftAddress", type: "address" }],
|
|
271
|
+
outputs: [
|
|
272
|
+
{
|
|
273
|
+
name: "",
|
|
274
|
+
type: "tuple",
|
|
275
|
+
components: [
|
|
276
|
+
{ name: "dropType", type: "uint8" },
|
|
277
|
+
{ name: "payoutReceiver", type: "address" },
|
|
278
|
+
{ name: "initialSupply", type: "uint256" },
|
|
279
|
+
{ name: "supply", type: "uint256" },
|
|
280
|
+
{ name: "allowance", type: "uint256" },
|
|
281
|
+
{ name: "currencyAddress", type: "address" },
|
|
282
|
+
{ name: "startTime", type: "uint256" },
|
|
283
|
+
{ name: "presaleDuration", type: "uint256" },
|
|
284
|
+
{ name: "presaleCost", type: "uint256" },
|
|
285
|
+
{ name: "presaleMerkleRoot", type: "bytes32" },
|
|
286
|
+
{ name: "publicDuration", type: "uint256" },
|
|
287
|
+
{ name: "publicCost", type: "uint256" },
|
|
288
|
+
{ name: "decayRate", type: "int256" },
|
|
289
|
+
{ name: "baseUri", type: "string" }
|
|
290
|
+
]
|
|
291
|
+
}
|
|
292
|
+
]
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
type: "function",
|
|
296
|
+
name: "getDropPhase",
|
|
297
|
+
stateMutability: "view",
|
|
298
|
+
inputs: [{ name: "nftAddress", type: "address" }],
|
|
299
|
+
outputs: [{ name: "", type: "uint8" }]
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
type: "function",
|
|
303
|
+
name: "getNumberMinted",
|
|
304
|
+
stateMutability: "view",
|
|
305
|
+
inputs: [
|
|
306
|
+
{ name: "nftAddress", type: "address" },
|
|
307
|
+
{ name: "recipient", type: "address" }
|
|
308
|
+
],
|
|
309
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
type: "function",
|
|
313
|
+
name: "purchase",
|
|
314
|
+
stateMutability: "payable",
|
|
315
|
+
inputs: [
|
|
316
|
+
{ name: "nftAddress", type: "address" },
|
|
317
|
+
{ name: "recipient", type: "address" },
|
|
318
|
+
{ name: "numberToMint", type: "uint256" },
|
|
319
|
+
{ name: "presaleNumberCanMint", type: "uint256" },
|
|
320
|
+
{ name: "proof", type: "bytes32[]" }
|
|
321
|
+
],
|
|
322
|
+
outputs: [{ name: "refundAmount", type: "uint256" }]
|
|
323
|
+
},
|
|
324
|
+
{ type: "error", name: "MintZeroTokens", inputs: [] },
|
|
325
|
+
{ type: "error", name: "NotOnAllowlist", inputs: [] },
|
|
326
|
+
{ type: "error", name: "YouShallNotMint", inputs: [] },
|
|
327
|
+
{ type: "error", name: "AlreadyReachedMintAllowance", inputs: [] },
|
|
328
|
+
{ type: "error", name: "InsufficientFunds", inputs: [] },
|
|
329
|
+
{ type: "error", name: "SanctionedAddress", inputs: [] },
|
|
330
|
+
// OpenZeppelin v5 Pausable: purchase is whenNotPaused.
|
|
331
|
+
{ type: "error", name: "EnforcedPause", inputs: [] }
|
|
332
|
+
];
|
|
677
333
|
|
|
678
|
-
// src/core/adapters/
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
334
|
+
// src/core/adapters/tl-stacks/allowlist.ts
|
|
335
|
+
import { concatHex, encodeAbiParameters, getAddress as getAddress2, keccak256 as keccak2562 } from "viem";
|
|
336
|
+
var HASH = /^0x[0-9a-fA-F]{64}$/;
|
|
337
|
+
var UINT = /^\d+$/;
|
|
338
|
+
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
339
|
+
var unavailable = (message, cause) => new MintError("ALLOWLIST_UNAVAILABLE", message, cause === void 0 ? void 0 : { cause });
|
|
340
|
+
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
341
|
+
var hashWallet = (wallet) => keccak2562(encodeAbiParameters([{ type: "address" }], [getAddress2(wallet)]));
|
|
342
|
+
var leafOf = (hashedWallet, allowance) => keccak2562(concatHex([keccak2562(encodeAbiParameters([{ type: "bytes32" }, { type: "uint256" }], [hashedWallet, allowance]))]));
|
|
343
|
+
var hashPair = (a, b) => BigInt(a) < BigInt(b) ? keccak2562(concatHex([a, b])) : keccak2562(concatHex([b, a]));
|
|
344
|
+
var processProof = (leaf, proof) => proof.reduce((node, sibling) => hashPair(node, sibling), leaf);
|
|
345
|
+
function parseAllowlist(body) {
|
|
346
|
+
if (!isRecord(body)) throw unavailable("The allowlist file is not a merkle tree dump.");
|
|
347
|
+
if (body.format !== "standard-v1") throw unavailable("The allowlist file is in an unsupported format.");
|
|
348
|
+
const enc = body.leafEncoding;
|
|
349
|
+
if (!Array.isArray(enc) || enc.length !== 2 || enc[0] !== "bytes32" || enc[1] !== "uint256") {
|
|
350
|
+
throw unavailable("The allowlist file uses an unsupported leaf encoding.");
|
|
351
|
+
}
|
|
352
|
+
const { tree, values } = body;
|
|
353
|
+
if (!Array.isArray(tree) || tree.length === 0 || !tree.every((n) => typeof n === "string" && HASH.test(n))) {
|
|
354
|
+
throw unavailable("The allowlist file has no readable merkle tree.");
|
|
355
|
+
}
|
|
356
|
+
if (!Array.isArray(values)) throw unavailable("The allowlist file has no wallet values.");
|
|
357
|
+
const entries = /* @__PURE__ */ new Map();
|
|
358
|
+
for (const row of values) {
|
|
359
|
+
const value = isRecord(row) ? row.value : void 0;
|
|
360
|
+
const treeIndex = isRecord(row) ? row.treeIndex : void 0;
|
|
361
|
+
if (!Array.isArray(value) || value.length !== 2 || typeof value[0] !== "string" || !HASH.test(value[0])) {
|
|
362
|
+
throw unavailable("The allowlist file has an unreadable wallet entry.");
|
|
363
|
+
}
|
|
364
|
+
const raw = value[1];
|
|
365
|
+
const allowanceText = typeof raw === "string" ? raw : typeof raw === "number" && Number.isSafeInteger(raw) ? String(raw) : "";
|
|
366
|
+
if (!UINT.test(allowanceText) || BigInt(allowanceText) > MAX_UINT256) throw unavailable("The allowlist file has an unreadable allowance.");
|
|
367
|
+
if (typeof treeIndex !== "number") throw unavailable("The allowlist file has an unreadable tree index.");
|
|
368
|
+
const key = value[0].toLowerCase();
|
|
369
|
+
const allowance = BigInt(allowanceText);
|
|
370
|
+
const prev = entries.get(key);
|
|
371
|
+
if (!prev || allowance > prev.allowance) entries.set(key, { allowance, treeIndex });
|
|
372
|
+
}
|
|
373
|
+
return { root: tree[0].toLowerCase(), tree, entries };
|
|
374
|
+
}
|
|
375
|
+
var findEntry = (list, recipient) => list.entries.get(hashWallet(recipient).toLowerCase()) ?? null;
|
|
376
|
+
function assertRoot(list, presaleMerkleRoot) {
|
|
377
|
+
if (list.root !== presaleMerkleRoot.toLowerCase()) {
|
|
378
|
+
throw unavailable("The allowlist file does not match this drop. The creator may have updated the allowlist; reload the page.");
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
var buildProof = (list, recipient, entry) => buildProofForHash(list, hashWallet(recipient), entry);
|
|
382
|
+
function buildProofForHash(list, hashedWallet, entry) {
|
|
383
|
+
const { tree } = list;
|
|
384
|
+
const { treeIndex } = entry;
|
|
385
|
+
if (!Number.isInteger(treeIndex) || treeIndex < 1 || treeIndex >= tree.length) {
|
|
386
|
+
throw unavailable("The allowlist file has a tree index outside the tree.");
|
|
387
|
+
}
|
|
388
|
+
const leaf = leafOf(hashedWallet, entry.allowance);
|
|
389
|
+
if (tree[treeIndex].toLowerCase() !== leaf.toLowerCase()) throw unavailable("The allowlist file entry does not match its merkle leaf.");
|
|
390
|
+
const proof = [];
|
|
391
|
+
let i = treeIndex;
|
|
392
|
+
while (i > 0) {
|
|
393
|
+
const sibling = i % 2 === 0 ? i - 1 : i + 1;
|
|
394
|
+
const node = tree[sibling];
|
|
395
|
+
if (node === void 0) throw unavailable("The allowlist file has a broken merkle tree.");
|
|
396
|
+
proof.push(node);
|
|
397
|
+
i = Math.floor((i - 1) / 2);
|
|
398
|
+
}
|
|
399
|
+
if (processProof(leaf, proof).toLowerCase() !== list.root) throw unavailable("The allowlist file does not produce a valid proof for this wallet.");
|
|
400
|
+
return proof;
|
|
695
401
|
}
|
|
696
|
-
var isRecord = (v) => typeof v === "object" && v !== null;
|
|
697
402
|
var scopeCaches = /* @__PURE__ */ new WeakMap();
|
|
698
|
-
function memo(cache2, key, signal, load) {
|
|
699
|
-
const hit = cache2.get(key);
|
|
700
|
-
if (hit) return hit;
|
|
701
|
-
const p = withSignal(load(), signal);
|
|
702
|
-
cache2.set(key, p);
|
|
703
|
-
p.catch(() => {
|
|
704
|
-
if (cache2.get(key) === p) cache2.delete(key);
|
|
705
|
-
});
|
|
706
|
-
return p;
|
|
707
|
-
}
|
|
708
403
|
function withSignal(p, signal) {
|
|
709
404
|
if (!signal) return p;
|
|
710
405
|
return new Promise((resolve, reject) => {
|
|
711
|
-
const onAbort = () => reject(
|
|
406
|
+
const onAbort = () => reject(unavailable("The allowlist request was cancelled.", signal.reason));
|
|
712
407
|
p.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
|
713
408
|
if (signal.aborted) return onAbort();
|
|
714
409
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
715
410
|
});
|
|
716
411
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
const
|
|
722
|
-
|
|
723
|
-
if (
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
return body.map((row) => {
|
|
735
|
-
const proof = isRecord(row) ? row.merkleProof : void 0;
|
|
736
|
-
const value = isRecord(row) ? row.value : void 0;
|
|
737
|
-
if (!Array.isArray(proof) || !proof.every((p) => typeof p === "string" && /^0x[0-9a-fA-F]{64}$/.test(p)) || typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > UINT32_MAX) {
|
|
738
|
-
throw new MintError("ALLOWLIST_UNAVAILABLE", "The allowlist service returned an unexpected response.");
|
|
412
|
+
var ALLOWLIST_TIMEOUT_MS = 3e4;
|
|
413
|
+
var ALLOWLIST_MAX_BYTES = 10 * 1024 * 1024;
|
|
414
|
+
var tooLarge = () => unavailable(`The allowlist file is larger than ${ALLOWLIST_MAX_BYTES / (1024 * 1024)} MB.`);
|
|
415
|
+
async function readCapped(res) {
|
|
416
|
+
const declared = Number(res.headers.get("content-length"));
|
|
417
|
+
if (Number.isFinite(declared) && declared > ALLOWLIST_MAX_BYTES) throw tooLarge();
|
|
418
|
+
if (!res.body) return "";
|
|
419
|
+
const reader = res.body.getReader();
|
|
420
|
+
const chunks = [];
|
|
421
|
+
let total = 0;
|
|
422
|
+
for (; ; ) {
|
|
423
|
+
const { done, value } = await reader.read();
|
|
424
|
+
if (done) break;
|
|
425
|
+
total += value.byteLength;
|
|
426
|
+
if (total > ALLOWLIST_MAX_BYTES) {
|
|
427
|
+
void reader.cancel().catch(() => void 0);
|
|
428
|
+
throw tooLarge();
|
|
739
429
|
}
|
|
740
|
-
|
|
430
|
+
chunks.push(value);
|
|
431
|
+
}
|
|
432
|
+
const bytes = new Uint8Array(total);
|
|
433
|
+
let offset = 0;
|
|
434
|
+
for (const c of chunks) {
|
|
435
|
+
bytes.set(c, offset);
|
|
436
|
+
offset += c.byteLength;
|
|
437
|
+
}
|
|
438
|
+
return new TextDecoder().decode(bytes);
|
|
439
|
+
}
|
|
440
|
+
async function fetchAndParse(fetchFn, url, signal) {
|
|
441
|
+
let res;
|
|
442
|
+
try {
|
|
443
|
+
signal.throwIfAborted();
|
|
444
|
+
res = await fetchFn(url, { signal });
|
|
445
|
+
} catch (cause) {
|
|
446
|
+
throw unavailable("Could not reach the allowlist file.", cause);
|
|
447
|
+
}
|
|
448
|
+
if (res.status === 429) throw new MintError("RATE_LIMITED", "The allowlist host is rate limiting requests. Try again shortly.");
|
|
449
|
+
if (!res.ok) throw unavailable(`The allowlist file is not available (HTTP ${res.status}).`);
|
|
450
|
+
let body;
|
|
451
|
+
try {
|
|
452
|
+
const text = await readCapped(res);
|
|
453
|
+
body = JSON.parse(text);
|
|
454
|
+
} catch (cause) {
|
|
455
|
+
if (cause instanceof MintError) throw cause;
|
|
456
|
+
throw unavailable("The allowlist file could not be read.", cause);
|
|
457
|
+
}
|
|
458
|
+
return parseAllowlist(body);
|
|
459
|
+
}
|
|
460
|
+
async function download(fetchFn, url, signal) {
|
|
461
|
+
const controller = new AbortController();
|
|
462
|
+
let onAbort;
|
|
463
|
+
const stopped = new Promise((_, reject) => {
|
|
464
|
+
onAbort = () => {
|
|
465
|
+
const reason = controller.signal.reason;
|
|
466
|
+
reject(reason instanceof MintError ? reason : unavailable("The allowlist request was cancelled.", reason));
|
|
467
|
+
};
|
|
468
|
+
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
741
469
|
});
|
|
470
|
+
const onLifetimeAbort = () => controller.abort(signal?.reason);
|
|
471
|
+
if (signal?.aborted) onLifetimeAbort();
|
|
472
|
+
else signal?.addEventListener("abort", onLifetimeAbort, { once: true });
|
|
473
|
+
const timer = setTimeout(
|
|
474
|
+
() => controller.abort(unavailable(`The allowlist file did not load within ${ALLOWLIST_TIMEOUT_MS / 1e3} seconds. Try again.`)),
|
|
475
|
+
ALLOWLIST_TIMEOUT_MS
|
|
476
|
+
);
|
|
477
|
+
try {
|
|
478
|
+
return await Promise.race([fetchAndParse(fetchFn, url, controller.signal), stopped]);
|
|
479
|
+
} finally {
|
|
480
|
+
clearTimeout(timer);
|
|
481
|
+
if (onAbort) controller.signal.removeEventListener("abort", onAbort);
|
|
482
|
+
signal?.removeEventListener("abort", onLifetimeAbort);
|
|
483
|
+
}
|
|
742
484
|
}
|
|
743
|
-
|
|
744
|
-
if (args.
|
|
745
|
-
|
|
485
|
+
function loadAllowlist(args) {
|
|
486
|
+
if (!args.url) {
|
|
487
|
+
return Promise.reject(unavailable("This presale has no allowlist file configured (allowlistUrl). Ask the creator to add it."));
|
|
746
488
|
}
|
|
489
|
+
if (args.signal?.aborted) return Promise.reject(unavailable("The allowlist request was cancelled.", args.signal.reason));
|
|
747
490
|
const fetchFn = args.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
748
|
-
const key = `${args.api}|${args.instanceId}|${args.recipient.toLowerCase()}`;
|
|
749
491
|
let scoped = scopeCaches.get(args.scope);
|
|
750
492
|
if (!scoped || scoped.signal?.aborted) {
|
|
751
|
-
scoped = { signal: args.signal,
|
|
493
|
+
scoped = { signal: args.signal, loads: /* @__PURE__ */ new Map() };
|
|
752
494
|
scopeCaches.set(args.scope, scoped);
|
|
753
495
|
}
|
|
754
496
|
const cache2 = scoped;
|
|
755
|
-
const
|
|
756
|
-
|
|
497
|
+
const hit = cache2.loads.get(args.url);
|
|
498
|
+
if (hit) return hit;
|
|
499
|
+
const url = args.url;
|
|
500
|
+
const p = withSignal(download(fetchFn, url, cache2.signal), cache2.signal);
|
|
501
|
+
cache2.loads.set(url, p);
|
|
502
|
+
p.catch(() => {
|
|
503
|
+
if (cache2.loads.get(url) === p) cache2.loads.delete(url);
|
|
504
|
+
});
|
|
505
|
+
return p;
|
|
757
506
|
}
|
|
758
507
|
|
|
759
|
-
// src/core/adapters/
|
|
760
|
-
var
|
|
761
|
-
var
|
|
762
|
-
var
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
...v6(10),
|
|
780
|
-
...v7(10),
|
|
781
|
-
...v7(360),
|
|
782
|
-
{ chainId: 360, address: V8_1155, generation: "V8", spec: "1155", abi: "Claim1155V7" },
|
|
783
|
-
...v6(11155111),
|
|
784
|
-
...v7(11155111),
|
|
785
|
-
{ chainId: 11155111, address: V8_721, generation: "V8", spec: "721", abi: "Claim721V7" },
|
|
786
|
-
{ chainId: 11155111, address: V8_1155, generation: "V8", spec: "1155", abi: "Claim1155V7" }
|
|
787
|
-
];
|
|
788
|
-
function findExtension(chainId, address) {
|
|
789
|
-
const a = address.toLowerCase();
|
|
790
|
-
return EXTENSIONS.find((e) => e.chainId === chainId && e.address === a);
|
|
791
|
-
}
|
|
792
|
-
var MERKLE_API = "https://apps.api.manifoldxyz.dev";
|
|
793
|
-
var MAX_MINT_COUNT = 65535;
|
|
794
|
-
var GAS_UNITS_BASE = 100000n;
|
|
795
|
-
var GAS_UNITS_PER_TOKEN = 150000n;
|
|
796
|
-
var GAS_UNITS_APPROVE = 60000n;
|
|
797
|
-
var dropResolved = /* @__PURE__ */ new WeakMap();
|
|
798
|
-
var WALLET_DATA_BRAND = /* @__PURE__ */ Symbol("manifold-claim-wallet-data");
|
|
799
|
-
var isWalletData = (d) => typeof d === "object" && d !== null && d.brand === WALLET_DATA_BRAND;
|
|
800
|
-
var isAllowlist = (claim) => claim.merkleRoot !== zeroHash;
|
|
801
|
-
function isContractFailure(e) {
|
|
802
|
-
if (!(e instanceof BaseError)) return false;
|
|
803
|
-
return e.walk((x) => x instanceof ContractFunctionRevertedError || x.name === "ContractFunctionZeroDataError") !== null;
|
|
804
|
-
}
|
|
805
|
-
function toRpcError(e) {
|
|
806
|
-
if (e instanceof MintError) return e;
|
|
807
|
-
const msg = e instanceof BaseError ? e.shortMessage : e instanceof Error ? e.message : String(e);
|
|
808
|
-
return new MintError("RPC_ERROR", `RPC request failed: ${msg}`, { cause: e });
|
|
809
|
-
}
|
|
810
|
-
async function rpc(fn) {
|
|
811
|
-
try {
|
|
812
|
-
return await fn();
|
|
813
|
-
} catch (e) {
|
|
814
|
-
throw toRpcError(e);
|
|
508
|
+
// src/core/adapters/tl-stacks/index.ts
|
|
509
|
+
var FALLBACK_GAS_BASE = 150000n;
|
|
510
|
+
var FALLBACK_GAS_PER_TOKEN = 150000n;
|
|
511
|
+
var details = /* @__PURE__ */ new WeakMap();
|
|
512
|
+
function bad(field, message) {
|
|
513
|
+
throw new MintError("INVALID_CONFIG", `${field} ${message}`, { field });
|
|
514
|
+
}
|
|
515
|
+
function validateTlStacksConfig(config) {
|
|
516
|
+
if (!Number.isInteger(config.chainId) || config.chainId <= 0) bad("chainId", "must be a positive integer");
|
|
517
|
+
if (typeof config.stacks !== "string" || !isAddress(config.stacks, { strict: false })) bad("stacks", "is not a valid address");
|
|
518
|
+
if (typeof config.nftContract !== "string" || !isAddress(config.nftContract, { strict: false })) bad("nftContract", "is not a valid address");
|
|
519
|
+
if (config.allowlistUrl !== void 0) {
|
|
520
|
+
const https = (() => {
|
|
521
|
+
try {
|
|
522
|
+
return typeof config.allowlistUrl === "string" && new URL(config.allowlistUrl).protocol === "https:";
|
|
523
|
+
} catch {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
})();
|
|
527
|
+
if (!https) bad("allowlistUrl", "must be an https: URL");
|
|
815
528
|
}
|
|
816
529
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
530
|
+
var abortError = () => new DOMException("The operation was aborted", "AbortError");
|
|
531
|
+
var isAbortError = (e) => e?.name === "AbortError";
|
|
532
|
+
function throwIfAborted(ctx) {
|
|
533
|
+
if (ctx.signal?.aborted) throw abortError();
|
|
534
|
+
}
|
|
535
|
+
function io(ctx, start) {
|
|
536
|
+
const signal = ctx.signal;
|
|
537
|
+
if (!signal) return start();
|
|
538
|
+
if (signal.aborted) return Promise.reject(abortError());
|
|
539
|
+
return new Promise((resolve, reject) => {
|
|
540
|
+
const onAbort = () => reject(abortError());
|
|
541
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
542
|
+
start().then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
|
821
543
|
});
|
|
822
|
-
const d = hit?.data;
|
|
823
|
-
if (isHex2(d)) return d;
|
|
824
|
-
const inner = d?.data;
|
|
825
|
-
return isHex2(inner) ? inner : void 0;
|
|
826
544
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
545
|
+
var shortMessage = (e) => e instanceof BaseError ? e.shortMessage : e instanceof Error ? e.message : String(e);
|
|
546
|
+
function rpcError(e) {
|
|
547
|
+
if (isMintError(e) || isAbortError(e)) throw e;
|
|
548
|
+
throw new MintError("RPC_ERROR", `RPC request failed: ${shortMessage(e)}`, { cause: e });
|
|
549
|
+
}
|
|
550
|
+
async function readStacks(ctx, config, functionName, args = []) {
|
|
551
|
+
try {
|
|
552
|
+
return await io(
|
|
553
|
+
ctx,
|
|
554
|
+
() => ctx.client.readContract({ address: getAddress3(config.stacks), abi: stacksAbi, functionName, args })
|
|
555
|
+
);
|
|
556
|
+
} catch (e) {
|
|
557
|
+
if (isMintError(e) || isAbortError(e)) throw e;
|
|
558
|
+
if (e instanceof BaseError && e.walk((x) => x instanceof ContractFunctionRevertedError || x instanceof ContractFunctionZeroDataError)) {
|
|
559
|
+
throw new MintError("INVALID_CONFIG", `stacks ${functionName}() reverted or returned no data. Is ${config.stacks} a TL Stacks 721 contract on chain ${config.chainId}?`, {
|
|
560
|
+
field: "stacks",
|
|
561
|
+
cause: e
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
return rpcError(e);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
var versions = /* @__PURE__ */ new WeakMap();
|
|
568
|
+
async function checkVersion(ctx, config) {
|
|
569
|
+
const load = () => readStacks(ctx, config, "VERSION");
|
|
570
|
+
let version;
|
|
571
|
+
if (ctx.cacheScope) {
|
|
572
|
+
let byStacks = versions.get(ctx.cacheScope);
|
|
573
|
+
if (!byStacks) versions.set(ctx.cacheScope, byStacks = /* @__PURE__ */ new Map());
|
|
574
|
+
const key = `${config.chainId}:${config.stacks.toLowerCase()}`;
|
|
575
|
+
const cache2 = byStacks;
|
|
576
|
+
let hit = cache2.get(key);
|
|
577
|
+
if (!hit) {
|
|
578
|
+
const p = load();
|
|
579
|
+
cache2.set(key, hit = p);
|
|
580
|
+
p.catch(() => {
|
|
581
|
+
if (cache2.get(key) === p) cache2.delete(key);
|
|
582
|
+
});
|
|
838
583
|
}
|
|
584
|
+
version = hit;
|
|
585
|
+
} else {
|
|
586
|
+
version = load();
|
|
587
|
+
}
|
|
588
|
+
const v = await version;
|
|
589
|
+
if (!SUPPORTED_VERSIONS.includes(v)) {
|
|
590
|
+
bad("stacks", `is TL Stacks ${v}. Dropkit supports ${SUPPORTED_VERSIONS.join(" and ")} only.`);
|
|
839
591
|
}
|
|
840
|
-
const named = e.walk((x) => ["ExecutionRevertedError", "InsufficientFundsError"].includes(x.name));
|
|
841
|
-
return named instanceof BaseError ? named.shortMessage : null;
|
|
842
592
|
}
|
|
843
593
|
var money = (value, decimals, symbol) => ({ value, decimals, symbol, formatted: formatUnits2(value, decimals) });
|
|
844
|
-
function
|
|
845
|
-
|
|
846
|
-
|
|
594
|
+
function nativeMeta(ctx) {
|
|
595
|
+
const n = ctx.client.chain?.nativeCurrency;
|
|
596
|
+
return { decimals: n?.decimals ?? 18, symbol: n?.symbol ?? "ETH" };
|
|
597
|
+
}
|
|
598
|
+
function seconds(v, field) {
|
|
599
|
+
if (v > BigInt(Number.MAX_SAFE_INTEGER)) throw new MintError("INVALID_CONFIG", `${field} is too large for a unix timestamp`, { field });
|
|
847
600
|
return Number(v);
|
|
848
601
|
}
|
|
849
|
-
function
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
602
|
+
async function readDrop(ctx, config) {
|
|
603
|
+
validateTlStacksConfig(config);
|
|
604
|
+
throwIfAborted(ctx);
|
|
605
|
+
await checkVersion(ctx, config);
|
|
606
|
+
const nft = getAddress3(config.nftContract);
|
|
607
|
+
const [drop, phaseRaw, fee] = await Promise.all([
|
|
608
|
+
readStacks(ctx, config, "getDrop", [nft]),
|
|
609
|
+
readStacks(ctx, config, "getDropPhase", [nft]),
|
|
610
|
+
readStacks(ctx, config, "protocolFee")
|
|
611
|
+
]);
|
|
612
|
+
const phase = Number(phaseRaw);
|
|
613
|
+
if (phase === DROP_PHASE.NOT_CONFIGURED) {
|
|
614
|
+
throw new MintError("DROP_NOT_FOUND", "No Transient Labs drop is configured for this NFT contract on this chain.");
|
|
615
|
+
}
|
|
616
|
+
if (drop.currencyAddress !== zeroAddress2) {
|
|
617
|
+
bad("nftContract", `is sold for an ERC-20 (${drop.currencyAddress}). Dropkit supports drops priced in the native token only.`);
|
|
618
|
+
}
|
|
619
|
+
const hasPresale = drop.presaleDuration > 0n;
|
|
620
|
+
const presalePrice = phase === DROP_PHASE.PRESALE || phase === DROP_PHASE.NOT_STARTED && hasPresale;
|
|
621
|
+
const unitPrice = presalePrice ? drop.presaleCost : drop.publicCost;
|
|
622
|
+
const startTime = seconds(drop.startTime, "startTime");
|
|
623
|
+
const presaleEnd = seconds(drop.startTime + drop.presaleDuration, "presaleDuration");
|
|
624
|
+
const soldOut = drop.supply === 0n;
|
|
625
|
+
const neverMintable = drop.initialSupply === 0n;
|
|
626
|
+
const native = nativeMeta(ctx);
|
|
627
|
+
const info = {
|
|
628
|
+
chainId: config.chainId,
|
|
629
|
+
unitPrice: money(unitPrice, native.decimals, native.symbol),
|
|
630
|
+
unitFee: money(fee, native.decimals, native.symbol),
|
|
631
|
+
totalMinted: drop.initialSupply - drop.supply,
|
|
632
|
+
maxSupply: drop.initialSupply,
|
|
633
|
+
// A never-mintable drop has no start either, so the hook clock has no boundary ahead to tick towards (review F13).
|
|
634
|
+
startTime: neverMintable || startTime === 0 ? null : startTime,
|
|
635
|
+
// At supply 0 the drop is sold out for good: no end time, so it never turns into `ended` (D14).
|
|
636
|
+
endTime: neverMintable ? 0 : soldOut ? null : seconds(drop.startTime + drop.presaleDuration + drop.publicDuration, "publicDuration"),
|
|
637
|
+
isAllowlist: phase === DROP_PHASE.PRESALE,
|
|
638
|
+
paymentToken: null,
|
|
639
|
+
pricePer: "unit",
|
|
640
|
+
phaseEndsAt: presalePrice ? presaleEnd : null,
|
|
641
|
+
walletKey: `${phase}:${drop.presaleMerkleRoot}:${unitPrice}:${fee}`
|
|
863
642
|
};
|
|
643
|
+
details.set(info, {
|
|
644
|
+
phase,
|
|
645
|
+
presaleMerkleRoot: drop.presaleMerkleRoot,
|
|
646
|
+
allowance: drop.allowance,
|
|
647
|
+
presaleDuration: drop.presaleDuration,
|
|
648
|
+
publicDuration: drop.publicDuration,
|
|
649
|
+
startTime: drop.startTime
|
|
650
|
+
});
|
|
651
|
+
return info;
|
|
864
652
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
const
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
return { ...base2(c), signingAddress: null };
|
|
874
|
-
}
|
|
875
|
-
case "Claim1155V5Optimization": {
|
|
876
|
-
const c = await client.readContract({ address, abi: CLAIM_1155_V5_ABI, functionName: "getClaim", args });
|
|
877
|
-
return { ...base2(c), signingAddress: null };
|
|
878
|
-
}
|
|
879
|
-
case "Claim721V7": {
|
|
880
|
-
const c = await client.readContract({ address, abi: CLAIM_721_V7_ABI, functionName: "getClaim", args });
|
|
881
|
-
return { ...base2(c), signingAddress: c.signingAddress };
|
|
882
|
-
}
|
|
883
|
-
case "Claim1155V7": {
|
|
884
|
-
const c = await client.readContract({ address, abi: CLAIM_1155_V7_ABI, functionName: "getClaim", args });
|
|
885
|
-
return { ...base2(c), signingAddress: c.signingAddress };
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
} catch (e) {
|
|
889
|
-
if (isContractFailure(e)) throw notFound(config);
|
|
890
|
-
throw toRpcError(e);
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
function base2(c) {
|
|
653
|
+
async function detailOf(ctx, config, drop) {
|
|
654
|
+
const hit = details.get(drop);
|
|
655
|
+
if (hit) return hit;
|
|
656
|
+
const nft = getAddress3(config.nftContract);
|
|
657
|
+
const [raw, phase] = await Promise.all([
|
|
658
|
+
readStacks(ctx, config, "getDrop", [nft]),
|
|
659
|
+
readStacks(ctx, config, "getDropPhase", [nft])
|
|
660
|
+
]);
|
|
894
661
|
return {
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
cost: c.cost,
|
|
902
|
-
erc20: c.erc20
|
|
662
|
+
phase: Number(phase),
|
|
663
|
+
presaleMerkleRoot: raw.presaleMerkleRoot,
|
|
664
|
+
allowance: raw.allowance,
|
|
665
|
+
presaleDuration: raw.presaleDuration,
|
|
666
|
+
publicDuration: raw.publicDuration,
|
|
667
|
+
startTime: raw.startTime
|
|
903
668
|
};
|
|
904
669
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
async function resolveExtension(ctx, config) {
|
|
911
|
-
const { creator, instanceId, extension } = parseConfig(config);
|
|
912
|
-
const { client } = ctx;
|
|
913
|
-
if (extension) {
|
|
914
|
-
const entry = findExtension(config.chainId, extension);
|
|
915
|
-
if (!entry) throw unknownExtension(extension, `is not a supported Manifold claim extension on chain ${config.chainId}`);
|
|
916
|
-
if (!await hasCode(client, extension)) throw unknownExtension(extension, "has no contract code on this chain");
|
|
917
|
-
return { entry, claim: await readClaim(client, entry, creator, instanceId, config) };
|
|
918
|
-
}
|
|
919
|
-
let registered;
|
|
920
|
-
try {
|
|
921
|
-
registered = await client.readContract({ address: creator, abi: CREATOR_ABI, functionName: "getExtensions" });
|
|
922
|
-
} catch (e) {
|
|
923
|
-
if (isContractFailure(e)) throw notFound(config);
|
|
924
|
-
throw toRpcError(e);
|
|
925
|
-
}
|
|
926
|
-
const candidates = [...new Set(registered.map((a) => a.toLowerCase()))].map((a) => findExtension(config.chainId, a)).filter((e) => e !== void 0);
|
|
927
|
-
const found = await Promise.all(
|
|
928
|
-
candidates.map(async (entry) => {
|
|
929
|
-
if (!await hasCode(client, entry.address)) return null;
|
|
930
|
-
try {
|
|
931
|
-
return { entry, claim: await readClaim(client, entry, creator, instanceId, config) };
|
|
932
|
-
} catch (e) {
|
|
933
|
-
if (e instanceof MintError && e.reason === "CLAIM_NOT_FOUND") return null;
|
|
934
|
-
throw e;
|
|
935
|
-
}
|
|
936
|
-
})
|
|
937
|
-
);
|
|
938
|
-
const hits = found.filter((f) => f !== null);
|
|
939
|
-
if (hits.length === 0) throw notFound(config);
|
|
940
|
-
if (hits.length > 1) {
|
|
941
|
-
throw new MintError(
|
|
942
|
-
"AMBIGUOUS_EXTENSION",
|
|
943
|
-
`Claim ${config.instanceId} exists on ${hits.length} extensions (${hits.map((h) => h.entry.address).join(", ")}). Set config.extension.`
|
|
944
|
-
);
|
|
945
|
-
}
|
|
946
|
-
return hits[0];
|
|
670
|
+
var dayTime = (unix) => new Date(unix * 1e3).toLocaleString(void 0, { dateStyle: "medium", timeStyle: "short" });
|
|
671
|
+
function notOnAllowlist(d) {
|
|
672
|
+
const opens = Number(d.startTime + d.presaleDuration);
|
|
673
|
+
const message = d.publicDuration > 0n ? `This wallet is not on the allowlist. The public sale opens ${dayTime(opens)}.` : "This wallet is not on the allowlist. This drop is allowlist only.";
|
|
674
|
+
return { reason: "NOT_ON_ALLOWLIST", message };
|
|
947
675
|
}
|
|
948
|
-
async function
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
676
|
+
async function readWallet(ctx, config, { payer, recipient, quantity, drop }) {
|
|
677
|
+
validateTlStacksConfig(config);
|
|
678
|
+
throwIfAborted(ctx);
|
|
679
|
+
const detail = await detailOf(ctx, config, drop);
|
|
680
|
+
const nft = getAddress3(config.nftContract);
|
|
681
|
+
const presale = detail.phase === DROP_PHASE.PRESALE;
|
|
682
|
+
const [minted, native, gasPrice, list] = await Promise.all([
|
|
683
|
+
readStacks(ctx, config, "getNumberMinted", [nft, getAddress3(recipient)]),
|
|
684
|
+
io(ctx, () => ctx.client.getBalance({ address: payer })).catch(rpcError),
|
|
685
|
+
io(ctx, () => ctx.client.getGasPrice()).catch(rpcError),
|
|
686
|
+
presale ? loadAllowlist({ scope: ctx.cacheScope ?? drop, url: config.allowlistUrl, fetch: ctx.fetch, signal: ctx.signal }) : Promise.resolve(null)
|
|
687
|
+
]);
|
|
688
|
+
let walletMax = detail.allowance;
|
|
689
|
+
let proof = [];
|
|
690
|
+
let presaleNumberCanMint = 0n;
|
|
691
|
+
let ineligible = null;
|
|
692
|
+
let allowlistRemaining = null;
|
|
693
|
+
if (presale && list) {
|
|
694
|
+
assertRoot(list, detail.presaleMerkleRoot);
|
|
695
|
+
const entry = findEntry(list, recipient);
|
|
696
|
+
if (!entry) {
|
|
697
|
+
walletMax = 0n;
|
|
698
|
+
ineligible = notOnAllowlist(detail);
|
|
699
|
+
} else {
|
|
700
|
+
walletMax = entry.allowance;
|
|
701
|
+
presaleNumberCanMint = entry.allowance;
|
|
702
|
+
proof = buildProof(list, recipient, entry);
|
|
703
|
+
}
|
|
952
704
|
}
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
()
|
|
957
|
-
)
|
|
958
|
-
|
|
959
|
-
let unitPrice;
|
|
960
|
-
let paymentToken = null;
|
|
961
|
-
if (claim.erc20 === zeroAddress2) {
|
|
962
|
-
unitPrice = money(claim.cost, native.decimals, native.symbol);
|
|
963
|
-
} else {
|
|
964
|
-
paymentToken = getAddress3(claim.erc20);
|
|
965
|
-
const token = paymentToken;
|
|
966
|
-
const [symbol, decimals] = await rpc(
|
|
967
|
-
() => Promise.all([
|
|
968
|
-
client.readContract({ address: token, abi: ERC20_ABI, functionName: "symbol" }),
|
|
969
|
-
client.readContract({ address: token, abi: ERC20_ABI, functionName: "decimals" })
|
|
970
|
-
])
|
|
971
|
-
);
|
|
972
|
-
unitPrice = money(claim.cost, decimals, symbol);
|
|
705
|
+
const remaining = walletMax > minted ? walletMax - minted : 0n;
|
|
706
|
+
if (presale && ineligible === null) {
|
|
707
|
+
allowlistRemaining = Number(remaining < BigInt(MAX_QUANTITY) ? remaining : BigInt(MAX_QUANTITY));
|
|
708
|
+
if (remaining === 0n) ineligible = { reason: "ALLOWLIST_USED", message: `This wallet has already minted its allowlist allocation of ${walletMax}.` };
|
|
709
|
+
} else if (!presale && remaining === 0n) {
|
|
710
|
+
ineligible = { reason: "WALLET_LIMIT", message: `This wallet has already minted its limit of ${walletMax}.` };
|
|
973
711
|
}
|
|
974
|
-
const
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
};
|
|
986
|
-
const resolved = { entry, claim, fee };
|
|
987
|
-
dropResolved.set(drop, resolved);
|
|
988
|
-
return { drop, resolved };
|
|
989
|
-
}
|
|
990
|
-
async function loadWallet(ctx, config, args) {
|
|
991
|
-
const { creator, instanceId } = parseConfig(config);
|
|
992
|
-
const { client } = ctx;
|
|
993
|
-
const resolved = dropResolved.get(args.drop) ?? (await loadDrop(ctx, config)).resolved;
|
|
994
|
-
const { entry, claim } = resolved;
|
|
995
|
-
const extension = getAddress3(entry.address);
|
|
996
|
-
const payer = getAddress3(args.payer);
|
|
997
|
-
const recipient = getAddress3(args.recipient);
|
|
998
|
-
let walletMinted = null;
|
|
999
|
-
let walletMax = null;
|
|
1000
|
-
let allowlistRemaining = null;
|
|
1001
|
-
let maxQuantity = null;
|
|
1002
|
-
let ineligible = null;
|
|
1003
|
-
let unused = [];
|
|
1004
|
-
if (isAllowlist(claim)) {
|
|
1005
|
-
const entries = await fetchMerkleEntries({ scope: ctx.cacheScope ?? args.drop, api: MERKLE_API, instanceId: config.instanceId, recipient, fetch: ctx.fetch, signal: ctx.signal });
|
|
1006
|
-
if (entries.length > 0) {
|
|
1007
|
-
const minted = await rpc(
|
|
1008
|
-
() => client.readContract({
|
|
1009
|
-
address: extension,
|
|
1010
|
-
abi: EXTENSION_COMMON_ABI,
|
|
1011
|
-
functionName: "checkMintIndices",
|
|
1012
|
-
args: [creator, instanceId, entries.map((e) => e.mintIndex)]
|
|
1013
|
-
})
|
|
712
|
+
const maxQuantity = Number(remaining < BigInt(MAX_QUANTITY) ? remaining : BigInt(MAX_QUANTITY));
|
|
713
|
+
const data = { presaleNumberCanMint, proof, unitPrice: drop.unitPrice.value, unitFee: drop.unitFee.value };
|
|
714
|
+
const qty = BigInt(quantity);
|
|
715
|
+
const value = qty * (data.unitPrice + data.unitFee);
|
|
716
|
+
let gas = FALLBACK_GAS_BASE + FALLBACK_GAS_PER_TOKEN * qty;
|
|
717
|
+
const open = detail.phase === DROP_PHASE.PRESALE || detail.phase === DROP_PHASE.PUBLIC_SALE;
|
|
718
|
+
if (open && ineligible === null && native >= value) {
|
|
719
|
+
try {
|
|
720
|
+
const estimated = await io(
|
|
721
|
+
ctx,
|
|
722
|
+
() => ctx.client.estimateGas({ account: payer, to: getAddress3(config.stacks), data: encodePurchase(config, recipient, qty, data), value })
|
|
1014
723
|
);
|
|
1015
|
-
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
maxQuantity = unused.length;
|
|
1019
|
-
if (entries.length === 0) {
|
|
1020
|
-
ineligible = { reason: "NOT_ON_ALLOWLIST", message: "This wallet is not on the allowlist." };
|
|
1021
|
-
} else if (unused.length === 0) {
|
|
1022
|
-
ineligible = { reason: "ALLOWLIST_USED", message: "This wallet has already used all its allowlist spots." };
|
|
724
|
+
gas = estimated;
|
|
725
|
+
} catch (e) {
|
|
726
|
+
if (isAbortError(e)) throw e;
|
|
1023
727
|
}
|
|
1024
|
-
}
|
|
1025
|
-
walletMax = claim.walletMax;
|
|
1026
|
-
walletMinted = BigInt(
|
|
1027
|
-
await rpc(() => client.readContract({ address: extension, abi: EXTENSION_COMMON_ABI, functionName: "getTotalMints", args: [recipient, creator, instanceId] }))
|
|
1028
|
-
);
|
|
1029
|
-
const left = walletMax > walletMinted ? walletMax - walletMinted : 0n;
|
|
1030
|
-
maxQuantity = Number(left);
|
|
1031
|
-
if (left === 0n) ineligible = { reason: "WALLET_LIMIT", message: `This wallet has already minted its limit of ${walletMax}.` };
|
|
1032
|
-
}
|
|
1033
|
-
const token = claim.erc20 === zeroAddress2 ? null : getAddress3(claim.erc20);
|
|
1034
|
-
const [native, erc20, gasPrice] = await rpc(
|
|
1035
|
-
() => Promise.all([
|
|
1036
|
-
client.getBalance({ address: payer }),
|
|
1037
|
-
token ? client.readContract({ address: token, abi: ERC20_ABI, functionName: "balanceOf", args: [payer] }) : Promise.resolve(null),
|
|
1038
|
-
client.getGasPrice()
|
|
1039
|
-
])
|
|
1040
|
-
);
|
|
1041
|
-
const gasUnits = GAS_UNITS_BASE + GAS_UNITS_PER_TOKEN * BigInt(Math.max(1, args.quantity)) + (token ? GAS_UNITS_APPROVE : 0n);
|
|
1042
|
-
const data = { brand: WALLET_DATA_BRAND, resolved, config, recipient, unused };
|
|
728
|
+
}
|
|
1043
729
|
return {
|
|
1044
|
-
walletMinted,
|
|
730
|
+
walletMinted: minted,
|
|
1045
731
|
walletMax,
|
|
1046
732
|
allowlistRemaining,
|
|
1047
733
|
maxQuantity,
|
|
1048
734
|
ineligible,
|
|
1049
|
-
balances: { native, erc20 },
|
|
1050
|
-
gasReserve:
|
|
735
|
+
balances: { native, erc20: null },
|
|
736
|
+
gasReserve: gas * gasPrice,
|
|
1051
737
|
data
|
|
1052
738
|
};
|
|
1053
739
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
const
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
const drop = (await loadDrop(ctx, config)).drop;
|
|
1066
|
-
data = (await loadWallet(ctx, config, { payer, recipient, quantity, drop })).data;
|
|
1067
|
-
}
|
|
1068
|
-
if (!isWalletData(data)) throw new MintError("RPC_ERROR", "Wallet read produced no plan data.");
|
|
1069
|
-
const { entry, claim, fee } = data.resolved;
|
|
1070
|
-
const extension = getAddress3(entry.address);
|
|
1071
|
-
let mintIndices = [];
|
|
1072
|
-
let merkleProofs = [];
|
|
1073
|
-
if (isAllowlist(claim)) {
|
|
1074
|
-
if (quantity > data.unused.length) {
|
|
1075
|
-
throw new MintError("WALLET_LIMIT", `Only ${data.unused.length} allowlist spot(s) left for this wallet.`);
|
|
1076
|
-
}
|
|
1077
|
-
const picked = data.unused.slice(0, quantity);
|
|
1078
|
-
mintIndices = picked.map((p) => p.mintIndex);
|
|
1079
|
-
merkleProofs = picked.map((p) => p.proof);
|
|
740
|
+
function encodePurchase(config, recipient, quantity, data) {
|
|
741
|
+
return encodeFunctionData({
|
|
742
|
+
abi: stacksAbi,
|
|
743
|
+
functionName: "purchase",
|
|
744
|
+
args: [getAddress3(config.nftContract), getAddress3(recipient), quantity, data.presaleNumberCanMint, [...data.proof]]
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
function walletData(wallet) {
|
|
748
|
+
const d = wallet.data;
|
|
749
|
+
if (!d || typeof d.presaleNumberCanMint !== "bigint" || typeof d.unitPrice !== "bigint" || typeof d.unitFee !== "bigint" || !Array.isArray(d.proof)) {
|
|
750
|
+
throw new MintError("RPC_ERROR", "The wallet read is missing the data this mint needs. Reload and try again.");
|
|
1080
751
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
const
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
approve = {
|
|
1090
|
-
token: erc20,
|
|
1091
|
-
spender: extension,
|
|
1092
|
-
amount: cost,
|
|
1093
|
-
tx: { to: erc20, data: encodeFunctionData({ abi: ERC20_ABI, functionName: "approve", args: [extension, cost] }), value: 0n }
|
|
1094
|
-
};
|
|
1095
|
-
}
|
|
752
|
+
return d;
|
|
753
|
+
}
|
|
754
|
+
async function buildPlan(ctx, attempt, wallet) {
|
|
755
|
+
const { config, recipient } = attempt;
|
|
756
|
+
validateTlStacksConfig(config);
|
|
757
|
+
throwIfAborted(ctx);
|
|
758
|
+
if (!Number.isInteger(attempt.quantity) || attempt.quantity < 1) {
|
|
759
|
+
throw new MintError("SIMULATION_REVERTED", "Choose at least one token to mint.");
|
|
1096
760
|
}
|
|
761
|
+
const data = walletData(wallet);
|
|
762
|
+
const qty = BigInt(attempt.quantity);
|
|
1097
763
|
return {
|
|
1098
|
-
approve,
|
|
764
|
+
approve: null,
|
|
1099
765
|
mint: {
|
|
1100
|
-
to:
|
|
1101
|
-
data:
|
|
1102
|
-
|
|
1103
|
-
functionName: "mintProxy",
|
|
1104
|
-
args: [creator, instanceId, quantity, mintIndices, merkleProofs.map((p) => [...p]), recipient]
|
|
1105
|
-
}),
|
|
1106
|
-
value
|
|
766
|
+
to: getAddress3(config.stacks),
|
|
767
|
+
data: encodePurchase(config, recipient, qty, data),
|
|
768
|
+
value: qty * (data.unitPrice + data.unitFee)
|
|
1107
769
|
},
|
|
1108
|
-
tokenContract:
|
|
770
|
+
tokenContract: getAddress3(config.nftContract)
|
|
1109
771
|
};
|
|
1110
772
|
}
|
|
1111
|
-
var
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
773
|
+
var TL_MESSAGES = {
|
|
774
|
+
NotOnAllowlist: "This wallet is not on the allowlist for the current sale.",
|
|
775
|
+
YouShallNotMint: "This drop is not open for minting right now.",
|
|
776
|
+
AlreadyReachedMintAllowance: "This wallet has already minted its limit.",
|
|
777
|
+
InsufficientFunds: "The price or fee changed since the page loaded, so the payment is too low. Reload the page and try again.",
|
|
778
|
+
MintZeroTokens: "Choose at least one token to mint.",
|
|
779
|
+
SanctionedAddress: "This wallet or the recipient is blocked by a sanctions check, so it cannot mint.",
|
|
780
|
+
EnforcedPause: "Minting is paused by Transient Labs right now. Try again later."
|
|
781
|
+
};
|
|
782
|
+
function revertData(e) {
|
|
783
|
+
for (let cur = e, depth = 0; cur && depth < 12; depth++) {
|
|
784
|
+
const d = cur.data;
|
|
785
|
+
if (typeof d === "string" && d.startsWith("0x") && d.length >= 10) return d;
|
|
786
|
+
if (d && typeof d === "object" && typeof d.data === "string") {
|
|
787
|
+
const inner = d.data;
|
|
788
|
+
if (inner.startsWith("0x") && inner.length >= 10) return inner;
|
|
1127
789
|
}
|
|
790
|
+
cur = cur.cause;
|
|
1128
791
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
// src/core/adapters/contract/index.ts
|
|
1132
|
-
import {
|
|
1133
|
-
BaseError as BaseError2,
|
|
1134
|
-
ContractFunctionRevertedError as ContractFunctionRevertedError2,
|
|
1135
|
-
ContractFunctionZeroDataError,
|
|
1136
|
-
InsufficientFundsError,
|
|
1137
|
-
decodeErrorResult as decodeErrorResult2,
|
|
1138
|
-
encodeFunctionData as encodeFunctionData2,
|
|
1139
|
-
formatUnits as formatUnits4,
|
|
1140
|
-
getAddress as getAddress4,
|
|
1141
|
-
isAddress as isAddress2,
|
|
1142
|
-
parseAbi as parseAbi2,
|
|
1143
|
-
parseUnits
|
|
1144
|
-
} from "viem";
|
|
1145
|
-
|
|
1146
|
-
// src/core/machine.ts
|
|
1147
|
-
import { formatUnits as formatUnits3 } from "viem";
|
|
1148
|
-
var MINT_STATES = [
|
|
1149
|
-
"loading",
|
|
1150
|
-
"unsupported",
|
|
1151
|
-
"upcoming",
|
|
1152
|
-
"ended",
|
|
1153
|
-
"sold-out",
|
|
1154
|
-
"disconnected",
|
|
1155
|
-
"connecting",
|
|
1156
|
-
"live",
|
|
1157
|
-
"ineligible",
|
|
1158
|
-
"insufficient-funds",
|
|
1159
|
-
"switching-chain",
|
|
1160
|
-
"approving",
|
|
1161
|
-
"minting",
|
|
1162
|
-
"confirming",
|
|
1163
|
-
"success",
|
|
1164
|
-
"error"
|
|
1165
|
-
];
|
|
1166
|
-
var CLICKABLE_STATES = ["disconnected", "live", "success", "error"];
|
|
1167
|
-
var IN_FLIGHT_STATES = ["switching-chain", "approving", "minting", "confirming"];
|
|
1168
|
-
var MAX_QUANTITY = 65535;
|
|
1169
|
-
function initialState(opts) {
|
|
1170
|
-
return {
|
|
1171
|
-
name: "loading",
|
|
1172
|
-
reason: null,
|
|
1173
|
-
message: null,
|
|
1174
|
-
readGen: 0,
|
|
1175
|
-
walletGen: 0,
|
|
1176
|
-
drop: null,
|
|
1177
|
-
account: null,
|
|
1178
|
-
walletRead: null,
|
|
1179
|
-
blocked: null,
|
|
1180
|
-
quantity: toQuantity(opts?.quantity ?? 1, MAX_QUANTITY),
|
|
1181
|
-
attempt: null,
|
|
1182
|
-
result: null,
|
|
1183
|
-
pendingRefresh: false,
|
|
1184
|
-
now: 0,
|
|
1185
|
-
crossChain: false,
|
|
1186
|
-
routePending: false,
|
|
1187
|
-
route: null,
|
|
1188
|
-
short: null
|
|
1189
|
-
};
|
|
792
|
+
return null;
|
|
1190
793
|
}
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
if (
|
|
1195
|
-
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
if (drop === null) return named("loading");
|
|
1208
|
-
if (drop.endTime !== null && drop.endTime <= s.now) return named("ended");
|
|
1209
|
-
if (drop.maxSupply !== null && drop.maxSupply > 0n && drop.totalMinted >= drop.maxSupply) return named("sold-out");
|
|
1210
|
-
if (drop.startTime !== null && drop.startTime > s.now) return named("upcoming");
|
|
1211
|
-
if (s.account === null) return named("disconnected");
|
|
1212
|
-
if (walletRead === null) return named("loading");
|
|
1213
|
-
if (s.blocked !== null) return s.blocked;
|
|
1214
|
-
if (walletRead.ineligible !== null) {
|
|
1215
|
-
return { name: "ineligible", reason: walletRead.ineligible.reason, message: walletRead.ineligible.message };
|
|
1216
|
-
}
|
|
1217
|
-
if (s.routePending) return named("loading");
|
|
1218
|
-
if (s.route?.kind === "relay") return named("live");
|
|
1219
|
-
if (s.short !== null) {
|
|
1220
|
-
return { name: "insufficient-funds", reason: "INSUFFICIENT_FUNDS", message: `Not enough ${s.short.symbol} on ${s.short.name}. ${PICK_ANOTHER}` };
|
|
1221
|
-
}
|
|
1222
|
-
const total = selectTotal(s);
|
|
1223
|
-
if (total !== null) {
|
|
1224
|
-
const { native, erc20 } = total;
|
|
1225
|
-
if (native.value + walletRead.gasReserve > walletRead.balances.native) {
|
|
1226
|
-
const more = s.crossChain ? ` ${PICK_ANOTHER}` : "";
|
|
1227
|
-
return { name: "insufficient-funds", reason: "INSUFFICIENT_FUNDS", message: `Not enough ${native.symbol} to cover the mint.${more}` };
|
|
1228
|
-
}
|
|
1229
|
-
if (erc20 !== null && walletRead.balances.erc20 !== null && erc20.value > walletRead.balances.erc20) {
|
|
1230
|
-
return { name: "insufficient-funds", reason: "INSUFFICIENT_FUNDS", message: `Not enough ${erc20.symbol} to cover the mint.` };
|
|
794
|
+
function decodeTlRevert(e) {
|
|
795
|
+
if (isMintError(e)) return e;
|
|
796
|
+
if (isAbortError(e)) throw e;
|
|
797
|
+
if (e instanceof BaseError && e.walk((x) => x instanceof InsufficientFundsError)) {
|
|
798
|
+
return new MintError("INSUFFICIENT_FUNDS", `The wallet cannot cover the mint: ${e.shortMessage}`, { cause: e });
|
|
799
|
+
}
|
|
800
|
+
const data = revertData(e);
|
|
801
|
+
if (data) {
|
|
802
|
+
try {
|
|
803
|
+
const { errorName, args } = decodeErrorResult({ abi: stacksAbi, data });
|
|
804
|
+
const known = TL_MESSAGES[errorName];
|
|
805
|
+
if (known) return new MintError("SIMULATION_REVERTED", known, { cause: e, domain: "tl-stacks" });
|
|
806
|
+
const shown = (args ?? []).map(String).join(", ");
|
|
807
|
+
return new MintError("SIMULATION_REVERTED", `Transient Labs rejected the mint (${errorName}(${shown})).`, { cause: e, domain: "tl-stacks" });
|
|
808
|
+
} catch {
|
|
809
|
+
return new MintError("SIMULATION_REVERTED", `Transient Labs rejected the mint (unrecognized error ${data.slice(0, 10)}).`, { cause: e, domain: "tl-stacks" });
|
|
1231
810
|
}
|
|
1232
811
|
}
|
|
1233
|
-
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
if (!isIdle(s)) return s;
|
|
1237
|
-
return { ...s, ...derive(s, unsupported) };
|
|
1238
|
-
}
|
|
1239
|
-
function clampQuantity(s) {
|
|
1240
|
-
const quantity = toQuantity(s.quantity, selectMaxQuantity(s) ?? MAX_QUANTITY);
|
|
1241
|
-
return quantity === s.quantity ? s : { ...s, quantity };
|
|
812
|
+
const reverted = e instanceof BaseError && e.walk((x) => x.code === 3 || /execution reverted/i.test(x.message));
|
|
813
|
+
if (reverted) return new MintError("SIMULATION_REVERTED", shortMessage(e), { cause: e });
|
|
814
|
+
return new MintError("RPC_ERROR", `RPC request failed: ${shortMessage(e)}`, { cause: e });
|
|
1242
815
|
}
|
|
1243
|
-
function
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
};
|
|
1250
|
-
switch (event.type) {
|
|
1251
|
-
case "LOAD": {
|
|
1252
|
-
if (event.kind === "refresh") {
|
|
1253
|
-
const next2 = { ...state, readGen: event.gen };
|
|
1254
|
-
return inFlight ? { ...next2, pendingRefresh: true } : settle(next2);
|
|
1255
|
-
}
|
|
1256
|
-
const next = { ...state, ...NO_ROUTE, readGen: event.gen, drop: null, walletRead: null, blocked: null, walletGen: state.walletGen + 1 };
|
|
1257
|
-
return inFlight ? { ...next, pendingRefresh: true } : settle(next, false);
|
|
1258
|
-
}
|
|
1259
|
-
case "DROP_LOADED": {
|
|
1260
|
-
if (event.gen !== state.readGen) return state;
|
|
1261
|
-
const next = { ...state, drop: event.drop, now: Math.max(state.now, event.now) };
|
|
1262
|
-
if (inFlight) return { ...next, pendingRefresh: true };
|
|
1263
|
-
return isIdle(next) ? settle(clampQuantity(next), false) : clampQuantity(next);
|
|
1264
|
-
}
|
|
1265
|
-
case "DROP_UNSUPPORTED": {
|
|
1266
|
-
if (event.gen !== state.readGen) return state;
|
|
1267
|
-
if (inFlight) return { ...state, pendingRefresh: true };
|
|
1268
|
-
if (state.name === "success" || state.name === "error") return state;
|
|
1269
|
-
return { ...state, name: "unsupported", reason: event.reason, message: event.message };
|
|
1270
|
-
}
|
|
1271
|
-
case "READ_FAILED":
|
|
1272
|
-
return event.gen !== state.readGen ? state : failed(event.reason, event.message);
|
|
1273
|
-
case "ACCOUNT_CHANGED": {
|
|
1274
|
-
const next = { ...state, ...NO_ROUTE, account: event.account, walletRead: null, blocked: null, walletGen: state.walletGen + 1 };
|
|
1275
|
-
if (inFlight) return { ...next, pendingRefresh: true };
|
|
1276
|
-
if (state.name === "connecting") return event.account === null ? next : settle({ ...next, name: "loading" }, false);
|
|
1277
|
-
return settle(next);
|
|
1278
|
-
}
|
|
1279
|
-
case "WALLET_LOADED": {
|
|
1280
|
-
if (event.gen !== state.walletGen) return state;
|
|
1281
|
-
const route = event.routePending ? { routePending: state.route === null && state.short === null, route: state.route, short: state.short } : { routePending: false, route: null, short: null };
|
|
1282
|
-
const next = { ...state, ...route, crossChain: event.crossChain ?? false, walletRead: event.read, blocked: null };
|
|
1283
|
-
return inFlight ? { ...next, pendingRefresh: true } : settle(clampQuantity(next));
|
|
1284
|
-
}
|
|
1285
|
-
case "ROUTE_LOADED": {
|
|
1286
|
-
if (event.gen !== state.walletGen) return state;
|
|
1287
|
-
const next = { ...state, routePending: false, route: event.route, short: event.short };
|
|
1288
|
-
return inFlight ? { ...next, pendingRefresh: true } : settle(next);
|
|
1289
|
-
}
|
|
1290
|
-
case "WALLET_FAILED":
|
|
1291
|
-
return event.gen !== state.walletGen ? state : failed(event.reason, event.message);
|
|
1292
|
-
case "CONNECT_STARTED":
|
|
1293
|
-
return state.name === "disconnected" && state.attempt === null ? { ...state, name: "connecting", reason: null, message: null } : state;
|
|
1294
|
-
case "CONNECT_DISMISSED":
|
|
1295
|
-
return state.name === "connecting" ? settle({ ...state, name: "disconnected" }, false) : state;
|
|
1296
|
-
case "TICK":
|
|
1297
|
-
return settle({ ...state, now: Math.max(state.now, event.now) });
|
|
1298
|
-
case "SET_QUANTITY": {
|
|
1299
|
-
const next = { ...state, quantity: toQuantity(event.quantity, selectMaxQuantity(state) ?? MAX_QUANTITY) };
|
|
1300
|
-
return inFlight ? { ...next, pendingRefresh: true } : settle(next);
|
|
1301
|
-
}
|
|
1302
|
-
case "ATTEMPT_STARTED":
|
|
1303
|
-
if (state.attempt !== null) return state;
|
|
1304
|
-
if (event.resume === true) return IN_FLIGHT_STATES.includes(state.name) ? state : { ...state, attempt: event.attempt };
|
|
1305
|
-
return state.name === "live" ? { ...state, attempt: event.attempt } : state;
|
|
1306
|
-
case "SWITCHING_CHAIN":
|
|
1307
|
-
return step(state, event.attemptId, "switching-chain");
|
|
1308
|
-
case "APPROVING":
|
|
1309
|
-
return step(state, event.attemptId, "approving");
|
|
1310
|
-
case "MINT_PROMPTED":
|
|
1311
|
-
return step(state, event.attemptId, "minting");
|
|
1312
|
-
case "TX_SUBMITTED":
|
|
1313
|
-
return step(state, event.attemptId, "confirming");
|
|
1314
|
-
case "ATTEMPT_SUCCEEDED":
|
|
1315
|
-
return state.attempt?.attemptId === event.attemptId ? {
|
|
1316
|
-
...state,
|
|
1317
|
-
name: "success",
|
|
1318
|
-
reason: null,
|
|
1319
|
-
message: null,
|
|
1320
|
-
attempt: null,
|
|
1321
|
-
result: event.result,
|
|
1322
|
-
drop: null,
|
|
1323
|
-
readGen: state.readGen + 1
|
|
1324
|
-
} : state;
|
|
1325
|
-
case "ATTEMPT_FAILED": {
|
|
1326
|
-
if (state.attempt?.attemptId !== event.attemptId) return state;
|
|
1327
|
-
const { reason, message } = event;
|
|
1328
|
-
const done = { ...state, reason, message, attempt: null, result: null };
|
|
1329
|
-
if (INELIGIBLE_REASONS.includes(reason)) {
|
|
1330
|
-
return { ...done, name: "ineligible", blocked: { name: "ineligible", reason, message } };
|
|
1331
|
-
}
|
|
1332
|
-
if (reason === "INSUFFICIENT_FUNDS") {
|
|
1333
|
-
return { ...done, name: "insufficient-funds", blocked: { name: "insufficient-funds", reason, message } };
|
|
1334
|
-
}
|
|
1335
|
-
return {
|
|
1336
|
-
...done,
|
|
1337
|
-
name: "error",
|
|
1338
|
-
drop: state.name === "confirming" ? null : state.drop,
|
|
1339
|
-
readGen: state.name === "confirming" ? state.readGen + 1 : state.readGen
|
|
1340
|
-
};
|
|
1341
|
-
}
|
|
1342
|
-
case "RESET": {
|
|
1343
|
-
if (state.name !== "success" && state.name !== "error") return state;
|
|
1344
|
-
const next = {
|
|
1345
|
-
...state,
|
|
1346
|
-
readGen: event.gen,
|
|
1347
|
-
...NO_ROUTE,
|
|
1348
|
-
walletRead: null,
|
|
1349
|
-
blocked: null,
|
|
1350
|
-
walletGen: state.walletGen + 1,
|
|
1351
|
-
result: null,
|
|
1352
|
-
pendingRefresh: false
|
|
1353
|
-
};
|
|
1354
|
-
return settle(clampQuantity({ ...next, name: "loading" }), false);
|
|
1355
|
-
}
|
|
816
|
+
async function simulate(ctx, attempt, plan) {
|
|
817
|
+
throwIfAborted(ctx);
|
|
818
|
+
try {
|
|
819
|
+
await io(ctx, () => ctx.client.call({ account: attempt.payer, to: plan.mint.to, data: plan.mint.data, value: plan.mint.value }));
|
|
820
|
+
} catch (e) {
|
|
821
|
+
throw decodeTlRevert(e);
|
|
1356
822
|
}
|
|
1357
823
|
}
|
|
1358
|
-
var
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
return max;
|
|
1376
|
-
}
|
|
1377
|
-
function selectTotal(state) {
|
|
1378
|
-
const { drop } = state;
|
|
1379
|
-
if (drop === null) return null;
|
|
1380
|
-
const q = BigInt(state.quantity);
|
|
1381
|
-
const price = drop.unitPrice;
|
|
1382
|
-
const fee = drop.unitFee;
|
|
1383
|
-
const priceTotal = drop.pricePer === "total" ? price.value : price.value * q;
|
|
1384
|
-
if (drop.paymentToken !== null) {
|
|
1385
|
-
return {
|
|
1386
|
-
native: money2(fee.value * q, fee),
|
|
1387
|
-
erc20: money2(priceTotal, price)
|
|
1388
|
-
};
|
|
824
|
+
var tlStacksAdapter = { readDrop, readWallet, buildPlan, simulate };
|
|
825
|
+
|
|
826
|
+
// src/core/relay/outstanding.ts
|
|
827
|
+
var memory = /* @__PURE__ */ new Map();
|
|
828
|
+
var claimed = /* @__PURE__ */ new Set();
|
|
829
|
+
function targetOf(config) {
|
|
830
|
+
switch (config.type) {
|
|
831
|
+
case "manifold-claim":
|
|
832
|
+
return `${config.creatorContract.toLowerCase()}:${config.instanceId}`;
|
|
833
|
+
case "contract":
|
|
834
|
+
return config.address.toLowerCase();
|
|
835
|
+
case "tl-stacks":
|
|
836
|
+
return `stacks:${config.nftContract.toLowerCase()}`;
|
|
837
|
+
default: {
|
|
838
|
+
const unreachable = config;
|
|
839
|
+
return unreachable;
|
|
840
|
+
}
|
|
1389
841
|
}
|
|
1390
|
-
return { native: money2(priceTotal + fee.value * q, price), erc20: null };
|
|
1391
842
|
}
|
|
1392
|
-
function
|
|
1393
|
-
return {
|
|
843
|
+
function outstandingKey(config, payer) {
|
|
844
|
+
return `${String(config.chainId)}:${targetOf(config)}:${payer.toLowerCase()}`;
|
|
1394
845
|
}
|
|
1395
|
-
|
|
1396
|
-
|
|
846
|
+
var getOutstanding = (key) => memory.get(key) ?? null;
|
|
847
|
+
var setOutstanding = (key, out) => void memory.set(key, out);
|
|
848
|
+
var clearOutstanding = (key) => void memory.delete(key);
|
|
849
|
+
function claimDeposit(key) {
|
|
850
|
+
if (claimed.has(key) || memory.has(key)) return false;
|
|
851
|
+
claimed.add(key);
|
|
852
|
+
return true;
|
|
1397
853
|
}
|
|
854
|
+
var releaseDeposit = (key) => void claimed.delete(key);
|
|
1398
855
|
|
|
1399
856
|
// src/core/adapters/contract/index.ts
|
|
857
|
+
import {
|
|
858
|
+
BaseError as BaseError2,
|
|
859
|
+
ContractFunctionRevertedError as ContractFunctionRevertedError2,
|
|
860
|
+
ContractFunctionZeroDataError as ContractFunctionZeroDataError2,
|
|
861
|
+
InsufficientFundsError as InsufficientFundsError2,
|
|
862
|
+
decodeErrorResult as decodeErrorResult2,
|
|
863
|
+
encodeFunctionData as encodeFunctionData2,
|
|
864
|
+
formatUnits as formatUnits3,
|
|
865
|
+
getAddress as getAddress4,
|
|
866
|
+
isAddress as isAddress2,
|
|
867
|
+
parseAbi,
|
|
868
|
+
parseUnits
|
|
869
|
+
} from "viem";
|
|
1400
870
|
var PLACEHOLDERS = ["{recipient}", "{payer}", "{quantity}", "{unitPrice}", "{totalPrice}"];
|
|
1401
|
-
var
|
|
871
|
+
var ERC20_ABI = parseAbi([
|
|
1402
872
|
"function symbol() view returns (string)",
|
|
1403
873
|
"function decimals() view returns (uint8)",
|
|
1404
874
|
"function balanceOf(address) view returns (uint256)",
|
|
@@ -1410,7 +880,7 @@ var APPROVE_GAS = 65000n;
|
|
|
1410
880
|
var PLACEHOLDER_TOKEN = /\{[A-Za-z_]\w*\}/g;
|
|
1411
881
|
var DISPLAY_AMOUNT = /^\d+(\.\d+)?$/;
|
|
1412
882
|
var NUMERIC_TYPE = /^u?int\d*$/;
|
|
1413
|
-
function
|
|
883
|
+
function bad2(field, message) {
|
|
1414
884
|
throw new MintError("INVALID_CONFIG", `${field} ${message}`, { field });
|
|
1415
885
|
}
|
|
1416
886
|
var isHexAddress = (a) => typeof a === "string" && isAddress2(a, { strict: false });
|
|
@@ -1420,15 +890,15 @@ function functionsNamed(abi, name) {
|
|
|
1420
890
|
function validateTemplate(t, path, allowed) {
|
|
1421
891
|
if (typeof t === "string") {
|
|
1422
892
|
for (const token of t.match(PLACEHOLDER_TOKEN) ?? []) {
|
|
1423
|
-
if (!PLACEHOLDERS.includes(token))
|
|
1424
|
-
if (!allowed.includes(token))
|
|
893
|
+
if (!PLACEHOLDERS.includes(token)) bad2(path, `"${t}" is not a known placeholder (${token})`);
|
|
894
|
+
if (!allowed.includes(token)) bad2(path, `"${t}" uses ${token}, which is not available in this field`);
|
|
1425
895
|
}
|
|
1426
896
|
} else if (Array.isArray(t)) {
|
|
1427
897
|
t.forEach((x, i) => validateTemplate(x, `${path}[${i}]`, allowed));
|
|
1428
898
|
} else if (typeof t === "number") {
|
|
1429
|
-
if (!Number.isSafeInteger(t))
|
|
899
|
+
if (!Number.isSafeInteger(t)) bad2(path, `${t} is not a safe integer; pass larger or fractional values as a bigint or decimal string`);
|
|
1430
900
|
} else if (typeof t !== "bigint" && typeof t !== "boolean") {
|
|
1431
|
-
|
|
901
|
+
bad2(path, "must be a string, number, bigint, boolean or array");
|
|
1432
902
|
}
|
|
1433
903
|
}
|
|
1434
904
|
var ARRAY_TYPE = /^(.*)\[(\d*)\]$/;
|
|
@@ -1496,15 +966,15 @@ var NO_PLACEHOLDERS = [];
|
|
|
1496
966
|
function validateCall(abi, call, prefix, kind, allowed) {
|
|
1497
967
|
const c = call ?? {};
|
|
1498
968
|
const named = typeof c.functionName === "string" ? functionsNamed(abi, c.functionName) : [];
|
|
1499
|
-
if (named.length === 0)
|
|
1500
|
-
if (c.args !== void 0 && !Array.isArray(c.args))
|
|
1501
|
-
if (kind === "mint" && c.args === void 0)
|
|
969
|
+
if (named.length === 0) bad2(`${prefix}.functionName`, `"${String(c.functionName)}" is not a function in the abi`);
|
|
970
|
+
if (c.args !== void 0 && !Array.isArray(c.args)) bad2(`${prefix}.args`, "must be an array");
|
|
971
|
+
if (kind === "mint" && c.args === void 0) bad2(`${prefix}.args`, "is required");
|
|
1502
972
|
const args = c.args ?? [];
|
|
1503
973
|
args.forEach((a, i) => validateTemplate(a, `${prefix}.args[${i}]`, allowed));
|
|
1504
974
|
const sameArity = named.filter((f) => f.inputs.length === args.length);
|
|
1505
975
|
if (sameArity.length === 0) {
|
|
1506
976
|
const want = [...new Set(named.map((f) => f.inputs.length))].join(" or ");
|
|
1507
|
-
|
|
977
|
+
bad2(`${prefix}.args`, `has ${args.length} argument(s) but "${String(c.functionName)}" takes ${want}`);
|
|
1508
978
|
}
|
|
1509
979
|
const checked = sameArity.map((f) => {
|
|
1510
980
|
let issue = null;
|
|
@@ -1514,75 +984,75 @@ function validateCall(abi, call, prefix, kind, allowed) {
|
|
|
1514
984
|
const fits = checked.filter((x) => !x.issue);
|
|
1515
985
|
if (fits.length === 0) {
|
|
1516
986
|
const first = checked[0].issue;
|
|
1517
|
-
if (sameArity.length === 1)
|
|
1518
|
-
|
|
987
|
+
if (sameArity.length === 1) bad2(first.path, first.message);
|
|
988
|
+
bad2(`${prefix}.args`, `fit none of the overloads ${sameArity.map(signature).join(", ")}: ${first.path} ${first.message}`);
|
|
1519
989
|
}
|
|
1520
990
|
const roleFits = fits.filter(
|
|
1521
991
|
({ f }) => kind === "read" ? f.stateMutability === "view" || f.stateMutability === "pure" : f.stateMutability === "payable" || f.stateMutability === "nonpayable"
|
|
1522
992
|
);
|
|
1523
993
|
if (roleFits.length === 0) {
|
|
1524
994
|
const name = fits[0].f.name;
|
|
1525
|
-
if (kind === "read")
|
|
995
|
+
if (kind === "read") bad2(`${prefix}.functionName`, `"${name}" is not a view or pure function`);
|
|
1526
996
|
if (fits.some(({ f }) => f.stateMutability !== "view" && f.stateMutability !== "pure")) {
|
|
1527
|
-
|
|
997
|
+
bad2(`${prefix}.functionName`, `"${name}" has missing or invalid stateMutability; mint requires payable or nonpayable`);
|
|
1528
998
|
}
|
|
1529
|
-
|
|
999
|
+
bad2(`${prefix}.functionName`, `"${name}" is a view or pure function and cannot mint`);
|
|
1530
1000
|
}
|
|
1531
1001
|
if (roleFits.length > 1) {
|
|
1532
|
-
|
|
1002
|
+
bad2(`${prefix}.functionName`, `"${String(c.functionName)}" is ambiguous: ${roleFits.map((x) => signature(x.f)).join(" and ")} both accept these arguments`);
|
|
1533
1003
|
}
|
|
1534
1004
|
return roleFits[0].f;
|
|
1535
1005
|
}
|
|
1536
1006
|
function resolveOutput(fn, output, path) {
|
|
1537
1007
|
const outs = fn.outputs;
|
|
1538
|
-
if (outs.length === 0)
|
|
1008
|
+
if (outs.length === 0) bad2(`${path}.functionName`, `"${fn.name}" returns nothing`);
|
|
1539
1009
|
const first = outs[0];
|
|
1540
1010
|
const tuple = outs.length === 1 && first.type === "tuple";
|
|
1541
1011
|
const members = tuple ? first.components ?? [] : outs;
|
|
1542
1012
|
const memberNames = members.map((m, i) => m.name || String(i)).join(", ");
|
|
1543
1013
|
let index;
|
|
1544
1014
|
if (output === void 0) {
|
|
1545
|
-
if (members.length !== 1)
|
|
1015
|
+
if (members.length !== 1) bad2(`${path}.output`, `is required: "${fn.name}" returns several values (${memberNames})`);
|
|
1546
1016
|
index = 0;
|
|
1547
1017
|
} else if (typeof output === "number") {
|
|
1548
1018
|
if (!Number.isInteger(output) || output < 0 || output >= members.length) {
|
|
1549
|
-
|
|
1019
|
+
bad2(`${path}.output`, `${output} is not in the abi outputs of "${fn.name}" (${memberNames})`);
|
|
1550
1020
|
}
|
|
1551
1021
|
index = output;
|
|
1552
1022
|
} else if (typeof output === "string") {
|
|
1553
1023
|
index = members.findIndex((m) => m.name === output);
|
|
1554
|
-
if (index < 0)
|
|
1024
|
+
if (index < 0) bad2(`${path}.output`, `"${output}" is not in the abi outputs of "${fn.name}" (${memberNames})`);
|
|
1555
1025
|
} else {
|
|
1556
|
-
return
|
|
1026
|
+
return bad2(`${path}.output`, "must be an index or a name");
|
|
1557
1027
|
}
|
|
1558
1028
|
const type = members[index].type;
|
|
1559
1029
|
if (!NUMERIC_TYPE.test(type)) {
|
|
1560
1030
|
const reason = `returns ${type}, not a number`;
|
|
1561
|
-
if (output === void 0)
|
|
1562
|
-
|
|
1031
|
+
if (output === void 0) bad2(`${path}.functionName`, `"${fn.name}" ${reason}`);
|
|
1032
|
+
bad2(`${path}.output`, `selects a value that is ${type}, not a number`);
|
|
1563
1033
|
}
|
|
1564
1034
|
return { tuple, index, type };
|
|
1565
1035
|
}
|
|
1566
1036
|
function validateField(abi, name, src, allowed, isPrice) {
|
|
1567
|
-
if (typeof src !== "object" || src === null)
|
|
1037
|
+
if (typeof src !== "object" || src === null) bad2(name, "is required and must be { value } or { read }");
|
|
1568
1038
|
const s = src;
|
|
1569
1039
|
const hasRead = Object.hasOwn(s, "read");
|
|
1570
1040
|
const hasValue = Object.hasOwn(s, "value");
|
|
1571
|
-
if (hasRead && hasValue)
|
|
1041
|
+
if (hasRead && hasValue) bad2(name, "must be exactly one of { value } or { read }, not both");
|
|
1572
1042
|
if (hasRead) {
|
|
1573
|
-
if (typeof s.read !== "object" || s.read === null)
|
|
1574
|
-
if (s.unit !== void 0)
|
|
1043
|
+
if (typeof s.read !== "object" || s.read === null) bad2(`${name}.read`, "must be { functionName, args?, output? }");
|
|
1044
|
+
if (s.unit !== void 0) bad2(name, "unit is only valid with a string value");
|
|
1575
1045
|
const fn = validateCall(abi, s.read, `${name}.read`, "read", allowed);
|
|
1576
1046
|
resolveOutput(fn, s.read.output, `${name}.read`);
|
|
1577
1047
|
} else if (typeof s.value === "bigint") {
|
|
1578
|
-
if (s.unit !== void 0)
|
|
1579
|
-
if (s.value < 0n)
|
|
1048
|
+
if (s.unit !== void 0) bad2(name, "unit is only valid with a string value");
|
|
1049
|
+
if (s.value < 0n) bad2(`${name}.value`, "must not be negative");
|
|
1580
1050
|
} else if (typeof s.value === "string") {
|
|
1581
|
-
if (s.unit !== "display")
|
|
1582
|
-
if (!isPrice)
|
|
1583
|
-
if (!DISPLAY_AMOUNT.test(s.value))
|
|
1051
|
+
if (s.unit !== "display") bad2(name, 'string values need unit: "display"');
|
|
1052
|
+
if (!isPrice) bad2(name, "display units are only valid for price; use a bigint in base units");
|
|
1053
|
+
if (!DISPLAY_AMOUNT.test(s.value)) bad2(`${name}.value`, `"${s.value}" is not a decimal number like "0.01"`);
|
|
1584
1054
|
} else {
|
|
1585
|
-
|
|
1055
|
+
bad2(name, 'must be { value: bigint }, { value: string, unit: "display" } (price only) or { read: {...} }');
|
|
1586
1056
|
}
|
|
1587
1057
|
}
|
|
1588
1058
|
function hasNonzeroStaticPrice(price) {
|
|
@@ -1592,27 +1062,27 @@ function hasNonzeroStaticPrice(price) {
|
|
|
1592
1062
|
}
|
|
1593
1063
|
function validateContractConfig(config) {
|
|
1594
1064
|
const c = config;
|
|
1595
|
-
if (!c || typeof c !== "object")
|
|
1596
|
-
if (c.type !== "contract")
|
|
1597
|
-
if (!Number.isSafeInteger(c.chainId) || c.chainId <= 0)
|
|
1598
|
-
if (!isHexAddress(c.address))
|
|
1065
|
+
if (!c || typeof c !== "object") bad2("config", "must be an object");
|
|
1066
|
+
if (c.type !== "contract") bad2("type", 'must be "contract"');
|
|
1067
|
+
if (!Number.isSafeInteger(c.chainId) || c.chainId <= 0) bad2("chainId", "must be a positive safe integer");
|
|
1068
|
+
if (!isHexAddress(c.address)) bad2("address", `"${String(c.address)}" is not a valid address`);
|
|
1599
1069
|
if (c.tokenContract !== void 0 && !isHexAddress(c.tokenContract)) {
|
|
1600
|
-
|
|
1070
|
+
bad2("tokenContract", `"${String(c.tokenContract)}" is not a valid address`);
|
|
1601
1071
|
}
|
|
1602
|
-
if (!Array.isArray(c.abi))
|
|
1072
|
+
if (!Array.isArray(c.abi)) bad2("abi", "must be an abi array");
|
|
1603
1073
|
const abi = c.abi;
|
|
1604
1074
|
const mintFn = validateCall(abi, c.mint, "mint", "mint", PLACEHOLDERS);
|
|
1605
|
-
if (c.price === void 0 || c.price === null)
|
|
1075
|
+
if (c.price === void 0 || c.price === null) bad2("price", "is required");
|
|
1606
1076
|
validateField(abi, "price", c.price, NO_PLACEHOLDERS, true);
|
|
1607
1077
|
const cur = c.price.currency;
|
|
1608
1078
|
if (cur !== "native" && !(typeof cur === "object" && cur !== null && isHexAddress(cur.address))) {
|
|
1609
|
-
|
|
1079
|
+
bad2("price.currency", 'must be "native" or { address } with a valid ERC20 address');
|
|
1610
1080
|
}
|
|
1611
1081
|
if (cur === "native" && mintFn.stateMutability === "nonpayable" && hasNonzeroStaticPrice(c.price)) {
|
|
1612
|
-
|
|
1082
|
+
bad2("price", `is a native price, but "${mintFn.name}" is nonpayable and cannot take value`);
|
|
1613
1083
|
}
|
|
1614
1084
|
const per = c.price.per;
|
|
1615
|
-
if (per !== void 0 && per !== "unit" && per !== "total")
|
|
1085
|
+
if (per !== void 0 && per !== "unit" && per !== "total") bad2("price.per", 'must be "unit" or "total"');
|
|
1616
1086
|
validateOptional(abi, "totalMinted", c.totalMinted, NO_PLACEHOLDERS);
|
|
1617
1087
|
validateOptional(abi, "maxSupply", c.maxSupply, NO_PLACEHOLDERS);
|
|
1618
1088
|
validateOptional(abi, "startTime", c.startTime, NO_PLACEHOLDERS);
|
|
@@ -1642,38 +1112,38 @@ function fill(t, p, vars) {
|
|
|
1642
1112
|
}
|
|
1643
1113
|
var fillArgs = (args, inputs, vars) => (args ?? []).map((a, i) => fill(a, inputs[i], vars));
|
|
1644
1114
|
var totalFor = (per, price, quantity) => per === "total" ? price : price * quantity;
|
|
1645
|
-
function
|
|
1115
|
+
function shortMessage2(e) {
|
|
1646
1116
|
return e instanceof BaseError2 ? e.shortMessage : e instanceof Error ? e.message : String(e);
|
|
1647
1117
|
}
|
|
1648
|
-
var
|
|
1649
|
-
var
|
|
1650
|
-
function
|
|
1651
|
-
if (ctx.signal?.aborted) throw
|
|
1118
|
+
var abortError2 = () => new DOMException("The operation was aborted", "AbortError");
|
|
1119
|
+
var isAbortError2 = (e) => e?.name === "AbortError";
|
|
1120
|
+
function throwIfAborted2(ctx) {
|
|
1121
|
+
if (ctx.signal?.aborted) throw abortError2();
|
|
1652
1122
|
}
|
|
1653
|
-
function
|
|
1123
|
+
function io2(ctx, start) {
|
|
1654
1124
|
const signal = ctx.signal;
|
|
1655
1125
|
if (!signal) return start();
|
|
1656
|
-
if (signal.aborted) return Promise.reject(
|
|
1126
|
+
if (signal.aborted) return Promise.reject(abortError2());
|
|
1657
1127
|
return new Promise((resolve, reject) => {
|
|
1658
|
-
const onAbort = () => reject(
|
|
1128
|
+
const onAbort = () => reject(abortError2());
|
|
1659
1129
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
1660
1130
|
start().then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
|
1661
1131
|
});
|
|
1662
1132
|
}
|
|
1663
|
-
function
|
|
1664
|
-
if (isMintError(e) ||
|
|
1665
|
-
throw new MintError("RPC_ERROR", `RPC request failed: ${
|
|
1133
|
+
function rpcError2(e) {
|
|
1134
|
+
if (isMintError(e) || isAbortError2(e)) throw e;
|
|
1135
|
+
throw new MintError("RPC_ERROR", `RPC request failed: ${shortMessage2(e)}`, { cause: e });
|
|
1666
1136
|
}
|
|
1667
1137
|
function classifyReadError(e, field) {
|
|
1668
|
-
if (isMintError(e) ||
|
|
1669
|
-
if (e instanceof BaseError2 && e.walk((x) => x instanceof ContractFunctionRevertedError2 || x instanceof
|
|
1138
|
+
if (isMintError(e) || isAbortError2(e)) throw e;
|
|
1139
|
+
if (e instanceof BaseError2 && e.walk((x) => x instanceof ContractFunctionRevertedError2 || x instanceof ContractFunctionZeroDataError2)) {
|
|
1670
1140
|
throw new MintError("INVALID_CONFIG", `${field} read reverted or returned no data: ${e.shortMessage}`, { field, cause: e });
|
|
1671
1141
|
}
|
|
1672
|
-
return
|
|
1142
|
+
return rpcError2(e);
|
|
1673
1143
|
}
|
|
1674
1144
|
async function readCall(ctx, params, field) {
|
|
1675
1145
|
try {
|
|
1676
|
-
return await
|
|
1146
|
+
return await io2(ctx, () => ctx.client.readContract(params));
|
|
1677
1147
|
} catch (e) {
|
|
1678
1148
|
return classifyReadError(e, field);
|
|
1679
1149
|
}
|
|
@@ -1712,18 +1182,18 @@ function toSeconds(v, field) {
|
|
|
1712
1182
|
if (v > BigInt(Number.MAX_SAFE_INTEGER)) throw new MintError("INVALID_CONFIG", `${field} is too large for a unix timestamp`, { field });
|
|
1713
1183
|
return Number(v);
|
|
1714
1184
|
}
|
|
1715
|
-
function
|
|
1185
|
+
function nativeMeta2(ctx) {
|
|
1716
1186
|
const n = ctx.client.chain?.nativeCurrency;
|
|
1717
1187
|
return { decimals: n?.decimals ?? 18, symbol: n?.symbol ?? "ETH" };
|
|
1718
1188
|
}
|
|
1719
1189
|
var erc20Of = (config) => config.price.currency === "native" ? null : getAddress4(config.price.currency.address);
|
|
1720
1190
|
async function currencyMeta(ctx, config) {
|
|
1721
1191
|
const token = erc20Of(config);
|
|
1722
|
-
if (!token) return { token, ...
|
|
1192
|
+
if (!token) return { token, ...nativeMeta2(ctx) };
|
|
1723
1193
|
const field = "price.currency.address";
|
|
1724
1194
|
const [symbol, decimals] = await Promise.all([
|
|
1725
|
-
readCall(ctx, { address: token, abi:
|
|
1726
|
-
readCall(ctx, { address: token, abi:
|
|
1195
|
+
readCall(ctx, { address: token, abi: ERC20_ABI, functionName: "symbol" }, field),
|
|
1196
|
+
readCall(ctx, { address: token, abi: ERC20_ABI, functionName: "decimals" }, field)
|
|
1727
1197
|
]);
|
|
1728
1198
|
if (typeof symbol !== "string" || typeof decimals !== "number") {
|
|
1729
1199
|
throw new MintError("INVALID_CONFIG", `${field} ${token} is not an ERC20 (symbol/decimals unreadable)`, { field });
|
|
@@ -1744,11 +1214,11 @@ async function resolvePrice(ctx, config, meta) {
|
|
|
1744
1214
|
if (typeof value === "bigint") return value;
|
|
1745
1215
|
return parseDisplay(value, (meta ?? await currencyMeta(ctx, config)).decimals);
|
|
1746
1216
|
}
|
|
1747
|
-
var
|
|
1217
|
+
var money2 = (value, decimals, symbol) => ({
|
|
1748
1218
|
value,
|
|
1749
1219
|
decimals,
|
|
1750
1220
|
symbol,
|
|
1751
|
-
formatted:
|
|
1221
|
+
formatted: formatUnits3(value, decimals)
|
|
1752
1222
|
});
|
|
1753
1223
|
function revertData2(e) {
|
|
1754
1224
|
for (let cur = e, depth = 0; cur && depth < 12; depth++) {
|
|
@@ -1776,19 +1246,19 @@ function describeRevert(abi, data) {
|
|
|
1776
1246
|
}
|
|
1777
1247
|
function toSimulationError(e, abi) {
|
|
1778
1248
|
if (isMintError(e)) return e;
|
|
1779
|
-
if (
|
|
1780
|
-
if (e instanceof BaseError2 && e.walk((x) => x instanceof
|
|
1249
|
+
if (isAbortError2(e)) throw e;
|
|
1250
|
+
if (e instanceof BaseError2 && e.walk((x) => x instanceof InsufficientFundsError2)) {
|
|
1781
1251
|
return new MintError("INSUFFICIENT_FUNDS", `Payer cannot cover the mint: ${e.shortMessage}`, { cause: e });
|
|
1782
1252
|
}
|
|
1783
1253
|
const data = revertData2(e);
|
|
1784
1254
|
if (data) return new MintError("SIMULATION_REVERTED", describeRevert(abi, data), { cause: e });
|
|
1785
1255
|
const isRevert = e instanceof BaseError2 && e.walk((x) => x.code === 3 || /execution reverted/i.test(x.message));
|
|
1786
|
-
if (isRevert) return new MintError("SIMULATION_REVERTED",
|
|
1787
|
-
return new MintError("RPC_ERROR", `RPC request failed: ${
|
|
1256
|
+
if (isRevert) return new MintError("SIMULATION_REVERTED", shortMessage2(e), { cause: e });
|
|
1257
|
+
return new MintError("RPC_ERROR", `RPC request failed: ${shortMessage2(e)}`, { cause: e });
|
|
1788
1258
|
}
|
|
1789
1259
|
async function simulateMint(ctx, config, call) {
|
|
1790
1260
|
try {
|
|
1791
|
-
await
|
|
1261
|
+
await io2(ctx, () => ctx.client.call({ account: call.payer, to: call.to, data: call.data, value: call.value }));
|
|
1792
1262
|
} catch (e) {
|
|
1793
1263
|
throw toSimulationError(e, config.abi);
|
|
1794
1264
|
}
|
|
@@ -1799,21 +1269,21 @@ function encodeMint(config, vars) {
|
|
|
1799
1269
|
return encodeFunctionData2({ abi: [fn], functionName: fn.name, args: fillArgs(config.mint.args, fn.inputs, vars) });
|
|
1800
1270
|
} catch (e) {
|
|
1801
1271
|
if (isMintError(e)) throw e;
|
|
1802
|
-
throw new MintError("INVALID_CONFIG", `mint.args cannot be encoded for ${signature(fn)}: ${
|
|
1272
|
+
throw new MintError("INVALID_CONFIG", `mint.args cannot be encoded for ${signature(fn)}: ${shortMessage2(e)}`, { field: "mint.args", cause: e });
|
|
1803
1273
|
}
|
|
1804
1274
|
}
|
|
1805
1275
|
function mintValue(config, token, totalPrice) {
|
|
1806
1276
|
if (token) return 0n;
|
|
1807
1277
|
const fn = validateCall(config.abi, config.mint, "mint", "mint", PLACEHOLDERS);
|
|
1808
1278
|
if (fn.stateMutability === "nonpayable") {
|
|
1809
|
-
if (totalPrice > 0n)
|
|
1279
|
+
if (totalPrice > 0n) bad2("price", `is ${totalPrice} wei in native currency, but "${fn.name}" is nonpayable and cannot take value`);
|
|
1810
1280
|
return 0n;
|
|
1811
1281
|
}
|
|
1812
1282
|
return totalPrice;
|
|
1813
1283
|
}
|
|
1814
|
-
async function
|
|
1284
|
+
async function readDrop2(ctx, config) {
|
|
1815
1285
|
validateContractConfig(config);
|
|
1816
|
-
|
|
1286
|
+
throwIfAborted2(ctx);
|
|
1817
1287
|
const meta = await currencyMeta(ctx, config);
|
|
1818
1288
|
const none = {};
|
|
1819
1289
|
const [price, totalMinted, maxSupply, start, end] = await Promise.all([
|
|
@@ -1824,11 +1294,11 @@ async function readDrop(ctx, config) {
|
|
|
1824
1294
|
optField(ctx, config, "endTime", config.endTime, none)
|
|
1825
1295
|
]);
|
|
1826
1296
|
mintValue(config, meta.token, price);
|
|
1827
|
-
const native =
|
|
1297
|
+
const native = nativeMeta2(ctx);
|
|
1828
1298
|
return {
|
|
1829
1299
|
chainId: config.chainId,
|
|
1830
|
-
unitPrice:
|
|
1831
|
-
unitFee:
|
|
1300
|
+
unitPrice: money2(price, meta.decimals, meta.symbol),
|
|
1301
|
+
unitFee: money2(0n, native.decimals, native.symbol),
|
|
1832
1302
|
totalMinted: totalMinted ?? 0n,
|
|
1833
1303
|
maxSupply: maxSupply === null || maxSupply === 0n ? null : maxSupply,
|
|
1834
1304
|
startTime: toSeconds(start, "startTime"),
|
|
@@ -1838,9 +1308,9 @@ async function readDrop(ctx, config) {
|
|
|
1838
1308
|
pricePer: config.price.per ?? "unit"
|
|
1839
1309
|
};
|
|
1840
1310
|
}
|
|
1841
|
-
async function
|
|
1311
|
+
async function readWallet2(ctx, config, { payer, recipient, quantity, drop }) {
|
|
1842
1312
|
validateContractConfig(config);
|
|
1843
|
-
|
|
1313
|
+
throwIfAborted2(ctx);
|
|
1844
1314
|
const qty = BigInt(quantity);
|
|
1845
1315
|
const unitPrice = drop.unitPrice.value;
|
|
1846
1316
|
const totalPrice = totalFor(drop.pricePer, unitPrice, qty);
|
|
@@ -1848,8 +1318,8 @@ async function readWallet(ctx, config, { payer, recipient, quantity, drop }) {
|
|
|
1848
1318
|
const [walletMinted, rawMax, native, erc20] = await Promise.all([
|
|
1849
1319
|
optField(ctx, config, "walletMinted", config.walletMinted, vars),
|
|
1850
1320
|
optField(ctx, config, "walletMax", config.walletMax, vars),
|
|
1851
|
-
|
|
1852
|
-
drop.paymentToken ? readCall(ctx, { address: drop.paymentToken, abi:
|
|
1321
|
+
io2(ctx, () => ctx.client.getBalance({ address: payer })).catch(rpcError2),
|
|
1322
|
+
drop.paymentToken ? readCall(ctx, { address: drop.paymentToken, abi: ERC20_ABI, functionName: "balanceOf", args: [payer] }, "price.currency.address").then(
|
|
1853
1323
|
(v) => toBigInt(v, "price.currency.address")
|
|
1854
1324
|
) : Promise.resolve(null)
|
|
1855
1325
|
]);
|
|
@@ -1864,7 +1334,7 @@ async function readWallet(ctx, config, { payer, recipient, quantity, drop }) {
|
|
|
1864
1334
|
const call = { payer, to: getAddress4(config.address), data: encodeMint(config, vars), value: mintValue(config, null, totalPrice) };
|
|
1865
1335
|
try {
|
|
1866
1336
|
await simulateMint(ctx, config, call);
|
|
1867
|
-
gas = await
|
|
1337
|
+
gas = await io2(ctx, () => ctx.client.estimateGas({ account: payer, to: call.to, data: call.data, value: call.value })).catch((e) => {
|
|
1868
1338
|
throw toSimulationError(e, config.abi);
|
|
1869
1339
|
});
|
|
1870
1340
|
} catch (e) {
|
|
@@ -1873,7 +1343,7 @@ async function readWallet(ctx, config, { payer, recipient, quantity, drop }) {
|
|
|
1873
1343
|
}
|
|
1874
1344
|
}
|
|
1875
1345
|
if (drop.paymentToken) gas += APPROVE_GAS;
|
|
1876
|
-
const gasPrice = await
|
|
1346
|
+
const gasPrice = await io2(ctx, () => ctx.client.getGasPrice()).catch(rpcError2);
|
|
1877
1347
|
return {
|
|
1878
1348
|
walletMinted,
|
|
1879
1349
|
walletMax,
|
|
@@ -1888,7 +1358,7 @@ async function buildPlan2(ctx, attempt, wallet) {
|
|
|
1888
1358
|
void wallet;
|
|
1889
1359
|
const { config, payer, recipient } = attempt;
|
|
1890
1360
|
validateContractConfig(config);
|
|
1891
|
-
|
|
1361
|
+
throwIfAborted2(ctx);
|
|
1892
1362
|
const qty = BigInt(attempt.quantity);
|
|
1893
1363
|
const unitPrice = await resolvePrice(ctx, config);
|
|
1894
1364
|
const totalPrice = totalFor(config.price.per ?? "unit", unitPrice, qty);
|
|
@@ -1902,7 +1372,7 @@ async function buildPlan2(ctx, attempt, wallet) {
|
|
|
1902
1372
|
let approve = null;
|
|
1903
1373
|
if (token) {
|
|
1904
1374
|
const allowance = toBigInt(
|
|
1905
|
-
await readCall(ctx, { address: token, abi:
|
|
1375
|
+
await readCall(ctx, { address: token, abi: ERC20_ABI, functionName: "allowance", args: [payer, to] }, "price.currency.address"),
|
|
1906
1376
|
"price.currency.address"
|
|
1907
1377
|
);
|
|
1908
1378
|
if (allowance < totalPrice) {
|
|
@@ -1910,134 +1380,17 @@ async function buildPlan2(ctx, attempt, wallet) {
|
|
|
1910
1380
|
token,
|
|
1911
1381
|
spender: to,
|
|
1912
1382
|
amount: totalPrice,
|
|
1913
|
-
tx: { to: token, data: encodeFunctionData2({ abi:
|
|
1383
|
+
tx: { to: token, data: encodeFunctionData2({ abi: ERC20_ABI, functionName: "approve", args: [to, totalPrice] }), value: 0n }
|
|
1914
1384
|
};
|
|
1915
1385
|
}
|
|
1916
1386
|
}
|
|
1917
1387
|
return { approve, mint, tokenContract: getAddress4(attempt.tokenContract) };
|
|
1918
1388
|
}
|
|
1919
|
-
async function
|
|
1920
|
-
|
|
1389
|
+
async function simulate2(ctx, attempt, plan) {
|
|
1390
|
+
throwIfAborted2(ctx);
|
|
1921
1391
|
await simulateMint(ctx, attempt.config, { payer: attempt.payer, to: plan.mint.to, data: plan.mint.data, value: plan.mint.value });
|
|
1922
1392
|
}
|
|
1923
|
-
var contractAdapter = { readDrop, readWallet, buildPlan: buildPlan2, simulate };
|
|
1924
|
-
|
|
1925
|
-
// src/core/adapters/receipt.ts
|
|
1926
|
-
import { decodeAbiParameters, getAddress as getAddress5, zeroAddress as zeroAddress3 } from "viem";
|
|
1927
|
-
var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
1928
|
-
var TRANSFER_SINGLE_TOPIC = "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62";
|
|
1929
|
-
var TRANSFER_BATCH_TOPIC = "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb";
|
|
1930
|
-
var topicAddress = (t) => t && t.length === 66 ? getAddress5(`0x${t.slice(26)}`) : null;
|
|
1931
|
-
function parseMintReceipt(logs, { tokenContract, recipient }) {
|
|
1932
|
-
const contract = getAddress5(tokenContract);
|
|
1933
|
-
const to = getAddress5(recipient);
|
|
1934
|
-
const out = /* @__PURE__ */ new Map();
|
|
1935
|
-
const add = (id, amount) => out.set(id, (out.get(id) ?? 0n) + amount);
|
|
1936
|
-
for (const log of logs) {
|
|
1937
|
-
if (getAddress5(log.address) !== contract) continue;
|
|
1938
|
-
const [t0, t1, t2, t3] = log.topics;
|
|
1939
|
-
if (t0 === TRANSFER_TOPIC && log.topics.length === 4) {
|
|
1940
|
-
if (topicAddress(t1) !== zeroAddress3 || topicAddress(t2) !== to || !t3) continue;
|
|
1941
|
-
add(BigInt(t3), 1n);
|
|
1942
|
-
} else if (t0 === TRANSFER_SINGLE_TOPIC && log.topics.length === 4) {
|
|
1943
|
-
if (topicAddress(t2) !== zeroAddress3 || topicAddress(t3) !== to) continue;
|
|
1944
|
-
const [id, value] = decodeAbiParameters([{ type: "uint256" }, { type: "uint256" }], log.data);
|
|
1945
|
-
add(id, value);
|
|
1946
|
-
} else if (t0 === TRANSFER_BATCH_TOPIC && log.topics.length === 4) {
|
|
1947
|
-
if (topicAddress(t2) !== zeroAddress3 || topicAddress(t3) !== to) continue;
|
|
1948
|
-
const [ids, values] = decodeAbiParameters([{ type: "uint256[]" }, { type: "uint256[]" }], log.data);
|
|
1949
|
-
if (ids.length !== values.length) continue;
|
|
1950
|
-
ids.forEach((id, i) => add(id, values[i]));
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
return [...out].map(([tokenId, amount]) => ({ contract, tokenId, amount }));
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
// src/core/friendly-errors.ts
|
|
1957
|
-
var GENERIC = "Transaction failed. Please try again. If it fails again, please contact support.";
|
|
1958
|
-
var REVERTED = "Transaction reverted";
|
|
1959
|
-
var record = (value) => value !== null && typeof value === "object" ? value : void 0;
|
|
1960
|
-
var text = (value) => typeof value === "string" ? value.toLowerCase() : "";
|
|
1961
|
-
function collect(error) {
|
|
1962
|
-
const pending = [error];
|
|
1963
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1964
|
-
const errors = [];
|
|
1965
|
-
while (pending.length) {
|
|
1966
|
-
const e = record(pending.pop());
|
|
1967
|
-
if (!e || seen.has(e)) continue;
|
|
1968
|
-
seen.add(e);
|
|
1969
|
-
const data = record(e.data);
|
|
1970
|
-
errors.push({
|
|
1971
|
-
name: e.name,
|
|
1972
|
-
code: e.code,
|
|
1973
|
-
cancelled: e.cancelled,
|
|
1974
|
-
replacement: e.replacement,
|
|
1975
|
-
message: [e.message, e.shortMessage, isMintError(e) ? void 0 : e.reason, data?.message].map(text).join("\n")
|
|
1976
|
-
});
|
|
1977
|
-
pending.push(e.cause, data?.originalError);
|
|
1978
|
-
}
|
|
1979
|
-
return errors;
|
|
1980
|
-
}
|
|
1981
|
-
function fundsMessage(errors) {
|
|
1982
|
-
const hasText = (phrase) => errors.some((e) => e.message.includes(phrase));
|
|
1983
|
-
if (hasText("insufficient funds for gas")) {
|
|
1984
|
-
return "Your wallet does not have enough funds to pay for gas fees. Please try again with a different wallet or add more funds to your wallet.";
|
|
1985
|
-
}
|
|
1986
|
-
if (errors.some((e) => e.name === "InsufficientFundsError" || e.code === "INSUFFICIENT_FUNDS") || hasText("insufficient funds") || hasText("balance too low to proceed")) {
|
|
1987
|
-
return "Your wallet does not have enough funds to complete this transaction. Please try again with a different wallet or add more funds to your wallet.";
|
|
1988
|
-
}
|
|
1989
|
-
return null;
|
|
1990
|
-
}
|
|
1991
|
-
function friendlyErrorMessage(error) {
|
|
1992
|
-
if (isMintError(error)) {
|
|
1993
|
-
if (error.reason === "INSUFFICIENT_FUNDS") return fundsMessage(collect(error.cause)) ?? error.message;
|
|
1994
|
-
if (error.reason !== "RPC_ERROR" && error.reason !== "SIMULATION_REVERTED") return error.message;
|
|
1995
|
-
if (error.reason === "SIMULATION_REVERTED" && error.domain === "relay") return error.message;
|
|
1996
|
-
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;
|
|
1997
|
-
}
|
|
1998
|
-
const errors = collect(error);
|
|
1999
|
-
const hasText = (phrase) => errors.some((e) => e.message.includes(phrase));
|
|
2000
|
-
const hasName = (name) => errors.some((e) => e.name === name);
|
|
2001
|
-
const hasCode2 = (code2) => errors.some((e) => e.code === code2);
|
|
2002
|
-
if (hasName("UserRejectedRequestError") || hasCode2("ACTION_REJECTED") || hasCode2(4001) || hasText("user denied transaction signature") || hasText("userrefusedondevice") || hasText("rejected transaction") || hasText("user rejected") || errors.some((e) => e.message.includes("cancelled") && !e.cancelled)) {
|
|
2003
|
-
return "Transaction Rejected";
|
|
2004
|
-
}
|
|
2005
|
-
if (hasCode2(4902)) return "Network not available in wallet";
|
|
2006
|
-
const replaced = errors.find((e) => e.name === "TransactionReplacedError" || e.code === "TRANSACTION_REPLACED");
|
|
2007
|
-
if (replaced?.cancelled) return "Transaction was cancelled";
|
|
2008
|
-
if (replaced?.replacement) return "Transaction was replaced with a new transaction (usually due to speed up)";
|
|
2009
|
-
if (hasText("ledger")) return "Error with Ledger device. Please ensure device is connected and unlocked";
|
|
2010
|
-
if (errors.some((e) => e.code === -32002 && e.message.includes("pending"))) {
|
|
2011
|
-
return "Transaction already pending in wallet. Please check your wallet";
|
|
2012
|
-
}
|
|
2013
|
-
if (hasText("invalid amount")) return "Price calculation is incorrect, contact support!";
|
|
2014
|
-
const funds = fundsMessage(errors);
|
|
2015
|
-
if (funds) return funds;
|
|
2016
|
-
if (hasName("NonceTooLowError") || hasName("NonceExhaustedError") || hasText("nonce too low")) {
|
|
2017
|
-
return "Transaction nonce is too low. Please try again";
|
|
2018
|
-
}
|
|
2019
|
-
if (hasName("FeeCapTooLowError") || hasText("max fee per gas less than block base fee")) {
|
|
2020
|
-
return "Gas price too low for current network conditions. Please try again";
|
|
2021
|
-
}
|
|
2022
|
-
if (hasName("TimeoutError") || hasCode2("TIMEOUT") || hasText("timeout")) {
|
|
2023
|
-
return "Transaction timed out. Your wallet connection is having issues. Disconnect and reconnect your wallet, then please try again";
|
|
2024
|
-
}
|
|
2025
|
-
if (hasText("network disconnected")) return "Network connection lost. Please check your internet connection and try again";
|
|
2026
|
-
if (hasName("ChainMismatchError") || hasText("wrong network")) return "Wrong network selected. Please switch to the correct network";
|
|
2027
|
-
if (hasText("erc20: transfer amount exceeds balance")) {
|
|
2028
|
-
return "You do not have the required amount of ERC20 tokens to complete this transaction.";
|
|
2029
|
-
}
|
|
2030
|
-
if (hasName("EstimateGasExecutionError") || hasCode2("UNPREDICTABLE_GAS_LIMIT") || hasText("cannot estimate gas")) {
|
|
2031
|
-
return "Unable to estimate gas for transaction. The transaction may fail. Please try again.";
|
|
2032
|
-
}
|
|
2033
|
-
if (hasName("ContractFunctionExecutionError") || hasCode2("CALL_EXCEPTION")) {
|
|
2034
|
-
return "Transaction failed due to contract execution error";
|
|
2035
|
-
}
|
|
2036
|
-
if (hasName("ContractFunctionRevertedError") || hasName("ExecutionRevertedError") || hasText("revert")) return REVERTED;
|
|
2037
|
-
if (hasName("TransactionExecutionError")) return "Transaction execution failed";
|
|
2038
|
-
if (hasCode2("NETWORK_ERROR") || hasName("HttpRequestError") || hasText("network")) return "Network error occurred";
|
|
2039
|
-
return isMintError(error) && error.reason === "SIMULATION_REVERTED" ? REVERTED : GENERIC;
|
|
2040
|
-
}
|
|
1393
|
+
var contractAdapter = { readDrop: readDrop2, readWallet: readWallet2, buildPlan: buildPlan2, simulate: simulate2 };
|
|
2041
1394
|
|
|
2042
1395
|
// src/react/useMint.ts
|
|
2043
1396
|
var REFRESH_MS = 3e4;
|
|
@@ -2051,7 +1404,18 @@ function configKeyOf(config) {
|
|
|
2051
1404
|
return JSON.stringify(config, (_k, v) => typeof v === "bigint" ? { __dropkitBigInt: v.toString() } : v);
|
|
2052
1405
|
}
|
|
2053
1406
|
function adapterFor(config) {
|
|
2054
|
-
|
|
1407
|
+
switch (config.type) {
|
|
1408
|
+
case "manifold-claim":
|
|
1409
|
+
return manifoldClaimAdapter;
|
|
1410
|
+
case "contract":
|
|
1411
|
+
return contractAdapter;
|
|
1412
|
+
case "tl-stacks":
|
|
1413
|
+
return tlStacksAdapter;
|
|
1414
|
+
default: {
|
|
1415
|
+
const unreachable = config;
|
|
1416
|
+
return unreachable;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
2055
1419
|
}
|
|
2056
1420
|
var bridgeEnabled = (config) => config.type === "manifold-claim";
|
|
2057
1421
|
function payChainsOf(config, option) {
|
|
@@ -2074,28 +1438,34 @@ var symbolOf = (chainId) => {
|
|
|
2074
1438
|
};
|
|
2075
1439
|
var shortOf = (chainId) => ({ chainId, symbol: symbolOf(chainId), name: chainName(chainId) });
|
|
2076
1440
|
function tokenContractOf(config) {
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
1441
|
+
switch (config.type) {
|
|
1442
|
+
case "manifold-claim":
|
|
1443
|
+
return config.creatorContract;
|
|
1444
|
+
case "contract":
|
|
1445
|
+
return config.tokenContract ?? config.address;
|
|
1446
|
+
case "tl-stacks":
|
|
1447
|
+
return config.nftContract;
|
|
1448
|
+
default: {
|
|
1449
|
+
const unreachable = config;
|
|
1450
|
+
return unreachable;
|
|
1451
|
+
}
|
|
2087
1452
|
}
|
|
2088
|
-
return false;
|
|
2089
1453
|
}
|
|
2090
|
-
function
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
1454
|
+
function eventAddressOf(config) {
|
|
1455
|
+
switch (config.type) {
|
|
1456
|
+
case "manifold-claim":
|
|
1457
|
+
return config.creatorContract;
|
|
1458
|
+
case "contract":
|
|
1459
|
+
return config.address;
|
|
1460
|
+
case "tl-stacks":
|
|
1461
|
+
return config.nftContract;
|
|
1462
|
+
default: {
|
|
1463
|
+
const unreachable = config;
|
|
1464
|
+
return unreachable;
|
|
1465
|
+
}
|
|
2096
1466
|
}
|
|
2097
|
-
return { reason: "RPC_ERROR", message: friendlyErrorMessage(e) };
|
|
2098
1467
|
}
|
|
1468
|
+
var isUnsupported = (reason) => UNSUPPORTED_REASONS.includes(reason);
|
|
2099
1469
|
function errorReasonOf(e) {
|
|
2100
1470
|
const { reason, message } = toReason(e);
|
|
2101
1471
|
const ok = ["USER_REJECTED", "TX_REVERTED", "RATE_LIMITED", "RPC_ERROR", "CHAIN_SWITCH_FAILED", "ALLOWLIST_UNAVAILABLE"];
|
|
@@ -2181,7 +1551,7 @@ function useMint(options) {
|
|
|
2181
1551
|
}),
|
|
2182
1552
|
[clientFor]
|
|
2183
1553
|
);
|
|
2184
|
-
const
|
|
1554
|
+
const readDrop3 = useCallback(
|
|
2185
1555
|
async (life, gen, cfg) => {
|
|
2186
1556
|
const adapter = adapterFor(cfg);
|
|
2187
1557
|
try {
|
|
@@ -2206,9 +1576,9 @@ function useMint(options) {
|
|
|
2206
1576
|
const life = { key, controller: new AbortController(), cacheScope: {}, gen };
|
|
2207
1577
|
lifeRef.current = life;
|
|
2208
1578
|
dispatch({ type: "LOAD", gen, kind: "config" });
|
|
2209
|
-
void
|
|
1579
|
+
void readDrop3(life, gen, cfg);
|
|
2210
1580
|
},
|
|
2211
|
-
[dispatch,
|
|
1581
|
+
[dispatch, readDrop3]
|
|
2212
1582
|
);
|
|
2213
1583
|
const refreshNow = useCallback(
|
|
2214
1584
|
async (kind = "refresh") => {
|
|
@@ -2221,9 +1591,9 @@ function useMint(options) {
|
|
|
2221
1591
|
const gen = ++genRef.current;
|
|
2222
1592
|
life.gen = gen;
|
|
2223
1593
|
dispatch({ type: "LOAD", gen, kind });
|
|
2224
|
-
await
|
|
1594
|
+
await readDrop3(life, gen, propsRef.current.config);
|
|
2225
1595
|
},
|
|
2226
|
-
[dispatch,
|
|
1596
|
+
[dispatch, readDrop3]
|
|
2227
1597
|
);
|
|
2228
1598
|
const refresh = useCallback(() => refreshNow("refresh"), [refreshNow]);
|
|
2229
1599
|
const isBusy = useCallback(
|
|
@@ -2276,7 +1646,7 @@ function useMint(options) {
|
|
|
2276
1646
|
if (cancelled) throw new MintError("USER_REJECTED", "The transaction was cancelled or replaced with a different transaction.");
|
|
2277
1647
|
if (await walletFor().getChainId() !== attempt.chainId) throw e;
|
|
2278
1648
|
assertActive();
|
|
2279
|
-
receipt2 = await walletFor().extend(
|
|
1649
|
+
receipt2 = await walletFor().extend(publicActions).waitForTransactionReceipt(params);
|
|
2280
1650
|
}
|
|
2281
1651
|
assertActive();
|
|
2282
1652
|
if (cancelled) throw new MintError("USER_REJECTED", "The transaction was cancelled or replaced with a different transaction.");
|
|
@@ -2350,7 +1720,7 @@ function useMint(options) {
|
|
|
2350
1720
|
tokenContract: plan.tokenContract ?? attempt.tokenContract,
|
|
2351
1721
|
recipient: attempt.recipient
|
|
2352
1722
|
});
|
|
2353
|
-
const result = { txHash: confirmedHash, explorerUrl: explorerUrlFor(attempt.chainId, confirmedHash), tokens };
|
|
1723
|
+
const result = { txHash: confirmedHash, explorerUrl: explorerUrlFor(attempt.chainId, confirmedHash), tokens, requested: attempt.quantity };
|
|
2354
1724
|
dispatch({ type: "ATTEMPT_SUCCEEDED", attemptId: attempt.attemptId, result });
|
|
2355
1725
|
emit("success", { state: "success", reason: null, txHash: confirmedHash, quantity: attempt.quantity, chainId: attempt.chainId });
|
|
2356
1726
|
},
|
|
@@ -2399,6 +1769,7 @@ function useMint(options) {
|
|
|
2399
1769
|
txHash,
|
|
2400
1770
|
explorerUrl: explorerUrlFor(fillHash ? cfg.chainId : out.originChainId, txHash),
|
|
2401
1771
|
tokens,
|
|
1772
|
+
requested: attempt.quantity,
|
|
2402
1773
|
relay: { requestId: out.requestId, originTxHash: out.originTxHash, originChainId: out.originChainId }
|
|
2403
1774
|
};
|
|
2404
1775
|
clearOutstanding(key);
|
|
@@ -2554,7 +1925,7 @@ function useMint(options) {
|
|
|
2554
1925
|
if (life2) life2.gen = gen;
|
|
2555
1926
|
dispatch({ type: "RESET", gen });
|
|
2556
1927
|
dispatch({ type: "LOAD", gen, kind: "config" });
|
|
2557
|
-
if (life2) void
|
|
1928
|
+
if (life2) void readDrop3(life2, gen, propsRef.current.config);
|
|
2558
1929
|
return;
|
|
2559
1930
|
}
|
|
2560
1931
|
if (s.name !== "live") return;
|
|
@@ -2588,7 +1959,7 @@ function useMint(options) {
|
|
|
2588
1959
|
} finally {
|
|
2589
1960
|
attemptRef.current = null;
|
|
2590
1961
|
}
|
|
2591
|
-
}, [dispatch, emit,
|
|
1962
|
+
}, [dispatch, emit, readDrop3, executeAttempt, executeRelayAttempt, resumeOutstanding, refreshNow]);
|
|
2592
1963
|
const mint = useCallback(() => {
|
|
2593
1964
|
void runMint();
|
|
2594
1965
|
}, [runMint]);
|
|
@@ -2797,13 +2168,13 @@ function useMint(options) {
|
|
|
2797
2168
|
document.removeEventListener("visibilitychange", onVisible);
|
|
2798
2169
|
};
|
|
2799
2170
|
}, [refreshNow]);
|
|
2800
|
-
const boundary = machine.drop ? [machine.drop.startTime, machine.drop.endTime].some((t) => t !== null && t > machine.now) : false;
|
|
2171
|
+
const boundary = machine.drop ? [machine.drop.startTime, machine.drop.endTime, machine.drop.phaseEndsAt ?? null].some((t) => t !== null && t > machine.now) : false;
|
|
2801
2172
|
useEffect(() => {
|
|
2802
2173
|
if (!boundary) return;
|
|
2803
2174
|
const timer = setInterval(() => {
|
|
2804
2175
|
const s = stateRef.current;
|
|
2805
2176
|
const now = nowSec();
|
|
2806
|
-
const crossed = s.drop && [s.drop.startTime, s.drop.endTime].some((t) => t !== null && t > s.now && t <= now);
|
|
2177
|
+
const crossed = s.drop && [s.drop.startTime, s.drop.endTime, s.drop.phaseEndsAt ?? null].some((t) => t !== null && t > s.now && t <= now);
|
|
2807
2178
|
dispatch({ type: "TICK", now });
|
|
2808
2179
|
if (crossed) void refreshNow();
|
|
2809
2180
|
}, TICK_MS);
|
|
@@ -2869,13 +2240,13 @@ function useMint(options) {
|
|
|
2869
2240
|
|
|
2870
2241
|
// src/react/PayWithPicker.ts
|
|
2871
2242
|
import { createElement, useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
|
|
2872
|
-
import { formatUnits as
|
|
2243
|
+
import { formatUnits as formatUnits4 } from "viem";
|
|
2873
2244
|
var PICKER_REFRESH_MS = 2e4;
|
|
2874
2245
|
var PICKER_DEBOUNCE_MS = 500;
|
|
2875
2246
|
var RELAY_REASONS = /* @__PURE__ */ new Set(["BRIDGE_UNAVAILABLE", "BRIDGE_TIMEOUT", "BRIDGE_REFUNDED", "BRIDGE_FAILED"]);
|
|
2876
|
-
var
|
|
2247
|
+
var money3 = (value, chainId) => {
|
|
2877
2248
|
const c = getChain(chainId).nativeCurrency;
|
|
2878
|
-
return { value, decimals: c.decimals, symbol: c.symbol, formatted:
|
|
2249
|
+
return { value, decimals: c.decimals, symbol: c.symbol, formatted: formatUnits4(value, c.decimals) };
|
|
2879
2250
|
};
|
|
2880
2251
|
function usePayOptions(mint, onSelect) {
|
|
2881
2252
|
const info = mint.crossChain;
|
|
@@ -2951,7 +2322,7 @@ function usePayOptions(mint, onSelect) {
|
|
|
2951
2322
|
isDropChain,
|
|
2952
2323
|
relayFee: isDropChain ? null : quoted?.relayFee ?? null,
|
|
2953
2324
|
amountIn: isDropChain ? null : quoted?.amountIn ?? null,
|
|
2954
|
-
balance: balance && balance.chainId === chainId && chainId === current ?
|
|
2325
|
+
balance: balance && balance.chainId === chainId && chainId === current ? money3(balance.value, chainId) : null,
|
|
2955
2326
|
loading: !isDropChain && q === void 0,
|
|
2956
2327
|
select: () => select(chainId)
|
|
2957
2328
|
};
|
|
@@ -3062,7 +2433,7 @@ function MintButton(props) {
|
|
|
3062
2433
|
}
|
|
3063
2434
|
|
|
3064
2435
|
// src/index.ts
|
|
3065
|
-
var VERSION = "0.
|
|
2436
|
+
var VERSION = "0.4.0";
|
|
3066
2437
|
export {
|
|
3067
2438
|
CLICKABLE_STATES,
|
|
3068
2439
|
MAX_QUANTITY,
|