|
| 1 | +# #1. 포인트를 기준으로 사용자 나열하기 |
| 2 | +# # 조건) 500포인트 이상인 사람만 걸러서 나열할 것 |
| 3 | + |
| 4 | +# #2. 포인트에 따른 nft 등급 매핑 |
| 5 | + |
| 6 | +# #3.nft 발급 대상자 선정 |
| 7 | +# # 1) 전체 상위 3퍼센트 + 1500포가 넘는지 checking(저장) -> nft 발급 |
| 8 | +# # 2) 전체 상위 10퍼 + 상위 3퍼센트 제외 + 1000포가 넘는지 checking(저장) -> nft 발급급 |
| 9 | +# # 3) 전체 상위 40퍼 + 상위 3 + 상위 10 제외 + 500포 이상 chekcing(저장) -> ntt 발급 |
| 10 | + |
| 11 | +# #4. nft 발급 구현(병렬 처리) |
| 12 | +# #5. db에 저장 |
| 13 | +# #6. return 값은 void로 하는데 checking 용으로 해보기 |
| 14 | +# # service/nft_service.py |
| 15 | + |
| 16 | +# from src.main.nft.nft_model import NFTRecord |
| 17 | +# from src.main.nft.nft_Info_dto import NftResponseDTO |
| 18 | +# from src.main.users.repository.UserRepository import UserRepository |
| 19 | +# from src.main.nft.nft_repository import save_nfts_bulk |
| 20 | +# from datetime import datetime, timezone, timedelta |
| 21 | +# import asyncio |
| 22 | + |
| 23 | +# # XRPL 관련 모듈 |
| 24 | +# #pip install xrpl-py + python -m poetry add xrpl.py |
| 25 | +# from xrpl.asyncio.transaction import submit_and_wait |
| 26 | +# from xrpl.models.transactions.nftoken_mint import NFTokenMint, NFTokenMintFlag |
| 27 | +# from xrpl.asyncio.wallet import generate_faucet_wallet |
| 28 | +# from xrpl.wallet import Wallet |
| 29 | +# from xrpl.clients import JsonRpcClient |
| 30 | +# import json |
| 31 | + |
| 32 | +# # service 내부에서 임시 유저 생성 |
| 33 | +# # 예시 User 클래스 (있다고 가정) |
| 34 | +# # class User: |
| 35 | +# # def __init__(self, wallet, point): |
| 36 | +# # self.wallet = wallet |
| 37 | +# # self.point = point |
| 38 | + |
| 39 | +# # # 개별 사용자 생성 |
| 40 | +# # user1 = User(wallet="rWallet1", point=1800) |
| 41 | +# # user2 = User(wallet="rWallet2", point=1300) |
| 42 | +# # user3 = User(wallet="rWallet3", point=800) |
| 43 | + |
| 44 | +# # # 리스트로 묶어서 사용 |
| 45 | +# # users = [user1, user2, user3] |
| 46 | + |
| 47 | +# repo = UserRepository() |
| 48 | +# users = repo.get_all_user() |
| 49 | + |
| 50 | + |
| 51 | +# # XRPL 설정 |
| 52 | +# print("Connecting to Testnet...") |
| 53 | +# JSON_RPC_URL = "https://s.devnet.rippletest.net:51234/" |
| 54 | +# client = JsonRpcClient(JSON_RPC_URL) |
| 55 | + |
| 56 | + |
| 57 | +# #등급 조건건 |
| 58 | +# GRADE_RULES = [ |
| 59 | +# {"grade": "platinum", "percent": 0.03, "min_point": 1500}, |
| 60 | +# {"grade": "gold", "percent": 0.10, "min_point": 1000}, |
| 61 | +# {"grade": "silver", "percent": 0.40, "min_point": 500}, |
| 62 | +# {"grade": "bronze", "percent": 1, "min_point": 0} |
| 63 | +# ] |
| 64 | + |
| 65 | +# # 등급별 Taxon 값 설정 (NFT 분류 번호) |
| 66 | +# NFT_GRADE_TAXON = { |
| 67 | +# "platinum": 4, |
| 68 | +# "gold": 3, |
| 69 | +# "silver": 2, |
| 70 | +# "bronze": 1 # 기본값 |
| 71 | +# } |
| 72 | + |
| 73 | +# #metadataUri |
| 74 | +# NFT_METADATA_URI = { |
| 75 | +# "platinum" : "일단 플래티넘 주소", |
| 76 | +# "gold": "일단 골드 주소", |
| 77 | +# "silver" : "일단 실버 주소", |
| 78 | +# "bronze": "일단 브론즈즈 주소" |
| 79 | +# } |
| 80 | + |
| 81 | +# # 테스트 지갑 생성 (테스트넷용) |
| 82 | +# # generate~ 함수는 내부적으로 비동기 함수임(asyncio.run()) -> ㅇ미 비동기에서 비동기로 겹침침 |
| 83 | +# async def generate_wallet (): |
| 84 | +# wallet = await generate_faucet_wallet(client=client) |
| 85 | +# return wallet, wallet.address |
| 86 | + |
| 87 | +# # #포인트별로 필터링 함수 |
| 88 | +# # def filter_users_by_rank_and_point(users, start_idx, end_idx, min_point): |
| 89 | +# # return [ |
| 90 | +# # #전체 사용자 중 |
| 91 | +# # # 특정 범위의 사용자 중 최소 포인트 이상인 사람만 필터링해서 반환환 |
| 92 | +# # user |
| 93 | +# # for i, user in enumerate(users[start_idx:end_idx]) |
| 94 | +# # if user.point >= min_point |
| 95 | +# # ] |
| 96 | + |
| 97 | +# # XRPL 기반 NFT 민팅(XRPL 에서 NFT를 실제로 발급(MINt) 하는 핵심 함수) |
| 98 | +# # 개별 사용자 1명에게 NFT를 발급하는 함수 |
| 99 | +# async def mint_nft_on_xrpl(user, grade, issuser_wallet, issuserAddr): |
| 100 | +# # 발급일 = 현재 날짜 + 자정 |
| 101 | +# issued_at = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) |
| 102 | +# # 만료일 = 6개월 자정 |
| 103 | +# expired_at = issued_at + timedelta(days=180) |
| 104 | + |
| 105 | +# # now = datetime.utcnow() |
| 106 | +# # NFT 발급을 위한 트랜잭션 객체 생성 |
| 107 | +# # 트랜잭션 : 블록체인에서 일어나는 모든 행동을 기록한 데이터 조각 |
| 108 | +# # XRPL노드로 전송되면 블록체인에 기록 되고 실제 NFT가 생성됨됨 |
| 109 | +# # ex) A가 B에게 10코인을 보냄 등.,, |
| 110 | +# mint_tx = NFTokenMint( |
| 111 | +# account=issuserAddr, # 발급자(대표자) |
| 112 | +# nftoken_taxon=NFT_GRADE_TAXON[grade], # NFT 분류 Id (의미를 부여) grade에 따라 자동 적용용 |
| 113 | +# flags=NFTokenMintFlag.TF_TRANSFERABLE,# NFT 가 전송 가능한 것인지 여부 설정정 |
| 114 | +# uri=NFT_METADATA_URI[grade].encode("utf-8").hex() # NFT의 이미지, 무슨 등급, 이름/설명/속성 등을 설명해주는 정보 묶음 |
| 115 | +# ) |
| 116 | + |
| 117 | +# try: |
| 118 | +# # xrpl에서 해당 정보를 트랜잭션에 넘기기기 |
| 119 | +# # submit_and_wait(transaction, client(노드 클라이언트 자체), wallet(서명에 사용될 지갑 객체체)) |
| 120 | +# response = await submit_and_wait(transaction=mint_tx, client=client, wallet=issuser_wallet) |
| 121 | +# # transaction 처리 결과 |
| 122 | +# result = response.result |
| 123 | + |
| 124 | +# #print("result" + result) |
| 125 | + |
| 126 | +# # 트랜잭션 해시 추출(트랜잭션 고유 ID) -> 블록 탐색기에서 이 해시로 NFT 상태 조회 가능 |
| 127 | +# tx_hash = result['hash'] |
| 128 | +# if not tx_hash or not isinstance(tx_hash, str): |
| 129 | +# raise Exception("트랜잭션 해시를 정상적으로 받지 못했습니다.") |
| 130 | + |
| 131 | +# # NFT ID 파싱을 위함 |
| 132 | +# nft_id = "" |
| 133 | + |
| 134 | + |
| 135 | +# #print("트랜잭션 결과:\n", json.dumps(result, indent=2)) |
| 136 | + |
| 137 | + |
| 138 | +# # XRPL 은 발급된 NFT의 ID를 직접 반환 x |
| 139 | +# # -> AffectedNodes에서 새로 생성된 노드를 찾아서 안의 ID를 추출해야함함 |
| 140 | +# #for node in result['meta']['AffectedNodes']: |
| 141 | +# # if "CreatedNode" in list(node.keys())[0]: |
| 142 | +# # created = node['CreatedNode']['NewFields'] |
| 143 | +# # if "NFToken" in created.get("NFTokens", [{}])[0]: |
| 144 | +# # nft_id = created["NFTokens"][0]["NFToken"]["NFTokenID"] |
| 145 | +# # break |
| 146 | + |
| 147 | +# for node in result['meta']['AffectedNodes']: |
| 148 | +# node_data = node.get("CreatedNode") or node.get("ModifiedNode") |
| 149 | +# if node_data and node_data["LedgerEntryType"] == "NFTokenPage": |
| 150 | +# tokens = node_data.get("NewFields", {}).get("NFTokens") or node_data.get("FinalFields", {}).get("NFTokens") |
| 151 | +# if tokens: |
| 152 | +# for token in tokens: |
| 153 | +# nft = token.get("NFToken") |
| 154 | +# if nft and "NFTokenID" in nft: |
| 155 | +# nft_id = nft["NFTokenID"] |
| 156 | +# break |
| 157 | +# if nft_id: |
| 158 | +# break |
| 159 | + |
| 160 | + |
| 161 | + |
| 162 | +# if not nft_id: |
| 163 | +# raise Exception("NFT ID 추출 실패") |
| 164 | + |
| 165 | +# return { |
| 166 | +# "user_wallet": user.wallet, |
| 167 | +# "point": user.point, |
| 168 | +# "nft_id": nft_id, |
| 169 | +# "nft_grade": grade, |
| 170 | +# "transaction_hash": tx_hash, |
| 171 | +# "nft_metadata_uri": NFT_METADATA_URI[grade].encode("utf-8").hex(), |
| 172 | +# "issued_at": issued_at, |
| 173 | +# "expired_at": expired_at |
| 174 | +# } |
| 175 | + |
| 176 | +# except Exception as e: |
| 177 | +# print(f"NFT 민팅 실패: {e}") |
| 178 | +# return None |
| 179 | + |
| 180 | +# # 등급별 민팅(여러 사용자에게 등급별로 NFT를 발급해주는 함수) |
| 181 | +# async def mint_all_nfts(users, issuser_wallet, issuserAddr): |
| 182 | +# total = len(users) # user가 몇명인지지 |
| 183 | +# used_indices = 0 # 등급별로 인덱스 나누기 위한 변수 |
| 184 | +# results = [] # 민팅 결과 담는 곳곳 |
| 185 | + |
| 186 | +# # 등급 조건 별로 for 문 돌리기기 |
| 187 | +# for rule in GRADE_RULES: |
| 188 | +# # 퍼센트가 몇 등인지를 계산 |
| 189 | +# size = max(1, int(total * rule["percent"])) |
| 190 | +# # 후보자 필터링(상위 인덱스 구간 내에서 최소 포인트 이상인 유저만) |
| 191 | +# # fitler_user_by_rank_and_point(user, start_idx, end_idx, point) |
| 192 | +# # candidates = filter_users_by_rank_and_point( |
| 193 | +# # users, used_indices, min(used_indices + size, total), rule["min_point"] |
| 194 | +# # ) |
| 195 | +# # 다음 등급의 start_idx 설정정 |
| 196 | +# # used_indices += size |
| 197 | + |
| 198 | +# # 후보자 리스트에서 nft 발급 준비 |
| 199 | +# # mint_nft_on_xrpl(user, grade) |
| 200 | +# # tasks = [] |
| 201 | +# # for user in candidates : |
| 202 | +# # task = mint_nft_on_xrpl(user, rule["grade"]) |
| 203 | +# # tasks.append(task) |
| 204 | +# # tasks = [mint_nft_on_xrpl(user, rule["grade"], issuser_wallet, issuserAddr) for user in candidates] |
| 205 | +# tasks = [mint_nft_on_xrpl(user, rule["grade"], issuser_wallet, issuserAddr) for user in users] |
| 206 | +# # nft 발급 결과를 비동기식으로 minted에 저장(minted) |
| 207 | +# minted = await asyncio.gather(*tasks) # 위 모든 동작을 동시에 실행행 |
| 208 | + |
| 209 | +# # 발급 실패한 사람 제외 results 에 저장해두기 |
| 210 | +# # extend([저장된 발급 결과에 None 이라 저장되어있는 것]) |
| 211 | +# results.extend([r for r in minted if r is not None]) |
| 212 | + |
| 213 | +# return results |
| 214 | + |
| 215 | +# # 전체 로직 |
| 216 | + |
| 217 | +# async def process_nft_issuance_with_response() -> list[NftResponseDTO]: |
| 218 | +# issuser_wallet, issuserAddr = await generate_wallet() |
| 219 | + |
| 220 | +# # 포인트 별로 내림차순순 |
| 221 | +# users.sort(key=lambda u: u.point, reverse=True) |
| 222 | + |
| 223 | +# # 모든 사용자 nft 발급 요청하기 |
| 224 | +# mint_results = await mint_all_nfts(users, issuser_wallet, issuserAddr) |
| 225 | + |
| 226 | +# # DB 저장용 객체 변환 |
| 227 | +# nft_records = [ |
| 228 | +# NFTRecord( |
| 229 | +# nft_id=result["nft_id"], |
| 230 | +# user_wallet=result["user_wallet"], |
| 231 | +# nft_grade=result["nft_grade"], |
| 232 | +# transaction_hash=result["transaction_hash"], |
| 233 | +# metadata_uri=result["nft_metadata_uri"], |
| 234 | +# issued_at=result["issued_at"], |
| 235 | +# expires_at=result["expired_at"] |
| 236 | +# ) for result in mint_results |
| 237 | +# ] |
| 238 | +# save_nfts_bulk(nft_records) |
| 239 | + |
| 240 | +# # DTO 변환 |
| 241 | +# return [ |
| 242 | +# NftResponseDTO( |
| 243 | +# user_wallet_id=r["user_wallet"], |
| 244 | +# point=r["point"], |
| 245 | +# nft_id=r["nft_id"], |
| 246 | +# nft_grade=r["nft_grade"], |
| 247 | +# transaction_hash=r["transaction_hash"], |
| 248 | +# nft_metadata_uri=r["nft_metadata_uri"], |
| 249 | +# issued_at=r["issued_at"], |
| 250 | +# expired_at=r["expired_at"] |
| 251 | +# ) for r in mint_results |
| 252 | +# ] |
| 253 | + |
| 254 | + |
| 255 | +# # async def test_process_nft_issuance_without_db() -> list[NftResponseDTO]: |
| 256 | +# # issuser_wallet, issuserAddr = await generate_wallet() |
| 257 | + |
| 258 | +# # # 포인트 별로 내림차순순 |
| 259 | +# # users.sort(key=lambda u: u.point, reverse=True) |
| 260 | +# # # 모든 사용자 nft 발급 요청하기 |
| 261 | +# # mint_results = await mint_all_nfts(users, issuser_wallet, issuserAddr) |
| 262 | + |
| 263 | +# # return [ |
| 264 | +# # NftResponseDTO( |
| 265 | +# # user_wallet_id=r["user_wallet"], |
| 266 | +# # point=r["point"], |
| 267 | +# # nft_id=r["nft_id"], |
| 268 | +# # nft_grade=r["nft_grade"], |
| 269 | +# # transaction_hash=r["transaction_hash"], |
| 270 | +# # nft_metadata_uri=r["nft_metadata_uri"], |
| 271 | +# # issued_at=r["issued_at"], |
| 272 | +# # expired_at=r["expired_at"] |
| 273 | +# # ) |
| 274 | +# # for r in mint_results |
| 275 | +# # ] |
| 276 | + |
0 commit comments