bitpro가 원문에서 글자만 모은 내용이에요. 표·그림·수식은 빠지거나 깨질 수 있어요. 전체 41쪽 중 24쪽
f 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.
Connect Portkey Wallet
Click on
"Connect Wallet"
to connect your Portkey wallet.
You will be get below connect wallet request on your Portkey wallet.
Click on
Approve
button.
The button will change to
"Your Wallet Address"
when the connection is successful.
Add New Expense
Click on
"Add Expense"
button to add a new expense.
You will see the pop-up modal with form to add a new expense. Please fill all the necessary fields like
Description
,
Amount
and
Select Category
.
Click on
Add New Expense
button as shown below.
You will receive a transaction request on your portkey wallet to
Sign
the transaction.
Click on
Sign
the transaction.
After the transaction is successfully processed, Your first expense will be added ✅.
As we have
Added an Expense
successfully, You will be able to see the expense card and the total Amount of the
Outcome
.
Edit the Expense
Click on the
"Edit"
button to edit a selected expense.
You will see the pop-up modal with form to edit the expense. Edit the necessary fields according to your need.
Click on
Update Expense
Button.
Now, You will recieve a transaction request on your portkey wallet to
Sign
the transaction.
Click on
Sign
the transaction.
After the transaction is successfully processed, your expense details will be updated✅.
As we have
Edited an Expense
successfully. Let's test remove expense functionality.
Remove the Expense
Click on
"Remove"
button to remove the selected expense.
Now, You will recieve a transaction request on your portkey wallet to
Sign
the transaction.
Click on
Sign
the transaction.
After the transaction is successfully processed, your expense will be removed from the list.
success
🎉 Congratulations Learners! You have successfully built your Expense Tracker dApp.
🎯 Conclusion
🎉 Congratulations on completing the
Expense Tracker dApp
tutorial! 🎉 You've accomplished critical milestones, from setting up your development environment to creating, deploying, and interacting with your Expense Tracker smart contract on the aelf blockchain. 🌟
📚 What You've Learned
Throughout this tutorial, you've gained hands-on experience with:
🛠️ Setting Up Your Development Environment:
You successfully installed and configured all necessary tools to start developing smart contracts on the aelf blockchain.
💻 Developing Your Smart Contract:
You crafted the essential logic for the Expense Tracker, building a contract to manage tasks like adding, updating, and deleting expenses while keeping a record of all transactions.
🚀 Deploying the Smart Contract:
You deployed your Expense Tracker contract on the aelf blockchain, ensuring it was live and operational for real-world use.
🔧 Interacting with Your Deployed Smart Contract through the dApp:
You created a front-end interface, connected it to the blockchain using Portkey, and implemented crucial functions like wallet connectivity, creating new expense, and interacting with expense data.
🔍 Final Output
By now, you should have:
📜 A deployed smart contract that governs the logic of managing expenses and storing financial data on the blockchain.
💻 A fully functional Expense Tracker dApp where users can securely connect their wallets, create new expenses, update or delete them, and view their financial data — all in a decentralized manner.
➡️ What's Next?
Now that you've built the foundation of your Expense Tracker dApp, consider extending it with advanced features:
📊 Add Analytical Features:
Implement visualizations and analytics to help users gain better insights into their spending patterns and budgeting habits.
🔒 Enhance Security:
Strengthen your dApp by applying smart contract security best practices to ensure that users' financial data remains private and secure.
🌍 Exploring Cross-Chain Capabilities:
Explore aelf’s cross-chain capabilities to integrate your dApp with other blockchain networks and allow users to manage their finances across multiple chains.
You've taken a significant step toward mastering blockchain development with your Expense Tracker dApp. Now, you’re ready to continue innovating and expanding your decentralized applications on the aelf platform. 🚀
Happy coding and expanding your
Expense Tracker dApp!
😊
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 through dApp
Project Setup
Configure Portkey Provider & Write Connect Wallet Function
Configure Connect Wallet Function
Configure Create Expense Form
Check Contract Initialization
Initialize Contract
Add New Expense
Update Expense
Delete Expense
Handle Submit Form
Fetch All Expenses
Run Application
🎯 Conclusion
## /quick-start/developers/single-pool-staking-dapp
On this page
Description
: The Single Pool Staking dApp is a decentralized application built on the aelf blockchain that allows users to stake their tokens in a single staking pool. Users can earn rewards based on the amount and duration of their staked assets, with staking and reward distribution processes fully automated and secured by blockchain technology. The dApp offers a transparent and simple interface for users to monitor their staked assets and track reward accumulation over time.
Purpose
: The Single Pool Staking dApp aims to demonstrate the seamless integration of staking mechanisms with blockchain, providing users with a secure, transparent, and efficient way to grow their holdings. It serves as an educational tool for learning about staking contracts and their role in decentralized finance (DeFi), while showcasing the potential of blockchain technology for creating decentralized financial services that offer fairness and trustless reward distribution.
Difficulty Level
: Moderate
Step 1 - Setting up your development environment
Local
Codespaces
Basic knowledge of terminal commands
IDE
- Install
VS Code
Note for Apple Silicon users:
Ensure that Rosetta is installed, if it is not, use the following command:
Terminal
softwareupdate --install-rosetta
Install Required Packages
Install dotnet 8.0.x SDK
Install aelf contract templates
Linux and macOs
Windows
Terminal
dotnet new
--install
AElf.ContractTemplates
Command Prompt
dotnet new
install
AElf.ContractTemplates
AELF.ContractTemplates contains various predefined templates for the ease of developing smart contracts on the aelf blockchain.
Install aelf deploy tool
Terminal
dotnet tool
install
--global
aelf.deploy
aelf.deploy is a utility tool for deploying smart contracts on the aelf blockchain.
Please remember to export PATH after installing aelf.deploy.
info
ℹ️ Note: If you have installed aelf.deploy and your terminal says that there is no such command available, please uninstall and install aelf.deploy.
Install Node.js and Yarn
Install Node.js
Install Yarn
Install aelf-command
Linux and macOs
Windows
Terminal
sudo
npm
i
-g
aelf-command
Command Prompt
npm
i
-g
aelf-command
aelf-command is a CLI tool for interacting with the aelf blockchain, enabling tasks like creating wallets and managing transactions.
Provide required permissions while installing aelf-command globally.
Visit
aelf-devcontainer-template
.
Click the
Use this template
button. Choose
Create a new repository
.
Enter a suitable repository name. Click
Create repository
.
Within the GitHub interface of your new repository, click on
Code
.
Select
Codespaces
.
Click on the
+
sign to create a new Codespace.
After some time, your workspace will load with the contents of the repository.
You can now continue your development using GitHub Codespaces.
Step 2 - Develop Smart Contract
Start Your Smart Contract Project
Open your
Terminal
.
Enter the following command to generate a new project:
Terminal
mkdir
single-pool-staking-dapp
cd
single-pool-staking-dapp
dotnet new aelf
-n
SinglePoolStaking
Adding Your Smart Contract Code
Now that we have a template Single Pool Staking project, we can customise the template to incorporate our own contract logic.
Let's start by implementing methods to handle the basic functionality like deposit tokens to the staking pool, withdraw tokens from the staking pool, withdrawing tokens before the lock(stake) period ends (forceWithdraw), get the reward amount for an address from the pool, fetch all the deposits linked to a user and retrieve the total staked amount in the contract. Single Pool Staking dApp includes the below functionalities like:
Deposit: Allows users to stake tokens, update the total staked amount and the deposit gets linked to the user.
Withdraw: Allows users to withdraw tokens and rewards after the lock period ends.
ForceWithdraw: Allows users to withdraw tokens before the lock period ends without rewards.
GetReward: Retrieves the reward amount earned from a specific deposit.
GetDeposits: Lists all deposits linked to a user.
GetTotalStakedAmount: Retrieves the total staked amount
Enter this command in your
Terminal
.
Terminal
cd
src
Defining Methods and Messages
Rename the proto file name
hello_world_contract.proto
inside folder
Protobuf/contract/
to
single_pool_staking.proto
:
Terminal
mv
Protobuf/contract/hello_world_contract.proto Protobuf/contract/single_pool_staking.proto
The
.proto
file defines the structure and serialization of data, ensuring consistent communication and data exchange between the contract and external systems.
Open the project with your IDE.
The implementation of
single_pool_staking.proto
file inside folder
src/Protobuf/contract/
is as follows:
single_pool_staking.proto
syntax
=
"proto3"
;
import
"aelf/core.proto"
;
import
"aelf/options.proto"
;
import
"google/protobuf/empty.proto"
;
import
"Protobuf/reference/acs12.proto"
;
import
"google/protobuf/wrappers.proto"
;
option
csharp_namespace
=
"AElf.Contracts.StakingContract"
;
service
StakingContract
{
option
(
aelf
.
csharp_state
)
=
"AElf.Contracts.StakingContract.StakingContractState"
;
option
(
aelf
.
base
)
=
"Protobuf/reference/acs12.proto"
;
rpc
Initialize
(
InitializeInput
)
returns
(
google
.
protobuf
.
Empty
)
;
rpc
Deposit
(
DepositInput
)
returns
(
google
.
protobuf
.
StringValue
)
;
rpc
Withdraw
(
WithdrawInput
)
returns
(
google
.
protobuf
.
Empty
)
;
rpc
ForceWithdraw
(
google
.
protobuf
.
StringValue
)
returns
(
google
.
protobuf
.
Empty
)
;
rpc
GetReward
(
google
.
protobuf
.
StringValue
)
returns
(
google
.
protobuf
.
Int64Value
)
{
option
(
aelf
.
is_view
)
=
true
;
}
rpc
GetDeposits
(
google
.
protobuf
.
StringValue
)
returns
(
DepositList
)
{
option
(
aelf
.
is_view
)
=
true
;
}
// New functions
rpc
IfInitialized
(
google
.
protobuf
.
Empty
)
returns
(
google
.
protobuf
.
BoolValue
)
{
option
(
aelf
.
is_view
)
=
true
;
}
rpc
GetTotalStakedAmount
(
google
.
protobuf
.
Empty
)
returns
(
google
.
protobuf
.
Int64Value
)
{
option
(
aelf
.
is_view
)
=
true
;
}
}
message
DepositInput
{
string
token_symbol
=
1
;
int64
amount
=
2
;
int64
lock_time
=
3
;
}
message
InitializeInput
{
aelf
.
Address
token_contract_address
=
1
;
}
message
WithdrawInput
{
string
deposit_id
=
1
;
}
message
TransferInput
{
aelf
.
Address
to
=
1
;
string
symbol
=
2
;
int64
amount
=
3
;
string
memo
=
4
;
// Add this field
}
message
StringList
{
repeated
string
values
=
1
;
}
message
Deposit
{
string
deposit_id
=
1
;
string
address
=
2
;
string
token_symbol
=
3
;
// The specific FT token symbol
int64
amount
=
4
;
int64
lock_time
=
5
;
int64
deposit_time
=
6
;
}
message
DepositList
{
repeated
Deposit
deposits
=
1
;
}
rpc
methods define the callable functions within the contract, allowing external systems to interact with the contract's logic.
message
represent the structured data exchanged between the contract and external systems.
Define Contract States
The implementation of the Single Pool Staking smart contract state inside file
src/SinglePoolStakingState.cs
is as follows:
src/SinglePoolStakingState.cs
using
AElf
.
Sdk
.
CSharp
.
State
;
using
AElf
.
Types
;
using
AElf
.
Contracts
.
MultiToken
;
namespace
AElf
.
Contracts
.
StakingContract
{
public
class
StakingContractState
:
ContractState
{
public
BoolState
Initialized
{
get
;
set
;
}
public
SingletonState
<
Address
>
Owner
{
get
;
set
;
}
public
MappedState
<
string
,
Deposit
>
Deposits
{
get
;
set
;
}
// Mapping from deposit ID to Deposit
public
MappedState
<
Address
,
StringList
>
UserDeposits
{
get
;
set
;
}
// User to deposit IDs
public
Int32State
DepositCounter
{
get
;
set
;
}
public
Int64State
TotalStakedAmount
{
get
;
set
;
}
// New state to track total staked amount
internal
TokenContractContainer
.
TokenContractReferenceState
TokenContract
{
get
;
set
;
}
}
}
The
State.cs
file in the aelf blockchain smart contract holds the variables that store the contract's data, making sure this data is saved and accessible whenever the contract needs it.
Implement Single Pool Staking Smart Contract
The implementation of the Single Pool Staking smart contract inside file
src/SinglePoolStaking.cs
is as follows:
src/SinglePoolStaking.cs
using
Google
.
Protobuf
.
WellKnownTypes
;
using
AElf
.
Types
;
using
System
.
Collections
.
Generic
;
using
AElf
.
Contracts
.
MultiToken
;
namespace
AElf
.
Contracts
.
StakingContract
{
public
class
StakingContract
:
StakingContractContainer
.
StakingContractBase
{
private
const
int
RewardRate
=
10
;
// 10% reward
public
override
Empty
Initialize
(
InitializeInput
input
)
{
if
(
State
.
Initialized
.
Value
)
return
new
Empty
(
)
;
State
.
Initialized
.
Value
=
true
;
State
.
Owner
.
Value
=
Context
.
Sender
;
State
.
DepositCounter
.
Value
=
0
;
State
.
TotalStakedAmount
.
Value
=
0
;
// Initialize total staked amount
State
.
TokenContract
.
Value
=
input
.
TokenContractAddress
;
return
new
Empty
(
)
;
}
public
override
StringValue
Deposit
(
DepositInput
input
)
{
var
depositId
=
(
State
.
DepositCounter
.
Value
+
1
)
.
ToString
(
)
;
State
.
DepositCounter
.
Value
++
;
var
deposit
=
new
Deposit
{
DepositId
=
depositId
,
Address
=
Context
.
Sender
.
ToString
(
)
,
TokenSymbol
=
input
.
TokenSymbol
,
Amount
=
input
.
Amount
,
LockTime
=
input
.
LockTime
,
DepositTime
=
Context
.
CurrentBlockTime
.
Seconds
}
;
State
.
Deposits
[
depositId
]
=
deposit
;
var
userDeposits
=
State
.
UserDeposits
[
Context
.
Sender
]
??
new
StringList
(
)
;
userDeposits
.
Values
.
Add
(
depositId
)
;
State
.
UserDeposits
[
Context
.
Sender
]
=
userDeposits
;
State
.
TotalStakedAmount
.
Value
+=
input
.
Amount
;
// Update total staked amount
return
new
StringValue
{
Value
=
depositId
}
;
}
public
override
Empty
Withdraw
(
WithdrawInput
input
)
{
var
deposit
=
State
.
Deposits
[
input
.
DepositId
]
;
Assert
(
deposit
!=
null
,
"Deposit not found."
)
;
Assert
(
deposit
.
Address
==
Context
.
Sender
.
ToString
(
)
,
"Unauthorized."
)
;
Assert
(
Context
.
CurrentBlockTime
.
Seconds
>=
deposit
.
DepositTime
+
deposit
.
LockTime
,
"Lock period not over."
)
;
var
reward
=
CalculateReward
(
deposit
.
Amount
)
;
TransferFromContract
(
deposit
.
TokenSymbol
,
Context
.
Sender
,
deposit
.
Amount
+
reward
)
;
State
.
TotalStakedAmount
.
Value
-=
deposit
.
Amount
;
// Update total staked amount
RemoveDeposit
(
deposit
.
DepositId
)
;
return
new
Empty
(
)
;
}
public
override
Empty
ForceWithdraw
(
StringValue
input
)
{
var
deposit
=
State
.
Deposits
[
input
.
Value
]
;
Assert
(
deposit
!=
null
,
"Deposit not found."
)
;
Assert
(
deposit
.
Address
==
Context
.
Sender
.
ToString
(
)
,
"Unauthorized."
)
;
TransferFromContract
(
deposit
.
TokenSymbol
,
Context
.
Sender
,
deposit
.
Amount
)
;
State
.
TotalStakedAmount
.
Value
-=
deposit
.
Amount
;
// Update total staked amount
RemoveDeposit
(
deposit
.
DepositId
)
;
return
new
Empty
(
)
;
}
public
override
Int64Value
GetReward
(
StringValue
input
)
{
var
deposit
=
State
.
Deposits
[
input
.
Value
]
;
Assert
(
deposit
!=
null
,
"Deposit not found."
)
;
return
new
Int64Value
{
Value
=
CalculateReward
(
deposit
.
Amount
)
}
;
}
public
override
DepositList
GetDeposits
(
StringValue
input
)
{
var
deposits
=
State
.
UserDeposits
[
Address
.
FromBase58
(
input
.
Value
)
]
;
var
depositList
=
new
DepositList
(
)
;
foreach
(
var
depositId
in
deposits
.
Values
)
{
var
deposit
=
State
.
Deposits
[
depositId
]
;
if
(
deposit
!=
null
)
{
depositList
.
Deposits
.
Add
(
deposit
)
;
}
}
return
depositList
;
}
// New function to check if initialized
public
override
BoolValue
IfInitialized
(
Empty
input
)
{
return
new
BoolValue
{
Value
=
State
.
Initialized
.
Value
}
;
}
// New function to get the total staked amount
public
override
Int64Value
GetTotalStakedAmount
(
Empty
input
)
{
return
new
Int64Value
{
Value
=
State
.
TotalStakedAmount
.
Value
}
;
}
private
long
CalculateReward
(
long
amount
)
{
return
(
amount
*
RewardRate
)
/
100
;
}
private
void
TransferFromContract
(
string
symbol
,
Address
to
,
long
amount
)
{
var
virtualAddressHash
=
GetVirtualAddressHash
(
Context
.
Self
,
symbol
)
;
State
.
TokenContract
.
TransferFrom
.
Send
(
new
TransferFromInput
{
Symbol
=
symbol
,
Amount
=
amount
,
From
=
Context
.
Self
,
Memo
=
"Transfer from Staking Contract"
,
To
=
to
}
)
;
}
private
static
Hash
GetVirtualAddressHash
(
Address
contractAddress
,
string
symbol
)
{
return
HashHelper
.
ConcatAndCompute
(
HashHelper
.
ComputeFrom
(
contractAddress
)
,
HashHelper
.
ComputeFrom
(
symbol
)
)
;
}
private
Address
GetVirtualAddress
(
Hash
virtualAddressHash
)
{
return
Context
.
ConvertVirtualAddressToContractAddress
(
virtualAddressHash
)
;
}
private
void
RemoveDeposit
(
string
depositId
)
{
var
deposit
=
State
.
Deposits
[
depositId
]
;
State
.
Deposits
.
Remove
(
depositId
)
;
var
userDeposits
=
State
.
UserDeposits
[
Address
.
FromBase58
(
deposit
.
Address
)
]
;
userDeposits
.
Values
.
Remove
(
depositId
)
;
State
.
UserDeposits
[
Address
.
FromBase58
(
deposit
.
Address
)
]
=
userDeposits
;
}
}
}
Contract Reference State
Create a new file
token_contract.proto
under
src/Protobuf/reference/
.
Replace this code of implementation file of
token_contract.proto
:
token_contract.proto
/**
* MultiToken contract.
*/
syntax
=
"proto3"
;
package
token
;
import
"aelf/core.proto"
;
import
"aelf/options.proto"
;
import
"google/protobuf/empty.proto"
;
import
"google/protobuf/wrappers.proto"
;
option
csharp_namespace
=
"AElf.Contracts.MultiToken"
;
service
TokenContract
{
// Create a new token.
rpc
Create
(
CreateInput
)
returns
(
google
.
protobuf
.
Empty
)
{
}
// Issuing some amount of tokens to an address is the action of increasing that addresses balance
// for the given token. The total amount of issued tokens must not exceed the total supply of the token
프로젝트로 돌아가기