A full-stack NFT marketplace on Polygon. This document explains the project's purpose, how each component works, and how data/processes flow through the system.
BlueDragonIO (blue-dragon.io) is a full-stack NFT marketplace on the Polygon blockchain (chain 137). It sells 1-of-1 "membership" NFTs (DMS e-Solution Gold/Silver tiers, plus a "BluePass" collection) marketed as profit-sharing assets ("NFTs for profit").
Its distinguishing goal is to make Web3 purchasing feel like ordinary e-commerce: users sign in with their wallet, browse collections, add to cart, and pay with either a credit card / PayPal (Stripe) or USDT stablecoin. The platform then mints the NFT to them automatically, splits the revenue on-chain across a dozen team/company wallets, tracks referral rewards, and lets users withdraw those rewards — with all treasury movements guarded by a Gnosis Safe multisig so no single server key can drain funds.
The root is a monorepo of four independently-deployed services:
| Directory | Component | Stack | Role |
|---|---|---|---|
backend-master/ |
API + blockchain engine | Laravel 11 / PHP 8.2, MySQL, Redis | The brain: auth, catalog, orders, payments, minting, on-chain indexing, revenue split |
frontend_nuxt-main/ |
Web app | Nuxt 3 / Vue 3, Pinia, Reown AppKit + wagmi/viem | User & admin UI; wallet connection & signing |
dms_smart_contracts-main/ |
Smart contracts | Solidity 0.8.24, Hardhat + Ignition, OpenZeppelin v5 | The on-chain NFT (ERC-1155) and swap contracts |
safe-wallet-helper-main/ |
swh CLI |
Node/TypeScript (esbuild), Safe SDK, viem | Bridge for proposing Gnosis Safe multisig transactions |
dms_smart_contracts-main)DragonNftV2.sol — the live production NFT contract. An ERC-1155 enriched with AccessControl, Pausable, Supply, and ERC2981 royalties.
mint() requires totalSupply(id) == 0 and amount == 1 — so each token id can only ever be minted once, in a single unit. The on-chain id is deliberately set equal to the membership number for debugging sanity.MINTER_ROLE, BURNER_ROLE, PAUSER_ROLE, URI_SETTER_ROLE, ROYALTY_SETTER_ROLE. The constructor grants all of them to the owner (a Safe multisig, 0x3792…5941), which in turn grants MINTER_ROLE to the backend's hot wallet.contractURI() and uri() point at the backend's api.blue-dragon.io/api/web3/metadata/... endpoints (OpenSea-compatible).DragonSwapV2.sol — a migration/swap contract. It exists because a prior provider (Venly) assigned different ids to the old NFTs. It uses the ERC-1155 receiver-callback pattern: a user calls safeTransferFrom(user, swapContract, oldId, 1) on the old contract; that fires onERC1155Received, which looks up newId = oldIdToNewIdMap[oldId] and mints (or transfers) the correctly-numbered new NFT to the user.
setTokenMapping (guarded by isMapSet) prevents remapping even if deployer keys leak.reversible flag + newIdToOldIdMap provides the locking/unlocking path to recover NFTs stuck in the contract (two got stuck in the old v1 DragonSwap).Supporting: DragonNft.sol / DragonSwap.sol (legacy v1, still live), DragonNft_721.sol (unused ERC-721 variant), TestToken.sol (a fake USDT ERC-20 for staging). Deployment is scripted via Hardhat Ignition modules (ignition/modules/*.ts) targeting Polygon.
Key insight: the contracts contain no payment/escrow logic. Pricing, fiat, and USDT collection all happen off-chain in the backend, which — holding
MINTER_ROLE— mints the NFT after it confirms payment.
backend-master) — the engineAuthentication — Sign-In With Ethereum (SIWE / EIP-4361): Web3Controller issues a server nonce (/web3/nonce) and message args (/web3/message-args, chains:[137]); the user signs it in their wallet; /web3/login-verify verifies the signature (iltumio/siwe-php), recovers the address, creates/loads the User + UserWallet, and issues a Laravel Sanctum bearer token. No passwords.
HD-wallet key management (BlockchainManagerService + BlockchainService): the backend holds a master BIP-39 mnemonic and derives keys via BIP-44 path m/44'/chainId'/account'/0/addressIndex. Two crucial patterns:
getBlockchainServiceByPath('0','0') → the platform hot wallet (mints NFTs, pays gas).getBlockchainServiceByPath(userId, orderId) → a deterministic unique USDT deposit address per order, so payments can be detected simply by watching that address's balance.BlockchainService builds, gas-estimates (+15% buffer), signs (web3p/ethereum-tx), and broadcasts raw transactions — mintDragonNft, transferErc20, native MATIC transfer, event-log readers, and metadata fetchers.
Domain models fall into four clusters:
Collection → Nft → Product (+ ProductPrice, CollectionRatio for fee/profit %).Cart → Order → OrderProduct, Payment, Transaction, Discount.User → UserWallet, UserBalance, WithdrawRequest, Referral.BlockchainLogContract, BlockchainLogNftTransferEvent, BlockchainLogNftOwnership, BlockchainLogNftMetadata.Payment flow (both rails converge):
UserOrderController@checkout reserves products (15 min), applies discounts/fees, creates the Order (40-min expiry).StripeController/StripeWebhookController marks the order paid.
USDT → user gets the per-order deposit address; the every-minute app:check-order-usdt-payment scheduler dispatches CheckOrderPayment, which compares the on-chain USDT balance to the order total.PaymentVerified + MoneyTransfered and dispatches a MintNftJob per product → mints the ERC-1155 to the buyer's wallet.SegmentationsListener computes a weighted split (company, dev team, core team, CEO, COO, marketing, commission, collection fee, referral reward) from CollectionRatio; DoSegmentationsJob moves each USDT share on-chain from the order wallet to the destination wallets; referral rewards credit the referrer's UserBalance.Async / scheduled work (Redis queues): MintNftJob, CheckOrderPayment, FetchBlockchainNftEventLogs → ProcessBlockchainNftEventLogs (an event indexer that rebuilds BlockchainLogNftOwnership from Transfer logs), FetchBlockchainNftMetadata, ApplyBlockchainData (backfills historical/migrated mints), and ProcessWithdrawRequest.
Withdrawals via Gnosis Safe: WithdrawController creates a WithdrawRequest; ProcessWithdrawRequest calls BlockchainService::proposeTransferErc20ToSafe, which shells out to the swh CLI to propose the payout from the multisig rewards wallet. A human Safe owner must approve.
frontend_nuxt-main) — the UINuxt 3 SPA/SSR using Reown AppKit (formerly WalletConnect Web3Modal) + wagmi + viem, restricted to Polygon. State in Pinia (auth, cart stores, cookie-persisted, cross-tab synced). API calls go through a $api $fetch client that attaches the Sanctum bearer token (cookie auth-jwt) and auto-logs-out on 401.
plugins/parallel.walletconnect.ts wires a SIWE config into AppKit — connecting a wallet and signing the message logs the user in against the backend, matching the Web3Controller flow exactly.app/pages/): landing/marketing, collections/ (index + [slug] + [slug]/[id] detail), cart, and a dashboard/ area — orders/[id]/payment|usdt|success|failed|refunded, my-collection, referral, transactions, notifications. The USDT order page shows the deposit address + exact amount (lean-qr QR / copy) and polls /user/payment/check.components/Dashboard/NftCard.vue shows a "Swap With New" button when a collection has a swapContractAddress; clicking it uses wagmi writeContract to call safeTransferFrom on the old NFT contract, then notifies the backend (/user/nfts/swapped).card, paypal, usdt, mock (local only). Card/PayPal redirect to a hosted Stripe checkout URL.app/pages/admin/, role-gated): collection create/list, per-collection images/products/ratios/actions, coupons, users, withdraw-requests, notifications, settings, and a jobs page with a button to trigger the "fetch & process NFT event logs" backend job — matching the operator WORKFLOW.md.@tanstack/vue-query, sweetalert2 (dialogs/toasts), ultimate-table (admin datatables), axios for image uploads with progress (everything else uses $fetch), nuxt-security strict CSP allow-listing WalletConnect + DigitalOcean Spaces hosts.safe-wallet-helper-main) — the swh CLIA small Node/TypeScript CLI (built with esbuild to dist/swh.js, invoked by the backend as node /var/www/swh/dist/swh.js). It uses the official Safe SDK (@safe-global/api-kit + protocol-kit) and viem. Two commands:
propose-tx — ABI-encodes a contract call (e.g. ERC-20 transfer), wraps it in a Safe transaction, signs it as one owner, submits it to the Safe Transaction Service, and prints safetxhash:0x….check-tx — reports whether a proposal was executed or rejected; if multiple proposals share a nonce it confirms the siblings and exits 423.This keeps the platform's treasury under multisig control — the backend can propose payouts but a human owner must approve them.
StripeWebhookController@handle, the refund branch builds Payment::query()->where('details.id', …) but never calls ->first(), so it operates on a query builder instead of a model — the webhook-driven refund path appears broken. Worth verifying if refunds are in use.Smart contracts
dms_smart_contracts-main/contracts/DragonNftV2.sol — production ERC-1155 NFTdms_smart_contracts-main/contracts/DragonSwapV2.sol — migration/swap contractdms_smart_contracts-main/ignition/modules/*.ts — deployment modulesBackend (blockchain core)
backend-master/app/Services/BlockchainService.php, BlockchainManagerService.phpbackend-master/app/Jobs/CheckOrderPayment.php, MintNftJob.phpbackend-master/app/Jobs/FetchBlockchainNftEventLogs.php, ProcessBlockchainNftEventLogs.php, ApplyBlockchainData.phpbackend-master/app/Jobs/ProcessWithdrawRequest.phpbackend-master/app/Http/Controllers/Api/Web3Controller.php — SIWE authbackend-master/app/Http/Controllers/Api/User/UserOrderController.php, PaymentController.phpbackend-master/app/Http/Controllers/Api/StripeController.php, StripeWebhookController.phpbackend-master/routes/api.php, routes/console.phpFrontend
frontend_nuxt-main/app/plugins/parallel.walletconnect.ts — SIWE + AppKitfrontend_nuxt-main/app/plugins/02.api.ts — API client / bearer tokenfrontend_nuxt-main/app/stores/auth.ts, cart.tsfrontend_nuxt-main/app/components/Dashboard/NftCard.vue — swapfrontend_nuxt-main/app/pages/admin/jobs.vue — job triggersSafe helper
safe-wallet-helper-main/src/index.ts, src/commands/propose-tx.ts, check-tx.ts