From b58b0a166ca80a901165a73804ecb48f3937fc4d Mon Sep 17 00:00:00 2001 From: PhilippeR26 Date: Tue, 8 Apr 2025 15:33:27 +0200 Subject: [PATCH 1/2] docs: complete review of guides for v7 --- www/docs/guides/L1message.md | 2 +- www/docs/guides/connect_account.md | 31 +- www/docs/guides/connect_contract.md | 2 +- www/docs/guides/connect_network.md | 136 ++++-- www/docs/guides/create_account.md | 81 ++-- www/docs/guides/create_contract.md | 8 +- www/docs/guides/define_call_message.md | 2 +- www/docs/guides/doc_scripts/deployBraavos.ts | 454 +++++++++++++------ www/docs/guides/estimate_fees.md | 154 +++++-- www/docs/guides/interact.md | 104 ++--- www/docs/guides/intro.md | 6 +- www/docs/guides/outsideExecution.md | 18 +- www/docs/guides/pictures/SelectWallet.png | Bin 24395 -> 38265 bytes www/docs/guides/signature.md | 4 +- www/docs/guides/use_ERC20.md | 9 +- www/docs/guides/walletAccount.md | 6 +- www/docs/guides/websocket_channel.md | 2 +- www/docs/guides/what_s_starknet.js.md | 6 +- www/docusaurus.config.js | 4 +- 19 files changed, 670 insertions(+), 359 deletions(-) diff --git a/www/docs/guides/L1message.md b/www/docs/guides/L1message.md index 22708d55b..a946e5929 100644 --- a/www/docs/guides/L1message.md +++ b/www/docs/guides/L1message.md @@ -10,7 +10,7 @@ You can exchange messages between L1 & L2 networks: - L2 Starknet testnet ↔️ L1 Sepolia ETH testnet. - L2 local Starknet devnet ↔️ L1 local ETH testnet (anvil, ...). -You can find an explanation of the global mechanism [here](https://docs.starknet.io/documentation/architecture_and_concepts/L1-L2_Communication/messaging-mechanism/). +You can find an explanation of the global mechanism [here](https://docs.starknet.io/architecture-and-concepts/network-architecture/messaging-mechanism/). Most of the code for this messaging process will be written in Cairo, but Starknet.js provides some functionalities for this subject. diff --git a/www/docs/guides/connect_account.md b/www/docs/guides/connect_account.md index fa775c329..fa080c133 100644 --- a/www/docs/guides/connect_account.md +++ b/www/docs/guides/connect_account.md @@ -15,9 +15,9 @@ You need 2 pieces of data: import { Account, RpcProvider } from 'starknet'; ``` -## Connect to a pre-deployed account in Starknet-devnet-rs +## Connect to a pre-deployed account in Starknet-devnet -When you launch starknet-devnet-rs, 10 accounts are pre-deployed with 100 dummy ETH in each. +When you launch starknet-devnet, 10 accounts are pre-deployed with 100 dummy ETH & STRK in each. Addresses and private keys are displayed on the console at initialization. @@ -34,23 +34,17 @@ Public key : 0x7e52885445756b313ea16849145363ccb73fb4ab0440dbac333cf9d13de82b9 Then you can use this code: ```typescript -// initialize provider +// initialize provider for devnet v0.3.0 const provider = new RpcProvider({ nodeUrl: 'http://127.0.0.1:5050/rpc' }); // initialize existing pre-deployed account 0 of Devnet -const privateKey = '0x71d7bb07b9a64f6f78ac4c816aff4da9'; const accountAddress = '0x64b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691'; +const privateKey = '0x71d7bb07b9a64f6f78ac4c816aff4da9'; const account = new Account(provider, accountAddress, privateKey); ``` Your account is now connected, and you can use it. -```typescript -const account = new Account(provider, accountAddress, privateKey); -``` - -> Take care that this added parameter is a string, NOT a number. - ## 👛 Connect to an existing account (in any network) The code is the same, you just have to: @@ -65,7 +59,7 @@ For example, to connect an existing account on testnet, with a private key store import * as dotenv from 'dotenv'; dotenv.config(); -// initialize provider +// initialize rpc v0.8 provider const provider = new RpcProvider({ nodeUrl: `${myNodeUrl}` }); // initialize existing account const privateKey = process.env.OZ_NEW_ACCOUNT_PRIVKEY; @@ -74,6 +68,21 @@ const accountAddress = '0x051158d244c7636dde39ec822873b29e6c9a758c6a9812d005b628 const account = new Account(provider, accountAddress, privateKey); ``` +:::tip +If you are connected to a rpc v0.7 node, and if you want to use ETH as fees for this account: + +```typescript +const myAccount = new Account( + provider, + accountAddress, + privateKey, + undefined, + ETransactionVersion.V2 +); +``` + +::: + ## Connect to an account that uses Ethereum signature As a consequence of account abstraction, you can find accounts that uses Ethereum signature logical. diff --git a/www/docs/guides/connect_contract.md b/www/docs/guides/connect_contract.md index fd5921070..be3651c1a 100644 --- a/www/docs/guides/connect_contract.md +++ b/www/docs/guides/connect_contract.md @@ -11,7 +11,7 @@ You need 2 pieces of data: - the address of the contract - the ABI file of the contract (or the compiled/compressed contract file, that includes the abi) -> If you don't have the abi file, the `provider.getClassAt()` and `provider.getClassByHash()` commands will recover the compressed contract file. As these methods generate a significant workload for the sequencer/node, it's recommended to store the result on your computer to be able to reuse it later without using the provider each time: +> If you don't have the abi file, the `provider.getClassAt()` and `provider.getClassByHash()` commands will recover the compressed contract file. As these methods generate a significant workload for the node, it's recommended to store the result on your computer to be able to reuse it later without using the provider each time: ```typescript import fs from 'fs'; diff --git a/www/docs/guides/connect_network.md b/www/docs/guides/connect_network.md index cb59f412c..00d3c6d5c 100644 --- a/www/docs/guides/connect_network.md +++ b/www/docs/guides/connect_network.md @@ -4,7 +4,7 @@ sidebar_position: 3 # RpcProvider object 🔌 connect to the network -The first thing to do is to define with which network you want to interact. +The first thing to do is to define with which network you want to interact (Mainnet, Testnet, Devnet, ...). Then you need to select a node. A node is a safe way to connect with the Starknet blockchain. You can use: @@ -13,37 +13,38 @@ Then you need to select a node. A node is a safe way to connect with the Starkne - your own node, located on your local computer or in your local network. > you can spin up your own node with Pathfinder, Juno, Papyrus, Deoxys, ... - a local development node, that simulates a Starknet network. Useful for devs to perform quick tests without spending precious fee token. - > Main development devnets are Starknet-devnet-rs, Madara, ... + > Main development devnets are Starknet-devnet, Madara, ... Each node is communicating with Starknet.js using a rpc specification. Most of the nodes are able to use 2 rpc spec versions. -For example, this node is compatible with v0.6.0 & v0.7.0, using the following entry points : +For example, this node is compatible with v0.7.1 & v0.8.0, using the following entry points : -- "https://free-rpc.nethermind.io/sepolia-juno/v0_6" - "https://free-rpc.nethermind.io/sepolia-juno/v0_7" +- "https://free-rpc.nethermind.io/sepolia-juno/v0_8" From rpc spec v0.5.0, you can request the rpc spec version that uses a node address : ```typescript const resp = await myProvider.getSpecVersion(); console.log('rpc version =', resp); -// result : rpc version = 0.7.0 +// result : rpc version = 0.8.0 ``` On Starknet.js side, you have to select the proper version, to be in accordance with the node you want to use : -| Rpc spec version of your node | Starknet.js version to use | -| :---------------------------: | ---------------------------- | -| v0.4.0 | Starknet.js v5.21.1 | -| v0.5.0 | Starknet.js v5.23.0 | -| v0.5.1 | Starknet.js v5.29.0 & v6.1.0 | -| v0.6.0 | Starknet.js v6.23.1 | -| v0.7.1 | Starknet.js v6.23.1 | +| Rpc spec version of your node | Starknet.js version to use | +| :---------------------------: | ----------------------------- | +| v0.4.0 | Starknet.js v5.21.1 | +| v0.5.0 | Starknet.js v5.23.0 | +| v0.5.1 | Starknet.js v5.29.0 or v6.1.0 | +| v0.6.0 | Starknet.js v6.24.1 | +| v0.7.1 | Starknet.js v6.24.1 or v7.0.1 | +| v0.8.0 | Starknet.js v7.0.1 | :::note -Each Starknet.js version 6.x.x is compatible with 2 rpc spec versions, and recognize automatically the spec version if not provided. +From version 6.x.x, Starknet.js is compatible with 2 rpc spec versions. ::: -With the `RpcProvider` class, you define the Starknet Rpc node to use. +With the `RpcProvider` class, you define the Starknet Rpc node to use: ```typescript import { RpcProvider } from 'starknet'; @@ -51,9 +52,46 @@ import { RpcProvider } from 'starknet'; ## Connect your DAPP to an RPC node provider +### Available nodes + +**Mainnet network:** + +| Node | with public url | with API key | +| -----------------------: | :--------------: | :--------------: | +| Alchemy | No | v0_6, v0_7 | +| Infura | No | v0_7 | +| Blast | v0_6, v0_7, v0_8 | v0_6, v0_7, v0_8 | +| Nethermind | v0_6, v0_7, v0_8 | No | +| Lava | v0_6, v0_7, v0_8 | v0_8 | +| Local Pathfinder v0.16.2 | v0_6, v0_7, v0_8 | N/A | +| Local Juno v0.14.2 | v0_6, v0_7, v0_8 | N/A | + +**Sepolia Testnet network:** + +| Node | with public url | with API key | +| -----------------------: | :--------------: | :----------: | +| Alchemy | No | v0_6, v0_7 | +| Infura | No | v0_7 | +| Blast | v0_6, v0_7, v0_8 | No | +| Nethermind | v0_6, v0_7, v0_8 | No | +| Lava | v0_6, v0_7, v0_8 | No | +| Local Pathfinder v0.16.2 | v0_6, v0_7, v0_8 | N/A | +| Local Juno v0.14.2 | v0_6, v0_7, v0_8 | N/A | + +**Local Starknet-devnet network:** + +| Node | with public url | with API key | +| ---------------------: | :-------------: | :----------: | +| Starknet-devnet v0.2.4 | v0_7 | N/A | +| Starknet-devnet v0.3.0 | v0_8 | N/A | + +:::note +This status has been performed 02/apr/2025. +::: + ### Default Rpc node -If you don't want to use a specific node, or to handle an API key, you can use by default (using Rpc spec 0.7.0): +If you don't want to use a specific node, or to handle an API key, you can use by default (using Rpc spec 0.8.0): ```typescript const myProvider = new RpcProvider({ nodeUrl: constants.NetworkName.SN_SEPOLIA }); @@ -71,38 +109,59 @@ Some examples of RpcProvider instantiation to connect to RPC node providers: ### Mainnet ```typescript -// Infura node rpc 0.5.1 for Mainnet: +// Infura node rpc 0.7.0 for Mainnet: const providerInfuraMainnet = new RpcProvider({ nodeUrl: 'https://starknet-mainnet.infura.io/v3/' + infuraKey, + specVersion: '0.7', }); -// Blast node rpc 0.7.0 for Mainnet (0.4, 0.5 & 0_6 also available): +// Blast node rpc 0.8.0 for Mainnet (0.6 & 0_7 also available): const providerBlastMainnet = new RpcProvider({ - nodeUrl: 'https://starknet-mainnet.blastapi.io/' + blastKey + '/rpc/v0_7', + nodeUrl: 'https://starknet-mainnet.blastapi.io/' + blastKey + '/rpc/v0_8', }); -// Lava node rpc 0.6.0 for Mainnet: +// Lava node rpc 0.8.0 for Mainnet: const providerMainnetLava = new RpcProvider({ nodeUrl: 'https://g.w.lavanet.xyz:443/gateway/strk/rpc-http/' + lavaMainnetKey, }); -// Alchemy node rpc 0.6.0 for Mainnet: +// Alchemy node rpc 0.7.0 for Mainnet (0_6 also available): const providerAlchemyMainnet = new RpcProvider({ - nodeUrl: 'https://starknet-mainnet.g.alchemy.com/starknet/version/rpc/v0_6/' + alchemyKey, + nodeUrl: 'https://starknet-mainnet.g.alchemy.com/starknet/version/rpc/v0_7/' + alchemyKey, + specVersion: '0.7', }); -// Public Nethermind node rpc 0.7.0 for Mainnet (0_6 also available): +// Public Nethermind node rpc 0.8.0 for Mainnet (0_6 & 0_7 also available): const providerMainnetNethermindPublic = new RpcProvider({ - nodeUrl: 'https://free-rpc.nethermind.io/mainnet-juno/v0_7', + nodeUrl: 'https://free-rpc.nethermind.io/mainnet-juno/v0_8', }); -// Public Blast node rpc 0.7.0 for Mainnet (0.4, 0.5 & 0_6 also available) : +// Public Blast node rpc 0.8.0 for Mainnet (0.6 & 0_7 also available): const providerBlastMainnet = new RpcProvider({ - nodeUrl: 'https://starknet-mainnet.public.blastapi.io/rpc/v0_7', + nodeUrl: 'https://starknet-mainnet.public.blastapi.io/rpc/v0_8', }); -// Public Lava node rpc 0.6.0 for Mainnet: +// Public Lava node rpc 0.8.0 for Mainnet (0.6 & 0_7 also available): const providerLavaMainnet = new RpcProvider({ - nodeUrl: 'https://json-rpc.starknet-mainnet.public.lavanet.xyz', + nodeUrl: 'https://rpc.starknet.lava.build/rpc/v0_8', }); ``` > Take care to safely manage your API key. It's a confidential item! +:::tip +If the rpc version of the node is 0.7, use : + +```typescript +const myProvider = new RpcProvider({ + nodeUrl: `${myNodeUrl}`, + specVersion: '0.7', +}); +``` + +If you are not sure of the rpc version (0.7 or 0.8), use: + +```typescript +const myProvider = await RpcProvider.create({ nodeUrl: `${myNodeUrl}` }); +``` + +This line of code is slow, as it performs a request to the node. +::: + ### Goerli Testnet :::info @@ -112,17 +171,22 @@ The Goerli testnet is no more in service. ### Sepolia Testnet ```typescript -// Infura node rpc 0.5.1 for Sepolia Testnet : +// Infura node rpc 0.7.0 for Sepolia Testnet : const providerInfuraSepoliaTestnet = new RpcProvider({ nodeUrl: 'https://starknet-sepolia.infura.io/v3/' + infuraKey, + specVersion: '0.7', }); -// Public Nethermind node rpc 0.7.0 for Sepolia Testnet (0_6 also available) : +// Public Nethermind node rpc 0.8.0 for Sepolia Testnet (0_6 & 0_7 also available) : const providerSepoliaTestnetNethermindPublic = new RpcProvider({ - nodeUrl: 'https://free-rpc.nethermind.io/sepolia-juno/v0_7', + nodeUrl: 'https://free-rpc.nethermind.io/sepolia-juno/v0_8', +}); +// Public Blast node rpc 0.8.0 for Sepolia Testnet (0_6 & 0_7 also available) : +const providerSepoliaTestnetBlastPublic = new RpcProvider({ + nodeUrl: 'https://starknet-sepolia.public.blastapi.io/rpc/v0_8', }); -// Public Blast node rpc 0.7.0 for Sepolia Testnet (0_6 also available) : +// Public Lava node rpc 0.8.0 for Sepolia Testnet (0_6 & 0_7 also available) : const providerSepoliaTestnetBlastPublic = new RpcProvider({ - nodeUrl: 'https://starknet-sepolia.public.blastapi.io/rpc/v0_7', + nodeUrl: 'https://rpc.starknet-testnet.lava.build/rpc/v0_8', }); ``` @@ -133,14 +197,14 @@ const providerSepoliaTestnetBlastPublic = new RpcProvider({ For a local [Pathfinder](https://github.com/eqlabs/pathfinder) node: ```typescript -const provider = new RpcProvider({ nodeUrl: '127.0.0.1:9545/rpc/v0_7' }); +const provider = new RpcProvider({ nodeUrl: '127.0.0.1:9545/rpc/v0_8' }); ``` Your node can be located in your local network (example: Pathfinder node running on a computer in your network, launched with this additional option: `--http-rpc 0.0.0.0:9545`). You can connect with: ```typescript -const provider = new RpcProvider({ nodeUrl: '192.168.1.99:9545/rpc/v0_7' }); +const provider = new RpcProvider({ nodeUrl: '192.168.1.99:9545/rpc/v0_8' }); ``` ### Juno @@ -148,14 +212,14 @@ const provider = new RpcProvider({ nodeUrl: '192.168.1.99:9545/rpc/v0_7' }); For a local [Juno](https://github.com/NethermindEth/juno) node initialize the provider with: ```typescript -const provider = new RpcProvider({ nodeUrl: 'http://127.0.0.1:6060/v0_7' }); +const provider = new RpcProvider({ nodeUrl: 'http://127.0.0.1:6060/v0_8' }); ``` > If Juno is running on a separate computer in your local network, don't forget to add the option `--http-host 0.0.0.0` when launching Juno. ## Devnet -Example of a connection to a local development node (rpc 0.7.0), with Starknet-devnet-rs v0.0.6: +Example of a connection to a local development node (rpc 0.8.0), with Starknet-devnet v0.3.0: ```typescript const provider = new RpcProvider({ nodeUrl: 'http://127.0.0.1:5050/rpc' }); diff --git a/www/docs/guides/create_account.md b/www/docs/guides/create_account.md index 03bae01e6..32fe502bc 100644 --- a/www/docs/guides/create_account.md +++ b/www/docs/guides/create_account.md @@ -18,7 +18,7 @@ Creating an account is a bit tricky; you have several steps: ## Create an OZ (Open Zeppelin) account -Here, we will create a wallet with the Open Zeppelin smart contract v0.8.1. The contract class is already implemented in Testnet. +Here, we will create a wallet with the Open Zeppelin smart contract v0.17.0. The contract class is already implemented in Testnet. This contract is coded in Cairo 1. ```typescript @@ -28,17 +28,17 @@ import { Account, constants, ec, json, stark, RpcProvider, hash, CallData } from ### Compute address ```typescript -// connect provider (Mainnet or Sepolia) +// connect rpc 0.8 provider (Mainnet or Sepolia) const provider = new RpcProvider({ nodeUrl: `${myNodeUrl}` }); -// new Open Zeppelin account v0.8.1 +// new Open Zeppelin account v0.17.0 // Generate public and private key pair. const privateKey = stark.randomAddress(); console.log('New OZ account:\nprivateKey=', privateKey); const starkKeyPub = ec.starkCurve.getStarkKey(privateKey); console.log('publicKey=', starkKeyPub); -const OZaccountClassHash = '0x061dac032f228abef9c6626f995015233097ae253a7f72d68552db02f2971b8f'; +const OZaccountClassHash = '0x540d7f5ec7ecf317e68d48564934cb99259781b1ee3cedbbc37ec5337f8e688'; // Calculate future address of the account const OZaccountConstructorCallData = CallData.compile({ publicKey: starkKeyPub }); const OZcontractAddress = hash.calculateContractAddressFromHash( @@ -50,27 +50,27 @@ const OZcontractAddress = hash.calculateContractAddressFromHash( console.log('Precalculated account address=', OZcontractAddress); ``` -If you want a specific private key, replace `stark.randomAddress`()` with your choice. +If you want a specific private key, replace `stark.randomAddress()` with your choice. Then you have to fund this address! How to proceed is out of the scope of this guide, but you can for example: -- Transfer ETH from another wallet. -- Bridge ETH to this Starknet address. +- Transfer STRK from another wallet. +- Bridge STRK to this Starknet address. - Use a faucet. (https://starknet-faucet.vercel.app/) -- Mint ETH on starknet-devnet-rs, like so: +- Mint STRK on starknet-devnet, like so: ```bash -// ETH -curl -X POST http://127.0.0.1:5050/mint -d '{"address":"0x04a093c37ab61065d001550089b1089922212c60b34e662bb14f2f91faee2979","amount":50000000000000000000}' -H "Content-Type:application/json" // STRK curl -X POST http://127.0.0.1:5050/mint -d '{"address":"0x04a093c37ab61065d001550089b1089922212c60b34e662bb14f2f91faee2979","amount":50000000000000000000,"unit":"FRI"}' -H "Content-Type:application/json" +// ETH +curl -X POST http://127.0.0.1:5050/mint -d '{"address":"0x04a093c37ab61065d001550089b1089922212c60b34e662bb14f2f91faee2979","amount":50000000000000000000,"unit":"WEI"}' -H "Content-Type:application/json" ``` ### Deployment of the new account -If you have sent enough funds to this new address, you can go forward to the final step: +If you have sent enough STRK to this new address, you can go forward to the final step: ```typescript const OZaccount = new Account(provider, OZcontractAddress, privateKey); @@ -89,6 +89,10 @@ console.log('✅ New OpenZeppelin account created.\n address =', contract_addr Here, we will create a wallet with the Argent smart contract v0.4.0. The contract class is already implemented in the networks. +:::caution +Smart ArgentX accounts can't be used outside of the ArgentX wallet. With Starknet.js, use only standard ArgentX accounts. +::: + ```typescript import { Account, @@ -107,7 +111,7 @@ import { ### Compute address ```typescript -// connect provider +// connect rpc 0.8 provider const provider = new RpcProvider({ nodeUrl: `${myNodeUrl}` }); //new Argent X account v0.4.0 @@ -142,7 +146,7 @@ Then you have to fund this address. ### Deployment of the new account -If you have sent enough funds to this new address, you can go forward to the final step: +If you have sent enough STRK to this new address, you can go forward to the final step: ```typescript const accountAX = new Account(provider, AXcontractAddress, privateKeyAX); @@ -161,12 +165,12 @@ console.log('✅ ArgentX wallet deployed at:', AXcontractFinalAddress); ## Create a Braavos account -More complicated, a Braavos account needs a proxy and a specific signature. Starknet.js is handling only Starknet standard signatures; so we need extra code to handle this specific signature for account creation. These nearly 200 lines of code are not displayed here but are available in a module [here](./doc_scripts/deployBraavos.ts). +More complicated, a Braavos account needs a proxy and a specific signature. Starknet.js is handling only Starknet standard signatures; so we need extra code to handle this specific signature for account creation. These nearly 400 lines of code are not displayed here but are available in a module [here](./doc_scripts/deployBraavos.ts). We will deploy hereunder a Braavos account in devnet. So launch starknet-devnet with these parameters: ```bash -starknet-devnet --seed 0 --fork-network 'https://free-rpc.nethermind.io/sepolia-juno/v0_7' +starknet-devnet --seed 0 --fork-network 'https://free-rpc.nethermind.io/sepolia-juno/v0_8' ``` Initialization: @@ -207,8 +211,10 @@ console.log('Calculated account address=', BraavosProxyAddress); ```typescript // estimate fees -const estimatedFee = await estimateBraavosAccountDeployFee(privateKeyBraavos, providerDevnet); -console.log('calculated fee =', estimatedFee); +const estimatedFee = await estimateBraavosAccountDeployFee(privateKeyBraavos, providerDevnet, { + version: ETransactionVersion.V3, +}); +console.log('calculated fees =', estimatedFee); ``` ### Deploy account @@ -219,22 +225,22 @@ const { data: answer } = await axios.post( 'http://127.0.0.1:5050/mint', { address: BraavosProxyAddress, - amount: 10_000_000_000_000_000_000, + amount: 100_000_000_000_000_000_000, + unit: 'FRI', }, { headers: { 'Content-Type': 'application/json' } } ); -console.log('Answer mint =', answer); // 10 ETH +console.log('Answer mint =', answer); // 100 STRK // deploy Braavos account const { transaction_hash, contract_address: BraavosAccountFinalAddress } = - await deployBraavosAccount(privateKeyBraavos, providerDevnet, estimatedFee); -// estimatedFee is optional + await deployBraavosAccount(privateKeyBraavos, providerDevnet); console.log('Transaction hash =', transaction_hash); await providerDevnet.waitForTransaction(transaction_hash); -console.log('✅ Braavos wallet deployed at', BraavosAccountFinalAddress); +console.log('✅ Braavos account deployed at', BraavosAccountFinalAddress); ``` -The computed address has been funded automatically by minting a new dummy ETH in Starknet devnet! +The computed address has been funded automatically by minting dummy STRK in Starknet devnet! ## Create an Ethereum account @@ -249,8 +255,9 @@ Below is an example of account creation in Sepolia Testnet. const privateKeyETH = '0x45397ee6ca34cb49060f1c303c6cb7ee2d6123e617601ef3e31ccf7bf5bef1f9'; const ethSigner = new EthSigner(privateKeyETH); const ethFullPublicKey = await ethSigner.getPubKey(); -const accountEthClassHash = '0x23e416842ca96b1f7067693892ed00881d97a4b0d9a4c793b75cb887944d98d'; -const myCallData = new CallData(ethAccountAbi); +// OpenZeppelin v0.17.0 : +const accountEthClassHash = '0x3940bc18abf1df6bc540cabadb1cad9486c6803b95801e57b6153ae21abfe06'; +const myCallData = new CallData(sierraContract.abi); const accountETHconstructorCalldata = myCallData.compile('constructor', { public_key: ethFullPublicKey, }); @@ -270,7 +277,7 @@ console.log('Pre-calculated ETH account address =', contractETHaddress); > const myPrivateKey = eth.ethRandomPrivateKey(); > ``` -Then you have to fund this address. +Then you have to fund this address with some STRK. ### Deployment of the new account @@ -283,12 +290,12 @@ const deployPayload = { constructorCalldata: accountETHconstructorCalldata, addressSalt: salt, }; -const { suggestedMaxFee: feeDeploy } = await ethAccount.estimateAccountDeployFee(deployPayload); -const { transaction_hash, contract_address } = await ethAccount.deployAccount( - deployPayload, - { maxFee: stark.estimatedFeeToMaxFee(feeDeploy, 100) } - // Extra fee to fund the validation of the transaction -); +const estimatedFees = await ethAccount.estimateAccountDeployFee(deployPayload, { + skipValidate: false, +}); +const { transaction_hash, contract_address } = await ethAccount.deployAccount(deployPayload, { + skipValidate: false, +}); await provider.waitForTransaction(transaction_hash); console.log('✅ New Ethereum account final address =', contract_address); ``` @@ -315,7 +322,7 @@ You can entirely customize the wallet - for example: The only limitation is your imagination... -Here is an example of a customized wallet, including super administrator management, on a local starknet-devnet-rs: +Here is an example of a customized wallet, including super administrator management, on a local starknet-devnet: > launch `cargo run --release -- --seed 0` before using this script @@ -329,7 +336,7 @@ import axios from 'axios'; // connect provider const provider = new RpcProvider({ network: 'http://127.0.0.1:5050/rpc' }); -// initialize existing pre-deployed account 0 of Devnet-rs +// initialize existing pre-deployed account 0 of Devnet const privateKey0 = '0x71d7bb07b9a64f6f78ac4c816aff4da9'; const accountAddress0 = '0x64b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691'; const account0 = new Account(provider, accountAddress0, privateKey0); @@ -369,7 +376,11 @@ console.log('Precalculated account address=', AAcontractAddress); // fund account address before account creation const { data: answer } = await axios.post( 'http://127.0.0.1:5050/mint', - { address: AAcontractAddress, amount: 50_000_000_000_000_000_000, lite: true }, + { + address: AAcontractAddress, + amount: 50_000_000_000_000_000_000, + unit: 'FRI', + }, { headers: { 'Content-Type': 'application/json' } } ); console.log('Answer mint =', answer); diff --git a/www/docs/guides/create_contract.md b/www/docs/guides/create_contract.md index 6f826eacb..9bb8f8391 100644 --- a/www/docs/guides/create_contract.md +++ b/www/docs/guides/create_contract.md @@ -46,10 +46,10 @@ const account0 = new Account(provider, account0Address, privateKey0); // Declare & deploy Test contract in devnet const compiledTestSierra = json.parse( - fs.readFileSync('./compiledContracts/test.sierra').toString('ascii') + fs.readFileSync('./compiledContracts/test.contract_class.json').toString('ascii') ); const compiledTestCasm = json.parse( - fs.readFileSync('./compiledContracts/test.casm').toString('ascii') + fs.readFileSync('./compiledContracts/test.compiled_contract_class.json').toString('ascii') ); const deployResponse = await account0.declareAndDeploy({ contract: compiledTestSierra, @@ -201,4 +201,6 @@ await provider.waitForTransaction(declareResponse.transaction_hash); console.log('✅ Test Completed.'); ``` -> If the class is already declared, `declare()` will fail. You can also use `declareIfNot()` to not fail in this case. +:::tip +If the class is already declared, `declare()` will fail. You can also use `declareIfNot()` to not fail in this case. +::: diff --git a/www/docs/guides/define_call_message.md b/www/docs/guides/define_call_message.md index 6165c51a6..106706d06 100644 --- a/www/docs/guides/define_call_message.md +++ b/www/docs/guides/define_call_message.md @@ -129,7 +129,7 @@ bytes31 is similar to shortString. You can send to Starknet.js methods: string. ```typescript -await myContract.my_function('Token', '0x0x534e5f4d41494e'); // send 2 shortStrings +await myContract.my_function('Token', '0x534e5f4d41494e'); // send 2 shortStrings ``` To encode yourself a string: diff --git a/www/docs/guides/doc_scripts/deployBraavos.ts b/www/docs/guides/doc_scripts/deployBraavos.ts index 0848be974..04936cfa4 100644 --- a/www/docs/guides/doc_scripts/deployBraavos.ts +++ b/www/docs/guides/doc_scripts/deployBraavos.ts @@ -1,54 +1,119 @@ -// Collection of functions for Braavos account creation -// coded with Starknet.js v5.11.1, 01/jun/2023 +// Collection of functions for Braavos account v1.1.0 creation +// coded with Starknet.js v7.0.1 (08/apr/2025) import { - BigNumberish, - CairoVersion, - CallData, - Calldata, - DeployAccountContractPayload, - DeployAccountContractTransaction, - DeployContractResponse, - EstimateFeeDetails, - InvocationsSignerDetails, - RawCalldata, - RpcProvider, - constants, ec, hash, num, + constants, + CallData, stark, + BigNumberish, + type RpcProvider, + type V2InvocationsSignerDetails, + type DeployAccountSignerDetails, + type V2DeployAccountSignerDetails, + type V3DeployAccountSignerDetails, + type V3InvocationsSignerDetails, + type UniversalDetails, + type V3TransactionDetails, + type EstimateFeeResponse, +} from 'starknet'; +import { + type DeployContractResponse, + type Calldata, + type DeployAccountContractPayload, + type EstimateFeeDetails, + type CairoVersion, + type DeployAccountContractTransaction, } from 'starknet'; +import { + EDAMode, + EDataAvailabilityMode, + ETransactionVersion, + ETransactionVersion2, + ETransactionVersion3, + type ResourceBounds, +} from '@starknet-io/types-js'; + +const BraavosBaseClassHash = '0x3d16c7a9a60b0593bd202f660a28c5d76e0403601d9ccc7e4fa253b6a70c201'; +const BraavosAccountClassHash = '0x2c8c7e6fbcfb3e8e15a46648e8914c6aa1fc506fc1e7fb3d1e19630716174bc'; -const BraavosProxyClassHash: BigNumberish = - '0x03131fa018d520a037686ce3efddeab8f28895662f019ca3ca18a626650f7d1e'; -const BraavosInitialClassHash = '0x5aa23d5bb71ddaa783da7ea79d405315bafa7cf0387a74f4593578c3e9e6570'; -const BraavosAccountClassHash = '0x2c2b8f559e1221468140ad7b2352b1a5be32660d0bf1a3ae3a054a4ec5254e4'; // will probably change over time +type CalcV2DeployAccountTxHashArgs = { + contractAddress: BigNumberish; + classHash: BigNumberish; + constructorCalldata: Calldata; + salt: BigNumberish; + version: `${ETransactionVersion2}`; + maxFee: BigNumberish; + chainId: constants.StarknetChainId; + nonce: BigNumberish; +}; + +type CalcV3DeployAccountTxHashArgs = { + contractAddress: BigNumberish; + classHash: BigNumberish; + compiledConstructorCalldata: Calldata; + salt: BigNumberish; + version: `${ETransactionVersion3}`; + chainId: constants.StarknetChainId; + nonce: BigNumberish; + nonceDataAvailabilityMode: EDAMode; + feeDataAvailabilityMode: EDAMode; + resourceBounds: ResourceBounds; + tip: BigNumberish; + paymasterData: BigNumberish[]; +}; export function getBraavosSignature( - BraavosProxyAddress: BigNumberish, - BraavosProxyConstructorCallData: RawCalldata, - starkKeyPubBraavos: BigNumberish, - version: bigint, - max_fee: BigNumberish, - chainId: constants.StarknetChainId, - nonce: bigint, + details: DeployAccountSignerDetails, privateKeyBraavos: BigNumberish ): string[] { - const txnHash = hash.calculateDeployAccountTransactionHash( - BraavosProxyAddress, - BraavosProxyClassHash, - BraavosProxyConstructorCallData, - starkKeyPubBraavos, - version, - max_fee, - chainId, - nonce - ); + const starkKeyPubBraavos = ec.starkCurve.getStarkKey(num.toHex(privateKeyBraavos)); + let txnHash: string = ''; + if (Object.values(ETransactionVersion3).includes(details.version as any)) { + const det = details as V3DeployAccountSignerDetails; + const v3det = stark.v3Details(det); + txnHash = hash.calculateDeployAccountTransactionHash({ + contractAddress: det.contractAddress, + classHash: det.classHash, + compiledConstructorCalldata: det.constructorCalldata, + salt: det.addressSalt, + version: det.version, + chainId: det.chainId, + nonce: det.nonce, + nonceDataAvailabilityMode: stark.intDAM(v3det.nonceDataAvailabilityMode), + feeDataAvailabilityMode: stark.intDAM(v3det.feeDataAvailabilityMode), + tip: v3det.tip, + paymasterData: v3det.paymasterData, + resourceBounds: v3det.resourceBounds, + } as CalcV3DeployAccountTxHashArgs); + } else if (Object.values(ETransactionVersion2).includes(details.version as any)) { + const det = details as V2DeployAccountSignerDetails; + txnHash = hash.calculateDeployAccountTransactionHash({ + ...det, + salt: det.addressSalt, + } as CalcV2DeployAccountTxHashArgs); + } else { + throw Error('unsupported signDeployAccountTransaction version'); + } + + // braavos v1.0.0 specific deployment signature : + // sig[0: 1] - r,s from stark sign on txn_hash + // sig[2] - actual impl hash - the impl hash we will replace class into + // sig[3: n - 2] - auxiliary data - hws public key, multisig, daily withdrawal limit etc + // sig[n - 2] - chain_id - guarantees aux sig is not replayed from other chain ids + // sig[n - 1: n] - r,s from stark sign on poseidon_hash(sig[2: n-2]) - const parsedOtherSigner = [0, 0, 0, 0, 0, 0, 0]; - const { r, s } = ec.starkCurve.sign( - hash.computeHashOnElements([txnHash, BraavosAccountClassHash, ...parsedOtherSigner]), + const parsedOtherSigner = Array(9).fill(0); + const { r, s } = ec.starkCurve.sign(txnHash, num.toHex(privateKeyBraavos)); + const txnHashPoseidon = hash.computePoseidonHashOnElements([ + BraavosAccountClassHash, + ...parsedOtherSigner, + details.chainId, + ]); + const { r: rPoseidon, s: sPoseidon } = ec.starkCurve.sign( + txnHashPoseidon, num.toHex(privateKeyBraavos) ); const signature = [ @@ -56,28 +121,24 @@ export function getBraavosSignature( s.toString(), BraavosAccountClassHash.toString(), ...parsedOtherSigner.map((e) => e.toString()), + details.chainId.toString(), + rPoseidon.toString(), + sPoseidon.toString(), ]; - console.log('signature =', signature); + console.log('Braavos special signature =', signature); return signature; } -const calcBraavosInit = (starkKeyPubBraavos: string) => +const BraavosConstructor = (starkKeyPubBraavos: string) => CallData.compile({ public_key: starkKeyPubBraavos }); -const BraavosProxyConstructor = (BraavosInitializer: Calldata) => - CallData.compile({ - implementation_address: BraavosInitialClassHash, - initializer_selector: hash.getSelectorFromName('initializer'), - calldata: [...BraavosInitializer], - }); export function calculateAddressBraavos(privateKeyBraavos: BigNumberish): string { const starkKeyPubBraavos = ec.starkCurve.getStarkKey(num.toHex(privateKeyBraavos)); - const BraavosInitializer = calcBraavosInit(starkKeyPubBraavos); - const BraavosProxyConstructorCallData = BraavosProxyConstructor(BraavosInitializer); + const BraavosProxyConstructorCallData = BraavosConstructor(starkKeyPubBraavos); return hash.calculateContractAddressFromHash( starkKeyPubBraavos, - BraavosProxyClassHash, + BraavosBaseClassHash, BraavosProxyConstructorCallData, 0 ); @@ -91,107 +152,238 @@ async function buildBraavosAccountDeployPayload( constructorCalldata, contractAddress: providedContractAddress, }: DeployAccountContractPayload, - { nonce, chainId, version, maxFee }: InvocationsSignerDetails + invocationDetails: V2InvocationsSignerDetails | V3InvocationsSignerDetails ): Promise { const compiledCalldata = CallData.compile(constructorCalldata ?? []); const contractAddress = providedContractAddress ?? calculateAddressBraavos(privateKeyBraavos); - const starkKeyPubBraavos = ec.starkCurve.getStarkKey(num.toHex(privateKeyBraavos)); - const signature = getBraavosSignature( - contractAddress, - compiledCalldata, - starkKeyPubBraavos, - BigInt(version), - maxFee, - chainId, - BigInt(nonce), - privateKeyBraavos - ); - return { - classHash, - addressSalt, - constructorCalldata: compiledCalldata, - signature, - }; + if (Object.values(ETransactionVersion3).includes(invocationDetails.version as any)) { + const v3invocation = invocationDetails as V3InvocationsSignerDetails; + const details: V3DeployAccountSignerDetails = { + classHash, + constructorCalldata: constructorCalldata ?? [], + addressSalt: addressSalt ?? 0, + contractAddress, + ...v3invocation, + }; + const signature = getBraavosSignature(details, privateKeyBraavos); + return { + classHash, + addressSalt, + constructorCalldata: compiledCalldata, + signature, + }; + } else if (Object.values(ETransactionVersion2).includes(invocationDetails.version as any)) { + //tx V1 + const v2invocation = invocationDetails as V2InvocationsSignerDetails; + const details: V2DeployAccountSignerDetails = { + classHash, + constructorCalldata: constructorCalldata ?? [], + addressSalt: addressSalt ?? 0, + contractAddress, + ...v2invocation, + ...stark.v3Details({}), + }; + const signature = getBraavosSignature(details, privateKeyBraavos); + return { + classHash, + addressSalt, + constructorCalldata: compiledCalldata, + signature, + }; + } else { + throw Error('wrong version in buildBraavosAccountDeployPayload'); + } } export async function estimateBraavosAccountDeployFee( privateKeyBraavos: BigNumberish, provider: RpcProvider, - { blockIdentifier, skipValidate }: EstimateFeeDetails = {} -): Promise { - const version = hash.feeTransactionVersion; + { blockIdentifier, skipValidate, version: txVersion, tip: tip0 }: EstimateFeeDetails +): Promise { + console.log('start estimate fees...', txVersion); + const tip = tip0 ?? 0n; + const EstimateVersion = + txVersion === ETransactionVersion.V3 ? ETransactionVersion.F3 : ETransactionVersion2.F1; const nonce = constants.ZERO; const chainId = await provider.getChainId(); - const cairoVersion: CairoVersion = '0'; + const cairoVersion: CairoVersion = '1'; // dummy value, not used but mandatory const starkKeyPubBraavos = ec.starkCurve.getStarkKey(num.toHex(privateKeyBraavos)); - const BraavosProxyAddress = calculateAddressBraavos(privateKeyBraavos); - const BraavosInitializer = calcBraavosInit(starkKeyPubBraavos); - const BraavosProxyConstructorCallData = BraavosProxyConstructor(BraavosInitializer); - - const payload = await buildBraavosAccountDeployPayload( - privateKeyBraavos, - { - classHash: BraavosProxyClassHash.toString(), - addressSalt: starkKeyPubBraavos, - constructorCalldata: BraavosProxyConstructorCallData, - contractAddress: BraavosProxyAddress, - }, - { - nonce, - chainId, - version, - walletAddress: BraavosProxyAddress, - maxFee: constants.ZERO, - cairoVersion, - } - ); + const BraavosAccountAddress = calculateAddressBraavos(privateKeyBraavos); + const BraavosConstructorCallData = BraavosConstructor(starkKeyPubBraavos); - const response = await provider.getDeployAccountEstimateFee( - { ...payload }, - { version, nonce }, - blockIdentifier, - skipValidate - ); - const suggestedMaxFee = stark.estimatedFeeToMaxFee(response.overall_fee); + if (EstimateVersion == ETransactionVersion.F3) { + // transaction V3 for RPC0.8 + const payload: DeployAccountContractTransaction = await buildBraavosAccountDeployPayload( + privateKeyBraavos, + { + classHash: BraavosBaseClassHash.toString(), + addressSalt: starkKeyPubBraavos, + constructorCalldata: BraavosConstructorCallData, + contractAddress: BraavosAccountAddress, + }, + { + chainId, + nonce, + version: EstimateVersion, + walletAddress: BraavosAccountAddress, + cairoVersion: cairoVersion, + tip, + } as V3InvocationsSignerDetails + ); + console.log('estimate deploy payload V3 =', payload); + const v3det = stark.v3Details({}, '0.8'); + const response: EstimateFeeResponse = await provider.getDeployAccountEstimateFee( + { + classHash: BraavosBaseClassHash, + addressSalt: starkKeyPubBraavos, + constructorCalldata: BraavosConstructorCallData, + signature: payload.signature, + }, + { + nonce, + version: EstimateVersion, + ...v3det, + } as V3TransactionDetails, + blockIdentifier, + skipValidate + ); + console.log('response estimate fee V3 =', response); + const suggestedMaxFee = stark.estimateFeeToBounds({ + ...response, + overall_fee: response.overall_fee.toString(), + l1_gas_consumed: response.l1_gas_consumed.toString(), + l1_gas_price: response.l1_gas_price.toString(), + l2_gas_consumed: (response.l2_gas_consumed ?? 0n).toString(), + l2_gas_price: response.l1_data_gas_price.toString(), + l1_data_gas_consumed: response.l1_data_gas_consumed.toString(), + l1_data_gas_price: response.l1_data_gas_price.toString(), + }); + return { + resourceBounds: suggestedMaxFee, + feeDataAvailabilityMode: EDataAvailabilityMode.L1, + nonceDataAvailabilityMode: EDataAvailabilityMode.L1, + tip: 10 ** 13, // not handled in Starknet 0.13.3 + paymasterData: [], + }; + } else { + // V1 tx + const payload: DeployAccountContractTransaction = await buildBraavosAccountDeployPayload( + privateKeyBraavos, + { + classHash: BraavosBaseClassHash, + addressSalt: starkKeyPubBraavos, + constructorCalldata: BraavosConstructorCallData, + contractAddress: BraavosAccountAddress, + }, + { + nonce, + chainId, + version: EstimateVersion, + walletAddress: BraavosAccountAddress, + maxFee: constants.ZERO, + cairoVersion: cairoVersion, + } as V2InvocationsSignerDetails + ); + console.log('estimate payload V1 =', payload); + + const response = await provider.getDeployAccountEstimateFee( + { ...payload }, + { version: EstimateVersion, nonce }, + blockIdentifier, + skipValidate + ); + console.log('response estimate fee V1 =', response); + const suggestedMaxFee = stark.estimatedFeeToMaxFee(response.overall_fee); - return suggestedMaxFee; + return { maxFee: suggestedMaxFee }; + } +} + +type Version = typeof ETransactionVersion.V3 | typeof ETransactionVersion.F3; +export function isV3tx(version: string): boolean { + return [ETransactionVersion.V3, ETransactionVersion.F3].includes(version as Version); } export async function deployBraavosAccount( privateKeyBraavos: BigNumberish, provider: RpcProvider, - max_fee?: BigNumberish + maxFeeDetails?: UniversalDetails, + txVersion: ETransactionVersion = ETransactionVersion.V3 ): Promise { const nonce = constants.ZERO; + const chainId = await provider.getChainId(); + const cairoVersion: CairoVersion = '1'; // dummy value, not used but mandatory const starkKeyPubBraavos = ec.starkCurve.getStarkKey(num.toHex(privateKeyBraavos)); - console.log('pubkey =', starkKeyPubBraavos.toString()); - const BraavosProxyAddress = calculateAddressBraavos(privateKeyBraavos); - const BraavosInitializer = calcBraavosInit(starkKeyPubBraavos); - const BraavosProxyConstructorCallData = BraavosProxyConstructor(BraavosInitializer); - max_fee ??= await estimateBraavosAccountDeployFee(privateKeyBraavos, provider); - const version = hash.transactionVersion; - const signatureBraavos = getBraavosSignature( - BraavosProxyAddress, - BraavosProxyConstructorCallData, - starkKeyPubBraavos, - version, - max_fee, - await provider.getChainId(), - nonce, - privateKeyBraavos - ); - - return provider.deployAccountContract( - { - classHash: BraavosProxyClassHash.toString(), - addressSalt: starkKeyPubBraavos, - constructorCalldata: BraavosProxyConstructorCallData, - signature: signatureBraavos, - }, - { - nonce, - maxFee: max_fee, - version, - } - ); + const BraavosAccountAddress = calculateAddressBraavos(privateKeyBraavos); + const BraavosConstructorCallData = BraavosConstructor(starkKeyPubBraavos); + const feeDetails: UniversalDetails = + maxFeeDetails ?? + (await estimateBraavosAccountDeployFee(privateKeyBraavos, provider, { version: txVersion })); + const isV3 = isV3tx(txVersion); + if (isV3) { + const payload: DeployAccountContractTransaction = await buildBraavosAccountDeployPayload( + privateKeyBraavos, + { + classHash: BraavosBaseClassHash.toString(), + addressSalt: starkKeyPubBraavos, + constructorCalldata: BraavosConstructorCallData, + contractAddress: BraavosAccountAddress, + }, + { + chainId, + nonce, + version: txVersion, + walletAddress: BraavosAccountAddress, + cairoVersion: cairoVersion, + ...feeDetails, + } as V3InvocationsSignerDetails + ); + console.log('deploy payload V3 =', payload); + return provider.deployAccountContract( + { + classHash: BraavosBaseClassHash, + addressSalt: starkKeyPubBraavos, + constructorCalldata: BraavosConstructorCallData, + signature: payload.signature, + }, + { + nonce, + version: txVersion, + ...feeDetails, + } + ); + } else { + // V1 tx + const payload: DeployAccountContractTransaction = await buildBraavosAccountDeployPayload( + privateKeyBraavos, + { + classHash: BraavosBaseClassHash.toString(), + addressSalt: starkKeyPubBraavos, + constructorCalldata: BraavosConstructorCallData, + contractAddress: BraavosAccountAddress, + }, + { + nonce, + chainId, + version: txVersion as ETransactionVersion2, + walletAddress: BraavosAccountAddress, + maxFee: feeDetails.maxFee as BigNumberish, + cairoVersion: cairoVersion, + } + ); + console.log('deploy payload V1 =', payload); + return provider.deployAccountContract( + { + classHash: BraavosBaseClassHash, + addressSalt: starkKeyPubBraavos, + constructorCalldata: BraavosConstructorCallData, + signature: payload.signature, + }, + { + nonce, + maxFee: feeDetails.maxFee, + version: txVersion, + } + ); + } } diff --git a/www/docs/guides/estimate_fees.md b/www/docs/guides/estimate_fees.md index de24f8d3d..466c8643b 100644 --- a/www/docs/guides/estimate_fees.md +++ b/www/docs/guides/estimate_fees.md @@ -6,7 +6,7 @@ sidebar_position: 11 By default, all non-free Starknet commands (declare, deploy, invoke) work without any limitation of cost. -Nevertheless, you might want to inform the DAPP user of the cost of the incoming transaction before proceeding and requesting its validation. +You might want to inform the DAPP user of the cost of the incoming paid command before proceeding and requesting its validation. Starknet.js proposes several functions to estimate the fees: @@ -15,43 +15,72 @@ Starknet.js proposes several functions to estimate the fees: To estimate the cost to invoke a contract in the network: ```typescript -const { suggestedMaxFee: estimatedFee1 } = await account0.estimateInvokeFee({ +const { suggestedMaxFee, unit } = await account0.estimateInvokeFee({ contractAddress: testAddress, entrypoint: 'increase_balance', calldata: ['10', '30'], }); ``` -The result is in `estimatedFee1`, of type BigInt. Unit is WEI for "legacy" transactions, and FRI for V3 transactions. +The result is in `suggestedMaxFee`, of type BigInt. The unit of this number is in `unit`. it's WEI for "legacy" transactions, and FRI for V3 transactions. -The complete answer for a "legacy" transaction : +:::tip +More details about the complex subject of Starknet fees in [Starknet docs](https://docs.starknet.io/architecture-and-concepts/network-architecture/fee-mechanism/) +::: + +The complete answer for a rpc 0.7 "legacy" transaction : ```typescript { - overall_fee: 2499000034986n, - gas_consumed: 2499n, - gas_price: 1000000014n, + overall_fee: 123900000000000n, unit: 'WEI', - suggestedMaxFee: 3748500052479n, + l1_gas_consumed: 1047n, + l1_gas_price: 100000000000n, + l1_data_gas_consumed: 192n, + l1_data_gas_price: 100000000000n, + suggestedMaxFee: 185850000000000n, resourceBounds: { l2_gas: { max_amount: '0x0', max_price_per_unit: '0x0' }, - l1_gas: { max_amount: '0xabc', max_price_per_unit: '0x59682f15' } + l1_gas: { max_amount: '0x742', max_price_per_unit: '0x22ecb25c00' } } } ``` -The complete answer for a V3 transaction : +The complete answer for a rpc 0.7 V3 transaction : ```typescript { - overall_fee: 46098414083169n, - gas_consumed: 2499n, - gas_price: 18446744331n, + overall_fee: 123900000000000n, unit: 'FRI', - suggestedMaxFee: 69147621124753n, + l1_gas_consumed: 1047n, + l1_gas_price: 100000000000n, + l1_data_gas_consumed: 192n, + l1_data_gas_price: 100000000000n, + suggestedMaxFee: 185850000000000n, resourceBounds: { l2_gas: { max_amount: '0x0', max_price_per_unit: '0x0' }, - l1_gas: { max_amount: '0xabc', max_price_per_unit: '0x671447890' } + l1_gas: { max_amount: '0x742', max_price_per_unit: '0x22ecb25c00' } + } +} +``` + +The complete answer for a rpc 0.8 V3 transaction : + +```typescript +{ + overall_fee: 4188627200000000000n, + unit: 'FRI', + l1_gas_consumed: 0n, + l1_gas_price: 100000000000n, + l2_gas_consumed: 41886080n, + l2_gas_price: 100000000000n, + l1_data_gas_consumed: 192n, + l1_data_gas_price: 100000000000n, + suggestedMaxFee: 6282940800000000000n, + resourceBounds: { + l2_gas: { max_amount: '0x3beb240', max_price_per_unit: '0x22ecb25c00' }, + l1_gas: { max_amount: '0x0', max_price_per_unit: '0x22ecb25c00' }, + l1_data_gas: { max_amount: '0x120', max_price_per_unit: '0x22ecb25c00' } } } ``` @@ -61,78 +90,117 @@ The complete answer for a V3 transaction : To estimate the cost to declare a contract in the network: ```typescript -const { suggestedMaxFee: estimatedFee1 } = await account0.estimateDeclareFee({ +const { suggestedMaxFee } = await account0.estimateDeclareFee({ contract: compiledTest, classHash: testClassHash, }); ``` -The result is in `estimatedFee1`, of type BigInt. +The result is in `suggestedMaxFee`, of type BigInt. Units and full response format are the same than `invoke`. ## estimateDeployFee To estimate the cost to deploy a contract in the network: ```typescript -const { suggestedMaxFee: estimatedFee1 } = await account0.estimateDeployFee({ +const { suggestedMaxFee } = await account0.estimateDeployFee({ classHash: testClassHash, - // constructorCalldata is not necessary if the contract to deploy has no constructor + // `constructorCalldata` is not necessary if the contract to deploy has no constructor constructorCalldata: callData, }); ``` -The result is in `estimatedFee1`, of type BigInt. +The result is in `suggestedMaxFee`, of type BigInt. Units and full response format are the same than `invoke`. ## estimateAccountDeployFee To estimate the cost to deploy an account in the network: ```typescript -const { suggestedMaxFee: estimatedFee1 } = await account0.estimateAccountDeployFee({ +const { suggestedMaxFee } = await account0.estimateAccountDeployFee({ classHash: OZaccountClassHash, constructorCalldata: OZaccountConstructorCallData, contractAddress: OZcontractAddress, }); ``` -The result is in `estimatedFee1`, of type BigInt. +The result is in `suggestedMaxFee`, of type BigInt. Units and full response format are the same than `invoke`. ## Fee limitation -In all non-free functions, you can add an optional parameter limiting the fee consumption. -If the fee has been previously estimated, you can use this value for this parameter, but sometimes this value is under-evaluated: **don't hesitate to add a margin of approximately 10%**: +In some cases, a transaction can fail due to underestimation of the fees. You can increase these limits by setting a global config setting (default values are 50) : ```typescript -(estimatedFee1 * 11n) / 10n; +config.set('feeMarginPercentage', { + bounds: { + l1_gas: { + max_amount: 75, + max_price_per_unit: 60, + }, + l2_gas: { + max_amount: 100, + max_price_per_unit: 60, + }, + l1_data_gas: { + max_amount: 80, + max_price_per_unit: 70, + }, + }, + maxFee: 22, +}); ``` -You can also use the `stark.estimatedFeeToMaxFee` function: +:::note + +- Values are additional percentage: 75 means 75% additional fees. +- To get back to normal values: set all values to 50. + ::: + +Example for declaring, with 80% additional fees: ```typescript -import { stark } from 'starknet'; -stark.estimatedFeeToMaxFee(estimatedFee1, 0.1); +config.set('feeMarginPercentage', { + bounds: { + l1_gas: { + max_amount: 80, + max_price_per_unit: 80, + }, + l2_gas: { + max_amount: 80, + max_price_per_unit: 80, + }, + l1_data_gas: { + max_amount: 80, + max_price_per_unit: 80, + }, + }, + maxFee: 80, +}); +const declareResponse = await account0.declareIfNot({ contract: testSierra, casm: testCasm }); ``` -Example for declaring: +## Real fees paid + +After the processing of the transaction, you can read the fees that have really been paid : ```typescript -const { suggestedMaxFee: estimatedFee1 } = await account0.estimateDeclareFee({ - contract: compiledTest, +const txR = await provider.waitForTransaction(declareResponse.transaction_hash); +txR.match({ + success: (txR: SuccessfulTransactionReceiptResponse) => { + console.log('Fees paid =', txR.actual_fee); + }, + _: () => {}, }); - -const declareResponse = await account0.declare( - { contract: compiledTest }, - { maxFee: (estimatedFee1 * 11n) / 10n } -); ``` -## Real fee paid +For STRK fees, the result is: -After the processing of the transaction, you can read the fee that has really been paid : +```json +{ "unit": "FRI", "amount": "0x3a4f43814e180000" } +``` -```typescript -const txR = await provider.waitForTransaction(txH); -if (txR.isSuccess()) { - console.log('Fee paid =', txR.actual_fee); -} +For ETH fees : + +```json +{ "unit": "WEI", "amount": "0x70c6fff3c000" } ``` diff --git a/www/docs/guides/interact.md b/www/docs/guides/interact.md index ff8392f40..b76dce91a 100644 --- a/www/docs/guides/interact.md +++ b/www/docs/guides/interact.md @@ -8,11 +8,11 @@ Once your provider, contract, and account are connected, you can interact with t - you can read the memory of the contract, without fees. - you can write to memory, but you have to pay fees. - - On Mainnet, you have to pay fees with a bridged ETH token. - - On Testnet, you have to pay with a bridged Sepolia ETH token. - - On devnet, you have to pay with a dummy ETH token. + - On Mainnet, you have to pay fees with bridged STRK or ETH token. + - On Testnet, you have to pay with bridged Sepolia STRK or Sepolia ETH token. + - On devnet, you have to pay with dummy STRK or ETH token. -Your account should be funded enough to pay fees (0.01 ETH should be enough to start). +Your account should be funded enough to pay fees (20 STRK should be enough to start). ![](./pictures/Interact_contract.png) @@ -62,6 +62,10 @@ You have to invoke Starknet, with the use of the meta-class method: `contract.fu > After the invoke, you have to wait the incorporation of the modification of Balance in the network, with `await provider.waitForTransaction(transaction_hash)` +:::note +By default, you are executing transactions that uses STRK token to pay the fees. +::: + Here is an example of how to increase and check the balance: ```typescript @@ -99,84 +103,47 @@ console.log('Final balance =', bal2); `Contract.populate()` is the recommended method to define the parameters to call/invoke the Cairo functions. -## ✍️ Send a V3 transaction, paying fees with STRK +## ✍️ Send a transaction, paying fees with ETH + +You need to be connected to a node using Rpc 0.7: + +- Define `specVersion: '0.7'` when instantiating an RpcProvider +- Use `config.set('legacyMode', true)` to enable **V1** transactions (ETH fees) +- Use `logger.setLogLevel('ERROR')` if you want to remove the warnings when processing **V1** transactions + +```typescript +import { RpcProvider, Account, config, logger, ETransactionVersion } from 'starknet'; + +const myProvider = new RpcProvider({ + nodeUrl: 'https://free-rpc.nethermind.io/sepolia-juno/v0_7', + specVersion: '0.7', +}); -We have seen in the previous chapter how to send a "legacy" transaction, with fees paid in ETH. -You can also send transactions and pay the fees with the STRK token. It is called a V3 transaction. -To perform a such transaction, you need: +config.set('legacyMode', true); -- an account compatible with V3 transactions. -- Some STRK tokens in this account. -- a node with a rpc spec 0.6.0. -- Starknet.js v6. +logger.setLogLevel('ERROR'); +``` -You have to initialize the account this way : +With the above settings the code still uses **V3** transactions (STRK fees) with RPC **0.7** by default. To utilize **V1** transactions (ETH fees) there are two approaches: + +- either configure it at the `Account` instance level by setting the appropriate constructor parameter: ```typescript const account0 = new Account( - provider, + myProvider, accountAddress0, privateKey0, undefined, - constants.TRANSACTION_VERSION.V3 + ETransactionVersion.V2 ); ``` -By this way, all the transactions sent by this account are by default performed in V3 (paid with STRK). If the transactionVersion parameter is omitted, "legacy" transactions will be performed. - -One example of V3 transaction, using account.execute : - -```typescript -const myCall = myTestContract.populate('test_fail', [100]); -const maxQtyGasAuthorized = 1800n; // max quantity of gas authorized -const maxPriceAuthorizeForOneGas = 12n * 10n ** 9n; // max FRI authorized to pay 1 gas (1 FRI=10**-18 STRK) -console.log('max authorized cost =', maxQtyGasAuthorized * maxPriceAuthorizeForOneGas, 'FRI'); -const { transaction_hash: txH } = await account0.execute(myCall, { - version: 3, - maxFee: 10 ** 15, - feeDataAvailabilityMode: RPC.EDataAvailabilityMode.L1, - tip: 10 ** 13, - paymasterData: [], - resourceBounds: { - l1_gas: { - max_amount: num.toHex(maxQtyGasAuthorized), - max_price_per_unit: num.toHex(maxPriceAuthorizeForOneGas), - }, - l2_gas: { - max_amount: num.toHex(0), - max_price_per_unit: num.toHex(0), - }, - }, -}); -const txR = await provider.waitForTransaction(txH); -if (txR.isSuccess()) { - console.log('Paid fee =', txR.actual_fee); -} -``` - -Yes, it's much more complicated. Let's see in detail. -In fact, Starknet v0.13.0 is using few of these parameters : -`feeDataAvailabilityMode: RPC.EDataAvailabilityMode.L2` is not yet accepted. -`feeDataAvailabilityMode: RPC.EDataAvailabilityMode.L1` is accepted. -`maxFee : 10**15` : value not taken into account in V3 -`tip: 10**13` : value not yet taken into account -`paymasterData: []` : only empty value currently authorized +- or configure it for individual method invocations by setting the corresponding options parameter property: ```typescript -l1_gas: { - max_amount: num.toHex(2000n), // max quantity of gas authorized - max_price_per_unit: num.toHex(12n * 10n ** 9n) // max FRI authorized to pay 1 gas (here 12 G FRI) -}, -l2_gas: { - max_amount: num.toHex(0), // currently set to 0 - max_price_per_unit: num.toHex(0) // currently set to 0 -} +const res = await account0.execute(myCall, { version: 1 }); ``` -Take care that these gas values have to be `string` type. -In future versions, Starknet will uses all these parameters. -The `version` parameter is optional (account settings by default), and overtakes the `transactionVersion` parameter of the Account instantiation. Here, it's not really necessary to use this parameter, as the same transaction version has been already initialized in the account instantiation. - ## Sending sequential transactions If you intend to send sequential transactions through the contract object, like so: @@ -251,7 +218,7 @@ const result = await account.execute(myCall); const txR = await provider.waitForTransaction(result.transaction_hash); console.log(txR.statusReceipt, txR.value); -console.log(txR.isSuccess(), txR.isRejected(), txR.isReverted(), txR.isError()); +console.log(txR.isSuccess(), txR.isReverted(), txR.isError()); txR.match({ success: () => { @@ -266,9 +233,6 @@ txR.match({ success: (txR: SuccessfulTransactionReceiptResponse) => { console.log('Success =', txR); }, - rejected: (txR: RejectedTransactionReceiptResponse) => { - console.log('Rejected =', txR); - }, reverted: (txR: RevertedTransactionReceiptResponse) => { console.log('Reverted =', txR); }, diff --git a/www/docs/guides/intro.md b/www/docs/guides/intro.md index 232ea159a..ee8722836 100644 --- a/www/docs/guides/intro.md +++ b/www/docs/guides/intro.md @@ -18,7 +18,7 @@ npm install starknet@next ## Running tests locally -Local tests rely on [Starknet Devnet](https://github.com/0xSpaceShard/starknet-devnet-rs), a local testnet emulation. +Local tests rely on [Starknet Devnet](https://github.com/0xSpaceShard/starknet-devnet), a local testnet emulation. Launch a Devnet instance and run: @@ -41,7 +41,7 @@ npm run start # fires up a local documentation site Please check the Starknet documentation [here](https://docs.starknet.io/documentation/quick_start/declare_a_smart_contract/#compiling_a_smart_contract) to compile Starknet contracts. -Additional helpful resources can also be found at [OpenZeppelin](https://docs.openzeppelin.com/contracts-cairo/0.6.1/) documentation site. +Additional helpful resources can also be found at [OpenZeppelin](https://docs.openzeppelin.com/contracts-cairo/) documentation site. ## Interacting with contracts and accounts @@ -51,4 +51,4 @@ For some more extensive examples visit PhilippeR26's [workshop](https://git ## Contracts used in the guides -You can find the compiled contracts used in these guides in the [\_\_mocks\_\_](https://github.com/starknet-io/starknet.js/tree/develop/__mocks__/cairo/myAccountAbstraction/) directory. +You can find the compiled contracts used in these guides in the [\_\_mocks\_\_](https://github.com/starknet-io/starknet.js/tree/develop/__mocks__/cairo/) directory. diff --git a/www/docs/guides/outsideExecution.md b/www/docs/guides/outsideExecution.md index db3111bfb..6422038a5 100644 --- a/www/docs/guides/outsideExecution.md +++ b/www/docs/guides/outsideExecution.md @@ -18,16 +18,16 @@ Outside Execution provides several benefits: ### Check SNIP-9 Support The account that will sign the outside transaction has to be compatible with SNIP-9 (V1 or V2). -At mid-2024 : +At early-2025 : -| account | compatibility | -| :------------------: | :-----------: | -| ArgentX v0.3.0 | v1 | -| ArgentX v0.4.0 | v2 | -| Braavos v1.0.0 | v2 | -| OpenZeppelin v0.17.0 | v2 (\*) | +| account | compatibility | +| :-----------------: | :-----------: | +| ArgentX v0.3.0 | v1 | +| ArgentX v0.4.0 | v2 | +| Braavos v1.1.0 | v2 | +| OpenZeppelin v1.0.0 | v2 (\*) | -> (\*): only OpenZeppelin accounts including the `src9` component : +> (\*): only OpenZeppelin accounts including the `src9` component. Examples for v0.17.0: > Starknet account: class = [0x540d7f5ec7ecf317e68d48564934cb99259781b1ee3cedbbc37ec5337f8e688](https://voyager.online/class/0x0540d7f5ec7ecf317e68d48564934cb99259781b1ee3cedbbc37ec5337f8e688) > ETH account: class = [0x3940bc18abf1df6bc540cabadb1cad9486c6803b95801e57b6153ae21abfe06](https://voyager.online/class/0x3940bc18abf1df6bc540cabadb1cad9486c6803b95801e57b6153ae21abfe06) @@ -157,7 +157,7 @@ In this example, we want to sign, with a Ledger Nano X, several transactions at By this way, you can pre-sign some transactions with the Ledger, and if during the night something occurs, a backend can execute automatically some of these transactions, **in any order**. In this process, **the private key of the Ledger account is never exposed**. -First, create a Ledger account in devnet-rs. You will find some documentation [here](./signature.md#signing-with-a-ledger-hardware-wallet), and an example [here](https://github.com/PhilippeR26/starknet.js-workshop-typescript/blob/main/src/scripts/ledgerNano/4.deployLedgerAccount.ts). +First, create a Ledger account in devnet. You will find some documentation [here](./signature.md#signing-with-a-ledger-hardware-wallet), and an example [here](https://github.com/PhilippeR26/starknet.js-workshop-typescript/blob/main/src/scripts/ledgerNano/4.deployLedgerAccount.ts). The initial balances are : diff --git a/www/docs/guides/pictures/SelectWallet.png b/www/docs/guides/pictures/SelectWallet.png index 9fd5548576c65ade9607e2a557303a66851a8650..0f3395cbaaff70094b2b3772622565dc03a12c64 100644 GIT binary patch literal 38265 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%0D)Pq94xY}P7@g9{g z#s1$3jh-mgSQp6LeK5ikFCBlB<4ezjO=Hy%kmAkZx)}Tos6|C5yNd`7NJtaI3@9z& z=*-dIhOu{6*+5ZjW)=rBwu1Q`Z`<%UW=ig+i(#l=q{Knj#PwLJpnq_(|Afm`3) zUuK}z+4tzeQmbxhZod4FE)Dh;scQdVuHy?bFaqZ5@0%xZqQ1WO^;W$4&ZEJVO|Z&J zpVjbJe%ITtpyulxo2suHuq{hq?AjdivDlj>dnN@33T3{F9&2_jR>cc;_Ew=NJUX?1 zPf`s%XAUmlx?q}1z5mSS8jVQ(ZjZn^1G(>iwFCszOUWg~AYFg(%s|1W0STxvZ|&JLileri?9)?g|!f{w+W@r=NPJ| zH_xt3w4M_X45zX8uN{H1F{mY0JXssTX#pw}D0$heJvtrgEJe_vO34DLNMR2kGe%FB z(&cSg8n8^CZgYYlF`An+_5$Rhbin?x7ClWDtsb8wn+U$~C|-fM(ouyT8@c27HzTkX z7@3-|D=EAWVF$(OXU=plG~n_fH9}4Q-=gN9<;On}YQK7_L8E~vhA0r1d81oR$EAmW zJND;T-ebtMvu_w7IgBA*gP~0FO?aW?^9k2iF`(uR8dJu>xeso9b;>0L%+kJ&okAcE zMEB)^5NEBty2JgT-3^GRaLd62hH>kc!GVv3Op%AH(j~I@^cnY?BKF6hKaNTfuo&L{ z5;$lBiNBFAQ;w8kzMQG)+V}885Twlk*M-S`QvHOQwK0@h9E(OMn;xFhxz?|w(=&pBw&cl1>=-||0z(SK({bY<{k({ z8R)hi_lM|Ef&KvB=|`o&?znuq$C%XXfO()l5-);?C=2AV=b(ws{WtxqrrRp=iN_k& zFLL?jMGQ#REQc(|q96)q4Ru&lwVSEyGN%|RRCf?w&4tbt=6AEWhRGffC(Hb#IwR6- zv*$&pYfwIr0)X^3zUQjP1Rm@r$d?b(Fo2KN@u2-dRTJP2LAnPvl=v{;{FwKE@HEl; z6S(BBKt2S-HBgBJ;M5I9P~ippNj*xkKq>+_&)mq5%H!t^+zT1k||Q#dahkPKRax zpXzg&GD=Vy-%$bptfB;tys5I?pD!_;;^N}F5|@|1t;y%fX;)Pj71;15?ML^)bxGanQYCT}Nz1}qk)&&GeN)1%*i&8L|-8U&5#_AT4R0BXLGIXN{2FKS_-KB;eE@!SYR*HNvTAWxQ@HmNUz!K_z$f*z6`^gwuQ6e;)siWfau;~!kZ z*`D5H&sJ2o*Q2&97i3$}6JA<1^5Q-+u()YJib}gG}QR62`ng0qiUyyf2i- z?}8eO1Y8V#G6K(8@1yKZlem@~`#u0h4`4k4E6^O^L%{mo_R(l6A!#fUL|sOY^3I!}Xn0fQ(7Z9fqT;sqr6qs1G6VpDp7ZZ=sO$qrs*ylk z`$E#HB|Z$>*N*|ahYk)kN*dSM6))W_hj~sK?(_Ge!OH=?wRcI_s8QHpMWR?iG!(L& z517a~)dz%hkP-pGFGxI3V83|{Ko`UJ{h%&DR=euinOp)bOQEbc-f$4G2;97@j*J6X z<3MaW1`Z`Cg0(2Yn?f6QdcFUc?){X{Bc#p;He6y`+QE-A{ci|DAml>KJ46>wf*bvosvR$`jf4p4n7VoIm&2wJ;gbN37nj`o zHEs}PB3r#p95Q75JBjiBdO8!VH|N0NW$)rL2!zCw-;Au`CgTT<(=YeOEA+o|6j%1G zC7aKfaU4C&?z!eTt$|+qOZd(gr8Ge2`NL5K6r$KZ#{P&JTKohmnW*t-g?XT=0gVCp zpC#!5k@6a3-Y1V|I{oO3`4sZDaGsnalo(E)kcM1TV@5nk^4GJzbTWgDY3I)?@4piO zE>=N){HT$vAnDy%H7{DA!)~P?BMZwSXov(G8|C9Po-K_Om4!A)=C&V5X zbvU{6$kY{M+Z(n6`?bPgLT3C zOT5loTv8soRdoYrh0JiZWg8*4hS()cBXAZfMZxaKKQ zHE$bJ|H?1xJ6}ID4Bs*&dD3(6z`|@5Kh91t<|pC6!&|ldNM?V2x`Wkz;I)IwQISiH z!ggg=$MOwHB*^R;0z7_Ld@)s5t4utC!J9hgM+a)-oSnx`+3Q-SC67&mgSHb_EiDV+ ziv`B8%S4sufCqMxpuj@Uz%W~5YWd+q<&3Q`xIqP(C4jyFblKZWlov0yrE9)n@YZ|v zekJT9=DY;uLC{&e4LU=?g7mJU6Zk=;N{|W`z~{3k(zb!ZC0ItELs_n~rC>$;*GD#e zbVlZLdIaJj;`Vh=Mtm&Rnz#}oBh1>?0-zI!QFw}1(k){)(16`*3X~Jb@IC_jq}E!4 zVm3d`zKvL^5-8dJ`ix4&Q4pbg)fsvb5!RdE81y-G&~TYqxNrx3fby$zp~vbkS|mg@ zntas!-#5~2QJEPUCU3%Dml@*lf(;KG*M^?>11-9BzeP_|69RHt=aTzcG^nuw$0=AX zt12snz+|>D==FMIQB!`~X;{d#PRH-cytVi-45(_|Cj_NRoHosOSibaWQgZ z?r#GkIwjfmh){`)$bn&rs2DZ1Na9e(>|(VjJ{^MizsjO+d%rjM!K&hP6!`wQ;uP~j z@DTjrst}3bWO3Cl5O-^tHa9l-iDR}(Xce0QDdYRf7vR5_hrk+!jsOqJ=bV8up~wjE zAbh1wECUo0lo-GWyjlPF_2l@YWX)b?13RdgiQ)SeC^o}qmTb_yOM-tErq&2Sfxi*N z>CDeuW;++ph1|2-^{R}y6X{Kv$iHO_X21@w5lw<9e|JtJ%Z|6#k_5e(1(Kl_51VlM z(YCxm)_>~IMRkp3kni-N(+l~hYR<47$ z@;H}lO+tU4&JdqrEt3Q?a*>}3StQ%tA<3CZklvAEI`G(RjpEOLHT)F61a zxVwR82myhzqJ=>mpTU>l5d+{R<$m^S^hmC zH222&ChbI(M!gReD(!uLT8pV+sevSB890OhJY%XPrBCyp20jhM%vVOhkjxGv3M0aC zq1=`<87XKuh7TGcLw$_IPgGJBlW+{47-02LWa9`i&~C3Fp|C=e{xY;;u?W;8L5H6X z{iH}FNf1aBMW2#XyZ%oa{CF8zt_kjA#$wc>&eG1=eH0@v-bHOiage6_6T*c{<*bT@ z!I_h@QQ-#{;Ft&PNW#AvQP|}!ohgJN=ZVlgrkx40``PJZ%re+lgCg;=-wJTHy4JAY z#Up0p^wL4^lmZxt6Di}vImBIP;R`Ll)V=!kR@8rF^a+p}-ZZhCuJ;kYB4N~3B*&H} zJVat!_8~WhqHvI_Hw@6I`AiFY9oRBj89yqI-*C$rN~TN_|6aL zS4!eeBa;&oB}FrSnRr0IIz{Rtw6Q(?yJ69EwU7`e8yJI)jgW6eBKVRJ$Xz0AO5-RL zD86$Fz}AC}AeVrT$Y=v(9~ya0?z)+YG2V|qWJ_(kP1qSD0!JT(7)lLIHBG?drEc~S zKl*J^XPvQeg2<(mO09N*SVm@Q1QZrM5(rDdk+r4~&1kwwpem=WL!G}d0 zFt6D&mBRZd-fPpXaw*wZqJgi=pvlYOE~(D%KW+nt?7~ER28>Vtf2FYhv8&YvbpXce z@BVV%$&*Jz8`5~CUMfP<_w7f>s|&~fVr~0heO~_?YTN%D;{W_Lo&51Uc>ny;i2V1k z9yWgIQ~@K^%31zs>XC7q z$Nl}un_h_Uk`_M#UkbxxmkGlt^1fZhHRn-fyVS55R_)c6uV|T&2Z!Ysh8r6c2=88;IMtxxFDI)0Vl;Dfh_-F^`$8@ z*!k=KX@0ZLm2lZ^VR8N^{A^N7%l_bx4<(ptWCiv^q`2*DJU$f-neL>P>kfzogwRdK zn1$B7_d;EFikbKu=pVT<uiVa%U@dfD{&RLn{YDLS50uqd44A2pr5QuPT{3x@jr5fmK zB`hf+E(Gs&jFjT+a+0hI2_R>qp-Y9s9Q916G@1)Na~B2l5ZP=fIH5EpF?refjSOY8 zAKz0o4Vay*a{OsLKgCf_G}oAd%O`qAC!bXDeY+tDzok=o_zV4OR}|ofCl|lne!$n< zM2jyZ?l{@gnxXqZUkBgS#nPRlv!@Wz7|wpkn8f}i-I?fNO_G=*xs<4;u_WB67$I#k zX@u+h7Bcrk_KcyG&;&VL*H7U^jmR8h##5VbXeRk@K5$I*>5yetsB_EVbNO$GDVQqy zTgZJq*KMX4XAF%beU-C3@P#A=OTUe~auyFWhydO0UrH5qvtFlSJ4J@Yz;pZeKmS#+(1JD@f5&J%kQjS%r7hOa3IfIuez4 zY>?7IfmGpzIR3`_w6WXUN9`p~fGtZW)+-;a&iFJFv=vxcNxqoKR-Q`5#Dkz zJ7q*!o}J%P+7?o!BN8OYs2bkk;(6R&A-?mV>*nCay1GLo_ z#I|fc^`Hc1eN=6#_tj{Dg*zPVMNk3%xWnb_zm`Aau;|0!O}Mb`zw-K zuYfDEx>V5Xf4JBi#oAc%!ltE=!IHpZh~@b7=jEgTFaMK!0an#03D5~Zr)E(Toe%_u z?sUy>X?TY}DNz9eqHbEaDxaf;OLgQ+xw8CD7yaDn_*LyqpzxA#blB6I-GnyrBTjm^ zbaCtPfR|6iKN-ocD)LLoeF``F>_oEjH*cuy+so_q5Qw}qp>n;kSqVS7LLdzuvUvF3K=?M;z!a^=oG;`m#6xz)(QDM zrXS1d_Lc@G8e1eo^h4jw&`r8as>JbA{@Q13~>H=Lhw5DMD{&vGmHG3OL9@|4J!(N!8?dwmtzt_ff#UbQG|f6yU%g< zGwP&()r##y`IqBt1$aF-wxU_)H>g6K>nlP&qgP~|icKkc{ra?>ic-I8-%P+61O~_Z zG#*pMoQ!3Soy*%~t04GuMe=-B_@d%U*MePgW(wC;{6PC_}USeG*SnVe@U^LVouAxHk4LicmecD3Yy3Bb)1@u^kjto8|C{`D&P0 zTSfI#2*RJ2lN8f%Y;ZBk{`C?U;WvlvEDGYiekdw*Cj4#>&Z|KW9>GPE;nw=biH;)#}8=%>%s?q-#$azg!YCXcJx2R?{qiq?3Ds?nm zIOvnoTBy=gbYd7Nn=V7J>%F>9bh357*nQ=QipEXSaY<9zriM#@b8^H|Gt-)BF^V~r z33X9_*@`^ac$4``QVG7FxO9VmBGHqWC+V4LPpY9h+V=gKmJslPavKOb+LrRv3yf=fS4WF3f`PW=NPZ$w(Mw4=S12Dkt7S6J>{0;xtukJKll zKk7EZjW&l-zNFCmZJGJou$S_-ZA}EP;~c(pPw>8S-WTPKmHt4=I1gJyfPPG-*t-UE zrtE)K-F!Ao^4GA9`}X84Xv=bLtOT>VDf4Vg%jTlw5JHE9)2rt9;xs+Q-9$ir$h(C( zTZT!_7$&{bwwgn9@>*&i&Olr&_*e+?hb4YGqg_1so0|xDYeMHrELDxR;Zg5-o(3^A z;cNUnLOfDV8I{Wv%S4YTU!Q zj;U_CQ2jR{QFCaEzV+(Dge;$Vgh*MVD(|gldgs?^YlX4DZJ+KYl!edq{0JHZw6k1NDe{kCKy)^Qryt&<0jVP9YNp8^LGFMtucb#?Y7K$3Qm5GO&DdndW z%6;=Kk?p8}cQaBW=B?5S3q6f7jtg{H&(`)A4Fxhz&hX{5jc?2`zKtd4sx5IrQb*-T zI+D;YIXgNY7~Z$$BrwF=S83?QDp)qROSE0OmCL`DgYd% zEwf+X1$(8dmd+5_y!*GGEp-usg7=lIHAlJkRYZ)j5`8XciZ6BCCh5=GEIT*+v)RFv zoT%b_Z2Fq%`Y&EK0qJhUO)pWSw(bzgR>%3o_2}KVK3zAqdwU|tmM^KtuSVryx82ul z0X(9QBrsUwv(`7!oo)eaU50A4Z1^+`g~%x>eIJk2*Kdn^M!t{s+}ma_Aq7$A-U9kGu8tZ9egH z=DsRj>f&t&U8CXGPv%XZHhVPKs2N@sm{eYTiNSobm6L~zuaSM2L_k(6&%Vtr#vbo; zEshV-y`znX^OrL#xbdnYoN_q(<+u|+MW#5T35XGOk@LYvp3fjrWc+nRIP$`TkJ` z@im{b?X+>{eB59m))3b5e0F|3hjx}Pv*}9dHrwv)dYTkZ#Ymr#xMtz_FOeDJLGh1l zVyUIg+=v!(!;bH*XvHUsEp`5hI*(UdG#1V|;cFw$!%@&0r6x|rWa_mRF@yd?v>Y_4y9bhY362O$GINVDZs{-3H0GsL~QDg$0u0IVY$T6rWJC5G0d-v^5^ zQ$JDvW*@0zh8M~ojWO$0vHxOakb|m#zIOq!_flRo=8n#`{&8tq7i~v@^x<$shZ@Uu z=&h6W7+f;oQO(ik<1vd8G?F*o^~r0xo6KwzcyYJJ|9qJ*$Xv$Bb{`XHefif$kh1z} zL#)&zpO6L~WFz=y9#>gtg7D@uQi*iiM9Ds(Qt@f3_$Nk;qw1UkTzYYu4U6TiPXCG=TI-xyd9z%q8~2x=#J?)Mae1?oB%eJNf}6Qek*offh0i-}p_z%) zmaX+x+_Rq-GB18VLvNj}BB*N5-_AO5^S$>o1W9>xZ|xQ{k~1x43`N%6n1v3#Shia4 zz+R(gw#K_x`%c~K=aJAQ&M=x^l_nkSr9k!Osouok7xtG(R3c)#D+W+0S| zQJN0d#hc^qtIoqP8A_?3SZI4b=-RVJsw>PMsZ%RVhv5AYAjN6ZRaK`!%ET%JaoWru z)Dc(Pak-eaFx=VwE8lA~Bsa~>euFnKnyhRaUmW&c@tAEQTjAqei*P^0C3Txc4Qs^5 zCmhvb*s60967*m<@P1p-(=u2KS4NdaaO^FOkcbir$^EPBpTdKbHz(NI(K3P)#xQ3E zO*{HRTtf_(t1ls2FW2@g?g~loHNEySFqifnqsYc)OGVWTOYJ(hFh==v-)nTqSZ>aX zUH=;1_C7Ut&+~hmSACWvY5mvdq(3P(dY8$&$ZmW8P9t&yL#=bZ-pI&IgT5g+S3K!C z>jx7w9pSYxdRp&-BGDJn=!^HBw*eQJf?ACEtqaF1UWP4Hlh3e`12}Iy&^RR$x;hrt z{;1=5sp!@Ap|bXoY_l8cR!NX7Ea9D0Fyr=e*yGO}@Bjr#+)cc~9`2GLzP%tn$* z3%Jv>v7_IRK!l&Ryz8Xt3O?`RLxgw`ojEy&iFXTUI3>k%$i8)`P^Osa&GR?EVi?d4 zv)*!UPaPTs!^D)^z*b7;Lk`o{@7+X(mo>lYDe%9go?=#_cM2}I@UrZ~)M>EE{fe24 zWwVXWLsEO^&L%Y}S2j*eA|K0o2;$0-WrD`XIC-qf+KJJs#rJDB_l-JXSGVVQ06!k& zETPp0cm#W9hR2;=9ZBu}kEC z5x3k#FP<7+Jk$6Yi`2v2!5m$45=H#*{sk{x<5!hVMUg*QM>|+Ey$~PG2+hazlpwzL z=tQq;_n7q?pL12TGkD0Dch@_~M7tq4HW4E%LNBx5w#_bI0hVDhc12Qs+bz=%x4h?~ z!&*J$vEE92zqG$yD`f_KDH_#yI97Ba*YiV6EYF9wKz{iY-u*}_;S7jdr&DLn%0imH4Huz(5#X1-v)+NZ7 zrRiAhg|_H2FCrd1;&#NS4hmh;!{UBv{er)SUsb}3A=mnQ1XNEVabLyS*IT`adBER2Nmy$R$DfanC7v%2GXJ zL4k-Zg!Yq?EaJ0g`)>yAWJL39&gU{MmC^4`0?k>%{VJZnSMTyKm1Bg>uY#eFuNR)A zBvM&*wI~Uvytk+Kbq)+r`^tWfDX{KaG^jQtr^3$rG4{&X56U@ZhW~JJyvfZb4wWLC znY&pKZsnCExK9GX=gTMXvBU=BNfZk`-1CEe-}_vn?+%6eL{#<7%g)2RYz7+^70U}x zdcNQ%3b&1iLW^FNkGfj<6&sRcWhcK_wCjhP)q_mbM{dYd()E*TF8ml=)zJJqYmh9#KY zAi^46kvG-T>LBXD{y;fnjxL|izjM0(ef?YIKsg6wYIaN=eH4`402BHt;!;&a0%2sb zPpsdv8TK{ckbc6I2BKDxWqe z=pOiPem6>~e&LzxNy$j+{z^%JHovZKtel|b#3k3GP&d3pv;_3Kpzx_;|sA{@9uzW0ozxt=3Y;-i7D?<94v=X8q( z$~OfVBg02)XKOm=tM2aYB_$anf0bxY(MIp(`Z3UZo>W!$mS-QIzM<4LlnrJ6q%Jci zkcyD(MYQ}@XWrZ9D=+Z{k9<|uAL3$<6weFWi`65ux6#u0ivmqYx1)E;`+h6mHzv^* zzG38g-4~eNq{Ci#9HnsM1GGtxEEQH)BUTvBIeW-iP%)UlZoKQrAM z&+Kn6!CEgT*@fpEzbZ-K&6(pbKgivE9^a8!Y6x4@-EZ%hV9Z!+@+y9deA%Ghp8wLA zN;8p`uX*q3IMQb8D@^lplFXYuQ)ZR6z|fcXiT%5Mn@%__Gc~*djhFwlS)KQXU*&o* zx!tgYkhdFl`m9BUs^`h1$o{=cTz^1i`kOSHqnnXXqlc#_^>f{;ryWo+)H|%z-;xEphNaxq*yZ&QTuB&>VX2@Cg_eeqiH_{HRZqUXmn-$N3 zK`eJ=wO>wa2NB+=C2J*f{k+A{C&dP98qK{ThKu=_yTcHnpjQB@1Hn& z5tnNuIEkohDU_efE@j1r?f)8{+tW(c&i_=!fHEleU_#?2Y*6Pqpl|!E`(JYWiiZ|) z*ejkDDQhei{08eh%}l;T{zi_3E*q|BY7~ZIA^FmL`Sb{@4%@9uoWe9rB^}N3b-2p@ znXsP1p`xBk8kVAGd+h?!$)*c+t9lwuZ*o;9j|3d3P2Fa1EMrZRVeUllbdjYrU-yZ9 zq@^E|l}d^uJ8fE(Z{M#tlb;TgW#s-B|9mBT(nNU-a*tNi zg7T+`_;?7aL(VH@cDazM{YB4@9X>3XLx`-ME2LCjp$t944z)j z!R)LAOt>?Yf8&NneHGy}fg@#gYEUiYiLFk?xqbs z!BH#8C9^p|ud>jQ|33oN0V@8co*aEA`uiK|uSHQxW%IT*;t&ALDukjbXk8MDh@=*$ zgJU0x+TMNO%o#^JYbF}n>a5EaUbT&1;`Q%cUKWbbI|l%>3ZZBUN~go!FPWV&1Eo`~ zGa_oOeVNqG4(#cOdJ<}Pc6>?*od4|u+5BcR`IaJzqFxBvyN36*VQf7P1Yv@QzznOd&r#}tslGaCeZ=OeL^SAlzhh2YBUPT+0Hij{2q(A_G*?~|r z1*Lqw@#h{d(9)mV9dfFaS;VCMRSf{(F@_m*003~ns)Qm@e!8u6003adi5lJUb`N(C zMa;}fdt1H|C;$M!xL}V|UoE#%C{od!Yb@=nQUCxjOI-Yn)>V_1D~%NyV_b73Z=Tyn ztpEUER=CEpZ=SK^`-Dz#_x*|rX_~p-Ez`el2EXe3_C9I|00946K{fNai}~E;sCguc zO12vJocBJ2d)oOD0092)n8_GpjInl#WT7cSQ9)$dcK`suj1Xq#={NVC zIuro_0Nh_laRmT?1BOrp000LJp$Gr~4j4ia000~?gdzX{IA91x003~n5Q+c*;DG%H Xkt?QJ8tyt>00000NkvXXu0mjf9B}LS literal 24395 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 diff --git a/www/docs/guides/signature.md b/www/docs/guides/signature.md index 7728154f6..7c7f512f7 100644 --- a/www/docs/guides/signature.md +++ b/www/docs/guides/signature.md @@ -82,7 +82,7 @@ console.log('Result (boolean) =', isFullPubKeyRelatedToAccount); The sender can provide an account address, despite a full public key. ```typescript -const myProvider = new RpcProvider({ nodeUrl: 'http://127.0.0.1:5050/rpc' }); //devnet-rs +const myProvider = new RpcProvider({ nodeUrl: 'http://127.0.0.1:5050/rpc' }); //devnet const accountAddress = '0x...'; // account of sender const msgHash2 = hash.computeHashOnElements(message); @@ -251,7 +251,7 @@ The Ledger shall be connected, unlocked, with the Starknet internal APP activate Some complete examples : A Node script : [here](https://github.com/PhilippeR26/starknet.js-workshop-typescript/blob/main/src/scripts/ledgerNano/6.testLedgerAccount221.ts). -A test Web DAPP, to use in devnet-rs network : [here](https://github.com/PhilippeR26/Starknet-Ledger-Wallet). +A test Web DAPP, to use in devnet network : [here](https://github.com/PhilippeR26/Starknet-Ledger-Wallet). If you want to read the version of the Ledger Starknet APP : diff --git a/www/docs/guides/use_ERC20.md b/www/docs/guides/use_ERC20.md index b2f3bf999..6cdb8f601 100644 --- a/www/docs/guides/use_ERC20.md +++ b/www/docs/guides/use_ERC20.md @@ -25,11 +25,12 @@ The account contract will use the public key to check that you have the private This way, the ERC20 contract is absolutely sure that the caller of the transfer function knows the private key of this account. -## ETH token is an ERC20 in Starknet +## STRK token is an ERC20 in Starknet -In opposition to Ethereum, the ETH token is an ERC20 in Starknet, like all other tokens. In all networks, its ERC20 contract address is: +In opposition to Ethereum, the ETH & STRK fee tokens are both ERC20 in Starknet, like all other tokens. In all networks, their ERC20 contract addresses are : ```typescript +const addrSTRK = '0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d'; const addrETH = '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7'; ``` @@ -37,14 +38,14 @@ const addrETH = '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004 Let's dive down the rabbit hole! -This example works with an ERC20, that we will deploy on the devnet-rs (launched with `cargo run --release -- --seed 0`). +This example works with an ERC20, that we will deploy on the devnet rpc 0.8 (launched with `cargo run --release -- --seed 0`). First, let's initialize an existing account: ```typescript // initialize provider const provider = new RpcProvider({ nodeUrl: 'http://127.0.0.1:5050/rpc' }); -// initialize existing pre-deployed account 0 of Devnet-rs +// initialize existing pre-deployed account 0 of Devnet const privateKey = '0x71d7bb07b9a64f6f78ac4c816aff4da9'; const accountAddress = '0x64b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691'; diff --git a/www/docs/guides/walletAccount.md b/www/docs/guides/walletAccount.md index ceaf3dff6..f274e6be2 100644 --- a/www/docs/guides/walletAccount.md +++ b/www/docs/guides/walletAccount.md @@ -35,8 +35,8 @@ Instantiating a new `WalletAccount`: ```typescript import { connect } from '@starknet-io/get-starknet'; // v4.0.3 min -import { WalletAccount, wallet } from 'starknet'; // v6.18.0 min -const myFrontendProviderUrl = 'https://free-rpc.nethermind.io/sepolia-juno/v0_7'; +import { WalletAccount, wallet } from 'starknet'; // v7.0.1 min +const myFrontendProviderUrl = 'https://free-rpc.nethermind.io/sepolia-juno/v0_8'; // standard UI to select a wallet: const selectedWalletSWO = await connect({ modalMode: 'alwaysAsk', modalTheme: 'light' }); const myWalletAccount = await WalletAccount.connect( @@ -145,7 +145,7 @@ The above examples are using the SWO, because it is the simpler way to process. The `WalletAccount` class is able to interact with all the entrypoints of the Starknet Wallet API, including some functionalities that do not exists in the `Account` class. -A full description of this API can be found [**here**](https://github.com/PhilippeR26/Starknet-WalletAccount/blob/main/doc/walletAPIspec.md). +A full description of this API can be found [**here**](https://github.com/starknet-io/get-starknet/blob/master/packages/core/documentation/walletAPIdocumentation.md). Some examples: diff --git a/www/docs/guides/websocket_channel.md b/www/docs/guides/websocket_channel.md index dd3224875..49669b0e9 100644 --- a/www/docs/guides/websocket_channel.md +++ b/www/docs/guides/websocket_channel.md @@ -24,7 +24,7 @@ import { WebSocketChannel } from 'starknet'; ### Create instance ```typescript -// crete new ws channel +// create new ws channel const webSocketChannel = new WebSocketChannel({ nodeUrl: 'wss://sepolia-pathfinder-rpc.server.io/rpc/v0_8', }); diff --git a/www/docs/guides/what_s_starknet.js.md b/www/docs/guides/what_s_starknet.js.md index 7d43497de..662aa8f21 100644 --- a/www/docs/guides/what_s_starknet.js.md +++ b/www/docs/guides/what_s_starknet.js.md @@ -16,17 +16,17 @@ Some important topics that have to be understood: - [Starknet mainnet](https://starkscan.co) (Layer 2 of [Ethereum network](https://etherscan.io/) ). - [Starknet testnet](https://sepolia.starkscan.co/) (Layer 2 of [Sepolia network](https://sepolia.etherscan.io/) (testnet of Ethereum)). - - [Starknet-devnet](https://github.com/0xSpaceShard/starknet-devnet-rs) (your local Starknet network, for developers). + - [Starknet-devnet](https://github.com/0xSpaceShard/starknet-devnet) (your local Starknet network, for developers). and also to some more specific solutions: - private customized version of Starknet. - local Starknet node (connected to mainnet or testnet). -> Understanding what Starknet is and how it works is necessary. Then, you can learn how to interact with it using Starknet.js. So, at this stage, you should be aware of the content of the [Starknet official doc](https://docs.starknet.io/documentation/) and [the Starknet Book](https://book.starknet.io/). +> Understanding what Starknet is and how it works is necessary. Then, you can learn how to interact with it using Starknet.js. So, at this stage, you should be aware of the content of the [Starknet official doc](https://docs.starknet.io/documentation/) and [the Cairo Book](https://book.cairo-lang.org/). - The `RpcChannel` and `RpcProvider` classes and their methods are used for low-level communication with an RPC node. -- Your DAPP will mainly interact with `Account` and `Contract` class instances; they use underlying `RpcProvider` connections to provide high-level methods for interacting with their Starknet namesakes, accounts and contracts. +- Your DAPP will mainly interact with `Account` and `Contract` class instances ; they use underlying `RpcProvider` connections to provide high-level methods for interacting with their Starknet namesakes, accounts and contracts. - `Signer` and `Utils` objects contain many useful functions for interaction with Starknet.js. - The `Contract` object is mainly used to read the memory of a blockchain contract. - The `Account` object is the most useful: diff --git a/www/docusaurus.config.js b/www/docusaurus.config.js index 4cf4a71d8..cca7bbb96 100644 --- a/www/docusaurus.config.js +++ b/www/docusaurus.config.js @@ -69,7 +69,7 @@ const config = { //... other Algolia param }, announcementBar: { - content: `Migrate from v5`, + content: `Migrate from v6`, backgroundColor: 'rgb(230 231 232)', }, navbar: { @@ -119,7 +119,7 @@ const config = { to: '/docs/guides/intro', }, { - label: 'Migrate from v5', + label: 'Migrate from v6', to: migrationGuideLink, }, ], From 56915512d4463859d8acc1e0d928925613980795 Mon Sep 17 00:00:00 2001 From: Petar Penovic Date: Thu, 10 Apr 2025 12:39:58 +0200 Subject: [PATCH 2/2] docs: tweak guides --- www/docs/guides/L1message.md | 6 +-- www/docs/guides/cairo_enum.md | 2 +- www/docs/guides/connect_account.md | 15 +++--- www/docs/guides/connect_contract.md | 6 +-- www/docs/guides/connect_network.md | 72 +++++++++++++------------- www/docs/guides/create_account.md | 12 ++--- www/docs/guides/create_contract.md | 12 ++--- www/docs/guides/define_call_message.md | 8 +-- www/docs/guides/estimate_fees.md | 20 +++---- www/docs/guides/interact.md | 10 ++-- www/docs/guides/multiCall.md | 2 +- www/docs/guides/outsideExecution.md | 6 +-- www/docs/guides/signature.md | 16 +++--- www/docs/guides/use_ERC20.md | 4 +- www/docs/guides/websocket_channel.md | 2 +- www/docs/guides/what_s_starknet.js.md | 8 +-- www/docusaurus.config.js | 8 +-- 17 files changed, 106 insertions(+), 103 deletions(-) diff --git a/www/docs/guides/L1message.md b/www/docs/guides/L1message.md index a946e5929..9306da970 100644 --- a/www/docs/guides/L1message.md +++ b/www/docs/guides/L1message.md @@ -6,9 +6,9 @@ sidebar_position: 14 You can exchange messages between L1 & L2 networks: -- L2 Starknet mainnet ↔️ L1 Ethereum. -- L2 Starknet testnet ↔️ L1 Sepolia ETH testnet. -- L2 local Starknet devnet ↔️ L1 local ETH testnet (anvil, ...). +- L2 Starknet Mainnet ↔️ L1 Ethereum. +- L2 Starknet Testnet ↔️ L1 Sepolia ETH testnet. +- L2 local Starknet Devnet ↔️ L1 local ETH testnet (anvil, ...). You can find an explanation of the global mechanism [here](https://docs.starknet.io/architecture-and-concepts/network-architecture/messaging-mechanism/). diff --git a/www/docs/guides/cairo_enum.md b/www/docs/guides/cairo_enum.md index ed89d1f73..20dfeec7e 100644 --- a/www/docs/guides/cairo_enum.md +++ b/www/docs/guides/cairo_enum.md @@ -269,7 +269,7 @@ const res14e = (await myTestContract.call('test2a', [ ])) as bigint; ``` -Take care that if you call a method that do not use the abi (as `CallData.compile`), you have to list all the variants of the enum, like this: +Take care that if you call a method that do not use the ABI (as `CallData.compile`), you have to list all the variants of the enum, like this: ```typescript const orderToSend: Order = { p1: 8, p2: 10 }; diff --git a/www/docs/guides/connect_account.md b/www/docs/guides/connect_account.md index fa080c133..62f1b33c2 100644 --- a/www/docs/guides/connect_account.md +++ b/www/docs/guides/connect_account.md @@ -15,9 +15,9 @@ You need 2 pieces of data: import { Account, RpcProvider } from 'starknet'; ``` -## Connect to a pre-deployed account in Starknet-devnet +## Connect to a pre-deployed account in Starknet Devnet -When you launch starknet-devnet, 10 accounts are pre-deployed with 100 dummy ETH & STRK in each. +When you launch `starknet-devnet`, 10 accounts are pre-deployed with 100 dummy ETH and STRK in each. Addresses and private keys are displayed on the console at initialization. @@ -34,9 +34,9 @@ Public key : 0x7e52885445756b313ea16849145363ccb73fb4ab0440dbac333cf9d13de82b9 Then you can use this code: ```typescript -// initialize provider for devnet v0.3.0 +// initialize provider for Devnet v0.3.0 const provider = new RpcProvider({ nodeUrl: 'http://127.0.0.1:5050/rpc' }); -// initialize existing pre-deployed account 0 of Devnet +// initialize existing account 0 pre-deployed on Devnet const accountAddress = '0x64b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691'; const privateKey = '0x71d7bb07b9a64f6f78ac4c816aff4da9'; @@ -59,7 +59,7 @@ For example, to connect an existing account on testnet, with a private key store import * as dotenv from 'dotenv'; dotenv.config(); -// initialize rpc v0.8 provider +// initialize RPC v0.8 provider const provider = new RpcProvider({ nodeUrl: `${myNodeUrl}` }); // initialize existing account const privateKey = process.env.OZ_NEW_ACCOUNT_PRIVKEY; @@ -69,7 +69,7 @@ const account = new Account(provider, accountAddress, privateKey); ``` :::tip -If you are connected to a rpc v0.7 node, and if you want to use ETH as fees for this account: +If you are connected to an RPC v0.7 node and you want to use ETH as fees for this account: ```typescript const myAccount = new Account( @@ -85,7 +85,8 @@ const myAccount = new Account( ## Connect to an account that uses Ethereum signature -As a consequence of account abstraction, you can find accounts that uses Ethereum signature logical. +Accounts that use Ethereum signatures are possible because of account abstraction. + To connect to this type of account: ```typescript diff --git a/www/docs/guides/connect_contract.md b/www/docs/guides/connect_contract.md index be3651c1a..606968330 100644 --- a/www/docs/guides/connect_contract.md +++ b/www/docs/guides/connect_contract.md @@ -9,9 +9,9 @@ Once your provider is initialized, you can connect a contract already deployed i You need 2 pieces of data: - the address of the contract -- the ABI file of the contract (or the compiled/compressed contract file, that includes the abi) +- the ABI file of the contract (or the compiled/compressed contract file, that includes the ABI) -> If you don't have the abi file, the `provider.getClassAt()` and `provider.getClassByHash()` commands will recover the compressed contract file. As these methods generate a significant workload for the node, it's recommended to store the result on your computer to be able to reuse it later without using the provider each time: +> If you don't have the ABI file, the `provider.getClassAt()` and `provider.getClassByHash()` commands will recover the compressed contract file. As these methods generate a significant workload for the node, it's recommended to store the result on your computer to be able to reuse it later without using the provider each time: ```typescript import fs from 'fs'; @@ -22,7 +22,7 @@ fs.writeFileSync('./myAbi.json', json.stringify(compressedContract.abi, undefine > When possible, prefer to read the compiled contract from a local Json file, as it's much more faster, using the `json.parse` util provided by Starknet.js, as shown below. -## Get the abi from a compiled/compressed file +## Get the ABI from a compiled/compressed file ```typescript import { RpcProvider, Contract, json } from 'starknet'; diff --git a/www/docs/guides/connect_network.md b/www/docs/guides/connect_network.md index 00d3c6d5c..649ec2aea 100644 --- a/www/docs/guides/connect_network.md +++ b/www/docs/guides/connect_network.md @@ -13,25 +13,25 @@ Then you need to select a node. A node is a safe way to connect with the Starkne - your own node, located on your local computer or in your local network. > you can spin up your own node with Pathfinder, Juno, Papyrus, Deoxys, ... - a local development node, that simulates a Starknet network. Useful for devs to perform quick tests without spending precious fee token. - > Main development devnets are Starknet-devnet, Madara, ... + > Main development devnets are Starknet Devnet, Madara, ... -Each node is communicating with Starknet.js using a rpc specification. Most of the nodes are able to use 2 rpc spec versions. -For example, this node is compatible with v0.7.1 & v0.8.0, using the following entry points : +Starknet.js communicates with nodes in accordance to a version of the RPC specification. Most nodes are able to use two RPC versions. +For example, this node is compatible with v0.7.1 and v0.8.0, using the following entry points: - "https://free-rpc.nethermind.io/sepolia-juno/v0_7" - "https://free-rpc.nethermind.io/sepolia-juno/v0_8" -From rpc spec v0.5.0, you can request the rpc spec version that uses a node address : +From RPC v0.5.0, you can make a request to retrieve the RPC version that a node uses: ```typescript const resp = await myProvider.getSpecVersion(); -console.log('rpc version =', resp); -// result : rpc version = 0.8.0 +console.log('RPC version =', resp); +// result: RPC version = 0.8.0 ``` -On Starknet.js side, you have to select the proper version, to be in accordance with the node you want to use : +The Starknet.js version must align with the RPC version supported by the chosen node as shown below: -| Rpc spec version of your node | Starknet.js version to use | +| RPC spec version of your node | Starknet.js version to use | | :---------------------------: | ----------------------------- | | v0.4.0 | Starknet.js v5.21.1 | | v0.5.0 | Starknet.js v5.23.0 | @@ -41,10 +41,10 @@ On Starknet.js side, you have to select the proper version, to be in accordance | v0.8.0 | Starknet.js v7.0.1 | :::note -From version 6.x.x, Starknet.js is compatible with 2 rpc spec versions. +From version 6.x.x, Starknet.js is compatible with two RPC spec versions. ::: -With the `RpcProvider` class, you define the Starknet Rpc node to use: +With the `RpcProvider` class, you define the Starknet RPC node to use: ```typescript import { RpcProvider } from 'starknet'; @@ -78,20 +78,20 @@ import { RpcProvider } from 'starknet'; | Local Pathfinder v0.16.2 | v0_6, v0_7, v0_8 | N/A | | Local Juno v0.14.2 | v0_6, v0_7, v0_8 | N/A | -**Local Starknet-devnet network:** +**Local Starknet Devnet network:** | Node | with public url | with API key | | ---------------------: | :-------------: | :----------: | -| Starknet-devnet v0.2.4 | v0_7 | N/A | -| Starknet-devnet v0.3.0 | v0_8 | N/A | +| starknet-devnet v0.2.4 | v0_7 | N/A | +| starknet-devnet v0.3.0 | v0_8 | N/A | :::note -This status has been performed 02/apr/2025. +This status has been verified 02/apr/2025. ::: -### Default Rpc node +### Default RPC node -If you don't want to use a specific node, or to handle an API key, you can use by default (using Rpc spec 0.8.0): +If you don't want to use a specific node or to handle an API key, you can use one of the defaults (using RPC spec v0.8.0): ```typescript const myProvider = new RpcProvider({ nodeUrl: constants.NetworkName.SN_SEPOLIA }); @@ -100,42 +100,42 @@ const myProvider = new RpcProvider({ nodeUrl: constants.NetworkName.SN_MAIN }); const myProvider = new RpcProvider(); // Sepolia ``` -> when using this syntax, a random public node will be selected. +> When using this syntax, a random public node will be selected. -Using a specific nodeUrl is the better approach, as such a node will have fewer limitations, the last version of software and will be less crowded. +Using a specific `nodeUrl` is the better approach, as such nodes will have fewer limitations, their software will be more up to date, and they will be less congested. -Some examples of RpcProvider instantiation to connect to RPC node providers: +Some examples of `RpcProvider` instantiation to connect to RPC node providers: ### Mainnet ```typescript -// Infura node rpc 0.7.0 for Mainnet: +// Infura node RPC 0.7.0 for Mainnet: const providerInfuraMainnet = new RpcProvider({ nodeUrl: 'https://starknet-mainnet.infura.io/v3/' + infuraKey, specVersion: '0.7', }); -// Blast node rpc 0.8.0 for Mainnet (0.6 & 0_7 also available): +// Blast node RPC 0.8.0 for Mainnet (0.6 & 0_7 also available): const providerBlastMainnet = new RpcProvider({ nodeUrl: 'https://starknet-mainnet.blastapi.io/' + blastKey + '/rpc/v0_8', }); -// Lava node rpc 0.8.0 for Mainnet: +// Lava node RPC 0.8.0 for Mainnet: const providerMainnetLava = new RpcProvider({ nodeUrl: 'https://g.w.lavanet.xyz:443/gateway/strk/rpc-http/' + lavaMainnetKey, }); -// Alchemy node rpc 0.7.0 for Mainnet (0_6 also available): +// Alchemy node RPC 0.7.0 for Mainnet (0_6 also available): const providerAlchemyMainnet = new RpcProvider({ nodeUrl: 'https://starknet-mainnet.g.alchemy.com/starknet/version/rpc/v0_7/' + alchemyKey, specVersion: '0.7', }); -// Public Nethermind node rpc 0.8.0 for Mainnet (0_6 & 0_7 also available): +// Public Nethermind node RPC 0.8.0 for Mainnet (0_6 & 0_7 also available): const providerMainnetNethermindPublic = new RpcProvider({ nodeUrl: 'https://free-rpc.nethermind.io/mainnet-juno/v0_8', }); -// Public Blast node rpc 0.8.0 for Mainnet (0.6 & 0_7 also available): +// Public Blast node RPC 0.8.0 for Mainnet (0.6 & 0_7 also available): const providerBlastMainnet = new RpcProvider({ nodeUrl: 'https://starknet-mainnet.public.blastapi.io/rpc/v0_8', }); -// Public Lava node rpc 0.8.0 for Mainnet (0.6 & 0_7 also available): +// Public Lava node RPC 0.8.0 for Mainnet (0.6 & 0_7 also available): const providerLavaMainnet = new RpcProvider({ nodeUrl: 'https://rpc.starknet.lava.build/rpc/v0_8', }); @@ -144,7 +144,7 @@ const providerLavaMainnet = new RpcProvider({ > Take care to safely manage your API key. It's a confidential item! :::tip -If the rpc version of the node is 0.7, use : +If the RPC version of the node is 0.7, the `specVersion` parameter must be set: ```typescript const myProvider = new RpcProvider({ @@ -153,38 +153,38 @@ const myProvider = new RpcProvider({ }); ``` -If you are not sure of the rpc version (0.7 or 0.8), use: +If you are not sure about the RPC version (0.7 or 0.8), use: ```typescript const myProvider = await RpcProvider.create({ nodeUrl: `${myNodeUrl}` }); ``` -This line of code is slow, as it performs a request to the node. +Note that this approach is slower, it performs a request to the node. ::: ### Goerli Testnet :::info -The Goerli testnet is no more in service. +The Goerli Testnet is no longer in service. ::: ### Sepolia Testnet ```typescript -// Infura node rpc 0.7.0 for Sepolia Testnet : +// Infura node RPC 0.7.0 for Sepolia Testnet : const providerInfuraSepoliaTestnet = new RpcProvider({ nodeUrl: 'https://starknet-sepolia.infura.io/v3/' + infuraKey, specVersion: '0.7', }); -// Public Nethermind node rpc 0.8.0 for Sepolia Testnet (0_6 & 0_7 also available) : +// Public Nethermind node RPC 0.8.0 for Sepolia Testnet (0_6 & 0_7 also available) : const providerSepoliaTestnetNethermindPublic = new RpcProvider({ nodeUrl: 'https://free-rpc.nethermind.io/sepolia-juno/v0_8', }); -// Public Blast node rpc 0.8.0 for Sepolia Testnet (0_6 & 0_7 also available) : +// Public Blast node RPC 0.8.0 for Sepolia Testnet (0_6 & 0_7 also available) : const providerSepoliaTestnetBlastPublic = new RpcProvider({ nodeUrl: 'https://starknet-sepolia.public.blastapi.io/rpc/v0_8', }); -// Public Lava node rpc 0.8.0 for Sepolia Testnet (0_6 & 0_7 also available) : +// Public Lava node RPC 0.8.0 for Sepolia Testnet (0_6 & 0_7 also available) : const providerSepoliaTestnetBlastPublic = new RpcProvider({ nodeUrl: 'https://rpc.starknet-testnet.lava.build/rpc/v0_8', }); @@ -219,13 +219,13 @@ const provider = new RpcProvider({ nodeUrl: 'http://127.0.0.1:6060/v0_8' }); ## Devnet -Example of a connection to a local development node (rpc 0.8.0), with Starknet-devnet v0.3.0: +Example of a connection to a local development node (RPC 0.8.0), with starknet-devnet v0.3.0: ```typescript const provider = new RpcProvider({ nodeUrl: 'http://127.0.0.1:5050/rpc' }); ``` -> If you have customized host and port during starknet-devnet initialization, adapt in accordance your script. +> If you customized the host or port during starknet-devnet initialization, adapt the script accordingly. ## Batch JSON-RPC diff --git a/www/docs/guides/create_account.md b/www/docs/guides/create_account.md index 32fe502bc..fd073bfc9 100644 --- a/www/docs/guides/create_account.md +++ b/www/docs/guides/create_account.md @@ -28,7 +28,7 @@ import { Account, constants, ec, json, stark, RpcProvider, hash, CallData } from ### Compute address ```typescript -// connect rpc 0.8 provider (Mainnet or Sepolia) +// connect RPC 0.8 provider (Mainnet or Sepolia) const provider = new RpcProvider({ nodeUrl: `${myNodeUrl}` }); // new Open Zeppelin account v0.17.0 @@ -111,7 +111,7 @@ import { ### Compute address ```typescript -// connect rpc 0.8 provider +// connect RPC 0.8 provider const provider = new RpcProvider({ nodeUrl: `${myNodeUrl}` }); //new Argent X account v0.4.0 @@ -167,7 +167,7 @@ console.log('✅ ArgentX wallet deployed at:', AXcontractFinalAddress); More complicated, a Braavos account needs a proxy and a specific signature. Starknet.js is handling only Starknet standard signatures; so we need extra code to handle this specific signature for account creation. These nearly 400 lines of code are not displayed here but are available in a module [here](./doc_scripts/deployBraavos.ts). -We will deploy hereunder a Braavos account in devnet. So launch starknet-devnet with these parameters: +We will deploy hereunder a Braavos account in Devnet. So launch `starknet-devnet` with these parameters: ```bash starknet-devnet --seed 0 --fork-network 'https://free-rpc.nethermind.io/sepolia-juno/v0_8' @@ -220,7 +220,7 @@ console.log('calculated fees =', estimatedFee); ### Deploy account ```typescript -// fund account address before account creation (easy in devnet) +// fund account address before account creation (easy in Devnet) const { data: answer } = await axios.post( 'http://127.0.0.1:5050/mint', { @@ -240,7 +240,7 @@ await providerDevnet.waitForTransaction(transaction_hash); console.log('✅ Braavos account deployed at', BraavosAccountFinalAddress); ``` -The computed address has been funded automatically by minting dummy STRK in Starknet devnet! +The computed address has been funded automatically by minting dummy STRK in Starknet Devnet! ## Create an Ethereum account @@ -255,7 +255,7 @@ Below is an example of account creation in Sepolia Testnet. const privateKeyETH = '0x45397ee6ca34cb49060f1c303c6cb7ee2d6123e617601ef3e31ccf7bf5bef1f9'; const ethSigner = new EthSigner(privateKeyETH); const ethFullPublicKey = await ethSigner.getPubKey(); -// OpenZeppelin v0.17.0 : +// OpenZeppelin v0.17.0: const accountEthClassHash = '0x3940bc18abf1df6bc540cabadb1cad9486c6803b95801e57b6153ae21abfe06'; const myCallData = new CallData(sierraContract.abi); const accountETHconstructorCalldata = myCallData.compile('constructor', { diff --git a/www/docs/guides/create_contract.md b/www/docs/guides/create_contract.md index 9bb8f8391..f8ad6b263 100644 --- a/www/docs/guides/create_contract.md +++ b/www/docs/guides/create_contract.md @@ -34,7 +34,7 @@ import { RpcProvider, Account, Contract, json, stark, uint256, shortString } fro Starknet.js proposes a function to perform both operations in one step: `declareAndDeploy()`. -Here, to declare & deploy a `Test.cairo` smart contract, in devnet: +Here, to declare & deploy a `Test.cairo` smart contract, in Devnet: ```typescript // connect provider @@ -44,7 +44,7 @@ const privateKey0 = process.env.OZ_ACCOUNT_PRIVATE_KEY; const account0Address: string = '0x123....789'; const account0 = new Account(provider, account0Address, privateKey0); -// Declare & deploy Test contract in devnet +// Declare & deploy Test contract in Devnet const compiledTestSierra = json.parse( fs.readFileSync('./compiledContracts/test.contract_class.json').toString('ascii') ); @@ -79,14 +79,14 @@ const account0Address: string = '0x123....789'; const account0 = new Account(provider, account0Address, privateKey0); -// Deploy Test contract in devnet +// Deploy Test contract in Devnet // ClassHash of the already declared contract const testClassHash = '0xff0378becffa6ad51c67ac968948dbbd110b8a8550397cf17866afebc6c17d'; const deployResponse = await account0.deployContract({ classHash: testClassHash }); await provider.waitForTransaction(deployResponse.transaction_hash); -// read abi of Test contract +// read the ABI of the Test contract const { abi: testAbi } = await provider.getClassByHash(testClassHash); if (testAbi === undefined) { throw new Error('no abi.'); @@ -160,7 +160,7 @@ const deployResponse = await account0.deployContract({ }); ``` -Properties have to be ordered in conformity with the abi. +Properties have to be ordered in conformity with the ABI. Even easier: @@ -185,7 +185,7 @@ const account0Address: string = '0x123....789'; const account0 = new Account(provider, account0Address, privateKey0); -// Declare Test contract in devnet +// Declare Test contract in Devnet const compiledTestSierra = json.parse( fs.readFileSync('./compiledContracts/test.sierra').toString('ascii') ); diff --git a/www/docs/guides/define_call_message.md b/www/docs/guides/define_call_message.md index 106706d06..58acb1556 100644 --- a/www/docs/guides/define_call_message.md +++ b/www/docs/guides/define_call_message.md @@ -227,7 +227,7 @@ It's not mandatory to create manually an object conform to the Cairo 0 named tup ### Ethereum public key -If your abi is requesting this type : `core::starknet::secp256k1::Secp256k1Point`, it means that you have probably to send an Ethereum full public key. Example : +If your ABI is requesting this type: `core::starknet::secp256k1::Secp256k1Point`, it means that you have probably to send an Ethereum full public key. Example: ```json { @@ -242,7 +242,7 @@ If your abi is requesting this type : `core::starknet::secp256k1::Secp256k1Point } ``` -- If you are using a calldata construction method using the Abi, you have just to use a 512 bits number (so, without parity) : +- If you are using a calldata construction method using the ABI, you have just to use a 512 bits number (so, without parity): ```typescript const privateKeyETH = '0x45397ee6ca34cb49060f1c303c6cb7ee2d6123e617601ef3e31ccf7bf5bef1f9'; @@ -254,7 +254,7 @@ const accountETHconstructorCalldata = myCallData.compile('constructor', { }); ``` -- If you are using a calldata construction method without the Abi, you have to send a tuple of 2 u256 : +- If you are using a calldata construction method without the ABI, you have to send a tuple of 2 u256: ```typescript const ethFullPublicKey = @@ -456,7 +456,7 @@ const myCall: Call = myContract.populate('get_elements', functionParameters); const res = await myContract.get_elements(myCall.calldata); ``` -It can be used only with methods that know the abi: `Contract.populate, myCallData.compile`. +It can be used only with methods that know the ABI: `Contract.populate, myCallData.compile`. Starknet.js will perform a full check of conformity with the ABI of the contract, reorder the object's properties if necessary, stop if something is wrong or missing, remove not requested properties, and convert everything to Starknet format. Starknet.js will alert you earlier of errors in your parameters (with human comprehensible words), before the call to Starknet. So, no more incomprehensible Starknet messages due to parameters construction. diff --git a/www/docs/guides/estimate_fees.md b/www/docs/guides/estimate_fees.md index 466c8643b..6401fe41c 100644 --- a/www/docs/guides/estimate_fees.md +++ b/www/docs/guides/estimate_fees.md @@ -4,7 +4,7 @@ sidebar_position: 11 # Estimate fees -By default, all non-free Starknet commands (declare, deploy, invoke) work without any limitation of cost. +By default, all non-free Starknet commands (declare, deploy, invoke) work without any cost limits. You might want to inform the DAPP user of the cost of the incoming paid command before proceeding and requesting its validation. @@ -22,13 +22,13 @@ const { suggestedMaxFee, unit } = await account0.estimateInvokeFee({ }); ``` -The result is in `suggestedMaxFee`, of type BigInt. The unit of this number is in `unit`. it's WEI for "legacy" transactions, and FRI for V3 transactions. +The result is in `suggestedMaxFee`, of type BigInt. The corresponding unit for this number is in `unit`. It's WEI for "legacy" transactions, and FRI for V3 transactions. :::tip More details about the complex subject of Starknet fees in [Starknet docs](https://docs.starknet.io/architecture-and-concepts/network-architecture/fee-mechanism/) ::: -The complete answer for a rpc 0.7 "legacy" transaction : +The complete answer for an RPC 0.7 "legacy" transaction: ```typescript { @@ -46,7 +46,7 @@ The complete answer for a rpc 0.7 "legacy" transaction : } ``` -The complete answer for a rpc 0.7 V3 transaction : +The complete answer for an RPC 0.7 V3 transaction: ```typescript { @@ -64,7 +64,7 @@ The complete answer for a rpc 0.7 V3 transaction : } ``` -The complete answer for a rpc 0.8 V3 transaction : +The complete answer for an RPC 0.8 V3 transaction: ```typescript { @@ -96,7 +96,7 @@ const { suggestedMaxFee } = await account0.estimateDeclareFee({ }); ``` -The result is in `suggestedMaxFee`, of type BigInt. Units and full response format are the same than `invoke`. +The result is in `suggestedMaxFee`, of type BigInt. The units and full response format are the same as `invoke`. ## estimateDeployFee @@ -110,7 +110,7 @@ const { suggestedMaxFee } = await account0.estimateDeployFee({ }); ``` -The result is in `suggestedMaxFee`, of type BigInt. Units and full response format are the same than `invoke`. +The result is in `suggestedMaxFee`, of type BigInt. The units and full response format are the same as `invoke`. ## estimateAccountDeployFee @@ -128,7 +128,7 @@ The result is in `suggestedMaxFee`, of type BigInt. Units and full response form ## Fee limitation -In some cases, a transaction can fail due to underestimation of the fees. You can increase these limits by setting a global config setting (default values are 50) : +In some cases, a transaction can fail due to the fees being underestimated. You can increase these limits by setting a global config setting (default values are 50): ```typescript config.set('feeMarginPercentage', { @@ -181,7 +181,7 @@ const declareResponse = await account0.declareIfNot({ contract: testSierra, casm ## Real fees paid -After the processing of the transaction, you can read the fees that have really been paid : +After a transaction has been processed, you can read the fees that have actually been paid: ```typescript const txR = await provider.waitForTransaction(declareResponse.transaction_hash); @@ -199,7 +199,7 @@ For STRK fees, the result is: { "unit": "FRI", "amount": "0x3a4f43814e180000" } ``` -For ETH fees : +For ETH fees: ```json { "unit": "WEI", "amount": "0x70c6fff3c000" } diff --git a/www/docs/guides/interact.md b/www/docs/guides/interact.md index b76dce91a..0ffb68e44 100644 --- a/www/docs/guides/interact.md +++ b/www/docs/guides/interact.md @@ -10,7 +10,7 @@ Once your provider, contract, and account are connected, you can interact with t - you can write to memory, but you have to pay fees. - On Mainnet, you have to pay fees with bridged STRK or ETH token. - On Testnet, you have to pay with bridged Sepolia STRK or Sepolia ETH token. - - On devnet, you have to pay with dummy STRK or ETH token. + - On Devnet, you have to pay with dummy STRK or ETH token. Your account should be funded enough to pay fees (20 STRK should be enough to start). @@ -40,7 +40,7 @@ const provider = new RpcProvider({ nodeUrl: `${myNodeUrl}` }); // Connect the deployed Test contract in Sepolia Testnet const testAddress = '0x02d2a4804f83c34227314dba41d5c2f8a546a500d34e30bb5078fd36b5af2d77'; -// read abi of Test contract +// read the ABI of the Test contract const { abi: testAbi } = await provider.getClassAt(testAddress); if (testAbi === undefined) { throw new Error('no abi.'); @@ -63,7 +63,7 @@ You have to invoke Starknet, with the use of the meta-class method: `contract.fu > After the invoke, you have to wait the incorporation of the modification of Balance in the network, with `await provider.waitForTransaction(transaction_hash)` :::note -By default, you are executing transactions that uses STRK token to pay the fees. +By default, you are executing transactions that use the STRK token to pay the fees. ::: Here is an example of how to increase and check the balance: @@ -80,7 +80,7 @@ const account0 = new Account(provider, account0Address, privateKey0); // Connect the deployed Test contract in Testnet const testAddress = '0x02d2a4804f83c34227314dba41d5c2f8a546a500d34e30bb5078fd36b5af2d77'; -// read abi of Test contract +// read the ABI of the Test contract const { abi: testAbi } = await provider.getClassAt(testAddress); if (testAbi === undefined) { throw new Error('no abi.'); @@ -105,7 +105,7 @@ console.log('Final balance =', bal2); ## ✍️ Send a transaction, paying fees with ETH -You need to be connected to a node using Rpc 0.7: +You need to be connected to a node using RPC 0.7: - Define `specVersion: '0.7'` when instantiating an RpcProvider - Use `config.set('legacyMode', true)` to enable **V1** transactions (ETH fees) diff --git a/www/docs/guides/multiCall.md b/www/docs/guides/multiCall.md index 381135aa2..15e098554 100644 --- a/www/docs/guides/multiCall.md +++ b/www/docs/guides/multiCall.md @@ -11,7 +11,7 @@ Interacting with more than one contract with one transaction is one of Starknet' Set up basic stuff before multicall. ```javascript -// devnet private key from Account #0 if generated with --seed 0 +// Devnet private key from Account #0 if generated with --seed 0 const privateKey = '0xe3e70682c2094cac629f6fbed82c07cd'; const accountAddress = '0x7e00d496e324876bbc8531f2d9a82bf154d1a04a50218ee74cdd372f75a551a'; diff --git a/www/docs/guides/outsideExecution.md b/www/docs/guides/outsideExecution.md index 6422038a5..4c51cc200 100644 --- a/www/docs/guides/outsideExecution.md +++ b/www/docs/guides/outsideExecution.md @@ -18,7 +18,7 @@ Outside Execution provides several benefits: ### Check SNIP-9 Support The account that will sign the outside transaction has to be compatible with SNIP-9 (V1 or V2). -At early-2025 : +At early-2025: | account | compatibility | | :-----------------: | :-----------: | @@ -157,9 +157,9 @@ In this example, we want to sign, with a Ledger Nano X, several transactions at By this way, you can pre-sign some transactions with the Ledger, and if during the night something occurs, a backend can execute automatically some of these transactions, **in any order**. In this process, **the private key of the Ledger account is never exposed**. -First, create a Ledger account in devnet. You will find some documentation [here](./signature.md#signing-with-a-ledger-hardware-wallet), and an example [here](https://github.com/PhilippeR26/starknet.js-workshop-typescript/blob/main/src/scripts/ledgerNano/4.deployLedgerAccount.ts). +First, create a Ledger account in Devnet. You will find some documentation [here](./signature.md#signing-with-a-ledger-hardware-wallet), and an example [here](https://github.com/PhilippeR26/starknet.js-workshop-typescript/blob/main/src/scripts/ledgerNano/4.deployLedgerAccount.ts). -The initial balances are : +The initial balances are: | account | ETH balance | | ----------------------: | ----------- | diff --git a/www/docs/guides/signature.md b/www/docs/guides/signature.md index 7c7f512f7..856ab7946 100644 --- a/www/docs/guides/signature.md +++ b/www/docs/guides/signature.md @@ -40,7 +40,7 @@ On the receiver side, you can verify that: 2 ways to perform this verification: - off-chain, using the full public key (very fast, but only for standard Starknet hash & sign). -- on-chain, using the account address (slow, add workload to the node/sequencer, but can manage exotic account abstraction about hash or sign). +- on-chain, using the account address (slow, adds workload to the node, but can manage exotic account abstraction about hash or sign). ### Verify outside of Starknet: @@ -98,7 +98,7 @@ These items are designed to be able to be an interface with a browser wallet. At - the `message` at the bottom of the wallet window, showing clearly (not in hex) the message to sign. Its structure has to be in accordance with the type listed in `primaryType`, defined in `types`. - the `domain` above the message. Its structure has to be in accordance with `StarknetDomain`. -The types than can be used are defined in [SNIP-12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md). An example of simple message : +The types than can be used are defined in [SNIP-12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md). An example of simple message: ```typescript const myTypedData: TypedData = { @@ -231,7 +231,7 @@ In a Web DAPP, take care that some browsers are not compatible (FireFox, ...), a The last version of the Ledger Starknet APP (v2.2.1) supports explained V1 (ETH) & V3 (STRK) transactions & deploy accounts. For a class declaration or a message, you will have to blind sign a hash ; sign only hashes from a code that you trust. Do not forget to Enable `Blind signing` in the APP settings. ::: -For example, for a Node script : +For example, for a Node script: ```typescript import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'; @@ -249,11 +249,11 @@ const ledgerAccount = new Account(myProvider, ledger0addr, myLedgerSigner); The Ledger shall be connected, unlocked, with the Starknet internal APP activated, before launch of the script. ::: -Some complete examples : -A Node script : [here](https://github.com/PhilippeR26/starknet.js-workshop-typescript/blob/main/src/scripts/ledgerNano/6.testLedgerAccount221.ts). -A test Web DAPP, to use in devnet network : [here](https://github.com/PhilippeR26/Starknet-Ledger-Wallet). +Some complete examples: +A Node script: [here](https://github.com/PhilippeR26/starknet.js-workshop-typescript/blob/main/src/scripts/ledgerNano/6.testLedgerAccount221.ts). +A test Web DAPP, to use in Devnet: [here](https://github.com/PhilippeR26/Starknet-Ledger-Wallet). -If you want to read the version of the Ledger Starknet APP : +If you want to read the version of the Ledger Starknet APP: ```typescript const resp = await myLedgerTransport.send(Number('0x5a'), 0, 0, 0); @@ -268,7 +268,7 @@ You also have in Starknet.js a signer for the old v1.1.1 Ledger Starknet APP. const myLedgerSigner = new LedgerSigner111(myLedgerTransport, 0); ``` -If you want to use the accounts created with the v1.1.1, using the v2.2.1 : +If you want to use the accounts created with the v1.1.1, using the v2.2.1: ```typescript const myLedgerSigner = new LedgerSigner221(myLedgerTransport, 0, undefined, getLedgerPathBuffer111); diff --git a/www/docs/guides/use_ERC20.md b/www/docs/guides/use_ERC20.md index 6cdb8f601..1598d2ab5 100644 --- a/www/docs/guides/use_ERC20.md +++ b/www/docs/guides/use_ERC20.md @@ -27,7 +27,7 @@ This way, the ERC20 contract is absolutely sure that the caller of the transfer ## STRK token is an ERC20 in Starknet -In opposition to Ethereum, the ETH & STRK fee tokens are both ERC20 in Starknet, like all other tokens. In all networks, their ERC20 contract addresses are : +Unlike Ethereum, the ETH and STRK fee tokens are both ERC20 in Starknet, just like all other tokens. In all networks, their ERC20 contract addresses are: ```typescript const addrSTRK = '0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d'; @@ -38,7 +38,7 @@ const addrETH = '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004 Let's dive down the rabbit hole! -This example works with an ERC20, that we will deploy on the devnet rpc 0.8 (launched with `cargo run --release -- --seed 0`). +This example works with an ERC20, that we will deploy on Devnet RPC 0.8 (launched with `cargo run --release -- --seed 0`). First, let's initialize an existing account: diff --git a/www/docs/guides/websocket_channel.md b/www/docs/guides/websocket_channel.md index 49669b0e9..65107ae39 100644 --- a/www/docs/guides/websocket_channel.md +++ b/www/docs/guides/websocket_channel.md @@ -11,7 +11,7 @@ Channel represent implementation of the Starknet specification in it's strictly WebSocket channel provide convenient way to establish websocket connection to the [Starknet RPC Node](https://www.starknet.io/fullnodes-rpc-services/). -Ensure that you are using node supporting the required Rpc spec >= v0.8.0. Details regarding Starknet Nodes and supported RPC versions could be found on each node github/page. +Ensure that you are using node supporting the required RPC spec >= v0.8.0. Details regarding Starknet Nodes and supported RPC versions could be found on each node github/page. Websocket Channel implements specification methods defined by [@starknet-io/types-js](https://github.com/starknet-io/types-js/blob/b7d38ca30a1def28e89370068efff81b3a3062b7/src/api/methods.ts#L421) diff --git a/www/docs/guides/what_s_starknet.js.md b/www/docs/guides/what_s_starknet.js.md index 662aa8f21..1a214e776 100644 --- a/www/docs/guides/what_s_starknet.js.md +++ b/www/docs/guides/what_s_starknet.js.md @@ -14,9 +14,9 @@ Some important topics that have to be understood: - You can connect your DAPP to several networks: - - [Starknet mainnet](https://starkscan.co) (Layer 2 of [Ethereum network](https://etherscan.io/) ). - - [Starknet testnet](https://sepolia.starkscan.co/) (Layer 2 of [Sepolia network](https://sepolia.etherscan.io/) (testnet of Ethereum)). - - [Starknet-devnet](https://github.com/0xSpaceShard/starknet-devnet) (your local Starknet network, for developers). + - [Starknet Mainnet](https://starkscan.co) (Layer 2 of [Ethereum network](https://etherscan.io/) ). + - [Starknet Testnet](https://sepolia.starkscan.co/) (Layer 2 of [Sepolia network](https://sepolia.etherscan.io/) (testnet of Ethereum)). + - [Starknet Devnet](https://github.com/0xSpaceShard/starknet-devnet) (your local Starknet network, for developers). and also to some more specific solutions: @@ -26,7 +26,7 @@ Some important topics that have to be understood: > Understanding what Starknet is and how it works is necessary. Then, you can learn how to interact with it using Starknet.js. So, at this stage, you should be aware of the content of the [Starknet official doc](https://docs.starknet.io/documentation/) and [the Cairo Book](https://book.cairo-lang.org/). - The `RpcChannel` and `RpcProvider` classes and their methods are used for low-level communication with an RPC node. -- Your DAPP will mainly interact with `Account` and `Contract` class instances ; they use underlying `RpcProvider` connections to provide high-level methods for interacting with their Starknet namesakes, accounts and contracts. +- Your DAPP will mainly interact with `Account` and `Contract` class instances; they use underlying `RpcProvider` connections to provide high-level methods for interacting with their Starknet namesakes, accounts and contracts. - `Signer` and `Utils` objects contain many useful functions for interaction with Starknet.js. - The `Contract` object is mainly used to read the memory of a blockchain contract. - The `Account` object is the most useful: diff --git a/www/docusaurus.config.js b/www/docusaurus.config.js index cca7bbb96..5b64686b2 100644 --- a/www/docusaurus.config.js +++ b/www/docusaurus.config.js @@ -11,7 +11,9 @@ const generateSourceLinkTemplate = (gitRevision) => gitRevision || '{gitRevision}' }/{path}#L{line}`; -const migrationGuideLink = `${generateBaseUrl(process.env.DOCS_BASE_URL)}docs/guides/migrate`; +// TODO: restore after v7 full release +// const migrationGuideLink = `${generateBaseUrl(process.env.DOCS_BASE_URL)}docs/guides/migrate`; +const migrationGuideLink = `${generateBaseUrl(process.env.DOCS_BASE_URL)}docs/next/guides/migrate`; /** @type {import('@docusaurus/types').Config} */ const config = { @@ -69,7 +71,7 @@ const config = { //... other Algolia param }, announcementBar: { - content: `Migrate from v6`, + content: `Migrate to v7`, backgroundColor: 'rgb(230 231 232)', }, navbar: { @@ -119,7 +121,7 @@ const config = { to: '/docs/guides/intro', }, { - label: 'Migrate from v6', + label: 'Migrate to v7', to: migrationGuideLink, }, ],