bitpro가 원문에서 글자만 모은 내용이에요. 표·그림·수식은 빠지거나 깨질 수 있어요. 전체 41쪽 중 34쪽
setTimeout
(
(
)
=>
{
for
(
let
i
=
0
;
i
<
8
;
i
++
)
{
particleTypes
.
forEach
(
(
type
)
=>
{
const
offsetX
=
(
Math
.
random
(
)
-
0.5
)
*
20
;
const
offsetY
=
(
Math
.
random
(
)
-
0.5
)
*
20
;
createParticle
(
x
+
offsetX
,
y
+
offsetY
,
container
,
type
)
;
}
)
;
}
}
,
wave
*
100
)
;
}
}
,
[
createParticle
]
,
)
;
Step 5: Displays the result
Find the comment
// Step M - Displays the result message based on the game outcome.
.
Replace the existing
handleGameResult
fucntion with this code snippet:
page.tsx
// Step M - Displays the result message based on the game outcome.
const
handleGameResult
=
(
isWin
:
boolean
,
amount
:
string
)
=>
{
const
number
=
new
Bignumber
(
amount
)
.
div
(
10
**
8
)
;
let
message
=
`
Lose
${
number
}
ELF. Better luck next time!
`
;
if
(
isWin
)
{
message
=
`
Congratulations! You Win
${
number
.
multipliedBy
(
2
)
}
ELF!
`
;
}
setMessage
(
message
)
;
}
;
Step 6: Rolls the dice
Find the comment
// Step N - Rolls the dice
.
Replace the existing
rollDice
fucntion with this code snippet:
page.tsx
// Step N - Rolls the dice
const
rollDice
=
async
(
multiplier
=
1
)
=>
{
setMessage
(
''
)
;
if
(
isRolling
)
return
;
setIsRolling
(
true
)
;
setMessage
(
''
)
;
const
sequenceNumber
=
Math
.
floor
(
Math
.
random
(
)
*
3
)
+
1
;
setSequence
(
sequenceNumber
)
;
new
Audio
(
'https://www.soundjay.com/misc/sounds/dice-roll-01.mp3'
)
.
play
(
)
.
catch
(
(
)
=>
{
}
)
;
const
startTimePlay
=
Date
.
now
(
)
;
try
{
setIsPlaying
(
true
)
;
await
play
(
multiplier
)
;
}
catch
(
error
:
any
)
{
console
.
log
(
'play failed: '
,
error
)
;
setIsPlaying
(
false
)
;
setIsRolling
(
false
)
;
alert
(
error
.
message
)
;
return
;
}
const
startTime
=
Date
.
now
(
)
;
const
getPlayInfoInterval
=
setInterval
(
async
(
)
=>
{
const
result
=
await
getPlayerInfo
(
)
;
if
(
result
.
pending
===
false
)
{
// if (result.score !== prePlayScore) {
clearInterval
(
getPlayInfoInterval
)
;
setPlayerInfo
(
{
...
playerInfo
,
...
result
,
}
)
;
playerInfoGlobal
=
result
;
if
(
result
.
win
)
{
handleGameResult
(
true
,
result
.
amount
)
;
}
else
{
handleGameResult
(
false
,
result
.
amount
)
;
}
const
finalDice1
=
parseInt
(
playerInfoGlobal
.
dice1
,
10
)
;
const
finalDice2
=
parseInt
(
playerInfoGlobal
.
dice2
,
10
)
;
setDice1
(
finalDice1
)
;
setDice2
(
finalDice2
)
;
setIsRolling
(
false
)
;
setIsShaking
(
true
)
;
setIsPlaying
(
false
)
;
// Get dice positions for particle effects
const
dice1El
=
document
.
querySelector
(
'.dice-container:first-child'
)
;
const
dice2El
=
document
.
querySelector
(
'.dice-container:last-child'
)
;
if
(
dice1El
)
{
const
rect1
=
dice1El
.
getBoundingClientRect
(
)
;
spawnParticles
(
rect1
.
left
+
rect1
.
width
/
2
,
rect1
.
top
+
rect1
.
height
/
2
,
)
;
}
if
(
dice2El
)
{
const
rect2
=
dice2El
.
getBoundingClientRect
(
)
;
spawnParticles
(
rect2
.
left
+
rect2
.
width
/
2
,
rect2
.
top
+
rect2
.
height
/
2
,
)
;
}
// Remove screen shake after animation
setTimeout
(
(
)
=>
setIsShaking
(
false
)
,
500
)
;
console
.
log
(
'Time used: '
,
Date
.
now
(
)
-
startTime
)
;
console
.
log
(
'Time used with play: '
,
Date
.
now
(
)
-
startTimePlay
)
;
}
}
,
500
)
;
}
;
Now that we've written all the necessary frontend functions and hooks, we're ready to run the Dice Game in the next step.
Run Application
In this step, we will run the Dice Game dApp.
To begin, run the following command on your terminal.
Terminal
npm
run dev
info
Note
: Ensure that you are running this command under the
todo/2-dapp
folder.
You should observe the following as shown below.
Upon clicking on the
localhost URL
, you should be directed to the Dice Game landing page as shown below.
Create Portkey Wallet
info
Portkey is the first AA wallet from aelf's ecosystem, migrating users, developers and projects from Web2 to Web3 with DID solution.
Users can swiftly log into Portkey via their Web2 social info with no private keys or mnemonics required. Underpinned by social recovery and decentralized guardian design, Portkey safeguards users' assets from centralized control and theft. Portkey has a unique payment delegation mechanism which enables interested parties to function as delegatees to pay for user activities on users' behalf. This means that users can create accounts for free and fees for other usages may also be covered in Portkey.
Portkey also provides crypto on/off-ramp services, allowing users to exchange fiat with crypto freely. It supports the storage and management of various digital assets such as tokens, NFTs, etc. The compatibility with multi-chains and seamless connection to all kinds of DApps makes Portkey a great way to enter the world of Web3.
With DID solution as its core, Portkey provides both Portkey Wallet and Portkey SDKs.
For more information, you may visit the official documentation for Portkey at
https://doc.portkey.finance/
.
Download the Chrome extension for Portkey from
https://chromewebstore.google.com/detail/portkey-wallet/iglbgmakmggfkoidiagnhknlndljlolb
.
info
The Portkey extension supports Chrome browser only (for now). Please ensure that you are using Chrome browser.
You may download Chrome from
https://www.google.com/intl/en_sg/chrome/
.
Once you have downloaded the extension, you should see the following on your browser as shown below.
Click on
Get Start
and you should see the following interface as shown below.
Sign up
Switch to
aelf Testnet
network by selecting it:
danger
Please make sure you are using
aelf Testnet
in order to be able to receive your testnet tokens from the Faucet.
Proceed to sign up with a Google Account or your preferred login method and complete the necessary accounts creation prompts and you should observe the following interface once you have signed up.
With that, you have successfully created your very first Portkey wallet within seconds. How easy was that?
info
It is highly recommended to pin the Portkey wallet extension for easier access and navigation to your Portkey wallet!
Next, click on ‘Open Portkey’ and you should now observe the following as shown below.
Claim Test Token
Click on Copy icon in right side in the top as shown in the below image and copy your wallet address.
Open Faucet site :
https://faucet-ui.aelf.dev
Enter your wallet address and submit the form after verify the captcha.
ELF token will be claimed in your account successfully.
.
We’ve completed all the steps to set up the wallet and claim the testnet ELF tokens. Now, let’s move on to the wallet connection process in the next step.
Connect Portkey Wallet
Open your Running project and click on
"Login"
to connect your Portkey wallet.
You will get login popup so Click on
"Connect Wallet"
option as shown below.
Next, you will see a list of wallet options. Select the
"Portkey"
wallet from the list.
You will get the connection request on your
Portkey
wallet Next.
Click on
Approve
button on request modal.
After successfully connection with Portkey wallet, you will get the wallet info in Dice game page.
With the wallet connection setup complete, we’re now ready to start playing the game!
Play Dice Game
Let's start to play the dice game with step by step.
As you can see we have 3 token price option to play with ELF token. i.e: 0.1 ELF, 0.2 ELF and 0.4 ELF so let's play with 0.1 ELF tokens.
Click on
"0.1 ELF"
button to play the game with Token.
Game will start after click on
"0.1 ELF"
button and
PROCESSING...
message will be appear as below.
The first time you perform a transaction with ELF tokens, you will receive a Token Allowance request in your wallet.
Click on
"Pre-athorize
" button to authenticate and approve the Allowance transaction as shown below.
After the successfully transaction of Allowance, your will get
Play
transaction request as shown below.
Click on
Sign
the transaction.
After the successfully transaction of
Play
game, you will get the game result in Dice game.
success
🎉 Congratulations Learners! You have successfully built the Dice game 🎲🎲
🎯 Conclusion
🎉 Congratulations on completing the
Dice Game dApp
tutorial! 🎉 You've learned how to set up your development environment, develop and deploy a smart contract, and build a functional Dice Game on the aelf blockchain. 🌟
📚 What You've Learned
Throughout this tutorial, you’ve accomplished:
🛠️ Setting Up Your Development Environment:
You prepared your workspace with all necessary tools to start building the Dice Game.
🎲 Developing the Dice Game Smart Contract:
You created the core logic for the Dice Game, including dice rolling, reward handling, and managing bets.
🚀 Deploying the Smart Contract:
You deployed your smart contract to the aelf blockchain, enabling its functionalities to be used in a live environment.
🔧 Building and Running the dApp:
You connected your frontend to the blockchain and implemented key features like:
Wallet integration via Portkey.
Configuring token and dice contract methods.
Adding gameplay logic and user-friendly features.
🔍 Final Output
You now have:
A
deployed smart contract
managing game logic and rewards.
A
fully operational Dice Game dApp
where users can securely place bets, roll dice, and claim rewards.
➡️ Next Steps
Now that you’ve mastered the basics, consider:
Enhancing gameplay
with new features like customizable bets or multiplayer options.
Strengthening security
with advanced testing and best practices.
Exploring cross-chain functionality
to make your game interoperable with other blockchains.
You’ve taken a significant step into blockchain development. Keep building and innovating with aelf! 🚀
Happy coding! 😊
Step 1 - Setting up your development environment
Step 2 - Develop Smart Contract
Start Your Smart Contract Project
Adding Your Smart Contract Code
Building Smart Contract
Step 3 - Deploy Smart Contract
Step 4 - Interact with Your Deployed Smart Contract
Approving Smart Contract Spending
Initializing Dice Game Contract
Depositing funds into the Dice Game Contract
Playing the Dice Game
Understanding randomization using Oracle Contract in aelf
Step 5 - Interact Your Deployed Smart Contract With Frontend
Project Setup
Setting Up the Dice Game Smart Contract
Setting Up Wallet Integration
Setting Up Token Contract Methods
Setting Up Dice Contract Methods
Setting Up Game Features and Effects
Run Application
🎯 Conclusion
## /quick-start/advance-tutorials/nft-indexer
On this page
NFT AeIndexer
Description
: This application demonstrates how to maintain account balances and transfer records by indexing aelf's NFT issued data.
Purpose
: Shows you how to create, develop, and deploy your own AeIndexer on AeFinder.
Difficulty Level
: Easy
Step 1 - Setting up your development environment
Install
dotnet 8.0 SDK
Install IDE: Install your favorite IDE, such as:
VS Code
,
Visual Studio
,
Rider
, etc.
Step 2 - Create AeIndexer in AeFinder
Log in to the AeFinder website.
TestNet:
https://test.aefinder.io/login
Enter the AeIndexer Name and other information to create a NFT AeIndexer.
Step 3 - Develop NFT AeIndexer
Download the Development Template
Go to the AeIndexer details page for your created project.
Download the development template by entering your project name.
Unzip the downloaded file to extract the project files.
After unzipping the files, you are ready to start development
Project Structure
The initial version of the NFT AeIndexer project has the following structure:
To ensure the project is set up correctly, follow these steps:
Setting Up the Files for NFT AeIndexer
Rename Files in the
Contracts/
Folder:
Navigate to the
Contracts/
folder inside your project.
Rename
MyContract.c.cs
to
TokenContract.c.cs
.
Rename
MyContract.g.cs
to
TokenContract.g.cs
.
Download Token Contract Files:
Go to the
AeFinder GitHub NFT Indexer sample
.
Locate and download the token contract files.
Copy and Replace Code:
Open the downloaded token contract files.
Copy the content from
TokenContract.c.cs
and
TokenContract.g.cs
.
Paste the copied code into the corresponding files in the
src/NFTAeIndexer/Contracts
directory of your project:
Paste into
TokenContract.c.cs
.
Paste into
TokenContract.g.cs
.
Rename and add new files in the
Entities/
Folder:
Navigate to the
Entities/
folder inside your project.
Rename
MyEntity.cs
to
Account.cs
.
Add new
TransferRecord.cs
file inside
Entities
folder.
Rename and add new files in the
Processors/
Folder:
Navigate to the
Processors/
folder inside your project.
Rename
MyLogEventProcessor.cs
to
NFTTransferredProcessor.cs
.
Updated Project Structure
After the setup, your NFT AeIndexer project will have the following structure:
tip
ℹ️ Note: If other contract files are needed, they need to be generated by compiling the corresponding contract project!
1. Entity Models
Navigate to the
Entities/
folder inside your project.
Account.cs
file is as follows:
Account.cs
using
AeFinder
.
Sdk
.
Entities
;
using
Nest
;
namespace
NFTAeIndexer
.
Entities
;
public
class
Account
:
AeFinderEntity
,
IAeFinderEntity
{
[
Keyword
]
public
string
Address
{
get
;
set
;
}
[
Keyword
]
public
string
Symbol
{
get
;
set
;
}
public
long
Amount
{
get
;
set
;
}
public
string
TokenName
{
get
;
set
;
}
public
string
NftImageUri
{
get
;
set
;
}
public
string
NftAttributes
{
get
;
set
;
}
}
TransferRecord.cs
file is as follows:
TransferRecord.cs
using
AeFinder
.
Sdk
.
Entities
;
using
Nest
;
namespace
NFTAeIndexer
.
Entities
;
public
class
TransferRecord
:
AeFinderEntity
,
IAeFinderEntity
{
[
Keyword
]
public
string
Symbol
{
get
;
set
;
}
[
Keyword
]
public
string
ToAddress
{
get
;
set
;
}
public
long
Amount
{
get
;
set
;
}
[
Text
]
public
string
Memo
{
get
;
set
;
}
}
2. NFT Transfer Processor
The NFTTransferredProcessor handles NFT transfer events and updates account balances:
Navigate to the
Processors/
folder inside your project.
NFTTransferredProcessor.cs
file is as follows:
NFTTransferredProcessor.cs
using
Microsoft
.
Extensions
.
Logging
;
using
AElf
.
Contracts
.
MultiToken
;
using
AeFinder
.
Sdk
.
Logging
;
using
AeFinder
.
Sdk
.
Processor
;
using
AeFinder
.
Sdk
;
using
NFTAeIndexer
.
Entities
;
using
Volo
.
Abp
.
DependencyInjection
;
namespace
NFTAeIndexer
.
Processors
;
public
class
NFTTransferredProcessor
:
LogEventProcessorBase
<
Issued
>
,
ITransientDependency
{
private
readonly
IBlockChainService
_blockChainService
;
public
NFTTransferredProcessor
(
IBlockChainService
blockChainService
)
{
_blockChainService
=
blockChainService
;
}
public
override
string
GetContractAddress
(
string
chainId
)
{
return
chainId
switch
{
"AELF"
=>
"JRmBduh4nXWi1aXgdUsj5gJrzeZb2LxmrAbf7W99faZSvoAaE"
,
"tDVW"
=>
"ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx"
,
_
=>
string
.
Empty
}
;
}
public
override
async
Task
ProcessAsync
(
Issued
logEvent
,
LogEventContext
context
)
{
if
(
!
IsNftTransfer
(
logEvent
)
)
{
return
;
}
var
tokenInfoParam
=
new
GetTokenInfoInput
{
Symbol
=
logEvent
.
Symbol
}
;
var
nftTransfer
=
new
TransferRecord
{
Id
=
$"
{
context
.
ChainId
}
-
{
context
.
Transaction
.
TransactionId
}
-
{
context
.
LogEvent
.
Index
}
"
,
ToAddress
=
logEvent
.
To
.
ToBase58
(
)
,
Symbol
=
logEvent
.
Symbol
,
Amount
=
logEvent
.
Amount
,
Memo
=
logEvent
.
Memo
}
;
await
SaveEntityAsync
(
nftTransfer
)
;
await
ChangeNFTBalanceAsync
(
context
.
ChainId
,
logEvent
.
To
.
ToBase58
(
)
,
logEvent
.
Symbol
,
logEvent
.
Amount
)
;
}
private
async
Task
ChangeNFTBalanceAsync
(
string
chainId
,
string
address
,
string
symbol
,
long
amount
)
{
var
accountId
=
$"
{
chainId
}
-
{
address
}
-
{
symbol
}
"
;
var
account
=
await
GetEntityAsync
<
Account
>
(
accountId
)
;
var
tokenInfoParam
=
new
GetTokenInfoInput
{
Symbol
=
symbol
}
;
var
contractAddress
=
GetContractAddress
(
chainId
)
;
var
tokenInfo
=
await
_blockChainService
.
ViewContractAsync
<
TokenInfo
>
(
chainId
,
contractAddress
,
"GetTokenInfo"
,
tokenInfoParam
)
;
Logger
.
LogDebug
(
"TokenInfo response: {@TokenInfo}"
,
tokenInfo
)
;
if
(
account
==
null
)
{
account
=
new
Account
{
Id
=
accountId
,
Symbol
=
symbol
,
Amount
=
amount
,
Address
=
address
,
TokenName
=
tokenInfo
.
TokenName
,
NftImageUri
=
tokenInfo
.
ExternalInfo
?.
Value
[
"__nft_image_uri"
]
??
tokenInfo
.
ExternalInfo
?.
Value
[
"__nft_image_url"
]
,
NftAttributes
=
tokenInfo
.
ExternalInfo
?.
Value
[
"__nft_attributes"
]
}
;
}
else
{
account
.
Amount
+=
amount
;
}
Logger
.
LogDebug
(
"NFT Balance changed: {0} {1} {2} {3}"
,
account
.
Address
,
account
.
Symbol
,
account
.
Amount
,
account
.
TokenName
)
;
await
SaveEntityAsync
(
account
)
;
}
private
bool
IsNftTransfer
(
Issued
logEvent
)
{
return
!
string
.
IsNullOrEmpty
(
logEvent
.
Symbol
)
&&
logEvent
.
Symbol
.
Contains
(
"-"
)
&&
logEvent
.
Amount
>
0
&&
logEvent
.
To
!=
null
&&
!
string
.
IsNullOrEmpty
(
logEvent
.
To
.
ToBase58
(
)
)
;
}
}
Add files
AccountDto.cs
,
TransferRecordDto.cs
,
GetAccountInput.cs
,
GetTransferRecordInput.cs
to the directory
src/NFTAeIndexer/GraphQL
.
AccountDto.cs
using
AeFinder
.
Sdk
.
Dtos
;
namespace
NFTAeIndexer
.
GraphQL
;
public
class
AccountDto
:
AeFinderEntityDto
{
public
string
Address
{
get
;
set
;
}
public
string
Symbol
{
get
;
set
;
}
public
long
Amount
{
get
;
set
;
}
public
string
TokenName
{
get
;
set
;
}
public
string
NftImageUri
{
get
;
set
;
}
public
string
NftAttributes
{
get
;
set
;
}
}
TransferRecordDto.cs
using
AeFinder
.
Sdk
.
Dtos
;
namespace
NFTAeIndexer
.
GraphQL
;
public
class
TransferRecordDto
:
AeFinderEntityDto
{
public
string
Symbol
{
get
;
set
;
}
public
string
FromAddress
{
get
;
set
;
}
public
string
ToAddress
{
get
;
set
;
}
public
long
Amount
{
get
;
set
;
}
}
GetAccountInput.cs
namespace
NFTAeIndexer
.
GraphQL
;
public
class
GetAccountInput
{
public
string
ChainId
{
get
;
set
;
}
public
string
Address
{
get
;
set
;
}
public
string
Symbol
{
get
;
set
;
}
}
GetTransferRecordInput.cs
namespace
NFTAeIndexer
.
GraphQL
;
public
class
GetTransferRecordInput
{
public
string
ChainId
{
get
;
set
;
}
public
string
Address
{
get
;
set
;
}
public
string
Symbol
{
get
;
set
;
}
}
Modify
src/NFTAeIndexer/GraphQL/Query.cs
to add query logic.
Query.cs
using
AeFinder
.
Sdk
;
using
GraphQL
;
using
NFTAeIndexer
.
Entities
;
using
Volo
.
Abp
.
ObjectMapping
;
namespace
NFTAeIndexer
.
GraphQL
;
public
class
Query
{
public
static
async
Task
<
List
<
AccountDto
>
>
Account
(
[
FromServices
]
IReadOnlyRepository
<
Account
>
repository
,
[
FromServices
]
IObjectMapper
objectMapper
,
GetAccountInput
input
)
{
var
queryable
=
await
repository
.
GetQueryableAsync
(
)
;
queryable
=
queryable
.
Where
(
a
=>
a
.
Metadata
.
ChainId
==
input
.
ChainId
)
;
if
(
!
input
.
Address
.
IsNullOrWhiteSpace
(
)
)
{
queryable
=
queryable
.
Where
(
a
=>
a
.
Address
==
input
.
Address
)
;
}
if
(
!
input
.
Symbol
.
IsNullOrWhiteSpace
(
)
)
{
queryable
=
queryable
.
Where
(
a
=>
a
.
Symbol
==
input
.
Symbol
&&
a
.
Symbol
.
Contains
(
"-"
)
)
;
}
var
accounts
=
queryable
.
OrderBy
(
o
=>
o
.
Metadata
.
Block
.
BlockHeight
)
.
ToList
(
)
;
return
objectMapper
.
Map
<
List
<
Account
>
,
List
<
AccountDto
>
>
(
accounts
)
;
}
public
static
async
Task
<
List
<
TransferRecordDto
>
>
TransferRecord
(
[
FromServices
]
IReadOnlyRepo프로젝트로 돌아가기