Deze handleiding heeft betrekking op het maken, implementeren en communiceren van een ERC721 Smart Contract op StarkNet met OpenzePpelin, Starknet Foundry en Starknet.js. U leert hoe u het contract kunt compileren, verklaren en implementeren, en vervolgens NFT’s mint en ze efficiënt beheren op laag 2.
· Inhoudsopgave
· Inleiding tot Starknet en Cairo
· Starknet Foundry opgezet
∘ De Braavos -account importeren
∘ De profielen definiëren
∘ Installeer de Openzeppelin -bibliotheek
· Starknet -kraan
· Het bouwen van het contract
∘ het contract verklaart
· Implementeren van het contract
· Starknet.js
· GitHub -repository
· Conclusie
· Bronnen
Starknet is een Laag 2 (L2) schaaloplossing voor Ethereum dat gebruikt Stark (schaalbaar transparant argument van kennis) bewijst om meerdere transacties buiten de keten te batchen en een enkel bewijs in te dienen aan Ethereum. Dit verlaagt de gaskosten drastisch en verhoogt de doorvoer, met behoud van de veiligheid en decentralisatie van Ethereum.
In tegenstelling tot soliditeit, die wordt gebruikt voor Smart Contracten van Ethereum, is Cairo de native smartcontract-taal voor Starknet. Cairo maakt het mogelijk om een bewijsbaar programma te schrijven met Stark -geldigheidsbewijzen, zonder een diepe kennis van de complexe cryptografische concepten eronder.
Bekijk deze gids voor verdere verkenning op Starknet.
Om ons slimme contract samen te stellen en te implementeren, zullen we gebruiken Starknet Foundry In dit artikel. Voor installatie -instructies raad ik aan deze handleiding te bekijken.
Om een nieuw project te starten met de naam label_nft We zullen uitvoeren:
snforge new label_nft
De Braavos -account importeren
Voor dit project heb ik mijn Braavos -account geïmporteerd met behulp van sncast account importZowel voor Sepolia TestNet en Mainnet:
sncast \
account import \
--url https://starknet-sepolia.infura.io/v3/**** \
--name account_braavos \
--address 0xyjozq3nlq7f1fma6d2oz9rd8xa3np961igz7xpg3roxw37c8fl7nzfsz4avkeag4 \
--private-key 0x7a90aE1b2904F6F6787b0bcB6E4c8D08aF1e4fca5a33b96c8d8e7b8a6f78b60c \
--type braavos
Deze accounts worden opgeslagen in het bestand starknet_open_zeppelin_accounts.json at the path: ~/.StarkNet_Accounts/Starknet_open_zeppelin_accounts.json
Het bestand StarkNet_open_zeppelin_accounts.json moet hierop lijken:
{
"alpha-mainnet": {
"account_braavos_mainnet": {
"address": "0xyjozq3nlq7f1fma6d2oz9rd8xa3np961igz7xpg3roxw37c8fl7nzfsz4avkeag4",
"class_hash": "0xba780ad3e82a6756c2e892a252d527261177324fad6b0622f13aa7354433bef",
"deployed": true,
"legacy": false,
"private_key": "0x7a90aE1b2904F6F6787b0bcB6E4c8D08aF1e4fca5a33b96c8d8e7b8a6f78b60c",
"public_key": "0xyjozq3nlq7f1fma6d2oz9rd8xa3np961igz7xpg3roxw37c8fl7nzfsz4avkeag",
"type": "braavos"
}
},
"alpha-sepolia": {
"account_braavos": {
"address": "0xyjozq3nlq7f1fma6d2oz9rd8xa3np961igz7xpg3roxw37c8fl7nzfsz4avkeag4",
"class_hash": "0xba780ad3e82a6756c2e892a252d527261177324fad6b0622f13aa7354433bef",
"deployed": true,
"legacy": false,
"private_key": "0x7a90aE1b2904F6F6787b0bcB6E4c8D08aF1e4fca5a33b96c8d8e7b8a6f78b60c",
"public_key": "0xyjozq3nlq7f1fma6d2oz9rd8xa3np961igz7xpg3roxw37c8fl7nzfsz4avkeag",
"type": "braavos"
}
}
}
Hier vindt u de volledige gids.
Om de lijst van het account te controleren, kunnen we ook uitvoeren:
sncast account list
De profielen definiëren
In de snfoundry.toml Bestand, ik heb de accountconfiguratie en de RPC -URL toegevoegd voor het implementeren van het SMART -contract op zowel StarkNet TestNet Sepolia als Starknet MaNet:
[sncast.default]
url = "https://starknet-sepolia.infura.io/v3/*****"
accounts-file = "~/.starknet_accounts/starknet_open_zeppelin_accounts.json"
account = "account_braavos"[sncast.mainnet]
account = "account_braavos_mainnet"
accounts-file = "~/.starknet_accounts/starknet_open_zeppelin_accounts.json"
url = "https://starknet-mainnet.infura.io/v3/******"
Bekijk deze handleiding voor meer informatie over profielfefinitie.
Installeer de openzeppelin -bibliotheek
Om de Openzeppelin -bibliotheek in ons project te installeren, moeten we deze toevoegen aan het gedeelte afhankelijkheden van de Scarb.toml bestand:
openzeppelin = "0.20.0"
Dus onze afhankelijkheden moeten er zo uitzien:
[dependencies]
starknet = "2.9.2"
openzeppelin = "0.20.0"
Bekijk de Guide van Openzeppelin voor verdere verkenning.
Om aan te geven en uw tokens te implementeren of om uw slimme contract op Starknet Sepolia te testen, kunt u de Starknet -kraan gebruiken om testtokens te krijgen, zowel in ETH als in STRK op deze URL.
Om het ERC721 Smart Contract te maken, begon ik met de Openzeppelin -wizard, die toegankelijk is bij deze URL.
Ik koos ervoor om een mintable en op te lijken op te dienen slim contract te creëren met behulp van de wizard. Ik heb de ondersteuning van de eigenaar verwijderd om dingen te vereenvoudigen, maar een kaart toegevoegd met de naam token_uris om de token -URI op te slaan voor elke geslagen NFT. Deze praktijk wordt vaak gebruikt om NFT -metadata op te slaan op externe opslag, zoals IPF’s.
Ik heb ook een nieuwe getter en setter toegevoegd voor de token_uris variabel, evenals een mint_item functie om NFT’s te mint en te gebruiken token_uris om de NFT -metadata op te slaan.
Dit is het resulterende slimme contract:
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts for Cairo ^0.20.0use starknet::ContractAddress;
#[starknet::interface]
pub trait ILabels {
fn get_token_uri(self: @TContractState, token_id: u256) -> ByteArray;
fn set_token_uri(ref self: TContractState, token_id: u256, uri: ByteArray);
fn mint_item(ref self: TContractState, recipient: ContractAddress, uri: ByteArray);
}
#[starknet::contract]
mod Labels {
use openzeppelin::introspection::src5::SRC5Component;
use openzeppelin::token::erc721::ERC721Component;
use starknet::ContractAddress;
use openzeppelin::token::erc721::extensions::ERC721EnumerableComponent;
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
use starknet::storage::{Map, StorageMapReadAccess, StorageMapWriteAccess};
component!(path: ERC721Component, storage: erc721, event: ERC721Event);
component!(path: SRC5Component, storage: src5, event: SRC5Event);
component!(path: ERC721EnumerableComponent, storage: erc721_enumerable, event: ERC721EnumerableEvent);
// External
#[abi(embed_v0)]
impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl;
#[abi(embed_v0)]
impl ERC721EnumerableImpl = ERC721EnumerableComponent::ERC721EnumerableImpl;
// Internal
impl ERC721InternalImpl = ERC721Component::InternalImpl;
impl ERC721EnumerableInternalImpl = ERC721EnumerableComponent::InternalImpl;
#[storage]
struct Storage {
#[substorage(v0)]
erc721: ERC721Component::Storage,
#[substorage(v0)]
src5: SRC5Component::Storage,
#[substorage(v0)]
erc721_enumerable: ERC721EnumerableComponent::Storage,
pub counter: u256,
pub token_uris: Map,
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
ERC721Event: ERC721Component::Event,
#[flat]
SRC5Event: SRC5Component::Event,
#[flat]
ERC721EnumerableEvent: ERC721EnumerableComponent::Event,
}
#[constructor]
fn constructor(ref self: ContractState) {
self.erc721.initializer("Labels", "LBL", "");
self.erc721_enumerable.initializer();
}
impl ERC721HooksImpl of ERC721Component::ERC721HooksTrait {
fn before_update(
ref self: ERC721Component::ComponentState,
to: ContractAddress,
token_id: u256,
auth: ContractAddress,
) {
let mut contract_state = self.get_contract_mut();
contract_state.erc721_enumerable.before_update(to, token_id);
}
}
#[generate_trait]
#[abi(per_item)]
impl ExternalImpl of ExternalTrait {
#[external(v0)]
fn safe_mint(
ref self: ContractState,
recipient: ContractAddress,
token_id: u256,
data: Span,
) {
self.erc721.safe_mint(recipient, token_id, data);
}
#[external(v0)]
fn safeMint(
ref self: ContractState,
recipient: ContractAddress,
tokenId: u256,
data: Span,
) {
self.safe_mint(recipient, tokenId, data);
}
}
#[abi(embed_v0)]
impl ImplLabels of super::ILabels {
fn get_token_uri(self: @ContractState, token_id: u256) -> ByteArray {
assert(self.erc721.exists(token_id), ERC721Component::Errors::INVALID_TOKEN_ID);
return self.token_uris.read(token_id);
}
fn set_token_uri(ref self: ContractState, token_id: u256, uri: ByteArray) {
assert(self.erc721.exists(token_id), ERC721Component::Errors::INVALID_TOKEN_ID);
self.token_uris.write(token_id, uri);
}
fn mint_item(ref self: ContractState, recipient: ContractAddress, uri: ByteArray) {
let current_counter = self.counter.read();
let new_counter = current_counter + 1;
self.counter.write(new_counter);
self.erc721.mint(recipient, new_counter);
self.set_token_uri(new_counter, uri);
}
}
}
In een Cairo -project is het hoofdbestand lib.cairogelegen in de src map. Daarom moet u uw slimme contract in de lib.cairo bestand.
Het contract verklaren
Starknet onderscheidt zich tussen een contractklasse en een contractinstantie, vergelijkbaar met het onderscheid in objectgeoriënteerde programmering tussen het definiëren van een klasse (MyClass {}) en er een exemplaar van maken (let myInstance = MyClass()).
Het verklaren van een contract is een vereiste stap om het beschikbaar te maken op het netwerk. Eenmaal aangegeven, kan het contract worden ingezet en met interactie.
Om ons slimme contract op het Sepolia -testnet te verklaren, kunnen we het standaardprofiel gebruiken, dat wijst op Sepolia, zoals eerder gezien. Bovendien, als we het slimme contract verklaren met behulp van V2, worden de kosten betaald in ETH. Om de nieuwste versie en betaalkosten in STRK echter te gebruiken, moeten we echter gebruiken v3. Bekijk deze gids voor meer informatie.
Dus als we rennen:
sncast declare -v v3 -c Labels
We betalen de gaskosten in STRK en verklaren de Sepolia testnet omdat we het standaardprofiel gebruiken. In feite omvat dit opdracht impliciet --profile default. Labels is de naam van ons slimme contract.
In plaats daarvan moeten we, als we de gass -vergoedingen in STRK willen betalen, uitvoeren:
sncast declare -v v2 -c Labels
Als we ons slimme contract op het StarkNet -mainnet willen verklaren, moeten we uitvoeren:
sncast --profile mainnet declare -v v2 -c Labels
Zoals we eerder in dit geval hebben gezegd, betalen we de kosten in ETH, maar als we in Strk willen betalen, moeten we V3 gebruiken.
Als we het profiel niet willen gebruiken, kunnen we het contract ook op deze manier verklaren:
sncast --account account_braavos \
declare \
--url https://starknet-sepolia.infura.io/v3/******* \
--contract-name Labels
Bekijk deze gids voor meer informatie over het verklaren van contracten.
Zodra u de opdracht uitvoert, wordt het slimme contract gecompileerd en wordt het een klasse -hash uitgevoerd. In dit geval is het: 0x000DA2972B416E39CE7CC2A59EdF9FF6F406661888888ADD93A1011BA3E59C73C
Starknet Foundry’s sncast tool stelt Smart Contract -implementatie in een opgegeven netwerk mogelijk met behulp van de sncast deploy commando.
Het werkt door een Universal Deployer Contract aan te roepen, dat het contract implementeert met behulp van de verstrekte klasse Hash en Constructor -argumenten.
Na het verklaren van ons slimme contract kunnen we het op Sepolia implementeren met:
sncast deploy -v v3 --class-hash 0x000da2972b416e39ce7cc2a59edf9ff6f40666188ea2add93a1011ba3e59c73c
In dit geval gebruiken we V3 om de kosten in STRK te betalen. Als we de kosten in ETH willen betalen, moeten we V1 gebruiken.
Als we het SMART -contract op StarkNet MaNet willen implementeren, kunnen we uitvoeren:
sncast --profile mainnet deploy -v v1 --class-hash 0x000da2972b416e39ce7cc2a59edf9ff6f40666188ea2add93a1011ba3e59c73c
Als u het profiel niet wilt gebruiken, kunt u het contract implementeren met behulp van:
sncast \
--account my_account \
deploy \
--url http://127.0.0.1:5055/rpc \
--class-hash 0x000da2972b416e39ce7cc2a59edf9ff6f40666188ea2add93a1011ba3e59c73c
De output zou zoiets moeten zijn:
command: deploy
contract_address: 0x042de0e88b8d70b02ff6303fdb69cec5718154db91b8c53a58de047dfcbc41c0
transaction_hash: 0x009282ecfa8611a6b2d5a213c9da1a861a2760b96c0a72f1b94b85cb7ed36d97To see deployment details, visit:
contract: https://sepolia.starkscan.co/contract/0x042de0e88b8d70b02ff6303fdb69cec5718154db91b8c53a58de047dfcbc41c0
transaction: https://sepolia.starkscan.co/tx/0x009282ecfa8611a6b2d5a213c9da1a861a2760b96c0a72f1b94b85cb7ed36d97
Als u meer wilt weten over het implementeren van slimme contracten met Starknet Foundry, bekijk dan deze gids.
Om ons slimme contract te testen, kunnen we StarkNet.js gebruiken, een bibliotheek die is ontworpen om te communiceren met StarkNet en bewerkingen uit te voeren met JavaScript.
Laten we drie omgevingsvariabelen maken in onze .envbestand:
STARKNET_ADDRESS=your_starnet_address
STARKNET_PRIVATE_KEY=your_account_private_key
STARKNET_RPC_URL=your_rpc_url
Laten we beginnen met het maken van een RPCProvider -object:
const provider = new RpcProvider({
nodeUrl: process.env.STARKNET_RPC_URL,
});
Laten we nu verbinding maken met ons account:
const account = new Account(
provider,
process.env.STARKNET_ADDRESS,
process.env.STARKNET_PRIVATE_KEY
);
Nu, om verbinding te maken met ons slimme contract, hebben we de ABI nodig. We kunnen het extraheren uit onze Starknet Foundry -repository, waar onze code is samengesteld, specifiek uit het bestand label_nft_Labels.contract_class.json gelegen in de target/release map.
Om het ABI -veld uit dit bestand te halen, gebruikte ik:
jq -r '.abi' ./target/release/label_nft_Labels.contract_class.json > labels.json
Dit haalt het ABI -veld uit en slaat het op in een nieuwe bestandslabels.json.
Om verbinding te maken met ons slimme contract met Starknet.js moeten we eerst de ABI importeren:
import labelsAbi from "../labels.json" with { type: 'json' };
Dan kunnen we schrijven:
const erc721 = new Contract(labelsAbi, DEPLOYED_CONTRACT, provider);
erc721.connect(account);
We moeten de name functie van het slimme contract:
await contract.name(); // Labels
Om een nieuwe NFT te minteren, kunnen we de mint_itemfunctie die we hebben gemaakt in het slimme contract:
const mintNft = async (
contract: Contract,
account: Account,
provider: RpcProvider,
recipient: string,
uri: string
) => {
// Transaction with fees paid in ETH
const mintCall = contract.populate("mint_item", { recipient, uri });
const { transaction_hash: transferTxHash } = await account.execute(mintCall, {
version: 3, // version 3 to pay fees in STRK. To pay fees in ETH remove the version field
});
console.log(`Minting NFT with transaction hash: ${transferTxHash}`);
// Wait for the invoke transaction to be accepted on Starknet
const receipt = await provider.waitForTransaction(transferTxHash);
console.log(receipt);
};await mintNft(
erc721,
account,
provider,
account.address,
"NFT1"
);
Ook in dit geval als we plaatsen version: 3 We betalen de kosten in STRK, als we dit veld verwijderen, betalen we in ETH.
Dus als ik dit eenmaal heb uitgevoerd, zal het mijn eerste NFT mint account.addressdat is:0x032e21f8277033fd4ddbb2127f5ebe74c7cdb09e36e72bd0071ad9bf6039b7bdmet de token uri NFT1En het heeft de transactiedash geretourneerd: 0x6444953ee970574896ccc025b33636aceeb314247dadc4913c3d10f1d9db401.
Het is mogelijk om de transactie op de Stark -scan te controleren.
We kunnen de total_supplymethode van het slimme contract om te controleren hoeveel NFT’s zijn geslagen:
const totalSupply = await contract.total_supply(); // 1
We kunnen ook de token -URI van de eerste NFT controleren:
const uri = await contract.get_token_uri(1); // NFT1
Eindelijk konden we ook de balance_of methode door het aan te roepen account.address:
await contract.balance_of(account.address);
De hele code is beschikbaar op deze github -repository.
In dit artikel liepen we door het proces van het maken van een ERC721 Smart Contract met behulp van OpenzePpelin, compileren en implementeren met StarkNet Foundry en ermee omgaan met StarkNet.js. Met de schaalbaarheid van Starknet en goedkope transacties biedt deze aanpak een efficiënte manier om NFT’s op laag 2 te bouwen en te beheren.
