Starknet Hardhat Plugin
If you've used Hardhat 👷♀️👷♂️ and want to develop for Starknet , this plugin might come in hand. If you've never set up a Hardhat project, check out this guide.
Contents
- Install
- CLI commands
- API
- Testing
- Debugging contracts
- Configure the plugin
- Account support
- More examples
- Contribute
Install
npm i @shardlabs/starknet-hardhat-plugin --save-dev
For the latest unstable version, use
npm i @shardlabs/starknet-hardhat-plugin@alpha --save-dev
Add the following line to the top of your hardhat.config.ts
(or hardhat.config.js
):
import "@shardlabs/starknet-hardhat-plugin";
// or
require("@shardlabs/starknet-hardhat-plugin");
Requirements
This plugin was tested with:
- Node.js v14.17.3
- npm/npx v7.19.1
- Docker v20.10.8 (optional):
- Linux / macOS:
CLI commands
This plugin defines the following Hardhat commands (also called tasks):
starknet-compile-deprecated
$ npx hardhat starknet-compile-deprecated [PATH...] [--cairo-path "<LIB_PATH1>:<LIB_PATH2>:..."] [--account-contract] [--disable-hint-validation]
Compiles Starknet Cairo 0 contracts. If no paths are provided, all Starknet contracts in the default contracts directory are compiled. Paths can be files and directories.
--cairo-path
allows specifying the locations of imported files, if necessary. Separate them with a colon (:), e.g. --cairo-path='path/to/lib1:path/to/lib2'
--account-contract
allows compiling an account contract.
--disable-hint-validation
allows compiling a contract without hint validation (any python code is allowed in hints, ex: print ...).
starknet-compile
$ npx hardhat starknet-compile [PATH...] [--add-pythonic-hints] [--single-file] [--replace-ids] [--allowed-libfuncs-list-file] [--allowed-libfuncs-list-name] [--cairo1-bin-dir <PATH>]
Compiles Starknet Cairo 1 contracts in the provided path. Paths can be files and directories. Currently, contracts importing other contracts are not supported (until this is supported, you may try to use Scarb and modifying its artifacts to be compatible with this plugin).
Custom local compiler can be specified by providing the path of the directory holding compiler binaries (starknet-compile
and starknet-sierra-compile
) to --cairo1-bin-dir
or to the cairo1BinDir
option in your config file. E.g. if your cairo compiler repository is in /path/to/cairo
and you built the compiler with cargo build --bin starknet-compile --bin starknet-sierra-compile --release
, the path would be /path/to/cairo/target/release
.
If neither --cairo1-bin-dir
nor cairo1BinDir
is set, the plugin will automatically download a compiler version it is adapted to. To download a specific version, set compilerVersion
in hardhat.config.ts
to a semver string matching one of the official releases.
module.exports = {
starknet: {
// Only one of these properties can be specified.
cairo1BinDir: "/path/to/cairo/target/release/",
compilerVersion: "1.1.1"
...
}
...
};
Other CLI options are the same as in the native Cairo compiler.
To build more complex Cairo 1 projects, read about hardhat starknet-build
.
starknet-build
$ npx hardhat starknet-build [PATH...] [--scarb-command <STRING>] [--skip-validate]
Builds Scarb projects.
Each of the provided paths is recursively looked into while searching for Scarb projects. If no paths are provided, the default contracts directory is traversed.
Each project must be a valid Scarb project with lib.cairo and Scarb.toml in its root. The toml file must have sierra
and casm
set to true
under [[target.starknet-contract]]
. If you know what you are doing, you can skip the validation by providing --skip-validate
.
In code, load the generated contracts with an underscore-separated string:
starknet.getContractFactory("<PACKAGE_NAME>_<CONTRACT_NAME>");
E.g. if your toml specifies name = MyPackage
and there is a contract called FooContract in your source files, you would load it with:
// alternatively prepend the directory name to avoid ambiguity, but be sure to apply the underscore syntax
starknet.getContractFactory("MyPackage_FooContract");
The name of the file where the contract was defined doesn't play a role.
The plugin doesn't have a default Scarb command yet. You need to provide a scarbCommand
(either an exact command or the path to it) under starknet
in your hardhat config file, or you can override that via --scarb-command <COMMAND>
.
starknet-verify
$ npx hardhat starknet-verify [--starknet-network <NAME>] [--path <PATH>] [<DEPENDENCY_PATH> ...] [--address <CONTRACT_ADDRESS>] [--compiler-version <COMPILER_VERSION>] [--license <LICENSE_SCHEME>] [--contract-name <CONTRACT_NAME>] [--account-contract]
Queries Voyager to verify the contract deployed at <CONTRACT_ADDRESS>
using the source files at <PATH>
and any number of <DEPENDENCY_PATH>
.
Like in the previous command, this plugin relies on --starknet-network
, but will default to 'alphaGoerli' network in case this parameter is not passed.
The verifier expects <COMPILER_VERSION>
to be passed on request. Supported compiler versions are listed here in the dropdown menu.
We pass --account-contract
to tell the verifier that the contract is of type account.
For <LICENSE_SCHEME>
the command takes No License (None) as default license scheme. Here is a list of available options.
starknet-plugin-version
$ npx hardhat starknet-plugin-version
Prints the version of the plugin.
run
Using --starknet-network
with hardhat run
currently does not have effect. Use the network
property of the starknet
object in your hardhat config file.
amarna
$ npx hardhat amarna
Runs Amarna, the static-analyzer and linter for Cairo, in a Docker container. The output from amarna goes in out.sarif
file.
Use flag --script
to run custom ./amarna.sh
file to use Amarna with custom rules and args.
You need to have Docker installed and running to use hardhat amarna
.
test
Introduces the --starknet-network
option to the existing hardhat test
task.
API
Adding this plugin to your project expands Hardhat's runtime with a starknet
object. It can be imported with:
import { starknet } from "hardhat";
// or
const starknet = require("hardhat").starknet;
To see all the utilities introduced by the starknet
object, check this out.
Testing
Relying on the above described API makes it easier to interact with your contracts and test them.
To test Starknet contracts with Mocha, use the regular Hardhat test
task which expects test files in your designated test directory:
$ npx hardhat test
Read more about the network used in tests in the Runtime network section. These examples are inspired by the official Starknet Python tutorial.
Important notes
BigInt
is used becausefelt
may be too big for javascript. Use it likeBigInt("10")
or, since ES2020, like10n
.- All function names, argument names and return value names should be referred to by the names specified in contract source files.
- The argument of
getContractFactory
is the name or the path of the source of the target contract:- if providing a path, it should be relative to the project root or the contracts directory:
getContractFactory("contracts/subdir/MyContract.cairo")
getContractFactory("subdir/MyContract.cairo")
- the extension can be omitted:
getContractFactory("subdir/MyContract")
getContractFactory("MyContract")
- if providing a path, it should be relative to the project root or the contracts directory:
- Nested arrays are currently not supported (eg.
core::array::Array::<core::array::Array<felt252>>
)
Test examples
Setup
import { expect } from "chai";
import { starknet } from "hardhat";
// or
const expect = require("chai").expect;
const starknet = require("hardhat").starknet;
describe("My Test", function () {
this.timeout(...); // Recommended to use a big value if interacting with Alpha Goerli
Deploy / load contract
/**
* Assumes there is a file MyContract.cairo whose compilation artifacts have been generated.
* The contract is assumed to have:
* - constructor function constructor(initial_balance: felt)
* - external function increase_balance(amount: felt) -> (res: felt)
* - view function get_balance() -> (res: felt)
*/
it("should load a previously deployed contract", async function () {
const contractFactory = await starknet.getContractFactory("MyContract");
const contract = contractFactory.getContractAt("0x123..."); // address of a previously deployed contract
});
it("should declare class and deploy", async function() {
// not compatible with accounts deployed with Starknet CLI
const account = await starknet.OpenZeppelinAccount.getAccountFromAddress(...);
const contractFactory = await starknet.getContractFactory("MyContract");
// will call declare version 2 if contract is cairo 1
const txHash = await account.declare(contractFactory); // class declaration
const classHash = await contractFactory.getClassHash();
const constructorArgs = { initial_balance: 0 };
const options = { maxFee: ... };
// implicitly invokes UDC
const contract = await account.deploy(contractFactory, constructorArgs, options);
});
Arrays
/**
* The contract is assumed to have:
* - view function sum_array(a_len: felt, a: felt*) -> (res: felt)
*/
it("should work with arrays", async function () {
const contract = ...;
// you don't have to specify the array length separately
const { res } = await contract.call("sum_array", { a: [1, 2, 3] });
expect(res).to.deep.equal(BigInt(6));
});
Tuples
/**
* The contract is assumed to have:
* - view function sum_pair(pair: (felt, felt)) -> (res : felt)
* - view func sum_named_pair(pair : (x : felt, y : felt) -> (res : felt)
* - using PairAlias = (x : felt, y : felt)
* - view func sum_type_alias(pair : PairAlias) -> (res : felt)
*/
it("should work with tuples", async function () {
const contract = ...;
// notice how the pair tuple is passed as javascript array
const { res } = await contract.call("sum_pair", { pair: [10, 20] });
... = await contract.call("sum_named_pair", { pair: { x: 10, y: 20 } });
... = await contract.call("sum_type_alias", { pair: { x: 10, y: 20 } });
expect(res).to.deep.equal(BigInt(30));
});
Fee estimation
it("should estimate fee", async function () {
const account = await starknet.OpenZeppelinAccount.createAccount({
salt: "0x42",
privateKey: OZ_ACCOUNT_PRIVATE_KEY
});
const estimatedFee = await account.estimateDeployAccountFee();
await account.deployAccount(); // computes max fee implicitly
// Every fee estimation returns an object with gas information
const contractFactory = await starknet.getContractFactory("contract");
const declareFee = await account.estimateDeclareFee(contractFactory);
await account.declare(contractFactory); // computes max fee implicitly
const deployFee = await account.estimateDeployFee(contractFactory);
const contract = await account.deploy(contractFactory); // computes max fee implicitly
const invokeFee = await account.estimateFee(contract, "method", { arg1: 10n });
await account.invoke(contract, "method", { arg1: 10n }); // computes max fee implicitly
// computes message estimate fee
const estimatedMessageFee = await l2contract.estimateMessageFee("deposit", {
from_address: L1_CONTRACT_ADDRESS,
amount: 123,
user: 1
});
});
Delegate Proxy
it("should forward to the implementation contract", async function () {
const implementationFactory = await starknet.getContractFactory("contract");
const account = ...;
const txHash = await account.declare(implementationFactory);
const implementationClassHash = await implementationFactory.getClassHash();
const proxyFactory = await starknet.getContractFactory("delegate_proxy");
await account.declare(proxyFactory);
const proxy = await account.deploy(proxyFactory, {
implementation_hash_: implementationClassHash
});
proxy.setImplementation(implementationFactory);
const { res: initialProxyBalance } = await proxy.call("get_balance");
});
Transaction information and receipt with events
it("should return transaction data and transaction receipt", async function () {
const contract: StarknetContract = ...;
console.log("Deployment transaction hash:", contract.deployTxHash);
const transaction = await starknet.getTransaction(contract.deployTxHash);
console.log(transaction);
const account = ...;
const txHash = await account.invoke(contract, "increase_balance", { amount: 10 });
const receipt = await starknet.getTransactionReceipt(txHash);
const decodedEvents = contract.decodeEvents(receipt.events);
const txTrace = await starknet.getTransactionTrace(txHash);
// decodedEvents contains hex data array converted to a structured object
// { name: "increase_balance_called", data: { current_balance: 0n, amount: 10n } }
});
For more usage examples, including tuple, array and struct support check sample-test.ts of starknet-hardhat-example.
Devnet examples
L1-L2 communication (Postman message exchange with Devnet)
Exchanging messages between L1 (Ganache, Hardhat node, Ethereum testnet) and L2 (only supported for starknet-devnet) can be done using this plugin:
- Ensure there is an available L1 network and that you know its RPC endpoint URL.
- Load an L1 Messaging contract using
starknet.devnet.loadL1MessagingContract
. - Call
starknet.devnet.flush
after youinvoke
your contract and want to propagate your message. - When running a hardhat test or script which relies on
network["config"]
, specify the name of an L1 network you defined inhardhat.config
. Usenpx hardhat test --network <NETWORK_NAME>
. Networklocalhost
is predefined in hardhat so--network localhost
should work if you're using e.g.npx hardhat node
as the L1 network. - Check this example for more info.
it("should exchange messages with Devnet", async function() {
await starknet.devnet.loadL1MessagingContract(...);
// Exact syntax may vary depending on your L1 contract interaction library
const l1contract = ...;
const l2contract = ...;
// If the L1 function is expected to send a message to L2,
// it needs to be paid for by providing some value to the transaction
await l1contract.send(..., {
value: 1000 // pay for L1->L2 message
});
await starknet.devnet.flush();
const account = ...;
await account.invoke(l2contract, ...);
await starknet.devnet.flush();
});
Mock message between L1 and L2
To send mock messages between L1 and L2 the following two functions can be used. Detailed example can be found here.
starknet.devnet.sendMessageToL2(...); // Sends message to L2
starknet.devnet.consumeMessageFromL2(...); // Sends message from L2 to L1
Restart
Devnet can be restarted by calling starknet.devnet.restart()
. All of the deployed contracts, blocks and storage updates will be restarted to the empty state.
await starknet.devnet.restart();
Dumping
Use starknet.devnet.dump()
to maintain the Devnet instance from the plugin.
await starknet.devnet.dump(path); // path to dump file (eg. dump.pkl)
Loading
Dumped Devnet instance can be loaded using starknet.devnet.load()
.
await starknet.devnet.load(path); // path for dump file (eg. dump.pkl)
Advancing time
The plugin comes with support for Devnet's timestamp management.
The time offset for each generated block can be increased by calling starknet.devnet.increaseTime()
. The time for the next block can be set by calling starknet.devnet.setTime()
, with subsequent blocks keeping the set offset.
Warning: block time can be set in the past and lead to unexpected behaviour!
await starknet.devnet.setTime(1000); // time in seconds
await starknet.devnet.increaseTime(1000); // time in seconds
Creating an empty block
Devnet offers empty block creation. It can be useful to make available those changes that take effect with the next block.
const emptyBlock = await starknet.devnet.createBlock();
Mint tokens to an account
Devnet allows minting token. You can call starknet.devnet.mint
like this
const lite_mode = true; // Optional, Default true
await starknet.devnet.mint(account_address, 2e12, lite_mode);
Debugging contracts
To debug Starknet contracts, you can use print()
in cairo hints in your contract, and the printed lines will appear in Devnet's log.
Compile with --disable-hint-validation
flag to allow hints.
hardhat starknet-compile-deprecated --disable-hint-validation
For example, when calling the following increase_balance
with input 25
.
@external
func increase_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
amount: felt
) {
let (res) = balance.read();
%{ print( "Adding balance..." ) %}
%{ print( ids.res ) %}
balance.write(res + amount);
let (afterUpdate) = balance.read();
%{ print( ids.afterUpdate ) %}
return ();
}
Devnet logs look like this,
Adding balance...
0
25
If you want to have your debug lines printed in the same terminal as your hardhat script/test, use integrated-devnet
with stdout
set to "STDOUT"
as documented in runtime network section.
Configure the plugin
Specify custom configuration by editing your project's hardhat.config.ts
(or hardhat.config.js
).
Cairo 0 compilation
Cairo 0 compilation is by default done using the latest stable Dockerized compiler. If you want to use an older Cairo 0 compiler, specify the full semver string (available versions):
module.exports = {
starknet: {
// Docker image tailored to the machine is pulled. If not applied, the `-arm` suffix is applied to the version name if on an arm64 machine.
dockerizedVersion: "0.11.2"
}
...
};
If you cannot use Docker (e.g. on Mac), you'll need to have the cairo-lang
Python package installed locally and python3
command runnable. Use one of the following:
module.exports = {
starknet: {
// venv: "active" <- for the active virtual environment or global environment
// venv: "path/to/my-venv" <- for env created with e.g. `python -m venv path/to/my-venv`
// Python virtual environment can be created with: pyenv, poetry, conda, miniconda, ...
venv: "<VENV_PATH>"
}
};
Building Cairo 1 projects
This plugin comes with a Scarb wrapper. Read about how to use it in this section. If not via CLI, you may specify the Scarb command via:
module.exports = {
starknet: {
scarbCommand: "scarb" // or alternatively an exact path to the desired command
}
};
Request Timeout
Default requestTimeout is 30s. It can be changed using the following configuration. You may need to increase the timeout value in some situation (declaring large smart contract).
module.exports = {
starknet: {
requestTimeout: 90_000 // 90s
}
};
Paths
Prefer providing absolute paths when using the plugin. If a relative path is provided, it will be resolved relative to the root of the project.
module.exports = {
paths: {
// Defaults to "contracts" (the same as `paths.sources`).
starknetSources: "my-own-starknet-path",
// Defaults to "starknet-artifacts".
// Has to be different from the value set in `paths.artifacts` (which is used by core Hardhat and has a default value of `artifacts`).
starknetArtifacts: "also-my-own-starknet-path",
// Same purpose as the `--cairo-path` argument of the `starknet-compile-deprecated` command
// Allows specifying the locations of imported files, if necessary.
cairoPaths: ["my/own/cairo-path1", "also/my/own/cairo-path2"]
}
...
};
Runtime network
To set the network used in your Hardhat scripts/tests, use starknet["network"]
or the --starknet-network
CLI option. Not specifying one will default to using alpha-goerli. Do not confuse this network with Hardhat's default --network
option which refers to the L1 network.
A faster approach is to use starknet-devnet, a Ganache-like local testnet.
module.exports = {
starknet: {
network: "myNetwork"
},
networks: {
devnet: { // this way you can also specify it with `--starknet-network devnet`
url: "http://127.0.0.1:5050"
}
}
...
};
Predefined networks include alpha-goerli
, alpha-goerli2
, alpha-mainnet
and integrated-devnet
.
Runtime network - Integrated Devnet
starknet-devnet is available out of the box as a starknet network called integrated-devnet
. By default, it will spawn Devnet using its Docker image and listening on http://127.0.0.1:5050
. Target it via the hardhat config file or --starknet-network integrated-devnet
. Using integrated-devnet
makes forking of existing blockchains very easy.
By defining/modifying networks["integratedDevnet"]
in your hardhat config file, you can specify:
- the version of dockerized Devnet to use
- a Python environment with installed starknet-devnet (can be active environment); this will avoid using the dockerized version
- CLI arguments to be used on Devnet startup: options
- where output should be flushed (either to the terminal or to a file).
Dockerized Integrated Devnet
Dockerized integrated-devnet is the default mode, but can be specified via the dockerizedVersion
property of integratedDevnet
:
- the full image can be provided:
shardlabs/starknet-devnet:<TAG>
(Pythonic Devnet) - Docker image infoshardlabs/starknet-devnet-rs:<TAG>
(Rust Devnet) - Docker image info
- if just
<TAG>
is provided, it defaults to Pythonic Devnet
Integrated Devnet config example
module.exports = {
starknet: {
network: "integrated-devnet"
},
networks: {
integratedDevnet: {
url: "http://127.0.0.1:5050",
// venv: "active" <- for the active virtual environment with installed starknet-devnet
// venv: "path/to/venv" <- for env with installed starknet-devnet (created with e.g. `python -m venv path/to/venv`)
venv: "<VENV_PATH>",
// This section covers the VM selection in Pythonic Devnet (starknet-devnet): Python VM or Rust VM
// This is distinct from selecting between Pythonic Devnet and Rust Devnet, which can be done via `dockerizedVersion`
// vmLang: "python" <- use python vm (default value)
// vmLang: "rust" <- use rust vm
// (rust vm is available out of the box using dockerized integrated-devnet)
// (rustc and cairo-rs-py required using installed devnet)
// read more here : https://0xspaceshard.github.io/starknet-devnet/docs/guide/run/#run-with-the-rust-implementation-of-cairo-vm
vmLang: "<VM_LANG>",
// or use dockerized Devnet by specifying [IMAGE:]<TAG> (if IMAGE omitted - defaults to "shardlabs/starknet-devnet")
dockerizedVersion: "<DEVNET_VERSION>",
// dockerizedVersion: "shardlabs/starknet-devnet:<TAG>",
// dockerizedVersion: "shardlabs/starknet-devnet-rs:<TAG>",
// optional devnet CLI arguments, read more here: https://0xspaceshard.github.io/starknet-devnet/docs/guide/run
args: ["--gas-price", "2000000000", "--fork-network", "alpha-goerli"],
// stdout: "logs/stdout.log" <- dumps stdout to the file
stdout: "STDOUT", // <- logs stdout to the terminal
// stderr: "logs/stderr.log" <- dumps stderr to the file
stderr: "STDERR" // <- logs stderr to the terminal
}
}
...
};
Installing third-party libraries
If you want to install a third-party Cairo library and be able to import it in your Cairo files, use the following pattern:
With npm packages:
- Install (example package:
influenceth__cairo_math_64x61@npm:@influenceth/cairo-math-64x61
)
npm install --save-dev influenceth__cairo_math_64x61@npm:@influenceth/cairo-math-64x61
- Edit the
paths.cairoPaths
section of yourhardhat.config
file (docs):
paths: {
cairoPaths: ["./node_modules"];
}
- Import
from influenceth__cairo_math_64x61.contracts.Math64x61 import Math64x61_ONE, Math64x61_mul
With pip packages:
- Install (example package:
openzeppelin-cairo-contracts
)
pip install openzeppelin-cairo-contracts
- If you are installing in a virtual environment, edit the
paths.cairoPaths
section of yourhardhat.config
file (docs) as:
paths: {
// this directory contains the openzeppelin directory
cairoPaths: ["path/to/cairo_venv/lib/python3.9/site-packages"],
}
- Import
from openzeppelin.token.erc20.library import ERC20
With non-npm git repositories:
If you want to install directly from a git repo that doesn't contain package.json
, you cannot use npm i
. However, yarn
supports this.
- Install (example package:
https://github.com/OpenZeppelin/cairo-contracts
)
yarn add openzeppelin__cairo_contracts@git+https://git@github.com/OpenZeppelin/cairo-contracts.git
Using starknet.getContractFactory
with third-party libraries
This paragraph assumes you've read and run 3rd party library installation.
The example package used is https://github.com/OpenZeppelin/cairo-contracts
so you may want to check non-npm git repos.
- Compile
$ npx hardhat starknet-compile-deprecated node_modules/openzeppelin__cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20.cairo
- Get contract factory
const contractFactory = await starknet.getContractFactory(
"node_modules/openzeppelin__cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20"
);
Recompilation
Recompilation is performed when contracts are updated or when artifacts are missing. A file will be created with the name cairo-files-cache.json
to handle caching. Recompilation is handled before the following CLI commands are executed.
This feature is only guaranteed to work with Cairo 0 contracts.
npx hardhat run
npx hardhat test
This feature is turned off by default and is specified in the hardhat.config.ts
file.
module.exports = {
starknet: {
recompile: true // <- to switch recompilation on
}
};
Account
In Starknet, an account is a contract through which you interact with other contracts. Its usage is exemplified earlier in the docs and in the example repo.
There are several Starknet account implementations; this plugin supports the following as properties of hre.starknet
:
Create account
import { starknet } from "hardhat";
const account = await starknet.OpenZeppelinAccount.createAccount();
const accountFromOptions = await starknet.OpenZeppelinAccount.createAccount({
salt: "0x123", // salt to always deploy to an expected address
privateKey: process.env.MY_KEY // the key only known to you, the public key will be inferred
});
console.log(account.address);
Fund account
After creating the account, you need to fund it (give it some ETH):
- On alpha-goerli use this faucet.
- On alpha-goerli2 use this
- On starknet-devnet call
starknet.devnet.mint()
which uses devnet faucet. - Alternatively transfer some amount from an already funded account to the newly deployed account.
If you're facing issues loading the account you've just funded, check out this issue.
Get balance
To find out the balance of your account on the current Starknet network, you can use starknet.getBalance
:
import { starknet } from "hardhat";
const someBalance = starknet.getBalance("0x123...def")
const myAccount = ...;
const myAccountBalance = starknet.getBalance(myAccount.address);
Deploy account
After funding the account, you need to deploy it (in case of ArgentAccount
, this will also take care of initialization):
await account.deployAccount({ maxFee: ... });
Alternatively deploy your account by running this script.
To successfully deploy ArgentAccount
, the chain you are interacting with is expected to have ArgentAccount
contracts declared. Alpha Goerli and Alpha Mainnet satisfy this criterion, but if you're working with Devnet, this is most easily achievable by running Devnet forked from e.g. Alpha Goerli.
Reuse account
To retrieve an already deployed Account, use the getAccountFromAddress
method. Do not use this method for accounts deployed by e.g. Starknet CLI (those are modified OZ accounts that are not compatible with the OZ version supported by this plugin). What may be especially useful are predeployed+predefined accounts that come with Devnet (retrieve them with starknet.devnet.getPredeployedAccounts()
).
const account = await starknet.OpenZeppelinAccount.getAccountFromAddress(
accountAddress,
process.env.PRIVATE_KEY
);
Interact through account
Use the invoke
method of Account
to invoke (change the state), but call
method of StarknetContract
to call (read the state).
await account.invoke(contract, "increase_balance", { amount });
const { res: amount } = await contract.call("get_balance");
Once your account is funded and deployed, you can specify a max fee greater than zero:
await account.invoke(contract, "foo", { arg1: ... }, { maxFee: BigInt(...) });
If you don't specify a maxFee
, one will be calculated for you by applying an overhead of 50% to the result of fee estimation. You can also customize the overhead by providing a value for overhead
:
// maxFee will be 40% of estimated fee; if overhead not provided, the default value is used.
await account.invoke(contract, "foo", { arg1: ... }, { overhead: 0.4 });
Set the rawInput
option to true
to suppress validation of the args passed to the contract function:
// pass a direct array
await account.invoke(contract, "foo", ["10", "20"], { rawInput: true });
await contract.call("bar", ["30", "40"], { rawInput: true });
Multicalls
You can also use the Account object to perform multi{invokes, fee estimations}.
const interactionArray = [
{
toContract: contract1,
functionName: "increase_balance",
calldata: { amount: 10n }
},
{
toContract: contract2,
functionName: "increase_balance",
calldata: { amount: 20n }
}
];
const fee = await account.multiEstimateFee(interactionArray);
const txHash = await account.multiInvoke(interactionArray);
Guardian
Unlike OpenZeppelin account, Argent account offers guardian functionality. The guardian is by default not set (the guardian key is undefined), but if you want to change it, cast the account
to ArgentAccount
and execute setGuardian
.
await argentAccount.setGuardian(process.env.GUARDIAN_PRIVATE_KEY, { maxFee: 1e18 });
// to unset it, use an undefined key
await argentAccount.setGuardian(undefined, { maxFee: 1e18 });
More examples
An example Hardhat project using this plugin can be found here.