Créer une dApp à partir de zéro
Ce guide explorera les bases de la création d'un projet d'application décentralisée (dApp) sur Alephium.
Prérequis
- Écrire du code en Typescript
- Opérer dans un Terminal
- Version de nodejs >= 16 installée
- Version de
npm
>= 8 installée
Créez un nouveau projet de dApp: Distributeur de jetons
Dans ce tutoriel, nous écrirons notre première dApp: un distributeur de jetons.
Le code ici est tiré de notre page de démarrage, mais nous verrons étape par étape comment construire ce guide.
Créez un nouveau dossier de projet et naviguez dedans :
Créons maintenant un dossier contracts
où nous stockerons tous nos contrats:
Notre premier contrat sera token.ral
que vous pouvez trouver ici. Vous pouvez copier l'intégralité du fichier dans votre dossiercontracts
.
Examinons-le, morceau par morceau :
token.ral
Les quatre premiers champs seront des valeurs immuables qui stockent les données nécessaires pour servir notre IFungibleToken interface.
mut balance
est une valeur mutable qui garde une trace du nombre de jetons restants dans ce distributeur.
Vous pouvez voir que notre contrat émet un event
et définit un code d'error
. Lisez ce qui suit pour plus d'informations sur les événements et la gestion des erreurs.
Cela est suivi de 5 méthodes d'accès pour les différents arguments du contrat.
La dernière méthode est là où la magie opère :
token.ral
@using(assetsInContract = true, updateFields = true, checkExternalCaller = false)
pub fn withdraw(amount: U256) -> () {
// Les événements de débogage peuvent être utiles pour l'analyse des erreurs
emit Debug(`The current balance is ${balance}`)
// Assurez-vous que le montant est valide
assert!(amount <= 2, ErrorCodes.InvalidWithdrawAmount)
// Les fonctions suffixées par `!` sont des fonctions intégrées.
transferTokenFromSelf!(callerAddress!(), selfTokenId!(), amount)
// Ralph n'autorise pas le débordement.
balance = balance - amount
// Émettre l'événement défini précédemment.
emit Withdraw(callerAddress!(), amount)
}
Avec assert!
, nous nous assurons que personne ne prend plus de 2 jetons en même temps.
transferTokenFromSelf
effectuera en fait le transfert des jetons.
Nous mettons à jour le champ mut balance
avec le nouveau solde. En cas de débordement, une erreur sera déclenchée et la transaction ne sera pas effectuée.
callerAddress!()
et selfTokenId!()
sont des fonctions intégrées, vous pouvez en lire plus à leur sujet sur notre page des fonctions intégrées.
Compilez votre contrat
Le compilateur doit contacter le nœud complet pour compiler le contrat, vous devrez utiliser les bonnes informations définies lors de la création de votre devnet. Si vous ne l'avez pas encore démarré, c'est le moment.
Nous définissons l'URL du nœud en utilisant le fichier de configuration suivant: alephium.config.ts
.
Créez ce fichier dans le répertoire racine de votre projet et collez le code suivant:
alephium.config.ts
import { Configuration } from '@alephium/cli'
export type Settings = {}
const configuration: Configuration<Settings> = {
networks: {
devnet: {
// Assurez-vous que les deux valeurs correspondent à ce qui est dans votre configuration devnet
nodeUrl: 'http://localhost:22973',
networkId: 2
}
}
}
export default configuration
Maintenant, compilons :
Il se peut qu'il vous demande une confirmation pour installer le dernier paquet @alephium/cli
. Sélectionnez oui pour continuer.
Une fois que la commande ci-dessus réussit, vous remarquerez qu'un nouveau dossier appelé artifacts
a été créé. IIl contient plusieurs fichiers liés à votre contrat. Par exemple, artifacts/ts/TokenFaucet.ts
génère de nombreuses fonctions d'aide telles que at
, fetchState
, call*
, etc., ainsi que de nombreuses fonctions de test.
Testez votre contrat
Le SDK fournit des fonctionnalités de test unitaire, qui appellent le contrat en envoyant une transaction, mais au lieu de changer l'état de la blockchain, elles renvoient le nouvel état du contrat, les sorties de transaction et les événements.
Installez le framework de test:
Vous aurez également besoin de notre package @alephium/web3
:
Créez un dossier test
:
et créez le fichier de test minimaliste test/token.test.ts
avec le contenu suivant:
test/token.test.ts
import { web3, Project, addressFromContractId } from '@alephium/web3'
import { randomContractId, testAddress } from '@alephium/web3-test'
import { TokenFaucet } from '../artifacts/ts'
describe('unit tests', () => {
it('Withdraws 1 token from TokenFaucet', async () => {
// Utilisez le bon hôte et le bon port
web3.setCurrentNodeProvider('http://127.0.0.1:22973')
await Project.build()
const testContractId = randomContractId()
const testParams = {
// une adresse aléatoire où réside le contrat de test dans les tests
address: addressFromContractId(testContractId),
// actifs détenus par le contrat de test avant un test
initialAsset: { alphAmount: 10n ** 18n, tokens: [{ id: testContractId, amount: 10n }] },
// état initial du contrat de test
initialFields: {
symbol: Buffer.from('TF', 'utf8').toString('hex'),
name: Buffer.from('TokenFaucet', 'utf8').toString('hex'),
decimals: 18n,
supply: 10n ** 18n,
balance: 10n
},
// arguments pour tester la fonction cible du contrat de test
testArgs: { amount: 1n },
// actifs détenus par l'appelant de la fonction
inputAssets: [{ address: testAddress, asset: { alphAmount: 10n ** 18n } }]
}
const testResult = await TokenFaucet.tests.withdraw(testParams)
console.log(testResult)
})
})
Un test plus complexe peut être trouvé dans notre projet de modèle.
Sans entrer dans trop de détails, TypeScript a besoin d'une configuration pour exécuter le test, alors créez simplement un fichier appelé tsconfig.json
idans le répertoire racine de votre projet et collez le code suivant:
tsconfig.json
Vous pouvez maintenant exécuter le test:
Vous devriez pouvoir voir sur votre terminal la sortie de l'appel à la méthode de retrait.
🎉🎉 Félicitations! 🎉🎉
Vous avez créé votre premier contrat et écrit un test pour l'appeler et le tester localement! Il est temps de déployer votre contrat.
Déployez votre contrat
Maintenant, les choses deviennent sérieuses, nous allons déployer notre contrat sur notre devnet
La commande deploy
exécutera tous les scripts de déploiement qu'elle trouve dans le dossier scripts
. Créez le dossier scripts
dans le dossier racine du projet:
Créez maintenant un fichier de script de déploiement appelé 0_deploy_faucet.ts
dans le dossier scripts
et collez-y le code suivant.
Notez que les scripts de déploiement doivent toujours être préfixés par des nombres (en commençant par0
).
0_deploy_faucet.ts
import { Deployer, DeployFunction, Network } from '@alephium/cli'
import { Settings } from '../alephium.config'
import { TokenFaucet } from '../artifacts/ts'
// Cette fonction de déploiement sera appelée automatiquement par l'outil de déploiement de l'interface en ligne de commande (CLI)
// Notez que les scripts de déploiement doivent être préfixés par des nombres (en commençant par 0)
const deployFaucet: DeployFunction<Settings> = async (
deployer: Deployer
): Promise<void> => {
const issueTokenAmount = 100n
const result = await deployer.deployContract(TokenFaucet, {
// Le montant de jetons à émettre
issueTokenAmount: issueTokenAmount,
// Les états initiaux du contrat du robinet à jetons
initialFields: {
symbol: Buffer.from('TF', 'utf8').toString('hex'),
name: Buffer.from('TokenFaucet', 'utf8').toString('hex'),
decimals: 18n,
supply: issueTokenAmount,
balance: issueTokenAmount
}
})
console.log('Token faucet contract id: ' + result.contractInstance.contractId)
console.log('Token faucet contract address: ' + result.contractInstance.address)
}
export default deployFaucet
La fonction deployContrat du Deployer
tprend notre contrat et le déploie avec les arguments corrects. Vous pouvez également ajouter un argument taskTag
a pour marquer votre déploiement avec un nom spécifique. Par défaut, il utilisera le nom du contrat, mais si vous déployez le même contrat plusieurs fois avec des champs initiaux différents, votre fichier .deployment
sera écrasé. L'utilisation d'un taskTag
spécifique résout ce problème.
À partir de l'interface DeployContractParams, nous pouvons voir que initialFields
est obligatoire car il contient les arguments de notre contrat TokenFaucet
.
Avec issueTokenAmount
vous pouvez définir combien de jetons vous voulez émettre, c'est nécessaire si vous voulez créer un jeton, sinon aucun ID de jeton ne sera créé.
Maintenant, déployons!
...OUPS... Ça ne fonctionne pas???
Si vous avez reçu l'erreur The node chain id x is different from configured chain id y
, vérifiez votre networkId
dans la configuration devnet et le fichier alephium.config.ts
.
No UTXO found
???
Bien sûr, nous n'avons pas fourni le how-to-use-my-utxos
, nous devons définir nos clés privées.
Vous devrez exporter les clés privées depuis notre extension de portefeuille (peut-être le ferons-nous depuis nos autres portefeuilles plus tard), assurez-vous d'utiliser un portefeuille avec des fonds, comme celui de l'allocation de genèse de votre devnet. Si vous avez utilisé la méthode docker pour lancer votre devnet, cela pourrait avoir fonctionné car nous définissons une clé privée par défaut dans notre package cli basée sur l'allocation de genèse.
Modifions notre alephium.config.ts
`alephium.config.ts
const configuration: Configuration<void> = {
networks: {
devnet: {
nodeUrl: 'http://localhost:22973',
networkId: 2,
// La clé privée de mon adresse de genèse 132mqFF2BuxGigdaMTGSruuW29kmEs2eEGcpquG4YZRNh
privateKeys: ['672c8292041176c9056bb0dd1d91d34711ceed2493b5afc83f2012b27df2c559']
}
}
}
ATTENTION
Les applications réelles devraient utiliser des variables d'environnement ou des techniques similaires pour les paramètres sensibles comme les privateKeys
.
Ne communiquez pas vos clés privées sur un dépôt de code source.
et réessayons de déployer :
Contracts are compiled already. Loading them from folder "artifacts"
Deploying contract TokenFaucet
Deployer - group 1 - 132mqFF2BuxGigdaMTGSruuW29kmEs2eEGcpquG4YZRNh
Token faucet contract id: d00e9c788ddd572b0c186f0599a264f4c79f009c632c8040b7c5f71bfc0ec301
Token faucet contract address: 28h7qSmkAAeNyoBuQKGyp1WG8VfdKPePCCFGKwp2Y8yyA
✅ Deployment scripts executed!
Félicitations! Votre contrat est déployé. Nous pouvons vérifier le solde du contrat. Utilisez curl
et modifiez l'adresse du contrat en fonction du résultat de votre déploiement:
La réponse devrait ressembler à ceci :
réponse
Nous pouvons voir notre ID de token, avec les 100 tokens que nous avons décidé d'émettre.
Vérifions maintenant l'état du contrat en obtenant d'abord le groupe de notre adresse :
curl 'http://localhost:22973/addresses/28h7qSmkAAeNyoBuQKGyp1WG8VfdKPePCCFGKwp2Y8yyA/group'
curl 'http://localhost:22973/contracts/28h7qSmkAAeNyoBuQKGyp1WG8VfdKPePCCFGKwp2Y8yyA/state?group=1'
Réponse de l'état du contrat :
réponse
{
"address": "28h7qSmkAAeNyoBuQKGyp1WG8VfdKPePCCFGKwp2Y8yyA",
"bytecode": "050609121b4024402d404a010000000102ce0002010000000102ce0102010000000102ce0202010000000102ce0302010000000102a0000201020101001116000e320c7bb4b11600aba00016002ba10005b416005f",
"codeHash": "641343b4f1c08b03969b127b452acc7535cad20231bc32af6c0b5f218dd8ff0c",
"initialStateHash": "06595afa695949e915dfc1220dfb47125b01751d9e193f4c5fa1c7fc3566673d",
"immFields": [
{
"type": "ByteVec",
"value": "5446"
},
{
"type": "ByteVec",
"value": "546f6b656e466175636574"
},
{
"type": "U256",
"value": "18"
},
{
"type": "U256",
"value": "100"
}
],
"mutFields": [
{
"type": "U256",
"value": "100"
}
],
"asset": {
"attoAlphAmount": "1000000000000000000",
"tokens": [
{
"id": "d00e9c788ddd572b0c186f0599a264f4c79f009c632c8040b7c5f71bfc0ec301",
"amount": "100"
}
]
}
}
Dans les immFields
, nous pouvons voir nos arguments initiaux de TokenFaucet
(symbol
, name
, decimals
, supply
). Nous pouvons également voir que mutFields
contient le solde actuel du token. Nous vérifierons ce champ plus tard après avoir appelé le faucet.
La commande deploy
a également créé un fichier .deployments.devnet.json
, avec le résultat du déploiement. Il est important de conserver ce fichier pour interagir facilement avec le contrat, même si toutes les informations peuvent être trouvées sur la blockchain.
Interagir avec le contrat déployé
Avoir un faucet de tokens c'est bien, obtenir des tokens à partir de celui-ci c'est encore mieux.
Nous pouvons maintenant écrire du code pour interagir avec le contrat de faucet.
Nous devrons installer notre package cli
et la dépendance typescript
si ce n'est pas encore le cas:
Nous allons maintenant voir une autre option pour interagir avec la blockchain. Auparavant, nous utilisions la DeployFunction
avec nos fichiers scripts/<number>_*
qui sont automatiquement déployés avec l'outil CLI.
Une autre façon est de créer un projet d'application Web squelette en utilisant TypeScript. Créez un dossier src
dans le dossier racine du projet et un fichier appelé tokens.ts
avec le contenu suivant.
tokens.ts
import { Deployments } from '@alephium/cli'
import { DUST_AMOUNT, web3, Project, NodeProvider } from '@alephium/web3'
import { PrivateKeyWallet} from '@alephium/web3-wallet'
import configuration from '../alephium.config'
import { TokenFaucet, Withdraw } from '../artifacts/ts'
async function withdraw() {
// Sélectionnez notre réseau défini dans alephium.config.ts
const network = configuration.networks.devnet
// NodeProvider est une abstraction d'une connexion au réseau Alephium
const nodeProvider = new NodeProvider(network.nodeUrl)
// Parfois, il est pratique de configurer un NodeProvider global pour votre projet:
web3.setCurrentNodeProvider(nodeProvider)
// Connectez notre portefeuille, typiquement dans une application réelle vous connecteriez votre extension Web ou votre portefeuille de bureau
const wallet = new PrivateKeyWallet({privateKey: '672c8292041176c9056bb0dd1d91d34711ceed2493b5afc83f2012b27df2c559' })
// Compilez les contrats du projet s'ils ne sont pas compilés
Project.build()
//.deployments contient les infos de notre déploiement `TokenFaucet` , car nous devons connaître le contractId et l'adresse
// Ceci a été auto-généré avec le `cli deploy` de notre `scripts/0_deploy_faucet.ts`
const deployments = await Deployments.from('.deployments.devnet.json')
// Assurez-vous qu'il correspond à votre groupe d'adresses
const accountGroup = 1
const deployed = deployments.getDeployedContractResult(accountGroup, 'TokenFaucet')
if(deployed !== undefined) {
const tokenId = deployed.contractInstance.contractId
const tokenAddress = deployed.contractInstance.address
// Soumettez une transaction pour utiliser le script de transaction
// Il utilise notre `wallet` pour signer la transaction.
await Withdraw.execute(wallet, {
initialFields: { token: tokenId, amount: 1n },
attoAlphAmount: DUST_AMOUNT
})
// Obtenez l'état le plus récent du contrat de jeton, `mut balance` devrait avoir changé
const faucet = TokenFaucet.at(tokenAddress)
const state = await faucet.fetchState()
console.log(state.fields)
// Obtenez le solde du portefeuille pour voir si le jeton y est
const balance = await wallet.nodeProvider.addresses.getAddressesAddressBalance(wallet.account.address)
console.log(balance)
} else {
console.log('`deployed` is undefined')
}
}
// Effectuons un retrait
withdraw()
Pour les personnes attentives, vous verrez quelque chose de nouveau provenant de nos artifacts
: Withdraw
qui est un TxScript
requis pour interagir avec le contrat TokenFaucet
. Son code est assez simple. Créez un fichier appelé withdraw.ral
dans le dossier contracts
et collez le code suivant:
Nous devons maintenant recompiler nos contrats pour obtenir l'artefact pour Withdraw
:
Vous pouvez maintenant compiler le code TypeScript en JavaScript avec:
OOPS, vous devriez obtenir une erreur provenant du fichier alephium.config.ts
, jusqu'à présent la configuration était utilisée comme un simple JSON, mais maintenant TypeScript
veut qu'elle respecte son interface. En particulier, les networks
sont un enregistrement qui doit contenir les 3 NetworkType
. Vous pouvez essayer de le corriger vous-même ou mettre à jour votre fichier alephium.config.ts
avec:
alephium.config.ts
import { Configuration } from '@alephium/cli'
export type Settings = {}
const configuration: Configuration<Settings> = {
defaultNetwork: 'devnet',
networks: {
devnet: {
nodeUrl: 'http://localhost:22973',
networkId: 2, //Use the same as in your devnet configuration
privateKeys: ['672c8292041176c9056bb0dd1d91d34711ceed2493b5afc83f2012b27df2c559'],
settings: {}
},
testnet: {
nodeUrl: '',
privateKeys: [],
settings: {}
},
mainnet: {
nodeUrl: '',
privateKeys: [],
settings: {}
}
}
}
export default configuration
Maintenant, recompilez
Un dossier dist
devrait avoir été créé, continuez et interagissez avec le robinet à jetons déployé:
Vous devriez maintenant être un fier propriétaire du jeton que vous avez créé.
Et après?
Vous pouvez trouver un exemple plus complexe du tutoriel du robinet à jetons dans le projet alephium nextjs-template project.
Connexion aux portefeuilles
Les dApps nécessitent une intégration de portefeuille pour les utilisateurs de la dApp afin d'authentifier et d'interagir avec la blockchain Alephium, telles que la signature de transactions. Actuellement, les dApps peuvent être intégrées à la fois avec le Extension Wallet et WalletConnect. Veuillez vous référer aux pages respectives pour plus de détails.
En savoir plus
- Pour en savoir plus sur l'écosystème, veuillez visiter la vue d'ensemble de l'écosystème.
- Pour en savoir plus sur le SDK web3, veuillez visiter le guide du web3 SDK.
- Pour en savoir plus sur le langage Ralph, veuillez visiter le guide de Ralph.
- Pour apprendre à construire une dApp Nextjs, veuillez visiter Construire une dApp avec Nextjs