mint-day 0.3.3 → 0.4.1

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/site/index.html CHANGED
@@ -374,12 +374,18 @@
374
374
  <!-- ── Hero ── -->
375
375
  <section class="hero">
376
376
  <h1>Free <span class="green">NFT minting</span> for agents. On Base.</h1>
377
- <p class="subtitle">No wallet. No gas. No code. Tell your agent what to mint and it lands on-chain in seconds. Identity, credentials, proof of work. <strong>Works for humans too.</strong></p>
378
- <div class="install-row hero-install">
379
- <div class="npx-block" style="cursor:pointer;" onclick="copyNpx(this)" title="Copy">
380
- <code><span class="prompt">$</span> npx mint-day</code>
381
- <svg id="npx-copy-icon" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color:var(--text-dim);margin-left:4px;vertical-align:-1px;flex-shrink:0"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
377
+ <p class="subtitle">No wallet. No gas. No code. Describe what to mint — identity, credentials, art — and it lands on Base in seconds. <strong>Works for humans too.</strong></p>
378
+ <div style="margin-top:32px;margin-bottom:24px;font-family:'JetBrains Mono',monospace;font-size:14px;color:var(--text-secondary);display:flex;flex-direction:column;gap:12px;">
379
+ <div style="display:flex;align-items:center;gap:12px;">
380
+ <span style="width:20px;height:20px;border-radius:50%;background:var(--surface-raised);border:1px solid var(--border-bright);display:inline-flex;align-items:center;justify-content:center;font-size:11px;color:var(--text-dim);flex-shrink:0;">1</span>
381
+ <span>Add mint.day to your agent</span>
382
+ </div>
383
+ <div style="display:flex;align-items:center;gap:12px;">
384
+ <span style="width:20px;height:20px;border-radius:50%;background:var(--surface-raised);border:1px solid var(--border-bright);display:inline-flex;align-items:center;justify-content:center;font-size:11px;color:var(--text-dim);flex-shrink:0;">2</span>
385
+ <span>Tell it what to mint — it lands on Base</span>
382
386
  </div>
387
+ </div>
388
+ <div class="install-row hero-install">
383
389
  <button class="copy-setup-btn" onclick="copySetup(this)"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>Copy setup for your agent</button>
384
390
  <a href="https://github.com/jordanlyall/mintday" target="_blank" class="github-btn"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>Docs & Source</a>
385
391
  </div>
@@ -479,6 +485,29 @@
479
485
  </div>
480
486
  </section>
481
487
 
488
+ <!-- ── Protocols ── -->
489
+ <section class="usecases" style="margin-top:64px;">
490
+ <div class="section-label">Two protocols, one tool</div>
491
+ <div class="usecase-grid" style="grid-template-columns:1fr 1fr;">
492
+ <div class="usecase">
493
+ <div style="font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:600;color:var(--text);margin-bottom:8px;">mint.day</div>
494
+ <div class="usecase-scenario">Gas-sponsored minting on the MintFactory contract. Free, zero config. Best for identity, credentials, attestations, and utility tokens.</div>
495
+ <div style="margin-top:12px;font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-dim);">
496
+ <div>platform: <span style="color:var(--green);">"mintday"</span> <span style="opacity:0.5;">(default)</span></div>
497
+ <div style="margin-top:4px;">gas: sponsored</div>
498
+ </div>
499
+ </div>
500
+ <div class="usecase">
501
+ <div style="font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:600;color:var(--text);margin-bottom:8px;">Rare Protocol <span style="font-weight:400;color:var(--text-dim);font-size:11px;">(SuperRare)</span></div>
502
+ <div class="usecase-scenario">Mint on Rare Protocol ERC-721 contracts on Base. Permissionless. Best for art, collectibles, and visual tokens that belong on SuperRare.</div>
503
+ <div style="margin-top:12px;font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-dim);">
504
+ <div>platform: <span style="color:var(--green);">"superrare"</span></div>
505
+ <div style="margin-top:4px;">gas: bring your own key</div>
506
+ </div>
507
+ </div>
508
+ </div>
509
+ </section>
510
+
482
511
  <!-- ── FAQ ── -->
483
512
  <section class="faq" id="faq">
484
513
  <div class="section-label">FAQ</div>
@@ -507,8 +536,12 @@
507
536
  <div class="faq-a">Yes. Pass an image URL or a local file path. Local files are auto-uploaded and pinned. PFPs, art, screenshots, generated visuals. If your agent can see it, it can mint it.</div>
508
537
  </div>
509
538
  <div class="faq-item">
510
- <div class="faq-q">Where is the contract?</div>
511
- <div class="faq-a"><a href="https://basescan.org/address/0x12a1c11a0b2860f64e7d8df20989f97d40de7f2c" target="_blank">0x12a1c11a...7f2c</a> on Base. Permissionless. Anyone can call <code>mint()</code> directly without the MCP server.</div>
539
+ <div class="faq-q">Can I mint on SuperRare / Rare Protocol?</div>
540
+ <div class="faq-a">Yes. Set <code>platform: "superrare"</code> in your mint call. Tokens mint on Rare Protocol ERC-721 contracts on Base. Requires a private key (no sponsored gas for this path). Set <code>RARE_CONTRACT</code> to your collection address, or deploy one with <code>node scripts/deploy-rare-collection.mjs</code>.</div>
541
+ </div>
542
+ <div class="faq-item">
543
+ <div class="faq-q">Where are the contracts?</div>
544
+ <div class="faq-a">MintFactory: <a href="https://basescan.org/address/0x12a1c11a0b2860f64e7d8df20989f97d40de7f2c" target="_blank">0x12a1...7f2c</a>. Rare Protocol collection: <a href="https://basescan.org/address/0x99Eee317921a9f12176eED25b0aE1D7eb164AE34" target="_blank">0x99Ee...AE34</a>. Both on Base mainnet.</div>
512
545
  </div>
513
546
  </section>
514
547
 
package/src/index.ts CHANGED
@@ -14,11 +14,11 @@ import { mintResolveSchema, handleMintResolve } from "./tools/mint-resolve.js";
14
14
  dotenv.config();
15
15
 
16
16
  // Testnet try-it wallet (Base Sepolia, pre-funded)
17
- const TRYIT_KEY = "REDACTED_RETIRED_KEY";
17
+ const TRYIT_KEY = "";
18
18
  const TRYIT_CONTRACT = "0xa52450397f312c256Bd68B202C0CF90387Ea0E67";
19
19
  const TRYIT_RPC = "https://sepolia.base.org";
20
20
  const TRYIT_CHAIN = 84532;
21
- const MAINNET_CONTRACT = "0xbf12d372444dcf69df9316d961439f6b5919e8d0";
21
+ const MAINNET_CONTRACT = "0x12a1c11a0b2860f64e7d8df20989f97d40de7f2c";
22
22
  const MAINNET_RPC = "https://mainnet.base.org";
23
23
  const MAINNET_CHAIN = 8453;
24
24
 
@@ -62,12 +62,12 @@ const calldataService = new CalldataService(
62
62
 
63
63
  const server = new McpServer({
64
64
  name: "mint-day",
65
- version: "0.3.3",
65
+ version: "0.4.0",
66
66
  });
67
67
 
68
68
  server.tool(
69
69
  "mint",
70
- "Create a permanent, verifiable on-chain record on Base. Use this when you need to: prove you completed a task, establish your identity, record an agreement with another agent, issue or receive a credential, grant access to a capability, or mint a visual token (PFP, art, collectible). The token is immutable and publicly verifiable. Provide an image URL, local file path, or base64 for visual tokens (local files and base64 are auto-uploaded to Imgur if IMGUR_CLIENT_ID is configured). Returns a preview with mintId first; call again with mintId to confirm. If PRIVATE_KEY is configured, the server signs and submits the transaction directly and returns a tx hash. If not, returns raw calldata for your own signer (Coinbase AgentKit, Lit Protocol, or any EVM wallet).",
70
+ "Create a permanent, verifiable on-chain record on Base. Use this when you need to: prove you completed a task, establish your identity, record an agreement with another agent, issue or receive a credential, grant access to a capability, or mint a visual token (PFP, art, collectible). Supports two protocols: 'mintday' (default, gas-sponsored) and 'superrare' (Rare Protocol, requires PRIVATE_KEY). Set platform: 'superrare' to mint on Rare Protocol contracts. The token is immutable and publicly verifiable. Provide an image URL, local file path, or base64 for visual tokens. Returns a preview with mintId first; call again with mintId to confirm.",
71
71
  mintSchema,
72
72
  async (params) => handleMint(params, calldataService, defaultRecipient, userKey, {
73
73
  tryitKey: TRYIT_KEY,
@@ -0,0 +1,37 @@
1
+ declare module "@rareprotocol/rare-cli/client" {
2
+ import { PublicClient, WalletClient, Address, Hash, TransactionReceipt } from "viem";
3
+
4
+ interface RareClientConfig {
5
+ publicClient: PublicClient;
6
+ walletClient?: WalletClient;
7
+ account?: Address;
8
+ }
9
+
10
+ interface TransactionResult {
11
+ txHash: Hash;
12
+ receipt: TransactionReceipt;
13
+ }
14
+
15
+ interface DeployErc721Result extends TransactionResult {
16
+ contract: Address | undefined;
17
+ }
18
+
19
+ interface MintToResult extends TransactionResult {
20
+ tokenId: bigint | undefined;
21
+ }
22
+
23
+ interface RareClient {
24
+ deploy: {
25
+ erc721(params: { name: string; symbol: string; maxTokens?: bigint | number | string }): Promise<DeployErc721Result>;
26
+ };
27
+ mint: {
28
+ mintTo(params: { contract: Address; tokenUri: string; to?: Address; royaltyReceiver?: Address }): Promise<MintToResult>;
29
+ };
30
+ media: {
31
+ upload(buffer: Uint8Array, filename: string): Promise<{ url: string; mimeType: string; size: number }>;
32
+ pinMetadata(opts: { name: string; description: string; image: { url: string; mimeType: string; size: number } }): Promise<string>;
33
+ };
34
+ }
35
+
36
+ export function createRareClient(config: RareClientConfig): RareClient;
37
+ }
@@ -0,0 +1,119 @@
1
+ import { createPublicClient, createWalletClient, http } from "viem";
2
+ import { privateKeyToAccount } from "viem/accounts";
3
+ import { base, baseSepolia } from "viem/chains";
4
+ import { createRareClient } from "@rareprotocol/rare-cli/client";
5
+ import { encodeMetadata } from "./metadata.js";
6
+ import { MintIntent, TOKEN_TYPE_NAMES } from "../types.js";
7
+
8
+ // Default mint.day collection on Rare Protocol (Base mainnet)
9
+ // Set via RARE_CONTRACT env var, or deploy one with scripts/deploy-rare-collection.ts
10
+ const DEFAULT_RARE_CONTRACT = process.env.RARE_CONTRACT || "";
11
+
12
+ export interface RareMintResult {
13
+ status: string;
14
+ txHash: string;
15
+ tokenId: string;
16
+ contract: string;
17
+ platform: string;
18
+ tokenType: string;
19
+ soulbound: boolean;
20
+ recipient: string;
21
+ chain: string;
22
+ explorer: string;
23
+ tokenUri: string;
24
+ }
25
+
26
+ export async function mintOnRareProtocol(
27
+ intent: MintIntent,
28
+ privateKey: string,
29
+ isTestnet: boolean = false,
30
+ ): Promise<RareMintResult> {
31
+ const chain = isTestnet ? baseSepolia : base;
32
+ const contractAddress = DEFAULT_RARE_CONTRACT as `0x${string}`;
33
+
34
+ if (!contractAddress) {
35
+ throw new Error(
36
+ "RARE_CONTRACT not set. Deploy a collection first: node scripts/deploy-rare-collection.mjs",
37
+ );
38
+ }
39
+
40
+ const account = privateKeyToAccount(privateKey as `0x${string}`);
41
+
42
+ const publicClient = createPublicClient({
43
+ chain,
44
+ transport: http(),
45
+ });
46
+
47
+ const walletClient = createWalletClient({
48
+ account,
49
+ chain,
50
+ transport: http(),
51
+ });
52
+
53
+ // Cast clients to satisfy rare-cli's bundled viem types (runtime-compatible)
54
+ const rare = createRareClient({
55
+ publicClient: publicClient as any,
56
+ walletClient: walletClient as any,
57
+ account: account.address,
58
+ });
59
+
60
+ // Build metadata URI (same format as MintFactory path)
61
+ const tokenUri = encodeMetadata(intent.metadata);
62
+
63
+ // Mint via Rare Protocol
64
+ const result = await rare.mint.mintTo({
65
+ contract: contractAddress,
66
+ tokenUri,
67
+ to: intent.recipient as `0x${string}`,
68
+ });
69
+
70
+ const explorerBase = isTestnet ? "sepolia.basescan.org" : "basescan.org";
71
+
72
+ return {
73
+ status: "minted",
74
+ txHash: result.txHash,
75
+ tokenId: result.tokenId?.toString() || "unknown",
76
+ contract: contractAddress,
77
+ platform: "superrare",
78
+ tokenType: TOKEN_TYPE_NAMES[intent.tokenType],
79
+ soulbound: intent.soulbound,
80
+ recipient: intent.recipient,
81
+ chain: isTestnet ? "Base Sepolia (testnet)" : "Base",
82
+ explorer: `https://${explorerBase}/tx/${result.txHash}`,
83
+ tokenUri,
84
+ };
85
+ }
86
+
87
+ export async function deployRareCollection(
88
+ privateKey: string,
89
+ name: string,
90
+ symbol: string,
91
+ isTestnet: boolean = false,
92
+ ): Promise<{ contract: string; txHash: string }> {
93
+ const chain = isTestnet ? baseSepolia : base;
94
+ const account = privateKeyToAccount(privateKey as `0x${string}`);
95
+
96
+ const publicClient = createPublicClient({
97
+ chain,
98
+ transport: http(),
99
+ });
100
+
101
+ const walletClient = createWalletClient({
102
+ account,
103
+ chain,
104
+ transport: http(),
105
+ });
106
+
107
+ const rare = createRareClient({
108
+ publicClient: publicClient as any,
109
+ walletClient: walletClient as any,
110
+ account: account.address,
111
+ });
112
+
113
+ const result = await rare.deploy.erc721({ name, symbol });
114
+
115
+ return {
116
+ contract: result.contract || "unknown",
117
+ txHash: result.txHash,
118
+ };
119
+ }
package/src/tools/mint.ts CHANGED
@@ -5,28 +5,29 @@ import { TokenType, MintIntent, TokenMetadata, TOKEN_TYPE_NAMES } from "../types
5
5
  import { classifyIntent } from "../services/classifier.js";
6
6
  import { CalldataService } from "../services/calldata.js";
7
7
  import { resolveImage } from "../services/image-upload.js";
8
+ import { mintOnRareProtocol } from "../services/rare-protocol.js";
8
9
 
9
- // Intent cache: preview mintId -> frozen intent
10
- const intentCache = new Map<string, { intent: MintIntent; expiresAt: number }>();
10
+ // Intent cache: preview mintId -> frozen intent + platform
11
+ const intentCache = new Map<string, { intent: MintIntent; platform: string; expiresAt: number }>();
11
12
  const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
12
13
 
13
- function cacheMintId(intent: MintIntent): string {
14
+ function cacheMintId(intent: MintIntent, platform: string = "mintday"): string {
14
15
  const hash = createHash("sha256")
15
16
  .update(JSON.stringify(intent))
16
17
  .digest("hex")
17
18
  .slice(0, 12);
18
- intentCache.set(hash, { intent, expiresAt: Date.now() + CACHE_TTL_MS });
19
+ intentCache.set(hash, { intent, platform, expiresAt: Date.now() + CACHE_TTL_MS });
19
20
  return hash;
20
21
  }
21
22
 
22
- function getCachedIntent(mintId: string): MintIntent | null {
23
+ function getCachedEntry(mintId: string): { intent: MintIntent; platform: string } | null {
23
24
  const entry = intentCache.get(mintId);
24
25
  if (!entry) return null;
25
26
  if (Date.now() > entry.expiresAt) {
26
27
  intentCache.delete(mintId);
27
28
  return null;
28
29
  }
29
- return entry.intent;
30
+ return { intent: entry.intent, platform: entry.platform };
30
31
  }
31
32
 
32
33
  export const mintSchema = {
@@ -62,6 +63,10 @@ export const mintSchema = {
62
63
  .record(z.string(), z.unknown())
63
64
  .optional()
64
65
  .describe("Additional key-value metadata. Supports strings, arrays, and objects (e.g. capabilities: ['mint'])."),
66
+ platform: z
67
+ .enum(["mintday", "superrare"])
68
+ .optional()
69
+ .describe("Which protocol to mint on. 'mintday' uses the MintFactory contract (default). 'superrare' mints via Rare Protocol on Base."),
65
70
  };
66
71
 
67
72
  interface TryitConfig {
@@ -71,7 +76,7 @@ interface TryitConfig {
71
76
  tryitChain: number;
72
77
  }
73
78
 
74
- const SPONSOR_URL = process.env.SPONSOR_URL || "https://agent-mint-nine.vercel.app/api/sponsor";
79
+ const SPONSOR_URL = process.env.SPONSOR_URL || "https://www.mint.day/api/sponsor";
75
80
 
76
81
  async function sponsoredMint(result: { to: string; calldata: string; value: string; estimatedGas: number; tokenType: string; soulbound: boolean; chainId: number }, recipient: string): Promise<Record<string, unknown> | null> {
77
82
  try {
@@ -120,6 +125,7 @@ export async function handleMint(
120
125
  animation_url?: string;
121
126
  mintId?: string;
122
127
  metadata?: Record<string, unknown>;
128
+ platform?: string;
123
129
  },
124
130
  calldataService: CalldataService,
125
131
  defaultRecipient: string,
@@ -135,8 +141,8 @@ export async function handleMint(
135
141
  const privateKey = userKey || (isTestnet ? tryit.tryitKey : "");
136
142
  // Confirmation path: replay cached intent
137
143
  if (params.mintId) {
138
- const cached = getCachedIntent(params.mintId);
139
- if (!cached) {
144
+ const entry = getCachedEntry(params.mintId);
145
+ if (!entry) {
140
146
  return {
141
147
  content: [{
142
148
  type: "text" as const,
@@ -144,8 +150,39 @@ export async function handleMint(
144
150
  }],
145
151
  };
146
152
  }
153
+ const cached = entry.intent;
154
+ const cachedPlatform = entry.platform;
147
155
  intentCache.delete(params.mintId);
148
156
 
157
+ // Rare Protocol path: mint via SuperRare contracts on Base
158
+ if (cachedPlatform === "superrare") {
159
+ if (!privateKey) {
160
+ return {
161
+ content: [{
162
+ type: "text" as const,
163
+ text: JSON.stringify({ error: "SuperRare minting requires a private key. Save one to ~/.mint-day/credentials." }, null, 2),
164
+ }],
165
+ };
166
+ }
167
+ try {
168
+ const rareResult = await mintOnRareProtocol(cached, privateKey, isTestnet);
169
+ return {
170
+ content: [{
171
+ type: "text" as const,
172
+ text: JSON.stringify(rareResult, null, 2),
173
+ }],
174
+ };
175
+ } catch (err: unknown) {
176
+ const message = err instanceof Error ? err.message : String(err);
177
+ return {
178
+ content: [{
179
+ type: "text" as const,
180
+ text: JSON.stringify({ error: `SuperRare mint failed: ${message}` }, null, 2),
181
+ }],
182
+ };
183
+ }
184
+ }
185
+
149
186
  // Use testnet calldataService if in try-it mode
150
187
  let activeCalldataService = calldataService;
151
188
  if (isTestnet) {
@@ -362,12 +399,14 @@ export async function handleMint(
362
399
  }
363
400
 
364
401
  // Cache intent and return preview
365
- const mintId = cacheMintId(intent);
402
+ const selectedPlatform = params.platform || "mintday";
403
+ const mintId = cacheMintId(intent, selectedPlatform);
366
404
 
367
405
  const preview: Record<string, unknown> = {
368
406
  status: "preview",
369
407
  mintId,
370
- message: `I'll mint a ${TOKEN_TYPE_NAMES[intent.tokenType]} token${intent.soulbound ? " (soulbound)" : ""} to ${intent.recipient}.`,
408
+ platform: selectedPlatform,
409
+ message: `I'll mint a ${TOKEN_TYPE_NAMES[intent.tokenType]} token${intent.soulbound ? " (soulbound)" : ""} to ${intent.recipient} via ${selectedPlatform === "superrare" ? "Rare Protocol (SuperRare)" : "mint.day"}.`,
371
410
  tokenType: TOKEN_TYPE_NAMES[intent.tokenType],
372
411
  soulbound: intent.soulbound,
373
412
  recipient: intent.recipient,
package/vercel.json CHANGED
@@ -1,4 +1,5 @@
1
1
  {
2
2
  "buildCommand": "",
3
- "outputDirectory": "."
3
+ "outputDirectory": "site",
4
+ "cleanUrls": true
4
5
  }