μ΄ νλ‘μ νΈλ λΈλ‘μ²΄μΈ κ²½λ§€ μμ€ν μ ꡬνν μΉ μ ν리μΌμ΄μ μ λλ€. μ¬μ©μλ κ²½λ§€μ μ°Έμ¬(μ μ°°)νκ³ , μΆκΈν μ μμΌλ©°, κ²½λ§€ μ£Όμ΅μλ κ²½λ§€ μνλ₯Ό νμΈν μ μμ΅λλ€.
| μ΄λ¦ | μν | GitHub |
|---|---|---|
| κΉμ§λͺ¨ | FE/BE | GitHub |
| λ°©νλ―Ό | BE | GitHub |
κ° νμμ νλ‘μ νΈμ λ°±μλ κ°λ°μ κΈ°μ¬νμμΌλ©°, GitHub λ§ν¬λ₯Ό ν΅ν΄ κ°μμ μμ μ νμΈν μ μμ΅λλ€.
- κ²½λ§€ μ΄λ²€νΈ μ²λ¦¬: κ²½λ§€μ κ΄λ ¨λ λ€μν μ΄λ²€νΈλ₯Ό μ€μκ°μΌλ‘ μ²λ¦¬ν©λλ€.
- μν κ΄λ¦¬:
zustandλ₯Ό μ¬μ©νμ¬ μνλ₯Ό κ΄λ¦¬νλ©°, κ° μ»΄ν¬λνΈμ μνκ° μ΅μ νλλλ‘ λ³΄μ₯ν©λλ€. - Web3.js μ¬μ©: μ리λν° λ©μλλ₯Ό νΈμΆνκ³ , μμΌ ν΅μ μ ν΅ν΄ μ΄λ²€νΈλ₯Ό ꡬλ ν©λλ€.
- νλ‘ νΈμλ: React, TypeScript, Web3.js, Ganache
- λ°±μλ: Solidity(Remix)
- μν κ΄λ¦¬: Zustand
- μ€νμΌλ§: CSS, Tailwind CSS
- κΈ°ν: lodash
λΉλκΈ° ν¨μμ λκΈ°ν λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄ lodashμ debounceλ₯Ό μ¬μ©νμ΅λλ€. μ΄λ λ²νΌμ λΉ λ₯΄κ² ν΄λ¦νμ λ λΉλκΈ° ν¨μμμ μ±ν¬λ₯Ό μ‘°μ νλ λ° μ μ©νμ΅λλ€.
import { debounce } from "lodash";
const handlePlaceBid = debounce(async () => {
if (amount > balance) {
addLog(`[μλ¬]: μ§κ°μ μκΈμ΄ λΆμ‘±ν©λλ€!`);
} else {
try {
await placeBid(amount, currentWallet);
} catch (e: unknown) {
const errorMsg = e instanceof Error ? e.message : String(e);
const delimeter = errorMsg.split("revert")[1];
addLog(`[μλ¬]: ${delimeter}`);
}
}
}, 200); // λΉλκΈ° ν¨μμ μ±ν¬λ₯Ό λ§μΆκΈ° μν΄ 0.2μ΄ λλ°μ΄μ€ μΆκ°μνκ° κ° μ»΄ν¬λνΈλ³λ‘ μ΅μ νλμ§ μλ λ¬Έμ λ₯Ό zustandλ‘ ν΄κ²°νμ΅λλ€. zustandλ₯Ό μ¬μ©νμ¬ μνλ₯Ό μ€μμμ κ΄λ¦¬νκ³ , κ° μ»΄ν¬λνΈκ° λμΌν μνλ₯Ό μ°Έμ‘°νλλ‘ νμ΅λλ€.
// useUserStore
walletAddresses: string[]; // μ¬μ©μμ μ§κ° μ£Όμ λͺ©λ‘
currentWallet: string; // νμ¬ μ νλ μ§κ° μ£Όμ
ownerWallet: string; // κ²½λ§€ μμ μμ μ§κ° μ£Όμ
bid: number; // νμ¬ μ
μ°° κΈμ‘
balance: number; // νμ¬ μ§κ°μ μμ‘
// λ©μλ
getWallets: () => Promise<void>; // μ¬μ©μμ μ§κ° μ£Όμλ₯Ό κ°μ Έμ€λ λ©μλ
placeBid: (bid: number, currentWallet: string) => Promise<void>; // μ
μ°°μ μννλ λ©μλ
withdraw: (currentWallet: string) => Promise<void>; // μΆκΈμ μννλ λ©μλ
switchWallet: (currentWallet: string) => void; // μ§κ°μ μ ννλ λ©μλ
getBalance: (currentWallet: string) => Promise<void>; // μ§κ°μ μμ‘μ κ°μ Έμ€λ λ©μλ
// useAuctionStore
item: AuctionItem; // νμ¬ κ²½λ§€μμ΄ν
μ 보
status: "μ’
λ£λ¨" | "μ§νμ€" | "μλ¬"; // κ²½λ§€ μν
highestBid: number; // νμ¬ μ΅κ³ μ
μ°°κ°
highestBidder: string; // νμ¬ μ΅κ³ μ
μ°°κ°μ μ£Όμ
timeLeft: number; // λ¨μ μκ° (μ΄λ¨μ)
getAuctionItem: () => Promise<void>; // κ²½λ§€ μμ΄ν
μ 보λ₯Ό κ°μ Έμ€λ λ©μλ
updateHighestBid: (amount: number, bidder: string) => void; // μ΅κ³ μ
μ°°κ°λ₯Ό μ
λ°μ΄νΈνλ λ©μλ
getTimeLeft: () => Promise<void>; // λ¨μ μκ°μ κ°μ Έμ€λ λ©μλ
getStatus: () => Promise<void>; // κ²½λ§€ μνλ₯Ό κ°μ Έμ€λ λ©μλ
getHighestBid: () => Promise<void>; // μ΅κ³ μ
μ°°κ°λ₯Ό κ°μ Έμ€λ λ©μλ
getHighestBidder: () => Promise<void>; // μ΅κ³ μ
μ°°μμ μ£Όμλ₯Ό κ°μ Έμ€λ λ©μλ
deactivate: (ownerWallet: string) => Promise<void>; // κ²½λ§€λ₯Ό λΉνμ±ννλ λ©μλ
withdrawFunds: (ownerWallet: string) => Promise<void>; // λ¨μ μκΈμ μΆκΈνλ λ©μλ
// useLogStore
logs: LogItem[]; // λ‘κ·Έ νλͺ© λͺ©λ‘
// λ©μλ
addLog: (message: string) => void; // λ‘κ·Έλ₯Ό μΆκ°νλ λ©μλ
clearLogs: () => void; // λͺ¨λ λ‘κ·Έλ₯Ό μ§μ°λ λ©μλWeb3.jsλ₯Ό μ²μ μ¬μ©νλ©΄μ μ리λν°μ λ©μλλ₯Ό νΈμΆνλ λ°©λ²κ³Ό μμΌμΌλ‘ μ΄λ²€νΈλ₯Ό ꡬλ ν μ μλ€λ μ μ μκ² λμμ΅λλ€.
useAuctionEvents ν
μ λ§λ€μ΄ κ° μ΄λ²€νΈκ° λ°μν λλ§λ€ λ°μμ€λ λ‘κ·Έλ₯Ό μ μ μνλ‘ κ΄λ¦¬νκ³ LogBox μ»΄ν¬λνΈμ λ‘κ·Έλ₯Ό μΆλ ₯νκ²λ νμ΅λλ€.
λμμ, μ΄λ²€νΈμ λ°λΌ getBalance() λ©μλλ₯Ό νΈμΆνμ¬ μ μ μνλ‘ κ΄λ¦¬λκ³ μλ balanceλ₯Ό μλ²μ μ΅μ λ°μ΄ν°μ λκΈ°νν©λλ€.
μ΄λ₯Ό ν΅ν΄ μ¬μ©μ μΈν°νμ΄μ€μ μλ² κ°μ λ°μ΄ν° μΌκ΄μ±μ μ μ§ν©λλ€.
// auctionInstance.ts
import Web3 from "web3";
const web3 = new Web3(new Web3.providers.WebsocketProvider("ws://localhost:7545"));
const auctionContract = new web3.eth.Contract(AuctionABI as AbiItem[], CONTRACT_ADDRESS);// useAuctionEvents
import { useEffect } from "react";
import { useAuctionStore } from "./stores/useAuctionStore";
import { useUserStore } from "./stores/useUserStore";
import { auctionContract } from "./auctionInstance";
import { EventData } from "web3-eth-contract";
import { useLogStore } from "./stores/useLogStore";
import { formatTime, weiToEther } from "./utils";
const useAuctionEvents = () => {
const updateHighestBid = useAuctionStore((state) => state.updateHighestBid);
const currentWallet = useUserStore((state) => state.currentWallet);
const getBalance = useUserStore((state) => state.getBalance);
const addLog = useLogStore((state) => state.addLog);
const getStatus = useAuctionStore((state) => state.getStatus);
useEffect(() => {
// 1. μ
μ°° μ΄λ²€νΈ
const bidEvent = auctionContract.events.BidEvent().on("data", async (event: EventData) => {
const { highestBidder, highestBid } = event.returnValues;
const truncatedBidder = highestBidder.substring(0, 7);
console.log("μ
μ°° μ΄λ²€νΈ λ°μ΄ν°:", event.returnValues); // λλ²κΉ
μ© λ‘κ·Έ
addLog(`[μ
μ°°] ${truncatedBidder}: ${weiToEther(highestBid)} eth `);
updateHighestBid(Number(highestBid), highestBidder); // μ΅κ³ κ° μ
μ°°κΈ κ°±μ
if (currentWallet) {
await getBalance(currentWallet);
}
});
// 2. κ²½λ§€ μ·¨μ μ΄λ²€νΈ
const cancelEvent = auctionContract.events
.CanceledEvent()
.on("data", async (event: EventData) => {
const { time } = event.returnValues;
getBalance(currentWallet); // νμ¬ μ§κ°μ μμ‘ κ°±μ
console.log("κ²½λ§€ μ·¨μ μ΄λ²€νΈ λ°μ΄ν°:", event.returnValues); // λλ²κΉ
μ© λ‘κ·Έ
addLog(`[κ²½λ§€ μ·¨μ] ${formatTime(time)}`);
if (currentWallet) {
await getStatus();
}
});
// 3. κ²½λ§€ μν μ΄λ²€νΈ
const auctionStateEvent = auctionContract.events
.StateUpdated()
.on("data", async (event: EventData) => {
const { message, time } = event.returnValues;
console.log("κ²½λ§€ μν μ΄λ²€νΈ λ°μ΄ν°:", event.returnValues); // λλ²κΉ
μ© λ‘κ·Έ
addLog(`[μμ€ν
] ${message} μκ° : ${formatTime(time)}`);
await getStatus(); // μλ²μ μνλ₯Ό λ°μμμ μ΅μ ν
});
// 4. μΆκΈ μ΄λ²€νΈ
const withdrawEvent = auctionContract.events
.WithdrawalEvent()
.on("data", async (event: EventData) => {
const { withdrawer, amount } = event.returnValues;
// withdrawer λ¬Έμμ΄μ κΈΈμ΄ 7λ‘ μλΌμ€λλ€.
const truncatedWithdrawer = withdrawer.substring(0, 7);
console.log("μΆκΈ μ΄λ²€νΈ λ°μ΄ν°:", event.returnValues); // λλ²κΉ
μ© λ‘κ·Έ
addLog(`[μΆκΈ] ${truncatedWithdrawer} : ${weiToEther(amount)} eth`);
if (currentWallet) {
await getBalance(currentWallet);
}
});
// μ΄λ²€νΈ 리μ€λ μ κ±°
return () => {
bidEvent.off();
cancelEvent.off();
auctionStateEvent.off();
withdrawEvent.off();
};
}, []);
};
export default useAuctionEvents;