diff --git a/README.md b/README.md index 96b4fcd2..6b6c0e24 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,11 @@ The Starknet snap allows developers to **deploy Starknet accounts, make transactions on Starknet, and interact with Starknet smart contracts.** It can be connected with any dapp to access Starknet and developers can experiment integrating their dapp with this snap today. -[Starknet dapp](https://snaps.consensys.net/starknet) +[Starknet Snap](https://snaps.metamask.io/snap/npm/consensys/starknet-snap) -[Blog post](https://consensys.net/blog/metamask/metamask-integrates-starkware-into-first-of-its-kind-zk-rollup-snap/) +[Starknet Dapp](https://snaps.consensys.io/starknet) +[Blog Post](https://consensys.io/blog/metamask/metamask-integrates-starkware-into-first-of-its-kind-zk-rollup-snap/) # Development ### Prerequisites @@ -61,6 +62,40 @@ yarn workspace wallet-ui storybook - Wallet UI dapp: http://localhost:3000/ - Storybook: http://localhost:6006/ +# Dapp intergation Guide + +### How to install +From the dApp, issue the following RPC request to install the Snap, make sure it is using the latest version +```javascript +provider.request({ + method: 'wallet_requestSnaps', + params: { + ["npm:@consensys/starknet-snap"]: { version: "2.2.0"}, //Snap's version + }, +}) +``` + +### Interact with Starknet Snap's Api +From the dApp, issue the following RPC request to interact with the Snap + +e.g +```javascript +provider.request({ + method: 'wallet_invokeSnap', + params: { + snapId: "npm:@consensys/starknet-snap", + request: { + method: 'starkNet_getStoredUserAccounts', //Snap method + params: { + chainId : "1", //Snap method's parameter + }, + }, + }, +})) +``` +### Starknet Snap's Api +The corresponding request payload and response for latest Starknet Snap's Api are documented in the [openrpc spec](https://github.com/Consensys/starknet-snap/blob/starknet-snap-v2.2.0/packages/starknet-snap/openrpc/starknet_snap_api_openrpc.json) + # Licenses This project is dual-licensed under Apache 2.0 and MIT terms: @@ -68,4 +103,4 @@ This project is dual-licensed under Apache 2.0 and MIT terms: - Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) - MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) -Copyright (c) 2022 ConsenSys Software Inc. +Copyright (c) 2023 ConsenSys Software Inc. diff --git a/packages/starknet-snap/.eslintrc.js b/packages/starknet-snap/.eslintrc.js index 7ddfcaa4..0a8f4696 100644 --- a/packages/starknet-snap/.eslintrc.js +++ b/packages/starknet-snap/.eslintrc.js @@ -3,17 +3,13 @@ module.exports = { browser: true, es2021: true, }, - extends: [ - 'plugin:@typescript-eslint/recommended', - ], + extends: ['plugin:@typescript-eslint/recommended'], parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: 'latest', sourceType: 'module', }, - plugins: [ - '@typescript-eslint', - ], + plugins: ['@typescript-eslint'], rules: { }, }; diff --git a/packages/starknet-snap/index.html b/packages/starknet-snap/index.html index 3cad5b40..2913a5a9 100644 --- a/packages/starknet-snap/index.html +++ b/packages/starknet-snap/index.html @@ -1,992 +1,905 @@ - + Hello, Snaps! - - - -

Hello, Snaps!

-
- Instructions - -
-
- - - -
- -
- -
- - -
-
-
- Recover User Accounts - -
- -
- -
- -
-
-
-
- Get Stored User Accounts - -
-
-
-
- Add ERC-20 Token - -
- -
- -
- -
- -
-
-
-
- Get Stored ERC-20 Tokens - -
-
-
-
- Add Network (Currently disabled) - -
- -
- -
- -
- -
- -
- -
-
-
-
- Get Stored Networks - -
-
-
-
- Get Stored Transactions (Obsolete) - -
- -
- -
- -
- -
-
-
-
- Get Transactions from Voyager - -
- -
- -
- -
- -
- -
- -
-
-
-
- Get Transaction Status - -
- -
-
-
-
- Get the ERC-20 balance of an user - -
- -
- -
-
-
-
- Get Estimate Fee - -
- -
- -
- -
- -
-
-
-
- Get Estimate Account Deploy Fee - -
- -
-
-
-
- Call Contract (READ-ONLY) - -
- -
- -
- -
-
-
-
- Send Transaction - -
- -
- -
- -
- -
- -
-
-
-
- Transfer ERC-20 - -
- -
- -
- -
- -
- -
-
-
-
- Deploy User Account Contract - -
- -
- -
-
-
-
- Retrieve Private Key from User Address - -
- -
-
-
-
- Retrieve Public Key from User Address - -
- -
-
-
-
- Sign Typed Data Message - -
- -
- -
-
-
-
- Verify Typed Data Message Signature - -
- -
- -
- -
-
- - - + // here we call the snap's "starkNet_estimateFee" method + async function getEstimateFee(e) { + e.preventDefault(); // to prevent default form behavior + + const contractAddress = document.getElementById('contractAddress').value; + const contractFuncName = document.getElementById('contractFuncName').value; + const contractCallData = document.getElementById('contractCallData').value; + const senderAddress = document.getElementById('feeSenderAddress').value; + + await callSnap('starkNet_estimateFee', { contractAddress, contractFuncName, contractCallData, senderAddress }); + } + + // here we call the snap's "starkNet_estimateAccountDeployFee" method + async function getEstimateAccountDeployFee(e) { + e.preventDefault(); // to prevent default form behavior + + const addressIndex = document.getElementById('estimateDeployFeeAddressIndex').value; + + await callSnap('starkNet_estimateAccountDeployFee', { addressIndex }); + } - \ No newline at end of file + // here we call the snap's "starkNet_sendTransaction" method + async function transferErc20(e) { + e.preventDefault(); // to prevent default form behavior + + const erc20Address = document.getElementById('transferTokenAddress').value; + const recipientAddress = document.getElementById('recipientAddress').value; + const transferAmount = document.getElementById('transferAmount').value; + const senderAddress = document.getElementById('senderAddress').value; + const maxFee = document.getElementById('transferMaxFee').value; + + await callSnap('starkNet_sendTransaction', { + erc20Address, + recipientAddress, + transferAmount, + senderAddress, + maxFee, + }); + } + + // here we call the snap's "starkNet_getStoredUserAccounts" method + async function getDeployedAccContracts(e) { + e.preventDefault(); // to prevent default form behavior + + await callSnap('starkNet_getStoredUserAccounts', {}); + } + + // here we call the snap's "starkNet_getTransactionStatus" method + async function getTransactionStatus(e) { + e.preventDefault(); // to prevent default form behavior + + const transactionHash = document.getElementById('transactionHash').value; + + await callSnap('starkNet_getTransactionStatus', { transactionHash }); + } + + // here we call the snap's "starkNet_createAccount" method + async function deployUserAccContract(e) { + e.preventDefault(); // to prevent default form behavior + + const addressIndex = document.getElementById('addressIndex').value; + const deploy = document.getElementById('sendDeployTxn').checked; + + await callSnap('starkNet_createAccount', { addressIndex, deploy }); + } + + // here we call the snap's "starkNet_extractPrivateKey" method + async function retrievePrivateKeyFromAddress(e) { + e.preventDefault(); // to prevent default form behavior + + const userAddress = document.getElementById('userAddress').value; + + await callSnap('starkNet_extractPrivateKey', { userAddress }); + } + + // here we call the snap's "starkNet_extractPublicKey" method + async function retrievePublicKeyFromAddress(e) { + e.preventDefault(); // to prevent default form behavior + + const userAddress = document.getElementById('pkUserAddress').value; + + await callSnap('starkNet_extractPublicKey', { userAddress }); + } + + // here we call the snap's "starkNet_signMessage" method + async function signTypedDataMessage(e) { + e.preventDefault(); // to prevent default form behavior + + const typedDataMessage = document.getElementById('typedDataMessage').value; + const signerAddress = document.getElementById('signerUserAddress').value; + + await callSnap('starkNet_signMessage', { + typedDataMessage: typedDataMessage ? JSON.parse(typedDataMessage) : {}, + signerAddress, + }); + } + + // here we call the snap's "starkNet_signTransaction" method + async function signTransaction(e) { + e.preventDefault(); // to prevent default form behavior + + const userAddress = document.getElementById('signTransaction_userAddress').value; + const transactions = document.getElementById('signTransaction_transactions').value; + const transactionsDetail = document.getElementById('signTransaction_transactionsDetail').value; + + await callSnap('starkNet_signTransaction', { + signerAddress: userAddress, + transactions: transactions ? JSON.parse(transactions) : {}, + transactionsDetail: transactionsDetail ? JSON.parse(transactionsDetail) : {}, + }); + } + + // here we call the snap's "starkNet_signDeployAccounrTransaction" method + async function signDeployAccountTransaction(e) { + e.preventDefault(); // to prevent default form behavior + + const userAddress = document.getElementById('signDeployAccountTransaction_userAddress').value; + const transaction = document.getElementById('signDeployAccountTransaction_transaction').value; + + await callSnap('starkNet_signDeployAccounrTransaction', { + signerAddress: userAddress, + transaction: transaction ? JSON.parse(transaction) : {}, + }); + } + + // here we call the snap's "starkNet_signDeclareTransaction" method + async function signDeclareTransaction(e) { + e.preventDefault(); // to prevent default form behavior + + const userAddress = document.getElementById('signDeclareTransaction_userAddress').value; + const transaction = document.getElementById('signDeclareTransaction_transaction').value; + + await callSnap('starkNet_signDeclareTransaction', { + signerAddress: userAddress, + transaction: transaction ? JSON.parse(transaction) : {}, + }); + } + + // here we call the snap's "starkNet_verifySignedMessage" method + async function verifyTypedDataSignature(e) { + e.preventDefault(); // to prevent default form behavior + + const typedDataMessage = document.getElementById('verifyTypedDataMessage').value; + const signerAddress = document.getElementById('verifySignerUserAddress').value; + const signature = document.getElementById('verifySignature').value; + + await callSnap('starkNet_verifySignedMessage', { typedDataMessage, signerAddress, signature }); + } + + // here we call the snap's "starkNet_addErc20Token" method + async function addErc20Token(e) { + e.preventDefault(); // to prevent default form behavior + + const tokenAddress = document.getElementById('addTokenAddress').value; + const tokenName = document.getElementById('addTokenName').value; + const tokenSymbol = document.getElementById('addTokenSymbol').value; + const tokenDecimals = document.getElementById('addTokenDecimals').value; + + await callSnap('starkNet_addErc20Token', { tokenAddress, tokenName, tokenSymbol, tokenDecimals }); + } + + // here we call the snap's "starkNet_getStoredErc20Tokens" method + async function getStoredErc20Tokens(e) { + e.preventDefault(); // to prevent default form behavior + + await callSnap('starkNet_getStoredErc20Tokens', {}); + } + + // here we call the snap's "starkNet_sendTransaction" method + async function sendTransaction(e) { + e.preventDefault(); // to prevent default form behavior + + const contractAddress = document.getElementById('txnContractAddress').value; + const contractFuncName = document.getElementById('txnContractFuncName').value; + const contractCallData = document.getElementById('txnContractCallData').value; + const senderAddress = document.getElementById('txnSenderAddress').value; + const maxFee = document.getElementById('txnMaxFee').value; + const chainId = document.getElementById('targetChainId').value; + + await callSnap('starkNet_sendTransaction', { + contractAddress, + contractFuncName, + contractCallData, + senderAddress, + maxFee, + }); + } + + // here we call the snap's "starkNet_getValue" method + async function callContract(e) { + e.preventDefault(); // to prevent default form behavior + + const contractAddress = document.getElementById('callContractAddress').value; + const contractFuncName = document.getElementById('callContractFuncName').value; + const contractCallData = document.getElementById('callContractCallData').value; + const chainId = document.getElementById('targetChainId').value; + + await callSnap('starkNet_getValue', { contractAddress, contractFuncName, contractCallData }); + } + + // here we call the snap's "starkNet_addNetwork" method + async function addNetwork(e) { + e.preventDefault(); // to prevent default form behavior + + const networkName = document.getElementById('addNetworkName').value; + const networkChainId = document.getElementById('addNetworkChainId').value; + const networkBaseUrl = document.getElementById('addNetworkBaseUrl').value; + const networkNodeUrl = document.getElementById('addNetworkNodeUrl').value; + const networkVoyagerUrl = document.getElementById('addNetworkVoyagerUrl').value; + const accountClassHash = document.getElementById('addNetworkAccClassHash').value; + + await callSnap('starkNet_addNetwork', { + networkName, + networkChainId, + networkBaseUrl, + networkNodeUrl, + networkVoyagerUrl, + accountClassHash, + }); + } + + // here we call the snap's "starkNet_switchNetwork" method + async function switchNetwork(e) { + e.preventDefault(); // to prevent default form behavior + + const networkChainId = document.getElementById('switchNetwork_ChainId').value; + + await callSnap('starkNet_switchNetwork', { + chainId: networkChainId, + }); + } + + // here we call the snap's "starkNet_getCurrentNetwork" method + async function getCurrentNetwork(e) { + e.preventDefault(); // to prevent default form behavior + + await callSnap('starkNet_getCurrentNetwork', {}); + } + + // here we call the snap's "starkNet_getStoredNetworks" method + async function getStoredNetworks(e) { + e.preventDefault(); // to prevent default form behavior + + await callSnap('starkNet_getStoredNetworks', {}); + } + + // here we call the snap's "starkNet_getStoredTransactions" method + async function getStoredTransactions(e) { + e.preventDefault(); // to prevent default form behavior + + const senderAddress = document.getElementById('getTxnSenderAddress').value; + const contractAddress = document.getElementById('getTxnContractAddress').value; + const txnType = document.getElementById('txnType').value; + const txnsInLastNumOfDays = document.getElementById('txnInLastNumOfDays').value; + + await callSnap('starkNet_getStoredTransactions', { + senderAddress, + contractAddress, + txnType, + txnsInLastNumOfDays, + }); + } + + // here we call the snap's "starkNet_getTransactions" method + async function getTransactions(e) { + e.preventDefault(); // to prevent default form behavior + + const senderAddress = document.getElementById('getTxnsSenderAddress').value; + const contractAddress = document.getElementById('getTxnsContractAddress').value; + const pageSize = document.getElementById('getTxnsPageSize').value; + const txnsInLastNumOfDays = document.getElementById('getTxnsInLastNumOfDays').value; + const onlyFromState = document.getElementById('getTxnsOnlyFromState').checked; + const withDeployTxn = document.getElementById('getTxnsWithDeployTxn').checked; + + await callSnap('starkNet_getTransactions', { + senderAddress, + contractAddress, + pageSize, + txnsInLastNumOfDays, + onlyFromState, + withDeployTxn, + }); + } + + // here we call the snap's "starkNet_recoverAccounts" method + async function recoverUserAccounts(e) { + e.preventDefault(); // to prevent default form behavior + + const startScanIndex = document.getElementById('startScanIndex').value; + const maxScanned = document.getElementById('maxScanned').value; + const maxMissed = document.getElementById('maxMissed').value; + + await callSnap('starkNet_recoverAccounts', { + startScanIndex, + maxScanned, + maxMissed, + }); + } + + async function executeTxn(e) { + e.preventDefault(); // to prevent default form behavior + + const senderAddress = document.getElementById('executetxn_senderAddress').value; + const txnInvocation = document.getElementById('executetxn_txnInvocation').value; + const abis = document.getElementById('executetxn_abis').value; + const invocationsDetails = document.getElementById('executetxn_invocationsDetails').value; + + await callSnap('starkNet_executeTxn', { + senderAddress, + txnInvocation: txnInvocation ? JSON.parse(txnInvocation) : undefined, + abis: abis ? JSON.parse(abis) : undefined, + invocationsDetails: invocationsDetails ? JSON.parse(invocationsDetails) : undefined, + }); + } + + async function declareContract(e) { + e.preventDefault(); // to prevent default form behavior + + const senderAddress = document.getElementById('declareContract_senderAddress').value; + const contractPayload = document.getElementById('declareContract_contract').value; + const invocationsDetails = document.getElementById('declareContract_invocationsDetails').value; + + await callSnap('starkNet_declareContract', { + senderAddress, + contractPayload: contractPayload ? JSON.parse(contractPayload) : contractPayload, + invocationsDetails: invocationsDetails ? JSON.parse(invocationsDetails) : invocationsDetails, + }); + } + + async function estimateFees(e) { + e.preventDefault(); // to prevent default form behavior + + const senderAddress = document.getElementById('estimateFees_senderAddress').value; + const invocationDetails = document.getElementById('estimateFees_invocationDetails').value; + const invocations = document.getElementById('estimateFees_invocations').value; + + await callSnap('starkNet_estimateFees', { + senderAddress, + invocations: invocations ? JSON.parse(invocations) : undefined, + invocationDetails: invocationDetails ? JSON.parse(invocationDetails) : undefined, + }); + } + + async function callSnap(method, params) { + try { + const chainId = document.getElementById('targetChainId').value; + //0x534e5f474f45524c42 + console.log(params) + const response = await ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId, + request: { + method: method, + params: { + chainId, + ...getReqFlagsFromCheckbox(), + ...params, + }, + }, + }, + }); + console.log(`${method} response:`, response); + } catch (err) { + console.error(`${method} problem happened:`, err); + alert(`${method} problem happened: ${err.message || err}`); + } + } + + diff --git a/packages/starknet-snap/openrpc/starknet_snap_api_openrpc.json b/packages/starknet-snap/openrpc/starknet_snap_api_openrpc.json index a6c18645..510f7b4b 100644 --- a/packages/starknet-snap/openrpc/starknet_snap_api_openrpc.json +++ b/packages/starknet-snap/openrpc/starknet_snap_api_openrpc.json @@ -846,6 +846,327 @@ } }, "errors": [] + }, + { + "name": "starkNet_estimateFees", + "summary": "Get multiple estimated gas fee", + "paramStructure": "by-name", + "params": [ + { + "name": "senderAddress", + "summary": "Address of the account contract", + "description": "Address of the account contract", + "required": true, + "schema": { + "$ref": "#/components/schemas/ADDRESS" + } + }, + { + "name": "chainId", + "summary": "Id of the target Starknet network", + "description": "Id of the target Starknet network (default to Starknet Goerli Testnet)", + "required": false, + "schema": { + "$ref": "#/components/schemas/CHAIN_ID" + } + }, + { + "name": "invocations", + "summary": "Transaction(s)", + "description": "Array with consist of transaction payload and transaction type", + "required": true, + "schema": { + "$ref": "#/components/schemas/INVOCATIONS" + } + }, + { + "name": "invocationDetails", + "summary": "Transaction configurate parameters", + "description": "A struct consist of maxFee, nonce and version", + "required": false, + "schema": { + "$ref": "#/components/schemas/INVOCATION_DETAILS" + } + } + ], + "result": { + "name": "result", + "summary": "List of gas fee estimate object", + "description": "List of gas fee estimate object", + "schema": { + "$ref": "#/components/schemas/ESTIMATE_FEE_RESULTS" + } + }, + "errors": [] + }, + { + "name": "starkNet_executeTxn", + "summary": "Execute multiple txn", + "paramStructure": "by-name", + "params": [ + { + "name": "senderAddress", + "summary": "Address of the account contract", + "description": "Address of the account contract", + "required": true, + "schema": { + "$ref": "#/components/schemas/ADDRESS" + } + }, + { + "name": "chainId", + "summary": "Id of the target Starknet network", + "description": "Id of the target Starknet network (default to Starknet Goerli Testnet)", + "required": false, + "schema": { + "$ref": "#/components/schemas/CHAIN_ID" + } + }, + { + "name": "txnInvocation", + "summary": "Transaction(s)", + "description": "Array or Object that consist of transaction payload", + "required": true, + "schema": { + "$ref": "#/components/schemas/INVOCATION_PAYLOAD" + } + }, + { + "name": "abis", + "summary": "abis", + "description": "Array or Object that consist of abi", + "required": false, + "schema": { + "$ref": "#/components/schemas/ABIS" + } + }, + { + "name": "invocationDetails", + "summary": "Transaction configurate parameters", + "description": "A struct consist of maxFee, nonce and version", + "required": false, + "schema": { + "$ref": "#/components/schemas/INVOCATION_DETAILS" + } + } + ], + "result": { + "name": "result", + "summary": "Transaction result", + "description": "Transaction result consist of transaction hash", + "schema": { + "$ref": "#/components/schemas/SEND_TRANSACTION_RESULT" + } + }, + "errors": [] + }, + { + "name": "starkNet_declareContract", + "summary": "Declare contract", + "paramStructure": "by-name", + "params": [ + { + "name": "senderAddress", + "summary": "Address of the account contract", + "description": "Address of the account contract", + "required": true, + "schema": { + "$ref": "#/components/schemas/ADDRESS" + } + }, + { + "name": "chainId", + "summary": "Id of the target Starknet network", + "description": "Id of the target Starknet network (default to Starknet Goerli Testnet)", + "required": false, + "schema": { + "$ref": "#/components/schemas/CHAIN_ID" + } + }, + { + "name": "contractPayload", + "summary": "Payload of the contract", + "description": "Object that consist of contract payload", + "required": true, + "schema": { + "$ref": "#/components/schemas/CONTRACT_PAYLOAD" + } + }, + { + "name": "invocationDetails", + "summary": "Transaction configurate parameters", + "description": "A struct consist of maxFee, nonce and version", + "required": false, + "schema": { + "$ref": "#/components/schemas/INVOCATION_DETAILS" + } + } + ], + "result": { + "name": "result", + "summary": "Transaction result", + "description": "Transaction result consist of transaction hash and class hash", + "schema": { + "$ref": "#/components/schemas/DECLARE_CONTRACT_RESULT" + } + }, + "errors": [] + }, + { + "name": "starkNet_signTransaction", + "summary": "Sign transaction", + "paramStructure": "by-name", + "params": [ + { + "name": "signerAddress", + "summary": "Address of the account contract", + "description": "Address of the account contract", + "required": true, + "schema": { + "$ref": "#/components/schemas/ADDRESS" + } + }, + { + "name": "chainId", + "summary": "Id of the target Starknet network", + "description": "Id of the target Starknet network (default to Starknet Goerli Testnet)", + "required": false, + "schema": { + "$ref": "#/components/schemas/CHAIN_ID" + } + }, + { + "name": "transactions", + "summary": "List of calldata payload", + "description": "List of calldata payload", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CALLDATA" + } + } + }, + { + "name": "abis", + "summary": "abis", + "description": "Array or Object that consist of abi", + "required": false, + "schema": { + "$ref": "#/components/schemas/ABIS" + } + }, + { + "name": "transactionsDetail", + "summary": "Transaction Sign configurate parameters", + "description": "A struct consist of maxFee, nonce, version, walletAddress, chainId and cairoVersion", + "required": false, + "schema": { + "$ref": "#/components/schemas/SIGNER_INVOCATION_DETAILS" + } + } + ], + "result": { + "name": "result", + "summary": "Sign Transaction result", + "description": "Sign Transaction result consist of list of string", + "schema": { + "type": "array" + } + }, + "errors": [] + }, + { + "name": "starkNet_signDeclareTransaction", + "summary": "Sign Declare transaction", + "paramStructure": "by-name", + "params": [ + { + "name": "signerAddress", + "summary": "Address of the account contract", + "description": "Address of the account contract", + "required": true, + "schema": { + "$ref": "#/components/schemas/ADDRESS" + } + }, + { + "name": "chainId", + "summary": "Id of the target Starknet network", + "description": "Id of the target Starknet network (default to Starknet Goerli Testnet)", + "required": false, + "schema": { + "$ref": "#/components/schemas/CHAIN_ID" + } + }, + { + "name": "transaction", + "summary": "Declare transaction payload", + "description": "Declare transaction payload", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DECLARE_TRANSACTION_PAYLOAD" + } + } + } + ], + "result": { + "name": "result", + "summary": "Sign Transaction result", + "description": "Sign Transaction result consist of list of string", + "schema": { + "type": "array" + } + }, + "errors": [] + }, + { + "name": "starkNet_signDeployAccountTransaction", + "summary": "Sign Deploy Account transaction", + "paramStructure": "by-name", + "params": [ + { + "name": "signerAddress", + "summary": "Address of the account contract", + "description": "Address of the account contract", + "required": true, + "schema": { + "$ref": "#/components/schemas/ADDRESS" + } + }, + { + "name": "chainId", + "summary": "Id of the target Starknet network", + "description": "Id of the target Starknet network (default to Starknet Goerli Testnet)", + "required": false, + "schema": { + "$ref": "#/components/schemas/CHAIN_ID" + } + }, + { + "name": "transaction", + "summary": "Deploy Account transaction payload", + "description": "DecDeploy Accountlare transaction payload", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DEPLOY_ACCOUNT_TRANSACTION_PAYLOAD" + } + } + } + ], + "result": { + "name": "result", + "summary": "Sign Transaction result", + "description": "Sign Transaction result consist of list of string", + "schema": { + "type": "array" + } + }, + "errors": [] } ], "components": { @@ -868,6 +1189,156 @@ "type": "string", "pattern": "^0x[a-fA-F0-9]+$" }, + "ABIS": { + "type": "array" + }, + "CALLDATA": { + "type": "array" + }, + "CONTRACT_PAYLOAD": { + "type": "object", + "properties": { + "contract": { + "type": "string" + }, + "classHash": { + "type": "string" + }, + "casm": { + "type": "object" + }, + "compiledClassHash": { + "type": "string" + } + } + }, + "DECLARE_TRANSACTION_PAYLOAD" : { + "type": "object", + "properties": { + "classHash": { + "type": "string" + }, + "senderAddress": { + "$ref": "#/components/schemas/FELT" + }, + "chainId": { + "$ref": "#/components/schemas/CHAIN_ID" + }, + "maxFee": { + "type": "integer" + }, + "nonce": { + "type": "integer" + }, + "version": { + "type": "integer" + }, + "compiledClassHash": { + "type": "string" + } + } + }, + "DEPLOY_ACCOUNT_TRANSACTION_PAYLOAD" : { + "type": "object", + "properties": { + "classHash": { + "type": "string" + }, + "contractAddress": { + "$ref": "#/components/schemas/FELT" + }, + "constructorCalldata": { + "type": "array" + }, + "addressSalt": { + "type": "string" + }, + "chainId": { + "$ref": "#/components/schemas/CHAIN_ID" + }, + "maxFee": { + "type": "integer" + }, + "nonce": { + "type": "integer" + }, + "version": { + "type": "integer" + }, + "compiledClassHash": { + "type": "string" + } + } + }, + "INVOCATION_PAYLOAD": { + "type": "object", + "properties": { + "entrypoint": { + "type": "string" + }, + "contractAddress": { + "$ref": "#/components/schemas/FELT" + }, + "calldata": { + "$ref": "#/components/schemas/CALLDATA" + } + } + }, + "INVOCATION": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["DECLARE", "DEPLOY", "DEPLOY_ACCOUNT", "INVOKE_FUNCTION"] + }, + "payload": { + "$ref": "#/components/schemas/INVOCATION_PAYLOAD" + } + } + }, + "INVOCATIONS": { + "type": "array", + "items": { + "$ref": "#/components/schemas/INVOCATION" + } + }, + "INVOCATION_DETAILS": { + "type": "object", + "properties": { + "maxFee": { + "type": "integer" + }, + "nonce": { + "type": "integer" + }, + "version": { + "type": "integer" + } + } + }, + "SIGNER_INVOCATION_DETAILS": { + "type": "object", + "properties": { + "maxFee": { + "type": "integer" + }, + "nonce": { + "type": "integer" + }, + "version": { + "type": "integer" + }, + "walletAddress": { + "type": "string" + }, + "chainId": { + "type": "string" + }, + "cairoVersion": { + "type": "string" + } + } + }, "ADDRESS": { "$ref": "#/components/schemas/FELT" }, @@ -882,6 +1353,11 @@ "description": "The transaction hash, as assigned in Starknet", "title": "A transaction's hash" }, + "CLASS_HASH": { + "$ref": "#/components/schemas/FELT", + "description": "The class hash, as assigned in Starknet", + "title": "A class's hash" + }, "FELT": { "type": "string", "title": "Field element", @@ -899,30 +1375,17 @@ }, "TXN_STATUS": { "type": "string", - "enum": [ - "UNKNOWN", - "RECEIVED", - "PENDING", - "ACCEPTED_ON_L2", - "ACCEPTED_ON_L1", - "REJECTED" - ], + "enum": ["UNKNOWN", "RECEIVED", "PENDING", "ACCEPTED_ON_L2", "ACCEPTED_ON_L1", "REJECTED"], "description": "The status of the transaction. May be unknown in case node is not aware of it" }, "VOYAGER_TXN_TYPE": { "type": "string", - "enum": [ - "invoke", - "deploy" - ], + "enum": ["invoke", "deploy"], "description": "The txn type of the transaction from Voyager. May not be a full list here and subject to change" }, "VOYAGER_TXN_STATUS": { "type": "string", - "enum": [ - "Accepted on L2", - "Accepted on L1" - ], + "enum": ["Accepted on L2", "Accepted on L1"], "description": "The status of the transaction. May be unknown in case node is not aware of it" }, "TYPED_DATA_SIGNATURE": { @@ -1093,16 +1556,25 @@ } } }, - "SEND_TRANSACTION_RESULT": { + "DECLARE_CONTRACT_RESULT": { "type": "object", "properties": { "transaction_hash": { "$ref": "#/components/schemas/TXN_HASH", "description": "The hash identifying the transaction" }, - "code": { - "type": "string", - "description": "Response code of the sent txn" + "class_hash": { + "$ref": "#/components/schemas/CLASS_HASH", + "description": "The hash identifying the class" + } + } + }, + "SEND_TRANSACTION_RESULT": { + "type": "object", + "properties": { + "transaction_hash": { + "$ref": "#/components/schemas/TXN_HASH", + "description": "The hash identifying the transaction" } } }, @@ -1143,6 +1615,30 @@ } } }, + "ESTIMATE_FEE_RESULTS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "suggestedMaxFee": { + "type": "string", + "description": "Estimated gas fee with overhead added in decimal string" + }, + "overallFee": { + "type": "string", + "description": "Overall gas fee in in decimal string" + }, + "gasConsumed": { + "type": "string", + "description": "Total amount of consumed gas in decimal string" + }, + "gasPrice": { + "type": "string", + "description": "Price for each gas unit in decimal string" + } + } + } + }, "ADD_ERC_20_TOKEN_RESULT": { "$ref": "#/components/schemas/ERC_20_TOKEN" }, diff --git a/packages/starknet-snap/package.json b/packages/starknet-snap/package.json index 44b0398a..1a9f72a7 100644 --- a/packages/starknet-snap/package.json +++ b/packages/starknet-snap/package.json @@ -17,6 +17,7 @@ "watch:snap": "mm-snap watch", "prettier": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "lint": "eslint . --max-warnings 0 -f json -o eslint-report.json", + "lint:fix": "eslint '**/*.{js,ts,tsx}' --fix", "test": "yarn run test:unit && yarn run cover:report", "test:unit": "nyc --check-coverage --statements 80 --branches 80 --functions 80 --lines 80 mocha --colors -r ts-node/register \"test/**/*.test.ts\"", "cover:report": "nyc report --reporter=lcov --reporter=text" diff --git a/packages/starknet-snap/src/addErc20Token.ts b/packages/starknet-snap/src/addErc20Token.ts index 72f5e22b..62e18c82 100644 --- a/packages/starknet-snap/src/addErc20Token.ts +++ b/packages/starknet-snap/src/addErc20Token.ts @@ -6,10 +6,11 @@ import { upsertErc20Token, getValidNumber, validateAddErc20TokenParams, + getAddTokenText, } from './utils/snapUtils'; import { DEFAULT_DECIMAL_PLACES } from './utils/constants'; import { DialogType } from '@metamask/rpc-methods'; -import { heading, panel, text, copyable } from '@metamask/snaps-ui'; +import { heading, panel } from '@metamask/snaps-ui'; import { logger } from './utils/logger'; export async function addErc20Token(params: ApiParams) { @@ -37,16 +38,7 @@ export async function addErc20Token(params: ApiParams) { type: DialogType.Confirmation, content: panel([ heading('Do you want to add this token?'), - text('**Token Address:**'), - copyable(tokenAddress), - text('**Token Name:**'), - copyable(tokenName), - text('**Token Symbol:**'), - copyable(tokenSymbol), - text('**Token Decimals:**'), - copyable(tokenDecimals.toString()), - text('**Network:**'), - copyable(network.name), + ...getAddTokenText(tokenAddress, tokenName, tokenSymbol, tokenDecimals, network), ]), }, }); diff --git a/packages/starknet-snap/src/addNetwork.ts b/packages/starknet-snap/src/addNetwork.ts index 8e743a87..8f92f2be 100644 --- a/packages/starknet-snap/src/addNetwork.ts +++ b/packages/starknet-snap/src/addNetwork.ts @@ -1,11 +1,14 @@ import { toJson } from './utils/serializer'; import { AddNetworkRequestParams, ApiParams } from './types/snapApi'; -import { validateAddNetworkParams } from './utils/snapUtils'; +import { validateAddNetworkParams, upsertNetwork, getNetworkTxt } from './utils/snapUtils'; import { logger } from './utils/logger'; +import { Network } from './types/snapState'; +import { DialogType } from '@metamask/rpc-methods'; +import { panel, heading } from '@metamask/snaps-ui'; export async function addNetwork(params: ApiParams) { try { - const { requestParams } = params; + const { state, wallet, saveMutex, requestParams } = params; const requestParamsObj = requestParams as AddNetworkRequestParams; if ( @@ -22,7 +25,28 @@ export async function addNetwork(params: ApiParams) { validateAddNetworkParams(requestParamsObj); - throw new Error('addNetwork is currently disabled'); + const network = { + name: requestParamsObj.networkName, + chainId: requestParamsObj.networkChainId, + baseUrl: requestParamsObj.networkBaseUrl, + nodeUrl: requestParamsObj.networkNodeUrl, + voyagerUrl: requestParamsObj.networkVoyagerUrl, + } as Network; + + const components = getNetworkTxt(network); + + const response = await wallet.request({ + method: 'snap_dialog', + params: { + type: DialogType.Confirmation, + content: panel([heading('Do you want to add this network?'), ...components]), + }, + }); + if (!response) return false; + + await upsertNetwork(network, wallet, saveMutex, state); + + return true; } catch (err) { logger.error(`Problem found: ${err}`); throw err; diff --git a/packages/starknet-snap/src/createAccount.ts b/packages/starknet-snap/src/createAccount.ts index 51546ea2..2bbe61df 100644 --- a/packages/starknet-snap/src/createAccount.ts +++ b/packages/starknet-snap/src/createAccount.ts @@ -13,6 +13,7 @@ import { getValidNumber, upsertAccount, upsertTransaction, + addDialogTxt, } from './utils/snapUtils'; import { AccContract, VoyagerTransactionType, Transaction, TransactionStatus } from './types/snapState'; import { ApiParams, CreateAccountRequestParams } from './types/snapApi'; @@ -38,10 +39,7 @@ export async function createAccount(params: ApiParams, silentMode = false) { derivationPath, } = await getKeysFromAddressIndex(keyDeriver, network.chainId, state, addressIndex); - const { address: contractAddress, callData: contractCallData } = getAccContractAddressAndCallData( - network.accountClassHash, - publicKey, - ); + const { address: contractAddress, callData: contractCallData } = getAccContractAddressAndCallData(publicKey); logger.log( `createAccount:\ncontractAddress = ${contractAddress}\npublicKey = ${publicKey}\naddressIndex = ${addressIndexInUsed}`, @@ -52,6 +50,11 @@ export async function createAccount(params: ApiParams, silentMode = false) { if (deploy) { if (!silentMode) { + const components = []; + addDialogTxt(components, 'Address', contractAddress); + addDialogTxt(components, 'Public Key', publicKey); + addDialogTxt(components, 'Address Index', addressIndex.toString()); + const response = await wallet.request({ method: 'snap_dialog', params: { @@ -59,9 +62,7 @@ export async function createAccount(params: ApiParams, silentMode = false) { content: panel([ heading('Do you want to sign this deploy account transaction ?'), text(`It will be signed with address: ${contractAddress}`), - text( - `Account Info:\n\nAddress: ${contractAddress}\n\nPublic Key: ${publicKey}\n\nAddress Index: ${addressIndex}`, - ), + ...components, ]), }, }); diff --git a/packages/starknet-snap/src/declareContract.ts b/packages/starknet-snap/src/declareContract.ts new file mode 100644 index 00000000..6a2e7028 --- /dev/null +++ b/packages/starknet-snap/src/declareContract.ts @@ -0,0 +1,48 @@ +import { toJson } from './utils/serializer'; +import { ApiParams, DeclareContractRequestParams } from './types/snapApi'; +import { getNetworkFromChainId, getDeclareSnapTxt } from './utils/snapUtils'; +import { getKeysFromAddress, declareContract as declareContractUtil } from './utils/starknetUtils'; +import { DialogType } from '@metamask/rpc-methods'; +import { heading, panel } from '@metamask/snaps-ui'; +import { logger } from './utils/logger'; + +export async function declareContract(params: ApiParams) { + try { + const { state, keyDeriver, requestParams, wallet } = params; + const requestParamsObj = requestParams as DeclareContractRequestParams; + + logger.log(`executeTxn params: ${toJson(requestParamsObj, 2)}}`); + + const senderAddress = requestParamsObj.senderAddress; + const network = getNetworkFromChainId(state, requestParamsObj.chainId); + const { privateKey } = await getKeysFromAddress(keyDeriver, network, state, senderAddress); + + const snapComponents = getDeclareSnapTxt( + senderAddress, + network, + requestParamsObj.contractPayload, + requestParamsObj.invocationsDetails, + ); + + const response = await wallet.request({ + method: 'snap_dialog', + params: { + type: DialogType.Confirmation, + content: panel([heading('Do you want to sign this transaction?'), ...snapComponents]), + }, + }); + + if (!response) return false; + + return await declareContractUtil( + network, + senderAddress, + privateKey, + requestParamsObj.contractPayload, + requestParamsObj.invocationsDetails, + ); + } catch (err) { + logger.error(`Problem found: ${err}`); + throw err; + } +} diff --git a/packages/starknet-snap/src/estimateAccountDeployFee.ts b/packages/starknet-snap/src/estimateAccountDeployFee.ts index ecba799a..e60d5352 100644 --- a/packages/starknet-snap/src/estimateAccountDeployFee.ts +++ b/packages/starknet-snap/src/estimateAccountDeployFee.ts @@ -22,10 +22,7 @@ export async function estimateAccDeployFee(params: ApiParams) { addressIndex: addressIndexInUsed, privateKey, } = await getKeysFromAddressIndex(keyDeriver, network.chainId, state, addressIndex); - const { address: contractAddress, callData: contractCallData } = getAccContractAddressAndCallData( - network.accountClassHash, - publicKey, - ); + const { address: contractAddress, callData: contractCallData } = getAccContractAddressAndCallData(publicKey); logger.log( `estimateAccountDeployFee:\ncontractAddress = ${contractAddress}\npublicKey = ${publicKey}\naddressIndex = ${addressIndexInUsed}`, ); diff --git a/packages/starknet-snap/src/estimateFee.ts b/packages/starknet-snap/src/estimateFee.ts index 13f376dc..987b9de2 100644 --- a/packages/starknet-snap/src/estimateFee.ts +++ b/packages/starknet-snap/src/estimateFee.ts @@ -13,7 +13,7 @@ import { isAccountDeployed, isUpgradeRequired, } from './utils/starknetUtils'; - +import { ACCOUNT_CLASS_HASH_V1 } from './utils/constants'; import { logger } from './utils/logger'; export async function estimateFee(params: ApiParams) { @@ -73,9 +73,10 @@ export async function estimateFee(params: ApiParams) { }, ]; if (!accountDeployed) { - const { callData } = getAccContractAddressAndCallData(network.accountClassHash, publicKey); + const { callData } = getAccContractAddressAndCallData(publicKey); + const deployAccountpayload = { - classHash: network.accountClassHash, + classHash: ACCOUNT_CLASS_HASH_V1, contractAddress: senderAddress, constructorCalldata: callData, addressSalt: publicKey, diff --git a/packages/starknet-snap/src/estimateFees.ts b/packages/starknet-snap/src/estimateFees.ts new file mode 100644 index 00000000..624bbd5a --- /dev/null +++ b/packages/starknet-snap/src/estimateFees.ts @@ -0,0 +1,36 @@ +import { toJson } from './utils/serializer'; +import { getNetworkFromChainId } from './utils/snapUtils'; +import { getKeysFromAddress, estimateFeeBulk } from './utils/starknetUtils'; +import { ApiParams, EstimateFeesRequestParams } from './types/snapApi'; +import { logger } from './utils/logger'; + +export async function estimateFees(params: ApiParams) { + try { + const { state, keyDeriver, requestParams } = params; + const requestParamsObj = requestParams as EstimateFeesRequestParams; + + logger.log(`estimateFees params: ${toJson(requestParamsObj, 2)}`); + + const senderAddress = requestParamsObj.senderAddress; + const network = getNetworkFromChainId(state, requestParamsObj.chainId); + const { privateKey: senderPrivateKey } = await getKeysFromAddress(keyDeriver, network, state, senderAddress); + + const fees = await estimateFeeBulk( + network, + senderAddress, + senderPrivateKey, + requestParamsObj.invocations, + requestParamsObj.invocationDetails ? requestParamsObj.invocationDetails : undefined, + ); + + return fees.map((fee) => ({ + overall_fee: fee.overall_fee.toString(10) || '0', + gas_consumed: fee.gas_consumed.toString(10) || '0', + gas_price: fee.gas_price.toString(10) || '0', + suggestedMaxFee: fee.suggestedMaxFee.toString(10) || '0', + })); + } catch (err) { + logger.error(`Problem found: ${err}`); + throw err; + } +} diff --git a/packages/starknet-snap/src/executeTxn.ts b/packages/starknet-snap/src/executeTxn.ts new file mode 100644 index 00000000..e6b8958a --- /dev/null +++ b/packages/starknet-snap/src/executeTxn.ts @@ -0,0 +1,50 @@ +import { toJson } from './utils/serializer'; +import { getNetworkFromChainId, getTxnSnapTxt } from './utils/snapUtils'; +import { getKeysFromAddress, executeTxn as executeTxnUtil } from './utils/starknetUtils'; +import { ApiParams, ExecuteTxnRequestParams } from './types/snapApi'; +import { DialogType } from '@metamask/rpc-methods'; +import { heading, panel } from '@metamask/snaps-ui'; +import { logger } from './utils/logger'; + +export async function executeTxn(params: ApiParams) { + try { + const { state, keyDeriver, requestParams, wallet } = params; + const requestParamsObj = requestParams as ExecuteTxnRequestParams; + + logger.log(`executeTxn params: ${toJson(requestParamsObj, 2)}}`); + + const senderAddress = requestParamsObj.senderAddress; + const network = getNetworkFromChainId(state, requestParamsObj.chainId); + const { privateKey: senderPrivateKey } = await getKeysFromAddress(keyDeriver, network, state, senderAddress); + + const snapComponents = getTxnSnapTxt( + senderAddress, + network, + requestParamsObj.txnInvocation, + requestParamsObj.abis, + requestParamsObj.invocationsDetails, + ); + + const response = await wallet.request({ + method: 'snap_dialog', + params: { + type: DialogType.Confirmation, + content: panel([heading('Do you want to sign this transaction(s)?'), ...snapComponents]), + }, + }); + + if (!response) return false; + + return await executeTxnUtil( + network, + senderAddress, + senderPrivateKey, + requestParamsObj.txnInvocation, + requestParamsObj.abis, + requestParamsObj.invocationsDetails, + ); + } catch (err) { + logger.error(`Problem found: ${err}`); + throw err; + } +} diff --git a/packages/starknet-snap/src/getCurrentNetwork.ts b/packages/starknet-snap/src/getCurrentNetwork.ts new file mode 100644 index 00000000..f85d1c4f --- /dev/null +++ b/packages/starknet-snap/src/getCurrentNetwork.ts @@ -0,0 +1,16 @@ +import { toJson } from './utils/serializer'; +import { ApiParams } from './types/snapApi'; +import { getCurrentNetwork as getCurrentNetworkUtil } from './utils/snapUtils'; +import { logger } from './utils/logger'; + +export async function getCurrentNetwork(params: ApiParams) { + try { + const { state } = params; + const networks = getCurrentNetworkUtil(state); + logger.log(`getCurrentNetwork: networks:\n${toJson(networks, 2)}`); + return networks; + } catch (err) { + logger.error(`Problem found: ${err}`); + throw err; + } +} diff --git a/packages/starknet-snap/src/index.ts b/packages/starknet-snap/src/index.ts index 38ef0038..f2fd8910 100644 --- a/packages/starknet-snap/src/index.ts +++ b/packages/starknet-snap/src/index.ts @@ -2,6 +2,7 @@ import { toJson } from './utils/serializer'; import { getAddressKeyDeriver } from './utils/keyPair'; import { createAccount } from './createAccount'; import { signMessage } from './signMessage'; +import { signTransaction } from './signTransaction'; import { getErc20TokenBalance } from './getErc20TokenBalance'; import { getTransactionStatus } from './getTransactionStatus'; import { sendTransaction } from './sendTransaction'; @@ -15,6 +16,8 @@ import { SnapState } from './types/snapState'; import { extractPrivateKey } from './extractPrivateKey'; import { extractPublicKey } from './extractPublicKey'; import { addNetwork } from './addNetwork'; +import { switchNetwork } from './switchNetwork'; +import { getCurrentNetwork } from './getCurrentNetwork'; import { PRELOADED_TOKENS, STARKNET_INTEGRATION_NETWORK, @@ -30,6 +33,11 @@ import { Mutex } from 'async-mutex'; import { OnRpcRequestHandler } from '@metamask/snaps-types'; import { ApiParams, ApiRequestParams } from './types/snapApi'; import { estimateAccDeployFee } from './estimateAccountDeployFee'; +import { executeTxn } from './executeTxn'; +import { estimateFees } from './estimateFees'; +import { declareContract } from './declareContract'; +import { signDeclareTransaction } from './signDeclareTransaction'; +import { signDeployAccountTransaction } from './signDeployAccountTransaction'; import { logger } from './utils/logger'; declare const snap; @@ -113,6 +121,18 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ origin, request }) => apiParams.keyDeriver = await getAddressKeyDeriver(snap); return signMessage(apiParams); + case 'starkNet_signTransaction': + apiParams.keyDeriver = await getAddressKeyDeriver(snap); + return signTransaction(apiParams); + + case 'starkNet_signDeclareTransaction': + apiParams.keyDeriver = await getAddressKeyDeriver(snap); + return signDeclareTransaction(apiParams); + + case 'starkNet_signDeployAccountTransaction': + apiParams.keyDeriver = await getAddressKeyDeriver(snap); + return signDeployAccountTransaction(apiParams); + case 'starkNet_verifySignedMessage': apiParams.keyDeriver = await getAddressKeyDeriver(snap); return verifySignedMessage(apiParams); @@ -147,6 +167,12 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ origin, request }) => case 'starkNet_addNetwork': return addNetwork(apiParams); + case 'starkNet_switchNetwork': + return switchNetwork(apiParams); + + case 'starkNet_getCurrentNetwork': + return getCurrentNetwork(apiParams); + case 'starkNet_getStoredNetworks': return getStoredNetworks(apiParams); @@ -160,6 +186,18 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ origin, request }) => apiParams.keyDeriver = await getAddressKeyDeriver(snap); return recoverAccounts(apiParams); + case 'starkNet_executeTxn': + apiParams.keyDeriver = await getAddressKeyDeriver(snap); + return executeTxn(apiParams); + + case 'starkNet_estimateFees': + apiParams.keyDeriver = await getAddressKeyDeriver(snap); + return estimateFees(apiParams); + + case 'starkNet_declareContract': + apiParams.keyDeriver = await getAddressKeyDeriver(snap); + return declareContract(apiParams); + default: throw new Error('Method not found.'); } diff --git a/packages/starknet-snap/src/recoverAccounts.ts b/packages/starknet-snap/src/recoverAccounts.ts index d33454f6..d50f9e4a 100644 --- a/packages/starknet-snap/src/recoverAccounts.ts +++ b/packages/starknet-snap/src/recoverAccounts.ts @@ -4,8 +4,6 @@ import { getKeysFromAddressIndex, getCorrectContractAddress, isUpgradeRequired } import { getNetworkFromChainId, getValidNumber, upsertAccount } from './utils/snapUtils'; import { AccContract } from './types/snapState'; import { ApiParams, RecoverAccountsRequestParams } from './types/snapApi'; -import { DialogType } from '@metamask/rpc-methods'; -import { heading, panel, text } from '@metamask/snaps-ui'; import { logger } from './utils/logger'; export async function recoverAccounts(params: ApiParams) { @@ -20,20 +18,6 @@ export async function recoverAccounts(params: ApiParams) { logger.log(`recoverAccounts:\nstartIndex: ${startIndex}, maxScanned: ${maxScanned}, maxMissed: ${maxMissed}`); - if (!network.accountClassHash) { - await wallet.request({ - method: 'snap_dialog', - params: { - type: DialogType.Alert, - content: panel([ - heading('Failed to recover accounts'), - text('Recover Accounts not supported in network without class hash'), - ]), - }, - }); - return null; - } - let i = startIndex, j = 0; const scannedAccounts: AccContract[] = []; diff --git a/packages/starknet-snap/src/sendTransaction.ts b/packages/starknet-snap/src/sendTransaction.ts index 7ecf1fca..5c9ee72b 100644 --- a/packages/starknet-snap/src/sendTransaction.ts +++ b/packages/starknet-snap/src/sendTransaction.ts @@ -3,7 +3,7 @@ import { num, constants } from 'starknet'; import { validateAndParseAddress } from '../src/utils/starknetUtils'; import { estimateFee } from './estimateFee'; import { Transaction, TransactionStatus, VoyagerTransactionType } from './types/snapState'; -import { getNetworkFromChainId, getSigningTxnText, upsertTransaction } from './utils/snapUtils'; +import { getNetworkFromChainId, getSendTxnText, upsertTransaction } from './utils/snapUtils'; import { getKeysFromAddress, getCallDataArray, @@ -62,7 +62,7 @@ export async function sendTransaction(params: ApiParams) { maxFee = num.toBigInt(suggestedMaxFee); } - const signingTxnComponents = getSigningTxnText( + const signingTxnComponents = getSendTxnText( state, contractAddress, contractFuncName, @@ -108,14 +108,10 @@ export async function sendTransaction(params: ApiParams) { //In case this is the first transaction we assign a nonce of 1 to make sure it does after the deploy transaction const nonceSendTransaction = accountDeployed ? undefined : 1; - const txnResp = await executeTxn( - network, - senderAddress, - senderPrivateKey, - txnInvocation, + const txnResp = await executeTxn(network, senderAddress, senderPrivateKey, txnInvocation, undefined, { maxFee, - nonceSendTransaction, - ); + nonce: nonceSendTransaction, + }); logger.log(`sendTransaction:\ntxnResp: ${toJson(txnResp)}`); diff --git a/packages/starknet-snap/src/signDeclareTransaction.ts b/packages/starknet-snap/src/signDeclareTransaction.ts new file mode 100644 index 00000000..ab1f782f --- /dev/null +++ b/packages/starknet-snap/src/signDeclareTransaction.ts @@ -0,0 +1,39 @@ +import { toJson } from './utils/serializer'; +import { Signature } from 'starknet'; +import { ApiParams, SignDeclareTransactionRequestParams } from './types/snapApi'; +import { getKeysFromAddress, signDeclareTransaction as signDeclareTransactionUtil } from './utils/starknetUtils'; +import { getNetworkFromChainId, getSignTxnTxt } from './utils/snapUtils'; +import { DialogType } from '@metamask/rpc-methods'; +import { heading, panel } from '@metamask/snaps-ui'; +import { logger } from './utils/logger'; + +export async function signDeclareTransaction(params: ApiParams): Promise { + try { + const { state, keyDeriver, requestParams, wallet } = params; + const requestParamsObj = requestParams as SignDeclareTransactionRequestParams; + const signerAddress = requestParamsObj.signerAddress; + const network = getNetworkFromChainId(state, requestParamsObj.chainId); + const { privateKey } = await getKeysFromAddress(keyDeriver, network, state, signerAddress); + + logger.log(`signDeclareTransaction params: ${toJson(requestParamsObj.transaction, 2)}}`); + + const snapComponents = getSignTxnTxt(signerAddress, network, requestParamsObj.transaction); + + if (requestParamsObj.enableAutherize === true) { + const response = await wallet.request({ + method: 'snap_dialog', + params: { + type: DialogType.Confirmation, + content: panel([heading('Do you want to sign this transaction?'), ...snapComponents]), + }, + }); + + if (!response) return false; + } + + return await signDeclareTransactionUtil(privateKey, requestParamsObj.transaction); + } catch (error) { + logger.error(`Problem found: ${error}`); + throw error; + } +} diff --git a/packages/starknet-snap/src/signDeployAccountTransaction.ts b/packages/starknet-snap/src/signDeployAccountTransaction.ts new file mode 100644 index 00000000..9b35a2e3 --- /dev/null +++ b/packages/starknet-snap/src/signDeployAccountTransaction.ts @@ -0,0 +1,42 @@ +import { toJson } from './utils/serializer'; +import { Signature } from 'starknet'; +import { ApiParams, SignDeployAccountTransactionRequestParams } from './types/snapApi'; +import { + getKeysFromAddress, + signDeployAccountTransaction as signDeployAccountTransactionUtil, +} from './utils/starknetUtils'; +import { getNetworkFromChainId, getSignTxnTxt } from './utils/snapUtils'; +import { DialogType } from '@metamask/rpc-methods'; +import { heading, panel } from '@metamask/snaps-ui'; +import { logger } from '../src/utils/logger'; + +export async function signDeployAccountTransaction(params: ApiParams): Promise { + try { + const { state, keyDeriver, requestParams, wallet } = params; + const requestParamsObj = requestParams as SignDeployAccountTransactionRequestParams; + const signerAddress = requestParamsObj.signerAddress; + const network = getNetworkFromChainId(state, requestParamsObj.chainId); + const { privateKey } = await getKeysFromAddress(keyDeriver, network, state, signerAddress); + + logger.log(`signDeployAccountTransaction params: ${toJson(requestParamsObj.transaction, 2)}}`); + + const snapComponents = getSignTxnTxt(signerAddress, network, requestParamsObj.transaction); + + if (requestParamsObj.enableAutherize === true) { + const response = await wallet.request({ + method: 'snap_dialog', + params: { + type: DialogType.Confirmation, + content: panel([heading('Do you want to sign this transaction?'), ...snapComponents]), + }, + }); + + if (!response) return false; + } + + return await signDeployAccountTransactionUtil(privateKey, requestParamsObj.transaction); + } catch (error) { + logger.error(`Problem found: ${error}`); + throw error; + } +} diff --git a/packages/starknet-snap/src/signMessage.ts b/packages/starknet-snap/src/signMessage.ts index 1c4cf2ca..c881a140 100644 --- a/packages/starknet-snap/src/signMessage.ts +++ b/packages/starknet-snap/src/signMessage.ts @@ -1,11 +1,10 @@ import { toJson } from './utils/serializer'; -import typedDataExample from './typedData/typedDataExample.json'; -import { getTypedDataMessageSignature, getKeysFromAddress, isUpgradeRequired } from './utils/starknetUtils'; -import { getNetworkFromChainId } from './utils/snapUtils'; +import { signMessage as signMessageUtil, getKeysFromAddress, isUpgradeRequired } from './utils/starknetUtils'; +import { getNetworkFromChainId, addDialogTxt } from './utils/snapUtils'; import { ApiParams, SignMessageRequestParams } from './types/snapApi'; import { validateAndParseAddress } from '../src/utils/starknetUtils'; import { DialogType } from '@metamask/rpc-methods'; -import { heading, panel, copyable, text } from '@metamask/snaps-ui'; +import { heading, panel } from '@metamask/snaps-ui'; import { logger } from './utils/logger'; export async function signMessage(params: ApiParams) { @@ -13,9 +12,7 @@ export async function signMessage(params: ApiParams) { const { state, wallet, keyDeriver, requestParams } = params; const requestParamsObj = requestParams as SignMessageRequestParams; const signerAddress = requestParamsObj.signerAddress; - const typedDataMessage = requestParamsObj.typedDataMessage - ? JSON.parse(requestParamsObj.typedDataMessage) - : typedDataExample; + const typedDataMessage = requestParamsObj.typedDataMessage; const network = getNetworkFromChainId(state, requestParamsObj.chainId); logger.log(`signMessage:\nsignerAddress: ${signerAddress}\ntypedDataMessage: ${toJson(typedDataMessage)}`); @@ -34,30 +31,29 @@ export async function signMessage(params: ApiParams) { throw new Error('Upgrade required'); } - const response = await wallet.request({ - method: 'snap_dialog', - params: { - type: DialogType.Confirmation, - content: panel([ - heading('Do you want to sign this message ?'), - text(`**Message:**`), - copyable(toJson(typedDataMessage)), - text(`**Signer address:**`), - copyable(`${signerAddress}`), - ]), - }, - }); - if (!response) return false; + const components = []; + addDialogTxt(components, 'Message', toJson(typedDataMessage)); + addDialogTxt(components, 'Signer Address', signerAddress); - const { privateKey: signerPrivateKey } = await getKeysFromAddress(keyDeriver, network, state, signerAddress); + if (requestParamsObj.enableAutherize === true) { + const response = await wallet.request({ + method: 'snap_dialog', + params: { + type: DialogType.Confirmation, + content: panel([heading('Do you want to sign this message?'), ...components]), + }, + }); + + if (!response) return false; + } - const typedDataSignature = getTypedDataMessageSignature(signerPrivateKey, typedDataMessage, signerAddress); + const { privateKey: signerPrivateKey } = await getKeysFromAddress(keyDeriver, network, state, signerAddress); - const result = typedDataSignature.toDERHex(); + const typedDataSignature = signMessageUtil(signerPrivateKey, typedDataMessage, signerAddress); - logger.log(`signMessage:\ntypedDataSignature: ${result}`); + logger.log(`signMessage:\ntypedDataSignature: ${toJson(typedDataSignature)}`); - return result; + return typedDataSignature; } catch (err) { logger.error(`Problem found: ${err}`); throw err; diff --git a/packages/starknet-snap/src/signTransaction.ts b/packages/starknet-snap/src/signTransaction.ts new file mode 100644 index 00000000..57991e3c --- /dev/null +++ b/packages/starknet-snap/src/signTransaction.ts @@ -0,0 +1,46 @@ +import { toJson } from './utils/serializer'; +import { Signature } from 'starknet'; +import { ApiParams, SignTransactionRequestParams } from './types/snapApi'; +import { getKeysFromAddress, signTransactions } from './utils/starknetUtils'; +import { getNetworkFromChainId, getSignTxnTxt } from './utils/snapUtils'; +import { DialogType } from '@metamask/rpc-methods'; +import { heading, panel } from '@metamask/snaps-ui'; +import { logger } from '../src/utils/logger'; + +export async function signTransaction(params: ApiParams): Promise { + try { + const { state, keyDeriver, requestParams, wallet } = params; + const requestParamsObj = requestParams as SignTransactionRequestParams; + const signerAddress = requestParamsObj.signerAddress; + const network = getNetworkFromChainId(state, requestParamsObj.chainId); + const { privateKey } = await getKeysFromAddress(keyDeriver, network, state, signerAddress); + + logger.log(`signTransaction params: ${toJson(requestParamsObj.transactions, 2)}}`); + + const snapComponents = getSignTxnTxt(signerAddress, network, requestParamsObj.transactions); + + if (requestParamsObj.enableAutherize === true) { + const response = await wallet.request({ + method: 'snap_dialog', + params: { + type: DialogType.Confirmation, + content: panel([heading('Do you want to sign this transaction?'), ...snapComponents]), + }, + }); + + if (!response) return false; + } + + const signatures = await signTransactions( + privateKey, + requestParamsObj.transactions, + requestParamsObj.transactionsDetail, + requestParamsObj.abis, + ); + + return signatures; + } catch (error) { + logger.error(`Problem found: ${error}`); + throw error; + } +} diff --git a/packages/starknet-snap/src/switchNetwork.ts b/packages/starknet-snap/src/switchNetwork.ts new file mode 100644 index 00000000..1159710f --- /dev/null +++ b/packages/starknet-snap/src/switchNetwork.ts @@ -0,0 +1,35 @@ +import { toJson } from './utils/serializer'; +import { ApiParams, SwitchNetworkRequestParams } from './types/snapApi'; +import { getNetwork, setCurrentNetwork, getNetworkTxt } from './utils/snapUtils'; +import { DialogType } from '@metamask/rpc-methods'; +import { panel, heading } from '@metamask/snaps-ui'; +import { logger } from './utils/logger'; + +export async function switchNetwork(params: ApiParams) { + try { + const { state, wallet, saveMutex, requestParams } = params; + const requestParamsObj = requestParams as SwitchNetworkRequestParams; + const network = getNetwork(state, requestParamsObj.chainId); + if (!network) { + throw new Error(`The given chainId is invalid: ${requestParamsObj.chainId}`); + } + const components = getNetworkTxt(network); + + const response = await wallet.request({ + method: 'snap_dialog', + params: { + type: DialogType.Confirmation, + content: panel([heading('Do you want to switch to this network?'), ...components]), + }, + }); + if (!response) return false; + + logger.log(`switchNetwork: network:\n${toJson(network, 2)}`); + await setCurrentNetwork(network, wallet, saveMutex, state); + + return true; + } catch (err) { + logger.error(`Problem found: ${err}`); + throw err; + } +} diff --git a/packages/starknet-snap/src/types/snapApi.ts b/packages/starknet-snap/src/types/snapApi.ts index 0273c95a..37f80539 100644 --- a/packages/starknet-snap/src/types/snapApi.ts +++ b/packages/starknet-snap/src/types/snapApi.ts @@ -1,6 +1,18 @@ import { BIP44AddressKeyDeriver } from '@metamask/key-tree'; import Mutex from 'async-mutex/lib/Mutex'; import { SnapState, VoyagerTransactionType } from './snapState'; +import { + Abi, + Call, + InvocationsSignerDetails, + DeclareContractPayload, + InvocationsDetails, + Invocations, + EstimateFeeDetails, + DeployAccountSignerDetails, + DeclareSignerDetails, + typedData, +} from 'starknet'; export interface ApiParams { state: SnapState; @@ -29,7 +41,11 @@ export type ApiRequestParams = | GetStoredNetworksRequestParams | GetStoredTransactionsRequestParams | GetTransactionsRequestParams - | RecoverAccountsRequestParams; + | RecoverAccountsRequestParams + | ExecuteTxnRequestParams + | EstimateFeesRequestParams + | DeclareContractRequestParams + | SignTransactionRequestParams; export interface BaseRequestParams { chainId?: string; @@ -59,9 +75,8 @@ export interface ExtractPublicKeyRequestParams extends BaseRequestParams { userAddress: string; } -export interface SignMessageRequestParams extends BaseRequestParams { - signerAddress: string; - typedDataMessage?: string; +export interface SignMessageRequestParams extends SignRequestParams, BaseRequestParams { + typedDataMessage: typedData.TypedData; } export interface VerifySignedMessageRequestParams extends BaseRequestParams { @@ -142,7 +157,49 @@ export interface RecoverAccountsRequestParams extends BaseRequestParams { maxMissed?: string | number; } +export interface ExecuteTxnRequestParams extends BaseRequestParams { + senderAddress: string; + txnInvocation: Call | Call[]; + abis?: Abi[]; + invocationsDetails?: InvocationsDetails; +} + +export interface EstimateFeesRequestParams extends BaseRequestParams { + senderAddress: string; + invocations: Invocations; + invocationDetails?: EstimateFeeDetails; +} + +export interface DeclareContractRequestParams extends BaseRequestParams { + senderAddress: string; + contractPayload: DeclareContractPayload; + invocationsDetails?: InvocationsDetails; +} + export interface RpcV4GetTransactionReceiptResponse { execution_status?: string; finality_status?: string; } + +export interface SignRequestParams { + signerAddress: string; + enableAutherize?: boolean; +} + +export interface SignTransactionRequestParams extends SignRequestParams, BaseRequestParams { + transactions: Call[]; + transactionsDetail: InvocationsSignerDetails; + abis?: Abi[]; +} + +export interface SignDeployAccountTransactionRequestParams extends SignRequestParams, BaseRequestParams { + transaction: DeployAccountSignerDetails; +} + +export interface SignDeclareTransactionRequestParams extends SignRequestParams, BaseRequestParams { + transaction: DeclareSignerDetails; +} + +export interface SwitchNetworkRequestParams extends BaseRequestParams { + chainId: string; +} diff --git a/packages/starknet-snap/src/types/snapState.ts b/packages/starknet-snap/src/types/snapState.ts index b149e319..dca1e0c9 100644 --- a/packages/starknet-snap/src/types/snapState.ts +++ b/packages/starknet-snap/src/types/snapState.ts @@ -5,6 +5,7 @@ export interface SnapState { erc20Tokens: Erc20Token[]; networks: Network[]; transactions: Transaction[]; + currentNetwork?: Network; } export interface AccContract { diff --git a/packages/starknet-snap/src/utils/constants.ts b/packages/starknet-snap/src/utils/constants.ts index bc44de46..3b62bcdf 100644 --- a/packages/starknet-snap/src/utils/constants.ts +++ b/packages/starknet-snap/src/utils/constants.ts @@ -13,6 +13,9 @@ export const MAXIMUM_TOKEN_SYMBOL_LENGTH = 16; export const TRANSFER_SELECTOR_HEX = '0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e'; +export const ACCOUNT_CLASS_HASH_V0 = '0x033434ad846cdd5f23eb73ff09fe6fddd568284a0fb7d1be20ee482f044dabe2'; // from argent-x repo +export const ACCOUNT_CLASS_HASH_V1 = '0x1a736d6ed154502257f02b1ccdf4d9d1089f80811cd6acad48e6b6a9d1f2003'; // from argent-x repo + export const STARKNET_MAINNET_NETWORK: Network = { name: 'Starknet Mainnet', chainId: constants.StarknetChainId.SN_MAIN, diff --git a/packages/starknet-snap/src/utils/snapUtils.ts b/packages/starknet-snap/src/utils/snapUtils.ts index 4d87f282..3bbe34dc 100644 --- a/packages/starknet-snap/src/utils/snapUtils.ts +++ b/packages/starknet-snap/src/utils/snapUtils.ts @@ -1,6 +1,14 @@ import { toJson } from './serializer'; import { Mutex } from 'async-mutex'; -import { num } from 'starknet'; +import { + num, + InvocationsDetails, + DeclareContractPayload, + Abi, + DeclareSignerDetails, + Call, + DeployAccountSignerDetails, +} from 'starknet'; import { validateAndParseAddress } from './starknetUtils'; import { Component, text, copyable } from '@metamask/snaps-ui'; import { @@ -71,26 +79,25 @@ function isValidNetworkName(networkName: string) { function isPreloadedTokenName(tokenName: string, chainId: string) { return !!PRELOADED_TOKENS.find( - (token) => token.name.trim() === tokenName.trim() && Number(token.chainId) === Number(chainId), + (token) => token.name.trim() === tokenName.trim() && isSameChainId(token.chainId, chainId), ); } function isPreloadedTokenSymbol(tokenSymbol: string, chainId: string) { return !!PRELOADED_TOKENS.find( - (token) => token.symbol.trim() === tokenSymbol.trim() && Number(token.chainId) === Number(chainId), + (token) => token.symbol.trim() === tokenSymbol.trim() && isSameChainId(token.chainId, chainId), ); } function isPreloadedTokenAddress(tokenAddress: string, chainId: string) { const bigIntTokenAddress = num.toBigInt(tokenAddress); return !!PRELOADED_TOKENS.find( - (token) => num.toBigInt(token.address) === bigIntTokenAddress && Number(token.chainId) === Number(chainId), + (token) => num.toBigInt(token.address) === bigIntTokenAddress && isSameChainId(token.chainId, chainId), ); } -function isPreloadedNetworkChainId(networkChainId: string) { - const bigIntNetworkChainId = num.toBigInt(networkChainId); - return !!PRELOADED_NETWORKS.find((network) => num.toBigInt(network.chainId) === bigIntNetworkChainId); +function isPreloadedNetworkChainId(chainId: string) { + return !!PRELOADED_NETWORKS.find((network) => isSameChainId(network.chainId, chainId)); } function isPreloadedNetworkName(networkName: string) { @@ -150,14 +157,6 @@ export function validateAddNetworkParams(params: AddNetworkRequestParams) { throw new Error(`The given network Voyager URL is not an valid HTTP/HTTPS URL: ${params.networkVoyagerUrl}`); } - if (params.accountClassHash) { - try { - validateAndParseAddress(params.accountClassHash); - } catch (err) { - throw new Error(`The given account class hash is invalid: ${params.accountClassHash}`); - } - } - if (isPreloadedNetworkChainId(params.networkChainId) || isPreloadedNetworkName(params.networkName)) { throw new Error( 'The given network chainId or name is the same as one of the preloaded networks, and thus cannot be added', @@ -165,6 +164,10 @@ export function validateAddNetworkParams(params: AddNetworkRequestParams) { } } +export function isSameChainId(chainId1: string, chainId2: string) { + return num.toBigInt(chainId1) === num.toBigInt(chainId2); +} + export const getValidNumber = ( obj, defaultValue: number, @@ -175,7 +178,55 @@ export const getValidNumber = ( return obj === '' || isNaN(toNum) || toNum > maxVal || toNum < minVal ? defaultValue : toNum; }; -export function getSigningTxnText( +export function addDialogTxt(components: Array, label: string, value: string) { + components.push(text(`**${label}:**`)); + components.push(copyable(value)); +} + +export function getNetworkTxt(network: Network) { + const components = []; + addDialogTxt(components, 'Chain Name', network.name); + addDialogTxt(components, 'Chain ID', network.chainId); + if (network.baseUrl) { + addDialogTxt(components, 'Base URL', network.baseUrl); + } + if (network.nodeUrl) { + addDialogTxt(components, 'RPC URL', network.nodeUrl); + } + if (network.voyagerUrl) { + addDialogTxt(components, 'Explorer URL', network.voyagerUrl); + } + return components; +} + +export function getTxnSnapTxt( + senderAddress: string, + network: Network, + txnInvocation: Call | Call[], + abis?: Abi[], + invocationsDetails?: InvocationsDetails, +) { + const components = []; + addDialogTxt(components, 'Network', network.name); + addDialogTxt(components, 'Signer Address', senderAddress); + addDialogTxt(components, 'Transaction Invocation', JSON.stringify(txnInvocation, null, 2)); + if (abis && abis.length > 0) { + addDialogTxt(components, 'Abis', JSON.stringify(abis, null, 2)); + } + + if (invocationsDetails?.maxFee) { + addDialogTxt(components, 'Max Fee(ETH)', convert(invocationsDetails.maxFee, 'wei', 'ether')); + } + if (invocationsDetails?.nonce) { + addDialogTxt(components, 'Nonce', invocationsDetails.nonce.toString()); + } + if (invocationsDetails?.version) { + addDialogTxt(components, 'Version', invocationsDetails.version.toString()); + } + return components; +} + +export function getSendTxnText( state: SnapState, contractAddress: string, contractFuncName: string, @@ -186,8 +237,13 @@ export function getSigningTxnText( ): Array { // Retrieve the ERC-20 token from snap state for confirmation display purpose const token = getErc20Token(state, contractAddress, network.chainId); - const tokenTransferComponents1 = []; - const tokenTransferComponents2 = []; + const components = []; + addDialogTxt(components, 'Signer Address', senderAddress); + addDialogTxt(components, 'Contract', contractAddress); + addDialogTxt(components, 'Call Data', `[${contractCallData.join(', ')}]`); + addDialogTxt(components, 'Estimated Gas Fee(ETH)', convert(maxFee, 'wei', 'ether')); + addDialogTxt(components, 'Network', network.name); + if (token && contractFuncName === 'transfer') { try { let amount = ''; @@ -196,28 +252,65 @@ export function getSigningTxnText( } else { amount = (Number(contractCallData[1]) * Math.pow(10, -1 * token.decimals)).toFixed(token.decimals); } - tokenTransferComponents2.push(text('**Sender Address:**')); - tokenTransferComponents2.push(copyable(senderAddress)); - tokenTransferComponents2.push(text('**Recipient Address:**')); - tokenTransferComponents2.push(copyable(contractCallData[0])); - tokenTransferComponents2.push(text(`**Amount(${token.symbol}):**`)); - tokenTransferComponents2.push(copyable(amount)); + addDialogTxt(components, 'Sender Address', senderAddress); + addDialogTxt(components, 'Recipient Address', contractCallData[0]); + addDialogTxt(components, `Amount(${token.symbol})`, amount); } catch (err) { logger.error(`getSigningTxnText: error found in amount conversion: ${err}`); } } - tokenTransferComponents1.push(text('**Signer Address:**')); - tokenTransferComponents1.push(copyable(senderAddress)); - tokenTransferComponents1.push(text('**Contract:**')); - tokenTransferComponents1.push(copyable(contractAddress)); - tokenTransferComponents1.push(text('**Call Data:**')); - tokenTransferComponents1.push(copyable(`[${contractCallData.join(', ')}]`)); - tokenTransferComponents1.push(text('**Estimated Gas Fee(ETH):**')); - tokenTransferComponents1.push(copyable(convert(maxFee, 'wei', 'ether'))); - tokenTransferComponents1.push(text('**Network:**')); - tokenTransferComponents1.push(copyable(network.name)); - return tokenTransferComponents1.concat(tokenTransferComponents2); + return components; +} + +export function getSignTxnTxt( + signerAddress: string, + network: Network, + txnInvocation: Call[] | DeclareSignerDetails | DeployAccountSignerDetails, +) { + const components = []; + addDialogTxt(components, 'Network', network.name); + addDialogTxt(components, 'Signer Address', signerAddress); + addDialogTxt(components, 'Transaction', JSON.stringify(txnInvocation, null, 2)); + return components; +} + +export function getDeclareSnapTxt( + senderAddress: string, + network: Network, + contractPayload: DeclareContractPayload, + invocationsDetails?: InvocationsDetails, +) { + const components = []; + addDialogTxt(components, 'Network', network.name); + addDialogTxt(components, 'Signer Address', senderAddress); + + if (contractPayload.contract) { + const _contractPayload = + typeof contractPayload.contract === 'string' || contractPayload.contract instanceof String + ? contractPayload.contract.toString() + : JSON.stringify(contractPayload.contract, null, 2); + addDialogTxt(components, 'Contract', _contractPayload); + } + if (contractPayload.compiledClassHash) { + addDialogTxt(components, 'Complied Class Hash', contractPayload.compiledClassHash); + } + if (contractPayload.classHash) { + addDialogTxt(components, 'Class Hash', contractPayload.classHash); + } + if (contractPayload.casm) { + addDialogTxt(components, 'Casm', JSON.stringify(contractPayload.casm, null, 2)); + } + if (invocationsDetails?.maxFee !== undefined) { + addDialogTxt(components, 'Max Fee(ETH)', convert(invocationsDetails.maxFee, 'wei', 'ether')); + } + if (invocationsDetails?.nonce !== undefined) { + addDialogTxt(components, 'Nonce', invocationsDetails.nonce.toString()); + } + if (invocationsDetails?.version !== undefined) { + addDialogTxt(components, 'Version', invocationsDetails.version.toString()); + } + return components; } export function getAddTokenText( @@ -227,19 +320,25 @@ export function getAddTokenText( tokenDecimals: number, network: Network, ) { - return `Token Address: ${tokenAddress}\n\nToken Name: ${tokenName}\n\nToken Symbol: ${tokenSymbol}\n\nToken Decimals: ${tokenDecimals}\n\nNetwork: ${network.name}`; + const components = []; + addDialogTxt(components, 'Network', network.name); + addDialogTxt(components, 'Token Address', tokenAddress); + addDialogTxt(components, 'Token Name', tokenName); + addDialogTxt(components, 'Token Symbol', tokenSymbol); + addDialogTxt(components, 'Token Decimals', tokenDecimals.toString()); + return components; } export function getAccount(state: SnapState, accountAddress: string, chainId: string) { const bigIntAccountAddress = num.toBigInt(accountAddress); return state.accContracts?.find( - (acc) => num.toBigInt(acc.address) === bigIntAccountAddress && Number(acc.chainId) === Number(chainId), + (acc) => num.toBigInt(acc.address) === bigIntAccountAddress && isSameChainId(acc.chainId, chainId), ); } export function getAccounts(state: SnapState, chainId: string) { return state.accContracts - .filter((acc) => Number(acc.chainId) === Number(chainId)) + .filter((acc) => isSameChainId(acc.chainId, chainId)) .sort((a: AccContract, b: AccContract) => a.addressIndex - b.addressIndex); } @@ -285,7 +384,7 @@ export async function upsertAccount(userAccount: AccContract, wallet, mutex: Mut export function getNetwork(state: SnapState, chainId: string) { return state.networks?.find( - (network) => Number(network.chainId) === Number(chainId) && !Boolean(network?.useOldAccounts), + (network) => isSameChainId(network.chainId, chainId) && !Boolean(network?.useOldAccounts), ); } @@ -319,7 +418,6 @@ export async function upsertNetwork(network: Network, wallet, mutex: Mutex, stat storedNetwork.baseUrl = network.baseUrl; storedNetwork.nodeUrl = network.nodeUrl; storedNetwork.voyagerUrl = network.voyagerUrl; - storedNetwork.accountClassHash = network.accountClassHash; } await wallet.request({ @@ -335,16 +433,16 @@ export async function upsertNetwork(network: Network, wallet, mutex: Mutex, stat export function getErc20Token(state: SnapState, tokenAddress: string, chainId: string) { const bigIntTokenAddress = num.toBigInt(tokenAddress); return state.erc20Tokens?.find( - (token) => num.toBigInt(token.address) === bigIntTokenAddress && Number(token.chainId) === Number(chainId), + (token) => num.toBigInt(token.address) === bigIntTokenAddress && isSameChainId(token.chainId, chainId), ); } export function getErc20Tokens(state: SnapState, chainId: string) { - return state.erc20Tokens?.filter((token) => Number(token.chainId) === Number(chainId)); + return state.erc20Tokens?.filter((token) => isSameChainId(token.chainId, chainId)); } export function getEtherErc20Token(state: SnapState, chainId: string) { - return state.erc20Tokens?.find((token) => Number(token.chainId) === Number(chainId) && token.symbol === 'ETH'); + return state.erc20Tokens?.find((token) => isSameChainId(token.chainId, chainId) && token.symbol === 'ETH'); } export async function upsertErc20Token(erc20Token: Erc20Token, wallet, mutex: Mutex, state: SnapState = undefined) { @@ -397,7 +495,7 @@ export function getNetworkFromChainId(state: SnapState, targerChainId: string | } export function getChainIdHex(network: Network) { - return `0x${Number(network.chainId).toString(16)}`; + return `0x${num.toBigInt(network.chainId).toString(16)}`; } export function getTransactionFromVoyagerUrl(network: Network) { @@ -411,7 +509,7 @@ export function getTransactionsFromVoyagerUrl(network: Network) { export function getTransaction(state: SnapState, txnHash: string, chainId: string) { const bigIntTxnHash = num.toBigInt(txnHash); return state.transactions?.find( - (txn) => num.toBigInt(txn.txnHash) === bigIntTxnHash && Number(txn.chainId) === Number(chainId), + (txn) => num.toBigInt(txn.txnHash) === bigIntTxnHash && isSameChainId(txn.chainId, chainId), ); } @@ -428,7 +526,7 @@ export function getTransactions( let filteredTxns: Transaction[] = []; if (state.transactions) { filteredTxns = filterTransactions(state.transactions, [ - new ChainIdFilter(Number(chainId)), + new ChainIdFilter(chainId), new TimestampFilter(minTimestamp), new SenderAddressFilter(senderAddress ? num.toBigInt(senderAddress) : undefined), new ContractAddressFilter(contractAddress ? num.toBigInt(contractAddress) : undefined), @@ -550,6 +648,33 @@ export async function removeAcceptedTransaction( }); } +export function getCurrentNetwork(state: SnapState) { + return state.currentNetwork || STARKNET_TESTNET_NETWORK; +} + +export async function setCurrentNetwork(network: Network, wallet, mutex: Mutex, state: SnapState = undefined) { + return mutex.runExclusive(async () => { + if (!state) { + state = await wallet.request({ + method: 'snap_manageState', + params: { + operation: 'get', + }, + }); + } + + state.currentNetwork = network; + + await wallet.request({ + method: 'snap_manageState', + params: { + operation: 'update', + newState: state, + }, + }); + }); +} + export function toMap(arr: Array, key: string, keyConverter?: (v: z) => k): Map { return arr.reduce((map, obj: v) => { map.set(keyConverter && typeof keyConverter === 'function' ? keyConverter(obj[key] as z) : obj[key], obj); diff --git a/packages/starknet-snap/src/utils/starknetUtils.ts b/packages/starknet-snap/src/utils/starknetUtils.ts index d81fc05e..c06de763 100644 --- a/packages/starknet-snap/src/utils/starknetUtils.ts +++ b/packages/starknet-snap/src/utils/starknetUtils.ts @@ -21,10 +21,27 @@ import { GetTransactionResponse, Invocations, validateAndParseAddress as _validateAndParseAddress, + EstimateFeeDetails, + DeclareContractPayload, + DeclareContractResponse, + InvocationsDetails, + Signer, + Signature, + stark, + InvocationsSignerDetails, + Abi, + DeclareSignerDetails, + DeployAccountSignerDetails, } from 'starknet'; import type { Hex } from '@noble/curves/abstract/utils'; import { Network, SnapState, Transaction, TransactionType } from '../types/snapState'; -import { PROXY_CONTRACT_HASH, TRANSFER_SELECTOR_HEX, MIN_ACC_CONTRACT_VERSION } from './constants'; +import { + PROXY_CONTRACT_HASH, + TRANSFER_SELECTOR_HEX, + MIN_ACC_CONTRACT_VERSION, + ACCOUNT_CLASS_HASH_V0, + ACCOUNT_CLASS_HASH_V1, +} from './constants'; import { getAddressKey } from './keyPair'; import { getAccount, getAccounts, getTransactionFromVoyagerUrl, getTransactionsFromVoyagerUrl } from './snapUtils'; import { logger } from './logger'; @@ -73,6 +90,18 @@ export const callContract = async ( ); }; +export const declareContract = async ( + network: Network, + senderAddress: string, + privateKey: string | Uint8Array, + contractPayload: DeclareContractPayload, + transactionsDetail?: InvocationsDetails, +): Promise => { + const provider = getProvider(network); + const account = new Account(provider, senderAddress, privateKey); + return account.declare(contractPayload, transactionsDetail); +}; + export const estimateFee = async ( network: Network, senderAddress: string, @@ -89,12 +118,13 @@ export const estimateFeeBulk = async ( senderAddress: string, privateKey: string | Uint8Array, txnInvocation: Invocations, + invocationsDetails: EstimateFeeDetails = { blockIdentifier: 'latest' }, ): Promise => { // ensure always calling the sequencer endpoint since the rpc endpoint and // starknet.js are not supported yet. const provider = getProvider(network); const account = new Account(provider, senderAddress, privateKey, '1'); - return account.estimateFeeBulk(txnInvocation, { blockIdentifier: 'latest' }); + return account.estimateFeeBulk(txnInvocation, invocationsDetails); }; export const executeTxn = async ( @@ -102,12 +132,12 @@ export const executeTxn = async ( senderAddress: string, privateKey: string | Uint8Array, txnInvocation: Call | Call[], - maxFee: num.BigNumberish, - nonce?: number, + abis?: Abi[], + invocationsDetails?: InvocationsDetails, ): Promise => { const provider = getProvider(network); const account = new Account(provider, senderAddress, privateKey, '1'); - return account.execute(txnInvocation, undefined, { nonce, maxFee }); + return account.execute(txnInvocation, abis, invocationsDetails); }; export const deployAccount = async ( @@ -391,31 +421,26 @@ export const getNextAddressIndex = (chainId: string, state: SnapState, derivatio return uninitializedAccount?.addressIndex ?? accounts.length; }; -export const getAccContractAddressAndCallData = (accountClassHash: string, publicKey) => { - const constructorCallData = { +export const getAccContractAddressAndCallData = (publicKey) => { + const callData = CallData.compile({ signer: publicKey, guardian: '0', - }; + }); - let address = hash.calculateContractAddressFromHash( - constructorCallData.signer, - accountClassHash, - constructorCallData, - 0, - ); + let address = hash.calculateContractAddressFromHash(publicKey, ACCOUNT_CLASS_HASH_V1, callData, 0); if (address.length < 66) { address = address.replace('0x', '0x' + '0'.repeat(66 - address.length)); } return { address, - callData: CallData.compile(constructorCallData), + callData, }; }; -export const getAccContractAddressAndCallDataCairo0 = (accountClassHash: string, publicKey) => { +export const getAccContractAddressAndCallDataCairo0 = (publicKey) => { const callData = CallData.compile({ - implementation: accountClassHash, + implementation: ACCOUNT_CLASS_HASH_V0, selector: hash.getSelectorFromName('initialize'), calldata: CallData.compile({ signer: publicKey, guardian: '0' }), }); @@ -442,23 +467,10 @@ export const getKeysFromAddress = async ( addressIndex = acc.addressIndex; logger.log(`getNextAddressIndex:\nFound address in state: ${addressIndex} ${address}`); } else { - const bigIntAddress = num.toBigInt(address); - for (let i = 0; i < maxScan; i++) { - const { publicKey } = await getKeysFromAddressIndex(keyDeriver, network.chainId, state, i); - const { address: calculatedAddress } = getAccContractAddressAndCallData(network.accountClassHash, publicKey); - if (num.toBigInt(calculatedAddress) === bigIntAddress) { - addressIndex = i; - logger.log(`getNextAddressIndex:\nFound address in scan: ${addressIndex} ${address}`); - break; - } - } - } - - if (!isNaN(addressIndex)) { - return getKeysFromAddressIndex(keyDeriver, network.chainId, state, addressIndex); + const result = await findAddressIndex(network.chainId, address, keyDeriver, state, maxScan); + addressIndex = result.index; } - logger.log(`getNextAddressIndex:\nAddress not found: ${address}`); - throw new Error(`Address not found: ${address}`); + return getKeysFromAddressIndex(keyDeriver, network.chainId, state, addressIndex); }; export const getKeysFromAddressIndex = async ( @@ -485,12 +497,12 @@ export const getKeysFromAddressIndex = async ( }; export const isAccountDeployedCairo0 = async (network: Network, publicKey: string) => { - const { address } = getAccContractAddressAndCallDataCairo0(network.accountClassHash, publicKey); + const { address } = getAccContractAddressAndCallDataCairo0(publicKey); return isAccountAddressDeployed(network, address, 0); }; export const isAccountDeployed = async (network: Network, publicKey: string) => { - const { address } = getAccContractAddressAndCallData(network.accountClassHash, publicKey); + const { address } = getAccContractAddressAndCallData(publicKey); return isAccountAddressDeployed(network, address, 1); }; @@ -540,6 +552,29 @@ export const validateAndParseAddress = (address: num.BigNumberish, length = 63) return _validateAndParseAddressFn(address); }; +export const findAddressIndex = async ( + chainId: string, + address: string, + keyDeriver, + state: SnapState, + maxScan = 20, +) => { + const bigIntAddress = num.toBigInt(address); + for (let i = 0; i < maxScan; i++) { + const { publicKey } = await getKeysFromAddressIndex(keyDeriver, chainId, state, i); + const { address: calculatedAddress } = getAccContractAddressAndCallData(publicKey); + const { address: calculatedAddressCairo0 } = getAccContractAddressAndCallDataCairo0(publicKey); + if (num.toBigInt(calculatedAddress) === bigIntAddress || num.toBigInt(calculatedAddressCairo0) === bigIntAddress) { + logger.log(`findAddressIndex:\nFound address in scan: ${i} ${address}`); + return { + index: i, + cairoVersion: num.toBigInt(calculatedAddress) === bigIntAddress ? 1 : 0, + }; + } + } + throw new Error(`Address not found: ${address}`); +}; + export const isUpgradeRequired = async (network: Network, address: string) => { try { logger.log(`isUpgradeRequired: address = ${address}`); @@ -550,16 +585,14 @@ export const isUpgradeRequired = async (network: Network, address: string) => { if (!err.message.includes('Contract not found')) { throw err; } + //[TODO] if address is cario0 but not deployed we should throw error return false; } }; export const getCorrectContractAddress = async (network: Network, publicKey: string) => { - const { address: contractAddress } = getAccContractAddressAndCallData(network.accountClassHash, publicKey); - const { address: contractAddressCairo0 } = getAccContractAddressAndCallDataCairo0( - network.accountClassHashV0, - publicKey, - ); + const { address: contractAddress } = getAccContractAddressAndCallData(publicKey); + const { address: contractAddressCairo0 } = getAccContractAddressAndCallDataCairo0(publicKey); let pk = ''; logger.log( `getContractAddressByKey: contractAddressCario1 = ${contractAddress}\ncontractAddressCairo0 = ${contractAddressCairo0}\npublicKey = ${publicKey}`, @@ -591,3 +624,42 @@ export const getCorrectContractAddress = async (network: Network, publicKey: str signerPubKey: pk, }; }; + +export const signTransactions = async ( + privateKey: string, + transactions: Call[], + transactionsDetail: InvocationsSignerDetails, + abis: Abi[], +): Promise => { + const signer = new Signer(privateKey); + const signatures = await signer.signTransaction(transactions, transactionsDetail, abis); + return stark.signatureToDecimalArray(signatures); +}; + +export const signDeployAccountTransaction = async ( + privateKey: string, + transaction: DeployAccountSignerDetails, +): Promise => { + const signer = new Signer(privateKey); + const signatures = await signer.signDeployAccountTransaction(transaction); + return stark.signatureToDecimalArray(signatures); +}; + +export const signDeclareTransaction = async ( + privateKey: string, + transaction: DeclareSignerDetails, +): Promise => { + const signer = new Signer(privateKey); + const signatures = await signer.signDeclareTransaction(transaction); + return stark.signatureToDecimalArray(signatures); +}; + +export const signMessage = async ( + privateKey: Hex, + typedDataMessage: typedData.TypedData, + signerUserAddress: string, +) => { + const signer = new Signer(privateKey); + const signatures = await signer.signMessage(typedDataMessage, signerUserAddress); + return stark.signatureToDecimalArray(signatures); +}; diff --git a/packages/starknet-snap/src/utils/transaction/filter.ts b/packages/starknet-snap/src/utils/transaction/filter.ts index 677e2f09..df16d33c 100644 --- a/packages/starknet-snap/src/utils/transaction/filter.ts +++ b/packages/starknet-snap/src/utils/transaction/filter.ts @@ -106,12 +106,12 @@ export class StatusFilter implements ITransactionFilter { } export class ChainIdFilter implements ITransactionFilter { - chainId: number | undefined; - constructor(chainId: number | undefined) { + chainId: string | undefined; + constructor(chainId: string | undefined) { this.chainId = chainId; } apply(txn: Transaction): boolean { - if (this.chainId) return Number(txn.chainId) === Number(this.chainId); + if (this.chainId) return num.toBigInt(txn.chainId) === num.toBigInt(this.chainId); return true; } diff --git a/packages/starknet-snap/test/constants.test.ts b/packages/starknet-snap/test/constants.test.ts index 9b02520b..b5d6c68c 100644 --- a/packages/starknet-snap/test/constants.test.ts +++ b/packages/starknet-snap/test/constants.test.ts @@ -61,6 +61,16 @@ export const account4: AccContract = { chainId: constants.StarknetChainId.SN_GOERLI, }; +export const Cario1Account1: AccContract = { + address: '0x043e3d703b005b8367a9783fb680713349c519202aa01e9beb170bdf710ae20b', + addressSalt: '0x019e59f349e1aa813ab4556c5836d0472e5e1ae82d1e5c3b3e8aabfeb290befd', + addressIndex: 1, + derivationPath: "m / bip32:44' / bip32:9004' / bip32:0' / bip32:0", + deployTxnHash: '0x5bc00132b8f2fc0f673dc232594b26727e712b204a2716f9dc28a8c5f607b5e', + publicKey: '0x019e59f349e1aa813ab4556c5836d0472e5e1ae82d1e5c3b3e8aabfeb290befd', + chainId: constants.StarknetChainId.SN_GOERLI, +}; + export const token1: Erc20Token = { address: '0x244c20d51109adcf604fde1bbf878e5dcd549b3877ac87911ec6a158bd7aa62', name: 'Starknet ERC-20 sample', @@ -90,6 +100,23 @@ export const signature1 = export const signature2 = '30440220052956ac852275b6004c4e8042450f6dce83059f068029b037cc47338c80d062022002bc0e712f03e341bb3532fc356b779d84fcb4dbfe8ed34de2db66e121971d92'; +export const signature4Cario1SignMessage = [ + '2941323345698930086258187297320132789256148405011604592758945785805412997864', + '1024747634926675542679366527128384456926978174336360356924884281219915547518', +]; +export const signature4SignMessage = [ + '784041227270069705374122994163964526105670242785431143890307285886848872447', + '2211270729821731368290303126976610283184761443640531855459727543936510195980', +]; +export const signature4SignMessageWithUnfoundAddress = [ + '2334603173889607172621639019166048758876763440301330097513705152117706117218', + '1236892621218974511001789530309582149943950152899985052022949903736308702610', +]; + +export const signature3 = [ + '607985888254383597713678062496326274484078117880260232069103402822721981909', + '2165861203006010568296813740808310355035386348183576985427784501900302491063', +]; // Derived from seed phrase: "dog simple gown ankle release anger local pulp rose river approve miracle" export const bip44Entropy: JsonBIP44CoinTypeNode = { depth: 2, @@ -308,6 +335,20 @@ export const txn5: Transaction = { eventIds: [], }; +export const mainnetTxn1: Transaction = { + chainId: STARKNET_MAINNET_NETWORK.chainId, + contractAddress: '0x07394cbe418daa16e42b87ba67372d4ab4a5df0b05c6e554d158458ce245bc10', + contractCallData: ['0x0256d8f49882cc9366037415f48fa9fd2b5b7344ded7573ebfcef7c90e3e6b75', '2000000000000000000', '0'], + contractFuncName: 'transfer', + senderAddress: '0x05a98ec74a40383cf99896bfea2ec5e6aad16c7eed50025a5f569d585ebb13a2', + timestamp: 1653569160, + txnHash: '0x75ff16a2fd6b489d2e17673addba34af372907b0b23ff9068a23afa49c61999', + txnType: VoyagerTransactionType.INVOKE, + failureReason: '', + status: 'PENDING', + eventIds: [], +}; + export const getBalanceResp = { result: ['0x0', '0x0'], }; diff --git a/packages/starknet-snap/test/src/addNetwork.test.ts b/packages/starknet-snap/test/src/addNetwork.test.ts index 1f678846..64e83b53 100644 --- a/packages/starknet-snap/test/src/addNetwork.test.ts +++ b/packages/starknet-snap/test/src/addNetwork.test.ts @@ -26,9 +26,13 @@ describe('Test function: addNetwork', function () { wallet: walletStub, saveMutex: new Mutex(), }; - + let stateStub: sinon.SinonStub; + let dialogStub: sinon.SinonStub; beforeEach(function () { - walletStub.rpcStubs.snap_manageState.resolves(state); + stateStub = walletStub.rpcStubs.snap_manageState; + dialogStub = walletStub.rpcStubs.snap_dialog; + stateStub.resolves(state); + dialogStub.resolves(true); }); afterEach(function () { @@ -36,68 +40,60 @@ describe('Test function: addNetwork', function () { sandbox.restore(); }); - it('should add the network correctly (should throw error as temporarily disabled)', async function () { + it('should add the network correctly', async function () { const requestObject: AddNetworkRequestParams = { networkName: 'Starknet Unit SN_GOERLI', networkChainId: '0x534e5f474f777', networkBaseUrl: 'https://alpha-unit-SN_GOERLI.starknet.io', - networkNodeUrl: '', + networkNodeUrl: 'https://alpha-unit-SN_GOERLI.starknet.io', }; apiParams.requestParams = requestObject; - try { - await addNetwork(apiParams); - return; - } catch (err) { - expect(err).to.be.an('Error'); - expect(err.message).to.be.eql('addNetwork is currently disabled'); - } + const result = await addNetwork(apiParams); + expect(result).to.be.eql(true); + expect(stateStub).to.be.calledOnce; + expect(state.networks.length).to.be.eql(3); }); - it('should update the network correctly (should throw error as temporarily disabled)', async function () { + it('should update the network correctly', async function () { const requestObject: AddNetworkRequestParams = { networkName: 'Starknet Unit SN_GOERLI 2', networkChainId: '0x534e5f474f777', networkBaseUrl: 'https://alpha-unit-SN_GOERLI-2.starknet.io', - networkNodeUrl: '', + networkNodeUrl: 'https://alpha-unit-SN_GOERLI.starknet.io', }; apiParams.requestParams = requestObject; - try { - await addNetwork(apiParams); - } catch (err) { - expect(err).to.be.an('Error'); - expect(err.message).to.be.eql('addNetwork is currently disabled'); - } + const result = await addNetwork(apiParams); + expect(result).to.be.eql(true); + expect(stateStub).to.be.calledOnce; + expect(state.networks.length).to.be.eql(3); }); - it('should not update snap state with the duplicated network (should throw error as temporarily disabled)', async function () { + it('should not update snap state with the duplicated network', async function () { const requestObject: AddNetworkRequestParams = { networkName: 'Starknet Unit SN_GOERLI 2', networkChainId: '0x534e5f474f777', networkBaseUrl: 'https://alpha-unit-SN_GOERLI-2.starknet.io', - networkNodeUrl: '', + networkNodeUrl: 'https://alpha-unit-SN_GOERLI.starknet.io', }; apiParams.requestParams = requestObject; - try { - await addNetwork(apiParams); - } catch (err) { - expect(err).to.be.an('Error'); - expect(err.message).to.be.eql('addNetwork is currently disabled'); - } + const result = await addNetwork(apiParams); + expect(result).to.be.eql(true); + expect(stateStub).to.be.callCount(0); + expect(state.networks.length).to.be.eql(3); }); - it('should throw error if upsertNetwork failed', async function () { + it('should throw an error if upsertNetwork failed', async function () { sandbox.stub(snapUtils, 'upsertNetwork').throws(new Error()); const requestObject: AddNetworkRequestParams = { networkName: 'Starknet Unit SN_GOERLI 2', networkChainId: '0x534e5f474f777', networkBaseUrl: 'https://alpha-unit-SN_GOERLI-2.starknet.io', - networkNodeUrl: '', + networkNodeUrl: 'https://alpha-unit-SN_GOERLI.starknet.io', }; apiParams.requestParams = requestObject; - let result; try { - await addNetwork(apiParams); + result = await addNetwork(apiParams); } catch (err) { result = err; } finally { @@ -286,34 +282,12 @@ describe('Test function: addNetwork', function () { } }); - it('should throw an error if the network account class hash is not valid', async function () { - const requestObject: AddNetworkRequestParams = { - networkName: 'Starknet Unit SN_GOERLI', - networkChainId: '0x534e5f474f777', - networkBaseUrl: '', - networkNodeUrl: 'http://alpha-unit-SN_GOERLI-2.starknet.io', - accountClassHash: '0x811111111111111111111111111111111111111111111111111111111111111', - // a valid Starknet hash is essentially a cario felt, which is a 251 bit positive number - // which means it can only be 63 hex character long with the leading char being [1-7] - }; - apiParams.requestParams = requestObject; - let result; - try { - result = await addNetwork(apiParams); - } catch (err) { - result = err; - } finally { - expect(result).to.be.an('Error'); - } - }); - it('should throw an error if the network chainId is one of the preloaded network chainId', async function () { const requestObject: AddNetworkRequestParams = { networkName: 'Starknet Unit SN_GOERLI', networkChainId: '0x534e5f474f45524c49', networkBaseUrl: 'http://alpha-unit-SN_GOERLI-2.starknet.io', networkNodeUrl: '', - accountClassHash: '0x3e327de1c40540b98d05cbcb13552008e36f0ec8d61d46956d2f9752c294328', }; apiParams.requestParams = requestObject; let result; @@ -328,11 +302,10 @@ describe('Test function: addNetwork', function () { it('should throw an error if the network name is one of the preloaded network name', async function () { const requestObject: AddNetworkRequestParams = { - networkName: 'Goerli SN_GOERLI', - networkChainId: '0x12345678', - networkBaseUrl: 'http://alpha-unit-SN_GOERLI-2.starknet.io', - networkNodeUrl: '', - accountClassHash: '0x3e327de1c40540b98d05cbcb13552008e36f0ec8d61d46956d2f9752c294328', + networkName: STARKNET_TESTNET_NETWORK.name, + networkChainId: STARKNET_TESTNET_NETWORK.chainId, + networkBaseUrl: STARKNET_TESTNET_NETWORK.baseUrl, + networkNodeUrl: STARKNET_TESTNET_NETWORK.nodeUrl, }; apiParams.requestParams = requestObject; let result; diff --git a/packages/starknet-snap/test/src/createAccount.test.ts b/packages/starknet-snap/test/src/createAccount.test.ts index 88a9de7b..2b9a8936 100644 --- a/packages/starknet-snap/test/src/createAccount.test.ts +++ b/packages/starknet-snap/test/src/createAccount.test.ts @@ -75,10 +75,7 @@ describe('Test function: createAccount', function () { state, -1, ); - const { address: contractAddress } = utils.getAccContractAddressAndCallData( - STARKNET_MAINNET_NETWORK.accountClassHash, - publicKey, - ); + const { address: contractAddress } = utils.getAccContractAddressAndCallData(publicKey); expect(walletStub.rpcStubs.snap_manageState).to.have.been.callCount(0); expect(result.address).to.be.eq(contractAddress); expect(state.accContracts.length).to.be.eq(0); @@ -292,6 +289,8 @@ describe('Test function: createAccount', function () { return createAccountFailedProxyResp; }); sandbox.stub(utils, 'isAccountAddressDeployed').resolves(false); + sandbox.stub(utils, 'callContract').resolves(getBalanceResp); + sandbox.stub(utils, 'getSigner').throws(new Error()); sandbox.stub(utils, 'estimateAccountDeployFee').callsFake(async () => { return estimateDeployFeeResp; }); @@ -311,6 +310,8 @@ describe('Test function: createAccount', function () { return createAccountProxyResp; }); sandbox.stub(utils, 'isAccountAddressDeployed').resolves(false); + sandbox.stub(utils, 'callContract').resolves(getBalanceResp); + sandbox.stub(utils, 'getSigner').throws(new Error()); sandbox.stub(utils, 'estimateAccountDeployFee').callsFake(async () => { return estimateDeployFeeResp; }); diff --git a/packages/starknet-snap/test/src/declareContract.test.ts b/packages/starknet-snap/test/src/declareContract.test.ts new file mode 100644 index 00000000..30f1616a --- /dev/null +++ b/packages/starknet-snap/test/src/declareContract.test.ts @@ -0,0 +1,121 @@ +import chai, { expect } from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import { WalletMock } from '../wallet.mock.test'; +import * as utils from '../../src/utils/starknetUtils'; +import { declareContract } from '../../src/declareContract'; +import { SnapState } from '../../src/types/snapState'; +import { STARKNET_MAINNET_NETWORK, STARKNET_TESTNET_NETWORK } from '../../src/utils/constants'; +import { createAccountProxyTxn, getBip44EntropyStub, account1 } from '../constants.test'; +import { getAddressKeyDeriver } from '../../src/utils/keyPair'; +import { Mutex } from 'async-mutex'; +import { ApiParams, DeclareContractRequestParams } from '../../src/types/snapApi'; + +chai.use(sinonChai); +const sandbox = sinon.createSandbox(); + +describe('Test function: declareContract', function () { + this.timeout(10000); + const walletStub = new WalletMock(); + const state: SnapState = { + accContracts: [account1], + erc20Tokens: [], + networks: [STARKNET_MAINNET_NETWORK, STARKNET_TESTNET_NETWORK], + transactions: [], + }; + const apiParams: ApiParams = { + state, + requestParams: {}, + wallet: walletStub, + saveMutex: new Mutex(), + }; + + const requestObject: DeclareContractRequestParams = { + chainId: STARKNET_TESTNET_NETWORK.chainId, + senderAddress: account1.address, + contractPayload: { + contract: 'TestContract', + }, + invocationsDetails: { + maxFee: 100, + }, + }; + + beforeEach(async function () { + walletStub.rpcStubs.snap_getBip44Entropy.callsFake(getBip44EntropyStub); + apiParams.keyDeriver = await getAddressKeyDeriver(walletStub); + apiParams.requestParams = requestObject; + sandbox.useFakeTimers(createAccountProxyTxn.timestamp); + walletStub.rpcStubs.snap_dialog.resolves(true); + walletStub.rpcStubs.snap_manageState.resolves(state); + }); + + afterEach(function () { + walletStub.reset(); + sandbox.restore(); + }); + + it('should declareContract correctly', async function () { + const declareContractStub = sandbox.stub(utils, 'declareContract').resolves({ + transaction_hash: 'transaction_hash', + class_hash: 'class_hash', + }); + const result = await declareContract(apiParams); + const { privateKey } = await utils.getKeysFromAddress( + apiParams.keyDeriver, + STARKNET_TESTNET_NETWORK, + state, + account1.address, + ); + + expect(result).to.eql({ + transaction_hash: 'transaction_hash', + class_hash: 'class_hash', + }); + expect(declareContractStub).to.have.been.calledOnce; + expect(declareContractStub).to.have.been.calledWith( + STARKNET_TESTNET_NETWORK, + account1.address, + privateKey, + { contract: 'TestContract' }, + { maxFee: 100 }, + ); + }); + + it('should throw error if declareContract fail', async function () { + const declareContractStub = sandbox.stub(utils, 'declareContract').rejects('error'); + const { privateKey } = await utils.getKeysFromAddress( + apiParams.keyDeriver, + STARKNET_TESTNET_NETWORK, + state, + account1.address, + ); + let result; + try { + await declareContract(apiParams); + } catch (e) { + result = e; + } finally { + expect(result).to.be.an('Error'); + expect(declareContractStub).to.have.been.calledOnce; + expect(declareContractStub).to.have.been.calledWith( + STARKNET_TESTNET_NETWORK, + account1.address, + privateKey, + { contract: 'TestContract' }, + { maxFee: 100 }, + ); + } + }); + + it('should return false if user rejected to sign the transaction', async function () { + walletStub.rpcStubs.snap_dialog.resolves(false); + const declareContractStub = sandbox.stub(utils, 'declareContract').resolves({ + transaction_hash: 'transaction_hash', + class_hash: 'class_hash', + }); + const result = await declareContract(apiParams); + expect(result).to.equal(false); + expect(declareContractStub).to.have.been.not.called; + }); +}); diff --git a/packages/starknet-snap/test/src/estimateFee.test.ts b/packages/starknet-snap/test/src/estimateFee.test.ts index 09cc27fe..8be863b6 100644 --- a/packages/starknet-snap/test/src/estimateFee.test.ts +++ b/packages/starknet-snap/test/src/estimateFee.test.ts @@ -5,11 +5,19 @@ import { WalletMock } from '../wallet.mock.test'; import * as utils from '../../src/utils/starknetUtils'; import { estimateFee } from '../../src/estimateFee'; import { SnapState } from '../../src/types/snapState'; -import { STARKNET_TESTNET_NETWORK } from '../../src/utils/constants'; +import { ACCOUNT_CLASS_HASH_V1, STARKNET_TESTNET_NETWORK } from '../../src/utils/constants'; import { getAddressKeyDeriver } from '../../src/utils/keyPair'; -import { account2, estimateDeployFeeResp4, estimateFeeResp, getBip44EntropyStub } from '../constants.test'; +import { + account2, + Cario1Account1, + estimateDeployFeeResp4, + estimateFeeResp, + getBip44EntropyStub, + getBalanceResp, +} from '../constants.test'; import { Mutex } from 'async-mutex'; import { ApiParams, EstimateFeeRequestParams } from '../../src/types/snapApi'; +import { TransactionType } from 'starknet'; chai.use(sinonChai); const sandbox = sinon.createSandbox(); @@ -18,7 +26,7 @@ describe('Test function: estimateFee', function () { const walletStub = new WalletMock(); const state: SnapState = { - accContracts: [account2], + accContracts: [], erc20Tokens: [], networks: [STARKNET_TESTNET_NETWORK], transactions: [], @@ -39,6 +47,7 @@ describe('Test function: estimateFee', function () { beforeEach(async function () { walletStub.rpcStubs.snap_getBip44Entropy.callsFake(getBip44EntropyStub); apiParams.keyDeriver = await getAddressKeyDeriver(walletStub); + sandbox.stub(utils, 'callContract').resolves(getBalanceResp); }); afterEach(function () { @@ -129,69 +138,133 @@ describe('Test function: estimateFee', function () { sandbox.stub(utils, 'isUpgradeRequired').resolves(false); }); - describe('when account is deployed', function () { + describe('when account is cario1 address', function () { beforeEach(async function () { - estimateFeeBulkStub = sandbox.stub(utils, 'estimateFeeBulk'); - sandbox.stub(utils, 'isAccountDeployed').resolves(true); + apiParams.requestParams = { + ...apiParams.requestParams, + senderAddress: Cario1Account1.address, + }; }); - it('should estimate the fee correctly', async function () { - estimateFeeStub = sandbox.stub(utils, 'estimateFee').resolves(estimateFeeResp); + describe('when account is deployed', function () { + beforeEach(async function () { + estimateFeeBulkStub = sandbox.stub(utils, 'estimateFeeBulk'); + sandbox.stub(utils, 'isAccountDeployed').resolves(true); + }); - const result = await estimateFee(apiParams); - expect(result.suggestedMaxFee).to.be.eq(estimateFeeResp.suggestedMaxFee.toString(10)); - expect(estimateFeeStub).callCount(1); - expect(estimateFeeBulkStub).callCount(0); - }); - - it('should throw error if estimateFee failed', async function () { - estimateFeeStub = sandbox.stub(utils, 'estimateFee').throws('Error'); - apiParams.requestParams = requestObject; - - let result; - try { - await estimateFee(apiParams); - } catch (err) { - result = err; - } finally { - expect(result).to.be.an('Error'); + it('should estimate the fee correctly', async function () { + estimateFeeStub = sandbox.stub(utils, 'estimateFee').resolves(estimateFeeResp); + const result = await estimateFee(apiParams); + expect(result.suggestedMaxFee).to.be.eq(estimateFeeResp.suggestedMaxFee.toString(10)); expect(estimateFeeStub).callCount(1); expect(estimateFeeBulkStub).callCount(0); - } + }); }); - }); - describe('when account is not deployed', function () { - beforeEach(async function () { - estimateFeeStub = sandbox.stub(utils, 'estimateFee'); - sandbox.stub(utils, 'isAccountDeployed').resolves(false); - }); + describe('when account is not deployed', function () { + beforeEach(async function () { + estimateFeeStub = sandbox.stub(utils, 'estimateFee'); + sandbox.stub(utils, 'isAccountDeployed').resolves(false); + }); - it('should estimate the fee including deploy txn correctly', async function () { - estimateFeeBulkStub = sandbox - .stub(utils, 'estimateFeeBulk') - .resolves([estimateFeeResp, estimateDeployFeeResp4]); - const expectedSuggestedMaxFee = estimateDeployFeeResp4.suggestedMaxFee + estimateFeeResp.suggestedMaxFee; - const result = await estimateFee(apiParams); - expect(result.suggestedMaxFee).to.be.eq(expectedSuggestedMaxFee.toString(10)); - expect(estimateFeeStub).callCount(0); - expect(estimateFeeBulkStub).callCount(1); - }); + it('should estimate the fee including deploy txn correctly', async function () { + estimateFeeBulkStub = sandbox + .stub(utils, 'estimateFeeBulk') + .resolves([estimateFeeResp, estimateDeployFeeResp4]); + const expectedSuggestedMaxFee = estimateDeployFeeResp4.suggestedMaxFee + estimateFeeResp.suggestedMaxFee; + const result = await estimateFee(apiParams); + + const { privateKey, publicKey } = await utils.getKeysFromAddress( + apiParams.keyDeriver, + STARKNET_TESTNET_NETWORK, + state, + Cario1Account1.address, + ); + const { callData } = utils.getAccContractAddressAndCallData(publicKey); + const apiRequest = apiParams.requestParams as EstimateFeeRequestParams; + + const expectedBulkTransaction = [ + { + type: TransactionType.DEPLOY_ACCOUNT, + payload: { + classHash: ACCOUNT_CLASS_HASH_V1, + contractAddress: Cario1Account1.address, + constructorCalldata: callData, + addressSalt: publicKey, + }, + }, + { + type: TransactionType.INVOKE, + payload: { + contractAddress: apiRequest.contractAddress, + entrypoint: apiRequest.contractFuncName, + calldata: utils.getCallDataArray(apiRequest.contractCallData), + }, + }, + ]; - it('should throw error if estimateFee failed', async function () { - estimateFeeBulkStub = sandbox.stub(utils, 'estimateFeeBulk').throws('Error'); - apiParams.requestParams = requestObject; - - let result; - try { - await estimateFee(apiParams); - } catch (err) { - result = err; - } finally { - expect(result).to.be.an('Error'); + expect(result.suggestedMaxFee).to.be.eq(expectedSuggestedMaxFee.toString(10)); expect(estimateFeeStub).callCount(0); expect(estimateFeeBulkStub).callCount(1); - } + expect(estimateFeeBulkStub).to.be.calledWith( + STARKNET_TESTNET_NETWORK, + Cario1Account1.address, + privateKey, + expectedBulkTransaction, + ); + }); + + it('should throw error if estimateFee failed', async function () { + estimateFeeBulkStub = sandbox.stub(utils, 'estimateFeeBulk').throws('Error'); + apiParams.requestParams = requestObject; + + let result; + try { + await estimateFee(apiParams); + } catch (err) { + result = err; + } finally { + expect(result).to.be.an('Error'); + expect(estimateFeeStub).callCount(0); + expect(estimateFeeBulkStub).callCount(1); + } + }); + }); + }); + + describe('when account is cario0 address', function () { + describe('when account is deployed', function () { + beforeEach(async function () { + estimateFeeBulkStub = sandbox.stub(utils, 'estimateFeeBulk'); + sandbox.stub(utils, 'isAccountDeployed').resolves(true); + }); + + it('should estimate the fee correctly', async function () { + estimateFeeStub = sandbox.stub(utils, 'estimateFee').resolves(estimateFeeResp); + const result = await estimateFee(apiParams); + expect(result.suggestedMaxFee).to.be.eq(estimateFeeResp.suggestedMaxFee.toString(10)); + expect(estimateFeeStub).callCount(1); + expect(estimateFeeBulkStub).callCount(0); + }); + + it('should throw error if estimateFee failed', async function () { + estimateFeeStub = sandbox.stub(utils, 'estimateFee').throws('Error'); + apiParams.requestParams = requestObject; + + let result; + try { + await estimateFee(apiParams); + } catch (err) { + result = err; + } finally { + expect(result).to.be.an('Error'); + expect(estimateFeeStub).callCount(1); + expect(estimateFeeBulkStub).callCount(0); + } + }); + }); + describe('when account is not deployed', function () { + //not support cario0 address if not deployed }); }); }); diff --git a/packages/starknet-snap/test/src/estimateFees.test.ts b/packages/starknet-snap/test/src/estimateFees.test.ts new file mode 100644 index 00000000..5e004d03 --- /dev/null +++ b/packages/starknet-snap/test/src/estimateFees.test.ts @@ -0,0 +1,101 @@ +import chai, { expect } from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import { WalletMock } from '../wallet.mock.test'; +import { SnapState } from '../../src/types/snapState'; +import { estimateFees } from '../../src/estimateFees'; +import { STARKNET_TESTNET_NETWORK } from '../../src/utils/constants'; +import { account2, estimateDeployFeeResp2, estimateDeployFeeResp3, getBip44EntropyStub } from '../constants.test'; +import { getAddressKeyDeriver } from '../../src/utils/keyPair'; +import * as utils from '../../src/utils/starknetUtils'; +import { Mutex } from 'async-mutex'; +import { ApiParams } from '../../src/types/snapApi'; +import { TransactionType } from 'starknet'; +chai.use(sinonChai); +const sandbox = sinon.createSandbox(); + +describe('Test function: estimateFees', function () { + this.timeout(5000); + const walletStub = new WalletMock(); + const state: SnapState = { + accContracts: [account2], + erc20Tokens: [], + networks: [STARKNET_TESTNET_NETWORK], + transactions: [], + }; + const apiParams: ApiParams = { + state, + requestParams: {}, + wallet: walletStub, + saveMutex: new Mutex(), + }; + + beforeEach(async function () { + walletStub.rpcStubs.snap_getBip44Entropy.callsFake(getBip44EntropyStub); + apiParams.keyDeriver = await getAddressKeyDeriver(walletStub); + }); + + afterEach(function () { + walletStub.reset(); + sandbox.restore(); + }); + + it('should estimate the fee including deploy txn correctly', async function () { + const feeResult = [estimateDeployFeeResp2, estimateDeployFeeResp3]; + sandbox.stub(utils, 'estimateFeeBulk').callsFake(async () => { + return feeResult; + }); + apiParams.requestParams = { + senderAddress: account2.address, + chainId: STARKNET_TESTNET_NETWORK.chainId, + invocations: [ + { + type: TransactionType.INVOKE, + payload: { + entrypoint: 'transfer', + contractAddress: '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7', + calldata: ['1697416752243704114657612983658108968471303240361660550219082009242042413588', '1', '0'], + }, + }, + ], + }; + const expectedResult = feeResult.map((fee) => ({ + overall_fee: fee.overall_fee.toString(10) || '0', + gas_consumed: fee.gas_consumed.toString(10) || '0', + gas_price: fee.gas_price.toString(10) || '0', + suggestedMaxFee: fee.suggestedMaxFee.toString(10) || '0', + })); + const result = await estimateFees(apiParams); + expect(result).to.eql(expectedResult); + }); + + it('should throw error if estimateFee failed', async function () { + sandbox.stub(utils, 'getSigner').callsFake(async () => { + return account2.publicKey; + }); + sandbox.stub(utils, 'estimateFeeBulk').throws(new Error()); + apiParams.requestParams = { + senderAddress: account2.address, + chainId: STARKNET_TESTNET_NETWORK.chainId, + invocations: [ + { + type: TransactionType.INVOKE, + payload: { + entrypoint: 'transfer', + contractAddress: '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7', + calldata: ['1697416752243704114657612983658108968471303240361660550219082009242042413588', '1', '0'], + }, + }, + ], + }; + + let result; + try { + await estimateFees(apiParams); + } catch (err) { + result = err; + } finally { + expect(result).to.be.an('Error'); + } + }); +}); diff --git a/packages/starknet-snap/test/src/executeTxn.test.ts b/packages/starknet-snap/test/src/executeTxn.test.ts new file mode 100644 index 00000000..036fd045 --- /dev/null +++ b/packages/starknet-snap/test/src/executeTxn.test.ts @@ -0,0 +1,187 @@ +import chai, { expect } from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import { WalletMock } from '../wallet.mock.test'; +import * as utils from '../../src/utils/starknetUtils'; +import { executeTxn } from '../../src/executeTxn'; +import { SnapState } from '../../src/types/snapState'; +import { STARKNET_MAINNET_NETWORK, STARKNET_TESTNET_NETWORK } from '../../src/utils/constants'; +import { createAccountProxyTxn, getBip44EntropyStub, account1 } from '../constants.test'; +import { getAddressKeyDeriver } from '../../src/utils/keyPair'; +import { Mutex } from 'async-mutex'; +import { ApiParams, ExecuteTxnRequestParams } from '../../src/types/snapApi'; + +chai.use(sinonChai); +const sandbox = sinon.createSandbox(); + +describe('Test function: executeTxn', function () { + this.timeout(10000); + const walletStub = new WalletMock(); + const state: SnapState = { + accContracts: [], + erc20Tokens: [], + networks: [STARKNET_MAINNET_NETWORK, STARKNET_TESTNET_NETWORK], + transactions: [], + }; + const apiParams: ApiParams = { + state, + requestParams: {}, + wallet: walletStub, + saveMutex: new Mutex(), + }; + + const requestObject: ExecuteTxnRequestParams = { + chainId: STARKNET_MAINNET_NETWORK.chainId, + senderAddress: account1.address, + txnInvocation: { + entrypoint: 'transfer', + calldata: ['0', '0', '0'], + contractAddress: createAccountProxyTxn.contractAddress, + }, + invocationsDetails: { + maxFee: 100, + }, + }; + + beforeEach(async function () { + walletStub.rpcStubs.snap_getBip44Entropy.callsFake(getBip44EntropyStub); + apiParams.keyDeriver = await getAddressKeyDeriver(walletStub); + apiParams.requestParams = requestObject; + sandbox.useFakeTimers(createAccountProxyTxn.timestamp); + walletStub.rpcStubs.snap_dialog.resolves(true); + walletStub.rpcStubs.snap_manageState.resolves(state); + }); + + afterEach(function () { + walletStub.reset(); + sandbox.restore(); + apiParams.requestParams = requestObject; + }); + + it('should executeTxn correctly', async function () { + const stub = sandbox.stub(utils, 'executeTxn').resolves({ + transaction_hash: 'transaction_hash', + }); + const result = await executeTxn(apiParams); + const { privateKey } = await utils.getKeysFromAddress( + apiParams.keyDeriver, + STARKNET_MAINNET_NETWORK, + state, + account1.address, + ); + + expect(result).to.eql({ + transaction_hash: 'transaction_hash', + }); + expect(stub).to.have.been.calledOnce; + expect(stub).to.have.been.calledWith( + STARKNET_MAINNET_NETWORK, + account1.address, + privateKey, + { + entrypoint: 'transfer', + calldata: ['0', '0', '0'], + contractAddress: createAccountProxyTxn.contractAddress, + }, + undefined, + { maxFee: 100 }, + ); + }); + + it('should executeTxn multiple', async function () { + const stub = sandbox.stub(utils, 'executeTxn').resolves({ + transaction_hash: 'transaction_hash', + }); + apiParams.requestParams = { + chainId: STARKNET_MAINNET_NETWORK.chainId, + senderAddress: account1.address, + txnInvocation: [ + { + entrypoint: 'transfer', + calldata: ['0', '0', '0'], + contractAddress: createAccountProxyTxn.contractAddress, + }, + { + entrypoint: 'transfer2', + calldata: ['0', '0', '0'], + contractAddress: createAccountProxyTxn.contractAddress, + }, + ], + invocationsDetails: { + maxFee: 100, + }, + }; + const result = await executeTxn(apiParams); + const { privateKey } = await utils.getKeysFromAddress( + apiParams.keyDeriver, + STARKNET_MAINNET_NETWORK, + state, + account1.address, + ); + + expect(result).to.eql({ + transaction_hash: 'transaction_hash', + }); + expect(stub).to.have.been.calledOnce; + expect(stub).to.have.been.calledWith( + STARKNET_MAINNET_NETWORK, + account1.address, + privateKey, + [ + { + entrypoint: 'transfer', + calldata: ['0', '0', '0'], + contractAddress: createAccountProxyTxn.contractAddress, + }, + { + entrypoint: 'transfer2', + calldata: ['0', '0', '0'], + contractAddress: createAccountProxyTxn.contractAddress, + }, + ], + undefined, + { maxFee: 100 }, + ); + }); + + it('should throw error if executeTxn fail', async function () { + const stub = sandbox.stub(utils, 'executeTxn').rejects('error'); + const { privateKey } = await utils.getKeysFromAddress( + apiParams.keyDeriver, + STARKNET_MAINNET_NETWORK, + state, + account1.address, + ); + let result; + try { + await executeTxn(apiParams); + } catch (e) { + result = e; + } finally { + expect(result).to.be.an('Error'); + expect(stub).to.have.been.calledOnce; + expect(stub).to.have.been.calledWith( + STARKNET_MAINNET_NETWORK, + account1.address, + privateKey, + { + entrypoint: 'transfer', + calldata: ['0', '0', '0'], + contractAddress: createAccountProxyTxn.contractAddress, + }, + undefined, + { maxFee: 100 }, + ); + } + }); + + it('should return false if user rejected to sign the transaction', async function () { + walletStub.rpcStubs.snap_dialog.resolves(false); + const stub = sandbox.stub(utils, 'executeTxn').resolves({ + transaction_hash: 'transaction_hash', + }); + const result = await executeTxn(apiParams); + expect(result).to.equal(false); + expect(stub).to.have.been.not.called; + }); +}); diff --git a/packages/starknet-snap/test/src/getCurrentNetwork.test.ts b/packages/starknet-snap/test/src/getCurrentNetwork.test.ts new file mode 100644 index 00000000..1b706305 --- /dev/null +++ b/packages/starknet-snap/test/src/getCurrentNetwork.test.ts @@ -0,0 +1,53 @@ +import chai, { expect } from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import { WalletMock } from '../wallet.mock.test'; +import { SnapState } from '../../src/types/snapState'; +import { STARKNET_MAINNET_NETWORK, STARKNET_TESTNET_NETWORK } from '../../src/utils/constants'; +import { getCurrentNetwork } from '../../src/getCurrentNetwork'; +import { Mutex } from 'async-mutex'; +import { ApiParams } from '../../src/types/snapApi'; + +chai.use(sinonChai); +const sandbox = sinon.createSandbox(); + +describe('Test function: getStoredNetworks', function () { + const walletStub = new WalletMock(); + const state: SnapState = { + accContracts: [], + erc20Tokens: [], + networks: [STARKNET_TESTNET_NETWORK, STARKNET_MAINNET_NETWORK], + transactions: [], + currentNetwork: STARKNET_MAINNET_NETWORK, + }; + const apiParams: ApiParams = { + state, + requestParams: {}, + wallet: walletStub, + saveMutex: new Mutex(), + }; + + let stateStub: sinon.SinonStub; + beforeEach(function () { + stateStub = walletStub.rpcStubs.snap_manageState; + stateStub.resolves(state); + }); + + afterEach(function () { + walletStub.reset(); + sandbox.restore(); + }); + + it('should get the current network correctly', async function () { + const result = await getCurrentNetwork(apiParams); + expect(stateStub).not.to.have.been.called; + expect(result).to.be.eql(STARKNET_MAINNET_NETWORK); + }); + + it('should get STARKNET_TESTNET_NETWORK if current network is undefined', async function () { + state.currentNetwork = undefined; + const result = await getCurrentNetwork(apiParams); + expect(stateStub).not.to.have.been.called; + expect(result).to.be.eql(STARKNET_TESTNET_NETWORK); + }); +}); diff --git a/packages/starknet-snap/test/src/getTransactions.test.ts b/packages/starknet-snap/test/src/getTransactions.test.ts index cc9fe024..938f706a 100644 --- a/packages/starknet-snap/test/src/getTransactions.test.ts +++ b/packages/starknet-snap/test/src/getTransactions.test.ts @@ -23,6 +23,7 @@ import { txn3, txn4, txn5, + mainnetTxn1, } from '../constants.test'; import { getTransactions, updateStatus } from '../../src/getTransactions'; import { Mutex } from 'async-mutex'; @@ -45,6 +46,7 @@ describe('Test function: getTransactions', function () { { ...txn3 }, { ...txn4 }, { ...txn5 }, + { ...mainnetTxn1 }, { ...createAccountProxyTxn }, { ...initAccountTxn }, ], diff --git a/packages/starknet-snap/test/src/recoverAccounts.test.ts b/packages/starknet-snap/test/src/recoverAccounts.test.ts index 36cc4ff9..27264081 100644 --- a/packages/starknet-snap/test/src/recoverAccounts.test.ts +++ b/packages/starknet-snap/test/src/recoverAccounts.test.ts @@ -283,24 +283,4 @@ describe('Test function: recoverAccounts', function () { expect(result).to.be.an('Error'); } }); - - it('should show confirmation box with failure msg if network accountClassHash is missing', async function () { - const maxScanned = 5; - const maxMissed = 3; - const getCorrectContractAddressStub = sandbox.stub(utils, 'getCorrectContractAddress'); - const isUpgradeRequiredStub = sandbox.stub(utils, 'isUpgradeRequired'); - getCorrectContractAddressStub.resolves({ address: mainnetAccAddresses[0], signerPubKey: mainnetPublicKeys[0] }); - isUpgradeRequiredStub.resolves(false); - - const requestObject: RecoverAccountsRequestParams = { - startScanIndex: 0, - maxScanned, - maxMissed, - chainId: INVALID_NETWORK.chainId, - }; - apiParams.requestParams = requestObject; - const result = await recoverAccounts(apiParams); - expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; - expect(result).eql(null); - }); }); diff --git a/packages/starknet-snap/test/src/sendTransaction.test.ts b/packages/starknet-snap/test/src/sendTransaction.test.ts index 4610bb52..c1e5ee43 100644 --- a/packages/starknet-snap/test/src/sendTransaction.test.ts +++ b/packages/starknet-snap/test/src/sendTransaction.test.ts @@ -21,6 +21,7 @@ import { token2, token3, unfoundUserAddress, + Cario1Account1, } from '../constants.test'; import { getAddressKeyDeriver } from '../../src/utils/keyPair'; import { Mutex } from 'async-mutex'; @@ -34,7 +35,7 @@ describe('Test function: sendTransaction', function () { this.timeout(5000); const walletStub = new WalletMock(); const state: SnapState = { - accContracts: [account1], + accContracts: [], erc20Tokens: [token2, token3], networks: [STARKNET_TESTNET_NETWORK], transactions: [], @@ -171,44 +172,85 @@ describe('Test function: sendTransaction', function () { apiParams.requestParams = Object.assign({}, requestObject); }); - describe('when require upgrade checking fail', function () { - it('should throw error', async function () { - const isUpgradeRequiredStub = sandbox.stub(utils, 'isUpgradeRequired').throws('network error'); - let result; - try { - result = await sendTransaction(apiParams); - } catch (err) { - result = err; - } finally { - expect(isUpgradeRequiredStub).to.have.been.calledOnceWith(STARKNET_TESTNET_NETWORK, account1.address); - expect(result).to.be.an('Error'); - } + describe('when account is cairo 0', function () { + describe('when require upgrade checking fail', function () { + it('should throw error', async function () { + const isUpgradeRequiredStub = sandbox.stub(utils, 'isUpgradeRequired').throws('network error'); + let result; + try { + result = await sendTransaction(apiParams); + } catch (err) { + result = err; + } finally { + expect(isUpgradeRequiredStub).to.have.been.calledOnceWith(STARKNET_TESTNET_NETWORK, account1.address); + expect(result).to.be.an('Error'); + } + }); }); - }); - describe('when account require upgrade', function () { - let isUpgradeRequiredStub: sinon.SinonStub; - beforeEach(async function () { - isUpgradeRequiredStub = sandbox.stub(utils, 'isUpgradeRequired').resolves(true); + describe('when account require upgrade', function () { + let isUpgradeRequiredStub: sinon.SinonStub; + beforeEach(async function () { + isUpgradeRequiredStub = sandbox.stub(utils, 'isUpgradeRequired').resolves(true); + }); + + it('should throw error if upgrade required', async function () { + let result; + try { + result = await sendTransaction(apiParams); + } catch (err) { + result = err; + } finally { + expect(isUpgradeRequiredStub).to.have.been.calledOnceWith(STARKNET_TESTNET_NETWORK, account1.address); + expect(result).to.be.an('Error'); + } + }); }); - it('should throw error if upgrade required', async function () { - let result; - try { - result = await sendTransaction(apiParams); - } catch (err) { - result = err; - } finally { - expect(isUpgradeRequiredStub).to.have.been.calledOnceWith(STARKNET_TESTNET_NETWORK, account1.address); - expect(result).to.be.an('Error'); - } + describe('when account do not require upgrade', function () { + let executeTxnResp; + beforeEach(async function () { + sandbox.stub(utils, 'isUpgradeRequired').resolves(false); + sandbox.stub(estimateFeeSnap, 'estimateFee').resolves({ + suggestedMaxFee: estimateFeeResp.suggestedMaxFee.toString(10), + overallFee: estimateFeeResp.overall_fee.toString(10), + gasConsumed: '0', + gasPrice: '0', + unit: 'wei', + includeDeploy: true, + }); + executeTxnResp = sendTransactionResp; + sandbox.stub(utils, 'executeTxn').resolves(executeTxnResp); + walletStub.rpcStubs.snap_manageState.resolves(state); + walletStub.rpcStubs.snap_dialog.resolves(true); + }); + describe('when account is deployed', function () { + beforeEach(async function () { + sandbox.stub(utils, 'isAccountAddressDeployed').resolves(true); + }); + + it('should send a transaction for transferring 10 tokens correctly', async function () { + const result = await sendTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(walletStub.rpcStubs.snap_manageState).to.have.been.called; + expect(result).to.be.eql(sendTransactionResp); + }); + }); + + describe('when account is not deployed', function () { + // not support cario0 address not deployed account + }); }); }); - describe('when account is not require upgrade', function () { + describe('when account is cairo 1', function () { let executeTxnResp; let executeTxnStub: sinon.SinonStub; beforeEach(async function () { + apiParams.requestParams = { + ...apiParams.requestParams, + senderAddress: Cario1Account1.address, + }; sandbox.stub(utils, 'isUpgradeRequired').resolves(false); sandbox.stub(estimateFeeSnap, 'estimateFee').resolves({ suggestedMaxFee: estimateFeeResp.suggestedMaxFee.toString(10), @@ -221,252 +263,178 @@ describe('Test function: sendTransaction', function () { executeTxnResp = sendTransactionResp; executeTxnStub = sandbox.stub(utils, 'executeTxn').resolves(executeTxnResp); walletStub.rpcStubs.snap_manageState.resolves(state); + walletStub.rpcStubs.snap_dialog.resolves(true); }); - it('should return false if user rejected to sign the transaction', async function () { - sandbox.stub(utils, 'isAccountAddressDeployed').resolves(true); - walletStub.rpcStubs.snap_dialog.resolves(false); - apiParams.requestParams = requestObject; - const result = await sendTransaction(apiParams); - expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; - expect(walletStub.rpcStubs.snap_manageState).not.to.have.been.called; - expect(result).to.be.eql(false); - }); - - it('should use heading, text and copyable component', async function () { - sandbox.stub(utils, 'isAccountAddressDeployed').resolves(true); - executeTxnResp = sendTransactionFailedResp; - const requestObject: SendTransactionRequestParams = { - contractAddress: account1.address, - contractFuncName: 'get_signer', - contractCallData: '**foo**', - senderAddress: account1.address, - }; - apiParams.requestParams = requestObject; - await sendTransaction(apiParams); - const expectedDialogParams = { - type: 'confirmation', - content: { - type: 'panel', - children: [ - { type: 'heading', value: 'Do you want to sign this transaction ?' }, - { - type: 'text', - value: `**Signer Address:**`, - }, - { - type: 'copyable', - value: '0x04882a372da3dfe1c53170ad75893832469bf87b62b13e84662565c4a88f25cd', - }, - { - type: 'text', - value: `**Contract:**`, - }, - { - type: 'copyable', - value: '0x04882a372da3dfe1c53170ad75893832469bf87b62b13e84662565c4a88f25cd', - }, - { - type: 'text', - value: `**Call Data:**`, - }, - { - type: 'copyable', - value: '[**foo**]', - }, - { - type: 'text', - value: `**Estimated Gas Fee(ETH):**`, - }, - { - type: 'copyable', - value: '0.000022702500105945', - }, - { - type: 'text', - value: `**Network:**`, - }, - { - type: 'copyable', - value: 'Goerli Testnet', - }, - ], - }, - }; - expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; - expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledWith(expectedDialogParams); - }); - - describe('when account is cairo 0', function () { - //TODO - }); - - describe('when account is cairo 1', function () { + describe('when account is deployed', function () { beforeEach(async function () { - walletStub.rpcStubs.snap_dialog.resolves(true); + sandbox.stub(utils, 'isAccountAddressDeployed').resolves(true); }); - describe('when account is deployed', function () { - beforeEach(async function () { - sandbox.stub(utils, 'isAccountAddressDeployed').resolves(true); - }); + it('should send a transaction for transferring 10 tokens correctly', async function () { + const result = await sendTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(walletStub.rpcStubs.snap_manageState).to.have.been.called; + expect(result).to.be.eql(sendTransactionResp); + }); - it('should send a transaction for transferring 10 tokens correctly', async function () { - const requestObject: SendTransactionRequestParams = { - contractAddress: '0x07394cbe418daa16e42b87ba67372d4ab4a5df0b05c6e554d158458ce245bc10', - contractFuncName: 'transfer', - contractCallData: - '0x0256d8f49882cc9366037415f48fa9fd2b5b7344ded7573ebfcef7c90e3e6b75,100000000000000000000,0', - senderAddress: account1.address, - }; - apiParams.requestParams = requestObject; - const result = await sendTransaction(apiParams); - expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; - expect(walletStub.rpcStubs.snap_manageState).to.have.been.called; - expect(result).to.be.eql(sendTransactionResp); - }); + it('should send a transaction for transferring 10 tokens but not update snap state if transaction_hash is missing from response', async function () { + executeTxnStub.restore(); + executeTxnStub = sandbox.stub(utils, 'executeTxn').resolves(sendTransactionFailedResp); + const result = await sendTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(walletStub.rpcStubs.snap_manageState).not.to.have.been.called; + expect(result).to.be.eql(sendTransactionFailedResp); + }); - it('should send a transaction for transferring 10 tokens but not update snap state if transaction_hash is missing from response', async function () { - executeTxnStub.restore(); - executeTxnStub = sandbox.stub(utils, 'executeTxn').resolves(sendTransactionFailedResp); - const requestObject: SendTransactionRequestParams = { - contractAddress: '0x07394cbe418daa16e42b87ba67372d4ab4a5df0b05c6e554d158458ce245bc10', - contractFuncName: 'transfer', - contractCallData: - '0x0256d8f49882cc9366037415f48fa9fd2b5b7344ded7573ebfcef7c90e3e6b75,100000000000000000000,0', - senderAddress: account1.address, - }; - apiParams.requestParams = requestObject; - const result = await sendTransaction(apiParams); - expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; - expect(walletStub.rpcStubs.snap_manageState).not.to.have.been.called; - expect(result).to.be.eql(sendTransactionFailedResp); - }); + it('should send a transaction with given max fee for transferring 10 tokens correctly', async function () { + const apiRequest = apiParams.requestParams as SendTransactionRequestParams; + apiRequest.maxFee = '15135825227039'; + const result = await sendTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(walletStub.rpcStubs.snap_manageState).to.have.been.called; + expect(result).to.be.eql(sendTransactionResp); + }); - it('should send a transaction with given max fee for transferring 10 tokens correctly', async function () { - const requestObject: SendTransactionRequestParams = { - contractAddress: '0x07394cbe418daa16e42b87ba67372d4ab4a5df0b05c6e554d158458ce245bc10', - contractFuncName: 'transfer', - contractCallData: - '0x0256d8f49882cc9366037415f48fa9fd2b5b7344ded7573ebfcef7c90e3e6b75,100000000000000000000,0', - senderAddress: account1.address, - maxFee: '15135825227039', - }; - apiParams.requestParams = requestObject; - const result = await sendTransaction(apiParams); - expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; - expect(walletStub.rpcStubs.snap_manageState).to.have.been.called; - expect(result).to.be.eql(sendTransactionResp); - }); + it('should send a transfer transaction for empty call data', async function () { + const apiRequest = apiParams.requestParams as SendTransactionRequestParams; + apiRequest.contractCallData = undefined; + const result = await sendTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(walletStub.rpcStubs.snap_manageState).to.have.been.called; + expect(result).to.be.eql(sendTransactionResp); + }); - it('should send a transfer transaction for empty call data', async function () { - const requestObject: SendTransactionRequestParams = { - contractAddress: '0x07394cbe418daa16e42b87ba67372d4ab4a5df0b05c6e554d158458ce245bc10', - contractFuncName: 'transfer', - contractCallData: undefined, - senderAddress: account1.address, - }; - apiParams.requestParams = requestObject; - const result = await sendTransaction(apiParams); - expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; - expect(walletStub.rpcStubs.snap_manageState).to.have.been.called; - expect(result).to.be.eql(sendTransactionResp); - }); + it('should send a transaction for empty call data', async function () { + const apiRequest = apiParams.requestParams as SendTransactionRequestParams; + apiRequest.contractCallData = undefined; + apiRequest.contractFuncName = 'get_signer'; + const result = await sendTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(walletStub.rpcStubs.snap_manageState).to.have.been.called; + expect(result).to.be.eql(sendTransactionResp); + }); - it('should send a transaction for empty call data', async function () { - const requestObject: SendTransactionRequestParams = { - contractAddress: account1.address, - contractFuncName: 'get_signer', - contractCallData: undefined, - senderAddress: account1.address, - }; - apiParams.requestParams = requestObject; - const result = await sendTransaction(apiParams); - expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; - expect(walletStub.rpcStubs.snap_manageState).to.have.been.called; - expect(result).to.be.eql(sendTransactionResp); - }); + it('should send a transaction for transferring 10 tokens from an unfound user correctly', async function () { + const apiRequest = apiParams.requestParams as SendTransactionRequestParams; + apiRequest.senderAddress = unfoundUserAddress; + const result = await sendTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(walletStub.rpcStubs.snap_manageState).to.have.been.called; + expect(result).to.be.eql(sendTransactionResp); + }); - it('should send a transaction for transferring 10 tokens from an unfound user correctly', async function () { - const requestObject: SendTransactionRequestParams = { - contractAddress: '0x07394cbe418daa16e42b87ba67372d4ab4a5df0b05c6e554d158458ce245bc10', - contractFuncName: 'transfer', - contractCallData: - '0x0256d8f49882cc9366037415f48fa9fd2b5b7344ded7573ebfcef7c90e3e6b75,100000000000000000000,0', - senderAddress: unfoundUserAddress, - }; - apiParams.requestParams = requestObject; - const result = await sendTransaction(apiParams); - expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; - expect(walletStub.rpcStubs.snap_manageState).to.have.been.called; - expect(result).to.be.eql(sendTransactionResp); - }); + it('should throw error if upsertTransaction failed', async function () { + sandbox.stub(snapUtils, 'upsertTransaction').throws(new Error()); + let result; + try { + await sendTransaction(apiParams); + } catch (err) { + result = err; + } finally { + expect(result).to.be.an('Error'); + } + }); - it('should send a transaction for transferring 10 tokens (token of 10 decimal places) from an unfound user correctly', async function () { - const requestObject: SendTransactionRequestParams = { - contractAddress: '0x06a09ccb1caaecf3d9683efe335a667b2169a409d19c589ba1eb771cd210af75', - contractFuncName: 'transfer', - contractCallData: - '0x0256d8f49882cc9366037415f48fa9fd2b5b7344ded7573ebfcef7c90e3e6b75,100000000000000000000,0', - senderAddress: unfoundUserAddress, - }; - apiParams.requestParams = requestObject; - const result = await sendTransaction(apiParams); - expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; - expect(walletStub.rpcStubs.snap_manageState).to.have.been.called; - expect(result).to.be.eql(sendTransactionResp); - }); + it('should return false if user rejected to sign the transaction', async function () { + walletStub.rpcStubs.snap_dialog.resolves(false); + const result = await sendTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(walletStub.rpcStubs.snap_manageState).not.to.have.been.called; + expect(result).to.be.eql(false); + }); - it('should throw error if upsertTransaction failed', async function () { - sandbox.stub(snapUtils, 'upsertTransaction').throws(new Error()); - const requestObject: SendTransactionRequestParams = { - contractAddress: '0x07394cbe418daa16e42b87ba67372d4ab4a5df0b05c6e554d158458ce245bc10', - contractFuncName: 'transfer', - contractCallData: - '0x0256d8f49882cc9366037415f48fa9fd2b5b7344ded7573ebfcef7c90e3e6b75,100000000000000000000,0', - senderAddress: unfoundUserAddress, - }; - apiParams.requestParams = requestObject; - - let result; - try { - await sendTransaction(apiParams); - } catch (err) { - result = err; - } finally { - expect(result).to.be.an('Error'); - } - }); + it('should use heading, text and copyable component', async function () { + executeTxnResp = sendTransactionFailedResp; + const requestObject: SendTransactionRequestParams = { + contractAddress: account1.address, + contractFuncName: 'get_signer', + contractCallData: '**foo**', + senderAddress: account1.address, + }; + apiParams.requestParams = requestObject; + await sendTransaction(apiParams); + const expectedDialogParams = { + type: 'confirmation', + content: { + type: 'panel', + children: [ + { type: 'heading', value: 'Do you want to sign this transaction ?' }, + { + type: 'text', + value: `**Signer Address:**`, + }, + { + type: 'copyable', + value: '0x04882a372da3dfe1c53170ad75893832469bf87b62b13e84662565c4a88f25cd', + }, + { + type: 'text', + value: `**Contract:**`, + }, + { + type: 'copyable', + value: '0x04882a372da3dfe1c53170ad75893832469bf87b62b13e84662565c4a88f25cd', + }, + { + type: 'text', + value: `**Call Data:**`, + }, + { + type: 'copyable', + value: '[**foo**]', + }, + { + type: 'text', + value: `**Estimated Gas Fee(ETH):**`, + }, + { + type: 'copyable', + value: '0.000022702500105945', + }, + { + type: 'text', + value: `**Network:**`, + }, + { + type: 'copyable', + value: 'Goerli Testnet', + }, + ], + }, + }; + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledWith(expectedDialogParams); }); + }); - describe('when account is not deployed', function () { - beforeEach(async function () { - sandbox.stub(utils, 'isAccountAddressDeployed').resolves(false); - }); + describe('when account is not deployed', function () { + beforeEach(async function () { + sandbox.stub(utils, 'isAccountAddressDeployed').resolves(false); + }); - it('should send a transaction for transferring 10 tokens and a transaction for deploy correctly', async function () { - sandbox.stub(utils, 'deployAccount').callsFake(async () => { - return createAccountProxyResp; - }); - sandbox.stub(utils, 'getBalance').callsFake(async () => { - return getBalanceResp[0]; - }); - sandbox.stub(utils, 'estimateAccountDeployFee').callsFake(async () => { - return estimateDeployFeeResp; - }); - const requestObject: SendTransactionRequestParams = { - contractAddress: '0x07394cbe418daa16e42b87ba67372d4ab4a5df0b05c6e554d158458ce245bc10', - contractFuncName: 'transfer', - contractCallData: - '0x0256d8f49882cc9366037415f48fa9fd2b5b7344ded7573ebfcef7c90e3e6b75,100000000000000000000,0', - senderAddress: account1.address, - }; - apiParams.requestParams = requestObject; - const result = await sendTransaction(apiParams); - expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; - expect(walletStub.rpcStubs.snap_manageState).to.have.been.called; - expect(result).to.be.eql(sendTransactionResp); + it('should send a transaction for transferring 10 tokens and a transaction for deploy correctly', async function () { + sandbox.stub(utils, 'deployAccount').callsFake(async () => { + return createAccountProxyResp; + }); + sandbox.stub(utils, 'getBalance').callsFake(async () => { + return getBalanceResp[0]; + }); + sandbox.stub(utils, 'estimateAccountDeployFee').callsFake(async () => { + return estimateDeployFeeResp; }); + const requestObject: SendTransactionRequestParams = { + contractAddress: '0x07394cbe418daa16e42b87ba67372d4ab4a5df0b05c6e554d158458ce245bc10', + contractFuncName: 'transfer', + contractCallData: + '0x0256d8f49882cc9366037415f48fa9fd2b5b7344ded7573ebfcef7c90e3e6b75,100000000000000000000,0', + senderAddress: account1.address, + }; + apiParams.requestParams = requestObject; + const result = await sendTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(walletStub.rpcStubs.snap_manageState).to.have.been.called; + expect(result).to.be.eql(sendTransactionResp); }); }); }); diff --git a/packages/starknet-snap/test/src/signDeclareTransaction.test.ts b/packages/starknet-snap/test/src/signDeclareTransaction.test.ts new file mode 100644 index 00000000..68d30b72 --- /dev/null +++ b/packages/starknet-snap/test/src/signDeclareTransaction.test.ts @@ -0,0 +1,112 @@ +import chai, { expect } from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import { WalletMock } from '../wallet.mock.test'; +import { signDeclareTransaction } from '../../src/signDeclareTransaction'; +import { SnapState } from '../../src/types/snapState'; +import { STARKNET_MAINNET_NETWORK, STARKNET_TESTNET_NETWORK } from '../../src/utils/constants'; +import { createAccountProxyTxn, getBip44EntropyStub, account1, signature3 } from '../constants.test'; +import { getAddressKeyDeriver } from '../../src/utils/keyPair'; +import { Mutex } from 'async-mutex'; +import { ApiParams, SignDeclareTransactionRequestParams } from '../../src/types/snapApi'; +import { constants } from 'starknet'; +import * as utils from '../../src/utils/starknetUtils'; + +chai.use(sinonChai); +const sandbox = sinon.createSandbox(); + +describe('Test function: signDeclareTransaction', function () { + this.timeout(10000); + const walletStub = new WalletMock(); + const state: SnapState = { + accContracts: [], + erc20Tokens: [], + networks: [STARKNET_MAINNET_NETWORK, STARKNET_TESTNET_NETWORK], + transactions: [], + }; + const apiParams: ApiParams = { + state, + requestParams: {}, + wallet: walletStub, + saveMutex: new Mutex(), + }; + + const requestObject: SignDeclareTransactionRequestParams = { + chainId: STARKNET_TESTNET_NETWORK.chainId, + signerAddress: account1.address, + transaction: { + classHash: '0x025ec026985a3bf9d0cc1fe17326b245dfdc3ff89b8fde106542a3ea56c5a918', + senderAddress: account1.address, + chainId: constants.StarknetChainId.SN_GOERLI, + nonce: '0x1', + version: '0x0', + maxFee: 100, + }, + enableAutherize: true, + }; + + beforeEach(async function () { + walletStub.rpcStubs.snap_getBip44Entropy.callsFake(getBip44EntropyStub); + apiParams.keyDeriver = await getAddressKeyDeriver(walletStub); + apiParams.requestParams = requestObject; + sandbox.useFakeTimers(createAccountProxyTxn.timestamp); + walletStub.rpcStubs.snap_dialog.resolves(true); + walletStub.rpcStubs.snap_manageState.resolves(state); + }); + + afterEach(function () { + walletStub.reset(); + sandbox.restore(); + apiParams.requestParams = requestObject; + }); + + it('should sign a transaction from an user account correctly', async function () { + sandbox.stub(utils, 'signDeclareTransaction').resolves(signature3); + const result = await signDeclareTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(result).to.be.eql(signature3); + }); + + it('should throw error if signDeclareTransaction fail', async function () { + sandbox.stub(utils, 'signDeclareTransaction').throws(new Error()); + let result; + try { + await signDeclareTransaction(apiParams); + } catch (err) { + result = err; + } finally { + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(result).to.be.an('Error'); + } + }); + + it('should return false if user deny to sign the transaction', async function () { + const stub = sandbox.stub(utils, 'signDeclareTransaction'); + walletStub.rpcStubs.snap_dialog.resolves(false); + + const result = await signDeclareTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(stub).to.have.been.callCount(0); + expect(result).to.be.eql(false); + }); + + it('should skip dialog if enableAutherize is false', async function () { + sandbox.stub(utils, 'signDeclareTransaction').resolves(signature3); + const paramsObject = apiParams.requestParams as SignDeclareTransactionRequestParams; + paramsObject.enableAutherize = false; + const result = await signDeclareTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.callCount(0); + expect(result).to.be.eql(signature3); + paramsObject.enableAutherize = true; + }); + + it('should skip dialog if enableAutherize is omit', async function () { + sandbox.stub(utils, 'signDeclareTransaction').resolves(signature3); + const paramsObject = apiParams.requestParams as SignDeclareTransactionRequestParams; + paramsObject.enableAutherize = undefined; + const result = await signDeclareTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.callCount(0); + expect(result).to.be.eql(signature3); + paramsObject.enableAutherize = true; + }); +}); diff --git a/packages/starknet-snap/test/src/signDeployAccountTransaction.test.ts b/packages/starknet-snap/test/src/signDeployAccountTransaction.test.ts new file mode 100644 index 00000000..88c6db54 --- /dev/null +++ b/packages/starknet-snap/test/src/signDeployAccountTransaction.test.ts @@ -0,0 +1,114 @@ +import chai, { expect } from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import { WalletMock } from '../wallet.mock.test'; +import { signDeployAccountTransaction } from '../../src/signDeployAccountTransaction'; +import { SnapState } from '../../src/types/snapState'; +import { STARKNET_MAINNET_NETWORK, STARKNET_TESTNET_NETWORK } from '../../src/utils/constants'; +import { createAccountProxyTxn, getBip44EntropyStub, account1, signature3 } from '../constants.test'; +import { getAddressKeyDeriver } from '../../src/utils/keyPair'; +import { Mutex } from 'async-mutex'; +import { ApiParams, SignDeployAccountTransactionRequestParams } from '../../src/types/snapApi'; +import { constants } from 'starknet'; +import * as utils from '../../src/utils/starknetUtils'; + +chai.use(sinonChai); +const sandbox = sinon.createSandbox(); + +describe('Test function: signDeployAccountTransaction', function () { + this.timeout(10000); + const walletStub = new WalletMock(); + const state: SnapState = { + accContracts: [], + erc20Tokens: [], + networks: [STARKNET_MAINNET_NETWORK, STARKNET_TESTNET_NETWORK], + transactions: [], + }; + const apiParams: ApiParams = { + state, + requestParams: {}, + wallet: walletStub, + saveMutex: new Mutex(), + }; + + const requestObject: SignDeployAccountTransactionRequestParams = { + chainId: STARKNET_TESTNET_NETWORK.chainId, + signerAddress: account1.address, + transaction: { + classHash: '0x025ec026985a3bf9d0cc1fe17326b245dfdc3ff89b8fde106542a3ea56c5a918', + contractAddress: account1.address, + constructorCalldata: [], + addressSalt: '0x025ec026985a3bf9d0cc1fe17326b245dfdc3ff89b8fde106542a3ea56c5a918', + chainId: constants.StarknetChainId.SN_MAIN, + nonce: '0x1', + version: '0x0', + maxFee: 100, + }, + enableAutherize: true, + }; + + beforeEach(async function () { + walletStub.rpcStubs.snap_getBip44Entropy.callsFake(getBip44EntropyStub); + apiParams.keyDeriver = await getAddressKeyDeriver(walletStub); + apiParams.requestParams = requestObject; + sandbox.useFakeTimers(createAccountProxyTxn.timestamp); + walletStub.rpcStubs.snap_dialog.resolves(true); + walletStub.rpcStubs.snap_manageState.resolves(state); + }); + + afterEach(function () { + walletStub.reset(); + sandbox.restore(); + apiParams.requestParams = requestObject; + }); + + it('should sign a transaction from an user account correctly', async function () { + sandbox.stub(utils, 'signDeployAccountTransaction').resolves(signature3); + const result = await signDeployAccountTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(result).to.be.eql(signature3); + }); + + it('should throw error if signDeployAccountTransaction fail', async function () { + sandbox.stub(utils, 'signDeployAccountTransaction').throws(new Error()); + let result; + try { + await signDeployAccountTransaction(apiParams); + } catch (err) { + result = err; + } finally { + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(result).to.be.an('Error'); + } + }); + + it('should return false if user deny to sign the transaction', async function () { + const stub = sandbox.stub(utils, 'signDeployAccountTransaction'); + walletStub.rpcStubs.snap_dialog.resolves(false); + + const result = await signDeployAccountTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(stub).to.have.been.callCount(0); + expect(result).to.be.eql(false); + }); + + it('should skip dialog if enableAutherize is false', async function () { + sandbox.stub(utils, 'signDeployAccountTransaction').resolves(signature3); + const paramsObject = apiParams.requestParams as SignDeployAccountTransactionRequestParams; + paramsObject.enableAutherize = false; + const result = await signDeployAccountTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.callCount(0); + expect(result).to.be.eql(signature3); + paramsObject.enableAutherize = true; + }); + + it('should skip dialog if enableAutherize is omit', async function () { + sandbox.stub(utils, 'signDeployAccountTransaction').resolves(signature3); + const paramsObject = apiParams.requestParams as SignDeployAccountTransactionRequestParams; + paramsObject.enableAutherize = undefined; + const result = await signDeployAccountTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.callCount(0); + expect(result).to.be.eql(signature3); + paramsObject.enableAutherize = true; + }); +}); diff --git a/packages/starknet-snap/test/src/signMessage.test.ts b/packages/starknet-snap/test/src/signMessage.test.ts index b3d07835..2174004f 100644 --- a/packages/starknet-snap/test/src/signMessage.test.ts +++ b/packages/starknet-snap/test/src/signMessage.test.ts @@ -1,4 +1,3 @@ -import { toJson } from '../../src/utils/serializer'; import chai, { expect } from 'chai'; import sinon from 'sinon'; import sinonChai from 'sinon-chai'; @@ -7,7 +6,15 @@ import { SnapState } from '../../src/types/snapState'; import { signMessage } from '../../src/signMessage'; import typedDataExample from '../../src/typedData/typedDataExample.json'; import { STARKNET_TESTNET_NETWORK } from '../../src/utils/constants'; -import { account1, getBip44EntropyStub, signature1, signature2, unfoundUserAddress } from '../constants.test'; +import { + account1, + Cario1Account1, + getBip44EntropyStub, + signature4SignMessageWithUnfoundAddress, + unfoundUserAddress, + signature4SignMessage, + signature4Cario1SignMessage, +} from '../constants.test'; import { getAddressKeyDeriver } from '../../src/utils/keyPair'; import * as utils from '../../src/utils/starknetUtils'; import { Mutex } from 'async-mutex'; @@ -20,7 +27,7 @@ describe('Test function: signMessage', function () { this.timeout(5000); const walletStub = new WalletMock(); const state: SnapState = { - accContracts: [account1], + accContracts: [], erc20Tokens: [], networks: [STARKNET_TESTNET_NETWORK], transactions: [], @@ -33,13 +40,16 @@ describe('Test function: signMessage', function () { }; const requestObject: SignMessageRequestParams = { + chainId: STARKNET_TESTNET_NETWORK.chainId, signerAddress: account1.address, - typedDataMessage: toJson(typedDataExample), + typedDataMessage: typedDataExample, + enableAutherize: true, }; beforeEach(async function () { walletStub.rpcStubs.snap_getBip44Entropy.callsFake(getBip44EntropyStub); apiParams.keyDeriver = await getAddressKeyDeriver(walletStub); + apiParams.requestParams = requestObject; walletStub.rpcStubs.snap_dialog.resolves(true); }); @@ -92,134 +102,116 @@ describe('Test function: signMessage', function () { apiParams.requestParams = Object.assign({}, requestObject); }); - describe('when require upgrade checking fail', function () { - it('should throw error', async function () { - const isUpgradeRequiredStub = sandbox.stub(utils, 'isUpgradeRequired').throws('network error'); - let result; - try { - result = await signMessage(apiParams); - } catch (err) { - result = err; - } finally { - expect(isUpgradeRequiredStub).to.have.been.calledOnceWith(STARKNET_TESTNET_NETWORK, account1.address); - expect(result).to.be.an('Error'); - } + describe('when account is cairo 0', function () { + describe('when require upgrade checking fail', function () { + it('should throw error', async function () { + const isUpgradeRequiredStub = sandbox.stub(utils, 'isUpgradeRequired').throws('network error'); + let result; + try { + result = await signMessage(apiParams); + } catch (err) { + result = err; + } finally { + expect(isUpgradeRequiredStub).to.have.been.calledOnceWith(STARKNET_TESTNET_NETWORK, account1.address); + expect(result).to.be.an('Error'); + } + }); + }); + + describe('when account require upgrade', function () { + let isUpgradeRequiredStub: sinon.SinonStub; + beforeEach(async function () { + isUpgradeRequiredStub = sandbox.stub(utils, 'isUpgradeRequired').resolves(true); + }); + + it('should throw error if upgrade required', async function () { + let result; + try { + result = await signMessage(apiParams); + } catch (err) { + result = err; + } finally { + expect(isUpgradeRequiredStub).to.have.been.calledOnceWith(STARKNET_TESTNET_NETWORK, account1.address); + expect(result).to.be.an('Error'); + } + }); + }); + + describe('when account is not require upgrade', function () { + beforeEach(async function () { + sandbox.stub(utils, 'isUpgradeRequired').resolves(false); + }); + + it('should sign a message from an user account correctly', async function () { + const result = await signMessage(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(walletStub.rpcStubs.snap_manageState).not.to.have.been.called; + expect(result).to.be.eql(signature4SignMessage); + }); }); }); - describe('when account require upgrade', function () { - let isUpgradeRequiredStub: sinon.SinonStub; + describe('when account is cairo 1', function () { beforeEach(async function () { - isUpgradeRequiredStub = sandbox.stub(utils, 'isUpgradeRequired').resolves(true); + apiParams.requestParams = { + ...apiParams.requestParams, + signerAddress: Cario1Account1.address, + }; + sandbox.stub(utils, 'isUpgradeRequired').resolves(false); + }); + + it('should sign a message from an user account correctly', async function () { + const result = await signMessage(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(walletStub.rpcStubs.snap_manageState).not.to.have.been.called; + expect(result).to.be.eql(signature4Cario1SignMessage); + }); + + it('should sign a message from an unfound user account correctly', async function () { + const paramsObject = apiParams.requestParams as SignMessageRequestParams; + paramsObject.signerAddress = unfoundUserAddress; + const result = await signMessage(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(walletStub.rpcStubs.snap_manageState).not.to.have.been.called; + expect(result).to.be.eql(signature4SignMessageWithUnfoundAddress); }); - it('should throw error if upgrade required', async function () { + it('should throw error if getKeysFromAddress failed', async function () { + sandbox.stub(utils, 'getKeysFromAddress').throws(new Error()); let result; try { - result = await signMessage(apiParams); + await signMessage(apiParams); } catch (err) { result = err; } finally { - expect(isUpgradeRequiredStub).to.have.been.calledOnceWith(STARKNET_TESTNET_NETWORK, account1.address); expect(result).to.be.an('Error'); } - }); - }); - - describe('when account is not require upgrade', function () { - beforeEach(async function () { - sandbox.stub(utils, 'isUpgradeRequired').resolves(false); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(walletStub.rpcStubs.snap_manageState).not.to.have.been.called; }); it('should return false if the user not confirmed', async function () { walletStub.rpcStubs.snap_dialog.resolves(false); - const requestObject: SignMessageRequestParams = { - signerAddress: account1.address, - typedDataMessage: undefined, // will use typedDataExample.json - }; - apiParams.requestParams = requestObject; const result = await signMessage(apiParams); expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; expect(walletStub.rpcStubs.snap_manageState).not.to.have.been.called; expect(result).to.be.eql(false); }); + }); - describe('when account is cairo 0', function () { - //TODO - }); + it('should skip dialog if enableAutherize is false or omit', async function () { + sandbox.stub(utils, 'isUpgradeRequired').resolves(false); + const paramsObject = apiParams.requestParams as SignMessageRequestParams; - describe('when account is cairo 1', function () { - it('should sign a message from an user account correctly', async function () { - const requestObject: SignMessageRequestParams = { - signerAddress: account1.address, - typedDataMessage: undefined, // will use typedDataExample.json - }; - apiParams.requestParams = requestObject; - const result: boolean | string = await signMessage(apiParams); - const expectedDialogParams = { - type: 'confirmation', - content: { - type: 'panel', - children: [ - { type: 'heading', value: 'Do you want to sign this message ?' }, - - { - type: 'text', - value: `**Message:**`, - }, - { - type: 'copyable', - value: toJson(typedDataExample), - }, - { - type: 'text', - value: `**Signer address:**`, - }, - { - type: 'copyable', - value: `${account1.address}`, - }, - ], - }, - }; - expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; - expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledWith(expectedDialogParams); - expect(walletStub.rpcStubs.snap_manageState).not.to.have.been.called; - expect(result).to.be.eql(signature1); - }); - - it('should sign a message from an unfound user account correctly', async function () { - const requestObject: SignMessageRequestParams = { - signerAddress: unfoundUserAddress, - typedDataMessage: toJson(typedDataExample), - }; - apiParams.requestParams = requestObject; - const result = await signMessage(apiParams); - expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; - expect(walletStub.rpcStubs.snap_manageState).not.to.have.been.called; - expect(result).to.be.eql(signature2); - }); + paramsObject.enableAutherize = false; + await signMessage(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.callCount(0); - it('should throw error if getKeysFromAddress failed', async function () { - sandbox.stub(utils, 'getKeysFromAddress').throws(new Error()); - const requestObject: SignMessageRequestParams = { - signerAddress: account1.address, - typedDataMessage: undefined, // will use typedDataExample.json - }; - apiParams.requestParams = requestObject; + paramsObject.enableAutherize = undefined; + await signMessage(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.callCount(0); - let result; - try { - await signMessage(apiParams); - } catch (err) { - result = err; - } finally { - expect(result).to.be.an('Error'); - } - expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; - expect(walletStub.rpcStubs.snap_manageState).not.to.have.been.called; - }); - }); + paramsObject.enableAutherize = true; }); }); }); diff --git a/packages/starknet-snap/test/src/signTransaction.test.ts b/packages/starknet-snap/test/src/signTransaction.test.ts new file mode 100644 index 00000000..f978b53d --- /dev/null +++ b/packages/starknet-snap/test/src/signTransaction.test.ts @@ -0,0 +1,121 @@ +import chai, { expect } from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import { WalletMock } from '../wallet.mock.test'; +import { signTransaction } from '../../src/signTransaction'; +import { SnapState } from '../../src/types/snapState'; +import { STARKNET_MAINNET_NETWORK, STARKNET_TESTNET_NETWORK } from '../../src/utils/constants'; +import { createAccountProxyTxn, getBip44EntropyStub, account1, signature3 } from '../constants.test'; +import { getAddressKeyDeriver } from '../../src/utils/keyPair'; +import { Mutex } from 'async-mutex'; +import { ApiParams, SignTransactionRequestParams } from '../../src/types/snapApi'; +import { constants } from 'starknet'; +import * as utils from '../../src/utils/starknetUtils'; + +chai.use(sinonChai); +const sandbox = sinon.createSandbox(); + +describe('Test function: signMessage', function () { + this.timeout(10000); + const walletStub = new WalletMock(); + const state: SnapState = { + accContracts: [], + erc20Tokens: [], + networks: [STARKNET_MAINNET_NETWORK, STARKNET_TESTNET_NETWORK], + transactions: [], + }; + const apiParams: ApiParams = { + state, + requestParams: {}, + wallet: walletStub, + saveMutex: new Mutex(), + }; + + const requestObject: SignTransactionRequestParams = { + chainId: STARKNET_TESTNET_NETWORK.chainId, + signerAddress: account1.address, + transactions: [ + { + entrypoint: 'transfer', + calldata: ['0', '0', '0'], + contractAddress: createAccountProxyTxn.contractAddress, + }, + { + entrypoint: 'transfer2', + calldata: ['0', '0', '0'], + contractAddress: createAccountProxyTxn.contractAddress, + }, + ], + transactionsDetail: { + walletAddress: '0x00b28a089e7fb83debee4607b6334d687918644796b47d9e9e38ea8213833137', + chainId: constants.StarknetChainId.SN_MAIN, + cairoVersion: '0', + nonce: '0x1', + version: '0x0', + maxFee: 100, + }, + enableAutherize: true, + }; + + beforeEach(async function () { + walletStub.rpcStubs.snap_getBip44Entropy.callsFake(getBip44EntropyStub); + apiParams.keyDeriver = await getAddressKeyDeriver(walletStub); + apiParams.requestParams = requestObject; + sandbox.useFakeTimers(createAccountProxyTxn.timestamp); + walletStub.rpcStubs.snap_dialog.resolves(true); + walletStub.rpcStubs.snap_manageState.resolves(state); + }); + + afterEach(function () { + walletStub.reset(); + sandbox.restore(); + apiParams.requestParams = requestObject; + }); + + it('should sign a transaction from an user account correctly', async function () { + const result = await signTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(result).to.be.eql(signature3); + }); + + it('should throw error if signTransaction fail', async function () { + sandbox.stub(utils, 'signTransactions').throws(new Error()); + let result; + try { + await signTransaction(apiParams); + } catch (err) { + result = err; + } finally { + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(result).to.be.an('Error'); + } + }); + + it('should return false if user deny to sign the transaction', async function () { + const stub = sandbox.stub(utils, 'signTransactions'); + walletStub.rpcStubs.snap_dialog.resolves(false); + + const result = await signTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.calledOnce; + expect(stub).to.have.been.callCount(0); + expect(result).to.be.eql(false); + }); + + it('should skip dialog if enableAutherize is false', async function () { + const paramsObject = apiParams.requestParams as SignTransactionRequestParams; + paramsObject.enableAutherize = false; + const result = await signTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.callCount(0); + expect(result).to.be.eql(signature3); + paramsObject.enableAutherize = true; + }); + + it('should skip dialog if enableAutherize is omit', async function () { + const paramsObject = apiParams.requestParams as SignTransactionRequestParams; + paramsObject.enableAutherize = undefined; + const result = await signTransaction(apiParams); + expect(walletStub.rpcStubs.snap_dialog).to.have.been.callCount(0); + expect(result).to.be.eql(signature3); + paramsObject.enableAutherize = true; + }); +}); diff --git a/packages/starknet-snap/test/src/switchNetwork.test.ts b/packages/starknet-snap/test/src/switchNetwork.test.ts new file mode 100644 index 00000000..e8cc4573 --- /dev/null +++ b/packages/starknet-snap/test/src/switchNetwork.test.ts @@ -0,0 +1,92 @@ +import chai, { expect } from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import { WalletMock } from '../wallet.mock.test'; +import { SnapState } from '../../src/types/snapState'; +import * as snapUtils from '../../src/utils/snapUtils'; +import { STARKNET_MAINNET_NETWORK, STARKNET_TESTNET_NETWORK } from '../../src/utils/constants'; +import { Mutex } from 'async-mutex'; +import { SwitchNetworkRequestParams, ApiParams } from '../../src/types/snapApi'; +import { switchNetwork } from '../../src/switchNetwork'; + +chai.use(sinonChai); +const sandbox = sinon.createSandbox(); + +describe('Test function: switchNetwork', function () { + const walletStub = new WalletMock(); + const state: SnapState = { + accContracts: [], + erc20Tokens: [], + networks: [STARKNET_TESTNET_NETWORK, STARKNET_MAINNET_NETWORK], + transactions: [], + currentNetwork: STARKNET_TESTNET_NETWORK, + }; + const apiParams: ApiParams = { + state, + requestParams: {}, + wallet: walletStub, + saveMutex: new Mutex(), + }; + let stateStub: sinon.SinonStub; + let dialogStub: sinon.SinonStub; + beforeEach(function () { + stateStub = walletStub.rpcStubs.snap_manageState; + dialogStub = walletStub.rpcStubs.snap_dialog; + stateStub.resolves(state); + dialogStub.resolves(true); + }); + + afterEach(function () { + walletStub.reset(); + sandbox.restore(); + }); + + it('should switch the network correctly', async function () { + const requestObject: SwitchNetworkRequestParams = { + chainId: STARKNET_MAINNET_NETWORK.chainId, + }; + apiParams.requestParams = requestObject; + const result = await switchNetwork(apiParams); + expect(result).to.be.eql(true); + expect(stateStub).to.be.calledOnce; + expect(dialogStub).to.be.calledOnce; + expect(state.currentNetwork).to.be.eql(STARKNET_MAINNET_NETWORK); + expect(state.networks.length).to.be.eql(2); + }); + + it('should throw an error if network not found', async function () { + const requestObject: SwitchNetworkRequestParams = { + chainId: '123', + }; + apiParams.requestParams = requestObject; + let result; + try { + await switchNetwork(apiParams); + } catch (err) { + result = err; + } finally { + expect(result).to.be.an('Error'); + expect(stateStub).to.be.callCount(0); + expect(dialogStub).to.be.callCount(0); + expect(state.currentNetwork).to.be.eql(STARKNET_MAINNET_NETWORK); + } + }); + + it('should throw an error if setCurrentNetwork failed', async function () { + sandbox.stub(snapUtils, 'setCurrentNetwork').throws(new Error()); + const requestObject: SwitchNetworkRequestParams = { + chainId: STARKNET_TESTNET_NETWORK.chainId, + }; + apiParams.requestParams = requestObject; + let result; + try { + await switchNetwork(apiParams); + } catch (err) { + result = err; + } finally { + expect(result).to.be.an('Error'); + expect(dialogStub).to.be.callCount(1); + expect(state.currentNetwork).to.be.eql(STARKNET_MAINNET_NETWORK); + } + }); +}); diff --git a/packages/starknet-snap/test/utils/starknetUtils.test.ts b/packages/starknet-snap/test/utils/starknetUtils.test.ts index 5c50fe51..4835126d 100644 --- a/packages/starknet-snap/test/utils/starknetUtils.test.ts +++ b/packages/starknet-snap/test/utils/starknetUtils.test.ts @@ -253,14 +253,8 @@ describe('Test function: getCorrectContractAddress', function () { it('should permutation both Cairo0 and Cario1 address', async function () { await utils.getCorrectContractAddress(STARKNET_TESTNET_NETWORK, PK); - expect(getAccContractAddressAndCallDataStub).to.have.been.calledOnceWith( - STARKNET_TESTNET_NETWORK.accountClassHash, - PK, - ); - expect(getAccContractAddressAndCallDataCairo0Stub).to.have.been.calledOnceWith( - STARKNET_TESTNET_NETWORK.accountClassHashV0, - PK, - ); + expect(getAccContractAddressAndCallDataStub).to.have.been.calledOnceWith(PK); + expect(getAccContractAddressAndCallDataCairo0Stub).to.have.been.calledOnceWith(PK); }); it('should return Cairo1 address with pubic key when Cario1 deployed', async function () { diff --git a/packages/starknet-snap/test/utils/transaction/filter.test.ts b/packages/starknet-snap/test/utils/transaction/filter.test.ts index fd207fcc..a05fe3c5 100644 --- a/packages/starknet-snap/test/utils/transaction/filter.test.ts +++ b/packages/starknet-snap/test/utils/transaction/filter.test.ts @@ -75,7 +75,7 @@ describe('Test function: getTransactions', function () { it('Should filter transactions based on chainId', () => { const orgChainId = transactions[0].chainId; transactions[0].chainId = '99'; - const chainIdFilter = new filter.ChainIdFilter(Number(transactions[0].chainId)); + const chainIdFilter = new filter.ChainIdFilter(transactions[0].chainId); const filteredTxnList = filter.filterTransactions(transactions, [chainIdFilter]); expect(filteredTxnList).to.have.lengthOf(1); transactions[0].chainId = orgChainId; diff --git a/packages/wallet-ui/package.json b/packages/wallet-ui/package.json index 03f7cd49..e7867cb5 100644 --- a/packages/wallet-ui/package.json +++ b/packages/wallet-ui/package.json @@ -62,7 +62,7 @@ "react-scripts": "5.0.1", "rimraf": "^3.0.2", "typescript": "^4.7.4", - "webpack": "5" + "webpack": "^5.76.0" }, "resolutions": { "@storybook/{app}/webpack": "^4", diff --git a/yarn.lock b/yarn.lock index c96d3337..aa94bbcb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -44,6 +44,16 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.22.13": + version: 7.22.13 + resolution: "@babel/code-frame@npm:7.22.13" + dependencies: + "@babel/highlight": ^7.22.13 + chalk: ^2.4.2 + checksum: 22e342c8077c8b77eeb11f554ecca2ba14153f707b85294fcf6070b6f6150aae88a7b7436dd88d8c9289970585f3fe5b9b941c5aa3aa26a6d5a8ef3f292da058 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.17.7, @babel/compat-data@npm:^7.20.1, @babel/compat-data@npm:^7.20.5": version: 7.21.0 resolution: "@babel/compat-data@npm:7.21.0" @@ -124,6 +134,18 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.23.0": + version: 7.23.0 + resolution: "@babel/generator@npm:7.23.0" + dependencies: + "@babel/types": ^7.23.0 + "@jridgewell/gen-mapping": ^0.3.2 + "@jridgewell/trace-mapping": ^0.3.17 + jsesc: ^2.5.1 + checksum: 8efe24adad34300f1f8ea2add420b28171a646edc70f2a1b3e1683842f23b8b7ffa7e35ef0119294e1901f45bfea5b3dc70abe1f10a1917ccdfb41bed69be5f1 + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.16.0, @babel/helper-annotate-as-pure@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-annotate-as-pure@npm:7.18.6" @@ -229,6 +251,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-environment-visitor@npm:^7.22.20": + version: 7.22.20 + resolution: "@babel/helper-environment-visitor@npm:7.22.20" + checksum: d80ee98ff66f41e233f36ca1921774c37e88a803b2f7dca3db7c057a5fea0473804db9fb6729e5dbfd07f4bed722d60f7852035c2c739382e84c335661590b69 + languageName: node + linkType: hard + "@babel/helper-explode-assignable-expression@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-explode-assignable-expression@npm:7.18.6" @@ -248,6 +277,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-function-name@npm:^7.23.0": + version: 7.23.0 + resolution: "@babel/helper-function-name@npm:7.23.0" + dependencies: + "@babel/template": ^7.22.15 + "@babel/types": ^7.23.0 + checksum: e44542257b2d4634a1f979244eb2a4ad8e6d75eb6761b4cfceb56b562f7db150d134bc538c8e6adca3783e3bc31be949071527aa8e3aab7867d1ad2d84a26e10 + languageName: node + linkType: hard + "@babel/helper-hoist-variables@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-hoist-variables@npm:7.18.6" @@ -257,6 +296,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-hoist-variables@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-hoist-variables@npm:7.22.5" + dependencies: + "@babel/types": ^7.22.5 + checksum: 394ca191b4ac908a76e7c50ab52102669efe3a1c277033e49467913c7ed6f7c64d7eacbeabf3bed39ea1f41731e22993f763b1edce0f74ff8563fd1f380d92cc + languageName: node + linkType: hard + "@babel/helper-member-expression-to-functions@npm:^7.20.7, @babel/helper-member-expression-to-functions@npm:^7.21.0": version: 7.21.0 resolution: "@babel/helper-member-expression-to-functions@npm:7.21.0" @@ -369,6 +417,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-split-export-declaration@npm:^7.22.6": + version: 7.22.6 + resolution: "@babel/helper-split-export-declaration@npm:7.22.6" + dependencies: + "@babel/types": ^7.22.5 + checksum: e141cace583b19d9195f9c2b8e17a3ae913b7ee9b8120246d0f9ca349ca6f03cb2c001fd5ec57488c544347c0bb584afec66c936511e447fd20a360e591ac921 + languageName: node + linkType: hard + "@babel/helper-string-parser@npm:^7.19.4": version: 7.19.4 resolution: "@babel/helper-string-parser@npm:7.19.4" @@ -376,6 +433,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-string-parser@npm:7.22.5" + checksum: 836851ca5ec813077bbb303acc992d75a360267aa3b5de7134d220411c852a6f17de7c0d0b8c8dcc0f567f67874c00f4528672b2a4f1bc978a3ada64c8c78467 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.18.6, @babel/helper-validator-identifier@npm:^7.19.1": version: 7.19.1 resolution: "@babel/helper-validator-identifier@npm:7.19.1" @@ -383,6 +447,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.22.20": + version: 7.22.20 + resolution: "@babel/helper-validator-identifier@npm:7.22.20" + checksum: 136412784d9428266bcdd4d91c32bcf9ff0e8d25534a9d94b044f77fe76bc50f941a90319b05aafd1ec04f7d127cd57a179a3716009ff7f3412ef835ada95bdc + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.18.6, @babel/helper-validator-option@npm:^7.21.0": version: 7.21.0 resolution: "@babel/helper-validator-option@npm:7.21.0" @@ -424,6 +495,17 @@ __metadata: languageName: node linkType: hard +"@babel/highlight@npm:^7.22.13": + version: 7.22.20 + resolution: "@babel/highlight@npm:7.22.20" + dependencies: + "@babel/helper-validator-identifier": ^7.22.20 + chalk: ^2.4.2 + js-tokens: ^4.0.0 + checksum: 84bd034dca309a5e680083cd827a766780ca63cef37308404f17653d32366ea76262bd2364b2d38776232f2d01b649f26721417d507e8b4b6da3e4e739f6d134 + languageName: node + linkType: hard + "@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.12.11, @babel/parser@npm:^7.12.7, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.21.3": version: 7.21.3 resolution: "@babel/parser@npm:7.21.3" @@ -433,6 +515,15 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.22.15, @babel/parser@npm:^7.23.0": + version: 7.23.0 + resolution: "@babel/parser@npm:7.23.0" + bin: + parser: ./bin/babel-parser.js + checksum: 453fdf8b9e2c2b7d7b02139e0ce003d1af21947bbc03eb350fb248ee335c9b85e4ab41697ddbdd97079698de825a265e45a0846bb2ed47a2c7c1df833f42a354 + languageName: node + linkType: hard + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.18.6" @@ -1622,21 +1713,32 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.12.11, @babel/traverse@npm:^7.12.9, @babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.20.5, @babel/traverse@npm:^7.20.7, @babel/traverse@npm:^7.21.0, @babel/traverse@npm:^7.21.2, @babel/traverse@npm:^7.21.3, @babel/traverse@npm:^7.4.5, @babel/traverse@npm:^7.7.2": - version: 7.21.3 - resolution: "@babel/traverse@npm:7.21.3" +"@babel/template@npm:^7.22.15": + version: 7.22.15 + resolution: "@babel/template@npm:7.22.15" dependencies: - "@babel/code-frame": ^7.18.6 - "@babel/generator": ^7.21.3 - "@babel/helper-environment-visitor": ^7.18.9 - "@babel/helper-function-name": ^7.21.0 - "@babel/helper-hoist-variables": ^7.18.6 - "@babel/helper-split-export-declaration": ^7.18.6 - "@babel/parser": ^7.21.3 - "@babel/types": ^7.21.3 + "@babel/code-frame": ^7.22.13 + "@babel/parser": ^7.22.15 + "@babel/types": ^7.22.15 + checksum: 1f3e7dcd6c44f5904c184b3f7fe280394b191f2fed819919ffa1e529c259d5b197da8981b6ca491c235aee8dbad4a50b7e31304aa531271cb823a4a24a0dd8fd + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.12.11, @babel/traverse@npm:^7.12.9, @babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.20.5, @babel/traverse@npm:^7.20.7, @babel/traverse@npm:^7.21.0, @babel/traverse@npm:^7.21.2, @babel/traverse@npm:^7.21.3, @babel/traverse@npm:^7.4.5, @babel/traverse@npm:^7.7.2": + version: 7.23.2 + resolution: "@babel/traverse@npm:7.23.2" + dependencies: + "@babel/code-frame": ^7.22.13 + "@babel/generator": ^7.23.0 + "@babel/helper-environment-visitor": ^7.22.20 + "@babel/helper-function-name": ^7.23.0 + "@babel/helper-hoist-variables": ^7.22.5 + "@babel/helper-split-export-declaration": ^7.22.6 + "@babel/parser": ^7.23.0 + "@babel/types": ^7.23.0 debug: ^4.1.0 globals: ^11.1.0 - checksum: 0af5bcd47a2fc501592b90ac1feae9d449afb9ab0772a4f6e68230f4cd3a475795d538c1de3f880fe3414b6c2820bac84d02c6549eea796f39d74a603717447b + checksum: 26a1eea0dde41ab99dde8b9773a013a0dc50324e5110a049f5d634e721ff08afffd54940b3974a20308d7952085ac769689369e9127dea655f868c0f6e1ab35d languageName: node linkType: hard @@ -1651,6 +1753,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.22.15, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0": + version: 7.23.0 + resolution: "@babel/types@npm:7.23.0" + dependencies: + "@babel/helper-string-parser": ^7.22.5 + "@babel/helper-validator-identifier": ^7.22.20 + to-fast-properties: ^2.0.0 + checksum: 215fe04bd7feef79eeb4d33374b39909ce9cad1611c4135a4f7fdf41fe3280594105af6d7094354751514625ea92d0875aba355f53e86a92600f290e77b0e604 + languageName: node + linkType: hard + "@babel/types@npm:^7.8.3": version: 7.21.4 resolution: "@babel/types@npm:7.21.4" @@ -1921,14 +2034,14 @@ __metadata: "@consensys/starknet-snap@file:../starknet-snap::locator=wallet-ui%40workspace%3Apackages%2Fwallet-ui": version: 2.2.0 - resolution: "@consensys/starknet-snap@file:../starknet-snap#../starknet-snap::hash=f33260&locator=wallet-ui%40workspace%3Apackages%2Fwallet-ui" + resolution: "@consensys/starknet-snap@file:../starknet-snap#../starknet-snap::hash=09b51d&locator=wallet-ui%40workspace%3Apackages%2Fwallet-ui" dependencies: async-mutex: ^0.3.2 ethereum-unit-converter: ^0.0.17 ethers: ^5.5.1 starknet: ^5.14.0 starknet_v4.22.0: "npm:starknet@4.22.0" - checksum: 6457a8423d6468621a5550c2235517929c6d23012b1574ed6655a5224cdd9bf50328279079fdd66cc2b4fcdd3a017314a4429758a9051249fc2780800536d8f8 + checksum: fee364b7c342fedbbeda32f0d35fc90a826ad42d2948e329f5893ab03e25536b57dd15a342428bda8e628a4416f01dc0102d63450056f0a4799e700279d34fec languageName: node linkType: hard @@ -3374,6 +3487,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/source-map@npm:^0.3.3": + version: 0.3.5 + resolution: "@jridgewell/source-map@npm:0.3.5" + dependencies: + "@jridgewell/gen-mapping": ^0.3.0 + "@jridgewell/trace-mapping": ^0.3.9 + checksum: 1ad4dec0bdafbade57920a50acec6634f88a0eb735851e0dda906fa9894e7f0549c492678aad1a10f8e144bfe87f238307bf2a914a1bc85b7781d345417e9f6f + languageName: node + linkType: hard + "@jridgewell/sourcemap-codec@npm:1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.10": version: 1.4.14 resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" @@ -6104,6 +6227,13 @@ __metadata: languageName: node linkType: hard +"@types/estree@npm:^1.0.0": + version: 1.0.4 + resolution: "@types/estree@npm:1.0.4" + checksum: dcd08e6e967def3afff745774b6b9b912d6394ddacbb3e8be05bb291c1803f5f03f1ab0eeb852bf8a85ca14842663f461f3dac82179dcdccbf45fbc067673bbc + languageName: node + linkType: hard + "@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.33": version: 4.17.33 resolution: "@types/express-serve-static-core@npm:4.17.33" @@ -6860,6 +6990,16 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/ast@npm:1.11.6, @webassemblyjs/ast@npm:^1.11.5": + version: 1.11.6 + resolution: "@webassemblyjs/ast@npm:1.11.6" + dependencies: + "@webassemblyjs/helper-numbers": 1.11.6 + "@webassemblyjs/helper-wasm-bytecode": 1.11.6 + checksum: 38ef1b526ca47c210f30975b06df2faf1a8170b1636ce239fc5738fc231ce28389dd61ecedd1bacfc03cbe95b16d1af848c805652080cb60982836eb4ed2c6cf + languageName: node + linkType: hard + "@webassemblyjs/ast@npm:1.9.0": version: 1.9.0 resolution: "@webassemblyjs/ast@npm:1.9.0" @@ -6878,6 +7018,13 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/floating-point-hex-parser@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.11.6" + checksum: 29b08758841fd8b299c7152eda36b9eb4921e9c584eb4594437b5cd90ed6b920523606eae7316175f89c20628da14326801090167cc7fbffc77af448ac84b7e2 + languageName: node + linkType: hard + "@webassemblyjs/floating-point-hex-parser@npm:1.9.0": version: 1.9.0 resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.9.0" @@ -6892,6 +7039,13 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/helper-api-error@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/helper-api-error@npm:1.11.6" + checksum: e8563df85161096343008f9161adb138a6e8f3c2cc338d6a36011aa55eabb32f2fd138ffe63bc278d009ada001cc41d263dadd1c0be01be6c2ed99076103689f + languageName: node + linkType: hard + "@webassemblyjs/helper-api-error@npm:1.9.0": version: 1.9.0 resolution: "@webassemblyjs/helper-api-error@npm:1.9.0" @@ -6906,6 +7060,13 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/helper-buffer@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/helper-buffer@npm:1.11.6" + checksum: b14d0573bf680d22b2522e8a341ec451fddd645d1f9c6bd9012ccb7e587a2973b86ab7b89fe91e1c79939ba96095f503af04369a3b356c8023c13a5893221644 + languageName: node + linkType: hard + "@webassemblyjs/helper-buffer@npm:1.9.0": version: 1.9.0 resolution: "@webassemblyjs/helper-buffer@npm:1.9.0" @@ -6949,6 +7110,17 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/helper-numbers@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/helper-numbers@npm:1.11.6" + dependencies: + "@webassemblyjs/floating-point-hex-parser": 1.11.6 + "@webassemblyjs/helper-api-error": 1.11.6 + "@xtuc/long": 4.2.2 + checksum: f4b562fa219f84368528339e0f8d273ad44e047a07641ffcaaec6f93e5b76fd86490a009aa91a294584e1436d74b0a01fa9fde45e333a4c657b58168b04da424 + languageName: node + linkType: hard + "@webassemblyjs/helper-wasm-bytecode@npm:1.11.1": version: 1.11.1 resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.11.1" @@ -6956,6 +7128,13 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/helper-wasm-bytecode@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.11.6" + checksum: 3535ef4f1fba38de3475e383b3980f4bbf3de72bbb631c2b6584c7df45be4eccd62c6ff48b5edd3f1bcff275cfd605a37679ec199fc91fd0a7705d7f1e3972dc + languageName: node + linkType: hard + "@webassemblyjs/helper-wasm-bytecode@npm:1.9.0": version: 1.9.0 resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.9.0" @@ -6975,6 +7154,18 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/helper-wasm-section@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/helper-wasm-section@npm:1.11.6" + dependencies: + "@webassemblyjs/ast": 1.11.6 + "@webassemblyjs/helper-buffer": 1.11.6 + "@webassemblyjs/helper-wasm-bytecode": 1.11.6 + "@webassemblyjs/wasm-gen": 1.11.6 + checksum: b2cf751bf4552b5b9999d27bbb7692d0aca75260140195cb58ea6374d7b9c2dc69b61e10b211a0e773f66209c3ddd612137ed66097e3684d7816f854997682e9 + languageName: node + linkType: hard + "@webassemblyjs/helper-wasm-section@npm:1.9.0": version: 1.9.0 resolution: "@webassemblyjs/helper-wasm-section@npm:1.9.0" @@ -6996,6 +7187,15 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/ieee754@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/ieee754@npm:1.11.6" + dependencies: + "@xtuc/ieee754": ^1.2.0 + checksum: 13574b8e41f6ca39b700e292d7edf102577db5650fe8add7066a320aa4b7a7c09a5056feccac7a74eb68c10dea9546d4461412af351f13f6b24b5f32379b49de + languageName: node + linkType: hard + "@webassemblyjs/ieee754@npm:1.9.0": version: 1.9.0 resolution: "@webassemblyjs/ieee754@npm:1.9.0" @@ -7014,6 +7214,15 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/leb128@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/leb128@npm:1.11.6" + dependencies: + "@xtuc/long": 4.2.2 + checksum: 7ea942dc9777d4b18a5ebfa3a937b30ae9e1d2ce1fee637583ed7f376334dd1d4274f813d2e250056cca803e0952def4b954913f1a3c9068bcd4ab4ee5143bf0 + languageName: node + linkType: hard + "@webassemblyjs/leb128@npm:1.9.0": version: 1.9.0 resolution: "@webassemblyjs/leb128@npm:1.9.0" @@ -7030,6 +7239,13 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/utf8@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/utf8@npm:1.11.6" + checksum: 807fe5b5ce10c390cfdd93e0fb92abda8aebabb5199980681e7c3743ee3306a75729bcd1e56a3903980e96c885ee53ef901fcbaac8efdfa480f9c0dae1d08713 + languageName: node + linkType: hard + "@webassemblyjs/utf8@npm:1.9.0": version: 1.9.0 resolution: "@webassemblyjs/utf8@npm:1.9.0" @@ -7069,6 +7285,22 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/wasm-edit@npm:^1.11.5": + version: 1.11.6 + resolution: "@webassemblyjs/wasm-edit@npm:1.11.6" + dependencies: + "@webassemblyjs/ast": 1.11.6 + "@webassemblyjs/helper-buffer": 1.11.6 + "@webassemblyjs/helper-wasm-bytecode": 1.11.6 + "@webassemblyjs/helper-wasm-section": 1.11.6 + "@webassemblyjs/wasm-gen": 1.11.6 + "@webassemblyjs/wasm-opt": 1.11.6 + "@webassemblyjs/wasm-parser": 1.11.6 + "@webassemblyjs/wast-printer": 1.11.6 + checksum: 29ce75870496d6fad864d815ebb072395a8a3a04dc9c3f4e1ffdc63fc5fa58b1f34304a1117296d8240054cfdbc38aca88e71fb51483cf29ffab0a61ef27b481 + languageName: node + linkType: hard + "@webassemblyjs/wasm-gen@npm:1.11.1": version: 1.11.1 resolution: "@webassemblyjs/wasm-gen@npm:1.11.1" @@ -7082,6 +7314,19 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/wasm-gen@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/wasm-gen@npm:1.11.6" + dependencies: + "@webassemblyjs/ast": 1.11.6 + "@webassemblyjs/helper-wasm-bytecode": 1.11.6 + "@webassemblyjs/ieee754": 1.11.6 + "@webassemblyjs/leb128": 1.11.6 + "@webassemblyjs/utf8": 1.11.6 + checksum: a645a2eecbea24833c3260a249704a7f554ef4a94c6000984728e94bb2bc9140a68dfd6fd21d5e0bbb09f6dfc98e083a45760a83ae0417b41a0196ff6d45a23a + languageName: node + linkType: hard + "@webassemblyjs/wasm-gen@npm:1.9.0": version: 1.9.0 resolution: "@webassemblyjs/wasm-gen@npm:1.9.0" @@ -7107,6 +7352,18 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/wasm-opt@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/wasm-opt@npm:1.11.6" + dependencies: + "@webassemblyjs/ast": 1.11.6 + "@webassemblyjs/helper-buffer": 1.11.6 + "@webassemblyjs/wasm-gen": 1.11.6 + "@webassemblyjs/wasm-parser": 1.11.6 + checksum: b4557f195487f8e97336ddf79f7bef40d788239169aac707f6eaa2fa5fe243557c2d74e550a8e57f2788e70c7ae4e7d32f7be16101afe183d597b747a3bdd528 + languageName: node + linkType: hard + "@webassemblyjs/wasm-opt@npm:1.9.0": version: 1.9.0 resolution: "@webassemblyjs/wasm-opt@npm:1.9.0" @@ -7133,6 +7390,20 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/wasm-parser@npm:1.11.6, @webassemblyjs/wasm-parser@npm:^1.11.5": + version: 1.11.6 + resolution: "@webassemblyjs/wasm-parser@npm:1.11.6" + dependencies: + "@webassemblyjs/ast": 1.11.6 + "@webassemblyjs/helper-api-error": 1.11.6 + "@webassemblyjs/helper-wasm-bytecode": 1.11.6 + "@webassemblyjs/ieee754": 1.11.6 + "@webassemblyjs/leb128": 1.11.6 + "@webassemblyjs/utf8": 1.11.6 + checksum: 8200a8d77c15621724a23fdabe58d5571415cda98a7058f542e670ea965dd75499f5e34a48675184947c66f3df23adf55df060312e6d72d57908e3f049620d8a + languageName: node + linkType: hard + "@webassemblyjs/wasm-parser@npm:1.9.0": version: 1.9.0 resolution: "@webassemblyjs/wasm-parser@npm:1.9.0" @@ -7171,6 +7442,16 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/wast-printer@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/wast-printer@npm:1.11.6" + dependencies: + "@webassemblyjs/ast": 1.11.6 + "@xtuc/long": 4.2.2 + checksum: d2fa6a4c427325ec81463e9c809aa6572af6d47f619f3091bf4c4a6fc34f1da3df7caddaac50b8e7a457f8784c62cd58c6311b6cb69b0162ccd8d4c072f79cf8 + languageName: node + linkType: hard + "@webassemblyjs/wast-printer@npm:1.9.0": version: 1.9.0 resolution: "@webassemblyjs/wast-printer@npm:1.9.0" @@ -7251,6 +7532,15 @@ __metadata: languageName: node linkType: hard +"acorn-import-assertions@npm:^1.9.0": + version: 1.9.0 + resolution: "acorn-import-assertions@npm:1.9.0" + peerDependencies: + acorn: ^8 + checksum: 944fb2659d0845c467066bdcda2e20c05abe3aaf11972116df457ce2627628a81764d800dd55031ba19de513ee0d43bb771bc679cc0eda66dc8b4fade143bc0c + languageName: node + linkType: hard + "acorn-jsx@npm:^5.3.1, acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" @@ -7312,6 +7602,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.8.2": + version: 8.11.2 + resolution: "acorn@npm:8.11.2" + bin: + acorn: bin/acorn + checksum: 818450408684da89423e3daae24e4dc9b68692db8ab49ea4569c7c5abb7a3f23669438bf129cc81dfdada95e1c9b944ee1bfca2c57a05a4dc73834a612fbf6a7 + languageName: node + linkType: hard + "add-stream@npm:^1.0.0": version: 1.0.0 resolution: "add-stream@npm:1.0.0" @@ -8516,7 +8815,7 @@ __metadata: languageName: node linkType: hard -"bn.js@npm:^5.0.0, bn.js@npm:^5.1.1, bn.js@npm:^5.1.2, bn.js@npm:^5.2.0, bn.js@npm:^5.2.1": +"bn.js@npm:^5.0.0, bn.js@npm:^5.1.2, bn.js@npm:^5.2.0, bn.js@npm:^5.2.1": version: 5.2.1 resolution: "bn.js@npm:5.2.1" checksum: 3dd8c8d38055fedfa95c1d5fc3c99f8dd547b36287b37768db0abab3c239711f88ff58d18d155dd8ad902b0b0cee973747b7ae20ea12a09473272b0201c9edd3 @@ -8723,7 +9022,7 @@ __metadata: languageName: node linkType: hard -"browserify-rsa@npm:^4.0.0, browserify-rsa@npm:^4.0.1": +"browserify-rsa@npm:^4.0.0, browserify-rsa@npm:^4.1.0": version: 4.1.0 resolution: "browserify-rsa@npm:4.1.0" dependencies: @@ -8734,19 +9033,19 @@ __metadata: linkType: hard "browserify-sign@npm:^4.0.0": - version: 4.2.1 - resolution: "browserify-sign@npm:4.2.1" + version: 4.2.2 + resolution: "browserify-sign@npm:4.2.2" dependencies: - bn.js: ^5.1.1 - browserify-rsa: ^4.0.1 + bn.js: ^5.2.1 + browserify-rsa: ^4.1.0 create-hash: ^1.2.0 create-hmac: ^1.1.7 - elliptic: ^6.5.3 + elliptic: ^6.5.4 inherits: ^2.0.4 - parse-asn1: ^5.1.5 - readable-stream: ^3.6.0 - safe-buffer: ^5.2.0 - checksum: 0221f190e3f5b2d40183fa51621be7e838d9caa329fe1ba773406b7637855f37b30f5d83e52ff8f244ed12ffe6278dd9983638609ed88c841ce547e603855707 + parse-asn1: ^5.1.6 + readable-stream: ^3.6.2 + safe-buffer: ^5.2.1 + checksum: b622730c0fc183328c3a1c9fdaaaa5118821ed6822b266fa6b0375db7e20061ebec87301d61931d79b9da9a96ada1cab317fce3c68f233e5e93ed02dbb35544c languageName: node linkType: hard @@ -11615,6 +11914,16 @@ __metadata: languageName: node linkType: hard +"enhanced-resolve@npm:^5.15.0": + version: 5.15.0 + resolution: "enhanced-resolve@npm:5.15.0" + dependencies: + graceful-fs: ^4.2.4 + tapable: ^2.2.0 + checksum: fbd8cdc9263be71cc737aa8a7d6c57b43d6aa38f6cc75dde6fcd3598a130cc465f979d2f4d01bb3bf475acb43817749c79f8eef9be048683602ca91ab52e4f11 + languageName: node + linkType: hard + "entities@npm:^2.0.0": version: 2.2.0 resolution: "entities@npm:2.2.0" @@ -11738,6 +12047,13 @@ __metadata: languageName: node linkType: hard +"es-module-lexer@npm:^1.2.1": + version: 1.3.1 + resolution: "es-module-lexer@npm:1.3.1" + checksum: 3beafa7e171eb1e8cc45695edf8d51638488dddf65294d7911f8d6a96249da6a9838c87529262cc6ea53988d8272cec0f4bff93f476ed031a54ba3afb51a0ed3 + languageName: node + linkType: hard + "es-set-tostringtag@npm:^2.0.1": version: 2.0.1 resolution: "es-set-tostringtag@npm:2.0.1" @@ -18616,7 +18932,7 @@ __metadata: languageName: node linkType: hard -"parse-asn1@npm:^5.0.0, parse-asn1@npm:^5.1.5": +"parse-asn1@npm:^5.0.0, parse-asn1@npm:^5.1.6": version: 5.1.6 resolution: "parse-asn1@npm:5.1.6" dependencies: @@ -20822,7 +21138,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:3, readable-stream@npm:^3.0.0, readable-stream@npm:^3.0.2, readable-stream@npm:^3.0.6, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0": +"readable-stream@npm:3, readable-stream@npm:^3.0.0, readable-stream@npm:^3.0.2, readable-stream@npm:^3.0.6, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0, readable-stream@npm:^3.6.2": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -21482,7 +21798,7 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491 @@ -21636,6 +21952,17 @@ __metadata: languageName: node linkType: hard +"schema-utils@npm:^3.2.0": + version: 3.3.0 + resolution: "schema-utils@npm:3.3.0" + dependencies: + "@types/json-schema": ^7.0.8 + ajv: ^6.12.5 + ajv-keywords: ^3.5.2 + checksum: ea56971926fac2487f0757da939a871388891bc87c6a82220d125d587b388f1704788f3706e7f63a7b70e49fc2db974c41343528caea60444afd5ce0fe4b85c0 + languageName: node + linkType: hard + "schema-utils@npm:^4.0.0": version: 4.0.0 resolution: "schema-utils@npm:4.0.0" @@ -23254,6 +23581,28 @@ __metadata: languageName: node linkType: hard +"terser-webpack-plugin@npm:^5.3.7": + version: 5.3.9 + resolution: "terser-webpack-plugin@npm:5.3.9" + dependencies: + "@jridgewell/trace-mapping": ^0.3.17 + jest-worker: ^27.4.5 + schema-utils: ^3.1.1 + serialize-javascript: ^6.0.1 + terser: ^5.16.8 + peerDependencies: + webpack: ^5.1.0 + peerDependenciesMeta: + "@swc/core": + optional: true + esbuild: + optional: true + uglify-js: + optional: true + checksum: 41705713d6f9cb83287936b21e27c658891c78c4392159f5148b5623f0e8c48559869779619b058382a4c9758e7820ea034695e57dc7c474b4962b79f553bc5f + languageName: node + linkType: hard + "terser@npm:^4.1.2, terser@npm:^4.6.3": version: 4.8.1 resolution: "terser@npm:4.8.1" @@ -23281,6 +23630,20 @@ __metadata: languageName: node linkType: hard +"terser@npm:^5.16.8": + version: 5.24.0 + resolution: "terser@npm:5.24.0" + dependencies: + "@jridgewell/source-map": ^0.3.3 + acorn: ^8.8.2 + commander: ^2.20.0 + source-map-support: ~0.5.20 + bin: + terser: bin/terser + checksum: d88f774b6fa711a234fcecefd7657f99189c367e17dbe95a51c2776d426ad0e4d98d1ffe6edfdf299877c7602e495bdd711d21b2caaec188410795e5447d0f6c + languageName: node + linkType: hard + "test-exclude@npm:^6.0.0": version: 6.0.0 resolution: "test-exclude@npm:6.0.0" @@ -24464,7 +24827,7 @@ __metadata: toastr2: ^3.0.0-alpha.18 typescript: ^4.7.4 web-vitals: ^2.1.4 - webpack: 5 + webpack: ^5.76.0 languageName: unknown linkType: soft @@ -24803,7 +25166,7 @@ __metadata: languageName: node linkType: hard -"webpack@npm:5, webpack@npm:>=4.43.0 <6.0.0, webpack@npm:^5.64.4, webpack@npm:^5.9.0": +"webpack@npm:>=4.43.0 <6.0.0, webpack@npm:^5.64.4, webpack@npm:^5.9.0": version: 5.76.1 resolution: "webpack@npm:5.76.1" dependencies: @@ -24840,6 +25203,43 @@ __metadata: languageName: node linkType: hard +"webpack@npm:^5.76.0": + version: 5.89.0 + resolution: "webpack@npm:5.89.0" + dependencies: + "@types/eslint-scope": ^3.7.3 + "@types/estree": ^1.0.0 + "@webassemblyjs/ast": ^1.11.5 + "@webassemblyjs/wasm-edit": ^1.11.5 + "@webassemblyjs/wasm-parser": ^1.11.5 + acorn: ^8.7.1 + acorn-import-assertions: ^1.9.0 + browserslist: ^4.14.5 + chrome-trace-event: ^1.0.2 + enhanced-resolve: ^5.15.0 + es-module-lexer: ^1.2.1 + eslint-scope: 5.1.1 + events: ^3.2.0 + glob-to-regexp: ^0.4.1 + graceful-fs: ^4.2.9 + json-parse-even-better-errors: ^2.3.1 + loader-runner: ^4.2.0 + mime-types: ^2.1.27 + neo-async: ^2.6.2 + schema-utils: ^3.2.0 + tapable: ^2.1.1 + terser-webpack-plugin: ^5.3.7 + watchpack: ^2.4.0 + webpack-sources: ^3.2.3 + peerDependenciesMeta: + webpack-cli: + optional: true + bin: + webpack: bin/webpack.js + checksum: 43fe0dbc30e168a685ef5a86759d5016a705f6563b39a240aa00826a80637d4a3deeb8062e709d6a4b05c63e796278244c84b04174704dc4a37bedb0f565c5ed + languageName: node + linkType: hard + "websocket-driver@npm:>=0.5.1, websocket-driver@npm:^0.7.4": version: 0.7.4 resolution: "websocket-driver@npm:0.7.4"