Aller au contenu

Web3 SDK

Installation

Text Only
npm install --save @alephium/web3

Connexion à Alephium

NodeProvider est une abstraction d'une connexion au réseau Alephium, vous pouvez obtenir un NodeProvider en:

TypeScript
const nodeProvider = new NodeProvider('http://localhost:22973')

Ou spécifiez la API_KEY si vous avez alephium.api.api-key dans votre fichier de configuration du nœud complet :

TypeScript
const API_KEY = // alephium.api.api-key à partir de votre configuration de nœud complet
const nodeProvider = new NodeProvider('http://localhost:22973', API_KEY)

Parfois, il est pratique de configurer un NodeProvider global pour votre projet:

TypeScript
web3.setCurrentNodeProvider(<nodeURL>)

Interrogation de la Blockchain

Une fois que vous avez un NodeProvider, vous avez une connexion à la blockchain, que vous pouvez utiliser pour interroger l'état actuel du contrat, récupérer les événements historiques du contrat, rechercher les contrats déployés, etc.

TypeScript
// Obtenez la hauteur de la blockchain à partir de l'index de la chaîne donnée
await nodeProvider.blockflow.getBlockflowChainInfo({
  fromGroup: 0,
  toGroup: 0
})
// { currentHeight: 315 }

// Obtenez le bloc à partir du hachage de bloc donné
await nodeProvider.blockflow.getBlockflowBlocksBlockHash('1ccfe845988ebf878384dd2dc9e55920261566c2ad9143963180222059ffd3b0')
// {
//   hash: '1ccfe845988ebf878384dd2dc9e55920261566c2ad9143963180222059ffd3b0',
//   timestamp: 1665207807876,
//   chainFrom: 0,
//   chainTo: 0,
//   height: 315,
//   deps: [
//     '5e9209fbf2b4c656b136933684b8606382575f16795ddc7a6317c5d4b7e378c5',
//     'b5d69f03d4d0eea5a4c7e0bc0e2e8c5a8f99306050d641b599991b441ea0f9ea',
//     'f344b3dd45f39d62cfd200cfa3312c080018102908787c5565b8c8af3647368f',
//     'fa359ce0b5accb1584cd280ae3c32f210424a9fd32cb736b6cdfe64882651010',
//     '8bc7d94ddfc66129049862e501ff5d5042e1e978b1a51d28e238321444a91071',
//     '5896ee3cbc8c4f4432a0b221039113061b6cf7eeb794b25758d247120d0df712',
//     'fc312b0c04a0eeb0767ec98137b740047d7c5e8f64463531828b7015cbeaeaa3'
//   ],
//   transactions: [
//     {
//       unsigned: [Object],
//       scriptExecutionOk: true,
//       contractInputs: [],
//       generatedOutputs: [],
//       inputSignatures: [],
//       scriptSignatures: []
//     }
//   ],
//   nonce: '5da513de7183d7eb1a90b46c4ffe6f7ae4fdf012f0018f41',
//   version: 0,
//   depStateHash: '2edccc3e717d38a6564fb970f6714f0c00c074226b2cb1ee6272781d3c9bc870',
//   txsHash: '738e879791e0c164a5a12b658e2b37e65ed9c415c2e152656c8469273f775f5a',
//   target: '20ffffff'
// }

// Obtenez le statut de la transaction

await nodeProvider.transactions.getTransactionsStatus({
  txId: 'f33da0d8f4c00d68e2d5818cb9617219a1108b801f387fc8d1595287e4dbf2aa'
})
// {
//   type: 'Confirmed',
//   blockHash: '47c95e02a7d7ca442ee1849d76a5987c7b72ddcad0b05e9f01df4bc4878f5980',
//   txIndex: 0,
//   chainConfirmations: 295,
//   fromGroupConfirmations: 295,
//   toGroupConfirmations: 295
// }

// Obtenez le solde du compte
await nodeProvider.addresses.getAddressesAddressBalance('1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH')
// {
//   balance: '999972904436900000000000',
//   balanceHint: '999972.9044369 ALPH',
//   lockedBalance: '0',
//   lockedBalanceHint: '0 ALPH',
//   tokenBalances: [
//     {
//       id: 'ee682f8182f56b55a36a7f916a901685752e00d0e4b90de073e6658156ae82a9',
//       amount: '10000000000000000000'
//     }
//   ],
//   utxoNum: 1
// }

Écriture dans la blockchain

Les transactions sont utilisées pour changer l'état de la blockchain. Chaque transaction doit être signée avec une clé privée, ce qui peut être fait via le SignerProvider. Et il existe deux SignerProvider dans alephium/web3-wallet.

Installation de Web3 Wallet

Text Only
npm install --save @alephium/web3-wallet

Note

Les deux portefeuilles sont utilisés pour le développement et le déploiement de contrats, veuillez ne pas les utiliser pour stocker de grandes quantités de jetons.

Portefeuille Node

Veuillez suivre le guide pour créer un portefeuille de nœud complet.

TypeScript
// Créez un portefeuille de nœud en utilisant le nom du portefeuille
const nodeWallet = new NodeWallet('alephium-web3-test-only-wallet', nodeProvider)

// Déverrouillez le portefeuille avec le mot de passe
await nodeWallet.unlock('alph')

// Obtenez les comptes
await nodeWallet.getAccounts()
// [
//   {
//     publicKey: '0381818e63bd9e35a5489b52a430accefc608fd60aa2c7c0d1b393b5239aedf6b0',
//     address: '1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH',
//     group: 0
//   }
// ]

// Transférer 1 ALPH à 15Z54erRksUHb7qxegcKN5DePMv96tXdc1jW26fW3REwT
await nodeWallet.signAndSubmitTransferTx({
  signerAddress: '1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH',
  destinations: [{
    address: '15Z54erRksUHb7qxegcKN5DePMv96tXdc1jW26fW3REwT',
    attoAlphAmount: 10n ** 18n,
  }]
})
// {
//   txId: '7d11b0e88f0158cdd94fcf4e7af04a76be1101293a50050523f72422a9269f36',
//   fromGroup: 0,
//   toGroup: 0
// }

// Verrouillez le portefeuille
await nodeWallet.lock()

PrivateKeyWallet

TypeScript
// Créez un portefeuille PrivateKeyWallet à partir de la clé privée
const wallet =  new PrivateKeyWallet({privateKey: 'a642942e67258589cd2b1822c631506632db5a12aabcf413604e785300d762a5', keyType: undefined, nodeProvider: web3.getCurrentNodeProvider()})

// Créez un portefeuille PrivateKeyWallet à partir de la mnémonique et du groupe, ici il créera un compte sur le groupe 0
const wallet = PrivateKeyWallet.FromMnemonicWithGroup(
  'vault alarm sad mass witness property virus style good flower rice alpha viable evidence run glare pretty scout evil judge enroll refuse another lava',
  0,
  undefined,
  undefined,
  undefined,
  nodeProvider
)
console.log(wallet.account)
// {
//   address: '1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH',
//   publicKey: '0381818e63bd9e35a5489b52a430accefc608fd60aa2c7c0d1b393b5239aedf6b0',
//   group: 0
// }

// Transférer 1 ALPH à 15Z54erRksUHb7qxegcKN5DePMv96tXdc1jW26fW3REwT
await wallet.signAndSubmitTransferTx({
  signerAddress: wallet.account.address,
  destinations: [{
    address: '15Z54erRksUHb7qxegcKN5DePMv96tXdc1jW26fW3REwT',
    attoAlphAmount: 10n ** 18n
  }]
})
// {
//   txId: '9a23eab3796a56f538a4574d617b667fb9187721c8df9f39ac89d878cb9755a0',
//   fromGroup: 0,
//   toGroup: 0
// }

Contrats

Similaire à Ethereum, un contrat est une abstraction du code de programme qui vit sur la blockchain Alephium. Utilisons l'exemple suivant pour illustrer comment tester, déployer et appeler un contrat, veuillez suivre le guide pour créer un projet.

Testez le contrat

Tests unitaires

Le SDK fournit une fonctionnalité de test unitaire, qui appelle le contrat comme une transaction normale, mais au lieu de changer l'état de la blockchain, il renvoie le nouveau contrat état, les sorties de transaction et les événements..

TypeScript
web3.setCurrentNodeProvider('http://localhost:22973')
const wallet = new PrivateKeyWallet('a642942e67258589cd2b1822c631506632db5a12aabcf413604e785300d762a5')
// Commencez par construire le projet
await Project.build()

// Testez la méthode `withdraw` du contrat TokenFaucet, cela NE changera PAS l'état de la blockchain
const testContractAddress = randomContractAddress()
// Le `TokenFaucet` est généré dans le guide de démarrage
const result = await TokenFaucet.tests.withdraw({
  address: testContractAddress,
  // É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
  },
  // Actifs détenus par le contrat de test avant un test
  initialAsset: {
    alphAmount: 10n ** 18n,
    tokens: [{
      id: binToHex(contractIdFromAddress(testContractAddress)),
      amount: 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: wallet.account.address,
    asset: { alphAmount: 10n ** 18n }
  }]
})

const contractState = result.contracts[0] as TokenFaucetTypes.State
expect(contractState.address).toEqual(testContractAddress)

Un exemple complet peut être trouvé dans notre alephium-nextjs-template

Tests d'intégration

En plus des tests unitaires, vous pouvez également exécuter certains tests d'intégration, soyez prudent car ceux-ci peuvent modifier l'état de la blockchain.

TypeScript
web3.setCurrentNodeProvider('http://127.0.0.1:22973', undefined, fetch)
await Project.build()

const accounts = signer.getAccounts()
const account = accounts[0]
const testAddress = account.address
await signer.setSelectedAccount(testAddress)
const testGroup = account.group

const deployed = deployments.getDeployedContractResult(testGroup, 'TokenFaucet')
const tokenId = deployed.contractInstance.contractId
const tokenAddress = deployed.contractInstance.address

const faucet = TokenFaucet.at(tokenAddress)
const initialState = await faucet.fetchState()
const initialBalance = initialState.fields.balance

// Appelez la fonction `withdraw` 10 fois
for (let i = 0; i < 10; i++) {
  await Withdraw.execute(signer, {
    initialFields: { token: tokenId, amount: 1n },
    attoAlphAmount: DUST_AMOUNT * 2n
  })

  //!!! L'état de la blockchain est modifié !!!
  const newState = await faucet.fetchState()
  const newBalance = newState.fields.balance
  expect(newBalance).toEqual(initialBalance - BigInt(i) - 1n)
}

Plus de détails peuvent être trouvés dans notre dossier de tests d'intégration

Déployez le contrat

TypeScript
web3.setCurrentNodeProvider('http://localhost:22973')
const wallet = new PrivateKeyWallet('a642942e67258589cd2b1822c631506632db5a12aabcf413604e785300d762a5')
await Project.build()

// Créez une transaction pour déployer le contrat et soumettez la transaction au réseau Alephium :
// `initialFields` est requis si le contrat a des champs
// `initialAttoAlphAmount` doit être supérieur ou égal à 1 ALPH, les actifs seront envoyés au contrat depuis le compte de l'expéditeur de la transaction
// `issueTokenAmount` spécifie la quantité de jetons à émettre
const issueTokenAmount = 10n
const deployResult = await TokenFaucet.deploy(wallet, {
  initialFields: {
    symbol: Buffer.from('TF', 'utf8').toString('hex'),
    name: Buffer.from('TokenFaucet', 'utf8').toString('hex'),
    decimals: 18n,
    supply: issueTokenAmount,
    balance: issueTokenAmount
  },
  initialAttoAlphAmount: 10n ** 18n,
  issueTokenAmount: issueTokenAmount
})
console.log(JSON.stringify(deployResult, null, 2))
// {
//   "signature": "ea95754bae7935311acf15d3323293f03bce89bb6c82939427da5e3074f0ada93b0cda24138dcec1de4e21a1a66dc1f0c8e99297e6ff8fe10587d1821cbae23f",
//   "fromGroup": 0,
//   "toGroup": 0,
//   "unsignedTx": "000401010103000000091500bee85f379545a2ed9f6cceb331288842f378cf0f04012ad4ac8824aae7d6f80a13c40de0b6b3a7640000a2144055050609121b4024402d404a010000000102ce0002010000000102ce0102010000000102ce0202010000000102ce0302010000000102a0000201020101001116000e320c7bb4b11600aba00016002ba10005b416005f14160403025446030b546f6b656e4661756365740212020a140301020a130aae188000dfdfc1174876e8000137a444479fa782e8b88d4f95e28b3b2417e5bc30d33a5ae8486d4a8885b82b224259c1e6000381818e63bd9e35a5489b52a430accefc608fd60aa2c7c0d1b393b5239aedf6b000",
//   "gasAmount": 57311,
//   "gasPrice": "100000000000",
//   "txId": "c9adbbc02f34d2b3f2db8790354bca9f3d6f7a1fde9b9269a393d6693663c084",
//   "contractAddress": "v8uU9yLfzUwqpJeS9Kd76DB75WJBcEuMdYgEQ2Gn8pLF",
//   "groupIndex": 0,
//   "contractId": "1581cc793fc7bafce6ef89eaf66404c9eec17561ef0e169ef2a4329c9f190c00",
//   "instance": {
//     "address": "v8uU9yLfzUwqpJeS9Kd76DB75WJBcEuMdYgEQ2Gn8pLF",
//     "contractId": "1581cc793fc7bafce6ef89eaf66404c9eec17561ef0e169ef2a4329c9f190c00",
//     "groupIndex": 0
//   }
// }

// Obtenez l'état du contrat
const tokenFaucet = deployResult.instance
const contractState = await tokenFaucet.fetchState()
console.log(JSON.stringify(contractState, null, 2))
// {
//   "address": "v8uU9yLfzUwqpJeS9Kd76DB75WJBcEuMdYgEQ2Gn8pLF",
//   "contractId": "1581cc793fc7bafce6ef89eaf66404c9eec17561ef0e169ef2a4329c9f190c00",
//   "bytecode": "050609121b4024402d404a010000000102ce0002010000000102ce0102010000000102ce0202010000000102ce0302010000000102a0000201020101001116000e320c7bb4b11600aba00016002ba10005b416005f",
//   "initialStateHash": "236a82352f5e34f813ecf274385912ed0ba67f1c305c24f7a6934c18d32213b1",
//   "codeHash": "641343b4f1c08b03969b127b452acc7535cad20231bc32af6c0b5f218dd8ff0c",
//   "fields": {
//     "symbol": "5446",
//     "name": "546f6b656e466175636574",
//     "decimals": "18",
//     "supply": "10",
//     "balance": "10"
//   },
//   "fieldsSig": {
//     "names": [
//       "symbol",
//       "name",
//       "decimals",
//       "supply",
//       "balance"
//     ],
//     "types": [
//       "ByteVec",
//       "ByteVec",
//       "U256",
//       "U256",
//       "U256"
//     ],
//     "isMutable": [
//       false,
//       false,
//       false,
//       false,
//       true
//     ]
//   },
//   "asset": {
//     "alphAmount": "1000000000000000000",
//     "tokens": [
//       {
//         "id": "1581cc793fc7bafce6ef89eaf66404c9eec17561ef0e169ef2a4329c9f190c00",
//         "amount": "10"
//       }
//     ]
//   }
// }

À partir de la sortie, nous pouvons voir que nous avons déployé avec succès le contrat, et il y a 10 jetons dans l'actif du contrat.

Appelez le contrat

Vous pouvez utiliser des scripts pour appeler des contrats sur la blockchain Alephium, le code du script sera exécuté lorsque la transaction est soumise au réseau Alephium, mais le code du script ne sera pas stocké dans l'état de la blockchain.

TypeScript
web3.setCurrentNodeProvider('http://localhost:22973')
const wallet = new PrivateKeyWallet('a642942e67258589cd2b1822c631506632db5a12aabcf413604e785300d762a5')
await Project.build()

// Adresse du contrat à partir du résultat du déploiement
const contractAddress = deployResult.instance.address

// ID de contrat à partir du résultat du déploiement
const contractId = deployResult.instance.contractId

// Créez une transaction d'appel de contrat, `initialFields` est requis si le script a des champs
// Le `Withdraw` est généré dans le guide de démarrage
const executeResult = await Withdraw.execute(wallet, {
  initialFields: {
    token: contractId,
    amount: 1n
  }
})
console.log(JSON.stringify(executeResult, null, 2))
// {
//   "signature": "16ec5eed788bfe7a803ec89f47d8ef7c1ac5f626ad88e4b46ecdde8be9fdef5719db49b09ef4e11a94901082e34c52629aafad4b9753483bf853ff695b19b9a4",
//   "fromGroup": 0,
//   "toGroup": 0,
//   "unsignedTx": "0004010101030000000513010d0c1440201581cc793fc7bafce6ef89eaf66404c9eec17561ef0e169ef2a4329c9f190c0001058000a447c1174876e8000137a44447f11525fe8e58af5a02b2f81bbbf35ea3c1bdf319ffe76794709c9e9d4e80c599000381818e63bd9e35a5489b52a430accefc608fd60aa2c7c0d1b393b5239aedf6b000",
//   "gasAmount": 42055,
//   "gasPrice": "100000000000",
//   "txId": "c29e9cb10b3e0b34979b9daac73151d98ee4de8f913e66aa0f0c8dc0cb99a617",
//   "groupIndex": 0
// }

// Obtenez le solde du compte
const balance = await wallet.nodeProvider.addresses.getAddressesAddressBalance(wallet.account.address)
console.log(JSON.stringify(balance, null, 2))
// {
//   "balance": "999998990063400000000000",
//   "balanceHint": "999998.9900634 ALPH",
//   "lockedBalance": "0",
//   "lockedBalanceHint": "0 ALPH",
//   "tokenBalances": [
//     {
//       "id": "1581cc793fc7bafce6ef89eaf66404c9eec17561ef0e169ef2a4329c9f190c00",
//       "amount": "1"
//     }
//   ],
//   "utxoNum": 2
// }

Interrogez les événements de contrat historiques

Les événements de contrat sont indexés par adresse de contrat avec des décalages, et vous pouvez interroger les événements historiques d'une adresse de contrat en spécifiant le décalage et la limite (facultatif).

TypeScript
const nodeProvider = new NodeProvider('http://localhost:22973')
// Adresse du contrat à partir du résultat du déploiement du contrat
const contractAddress = deployResult.instance.address

// Interrogez les événements de contrat à partir de l'index 0, et la `limit`  ne peut pas être supérieure à 100
const result = await nodeProvider.events.getEventsContractContractaddress(
  contractAddress, {start: 0, limit: 100}
)

// Dans la prochaine requête, vous pouvez commencer par `result.nextStart`
console.log(JSON.stringify(result, null, 2))
// {
//   "events": [
//     {
//       "blockHash": "0c07e672c40629a5c943cc7e4ec677140cbd50fdc9dcacb6a1d30bc38c0e7b50",
//       "txId": "c29e9cb10b3e0b34979b9daac73151d98ee4de8f913e66aa0f0c8dc0cb99a617",
//       "eventIndex": 0,
//       "fields": [
//         {
//           "type": "Address",
//           "value": "1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH"
//         },
//         {
//           "type": "U256",
//           "value": "1"
//         }
//       ]
//     }
//   ],
//   "nextStart": 1
// }

// Parfois, des événements peuvent être émis à partir de blocs non canoniques en raison de la réorganisation des blocs, vous pouvez vérifier si le bloc est dans la chaîne principale
await nodeProvider.blockflow.getBlockflowIsBlockInMainChain({blockHash: events[0].blockHash})
// true

// Obtenez le compteur d'événements de contrat actuel
await nodeProvider.events.getEventsContractContractaddressCurrentCount(contractAddress)
// 1

// Vous pouvez également obtenir des événements par identifiant de transaction si `alephium.node.event-log.index-by-tx-id` est activé dans votre fichier de configuration de nœud complet
await nodeProvider.events.getEventsTxIdTxid('c29e9cb10b3e0b34979b9daac73151d98ee4de8f913e66aa0f0c8dc0cb99a617')
// {
//   "events": [
//     {
//       "blockHash": "0c07e672c40629a5c943cc7e4ec677140cbd50fdc9dcacb6a1d30bc38c0e7b50",
//       "contractAddress": "v8uU9yLfzUwqpJeS9Kd76DB75WJBcEuMdYgEQ2Gn8pLF",
//       "eventIndex": 0,
//       "fields": [
//         {
//           "type": "Address",
//           "value": "1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH"
//         },
//         {
//           "type": "U256",
//           "value": "1"
//         }
//       ]
//     }
//   ]
// }

Écoute des événements

En plus d'interroger les événements un par un, vous pouvez également obtenir des événements par abonnement aux événements. Il interrogera périodiquement et obtiendra de nouveaux événements.

TypeScript
web3.setCurrentNodeProvider('http://localhost:22973')
// L'instance de contrat `TokenFaucet` à partir du résultat du déploiement
const tokenFaucet = deployResult.instance
// Le `TokenFaucetTypes.WithdrawEvent` est généré dans le guide de démarrage
const events: TokenFaucetTypes.WithdrawEvent[] = []
const subscribeOptions = {
  // Il vérifiera les nouveaux événements depuis le nœud complet toutes les `pollingInterval`
  pollingInterval: 500,
  // La fonction de rappel sera appelée pour chaque événement
  messageCallback: (event: TokenFaucetTypes.WithdrawEvent): Promise<void> => {
    events.push(event)
    return Promise.resolve()
  },
  // Cette fonction de rappel sera appelée en cas d'erreur
  errorCallback: (error: any, subscription): Promise<void> => {
    console.log(error)
    subscription.unsubscribe()
    return Promise.resolve()
  }
}

// Abonnez-vous aux événements du contrat à partir de l'index 0
const subscription = tokenFaucet.subscribeWithdrawEvent(subscribeOptions, 0)
await new Promise((resolve) => setTimeout(resolve, 1000))
console.log(JSON.stringify(events, null, 2))
// [
//   {
//     "contractAddress": "v8uU9yLfzUwqpJeS9Kd76DB75WJBcEuMdYgEQ2Gn8pLF",
//     "blockHash": "0c07e672c40629a5c943cc7e4ec677140cbd50fdc9dcacb6a1d30bc38c0e7b50",
//     "txId": "c29e9cb10b3e0b34979b9daac73151d98ee4de8f913e66aa0f0c8dc0cb99a617",
//     "eventIndex": 0,
//     "name": "Withdraw",
//     "fields": {
//       "to": "1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH",
//       "amount": "1"
//     }
//   }
// ]

// Se désabonner
subscription.unsubscribe()

Outils

Conversion entre l'ID de contrat et l'adresse de contrat

TypeScript
const contractId = 'bfc891f2f7fbb466bd7808f71cc022debb71fd3c1ceb752b623eb9c48ec4d165'
const contractAddress = addressFromContractId(contractId)
console.log(binToHex(contractIdFromAddress(contractAddress)) === contractId)
// true

Obtenez le groupe d'une adresse

TypeScript
const group = groupOfAddress('1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH')
console.log(group)
// 0

Obtenez l'identifiant du sous-contrat

TypeScript
const contractId = 'bfc891f2f7fbb466bd7808f71cc022debb71fd3c1ceb752b623eb9c48ec4d165'
console.log(subContractId(contractId, '00'))
// 303483cfe0eaead281879233f884e8b64c2ecf26e368ccd4b05b2b5bda87ec3d