diff --git a/packages/starknet-snap/src/rpcs/executeTxn.test.ts b/packages/starknet-snap/src/rpcs/executeTxn.test.ts index ef76d249..23e35384 100644 --- a/packages/starknet-snap/src/rpcs/executeTxn.test.ts +++ b/packages/starknet-snap/src/rpcs/executeTxn.test.ts @@ -120,40 +120,56 @@ describe('ExecuteTxn', () => { expect(createAccountSpy).not.toHaveBeenCalled(); }); - it('creates an account if the account is not deployed and execute the transaction', async () => { - callsExample = callsExamples[1]; - const { - account, - createAccountSpy, - executeTxnUtilSpy, - getEstimatedFeesSpy, - getEstimatedFeesRepsMock, - network, - request, - } = await prepareMockExecuteTxn( - callsExample.hash, - callsExample.calls, - callsExample.details, - false, - ); - - await executeTxn.execute(request); - - expect(getEstimatedFeesSpy).toHaveBeenCalled(); - expect(createAccountSpy).toHaveBeenCalledTimes(1); - expect(executeTxnUtilSpy).toHaveBeenCalledWith( - network, - account.address, - account.privateKey, - callsExample.calls, - undefined, - { - ...callsExample.details, - maxFee: getEstimatedFeesRepsMock.suggestedMaxFee, - nonce: 1, - }, - ); - }); + it.each([constants.TRANSACTION_VERSION.V1, constants.TRANSACTION_VERSION.V3])( + 'creates an account and execute the transaction with nonce 1 with transaction version %s if the account is not deployed', + async (transactionVersion) => { + callsExample = callsExamples[1]; + const { + account, + createAccountSpy, + executeTxnUtilSpy, + getEstimatedFeesSpy, + getEstimatedFeesRepsMock, + network, + request, + } = await prepareMockExecuteTxn( + callsExample.hash, + callsExample.calls, + { + ...callsExample.details, + version: transactionVersion, + }, + false, + ); + + await executeTxn.execute(request); + + expect(getEstimatedFeesSpy).toHaveBeenCalled(); + expect(createAccountSpy).toHaveBeenCalledTimes(1); + expect(createAccountSpy).toHaveBeenCalledWith({ + address: account.address, + callback: expect.any(Function), + network, + privateKey: account.privateKey, + publicKey: account.publicKey, + version: transactionVersion, + waitMode: true, + }); + expect(executeTxnUtilSpy).toHaveBeenCalledWith( + network, + account.address, + account.privateKey, + callsExample.calls, + undefined, + { + ...callsExample.details, + version: transactionVersion, + maxFee: getEstimatedFeesRepsMock.suggestedMaxFee, + nonce: 1, + }, + ); + }, + ); it('throws UserRejectedRequestError if user cancels execution', async () => { callsExample = callsExamples[1]; diff --git a/packages/starknet-snap/src/rpcs/executeTxn.ts b/packages/starknet-snap/src/rpcs/executeTxn.ts index 30c1482e..28434984 100644 --- a/packages/starknet-snap/src/rpcs/executeTxn.ts +++ b/packages/starknet-snap/src/rpcs/executeTxn.ts @@ -111,6 +111,8 @@ export class ExecuteTxnRpc extends AccountRpcController< ); const accountDeployed = !includeDeploy; + const version = + details?.version as unknown as constants.TRANSACTION_VERSION; if ( !(await this.getExecuteTxnConsensus( @@ -118,7 +120,7 @@ export class ExecuteTxnRpc extends AccountRpcController< accountDeployed, calls, suggestedMaxFee, - details?.version as unknown as constants.TRANSACTION_VERSION, + version, )) ) { throw new UserRejectedRequestError() as unknown as Error; @@ -134,6 +136,7 @@ export class ExecuteTxnRpc extends AccountRpcController< callback: async (contractAddress: string, transactionHash: string) => { await this.updateAccountAsDeploy(contractAddress, transactionHash); }, + version, }); } diff --git a/packages/starknet-snap/src/utils/starknetUtils.ts b/packages/starknet-snap/src/utils/starknetUtils.ts index fe4fc00f..0cafe7c2 100644 --- a/packages/starknet-snap/src/utils/starknetUtils.ts +++ b/packages/starknet-snap/src/utils/starknetUtils.ts @@ -747,6 +747,13 @@ export const getAccContractAddressAndCallDataLegacy = (publicKey) => { * Optionally, if `waitMode` is enabled, the function will wait for the transaction to be confirmed * before returning the account address and transaction hash. * + * The function also supports an optional `callback` that will be invoked with the deployed account address + * and transaction hash once the deployment is complete. Can be used to update internal state after + * successful transaction + * + * Additionally, an optional `version` parameter can be specified to use a transaction version 3 + * ('0x3') during the deployment process. + * * @param network - The network on which to deploy the account. * @param address - The account address. * @param publicKey - The public key of the account address. @@ -754,6 +761,8 @@ export const getAccContractAddressAndCallDataLegacy = (publicKey) => { * @param cairoVersion - The version of Cairo to use for the account deployment. * @param recordAccountDeployment - A function to record deployment information into the state. * @param waitMode - If true, waits for the transaction to be confirmed before returning. Defaults to false. + * @param version - (Optional) Specifies the transaction version to use ('0x3'). + * @param callback - (Optional) A function to be called with the account address and transaction hash once deployment is complete. * @returns An object containing the deployed account address and the transaction hash. */ export async function createAccount({ @@ -764,6 +773,7 @@ export async function createAccount({ cairoVersion = CAIRO_VERSION, waitMode = false, callback, + version = undefined, }: { network: Network; address: string; @@ -771,6 +781,7 @@ export async function createAccount({ privateKey: string; cairoVersion?: CairoVersion; waitMode?: boolean; + version?: constants.TRANSACTION_VERSION; callback?: (address: string, transactionHash: string) => Promise; }) { // Deploy account will auto estimate the fee from the network if not provided @@ -784,6 +795,7 @@ export async function createAccount({ publicKey, privateKey, cairoVersion, + { version }, ); if (contractAddress !== address) { diff --git a/packages/wallet-ui/src/components/ui/molecule/AmountInput/AmountInput.stories.tsx b/packages/wallet-ui/src/components/ui/molecule/AmountInput/AmountInput.stories.tsx index 8ae157ad..c286b655 100644 --- a/packages/wallet-ui/src/components/ui/molecule/AmountInput/AmountInput.stories.tsx +++ b/packages/wallet-ui/src/components/ui/molecule/AmountInput/AmountInput.stories.tsx @@ -11,6 +11,7 @@ export default { const asset = { address: '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7', amount: BigNumber.from('1000000000000000000'), + spendableAmount: BigNumber.from('1000000000000000000'), chainId: constants.StarknetChainId.SN_SEPOLIA, decimals: 18, name: 'Ether', diff --git a/packages/wallet-ui/src/components/ui/molecule/AmountInput/AmountInput.view.tsx b/packages/wallet-ui/src/components/ui/molecule/AmountInput/AmountInput.view.tsx index 52409d84..7e38d1a6 100644 --- a/packages/wallet-ui/src/components/ui/molecule/AmountInput/AmountInput.view.tsx +++ b/packages/wallet-ui/src/components/ui/molecule/AmountInput/AmountInput.view.tsx @@ -112,7 +112,7 @@ export const AmountInputView = ({ const handleMaxClick = () => { if (inputRef.current && asset.usdPrice) { const amountStr = ethers.utils - .formatUnits(asset.amount, asset.decimals) + .formatUnits(asset.spendableAmount, asset.decimals) .toString(); const amountFloat = parseFloat(amountStr); inputRef.current.value = usdMode diff --git a/packages/wallet-ui/src/components/ui/molecule/AssetsList/AssetListItem/AssetListItem.stories.tsx b/packages/wallet-ui/src/components/ui/molecule/AssetsList/AssetListItem/AssetListItem.stories.tsx index 273451e8..ddbde96f 100644 --- a/packages/wallet-ui/src/components/ui/molecule/AssetsList/AssetListItem/AssetListItem.stories.tsx +++ b/packages/wallet-ui/src/components/ui/molecule/AssetsList/AssetListItem/AssetListItem.stories.tsx @@ -12,6 +12,7 @@ export default { const asset: Erc20TokenBalance = { address: '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7', amount: BigNumber.from('1000000000000000000'), + spendableAmount: BigNumber.from('1000000000000000000'), chainId: constants.StarknetChainId.SN_SEPOLIA, decimals: 18, name: 'Ether', diff --git a/packages/wallet-ui/src/components/ui/molecule/TransactionsList/TransactionListItem/TransactionListItem.view.tsx b/packages/wallet-ui/src/components/ui/molecule/TransactionsList/TransactionListItem/TransactionListItem.view.tsx index 1daf92e3..7cafbdb1 100644 --- a/packages/wallet-ui/src/components/ui/molecule/TransactionsList/TransactionListItem/TransactionListItem.view.tsx +++ b/packages/wallet-ui/src/components/ui/molecule/TransactionsList/TransactionListItem/TransactionListItem.view.tsx @@ -77,9 +77,9 @@ export const TransactionListItemView = ({ transaction }: Props) => { {txnDate} +
- {' '} - . {txnStatus} + {txnStatus} {txnFailureReason}
diff --git a/packages/wallet-ui/src/components/ui/molecule/TransactionsList/TransactionListItem/types.ts b/packages/wallet-ui/src/components/ui/molecule/TransactionsList/TransactionListItem/types.ts index f385cc03..f04a6983 100644 --- a/packages/wallet-ui/src/components/ui/molecule/TransactionsList/TransactionListItem/types.ts +++ b/packages/wallet-ui/src/components/ui/molecule/TransactionsList/TransactionListItem/types.ts @@ -37,11 +37,15 @@ export const getTxnName = (transaction: Transaction): string => { }; export const getTxnDate = (transaction: Transaction): string => { - return new Date(transaction.timestamp * 1000) - .toDateString() - .split(' ') - .slice(1, 3) - .join(' '); + const date = new Date(transaction.timestamp * 1000); + + return date.toLocaleString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); }; export const getTxnStatus = (transaction: Transaction): string => { @@ -78,7 +82,16 @@ export const getTxnToFromLabel = (transaction: Transaction): string => { const txnName = getTxnName(transaction); switch (txnName) { case 'Send': - return 'To ' + shortenAddress(transaction.contractCallData[0].toString()); + // TODO : This will not be needed after getTransactions revamp. + if (transaction.contractCallData.length === 3) { + return ( + 'To ' + shortenAddress(transaction.contractCallData[0].toString()) + ); + } else { + return ( + 'To ' + shortenAddress(transaction.contractCallData[4].toString()) + ); + } case 'Receive': return 'From ' + shortenAddress(transaction.senderAddress); case 'Deploy': diff --git a/packages/wallet-ui/src/components/ui/organism/Header/Header.view.tsx b/packages/wallet-ui/src/components/ui/organism/Header/Header.view.tsx index 5acd07d3..9980874d 100644 --- a/packages/wallet-ui/src/components/ui/organism/Header/Header.view.tsx +++ b/packages/wallet-ui/src/components/ui/organism/Header/Header.view.tsx @@ -27,7 +27,7 @@ export const HeaderView = ({ address }: Props) => { const getUSDValue = () => { const amountFloat = parseFloat( ethers.utils.formatUnits( - wallet.erc20TokenBalanceSelected.amount, + wallet.erc20TokenBalanceSelected.spendableAmount, wallet.erc20TokenBalanceSelected.decimals, ), ); diff --git a/packages/wallet-ui/src/components/ui/organism/Header/SendModal/SendModal.view.tsx b/packages/wallet-ui/src/components/ui/organism/Header/SendModal/SendModal.view.tsx index 0acc05ca..a206cd52 100644 --- a/packages/wallet-ui/src/components/ui/organism/Header/SendModal/SendModal.view.tsx +++ b/packages/wallet-ui/src/components/ui/organism/Header/SendModal/SendModal.view.tsx @@ -53,7 +53,7 @@ export const SendModalView = ({ closeModal }: Props) => { fieldValue, wallet.erc20TokenBalanceSelected.decimals, ); - const userBalance = wallet.erc20TokenBalanceSelected.amount; + const userBalance = wallet.erc20TokenBalanceSelected.spendableAmount; if (inputAmount.gt(userBalance)) { setErrors((prevErrors) => ({ ...prevErrors, diff --git a/packages/wallet-ui/src/components/ui/organism/Header/SendSummaryModal/SendSummaryModal.view.tsx b/packages/wallet-ui/src/components/ui/organism/Header/SendSummaryModal/SendSummaryModal.view.tsx index 44303de8..8428a122 100644 --- a/packages/wallet-ui/src/components/ui/organism/Header/SendSummaryModal/SendSummaryModal.view.tsx +++ b/packages/wallet-ui/src/components/ui/organism/Header/SendSummaryModal/SendSummaryModal.view.tsx @@ -188,11 +188,11 @@ export const SendSummaryModalView = ({ wallet.accounts[0] as unknown as string, gasFees.suggestedMaxFee, chainId, + selectedFeeToken, ) .then((result) => { if (result) { toastr.success('Transaction sent successfully'); - // can't trigger getTransaction by calling dispatch or setErc20TokenBalance here getTransactions( wallet.accounts[0] as unknown as string, wallet.erc20TokenBalanceSelected.address, diff --git a/packages/wallet-ui/src/services/useStarkNetSnap.ts b/packages/wallet-ui/src/services/useStarkNetSnap.ts index 53f05ecc..9fada059 100644 --- a/packages/wallet-ui/src/services/useStarkNetSnap.ts +++ b/packages/wallet-ui/src/services/useStarkNetSnap.ts @@ -28,7 +28,7 @@ import { Account } from '../types'; import { Erc20TokenBalance, Erc20Token } from '../types'; import { disableLoading, enableLoadingWithMessage } from '../slices/UISlice'; import { Transaction } from 'types'; -import { ethers } from 'ethers'; +import { BigNumber, ethers } from 'ethers'; import { getAssetPriceUSD } from './coinGecko'; import semver from 'semver/preload'; import { setActiveNetwork } from 'slices/networkSlice'; @@ -382,6 +382,7 @@ export const useStarkNetSnap = () => { address: string, maxFee: string, chainId: string, + feeToken: string, ) { dispatch(enableLoadingWithMessage('Sending transaction...')); try { @@ -402,7 +403,13 @@ export const useStarkNetSnap = () => { ...defaultParam, address, calls, - details: { maxFee } as UniversalDetails, + details: { + version: + feeToken === 'STRK' + ? constants.TRANSACTION_VERSION.V3 + : undefined, + maxFee, + } as UniversalDetails, chainId, }, }, @@ -710,13 +717,16 @@ export const useStarkNetSnap = () => { }, }, }); - return response; + return { + balanceLatest: BigNumber.from(response.balanceLatest), + balancePending: BigNumber.from(response.balancePending), + }; } catch (err) { //eslint-disable-next-line no-console console.error(err); return { - balanceLatest: '0x0', - balancePending: '0x0', + balanceLatest: BigNumber.from('0x0'), + balancePending: BigNumber.from('0x0'), }; } }; diff --git a/packages/wallet-ui/src/types/index.ts b/packages/wallet-ui/src/types/index.ts index cc3dd1f4..6ebee8be 100644 --- a/packages/wallet-ui/src/types/index.ts +++ b/packages/wallet-ui/src/types/index.ts @@ -12,8 +12,8 @@ export type Network = Pick< export interface Erc20TokenBalance extends Types.Erc20Token { amount: BigNumber; + spendableAmount: BigNumber; usdPrice?: number; - spendableAmount?: BigNumber; } export type TransactionStatusOptions = | 'Received' diff --git a/packages/wallet-ui/src/utils/utils.ts b/packages/wallet-ui/src/utils/utils.ts index 4022c692..6ac6e0b1 100644 --- a/packages/wallet-ui/src/utils/utils.ts +++ b/packages/wallet-ui/src/utils/utils.ts @@ -48,12 +48,15 @@ export const addMissingPropertiesToToken = ( balanceSpendable?: string, usdPrice?: number, ): Erc20TokenBalance => { + // when balance is undefined, use 0 + const hexBalance = balance ?? '0x0'; + // when balanceSpendable is undefined, we use hexBalance + const hexSpendableBalance = balanceSpendable ?? hexBalance; + return { ...token, - amount: ethers.BigNumber.from(balance ? balance : '0x0'), - spendableAmount: ethers.BigNumber.from( - balanceSpendable ? balanceSpendable : '0x0', - ), + amount: ethers.BigNumber.from(hexBalance), + spendableAmount: ethers.BigNumber.from(hexSpendableBalance), usdPrice: usdPrice, }; }; @@ -246,8 +249,9 @@ export function getTokenBalanceWithDetails( tokenUSDPrice?: number, ): Erc20TokenBalance { const { balancePending, balanceLatest } = tokenBalance; - const spendableBalance = - balancePending < balanceLatest ? balancePending : balanceLatest; + const spendableBalance = balancePending.lt(balanceLatest) + ? balancePending + : balanceLatest; return addMissingPropertiesToToken( token, balanceLatest.toString(),