diff --git a/.eslintignore b/.eslintignore index b94707787..f9e3f7991 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ node_modules/ dist/ +www/ diff --git a/www/docs/guides/L1message.md b/www/docs/guides/L1message.md index 250a7bfdc..b742b65e8 100644 --- a/www/docs/guides/L1message.md +++ b/www/docs/guides/L1message.md @@ -35,7 +35,7 @@ You have to pay in the L1 an extra fee when invoking `sendMessageToL2` (of cours ```typescript import { SequencerProvider } from "starknet"; -const provider = new SequencerProvider({ baseUrl: BaseUrl.SN_GOERLI }); // for testnet 1 +const provider = new SequencerProvider({ baseUrl: constants.BaseUrl.SN_GOERLI }); // for testnet 1 const responseEstimateMessageFee = await provider.estimateMessageFee({ from_address: L1address, diff --git a/www/docs/guides/compiled_contracts/deployBraavos.ts b/www/docs/guides/compiled_contracts/deployBraavos.ts new file mode 100644 index 000000000..d2ef23746 --- /dev/null +++ b/www/docs/guides/compiled_contracts/deployBraavos.ts @@ -0,0 +1,197 @@ +// Collection of functions for Braavos account creation +// coded with Starknet.js v5.11.1, 01/jun/2023 + +import { + BigNumberish, + CairoVersion, + CallData, + Calldata, + DeployAccountContractPayload, + DeployAccountContractTransaction, + DeployContractResponse, + EstimateFeeDetails, + InvocationsSignerDetails, + Provider, + RawCalldata, + constants, + ec, + hash, + num, + stark, +} from 'starknet'; + +const BraavosProxyClassHash: BigNumberish = + '0x03131fa018d520a037686ce3efddeab8f28895662f019ca3ca18a626650f7d1e'; +const BraavosInitialClassHash = '0x5aa23d5bb71ddaa783da7ea79d405315bafa7cf0387a74f4593578c3e9e6570'; +const BraavosAccountClassHash = '0x2c2b8f559e1221468140ad7b2352b1a5be32660d0bf1a3ae3a054a4ec5254e4'; // will probably change over time + +export function getBraavosSignature( + BraavosProxyAddress: BigNumberish, + BraavosProxyConstructorCallData: RawCalldata, + starkKeyPubBraavos: BigNumberish, + version: bigint, + max_fee: BigNumberish, + chainId: constants.StarknetChainId, + nonce: bigint, + privateKeyBraavos: BigNumberish +): string[] { + const txnHash = hash.calculateDeployAccountTransactionHash( + BraavosProxyAddress, + BraavosProxyClassHash, + BraavosProxyConstructorCallData, + starkKeyPubBraavos, + version, + max_fee, + chainId, + nonce + ); + + const parsedOtherSigner = [0, 0, 0, 0, 0, 0, 0]; + const { r, s } = ec.starkCurve.sign( + hash.computeHashOnElements([txnHash, BraavosAccountClassHash, ...parsedOtherSigner]), + num.toHex(privateKeyBraavos) + ); + const signature = [ + r.toString(), + s.toString(), + BraavosAccountClassHash.toString(), + ...parsedOtherSigner.map((e) => e.toString()), + ]; + console.log('signature =', signature); + return signature; +} + +const calcBraavosInit = (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); + + return hash.calculateContractAddressFromHash( + starkKeyPubBraavos, + BraavosProxyClassHash, + BraavosProxyConstructorCallData, + 0 + ); +} + +async function buildBraavosAccountDeployPayload( + privateKeyBraavos: BigNumberish, + { + classHash, + addressSalt, + constructorCalldata, + contractAddress: providedContractAddress, + }: DeployAccountContractPayload, + { nonce, chainId, version, maxFee }: InvocationsSignerDetails +): 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, + }; +} + +export async function estimateBraavosAccountDeployFee( + privateKeyBraavos: BigNumberish, + provider: Provider, + { blockIdentifier, skipValidate }: EstimateFeeDetails = {} +): Promise { + const version = hash.feeTransactionVersion; + const nonce = constants.ZERO; + const chainId = await provider.getChainId(); + const cairoVersion: CairoVersion = '0'; + 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 response = await provider.getDeployAccountEstimateFee( + { ...payload }, + { version, nonce }, + blockIdentifier, + skipValidate + ); + const suggestedMaxFee = stark.estimatedFeeToMaxFee(response.overall_fee); + + return suggestedMaxFee; +} + +export async function deployBraavosAccount( + privateKeyBraavos: BigNumberish, + provider: Provider, + max_fee?: BigNumberish +): Promise { + const nonce = constants.ZERO; + 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, + } + ); +} diff --git a/www/docs/guides/connect_account.md b/www/docs/guides/connect_account.md index 84a09b9fb..9d3ab0022 100644 --- a/www/docs/guides/connect_account.md +++ b/www/docs/guides/connect_account.md @@ -12,7 +12,7 @@ You need 2 data : - the private key of this account ```typescript -import { Account, ec, Provider } from "starknet"; +import { Account, Provider } from "starknet"; ``` ## Connect a predeployed account in Starknet-devnet @@ -38,10 +38,9 @@ Then you can use this code : const provider = new Provider({ sequencer: { baseUrl:"http://127.0.0.1:5050" } }); // initialize existing pre-deployed account 0 of Devnet const privateKey = "0xe3e70682c2094cac629f6fbed82c07cd"; -const starkKeyPair = ec.getKeyPair(privateKey); const accountAddress = "0x7e00d496e324876bbc8531f2d9a82bf154d1a04a50218ee74cdd372f75a551a"; -const account = new Account(provider, accountAddress, starkKeyPair); +const account = new Account(provider, accountAddress, privateKey); ``` Your account is now connected, and you can use it. @@ -61,11 +60,10 @@ import * as dotenv from "dotenv"; dotenv.config(); // initialize provider -const provider = new Provider({ sequencer: { network: NetworkName.SN_GOERLI2 } }); +const provider = new Provider({ sequencer: { network: constants.NetworkName.SN_GOERLI2 } }); // initialize existing account const privateKey = process.env.OZ_NEW_ACCOUNT_PRIVKEY; -const starkKeyPair = ec.getKeyPair(privateKey); const accountAddress = "0x051158d244c7636dde39ec822873b29e6c9a758c6a9812d005b6287564908667"; -const account = new Account(provider, accountAddress, starkKeyPair); +const account = new Account(provider, accountAddress, privateKey); ``` diff --git a/www/docs/guides/connect_contract.md b/www/docs/guides/connect_contract.md index 63770f946..a3246739a 100644 --- a/www/docs/guides/connect_contract.md +++ b/www/docs/guides/connect_contract.md @@ -9,19 +9,25 @@ Once your provider is initialized, you can connect a contract already deployed i You need 2 data : - the address of the contract -- the ABI file of the contract (or the compiled 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()` command can help you. +> 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 to the sequencer/node, it's recommended to store the result in your computer, to be able to reuse it later without using the provider : + +```typescript +import fs from "fs"; +const compressedContract = await provider.getClassAt(addrContract); +fs.writeFileSync('./myAbi.json', json.stringify( compressedContract.abi, undefined, 2)); +``` > 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 file +## Get the abi from a compiled/compressed file ```typescript import { Provider, Contract, json } from "starknet"; ``` -If you have the compiled file of the contract, use this code to recover all data, including ABI : +If you have the compiled/compressed file of the contract, use this code to recover all data, including ABI : ```typescript const compiledContract = json.parse(fs.readFileSync("./compiledContracts/test.json").toString("ascii")); @@ -33,7 +39,7 @@ const compiledContract = json.parse(fs.readFileSync("./compiledContracts/test.js ```typescript // initialize provider -const provider = new Provider({ sequencer: { network: NetworkName.SN_GOERLI } }); +const provider = new Provider({ sequencer: { network: constants.NetworkName.SN_GOERLI } }); // initialize deployed contract const testAddress = "0x7667469b8e93faa642573078b6bf8c790d3a6184b2a1bb39c5c923a732862e1"; diff --git a/www/docs/guides/connect_network.md b/www/docs/guides/connect_network.md index 088f82ec5..bece4ba4a 100644 --- a/www/docs/guides/connect_network.md +++ b/www/docs/guides/connect_network.md @@ -15,14 +15,14 @@ import {Provider} from 'starknet'; ## Connect your DAPP to Starknet mainnet ```typescript -const provider = new Provider({ sequencer: { network: NetworkName.SN_MAIN } }) +const provider = new Provider({ sequencer: { network: constants.NetworkName.SN_MAIN } }) ``` ## Connect your DAPP to Starknet testnet 1 & 2 ```typescript -const provider = new Provider({ sequencer: { network: NetworkName.SN_GOERLI } }) // for testnet 1 -const provider = new Provider({ sequencer: { network: NetworkName.SN_GOERLI2 } }) // for testnet 2 +const provider = new Provider({ sequencer: { network: constants.NetworkName.SN_GOERLI } }) // for testnet 1 +const provider = new Provider({ sequencer: { network: constants.NetworkName.SN_GOERLI2 } }) // for testnet 2 ``` ## Connect your DAPP to Starknet-devnet @@ -55,6 +55,12 @@ For a local [Pathfinder](https://github.com/eqlabs/pathfinder) node : const provider = new Provider({ rpc: { nodeUrl: '127.0.0.1:9545' } }) ``` +Your node can be located in your local network (example : pathfinder node located in a computer of you network, launched with this additional option : `--http-rpc 0.0.0.0:9545`). You connect with : + +```typescript +const provider = new Provider({ rpc: { nodeUrl: '192.168.1.99:9545' } }) +``` + ## Specific methods Some methods are available only if connected to a sequencer, and some others are available only if connected to a node (using RPC). @@ -64,8 +70,8 @@ Some methods are available only if connected to a sequencer, and some others are For example, if you want to estimate the fee of a L1 ➡️ L2 message, you need to use a method that is available only in the sequencer. The class `SequencerProvider` is available for this case : ```typescript -import { SequencerProvider } from "starknet"; -const provider = new SequencerProvider({ baseUrl: BaseUrl.SN_GOERLI2 }); // for testnet 2 +import { SequencerProvider, constants } from "starknet"; +const provider = new SequencerProvider({ baseUrl: constants.BaseUrl.SN_GOERLI2 }); // for testnet 2 const responseEstimateMessageFee = await provider.estimateMessageFee(.....) ``` @@ -76,12 +82,19 @@ For example, if you want to read the events recorded in a range of blocks, you n ```typescript import { RpcProvider } from "starknet"; const providerRPC = new RpcProvider({ nodeUrl: "http://192.168.1.99:9545" }); // for a pathfinder node located in a PC in the local network +const lastBlock = await providerRPC.getBlock('latest'); let eventsList = await providerRPC.getEvents({ address: myContractAddress, - from_block: {block_number: block_number0}, - to_block: {block_number: block_number1}, - chunk_size: 1000 + from_block: {block_number: lastBlock.block_number-2}, + to_block: {block_number: lastBlock.block_number}, + chunk_size: 400 }); ``` RPC providers are for example Infura, Alchemy, Chainstack... Or you can spin up your own Pathfinder node! + +For example, to connect to Alchemy with your personal API key : + +```typescript +const providerRPC = new RpcProvider({ nodeUrl: 'https://starknet-mainnet.g.alchemy.com/v2/' + alchemyKey}); +``` diff --git a/www/docs/guides/create_account.md b/www/docs/guides/create_account.md index ee2dccc8c..ffd6653fe 100644 --- a/www/docs/guides/create_account.md +++ b/www/docs/guides/create_account.md @@ -11,31 +11,33 @@ Unlike in Ethereum where a wallet is created with a public and private key pair, Account contracts on Starknet cannot be deployed without paying a fee. Create an account is a bit tricky ; you have several steps : -1. Decide on your account type (OpenZeppelin, Argent, ...). +1. Decide on your account type (OpenZeppelin, ArgentX, Braavos, ...). 2. Compute the address of your future account. 3. Send funds to this pre-computed address. The funds will be used to pay for the account contract deployment, and remains will fund the new account. 4. Actual deployment of the Account ## Create OZ (Open Zeppelin) account : -Here, we will create a wallet with the Open Zeppelin smart contract v0.5.1. The contract class is already implemented in both Testnet 1 & 2. +> Level : easy. + +Here, we will create a wallet with the Open Zeppelin smart contract v0.5.1. The contract class is already implemented in both Testnet 1 & 2. +This contract is coded in Cairo 0, so it will not survive to the upcoming regenesis of Starknet. ```typescript -import { Account, ec, json, stark, Provider, hash, CallData } from "starknet"; +import { Account, constants, ec, json, stark, Provider, hash, CallData } from "starknet"; ``` ### compute address : ```typescript // connect provider -const provider = new Provider({ sequencer: { network: NetworkName.SN_GOERLI } }); +const provider = new Provider({ sequencer: { network: constants.NetworkName.SN_GOERLI } }); // new Open Zeppelin account v0.5.1 : // Generate public and private key pair. const privateKey = stark.randomAddress(); console.log('New OZ account :\nprivateKey=', privateKey); -const starkKeyPair = ec.getKeyPair(privateKey); -const starkKeyPub = ec.getStarkKey(starkKeyPair); +const starkKeyPub = ec.starkCurve.getStarkKey(privateKey); console.log('publicKey=', starkKeyPub); const OZaccountClassHash = "0x2794ce20e5f2ff0d40e632cb53845b9f4e526ebd8471983f7dbd355b721d5a"; @@ -54,7 +56,7 @@ If you want a specific private key, replace `stark.randomAddress()` by your choi Then you have to fund this address! -How to proceed is out of the scope of this guide, by you can for example : +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. @@ -71,7 +73,7 @@ curl -X POST http://127.0.0.1:5050/mint -d '{"address":"0x04a093c37ab61065d00155 If you have sent enough fund to this new address, you can go forward to the final step : ```typescript -const OZaccount = new Account(provider, OZcontractAddress, starkKeyPair); +const OZaccount = new Account(provider, OZcontractAddress, privateKey); const { transaction_hash, contract_address } = await OZaccount.deployAccount({ classHash: OZaccountClassHash, @@ -85,7 +87,9 @@ console.log('✅ New OpenZeppelin account created.\n address =', contract_addr ## Create Argent account -Here, we will create a wallet with the Argent smart contract v0.2.3. This case is more complicated, because we will have the wallet behind a proxy contract (this way, the wallet contract can be updated). The contract classes of both contracts are already implemented in both Testnet 1 & 2. +> Level : medium. + +Here, we will create a wallet with the Argent smart contract v0.2.3. This case is more complicated, because we will have the account behind a proxy contract (this way, the wallet contract can be updated). The contract classes of both contracts are already implemented in both Testnet 1 & 2. > If necessary OZ contracts can also be created with a proxy. @@ -97,7 +101,7 @@ import { Account, ec, json, stark, Provider, hash, CallData } from "starknet"; ```typescript // connect provider -const provider = new Provider({ sequencer: { network: NetworkName.SN_GOERLI } }); +const provider = new Provider({ sequencer: { network: constants.NetworkName.SN_GOERLI } }); //new Argent X account v0.2.3 : const argentXproxyClassHash = "0x25ec026985a3bf9d0cc1fe17326b245dfdc3ff89b8fde106542a3ea56c5a918"; @@ -106,8 +110,7 @@ const argentXaccountClassHash = "0x033434ad846cdd5f23eb73ff09fe6fddd568284a0fb7d // Generate public and private key pair. const privateKeyAX = stark.randomAddress(); console.log('AX_ACCOUNT_PRIVATE_KEY=', privateKeyAX); -const starkKeyPairAX = ec.getKeyPair(privateKeyAX); -const starkKeyPubAX = ec.getStarkKey(starkKeyPairAX); +const starkKeyPubAX = ec.starkCurve.getStarkKey(privateKey); console.log('AX_ACCOUNT_PUBLIC_KEY=', starkKeyPubAX); // Calculate future address of the ArgentX account @@ -134,7 +137,7 @@ Then you have to fund this address. If you have sent enough fund to this new address, you can go forward to the final step : ```typescript -const accountAX = new Account(provider, AXcontractAddress, starkKeyPairAX); +const accountAX = new Account(provider, AXcontractAddress, privateKeyAX); const deployAccountPayload = { classHash: argentXproxyClassHash, @@ -146,9 +149,84 @@ const { transaction_hash: AXdAth, contract_address: AXcontractFinalAdress } = aw console.log('✅ ArgentX wallet deployed at :',AXcontractFinalAdress); ``` +## Create Braavos account + +> Level : hard. + +Even more complicated, a Braavos account needs also a proxy, but needs in addition a specific signature. Starknet.js is handling only Starknet standard signatures ; so we needs 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](./compiled_contracts/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 alpha-goerli +``` + +Initialization : + +```typescript +import { Provider, Account, num, stark } from "starknet"; +import { calculateAddressBraavos, + deployBraavosAccount, + estimateBraavosAccountDeployFee +} from "./deployBraavos"; +import axios from "axios"; +``` + +If you want to create yourself the private key, for example with a random number : + +```typescript +const privateKeyBraavos = stark.randomAddress(); +``` + +If you want to use a private key generated by your browser wallet, create a new account (without deploying it), then copy/paste the account private key (it's useless to copy the public key). + +```typescript +const privateKeyBraavos = "0x02e8....e12"; +``` + +### Compute address + +```typescript +// initialize Provider +const providerDevnet = new Provider({ sequencer: { baseUrl: "http://127.0.0.1:5050" } }); +// address +const BraavosProxyAddress = calculateAddressBraavos(privateKeyBraavos); +console.log('Calculated account address=', BraavosProxyAddress); +``` + +### Estimate fees + +```typescript +// estimate fees +const estimatedFee = await estimateBraavosAccountDeployFee(privateKeyBraavos, providerDevnet); +console.log("calculated fee =", estimatedFee); +``` + +### Deploy account + +```typescript +// fund account address before account creation (easy in devnet) +const { data: answer } = await axios.post('http://127.0.0.1:5050/mint', { + "address": BraavosProxyAddress, + "amount": 10_000_000_000_000_000_000, + "lite": true + }, { headers: { "Content-Type": "application/json" } }); +console.log('Answer mint =', answer); // 10 ETH + +// deploy Braavos account +const { transaction_hash, contract_address: BraavosAccountFinalAddress } = + await deployBraavosAccount(privateKeyBraavos, providerDevnet,estimatedFee); + // estimatedFee is optional +console.log('Transaction hash =', transaction_hash); +await providerDevnet.waitForTransaction(transaction_hash); +console.log('✅ Braavos wallet deployed at', BraavosAccountFinalAddress); +``` + +The computed address has been funded automatically by minting new dummy ETH in Starknet devnet! + ## Create your account abstraction -You are not limited to OZ or Argent contracts. You can create your own contract for wallet. It's the concept of Account Abstraction. +You are not limited to these 3 contracts contracts. You can create your own contract for wallet. It's the concept of Account Abstraction. You can customize entirely the wallet - for example : @@ -162,15 +240,11 @@ You can customize entirely the wallet - for example : - whitelist of address for transfer. -- multisig +- multisig. -The only limitation is your imagination... - -> Prior to the declaration of the contract, do not forget to read the compiled contract with `json.parse` : +- delayed withdraw. -```typescript -const compiledAAaccount = json.parse(fs.readFileSync("./compiled_contracts/myAccountAbstraction.json").toString("ascii") -``` +The only limitation is your imagination... Here is an example of a customized wallet, including super administrator management, on a local starknet-devnet : @@ -178,6 +252,7 @@ Here is an example of a customized wallet, including super administrator managem ```typescript import { Account, ec, json, stark, Provider, hash, CallData } from "starknet"; +import fs from "fs"; import axios from "axios"; ``` @@ -187,25 +262,20 @@ const provider = new Provider({ sequencer: { network: "http://127.0.0.1:5050" } // initialize existing predeployed account 0 of Devnet const privateKey0 = "0xe3e70682c2094cac629f6fbed82c07cd"; -const starkKeyPair0 = ec.getKeyPair(privateKey0); const accountAddress0 = "0x7e00d496e324876bbc8531f2d9a82bf154d1a04a50218ee74cdd372f75a551a"; -const account0 = new Account(provider, accountAddress0, starkKeyPair0); +const account0 = new Account(provider, accountAddress0, privateKey0); // new account abstraction : // Generate public and private key pair. const AAprivateKey = stark.randomAddress(); console.log('New account :\nprivateKey=', AAprivateKey); -const AAstarkKeyPair = ec.getKeyPair(AAprivateKey); -const AAstarkKeyPub = ec.getStarkKey(AAstarkKeyPair); +const AAstarkKeyPub = ec.starkCurve.getStarkKey(AAprivateKey); console.log('publicKey=', AAstarkKeyPub); // declare the contract -const compiledAAaccount = json.parse(fs.readFileSync("./compiled_contracts/myAccountAbstraction.json").toString("ascii"); -const AAaccountClassHash = "0x5139780c7ec8246e21a22e49f4fa0ce430237df4a4b241214a3a5a5c120120d"; -const { transaction_hash: declTH, class_hash: decCH } = await account0.declare({ - classHash: AAaccountClassHash, - contract: compiledAAaccount -}); +const compiledAAaccount = json.parse(fs.readFileSync("./compiled_contracts/myAccountAbstraction.json").toString("ascii")); +const { transaction_hash: declTH, class_hash: decCH } = + await account0.declare({contract: compiledAAaccount}); console.log('Customized account class hash =', decCH); await provider.waitForTransaction(declTH); @@ -227,7 +297,7 @@ const { data: answer } = await axios.post('http://127.0.0.1:5050/mint', { "addre console.log('Answer mint =', answer); // deploy account -const AAaccount = new Account(provider, AAcontractAddress, AAstarkKeyPair); +const AAaccount = new Account(provider, AAcontractAddress, AAprivateKey); const { transaction_hash, contract_address } = await AAaccount.deployAccount({ classHash: AAaccountClassHash, constructorCalldata: AAaccountConstructorCallData, @@ -237,4 +307,7 @@ await provider.waitForTransaction(transaction_hash); console.log('✅ New customized account created.\n address =', contract_address); ``` -The pre-computed address has been funded automatically by minting new dummy ETH in Starknet devnet! +## Account update + +For ArgentX and Braavos wallets, if you have created the privkey inside the browser wallet, necessary upgrades will be automatically managed in the wallet. +However, if you have created yourself the private key, it becomes your responsibility to update the account implementation class when it's necessary. It can be done with the `upgrade` function of the implementation class. diff --git a/www/docs/guides/create_contract.md b/www/docs/guides/create_contract.md index 3acee095e..84f8bb08b 100644 --- a/www/docs/guides/create_contract.md +++ b/www/docs/guides/create_contract.md @@ -20,74 +20,63 @@ In Starknet, a new contract has to be added in two phases : - The contract class contains the logic of the contract. A contract class is identified by its Class Hash. - The contract instance contains the memory storage of this instance. A contract instance is identified by its contract address. You will interact with the contract instance by using this address. -You will have only one Class Hash for the contract code, but you can have as many contract instances as you need. +You will have only one Class Hash for one contract code, but you can have as many contract instances as you need. Other users of the network can use your declared contract. It means that if somebody has already declared a contract class (and paid this declaration), and if you would like to have your own instance of this contract, you have only to deploy (and pay) a new instance. Example : if you want an ERC20 contract, and somebody has already declared an ERC20 contract that conforms to your needs, you have just to deploy a new instance of this contract class. ```typescript -import { Provider, Account, Contract, ec, json, stark, uint256, shortString } from "starknet"; +import { Provider, Account, Contract, json, stark, uint256, shortString } from "starknet"; ``` -## Class Hash 😕 +## `declareAndDeploy()` your new contract -TLDR : Starknet.js is not (yet) able to calculate a Class Hash 😮. +Starknet.js proposes a function to perform both operations in one step : `declareAndDeploy()`. -The Class Hash should be a result of the compilation, but today, it's not recorded in the compiled file. It could be calculated during the declaration activity, but it's actually too long to process. - -So you need to calculate yourself the Class hash, and add it as constant in your code. - -> You can calculate it with some other tool, for example: [Starkli](https://github.com/xJonathanLEI/starkli) - -## `declareDeploy()` your new contract - -Starknet.js proposes a function to perform both operations in one step : `declareDeploy()`. - -Here, to declare & deploy a `Test.cairo` smartcontract, in Testnet 1 : +Here, to declare & deploy a `Test.cairo` smartcontract, in devnet : ```typescript // connect provider -const provider = new Provider({ sequencer: { network: NetworkName.SN_GOERLI } }); +const provider = new Provider({ sequencer: { baseUrl: "http://127.0.0.1:5050" } }); // connect your account. To adapt to your own account : const privateKey0 = process.env.OZ_ACCOUNT_PRIVATE_KEY; const account0Address: string = "0x123....789"; - -const starkKeyPair0 = ec.getKeyPair(privateKey0); -const account0 = new Account(provider, account0Address, starkKeyPair0); +const account0 = new Account(provider, account0Address, privateKey0); // Declare & deploy Test contract in devnet -// ClassHash has been calculated previously with specific tool -const testClassHash = "0xff0378becffa6ad51c67ac968948dbbd110b8a8550397cf17866afebc6c17d"; -const compiledTest = json.parse(fs.readFileSync("./compiled_contracts/test.json").toString("ascii")); -const deployResponse = await account0.declareDeploy({ contract: compiledTest, classHash: testClassHash }); +const compiledTestSierra = json.parse(fs.readFileSync( "./compiledContracts/test.sierra").toString( "ascii")); +const compiledTestCasm = json.parse(fs.readFileSync( "./compiledContracts/test.casm").toString( "ascii")); +const deployResponse = await account0.declareAndDeploy({ contract: compiledTestSierra, casm: compiledTestCasm }); // Connect the new contract instance : const myTestContract = new Contract(compiledTest.abi, deployResponse.deploy.contract_address, provider); +console.log("Test Contract Class Hash =", deployResponse.declare.class_hash); console.log('✅ Test Contract connected at =', myTestContract.address); ``` ## `deployContract()` for a new instance -If the contract class is already deployed, it's faster and cheaper to use `deployContract()`. +If the contract class is already declared, it's faster and cheaper : just use `deployContract()`. ```typescript // connect provider -const provider = new Provider({ sequencer: { network: NetworkName.SN_GOERLI } }); +const provider = new Provider({ sequencer: { baseUrl: "http://127.0.0.1:5050" } }); // connect your account. To adapt to your own account : const privateKey0 = process.env.OZ_ACCOUNT_PRIVATE_KEY; const account0Address: string = "0x123....789"; -const starkKeyPair0 = ec.getKeyPair(privateKey0); -const account0 = new Account(provider, account0Address, starkKeyPair0); +const account0 = new Account(provider, account0Address, privateKey0); // Deploy Test contract in devnet -// ClassHash has been calculated previously with specific tool +// 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 -const { abi: testAbi } = await provider.getClassAt(deployResponse.contract_address); +const { abi: testAbi } = await provider.getClassByHash( testClassHash); if (testAbi === undefined) { throw new Error("no abi.") }; // Connect the new contract instance : @@ -95,28 +84,94 @@ const myTestContract = new Contract(testAbi, deployResponse.contract_address, pr console.log('✅ Test Contract connected at =', myTestContract.address); ``` +## Construct the constructor + +If your contract has a constructor with inputs, you have to provide these inputs in the `deployContract` or `declareAndDeploy` commands. +For example, with this contract constructor : + +```json + "name": "constructor", + "inputs": [ + { + "name": "text", + "type": "core::felt252" + }, + { + "name": "longText", + "type": "core::array::Array::" + }, + { + "name": "array1", + "type": "core::array::Array::" + } + ], +``` + +You have several ways to define these inputs : + +### myCalldata.compile + +This is the recommended way to proceed : + +```typescript +const myArray1: RawCalldata = ["0x0a", 24, 36n]; +const contractCallData: CallData = new CallData(compiledContractSierra.abi); +const contractConstructor: Calldata = contractCallData.compile("constructor", { + text: 'niceToken', + longText: "http://addressOfMyERC721pictures/image1.jpg", + array1: myArray1 + }); +const deployResponse = await account0.deployContract({ + classHash: contractClassHash, + constructorCalldata: contractConstructor +}); +``` + +Starknet.js will perform a full verification of conformity with the abi. Properties can be unordered. Do not use properties for array_len, it will be handled automatically by Starknet.js. + +### CallData.compile + +For very simple constructors, you can use `CallData.compile` : + +```typescript +const myArray1: RawCalldata = ["0x0a", 24, 36n]; +const contractConstructor: Calldata = CallData.compile({ + text: 'niceToken', + longText: "http://addressOfMyERC721pictures/image1.jpg", + array1: myArray1 + }); +const deployResponse = await account0.deployContract({ + classHash: contractClassHash, + constructorCalldata: contractConstructor +}); +``` + +Properties have to be ordered in conformity with the abi. + +Even easier: + +```typescript +const contractConstructor: Calldata = CallData.compile(['niceToken', "http://addressOfMyERC721pictures/image1.jpg", myArray1]); +``` + ## `declare()` for a new class If you want only declare a new Contract Class, use `declare()`. ```typescript // connect provider -const provider = new Provider({ sequencer: { network: NetworkName.SN_GOERLI } }); +const provider = new Provider({ sequencer: { baseUrl: "http://127.0.0.1:5050" } }); // connect your account. To adapt to your own account : const privateKey0 = process.env.OZ_ACCOUNT_PRIVATE_KEY; const account0Address: string = "0x123....789"; -const starkKeyPair0 = ec.getKeyPair(privateKey0); const account0 = new Account(provider, account0Address, starkKeyPair0); // Declare Test contract in devnet -// ClassHash has been calculated previously with specific tool -const testClassHash = "0xff0378becffa6ad51c67ac968948dbbd110b8a8550397cf17866afebc6c17d"; -const compiledTest = json.parse(fs.readFileSync("./compiled_contracts/test.json").toString("ascii")); -const declareResponse = await account0.declare({ contract: compiledTest, classHash: testClassHash }); - +const compiledTestSierra = json.parse(fs.readFileSync( "./compiledContracts/test.sierra").toString("ascii")); +const compiledtestCasm = json.parse(fs.readFileSync( "./compiledContracts/test.casm").toString("ascii")); +const declareResponse = await account0.declare({ contract: compiledTestSierra, casm: compiledTestCasm }); +console.log('Test Contract declared with classHash =', declareResponse.class_hash); await provider.waitForTransaction(declareResponse.transaction_hash); -console.log('✅ Test Contract Class Hash =', declareResponse.class_hash); +console.log("✅ Test Completed."); ``` - -You can use the `declare()` function for an already declared contract - it will not generate any error. diff --git a/www/docs/guides/define_call_message.md b/www/docs/guides/define_call_message.md index 9502ce2dd..c6112a9c6 100644 --- a/www/docs/guides/define_call_message.md +++ b/www/docs/guides/define_call_message.md @@ -4,255 +4,488 @@ sidebar_position: 9 # Data transformation -Cairo contracts and Javascript/Typescript languages do not have the same types of data. +This guide is the most important of all this documentation. Take your time, and read it carefully... -So, it's necessary to prepare the data before sending them to a contract (for invoke/execute, or for a constructor). +Cairo contracts and Javascript/Typescript languages do not have the same types of data. To exchange data with Starknet, the data have to be transformed and formatted in a list of numbers. +So, it's necessary to prepare the data before sending them to a contract. On the other side, when a contract sends data to your DAPP (result of a call), you also have to transform them before using them in your code. +In Starknet.js, you can perform these transformations manually, but you can take advantage of methods that performs these transformations. + ## Types of data -In Cairo, everything is felt, an integer on 251 bits. +### Cairo + +Cairo has 2 versions, involving 2 types of data : + +- **Cairo 0** : here, everything is felt, an integer on 251 bits. + Available : array, struct, tuple, named tuple, a mix of these elements. +- **Cairo 1** : with plethora of literal types : u8, u16, u32, usize, u64, u128, felt252, u256, bool, address. + Available : array, struct, tuple, a mix of these elements. + +Starknet.js is compatible with both versions. -This type does not exist in JS/TS - you have Number, bigInt, string, array, objects... and types defined in libraries. +### Starknet + +Starknet is waiting a list of felts, and answers with the same format. + +### Javascript / Typescript + +These types does not exist in JS/TS - you have Number, bigInt, string, array, objects... and types defined in libraries. In Starknet.js, it's a bit ... complicated : you have the BigNumberish type and it can include: -- String : "123", "0xabc2" +- String representing a number : "123", "0xabc2" - Number (max 53 bits) : 123 -- BigInt: 123n +- BigInt (max 255 bits): 12345612345n + +```typescript +import { BigNumberish } from "starknet"; +const decimals: BigNumberish = 18; +``` + +## Preparation of data before delivery + +If your Cairo smart-contract is waiting a : + +### felt, u8, u16, u32, usize, u64, u128, felt252, address + +Starknet is waiting a felt. +You can send to Starknet.js methods : bigNumberish. + +```typescript +await myContract.my_function(12,"13","0xe",15n); +``` + +### bool + +Starknet is waiting a felt, containing 0 or 1. +You can send to Starknet.js methods : boolean, bigNumberish. + +```typescript +await myContract.my_function(true,1); +``` -## function argument types +### u256 -There are 4 different types of contract function arguments used in Starknet.js. +Starknet is waiting 2 felts, the first including the lowest 128 bits, the second including the 128 highest bits. +You can send to Starknet.js methods : bigNumberish (Cairo 1 only), Uint256 object (both Cairo 0 & 1). -### Array of < BigNumberish > +```typescript +await myContract0.my_function({low: 100, high: 0}) // Cairo 0 & 1 contract +await myContract1.my_function(cairo.uint256(100)) // Cairo 0 & 1 contract +await myContract2.my_function(12345678, "13456789765", "0xe23a40b543f", 1534566734334n) // Cairo 1 contract +``` -You have to create by yourself this array of < BigNumberish >, in respect with the order of the Cairo function parameters : +In specific cases that we will see hereunder, you can use an object, with the following format : ```typescript -const myCallData = [ // array of - 123, // number 53 bits - "0x2345", // string - bn1, // BigInt - bi1.toString(), // BigInt converted to string - num1, // number 53 bits - initialUint256.low, initialUint256.high, //object converted to BigNumberish - coord.x0, coord.y0, coord.z0, //object converted to BigNumberish - shortString.encodeShortString('A'), - 2, "123", "0x2345" // an array of 2 felts -]; -// in Typescript, this object type is : `RawCalldata` +const a1: Uint256 = cairo.uint256("0x05f7cd1fd465baff2ba9d2d1501ad0a2eb5337d9a885be319366b5205a414fdd") +const a2: Uint256 = {low: "0xeb5337d9a885be319366b5205a414fdd", high: "0x05f7cd1fd465baff2ba9d2d1501ad0a2"}; +const a3: Uint256 = {low: a1.low,high: a1.high}; ``` -### Object +### string -You can list your parameters in an object: +Starknet is waiting a felt, including 31 ASCII characters max. +You can send to Starknet.js methods : string, bigNumberish. -- The names of the object parameters are the names of the Cairo function parameters. -- Simple types have to be converted in strings. -- For an array, you have to use an array of strings. -- For a Cairo struct, you have to code this way (example for an Uint256) : - `my_uint: { type: 'struct', low: initialUint256.low, high: initialUint256.high }`. +```typescript +await myContract.my_function("Token","0x0x534e5f4d41494e") +``` + +To encode yourself a string : + +```typescript +const encStr: string = shortString.encodeShortString("Stark"); +``` -Example for a constructor : +To decode yourself a string : ```typescript +const decStr: string = shortString.decodeShortString("0x7572692f706963742f7433382e6a7067"); +``` + +the result is : "uri/pict/t38.jpg" + +### longString + +longString is a string that may contain more than 31 characters. +Starknet is waiting an array of felt: string_len, string1, string2, ... +You can send to Starknet.js methods : string, bigNumberish[]. + +```typescript +await myContract.my_function("http://addressOfMyERC721pictures/image1.jpg") +``` + +If you want to split yourself your longString in 31 chars substrings : + +```typescript +const splitted: string[] = shortString.splitLongString("http://addressOfMyERC721pictures/image1.jpg") +``` + +If you want to split yourself your longString in an array of felts : + +```typescript +const longString: string[] = shortString.splitLongString("http://addressOfMyERC721pictures/image1.jpg" ).map( str => shortString.encodeShortString( str)) +``` + +### tuple + +Starknet is waiting a list of felts. +You can send to Starknet.js methods : `cairo.tuple()`, object. + +```typescript +const myTpl = cairo.tuple("0x0a", 200); +await myContract.my_function(myTpl); +``` + +To construct yourself your tuple : + +```typescript +const myTpl = {"0": "0x0a", "1": 200}; +``` + +### named tuple + +> Only for Cairo 0. + +Starknet is waiting a list of felts. +You can send to Starknet.js methods : an object, `cairo.tuple()`, list of bigNumberish. +From this ABI : + +```json { - name: shortString.encodeShortString('MyToken'), - symbol: shortString.encodeShortString('MTK'), - decimals: "18", - initial_supply: { type: 'struct', low: initialTk.low, high: initialTk.high }, - recipient: account0.address, - owner: account0.address, - list:["678","321","7890"] // array of 3 cairo felts + "name": "data2", + "type": "(min: felt, max: felt)" } ``` -> 🚨 In opposition with the object philosophy, your object content has to be ordered in respect with the order of the definition of the Cairo function parameters. +you can create this code : + +```typescript +const namedTup = {min: "0x4e65ac6", max: 296735486n}; +await myContract.my_function(namedTup); +``` + +> It's not mandatory to create an object conform to the Cairo 0 named tuple ; you can just use the `cairo.tuple()` function. -> You can't send an array of cairo struct with this object type. +### struct -### Array of < string > +Starknet is waiting a list of felts. +You can send to Starknet.js methods : an object. -You can create by yourself this array of < string >, in respect with the order of the Cairo function parameters : +```typescript +const myStruct = {type: "TR1POST", tries: 8, isBridged: true}; +await myContract.my_function(myStruct); +``` + +### array + +Starknet is waiting an array of felt: array_len, array1, array2, ... +You can send to Starknet.js methods : bigNumberish[]. + +```typescript +Const myArray = [10,"0xaa",567n]; +await myContract.my_function(myArray); +``` + +> Do not add the `array_len` parameter before your array ; Starknet.js will manage automatically this element. + +### complex types + +You can mix and nest literals, arrays, structs and tuples. + +Starknet is waiting a list of felt. +All these examples are valid : ```typescript -const myCallData = [ - "123", - "0x2345", - bn1.toString(), // BigInt converted to string - bi1.toString(), // BigInt converted to string - num.toBN(num1).toString(), // Number 53 bits converted to string - initialUint256.low.toString(), initialUint256.high.toString(), - coord.x0.toString(), coord.y0.toString(), coord.z0.toString(), - shortString.encodeShortString('A'), - "3", "52788", "123", "0x2345", // an array of 3 felts - "2", "100", "0", "234", "456" // an array of 2 Uint256 -]; -// in Typescript, this object type is : `Calldata` +type Order2 = { + p1: BigNumberish; + p2: BigNumberish[]; + }; // struct +const myOrder2: Order2 = { + p1: 17, + p2: [234, 467456745457n, '0x56ec'], + }; +const param1 = cairo.tuple(cairo.tuple(34, '0x5e'), 234n); +const param2 = [[200, 201],[202, 203],[204, 205]]; +const param3 = [myOrder2, myOrder2]; +const param4 = [cairo.tuple(251, 40000n), cairo.tuple(252, 40001n)]; +await myContract.my_function(param1, param2, param3, param4); ``` -Or you can use the function `CallData.compile()`, that converts an object type to an `array of string` type. +## Authorized types for Starknet.js methods + +There are 12 methods using contract parameters. Some types are authorized for each method : -For a cairo contract, with this constructor : +### list of parameters -```cairo -func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - name: felt, symbol: felt, decimals: felt, initial_supply: Uint256, recipient: felt, owner: felt -) +Only meta-class methods are using a list of parameters (as illustrated in the previous chapter). +A Meta-Class is a Class which has any of its properties determined at run-time. The Contract object uses a Contract's ABI to determine what methods are available. + +```typescript +await myContract.my_function("TOKEN", "13", [10, 11, 12], 135438734812n); +// or +const functionName="my_function"; +await myContract[functionName]("TOKEN", "13", [10, 11, 12], 135438734812n); ``` -You will have to create in your code this set of data : +### Array of parameters + +An array of parameters can be used as input: ```typescript -const initialTk = uint256.bnToUint256(100); -const ERC20ConstructorCallData = CallData.compile({ - name: shortString.encodeShortString('MyToken'), - symbol: shortString.encodeShortString('MTK'), - decimals: "18", - initial_supply: { type: 'struct', low: initialTk.low, high: initialTk.high }, - recipient: account0.address, - owner: account0.address -}); +const myParams = [ {x: 100, y: 200}, + 13, + [10, 11, 12], + cairo.uint256("0x295fa652e32b")]; +const txResp = await account0.execute({ + contractAddress:testAddress, + entrypoint: "change_activity", + calldata: myParams}); ``` -### Array of < any > +All Starknet.js methods accepts this type of input, except meta-class, that needs 3 dots prefix : -With this type, you can include : +```typescript +const myParams = ["TOKEN", "13", [10, 11, 12], 135438734812n]; +await myContract.my_function(...myParams); +// or +const functionName="my_function"; +await myContract[functionName](...myParams); +``` -- BigNumberish -- objects representing a Cairo struct -- arrays +> Objects properties have to be ordered in accordance with the ABI. -...in respect with the order of the Cairo function parameters. +### Object (without ABI conformity check) -Example : +Use of objects allows a clear representation of the list of parameters : ```typescript -const myCallData = [ - 123, // number 53 bits - "0x2345", - bn1, // BigNum - bi1.toString(), // Bigint converted to string - num1, // number 53 bits - initialUint256, // object representing a struct of 2 felt - coord, // object representing a struct of 3 felt - shortString.encodeShortString('A'), // short string - [123, "0x2345"], // for an array of 2 cairo felts - [initialUint256, finallUint256] // for an array of 2 structs (Uint256 here) -]; -// in Typescript, the object type is : `Array` +const myParams = { + name: "TOKEN", + decimals: "13", + amount: 135438734812n}; +const deployResponse = await myAccount.deployContract({ + classHash: contractClassHash, + constructorCalldata: myParams }); ``` -Object representing a Cairo struct are made of `BigNumberish` elements. For example : +This type is available for : `CallData.compile(), hash.calculateContractAddressFromHash, account.deployContract, account.deployAccount, account.execute` + +> Objects properties have to be ordered in accordance with the ABI. + +### Object (with ABI conformity check) + +This is the recommended type of inputs to use, especially for complex ABI. ```typescript -interface c3D { - x0: BigNumberish; - y0: BigNumberish; - z0: BigNumberish; +const myFalseUint256 = { high: 1, low: 23456 }; // wrong order ; should be low first +type Order2 = { + p1: BigNumberish, + p2: BigNumberish[] +} +const myOrder2bis: Order2 = {// wrong order ; p1 should be first + p2: [234, 467456745457n, "0x56ec"], + p1: "17" } +const functionParameters: RawArgsObject = {//wrong order ; all properties are mixed + active: true, + symbol: "NIT", + initial_supply: myFalseUint256, + recipient: account0.address, + decimals: 18, + tupoftup: cairo.tuple(cairo.tuple(34,"0x5e") ,myFalseUint256), + card: myOrder2bis, + longText: "Zorg is back, for ever, here and everywhere", + array1: [100, 101, 102], + array2: [[200, 201], [202, 203], [204, 205]], + array3: [myOrder2bis, myOrder2bis], + array4: [myFalseUint256,myFalseUint256], + tuple1: cairo.tuple(40000n, myOrder2bis, [54,55n,"0xae"], "texte"), + name: "niceToken", + array5: [cairo.tuple(251,40000n),cairo.tuple(252,40001n)], +} +const contractCallData: CallData = new CallData(compiledContractSierra.abi); +const myCalldata: Calldata = contractCallData.compile("constructor", functionParameters); +const deployResponse = await account0.deployContract({ + classHash: contractClassHash, + constructorCalldata: myCalldata }); +// or +const myCall: Call = myContract.populate("setup_elements", functionParameters); +const tx = await account0.execute(myCall); +// or +const myCall: Call = myContract.populate("get_elements", functionParameters); +const res = await myContract.get_elements(...myCall.calldata); ``` -Same for arrays - their elements must have the `BigNumberish` type. +It can be used only with methods that knows the abi : `Contract.populate, myCallData.compile`. +Starknet.js will perform a full check of conformity with the ABI of the contract, reorder the objects properties if necessary, stop if something is wrong or missing, remove not requested properties, convert everything to Starknet format. +Starknet.js will alert earlier of errors in your parameters (with human comprehensible words), before call to Starknet. So, no more incomprehensible Starknet messages due to parameters construction. -### summary table for arguments +If a property `array_len` has been added before an array , this property is ignored as it's automatically managed by Starknet.js. -These 4 types of arguments can't be used at your convenience everywhere. Here is a table showing which types can be used in which function : +### Call, or Call[] -| Function | array of < BigNumberish > | array of < string > | object | array of < any > | MultiInvoke | -| ------------------------------------: | :-----------------------: | :--------------------------------: | :-----: | :--------------: | :---------: | -| **Typescript type** | RawCalldata | Calldata or RawArgs or RawCalldata | RawArgs | Array< any > | array | -| contract.call contract.metaClass | | ⚠️ | | ✔️ | | -| contract.invoke contract.metaClass | | ⚠️ | | ✔️ | | -| account.execute | ✔️ | ✔️ | | | ✅ | -| account.deploy | | ✔️ | ✔️ | | ✅ | -| account.deployContract | | ✔️ | ✔️ | | ✅ | -| account.declareDeploy | | ✔️ | ✔️ | | | -| account.deployAccount | ✔️ | ✔️ | | | | -| hash.calculateContractAddressFromHash | ✔️ | ✔️ | | | | +A Call is an object with this format : -⚠️ = only for a list of felt (no array or struct). +```typescript +type Call = { + contractAddress: string, + entrypoint: string, + calldata?: RawArgs, +} +``` -> for Typescript, you can import these type of data : +and is only authorized with `Account.execute `. It can be generated manually or by `Contract.populate()` : ```typescript -import { type Calldata, type RawArgs } from "starknet"; -import { type RawCalldata } from "starknet/dist/types/lib"; +const myCall: Call = myContract.populate("get_component", [100, recipient]); +// or +const myCall: Call = { + contractAddress: tokenContract.address, + entrypoint: "get_component", + calldata: CallData.compile( [100, recipient]), + } + +const tx = await account0.execute(myCall); ``` -## Receive data from a Cairo contract +It's particularly interesting when you want to invoke a function several times in the same transaction : -When you perform a call, you have the result in an object : +```typescript +const myCall1: Call = myContract.populate("mint", {type: 7, qty: 10}); +const myCall2: Call = myContract.populate("mint", {type: 21, qty: 3}); +const myCall3: Call = myContract.populate("mint", {type: 2, qty: 1}); +const tx = await account0.execute([myCall1, myCall2, myCall3]); +``` -- With a contract.call : `const result=contract.call("read_val", myParameters)`. -- With a contract.meta-class : `const result=contract.read_val(...myParameters)`. +### Array of strings (representing numbers) -| Type in Cairo | Cairo code | Type expected in JS/TS | JS/TS function to recover data | -| ------------------------------------- | ------------------------------------------ | --------------------------------- | ------------------------------------------------------------------ | -| felt (251 bits max) | `func getV()->(total:felt)` | BN | `const total = result.total` | -| | | number (53 bits max) | `const total:number = parseInt(result.total)` | -| | | string representing an hex number | `const address:string = number.toHex(result.total)` | -| Uint256 (256 bits max) | `func getV()->(balance:Uint256)` | BN | `const balance = uint256.uint256toBN(result.balance)` | -| array of felt | `func getV()->(list_len:felt, list:felt*)` | BN[] | `const list= result.list` | -| shortString (31 ASCII characters max) | `func getV()->(title:felt)` | string | `const title:string = shortString.decodeShortString(result.title)` | +This type is particularly useful when you need the maximum of performance and speed in your code ; You have no automatic transformation, no checks with ABI, no parsing. -## Handle Strings : +You provide to starknet.js the low level data expected by Starknet : -In Javascript/Typescript, the max length of a string is nearly limitless. In Cairo, a string is limited to only 31 characters, and is called a ShortString. +```typescript +const specialParameters: Calldata = [ + '2036735872918048433518', + '5130580', + '18', + '23456', + '1', + '17', + '3', + '234', + '467456745457', + '22252']; +const getResponse = await myAccount.get_bal(specialParameters, + {parseRequest: false}); +``` + +To use with `parseRequest: false` (see hereunder). + +### summary table for arguments + +These types of arguments can't be used at your convenience everywhere. Here is a table showing which types can be used in which function : + +| Function | array of parameters | ordered object | non ordered object | Call & MultiCall | list of parameters | array of strings (\*) | array of strings (\*\*) | +| ----------------------------------------------------------: | :-----------------: | :-------------: | :----------------: | :--------------------------: | :----------------: | :-------------------: | :---------------------: | +| **Typescript type** | []
Calldata | {} RawArgsArray | {} RawArgsObject | Call & Call[] | ...Calldata | string[] | string[] | +| contract.metaClass() contract\[metaclass]() | | | | | ✔️ | ✔️ | ✔️ | +| contract.call / contract.invoke | ✔️ | | | | | ✔️ | ✔️ | +| account.execute

(with 3 params, incl. calldata) |

✔️ |

✔️ | | ✔️



| | |

✔️ | +| account.deployContract / Account | ✔️ | ✔️ | | | | | ✔️ | +| account.declareAndDeploy | ✔️ | ✔️ | | | | | ✔️ | +| CallData.compile | ✔️ | ✔️ | | | | | ✔️ | +| myCallData.compile | ✔️ | ✔️ | ✔️ | | | | ✔️ | +| Contract.populate | ✔️ | ✔️ | ✔️ | | | | ✔️ | +| hash. calculateContract AddressFromHash | ✔️ | ✔️ | | | | | ✔️ | + +> (\*) = with `parseRequest: false` +> (\*\*) = result of `Calldata.compile, myCallData.compile, myContract.populate().calldata` -### Encode ShortString : +## Receive data from a Cairo contract + +When you perform a call, the result depends of the contract language : + +- In Cairo 0, then answer is an object, with keys using the Cairo variables names. Example : + +```typescript +const res=myContract.call(...); +const amount = res.amount; +``` -From JS to Cairo, you need to encode this ShortString to a number on 248 bits : +- In Cairo 1, the result is a variable : ```typescript -const myText = "uri/pict/t38.jpg"; // 31 chars max -const encodedText: string = shortString.encodeShortString(myText); +const amount = myContract.call(...); ``` -the result is Hex number string : "0x7572692f706963742f7433382e6a7067" +| Type in Cairo 1 | Cairo 1 code | Type expected in JS/TS | JS/TS function to recover data | +| --------------------------------------------------------- | ---------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| u8, u16, u32, usize, u64, u128, felt252, address | `func get_v()->u128` | bigint | `const res: bigint = myContract.call(...` | +| | | string representing an hex number | `const res=myContract.call(...`
`const address: string = num.toHex(res);` | +| u8, u16, u32, usize | `func get_v() -> u16` | number (53 bits max) | `const res=myContract.call(...`
`const total: number = Number(res)` | +| u256 (255 bits max) | `func get_v() -> u256` | bigint | `const res: bigint = myContract.call(...` | +| array of u8, u16, u32, usize, u64, u128, felt252, address | `func get_v() -> Array` | bigint[] | `const res: bigint[] = myContract.call(...` | +| shortString (31 ASCII characters max) | `func get_v() -> felt252` | string | `const res=myContract.call(...`
`const title:string = shortString.decodeShortstring(res)` | +| longString | `func get_v() -> Array` | string | `const res=myContract.call(...`
`const longString = res.map( (shortStr: bigint) => { return shortString.decodeShortString( num.toHex( shortStr)) }).join("");` | +| Tuple | `func get_v() -> (felt252, u8)` | Object {"0": bigint, "1": bigint} | `const res = myContract.call(...`
`const res0: bigint = res["0"];`
`const results: bigint[] = Object.values(res)` | +| Struct | ` func get_v() -> MyStruct` | MyStruct = { account: bigint, amount: bigint} | `const res: MyStruct = myContract.call(...` | +| complex array | `func get_v() -> Array` | MyStruct[] | `const res: MyStruct[] = myContract.call(...` | -### Decode ShortString : +## Parse configuration -From Cairo to JS, you need to decode a BN (big number) to a string of 31 character max. +### parseRequest + +If for any reason (mainly for speed of processing), you want to define yourself the low level parameters to send to Starknet, you can use the parseRequest option. +Parameters are an array of strings (representing numbers). ```typescript -const myShortString= new BN("156113730760229877043789998731456835687"); // or result of a Contract.call -const myTextDecoded = shortString.decodeShortString(myShortString); +const txH = await myContract.send_tk([ + '2036735872918048433518', + '5130580', + '18'], + {parseRequest: false} +); ``` -the result is : "uri/pict/t38.jpg" +### parseResponse -### LongString +If for any reason, you want to receive a low level answer from Starknet, you can use the parseResponse option. -How to handle a string with more than 31 characters : +```typescript +const result = await myContract.call("get_bals",100n, {parseResponse: false}); +``` + +answer is an array of strings (representing numbers). + +### formatResponse -1. The Cairo contract has to manage this string as array of ShortString (array of felt). -2. The JS code has to split/encode the string before call/invoke. -3. The JS code has to decode/merge the BNs received from a call. +As seen above, the strings returned by Starknet are not automatically parsed, because ABI do not inform when a contract returns a string. +But there is a way to have an automatic parse of a string : + +As example, if a contract returns a struct containing a shortString and a longString : ```typescript -function splitString(myString: string): string[] { - const myShortStrings: string[] = []; - while (myString.length > 0) { - myShortStrings.push(myString.slice(0, 31)); - myString = myString.slice(31); - } - return (myShortStrings); -} -let myString = "uri:myProject/atosmotor/recurr/monkey148.jpg"; -// encoding -const myShortStrings = splitString(myString); -const myShortStringsEncoded = myShortStrings.map((shortStr) => { - return shortString.encodeShortString(shortStr) -}); // to use as input in call/invoke/deploy - -// decoding from a call -// receiving a BN[] -const stringsCoded: BN[] = result.token_uri; -const myShortStringsDecoded = stringsCoded.map((shortStr: BN) => { - return shortString.decodeShortString(shortStr.toString()) +{ name: felt252, description: Array } +``` + +You can automatize the strings parse with : + +```typescript +const formatAnswer = { name: 'string', description: 'string' } +const result = await myContract.get_text(calldata, { + parseRequest: true, + parseResponse: true, + formatResponse: formatAnswer, }); -const finalString = myShortStringsDecoded.join(""); +``` + +The result will be an object, with 2 string : + +```typescript +{ name: "Organic", description: "The best way to read a long string!!!" } ``` diff --git a/www/docs/guides/estimate_fees.md b/www/docs/guides/estimate_fees.md index 30e9908ab..0c4bd5b8e 100644 --- a/www/docs/guides/estimate_fees.md +++ b/www/docs/guides/estimate_fees.md @@ -64,7 +64,7 @@ The result is in `estimatedFee1`, of type BigInt. ## 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 do not forget to add a margin of approximately 10%** : +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%** : ```typescript estimatedFee1 * 11n / 10n @@ -80,18 +80,9 @@ stark.estimatedFeeToMaxFee(estimatedFee1, 0.1); Example for declare : ```typescript -const { suggestedMaxFee: estimatedFee1 } = await account0.estimateDeclareFee({ - contract: compiledTest, - classHash: testClassHash -}); +const { suggestedMaxFee: estimatedFee1 } = await account0.estimateDeclareFee({ contract: compiledTest }); -const declareResponse = await account0.declare( - { - contract: compiledTest, - classHash: testClassHash - }, - { - maxFee: estimatedFee1 * 11n / 10n - } +const declareResponse = await account0.declare({ contract: compiledTest}, + { maxFee: estimatedFee1 * 11n / 10n} ); ``` diff --git a/www/docs/guides/events.md b/www/docs/guides/events.md index f6654bf70..d974bcf52 100644 --- a/www/docs/guides/events.md +++ b/www/docs/guides/events.md @@ -16,7 +16,7 @@ The events are stored in a block on the blockchain. ## Events in the Cairo code -You have to analyze the Cairo code of your smart contract, to recover the list of data emitted by the event : +You have to analyze the Cairo code of your smart contract, to recover the list of data emitted by the event. An example in Cairo 0 : ```cairo @event @@ -53,17 +53,10 @@ Once compiled, this code will generate an abi file containing : Once the `my_func` is invoked, the event is stored in the blockchain and you get in return the transaction hash. -```javascript -const resu = await myTestContract.invoke("my_func"); -const txReceiptDeployTest = await provider.waitForTransaction(resu.transaction_hash); -``` - -In Typescript, you have to change a little the code : - ```typescript -import { num, InvokeTransactionReceiptResponse } from "starknet"; +import { InvokeTransactionReceiptResponse } from "starknet"; -const resu = await myTestContract.invoke("my_func"); +const resu = await myTestContract.my_func(); const txReceiptDeployTest: InvokeTransactionReceiptResponse = await provider.waitForTransaction(resu.transaction_hash); console.log("events =",txReceiptDeployTest.events); ``` @@ -96,7 +89,7 @@ Now, you have all the events of the block. Here, we have 2 events - the last one ``` -Use the contract deployment address, to filter the events and read the data from your smart contract : +Use the contract deployment address `testContractAddress`, to filter the events and read the data from your smart contract : ```typescript const event = txReceiptDeployTest.events.find( @@ -107,3 +100,5 @@ const eventD1 = event.data[0]; const eventD2 = event.data[1]; const eventD3 = event.data[2]; ``` + +If you do not have the transaction hash, you have to search in the blocks of Starknet. See an example [here](connect_network#specific-rpc-methods). diff --git a/www/docs/guides/interact.md b/www/docs/guides/interact.md index adab56cc7..83f1af4c0 100644 --- a/www/docs/guides/interact.md +++ b/www/docs/guides/interact.md @@ -16,7 +16,7 @@ Your account should be funded enough to pay fees (0.01 ETH should be enough to s ![](./pictures/Interact_contract.png) -Here we will interact with a `test.cairo` contract, already deployed in Testnet 1 and Tesnet 2, at addresses : +Here we will interact with a `test.cairo` contract (Cairo 0), already deployed in Testnet 1 and Tesnet 2, at addresses : - testnet1 : [0x5f7cd1fd465baff2ba9d2d1501ad0a2eb5337d9a885be319366b5205a414fdd](https://testnet.starkscan.co/contract/0x5f7cd1fd465baff2ba9d2d1501ad0a2eb5337d9a885be319366b5205a414fdd#read-contract). - testnet2 : [0x2367db6b0df07033d196dcd25961109d8fbc86227158343149742284c7582e](https://testnet-2.starkscan.co/contract/0x002367db6b0df07033d196dcd25961109d8fbc86227158343149742284c7582e#read-contract). @@ -32,14 +32,14 @@ This contract contains a storage memory called `balance`. import { Provider, Contract, Account, ec, json } from "starknet"; ``` -## 🔍 Read contract memory, with call +## 🔍 Read contract memory, with meta-class To read the balance, you need only to connect a Provider and a Contract. -You have to use the call function : `contract.call("function_name",[params])` (here `params` is not necessary, because there are no parameters for the `get_balance` function). +You have to call Starknet, with use of the meta-class method : `contract.function_name(params)` (here `params` is not necessary, because there are no parameters for the `get_balance` function). ```typescript //initialize Provider -const provider = new Provider({ sequencer: { network: NetworkName.SN_GOERLI } }); +const provider = new Provider({ sequencer: { network: constants.NetworkName.SN_GOERLI } }); // Connect the deployed Test contract in Tesnet const testAddress = "0x5f7cd1fd465baff2ba9d2d1501ad0a2eb5337d9a885be319366b5205a414fdd"; @@ -49,28 +49,28 @@ if (testAbi === undefined) { throw new Error("no abi.") }; const myTestContract = new Contract(testAbi, testAddress, provider); // Interaction with the contract with call -const bal1 = await myTestContract.call("get_balance"); -console.log("Initial balance =", bal1.res.toString()); // .res because the return value is called 'res' in the cairo contract +const bal1 = await myTestContract.get_balance(); +console.log("Initial balance =", bal1.res.toString()); // .res because the return value is called 'res' in the Cairo 0 contract. +// With Cairo 1 contract, the result value is in bal1, as bigint. ``` -## ✍️ Write contract memory, with invoke +## ✍️ Write contract memory, with meta-class To increase the balance, you need in addition a connected and funded Account. -You have to use the invoke function : `contract.invoke("function_name",[params])` -After the invoke function, you have to wait the incorporation of the modification of Balance in the network, with `await provider.waitForTransaction(transaction_hash)` +You have to invoke Starknet, with use of the meta-class method : `contract.function_name(params)` +After the invoke, you have to wait the incorporation of the modification of Balance in the network, with `await provider.waitForTransaction(transaction_hash)` Here is an example to increase and check the balance : ```typescript //initialize Provider -const provider = new Provider({ sequencer: { network: NetworkName.SN_GOERLI } }); +const provider = new Provider({ sequencer: { network: constants.NetworkName.SN_GOERLI } }); // connect your account. To adapt to your own account : const privateKey0 = process.env.OZ_ACCOUNT_PRIVATE_KEY; const account0Address = "0x123....789"; -const starkKeyPair0 = ec.getKeyPair(privateKey0); -const account0 = new Account(provider, account0Address, starkKeyPair0); +const account0 = new Account(provider, account0Address, privateKey0); // Connect the deployed Test contract in Tesnet const testAddress = "0x5f7cd1fd465baff2ba9d2d1501ad0a2eb5337d9a885be319366b5205a414fdd"; @@ -83,77 +83,69 @@ const myTestContract = new Contract(testAbi, testAddress, provider); // Connect account with the contract myTestContract.connect(account0); -// Interactions with the contract with call & invoke -const bal1 = await myTestContract.call("get_balance"); -console.log("Initial balance =", bal1.res.toString()); -const res = await myTestContract.invoke("increase_balance", [10, 30]); - -await provider.waitForTransaction(res.transaction_hash); -const bal2 = await myTestContract.call("get_balance"); -console.log("Initial balance =", bal2.res.toString()); -``` - -## Use meta-class of Contract - -You have another way to interact with a contract - the meta-class : each `Contract` object as specific functions to interact. For example here, we have 2 additional functions for the Test contract object: - -- `Contract.get_balance()` -- `Contract.increase_balance()` - -The code can be modified this way : - -```typescript -//initialize Provider -const provider = new Provider({ sequencer: { network: NetworkName.SN_GOERLI } }); -// connect your account. To adapt to your own account : -const privateKey0 = process.env.OZ_ACCOUNT_PRIVATE_KEY; -const account0Address = "0x123....789"; - -const starkKeyPair0 = ec.getKeyPair(privateKey0); -const account0 = new Account(provider, account0Address, starkKeyPair0); - -// Connect the deployed Test contract in Tesnet -const testAddress = "0x5f7cd1fd465baff2ba9d2d1501ad0a2eb5337d9a885be319366b5205a414fdd"; - -// read abi of Test contract -const { abi: testAbi } = await provider.getClassAt(testAddress); -if (testAbi === undefined) { throw new Error("no abi.") }; -const myTestContract = new Contract(testAbi, testAddress, provider); - -// Connect account with the contract -myTestContract.connect(account0); - -// Interactions with the contract with call & invoke +// Interactions with the contract with meta-class const bal1 = await myTestContract.get_balance(); -console.log("Initial balance =", bal1.res.toString()); - -const resu = await myTestContract.increase_balance(10, 30); -await provider.waitForTransaction(resu.transaction_hash); +console.log("Initial balance =", bal1.res.toString()); // Cairo 0 contract +// increase_balance needs 2 felts, to add them to the balance. +const myCall = myTestContract.populate("increase_balance", [10,30]); +const res = await myTestContract.increase_balance(myCall.calldata); +await provider.waitForTransaction(res.transaction_hash); -const bal2 = await myTestContract.get_balance();import { Provider, Contract, Account, ec, json } from "starknet"; -console.log("Initial balance =", bal2.res.toString()); +const bal2 = await myTestContract.get_balance(); +console.log("Final balance =", bal2.res.toString()); ``` -## Write to contract memory, with Account.execute +`Contract.populate()` is the recommended method to define the parameters to call/invoke the Cairo functions. + +## Write several operations, with Account.execute -If you have to invoke a function of a contract that need the proof that you have the private key of the account, you have to invoke this function with `account.execute`. +In a Starknet transaction, you can include several invoke operations. It will be performed with `account.execute`. -We will see this case more in detail in ERC20 scripts, but in summary, you use this command with the following parameters: +We will later see this case more in detail in this dedicated [guide](multiCall.md) , but in summary, you use this command with the following parameters: - address of the contract to invoke - name of the function to invoke - and array of parameters for this function ```typescript -const executeHash = await account.execute( +const result = await account.execute( { contractAddress: myContractAddress, entrypoint: 'transfer', calldata: CallData.compile({ recipient: receiverAddress, - amount: ['10'] + amount: cairo.uint256(100000n) }) } ); -await provider.waitForTransaction(executeHash.transaction_hash); +await provider.waitForTransaction(result.transaction_hash); ``` + +## Other existing methods + +Some other useful method to interact with Starknet : + +### Function name defined in the code + +If you want to call a function with its name contained in a variable : + +```typescript +const listFn = ["calc-sum","calc-hash","calc-proof"]; +// fnChoice is a number defined during execution +const res = await myTestContract[listFn[fnChoice]](200, 234567897n, 865423); +``` + +### Light and fast call + +If you want to have a very fast execution, with the minimum of resource usage : + +```typescript +const specialParameters: Calldata = [ + '2036735872918048433518', + '5130580', + '18']; +const getResponse = await myAccount.call("get_bal",specialParameters, + {parseRequest: false}); +``` + +You provide the low level numbers expected by Starknet, without any parsing or check. See more detail [here](define_call_message.md#parse-configuration) diff --git a/www/docs/guides/intro.md b/www/docs/guides/intro.md index 3470fd582..3486efb4b 100644 --- a/www/docs/guides/intro.md +++ b/www/docs/guides/intro.md @@ -20,13 +20,13 @@ npm install starknet@next ### With Devnet -Example devnet version is `0.7.1`. +Example devnet version is `0.5.3`. Get devnet with docker: ```bash -docker pull shardlabs/starknet-devnet:0.7.1 -docker run -p 5050:5050 shardlabs/starknet-devnet:0.7.1 --seed 0 +docker pull shardlabs/starknet-devnet:0.5.3 +docker run -p 5050:5050 shardlabs/starknet-devnet:0.5.3 --seed 0 ``` Open new console tab, go to your starknet.js directory and run: @@ -61,12 +61,14 @@ npm run start # fires up a local documentation site Please check the Starknet documentation [here](https://www.cairo-lang.org/docs/hello_starknet/intro.html) to compile Starknet contracts. -Additional helpful resources can also be found at [OpenZeppelin](https://docs.openzeppelin.com/contracts-cairo/0.5.0/) documentation site. - -Get the class hash of a contract: [starkli](https://github.com/xJonathanLEI/starkli). +Additional helpful resources can also be found at [OpenZeppelin](https://docs.openzeppelin.com/contracts-cairo/0.6.1/) documentation site. ## Full example with account & erc20 deployments Please take a look at our workshop using OpenZeppelin contracts [here](https://github.com/0xs34n/starknet.js-workshop). Example with Argent contract [here](https://github.com/0xs34n/starknet.js-account). + +## Contracts used in the guides + +You can find the compiled contracts used in these guides in `compiled_contracts` directory. diff --git a/www/docs/guides/multiCall.md b/www/docs/guides/multiCall.md index d370a5b55..d1bde9592 100644 --- a/www/docs/guides/multiCall.md +++ b/www/docs/guides/multiCall.md @@ -12,7 +12,7 @@ Set up basic stuff before multicall. ```javascript // devnet private key from Account #0 if generated with --seed 0 -const starkKeyPair = ec.getKeyPair("0xe3e70682c2094cac629f6fbed82c07cd"); +const privateKey = "0xe3e70682c2094cac629f6fbed82c07cd"; const accountAddress = "0x7e00d496e324876bbc8531f2d9a82bf154d1a04a50218ee74cdd372f75a551a"; // Ether token contract address @@ -24,7 +24,7 @@ const contractAddress_2 = '0x078f36c1d59dd29e00a0bb60aa2a9409856f4f9841c47f165ab const account = new Account( provider, accountAddress, - starkKeyPair + privateKey ); ``` @@ -42,7 +42,7 @@ const multiCall = await account.execute( // approve 1 wei for bridge calldata: CallData.compile({ spender: contractAddress_2, - amount: {type: 'struct', low: '1', high: '0'}, + amount: cairo.uint256(1), }) }, // Calling the second contract @@ -51,7 +51,7 @@ const multiCall = await account.execute( entrypoint: "transfer_ether", // transfer 1 wei to the contract address calldata: CallData.compile({ - amount: {type: 'struct', low: '1', high: '0'}, + amount: cairo.uint256(1), }) } ] diff --git a/www/docs/guides/signature.md b/www/docs/guides/signature.md index 362993145..da6c379e2 100644 --- a/www/docs/guides/signature.md +++ b/www/docs/guides/signature.md @@ -10,27 +10,26 @@ You can use Starknet.js to sign a message outside of the network, using the stan Your message has to be an array of `BigNumberish`. First calculate the hash of this message, then calculate the signature. -> If the message does not respect some safety rules of composition, this method could be a way of attack of your smart contract. If you have any doubts, prefer the [EIP712 like method](#sign-and-verify-following-eip712), which is safe, but is also more complicated. +> If the message does not respect some safety rules of composition, this method could be a way of attack of your smart contract. If you have any doubt, prefer the [EIP712 like method](#sign-and-verify-following-eip712), which is safe, but is also more complicated. ```typescript import {ec, hash, num, json, Contract } from "starknet"; const privateKey = "0x1234567890987654321"; -const starkKeyPair = ec.getKeyPair(privateKey); -const starknetPublicKey = ec.getStarkKey(starkKeyPair); -const fullPublicKey=starkKeyPair.getPublic("hex"); +const starknetPublicKey = ec.starkCurve.getStarkKey(privateKey); +const fullPublicKey = encode.addHexPrefix( encode.buf2hex( ec.starkCurve.getPublicKey( privateKey, false))); const message : BigNumberish[] = [1, 128, 18, 14]; const msgHash = hash.computeHashOnElements(message); -const signature = ec.sign(starkKeyPair, msgHash); +const signature: weierstrass.SignatureType = ec.starkCurve.sign(msgHash,privateKey); ``` Then you can send, by any means, to the recipient of the message: - the message. - the signature. -- the full public key (or a wallet address). +- the full public key (or an account address using this private key). ## Receive and verify a message @@ -49,9 +48,8 @@ On receiver side, you can verify that: The sender provides the message, the signature and the full public key. Verification: ```typescript -const starkKeyPair1 = ec.getKeyPairFromPublicKey(fullPublicKey); const msgHash1 = hash.computeHashOnElements(message); -const result1 = ec.verify(starkKeyPair1, msgHash1, signature); +const result1 = ec.starkCurve.verify(signature, msgHash1, fullPublicKey); console.log("Result (boolean) =", result1); ``` @@ -71,8 +69,7 @@ Check that the pubKey of the account is part of the full pubKey: ```typescript const isFullPubKeyRelatedToAccount: boolean = - BigInt(pubKey3.publicKey.toString()) == - BigInt(encode.addHexPrefix(fullPublicKey.slice(4, 68))); + publicKey.publicKey == BigInt(encode.addHexPrefix( fullPublicKey.slice( 4, 68))); console.log("Result (boolean)=", isFullPubKeyRelatedToAccount); ``` @@ -90,7 +87,7 @@ const msgHash2 = hash.computeHashOnElements(message); // The call of isValidSignature will generate an error if not valid let result2: boolean; try { - await contractAccount.call("isValidSignature", [msgHash2, signature]); + await contractAccount.isValidSignature(msgHash2, [signature.r,signature.s]); result2 = true; } catch { result2 = false; @@ -115,9 +112,7 @@ The prefefined types that you can use : - merkletree : for a Root of a Merkle tree. root is calculated with the provided data. ```typescript -import { Account, typedData } from "starknet"; - -const typedDataValidate: typedData.TypedData = { +const typedDataValidate: TypedData = { types: { StarkNetDomain: [ { name: "name", type: "string" }, @@ -172,7 +167,8 @@ const typedDataValidate: typedData.TypedData = { }; // connect your account, then -const signature4 = await account.signMessage(typedDataValidate); +const signature2: weierstrass.SignatureType = await account.signMessage(typedDataValidate) as weierstrass.SignatureType; + ``` On receiver side, you receive the json, the signature and the account address. To verify the message: @@ -185,7 +181,7 @@ const msgHash5 = typedData.getMessageHash(typedDataValidate, accountAddress); // The call of isValidSignature will generate an error if not valid let result5: boolean; try { - await contractAccount.call("isValidSignature", [msgHash5, signature5]); + await contractAccount.isValidSignature(msgHash5, [signature2.r, signature2.s]); result5 = true; } catch { result5 = false; diff --git a/www/docs/guides/use_ERC20.md b/www/docs/guides/use_ERC20.md index dc282414b..0be2a7d76 100644 --- a/www/docs/guides/use_ERC20.md +++ b/www/docs/guides/use_ERC20.md @@ -17,7 +17,7 @@ Users have the feeling that their tokens are stored in their wallet, but it's ab If you want to have your balance of a token, ask its ERC20 contract, with the function `ERC20contract.balanceOf(accountAddress)`. -When you want to transfer some tokens in you possession, you have to use the ERC20 contract function `transfer`, through the `account.execute` function. In this way, Starknet.js will send to the account contract function `Execute` a message signed with the private key. +When you want to transfer some tokens in you possession, you have to use the ERC20 contract function `transfer`, through the `account.execute` function (or meta-class methods). In this way, Starknet.js will send to the account contract a message signed with the private key. This message contains the name of the function to call in the ERC20 contract, with its optional parameters. @@ -46,10 +46,9 @@ First, let's initialize an account : const provider = new Provider({ sequencer: { baseUrl:"http://127.0.0.1:5050" } }); // initialize existing pre-deployed account 0 of Devnet const privateKey = "0xe3e70682c2094cac629f6fbed82c07cd"; -const starkKeyPair = ec.getKeyPair(privateKey); const accountAddress = "0x7e00d496e324876bbc8531f2d9a82bf154d1a04a50218ee74cdd372f75a551a"; -const account0 = new Account(provider, accountAddress, starkKeyPair); +const account0 = new Account(provider, accountAddress, privateKey); ``` Declaration and deployment of the ERC20 contract : @@ -58,26 +57,24 @@ Declaration and deployment of the ERC20 contract : // Deploy an ERC20 contract console.log("Deployment Tx - ERC20 Contract to Starknet..."); const compiledErc20mintable = json.parse(fs.readFileSync("compiled_contracts/ERC20MintableOZ051.json").toString("ascii")); -const ERC20mintableClassHash = "0x795be772eab12ee65d5f3d9e8922d509d6672039978acc98697c0a563669e8"; -const initialTk = { low: 100, high: 0 }; - -const ERC20ConstructorCallData = CallData.compile({ - name: shortString.encodeShortString('MyToken'), - symbol: shortString.encodeShortString('MTK'), - decimals: "18", - initial_supply: { type: 'struct', low: initialTk.low, high: initialTk.high }, - recipient: account0.address, - owner: account0.address -}); -console.log("constructor=", ERC20ConstructorCallData); - -const deployERC20Response = await account0.declareDeploy({ - classHash: ERC20mintableClassHash, - contract: compiledErc20mintable, - constructorCalldata: ERC20ConstructorCallData -}); - -console.log("ERC20 deployed at address: ", deployERC20Response.deploy.contract_address); + const initialTk: Uint256 = cairo.uint256(100); + const erc20CallData: CallData = new CallData(compiledErc20mintable.abi); + const ERC20ConstructorCallData: Calldata = erc20CallData.compile("constructor", { + name: "niceToken", + symbol: "NIT", + decimals: 18, + initial_supply: initialTk, + recipient: account0.address, + owner: account0.address + }); + + console.log("constructor=", ERC20ConstructorCallData); + const deployERC20Response = await account0.declareAndDeploy({ + contract: compiledErc20mintable, + constructorCalldata: ERC20ConstructorCallData + }); + console.log("ERC20 declared hash: ", deployERC20Response.declare.class_hash); + console.log("ERC20 deployed at address: ", deployERC20Response.deploy.contract_address); // Get the erc20 contract address const erc20Address = deployERC20Response.deploy.contract_address; @@ -94,10 +91,10 @@ Here we will read the balance, mint new tokens, and transfer tokens : // Check balance - should be 100 console.log(`Calling Starknet for account balance...`); const balanceInitial = await erc20.balanceOf(account0.address); -console.log("account0 has a balance of :", uint256.uint256ToBN(balanceInitial.balance).toString()); +console.log("account0 has a balance of :", uint256.uint256ToBN(balanceInitial.balance).toString()); // Cairo 0 response // Mint 1000 tokens to account address -const amountToMint = uint256.bnToUint256(1000); +const amountToMint = cairo.uint256(1000); console.log("Invoke Tx - Minting 1000 tokens to account0..."); const { transaction_hash: mintTxHash } = await erc20.mint( account0.address, @@ -112,23 +109,16 @@ await provider.waitForTransaction(mintTxHash); // Check balance - should be 1100 console.log(`Calling Starknet for account balance...`); const balanceBeforeTransfer = await erc20.balanceOf(account0.address); -console.log("account0 has a balance of :", uint256.uint256ToBN(balanceBeforeTransfer.balance).toString()); +console.log("account0 has a balance of :", uint256.uint256ToBN(balanceBeforeTransfer.balance).toString()); // Cairo 0 response // Execute tx transfer of 10 tokens console.log(`Invoke Tx - Transfer 10 tokens back to erc20 contract...`); -const toTransferTk: uint256.Uint256 = uint256.bnToUint256(10); -const transferCallData = CallData.compile({ - recipient: erc20Address, - initial_supply: { type: 'struct', low: toTransferTk.low, high: toTransferTk.high } +const toTransferTk: Uint256 = cairo.uint256(10); +const transferCallData: Call = erc20.populate("transfer", { + recipient: erc20Address, + amount: toTransferTk // with Cairo 1 contract, 'toTransferTk' can be replaced by '10n' }); - -const { transaction_hash: transferTxHash } = await account0.execute({ - contractAddress: erc20Address, - entrypoint: "transfer", - calldata: transferCallData, }, - undefined, - { maxFee: 900_000_000_000_000 } -); + const { transaction_hash: transferTxHash } = await erc20.transfer( ...transferCallData.calldata); // Wait for the invoke transaction to be accepted on Starknet console.log(`Waiting for Tx to be Accepted on Starknet - Transfer...`); @@ -137,6 +127,6 @@ await provider.waitForTransaction(transferTxHash); // Check balance after transfer - should be 1090 console.log(`Calling Starknet for account balance...`); const balanceAfterTransfer = await erc20.balanceOf(account0.address); -console.log("account0 has a balance of :", uint256.uint256ToBN(balanceAfterTransfer.balance).toString()); +console.log("account0 has a balance of :", uint256.uint256ToBN(balanceAfterTransfer.balance).toString()); // Cairo 0 response console.log("✅ Script completed."); ``` diff --git a/www/docs/guides/what_s_starknet.js.md b/www/docs/guides/what_s_starknet.js.md index df3806839..899b958f9 100644 --- a/www/docs/guides/what_s_starknet.js.md +++ b/www/docs/guides/what_s_starknet.js.md @@ -23,6 +23,8 @@ Some important topics that have to be understood: - private customized version of Starknet. - local Starknet node (connected to mainnet or testnet). +> Understand what is Starknet 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/). + - Only the `Provider` object is talking directly to the network - your DAPP will talk mainly to `Account` and `Contract` objects. You will define with the `Provider` with which network you want to work. You can ask the Provider some low level data of the network (block, timestamp, ...). - `Signer` and `Utils` objects contain many useful functions for the interaction with Starknet.js. - The `Contract` object is mainly used to read the memory of a blockchain contract. diff --git a/www/tsconfig.json b/www/tsconfig.json index a8cb11c51..88e7237fb 100644 --- a/www/tsconfig.json +++ b/www/tsconfig.json @@ -4,5 +4,6 @@ "compilerOptions": { "jsx": "react", "baseUrl": "." - } + }, + "exclude": ["node_modules", "docs"] } diff --git a/www/versioned_docs/version-4.22.0/guides/signature.md b/www/versioned_docs/version-4.22.0/guides/signature.md index 4609272a4..fcc8ec541 100644 --- a/www/versioned_docs/version-4.22.0/guides/signature.md +++ b/www/versioned_docs/version-4.22.0/guides/signature.md @@ -18,7 +18,7 @@ import {ec, hash, number, json, Contract } from "starknet"; const privateKey = "0x1234567890987654321"; const starkKeyPair = ec.getKeyPair(privateKey); const starknetPublicKey = ec.getStarkKey(starkKeyPair); -const fullPublicKey=starkKeyPair.getPublic("hex"); +const fullPublicKey=encode.addHexPrefix(starkKeyPair.getPublic("hex")); const message : BigNumberish[] = [1, 128, 18, 14];