Native multicall, Starknet की वह क्षमता है जिससे कई contract calls को एक सिंगल atomic transaction में बंडल (bundle) किया जा सकता है। कुछ decentralized application वर्कफ्लो में अनुक्रम (sequence) में कई transactions को साइन करने की आवश्यकता होती है। Token swaps इसका एक आम उदाहरण है। जब आप एक पारंपरिक DEX पर token A को token B में बदलना चाहते हैं, तो आपको कई transactions साइन करने पड़ते हैं:
- आप अपने token A को खर्च करने के लिए DEX contract को approve करने के लिए साइन करते हैं
- फिर token A को token B के साथ स्वैप (swap) करने के लिए दोबारा साइन करते हैं
यह दो-चरणों (two-step) वाली transaction प्रक्रिया है, जिसमें हर बार साइन करने पर gas fees चुकानी पड़ती है। इससे भी महत्वपूर्ण बात यह है कि इन दो transactions की कोई atomicity गारंटी नहीं होती है। यदि approval सफल हो जाता है लेकिन swap विफल (fail) हो जाता है, तो DEX contract के पास अनिश्चित काल तक आपके टोकन खर्च करने का एक्सेस बना रहता है। यह अधूरा (dangling) approval एक अटैक सरफेस (attack surface) बनाता है: यदि बाद में DEX contract को एक्सप्लॉइट (exploit) किया जाता है, तो एक हमलावर (attacker) आपकी किसी भी कार्रवाई के बिना अप्रूव्ड राशि को निकालने के लिए contract की मौजूदा transferFrom परमिशन का उपयोग कर सकता है।
Starknet कई contract calls को एक सिंगल atomic transaction में बंडल करने की अनुमति देकर इन समस्याओं को दूर करता है। आप एक बार साइन करते हैं और एक ही बार gas fees चुकाते हैं।
यह क्षमता Starknet के प्रोटोकॉल में इसके नेटिव account abstraction आर्किटेक्चर के माध्यम से अंतर्निहित (built-in) है। इस लेख में, हम जानेंगे कि multicall कैसे काम करता है और starknet.js का उपयोग करके एक सिंगल atomic transaction के रूप में टोकन अप्रूवल और डिपॉजिट को निष्पादित (execute) करके इसका प्रदर्शन (demonstrate) करेंगे।
Multicall आंतरिक रूप से कैसे काम करता है
जैसा कि पिछले अध्याय में बताया गया था, Starknet नेटिव Account Abstraction लागू करता है जहाँ हर अकाउंट एक स्मार्ट कॉन्ट्रैक्ट (smart contract) होता है। प्रत्येक अकाउंट कॉन्ट्रैक्ट में एक __execute__ फ़ंक्शन होता है जिसे प्रोटोकॉल तब कॉल करता है जब आप कोई transaction भेजते हैं। यह execute करने के लिए calls का एक array लेता है:
fn __execute__(ref self: ContractState, calls: Array<Call>) -> Array<Span<felt252>>
पैरामीटर calls: Array<Call> ही multicall को संभव बनाता है। यह फ़ंक्शन को एक सिंगल transaction में एक या कई ऑपरेशन्स को execute करने की अनुमति देता है।
उस array में प्रत्येक Call को इस प्रकार परिभाषित किया गया है:
#[derive(Drop, Serde, Debug)]
struct Call {
to: ContractAddress, // Target contract address
selector: felt252, // Function selector
calldata: Array<felt252> // Encoded parameters
}
to: उस कॉन्ट्रैक्ट का एड्रेस जिसके साथ आप इंटरैक्ट करना चाहते हैं। टोकन स्वैप के लिए, यह DEX कॉन्ट्रैक्ट एड्रेस हो सकता है, या अप्रूवल के लिए, यह टोकन कॉन्ट्रैक्ट एड्रेस होगा।selector: उस फ़ंक्शन के लिए एक विशिष्ट पहचानकर्ता (unique identifier) जिसे आप लक्ष्य (target) कॉन्ट्रैक्ट पर कॉल करना चाहते हैं। उदाहरण के लिए,transferको कॉल करने के लिए, आप सिलेक्टर के रूप मेंsn_keccak(’transfer’)पास करते हैं।calldata: यहfelt252मानों (values) का एक array है जो उन तर्कों (arguments) को दर्शाता है जिन्हें आप फ़ंक्शन में पास कर रहे हैं।
Multicall का उपयोग करके स्वैप को अप्रूव और execute करने के लिए, आप calls पैरामीटर में निम्नलिखित दो कॉल्स पास करते हैं:
const calls: Call[] = [
{
contractAddress: TOKEN_ADDRESS, // to: token contract
entrypoint: "approve", // selector: approve function
calldata: [DEX_ADDRESS, amount] // calldata: spender and amount
},
{
contractAddress: DEX_ADDRESS, // to: DEX contract
entrypoint: "swap", // selector: swap function
calldata: [tokenA, tokenB, amount] // calldata: swap parameters
}
];
जब आप कई कॉल्स के साथ एक transaction भेजते हैं, तो __execute__ फ़ंक्शन उन्हें क्रमिक रूप से (sequentially) प्रोसेस करता है। यदि सभी कॉल्स सफल हो जाती हैं, तो __execute__ कॉलर (caller) को प्रत्येक कॉल का परिणाम लौटाता है। यदि कोई भी कॉल विफल (fail) हो जाती है, तो पूरा transaction रिवर्ट (revert) हो जाता है; किसी भी ऑपरेशन का कोई प्रभाव नहीं पड़ता है।
Starknet.js के साथ Multicalls को Execute करना
अब देखते हैं कि starknet.js का उपयोग करके multicalls को कैसे execute किया जाए। हम इसे RareBank में RareTokens जमा करके प्रदर्शित करेंगे, ये दो कॉन्ट्रैक्ट्स हमने पिछले अध्यायों में बनाए थे।
RareToken (ERC20 chapter में कवर किया गया) एक मानक टोकन कॉन्ट्रैक्ट है, जबकि RareBank (“Cross Contract Calls” लेख से) उपयोगकर्ताओं को इन टोकन को जमा करने (deposit) और निकालने (withdraw) की अनुमति देता है। डिपॉजिट करने के लिए दो चरणों की आवश्यकता होती है:
- आपके टोकन खर्च करने के लिए
RareBankको अप्रूव करना RareBankमें टोकन जमा करना
Multicall के बिना, इसके लिए दो अलग-अलग transactions की आवश्यकता होती है और प्रत्येक के लिए गैस फीस का भुगतान करना पड़ता है। Multicall के साथ, हम दोनों ऑपरेशन्स को एक सिंगल transaction में एटॉमिक रूप से (atomically) execute कर सकते हैं, इसलिए आप दो के बजाय एक ही transaction फीस देते हैं।
Contracts को डिप्लॉय (Deploy) करना
RareBank और RareToken कॉन्ट्रैक्ट्स के साथ इंटरैक्ट करने से पहले, हमें यह सुनिश्चित करना होगा कि दोनों Starknet पर डिप्लॉय किए गए हैं। इस डेमो के लिए, हम Starknet Sepolia का उपयोग करेंगे। RareToken कॉन्ट्रैक्ट “Deploying Contracts” अध्याय में पहले ही डिप्लॉय किया जा चुका था। नीचे दिए गए एड्रेस में एक अप्रतिबंधित (unrestricted) mint फ़ंक्शन है। यह RareBank कॉन्ट्रैक्ट की टेस्टिंग के लिए किसी को भी टोकन मिंट (mint) करने की अनुमति देता है।
RareToken कॉन्ट्रैक्ट एड्रेस:
0x03ec6283d9c7c8936991fad6523e01b60ad2bb092aa489087a3376c2ade7c09b
आप इसे Voyager पर देख सकते हैं।
नोट: यदि आप अपने स्वयं के टोकन का उपयोग कर रहे हैं, तो इस ट्यूटोरियल में सभी जगह RareToken एड्रेस को अपने टोकन के कॉन्ट्रैक्ट एड्रेस से बदलें।
हमें इस अध्याय के लिए केवल RareBank कॉन्ट्रैक्ट को डिप्लॉय करना है। नीचे RareBank कॉन्ट्रैक्ट दिया गया है:
use starknet::ContractAddress;
// RareToken ERC20 Interface - defines functions we can call on the token contract
#[starknet::interface]
pub trait IRareToken<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
}
// RareBank Interface - defines the bank's functions
#[starknet::interface]
pub trait IRareBank<TContractState> {
fn deposit(ref self: TContractState, amount: u256);
fn withdraw(ref self: TContractState, amount: u256);
fn get_balance(self: @TContractState, user: ContractAddress) -> u256;
}
#[starknet::contract]
mod RareBank {
use starknet::{ContractAddress, get_caller_address, get_contract_address};
use starknet::storage::{
StoragePointerReadAccess, StoragePointerWriteAccess,
Map, StoragePathEntry
};
// import the generated dispatcher and trait for cross contract calls
use super::{IRareTokenDispatcher, IRareTokenDispatcherTrait};
#[storage]
struct Storage {
owner: ContractAddress,
rare_token: ContractAddress, // address of the RareToken contract we'll interact with
balances: Map<ContractAddress, u256>, // maps user addresses to their bank balances
}
#[event]
#[derive(Drop, starknet::Event)]
pub enum Event {
DepositSuccessful: DepositSuccessful,
WithdrawSuccessful: WithdrawSuccessful,
}
#[derive(Drop, starknet::Event)]
struct DepositSuccessful {
user: ContractAddress,
amount: u256
}
#[derive(Drop, starknet::Event)]
struct WithdrawSuccessful {
user: ContractAddress,
amount: u256
}
// constructor sets up the bank with owner and RareToken contract address
#[constructor]
fn constructor(ref self: ContractState, owner: ContractAddress, rare_token_address: ContractAddress) {
assert!(owner != 0.try_into().unwrap(), "address zero detected");
assert!(rare_token_address != 0.try_into().unwrap(), "address zero detected");
self.owner.write(owner);
self.rare_token.write(rare_token_address); // store the token contract address
}
#[abi(embed_v0)]
impl RareBankImpl of super::IRareBank<ContractState> {
fn deposit(ref self: ContractState, amount: u256) {
assert!(amount > 0, "can't deposit zero amount");
let caller = get_caller_address();
let this_contract = get_contract_address();
let rare_token_address = self.rare_token.read(); // get the stored token address
// create dispatcher instance pointing to the RareToken contract
let rare_token = IRareTokenDispatcher { contract_address: rare_token_address };
// cross contract call: transfer tokens from user to this bank contract
// this calls the transfer_from function on the RareToken contract
let success = rare_token.transfer_from(caller, this_contract, amount);
assert!(success, "transfer failed");
// update the user's balance in our bank's storage
let prev_balance = self.balances.entry(caller).read();
self.balances.entry(caller).write(prev_balance + amount);
// emit DepositSuccessful event
self.emit(DepositSuccessful { user: caller, amount });
}
fn withdraw(ref self: ContractState, amount: u256) {
let caller = get_caller_address();
let rare_token_address = self.rare_token.read();
assert!(rare_token_address != 0.try_into().unwrap(), "RareToken not set");
// check if user has sufficient balance in the bank
let user_balance = self.balances.entry(caller).read();
assert!(user_balance >= amount, "insufficient funds");
// update balance first
self.balances.entry(caller).write(user_balance - amount);
// create dispatcher instance pointing to the RareToken contract
let rare_token = IRareTokenDispatcher { contract_address: rare_token_address };
// cross contract call: transfer tokens from bank back to user
// this calls the transfer function on the RareToken contract
let success = rare_token.transfer(caller, amount);
assert!(success, "transfer failed");
// emit WithdrawSuccessful event
self.emit(WithdrawSuccessful { user: caller, amount });
}
// view function to check user's balance in the bank
fn get_balance(self: @ContractState, user: ContractAddress) -> u256 {
self.balances.entry(user).read()
}
}
}
RareBank कॉन्ट्रैक्ट को डिप्लॉय करने के लिए, हम पहले इसके कॉन्ट्रैक्ट क्लास को डिक्लेयर (declare) करते हैं, फिर एक इंस्टेंस (instance) डिप्लॉय करते हैं।
RareBank को डिक्लेयर करना:
sncast \
--account <ACCOUNT_NAME> \
declare \
--url https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_10/<YOUR_API_KEY> \
--contract-name RareBank
बदलें:
<ACCOUNT_NAME>को sncast से अपने अकाउंट नाम के साथ<YOUR_API_KEY>को Alchemy से अपनी API कुंजी (key) के साथ
इस कमांड को रन करने के बाद, आपको अपने टर्मिनल में एक क्लास हैश (class hash) प्राप्त होगा; RareBank कॉन्ट्रैक्ट के डिप्लॉयमेंट के लिए इसकी आवश्यकता होगी।

RareBank को डिप्लॉय करना
RareBank कंस्ट्रक्टर (constructor) को देखते हुए:
#[constructor]
fn constructor(
ref self: ContractState,
owner: ContractAddress,
rare_token_address: ContractAddress
) {
assert!(owner != 0.try_into().unwrap(), "address zero detected");
assert!(rare_token_address != 0.try_into().unwrap(), "address zero detected");
self.owner.write(owner);
self.rare_token.write(rare_token_address);
}
कंस्ट्रक्टर दो पैरामीटर्स की अपेक्षा करता है:
- owner: वह एड्रेस जो
RareBankकॉन्ट्रैक्ट का स्वामी (owner) होगा - rare_token_address: टोकन कॉन्ट्रैक्ट एड्रेस
RareBank कॉन्ट्रैक्ट डिप्लॉय करें:
sncast \
--account <ACCOUNT_NAME> \
deploy \
--class-hash <CLASS_HASH> \
--arguments '<OWNER_ADDRESS>,<RARE_TOKEN_CONTRACT_ADDRESS>' \
--url https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_10/<YOUR_API_KEY>
बदलें:
<ACCOUNT_NAME>को अपने अकाउंट नाम के साथ<YOUR_API_KEY>को अपनी Alchemy API कुंजी (key) के साथ<CLASS_HASH>को डिक्लेरेशन से प्राप्त क्लास हैश के साथ<OWNER_ADDRESS>को अपने वॉलेट एड्रेस के साथ<RARE_TOKEN_CONTRACT_ADDRESS>को अपने डिप्लॉयड टोकन कॉन्ट्रैक्ट एड्रेस या0x03ec6283d9c7c8936991fad6523e01b60ad2bb092aa489087a3376c2ade7c09bके साथ
डिप्लॉयमेंट के बाद, कॉन्ट्रैक्ट एड्रेस को सेव कर लें; multicall इम्प्लीमेंटेशन के लिए इसकी आवश्यकता होगी।

प्रोजेक्ट सेटअप करना
कॉन्ट्रैक्ट एड्रेस तैयार होने के साथ, हम प्रोग्रामेटिक रूप से multicall को execute करने, और अपने अकाउंट से transaction को साइन और सबमिट करने के लिए starknet.js का उपयोग करेंगे। पहले से सेटअप किए गए प्रोजेक्ट स्ट्रक्चर और कॉन्फ़िगरेशन के साथ रिपॉजिटरी को क्लोन करने के लिए नीचे दिए गए कमांड को रन करें:
git clone https://github.com/Sayrarh/starknet-multicall-demo.git
cd starknet-multicall-demo
फिर डिपेंडेंसीज़ (dependencies) इनस्टॉल करें और अपनी environment फ़ाइल सेटअप करें:
npm install
cp .env.example .env
.env खोलें और प्लेसहोल्डर मानों (values) को बदलें:
ACCOUNT_ADDRESS: आपका Starknet अकाउंट एड्रेसPRIVATE_KEY: आपके अकाउंट की प्राइवेट की (private key)ALCHEMY_API_KEY: आपकी Alchemy API की (key)RARE_TOKEN_ADDRESS: दिए गए एड्रेस (पब्लिक मिंटिंग सक्षम) या अपने स्वयं के टोकन कॉन्ट्रैक्ट एड्रेस का उपयोग करेंRARE_BANK_ADDRESS: दिए गए एड्रेस या अपने स्वयं केRareBankकॉन्ट्रैक्ट एड्रेस का उपयोग करें
Multicall कोड लिखना
src/index.ts खोलें और बेसिक इम्पॉर्ट्स और कॉन्फ़िगरेशन सेट करके शुरुआत करें:
import { Account, Call, CallData, RpcProvider, uint256 } from "starknet";
import * as dotenv from "dotenv";
dotenv.config();
const alchemyApiKey = process.env.ALCHEMY_API_KEY;
// Initialize provider
const provider = new RpcProvider({
nodeUrl: `https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_10/${alchemyApiKey}`,
});
// Connect your account
const account = new Account({
provider: provider,
address: process.env.ACCOUNT_ADDRESS!,
signer: process.env.PRIVATE_KEY!,
});
हम starknet.js से आवश्यक मॉड्यूल्स इंपोर्ट करते हैं, जिनमें Call (कॉन्ट्रैक्ट इंटरैक्शन को परिभाषित करता है) और CallData (फ़ंक्शन पैरामीटर्स को एन्कोड करता है) शामिल हैं। फिर हम environment वेरिएबल्स लोड करते हैं, Alchemy के माध्यम से Starknet Sepolia से कनेक्ट करने के लिए RPC प्रोवाइडर सेटअप करते हैं, और अपने अकाउंट को इनिशियलाइज़ (initialize) करते हैं।
टोकन मिंट करना (Minting Tokens)
टोकन जमा करने से पहले, हमें अपने अकाउंट में कुछ RareTokens की आवश्यकता होती है। mintTokens फ़ंक्शन कनेक्टेड अकाउंट में 100 RareTokens मिंट करता है:
async function mintTokens() {
console.log("\n>> Minting RareTokens...");
const amount = 100n * 10n ** 18n;
const amountUint256 = uint256.bnToUint256(amount);
const result = await account.execute({
contractAddress: process.env.RARE_TOKEN_ADDRESS!,
entrypoint: "mint",
calldata: CallData.compile({
recipient: process.env.ACCOUNT_ADDRESS!,
amount: amountUint256,
}),
});
console.log("Transaction submitted!");
console.log(` Hash: ${result.transaction_hash}`);
console.log(` Voyager: https://sepolia.voyager.online/tx/${result.transaction_hash}`);
console.log("Waiting for confirmation...");
await provider.waitForTransaction(result.transaction_hash);
console.log("Successfully minted 100 RareTokens!\n");
}
यह सुनिश्चित करने के लिए कि डिपॉजिट करने से पहले आपके पास टोकन हों, स्क्रिप्ट में मिंटिंग को शामिल किया गया है।
कॉल्स (Calls) का निर्माण करना
चूँकि multicall केवल व्यक्तिगत Call ऑब्जेक्ट्स का एक array है, जब हम starknet.js में एक entrypoint (जैसे 'approve') निर्दिष्ट करते हैं, तो यह इसे एक सिलेक्टर हैश (selector hash) में बदल देता है, जो Call स्ट्रक्ट (struct) में मौजूद selector: felt252 फ़ील्ड के समान है। इसी तरह, starknet.js में contractAddress Call स्ट्रक्ट के to फ़ील्ड में मैप होता है।
आइए हमें जो दो कॉल्स चाहिए उन्हें बनाते हैं:
Call 1: RareBank को अप्रूव करना
approveCall, कनेक्टेड अकाउंट से 10 RareTokens खर्च करने के लिए RareBank को अप्रूव करता है।
const amount = 10n * 10n ** 18n; // 10 tokens (accounting for 18 decimals)
const amountUint256 = uint256.bnToUint256(amount);
const approveCall: Call = {
contractAddress: process.env.RARE_TOKEN_ADDRESS!,
entrypoint: 'approve',
calldata: CallData.compile({
spender: process.env.RARE_BANK_ADDRESS!,
amount: amountUint256
})
};
ध्यान दें कि हम 10¹⁸ से गुणा करते हैं क्योंकि RareToken में 18 डेसीमल्स (decimals) होते हैं।
Call 2: RareBank में डिपॉजिट करना
depositCall अप्रूव किए गए टोकन्स को RareBank में जमा करता है। यह Call 1 के अप्रूवल पर निर्भर करता है जो पहले ही उसी transaction के भीतर execute हो चुका होता है।
const depositCall: Call = {
contractAddress: process.env.RARE_BANK_ADDRESS!,
entrypoint: 'deposit',
calldata: CallData.compile({
amount: amountUint256
})
};
Multicall को Execute करना
अब आइए दोनों कॉल्स को मिलाएं और उन्हें एक सिंगल transaction में execute करें:
async function depositToRareBank() {
console.log(">> Executing multicall: Approve + Deposit");
const amount = 10n * 10n ** 18n;
const amountUint256 = uint256.bnToUint256(amount);
const multiCall: Call[] = [
{
contractAddress: process.env.RARE_TOKEN_ADDRESS!,
entrypoint: "approve",
calldata: CallData.compile({
spender: process.env.RARE_BANK_ADDRESS!,
amount: amountUint256,
}),
},
{
contractAddress: process.env.RARE_BANK_ADDRESS!,
entrypoint: "deposit",
calldata: CallData.compile({
amount: amountUint256,
}),
},
];
const result = await account.execute(multiCall);
console.log("Transaction submitted!");
console.log(` Hash: ${result.transaction_hash}`);
console.log(
` Voyager: https://sepolia.voyager.online/tx/${result.transaction_hash}`
);
console.log("Waiting for confirmation...");
await provider.waitForTransaction(result.transaction_hash);
console.log("Multicall executed successfully!");
console.log(
` Approved and deposited ${
amount / 10n ** 18n
} RareTokens in one transaction!\n`
);
}
यहाँ पूरा कोड दिया गया है; इसे src/index.ts फ़ाइल में कॉपी करें:
import { Account, Call, CallData, RpcProvider, uint256 } from "starknet";
import * as dotenv from "dotenv";
dotenv.config();
const alchemyApiKey = process.env.ALCHEMY_API_KEY;
// Initialize provider
const provider = new RpcProvider({
nodeUrl: `https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_8/${alchemyApiKey}`,
});
// initialize account
const account = new Account({
provider: provider,
address: process.env.ACCOUNT_ADDRESS!,
signer: process.env.PRIVATE_KEY!,
});
async function mintTokens() {
console.log("\n>> Minting RareTokens...");
const amount = 100n * 10n ** 18n;
const amountUint256 = uint256.bnToUint256(amount);
const result = await account.execute({
contractAddress: process.env.RARE_TOKEN_ADDRESS!,
entrypoint: "mint",
calldata: CallData.compile({
recipient: process.env.ACCOUNT_ADDRESS!,
amount: amountUint256,
}),
});
console.log("Transaction submitted!");
console.log(` Hash: ${result.transaction_hash}`);
console.log(` Voyager: https://sepolia.voyager.online/tx/${result.transaction_hash}`);
console.log("Waiting for confirmation...");
await provider.waitForTransaction(result.transaction_hash);
console.log("Successfully minted 100 RareTokens!\n");
}
async function depositToRareBank() {
console.log(">> Executing multicall: Approve + Deposit");
const amount = 10n * 10n ** 18n;
const amountUint256 = uint256.bnToUint256(amount);
const multiCall: Call[] = [
{
contractAddress: process.env.RARE_TOKEN_ADDRESS!,
entrypoint: "approve",
calldata: CallData.compile({
spender: process.env.RARE_BANK_ADDRESS!,
amount: amountUint256,
}),
},
{
contractAddress: process.env.RARE_BANK_ADDRESS!,
entrypoint: "deposit",
calldata: CallData.compile({
amount: amountUint256,
}),
},
];
const result = await account.execute(multiCall);
console.log("Transaction submitted!");
console.log(` Hash: ${result.transaction_hash}`);
console.log(` Voyager: https://sepolia.voyager.online/tx/${result.transaction_hash}`);
console.log("Waiting for confirmation...");
await provider.waitForTransaction(result.transaction_hash);
console.log("Multicall executed successfully!");
console.log(` Approved and deposited ${amount / 10n ** 18n} RareTokens in one transaction!\n`);
}
async function main() {
await mintTokens();
await depositToRareBank();
}
main()
कोड रन करने से पहले, सुनिश्चित करें कि आपके पास Sepolia पर गैस फीस के लिए STRK टोकन हैं; इन्हें Starknet Sepolia Faucet से प्राप्त करें।
इसके उपयोग से कोड रन करें:
npm start
आपको इसके समान आउटपुट देखना चाहिए:

Transaction Trace का निरीक्षण करना
Multicall के दौरान क्या होता है, इसे बेहतर ढंग से समझने के लिए, आइए transaction trace की जांच करें। Voyager पर अपना transaction खोलें, Internal Calls सेक्शन में जाएं, और __execute__ फ़ंक्शन का विस्तार (expand) करें; आप हमारे द्वारा बनाए गए दो कॉल्स का array देखेंगे:

Call 1: Approve
to:0x3ec6283d9c7c8936991fad6523e01b60ad2bb092aa489087a3376c2ade7c09b(RareTokenकॉन्ट्रैक्ट)selector:0x219209e083275171774dab1df80982e9df2096516f06319c5c6d71ae0a8480c(approveका हैश)calldata:0x4c623c5b67ce693af795f5e49a468ba943ef8fbde7ba898f40df270bf96890e(RareBankएड्रेस; स्पेंडर)0x8ac7230489e80000(हेक्स में सबसे कम महत्वपूर्ण बिट्स (least significant bits); 10 टोकन)0x0(सबसे महत्वपूर्ण बिट्स (most significant bits))
याद रखें कि Starknet में,
u256प्रकार (type) दो भागों में विभाजित होता है (low:u128और high:u128), यही कारण है कि आपको राशि (amount) के लिए दो मान (values) दिखाई देते हैं।
Call 2: Deposit
to:0x4c623c5b67ce693af795f5e49a468ba943ef8fbde7ba898f40df270bf96890e(RareBankकॉन्ट्रैक्ट)selector:0xc73f681176fc7b3f9693986fd7b14581e8d540519e27400e88b8713932be01(depositका हैश)calldata:0x8ac7230489e80000(हेक्स में सबसे कम महत्वपूर्ण बिट्स; 10 टोकन)0x0(सबसे महत्वपूर्ण बिट्स)
आउटपुट (Output) सेक्शन में, आपको कॉल ट्री (call tree) भी दिखाई देगा:

__execute__आपके multicall array को प्रोसेस करता हैRareTokenपरapproveकॉलRareBankपरdepositकॉल- आंतरिक रूप से
RareTokenपरtransfer_fromको कॉल करता है
- आंतरिक रूप से
सफलता की पुष्टि करने वाले Approval और DepositSuccessful इवेंट्स देखने के लिए Events टैब चेक करें।