$MCRT · BEP-20 · BNB Chain
MCRTPay Integration Guide
Add $MCRT payments to any website or app in minutes. Copy-paste lib files, API routes, and React UI with no payment processor account needed.
Wallet:
MCRT contract:
Flow: User sends MCRT, pastes tx hash, API verifies on-chain through BSC RPC, then you activate whatever you sell.
YOUR_WALLET_ADDRESS (BSC BEP-20)MCRT contract:
0x4b8285aB433D8f69CB48d5Ad62b415ed1a221e4fFlow: User sends MCRT, pastes tx hash, API verifies on-chain through BSC RPC, then you activate whatever you sell.
Step 1 - Required env vars
Add to .env.local / .env.production:
MCRTPAY_WALLET_ADDRESS=0xYourReceivingWallet
MCRT_PRICE_USD=0.00013 # optional fallback if price APIs are down
No BscScan API key is required for the hosted MCRTPay verifier. It checks transaction receipts through BSC RPC and matches the $MCRT transfer event to your receiving wallet.
Option A - HTML / Vanilla JS (any stack)
No build step. Drop into any HTML page.
1. Include the scripts
<!-- QR code library -->
<script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"></script>
<!-- MCRTPay widget -->
<script src="https://mcrtpay.com/js/mcrtpay.js"></script>
2. Add a container
<div id="mcrtpay-container"></div>
3. Initialize
<script>
new MCRTPay({
containerSelector: '#mcrtpay-container',
walletAddress: '0xYourReceivingWallet',
paymentAmount: 25, // USD — widget converts to MCRT live
planId: 'premium',
successCallback: function(txHash, network) {
// fired after on-chain verification succeeds
activateYourPlan(txHash);
}
});
</script>
Tip: Keep
validateOnBackend: true for real sales. Only set it to false for a controlled manual-review flow, because it cannot prove payment on-chain by itself.
Option B - Next.js (3 lib files, no changes needed)
Copy these 3 files into your project. They work for both Pages Router and App Router.
lib/mcrtpay-config.ts
const DEFAULT_WALLET = 'YOUR_WALLET_ADDRESS'
export function getMCRTPayWalletAddress(): string {
return process.env.MCRTPAY_WALLET_ADDRESS || DEFAULT_WALLET
}
export const MCRTPAY_NETWORKS = [
{
id: 'bep20' as const,
name: 'BNB Chain (BEP-20)',
prefix: 'binance:',
explorerUrl: 'https://bscscan.com/tx/',
tokenAddress: '0x4b8285aB433D8f69CB48d5Ad62b415ed1a221e4f'
}
] as const
export const MCRTPAY_CONFIG = {
defaultNetwork: 'bep20',
currencies: ['MCRT'] as const,
defaultCurrency: 'MCRT' as const
} as const
export type MCRTPayNetwork = typeof MCRTPAY_NETWORKS[number]
export type MCRTPayCurrency = typeof MCRTPAY_CONFIG.currencies[number]
lib/crypto-pricing.ts
export interface CryptoPricing {
currency: string
priceUSD: number
lastUpdated: number
source: string
}
const CACHE_TTL = 15 * 60 * 1000
const cache = new Map<string, { data: CryptoPricing; ts: number }>()
const pending = new Map<string, Promise<CryptoPricing>>()
export async function getMCRTPrice(): Promise<CryptoPricing> {
const key = 'MCRT-USD'
if (pending.has(key)) return pending.get(key)!
const req = (async () => {
const cached = cache.get(key)
if (cached && Date.now() - cached.ts < CACHE_TTL) return cached.data
const sources = [
async () => {
const r = await fetch('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=magiccraft')
if (!r.ok) return null
const a = await r.json()
const p = Array.isArray(a) ? a[0]?.current_price : null
return p > 0 ? { currency: 'MCRT', priceUSD: +p, lastUpdated: Date.now(), source: 'CoinGecko' } : null
},
async () => {
const r = await fetch('https://api.dexscreener.com/latest/dex/search?q=magiccraft')
if (!r.ok) return null
const d = await r.json()
const pairs = (d?.pairs || []).map((p: any) => ({ p: +p.priceUsd, l: +(p.liquidity?.usd || 0) })).filter((x: any) => x.p > 0)
if (!pairs.length) return null
const total = pairs.reduce((s: number, x: any) => s + x.l, 0) || 1
const weighted = pairs.reduce((s: number, x: any) => s + x.p * x.l, 0) / total
return weighted > 0 ? { currency: 'MCRT', priceUSD: weighted, lastUpdated: Date.now(), source: 'DexScreener' } : null
}
]
for (const src of sources) {
try { const r = await src(); if (r) { cache.set(key, { data: r, ts: Date.now() }); return r } } catch {}
}
const fallback = { currency: 'MCRT', priceUSD: +(process.env.MCRT_PRICE_USD || '0.00013'), lastUpdated: Date.now(), source: 'Fallback' }
cache.set(key, { data: fallback, ts: Date.now() })
return fallback
})()
pending.set(key, req)
req.finally(() => pending.delete(key))
return req
}
export async function getUSDTPrice(): Promise<CryptoPricing> {
return { currency: 'USDT', priceUSD: 1.0, lastUpdated: Date.now(), source: 'Fixed' }
}
export async function getAllTokenPrices() {
const [mcrt, usdt] = await Promise.all([getMCRTPrice(), getUSDTPrice()])
return { MCRT: mcrt, USDT: usdt }
}
lib/blockchain-verification.ts
import { getMCRTPayWalletAddress } from './mcrtpay-config'
const MCRT_CONTRACT = '0x4b8285aB433D8f69CB48d5Ad62b415ed1a221e4f'.toLowerCase()
const TRANSFER_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
function bigintPow(base: bigint, exp: number): bigint {
let r = BigInt(1)
for (let i = 0; i < exp; i++) r *= base
return r
}
function toUnits(amount: number, decimals: number): bigint {
if (amount <= 0) return BigInt(0)
const parts = amount.toString().split('.')
const frac = ((parts[1] || '') + '0'.repeat(decimals)).slice(0, decimals)
return BigInt(parts[0] || '0') * bigintPow(BigInt(10), decimals) + BigInt(frac || '0')
}
export interface TransactionVerification {
isValid: boolean
txHash: string
network: 'bep20'
amount?: number
currency: 'MCRT'
error?: string
}
async function fetchBscReceipt(txHash: string) {
const rpcUrls = [
'https://bsc-dataseed1.binance.org/',
'https://bsc-dataseed2.binance.org/',
'https://rpc.ankr.com/bsc'
]
for (const url of rpcUrls) {
try {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
method: 'eth_getTransactionReceipt',
params: [txHash],
id: 1
})
})
if (!res.ok) continue
const data = await res.json()
if (data.result?.logs) return data.result
} catch {}
}
return null
}
export async function verifyTransaction(
txHash: string,
network: 'bep20',
expectedAmount: number,
currency: 'MCRT'
): Promise<TransactionVerification> {
const receipt = await fetchBscReceipt(txHash)
if (!receipt) return { isValid: false, txHash, network, currency, error: 'Transaction not found on BSC' }
if (receipt.status !== '0x1') return { isValid: false, txHash, network, currency, error: 'Transaction failed or is not confirmed' }
const decimals = 18
const expectedUnits = toUnits(expectedAmount, decimals)
const tolerance = expectedUnits * BigInt(95) / BigInt(100)
const recipient = '0x000000000000000000000000' + getMCRTPayWalletAddress().toLowerCase().slice(2)
for (const log of receipt.logs) {
if (log.address?.toLowerCase() !== MCRT_CONTRACT) continue
if (log.topics?.[0]?.toLowerCase() !== TRANSFER_TOPIC) continue
if (log.topics?.[2]?.toLowerCase() !== recipient) continue
const sent = BigInt(log.data || '0')
if (sent >= tolerance) {
const amount = Number(sent / bigintPow(BigInt(10), decimals - 4)) / 10000
return { isValid: true, txHash, network, currency, amount }
}
}
return { isValid: false, txHash, network, currency, error: `Payment not found or too low. Expected ~${expectedAmount} MCRT to ${getMCRTPayWalletAddress()}` }
}
export function getMinimumConfirmations(network: 'bep20'): number {
return 3
}
Option B - API Routes
Pages Router: pages/api/crypto/prices.ts
import type { NextApiRequest, NextApiResponse } from 'next'
import { getAllTokenPrices } from '@/lib/crypto-pricing'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'GET') return res.status(405).end()
try {
const prices = await getAllTokenPrices()
res.setHeader('Cache-Control', 'public, max-age=300, s-maxage=300')
return res.status(200).json(prices)
} catch {
return res.status(200).json({
USDT: { currency: 'USDT', priceUSD: 1.0, source: 'Fallback' },
MCRT: { currency: 'MCRT', priceUSD: +(process.env.MCRT_PRICE_USD || '0.00013'), source: 'Fallback' }
})
}
}
Pages Router: pages/api/mcrtpay/verify.ts
Replace the activatePlan block with your own auth + DB logic.
import type { NextApiRequest, NextApiResponse } from 'next'
import { verifyTransaction } from '@/lib/blockchain-verification'
import { getMCRTPayWalletAddress } from '@/lib/mcrtpay-config'
// TODO: import { getSession } from '@/lib/your-auth'
// TODO: import { activatePlan } from '@/lib/your-db'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') return res.status(405).end()
// const session = await getSession(req, res)
// if (!session?.user) return res.status(401).json({ error: 'Not authenticated' })
const { txHash, network, expectedAmount, currency, planId } = req.body || {}
if (!txHash || !network || !expectedAmount || !currency)
return res.status(400).json({ error: 'Missing: txHash, network, expectedAmount, currency' })
try {
const v = await verifyTransaction(txHash, network, +expectedAmount, currency)
if (!v.isValid) return res.status(400).json({ success: false, error: v.error, verification: v })
// --- Replace this with your DB logic ---
// await activatePlan(session.user.id, planId, { via: 'mcrt', txHash })
console.log(`[mcrtpay/verify] ${txHash} — ${expectedAmount} ${currency} — plan: ${planId}`)
return res.status(200).json({ success: true, verification: v, walletAddress: getMCRTPayWalletAddress() })
} catch (err: any) {
return res.status(500).json({ error: err.message || 'Verification error' })
}
}
App Router: Same logic. Swap
NextApiRequest/NextApiResponse for NextRequest/NextResponse and use export async function POST(request: NextRequest).
Option B - React Payment Component
Drop this into your pricing or checkout page. Replace onSuccess with a redirect or state update.
import { useState, useEffect, useCallback } from 'react'
const MCRT_WALLET = 'YOUR_WALLET_ADDRESS'
const MCRT_CONTRACT = '0x4b8285aB433D8f69CB48d5Ad62b415ed1a221e4f'
export function McrtPaymentSection({
planUsd,
planId,
onSuccess,
}: {
planUsd: number
planId: string
onSuccess: () => void
}) {
const [open, setOpen] = useState(false)
const [mcrtPrice, setMcrtPrice] = useState<number | null>(null)
const [txHash, setTxHash] = useState('')
const [verifying, setVerifying] = useState(false)
const [status, setStatus] = useState<{ type: 'success' | 'error' | 'pending'; msg: string } | null>(null)
const [copied, setCopied] = useState(false)
useEffect(() => {
if (!open) return
fetch('/api/crypto/prices')
.then(r => r.json())
.then(d => setMcrtPrice(d?.MCRT?.priceUSD ?? null))
}, [open])
const mcrtAmount = mcrtPrice ? Math.ceil(planUsd / mcrtPrice) : null
const copy = useCallback(() => {
navigator.clipboard.writeText(MCRT_WALLET).then(() => {
setCopied(true)
setTimeout(() => setCopied(false), 2000)
})
}, [])
const verify = async () => {
if (!txHash.trim()) return
setVerifying(true); setStatus(null)
try {
const res = await fetch('/api/mcrtpay/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ txHash: txHash.trim(), network: 'bep20', expectedAmount: mcrtAmount, currency: 'MCRT', planId }),
})
const data = await res.json()
if (res.status === 202) {
setStatus({ type: 'pending', msg: data.error || 'Waiting for confirmations. Try again in ~30s.' })
} else if (data.success) {
setStatus({ type: 'success', msg: 'Payment confirmed! Activating…' })
setTimeout(onSuccess, 1500)
} else {
setStatus({ type: 'error', msg: data.error || 'Verification failed. Check tx hash.' })
}
} catch {
setStatus({ type: 'error', msg: 'Network error. Please retry.' })
} finally {
setVerifying(false)
}
}
return (
<div className="border-t pt-4 mt-4">
<button onClick={() => { setOpen(v => !v); setStatus(null) }}
className="w-full flex justify-between items-center text-sm font-semibold py-1">
<span>⚡ Pay with $MCRT (instant, no trial)</span>
<span>{open ? '▲' : '▼'}</span>
</button>
{open && (
<div className="mt-4 p-4 border border-yellow-300 bg-yellow-50 rounded-xl space-y-3 text-sm">
<p>Send <strong>{mcrtAmount ? mcrtAmount.toLocaleString() : '…'} $MCRT</strong> on BNB Chain (BEP-20) to:</p>
<div className="flex items-center gap-2 bg-white border rounded-lg px-3 py-2">
<code className="flex-1 text-xs break-all">{MCRT_WALLET}</code>
<button onClick={copy} className="text-xs text-blue-600">{copied ? '✓ Copied' : 'Copy'}</button>
</div>
{mcrtPrice && (
<p className="text-xs text-gray-500">
Live price: ${mcrtPrice.toFixed(8)} · ≈ {mcrtAmount?.toLocaleString()} MCRT for ${planUsd} USD ·{' '}
Token: <a href={`https://bscscan.com/token/${MCRT_CONTRACT}`} target="_blank" rel="noopener noreferrer">BscScan</a>
</p>
)}
<div>
<label className="text-xs font-semibold block mb-1">
Paste your BscScan transaction hash after sending:
</label>
<input type="text" value={txHash} onChange={e => setTxHash(e.target.value)}
placeholder="0x…"
className="w-full px-3 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-yellow-400" />
</div>
{status && (
<div className={`p-3 rounded-lg text-sm font-medium ${
status.type === 'success' ? 'bg-green-100 text-green-800' :
status.type === 'pending' ? 'bg-blue-100 text-blue-800' :
'bg-red-100 text-red-800'}`}>
{status.msg}
</div>
)}
<button onClick={verify} disabled={verifying || !txHash.trim()}
className="w-full py-2.5 bg-yellow-400 hover:bg-yellow-500 text-black font-bold rounded-lg disabled:opacity-50">
{verifying ? 'Verifying on-chain…' : 'Verify $MCRT payment'}
</button>
<p className="text-xs text-gray-400 text-center">
Need $MCRT?{' '}
<a href="https://pancakeswap.finance" target="_blank" rel="noopener noreferrer">PancakeSwap</a>
{' '}or{' '}
<a href="https://www.gate.io/trade/MCRT_USDT" target="_blank" rel="noopener noreferrer">Gate.io</a>
</p>
</div>
)}
</div>
)
}
Usage:
<McrtPaymentSection
planUsd={selectedPlan.amount} // e.g. 25
planId={selectedPlan.id} // e.g. "premium"
onSuccess={() => router.push('/success?via=mcrt')}
/>
Widget options reference
| Option | Type | Default | Description |
|---|---|---|---|
containerSelector | string | '#mcrtpay-container' | CSS selector for the mount element |
walletAddress | string | Required | BEP-20 destination wallet. The widget will not silently default to the token contract. |
paymentAmount | number | - | USD amount. Auto-converts to MCRT live. |
mcrtAmount | number | - | Fixed MCRT amount (skips price fetch) |
currency | string | 'MCRT' | Token symbol |
defaultNetwork | string | 'bep20' | Default network tab |
planId | string | '' | Passed to /api/mcrtpay/verify for your DB |
validateOnBackend | boolean | true | Set false to skip backend verification |
backendUrl | string | '/api/mcrtpay/verify' | Your verification endpoint |
priceApiUrl | string | '/api/crypto/prices' | Your price endpoint |
theme | string | 'dark' | 'dark' or 'light' |
successCallback | function | - | function(txHash, network). Fires on confirmed payment. |
Wallet & contract
| Item | Value |
|---|---|
| Payment wallet | YOUR_WALLET_ADDRESS |
| MCRT token contract | 0x4b8285aB433D8f69CB48d5Ad62b415ed1a221e4f |
| Network | BNB Chain (BEP-20) |
| BscScan token | View on BscScan |
Where to buy $MCRT
| Exchange | Link |
|---|---|
| PancakeSwap (DEX) | pancakeswap.finance |
| Gate.io (CEX) | gate.io/trade/MCRT_USDT |
| MagicCraft in-app | magiccraft.io |
Need the fastest start?
Use the setup generator for vanilla HTML, or copy the Next.js snippets above and replace
Use the setup generator for vanilla HTML, or copy the Next.js snippets above and replace
YOUR_WALLET_ADDRESS with your own BEP-20 receiving wallet.