在上一篇文章中,我们介绍了 Starknet 的 declare-deploy 模型,包括普通合约和账户合约的部署路径。本文将把这些概念付诸实践。我们将使用 ERC-20 章节中的 ERC-20 合约作为部署示例,通过 Starknet Foundry (sncast) 和 Starknet.js 演示完整的部署工作流:设置用于签名交易的账户,声明 ERC-20 合约类(contract class),部署 ERC-20 合约实例,以及与已部署的合约进行交互。
合约设置
使用 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
}
}
}
我们将使用两种方法部署此合约:Starknet Foundry (sncast) 和 Starknet.js。这两种方法遵循相同的部署步骤:
- 设置一个账户以签名交易并支付 Gas 费
- 声明 ERC-20 合约类(contract class),将代码注册到链上并获取 class hash
- 部署 ERC-20 合约实例,基于该 class hash 创建一个可运行的实例
使用 Starknet Foundry (sncast) 部署
账户设置
我们需要一个账户合约来签名交易并支付部署费用。如果你已经配置了 sncast 账户,可以跳过此步骤直接进行声明操作。否则,让我们创建一个。
使用 sncast 创建账户的命令是:
sncast account create --network <NETWORK_NAME> --name <ACCOUNT_NAME>
其中:
<NETWORK_NAME>是你想要部署到的网络(例如sepolia、mainnet)<ACCOUNT_NAME>是你选择的用于引用此账户的任意本地标识符(例如my_account、deployer_account)
例如,要在 Sepolia 上创建账户:
sncast account create --network sepolia --name new_account_1
运行上述命令后,sncast 会执行以下操作:
- 在本地生成一对公私钥
- 根据公钥和 salt 确定性地计算出账户地址
- 将账户详细信息保存在本地 JSON 文件中(
~/.starknet_accounts/starknet_open_zeppelin_accounts.json)。可以通过以下命令检查该文件:
cat ~/.starknet_accounts/starknet_open_zeppelin_accounts.json
- 该地址已被网络(在我们的例子中是 sepolia)记录,但尚未部署任何账户合约。
注意:我们在这里使用的是
account create(而不是declare),因为账户合约类通常由其提供者预先声明。在这个例子中,OpenZeppelin 已经声明了我们要使用的账户合约类,所以我们只是创建它的一个实例,而不是声明一个新的。
运行 create 命令后,你的输出应如下所示:

如果你在 Voyager 上搜索该地址,你会注意到账户地址已存在,但状态为“Uninitialized”(未初始化)。这表明账户合约尚未被部署。

要部署新创建的账户,请复制输出中的地址,并通过 Starknet faucet 向其充值 STRK 代币。这就是我们在上一篇文章中讨论的 counterfactual 部署过程:账户从其预先注资的地址支付自身的部署费用,从而生成一个独立存在且不与任何其他账户关联的新账户。
获取测试代币后,使用以下命令部署账户:
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 会提示你是否将此账户设为本地或全局默认账户。本地默认仅应用于当前项目,而全局默认则应用于所有 Scarb 项目。对于本教程,请选择本地(local)。确认后,账户即被部署:

如果你在 Voyager 上检查该交易,你会注意到交易类型为 DEPLOY_ACCOUNT。这是专门用于首次创建账户的协议级交易类型,在此过程中不需要现有账户来赞助部署。

在此交易期间,定序器(sequencer)验证部署,使用你的公钥运行账户构造函数,并从预先注资的地址中扣除部署费用。现在,你的账户合约已在 Starknet 上线运行。
声明 ERC-20 合约类
与由提供者预先声明的账户合约不同,像我们的 ERC-20 这样的普通合约必须在部署之前由我们自行声明。
声明普通合约的语法为:
sncast --account <ACCOUNT_NAME> \
declare \
--url <URL> \
--contract-name <CONTRACT_NAME>
其中:
<ACCOUNT_NAME>:你之前创建并部署的账户名称<URL>:你正要部署到的网络的 RPC 节点 URL<CONTRACT_NAME>:合约模块的名称,例如mod ERC20 {}中的ERC20
要在 Sepolia 上声明我们的 ERC-20 合约,请运行以下命令,将其中的 {apiKey} 替换为你的 Alchemy API 密钥,并将 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
回想一下,声明操作会将你的合约代码注册到链上并返回一个 class hash。在此过程中会发生以下情况:
- 编译 (Compilation):
sncast读取你的 Sierra 文件,在本地将其编译为 CASM (Cairo Assembly),并根据该 CASM 计算编译出的 class hash - 交易提交 (Transaction submission):
sncast将 Sierra 类和本地计算出的编译态 class hash 一起发送给定序器,并由你的账户签名 - 定序器验证 (Sequencer verification):定序器将 Sierra 编译为 CASM,并验证其编译的 class hash 是否与你提供的一致
- 网络存储 (Network storage):如果哈希值匹配,Sierra 类和编译后的 class hash 都将存储在 Starknet 上
- 返回 Class hash:定序器返回 class hash(由 Sierra 计算得出),用于标识此合约类。
输出显示了交易哈希和 class hash。复制该 class hash,因为在部署合约实例时我们将需要用到它:

sncast 在输出中提供了一个即用型的部署命令。你可以直接复制该生成的命令,或为了更好的可读性使用下面的格式。这两种方法的效果是完全相同的。
通过 UDC 部署合约实例
在我们的合约类声明完毕后,现在我们可以基于该 class hash 部署任意数量的实例。sncast deploy 命令对 UDC 交互进行了抽象,并在底层处理了部署细节。
部署合约的语法是:
sncast \
--account <ACCOUNT_NAME> \
deploy \
--class-hash <CLASS_HASH> \
--arguments "<CONSTRUCTOR_ARGS>" \
--url <URL>
默认情况下,sncast 会生成一个随机 salt 以确保合约地址的唯一性。如果需要预测或复现最终生成的合约地址,你可以传递一个带有特定值的 --salt 参数,例如 --salt 0x146。你也可以传递 --unique,这将使用你的部署者地址来修改 salt,确保该地址对你的账户而言是唯一的。在本教程中,我们将省略这两个标志,让 sncast 自动生成 salt。
要部署我们的 ERC-20 合约的一个实例,请运行以下命令。将 <OWNER_ADDRESS> 替换为你的实际账户地址,将 {apiKey} 替换为你的 Alchemy API 密钥。你可以使用自己声明时返回的 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 地址可以用单引号或双引号括起来。该地址将被设置为合约所有者(owner),这意味着只有此账户将被授权铸造(mint)代币,因此请确保它是你打算用于与合约交互的地址。
你将在终端中收到包含该合约地址的交易凭据(receipt):

你可以在 Voyager 上验证此次部署。请注意,交易类型为 INVOKE,操作(operation)为 deployContract。这证实了 sncast 是通过 UDC 部署该合约的:

点击 交易 哈希以打开交易详情。这里显示了传递给 UDC deployContract 函数的确切参数:

当你运行合约部署命令时,sncast 自动处理了与 UDC 的交互:
- 由于没有提供
--salt,sncast生成了一个随机 salt(如输入数据表格第二行所示的0xd815d280876b878e)以确保合约地址的唯一性 - 你的账户向 UDC 发送了一笔
INVOKE交易,包含:classHash:0x23a5a4819dcac3a6b5fe724596647d3fc1f176ca565a0fb908c4457f1cc875b(输入数据表中的第一行)salt:0xd815d280876b878e(输入数据表中的第二行)notFromZero:0x0(false);因为没有传递--unique,因此合约地址是根据部署者地址推导而来,而不是根据零地址推导calldata:[0x14154fb6dd088b5ceb46df635ecce6e1a9b0455357931ac7df4263a7dbf39a9](输入数据表中的第四行 - 你的构造函数参数,即 owner 地址)
- UDC 调用
deployContract以创建你的合约实例,返回合约地址0x02ceed65a4bd731034c01113685c831b01c15d7d432f71afb1cf1634b53a2125(在黄色高亮的输出数据字段中)
使用 Starknet.js 部署
作为 Starknet Foundry sncast 命令的替代方案,我们也可以使用 Starknet.js 部署合约。这种方法允许你以编程方式进行部署,从而更容易实现自动化部署或将其集成到你的开发工作流中。
设置环境
由于 ERC-20 合约类已经在使用 sncast 的方法时在链上被声明了,因此如果在同一个项目中使用相同的编译器版本再次尝试声明相同的代码,将会导致“contract has already been declared”(合约已被声明)错误,因为链上已经存在相同的 class hash。为了避免此错误,我们将创建一个新的 Scarb 项目:
scarb new erc20_starknetjs
cd erc20_starknetjs
将“合约设置”部分中相同的 ERC-20 合约代码复制到 src/lib.cairo 中,然后使用 scarb build 编译项目。这为你使用 Starknet.js 进行声明和部署提供了一个全新的起点。
现在,为部署代码创建一个 scripts 文件夹:
mkdir scripts
为部署脚本安装必需的包:
npm install starknet dotenv
npm install -D typescript @types/node tsx
这些包的用途:
- starknet:用于与 Starknet 合约交互的官方库
- dotenv:从
.env文件加载环境变量 - typescript & @types/node:为我们的脚本提供 TypeScript 支持
- tsx:处理 ES modules 的现代 TypeScript 运行器
通过向 package.json 中添加 "type": "module" 字段,将项目配置为使用 ES 模块语法(import/export)而不是 CommonJS(require/module.exports):
{
"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"
}
}
这使我们能够使用 import 语句代替较旧的 require() 语法。
在项目根目录下创建一个 .env 文件:
PRIVATE_KEY=your_private_key_here
ALCHEMY_API_KEY=your_alchemy_api_key_here
OWNER_ADDRESS=your_owner_address_here
由于我们将在 Voyager 上对已部署的合约调用写入函数,你需要连接一个钱包(如 Ready 或 Braavos)。你有两个选项:
选项 1:导入我们创建的账户(推荐)
使用来自 ~/.starknet_accounts/starknet_open_zeppelin_accounts.json 的私钥,将我们之前创建的账户导入到 Ready 钱包。按照以下指南导入你的账户:
导入完成后,在你的 .env 文件中进行以下设置:
OWNER_ADDRESS:导入账户的地址(与 JSON 文件中的相同)PRIVATE_KEY:JSON 文件中的私钥
选项 2:使用现有的账户
如果你已经有一个 Ready 或 Braavos 的 Sepolia 账户,可以直接使用那些凭证:
OWNER_ADDRESS:你现有钱包的地址PRIVATE_KEY:你现有钱包的私钥
将
.env添加到你的.gitignore文件中,以避免将私钥提交到版本控制系统中。
生成 CASM 文件
在运行部署脚本之前,你需要从 Sierra 文件生成 CASM 文件。较新版本的 Scarb 在你运行 scarb build 时不会自动生成 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

此命令接受两个参数:
- 输入 (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}的模式。如果你的合约名称与erc20_starknetjs_ERC20不同,请相应地调整这两个文件路径,然后运行该命令。
你会在 target/dev 目录中看到新生成的 .compiled_contract_class.json 文件,其中包含部署所需的 CASM 字节码。

编写部署脚本
我们要创建的脚本将自动化部署过程的每一步。它会连接到 Starknet Sepolia,使用你的私钥初始化你的账户,并读取 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 不同,请在部署脚本中将其更新为正确的名称。
要找到你项目的正确名称,请运行:
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 Foundry 的命令行方式相比,Starknet.js 采取了不同的方法。declareAndDeploy() 函数不会将声明和部署命令分开运行,而是一次性处理这两个步骤,这意味着你不需要在命令之间复制 class hash。虽然这两种方法都依赖于 UDC 进行部署,但 Starknet.js 允许我们以编程方式控制诸如 salt 生成和部署类型等方面,从而更容易构建具有完善错误处理功能的自动化部署脚本。
运行以下命令来部署你的合约:
npx tsx scripts/deploy.ts
如果成功,你将看到如下输出:

在 Voyager 上与已部署的合约交互
现在合约已部署,我们可以通过 Voyager 的 Web 界面与其交互。前往 Voyager Sepolia 上的已部署合约。点击“Write Contract”选项卡。该界面会显示合约的所有公共函数。
连接你的钱包(Ready 或 Braavos),并确保你正在使用的钱包地址就是你设为 owner 的那个地址。这很重要,因为只有 owner 才能调用受限的函数(如 mint)。

铸造代币 (Mint tokens):在 Write 部分,找到 mint 函数并填写参数:

点击 “Write” 并在你的钱包中确认该笔交易。确认后,你将收到一笔交易哈希。
切换到 Read 部分并找到 total_supply。点击 “Query” 查看结果:

检查你的余额 (Check your balance):在同一个 Read 部分中,使用 balance_of 并填入你刚刚接收铸造代币的地址。点击 “Query”,以确认你收到了 1200000000000000000(相当于铸造了 1000 个 Raretokens)。

你可以进一步尝试将代币转账给其他地址、铸造更多代币,或者检查授权额度(allowance),以全面测试合约的功能。
使用 Starknet.js 与 ERC-20 交互
我们已经看到了如何通过 Voyager Web 界面与合约交互。现在让我们改用 Starknet.js 执行相同的操作。下面的脚本会将代币铸造到一个账户,并显示包含事件详情的交易确认信息。使用以下代码创建 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 替换为你已部署的合约地址,并使用以下命令运行脚本:
npx tsx scripts/interact.ts
你应该会看到显示铸造成功以及从零地址到你账户的 Transfer 事件被触发的输出,如下所示:

与现有代币交互
你也可以通过更改合约地址来与现有的 ERC-20 代币(如 STRK)进行交互。创建一个新的 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 等现有代币交互时,你可以检查余额;如果你持有这些代币,还可以进行转账或授权,但是铸造(mint)代币需要具备合约所有者(owner)的身份。
总结
在本文中,我们介绍了使用 sncast 和 Starknet.js 部署和与 ERC-20 代币交互的实际示例。在下一篇文章中,我们将探索使用直接 deploy_syscall 的工厂模式(factory patterns),并展示合约如何在不通过 UDC 的情况下部署其他合约。