From 4ee8763a81186339f6cb4d10357b5da1e31395b5 Mon Sep 17 00:00:00 2001 From: khanti42 Date: Tue, 26 Nov 2024 16:07:57 +0100 Subject: [PATCH 1/6] chore: ci cd split pipeline enabled dev and prod (#438) * chore: ci-cd pipeline split * chore: ci-cd dev pipeline for test * chore: fix lint * chore: activate prod build * chore: fix comments --- .../workflows/deploy-wallet-get-starknet.yml | 104 ++++++++++++++++-- .github/workflows/publish-npm.yml | 37 ++++++- 2 files changed, 129 insertions(+), 12 deletions(-) diff --git a/.github/workflows/deploy-wallet-get-starknet.yml b/.github/workflows/deploy-wallet-get-starknet.yml index 7300d2b4..84a3a639 100644 --- a/.github/workflows/deploy-wallet-get-starknet.yml +++ b/.github/workflows/deploy-wallet-get-starknet.yml @@ -2,6 +2,7 @@ name: Deploy Wallet-UI and Get-starknet on: workflow_dispatch: + jobs: prepare-deployment: environment: production @@ -20,21 +21,17 @@ jobs: id: prepare_parameters run: | BASE=$(node -p "require('./packages/starknet-snap/package.json').version") - ENV=production - + { + echo "VERSION=${BASE}" + echo "ENV=prod" echo "AWS_CLOUDFRONT_DISTRIBUTIONS_ID=${{ vars.AWS_CLOUDFRONT_DISTRIBUTIONS_ID }}" echo "AWS_S3_GET_STARKNET_URL=${{ vars.AWS_S3_GET_STARKNET_URL }}" echo "AWS_S3_URL=${{ vars.AWS_S3_URL }}" echo "GET_STARKNET_PUBLIC_PATH=${{ vars.GET_STARKNET_PUBLIC_PATH }}" - echo "VERSION=${BASE}" - echo "TAG=latest" - echo "ENV=prod" } >> "$GITHUB_OUTPUT" outputs: VERSION: ${{ steps.prepare_parameters.outputs.VERSION }} - TAG: ${{ steps.prepare_parameters.outputs.TAG }} - ENV: ${{ steps.prepare_parameters.outputs.ENV }} AWS_S3_GET_STARKNET_URL: ${{ steps.prepare_parameters.outputs.AWS_S3_GET_STARKNET_URL }} AWS_CLOUDFRONT_DISTRIBUTIONS_ID: ${{ steps.prepare_parameters.outputs.AWS_CLOUDFRONT_DISTRIBUTIONS_ID }} AWS_S3_URL: ${{ steps.prepare_parameters.outputs.AWS_S3_URL }} @@ -67,7 +64,7 @@ jobs: echo "Building Get Starknet with GET_STARKNET_PUBLIC_PATH=$GET_STARKNET_PUBLIC_PATH" - SNAP_VERSION="${VERSION}$" GET_STARKNET_PUBLIC_PATH=$GET_STARKNET_PUBLIC_PATH yarn workspace @consensys/get-starknet build + SNAP_VERSION="${VERSION}" GET_STARKNET_PUBLIC_PATH=$GET_STARKNET_PUBLIC_PATH yarn workspace @consensys/get-starknet build env: VERSION: ${{ needs.prepare-deployment.outputs.VERSION }} GET_STARKNET_PUBLIC_PATH: ${{ needs.prepare-deployment.outputs.GET_STARKNET_PUBLIC_PATH }} @@ -79,4 +76,93 @@ jobs: ./packages/get-starknet/dist/webpack ./packages/wallet-ui/build ./node_modules/.yarn-state.yml - key: ${{ needs.prepare-deployment.outputs.CACHE_KEY }} \ No newline at end of file + key: ${{ needs.prepare-deployment.outputs.CACHE_KEY }} + + deploy-wallet-ui: + runs-on: ubuntu-latest + needs: + - prepare-deployment + - install-build + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: eu-central-1 + - uses: actions/checkout@v3 + with: + ref: ${{ github.sha }} + - name: Restore Cached Build + uses: actions/cache@v3 + id: restore-build + with: + path: | + ./packages/get-starknet/dist/webpack + ./packages/wallet-ui/build + ./node_modules/.yarn-state.yml + key: ${{ needs.prepare-deployment.outputs.CACHE_KEY }} + - name: Deploy to AWS + run: | + echo "Deployed Dapp to : $AWS_S3_URL" + aws s3 sync ./packages/wallet-ui/build "$AWS_S3_URL" + env: + AWS_S3_URL: ${{ needs.prepare-deployment.outputs.AWS_S3_URL }} + + deploy-get-starknet: + runs-on: ubuntu-latest + needs: + - prepare-deployment + - install-build + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: eu-central-1 + - uses: actions/checkout@v3 + with: + ref: ${{ github.sha }} + - name: Restore Cached Build + uses: actions/cache@v3 + id: restore-build + with: + path: | + ./packages/get-starknet/dist/webpack + ./packages/wallet-ui/build + ./node_modules/.yarn-state.yml + key: ${{ needs.prepare-deployment.outputs.CACHE_KEY }} + - name: Deploy to AWS + run: | + echo "Deployed get Starknet to : $AWS_S3_GET_STARKNET_URL" + aws s3 sync ./packages/get-starknet/dist/webpack "$AWS_S3_GET_STARKNET_URL" + env: + AWS_S3_GET_STARKNET_URL: ${{ needs.prepare-deployment.outputs.AWS_S3_GET_STARKNET_URL }} + + invalid-aws-cdn-cache: + runs-on: ubuntu-latest + needs: + - deploy-wallet-ui + - deploy-get-starknet + - prepare-deployment + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: eu-central-1 + - name: Invalid AWS CDN Cache + run: | + echo "Distribution ID : $AWS_CLOUDFRONT_DISTRIBUTIONS_ID" + echo "$AWS_CLOUDFRONT_DISTRIBUTIONS_ID" | tr ',' '\n' | while read -r DISTRIBUTIONS_ID + do + echo "Processing Distribution ID : $DISTRIBUTIONS_ID" + INVALIDED_ID="$(aws cloudfront create-invalidation --distribution-id "$DISTRIBUTIONS_ID" --paths "/starknet/*" | grep Id | awk -F'"' '{ print $4}')" + echo "Waiting for invalidation $INVALIDED_ID" + aws cloudfront wait invalidation-completed --id "$INVALIDED_ID" --distribution-id "$DISTRIBUTIONS_ID" + echo "Invalidation $INVALIDED_ID completed" + done + env: + AWS_CLOUDFRONT_DISTRIBUTIONS_ID: ${{ needs.prepare-deployment.outputs.AWS_CLOUDFRONT_DISTRIBUTIONS_ID }} diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index e58a152b..a0d80a05 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -2,6 +2,7 @@ name: Publish NPM on: workflow_dispatch: + jobs: prepare-deployment: environment: production @@ -20,8 +21,7 @@ jobs: id: prepare_parameters run: | BASE=$(node -p "require('./packages/starknet-snap/package.json').version") - ENV=production - + { echo "VERSION=${BASE}" echo "TAG=latest" @@ -113,4 +113,35 @@ jobs: run: | npm pack ./packages/starknet-snap --tag "$TAG" --access public env: - TAG: ${{ needs.prepare-deployment.outputs.TAG }} \ No newline at end of file + TAG: ${{ needs.prepare-deployment.outputs.TAG }} + + publish-npm: + runs-on: ubuntu-latest + needs: + - publish-npm-dry-run + - prepare-deployment + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.sha }} + - uses: actions/setup-node@v3 + with: + node-version: '20.x' + registry-url: 'https://registry.npmjs.org' + - name: Restore Cached Build + uses: actions/cache@v3 + id: restore-build + with: + # add /packages/snap/snap.manifest.json to include an updated shasum from build due to version update in auto PR + path: | + ./packages/starknet-snap/package.json + ./packages/starknet-snap/dist + ./packages/starknet-snap/snap.manifest.json + ./node_modules/.yarn-state.yml + key: ${{ needs.prepare-deployment.outputs.CACHE_KEY }} + - name: Run Publish + run: | + npm publish ./packages/starknet-snap --tag "$TAG" --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + TAG: ${{ needs.prepare-deployment.outputs.TAG }} From 02490cd44a8efa20572d056e2761f42324af6608 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 26 Nov 2024 15:11:42 +0000 Subject: [PATCH 2/6] chore: release main (#430) * chore: release main * chore: fix comments * chore: fix comments * chore: lint + prettier --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Florin Dzeladini --- .release-please-manifest.json | 4 ++-- packages/starknet-snap/CHANGELOG.md | 11 +++++++++++ .../openrpc/starknet_snap_api_openrpc.json | 2 +- packages/starknet-snap/package.json | 2 +- packages/starknet-snap/snap.manifest.json | 2 +- packages/wallet-ui/CHANGELOG.md | 12 ++++++++++++ packages/wallet-ui/package.json | 2 +- 7 files changed, 29 insertions(+), 6 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index b064d956..81d07fe2 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,5 +1,5 @@ { - "packages/starknet-snap": "2.11.0", - "packages/wallet-ui": "1.24.1", + "packages/starknet-snap": "3.0.0", + "packages/wallet-ui": "1.25.0", "packages/get-starknet": "1.3.0" } \ No newline at end of file diff --git a/packages/starknet-snap/CHANGELOG.md b/packages/starknet-snap/CHANGELOG.md index 8109724c..6495e7ca 100644 --- a/packages/starknet-snap/CHANGELOG.md +++ b/packages/starknet-snap/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [3.0.0](https://github.com/Consensys/starknet-snap/compare/starknet-snap-v2.11.0...starknet-snap-v3.0.0) (2024-11-26) + + +### ⚠ BREAKING CHANGES + +* Enable JSX support, 1) Allow users to select the fee token in the execute transaction dialog (#417, #418, #419, #420). 2) Add JSX support and detection (#415, #416). 3) Replace RPC dialogs with JSX-based components ([#422](https://github.com/Consensys/starknet-snap/issues/422)) + +### Features + +* Enable JSX support, 1) Allow users to select the fee token in the execute transaction dialog ([#417](https://github.com/Consensys/starknet-snap/issues/417), [#418](https://github.com/Consensys/starknet-snap/issues/418), [#419](https://github.com/Consensys/starknet-snap/issues/419), [#420](https://github.com/Consensys/starknet-snap/issues/420)). 2) Add JSX support and detection ([#415](https://github.com/Consensys/starknet-snap/issues/415), [#416](https://github.com/Consensys/starknet-snap/issues/416)). 3) Replace RPC dialogs with JSX-based components ([#422](https://github.com/Consensys/starknet-snap/issues/422)) ([abfc0e5](https://github.com/Consensys/starknet-snap/commit/abfc0e52cc5c9c4fc7ec7e04a9ff667acbf99813)) + ## [2.11.0](https://github.com/Consensys/starknet-snap/compare/starknet-snap-v2.10.1...starknet-snap-v2.11.0) (2024-10-28) diff --git a/packages/starknet-snap/openrpc/starknet_snap_api_openrpc.json b/packages/starknet-snap/openrpc/starknet_snap_api_openrpc.json index c596193b..72c853b6 100644 --- a/packages/starknet-snap/openrpc/starknet_snap_api_openrpc.json +++ b/packages/starknet-snap/openrpc/starknet_snap_api_openrpc.json @@ -1,7 +1,7 @@ { "openrpc": "1.0.0-rc1", "info": { - "version": "2.11.0", + "version": "3.0.0", "title": "Starknet MetaMask Snap API", "license": {} }, diff --git a/packages/starknet-snap/package.json b/packages/starknet-snap/package.json index 807678fd..5ebff7bb 100644 --- a/packages/starknet-snap/package.json +++ b/packages/starknet-snap/package.json @@ -1,6 +1,6 @@ { "name": "@consensys/starknet-snap", - "version": "2.11.0", + "version": "3.0.0", "keywords": [], "repository": { "type": "git", diff --git a/packages/starknet-snap/snap.manifest.json b/packages/starknet-snap/snap.manifest.json index 9583616a..e7d0c275 100644 --- a/packages/starknet-snap/snap.manifest.json +++ b/packages/starknet-snap/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "2.11.0", + "version": "3.0.0", "description": "Manage Starknet accounts and assets with MetaMask.", "proposedName": "Starknet", "repository": { diff --git a/packages/wallet-ui/CHANGELOG.md b/packages/wallet-ui/CHANGELOG.md index 076fc9ef..0414c885 100644 --- a/packages/wallet-ui/CHANGELOG.md +++ b/packages/wallet-ui/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.25.0](https://github.com/Consensys/starknet-snap/compare/wallet-ui-v1.24.1...wallet-ui-v1.25.0) (2024-11-26) + + +### Features + +* Add minimum MetaMask version requirements detection. ([#424](https://github.com/Consensys/starknet-snap/issues/424)) ([f6cde30](https://github.com/Consensys/starknet-snap/commit/f6cde302f491f6f2bd4322ce996a699e046fe9ee)) + + +### Bug Fixes + +* Add missing asset icon for `USDC`, `USDT` and `STRK` token ([#428](https://github.com/Consensys/starknet-snap/issues/428)) ([9f43a22](https://github.com/Consensys/starknet-snap/commit/9f43a228e844ab200984a0b5a1f8ff7bb0d8288d)) + ## [1.24.1](https://github.com/Consensys/starknet-snap/compare/wallet-ui-v1.24.0...wallet-ui-v1.24.1) (2024-10-28) diff --git a/packages/wallet-ui/package.json b/packages/wallet-ui/package.json index 889dbe10..e82ef186 100644 --- a/packages/wallet-ui/package.json +++ b/packages/wallet-ui/package.json @@ -1,6 +1,6 @@ { "name": "wallet-ui", - "version": "1.24.1", + "version": "1.25.0", "private": true, "homepage": "/starknet", "license": "(Apache-2.0 OR MIT)", From fbfcb54abd8f97b01e2e514c84c86ddf0086fbca Mon Sep 17 00:00:00 2001 From: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:48:28 +0800 Subject: [PATCH 3/6] chore: remove legacy API (#442) --- packages/starknet-snap/src/estimateFee.ts | 142 ------ packages/starknet-snap/src/estimateFees.ts | 51 -- packages/starknet-snap/src/index.tsx | 14 - packages/starknet-snap/src/sendTransaction.ts | 191 -------- .../test/src/estimateFee.test.ts | 273 ----------- .../test/src/estimateFees.test.ts | 117 ----- .../test/src/sendTransaction.test.ts | 458 ------------------ 7 files changed, 1246 deletions(-) delete mode 100644 packages/starknet-snap/src/estimateFee.ts delete mode 100644 packages/starknet-snap/src/estimateFees.ts delete mode 100644 packages/starknet-snap/src/sendTransaction.ts delete mode 100644 packages/starknet-snap/test/src/estimateFee.test.ts delete mode 100644 packages/starknet-snap/test/src/estimateFees.test.ts delete mode 100644 packages/starknet-snap/test/src/sendTransaction.test.ts diff --git a/packages/starknet-snap/src/estimateFee.ts b/packages/starknet-snap/src/estimateFee.ts deleted file mode 100644 index 624381b3..00000000 --- a/packages/starknet-snap/src/estimateFee.ts +++ /dev/null @@ -1,142 +0,0 @@ -import type { Invocations } from 'starknet'; -import { TransactionType } from 'starknet'; - -import { - FeeTokenUnit, - type ApiParamsWithKeyDeriver, - type EstimateFeeRequestParams, -} from './types/snapApi'; -import { ACCOUNT_CLASS_HASH } from './utils/constants'; -import { logger } from './utils/logger'; -import { toJson } from './utils/serializer'; -import { - getNetworkFromChainId, - verifyIfAccountNeedUpgradeOrDeploy, -} from './utils/snapUtils'; -import { - validateAndParseAddress, - getKeysFromAddress, - getCallDataArray, - getAccContractAddressAndCallData, - estimateFeeBulk, - addFeesFromAllTransactions, - isAccountDeployed, -} from './utils/starknetUtils'; - -/** - * - * @param params - */ -export async function estimateFee(params: ApiParamsWithKeyDeriver) { - try { - const { state, keyDeriver, requestParams } = params; - const requestParamsObj = requestParams as EstimateFeeRequestParams; - const { contractAddress } = requestParamsObj; - const { contractFuncName } = requestParamsObj; - const contractCallData = getCallDataArray( - requestParamsObj.contractCallData as unknown as string, - ); - const { senderAddress } = requestParamsObj; - const network = getNetworkFromChainId(state, requestParamsObj.chainId); - - if ( - !contractAddress || - !requestParamsObj.senderAddress || - !contractFuncName - ) { - throw new Error( - `The given contract address, sender address, and function name need to be non-empty string, got: ${toJson( - requestParamsObj, - )}`, - ); - } - - try { - validateAndParseAddress(contractAddress); - } catch (error) { - throw new Error( - `The given contract address is invalid: ${contractAddress}`, - ); - } - try { - validateAndParseAddress(senderAddress); - } catch (error) { - throw new Error(`The given sender address is invalid: ${senderAddress}`); - } - - const { privateKey: senderPrivateKey, publicKey } = - await getKeysFromAddress(keyDeriver, network, state, senderAddress); - - await verifyIfAccountNeedUpgradeOrDeploy( - network, - senderAddress, - publicKey, - false, - ); - - const txnInvocation = { - contractAddress, - entrypoint: contractFuncName, - calldata: contractCallData, - }; - - logger.log(`estimateFee:\ntxnInvocation: ${toJson(txnInvocation)}`); - - // Estimate deploy account fee if the signer has not been deployed yet - const accountDeployed = await isAccountDeployed(network, senderAddress); - let bulkTransactions: Invocations = [ - { - type: TransactionType.INVOKE, - payload: txnInvocation, - }, - ]; - if (!accountDeployed) { - const { callData } = getAccContractAddressAndCallData(publicKey); - const deployAccountpayload = { - classHash: ACCOUNT_CLASS_HASH, - contractAddress: senderAddress, - constructorCalldata: callData, - addressSalt: publicKey, - }; - - bulkTransactions = [ - { - type: TransactionType.DEPLOY_ACCOUNT, - payload: deployAccountpayload, - }, - { - type: TransactionType.INVOKE, - payload: txnInvocation, - }, - ]; - } - - const estimateBulkFeeResp = await estimateFeeBulk( - network, - senderAddress, - senderPrivateKey, - bulkTransactions, - ); - logger.log( - `estimateFee:\nestimateFeeBulk estimateBulkFeeResp: ${toJson( - estimateBulkFeeResp, - )}`, - ); - const estimateFeeResp = addFeesFromAllTransactions(estimateBulkFeeResp); - - logger.log(`estimateFee:\nestimateFeeResp: ${toJson(estimateFeeResp)}`); - - const resp = { - suggestedMaxFee: estimateFeeResp.suggestedMaxFee.toString(10), - overallFee: estimateFeeResp.overall_fee.toString(10), - unit: FeeTokenUnit.ETH, - includeDeploy: !accountDeployed, - }; - logger.log(`estimateFee:\nresp: ${toJson(resp)}`); - - return resp; - } catch (error) { - logger.error(`Problem found:`, error); - throw error; - } -} diff --git a/packages/starknet-snap/src/estimateFees.ts b/packages/starknet-snap/src/estimateFees.ts deleted file mode 100644 index 0303b3a9..00000000 --- a/packages/starknet-snap/src/estimateFees.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { - ApiParamsWithKeyDeriver, - EstimateFeesRequestParams, -} from './types/snapApi'; -import { logger } from './utils/logger'; -import { toJson } from './utils/serializer'; -import { getNetworkFromChainId } from './utils/snapUtils'; -import { getKeysFromAddress, estimateFeeBulk } from './utils/starknetUtils'; - -/** - * - * @param params - */ -export async function estimateFees(params: ApiParamsWithKeyDeriver) { - try { - const { state, keyDeriver, requestParams } = params; - const requestParamsObj = requestParams as EstimateFeesRequestParams; - - logger.log(`estimateFees params: ${toJson(requestParamsObj, 2)}`); - - const { senderAddress } = requestParamsObj; - 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, - ); - - return fees.map((fee) => ({ - // eslint-disable-next-line @typescript-eslint/naming-convention - overall_fee: fee.overall_fee.toString(10) || '0', - // eslint-disable-next-line @typescript-eslint/naming-convention - gas_consumed: fee.gas_consumed.toString(10) || '0', - // eslint-disable-next-line @typescript-eslint/naming-convention - gas_price: fee.gas_price.toString(10) || '0', - suggestedMaxFee: fee.suggestedMaxFee.toString(10) || '0', - })); - } catch (error) { - logger.error(`Problem found:`, error); - throw error; - } -} diff --git a/packages/starknet-snap/src/index.tsx b/packages/starknet-snap/src/index.tsx index 0d8f4585..af3b7b3f 100644 --- a/packages/starknet-snap/src/index.tsx +++ b/packages/starknet-snap/src/index.tsx @@ -14,7 +14,6 @@ import { addNetwork } from './addNetwork'; import { Config } from './config'; import { createAccount } from './createAccount'; import { estimateAccDeployFee } from './estimateAccountDeployFee'; -import { estimateFees } from './estimateFees'; import { extractPublicKey } from './extractPublicKey'; import { getCurrentNetwork } from './getCurrentNetwork'; import { getErc20TokenBalance } from './getErc20TokenBalance'; @@ -54,7 +53,6 @@ import { getDeploymentData, watchAsset, } from './rpcs'; -import { sendTransaction } from './sendTransaction'; import { signDeployAccountTransaction } from './signDeployAccountTransaction'; import type { ApiParams, @@ -205,12 +203,6 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { case 'starkNet_getTransactionStatus': return await getTransactionStatus(apiParams); - case 'starkNet_sendTransaction': - apiParams.keyDeriver = await getAddressKeyDeriver(snap); - return await sendTransaction( - apiParams as unknown as ApiParamsWithKeyDeriver, - ); - case 'starkNet_getValue': return await getValue(apiParams); @@ -264,12 +256,6 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { apiParams.requestParams as unknown as ExecuteTxnParams, ); - case 'starkNet_estimateFees': - apiParams.keyDeriver = await getAddressKeyDeriver(snap); - return await estimateFees( - apiParams as unknown as ApiParamsWithKeyDeriver, - ); - case 'starkNet_upgradeAccContract': apiParams.keyDeriver = await getAddressKeyDeriver(snap); return upgradeAccContract( diff --git a/packages/starknet-snap/src/sendTransaction.ts b/packages/starknet-snap/src/sendTransaction.ts deleted file mode 100644 index b06eb25c..00000000 --- a/packages/starknet-snap/src/sendTransaction.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { heading, panel, DialogType } from '@metamask/snaps-sdk'; -import { num as numUtils, constants } from 'starknet'; - -import { createAccount } from './createAccount'; -import { estimateFee } from './estimateFee'; -import type { - ApiParamsWithKeyDeriver, - SendTransactionRequestParams, -} from './types/snapApi'; -import type { Transaction } from './types/snapState'; -import { TransactionStatus, VoyagerTransactionType } from './types/snapState'; -import { logger } from './utils/logger'; -import { toJson } from './utils/serializer'; -import { - getNetworkFromChainId, - getSendTxnText, - upsertTransaction, -} from './utils/snapUtils'; -import { - validateAndParseAddress, - getKeysFromAddress, - getCallDataArray, - executeTxn, - isAccountDeployed, - isUpgradeRequired, -} from './utils/starknetUtils'; - -/** - * - * @param params - */ -export async function sendTransaction(params: ApiParamsWithKeyDeriver) { - try { - const { state, wallet, saveMutex, keyDeriver, requestParams } = params; - const requestParamsObj = requestParams as SendTransactionRequestParams; - - if ( - !requestParamsObj.contractAddress || - !requestParamsObj.senderAddress || - !requestParamsObj.contractFuncName - ) { - throw new Error( - `The given contract address, sender address, and function name need to be non-empty string, got: ${toJson( - requestParamsObj, - )}`, - ); - } - - try { - validateAndParseAddress(requestParamsObj.contractAddress); - } catch (error) { - throw new Error( - `The given contract address is invalid: ${requestParamsObj.contractAddress}`, - ); - } - try { - validateAndParseAddress(requestParamsObj.senderAddress); - } catch (error) { - throw new Error( - `The given sender address is invalid: ${requestParamsObj.senderAddress}`, - ); - } - - const { contractAddress } = requestParamsObj; - const { contractFuncName } = requestParamsObj; - const contractCallData = getCallDataArray( - requestParamsObj.contractCallData as unknown as string, - ); - const { senderAddress } = requestParamsObj; - const network = getNetworkFromChainId(state, requestParamsObj.chainId); - - if (await isUpgradeRequired(network, senderAddress)) { - throw new Error('Upgrade required'); - } - - const { privateKey: senderPrivateKey, addressIndex } = - await getKeysFromAddress(keyDeriver, network, state, senderAddress); - let maxFee = requestParamsObj.maxFee - ? numUtils.toBigInt(requestParamsObj.maxFee) - : constants.ZERO; - if (maxFee === constants.ZERO) { - const { suggestedMaxFee } = await estimateFee(params); - maxFee = numUtils.toBigInt(suggestedMaxFee); - } - - const signingTxnComponents = getSendTxnText( - state, - contractAddress, - contractFuncName, - contractCallData, - senderAddress, - maxFee, - network, - ); - const response = await wallet.request({ - method: 'snap_dialog', - params: { - type: DialogType.Confirmation, - content: panel([ - heading('Do you want to sign this transaction ?'), - ...signingTxnComponents, - ]), - }, - }); - if (!response) { - return false; - } - - const txnInvocation = { - contractAddress, - entrypoint: contractFuncName, - calldata: contractCallData, - }; - - logger.log( - `sendTransaction:\ntxnInvocation: ${toJson( - txnInvocation, - )}\nmaxFee: ${maxFee.toString()}}`, - ); - - const accountDeployed = await isAccountDeployed(network, senderAddress); - if (!accountDeployed) { - // Deploy account before sending the transaction - logger.log( - 'sendTransaction:\nFirst transaction : send deploy transaction', - ); - const createAccountApiParams = { - state, - wallet: params.wallet, - saveMutex: params.saveMutex, - keyDeriver, - requestParams: { - addressIndex, - deploy: true, - chainId: requestParamsObj.chainId, - }, - }; - await createAccount(createAccountApiParams, true, true); - } - - // 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, - undefined, - { - maxFee, - nonce: nonceSendTransaction, - }, - ); - - logger.log(`sendTransaction:\ntxnResp: ${toJson(txnResp)}`); - - if (txnResp.transaction_hash) { - const txn: Transaction = { - txnHash: txnResp.transaction_hash, - txnType: VoyagerTransactionType.INVOKE, - chainId: network.chainId, - senderAddress, - contractAddress, - contractFuncName, - contractCallData: contractCallData.map( - (data: numUtils.BigNumberish) => { - try { - return numUtils.toHex(numUtils.toBigInt(data)); - } catch (error) { - // data is already send to chain, hence we should not throw error - return '0x0'; - } - }, - ), - finalityStatus: TransactionStatus.RECEIVED, - executionStatus: TransactionStatus.RECEIVED, - status: '', // DEPRECATED LATER - failureReason: '', - eventIds: [], - timestamp: Math.floor(Date.now() / 1000), - }; - - await upsertTransaction(txn, wallet, saveMutex); - } - - return txnResp; - } catch (error) { - logger.error(`Problem found:`, error); - throw error; - } -} diff --git a/packages/starknet-snap/test/src/estimateFee.test.ts b/packages/starknet-snap/test/src/estimateFee.test.ts deleted file mode 100644 index 05bb2289..00000000 --- a/packages/starknet-snap/test/src/estimateFee.test.ts +++ /dev/null @@ -1,273 +0,0 @@ -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 { estimateFee } from '../../src/estimateFee'; -import { SnapState } from '../../src/types/snapState'; -import { - ACCOUNT_CLASS_HASH, - STARKNET_MAINNET_NETWORK, -} from '../../src/utils/constants'; -import { getAddressKeyDeriver } from '../../src/utils/keyPair'; -import { - account2, - Cairo1Account1, - estimateDeployFeeResp4, - estimateFeeResp, - getBip44EntropyStub, - getBalanceResp, -} from '../constants.test'; -import { Mutex } from 'async-mutex'; -import { - ApiParamsWithKeyDeriver, - EstimateFeeRequestParams, -} from '../../src/types/snapApi'; -import { TransactionType } from 'starknet'; -import { UpgradeRequiredError } from '../../src/utils/exceptions'; - -chai.use(sinonChai); -const sandbox = sinon.createSandbox(); - -describe('Test function: estimateFee', function () { - const walletStub = new WalletMock(); - - const state: SnapState = { - accContracts: [], - erc20Tokens: [], - networks: [STARKNET_MAINNET_NETWORK], - transactions: [], - }; - const requestObject: EstimateFeeRequestParams = { - contractAddress: - '0x07394cbe418daa16e42b87ba67372d4ab4a5df0b05c6e554d158458ce245bc10', - contractFuncName: 'balanceOf', - contractCallData: - '0x7426b2da7a8978e0d472d64f15f984d658226cb55a4fd8aa7689688a7eab37b', - senderAddress: account2.address, - }; - let apiParams: ApiParamsWithKeyDeriver; - - beforeEach(async function () { - walletStub.rpcStubs.snap_getBip44Entropy.callsFake(getBip44EntropyStub); - apiParams = { - state, - requestParams: requestObject, - wallet: walletStub, - saveMutex: new Mutex(), - keyDeriver: await getAddressKeyDeriver(walletStub), - }; - - sandbox.stub(utils, 'callContract').resolves(getBalanceResp); - sandbox - .stub(utils, 'getAccContractAddressAndCallDataLegacy') - .resolves(account2.address); - }); - - afterEach(function () { - walletStub.reset(); - sandbox.restore(); - }); - - describe('when request param validation fail', function () { - let invalidRequest = Object.assign({}, requestObject); - - afterEach(async function () { - invalidRequest = Object.assign({}, requestObject); - }); - - it('should throw an error if the function name is undefined', async function () { - invalidRequest.contractFuncName = undefined as unknown as string; - apiParams.requestParams = invalidRequest; - let result; - try { - result = await estimateFee(apiParams); - } catch (err) { - result = err; - } finally { - expect(result).to.be.an('Error'); - } - }); - - it('should throw an error if the contract address is invalid', async function () { - invalidRequest.contractAddress = 'wrongAddress'; - apiParams.requestParams = invalidRequest; - let result; - try { - result = await estimateFee(apiParams); - } catch (err) { - result = err; - } finally { - expect(result).to.be.an('Error'); - } - }); - - it('should throw an error if the sender address is invalid', async function () { - invalidRequest.senderAddress = 'wrongAddress'; - apiParams.requestParams = invalidRequest; - let result; - try { - result = await estimateFee(apiParams); - } catch (err) { - result = err; - } finally { - expect(result).to.be.an('Error'); - } - }); - }); - - describe('when request param validation pass', function () { - beforeEach(async function () { - apiParams.requestParams = Object.assign({}, requestObject); - sandbox.stub(utils, 'getKeysFromAddress').resolves({ - privateKey: 'pk', - publicKey: account2.publicKey, - addressIndex: account2.addressIndex, - derivationPath: `m / bip32:1' / bip32:1' / bip32:1' / bip32:1'`, - }); - }); - - afterEach(async function () { - apiParams.requestParams = Object.assign({}, requestObject); - }); - - describe('when account require upgrade', function () { - let validateAccountRequireUpgradeOrDeployStub: sinon.SinonStub; - beforeEach(async function () { - validateAccountRequireUpgradeOrDeployStub = sandbox - .stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .throws(new UpgradeRequiredError('Upgrade Required')); - }); - - it('should throw error if upgrade required', async function () { - let result; - try { - result = await estimateFee(apiParams); - } catch (err) { - result = err; - } finally { - expect( - validateAccountRequireUpgradeOrDeployStub, - ).to.have.been.calledOnceWith( - STARKNET_MAINNET_NETWORK, - account2.address, - account2.publicKey, - ); - expect(result).to.be.an('Error'); - expect(result.message).to.equal('Upgrade Required'); - } - }); - }); - - describe('when account is not require upgrade', function () { - let estimateFeeBulkStub: sinon.SinonStub; - - beforeEach(async function () { - sandbox.stub(utils, 'isUpgradeRequired').resolves(false); - apiParams.requestParams = { - ...apiParams.requestParams, - senderAddress: Cairo1Account1.address, - }; - }); - - describe('when account is deployed', function () { - beforeEach(async function () { - estimateFeeBulkStub = sandbox - .stub(utils, 'estimateFeeBulk') - .resolves([estimateFeeResp]); - sandbox - .stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .resolvesThis(); - }); - - it('should estimate the fee correctly', async function () { - const result = await estimateFee(apiParams); - expect(result.suggestedMaxFee).to.be.eq( - estimateFeeResp.suggestedMaxFee.toString(10), - ); - expect(estimateFeeBulkStub).callCount(1); - }); - }); - - describe('when account is not deployed', function () { - beforeEach(async function () { - sandbox - .stub(utils, 'validateAccountRequireUpgradeOrDeploy') - .resolvesThis(); - 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); - - const { privateKey, publicKey } = await utils.getKeysFromAddress( - apiParams.keyDeriver, - STARKNET_MAINNET_NETWORK, - state, - Cairo1Account1.address, - ); - const { callData } = - utils.getAccContractAddressAndCallData(publicKey); - const apiRequest = - apiParams.requestParams as EstimateFeeRequestParams; - - const expectedBulkTransaction = [ - { - type: TransactionType.DEPLOY_ACCOUNT, - payload: { - classHash: ACCOUNT_CLASS_HASH, - contractAddress: Cairo1Account1.address, - constructorCalldata: callData, - addressSalt: publicKey, - }, - }, - { - type: TransactionType.INVOKE, - payload: { - contractAddress: apiRequest.contractAddress, - entrypoint: apiRequest.contractFuncName, - calldata: utils.getCallDataArray( - apiRequest.contractCallData as unknown as string, - ), - }, - }, - ]; - - expect(result.suggestedMaxFee).to.be.eq( - expectedSuggestedMaxFee.toString(10), - ); - expect(estimateFeeBulkStub).callCount(1); - expect(estimateFeeBulkStub).to.be.calledWith( - STARKNET_MAINNET_NETWORK, - Cairo1Account1.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(estimateFeeBulkStub).callCount(1); - } - }); - }); - }); - }); -}); diff --git a/packages/starknet-snap/test/src/estimateFees.test.ts b/packages/starknet-snap/test/src/estimateFees.test.ts deleted file mode 100644 index 4e688ee3..00000000 --- a/packages/starknet-snap/test/src/estimateFees.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -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_SEPOLIA_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 { ApiParamsWithKeyDeriver } 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_SEPOLIA_TESTNET_NETWORK], - transactions: [], - }; - let apiParams: ApiParamsWithKeyDeriver; - - beforeEach(async function () { - walletStub.rpcStubs.snap_getBip44Entropy.callsFake(getBip44EntropyStub); - apiParams = { - state, - requestParams: {}, - wallet: walletStub, - saveMutex: new Mutex(), - keyDeriver: await getAddressKeyDeriver(walletStub), - }; - }); - - afterEach(function () { - walletStub.reset(); - sandbox.restore(); - }); - - it('should estimate fees correctly', async function () { - const feeResult = [estimateDeployFeeResp2, estimateDeployFeeResp3]; - sandbox.stub(utils, 'estimateFeeBulk').resolves(feeResult); - apiParams.requestParams = { - senderAddress: account2.address, - chainId: STARKNET_SEPOLIA_TESTNET_NETWORK.chainId, - invocations: [ - { - type: TransactionType.INVOKE, - payload: { - entrypoint: 'transfer', - contractAddress: - '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7', - calldata: [ - '1697416752243704114657612983658108968471303240361660550219082009242042413588', - '1', - '0', - ], - }, - }, - ], - invocationsDetails: { - nonce: '1', - }, - }; - 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, 'estimateFeeBulk').throws(new Error()); - apiParams.requestParams = { - senderAddress: account2.address, - chainId: STARKNET_SEPOLIA_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/sendTransaction.test.ts b/packages/starknet-snap/test/src/sendTransaction.test.ts deleted file mode 100644 index 809c7a69..00000000 --- a/packages/starknet-snap/test/src/sendTransaction.test.ts +++ /dev/null @@ -1,458 +0,0 @@ -import chai, { expect } from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import sinon from 'sinon'; -import sinonChai from 'sinon-chai'; -import { WalletMock } from '../wallet.mock.test'; -import * as utils from '../../src/utils/starknetUtils'; -import * as snapUtils from '../../src/utils/snapUtils'; -import { SnapState } from '../../src/types/snapState'; -import { sendTransaction } from '../../src/sendTransaction'; -import * as estimateFeeSnap from '../../src/estimateFee'; -import { - STARKNET_MAINNET_NETWORK, - STARKNET_SEPOLIA_TESTNET_NETWORK, -} from '../../src/utils/constants'; -import { - account1, - createAccountProxyResp, - estimateDeployFeeResp, - estimateFeeResp, - getBalanceResp, - getBip44EntropyStub, - sendTransactionFailedResp, - sendTransactionResp, - token2, - token3, - unfoundUserAddress, - Cairo1Account1, -} from '../constants.test'; -import { getAddressKeyDeriver } from '../../src/utils/keyPair'; -import { Mutex } from 'async-mutex'; -import { - ApiParamsWithKeyDeriver, - FeeTokenUnit, - SendTransactionRequestParams, -} from '../../src/types/snapApi'; -import { GetTransactionReceiptResponse } from 'starknet'; - -chai.use(sinonChai); -chai.use(chaiAsPromised); -const sandbox = sinon.createSandbox(); - -describe('Test function: sendTransaction', function () { - this.timeout(5000); - const walletStub = new WalletMock(); - const state: SnapState = { - accContracts: [], - erc20Tokens: [token2, token3], - networks: [STARKNET_MAINNET_NETWORK, STARKNET_SEPOLIA_TESTNET_NETWORK], - transactions: [], - }; - let apiParams: ApiParamsWithKeyDeriver; - - const requestObject: SendTransactionRequestParams = { - contractAddress: - '0x07394cbe418daa16e42b87ba67372d4ab4a5df0b05c6e554d158458ce245bc10', - contractFuncName: 'transfer', - contractCallData: - '0x0256d8f49882cc9366037415f48fa9fd2b5b7344ded7573ebfcef7c90e3e6b75,100000000000000000000,0', - senderAddress: account1.address, - chainId: STARKNET_SEPOLIA_TESTNET_NETWORK.chainId, - }; - - beforeEach(async function () { - walletStub.rpcStubs.snap_getBip44Entropy.callsFake(getBip44EntropyStub); - apiParams = { - state, - requestParams: { - chainId: STARKNET_SEPOLIA_TESTNET_NETWORK.chainId, - }, - wallet: walletStub, - saveMutex: new Mutex(), - keyDeriver: await getAddressKeyDeriver(walletStub), - }; - }); - - afterEach(function () { - walletStub.reset(); - sandbox.restore(); - }); - - describe('when request param validation fail', function () { - let invalidRequest: SendTransactionRequestParams = Object.assign( - {}, - requestObject, - ); - - afterEach(function () { - invalidRequest = Object.assign({}, requestObject); - apiParams.requestParams = requestObject; - }); - - it('should show error when request contractAddress is not given', async function () { - invalidRequest.contractAddress = undefined as unknown as string; - apiParams.requestParams = invalidRequest; - let result; - try { - result = await sendTransaction(apiParams); - } catch (err) { - result = err; - } finally { - expect(result).to.be.an('Error'); - expect(result.message).to.be.include( - 'The given contract address, sender address, and function name need to be non-empty string', - ); - } - }); - - it('should show error when request contractAddress is invalid', async function () { - invalidRequest.contractAddress = '0x0'; - apiParams.requestParams = invalidRequest; - let result; - try { - result = await sendTransaction(apiParams); - } catch (err) { - result = err; - } finally { - expect(result).to.be.an('Error'); - expect(result.message).to.be.include( - 'The given contract address is invalid', - ); - } - }); - - it('should show error when request senderAddress is not given', async function () { - invalidRequest.senderAddress = undefined as unknown as string; - apiParams.requestParams = invalidRequest; - let result; - try { - result = await sendTransaction(apiParams); - } catch (err) { - result = err; - } finally { - expect(result).to.be.an('Error'); - expect(result.message).to.be.include( - 'The given contract address, sender address, and function name need to be non-empty string', - ); - } - }); - - it('should show error when request contractAddress is invalid', async function () { - invalidRequest.senderAddress = '0x0'; - apiParams.requestParams = invalidRequest; - let result; - try { - result = await sendTransaction(apiParams); - } catch (err) { - result = err; - } finally { - expect(result).to.be.an('Error'); - expect(result.message).to.be.include( - 'The given sender address is invalid', - ); - } - }); - - it('should show error when request contractFuncName is not given', async function () { - invalidRequest.contractFuncName = undefined as unknown as string; - apiParams.requestParams = invalidRequest; - let result; - try { - result = await sendTransaction(apiParams); - } catch (err) { - result = err; - } finally { - expect(result).to.be.an('Error'); - expect(result.message).to.be.include( - 'The given contract address, sender address, and function name need to be non-empty string', - ); - } - }); - - it('should show error when request network not found', async function () { - invalidRequest.chainId = '0x0'; - apiParams.requestParams = invalidRequest; - let result; - try { - result = await sendTransaction(apiParams); - } catch (err) { - result = err; - } finally { - expect(result).to.be.an('Error'); - } - }); - }); - - describe('when request param validation pass', function () { - beforeEach(async function () { - apiParams.requestParams = Object.assign({}, requestObject); - }); - - afterEach(async 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_SEPOLIA_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 sendTransaction(apiParams); - } catch (err) { - result = err; - } finally { - expect(isUpgradeRequiredStub).to.have.been.calledOnceWith( - STARKNET_SEPOLIA_TESTNET_NETWORK, - account1.address, - ); - expect(result).to.be.an('Error'); - } - }); - }); - - describe('when account do not require upgrade', function () { - let executeTxnResp; - let executeTxnStub: sinon.SinonStub; - beforeEach(async function () { - apiParams.requestParams = { - ...apiParams.requestParams, - senderAddress: Cairo1Account1.address, - }; - sandbox.stub(utils, 'isUpgradeRequired').resolves(false); - sandbox.stub(estimateFeeSnap, 'estimateFee').resolves({ - suggestedMaxFee: estimateFeeResp.suggestedMaxFee.toString(10), - overallFee: estimateFeeResp.overall_fee.toString(10), - unit: FeeTokenUnit.ETH, - includeDeploy: true, - }); - executeTxnResp = sendTransactionResp; - executeTxnStub = sandbox - .stub(utils, 'executeTxn') - .resolves(executeTxnResp); - walletStub.rpcStubs.snap_manageState.resolves(state); - walletStub.rpcStubs.snap_dialog.resolves(true); - sandbox - .stub(utils, 'waitForTransaction') - .resolves({} as unknown as GetTransactionReceiptResponse); - }); - - describe('when account is deployed', function () { - beforeEach(async function () { - sandbox.stub(utils, 'isAccountDeployed').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 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 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 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 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 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 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 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 use heading, text and copyable component', async function () { - executeTxnResp = sendTransactionFailedResp; - sandbox.stub(utils, 'getSigner').callsFake(async () => { - return account1.publicKey; - }); - const requestObject: SendTransactionRequestParams = { - contractAddress: account1.address, - contractFuncName: 'get_signer', - contractCallData: '**foo**', - senderAddress: account1.address, - chainId: STARKNET_SEPOLIA_TESTNET_NETWORK.chainId, - }; - 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: 'Sepolia 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, 'isAccountDeployed').resolves(false); - }); - - it('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); - }); - }); - }); - }); -}); From 2a37d5047226ac2ead536a67dd32e628997a58d3 Mon Sep 17 00:00:00 2001 From: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:52:15 +0800 Subject: [PATCH 4/6] chore: add chain rpc controller (#443) --- .../abstract/chain-rpc-controller.test.ts | 48 +++++++++++++++++++ .../src/rpcs/abstract/chain-rpc-controller.ts | 48 +++++++++++++++++++ .../src/state/__tests__/helper.ts | 2 +- 3 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 packages/starknet-snap/src/rpcs/abstract/chain-rpc-controller.test.ts create mode 100644 packages/starknet-snap/src/rpcs/abstract/chain-rpc-controller.ts diff --git a/packages/starknet-snap/src/rpcs/abstract/chain-rpc-controller.test.ts b/packages/starknet-snap/src/rpcs/abstract/chain-rpc-controller.test.ts new file mode 100644 index 00000000..b4a4d505 --- /dev/null +++ b/packages/starknet-snap/src/rpcs/abstract/chain-rpc-controller.test.ts @@ -0,0 +1,48 @@ +import { string } from 'superstruct'; + +import { mockNetworkStateManager } from '../../state/__tests__/helper'; +import { STARKNET_SEPOLIA_TESTNET_NETWORK } from '../../utils/constants'; +import { InvalidNetworkError } from '../../utils/exceptions'; +import { BaseRequestStruct } from '../../utils/superstruct'; +import { ChainRpcController } from './chain-rpc-controller'; + +describe('ChainRpcController', () => { + type Request = { chainId: string }; + class MockRpc extends ChainRpcController { + protected requestStruct = BaseRequestStruct; + + protected responseStruct = string(); + + // Set it to public to be able to spy on it + async handleRequest(params: Request) { + return `tested with ${params.chainId}`; + } + } + + it('executes request', async () => { + const network = STARKNET_SEPOLIA_TESTNET_NETWORK; + const { getNetworkSpy } = mockNetworkStateManager(network); + const { chainId } = network; + + const rpc = new MockRpc(); + const result = await rpc.execute({ + chainId, + }); + + expect(getNetworkSpy).toHaveBeenCalledWith({ chainId }); + expect(result).toBe(`tested with ${chainId}`); + }); + + it('throws `InvalidNetworkError` error if the given chainId not found.', async () => { + const network = STARKNET_SEPOLIA_TESTNET_NETWORK; + mockNetworkStateManager(null); + const { chainId } = network; + + const rpc = new MockRpc(); + await expect( + rpc.execute({ + chainId, + }), + ).rejects.toThrow(InvalidNetworkError); + }); +}); diff --git a/packages/starknet-snap/src/rpcs/abstract/chain-rpc-controller.ts b/packages/starknet-snap/src/rpcs/abstract/chain-rpc-controller.ts new file mode 100644 index 00000000..1042f8f3 --- /dev/null +++ b/packages/starknet-snap/src/rpcs/abstract/chain-rpc-controller.ts @@ -0,0 +1,48 @@ +import type { Json } from '@metamask/snaps-sdk'; + +import { NetworkStateManager } from '../../state/network-state-manager'; +import type { Network } from '../../types/snapState'; +import { InvalidNetworkError } from '../../utils/exceptions'; +import { RpcController } from '../../utils/rpc'; + +/** + * A base class for all RPC controllers that require a chainId to be provided in the request parameters. + * + * @template Request - The expected structure of the request parameters that contains the chainId property. + * @template Response - The expected structure of the response. + * @augments RpcController - The base class for all RPC controllers. + * @class ChainRpcController + */ +export abstract class ChainRpcController< + Request extends { + chainId: string; + }, + Response extends Json, +> extends RpcController { + protected network: Network; + + protected networkStateMgr: NetworkStateManager; + + constructor() { + super(); + this.networkStateMgr = new NetworkStateManager(); + } + + protected async getNetwork(chainId: string): Promise { + const network = await this.networkStateMgr.getNetwork({ chainId }); + // if the network is not in the list of networks that we support, we throw an error + if (!network) { + throw new InvalidNetworkError() as unknown as Error; + } + + return network; + } + + protected async preExecute(params: Request): Promise { + await super.preExecute(params); + + const { chainId } = params; + + this.network = await this.getNetwork(chainId); + } +} diff --git a/packages/starknet-snap/src/state/__tests__/helper.ts b/packages/starknet-snap/src/state/__tests__/helper.ts index 6f31e09c..05044039 100644 --- a/packages/starknet-snap/src/state/__tests__/helper.ts +++ b/packages/starknet-snap/src/state/__tests__/helper.ts @@ -95,7 +95,7 @@ export const mockTransactionRequestStateManager = () => { }; }; -export const mockNetworkStateManager = (network: Network) => { +export const mockNetworkStateManager = (network: Network | null) => { const getNetworkSpy = jest.spyOn(NetworkStateManager.prototype, 'getNetwork'); getNetworkSpy.mockResolvedValue(network); return { From 6d972dc595313c222551c088a23b107c5d5eaf6b Mon Sep 17 00:00:00 2001 From: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> Date: Fri, 29 Nov 2024 17:19:29 +0800 Subject: [PATCH 5/6] chore: relocate base RPC controllers (#444) * chore: add chain rpc controller * chore: relocate base rpc controller --- .../abstract/account-rpc-controller.test.ts | 126 +++++++++++++++ .../rpcs/abstract/account-rpc-controller.ts | 86 ++++++++++ .../rpcs/abstract/base-rpc-controller.test.ts | 26 +++ .../src/rpcs/abstract/base-rpc-controller.ts | 51 ++++++ .../src/rpcs/abstract/chain-rpc-controller.ts | 2 +- .../src/rpcs/declare-contract.ts | 2 +- .../src/rpcs/display-private-key.ts | 7 +- .../starknet-snap/src/rpcs/estimate-fee.ts | 2 +- .../starknet-snap/src/rpcs/execute-txn.ts | 4 +- .../src/rpcs/get-deployment-data.ts | 8 +- .../src/rpcs/sign-declare-transaction.ts | 2 +- .../starknet-snap/src/rpcs/sign-message.ts | 2 +- .../src/rpcs/sign-transaction.ts | 2 +- .../starknet-snap/src/rpcs/switch-network.ts | 3 +- .../src/rpcs/verify-signature.ts | 8 +- .../starknet-snap/src/rpcs/watch-asset.ts | 2 +- packages/starknet-snap/src/utils/rpc.test.ts | 152 +----------------- packages/starknet-snap/src/utils/rpc.ts | 119 -------------- 18 files changed, 309 insertions(+), 295 deletions(-) create mode 100644 packages/starknet-snap/src/rpcs/abstract/account-rpc-controller.test.ts create mode 100644 packages/starknet-snap/src/rpcs/abstract/account-rpc-controller.ts create mode 100644 packages/starknet-snap/src/rpcs/abstract/base-rpc-controller.test.ts create mode 100644 packages/starknet-snap/src/rpcs/abstract/base-rpc-controller.ts diff --git a/packages/starknet-snap/src/rpcs/abstract/account-rpc-controller.test.ts b/packages/starknet-snap/src/rpcs/abstract/account-rpc-controller.test.ts new file mode 100644 index 00000000..6a64ca91 --- /dev/null +++ b/packages/starknet-snap/src/rpcs/abstract/account-rpc-controller.test.ts @@ -0,0 +1,126 @@ +import { constants } from 'starknet'; +import { object, string } from 'superstruct'; +import type { Infer } from 'superstruct'; + +import type { StarknetAccount } from '../../__tests__/helper'; +import { generateAccounts } from '../../__tests__/helper'; +import type { SnapState } from '../../types/snapState'; +import { STARKNET_SEPOLIA_TESTNET_NETWORK } from '../../utils/constants'; +import * as snapHelper from '../../utils/snap'; +import * as snapUtils from '../../utils/snapUtils'; +import * as starknetUtils from '../../utils/starknetUtils'; +import { AccountRpcController } from './account-rpc-controller'; + +jest.mock('../../utils/snap'); +jest.mock('../../utils/logger'); + +describe('AccountRpcController', () => { + const state: SnapState = { + accContracts: [], + erc20Tokens: [], + networks: [STARKNET_SEPOLIA_TESTNET_NETWORK], + transactions: [], + }; + + const RequestStruct = object({ + address: string(), + chainId: string(), + }); + + type Request = Infer; + + class MockAccountRpc extends AccountRpcController { + protected requestStruct = RequestStruct; + + protected responseStruct = string(); + + // Set it to public to be able to spy on it + async handleRequest(param: Request) { + return `done ${param.address} and ${param.chainId}`; + } + } + + const mockAccount = async (network: constants.StarknetChainId) => { + const accounts = await generateAccounts(network, 1); + return accounts[0]; + }; + + const prepareExecute = async (account: StarknetAccount) => { + const verifyIfAccountNeedUpgradeOrDeploySpy = jest.spyOn( + snapUtils, + 'verifyIfAccountNeedUpgradeOrDeploy', + ); + + const getKeysFromAddressSpy = jest.spyOn( + starknetUtils, + 'getKeysFromAddress', + ); + + const getStateDataSpy = jest.spyOn(snapHelper, 'getStateData'); + + getStateDataSpy.mockResolvedValue(state); + + getKeysFromAddressSpy.mockResolvedValue({ + privateKey: account.privateKey, + publicKey: account.publicKey, + addressIndex: account.addressIndex, + derivationPath: account.derivationPath as unknown as any, + }); + + verifyIfAccountNeedUpgradeOrDeploySpy.mockReturnThis(); + + return { + getKeysFromAddressSpy, + getStateDataSpy, + verifyIfAccountNeedUpgradeOrDeploySpy, + }; + }; + + it('executes request', async () => { + const chainId = constants.StarknetChainId.SN_SEPOLIA; + const account = await mockAccount(chainId); + await prepareExecute(account); + const rpc = new MockAccountRpc(); + + const result = await rpc.execute({ + address: account.address, + chainId, + }); + + expect(result).toBe(`done ${account.address} and ${chainId}`); + }); + + it('fetchs account before execute', async () => { + const chainId = constants.StarknetChainId.SN_SEPOLIA; + const account = await mockAccount(chainId); + const { getKeysFromAddressSpy } = await prepareExecute(account); + const rpc = new MockAccountRpc(); + + await rpc.execute({ address: account.address, chainId }); + + expect(getKeysFromAddressSpy).toHaveBeenCalled(); + }); + + it.each([true, false])( + `assign verifyIfAccountNeedUpgradeOrDeploy's argument "showAlert" to %s if the constructor option 'showInvalidAccountAlert' is set to %s`, + async (showInvalidAccountAlert: boolean) => { + const chainId = constants.StarknetChainId.SN_SEPOLIA; + const account = await mockAccount(chainId); + const { verifyIfAccountNeedUpgradeOrDeploySpy } = await prepareExecute( + account, + ); + const rpc = new MockAccountRpc({ + showInvalidAccountAlert, + }); + + await rpc.execute({ address: account.address, chainId }); + + expect(verifyIfAccountNeedUpgradeOrDeploySpy).toHaveBeenCalledWith( + expect.any(Object), + account.address, + account.publicKey, + showInvalidAccountAlert, + ); + }, + ); +}); diff --git a/packages/starknet-snap/src/rpcs/abstract/account-rpc-controller.ts b/packages/starknet-snap/src/rpcs/abstract/account-rpc-controller.ts new file mode 100644 index 00000000..d02ed372 --- /dev/null +++ b/packages/starknet-snap/src/rpcs/abstract/account-rpc-controller.ts @@ -0,0 +1,86 @@ +import type { getBIP44ChangePathString } from '@metamask/key-tree/dist/types/utils'; +import type { Json } from '@metamask/snaps-sdk'; + +import type { Network, SnapState } from '../../types/snapState'; +import { getBip44Deriver, getStateData } from '../../utils'; +import { + getNetworkFromChainId, + verifyIfAccountNeedUpgradeOrDeploy, +} from '../../utils/snapUtils'; +import { getKeysFromAddress } from '../../utils/starknetUtils'; +import { RpcController } from './base-rpc-controller'; + +export type AccountRpcParams = { + chainId: string; + address: string; +}; + +// TODO: the Account object should move into a account manager for generate account +export type Account = { + privateKey: string; + publicKey: string; + addressIndex: number; + // This is the derivation path of the address, it is used in `getNextAddressIndex` to find the account in state where matching the same derivation path + derivationPath: ReturnType; +}; + +export type AccountRpcControllerOptions = { + showInvalidAccountAlert: boolean; +}; + +/** + * A base class for rpc controllers that require account discovery. + * + * @template Request - The expected structure of the request parameters. + * @template Response - The expected structure of the response. + * @class AccountRpcController + */ +export abstract class AccountRpcController< + Request extends AccountRpcParams, + Response extends Json, +> extends RpcController { + protected account: Account; + + protected network: Network; + + protected options: AccountRpcControllerOptions; + + protected defaultOptions: AccountRpcControllerOptions = { + showInvalidAccountAlert: true, + }; + + constructor(options?: AccountRpcControllerOptions) { + super(); + this.options = Object.assign({}, this.defaultOptions, options); + } + + protected async preExecute(params: Request): Promise { + await super.preExecute(params); + + const { chainId, address } = params; + const { showInvalidAccountAlert } = this.options; + + const deriver = await getBip44Deriver(); + // TODO: Instead of getting the state directly, we should implement state management to consolidate the state fetching + const state = await getStateData(); + + // TODO: getNetworkFromChainId from state is still needed, due to it is supporting in get-starknet at this moment + this.network = getNetworkFromChainId(state, chainId); + + // TODO: This method should be refactored to get the account from an account manager + this.account = await getKeysFromAddress( + deriver, + this.network, + state, + address, + ); + + // TODO: rename this method to verifyAccount + await verifyIfAccountNeedUpgradeOrDeploy( + this.network, + address, + this.account.publicKey, + showInvalidAccountAlert, + ); + } +} diff --git a/packages/starknet-snap/src/rpcs/abstract/base-rpc-controller.test.ts b/packages/starknet-snap/src/rpcs/abstract/base-rpc-controller.test.ts new file mode 100644 index 00000000..4646af23 --- /dev/null +++ b/packages/starknet-snap/src/rpcs/abstract/base-rpc-controller.test.ts @@ -0,0 +1,26 @@ +import { string } from 'superstruct'; + +import { RpcController } from './base-rpc-controller'; + +jest.mock('../../utils/logger'); + +describe('RpcController', () => { + class MockRpc extends RpcController { + protected requestStruct = string(); + + protected responseStruct = string(); + + // Set it to public to be able to spy on it + async handleRequest(params: string) { + return `done ${params}`; + } + } + + it('executes request', async () => { + const rpc = new MockRpc(); + + const result = await rpc.execute('test'); + + expect(result).toBe('done test'); + }); +}); diff --git a/packages/starknet-snap/src/rpcs/abstract/base-rpc-controller.ts b/packages/starknet-snap/src/rpcs/abstract/base-rpc-controller.ts new file mode 100644 index 00000000..1a4c7914 --- /dev/null +++ b/packages/starknet-snap/src/rpcs/abstract/base-rpc-controller.ts @@ -0,0 +1,51 @@ +import type { Json } from '@metamask/snaps-sdk'; +import type { Struct } from 'superstruct'; + +import { logger, validateRequest, validateResponse } from '../../utils'; + +/** + * A base class for rpc controllers. + * + * @template Request - The expected structure of the request parameters. + * @template Response - The expected structure of the response. + * @class RpcController + */ +export abstract class RpcController< + Request extends Json, + Response extends Json, +> { + /** + * Superstruct for the request. + */ + protected abstract requestStruct: Struct; + + /** + * Superstruct for the response. + */ + protected abstract responseStruct: Struct; + + protected abstract handleRequest(params: Request): Promise; + + protected async preExecute(params: Request): Promise { + logger.info(`Request: ${JSON.stringify(params)}`); + validateRequest(params, this.requestStruct); + } + + protected async postExecute(response: Response): Promise { + logger.info(`Response: ${JSON.stringify(response)}`); + validateResponse(response, this.responseStruct); + } + + /** + * A method to execute the rpc method. + * + * @param params - An struct contains the require parameter for the request. + * @returns A promise that resolves to an json. + */ + async execute(params: Request): Promise { + await this.preExecute(params); + const resp = await this.handleRequest(params); + await this.postExecute(resp); + return resp; + } +} diff --git a/packages/starknet-snap/src/rpcs/abstract/chain-rpc-controller.ts b/packages/starknet-snap/src/rpcs/abstract/chain-rpc-controller.ts index 1042f8f3..c873591d 100644 --- a/packages/starknet-snap/src/rpcs/abstract/chain-rpc-controller.ts +++ b/packages/starknet-snap/src/rpcs/abstract/chain-rpc-controller.ts @@ -3,7 +3,7 @@ import type { Json } from '@metamask/snaps-sdk'; import { NetworkStateManager } from '../../state/network-state-manager'; import type { Network } from '../../types/snapState'; import { InvalidNetworkError } from '../../utils/exceptions'; -import { RpcController } from '../../utils/rpc'; +import { RpcController } from './base-rpc-controller'; /** * A base class for all RPC controllers that require a chainId to be provided in the request parameters. diff --git a/packages/starknet-snap/src/rpcs/declare-contract.ts b/packages/starknet-snap/src/rpcs/declare-contract.ts index 03d06bba..c6a7b48d 100644 --- a/packages/starknet-snap/src/rpcs/declare-contract.ts +++ b/packages/starknet-snap/src/rpcs/declare-contract.ts @@ -10,7 +10,6 @@ import { mapDeprecatedParams, UniversalDetailsStruct, confirmDialog, - AccountRpcController, signerUI, networkUI, rowUI, @@ -19,6 +18,7 @@ import { } from '../utils'; import { UserRejectedOpError } from '../utils/exceptions'; import { declareContract as declareContractUtil } from '../utils/starknetUtils'; +import { AccountRpcController } from './abstract/account-rpc-controller'; // Define the DeclareContractRequestStruct export const DeclareContractRequestStruct = assign( diff --git a/packages/starknet-snap/src/rpcs/display-private-key.ts b/packages/starknet-snap/src/rpcs/display-private-key.ts index bef32db8..3a625a6e 100644 --- a/packages/starknet-snap/src/rpcs/display-private-key.ts +++ b/packages/starknet-snap/src/rpcs/display-private-key.ts @@ -4,12 +4,9 @@ import { renderDisplayPrivateKeyAlertUI, renderDisplayPrivateKeyConfirmUI, } from '../ui/utils'; -import { - AccountRpcController, - AddressStruct, - BaseRequestStruct, -} from '../utils'; +import { AddressStruct, BaseRequestStruct } from '../utils'; import { UserRejectedOpError } from '../utils/exceptions'; +import { AccountRpcController } from './abstract/account-rpc-controller'; export const DisplayPrivateKeyRequestStruct = assign( object({ diff --git a/packages/starknet-snap/src/rpcs/estimate-fee.ts b/packages/starknet-snap/src/rpcs/estimate-fee.ts index e36df47c..b7217d14 100644 --- a/packages/starknet-snap/src/rpcs/estimate-fee.ts +++ b/packages/starknet-snap/src/rpcs/estimate-fee.ts @@ -6,11 +6,11 @@ import { FeeTokenUnit } from '../types/snapApi'; import { AddressStruct, BaseRequestStruct, - AccountRpcController, UniversalDetailsStruct, InvocationsStruct, } from '../utils'; import { getEstimatedFees } from '../utils/starknetUtils'; +import { AccountRpcController } from './abstract/account-rpc-controller'; export const EstimateFeeRequestStruct = assign( object({ diff --git a/packages/starknet-snap/src/rpcs/execute-txn.ts b/packages/starknet-snap/src/rpcs/execute-txn.ts index db31752c..75a6eeab 100644 --- a/packages/starknet-snap/src/rpcs/execute-txn.ts +++ b/packages/starknet-snap/src/rpcs/execute-txn.ts @@ -13,11 +13,9 @@ import { FeeToken } from '../types/snapApi'; import type { TransactionRequest } from '../types/snapState'; import { VoyagerTransactionType, type Transaction } from '../types/snapState'; import { generateExecuteTxnFlow } from '../ui/utils'; -import type { AccountRpcControllerOptions } from '../utils'; import { AddressStruct, BaseRequestStruct, - AccountRpcController, UniversalDetailsStruct, CallsStruct, mapDeprecatedParams, @@ -30,6 +28,8 @@ import { executeTxn as executeTxnUtil, getEstimatedFees, } from '../utils/starknetUtils'; +import type { AccountRpcControllerOptions } from './abstract/account-rpc-controller'; +import { AccountRpcController } from './abstract/account-rpc-controller'; export const ExecuteTxnRequestStruct = assign( object({ diff --git a/packages/starknet-snap/src/rpcs/get-deployment-data.ts b/packages/starknet-snap/src/rpcs/get-deployment-data.ts index 8206db31..ac8fcc29 100644 --- a/packages/starknet-snap/src/rpcs/get-deployment-data.ts +++ b/packages/starknet-snap/src/rpcs/get-deployment-data.ts @@ -1,18 +1,14 @@ import type { Infer } from 'superstruct'; import { object, string, assign, array } from 'superstruct'; -import { - AddressStruct, - BaseRequestStruct, - AccountRpcController, - CairoVersionStruct, -} from '../utils'; +import { AddressStruct, BaseRequestStruct, CairoVersionStruct } from '../utils'; import { ACCOUNT_CLASS_HASH, CAIRO_VERSION } from '../utils/constants'; import { AccountAlreadyDeployedError } from '../utils/exceptions'; import { getDeployAccountCallData, isAccountDeployed, } from '../utils/starknetUtils'; +import { AccountRpcController } from './abstract/account-rpc-controller'; export const GetDeploymentDataRequestStruct = assign( object({ diff --git a/packages/starknet-snap/src/rpcs/sign-declare-transaction.ts b/packages/starknet-snap/src/rpcs/sign-declare-transaction.ts index aa1fbf15..cc5d6cfe 100644 --- a/packages/starknet-snap/src/rpcs/sign-declare-transaction.ts +++ b/packages/starknet-snap/src/rpcs/sign-declare-transaction.ts @@ -6,12 +6,12 @@ import { renderSignDeclareTransactionUI } from '../ui/utils'; import { AddressStruct, BaseRequestStruct, - AccountRpcController, DeclareSignDetailsStruct, mapDeprecatedParams, } from '../utils'; import { UserRejectedOpError } from '../utils/exceptions'; import { signDeclareTransaction as signDeclareTransactionUtil } from '../utils/starknetUtils'; +import { AccountRpcController } from './abstract/account-rpc-controller'; export const SignDeclareTransactionRequestStruct = assign( object({ diff --git a/packages/starknet-snap/src/rpcs/sign-message.ts b/packages/starknet-snap/src/rpcs/sign-message.ts index 6b4387cb..794634ab 100644 --- a/packages/starknet-snap/src/rpcs/sign-message.ts +++ b/packages/starknet-snap/src/rpcs/sign-message.ts @@ -7,11 +7,11 @@ import { TypeDataStruct, AuthorizableStruct, BaseRequestStruct, - AccountRpcController, mapDeprecatedParams, } from '../utils'; import { UserRejectedOpError } from '../utils/exceptions'; import { signMessage as signMessageUtil } from '../utils/starknetUtils'; +import { AccountRpcController } from './abstract/account-rpc-controller'; export const SignMessageRequestStruct = assign( object({ diff --git a/packages/starknet-snap/src/rpcs/sign-transaction.ts b/packages/starknet-snap/src/rpcs/sign-transaction.ts index 24b152e9..d9954e3b 100644 --- a/packages/starknet-snap/src/rpcs/sign-transaction.ts +++ b/packages/starknet-snap/src/rpcs/sign-transaction.ts @@ -7,12 +7,12 @@ import { AddressStruct, AuthorizableStruct, BaseRequestStruct, - AccountRpcController, CallDataStruct, mapDeprecatedParams, } from '../utils'; import { UserRejectedOpError } from '../utils/exceptions'; import { signTransactions } from '../utils/starknetUtils'; +import { AccountRpcController } from './abstract/account-rpc-controller'; export const SignTransactionRequestStruct = assign( object({ diff --git a/packages/starknet-snap/src/rpcs/switch-network.ts b/packages/starknet-snap/src/rpcs/switch-network.ts index ae94048e..bb039885 100644 --- a/packages/starknet-snap/src/rpcs/switch-network.ts +++ b/packages/starknet-snap/src/rpcs/switch-network.ts @@ -3,8 +3,9 @@ import { assign, boolean } from 'superstruct'; import { NetworkStateManager } from '../state/network-state-manager'; import { renderSwitchNetworkUI } from '../ui/utils'; -import { AuthorizableStruct, BaseRequestStruct, RpcController } from '../utils'; +import { AuthorizableStruct, BaseRequestStruct } from '../utils'; import { InvalidNetworkError, UserRejectedOpError } from '../utils/exceptions'; +import { RpcController } from './abstract/base-rpc-controller'; export const SwitchNetworkRequestStruct = assign( AuthorizableStruct, diff --git a/packages/starknet-snap/src/rpcs/verify-signature.ts b/packages/starknet-snap/src/rpcs/verify-signature.ts index 5ce845e9..0eefd6fa 100644 --- a/packages/starknet-snap/src/rpcs/verify-signature.ts +++ b/packages/starknet-snap/src/rpcs/verify-signature.ts @@ -2,13 +2,9 @@ import { HexStruct } from '@metamask/utils'; import type { Infer } from 'superstruct'; import { object, assign, boolean, array } from 'superstruct'; -import { - AddressStruct, - TypeDataStruct, - BaseRequestStruct, - AccountRpcController, -} from '../utils'; +import { AddressStruct, TypeDataStruct, BaseRequestStruct } from '../utils'; import { verifyTypedDataMessageSignature } from '../utils/starknetUtils'; +import { AccountRpcController } from './abstract/account-rpc-controller'; export const VerifySignatureRequestStruct = assign( object({ diff --git a/packages/starknet-snap/src/rpcs/watch-asset.ts b/packages/starknet-snap/src/rpcs/watch-asset.ts index aa825d4e..d3582e11 100644 --- a/packages/starknet-snap/src/rpcs/watch-asset.ts +++ b/packages/starknet-snap/src/rpcs/watch-asset.ts @@ -7,7 +7,6 @@ import type { Erc20Token, Network } from '../types/snapState'; import { renderWatchAssetUI } from '../ui/utils'; import { BaseRequestStruct, - RpcController, AddressStruct, TokenNameStruct, TokenSymbolStruct, @@ -20,6 +19,7 @@ import { UserRejectedOpError, } from '../utils/exceptions'; import { getValidNumber } from '../utils/snapUtils'; +import { RpcController } from './abstract/base-rpc-controller'; export const WatchAssetRequestStruct = assign( object({ diff --git a/packages/starknet-snap/src/utils/rpc.test.ts b/packages/starknet-snap/src/utils/rpc.test.ts index d8f01ad7..1880bb72 100644 --- a/packages/starknet-snap/src/utils/rpc.test.ts +++ b/packages/starknet-snap/src/utils/rpc.test.ts @@ -1,24 +1,10 @@ -import { constants } from 'starknet'; -import { object, string } from 'superstruct'; -import type { Struct, Infer } from 'superstruct'; +import { object } from 'superstruct'; +import type { Struct } from 'superstruct'; -import type { StarknetAccount } from '../__tests__/helper'; -import { generateAccounts } from '../__tests__/helper'; -import type { SnapState } from '../types/snapState'; -import { STARKNET_SEPOLIA_TESTNET_NETWORK } from './constants'; import { InvalidRequestParamsError, UnknownError } from './exceptions'; -import { - AccountRpcController, - RpcController, - validateRequest, - validateResponse, -} from './rpc'; -import * as snapHelper from './snap'; -import * as snapUtils from './snapUtils'; -import * as starknetUtils from './starknetUtils'; +import { validateRequest, validateResponse } from './rpc'; import { AddressStruct } from './superstruct'; -jest.mock('./snap'); jest.mock('./logger'); const validateStruct = object({ @@ -65,135 +51,3 @@ describe('validateResponse', () => { ).toThrow(new UnknownError('Invalid Response')); }); }); - -describe('RpcController', () => { - class MockRpc extends RpcController { - protected requestStruct = string(); - - protected responseStruct = string(); - - // Set it to public to be able to spy on it - async handleRequest(params: string) { - return `done ${params}`; - } - } - - it('executes request', async () => { - const rpc = new MockRpc(); - - const result = await rpc.execute('test'); - - expect(result).toBe('done test'); - }); -}); - -describe('AccountRpcController', () => { - const state: SnapState = { - accContracts: [], - erc20Tokens: [], - networks: [STARKNET_SEPOLIA_TESTNET_NETWORK], - transactions: [], - }; - - const RequestStruct = object({ - address: string(), - chainId: string(), - }); - - type Request = Infer; - - class MockAccountRpc extends AccountRpcController { - protected requestStruct = RequestStruct; - - protected responseStruct = string(); - - // Set it to public to be able to spy on it - async handleRequest(param: Request) { - return `done ${param.address} and ${param.chainId}`; - } - } - - const mockAccount = async (network: constants.StarknetChainId) => { - const accounts = await generateAccounts(network, 1); - return accounts[0]; - }; - - const prepareExecute = async (account: StarknetAccount) => { - const verifyIfAccountNeedUpgradeOrDeploySpy = jest.spyOn( - snapUtils, - 'verifyIfAccountNeedUpgradeOrDeploy', - ); - - const getKeysFromAddressSpy = jest.spyOn( - starknetUtils, - 'getKeysFromAddress', - ); - - const getStateDataSpy = jest.spyOn(snapHelper, 'getStateData'); - - getStateDataSpy.mockResolvedValue(state); - - getKeysFromAddressSpy.mockResolvedValue({ - privateKey: account.privateKey, - publicKey: account.publicKey, - addressIndex: account.addressIndex, - derivationPath: account.derivationPath as unknown as any, - }); - - verifyIfAccountNeedUpgradeOrDeploySpy.mockReturnThis(); - - return { - getKeysFromAddressSpy, - getStateDataSpy, - verifyIfAccountNeedUpgradeOrDeploySpy, - }; - }; - - it('executes request', async () => { - const chainId = constants.StarknetChainId.SN_SEPOLIA; - const account = await mockAccount(chainId); - await prepareExecute(account); - const rpc = new MockAccountRpc(); - - const result = await rpc.execute({ - address: account.address, - chainId, - }); - - expect(result).toBe(`done ${account.address} and ${chainId}`); - }); - - it('fetchs account before execute', async () => { - const chainId = constants.StarknetChainId.SN_SEPOLIA; - const account = await mockAccount(chainId); - const { getKeysFromAddressSpy } = await prepareExecute(account); - const rpc = new MockAccountRpc(); - - await rpc.execute({ address: account.address, chainId }); - - expect(getKeysFromAddressSpy).toHaveBeenCalled(); - }); - - it.each([true, false])( - `assign verifyIfAccountNeedUpgradeOrDeploy's argument "showAlert" to %s if the constructor option 'showInvalidAccountAlert' is set to %s`, - async (showInvalidAccountAlert: boolean) => { - const chainId = constants.StarknetChainId.SN_SEPOLIA; - const account = await mockAccount(chainId); - const { verifyIfAccountNeedUpgradeOrDeploySpy } = await prepareExecute( - account, - ); - const rpc = new MockAccountRpc({ - showInvalidAccountAlert, - }); - - await rpc.execute({ address: account.address, chainId }); - - expect(verifyIfAccountNeedUpgradeOrDeploySpy).toHaveBeenCalledWith( - expect.any(Object), - account.address, - account.publicKey, - showInvalidAccountAlert, - ); - }, - ); -}); diff --git a/packages/starknet-snap/src/utils/rpc.ts b/packages/starknet-snap/src/utils/rpc.ts index 4d88e347..518b102a 100644 --- a/packages/starknet-snap/src/utils/rpc.ts +++ b/packages/starknet-snap/src/utils/rpc.ts @@ -1,17 +1,7 @@ -import type { getBIP44ChangePathString } from '@metamask/key-tree/dist/types/utils'; -import type { Json } from '@metamask/snaps-sdk'; import type { Struct } from 'superstruct'; import { assert } from 'superstruct'; -import type { Network, SnapState } from '../types/snapState'; import { InvalidRequestParamsError, UnknownError } from './exceptions'; -import { logger } from './logger'; -import { getBip44Deriver, getStateData } from './snap'; -import { - getNetworkFromChainId, - verifyIfAccountNeedUpgradeOrDeploy, -} from './snapUtils'; -import { getKeysFromAddress } from './starknetUtils'; /** * Validates that the request parameters conform to the expected structure defined by the provided struct. @@ -44,112 +34,3 @@ export function validateResponse(response: Params, struct: Struct) { throw new UnknownError('Invalid Response') as unknown as Error; } } - -export abstract class RpcController< - Request extends Json, - Response extends Json, -> { - /** - * Superstruct for the request. - */ - protected abstract requestStruct: Struct; - - /** - * Superstruct for the response. - */ - protected abstract responseStruct: Struct; - - protected abstract handleRequest(params: Request): Promise; - - protected async preExecute(params: Request): Promise { - logger.info(`Request: ${JSON.stringify(params)}`); - validateRequest(params, this.requestStruct); - } - - protected async postExecute(response: Response): Promise { - logger.info(`Response: ${JSON.stringify(response)}`); - validateResponse(response, this.responseStruct); - } - - /** - * A method to execute the rpc method. - * - * @param params - An struct contains the require parameter for the request. - * @returns A promise that resolves to an json. - */ - async execute(params: Request): Promise { - await this.preExecute(params); - const resp = await this.handleRequest(params); - await this.postExecute(resp); - return resp; - } -} - -// TODO: the Type should be moved to a common place -export type AccountRpcParams = { - chainId: string; - address: string; -}; - -// TODO: the Account object should move into a account manager for generate account -export type Account = { - privateKey: string; - publicKey: string; - addressIndex: number; - // This is the derivation path of the address, it is used in `getNextAddressIndex` to find the account in state where matching the same derivation path - derivationPath: ReturnType; -}; - -export type AccountRpcControllerOptions = { - showInvalidAccountAlert: boolean; -}; - -export abstract class AccountRpcController< - Request extends AccountRpcParams, - Response extends Json, -> extends RpcController { - protected account: Account; - - protected network: Network; - - protected options: AccountRpcControllerOptions; - - protected defaultOptions: AccountRpcControllerOptions = { - showInvalidAccountAlert: true, - }; - - constructor(options?: AccountRpcControllerOptions) { - super(); - this.options = Object.assign({}, this.defaultOptions, options); - } - - protected async preExecute(params: Request): Promise { - await super.preExecute(params); - - const { chainId, address } = params; - const { showInvalidAccountAlert } = this.options; - - const deriver = await getBip44Deriver(); - // TODO: Instead of getting the state directly, we should implement state management to consolidate the state fetching - const state = await getStateData(); - - // TODO: getNetworkFromChainId from state is still needed, due to it is supporting in get-starknet at this moment - this.network = getNetworkFromChainId(state, chainId); - - // TODO: This method should be refactored to get the account from an account manager - this.account = await getKeysFromAddress( - deriver, - this.network, - state, - address, - ); - - // TODO: rename this method to verifyAccount - await verifyIfAccountNeedUpgradeOrDeploy( - this.network, - address, - this.account.publicKey, - showInvalidAccountAlert, - ); - } -} From 464d65c0e3d181efe76048d7dbfffc2f88492ecd Mon Sep 17 00:00:00 2001 From: khanti42 Date: Mon, 2 Dec 2024 03:44:14 +0100 Subject: [PATCH 6/6] chore: update chunkFilename (#445) --- packages/get-starknet/webpack.config.build.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/get-starknet/webpack.config.build.js b/packages/get-starknet/webpack.config.build.js index 22412e2b..727c05dd 100644 --- a/packages/get-starknet/webpack.config.build.js +++ b/packages/get-starknet/webpack.config.build.js @@ -18,6 +18,8 @@ module.exports = (env) => merge(common, { mode: 'production', output: { + filename: '[name].[contenthash].js?v=[fullhash]', // Appends a cache-busting query string + chunkFilename: '[name].[contenthash].js?v=[fullhash]', // For dynamically imported chunks publicPath: process.env.GET_STARKNET_PUBLIC_PATH || 'https://snaps.consensys.io/starknet/get-starknet/v1/', }, plugins: [