콘텐츠 대표 이미지 - 라라벨로 블록체인 멤버십 관리 시스템 개발하기
🔗 Blockchain × Laravel

라라벨로 블록체인 멤버십 관리 시스템 개발하기

Web3 시대의 스마트 컨트랙트 기반 회원 관리, PHP 백엔드로 완성하는 법 🚀

🔗 블록체인 멤버십 시스템 아키텍처 👤 사용자 Web3 지갑 연결 🐘 Laravel 백엔드 API + 비즈니스 로직 ⛓️ Web3 / Ethereum 스마트 컨트랙트 🎫 NFT 멤버십 온체인 권한 관리 📦 MySQL DB 멤버십 메타데이터 저장 트랜잭션 로그 관리 🔑 지갑 인증 MetaMask 서명 검증 JWT 토큰 발급 ⚡ Queue 처리 블록체인 이벤트 리스닝 비동기 트랜잭션 처리 🛡️ 권한 미들웨어 NFT 보유 여부 실시간 확인 등급별 접근 제어 Laravel + Web3PHP + Ethereum Smart Contract 통합 아키텍처

🤔 왜 라라벨로 블록체인 멤버십을?

요즘 Web3 프로젝트 하면 다들 Next.js + Solidity 조합만 생각하는데... 사실 라라벨(Laravel)이 블록체인 백엔드로 엄청 강력하다는 거 알고 있었음? 😮

라라벨은 PHP 기반 풀스택 프레임워크인데, 큐(Queue) 시스템, 이벤트 리스닝, 강력한 ORM, 미들웨어 체계가 블록체인 연동에 찰떡임ㅋㅋ

특히 블록체인 멤버십 관리 시스템은 단순히 "NFT 갖고 있냐 없냐"를 체크하는 게 아니라, 온체인 데이터와 오프체인 데이터를 동시에 관리해야 하거든. 이런 복잡한 비즈니스 로직 처리에 라라벨이 진짜 빛을 발함 ✨

💡 이 글에서 다루는 것들
① 라라벨 + Web3PHP 환경 세팅
② 지갑 기반 인증 시스템 구현
③ 스마트 컨트랙트 연동 (ERC-721 NFT 멤버십)
④ 블록체인 이벤트 리스닝 & 큐 처리
⑤ 멤버십 등급별 권한 미들웨어
⑥ 실전 배포 팁

참고로 이런 기술 스택 조합을 배우고 싶다면 재능넷에서 블록체인 개발 관련 재능을 가진 분들을 찾아볼 수도 있어. 실무 경험자한테 직접 배우는 게 제일 빠르니까 😄

🛠️ 개발 환경 세팅

필요한 패키지들

먼저 라라벨 프로젝트 생성하고 필요한 패키지들 설치해야 함. 핵심 패키지는 web3p/web3.php야. 이게 PHP에서 Ethereum 노드랑 통신할 수 있게 해주는 라이브러리임.


# 라라벨 프로젝트 생성

composer create-project laravel/laravel blockchain-membership

cd blockchain-membership


# Web3 PHP 라이브러리 설치

composer require web3p/web3.php


# 지갑 서명 검증용

composer require kornrunner/keccak

composer require simplito/elliptic-php


# JWT 인증

composer require tymon/jwt-auth


# 환경변수 설정

cp .env.example .env

php artisan key:generate

.env 설정

블록체인 연결 정보를 환경변수로 관리해야 함. 하드코딩은 절대 금지ㅋㅋ 보안 사고 나면 진짜 큰일남.


# .env 파일에 추가

ETHEREUM_RPC_URL=https://mainnet.infura.io/v3/YOUR_PROJECT_ID

ETHEREUM_CHAIN_ID=1

CONTRACT_ADDRESS=0xYourNFTContractAddress

PRIVATE_KEY=your_backend_wallet_private_key


# 테스트넷 사용시 (Sepolia)

ETHEREUM_RPC_URL=https://sepolia.infura.io/v3/YOUR_PROJECT_ID

ETHEREUM_CHAIN_ID=11155111
⚠️ 주의! PRIVATE_KEY는 절대 Git에 올리면 안 됨!! .gitignore에 .env 파일 포함됐는지 반드시 확인. 실수로 올렸다가 지갑 털리는 사례 실제로 있음 😱

🔑 지갑 기반 인증 시스템

기존 이메일/패스워드 인증 대신 MetaMask 지갑 서명으로 로그인하는 시스템을 구현할 거임. 원리는 이렇게 됨:

① 프론트에서
지갑 주소 전송
→
② 서버가
Nonce 발급
→
③ 사용자가
Nonce 서명
→
④ 서버에서
서명 검증
→
⑤ JWT
토큰 발급

Nonce 발급 컨트롤러


<?php

// app/Http/Controllers/AuthController.php

namespace App\Http\Controllers;


use App\Models\User;

use Illuminate\Http\Request;

use Illuminate\Support\Str;


class AuthController extends Controller

{

// 지갑 주소로 Nonce 요청

public function getNonce(Request $request)

{

$request->validate([

'wallet_address' => 'required|string|size:42'

]);


$address = strtolower($request->wallet_address);

$nonce = Str::random(32);


$user = User::updateOrCreate(

['wallet_address' => $address],

['nonce' => $nonce]

);


return response()->json([

'nonce' => $nonce,

'message' => "멤버십 로그인 서명: {$nonce}"

]);

}

}

서명 검증 로직

이 부분이 핵심임. MetaMask가 서명한 메시지를 복호화해서 실제 서명자의 지갑 주소를 복원하고, 요청한 주소랑 일치하는지 확인하는 거임. 이게 ecRecover 방식이야.


<?php

// app/Services/Web3AuthService.php

namespace App\Services;


use kornrunner\Keccak;

use Elliptic\EC;


class Web3AuthService

{

public function verifySignature(

string $address,

string $message,

string $signature

): bool {

// Ethereum 서명 메시지 포맷

$msgLen = strlen($message);

$hash = Keccak::hash(

"\x19Ethereum Signed Message:\n{$msgLen}{$message}",

256

);


// 서명에서 r, s, v 추출

$sign = [

"r" => substr($signature, 2, 64),

"s" => substr($signature, 66, 64),

];

$recid = ord(hex2bin(substr($signature, 130, 2))) - 27;


// 공개키 복원

$ec = new EC('secp256k1');

$pubkey = $ec->recoverPubKey($hash, $sign, $recid);


// 주소 생성 및 비교

$recoveredAddress = $this->pubKeyToAddress($pubkey);

return strtolower($recoveredAddress) === strtolower($address);

}


private function pubKeyToAddress($pubkey): string

{

$pubkeyHex = $pubkey->encode('hex');

$hash = Keccak::hash(hex2bin(substr($pubkeyHex, 2)), 256);

return '0x' . substr($hash, -40);

}

}
✅ 이 방식의 장점
패스워드가 서버에 저장되지 않음! 사용자의 개인키는 절대 서버로 전송되지 않고, 서명값만 전달됨. 진짜 Web3스러운 보안 모델이지 ㅋㅋ
🎫 NFT 멤버십 등급 시스템 ERC-721 기반 온체인 등급 관리 🥉 브론즈 Token ID: 1~1000 기본 콘텐츠 접근 월 10회 API 호출 커뮤니티 참여 기본 대시보드 0.01 ETH 🥈 실버 Token ID: 1001~3000 프리미엄 콘텐츠 접근 월 100회 API 호출 고급 분석 도구 우선 고객 지원 0.05 ETH 🥇 골드 ⭐인기 Token ID: 3001~5000 전체 콘텐츠 무제한 무제한 API 호출 거버넌스 투표권 수익 배분 참여 전담 매니저 배정 0.1 ETH 💎 다이아몬드 Token ID: 5001~5100 골드 모든 혜택 포함 프로토콜 수수료 면제 DAO 제안권 화이트리스트 우선권 1.0 ETH * 등급은 NFT Token ID 범위로 자동 분류됨 (스마트 컨트랙트 기준)

⛓️ 스마트 컨트랙트 연동

Web3Service 클래스 만들기

라라벨에서 이더리움 노드와 통신하는 서비스 클래스를 만들 거임. web3p/web3.php 라이브러리를 래핑해서 쓰기 편하게 만드는 게 포인트임 ㅋㅋ


<?php

// app/Services/Web3Service.php

namespace App\Services;


use Web3\Web3;

use Web3\Contract;

use Web3\Providers\HttpProvider;

use Web3\RequestManagers\HttpRequestManager;


class Web3Service

{

protected Web3 $web3;

protected Contract $contract;

protected string $contractAddress;


// ERC-721 ABI (핵심 함수만)

protected array $abi = [

['name' => 'balanceOf', 'type' => 'function',

'inputs' => [['name' => 'owner', 'type' => 'address']],

'outputs' => [['name' => '', 'type' => 'uint256']],

'stateMutability' => 'view'],

['name' => 'tokenOfOwnerByIndex', 'type' => 'function',

'inputs' => [

['name' => 'owner', 'type' => 'address'],

['name' => 'index', 'type' => 'uint256']

],

'outputs' => [['name' => '', 'type' => 'uint256']],

'stateMutability' => 'view'],

];


public function __construct()

{

$rpcUrl = config('blockchain.rpc_url');

$this->contractAddress = config('blockchain.contract_address');


$this->web3 = new Web3(

new HttpProvider(

new HttpRequestManager($rpcUrl, 30)

)

);


$this->contract = new Contract(

$this->web3->provider,

json_encode($this->abi)

);

}


// NFT 보유 여부 확인

public function getNFTBalance(string $walletAddress): int

{

$balance = 0;

$this->contract->at($this->contractAddress)

->call('balanceOf', $walletAddress,

function ($err, $result) use (&$balance) {

if (!$err) {

$balance = (int) $result[0]->toString();

}

}

);

return $balance;

}


// 멤버십 등급 판별

public function getMembershipTier(string $walletAddress): string

{

$balance = $this->getNFTBalance($walletAddress);

if ($balance === 0) return 'none';


// 첫 번째 토큰 ID로 등급 판별

$tokenId = $this->getTokenIdByIndex($walletAddress, 0);


return match(true) {

$tokenId >= 5001 => 'diamond',

$tokenId >= 3001 => 'gold',

$tokenId >= 1001 => 'silver',

default => 'bronze'

};

}

}

Config 파일 분리


<?php

// config/blockchain.php

return [

'rpc_url' => env('ETHEREUM_RPC_URL'),

'chain_id' => env('ETHEREUM_CHAIN_ID', 1),

'contract_address' => env('CONTRACT_ADDRESS'),

'private_key' => env('PRIVATE_KEY'),

'cache_ttl' => 300, // 5분 캐싱

];

🛡️ 멤버십 권한 미들웨어

이제 진짜 꿀 파트임 ㅋㅋ 라라벨 미들웨어를 활용해서 라우트별로 멤버십 등급 체크를 자동화하는 거야. 이거 한번 만들어두면 나중에 라우트에 한 줄만 추가하면 됨.


<?php

// app/Http/Middleware/CheckMembership.php

namespace App\Http\Middleware;


use App\Services\Web3Service;

use Closure;

use Illuminate\Http\Request;

use Illuminate\Support\Facades\Cache;


class CheckMembership

{

protected array $tierHierarchy = [

'none' => 0,

'bronze' => 1,

'silver' => 2,

'gold' => 3,

'diamond' => 4,

];


public function handle(Request $request, Closure $next, string $requiredTier = 'bronze')

{

$user = $request->user();

$walletAddress = $user->wallet_address;


// 캐시로 블록체인 호출 최소화 (5분 캐싱)

$cacheKey = "membership_tier_{$walletAddress}";

$currentTier = Cache::remember($cacheKey, 300, function () use ($walletAddress) {

$web3Service = app(Web3Service::class);

return $web3Service->getMembershipTier($walletAddress);

});


// 등급 비교

$currentLevel = $this->tierHierarchy[$currentTier] ?? 0;

$requiredLevel = $this->tierHierarchy[$requiredTier] ?? 0;


if ($currentLevel < $requiredLevel) {

return response()->json([

'error' => '멤버십 등급이 부족합니다',

'required' => $requiredTier,

'current' => $currentTier,

], 403);

}


// 현재 등급 정보를 request에 추가

$request->merge(['membership_tier' => $currentTier]);

return $next($request);

}

}

라우트에 미들웨어 적용


<?php

// routes/api.php

use App\Http\Controllers\ContentController;

use App\Http\Controllers\AuthController;


// 인증 라우트

Route::post('/auth/nonce', [AuthController::class, 'getNonce']);

Route::post('/auth/verify', [AuthController::class, 'verifyAndLogin']);


// 멤버십 보호 라우트

Route::middleware(['auth:api'])->group(function () {


// 브론즈 이상

Route::middleware('membership:bronze')->group(function () {

Route::get('/content/basic', [ContentController::class, 'basic']);

Route::get('/community', [ContentController::class, 'community']);

});


// 실버 이상

Route::middleware('membership:silver')->group(function () {

Route::get('/content/premium', [ContentController::class, 'premium']);

Route::get('/analytics', [ContentController::class, 'analytics']);

});


// 골드 이상

Route::middleware('membership:gold')->group(function () {

Route::get('/governance/vote', [GovernanceController::class, 'vote']);

Route::get('/revenue/share', [RevenueController::class, 'share']);

});


// 다이아몬드 전용

Route::middleware('membership:diamond')->group(function () {

Route::post('/dao/propose', [DaoController::class, 'propose']);

});

});
💡 캐싱 전략이 핵심!
블록체인 RPC 호출은 비용이 발생하고 느림. 그래서 미들웨어에서 Redis 캐시를 활용해서 5분간 등급 정보를 캐싱하는 거야. NFT 전송이 일어나면 캐시를 무효화하는 이벤트 리스너도 같이 만들어야 함 👇

⚡ 블록체인 이벤트 리스닝 & 큐 처리

NFT가 전송되면 실시간으로 멤버십 상태를 업데이트해야 함. 이걸 라라벨 큐(Queue) + 이벤트 리스너로 처리하는 게 베스트 프랙티스임 ㅋㅋ

블록체인 이벤트 폴링 커맨드


<?php

// app/Console/Commands/ListenBlockchainEvents.php

namespace App\Console\Commands;


use App\Jobs\ProcessNFTTransfer;

use App\Services\Web3Service;

use Illuminate\Console\Command;

use Illuminate\Support\Facades\Cache;


class ListenBlockchainEvents extends Command

{

protected $signature = 'blockchain:listen';

protected $description = 'NFT Transfer 이벤트 리스닝';


public function handle(Web3Service $web3Service)

{

$this->info('블록체인 이벤트 리스닝 시작...');


// 마지막으로 처리한 블록 번호

$lastBlock = Cache::get('last_processed_block', 'latest');


while (true) {

$events = $web3Service->getTransferEvents($lastBlock);


foreach ($events as $event) {

// 큐에 작업 추가

ProcessNFTTransfer::dispatch(

$event['from'],

$event['to'],

$event['tokenId']

);

$lastBlock = $event['blockNumber'];

}


Cache::put('last_processed_block', $lastBlock, 86400);

sleep(15); // 15초마다 폴링

}

}

}

NFT 전송 처리 Job


<?php

// app/Jobs/ProcessNFTTransfer.php

namespace App\Jobs;


use App\Models\User;

use App\Models\MembershipLog;

use Illuminate\Bus\Queueable;

use Illuminate\Contracts\Queue\ShouldQueue;

use Illuminate\Foundation\Bus\Dispatchable;

use Illuminate\Support\Facades\Cache;


class ProcessNFTTransfer implements ShouldQueue

{

use Dispatchable, Queueable;


public function __construct(

public string $fromAddress,

public string $toAddress,

public int $tokenId

) {}


public function handle()

{

// 보낸 사람 캐시 무효화

if ($this->fromAddress !== '0x0000000000000000000000000000000000000000') {

Cache::forget("membership_tier_{$this->fromAddress}");

$this->updateUserMembership($this->fromAddress);

}


// 받은 사람 캐시 무효화

Cache::forget("membership_tier_{$this->toAddress}");

$this->updateUserMembership($this->toAddress);


// 전송 로그 기록

MembershipLog::create([

'from_address' => $this->fromAddress,

'to_address' => $this->toAddress,

'token_id' => $this->tokenId,

'event_type' => 'transfer',

'processed_at' => now(),

]);

}


private function updateUserMembership(string $address): void

{

$web3Service = app(Web3Service::class);

$tier = $web3Service->getMembershipTier($address);


User::where('wallet_address', $address)

->update(['membership_tier' => $tier]);

}

}
⚡ NFT 전송 이벤트 처리 플로우 ⛓️ 이더리움 Transfer 이벤트 발생 🔍 폴링 커맨드 15초 주기 감지 📬 Laravel Queue Redis 큐 적재 ⚙️ Job 처리 캐시 무효화 + DB 업데이트 ✅ 멤버십 갱신 실시간 권한 반영 🔄 큐 워커 실행 명령어 php artisan queue:work redis --queue=blockchain --tries=3 Supervisor로 프로세스 관리 권장 📊 처리 성능 지표 평균 처리 시간: ~200ms 재시도 횟수: 최대 3회 실패 시 Dead Letter Queue 이동

🗄️ 데이터베이스 마이그레이션

블록체인 멤버십 시스템에 필요한 DB 구조를 잡아보자. 온체인 데이터는 블록체인에 있고, 오프체인 메타데이터는 MySQL에 저장하는 하이브리드 구조임.


<?php

// database/migrations/create_users_table.php

Schema::create('users', function (Blueprint $table) {

$table->id();

$table->string('wallet_address', 42)->unique();

$table->string('nonce', 64)->nullable();

$table->enum('membership_tier', ['none','bronze','silver','gold','diamond'])

->default('none');

$table->string('username')->nullable();

$table->string('email')->nullable();

$table->timestamp('last_blockchain_sync')->nullable();

$table->timestamps();

$table->index('wallet_address');

$table->index('membership_tier');

});


// membership_logs 테이블

Schema::create('membership_logs', function (Blueprint $table) {

$table->id();

$table->string('from_address', 42)->nullable();

$table->string('to_address', 42);

$table->unsignedBigInteger('token_id');

$table->string('event_type', 20); // mint, transfer, burn

$table->string('tx_hash', 66)->nullable();

$table->unsignedBigInteger('block_number')->nullable();

$table->timestamp('processed_at');

$table->timestamps();

$table->index(['to_address', 'token_id']);

});

🚀 실전 배포 & 운영 팁

Supervisor 설정 (큐 워커 관리)


; /etc/supervisor/conf.d/laravel-blockchain.conf

[program:laravel-blockchain-worker]

process_name=%(program_name)s_%(process_num)02d

command=php /var/www/html/artisan queue:work redis

--queue=blockchain,default

--sleep=3

--tries=3

--max-time=3600

autostart=true

autorestart=true

stopasgroup=true

killasgroup=true

user=www-data

numprocs=2

redirect_stderr=true

stdout_logfile=/var/log/blockchain-worker.log


[program:laravel-blockchain-listener]

command=php /var/www/html/artisan blockchain:listen

autostart=true

autorestart=true

user=www-data

numprocs=1

stdout_logfile=/var/log/blockchain-listener.log

성능 최적화 체크리스트

항목 방법 효과
🔴 RPC 호출 최소화 Redis 캐싱 (TTL 5분) API 비용 90% 절감
🟡 DB 쿼리 최적화 wallet_address 인덱스 조회 속도 10배 향상
🟢 큐 처리 Redis Queue + Supervisor 비동기 처리로 응답속도 개선
🔵 Rate Limiting ThrottleRequests 미들웨어 DDoS 방어
🟣 모니터링 Laravel Telescope + Horizon 큐 상태 실시간 확인
⚠️ 운영 시 주의사항
① Infura/Alchemy 무료 플랜 한도 체크 필수 (일일 요청 제한 있음)
② 블록체인 네트워크 다운 시 폴백 처리 로직 필요
③ 가스비 급등 시 트랜잭션 실패 대비 재시도 로직 구현
④ 스마트 컨트랙트 업그레이드 시 ABI 동기화 필수

Horizon으로 큐 모니터링


# Laravel Horizon 설치 (큐 모니터링 대시보드)

composer require laravel/horizon

php artisan horizon:install

php artisan migrate


# Horizon 실행

php artisan horizon


# 대시보드 접근: /horizon
✅ Horizon의 장점
큐 처리 현황, 실패한 Job, 처리 속도 등을 웹 대시보드에서 실시간으로 확인 가능! 블록체인 이벤트 처리 상황을 한눈에 볼 수 있어서 운영이 훨씬 편해짐 😎
🛠️ 전체 기술 스택 요약 🖥️ 프론트엔드 Next.js / React ethers.js v6 wagmi + viem MetaMask SDK WalletConnect TailwindCSS 🐘 Laravel 백엔드 Laravel 11.x web3p/web3.php tymon/jwt-auth Laravel Horizon Laravel Telescope kornrunner/keccak ⛓️ 블록체인 Ethereum / EVM ERC-721 NFT Solidity 0.8.x Hardhat / Foundry OpenZeppelin Infura / Alchemy 🏗️ 인프라 Docker + Nginx MySQL 8.0 Redis 7.x Supervisor AWS EC2 / GCP GitHub Actions CI/CD * 각 레이어는 독립적으로 스케일 가능한 마이크로서비스 구조 권장

🎯 실전 개발 시 자주 겪는 문제들

1
web3p/web3.php 비동기 콜백 지옥

web3.php는 콜백 기반이라 중첩이 심해질 수 있음. ReactPHP나 별도 래퍼 클래스로 프로미스 패턴처럼 만들어 쓰는 게 좋음. 아니면 Guzzle HTTP로 직접 JSON-RPC 호출하는 방법도 있음.

2
BigNumber 처리 문제

이더리움 값은 wei 단위라 PHP의 일반 int로 처리하면 오버플로우 남. brick/math 패키지의 BigDecimal을 써야 함. 1 ETH = 10^18 wei라는 거 항상 기억!

3
체크섬 주소 vs 소문자 주소

이더리움 주소는 EIP-55 체크섬 형식(대소문자 혼합)과 소문자 형식이 있음. DB 저장 시 항상 strtolower()로 통일하고, 비교할 때도 소문자로 맞춰야 함.

4
RPC 레이트 리밋 초과

Infura 무료 플랜은 초당 10 요청 제한이 있음. 캐싱 전략 없이 미들웨어에서 매 요청마다 RPC 호출하면 금방 한도 초과됨. Redis 캐싱은 선택이 아닌 필수!

5
블록 재구성(Reorg) 처리

이더리움은 가끔 블록이 재구성됨. 이벤트 처리 시 최소 12 블록 확인 후 처리하는 게 안전함. confirmations 파라미터를 설정해서 처리하자.

💡 테스트 환경 구성 팁
로컬 개발 시 Hardhat으로 로컬 이더리움 노드를 띄우고, ETHEREUM_RPC_URL=http://localhost:8545로 설정하면 됨. 가스비 없이 무한 테스트 가능 ㅋㅋ 진짜 꿀팁임

📚 마무리 & 다음 단계

라라벨로 블록체인 멤버십 시스템을 구현하는 전체 흐름을 살펴봤음! 🎉

핵심을 정리하면:

지갑 인증 스마트 컨트랙트 연동 미들웨어 권한 관리 큐 기반 이벤트 처리 Redis 캐싱

이 다섯 가지가 라라벨 블록체인 멤버십 시스템의 핵심 축임. 각각을 잘 이해하고 조합하면 꽤 견고한 Web3 백엔드를 만들 수 있어 😎

더 나아가면 DAO 거버넌스 시스템, 온체인 투표, 수익 분배 스마트 컨트랙트까지 확장할 수 있음. 이런 심화 내용은 재능넷에서 블록체인 전문가들의 강의나 컨설팅을 통해 더 깊이 배울 수 있으니 참고해봐!

🚀 다음 단계로 추천하는 것들
① ERC-1155 멀티토큰으로 업그레이드 (더 유연한 등급 관리)
② The Graph 프로토콜로 이벤트 인덱싱 (폴링 대신 서브그래프 활용)
③ IPFS 연동으로 NFT 메타데이터 탈중앙화
④ Chainlink 오라클로 외부 데이터 연동
⑤ Account Abstraction (ERC-4337)으로 가스비 없는 UX 구현

Web3 개발은 빠르게 변하는 분야라 계속 공부해야 하는 건 맞는데, 라라벨이라는 든든한 백엔드 프레임워크를 기반으로 하면 훨씬 안정적으로 개발할 수 있음. PHP 개발자들도 충분히 Web3 세계에 뛰어들 수 있다는 거 이 글로 느꼈으면 좋겠음 💪

댓글 작성

이 글에 대한 여러분의 생각을 들려주세요

댓글 0
댓글을 불러오는 중입니다.

아직 댓글이 없습니다.