bitpro가 원문에서 글자만 모은 내용이에요. 표·그림·수식은 빠지거나 깨질 수 있어요. 전체 41쪽 중 35쪽
sitory
<
TransferRecord
>
repository
,
[
FromServices
]
IObjectMapper
objectMapper
,
GetTransferRecordInput
input
)
{
var
queryable
=
await
repository
.
GetQueryableAsync
(
)
;
queryable
=
queryable
.
Where
(
a
=>
a
.
Metadata
.
ChainId
==
input
.
ChainId
)
;
if
(
!
input
.
Address
.
IsNullOrWhiteSpace
(
)
)
{
queryable
=
queryable
.
Where
(
a
=>
a
.
ToAddress
==
input
.
Address
)
;
}
// Filter by Symbol, ensuring it matches the NFT pattern
if
(
!
input
.
Symbol
.
IsNullOrWhiteSpace
(
)
)
{
queryable
=
queryable
.
Where
(
a
=>
a
.
Symbol
==
input
.
Symbol
&&
a
.
Symbol
.
Contains
(
"-"
)
)
;
}
// Ensure NFT-specific conditions
queryable
=
queryable
.
Where
(
a
=>
a
.
Symbol
.
Contains
(
"-"
)
&&
a
.
Amount
>
0
)
;
var
transferRecords
=
queryable
.
OrderBy
(
o
=>
o
.
Metadata
.
Block
.
BlockHeight
)
.
ToList
(
)
;
return
objectMapper
.
Map
<
List
<
TransferRecord
>
,
List
<
TransferRecordDto
>
>
(
transferRecords
)
;
}
}
Register log event processor
Modify
src/NFTAeIndexer/NFTAeIndexerModule.cs
to register NFTTransferredProcessor.
NFTAeIndexerModule.cs
using
AeFinder
.
Sdk
.
Processor
;
using
NFTAeIndexer
.
GraphQL
;
using
NFTAeIndexer
.
Processors
;
using
GraphQL
.
Types
;
using
Microsoft
.
Extensions
.
DependencyInjection
;
using
Volo
.
Abp
.
AutoMapper
;
using
Volo
.
Abp
.
Modularity
;
namespace
NFTAeIndexer
;
public
class
NFTAeIndexerModule
:
AbpModule
{
public
override
void
ConfigureServices
(
ServiceConfigurationContext
context
)
{
Configure
<
AbpAutoMapperOptions
>
(
options
=>
{
options
.
AddMaps
<
NFTAeIndexerModule
>
(
)
;
}
)
;
context
.
Services
.
AddSingleton
<
ISchema
,
AeIndexerSchema
>
(
)
;
// Add your LogEventProcessor implementation.
context
.
Services
.
AddSingleton
<
ILogEventProcessor
,
NFTTransferredProcessor
>
(
)
;
}
}
Add entity mapping
Modify
src/NFTAeIndexer/NFTAeIndexerAutoMapperProfile.cs
and add entity mapping code.
NFTAeIndexerAutoMapperProfile.cs
using
NFTAeIndexer
.
Entities
;
using
NFTAeIndexer
.
GraphQL
;
using
AutoMapper
;
namespace
NFTAeIndexer
;
public
class
NFTAeIndexerAutoMapperProfile
:
Profile
{
public
NFTAeIndexerAutoMapperProfile
(
)
{
CreateMap
<
Account
,
AccountDto
>
(
)
;
CreateMap
<
TransferRecord
,
TransferRecordDto
>
(
)
;
}
}
Building code
Use the following command in the code directory to compile the code.
dotnet build
-c
Release
Step 4 - Deploy AeIndexer
Open the AeIndexer details page and click Deploy.
Fill out the subscription manifest and upload the DLL file.
Subscription manifest:
{
"subscriptionItems"
:
[
{
"chainId"
:
"tDVW"
,
"startBlockNumber"
:
151018963
,
"onlyConfirmed"
:
false
,
"transactions"
:
[
]
,
"logEvents"
:
[
{
"contractAddress"
:
"ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx"
,
"eventNames"
:
[
"Issued"
,
"Issue"
]
}
]
}
]
}
DLL file location: src/NFTAeIndexer/bin/Release/net8.0/NFTAeIndexer.dll
Click the deploy button to submit deployment information. When the normal processing block information appears on the Logs page at the bottom of the details page, it means that the deployment has been successful and data indexing has started.
Step 5 - Query indexed data
Through the Playground page below the details page, you can use GraphQL syntax to query the indexed data information. Enter the query statement on the left, and the query results will be displayed on the right.
query{
account(input: { chainId: "tDVW", address: "2AaBGTi2cJLWtEXR7w96hzun4qVz2KnsGZ1XfqErhKTgDj9Q8x"}) {
symbol,
amount,
address,
tokenName,
nftImageUri,
nftAttributes,
metadata {
chainId,
block {
blockHeight
}
}
}
}
tip
ℹ️ Note: For the complete demo code, please visit AeFinder github to download.
https://github.com/AeFinderProject/aefinder/tree/master/samples/TokenAeIndexer
Step 6 - Getting NFT Seed
In order to create an NFT collection on the aelf blockchain, the deployer wallet must have an
NFT SEED
.
Visit
NFT Faucet
to get your NFT SEED.
After the request is successfully processed, the requestor wallet will receive the
SEED
.
Please note this
SEED
symbol value, as it will be needed later while creating the NFT collection and NFT generation. This will become our
Token Symbol
.
Step 7 - Interact with Smart Contract through frontend dApp
Project Setup
Let's start by cloning the frontend project repository from GitHub.
Run the following command your Terminal:
Terminal
git
clone https://github.com/AElfProject/aelf-samples.git
Next, navigate to the frontend project directory with this command:
Terminal
cd
aelf-samples/advance/nft/2-dapp
Once you're in 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 packages and libraries
Run teh following command in the terminal:
Linux and macOs
Windows
Terminal
sudo
npm
install
Command Prompt
npm
install
We are now ready to build the frontend components of our NFT dApp.
Configure Portkey Provider & Write Connect Wallet Function
We'll set up our Portkey provider to allow users to connect their Portkey wallets to our app and interact with the aelf smart contracts. We'll be interacting with the already deployed multi-token contract for this tutorial.
Step 1. Locate the File:
Go to the
src/hooks/useNFTSmartContract.ts
file.
Step 2. Fetch the Smart Contract:
Find the comment
//Step A - Function to fetch a smart contract based on the chain symbol and the contract address.
Replace the existing
fetchContract
function with this updated code:
useNFTSmartContract.ts
//Step A - Function to fetch a smart contract based on the chain symbol and the contract address
const
fetchContract
=
async
(
symbol
:
"AELF"
|
"tDVW"
,
contractAddress
:
string
)
=>
{
try
{
// If no provider is available, return null
if
(
!
provider
)
return
null
;
// Fetch the chain information using the provider
const
chain
=
await
provider
.
getChain
(
symbol
)
;
if
(
!
chain
)
throw
new
Error
(
"Chain not found"
)
;
// Get the smart contract instance from the chain
const
contract
=
chain
.
getContract
(
contractAddress
)
;
// Return the smart contract instance
return
contract
;
}
catch
(
error
)
{
console
.
error
(
"Error in fetchContract"
,
{
symbol
,
contractAddress
,
error
}
)
;
}
}
;
Explanation:
fetchContract
Function
: This function fetches a smart contract based on the given chain symbol (e.g., "AELF" or "tDVW") and the contract address.
Check Provider
: If no provider is available, the function returns null.
Fetch Chain
: The function fetches chain information using the provider.
Get Contract
: It retrieves the smart contract instance from the chain.
Error Handling
: If an error occurs, it logs the error to the console.
Step 3. Initialize and Fetch the Smart Contracts:
Find the comment
// Step B - Effect hook to initialize and fetch the smart contracts when the provider changes.
Replace the existing
useEffect
hook with this updated code:
useNFTSmartContract.ts
// Step B - Effect hook to initialize and fetch the smart contracts when the provider changes
useEffect
(
(
)
=>
{
(
async
(
)
=>
{
// Fetch the MainChain Testnet Contract
const
mainChainContract
=
await
fetchContract
(
"AELF"
,
"JRmBduh4nXWi1aXgdUsj5gJrzeZb2LxmrAbf7W99faZSvoAaE"
)
;
setMainChainSmartContract
(
mainChainContract
as
IContract
)
;
// Fetch the dAppChain Testnet Contract
const
sideChainContract
=
await
fetchContract
(
"tDVW"
,
"ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx"
)
;
setSideChainSmartContract
(
sideChainContract
as
IContract
)
;
}
)
(
)
;
}
,
[
provider
]
)
;
// Dependency array ensures this runs when the provider changes
Explanation:
useEffect
Hook
: This hook initializes and fetches the smart contracts when the provider changes.
Check Provider
: If no provider is available, the function returns null.
Fetch Contracts
: It fetches and sets the smart contracts for the main chain, side chain, and cross chain.
MainChain Contract
: Fetches the MainChain Testnet Contract and sets it in the state.
dAppChain Contract
: Fetches the dAppChain Testnet Contract and sets it in the state.
By following these steps, we'll configure the Portkey provider to connect users' wallets to your app and interact with the multi-token smart contract including NFT related functionalities. This setup will enable our frontend components to perform actions like
create NFTs
,
validate NFTs
, and
transfer NFTs
.
Configure Connect Wallet Function
Step 1: Locate the File
Go to the
src/components/layout/header/index.tsx
file.
Step 2: Write the Connect Wallet Function
The
header/index.tsx
file is the header of our NFT dApp. It allows users to connect their Portkey wallet with the NFT dApp.
Before users can interact with the smart contract, we need to write the
Connect Wallet
function.
Find the comment
// Step C - Connect Portkey Wallet
.
Replace the existing connect function with this code snippet:
header/index.tsx
const
connect
=
async
(
walletProvider
?
:
IPortkeyProvider
)
=>
{
// Step C - Connect Portkey Wallet
const
accounts
=
await
(
walletProvider
?
walletProvider
:
provider
)
?.
request
(
{
method
:
MethodsBase
.
REQUEST_ACCOUNTS
,
}
)
;
const
account
=
accounts
?.
AELF
&&
accounts
?.
AELF
[
0
]
;
if
(
account
)
{
setCurrentWalletAddress
(
account
.
replace
(
/
^
ELF_
/
,
""
)
.
replace
(
/
_AELF
$
/
,
""
)
)
;
setIsConnected
(
true
)
;
}
!
walletProvider
&&
toast
.
success
(
"Successfully connected"
)
;
}
;
Explanation:
connect
Function
: This function connects the user's Portkey wallet with the dApp.
Fetch Accounts
: It fetches the wallet accounts using the provider.
Log Accounts
: Logs the accounts to the console for debugging.
Set Wallet Address
: Sets the current wallet address state variable with the fetched account.
Update Connection Status
: Updates the state to indicate that the wallet is connected.
User Notification
: Displays an alert to notify the user that their wallet is successfully connected.
In this code, we fetch the Portkey wallet account using the provider and update the wallet address state variable. An alert notifies the user that their wallet is successfully connected.
With the Connect Wallet function defined, we're ready to write the remaining functions in the next steps.
Configure Create NFT Form Code
Step 1: Locate the File
Go to the
src/pages/create-nft/index.tsx
file. This file is the
Create NFTs
page where users can enter details like the
tokenName
,
symbol
,
totalSupply
and
decimals
.
Step 2: Prepare Form to Create NFTs
Find the comment
// Step D - Configure NFT Form
.
Replace the form variable with this code snippet:
create-nft/index.tsx
// Step D - Configure NFT Form
const
form
=
useForm
<
z
.
infer
<
typeof
formSchema
>>
(
{
resolver
:
zodResolver
(
formSchema
)
,
defaultValues
:
{
tokenName
:
""
,
symbol
:
""
,
totalSupply
:
""
,
decimals
:
""
,
}
,
}
)
;
Here's what the function does:
Initializes a new form variable with default values needed to create an NFT.
Fields include:
tokenName
,
symbol
,
totalSupply
, and
decimals
.
Now the form is ready for users to fill in the necessary details for their NFT function interaction.
Create NFT Collection
Let's write the functions to
Create New NFT Collection
on the aelf mainchain and the dAppChain.
Step 1: Write the function to
Create New NFT Collection
on the MainChain
The
create-nft/index.tsx
file includes the code to create NFTs. It allows users to create new NFTs.
Find the comment
// step 1 - Create New NFT Collection on the mainchain
.
Replace the existing
createNftCollectionOnMainChain
function with this code snippet:
create-nft/index.tsx
// step - 1 Create New NFT Collection on the mainchain
const
createNftCollectionOnMainChain
=
async
(
values
:
z
.
infer
<
typeof
formSchema
>
)
:
Promise
<
INftInput
|
"error"
>
=>
{
let
createLoadingId
;
try
{
createLoadingId
=
toast
.
loading
(
"Creating NFT Collection.."
)
;
// Create an object with the necessary information for the new NFT collection.
const
createNftInput
:
INftInput
=
{
tokenName
:
values
.
tokenName
,
// Name of the nft Collection
symbol
:
values
.
symbol
,
// Symbol of the token (You have to get it from your PortKey wallet on NFT seed from NFT section)
totalSupply
:
values
.
totalSupply
,
// Total supply of the token
decimals
:
values
.
decimals
,
// Decimals of the token
issuer
:
currentWalletAddress
,
// Address of the token issuer
isBurnable
:
true
,
// Indicates if the token can be burned
issueChainId
:
sidechain_from_chain_id
,
// ID of the issuing chain
owner
:
currentWalletAddress
,
// Owner's wallet address
}
;
// Call the smart contract method to create the new NFT collection on the main chain.
const
result
=
await
mainChainSmartContract
?.
callSendMethod
(
"Create"
,
currentWalletAddress
,
createNftInput
)
;
// Log the result of the creation for debugging purposes.
console
.
log
(
"========= result of createNewNft ========="
,
result
)
;
toast
.
update
(
createLoadingId
,
{
render
:
"NFT Collection Created Successfully On MainChain"
,
type
:
"success"
,
isLoading
:
false
,
}
)
;
removeNotification
(
createLoadingId
)
;
// Return the input data for further use.
return
createNftInput
;
}
catch
(
error
)
{
handleError
(
createLoadingId
as
number
,
error
)
;
return
"error"
;
}
}
;
tip
ℹ️ Note: You need to get
symbol
from the Portkey wallet.
Follow Steps to get NFT symbol from Portkey Wallet:
Open Portkey Wallet.
Go to the NFTs tab.
You will find the SEED that you already got from the above
seed generation
step.
Click on the SEED to see details.
You will find the
Token Symbol
inside the
Token Creation via This Seed
section.
Copy and use that value of the token symbol.
What This Function Does:
Creates an Object with NFT Details
: It prepares the data needed to create a new NFT collection.
Calls Smart Contract Method
: It interacts with the blockchain smart contract to create the new NFT collection using the prepared data.
Logs Information
: It logs the result for debugging purposes.
Return Values of Object
: It returns necessary values as a object.
Next, we'll write the
Validate Collection Info Exist
function.
Step 2: Write the validates collection info exist function
Scroll up to find the comment
// step 2 - Validate Collection information exist
.
Replace the existing
validateNftCollectionInfo
function with this code snippet:
create-nft/index.tsx
// step 2 - Validate Collection information exist
// This function validates if the token collection information already exists on the main blockchain.
const
validateNftCollectionInfo
=
async
(
values
:
INftInput
)
:
Promise
<
ValidationResult
|
"error"
>
=>
{
let
validateLoadingId
;
try
{
// Start loading before initiating the transaction
validateLoadingId
=
toast
.
loading
(
<
CustomToast
title
=
"
Transaction is getting validated on aelf blockchain. Please wait!
"
message
=
"
Validation means transaction runs through a consensus algorithm to be selected or rejected. Once the status changes, the process will complete. It usually takes some time in distributed systems.
"
/>
)
;
// Create an object with the necessary information for token validation.
const
validateInput
:
INftInput
=
{
symbol
:
values
.
symbol
,
// Symbol of the token
tokenName
:
values
.
tokenName
,
// Name of the token
totalSupply
:
values
.
totalSupply
,
// Total supply of the token
decimals
:
values
.
decimals
,
// Decimals of the token
issuer
:
currentWalletAddress
,
// Address of the token issuer
isBurnable
:
true
,
// Indicates if the token can be burned
issueChainId
:
sidechain_from_chain_id
,
// ID of the issuing chain
owner
:
currentWalletAddress
,
// Owner's wallet address
}
;
// Get mainnet contract
const
aelfTokenContract
=
await
getTokenContract
(
aelf
,
wallet
)
;
// Prepare to sign the transaction using the contract method
const
signedTx
=
aelfTokenContract
.
ValidateTokenInfoExists
.
getSignedTx
(
validateInput
)
;
// Send the transaction using the signed transaction
const
{
TransactionId
:
VALIDATE_TXID
}
=
await
aelf
.
chain
.
sendTransaction
(
signedTx
)
;
// Get the validation result
let
VALIDATE_TXRESULT
=
await
aelf
.
chain
.
getTxResult
(
VALIDATE_TXID
)
;
// Wait until the latest index height is greater than or equal to the transaction block number
let
heightDone
=
false
;
while
(
!
heightDone
)
{
await
delay
(
5000
)
;
// Wait 5 seconds before checking again
// Get the latest index height
const
sideIndexMainHeight
=
await
GetParentChainHeight
(
)
;
if
(
Number
(
sideIndexMainHeight
)
>=
VALIDATE_TXRESULT
.
Transaction
.
RefBlockNumber
)
{
VALIDATE_TXRESULT
=
await
aelf
.
chain
.
getTxResult
(
VALIDATE_TXID
)
;
heightDone
=
true
;
}
}
console
.
log
(
"VALIDATE_TXRESULT"
,
VALIDATE_TXRESULT
)
;
// Update the loading message
toast
.
update
(
validateLoadingId
,
{
render
:
"Validating Token Successfully Executed"
,
type
:
"success"
,
isLoading
:
false
,
}
)
;
// Remove the loading message
removeNotification
(
validateLoadingId
)
;
// Return necessary details
return
{
transactionId
:
VALIDATE_TXID
,
signedTx
:
signedTx
,
BlockNumber
:
VALIDATE_TXRESULT
.
BlockNumber
,
}
;
}
catch
(
error
)
{
handleError
(
validateLoadingId
as
number
,
error
)
;
return
"error"
;
}
}
;
What This Function Does:
Creates an Object with Validate Collection Details
: It prepares the data needed to validate the token information.
Calls Smart Contract Method
: It interacts with the multi-token smart contract method to check if the token information already exists using the prepared data.
Return Values
: It returns necessary values as an object.
Next, we'll write the
Get the parent chain height
function.
Step 3: Write the get the parent chain height function
Scroll up to find the comment
// Step 3: Get the parent chain height
.
Replace the existing
GetParentChainHeight
function with the following code snippet:
create-nft/index.tsx
// Step 3: Get the parent chain height
// This function fetches the current height of the parent blockchain.
const
GetParentChainHeight
=
async
(
)
=>
{
try
{
const
tdvwCrossChainContract
=
await
getCrossChainContract
(
tdvw
,
wallet
)
;
// Call the smart contract method to get the parent chain height.
const
result
=
await
tdvwCrossChainContract
.
GetParentChainHeight
.
call
(
)
// Return the parent chain height if it exists, otherwise return an empty string.
return
result
?
(
result
.
value
as
string
)
:
""
;
}
catch
(
error
)
{
// If there's an error, log it and return an error status.
console
.
error
(
error
,
"=====error in GetParentChainHeight"
)
;
return
"error"
;
}
}
;
What This Function Does:
Calls Smart Contract Method
: It interacts with the side chain smart contract method to fetch the current height of the parent blockchain.
Returns Parent Chain's Height
: It returns the parent chain's height if it exists.
Next, we'll write the
Fetch the Merkle path
function.
Step 4: Write the fetch the merkle path function
Scroll up to find the comment
// step 4 - Fetch the merkle path by transaction Id
.
Replace the existing
getMerklePathByTxId
function with this code snippet:
create-nft/index.tsx
// step 4 - Fetch the merkle path by transaction Id
const
getMerklePathByTxId
=
async
(
aelf
:
AElf
,
txId
:
string
)
=>
{
try
{
const
{
MerklePathNodes
}
=
await
aelf
.
chain
.
getMerklePathByTxId
(
txId
)
;
const
formattedMerklePathNodes
=
MerklePathNodes
.
map
(
(
{
Hash
,
IsLeftChildNode
,
}
:
{
Hash
:프로젝트로 돌아가기