From 0c911420595193672885d97d12769570a96316ce Mon Sep 17 00:00:00 2001 From: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> Date: Tue, 11 Jun 2024 17:50:27 +0800 Subject: [PATCH] feat: add waiting mode for create account (#251) * feat: add waiting mode for create account * chore: add read me for waitmode --- packages/starknet-snap/snap.manifest.json | 2 +- packages/starknet-snap/src/createAccount.ts | 14 +++++++++- packages/starknet-snap/src/sendTransaction.ts | 2 +- packages/starknet-snap/src/utils/snapUtils.ts | 2 +- .../starknet-snap/src/utils/starknetUtils.ts | 14 ++++++++++ .../test/src/createAccount.test.ts | 26 ++++++++++++++++++- .../test/src/sendTransaction.test.ts | 2 ++ .../test/utils/snapUtils.test.ts | 10 ++++--- .../test/utils/starknetUtils.test.ts | 19 +++++++++++++- yarn.lock | 4 +-- 10 files changed, 84 insertions(+), 11 deletions(-) diff --git a/packages/starknet-snap/snap.manifest.json b/packages/starknet-snap/snap.manifest.json index fcb52269..f5285026 100644 --- a/packages/starknet-snap/snap.manifest.json +++ b/packages/starknet-snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/ConsenSys/starknet-snap.git" }, "source": { - "shasum": "trDDU4ANftSs6SE0RJK3uXaxRnCBq7fvgptzA280SPA=", + "shasum": "vVFyA2r3PuADhGuwrnvREDPCLnSidbQzT3lxf9uzvOA=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/starknet-snap/src/createAccount.ts b/packages/starknet-snap/src/createAccount.ts index 033bba30..735fdc2f 100644 --- a/packages/starknet-snap/src/createAccount.ts +++ b/packages/starknet-snap/src/createAccount.ts @@ -6,6 +6,7 @@ import { callContract, estimateAccountDeployFee, getSigner, + waitForTransaction, } from './utils/starknetUtils'; import { getEtherErc20Token, @@ -23,7 +24,14 @@ import { DialogType } from '@metamask/rpc-methods'; import { heading, panel, text } from '@metamask/snaps-sdk'; import { logger } from './utils/logger'; -export async function createAccount(params: ApiParams, silentMode = false) { +/** + * Create an starknet account. + * + * @template Params - The ApiParams of the request. + * @param silentMode - The flag to disable the confirmation dialog from snap. + * @param waitMode - The flag to enable an determination by doing an recursive fetch to check if the deploy account status is on L2 or not. The wait mode is only useful when it compose with other txn together, it can make sure the deploy txn execute complete, avoiding the latter txn failed. + */ +export async function createAccount(params: ApiParams, silentMode = false, waitMode = false) { try { const { state, wallet, saveMutex, keyDeriver, requestParams } = params; const requestParamsObj = requestParams as CreateAccountRequestParams; @@ -148,6 +156,10 @@ export async function createAccount(params: ApiParams, silentMode = false) { logger.log(`createAccount:\ndeployResp: ${toJson(deployResp)}`); + if (waitMode) { + await waitForTransaction(network, deployResp.contract_address, privateKey, deployResp.transaction_hash); + } + return { address: deployResp.contract_address, transaction_hash: deployResp.transaction_hash, diff --git a/packages/starknet-snap/src/sendTransaction.ts b/packages/starknet-snap/src/sendTransaction.ts index 39e6e960..219c37f3 100644 --- a/packages/starknet-snap/src/sendTransaction.ts +++ b/packages/starknet-snap/src/sendTransaction.ts @@ -92,7 +92,7 @@ export async function sendTransaction(params: ApiParams) { chainId: requestParamsObj.chainId, }, }; - await createAccount(createAccountApiParams, true); + 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 diff --git a/packages/starknet-snap/src/utils/snapUtils.ts b/packages/starknet-snap/src/utils/snapUtils.ts index 8c477c17..ff873d1d 100644 --- a/packages/starknet-snap/src/utils/snapUtils.ts +++ b/packages/starknet-snap/src/utils/snapUtils.ts @@ -537,7 +537,7 @@ export function getRPCUrl(chainId: string) { } export function getRPCCredentials(): string { - return process.env.ALCHEMY_API_KEY ?? '' + return process.env.ALCHEMY_API_KEY ?? ''; } export function getVoyagerUrl(chainId: string) { diff --git a/packages/starknet-snap/src/utils/starknetUtils.ts b/packages/starknet-snap/src/utils/starknetUtils.ts index e1df0492..920f9aeb 100644 --- a/packages/starknet-snap/src/utils/starknetUtils.ts +++ b/packages/starknet-snap/src/utils/starknetUtils.ts @@ -33,6 +33,8 @@ import { DeployAccountSignerDetails, InvocationsSignerDetails, ProviderInterface, + CairoVersion, + GetTransactionReceiptResponse, } from 'starknet'; import { Network, SnapState, Transaction, TransactionType } from '../types/snapState'; import { ACCOUNT_CLASS_HASH_V0, PROXY_CONTRACT_HASH, TRANSFER_SELECTOR_HEX } from './constants'; @@ -108,6 +110,18 @@ export const estimateFee = async ( }); }; +export const waitForTransaction = async ( + network: Network, + senderAddress: string, + privateKey: string | Uint8Array, + txnHash: num.BigNumberish, + cairoVersion?: CairoVersion, +): Promise => { + const provider = getProvider(network); + const account = new Account(provider, senderAddress, privateKey, cairoVersion ?? '0'); + return account.waitForTransaction(txnHash); +}; + export const estimateFeeBulk = async ( network: Network, senderAddress: string, diff --git a/packages/starknet-snap/test/src/createAccount.test.ts b/packages/starknet-snap/test/src/createAccount.test.ts index 8fa80403..2680dc5a 100644 --- a/packages/starknet-snap/test/src/createAccount.test.ts +++ b/packages/starknet-snap/test/src/createAccount.test.ts @@ -24,6 +24,7 @@ import { import { getAddressKeyDeriver } from '../../src/utils/keyPair'; import { Mutex } from 'async-mutex'; import { ApiParams, CreateAccountRequestParams } from '../../src/types/snapApi'; +import { GetTransactionReceiptResponse } from 'starknet'; chai.use(sinonChai); const sandbox = sinon.createSandbox(); @@ -31,6 +32,7 @@ const sandbox = sinon.createSandbox(); describe('Test function: createAccount', function () { this.timeout(10000); const walletStub = new WalletMock(); + let waitForTransactionStub; const state: SnapState = { accContracts: [], erc20Tokens: [], @@ -50,6 +52,8 @@ describe('Test function: createAccount', function () { sandbox.useFakeTimers(createAccountProxyTxn.timestamp); walletStub.rpcStubs.snap_dialog.resolves(true); walletStub.rpcStubs.snap_manageState.resolves(state); + waitForTransactionStub = sandbox.stub(utils, 'waitForTransaction'); + waitForTransactionStub.resolves({} as unknown as GetTransactionReceiptResponse); }); afterEach(function () { @@ -77,6 +81,27 @@ describe('Test function: createAccount', function () { expect(state.transactions.length).to.be.eq(0); }); + it('waits for tansaction after an account has deployed', async function () { + sandbox.stub(utils, 'deployAccount').callsFake(async () => { + return createAccountProxyMainnetResp; + }); + sandbox.stub(utils, 'getSigner').throws(new Error()); + sandbox.stub(utils, 'callContract').callsFake(async () => { + return getBalanceResp; + }); + sandbox.stub(utils, 'estimateAccountDeployFee').callsFake(async () => { + return estimateDeployFeeResp; + }); + const requestObject: CreateAccountRequestParams = { + chainId: STARKNET_MAINNET_NETWORK.chainId, + deploy: true, + }; + apiParams.requestParams = requestObject; + await createAccount(apiParams, false, true); + + expect(waitForTransactionStub).to.have.been.callCount(1); + }); + it('should create and store an user account with proxy in state correctly in mainnet', async function () { sandbox.stub(utils, 'deployAccount').callsFake(async () => { return createAccountProxyMainnetResp; @@ -100,7 +125,6 @@ describe('Test function: createAccount', function () { state, createAccountProxyMainnetResp.contract_address, ); - expect(walletStub.rpcStubs.snap_manageState).to.have.been.callCount(4); expect(result.address).to.be.eq(createAccountProxyMainnetResp.contract_address); expect(result.transaction_hash).to.be.eq(createAccountProxyMainnetResp.transaction_hash); expect(state.accContracts.length).to.be.eq(1); diff --git a/packages/starknet-snap/test/src/sendTransaction.test.ts b/packages/starknet-snap/test/src/sendTransaction.test.ts index debd95b8..131087a1 100644 --- a/packages/starknet-snap/test/src/sendTransaction.test.ts +++ b/packages/starknet-snap/test/src/sendTransaction.test.ts @@ -25,6 +25,7 @@ import { import { getAddressKeyDeriver } from '../../src/utils/keyPair'; import { Mutex } from 'async-mutex'; import { ApiParams, SendTransactionRequestParams } from '../../src/types/snapApi'; +import { GetTransactionReceiptResponse } from 'starknet'; chai.use(sinonChai); chai.use(chaiAsPromised); @@ -62,6 +63,7 @@ describe('Test function: sendTransaction', function () { }); walletStub.rpcStubs.snap_dialog.resolves(true); walletStub.rpcStubs.snap_manageState.resolves(state); + sandbox.stub(utils, 'waitForTransaction').resolves({} as unknown as GetTransactionReceiptResponse); }); afterEach(function () { diff --git a/packages/starknet-snap/test/utils/snapUtils.test.ts b/packages/starknet-snap/test/utils/snapUtils.test.ts index e0a9b5a1..a88f1eb7 100644 --- a/packages/starknet-snap/test/utils/snapUtils.test.ts +++ b/packages/starknet-snap/test/utils/snapUtils.test.ts @@ -137,11 +137,15 @@ describe('getVoyagerCredentials', () => { describe('getRPCUrl', () => { it('returns Mainnet RPC URL if chain id is Mainnet', () => { - expect(getRPCUrl(constants.StarknetChainId.SN_MAIN)).to.be.equal('https://starknet-mainnet.g.alchemy.com/starknet/version/rpc/v0_7/'); + expect(getRPCUrl(constants.StarknetChainId.SN_MAIN)).to.be.equal( + 'https://starknet-mainnet.g.alchemy.com/starknet/version/rpc/v0_7/', + ); }); it('returns Sepolia RPC URL if chain id is not either Mainnet or Sepolia', () => { - expect(getRPCUrl('0x534e5f474f45524c49')).to.be.equal('https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_7/'); + expect(getRPCUrl('0x534e5f474f45524c49')).to.be.equal( + 'https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_7/', + ); }); it('returns Sepolia RPC URL if chain id is Sepolia', () => { @@ -149,4 +153,4 @@ describe('getRPCUrl', () => { 'https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_7/', ); }); -}); \ No newline at end of file +}); diff --git a/packages/starknet-snap/test/utils/starknetUtils.test.ts b/packages/starknet-snap/test/utils/starknetUtils.test.ts index d0ee754d..f1ff6c8d 100644 --- a/packages/starknet-snap/test/utils/starknetUtils.test.ts +++ b/packages/starknet-snap/test/utils/starknetUtils.test.ts @@ -13,7 +13,7 @@ import { account2, } from '../constants.test'; import { SnapState } from '../../src/types/snapState'; -import { Calldata } from 'starknet'; +import { Calldata, GetTransactionReceiptResponse } from 'starknet'; chai.use(sinonChai); const sandbox = sinon.createSandbox(); @@ -129,3 +129,20 @@ describe('Test function: validateAndParseAddress', function () { ); }); }); + +describe('Test function: waitForTransaction', function () { + const walletStub = new WalletMock(); + const userAddress = '0x27f204588cadd08a7914f6a9808b34de0cbfc4cb53aa053663e7fd3a34dbc26'; + + afterEach(function () { + walletStub.reset(); + sandbox.restore(); + }); + + it('pass parameter to waitForTransaction correctly', async function () { + const stub = sandbox.stub(utils, 'waitForTransaction'); + stub.resolves({} as unknown as GetTransactionReceiptResponse); + await utils.waitForTransaction(STARKNET_SEPOLIA_TESTNET_NETWORK, userAddress, 'pk', 'txHash'); + expect(stub).to.have.been.calledWith(STARKNET_SEPOLIA_TESTNET_NETWORK, userAddress, 'pk', 'txHash'); + }); +}); diff --git a/yarn.lock b/yarn.lock index 57aa3c7f..629b2f25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3246,7 +3246,7 @@ __metadata: "@consensys/starknet-snap@file:../starknet-snap::locator=wallet-ui%40workspace%3Apackages%2Fwallet-ui": version: 2.7.0 - resolution: "@consensys/starknet-snap@file:../starknet-snap#../starknet-snap::hash=660e3f&locator=wallet-ui%40workspace%3Apackages%2Fwallet-ui" + resolution: "@consensys/starknet-snap@file:../starknet-snap#../starknet-snap::hash=099522&locator=wallet-ui%40workspace%3Apackages%2Fwallet-ui" dependencies: "@metamask/snaps-sdk": 3.0.1 async-mutex: ^0.3.2 @@ -3255,7 +3255,7 @@ __metadata: ethers: ^5.5.1 starknet: 6.7.0 starknet_v4.22.0: "npm:starknet@4.22.0" - checksum: bc84f37ad403ec57113becf7b6bd0be167fe2ceb0a2ca9359eff062b277c2f1b863ac4255b28ba43a0f455e8165633a84c5178beefd9ea9e23e4e954a0ea750e + checksum: a53e3b5b9b53448473b4b362ebdda3d1ecb97321baa7db52393c0b43e9454ccb81d641bd2a86956896a591c23f237566d4c131034806c2609de79f9f1a12ba41 languageName: node linkType: hard