पिछले लेख में, हमने Starknet के declare-deploy मॉडल को कवर किया था, जिसमें regular और account contracts के लिए डिप्लॉयमेंट पाथ शामिल थे। यह लेख उन कॉन्सेप्ट्स को व्यवहार में लाता है। ERC-20 अध्याय से ERC-20 contract को हमारे डिप्लॉयमेंट उदाहरण के रूप में उपयोग करते हुए, हम Starknet Foundry (sncast) और Starknet.js दोनों के साथ संपूर्ण डिप्लॉयमेंट वर्कफ़्लो से गुजरेंगे: transactions को साइन करने के लिए एक अकाउंट सेट करना, ERC-20 contract class को डिक्लेयर (declare) करना, एक ERC-20 contract इंस्टेंस को डिप्लॉय करना, और डिप्लॉय किए गए contract के साथ इंटरैक्ट करना।
Contract सेटअप
scarb new erc20 के साथ एक नया Scarb प्रोजेक्ट बनाएं, cd erc20 के साथ प्रोजेक्ट डायरेक्टरी में जाएं, फिर src/lib.cairo के कंटेंट को निम्नलिखित कोड से बदलें:
use starknet::ContractAddress;
#[starknet::interface]
pub trait IERC20<TContractState> {
fn total_supply(self: @TContractState) -> u256;
fn balance_of(self: @TContractState, account: ContractAddress) -> u256;
fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256;
fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool;
fn transfer_from(
ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256,
) -> bool;
fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool;
fn name(self: @TContractState) -> ByteArray;
fn symbol(self: @TContractState) -> ByteArray;
fn decimals(self: @TContractState) -> u8;
fn mint(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; // For testing purposes
}
#[starknet::contract]
pub mod ERC20 {
use starknet::{ContractAddress, get_caller_address};
use starknet::storage::{
Map, StoragePointerWriteAccess, StoragePointerReadAccess, StoragePathEntry,
};
#[storage]
pub struct Storage {
balances: Map<ContractAddress, u256>,
allowances: Map<
(ContractAddress, ContractAddress), u256,
>, // (owner, spender) -> amount
token_name: ByteArray,
symbol: ByteArray,
decimal: u8,
total_supply: u256,
owner: ContractAddress,
}
#[event]
#[derive(Drop, starknet::Event)]
pub enum Event {
Transfer: Transfer,
Approval: Approval,
}
#[derive(Drop, starknet::Event)]
pub struct Transfer {
#[key]
from: ContractAddress,
#[key]
to: ContractAddress,
amount: u256,
}
#[derive(Drop, starknet::Event)]
pub struct Approval {
#[key]
owner: ContractAddress,
#[key]
spender: ContractAddress,
value: u256,
}
#[constructor]
fn constructor(ref self: ContractState, owner: ContractAddress) {
self.token_name.write("Rare Token");
self.symbol.write("RST");
self.decimal.write(18);
self.owner.write(owner);
}
#[abi(embed_v0)]
impl ERC20Impl of super::IERC20<ContractState> {
fn total_supply(self: @ContractState) -> u256 {
self.total_supply.read()
}
fn balance_of(self: @ContractState, account: ContractAddress) -> u256 {
let balance = self.balances.entry(account).read();
balance
}
fn name(self: @ContractState) -> ByteArray {
self.token_name.read()
}
fn symbol(self: @ContractState) -> ByteArray {
self.symbol.read()
}
fn decimals(self: @ContractState) -> u8 {
self.decimal.read()
}
fn allowance(
self: @ContractState, owner: ContractAddress, spender: ContractAddress,
) -> u256 {
let allowance = self.allowances.entry((owner, spender)).read();
allowance
}
fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool {
let sender = get_caller_address();
let sender_prev_balance = self.balances.entry(sender).read();
let recipient_prev_balance = self.balances.entry(recipient).read();
assert(sender_prev_balance >= amount, 'Insufficient amount');
self.balances.entry(sender).write(sender_prev_balance - amount);
self.balances.entry(recipient).write(recipient_prev_balance + amount);
assert(
self.balances.entry(recipient).read() > recipient_prev_balance,
'Transaction failed',
);
self.emit(Transfer { from: sender, to: recipient, amount });
true
}
fn transfer_from(
ref self: ContractState,
sender: ContractAddress,
recipient: ContractAddress,
amount: u256,
) -> bool {
let spender = get_caller_address();
let spender_allowance = self.allowances.entry((sender, spender)).read();
let sender_balance = self.balances.entry(sender).read();
let recipient_balance = self.balances.entry(recipient).read();
assert(amount <= spender_allowance, 'amount exceeds allowance');
assert(amount <= sender_balance, 'amount exceeds balance');
self.allowances.entry((sender, spender)).write(spender_allowance - amount);
self.balances.entry(sender).write(sender_balance - amount);
self.balances.entry(recipient).write(recipient_balance + amount);
self.emit(Transfer { from: sender, to: recipient, amount });
true
}
fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool {
let caller = get_caller_address();
self.allowances.entry((caller, spender)).write(amount);
self.emit(Approval { owner: caller, spender, value: amount });
true
}
fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool {
let caller = get_caller_address();
assert(caller == self.owner.read(), 'Call not owner');
let previous_total_supply = self.total_supply.read();
let previous_balance = self.balances.entry(recipient).read();
self.total_supply.write(previous_total_supply + amount);
self.balances.entry(recipient).write(previous_balance + amount);
let zero_address: ContractAddress = 0.try_into().unwrap();
self.emit(Transfer { from: zero_address, to: recipient, amount });
true
}
}
}
हम इस contract को दो तरीकों का उपयोग करके डिप्लॉय करेंगे: Starknet Foundry (sncast) और Starknet.js। दोनों तरीके समान डिप्लॉयमेंट चरणों का पालन करते हैं:
- Transactions को साइन करने और gas fees का भुगतान करने के लिए एक अकाउंट सेट करें
- ऑन-चेन कोड को रजिस्टर करने और class hash प्राप्त करने के लिए एक ERC-20 contract class डिक्लेयर करें
- उस class hash से एक लाइव इंस्टेंस बनाने के लिए ERC-20 contract इंस्टेंस डिप्लॉय करें
Starknet Foundry (sncast) के साथ डिप्लॉय करना
अकाउंट सेटअप
Transactions को साइन करने और डिप्लॉयमेंट के लिए भुगतान करने के लिए हमें एक account contract की आवश्यकता होती है। यदि आपके पास पहले से ही एक sncast अकाउंट कॉन्फ़िगर किया हुआ है, तो आप डिक्लेरेशन (declaration) चरण पर जा सकते हैं। अन्यथा, आइए एक नया बनाएं।
sncast का उपयोग करके अकाउंट बनाने की कमांड यह है:
sncast account create --network <NETWORK_NAME> --name <ACCOUNT_NAME>
जहाँ:
<NETWORK_NAME>वह नेटवर्क है जिस पर आप डिप्लॉय करना चाहते हैं (उदाहरण के लिए,sepolia,mainnet)<ACCOUNT_NAME>एक मनमाना स्थानीय आइडेंटिफ़ायर (identifier) है जिसे आप इस अकाउंट को संदर्भित करने के लिए चुनते हैं (उदाहरण के लिए,my_account,deployer_account)
उदाहरण के लिए, Sepolia पर एक अकाउंट बनाने के लिए:
sncast account create --network sepolia --name new_account_1
जब आप उपरोक्त कमांड रन करते हैं, तो sncast:
- स्थानीय रूप से एक private/public key पेयर जेनरेट करता है
- public key और salt से नियतात्मक (deterministically) रूप से अकाउंट एड्रेस की गणना करता है
- एक स्थानीय JSON फ़ाइल (
~/.starknet_accounts/starknet_open_zeppelin_accounts.json) में अकाउंट विवरण सेव करता है। इस फ़ाइल का निरीक्षण इसके साथ करें:
cat ~/.starknet_accounts/starknet_open_zeppelin_accounts.json
- यह एड्रेस नेटवर्क (हमारे मामले में, sepolia) को ज्ञात हो जाता है लेकिन अभी तक कोई account contract डिप्लॉय नहीं किया गया है।
नोट: हम यहाँ
account createका उपयोग करते हैं (declareके बजाय) क्योंकि account contract classes आमतौर पर उनके प्रोवाइडर्स द्वारा पहले से डिक्लेयर की गई होती हैं। इस मामले में, OpenZeppelin ने उस account contract class को पहले ही डिक्लेयर कर दिया है जिसका हम उपयोग कर रहे हैं, इसलिए हम केवल इसका एक इंस्टेंस बना रहे हैं, नया डिक्लेयर नहीं कर रहे हैं।
create कमांड रन करने के बाद, आपका आउटपुट इस प्रकार दिखना चाहिए:

यदि आप इस एड्रेस को Voyager पर सर्च करते हैं, तो आप देखेंगे कि अकाउंट एड्रेस मौजूद है लेकिन इसका स्टेटस “Uninitialized” है। यह दर्शाता है कि account contract अभी तक डिप्लॉय नहीं हुआ है।

नए बनाए गए अकाउंट को डिप्लॉय करने के लिए, आउटपुट से एड्रेस कॉपी करें और इसे Starknet faucet से STRK टोकन के साथ फंड करें। यह वह counterfactual deployment प्रक्रिया है जिस पर हमने पिछले लेख में चर्चा की थी: अकाउंट अपने प्री-फंडेड एड्रेस से अपने स्वयं के डिप्लॉयमेंट के लिए भुगतान करता है, जिसके परिणामस्वरूप एक नया अकाउंट बनता है जो स्वतंत्र रूप से मौजूद होता है और किसी अन्य अकाउंट से लिंक नहीं होता है।
एक बार जब आपको टेस्ट टोकन मिल जाएं, तो इसका उपयोग करके अकाउंट को डिप्लॉय करें:
sncast account deploy --network sepolia --name <ACCOUNT_NAME>
<ACCOUNT_NAME> को अकाउंट बनाने के दौरान उपयोग किए गए उसी नाम से बदलें। इस उदाहरण के लिए, हम new_account_1 का उपयोग करेंगे क्योंकि हमने account create स्टेप में इसी का उपयोग किया था:
sncast account deploy --network sepolia --name new_account_1
उपरोक्त कमांड रन करने के बाद, sncast आपको इस अकाउंट को स्थानीय (local) या वैश्विक (global) डिफ़ॉल्ट बनाने के लिए संकेत (prompt) देगा। एक local डिफ़ॉल्ट केवल वर्तमान प्रोजेक्ट पर लागू होता है, जबकि एक global डिफ़ॉल्ट सभी Scarb प्रोजेक्ट्स में लागू होता है। इस ट्यूटोरियल के लिए, local चुनें। एक बार जब आप पुष्टि (confirm) कर देते हैं, तो अकाउंट डिप्लॉय हो जाता है:

यदि आप Voyager पर transaction चेक करते हैं, तो आप देखेंगे कि transaction का प्रकार DEPLOY_ACCOUNT है। यह प्रोटोकॉल-लेवल transaction प्रकार है जो पहली बार अकाउंट बनाने के लिए समर्पित है, जहाँ डिप्लॉयमेंट को प्रायोजित (sponsor) करने के लिए किसी मौजूदा अकाउंट की आवश्यकता नहीं होती है।

इस transaction के दौरान, sequencer डिप्लॉयमेंट को वैलिडेट करता है, आपकी public key के साथ account constructor को रन करता है, और प्री-फंडेड एड्रेस से डिप्लॉयमेंट फ़ीस चार्ज करता है। आपका account contract अब Starknet पर लाइव है।
ERC-20 Contract Class डिक्लेयर करना
Account contracts के विपरीत जिन्हें उनके प्रोवाइडर्स द्वारा पहले से डिक्लेयर किया गया होता है, हमारे ERC-20 जैसे regular contracts को डिप्लॉयमेंट से पहले हमारे द्वारा डिक्लेयर किया जाना चाहिए।
एक regular contract को डिक्लेयर करने का सिंटैक्स यह है:
sncast --account <ACCOUNT_NAME> \
declare \
--url <URL> \
--contract-name <CONTRACT_NAME>
जहाँ:
<ACCOUNT_NAME>: उस अकाउंट का नाम जो आपने पहले बनाया और डिप्लॉय किया था<URL>: उस नेटवर्क के लिए RPC एंडपॉइंट URL जहाँ आप डिप्लॉय कर रहे हैं<CONTRACT_NAME>: contract मॉड्यूल का नाम, उदाहरण के लिएmod ERC20 {}सेERC20
Sepolia पर हमारे ERC-20 contract को डिक्लेयर करने के लिए, निम्नलिखित कमांड रन करें, जिसमें {apiKey} को अपनी Alchemy API key से और new_account_1 को अपने अकाउंट नाम से बदलें:
sncast --account new_account_1 \
declare \
--url https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_10/{apiKey} \
--contract-name ERC20
याद रखें कि डिक्लेरेशन आपके contract कोड को ऑन-चेन रजिस्टर करता है और एक class hash लौटाता है। इस प्रक्रिया के दौरान यह होता है:
- कम्पाइलेशन (Compilation):
sncastआपकी Sierra फ़ाइल को पढ़ता है, इसे स्थानीय रूप से CASM (Cairo Assembly) में कम्पाइल करता है, और उस CASM से compiled class hash की गणना करता है - Transaction सबमिशन (submission):
sncastSierra class और स्थानीय रूप से गणना किए गए compiled class hash दोनों को आपके अकाउंट द्वारा साइन करके sequencer को भेजता है - Sequencer वेरिफिकेशन (verification): sequencer Sierra को CASM में कम्पाइल करता है और वेरिफाई करता है कि इसका compiled class hash आपके द्वारा प्रदान किए गए hash से मेल खाता है या नहीं
- नेटवर्क स्टोरेज (Network storage): यदि hashes मेल खाते हैं, तो Sierra class और compiled class hash दोनों Starknet पर स्टोर हो जाते हैं
- Class hash वापस मिलना: sequencer class hash लौटाता है (Sierra से गणना की गई), जो इस contract class की पहचान करता है।
आउटपुट transaction hash और class hash दोनों को दिखाता है। class hash को कॉपी करें क्योंकि contract इंस्टेंस को डिप्लॉय करने के लिए हमें इसकी आवश्यकता होगी:

sncast आउटपुट में उपयोग के लिए तैयार (ready-to-use) डिप्लॉयमेंट कमांड प्रदान करता है। आप उस जेनरेट की गई कमांड को सीधे कॉपी कर सकते हैं, या बेहतर पठनीयता (readability) के लिए नीचे दिए गए फॉर्मेट का उपयोग कर सकते हैं। दोनों तरीके समान रूप से काम करते हैं।
UDC के माध्यम से Contract Instances डिप्लॉय करना
हमारी contract class के डिक्लेयर होने के साथ, अब हम उस class hash से कितने भी इंस्टेंस डिप्लॉय कर सकते हैं। sncast deploy कमांड UDC इंटरेक्शन को छुपा देती है (abstracts away), और बैकग्राउंड में डिप्लॉयमेंट को संभालती है।
Contract डिप्लॉय करने का सिंटैक्स यह है:
sncast \
--account <ACCOUNT_NAME> \
deploy \
--class-hash <CLASS_HASH> \
--arguments "<CONSTRUCTOR_ARGS>" \
--url <URL>
डिफ़ॉल्ट रूप से, sncast एक अद्वितीय (unique) contract एड्रेस सुनिश्चित करने के लिए एक रैंडम salt जेनरेट करता है। यदि आपको परिणामी (resulting) contract एड्रेस की भविष्यवाणी करनी है या उसे पुनः उत्पन्न (reproduce) करना है, तो आप एक विशिष्ट वैल्यू के साथ --salt पास कर सकते हैं, उदाहरण के लिए --salt 0x146। आप --unique भी पास कर सकते हैं, जो आपके deployer एड्रेस का उपयोग करके salt को संशोधित करता है ताकि यह सुनिश्चित हो सके कि एड्रेस आपके अकाउंट के लिए अद्वितीय (unique) है। इस ट्यूटोरियल के लिए, हम दोनों फ़्लैग्स को छोड़ देते हैं और sncast को स्वचालित रूप से salt जेनरेट करने देते हैं।
हमारे ERC-20 contract का इंस्टेंस डिप्लॉय करने के लिए, निम्नलिखित कमांड रन करें। <OWNER_ADDRESS> को अपने वास्तविक अकाउंट एड्रेस से और {apiKey} को अपनी Alchemy API key से बदलें। आप अपनी स्वयं की डिक्लेरेशन से मिले class hash का या नीचे दिए गए class hash का उपयोग कर सकते हैं:
sncast \
--account new_account_1 \
deploy \
--class-hash 0x23a5a4819dcac3a6b5fe724596647d3fc1f176ca565a0fb908c4457f1cc875b \
--arguments "<OWNER_ADDRESS>" \
--url https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_10/{apiKey}
Owner एड्रेस को सिंगल या डबल कोट्स में रखा जा सकता है। इस एड्रेस को contract owner के रूप में सेट किया जाएगा, जिसका अर्थ है कि केवल यह अकाउंट ही टोकन मिंट (mint) करने के लिए अधिकृत (authorized) होगा, इसलिए सुनिश्चित करें कि यह वही एड्रेस है जिसका उपयोग आप contract के साथ इंटरैक्ट करने के लिए करना चाहते हैं।
आपको अपने टर्मिनल में contract एड्रेस के साथ एक transaction receipt प्राप्त होगी:

आप Voyager पर डिप्लॉयमेंट को वेरिफाई कर सकते हैं। ध्यान दें कि transaction प्रकार INVOKE है और ऑपरेशन deployContract है। यह पुष्टि करता है कि sncast ने UDC के माध्यम से contract को डिप्लॉय किया है:

Transaction विवरण खोलने के लिए transaction hash पर क्लिक करें। यह UDC के deployContract फ़ंक्शन में पास किए गए सटीक पैरामीटर्स (parameters) को दिखाता है:

जब आपने contract डिप्लॉयमेंट कमांड रन की, तो sncast ने UDC इंटरेक्शन को स्वचालित रूप से संभाला:
- चूँकि कोई
--saltप्रदान नहीं किया गया था,sncastएक अद्वितीय (unique) contract एड्रेस सुनिश्चित करने के लिए एक रैंडम salt (इनपुट डेटा टेबल की दूसरी पंक्ति में दिखाए गए अनुसार0xd815d280876b878e) जेनरेट करता है - आपका अकाउंट UDC को इसके साथ एक
INVOKEtransaction भेजता है:classHash:0x23a5a4819dcac3a6b5fe724596647d3fc1f176ca565a0fb908c4457f1cc875b(इनपुट डेटा टेबल में पहली पंक्ति)salt:0xd815d280876b878e(इनपुट डेटा टेबल में दूसरी पंक्ति)notFromZero:0x0(false); चूँकि--uniqueपास नहीं किया गया था, contract एड्रेस शून्य (zero) एड्रेस के बजाय deployer के एड्रेस से लिया (derived) जाता हैcalldata:[0x14154fb6dd088b5ceb46df635ecce6e1a9b0455357931ac7df4263a7dbf39a9](इनपुट डेटा टेबल में चौथी पंक्ति - आपके constructor आर्गुमेंट्स, owner का एड्रेस)
- UDC आपका contract इंस्टेंस बनाने के लिए
deployContractको कॉल करता है, जो contract एड्रेस0x02ceed65a4bd731034c01113685c831b01c15d7d432f71afb1cf1634b53a2125लौटाता है (पीले रंग में हाइलाइट किए गए आउटपुट डेटा फ़ील्ड में)
Starknet.js के साथ डिप्लॉय करना
वातावरण (Environment) सेटअप करना
चूँकि ERC-20 contract class को पहले ही sncast दृष्टिकोण का उपयोग करके ऑन-चेन डिक्लेयर किया जा चुका है, इसलिए समान कम्पाइलर वर्ज़न वाले उसी प्रोजेक्ट से उसी कोड को फिर से डिक्लेयर करने का प्रयास करने पर “contract has already been declared” त्रुटि (error) मिलेगी, क्योंकि एक समान class hash ऑन-चेन पहले से मौजूद है। इस त्रुटि से बचने के लिए, हम एक नया Scarb प्रोजेक्ट बनाएंगे:
scarb new erc20_starknetjs
cd erc20_starknetjs
Contract सेटअप अनुभाग से उसी ERC-20 contract कोड को src/lib.cairo में कॉपी करें, फिर scarb build का उपयोग करके प्रोजेक्ट को कम्पाइल करें। यह आपको Starknet.js का उपयोग करके डिक्लेरेशन और डिप्लॉयमेंट के लिए एक साफ़ स्लेट (clean slate) देता है।
अब, डिप्लॉयमेंट कोड के लिए एक scripts फ़ोल्डर बनाएँ:
mkdir scripts
डिप्लॉयमेंट स्क्रिप्ट के लिए आवश्यक पैकेजेज़ इंस्टॉल करें:
npm install starknet dotenv
npm install -D typescript @types/node tsx
पैकेज के उद्देश्य:
- starknet Starknet contracts के साथ इंटरैक्ट करने के लिए आधिकारिक लाइब्रेरी है
- dotenv
.envफ़ाइल से environment variables लोड करता है - typescript & @types/node हमारी स्क्रिप्ट के लिए typescript सपोर्ट है
- tsx आधुनिक typescript रनर है जो ES मॉड्यूल्स को संभालता है
अपने package.json में "type": "module" फ़ील्ड जोड़कर CommonJS (require/module.exports) के बजाय ES मॉड्यूल सिंटैक्स (import/export) का उपयोग करने के लिए अपने प्रोजेक्ट को कॉन्फ़िगर करें:
{
"dependencies": {
"dotenv": "^17.2.3",
"starknet": "^9.2.1"
},
"type": "module",
"devDependencies": {
"@types/node": "^25.0.3",
"tsx": "^4.21.0",
"typescript": "^5.9.3"
}
}
यह हमें पुराने require() सिंटैक्स के बजाय import स्टेटमेंट्स का उपयोग करने की अनुमति देता है।
अपने प्रोजेक्ट रूट में एक .env फ़ाइल बनाएँ:
PRIVATE_KEY=your_private_key_here
ALCHEMY_API_KEY=your_alchemy_api_key_here
OWNER_ADDRESS=your_owner_address_here
चूँकि हम Voyager के माध्यम से डिप्लॉय किए गए contract पर write फ़ंक्शंस को कॉल करेंगे, इसलिए आपको Ready या Braavos जैसे वॉलेट को कनेक्ट करना होगा। आपके पास दो विकल्प हैं:
विकल्प 1: हमारे द्वारा बनाए गए अकाउंट को इम्पोर्ट करें (अनुशंसित)
~/.starknet_accounts/starknet_open_zeppelin_accounts.json से private key का उपयोग करके हमारे द्वारा पहले बनाए गए अकाउंट को Ready वॉलेट में इम्पोर्ट करें। अपने अकाउंट को इम्पोर्ट करने के लिए इस गाइड का पालन करें:
एक बार इम्पोर्ट हो जाने के बाद, अपनी .env फ़ाइल में निम्नलिखित सेट करें:
OWNER_ADDRESS: इम्पोर्ट किए गए अकाउंट का एड्रेस (वही जो JSON फ़ाइल में है)PRIVATE_KEY: JSON फ़ाइल से प्राप्त private key
विकल्प 2: अपने मौजूदा अकाउंट का उपयोग करें
यदि आपके पास पहले से ही Ready या Braavos Sepolia अकाउंट है, तो आप उसके क्रेडेंशियल्स का उपयोग कर सकते हैं:
OWNER_ADDRESS: आपका मौजूदा वॉलेट एड्रेसPRIVATE_KEY: आपके मौजूदा वॉलेट की private key
अपनी private key को वर्ज़न कंट्रोल (version control) में कमिट (commit) करने से बचने के लिए अपनी
.gitignoreफ़ाइल में.envजोड़ें।
CASM फ़ाइल जेनरेट करना
डिप्लॉयमेंट स्क्रिप्ट रन करने से पहले, आपको अपनी Sierra फ़ाइल से CASM फ़ाइल जेनरेट करनी होगी। जब आप scarb build रन करते हैं तो Scarb के नए वर्ज़न स्वचालित रूप से CASM फ़ाइलें जेनरेट नहीं करते हैं।
इसके लिए Sierra कम्पाइलर की आवश्यकता होती है, जिसे आप यह रन करके इंस्टॉल कर सकते हैं:
cargo install starknet-sierra-compile
एक बार इंस्टॉल हो जाने पर, यह रन करके अपनी Sierra फ़ाइल को CASM में कम्पाइल करें:
starknet-sierra-compile \
target/dev/erc20_starknetjs_ERC20.contract_class.json \
target/dev/erc20_starknetjs_ERC20.compiled_contract_class.json

यह कमांड दो आर्गुमेंट्स (arguments) लेती है:
- इनपुट (Input):
target/dev/erc20_starknetjs_ERC20.contract_class.json-scarb buildद्वारा जेनरेट की गई Sierra फ़ाइल - आउटपुट (Output):
target/dev/erc20_starknetjs_ERC20.compiled_contract_class.json- CASM बाइटकोड फ़ाइल जो बनाई जाएगी
फ़ाइल का नाम
{package_name}_{contract_module_name}पैटर्न का अनुसरण करता है। यदि आपके contract का नामerc20_starknetjs_ERC20से अलग है, तो दोनों फ़ाइल पाथ्स (paths) को तदनुसार समायोजित (adjust) करें, और फिर कमांड रन करें।
आपको अपनी target/dev डायरेक्टरी में नई जेनरेट की गई .compiled_contract_class.json फ़ाइल दिखाई देगी, जिसमें डिप्लॉयमेंट के लिए आवश्यक CASM बाइटकोड शामिल होगा।

डिप्लॉयमेंट स्क्रिप्ट लिखना
जो स्क्रिप्ट हम बना रहे हैं वह डिप्लॉयमेंट प्रक्रिया के हर चरण को स्वचालित (automate) करती है। यह Starknet Sepolia से कनेक्ट होती है, आपकी private key का उपयोग करके आपके अकाउंट को इनिशियलाइज़ (initialize) करती है, और Sierra और CASM फ़ाइलें प्राप्त करती है।
निम्नलिखित कोड के साथ scripts/deploy.ts बनाएँ:
import { Account, RpcProvider, json, CallData } from "starknet";
import fs from "fs";
import * as dotenv from "dotenv";
dotenv.config();
async function main() {
// Setup provider with Alchemy RPC URL
const provider = new RpcProvider({
nodeUrl: `https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_10/${process.env.ALCHEMY_API_KEY}`,
});
// Setup account
const account = new Account({
provider: provider,
address: process.env.OWNER_ADDRESS!,
signer: process.env.PRIVATE_KEY!,
});
// Read the compiled contract files
const compiledSierra = json.parse(
fs
.readFileSync("./target/dev/erc20_starknetjs_ERC20.contract_class.json")
.toString("ascii")
);
const compiledCasm = json.parse(
fs
.readFileSync(
"./target/dev/erc20_starknetjs_ERC20.compiled_contract_class.json"
)
.toString("ascii")
);
// Declare the contract
console.log("\nDeclaring contract...");
const declareResponse = await account.declare({
contract: compiledSierra,
casm: compiledCasm,
});
console.log(
"Declaration transaction hash:",
declareResponse.transaction_hash
);
await provider.waitForTransaction(declareResponse.transaction_hash);
// Prepare constructor arguments
const myCallData = new CallData(compiledSierra.abi);
const constructorCalldata = myCallData.compile("constructor", {
owner: account.address,
});
// Deploy the contract
console.log("\nDeploying contract...");
const deployResponse = await account.deployContract({
classHash: declareResponse.class_hash,
constructorCalldata: constructorCalldata,
});
console.log("Deployment transaction hash:", deployResponse.transaction_hash);
await provider.waitForTransaction(deployResponse.transaction_hash);
console.log("\nDeployment Summary:");
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
console.log("Class Hash:", declareResponse.class_hash);
console.log("Contract Address:", deployResponse.contract_address);
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error("\nDeployment failed:", error);
process.exit(1);
});
यदि आपका नाम erc20_starknetjs_ERC20 से भिन्न है, तो अपनी डिप्लॉयमेंट स्क्रिप्ट में contract नाम को सही नाम से अपडेट करें।
अपने प्रोजेक्ट के लिए सही नाम खोजने के लिए, यह रन करें:
ls target/dev/*.contract_class.json
उदाहरण के लिए, यदि आप my_token_MyERC20.contract_class.json देखते हैं, तो फ़ाइल पाथ्स को इस प्रकार अपडेट करें:
// Change from:
fs.readFileSync("./target/dev/erc20_starknetjs_ERC20.contract_class.json");
// To:
fs.readFileSync("./target/dev/my_token_MyERC20.contract_class.json");
.contract_class.json और .compiled_contract_class.json दोनों फ़ाइलों के लिए ऐसा ही करें।
sncast से मुख्य अंतर:
Starknet.js starknet foundry कमांड-लाइन दृष्टिकोण की तुलना में एक अलग दृष्टिकोण अपनाता है। अलग-अलग declare और deploy कमांड रन करने के बजाय, declareAndDeploy() फ़ंक्शन दोनों चरणों को एक ही बार में संभाल लेता है, जिसका अर्थ है कि आपको कमांड्स के बीच class hashes को कॉपी करने की आवश्यकता नहीं है। जबकि दोनों तरीके डिप्लॉयमेंट के लिए UDC पर निर्भर करते हैं, Starknet.js हमें salt जनरेशन और डिप्लॉयमेंट प्रकारों जैसे पहलुओं को प्रोग्रामेटिक रूप से नियंत्रित करने की अनुमति देता है, जिससे उचित एरर हैंडलिंग (error handling) के साथ स्वचालित डिप्लॉयमेंट स्क्रिप्ट बनाना आसान हो जाता है।
अपने contract को डिप्लॉय करने के लिए निम्नलिखित कमांड रन करें:
npx tsx scripts/deploy.ts
यदि सफल रहा, तो आप आउटपुट इस प्रकार देखेंगे:

Voyager पर डिप्लॉय किए गए Contract के साथ इंटरैक्ट करना
अब जब contract डिप्लॉय हो गया है, तो हम Voyager के वेब इंटरफ़ेस के माध्यम से इसके साथ इंटरैक्ट कर सकते हैं। Voyager Sepolia पर डिप्लॉय किए गए contract पर जाएं। “Write Contract” टैब पर क्लिक करें। यह इंटरफ़ेस contract के सभी पब्लिक फ़ंक्शंस दिखाता है।
अपना वॉलेट (Ready या Braavos) कनेक्ट करें और सुनिश्चित करें कि आप उसी वॉलेट का उपयोग कर रहे हैं जिसका एड्रेस आपने owner के रूप में सेट किया है। यह महत्वपूर्ण है क्योंकि केवल owner ही mint जैसे प्रतिबंधित (restricted) फ़ंक्शंस को कॉल कर सकता है।

टोकन मिंट (Mint) करना: Write सेक्शन में, mint फ़ंक्शन खोजें और पैरामीटर्स (parameters) भरें:

“Write” पर क्लिक करें और अपने वॉलेट में transaction की पुष्टि (confirm) करें। पुष्टि के बाद, आपको transaction hash प्राप्त होगा।
Read सेक्शन पर जाएँ और total_supply खोजें। परिणाम देखने के लिए “Query” पर क्लिक करें:

अपना बैलेंस चेक करना: उसी Read सेक्शन में, उस एड्रेस के साथ balance_of का उपयोग करें जिस पर आपने अभी टोकन मिंट किए हैं। यह पुष्टि करने के लिए “Query” पर क्लिक करें कि आपको 1200000000000000000 (जो कि 1000 मिंट किए गए Raretokens के बराबर है) प्राप्त हुए हैं।

आप अन्य एड्रेसेस पर टोकन ट्रांसफर करके, अधिक टोकन मिंट करके, या अपने contract की कार्यक्षमता (functionality) का पूरी तरह से परीक्षण करने के लिए भत्तों (allowances) की जाँच करके आगे प्रयोग कर सकते हैं।
Starknet.js का उपयोग करके ERC-20 के साथ इंटरैक्ट करना
हमने देखा है कि Voyager वेब इंटरफ़ेस के माध्यम से contracts के साथ कैसे इंटरैक्ट किया जाता है। अब इसके बजाय Starknet.js का उपयोग करके ऐसा ही करते हैं। नीचे दी गई स्क्रिप्ट एक अकाउंट में टोकन मिंट करती है और इवेंट विवरण के साथ transaction की पुष्टि (confirmation) प्रदर्शित करती है। निम्नलिखित कोड के साथ scripts/interact.ts बनाएँ:
import { Account, RpcProvider, Contract } from "starknet";
import * as dotenv from "dotenv";
dotenv.config();
async function interactWithERC20() {
// initialize provider for Sepolia testnet
const provider = new RpcProvider({
nodeUrl: `https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_10/${process.env.ALCHEMY_API_KEY}`,
});
// initialize account
const account = new Account({
provider: provider,
address: process.env.OWNER_ADDRESS!,
signer: process.env.PRIVATE_KEY!,
});
// REPLACE WITH YOUR DEPLOYED CONTRACT ADDRESS
const contractAddress =
"0x1416c331f53ddfc9cd022984a4ef6b9871a3c82e59904de2cd8214cfa59f00c";
// fetch contract ABI from deployed contract
const { abi } = await provider.getClassAt(contractAddress);
if (abi === undefined) {
throw new Error("ABI not found");
}
// create contract instance
const contract = new Contract({
abi,
address: contractAddress,
providerOrAccount: provider,
});
// prepare the mint call data
const call = contract.populate("mint", [
account.address,
3000000000000000000000n, // mint 3000 tokens (with 18 decimals)
]);
console.log("Minting tokens...");
// execute the transaction through the account
const { transaction_hash: mintTxHash } = await account.execute(call);
console.log("Transaction hash:", mintTxHash);
// wait for transaction confirmation
const receipt = await provider.waitForTransaction(mintTxHash);
if (receipt.isSuccess()) {
console.log("\nMint successful!");
// Parse and display emitted events
const events = contract.parseEvents(receipt);
console.log("\nEvents emitted:");
console.log(events);
} else {
console.error("Transaction failed");
}
}
// run the function and handle any errors
interactWithERC20().catch(console.error);
contractAddress को अपने डिप्लॉय किए गए contract एड्रेस से बदलें और इसका उपयोग करके स्क्रिप्ट रन करें:
npx tsx scripts/interact.ts
आपको सफल मिंटिंग और शून्य (zero) एड्रेस से आपके अकाउंट में एमिट (emit) किए गए Transfer इवेंट को दिखाने वाला आउटपुट इस प्रकार दिखाई देना चाहिए:

मौजूदा (Existing) टोकन्स के साथ इंटरैक्ट करना
आप contract एड्रेस बदलकर STRK जैसे मौजूदा ERC-20 टोकन के साथ भी इंटरैक्ट कर सकते हैं। एक नई interact_existing.ts फ़ाइल बनाएँ और अपना STRK बैलेंस चेक करने के लिए निम्नलिखित स्क्रिप्ट पेस्ट करें:
import { RpcProvider, Contract } from "starknet";
import * as dotenv from "dotenv";
dotenv.config();
async function checkSTRKBalance() {
const alchemyApiKey = process.env.ALCHEMY_API_KEY;
const provider = new RpcProvider({
nodeUrl: `https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_10/${alchemyApiKey}`,
});
// REPLACE WITH YOUR ACCOUNT ADDRESS
const accountAddress =
"0x014154fb6Dd088b5ceB46df635eCCe6e1a9B0455357931aC7Df4263A7dBf39a9";
const strkContractAddress =
"0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d";
const { abi } = await provider.getClassAt(strkContractAddress);
const strkContract = new Contract({
abi,
address: strkContractAddress,
providerOrAccount: provider,
});
const balance = await strkContract.balance_of(accountAddress);
console.log(`STRK Balance (raw): ${balance.toString()}`);
const balanceInSTRK = Number(balance) / 10 ** 18;
console.log(`STRK Balance: ${balanceInSTRK} STRK`);
}
checkSTRKBalance().catch(console.error);
इसके साथ रन करें: npx tsx scripts/interact_existing.ts

नोट: STRK जैसे मौजूदा टोकन के साथ इंटरैक्ट करते समय, आप बैलेंस चेक कर सकते हैं और, यदि आपके पास टोकन हैं, तो उन्हें ट्रांसफर (transfer) या अप्रूव (approve) कर सकते हैं, लेकिन मिंट करने के लिए contract owner होना आवश्यक है।
निष्कर्ष (Wrapping Up)
इस लेख में, हमने sncast और Starknet.js का उपयोग करके ERC-20 टोकन को डिप्लॉय करने और उनके साथ इंटरैक्ट करने के व्यावहारिक उदाहरणों को कवर किया। अगले लेख में, हम सीधे deploy_syscall का उपयोग करके फ़ैक्टरी पैटर्न (factory patterns) का पता लगाएंगे, यह दिखाते हुए कि कैसे contracts UDC के बिना अन्य contracts को डिप्लॉय कर सकते हैं।