diff --git a/packages/dapp/package.json b/packages/dapp/package.json index 487ba5a2..04b3d482 100644 --- a/packages/dapp/package.json +++ b/packages/dapp/package.json @@ -10,15 +10,15 @@ "lint": "next lint" }, "dependencies": { - "@alephium/get-extension-wallet": "^1.7.3", - "@alephium/web3": "^1.7.3", + "@alephium/get-extension-wallet": "^1.8.2", + "@alephium/web3": "^1.8.2", "ethers": "^5.5.1", "next": "^13.0.0", "react": "^18.0.0", "react-dom": "^18.0.0" }, "devDependencies": { - "@alephium/cli": "^1.7.3", + "@alephium/cli": "^1.8.2", "@types/node": "18.11.18", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", diff --git a/packages/dapp/src/services/token.service.ts b/packages/dapp/src/services/token.service.ts index 9123dcae..ac07f339 100644 --- a/packages/dapp/src/services/token.service.ts +++ b/packages/dapp/src/services/token.service.ts @@ -83,7 +83,7 @@ export const mintToken = async ( decimals: 0n, totalSupply: BigInt(mintAmount) }, - initialAttoAlphAmount: BigInt(1100000000000000000), + initialAttoAlphAmount: web3.MINIMAL_CONTRACT_DEPOSIT, issueTokenAmount: BigInt(mintAmount), }) } diff --git a/packages/extension/package.json b/packages/extension/package.json index 232197b8..ee47b9ae 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -4,7 +4,7 @@ "main": "index.js", "license": "MIT", "devDependencies": { - "@alephium/get-extension-wallet": "^1.7.3", + "@alephium/get-extension-wallet": "^1.8.2", "@alephium/ledger-app": "0.6.0", "@alephium/token-list": "0.0.20", "@alephium/web3": "^1.8.5", diff --git a/packages/extension/src/background/actionHandlers.ts b/packages/extension/src/background/actionHandlers.ts index d43de3c3..750361a3 100644 --- a/packages/extension/src/background/actionHandlers.ts +++ b/packages/extension/src/background/actionHandlers.ts @@ -4,6 +4,7 @@ import { ActionItem, ExtQueueItem, ReviewTransactionResult, + TransactionResult, } from "../shared/actionQueue/types" import { MessageType } from "../shared/messages" import { addNetwork, getNetworks } from "../shared/network" @@ -13,7 +14,7 @@ import { assertNever } from "../ui/services/assertNever" import { analytics } from "./analytics" import { BackgroundService } from "./background" import { openUi } from "./openUi" -import { executeTransactionAction } from "./transactions/transactionExecution" +import { executeTransactionsAction, executeTransactionAction } from "./transactions/transactionExecution" import { transactionWatcher } from "./transactions/transactionWatcher" export const handleActionApproval = async ( @@ -50,21 +51,61 @@ export const handleActionApproval = async ( } case "ALPH_TRANSACTION": { - const { signature: signatureOpt, ...transaction } = - additionalData as ReviewTransactionResult & { signature?: string } + const transactions = additionalData as ReviewTransactionResult[] try { - const { signature } = await executeTransactionAction( - transaction, - signatureOpt, - background, - transaction.params.networkId, - ) + if (transactions.length === 0) { + return { + type: "ALPH_TRANSACTION_FAILED", + data: { actionHash, error: "No transactions to execute" }, + } + } + + let results: TransactionResult[] + if (transactions.length === 1) { + const { signature: signatureOpt, ...transaction } = + transactions[0] as ReviewTransactionResult & { signature?: string } - transactionWatcher.refresh() + const { signature } = await executeTransactionAction( + transaction, + signatureOpt, + background, + transaction.params.networkId, + ) + + transactionWatcher.refresh() + + results = [ + { + type: transaction.type, + result: { + ...transaction.result, + signature + } + } + ] as TransactionResult[] + } else { + const { signatures } = await executeTransactionsAction( + transactions, + background, + transactions[0].params.networkId, + ) + + transactionWatcher.refresh() + + results = transactions.map((transaction, index) => ( + { + type: transaction.type, + result: { + ...transaction.result, + signature: signatures[index], + } + } + )) as TransactionResult[] + } return { type: "ALPH_TRANSACTION_SUBMITTED", - data: { result: { ...transaction.result, signature }, actionHash }, + data: { result: results, actionHash }, } } catch (error: unknown) { return { diff --git a/packages/extension/src/background/transactions/transactionExecution.ts b/packages/extension/src/background/transactions/transactionExecution.ts index 381092d2..256cc98b 100644 --- a/packages/extension/src/background/transactions/transactionExecution.ts +++ b/packages/extension/src/background/transactions/transactionExecution.ts @@ -29,7 +29,35 @@ export const executeTransactionAction = async ( ) } - if (account !== undefined) { + + addTransaction({ + account: account, + hash: transaction.result.txId, + meta: { + request: transaction, + }, + }) + + return { signature: finalSignature } +} + +export const executeTransactionsAction = async ( + transactions: ReviewTransactionResult[], + { wallet }: BackgroundService, + networkId: string, +): Promise<{ signatures: string[] }> => { + const signatures: string[] = [] + for (const transaction of transactions) { + const account = await wallet.getAccount({ + address: transaction.params.signerAddress, + networkId: networkId, + }) + + const { signature } = await wallet.signAndSubmitUnsignedTx(account, { + signerAddress: account.address, + unsignedTx: transaction.result.unsignedTx, + }) + addTransaction({ account: account, hash: transaction.result.txId, @@ -37,7 +65,9 @@ export const executeTransactionAction = async ( request: transaction, }, }) + + signatures.push(signature) } - return { signature: finalSignature } + return { signatures } } diff --git a/packages/extension/src/inpage/alephiumWindowObject.ts b/packages/extension/src/inpage/alephiumWindowObject.ts index 3c0a38d3..bed3e55d 100644 --- a/packages/extension/src/inpage/alephiumWindowObject.ts +++ b/packages/extension/src/inpage/alephiumWindowObject.ts @@ -10,6 +10,8 @@ import { KeyType, NetworkId, NodeProvider, + SignChainedTxParams, + SignChainedTxResult, SignDeployContractTxParams, SignDeployContractTxResult, SignExecuteScriptTxParams, @@ -29,6 +31,7 @@ import { TransactionParams } from "../shared/actionQueue/types" import { sendMessage, waitForMessage } from "./messageActions" import { getIsPreauthorized, removePreAuthorization } from "./messaging" import { handleAddTokenRequest } from "./requestMessageHandlers" +import { signedChainedTxParamsToTransactionParams, transactionResultToSignUnsignedTxResult } from "../shared/transactions/transformers" const VERSION = `${process.env.VERSION}` const USER_ACTION_TIMEOUT = 10 * 60 * 1000 @@ -142,7 +145,7 @@ export const alephiumWindowObject: AlephiumWindowObject = signAndSubmitTransferTx = async ( params: SignTransferTxParams, ): Promise => { - const result = ( + const [txResult] = ( await this.#executeAlephiumTransaction( params, (p, host, networkId, keyType) => ({ @@ -151,14 +154,14 @@ export const alephiumWindowObject: AlephiumWindowObject = salt: Date.now().toString(), }), ) - ).result as SignTransferTxResult - return result + ).result + return txResult.result as SignTransferTxResult } signAndSubmitDeployContractTx = async ( params: SignDeployContractTxParams, ): Promise => { - const result = ( + const [txResult] = ( await this.#executeAlephiumTransaction( params, (p, host, networkId, keyType) => ({ @@ -167,14 +170,15 @@ export const alephiumWindowObject: AlephiumWindowObject = salt: Date.now().toString(), }), ) - ).result as SignDeployContractTxResult - return { ...result } + ).result + + return txResult.result as SignDeployContractTxResult } signAndSubmitExecuteScriptTx = async ( params: SignExecuteScriptTxParams, ): Promise => { - const result = ( + const [txResult] = ( await this.#executeAlephiumTransaction( params, (p, host, networkId, keyType) => ({ @@ -183,14 +187,14 @@ export const alephiumWindowObject: AlephiumWindowObject = salt: Date.now().toString(), }), ) - ).result as SignExecuteScriptTxResult - return result + ).result + return txResult.result as SignExecuteScriptTxResult } signAndSubmitUnsignedTx = async ( params: SignUnsignedTxParams, ): Promise => { - const result = ( + const [txResult] = ( await this.#executeAlephiumTransaction( params, (p, host, networkId, keyType) => ({ @@ -199,8 +203,58 @@ export const alephiumWindowObject: AlephiumWindowObject = salt: Date.now().toString(), }), ) - ).result as SignUnsignedTxResult - return result + ).result + + return txResult.result as SignUnsignedTxResult + } + + signAndSubmitChainedTx = async ( + params: SignChainedTxParams[], + ): Promise => { + if (params.length === 0) { + throw Error("Empty transaction params") + } + + const transactionParamz: TransactionParams[] = params.map(param => + signedChainedTxParamsToTransactionParams(param, this.#connectedNetworkId!) + ) + + sendMessage({ type: "ALPH_EXECUTE_TRANSACTION", data: transactionParamz }) + + const { actionHash } = await waitForMessage( + "ALPH_EXECUTE_TRANSACTION_RES", + USER_ACTION_TIMEOUT, + ) + + sendMessage({ type: "ALPH_OPEN_UI" }) + + const txMessage = await Promise.race([ + waitForMessage( + "ALPH_TRANSACTION_SUBMITTED", + USER_ACTION_TIMEOUT_LONGER, + (x) => x.data.actionHash === actionHash, + ), + waitForMessage( + "ALPH_TRANSACTION_FAILED", + USER_ACTION_TIMEOUT, + (x) => x.data.actionHash === actionHash, + ) + .then((res) => res) + .catch(() => { + const error = "User action time out" + sendMessage({ + type: "ALPH_TRANSACTION_FAILED", + data: { actionHash, error }, + }) + return { error } + }), + ]) + + if ("error" in txMessage) { + throw Error(txMessage.error) + } + + return txMessage.result.map(res => transactionResultToSignUnsignedTxResult(res)) } signUnsignedTx = async ( @@ -212,7 +266,6 @@ export const alephiumWindowObject: AlephiumWindowObject = throw new Error("Invalid unsigned tx") } - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion sendMessage({ type: "ALPH_SIGN_UNSIGNED_TX", data: { @@ -362,7 +415,6 @@ export const alephiumWindowObject: AlephiumWindowObject = ) { this.#checkParams(params) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const data = dataBuilder( params, window.location.host, @@ -370,7 +422,7 @@ export const alephiumWindowObject: AlephiumWindowObject = this.connectedAccount!.keyType, ) - sendMessage({ type: "ALPH_EXECUTE_TRANSACTION", data }) + sendMessage({ type: "ALPH_EXECUTE_TRANSACTION", data: [data] }) const { actionHash } = await waitForMessage( "ALPH_EXECUTE_TRANSACTION_RES", USER_ACTION_TIMEOUT, diff --git a/packages/extension/src/shared/actionQueue/types.ts b/packages/extension/src/shared/actionQueue/types.ts index 40d3fc98..4445100e 100644 --- a/packages/extension/src/shared/actionQueue/types.ts +++ b/packages/extension/src/shared/actionQueue/types.ts @@ -106,7 +106,7 @@ export type ActionItem = } | { type: "ALPH_TRANSACTION" - payload: TransactionParams + payload: TransactionParams[] } | { type: "ALPH_SIGN_MESSAGE" diff --git a/packages/extension/src/shared/messages/TransactionMessage.ts b/packages/extension/src/shared/messages/TransactionMessage.ts index 3086d1f7..ec770610 100644 --- a/packages/extension/src/shared/messages/TransactionMessage.ts +++ b/packages/extension/src/shared/messages/TransactionMessage.ts @@ -9,6 +9,7 @@ import { TransactionParams, TransactionResult } from "../actionQueue/types" import { Transaction } from "../transactions" import { DeclareContract } from "../udc/type" import { BaseWalletAccount } from "../wallet.model" +import { SignChainedTxParams } from "@alephium/web3" export interface EstimateFeeResponse { amount: string @@ -28,13 +29,13 @@ export interface DeclareDeployEstimateFeeResponse export type TransactionMessage = | { type: "ALPH_EXECUTE_TRANSACTION" - data: TransactionParams + data: TransactionParams[] } | { type: "ALPH_EXECUTE_TRANSACTION_RES"; data: { actionHash: string } } | { type: "TRANSACTION_UPDATES"; data: Transaction[] } | { type: "ALPH_TRANSACTION_SUBMITTED" - data: { result: TransactionResult["result"]; actionHash: string } + data: { result: TransactionResult[]; actionHash: string } } | { type: "ALPH_TRANSACTION_FAILED" diff --git a/packages/extension/src/shared/token/balance.ts b/packages/extension/src/shared/token/balance.ts index 0179713d..27c2ac62 100644 --- a/packages/extension/src/shared/token/balance.ts +++ b/packages/extension/src/shared/token/balance.ts @@ -1,4 +1,4 @@ -import { explorer, ExplorerProvider, ALPH_TOKEN_ID } from "@alephium/web3" +import { explorer, ExplorerProvider, ALPH_TOKEN_ID, NodeProvider } from "@alephium/web3" import { getNetwork } from "../network" import { BaseWalletAccount } from "../wallet.model" import { BigNumber } from "ethers" @@ -32,3 +32,13 @@ export function addTokenToBalances(balances: Map, tokenId: st balances.set(tokenId, tokenBalance.add(amount)) } } + +export async function getBalances(nodeProvider: NodeProvider, address: string): Promise> { + const result = await nodeProvider.addresses.getAddressesAddressBalance(address) + const balances = new Map() + balances.set(ALPH_TOKEN_ID, BigNumber.from(result.balance)) + if (result.tokenBalances !== undefined) { + result.tokenBalances.forEach((token) => addTokenToBalances(balances, token.id, BigNumber.from(token.amount))) + } + return balances +} diff --git a/packages/extension/src/shared/transactions/index.ts b/packages/extension/src/shared/transactions/index.ts index f8f96a61..8142f9a8 100644 --- a/packages/extension/src/shared/transactions/index.ts +++ b/packages/extension/src/shared/transactions/index.ts @@ -1,11 +1,16 @@ -import { ExplorerProvider } from "@alephium/web3" +import { ALPH_TOKEN_ID, DEFAULT_GAS_PRICE, DUST_AMOUNT, ExplorerProvider, MINIMAL_CONTRACT_DEPOSIT, NodeProvider, SignTransferChainedTxParams, TransactionBuilder } from "@alephium/web3" import { lowerCase, upperFirst } from "lodash-es" import { Call } from "starknet" -import { ReviewTransactionResult } from "../actionQueue/types" +import { ReviewTransactionResult, TransactionParams } from "../actionQueue/types" import { WalletAccount } from "../wallet.model" import { AlephiumExplorerTransaction } from "../explorer/type" -import { mapAlephiumTransactionToTransaction } from "./transformers" +import { mapAlephiumTransactionToTransaction, signedChainedTxResultToReviewTransactionResult, transactionParamsToSignChainedTxParams } from "./transformers" import { getNetwork } from "../network" +import { BaseTokenWithBalance } from "../token/type" +import { BigNumber } from "ethers" +import { addTokenToBalances, getBalances } from "../token/balance" +import i18n from "../../i18n" +import { tokenListStore } from "../token/storage" export type Status = 'NOT_RECEIVED' | 'RECEIVED' | 'PENDING' | 'ACCEPTED_ON_MEMPOOL' | 'ACCEPTED_ON_L2' | 'ACCEPTED_ON_CHAIN' | 'REJECTED' | 'REMOVED_FROM_MEMPOOL'; @@ -129,3 +134,235 @@ function buildGetTransactionsFn(metadataTransactions: Transaction[]) { ) } } + +export async function tryBuildChainedTransactions( + nodeUrl: string, + walletAccounts: WalletAccount[], + transactionParams: TransactionParams[] +): Promise { + try { + if (transactionParams.length === 0) { + throw new Error("Transaction params are empty"); + } + const networkId = transactionParams[0].params.networkId + if (transactionParams.some((params) => params.params.networkId !== networkId)) { + throw new Error(`All transaction params must have the same networkId ${networkId}`) + } + + const builder = TransactionBuilder.from(nodeUrl) + const chainedParams = transactionParams.map((params) => transactionParamsToSignChainedTxParams(params)) + const publicKeys = transactionParams.map((params) => { + const account = walletAccounts.find(acc => acc.address === params.params.signerAddress && acc.networkId === networkId) + if (!account) { + throw new Error(`No wallet account found for address ${params.params.signerAddress} in ${networkId} network`) + } + return account.signer.publicKey + }) + const chainedTxResults = await builder.buildChainedTx(chainedParams, publicKeys) + return chainedTxResults.map((signedChainedTxResult, index) => { + return signedChainedTxResultToReviewTransactionResult(chainedParams[index], signedChainedTxResult, networkId) + }) + } catch (error) { + console.log("Error building chained transaction", error) + throw error + } +} + +export async function tryBuildTransactions( + nodeUrl: string, + tokensWithBalance: BaseTokenWithBalance[], + selectedAccount: WalletAccount, + allAccounts: WalletAccount[], + transactionParams: TransactionParams, + useLedger: boolean +): Promise { + const builder = TransactionBuilder.from(nodeUrl) + try { + const transaction: ReviewTransactionResult = await buildTransaction(builder, selectedAccount, transactionParams) + return [transaction] + } catch (error) { + console.log("Error building transaction", error) + const missingBalances = await checkBalances(tokensWithBalance, transactionParams) + const isDAppTransaction = transactionParams.type === 'EXECUTE_SCRIPT' || transactionParams.type === 'DEPLOY_CONTRACT' + + if (missingBalances !== undefined) { + if (isDAppTransaction && !useLedger) { + const restOfAccounts = allAccounts.filter((account) => account.address !== selectedAccount.address) + const nodeProvider = new NodeProvider(nodeUrl) + const account = await accountWithEnoughBalance(missingBalances, restOfAccounts, nodeProvider) + + if (account !== undefined) { + const tokens = Array.from(missingBalances.entries()) + .filter(([tokenId]) => tokenId !== ALPH_TOKEN_ID) + .map(([tokenId, amount]) => ({ + id: tokenId, + amount: amount.toString() + })) + const attoAlphAmount = missingBalances.get(ALPH_TOKEN_ID)?.toString() || DUST_AMOUNT * BigInt(tokens.length) + const transferTransactionParams: SignTransferChainedTxParams = { + type: 'Transfer', + signerAddress: account.address, + signerKeyType: account.signer.keyType, + destinations: [{ + address: selectedAccount.address, + attoAlphAmount, + tokens + }] + } + + const dappTransactionParams = transactionParamsToSignChainedTxParams(transactionParams) + const chainedTxParams = [transferTransactionParams, dappTransactionParams] + const chainedTxResults = await builder.buildChainedTx( + chainedTxParams, + [account.signer.publicKey, selectedAccount.signer.publicKey] + ) + + const reviewTxResults = chainedTxParams.map((params, index) => { + return signedChainedTxResultToReviewTransactionResult(params, chainedTxResults[index], account.networkId) + }) + + return reviewTxResults + } + } + + const [firstMissingTokenId, firstMissingAmount] = missingBalances.entries().next().value; + const tokenListTokens = await tokenListStore.get() + const tokenSymbol = firstMissingTokenId === ALPH_TOKEN_ID ? + 'ALPH' : tokenListTokens.tokens.find(token => + token.id === firstMissingTokenId && token.networkId === selectedAccount.networkId + )?.symbol ?? firstMissingTokenId + const expectedStr = firstMissingAmount.toString(); + const haveStr = (tokensWithBalance.find(t => t.id === firstMissingTokenId)?.balance || '0').toString(); + const errorMsg = i18n.t("Insufficient token {{ tokenSymbol }}, expected at least {{ expectedStr }}, got {{ haveStr }}", { tokenSymbol, expectedStr, haveStr }) + throw new Error(errorMsg) + } + + throw error + } +} + +async function accountWithEnoughBalance( + missingBalances: Map, + accounts: WalletAccount[], + nodeProvider: NodeProvider +): Promise { + for (const account of accounts) { + const accountBalances = await getBalances(nodeProvider, account.address) + let hasEnoughBalance = true; + for (const [tokenId, amount] of missingBalances) { + const balance = accountBalances.get(tokenId); + if (!balance || balance.lt(amount)) { + hasEnoughBalance = false; + break; + } + } + + if (hasEnoughBalance) { + return account + } + } + return undefined +} + + +async function buildTransaction( + builder: TransactionBuilder, + account: WalletAccount, + transactionParams: TransactionParams +): Promise { + switch (transactionParams.type) { + case "TRANSFER": + return { + type: transactionParams.type, + params: transactionParams.params, + result: await builder.buildTransferTx( + transactionParams.params, + account.signer.publicKey, + ), + } + case "DEPLOY_CONTRACT": + return { + type: transactionParams.type, + params: transactionParams.params, + result: await builder.buildDeployContractTx( + transactionParams.params, + account.signer.publicKey, + ), + } + case "EXECUTE_SCRIPT": + return { + type: transactionParams.type, + params: transactionParams.params, + result: await builder.buildExecuteScriptTx( + transactionParams.params, + account.signer.publicKey, + ), + } + case "UNSIGNED_TX": + return { + type: transactionParams.type, + params: transactionParams.params, + result: await TransactionBuilder.buildUnsignedTx(transactionParams.params), + } + } +} + +export async function checkBalances( + tokensWithBalance: BaseTokenWithBalance[], + transactionParams: TransactionParams +): Promise | undefined> { + const expectedBalances: Map = new Map() + + switch (transactionParams.type) { + case 'TRANSFER': + transactionParams.params.destinations.forEach((destination) => { + addTokenToBalances(expectedBalances, ALPH_TOKEN_ID, BigNumber.from(destination.attoAlphAmount)) + if (destination.tokens !== undefined) { + destination.tokens.forEach((token) => addTokenToBalances(expectedBalances, token.id, BigNumber.from(token.amount))) + } + }) + break + case 'DEPLOY_CONTRACT': + addTokenToBalances(expectedBalances, ALPH_TOKEN_ID, + transactionParams.params.initialAttoAlphAmount !== undefined + ? BigNumber.from(transactionParams.params.initialAttoAlphAmount) + : BigNumber.from(MINIMAL_CONTRACT_DEPOSIT) + ) + if (transactionParams.params.initialTokenAmounts !== undefined) { + transactionParams.params.initialTokenAmounts.forEach((token) => addTokenToBalances(expectedBalances, token.id, BigNumber.from(token.amount))) + } + break + case 'EXECUTE_SCRIPT': + if (transactionParams.params.attoAlphAmount !== undefined) { + addTokenToBalances(expectedBalances, ALPH_TOKEN_ID, BigNumber.from(transactionParams.params.attoAlphAmount)) + } + if (transactionParams.params.tokens !== undefined) { + transactionParams.params.tokens.forEach((token) => addTokenToBalances(expectedBalances, token.id, BigNumber.from(token.amount))) + } + + break + case 'UNSIGNED_TX': + return + } + + const maxGasAmountPerTx = 5000000 + const gasFee = BigInt(transactionParams.params.gasAmount ?? maxGasAmountPerTx) * BigInt(transactionParams.params.gasPrice ?? DEFAULT_GAS_PRICE) + addTokenToBalances(expectedBalances, ALPH_TOKEN_ID, BigNumber.from(gasFee)) + + const zero = BigNumber.from(0) + const missingBalances: Map = new Map() + for (const [tokenId, amount] of expectedBalances) { + if (zero.eq(amount)) { + continue + } + const token = tokensWithBalance.find((t) => t.id === tokenId) + const tokenBalance = token?.balance + if (tokenBalance === undefined || tokenBalance.lt(amount)) { + missingBalances.set(tokenId, amount.sub(tokenBalance ?? zero)) + } + } + if (missingBalances.size > 0) { + return missingBalances + } + return undefined +} diff --git a/packages/extension/src/shared/transactions/transformers.ts b/packages/extension/src/shared/transactions/transformers.ts index 7c7f08b0..b1b34cf0 100644 --- a/packages/extension/src/shared/transactions/transformers.ts +++ b/packages/extension/src/shared/transactions/transformers.ts @@ -1,7 +1,7 @@ import { Transaction } from "../../shared/transactions" import { WalletAccount } from "../../shared/wallet.model" -import { explorer } from '@alephium/web3' -import { ReviewTransactionResult } from "../../shared/actionQueue/types"; +import { explorer, SignChainedTxParams, SignChainedTxResult, SignDeployContractChainedTxParams, SignDeployContractChainedTxResult, SignExecuteScriptChainedTxParams, SignExecuteScriptChainedTxResult, SignTransferChainedTxParams, SignTransferChainedTxResult, SignUnsignedTxResult } from '@alephium/web3' +import { ReviewTransactionResult, TransactionParams, TransactionResult } from "../../shared/actionQueue/types"; export const mapAlephiumTransactionToTransaction = ( transaction: explorer.Transaction, @@ -17,3 +17,86 @@ export const mapAlephiumTransactionToTransaction = ( status: "ACCEPTED_ON_CHAIN", timestamp: transaction.timestamp, }) + +export function signedChainedTxParamsToTransactionParams( + signedChainedTxParams: SignChainedTxParams, + networkId: string +): TransactionParams { + const paramsType = signedChainedTxParams.type + const salt = Date.now().toString() + switch (paramsType) { + case 'Transfer': + return { + type: 'TRANSFER', + params: { networkId, ...signedChainedTxParams }, + salt + } + case 'DeployContract': + return { + type: 'DEPLOY_CONTRACT', + params: { networkId, ...signedChainedTxParams }, + salt + } + case 'ExecuteScript': + return { + type: 'EXECUTE_SCRIPT', + params: { networkId, ...signedChainedTxParams }, + salt + } + default: + throw new Error(`Unsupported transaction type: ${paramsType}`); + } +} + +export function transactionParamsToSignChainedTxParams( + transactionParams: TransactionParams +): SignChainedTxParams { + switch (transactionParams.type) { + case "TRANSFER": + return { + type: 'Transfer', + ...transactionParams.params + } as SignTransferChainedTxParams + case "DEPLOY_CONTRACT": + return { + type: 'DeployContract', + ...transactionParams.params + } as SignDeployContractChainedTxParams + case "EXECUTE_SCRIPT": + return { + type: 'ExecuteScript', + ...transactionParams.params + } as SignExecuteScriptChainedTxParams + default: + throw new Error(`Unsupported transaction type: ${transactionParams.type}`); + } +} + +export function signedChainedTxResultToReviewTransactionResult( + signedChainedTxParams: SignChainedTxParams, + signedChainedTxResult: Omit, + networkId: string +): ReviewTransactionResult { + const txParams = signedChainedTxParamsToTransactionParams(signedChainedTxParams, networkId) + return { + type: txParams.type, + params: txParams.params, + result: signedChainedTxResult + } as ReviewTransactionResult +} + +export function transactionResultToSignUnsignedTxResult( + txResult: TransactionResult +): SignChainedTxResult { + const txResultType = txResult.type + switch (txResultType) { + case 'TRANSFER': + return { type: 'Transfer', ...txResult.result } as SignTransferChainedTxResult; + case 'DEPLOY_CONTRACT': + return { type: 'DeployContract', ...txResult.result } as SignDeployContractChainedTxResult; + case 'EXECUTE_SCRIPT': + return { type: 'ExecuteScript', ...txResult.result } as SignExecuteScriptChainedTxResult; + default: + throw new Error(`Unsupported transaction type: ${txResultType}`); + } +} diff --git a/packages/extension/src/ui/features/accountTokens/tokens.state.ts b/packages/extension/src/ui/features/accountTokens/tokens.state.ts index bcfcc139..a23876ce 100644 --- a/packages/extension/src/ui/features/accountTokens/tokens.state.ts +++ b/packages/extension/src/ui/features/accountTokens/tokens.state.ts @@ -1,4 +1,4 @@ -import { ALPH_TOKEN_ID, NodeProvider } from "@alephium/web3" +import { NodeProvider } from "@alephium/web3" import { BigNumber } from "ethers" import { memoize } from "lodash-es" import { useEffect, useMemo, useRef } from "react" @@ -14,7 +14,7 @@ import { getAccountIdentifier } from "../../../shared/wallet.service" import { useAccount } from "../accounts/accounts.state" import { useAccountTransactions } from "../accounts/accountTransactions.state" import { sortBy } from "lodash" -import { addTokenToBalances } from "../../../shared/token/balance" +import { getBalances } from "../../../shared/token/balance" import { Transaction, compareTransactions } from "../../../shared/transactions" import { fetchImmutable } from "../../../shared/utils/fetchImmutable" import { retryWhenRateLimited } from "../../services/swr" @@ -312,16 +312,6 @@ function hasNewPendingTx(prevTxs: Transaction[], newTxs: Transaction[]): boolean return false } -async function getBalances(nodeProvider: NodeProvider, address: string): Promise> { - const result = await nodeProvider.addresses.getAddressesAddressBalance(address) - const balances = new Map() - balances.set(ALPH_TOKEN_ID, BigNumber.from(result.balance)) - if (result.tokenBalances !== undefined) { - result.tokenBalances.forEach((token) => addTokenToBalances(balances, token.id, BigNumber.from(token.amount))) - } - return balances -} - async function fetchFungibleTokenFromFullNode(network: Network, tokenId: string): Promise { const nodeProvider = new NodeProvider(network.nodeUrl, network.nodeApiKey) try { diff --git a/packages/extension/src/ui/features/actions/ActionScreen.tsx b/packages/extension/src/ui/features/actions/ActionScreen.tsx index 160b10d2..af5c3a17 100644 --- a/packages/extension/src/ui/features/actions/ActionScreen.tsx +++ b/packages/extension/src/ui/features/actions/ActionScreen.tsx @@ -31,11 +31,9 @@ export const ActionScreen: FC = () => { const [action] = actions const isLastAction = actions.length === 1 - const signerAccount = useAccount(action.type === 'ALPH_TRANSACTION' && selectedAccount - ? { address: action.payload.params.signerAddress, networkId: action.payload.params.networkId ?? selectedAccount.networkId } - : (action.type === 'ALPH_SIGN_MESSAGE' || action.type === 'ALPH_SIGN_UNSIGNED_TX') && selectedAccount - ? { address: action.payload.signerAddress, networkId: action.payload.networkId ?? selectedAccount.networkId } - : undefined + const signerAccount = useAccount((action.type === 'ALPH_SIGN_MESSAGE' || action.type === 'ALPH_SIGN_UNSIGNED_TX') && selectedAccount + ? { address: action.payload.signerAddress, networkId: action.payload.networkId ?? selectedAccount.networkId } + : undefined ) const closePopup = useCallback(() => { @@ -134,50 +132,59 @@ export const ActionScreen: FC = () => { case "ALPH_TRANSACTION": return ( { + onSubmit={async (builtTransactions) => { analytics.track("signedTransaction", { networkId: selectedAccount?.networkId || "unknown", }) - if (!builtTransaction || "error" in builtTransaction) { + + if (!builtTransactions || builtTransactions.length === 0) { useAppState.setState({ - error: `${t('Transaction building failed')}: ${builtTransaction?.error || t("unknown")}`, + error: `${t('Transaction building failed')}`, isLoading: false, }) navigate(routes.error()) } else { - await approveAction(action, builtTransaction) - useAppState.setState({ isLoading: true }) - const result = await Promise.race([ - waitForMessage( - "ALPH_TRANSACTION_SUBMITTED", - ({ data }) => data.actionHash === action.meta.hash, - ), - waitForMessage( - "ALPH_TRANSACTION_FAILED", - ({ data }) => data.actionHash === action.meta.hash, - ), - ]) - // (await) blocking as the window may closes afterwards - await analytics.track("sentTransaction", { - success: !("error" in result), - networkId: selectedAccount?.networkId || t("unknown"), - }) - if ("error" in result) { + const errorTransaction = builtTransactions.find(tx => 'error' in tx); + if (errorTransaction && 'error' in errorTransaction) { useAppState.setState({ - error: `${t('Sending transaction failed')}: ${result.error}`, + error: `${t('Transaction building failed')}: ${errorTransaction.error}`, isLoading: false, }) navigate(routes.error()) } else { - closePopupIfLastAction() - useAppState.setState({ isLoading: false }) + await approveAction(action, builtTransactions) + useAppState.setState({ isLoading: true }) + const result = await Promise.race([ + waitForMessage( + "ALPH_TRANSACTION_SUBMITTED", + ({ data }) => data.actionHash === action.meta.hash, + ), + waitForMessage( + "ALPH_TRANSACTION_FAILED", + ({ data }) => data.actionHash === action.meta.hash, + ), + ]) + // (await) blocking as the window may closes afterwards + await analytics.track("sentTransaction", { + success: !("error" in result), + networkId: selectedAccount?.networkId || t("unknown"), + }) + if ("error" in result) { + useAppState.setState({ + error: `${t('Sending transaction failed')}: ${result.error}`, + isLoading: false, + }) + navigate(routes.error()) + } else { + closePopupIfLastAction() + useAppState.setState({ isLoading: false }) + } } } }} onReject={onReject} - selectedAccount={signerAccount} /> ) diff --git a/packages/extension/src/ui/features/actions/ApproveSignUnsignedTxScreen.tsx b/packages/extension/src/ui/features/actions/ApproveSignUnsignedTxScreen.tsx index 6c1df342..2eb18f22 100644 --- a/packages/extension/src/ui/features/actions/ApproveSignUnsignedTxScreen.tsx +++ b/packages/extension/src/ui/features/actions/ApproveSignUnsignedTxScreen.tsx @@ -91,7 +91,7 @@ export const ApproveSignUnsignedTxScreen: FC = } { selectedAccount && - + } { diff --git a/packages/extension/src/ui/features/actions/ApproveTransactionScreen.tsx b/packages/extension/src/ui/features/actions/ApproveTransactionScreen.tsx index d33fa712..4694b033 100644 --- a/packages/extension/src/ui/features/actions/ApproveTransactionScreen.tsx +++ b/packages/extension/src/ui/features/actions/ApproveTransactionScreen.tsx @@ -1,5 +1,4 @@ -import { ALPH_TOKEN_ID, ONE_ALPH, prettifyTokenAmount, TransactionBuilder } from "@alephium/web3" -import { Flex } from "@chakra-ui/react" +import { Flex, Text } from "@chakra-ui/react" import { FC, useCallback, useEffect, useState } from "react" import { Navigate, useNavigate } from "react-router-dom" @@ -7,12 +6,10 @@ import { ReviewTransactionResult, TransactionParams, } from "../../../shared/actionQueue/types" -import { BaseTokenWithBalance } from "../../../shared/token/type" import { useAppState } from "../../app.state" import { routes } from "../../routes" import { usePageTracking } from "../../services/analytics" import { rejectAction } from "../../services/backgroundActions" -import { Account } from "../accounts/Account" import { useAllTokensWithBalance } from "../accountTokens/tokens.state" import { useNetwork } from "../networks/useNetworks" import { ConfirmScreen } from "./ConfirmScreen" @@ -23,149 +20,29 @@ import { AccountNetworkInfo } from "./transaction/AccountNetworkInfo" import { DappHeader } from "./transaction/DappHeader" import { TransactionsList } from "./transaction/TransactionsList" import { TxHashContainer } from "./TxHashContainer" -import { getToken } from "../../../shared/token/storage" -import { BigNumber } from "ethers" -import { addTokenToBalances } from "../../../shared/token/balance" import { useTranslation } from "react-i18next" -import i18n from "../../../i18n" +import { tryBuildChainedTransactions, tryBuildTransactions } from "../../../shared/transactions" +import { IconButton } from "@mui/material" +import { ChevronLeftIcon, ChevronRightIcon } from "@chakra-ui/icons" +import { getAccounts } from "../../../shared/account/store" +import { useSelectedAccount } from "../accounts/accounts.state" import { LedgerStatus } from "./LedgerStatus" import { useLedgerApp } from "../ledger/useLedgerApp" import { getConfirmationTextByState } from "../ledger/types" -const minimalGasFee = BigInt(20000) * BigInt(100000000000) - export interface ApproveTransactionScreenProps extends Omit { actionHash: string - transaction: TransactionParams + transactionParams: TransactionParams[] onSubmit: ( result: - | (ReviewTransactionResult & { signature?: string }) - | { error: string } + | (ReviewTransactionResult & { signature?: string } | { error: string })[] | undefined, ) => void } -async function tryBuildTransaction( - nodeUrl: string, - tokensWithBalance: BaseTokenWithBalance[], - account: Account, - transaction: TransactionParams -): Promise { - const builder = TransactionBuilder.from(nodeUrl) - try { - return await buildTransaction(builder, account, transaction) - } catch (error) { - const errMsg = await checkBalances(tokensWithBalance, transaction, account.networkId) - if (errMsg !== undefined) { - throw new Error(errMsg) - } - throw error - } -} - -async function buildTransaction( - builder: TransactionBuilder, - account: Account, - transaction: TransactionParams -): Promise { - switch (transaction.type) { - case "TRANSFER": - return { - type: transaction.type, - params: transaction.params, - result: await builder.buildTransferTx( - transaction.params, - account.publicKey, - ), - } - case "DEPLOY_CONTRACT": - return { - type: transaction.type, - params: transaction.params, - result: await builder.buildDeployContractTx( - transaction.params, - account.publicKey, - ), - } - case "EXECUTE_SCRIPT": - return { - type: transaction.type, - params: transaction.params, - result: await builder.buildExecuteScriptTx( - transaction.params, - account.publicKey, - ), - } - case "UNSIGNED_TX": - return { - type: transaction.type, - params: transaction.params, - result: TransactionBuilder.buildUnsignedTx(transaction.params), - } - } -} - -export async function checkBalances( - tokensWithBalance: BaseTokenWithBalance[], - transaction: TransactionParams, - networkId: string -): Promise { - const expectedBalances: Map = new Map() - switch (transaction.type) { - case 'TRANSFER': - transaction.params.destinations.forEach((destination) => { - addTokenToBalances(expectedBalances, ALPH_TOKEN_ID, BigNumber.from(destination.attoAlphAmount)) - if (destination.tokens !== undefined) { - destination.tokens.forEach((token) => addTokenToBalances(expectedBalances, token.id, BigNumber.from(token.amount))) - } - }) - break - case 'DEPLOY_CONTRACT': - addTokenToBalances(expectedBalances, ALPH_TOKEN_ID, - transaction.params.initialAttoAlphAmount !== undefined - ? BigNumber.from(transaction.params.initialAttoAlphAmount) - : BigNumber.from(ONE_ALPH) - ) - if (transaction.params.initialTokenAmounts !== undefined) { - transaction.params.initialTokenAmounts.forEach((token) => addTokenToBalances(expectedBalances, token.id, BigNumber.from(token.amount))) - } - break - case 'EXECUTE_SCRIPT': - if (transaction.params.attoAlphAmount !== undefined) { - addTokenToBalances(expectedBalances, ALPH_TOKEN_ID, BigNumber.from(transaction.params.attoAlphAmount)) - } - if (transaction.params.tokens !== undefined) { - transaction.params.tokens.forEach((token) => addTokenToBalances(expectedBalances, token.id, BigNumber.from(token.amount))) - } - break - case 'UNSIGNED_TX': - return - } - addTokenToBalances(expectedBalances, ALPH_TOKEN_ID, BigNumber.from(minimalGasFee)) - - const zero = BigNumber.from(0) - for (const [tokenId, amount] of expectedBalances) { - if (zero.eq(amount)) { - continue - } - const token = tokensWithBalance.find((t) => t.id === tokenId) - const tokenBalance = token?.balance - if (tokenBalance === undefined || tokenBalance.lt(amount)) { - const tokenInfo = await getToken({ id: tokenId, networkId }) - const tokenSymbol = tokenInfo?.symbol ?? tokenId - const tokenDecimals = tokenInfo?.decimals ?? 0 - const expectedStr = prettifyTokenAmount(amount.toBigInt(), tokenDecimals) - const haveStr = prettifyTokenAmount((tokenBalance ?? zero).toBigInt(), tokenDecimals) - return i18n.t("Insufficient token {{ tokenSymbol }}, expected at least {{ expectedStr }}, got {{ haveStr }}", { tokenSymbol, expectedStr, haveStr }) - } - } - return undefined -} - export const ApproveTransactionScreen: FC = ({ - transaction, - selectedAccount, + transactionParams, actionHash, onSubmit, onReject, @@ -173,27 +50,34 @@ export const ApproveTransactionScreen: FC = ({ }) => { const { t } = useTranslation() const navigate = useNavigate() + const selectedAccount = useSelectedAccount() + usePageTracking("signTransaction", { networkId: selectedAccount?.networkId || "unknown", }) - const [buildResult, setBuildResult] = useState< - ReviewTransactionResult | undefined + const [buildResults, setBuildResults] = useState< + ReviewTransactionResult[] | undefined >() const { id: networkId, nodeUrl } = useNetwork( selectedAccount?.networkId ?? "unknown", ) - + const [currentIndex, setCurrentIndex] = useState(0) const { tokenDetails: allUserTokens, tokenDetailsIsInitialising } = useAllTokensWithBalance(selectedAccount) const useLedger = selectedAccount !== undefined && selectedAccount.signer.type === "ledger" const ledgerSubmit = useCallback((signature: string) => { - if (buildResult) { - onSubmit({ ...buildResult, signature }) + if (buildResults) { + if (buildResults.length !== 1) { + throw new Error("Ledger does not support chained transactions") + } + + const buildResult = buildResults[0] + onSubmit([{ ...buildResult, signature }]) } - }, [onSubmit, buildResult]) + }, [onSubmit, buildResults]) const { ledgerState, ledgerApp, ledgerSign } = useLedgerApp({ selectedAccount, - unsignedTx: buildResult?.result.unsignedTx, + unsignedTx: buildResults?.[0].result.unsignedTx, onSubmit: ledgerSubmit, navigate, onReject @@ -207,13 +91,30 @@ export const ApproveTransactionScreen: FC = ({ } try { - const buildResult = await tryBuildTransaction( - nodeUrl, - allUserTokens, - selectedAccount, - transaction - ) - setBuildResult(buildResult) + const walletAccounts = await getAccounts((account) => { + return account.networkId === selectedAccount.networkId && !account.hidden + }) + + let results: ReviewTransactionResult[] + if (transactionParams.length === 0) { + throw new Error("Transaction params are empty") + } else if (transactionParams.length === 1) { + results = await tryBuildTransactions( + nodeUrl, + allUserTokens, + selectedAccount, + walletAccounts, + transactionParams[0], + useLedger + ) + } else { + if (useLedger) { + throw new Error("Ledger does not support chained transactions") + } + results = await tryBuildChainedTransactions(nodeUrl, walletAccounts, transactionParams) + } + + setBuildResults(results) } catch (e: any) { useAppState.setState({ error: `${t("Transaction building failed")}: ${e.toString()}`, @@ -225,14 +126,14 @@ export const ApproveTransactionScreen: FC = ({ } build() - }, [nodeUrl, selectedAccount, transaction, tokenDetailsIsInitialising, actionHash, navigate, t]) + }, [nodeUrl, selectedAccount, transactionParams, tokenDetailsIsInitialising, actionHash, navigate, t]) if (!selectedAccount) { rejectAction(actionHash, t("No account found for network {{ networkId }}", { networkId })) return } - if (!buildResult) { + if (buildResults === undefined) { return } @@ -246,7 +147,7 @@ export const ApproveTransactionScreen: FC = ({ if (useLedger) { ledgerSign() } else { - onSubmit(buildResult) + onSubmit(buildResults) } }} onReject={() => { @@ -261,16 +162,16 @@ export const ApproveTransactionScreen: FC = ({ }} showHeader={false} footer={ - buildResult && ( + buildResults.length > 0 && ( { return }} - accountAddress={selectedAccount.address} - networkId={selectedAccount.networkId} - transaction={buildResult} + accountAddress={buildResults[currentIndex].params.signerAddress} + networkId={buildResults[currentIndex].params.networkId} + transaction={buildResults[currentIndex]} actionHash={actionHash} /> @@ -278,11 +179,39 @@ export const ApproveTransactionScreen: FC = ({ } {...props} > - - - - - + + + + + { + buildResults.length > 1 && ( + + + setCurrentIndex(prev => Math.max(0, prev - 1))} + disabled={currentIndex === 0} + size="small" + > + + + + + {`${currentIndex + 1} of ${buildResults.length}`} + + + setCurrentIndex(prev => Math.min(buildResults.length - 1, prev + 1))} + disabled={currentIndex === buildResults.length - 1} + size="small" + > + + + + + ) + } ) } diff --git a/packages/extension/src/ui/features/actions/transaction/AccountNetworkInfo.tsx b/packages/extension/src/ui/features/actions/transaction/AccountNetworkInfo.tsx index 9d32feb2..75e14913 100644 --- a/packages/extension/src/ui/features/actions/transaction/AccountNetworkInfo.tsx +++ b/packages/extension/src/ui/features/actions/transaction/AccountNetworkInfo.tsx @@ -6,10 +6,11 @@ import { PrettyAccountAddress } from "../../accounts/PrettyAccountAddress" import { useTranslation } from "react-i18next" interface AccountNetworkInfoProps { - account: Account + accountAddress: string + networkId: string } -export const AccountNetworkInfo = ({ account }: AccountNetworkInfoProps) => { +export const AccountNetworkInfo = ({ accountAddress, networkId }: AccountNetworkInfoProps) => { const { t } = useTranslation() return ( { @@ -35,7 +36,7 @@ export const AccountNetworkInfo = ({ account }: AccountNetworkInfoProps) => { {t("Network")} - {account.networkName} + {networkId} diff --git a/packages/extension/src/ui/features/actions/transaction/DappHeader.tsx b/packages/extension/src/ui/features/actions/transaction/DappHeader.tsx index 53e7f759..ec45b39d 100644 --- a/packages/extension/src/ui/features/actions/transaction/DappHeader.tsx +++ b/packages/extension/src/ui/features/actions/transaction/DappHeader.tsx @@ -1,12 +1,12 @@ import { H5, P4, icons } from "@argent/ui" import { Box, Flex } from "@chakra-ui/react" import React, { useMemo } from "react" -import { TransactionParams } from "../../../../shared/actionQueue/types" +import { ReviewTransactionResult, TransactionParams } from "../../../../shared/actionQueue/types" import { useTranslation } from "react-i18next" export interface DappHeaderProps { - transaction: TransactionParams + transaction: ReviewTransactionResult } export const DappHeader = ({ @@ -62,7 +62,7 @@ const { MulticallIcon } = icons -const TransactionIcon = ({transaction}: {transaction: TransactionParams}) => { +const TransactionIcon = ({transaction}: {transaction: ReviewTransactionResult}) => { switch (transaction.type) { case 'TRANSFER': return diff --git a/packages/extension/src/ui/services/backgroundTransactions.ts b/packages/extension/src/ui/services/backgroundTransactions.ts index 963bc5ce..464eccb7 100644 --- a/packages/extension/src/ui/services/backgroundTransactions.ts +++ b/packages/extension/src/ui/services/backgroundTransactions.ts @@ -6,7 +6,7 @@ import { DeclareContract } from "../../shared/udc/type" import { BaseWalletAccount } from "../../shared/wallet.model" export const executeTransaction = (data: TransactionParams) => { - return sendMessage({ type: "ALPH_EXECUTE_TRANSACTION", data }) + return sendMessage({ type: "ALPH_EXECUTE_TRANSACTION", data: [data] }) } export const getEstimatedFee = async (call: Call | Call[]) => { diff --git a/yarn.lock b/yarn.lock index ce0d2097..1792dff0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,13 +2,13 @@ # yarn lockfile v1 -"@alephium/cli@^1.7.3": - version "1.7.3" - resolved "https://registry.yarnpkg.com/@alephium/cli/-/cli-1.7.3.tgz#85555c25dd7f3e8e3e33addf9fa74eda435a3083" - integrity sha512-mz7VcqUlfW8812AMw3lKrhHIrbcGTtFeRwiuoEXGE+zgIOWrZNu1E+648/rC89ekV/xxGIxPlesCMDET67ULPA== +"@alephium/cli@^1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@alephium/cli/-/cli-1.8.2.tgz#9cc8b0617b2fee763a283e3f0607d17f31d18bae" + integrity sha512-iBxqcgXTPR51Mmt/WtCpZVA/Kay3gn3cT74QyETSdgic0lP2LCPhbs+XUeNLIYS1fDJSVdeFzt4ZD4p+BdbAbw== dependencies: - "@alephium/web3" "^1.7.3" - "@alephium/web3-wallet" "^1.7.3" + "@alephium/web3" "^1.8.2" + "@alephium/web3-wallet" "^1.8.2" "@swc/core" "^1.4.1" commander "^9.5.0" cross-fetch "^3.1.5" @@ -18,12 +18,12 @@ jest "^28.1.3" prettier "^2.8.7" -"@alephium/get-extension-wallet@^1.7.3": - version "1.7.3" - resolved "https://registry.yarnpkg.com/@alephium/get-extension-wallet/-/get-extension-wallet-1.7.3.tgz#72974a691cf0c15c1e8eb7bea8888131ede35140" - integrity sha512-g/Pw3rg2P0aVIPRJ4Nnpyb41WTfzaZTvE+8Ge6HBPzpWP9w95gzj8CqEHfoV2zg2MkmbU6gL0x0rtoDTOYkSKQ== +"@alephium/get-extension-wallet@^1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@alephium/get-extension-wallet/-/get-extension-wallet-1.8.2.tgz#1600d7631ce57e03bf493f0e149b310a15b6c20d" + integrity sha512-4rFRq0LhMOaoz9RE6g0fzRMD+KxfY03rKVSOIOJ0/89inlhIZG2QHsr5jcDjo7Z8JkU83vt/9OAymYFAMYypNw== dependencies: - "@alephium/web3" "^1.7.3" + "@alephium/web3" "^1.8.2" bowser "^2.11.0" "@alephium/ledger-app@0.6.0": @@ -50,12 +50,12 @@ "@alephium/web3" "^1.8.5" "@alephium/web3-wallet" "^1.8.5" -"@alephium/web3-wallet@^1.7.3": - version "1.7.3" - resolved "https://registry.yarnpkg.com/@alephium/web3-wallet/-/web3-wallet-1.7.3.tgz#7ff34b25853689b906ddfb12948104a6fe40437f" - integrity sha512-jR5hq4dZX7qkslZBMILK5wuZ/3nfQzfIWkOrT9Gln/lZ9ilLdBMIpIbVPKHbHz40fihWJDCu1UOvtw8B8p8dUg== +"@alephium/web3-wallet@^1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@alephium/web3-wallet/-/web3-wallet-1.8.2.tgz#fb39b8590c4dc1aa5115e4af0e13487012f1d55d" + integrity sha512-DjnfQBltmWflYBMsTpdl5ULMXlRvDvgVmeh8LFTTEvo0YpawdLvJ2NB4Fdfj1Y/PH3fz5+dyMFBSJ6NdLYjkTQ== dependencies: - "@alephium/web3" "^1.7.3" + "@alephium/web3" "^1.8.2" "@noble/secp256k1" "1.7.1" "@types/node" "^16.18.23" bip32 "3.1.0" @@ -93,10 +93,10 @@ path-browserify "^1.0.1" stream-browserify "^3.0.0" -"@alephium/web3@^1.7.3": - version "1.7.3" - resolved "https://registry.yarnpkg.com/@alephium/web3/-/web3-1.7.3.tgz#4ebaa45fca0bb7cbce1fc6c55e047c51b197d472" - integrity sha512-TvhHorYNctw9f31cuhpECpo2Ym3sMvBmCWquVqEMZ/gsF2PVeMLAFtG7QsHG625x41OSRAsfbBuQzT3lq2K/yw== +"@alephium/web3@^1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@alephium/web3/-/web3-1.8.2.tgz#2586e8c112bba3a26db7b862800addb4142c565d" + integrity sha512-iqaPSflaXj7bz3kmkTZOFpCQpvG6CXFS5PaeiE9+bxCgf1FNCf6Q/4Ms7DQuRbZ2U8sDlTbLTH/RYBy5giPADg== dependencies: "@noble/secp256k1" "1.7.1" base-x "4.0.0"