bitpro가 원문에서 글자만 모은 내용이에요. 표·그림·수식은 빠지거나 깨질 수 있어요. 전체 41쪽 중 33쪽
Terminal
Enter the params one by one, type `Enter` to skip optional param:
? Enter the required param <spender>: "INSERT_YOUR_CONTRACT_ADDRESS_HERE"
? Enter the required param <symbol>: ELF
? Enter the required param <amount>: 9000000000
Initializing Dice Game Contract
Terminal
aelf-command send
$CONTRACT_ADDRESS
-a
$WALLET_ADDRESS
-p
$WALLET_PASSWORD
-e
https://tdvw-test-node.aelf.io Initialize
Output:
Depositing funds into the Dice Game Contract
Terminal
aelf-command send
$CONTRACT_ADDRESS
-a
$WALLET_ADDRESS
-p
$WALLET_PASSWORD
-e
https://tdvw-test-node.aelf.io Deposit
You will be prompted for the following:
Terminal
Enter the params one by one, type `Enter` to skip optional param:
? Enter the required param <value>: 20000
Output:
Playing the Dice Game
Terminal
aelf-command send
$CONTRACT_ADDRESS
-a
$WALLET_ADDRESS
-p
$WALLET_PASSWORD
-e
https://tdvw-test-node.aelf.io Play
Let's check the
Contract Balance
Terminal
aelf-command call ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx
-a
$WALLET_ADDRESS
-p
$WALLET_PASSWORD
-e
https://tdvw-test-node.aelf.io GetContractBalance
Understanding randomization using Oracle Contract in aelf
In this section, we'll explore how oracle calls work in the aelf blockchain to generate a verifiable random number using the dice game example from the tutorial. This will help you understand how different smart contracts can interact to perform complex operations.
1.
Smart Contract Overview
Dice Master Contract
: Manages the game, including initialization, token deposit/withdrawal, interaction with oracle for random number generation and decision of win or lose based on dice outcome.
Token Contract
: Handles the token transactions needed for playing dice game.
Oracle Contract
: Handles the generation of verifiable random number needed for playing the dice game.
2.
Game Play Process
Starting Gameplay
: When a user wants to play the dice game, they interact with the dice master contract to deposit sufficent funds using deposit function. Then the master contract contract calls the oracle contract to generate a random number. The oracle contract calls the dice master contract to decide if the user wins or loses.
Token Transfer Requirement
: The Dice Master Contract must verify that the user has enough tokens and transfer those tokens to the smart contract account to play the game.
Random Number Generation using an Oracle
: The dice master contract connects with Aetherlink VRF oracle to generate a true random number.
Win or Lose Decision
: The decision for win or lose is made by the dice master contract based on random number generated from the Oracle.
3.
Initializing Contract Reference State
Setting Token and Oracle Contract Address
: The dice game needs to initialize state of the contract, including references to external contracts like the Token Contract and Oracle Contract.
4.
Making an Inter-Contract Call
Calling Token Contract
: The Dice Master Contract needs to interact with the Token Contract to transfer tokens.
Method Invocation
: It calls a method in the Token Contract, such as
Transfer
.
Parameters
: The call includes details like the sender’s address, the recipient’s address (the dice game account), and the amount of tokens.
Calling Oracle Contract
: The Dice Master Contract needs to interact with the Oracle Contract to generate a verifiable random number.
Encoding and Sending
: The parameters are encoded into a transaction format and sent to the Token Contract.
5.
Processing in the Token Contract
Token Transfer
: The Token Contract processes the transfer request by deducting tokens from the user’s account and adding them to the dice game account.
Return Response
: The Token Contract then returns a result indicating whether the transfer was successful or if it failed.
6.
Handling the Response
Dice Master Contract’s Role
: Once the dice master contract receives the response from the Token Contract, it checks if the transfer was successful.
Next Steps
: If successful, the dice master contract updates the contract balance and continues with the game logic.
7.
Authorization and Security
Permission Checks
: Ensures that the Dice Game Contract is authorized to invoke methods in the Token and Oracle Contract.
Secure Transactions
: Ensures that token transfers and random number generation are secure and correctly authorized.
8.
Error Handling
Failure Management
: If the token transfer fails (e.g., due to insufficient funds), the dice contract handles the error by potentially reverting the transaction or notifying the user.
By following these steps, you can see how oracle networks can be used to generate a truly verifiable random number. This modular approach helps in building complex applications like a dice game by ensuring secure and authorized interactions between contracts.
Step 5 - Interact Your Deployed Smart Contract With Frontend
Project Setup
Let's start by cloning the frontend project repository from github.
git clone https://github.com/AElfProject/aelf-samples.git
Next, navigate to the frontend project directory with this command:
Terminal
cd
aelf-samples/dice/2-dapp
Once you're inside the
2-dapp
directory, open the project with your preferred IDE (e.g., VSCode). You should see the project structure as shown below.
Install necessary libraries
Run this command in the terminal to install all necessary packages and libraries:
Terminal
npm
install
We are now ready to build the frontend components of our Dice Game.
Setting Up the Dice Game Smart Contract
Let’s configure your deployed Dice Game smart contract so you can interact with its functionality in this tutorial.
Step 1. Locate the Configuration File
Navigate to the file located at:
src/config/configTest.ts
Step 2. Update the Smart Contract Address
Find the comment
// Step A - Configure Dice Contract
Replace the placeholder value
add_your_deployed_dice_contract
with the address of your deployed smart contract.
Example:
// Replace with the address of your deployed smart contract
const
DICE_CONTRACT_ADDRESS
=
"your_deployed_contract_address"
;
tip
Make sure to use the smart contract address from the "Deploy Smart Contract" step. Double-check for accuracy to avoid errors later!
Setting Up Wallet Integration
Let’s configure wallet connection, disconnection, and display wallet information so you can seamlessly interact with the Dice Game.
Step 1. Locate the Wallet Configuration File
Navigate to the file located at:
src/app/dice/Account.tsx
Step 2. Update the Wallet Connection Handler
Find the comment
// Step B - Connect wallet handler
Replace the existing
onConnectBtnClickHandler
function with the updated code below:
Account.tsx
// Step B - Connect wallet handler
export
const
onConnectBtnClickHandler
=
async
(
connectWallet
:
(
)
=>
any
)
=>
{
try
{
const
rs
=
await
connectWallet
(
)
;
console
.
log
(
'walletConnected rs: '
,
rs
)
;
}
catch
(
e
:
any
)
{
console
.
log
(
e
.
message
)
;
}
}
;
Step 3. Update the Wallet Disconnection Handler
Find the comment
// Step C - Disconnect wallet handler
Replace the existing
onDisConnectBtnClickHandler
function with the updated code below:
Account.tsx
// Step C - Disconnect wallet handler
export
const
onDisConnectBtnClickHandler
=
async
(
disConnectWallet
:
(
)
=>
any
)
=>
{
await
disConnectWallet
(
)
;
}
;
Step 4. Update the Wallet Info Component
Find the comment
// Step D - Wallet info component
Replace the existing
WalletConnectWithRTK
function with the updated code below:
Account.tsx
// Step D - Wallet info component
export
const
WalletConnectWithRTK
=
(
)
=>
{
const
dispatch
=
useAppDispatch
(
)
;
const
walletConnected
=
useAppSelector
(
getWalletInfo
)
;
const
{
disConnectWallet
,
walletInfo
}
=
useConnectWallet
(
)
;
useEffect
(
(
)
=>
{
if
(
walletInfo
)
{
dispatch
(
setWalletInfo
(
formatWalletInfo
(
walletInfo
)
)
)
;
}
}
,
[
dispatch
,
walletInfo
]
)
;
if
(
!
walletInfo
)
{
return
""
;
}
if
(
walletConnected
&&
walletConnected
?.
address
)
{
return
(
<
div className
=
"flex items-start"
>
<
div className
=
"tron-glow mr-2 bg-gradient-to-r from-teal-300 to-teal-500 bg-clip-text text-2xl font-bold text-transparent"
>
Hello
,
{
walletConnected
.
name
}
<
/
div
>
<
div
className
=
"cursor-pointer text-teal-400 text-opacity-50"
onClick
=
{
(
)
=>
onDisConnectBtnClickHandler
(
disConnectWallet
)
}
>
Logout
<
/
div
>
<
/
div
>
)
;
}
return
""
;
}
;
Explanation:
Wallet Connection Handler (
connectWallet
):
Initiates the wallet connection process and logs the connection status.
Wallet Disconnection Handler (
disConnectWallet
):
Safely disconnects the wallet when triggered.
Wallet Info Component:
Displays the connected wallet’s information, including a friendly greeting.
Offers a logout option to disconnect the wallet.
Keeps wallet information up to date using React hooks and Redux Toolkit.
we will use these handlers and components to ensure a smooth wallet experience for your Dice Game.
You're all set to integrate wallet functionality into your project! 🚀
Setting Up Token Contract Methods
We’ll set up token contract methods to
get allowance
,
set allowance
, and combine them into a reusable hook for streamlined functionality.
Step 1: Locate the File
Go to the
src/app/dice/useDiceMethods.ts
file.
Step 2: Write the Get Allowance Functions
Find the comment
// Step E - Check Get Token Alowance
.
Replace the existing
getAllowance
function with this code snippet:
useDiceMethods.ts
// Step E - Check Get Token Alowance
const
getAllowance
=
async
(
)
=>
{
if
(
!
walletInfo
)
{
message
.
error
(
"Please login"
)
;
return
;
}
const
res
:
any
=
await
callViewMethod
(
{
chainId
:
CHAIN_ID
,
contractAddress
:
TOKEN_CONTRACT_ADDRESS
,
methodName
:
"GetAllowance"
,
args
:
{
symbol
:
"ELF"
,
owner
:
walletInfo
.
address
,
spender
:
DICE_CONTRACT_ADDRESS
,
}
,
}
)
;
console
.
log
(
"GetAllowance: "
,
res
)
;
return
res
.
data
.
allowance
;
}
;
Step 3: Write the Set Allowance Functions
Find the comment
// Step F - Set Token Alowance
.
Replace the existing
setAllowance
function with this code snippet:
useDiceMethods.ts
// Step F - Set Token Alowance
const
setAllowance
=
async
(
)
=>
{
const
allowance
:
string
=
await
getAllowance
(
)
;
if
(
new
Bignumber
(
allowance
)
.
div
(
1e8
)
.
isGreaterThan
(
10
)
)
{
return
;
}
const
res
:
any
=
await
callSendMethod
(
{
chainId
:
CHAIN_ID
,
contractAddress
:
TOKEN_CONTRACT_ADDRESS
,
methodName
:
"Approve"
,
args
:
{
symbol
:
"ELF"
,
amount
:
new
Bignumber
(
100
)
.
multipliedBy
(
1e8
)
.
toString
(
)
,
spender
:
DICE_CONTRACT_ADDRESS
,
}
,
}
)
;
const
txResult
=
await
getTxResultRetry
(
res
.
transactionId
,
EXPLORE_URL
)
;
return
res
;
}
;
Explanation:
getAllowance
Function
Retrieves the current token allowance for the Dice Game smart contract.
Ensures the wallet is connected before proceeding.
setAllowance
Function
Checks the current allowance using
getAllowance
.
If the allowance is less than 10 tokens, it sends a request to approve 100 tokens.
Waits for the transaction result and returns the response.
Combining these functions into a custom hook simplifies token allowance management and ensures smooth interactions with the Dice Game.
You're now ready to handle token allowances efficiently! 🚀
Setting Up Dice Contract Methods
Let’s create the methods needed to
get player info
,
play the game
, and put them together into a single, easy-to-use hook.
Step 1: Create the Get Player Info Function
Scroll down and find the comment
// Step G - Get Player Info From Dice Contract
.
Replace the existing
getPlayerInfo
function with this code snippet:
useDiceMethods.ts
// Step G - Get Player Info From Dice Contract
const
getPlayerInfo
=
async
(
)
=>
{
if
(
!
walletInfo
)
{
message
.
error
(
"Please login"
)
;
return
{
}
;
}
try
{
// Call the contract's "GetPlayerInfo" method with the player's wallet address
const
res
:
any
=
await
callViewMethod
(
{
chainId
:
CHAIN_ID
,
contractAddress
:
DICE_CONTRACT_ADDRESS
,
methodName
:
"GetPlayerInfo"
,
args
:
walletInfo
.
address
,
}
)
;
// Update playerInfo state with the retrieved data
setPlayerInfo
(
res
.
data
)
;
return
res
.
data
;
}
catch
(
e
)
{
// Log an error if no information is available yet
console
.
log
(
"getPlayerInfo:"
,
"no info yet"
)
;
return
{
}
;
}
}
;
Step 2: Automatically Fetch Player Info
Set up the code to automatically get player details whenever the wallet is connected.
Scroll down and find the comment
// Step H - Fetch player information when the wallet is connected.
.
Replace the existing
useEffect
with this code snippet:
useDiceMethods.ts
// Step H - Fetch player information when the wallet is connected.
useEffect
(
(
)
=>
{
if
(
!
walletInfo
)
{
return
;
}
// Define and invoke an asynchronous function to get player info
const
main
=
async
(
)
=>
{
await
getPlayerInfo
(
)
;
}
;
main
(
)
;
}
,
[
walletInfo
]
)
;
// Dependency array ensures this effect runs when walletInfo changes
Step 3: Create the Play Game Function
This function lets the user play the Dice game by interacting with the contract.
Scroll down and find the comment
// Step I - Play Game
.
Replace the existing
useEffect
with this code snippet:
useDiceMethods.ts
// Step I - Play Game
const
play
=
async
(
multiplier
=
1
)
=>
{
// Ensure the required token allowance is set
await
setAllowance
(
)
;
// Call the contract's "Play" method with the calculated value
const
res
:
any
=
await
callSendMethod
(
{
chainId
:
CHAIN_ID
,
contractAddress
:
DICE_CONTRACT_ADDRESS
,
methodName
:
"Play"
,
args
:
{
value
:
new
Bignumber
(
BASE_NUMBER
)
.
multipliedBy
(
10
**
8
)
.
multipliedBy
(
multiplier
)
,
}
,
}
)
;
// Wait for the transaction result to be confirmed
await
getTxResultRetry
(
res
.
transactionId
,
EXPLORE_URL
)
;
// Refresh the player's information after the game
await
getPlayerInfo
(
)
;
}
;
Explanation:
Get Player Info
: Fetches player stats from the blockchain and updates the app.
Auto Fetch Player Info
: Ensures player details are always synced when the wallet is connected.
Play Game
: Handles the gameplay logic by setting allowances, interacting with the contract, and updating the player’s stats.
Now you have a well-structured hook to manage all Dice game methods in one place.
Setting Up Game Features and Effects
Let’s create the functions needed to
fetch player information
,
spawn particle effects
,
display game results
, and
handle dice rolls
.
Fetching Player Information:
We’ll start by creating a function that fetches player data when the wallet is connected. This function will automatically update the player’s information so you can access their stats and game status seamlessly.
Creating and Spawning Particle Effects:
To add some excitement and interactivity to the game, we’ll set up particle effects triggered by player actions. This includes creating individual particle effects (e.g., stars, sparks) and spawning multiple particles at a specific position on the screen.
Displaying Game Results:
After each game round, we’ll handle displaying the result to the player. Whether they win or lose, the result message will update accordingly, including any ELF rewards earned.
Handling Dice Rolls:
We’ll implement the logic for rolling the dice, interacting with the game contract, and managing animations. The dice roll will trigger specific visual effects and update the player’s information once the game round concludes.
By the end of this setup, these functions will be integrated into a cohesive experience that allows players to interact with the game smoothly, see real-time updates, and enjoy engaging visual effects.
Step 1: Locate the File
Go to the
src/app/dice/page.tsx
file.
Step 2: Call Get Player Info Funcation
Find the comment
// Step J - Fetch player information when wallet is connected
.
Replace the existing
useEffect
with this code snippet:
page.tsx
// Step J - Fetch player information when wallet is connected
useEffect
(
(
)
=>
{
if
(
!
walletInfo
)
{
return
;
}
const
loopGetPlayInfo
=
async
(
)
=>
{
const
result
:
any
=
await
getPlayerInfo
(
)
;
setPlayerInfo
(
{
...
playerInfo
,
...
result
,
}
)
;
}
;
loopGetPlayInfo
(
)
;
}
,
[
walletInfo
]
)
;
Step 3: Creates a single particle effect
Find the comment
// Step K - Creates a single particle effect.
.
Replace the existing
createParticle
fucntion with this code snippet:
page.tsx
// Step K - Creates a single particle effect.
const
createParticle
=
useCallback
(
(
x
:
number
,
y
:
number
,
container
:
HTMLDivElement
,
type
:
string
)
=>
{
const
particle
=
document
.
createElement
(
'div'
)
;
particle
.
className
=
`
particle
${
type
}
`
;
particle
.
style
.
left
=
`
${
x
}
px
`
;
particle
.
style
.
top
=
`
${
y
}
px
`
;
// Random spread for different directions
const
angle
=
Math
.
random
(
)
*
Math
.
PI
*
2
;
const
distance
=
Math
.
random
(
)
*
100
+
50
;
particle
.
style
.
setProperty
(
'--spread-x'
,
`
${
Math
.
cos
(
angle
)
*
distance
}
px
`
,
)
;
particle
.
style
.
setProperty
(
'--spread-y'
,
`
${
Math
.
sin
(
angle
)
*
distance
}
px
`
,
)
;
// Random trail effect
particle
.
style
.
setProperty
(
'--trail-x'
,
`
${
(
Math
.
random
(
)
-
0.5
)
*
100
}
px
`
,
)
;
particle
.
style
.
setProperty
(
'--trail-y'
,
`
${
-
Math
.
random
(
)
*
100
-
50
}
px
`
)
;
container
.
appendChild
(
particle
)
;
// Define animations for different particle types
const
animations
=
{
'particle-basic'
:
[
{
transform
:
'scale(1) translate(0, 0)'
,
opacity
:
1
}
,
{
transform
:
`
scale(0) translate(
${
Math
.
random
(
)
*
100
-
50
}
px,
${
-
Math
.
random
(
)
*
100
}
px)
`
,
opacity
:
0
,
}
,
]
,
'particle-star'
:
[
{
transform
:
'rotate(0deg) scale(1)'
,
opacity
:
1
}
,
{
transform
:
'rotate(360deg) scale(0)'
,
opacity
:
0
}
,
]
,
'particle-spark'
:
[
{
transform
:
`
rotate(
${
Math
.
random
(
)
*
360
}
deg) translateX(0)
`
,
opacity
:
1
,
}
,
{
transform
:
`
rotate(
${
Math
.
random
(
)
*
360
}
deg) translateX(
${
Math
.
random
(
)
*
100
+
50
}
px)
`
,
opacity
:
0
,
}
,
]
,
'particle-ring'
:
[
{
transform
:
'scale(1)'
,
opacity
:
1
,
borderWidth
:
'2px'
}
,
{
transform
:
'scale(2)'
,
opacity
:
0
,
borderWidth
:
'0px'
}
,
]
,
'particle-trail'
:
{
duration
:
1000
,
easing
:
'cubic-bezier(0.4, 0, 0.2, 1)'
,
fill
:
'forwards'
,
iterations
:
1
,
}
,
}
;
// Apply animation and remove particle after animation ends
const
animation
=
particle
.
animate
(
animations
[
type
as
keyof
typeof
animations
]
||
animations
[
'particle-basic'
]
,
{
duration
:
1000
+
Math
.
random
(
)
*
500
,
easing
:
'cubic-bezier(0.4, 0, 0.2, 1)'
,
fill
:
'forwards'
,
}
,
)
;
animation
.
onfinish
=
(
)
=>
particle
.
remove
(
)
;
}
,
[
]
,
)
;
Step 4: Spawns multiple particle effects
Find the comment
// Step L - Spawns multiple particle effects at a specified position.
.
Replace the existing
spawnParticles
fucntion with this code snippet:
page.tsx
// Step L - Spawns multiple particle effects at a specified position.
const
spawnParticles
=
useCallback
(
(
x
:
number
,
y
:
number
)
=>
{
const
container
=
document
.
querySelector
(
'.particle-container'
,
)
as
HTMLDivElement
;
if
(
!
container
)
return
;
const
particleTypes
=
[
'particle-basic'
,
'particle-star'
,
'particle-spark'
,
'particle-ring'
,
'particle-trail'
,
]
;
// Spawn multiple waves of particles
for
(
let
wave
=
0
;
wave
<
3
;
wave
++
)
{프로젝트로 돌아가기