기와장터 GIWA Village

기술 문서 (One-Pager) — 한옥 저잣거리 소셜 플랫폼의 아키텍처와 온체인 구현
라이브 데모는 게임 서버 없이도 GIWA 체인만으로 동작합니다 — 노점·길드·던전 기록은 컨트랙트에, 아바타 위치는 프레즌스 비컨(이벤트)으로. "가치는 온체인, 존재감은 비컨." 자동 시연은 데모 URL에 ?showcase=1, 또는 클론 후 showcase.cmd

기와장터는 GIWA 체인(이더리움 L2, OP Stack) 위의 3D 소셜 플랫폼입니다. 지갑이 아바타가 되고, P2P 거래가 노점이 되고, 신원 인증이 상인의 뱃지가 됩니다. 설계 원칙은 하나입니다 — 서버는 공간만 담당하고, 가치는 전부 체인 위에서 움직인다.

1시스템 아키텍처 — 서버리스 풀온체인

게임 서버가 없습니다. 브라우저가 GIWA 체인과 직접 읽고 씁니다. 거래·소유·기록은 컨트랙트가 들고, 실시간 위치만 저장 없는 이벤트(비컨)로 흘립니다. 그래서 두 방문자는 서버 없이 서로를 보고 거래합니다.

브라우저 A (viem) React Three Fiber 3D 읽기: 폴링 · 쓰기: 지갑 서명 데드레커닝 렌더 tx 직렬화 큐(nonce 안전) 브라우저 B 다른 방문자 — 같은 체인을 폴링해 서로를 본다 GIWA Sepolia · 컨트랙트 10종 MarketV3 · 노점/쿠폰 Guilds · 던전 Offers · 흥정 Honors · 칭호 Boxes · 복주머니 Workshop · 문양 Hearth · 모닥불 Boss · 도깨비 Profile · SDK 집계 Presence · 비컨 읽기 전용 연동 (외부 발행) DojangScroll · 인증 UP.ID · 이름 쓰기 tx (서명) 읽기 · 이벤트 로그 폴링 2초 비컨 (위치+속도)

※ 자금·키는 어떤 서버도 다루지 않는 비수탁 구조. 선택적 Colyseus 서버는 성능(위치 릴레이·읽기 캐시) 용도로만 붙일 수 있고, 없어도 마을은 돕니다 (스케일 로드맵 참고).

따라오는 성질: 사람과 AI 에이전트가 같은 자격으로 산다 — 입장 자격이 지갑과 서명뿐이라, 컨트랙트에는 그 서명이 사람 손가락에서 나왔는지 에이전트 루프에서 나왔는지 구분할 자리가 없습니다. 에이전트 전용 특권 API도 없습니다. AI가 노점을 열려면 사람과 똑같이 openStall 트랜잭션을 보내고 똑같이 가스를 냅니다. 흥정도, 길드 가입도, 도깨비 타격 쿨다운 30초도 그대로 적용됩니다. 규칙을 강제하는 주체가 서버가 아니라 컨트랙트이므로, 참가자가 사람이든 에이전트든 심판을 새로 믿을 필요가 없다는 점이 이 구조의 실익입니다.
같은 이유로: 클라이언트도 하나일 필요가 없다 — 입장 자격이 서명뿐이라 컨트랙트는 그 좌표가 웹에서 왔는지 언리얼에서 왔는지 구분할 자리가 없습니다. 그래서 마을을 구현체가 아니라 프로토콜로 적어 공개합니다 (PROTOCOL.md). 붙는 길은 둘입니다 — 체인 경로(정본: 노점·거래·길드· 프레즌스가 전부 온체인)와 룸 경로(선택: 15Hz 위치 동기화). 문서가 맞는지도 기계가 봅니다. npm test는 world.json만 읽는 낯선 클라이언트인 척 마을 전체를 걸어 본 뒤 웹 클라이언트가 밀려난 자리와 1mm 안에서 같은지 대조하고, 실시간 룸 둘(village_live · expedition)이 주고받는 메시지와 정원 · 주기 · 유예 시간이 문서에 다 있는지 대조합니다. npm run smoke:protocol은 한 걸음 더 가서 문서만 보고 짠 클라이언트로 실제 룸에 입장합니다(colyseus.js를 일부러 쓰지 않습니다 — 쓰면 아무것도 증명하지 못합니다). 이 검증을 처음 돌렸을 때 실제로 문서의 누락이 하나 잡혔습니다: 입장 확인 한 바이트를 돌려보내지 않으면 서버가 모든 메시지를 큐에 쌓아 둡니다 — 소켓은 멀쩡히 열려 있고 스냅샷만 영영 오지 않는, 조용해서 찾기 어려운 종류입니다.

2온체인 컴포넌트 (10종 · v2 묶음 검증 제출 대기)

컨트랙트역할주소
GiwaMarketV3노점 레지스트리(개설+가격 단일 tx)·에스크로(분쟁/환불)·ERC-1155 쿠폰(선물·소각) 0x1f3450…36e2
GiwaGuilds길드·백층 던전 — 주차 blockhash 시드, settleRun 재계산 검증 0xea796f…a31b
GiwaPresence저장 없는 위치+속도 비컨 이벤트 → 클라 데드레커닝 0x4d6006…7a93
GiwaHonors소울바운드 칭호 5종 — 다른 컨트랙트 상태로 자격 검증·장착 0xeaa867…a8e2
GiwaOffers흥정 에스크로 — 수락 시 MarketV3 조합해 buy→confirm→쿠폰 전달 한 tx 0x534a29…b909
GiwaBoxes복주머니(무료·블록해시 확정)·소울바운드 장신구 8종 0xeb0349…6b2e
GiwaHearth모닥불 온기 — 10분 창에 2명↑ 모이면 claim, 장날 2배 0xf78026…3365
GiwaWorkshop문양 공방(UGC) — 8×8 픽셀 온체인 등록·판매(창작자 직송)·착용 0x664762…c975
GiwaBoss주간 도깨비 토벌 — 함께 때리는 동시성 코업(블록해시 데미지·온기 보정·장날 2배), 개인·길드 기여, 소울바운드 전리품 0x11dbe7…9d63
GiwaProfile소셜 프로필 애그리게이터 — profileOf 1콜로 길드·칭호·장신구·문양·온기·전리품 (외부 dApp/SDK 진입점) 0xf4c2a7…fdf3

각 컨트랙트는 클라이언트 src/chain/<모듈>.ts와 1:1로 대응합니다. 카탈로그: contracts/README.md. 외부 발행 DojangScroll(신원 인증)·UP.ID(이름)는 읽기만 연동합니다.

3핵심 플로우 — 그림으로

(a) 노점 구매 + 에스크로 — 가격은 체인이 강제하고, 대금은 확정까지 컨트랙트가 보관합니다.

구매자 MarketV3.buyStall() 에스크로 판매자 가격 == 리스팅?일치만 수락 쿠폰(ERC-1155)즉시 구매자에 민팅 대금 보관Purchased이벤트=영수증 구매자 confirm() → 대금이 판매자에게 (또는 24h 후 자동, 분쟁 시 7일·환불 가능)

(b) 던전 — 로컬 즉시 플레이 + settleRun 재계산 검증

enterExpedition() 주차 시드 = 직전blockhash (핀·검증가능) 원정 회차 +1→ 회차별 맵 확정 클라이언트 로컬 플레이 문 선택 → keccak(시드·길드·회차·스텝·문) 로 즉시 판정 함정=잠정층 상실 · 순풍=+2picks[] 누적 (tx 없음, 즉각) settleRun(picks) 컨트랙트가 picks를같은 해시로 재계산 함정 포함=거부통과=길드 층수 확정 시드 귀환 1 tx

문을 열 때마다 tx를 보내지 않고 귀환 한 번으로 전 과정을 검증합니다 — 서버판보다 빠르고, 서버 없이도 트러스트리스. (결정론 결과의 한계는 AI Self-Audit에 공개)

(c) 프레즌스 — 2초 비컨 + 데드레커닝

A가 2초마다 (위치, 속도)를 비컨으로 발행 → B는 마지막 비컨 + 속도×경과시간으로 예측 이동 (부드러운 걸음) t0 비컨실측 위치 예측(속도 외삽) t0+2s 비컨보정

위치는 분쟁 가치가 없어 체인 상태로 두지 않습니다 — 저장 없는 이벤트라 비컨 1회 ≈ 2µETH. 악의적 좌표는 수신 시 월드 반경으로 클램프, 잔액 부족 시 자동 관전 모드(읽기는 무료).

4재미 설계 — 왜 다시 오는가

지갑이 기록이라면 마을은 장소여야 합니다. 장소가 이기려면 세 가지가 필요합니다: 머무를 이유·만날 시간·자랑할 것. 네 가지 시스템이 이 축을 온체인으로 채웁니다.

🔥 모닥불 (머무름) 광장 모닥불에 함께(2명 이상) 앉으면 10분 창마다 온기가 쌓입니다. 혼자서는 안 됩니다 — "함께 있음"의 온체인 증명 (GiwaHearth).
🏮 장날 (시간 약속) 매주 토 21시(KST) 장날엔 온기 2배 — 컨트랙트가 요일을 판정합니다. 모두가 같은 시간에 모일 이유가 밀도를 만듭니다.
🚩 길드 깃발 (소유) 주간 등반 상위 길드의 깃발이 광장에 게양됩니다. 개인 배지를 넘어 공간에 보이는 순위 — 싸우고 자랑할 이유.
🎨 문양 공방 (경제) 유저가 8×8 문양을 그려 온체인 등록·판매·착용. 창작물 거래는 보상이 아니므로 안전선과 양립 — 경제의 상류가 생깁니다.
🧿 도깨비 토벌 (함께 싸울 상대) 광장의 주간 보스를 함께 때려잡습니다 — 데미지는 블록해시 롤+온기 보정, 장날 2배. 개인·길드 기여가 온체인에 쌓이고 소울바운드 전리품을 받습니다.
🏮 촌장의 부탁 (온보딩) 무료 첫걸음 뒤 장사꾼·원정대·장인의 길 중 하나를 고릅니다. 지갑 준비는 선택한 길에서 체인 기록이 필요할 때 안내하고, 모바일은 가상 조이스틱으로 조작합니다.
👥 주민과 놀기 + 오늘의 부탁 아바타를 눌러 인사·절·박수·춤·건배를 고르고, 가까운 주민과 같은 동작을 맞추면 합동 장단이 뜹니다. KST마다 무료 부탁 하나를 골라 개인 도장을 받습니다.

손맛은 체인을 기다리지 않습니다. 도깨비를 때린 그 프레임에 섬광·화면 흔들림·효과음이 나가고, 얼마나 깎였는지는 영수증이 확정한 뒤 숫자로 떠오릅니다 — 남이 때린 것도 함께. 즉각 반응은 화면이, 사실 확인은 체인이 합니다. 연출은 새 메시도 파티클도 만들지 않고 이미 있는 카메라·재질·라이트 값을 몇 프레임 흔드는 것이라 저사양에서도 비용이 거의 없고, prefers-reduced-motion이면 흔들림만 끕니다. 효과음도 파일 0개 — 배경음과 같은 평조 5음 어휘를 씁니다(성공은 올라가고 실패는 내려갑니다).

노점은 주인이 접속을 끊어도 마을에 남습니다(레지스트리·가격 모두 온체인) — 콜드 스타트를 유저 생성 콘텐츠가 공간을 채워 풉니다. 참고 결: 마비노기 모닥불(함께 있는 것 자체가 콘텐츠), 리니지 혈맹(소유의 정치), 로블록스/마크(플랫폼은 도구·유저는 콘텐츠).

5기술 스택 & 품질

스택 클라이언트: React Three Fiber(three.js) · zustand · viem
컨트랙트: Solidity 0.8.36 · 10종 (ERC-1155 자체 구현 포함, 외부 의존성 0)
서버: 없음 (선택적 Colyseus는 성능 캐시용) · 배포: GitHub Actions → Pages
코드: src/chain/<모듈>.ts가 컨트랙트와 1:1 대응
검증 테스트를 가스 드는 것과 안 드는 것으로 3단 분리 (바로 아래) — 로직 68건은 체인 없이, 컨트랙트 E2E는 로컬 체인에서, 실거래는 꼭 필요할 때만. 에스크로·쿠폰 민팅·가격 강제 revert·이중 정산 거부는 실거래로, Dojang·UP.ID는 실배포본 조회로 증명했고, 실시간 동기화는 npm run smoke:realtime이 실제 소켓으로 들어가 봅니다.
신뢰 설계 비수탁: 서버는 자금·개인키 미접촉 · 가격은 체인이 강제 · 영수증은 이벤트 로그 · 서버는 위치·이모트만 중계하고 주소는 세션마다 다른 글의 서명으로만 붙인다 (표시용 데이터는 체인이 진실 소스)
규제 안전선 게임 플레이 보상은 양도 불가(소울바운드 설계) — 사행성(환전) 고리 차단 · 거래 대상은 코스메틱·쿠폰 등 재화만 (금융상품·오더북 없음) · 포지셔닝은 게임이 아닌 아바타 소셜 플랫폼
테스트 전략: 가스가 드는 검증을 맨 마지막 한 겹으로 — 포셋이 주소당 24시간 0.005 ETH라, 실거래로만 테스트하면 개발이 포셋 속도에 묶입니다. 그래서 규칙으로 검증할 수 있는 것은 전부 공짜로 옮기고, 가스는 로컬에서 흉내 낼 수 없는 것에만 씁니다.
계층검증 대상가스시간
npm test 흥정 하한선 강제·경계값·주민 데이터 정합성·야간 조명 하한· 던전 확률표 대조·문 앞 기척(어느 것이든 열에 여덟 이상 · 문이 하나로 굳지 않는지)·마을 충돌(벽 통과·상호작용 도달)·주민 걸음· 소리·화면 임계 대조·공개 프로토콜 정합성·HUD 함정·재미 루프 (68건) 0 — 체인 없음2초 미만
npm run smoke:protocol PROTOCOL.md만 보고 짠 클라이언트로 룸 입장 — 매치메이킹·입장 확인·프레임 해독·15Hz 스냅샷 (colyseus.js 안 씀) 0 — 로컬 서버~10초
npm run smoke:boot 실브라우저로 첫 방문자 동선 — 캔버스 부팅·노점·주민·온보딩 노출, 미처리 예외와 콘솔 에러 0건 (배포 게이트) 0 — 읽기만~30초
npm run test:local 컨트랙트 10종 전체 — 에스크로·쿠폰·흥정·길드/던전·칭호·복주머니·공방· 비컨·프로필·v2 상태 이전, 그리고 장날·쿨다운·10분 창을 시간 여행으로 (54건) 0 — 로컬 체인~5초
npm run test:chain 실제 배포본 10종 생존·Dojang·UP.ID (읽기 전용, --yes 필요) 0 — 읽기만~10초
개별 스모크 실거래가 정말 필요한 검증 (에스크로 실결제 등) 든다분 단위

로컬 계층의 핵심은 anvil을 chain-id 91342(GIWA Sepolia와 동일)로 띄운다는 점입니다. 코드의 체인 가드가 그대로 통과하고 같은 컨트랙트를 컴파일·배포해 같은 봇 코드를 돌리므로, 코드 경로가 프로덕션과 동일한 진짜 E2E입니다 — 가스만 무한할 뿐입니다. (GIWA 자체 노드는 500GB NVMe로 실제 테스트넷을 동기화하는 구조라 개발용 devnet으로는 맞지 않고, 같은 체인이라 가스도 그대로 듭니다.)

그리고 공짜보다 중요한 것이 시간 여행입니다. 테스트넷에서는 실제로 그 시각이 될 때까지 기다려야만 확인할 수 있던 것들 — 장날(토 21시 KST) 온기 2배, 도깨비 타격 쿨다운 30초, 모닥불 10분 창이 닫힌 뒤에야 수령 가능 — 을 초 단위로 점프해 검증합니다. 덤으로 컨트랙트(Solidity)와 클라이언트(TS)의 장날 판정이 표본 64개에서 일치하는지 대조합니다. 어긋나면 HUD는 "장날!"인데 실제로는 2배가 아닌 상태가 되는데, 그 불일치는 실시간 관찰로는 주 1회 1시간만 확인할 수 있습니다.

실제 실행 결과 보기 — npm run test:local (컨트랙트 10종 · 54건 · 가스 0)

아래 이미지는 scripts/render-run.mjs가 실행 출력을 그대로 SVG로 렌더한 것입니다 — 손으로 옮겨 적은 게 아니라 그때 그 실행의 표준출력입니다.

npm run test:local 실행 결과 — 컨트랙트 10종 54건 통과

안전 불변식은 scripts/lib/haggle.mjs 한 곳에 모아 봇과 테스트가 공유합니다. enforce()가 최종 관문이라 모델이 "공짜로 드릴게요"를 반환해도 차단되고, 그 케이스가 테스트에 들어 있습니다. 테스트가 실제로 회귀를 잡는지도 확인했습니다 — 과거에 겪었던 HUD pointer-events 버그를 다시 심으면 테스트가 실패합니다.

규칙 검증만으로는 "마을이 실제로 뜨는가"를 알 수 없습니다. 그래서 마지막에 npm run smoke:boot이 데모 빌드를 진짜 크롬으로 열어 첫 방문자 동선을 밟습니다 — 캔버스 부팅 → 노점·주민 등장 → 촌장의 부탁 노출 → 풍류 토글. 이때 두 가지가 요령입니다. 빈 localStorage로 엽니다 — 개발자 브라우저에는 온보딩 진행 기록이 남아 있어, 그대로 검사하면 "처음 온 사람에게만 나는 문제"를 영영 못 잡습니다. 그리고 공개 테스트넷 RPC에서 온 콘솔 에러는 일부러 걸러냅니다 — 남의 레이트리밋으로 게이트가 빨간불이 되면 사람은 곧 그것을 무시하게 되고, 그 순간 게이트는 죽은 장치입니다. CI는 npm test → smoke:boot 순으로 돌리고, 어느 하나라도 실패하면 배포를 멈춥니다 — 깨진 데모가 올라가지 않습니다.

6GIWA Tech Use — 사용 중인 체인 기능

① Dojang Verified Address — isVerified(주소, 발행자 ID) 온체인 조회로 상인 인증 뱃지 표시. ② UP.ID(Upbit Web3 Names) — 주소→이름 역방향 조회로 아바타·노점 이름표. ③ 1초 블록 — 구매·비컨 확정 지연의 상한, 던전 주간 시드(블록 해시 핀)의 시간 해상도. ④ OP Stack L2 가스 단가 — 프레즌스 비컨 (이벤트 전용 tx, 회당 ~2µETH)을 실용적으로 만드는 전제. ⑤ 웹 기반이라 월렛 웹뷰 탑재가 가능한 구조.

7나중 작업 (P2) — 스케일 로드맵

현재 아키텍처는 "가치는 온체인, 존재감은 비컨" — 게임 서버 0으로 동작하고, 수십 명 규모의 밀도 있는 광장에 최적화돼 있다. 병목은 체인이 아니라 ① 공개 RPC 폴링 (유저 수 × 2.5초) ② 클라 렌더 O(N) ③ 전체 순회 뷰다. 다음 단계는 서버를 신뢰가 아닌 성능을 위해 되들이는 것이다.

구역 샤딩 (세계관과 맞물리는 확장) — 저잣거리를 여러 골목(비단골목·먹자골목…)으로 나누면 각 광장은 수십 명 밀도를 유지하면서 전체는 확장된다. 밀도 낮은 대광장보다 좁고 북적이는 골목이 사회적으로도 낫다. 아래 인덱서·하이브리드 릴레이와 함께 가면 수백~수천 동접 경로가 보인다. (메인넷 가스에서의 비컨 빈도 적응은 열린 문제)