mint-day 0.3.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 +166 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +78 -0
- package/dist/services/calldata.d.ts +12 -0
- package/dist/services/calldata.js +61 -0
- package/dist/services/classifier.d.ts +2 -0
- package/dist/services/classifier.js +81 -0
- package/dist/services/ens.d.ts +3 -0
- package/dist/services/ens.js +32 -0
- package/dist/services/image-upload.d.ts +11 -0
- package/dist/services/image-upload.js +70 -0
- package/dist/services/metadata.d.ts +2 -0
- package/dist/services/metadata.js +119 -0
- package/dist/tools/mint-check.d.ts +16 -0
- package/dist/tools/mint-check.js +177 -0
- package/dist/tools/mint-resolve.d.ts +13 -0
- package/dist/tools/mint-resolve.js +108 -0
- package/dist/tools/mint.d.ts +43 -0
- package/dist/tools/mint.js +425 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.js +9 -0
- package/package.json +41 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { ethers } from "ethers";
|
|
4
|
+
import { TokenType, TOKEN_TYPE_NAMES } from "../types.js";
|
|
5
|
+
import { classifyIntent } from "../services/classifier.js";
|
|
6
|
+
import { CalldataService } from "../services/calldata.js";
|
|
7
|
+
import { resolveImage } from "../services/image-upload.js";
|
|
8
|
+
import { resolveEns, looksLikeEns } from "../services/ens.js";
|
|
9
|
+
// Intent cache: preview mintId -> frozen intent
|
|
10
|
+
const intentCache = new Map();
|
|
11
|
+
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
12
|
+
function cacheMintId(intent) {
|
|
13
|
+
const hash = createHash("sha256")
|
|
14
|
+
.update(JSON.stringify(intent))
|
|
15
|
+
.digest("hex")
|
|
16
|
+
.slice(0, 12);
|
|
17
|
+
intentCache.set(hash, { intent, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
18
|
+
return hash;
|
|
19
|
+
}
|
|
20
|
+
function getCachedIntent(mintId) {
|
|
21
|
+
const entry = intentCache.get(mintId);
|
|
22
|
+
if (!entry)
|
|
23
|
+
return null;
|
|
24
|
+
if (Date.now() > entry.expiresAt) {
|
|
25
|
+
intentCache.delete(mintId);
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return entry.intent;
|
|
29
|
+
}
|
|
30
|
+
// --- Rate limiter (in-memory, resets on process restart) ---
|
|
31
|
+
const LIMITS = {
|
|
32
|
+
perAddress: 10,
|
|
33
|
+
perHour: 50,
|
|
34
|
+
perDay: 500,
|
|
35
|
+
};
|
|
36
|
+
const addressMints = new Map();
|
|
37
|
+
const hourlyWindow = { count: 0, resetAt: Date.now() + 60 * 60 * 1000 };
|
|
38
|
+
const dailyWindow = { count: 0, resetAt: Date.now() + 24 * 60 * 60 * 1000 };
|
|
39
|
+
function checkRateLimit(address) {
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
const addr = address.toLowerCase();
|
|
42
|
+
// Reset windows if expired
|
|
43
|
+
if (now >= hourlyWindow.resetAt) {
|
|
44
|
+
hourlyWindow.count = 0;
|
|
45
|
+
hourlyWindow.resetAt = now + 60 * 60 * 1000;
|
|
46
|
+
}
|
|
47
|
+
if (now >= dailyWindow.resetAt) {
|
|
48
|
+
dailyWindow.count = 0;
|
|
49
|
+
dailyWindow.resetAt = now + 24 * 60 * 60 * 1000;
|
|
50
|
+
addressMints.clear(); // reset per-address counts daily
|
|
51
|
+
}
|
|
52
|
+
const addrCount = addressMints.get(addr) || 0;
|
|
53
|
+
if (addrCount >= LIMITS.perAddress) {
|
|
54
|
+
return { allowed: false, reason: `Address limit reached (${LIMITS.perAddress} sponsored mints per address per day)` };
|
|
55
|
+
}
|
|
56
|
+
if (hourlyWindow.count >= LIMITS.perHour) {
|
|
57
|
+
return { allowed: false, reason: `Hourly limit reached (${LIMITS.perHour} sponsored mints per hour)` };
|
|
58
|
+
}
|
|
59
|
+
if (dailyWindow.count >= LIMITS.perDay) {
|
|
60
|
+
return { allowed: false, reason: `Daily limit reached (${LIMITS.perDay} sponsored mints per day)` };
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
allowed: true,
|
|
64
|
+
remaining: {
|
|
65
|
+
address: LIMITS.perAddress - addrCount - 1,
|
|
66
|
+
hourly: LIMITS.perHour - hourlyWindow.count - 1,
|
|
67
|
+
daily: LIMITS.perDay - dailyWindow.count - 1,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function recordMint(address) {
|
|
72
|
+
const addr = address.toLowerCase();
|
|
73
|
+
addressMints.set(addr, (addressMints.get(addr) || 0) + 1);
|
|
74
|
+
hourlyWindow.count++;
|
|
75
|
+
dailyWindow.count++;
|
|
76
|
+
}
|
|
77
|
+
// --- Schema + Handler ---
|
|
78
|
+
export const mintSchema = {
|
|
79
|
+
description: z
|
|
80
|
+
.string()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe("What to mint, in plain English. Required for new mints, omit when confirming with mintId."),
|
|
83
|
+
tokenType: z
|
|
84
|
+
.enum(["Identity", "Attestation", "Credential", "Receipt", "Pass"])
|
|
85
|
+
.optional()
|
|
86
|
+
.describe("Token type. If provided with soulbound, skips LLM classification."),
|
|
87
|
+
recipient: z
|
|
88
|
+
.string()
|
|
89
|
+
.optional()
|
|
90
|
+
.describe("Ethereum address or ENS name to receive the token. Required unless DEFAULT_RECIPIENT is set in server config."),
|
|
91
|
+
soulbound: z
|
|
92
|
+
.boolean()
|
|
93
|
+
.optional()
|
|
94
|
+
.describe("Whether the token is non-transferable. Defaults based on token type."),
|
|
95
|
+
image: z
|
|
96
|
+
.string()
|
|
97
|
+
.optional()
|
|
98
|
+
.describe("Image for visual tokens. Accepts: URL, local file path (/path/to/image.png or ~/image.png), or base64 data URI. Local files and base64 are auto-uploaded to Imgur if IMGUR_CLIENT_ID is set."),
|
|
99
|
+
animation_url: z
|
|
100
|
+
.string()
|
|
101
|
+
.optional()
|
|
102
|
+
.describe("Animation URL for rich media tokens (video, audio, HTML, generative art)."),
|
|
103
|
+
mintId: z
|
|
104
|
+
.string()
|
|
105
|
+
.optional()
|
|
106
|
+
.describe("Confirmation ID from a previous preview. Provide this to confirm and get calldata."),
|
|
107
|
+
metadata: z
|
|
108
|
+
.record(z.string(), z.unknown())
|
|
109
|
+
.optional()
|
|
110
|
+
.describe("Additional key-value metadata. Supports strings, arrays, and objects (e.g. capabilities: ['mint'])."),
|
|
111
|
+
};
|
|
112
|
+
export async function handleMint(params, calldataService, defaultRecipient, userKey = "", sponsored = {
|
|
113
|
+
sponsoredKey: "", sponsoredContract: "", sponsoredRpc: "", sponsoredChain: 8453,
|
|
114
|
+
fallbackContract: "", fallbackRpc: "", fallbackChain: 84532,
|
|
115
|
+
}) {
|
|
116
|
+
const hasRecipient = !!params.recipient;
|
|
117
|
+
const sponsoredGas = !userKey;
|
|
118
|
+
// Confirmation path: replay cached intent
|
|
119
|
+
if (params.mintId) {
|
|
120
|
+
const cached = getCachedIntent(params.mintId);
|
|
121
|
+
if (!cached) {
|
|
122
|
+
return {
|
|
123
|
+
content: [{
|
|
124
|
+
type: "text",
|
|
125
|
+
text: JSON.stringify({ error: "mintId not found or expired. Start a new mint." }, null, 2),
|
|
126
|
+
}],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
intentCache.delete(params.mintId);
|
|
130
|
+
// Sponsored mode: check rate limits before submitting
|
|
131
|
+
if (sponsoredGas) {
|
|
132
|
+
const limit = checkRateLimit(cached.recipient);
|
|
133
|
+
if (limit.allowed) {
|
|
134
|
+
// Sponsored mainnet mint
|
|
135
|
+
const sponsoredService = new CalldataService(sponsored.sponsoredRpc, sponsored.sponsoredContract, sponsored.sponsoredChain);
|
|
136
|
+
const result = await sponsoredService.buildMintCalldata(cached);
|
|
137
|
+
try {
|
|
138
|
+
const wallet = new ethers.Wallet(sponsored.sponsoredKey, sponsoredService.provider);
|
|
139
|
+
const tx = await wallet.sendTransaction({
|
|
140
|
+
to: result.to,
|
|
141
|
+
data: result.calldata,
|
|
142
|
+
value: BigInt(result.value),
|
|
143
|
+
gasLimit: result.estimatedGas,
|
|
144
|
+
});
|
|
145
|
+
const receipt = await tx.wait();
|
|
146
|
+
recordMint(cached.recipient);
|
|
147
|
+
return {
|
|
148
|
+
content: [{
|
|
149
|
+
type: "text",
|
|
150
|
+
text: JSON.stringify({
|
|
151
|
+
status: "minted",
|
|
152
|
+
txHash: receipt.hash,
|
|
153
|
+
blockNumber: receipt.blockNumber,
|
|
154
|
+
gasUsed: receipt.gasUsed.toString(),
|
|
155
|
+
tokenType: result.tokenType,
|
|
156
|
+
soulbound: result.soulbound,
|
|
157
|
+
recipient: cached.recipient,
|
|
158
|
+
chain: "Base",
|
|
159
|
+
explorer: `https://basescan.org/tx/${receipt.hash}`,
|
|
160
|
+
}, null, 2),
|
|
161
|
+
}],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
166
|
+
// On tx failure (e.g. sponsored wallet out of gas), fall back to calldata
|
|
167
|
+
const fallbackService = new CalldataService(sponsored.sponsoredRpc, sponsored.sponsoredContract, sponsored.sponsoredChain);
|
|
168
|
+
const fallbackResult = await fallbackService.buildMintCalldata(cached);
|
|
169
|
+
return {
|
|
170
|
+
content: [{
|
|
171
|
+
type: "text",
|
|
172
|
+
text: JSON.stringify({
|
|
173
|
+
status: "calldata",
|
|
174
|
+
reason: `Sponsored mint failed (${message}). Returning calldata for you to submit.`,
|
|
175
|
+
to: fallbackResult.to,
|
|
176
|
+
calldata: fallbackResult.calldata,
|
|
177
|
+
value: fallbackResult.value,
|
|
178
|
+
estimatedGas: fallbackResult.estimatedGas,
|
|
179
|
+
chainId: fallbackResult.chainId,
|
|
180
|
+
tokenType: fallbackResult.tokenType,
|
|
181
|
+
soulbound: fallbackResult.soulbound,
|
|
182
|
+
}, null, 2),
|
|
183
|
+
}],
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
// Rate limit exceeded: return calldata on mainnet (user pays gas)
|
|
189
|
+
const mainnetService = new CalldataService(sponsored.sponsoredRpc, sponsored.sponsoredContract, sponsored.sponsoredChain);
|
|
190
|
+
const result = await mainnetService.buildMintCalldata(cached);
|
|
191
|
+
return {
|
|
192
|
+
content: [{
|
|
193
|
+
type: "text",
|
|
194
|
+
text: JSON.stringify({
|
|
195
|
+
status: "calldata",
|
|
196
|
+
reason: limit.reason,
|
|
197
|
+
message: "Sponsored gas limit reached. Here's the calldata to submit with your own wallet.",
|
|
198
|
+
to: result.to,
|
|
199
|
+
calldata: result.calldata,
|
|
200
|
+
value: result.value,
|
|
201
|
+
estimatedGas: result.estimatedGas,
|
|
202
|
+
chainId: result.chainId,
|
|
203
|
+
tokenType: result.tokenType,
|
|
204
|
+
soulbound: result.soulbound,
|
|
205
|
+
chain: "Base",
|
|
206
|
+
setup: "Save a private key to ~/.mint-day/credentials for unlimited self-funded mints.",
|
|
207
|
+
}, null, 2),
|
|
208
|
+
}],
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// User-key mode: sign and submit with user's key
|
|
213
|
+
if (userKey) {
|
|
214
|
+
const result = await calldataService.buildMintCalldata(cached);
|
|
215
|
+
try {
|
|
216
|
+
const wallet = new ethers.Wallet(userKey, calldataService.provider);
|
|
217
|
+
const tx = await wallet.sendTransaction({
|
|
218
|
+
to: result.to,
|
|
219
|
+
data: result.calldata,
|
|
220
|
+
value: BigInt(result.value),
|
|
221
|
+
gasLimit: result.estimatedGas,
|
|
222
|
+
});
|
|
223
|
+
const receipt = await tx.wait();
|
|
224
|
+
return {
|
|
225
|
+
content: [{
|
|
226
|
+
type: "text",
|
|
227
|
+
text: JSON.stringify({
|
|
228
|
+
status: "minted",
|
|
229
|
+
txHash: receipt.hash,
|
|
230
|
+
blockNumber: receipt.blockNumber,
|
|
231
|
+
gasUsed: receipt.gasUsed.toString(),
|
|
232
|
+
tokenType: result.tokenType,
|
|
233
|
+
soulbound: result.soulbound,
|
|
234
|
+
recipient: cached.recipient,
|
|
235
|
+
chain: "Base",
|
|
236
|
+
explorer: `https://basescan.org/tx/${receipt.hash}`,
|
|
237
|
+
}, null, 2),
|
|
238
|
+
}],
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
243
|
+
return {
|
|
244
|
+
content: [{
|
|
245
|
+
type: "text",
|
|
246
|
+
text: JSON.stringify({ error: `Transaction failed: ${message}` }, null, 2),
|
|
247
|
+
}],
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// Calldata-only mode (recipient provided, no key)
|
|
252
|
+
const result = await calldataService.buildMintCalldata(cached);
|
|
253
|
+
return {
|
|
254
|
+
content: [{
|
|
255
|
+
type: "text",
|
|
256
|
+
text: JSON.stringify({
|
|
257
|
+
status: "calldata",
|
|
258
|
+
to: result.to,
|
|
259
|
+
calldata: result.calldata,
|
|
260
|
+
value: result.value,
|
|
261
|
+
estimatedGas: result.estimatedGas,
|
|
262
|
+
chainId: result.chainId,
|
|
263
|
+
tokenType: result.tokenType,
|
|
264
|
+
soulbound: result.soulbound,
|
|
265
|
+
}, null, 2),
|
|
266
|
+
}],
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
if (!params.description) {
|
|
270
|
+
return {
|
|
271
|
+
content: [{
|
|
272
|
+
type: "text",
|
|
273
|
+
text: JSON.stringify({ error: "description is required for new mints." }, null, 2),
|
|
274
|
+
}],
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
let recipient = params.recipient || defaultRecipient;
|
|
278
|
+
// Resolve ENS names to addresses
|
|
279
|
+
if (recipient && looksLikeEns(recipient)) {
|
|
280
|
+
try {
|
|
281
|
+
const resolved = await resolveEns(recipient);
|
|
282
|
+
recipient = resolved;
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
286
|
+
return {
|
|
287
|
+
content: [{
|
|
288
|
+
type: "text",
|
|
289
|
+
text: JSON.stringify({ error: message }, null, 2),
|
|
290
|
+
}],
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (!recipient) {
|
|
295
|
+
return {
|
|
296
|
+
content: [{
|
|
297
|
+
type: "text",
|
|
298
|
+
text: JSON.stringify({
|
|
299
|
+
status: "setup_required",
|
|
300
|
+
message: "mint.day needs a wallet to sign transactions.",
|
|
301
|
+
options: [
|
|
302
|
+
{
|
|
303
|
+
recommended: true,
|
|
304
|
+
method: "Save a private key to ~/.mint-day/credentials",
|
|
305
|
+
steps: [
|
|
306
|
+
"Generate a wallet: node -e \"console.log(require('ethers').Wallet.createRandom().privateKey)\"",
|
|
307
|
+
"Save it: mkdir -p ~/.mint-day && echo '0xYOUR_KEY' > ~/.mint-day/credentials && chmod 600 ~/.mint-day/credentials",
|
|
308
|
+
"Fund the address with a small amount of ETH on Base (gas is < $0.01 per mint)",
|
|
309
|
+
"Restart the MCP server"
|
|
310
|
+
]
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
method: "Use your own signer (AgentKit, Lit Protocol, etc.)",
|
|
314
|
+
steps: [
|
|
315
|
+
"Pass a recipient address in each mint call",
|
|
316
|
+
"mint.day returns calldata for your signer to submit",
|
|
317
|
+
"Works with Coinbase AgentKit, Lit Protocol, Privy, or any EVM wallet"
|
|
318
|
+
]
|
|
319
|
+
}
|
|
320
|
+
]
|
|
321
|
+
}, null, 2),
|
|
322
|
+
}],
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
// Validate address
|
|
326
|
+
if (!ethers.isAddress(recipient)) {
|
|
327
|
+
return {
|
|
328
|
+
content: [{
|
|
329
|
+
type: "text",
|
|
330
|
+
text: JSON.stringify({ error: `Invalid Ethereum address: ${recipient}` }, null, 2),
|
|
331
|
+
}],
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
// Resolve image: local files and base64 get uploaded to Imgur (if configured)
|
|
335
|
+
let resolvedImage = params.image;
|
|
336
|
+
let imageUploaded = false;
|
|
337
|
+
if (params.image) {
|
|
338
|
+
try {
|
|
339
|
+
const result = await resolveImage(params.image);
|
|
340
|
+
resolvedImage = result.url;
|
|
341
|
+
imageUploaded = result.uploaded;
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
345
|
+
return {
|
|
346
|
+
content: [{
|
|
347
|
+
type: "text",
|
|
348
|
+
text: JSON.stringify({ error: `Image resolution failed: ${message}` }, null, 2),
|
|
349
|
+
}],
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
let intent;
|
|
354
|
+
if (params.tokenType !== undefined) {
|
|
355
|
+
const typeIndex = TOKEN_TYPE_NAMES.indexOf(params.tokenType);
|
|
356
|
+
const tokenType = typeIndex >= 0 ? typeIndex : TokenType.Attestation;
|
|
357
|
+
const defaultSoulbound = tokenType === TokenType.Attestation || tokenType === TokenType.Credential;
|
|
358
|
+
const meta = {
|
|
359
|
+
name: params.description,
|
|
360
|
+
description: params.description,
|
|
361
|
+
tokenType: TOKEN_TYPE_NAMES[tokenType],
|
|
362
|
+
soulbound: params.soulbound ?? defaultSoulbound,
|
|
363
|
+
creator: recipient,
|
|
364
|
+
recipient,
|
|
365
|
+
timestamp: new Date().toISOString(),
|
|
366
|
+
chainId: 8453,
|
|
367
|
+
mintday_version: "1",
|
|
368
|
+
...(resolvedImage ? { image: resolvedImage } : {}),
|
|
369
|
+
...(params.animation_url ? { animation_url: params.animation_url } : {}),
|
|
370
|
+
...(params.metadata || {}),
|
|
371
|
+
};
|
|
372
|
+
// ERC-8004 defaults for Identity tokens
|
|
373
|
+
if (tokenType === TokenType.Identity) {
|
|
374
|
+
if (!meta.did)
|
|
375
|
+
meta.did = `did:pkh:eip155:8453:${recipient}`;
|
|
376
|
+
if (!meta.capabilities)
|
|
377
|
+
meta.capabilities = [];
|
|
378
|
+
if (!meta.endpoints)
|
|
379
|
+
meta.endpoints = [];
|
|
380
|
+
}
|
|
381
|
+
intent = {
|
|
382
|
+
tokenType,
|
|
383
|
+
soulbound: params.soulbound ?? defaultSoulbound,
|
|
384
|
+
recipient,
|
|
385
|
+
metadata: meta,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
intent = await classifyIntent(params.description, recipient);
|
|
390
|
+
if (resolvedImage)
|
|
391
|
+
intent.metadata.image = resolvedImage;
|
|
392
|
+
if (params.animation_url)
|
|
393
|
+
intent.metadata.animation_url = params.animation_url;
|
|
394
|
+
if (params.metadata) {
|
|
395
|
+
Object.assign(intent.metadata, params.metadata);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// Cache intent and return preview
|
|
399
|
+
const mintId = cacheMintId(intent);
|
|
400
|
+
const preview = {
|
|
401
|
+
status: "preview",
|
|
402
|
+
mintId,
|
|
403
|
+
message: `I'll mint a ${TOKEN_TYPE_NAMES[intent.tokenType]} token${intent.soulbound ? " (soulbound)" : ""} to ${intent.recipient}.`,
|
|
404
|
+
tokenType: TOKEN_TYPE_NAMES[intent.tokenType],
|
|
405
|
+
soulbound: intent.soulbound,
|
|
406
|
+
recipient: intent.recipient,
|
|
407
|
+
name: intent.metadata.name,
|
|
408
|
+
};
|
|
409
|
+
if (intent.metadata.image) {
|
|
410
|
+
preview.image = intent.metadata.image;
|
|
411
|
+
if (imageUploaded)
|
|
412
|
+
preview.imageUploaded = true;
|
|
413
|
+
}
|
|
414
|
+
if (intent.metadata.animation_url)
|
|
415
|
+
preview.animation_url = intent.metadata.animation_url;
|
|
416
|
+
if (intent.missingFields?.length) {
|
|
417
|
+
preview.missingFields = intent.missingFields;
|
|
418
|
+
preview.hint = "Identity tokens (ERC-8004) should include capabilities[], endpoints[], and did. Provide these in metadata for full compliance.";
|
|
419
|
+
}
|
|
420
|
+
preview.chain = "Base";
|
|
421
|
+
preview.instruction = "Call mint with mintId to confirm.";
|
|
422
|
+
return {
|
|
423
|
+
content: [{ type: "text", text: JSON.stringify(preview, null, 2) }],
|
|
424
|
+
};
|
|
425
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export declare enum TokenType {
|
|
2
|
+
Identity = 0,
|
|
3
|
+
Attestation = 1,
|
|
4
|
+
Credential = 2,
|
|
5
|
+
Receipt = 3,
|
|
6
|
+
Pass = 4
|
|
7
|
+
}
|
|
8
|
+
export declare const TOKEN_TYPE_NAMES: readonly ["Identity", "Attestation", "Credential", "Receipt", "Pass"];
|
|
9
|
+
export interface MintIntent {
|
|
10
|
+
tokenType: TokenType;
|
|
11
|
+
soulbound: boolean;
|
|
12
|
+
recipient: string;
|
|
13
|
+
metadata: TokenMetadata;
|
|
14
|
+
missingFields?: string[];
|
|
15
|
+
}
|
|
16
|
+
export interface TokenMetadata {
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
tokenType: string;
|
|
20
|
+
soulbound: boolean;
|
|
21
|
+
creator: string;
|
|
22
|
+
recipient: string;
|
|
23
|
+
timestamp: string;
|
|
24
|
+
chainId: number;
|
|
25
|
+
mintday_version: string;
|
|
26
|
+
image?: string;
|
|
27
|
+
animation_url?: string;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
export interface CalldataResult {
|
|
31
|
+
to: string;
|
|
32
|
+
calldata: string;
|
|
33
|
+
value: string;
|
|
34
|
+
estimatedGas: number;
|
|
35
|
+
chainId: number;
|
|
36
|
+
metadata: TokenMetadata;
|
|
37
|
+
tokenType: string;
|
|
38
|
+
soulbound: boolean;
|
|
39
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export var TokenType;
|
|
2
|
+
(function (TokenType) {
|
|
3
|
+
TokenType[TokenType["Identity"] = 0] = "Identity";
|
|
4
|
+
TokenType[TokenType["Attestation"] = 1] = "Attestation";
|
|
5
|
+
TokenType[TokenType["Credential"] = 2] = "Credential";
|
|
6
|
+
TokenType[TokenType["Receipt"] = 3] = "Receipt";
|
|
7
|
+
TokenType[TokenType["Pass"] = 4] = "Pass";
|
|
8
|
+
})(TokenType || (TokenType = {}));
|
|
9
|
+
export const TOKEN_TYPE_NAMES = ["Identity", "Attestation", "Credential", "Receipt", "Pass"];
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mint-day",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Agent-native minting on Base. One tool call, any token type.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mint-day": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "tsx src/index.ts",
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"mcp",
|
|
16
|
+
"agents",
|
|
17
|
+
"nft",
|
|
18
|
+
"minting",
|
|
19
|
+
"base",
|
|
20
|
+
"ethereum"
|
|
21
|
+
],
|
|
22
|
+
"author": "Jordan Lyall",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"type": "module",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
31
|
+
"dotenv": "^17.3.1",
|
|
32
|
+
"ethers": "^6.16.0",
|
|
33
|
+
"zod": "^4.3.6"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^25.5.0",
|
|
37
|
+
"@vercel/node": "^5.6.15",
|
|
38
|
+
"tsx": "^4.21.0",
|
|
39
|
+
"typescript": "^5.9.3"
|
|
40
|
+
}
|
|
41
|
+
}
|